blob: 1dc5fc70054f1fab10c5a7236fe4e563794b40e5 [file] [log] [blame]
/*
* Copyright (c) 2023 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*
* Large Composition Data test
*/
#include "mesh_test.h"
#include <stddef.h>
#include <string.h>
#include <zephyr/logging/log.h>
#include <mesh/access.h>
#include <mesh/net.h>
LOG_MODULE_REGISTER(test_lcd, LOG_LEVEL_INF);
#define CLI_ADDR 0x7728
#define SRV_ADDR 0x18f8
#define WAIT_TIME 60 /* seconds */
/* Length of additional status fields (offset, page and total size) */
#define LCD_STATUS_FIELDS_LEN 5
#define DUMMY_2_BYTE_OP BT_MESH_MODEL_OP_2(0xff, 0xff)
#define BT_MESH_LCD_PAYLOAD_MAX \
(BT_MESH_TX_SDU_MAX - BT_MESH_MODEL_OP_LEN(DUMMY_2_BYTE_OP) - \
LCD_STATUS_FIELDS_LEN - \
BT_MESH_MIC_SHORT) /* 378 bytes */
#define TEST_MODEL_CNT_CB(_dummy_op, _metadata) \
{ \
.id = 0x1234, \
.pub = NULL, \
.keys = NULL, \
.keys_cnt = 0, \
.groups = NULL, \
.groups_cnt = 0, \
.op = _dummy_op, \
.cb = NULL, \
.user_data = NULL, \
.metadata = _metadata, \
}
const struct bt_mesh_model_op dummy_op[] = {
{ 0xfeed, BT_MESH_LEN_MIN(1), NULL },
{ 0xface, BT_MESH_LEN_MIN(1), NULL },
BT_MESH_MODEL_OP_END,
};
static struct bt_mesh_models_metadata_entry *dummy_meta_entry[] = {};
/* Empty elements to create large composition/meta data */
#define DUMMY_ELEM(i, _) BT_MESH_ELEM((i) + 2, \
MODEL_LIST(TEST_MODEL_CNT_CB(dummy_op, dummy_meta_entry)), BT_MESH_MODEL_NONE)
static const struct bt_mesh_test_cfg cli_cfg = {
.addr = CLI_ADDR,
.dev_key = {0xaa},
};
static const struct bt_mesh_test_cfg srv_cfg = {
.addr = SRV_ADDR,
.dev_key = {0xab},
};
static struct bt_mesh_prov prov;
static struct bt_mesh_cfg_cli cfg_cli;
static struct bt_mesh_large_comp_data_cli lcd_cli;
/* Creates enough composition data to send a max SDU comp status message + 1 byte */
static struct bt_mesh_elem elements_1[] = {
BT_MESH_ELEM(1,
MODEL_LIST(BT_MESH_MODEL_CFG_SRV,
BT_MESH_MODEL_CFG_CLI(&cfg_cli),
BT_MESH_MODEL_LARGE_COMP_DATA_CLI(&lcd_cli),
BT_MESH_MODEL_LARGE_COMP_DATA_SRV),
BT_MESH_MODEL_NONE),
LISTIFY(88, DUMMY_ELEM, (,)),
};
/* Creates enough metadata to send a max SDU metadata status message + 1 byte */
static struct bt_mesh_elem elements_2[] = {
BT_MESH_ELEM(1,
MODEL_LIST(BT_MESH_MODEL_CFG_SRV,
BT_MESH_MODEL_CFG_CLI(&cfg_cli),
BT_MESH_MODEL_LARGE_COMP_DATA_CLI(&lcd_cli),
BT_MESH_MODEL_LARGE_COMP_DATA_SRV),
BT_MESH_MODEL_NONE),
LISTIFY(186, DUMMY_ELEM, (,)),
};
static const struct bt_mesh_comp comp_1 = {
.cid = TEST_VND_COMPANY_ID,
.vid = 0xabba,
.pid = 0xdead,
.elem = elements_1,
.elem_count = ARRAY_SIZE(elements_1),
};
static const struct bt_mesh_comp comp_2 = {
.cid = TEST_VND_COMPANY_ID,
.elem = elements_2,
.elem_count = ARRAY_SIZE(elements_2),
};
static void prov_and_conf(struct bt_mesh_test_cfg cfg)
{
uint8_t status;
ASSERT_OK(bt_mesh_provision(test_net_key, 0, 0, 0, cfg.addr, cfg.dev_key));
/* Check device key by adding appkey. */
ASSERT_OK(bt_mesh_cfg_cli_app_key_add(0, cfg.addr, 0, 0, test_app_key, &status));
ASSERT_OK(status);
}
/* Since nodes self-provision in this test and the LCD model uses device keys for crypto, the
* server node must be added to the client CDB manually.
*/
static void target_node_alloc(struct bt_mesh_comp comp, struct bt_mesh_test_cfg cfg)
{
struct bt_mesh_cdb_node *node;
node = bt_mesh_cdb_node_alloc(test_va_uuid, cfg.addr, comp.elem_count, 0);
ASSERT_TRUE(node);
memcpy(node->dev_key, cfg.dev_key, 16);
}
/* Assert equality between local data and merged sample data */
static void merge_and_compare_assert(struct net_buf_simple *sample1, struct net_buf_simple *sample2,
struct net_buf_simple *local_data)
{
uint8_t merged_data[sample1->len + sample2->len];
memcpy(&merged_data[0], sample1->data, sample1->len);
memcpy(&merged_data[sample1->len], sample2->data, sample2->len);
ASSERT_TRUE(memcmp(local_data->data, merged_data, ARRAY_SIZE(merged_data)) == 0);
}
/* Assert that the received status fields are equal to local values. Buffer state is saved.
*/
static void verify_status_fields(struct bt_mesh_large_comp_data_rsp *srv_rsp, uint8_t page_local,
uint16_t offset_local, uint16_t total_size_local)
{
ASSERT_EQUAL(page_local, srv_rsp->page);
ASSERT_EQUAL(offset_local, srv_rsp->offset);
ASSERT_EQUAL(total_size_local, srv_rsp->total_size);
}
/* Compare response data with local data.
* Note:
* * srv_rsp: Status field data (5 bytes) is removed form buffer.
* * local_data: state is preserved.
* * prev_len: Set to NULL if irrelevant. Used for split and merge testing.
*/
static void rsp_equals_local_data_assert(uint16_t addr, struct bt_mesh_large_comp_data_rsp *srv_rsp,
struct net_buf_simple *local_data, uint8_t page,
uint16_t offset, uint16_t total_size, uint16_t *prev_len)
{
struct net_buf_simple_state local_state = {0};
/* Check that status field data matches local values. */
verify_status_fields(srv_rsp, page, offset, total_size);
net_buf_simple_save(local_data, &local_state);
if (prev_len != NULL) {
size_t len = *prev_len;
net_buf_simple_pull_mem(local_data, len);
}
/* Check that local and rsp data are equal */
ASSERT_TRUE(memcmp(srv_rsp->data->data, local_data->data, srv_rsp->data->len) == 0);
net_buf_simple_restore(local_data, &local_state);
}
static void test_srv_init(void)
{
bt_mesh_test_cfg_set(&srv_cfg, WAIT_TIME);
}
static void test_cli_init(void)
{
bt_mesh_test_cfg_set(&cli_cfg, WAIT_TIME);
}
static void test_cli_max_sdu_comp_data_request(void)
{
int err;
uint8_t page = 0;
uint16_t offset, total_size;
NET_BUF_SIMPLE_DEFINE(local_comp, 500);
NET_BUF_SIMPLE_DEFINE(srv_rsp_comp, 500);
net_buf_simple_init(&local_comp, 0);
net_buf_simple_init(&srv_rsp_comp, 0);
struct bt_mesh_large_comp_data_rsp srv_rsp = {
.data = &srv_rsp_comp,
};
bt_mesh_device_setup(&prov, &comp_1);
prov_and_conf(cli_cfg);
target_node_alloc(comp_1, srv_cfg);
/* Note: an offset of 3 is necessary with the status data to be exactly
* 380 bytes of access payload.
*/
offset = 3;
/* Get local data */
err = bt_mesh_comp_data_get_page_0(&local_comp, offset);
/* Operation is successful even if all data cannot fit in the buffer (-E2BIG) */
if (err && err != -E2BIG) {
FAIL("CLIENT: Failed to get comp data Page 0: %d", err);
}
total_size = bt_mesh_comp_page_0_size();
/* Get server composition data and check integrity */
ASSERT_OK(bt_mesh_large_comp_data_get(0, SRV_ADDR, page, offset, &srv_rsp));
ASSERT_EQUAL(srv_rsp_comp.len, BT_MESH_LCD_PAYLOAD_MAX);
rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp, &local_comp, page, offset, total_size,
NULL);
PASS();
}
static void test_cli_split_comp_data_request(void)
{
int err;
uint8_t page = 0;
uint16_t offset, total_size, prev_len = 0;
NET_BUF_SIMPLE_DEFINE(local_comp, 200);
NET_BUF_SIMPLE_DEFINE(srv_rsp_comp_1, 64);
NET_BUF_SIMPLE_DEFINE(srv_rsp_comp_2, 64);
net_buf_simple_init(&local_comp, 0);
net_buf_simple_init(&srv_rsp_comp_1, 0);
net_buf_simple_init(&srv_rsp_comp_2, 0);
struct bt_mesh_large_comp_data_rsp srv_rsp_1 = {
.data = &srv_rsp_comp_1,
};
struct bt_mesh_large_comp_data_rsp srv_rsp_2 = {
.data = &srv_rsp_comp_2,
};
bt_mesh_device_setup(&prov, &comp_1);
prov_and_conf(cli_cfg);
target_node_alloc(comp_1, srv_cfg);
offset = 0;
/* Get local data */
err = bt_mesh_comp_data_get_page_0(&local_comp, offset);
/* Operation is successful even if all data cannot fit in the buffer (-E2BIG) */
if (err && err != -E2BIG) {
FAIL("CLIENT: Failed to get comp data Page 0: %d", err);
}
total_size = bt_mesh_comp_page_0_size();
/* Get first server composition data sample and verify data */
ASSERT_OK(bt_mesh_large_comp_data_get(0, SRV_ADDR, page, offset, &srv_rsp_1));
rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp_1, &local_comp, page, offset, total_size,
&prev_len);
prev_len = srv_rsp_comp_1.len;
offset += prev_len;
/* Get next server composition data sample */
ASSERT_OK(bt_mesh_large_comp_data_get(0, SRV_ADDR, page, offset, &srv_rsp_2));
rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp_2, &local_comp, page, offset, total_size,
&prev_len);
/* Check data integrity of merged sample data */
merge_and_compare_assert(&srv_rsp_comp_1, &srv_rsp_comp_2, &local_comp);
PASS();
}
static void test_cli_max_sdu_metadata_request(void)
{
int err;
uint8_t page = 0;
uint16_t offset, total_size;
NET_BUF_SIMPLE_DEFINE(local_metadata, 500);
NET_BUF_SIMPLE_DEFINE(srv_rsp_metadata, 500);
net_buf_simple_init(&local_metadata, 0);
net_buf_simple_init(&srv_rsp_metadata, 0);
struct bt_mesh_large_comp_data_rsp srv_rsp = {
.data = &srv_rsp_metadata,
};
bt_mesh_device_setup(&prov, &comp_2);
prov_and_conf(cli_cfg);
target_node_alloc(comp_2, srv_cfg);
/* Note: an offset of 4 is necessary for the status data to be exactly
* 380 bytes of access payload.
*/
offset = 4;
/* Get local data */
err = bt_mesh_metadata_get_page_0(&local_metadata, offset);
/* Operation is successful even if all data cannot fit in the buffer (-E2BIG) */
if (err && err != -E2BIG) {
FAIL("CLIENT: Failed to get Models Metadata Page 0: %d", err);
}
total_size = bt_mesh_metadata_page_0_size();
/* Get server metadata and check integrity */
ASSERT_OK(bt_mesh_models_metadata_get(0, SRV_ADDR, page, offset, &srv_rsp));
ASSERT_EQUAL(srv_rsp_metadata.len, BT_MESH_LCD_PAYLOAD_MAX);
rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp, &local_metadata, page, offset, total_size,
NULL);
PASS();
}
static void test_cli_split_metadata_request(void)
{
uint8_t page = 0;
uint16_t offset, total_size, prev_len = 0;
NET_BUF_SIMPLE_DEFINE(local_metadata, 500);
NET_BUF_SIMPLE_DEFINE(srv_rsp_metadata_1, 64);
NET_BUF_SIMPLE_DEFINE(srv_rsp_metadata_2, 64);
net_buf_simple_init(&local_metadata, 0);
net_buf_simple_init(&srv_rsp_metadata_1, 0);
net_buf_simple_init(&srv_rsp_metadata_2, 0);
struct bt_mesh_large_comp_data_rsp srv_rsp_1 = {
.data = &srv_rsp_metadata_1,
};
struct bt_mesh_large_comp_data_rsp srv_rsp_2 = {
.data = &srv_rsp_metadata_2,
};
bt_mesh_device_setup(&prov, &comp_2);
prov_and_conf(cli_cfg);
target_node_alloc(comp_2, srv_cfg);
offset = 0;
/* Get local data */
int err = bt_mesh_metadata_get_page_0(&local_metadata, offset);
/* Operation is successful even if not all metadata could fit in the buffer (-E2BIG) */
if (err && err != -E2BIG) {
FAIL("CLIENT: Failed to get Models Metadata Page 0: %d", err);
}
total_size = bt_mesh_metadata_page_0_size();
/* Get first server composition data sample and check integrity */
ASSERT_OK(bt_mesh_models_metadata_get(0, SRV_ADDR, page, offset, &srv_rsp_1));
rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp_1, &local_metadata, page, offset,
total_size, &prev_len);
prev_len = srv_rsp_metadata_1.len;
offset += prev_len;
/* Get next server composition data sample and check integrity */
ASSERT_OK(bt_mesh_models_metadata_get(0, SRV_ADDR, page, offset, &srv_rsp_2));
rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp_2, &local_metadata, page, offset,
total_size, &prev_len);
/* Check data integrity of merged sample data */
merge_and_compare_assert(&srv_rsp_metadata_1, &srv_rsp_metadata_2, &local_metadata);
PASS();
}
static void test_srv_comp_data_status_respond(void)
{
bt_mesh_device_setup(&prov, &comp_1);
prov_and_conf(srv_cfg);
/* No server callback available. Wait 10 sec for message to be recived */
k_sleep(K_SECONDS(10));
PASS();
}
static void test_srv_metadata_status_respond(void)
{
bt_mesh_device_setup(&prov, &comp_2);
prov_and_conf(srv_cfg);
if (atomic_test_bit(bt_mesh.flags, BT_MESH_METADATA_DIRTY)) {
FAIL("Metadata is dirty. Test is not suited for this purpose.");
}
/* No server callback available. Wait 10 sec for message to be recived */
k_sleep(K_SECONDS(10));
PASS();
}
#define TEST_CASE(role, name, description) \
{ \
.test_id = "lcd_" #role "_" #name, \
.test_descr = description, \
.test_tick_f = bt_mesh_test_timeout, \
.test_post_init_f = test_##role##_init, \
.test_main_f = test_##role##_##name, \
}
static const struct bst_test_instance test_lcd[] = {
TEST_CASE(cli, max_sdu_comp_data_request, "Request comp data with max SDU length"),
TEST_CASE(cli, split_comp_data_request, "Request continuous comp data in two samples."),
TEST_CASE(cli, max_sdu_metadata_request, "Request metadata with max SDU length"),
TEST_CASE(cli, split_metadata_request, "Request continuous metadata in two samples."),
TEST_CASE(srv, comp_data_status_respond, "Process incoming GET LCD messages."),
TEST_CASE(srv, metadata_status_respond, "Process incoming GET metadata messages."),
BSTEST_END_MARKER};
struct bst_test_list *test_lcd_install(struct bst_test_list *tests)
{
tests = bst_add_tests(tests, test_lcd);
return tests;
}