| /* |
| * 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> |
| #include "argparse.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, \ |
| BT_MESH_MODEL_RUNTIME_INIT(NULL) \ |
| .pub = NULL, \ |
| .keys = NULL, \ |
| .keys_cnt = 0, \ |
| .groups = NULL, \ |
| .groups_cnt = 0, \ |
| .op = _dummy_op, \ |
| .cb = 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 const uint8_t elem_offset2[3] = {4, 5, 6}; |
| static const uint8_t additional_data[2] = {100, 200}; /* A Mesh Profile may have additional data. */ |
| static const struct bt_mesh_comp2_record comp_rec[40] = { |
| [0 ... 39] = {.id = 10, |
| .version.x = 20, |
| .version.y = 30, |
| .version.z = 40, |
| .elem_offset_cnt = sizeof(elem_offset2), |
| .elem_offset = elem_offset2, |
| .data_len = sizeof(additional_data), |
| .data = additional_data}, |
| }; |
| static const struct bt_mesh_comp2 comp_p2 = {.record_cnt = ARRAY_SIZE(comp_rec), |
| .record = comp_rec}; |
| |
| static int comp_page; |
| static bool comp_changed; |
| static void test_args_parse(int argc, char *argv[]) |
| { |
| bs_args_struct_t args_struct[] = { |
| {.dest = &comp_page, |
| .type = 'i', |
| .name = "{page}", |
| .option = "page", |
| .descript = "Current composition data page"}, |
| {.dest = &comp_changed, |
| .type = 'b', |
| .name = "{0, 1}", |
| .option = "comp-changed-mode", |
| .descript = "Composition data has changed"}, |
| }; |
| |
| bs_args_parse_all_cmd_line(argc, argv, args_struct); |
| } |
| |
| static const 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 const 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 const 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; |
| int err; |
| |
| node = bt_mesh_cdb_node_alloc(test_va_uuid, cfg.addr, comp.elem_count, 0); |
| ASSERT_TRUE(node); |
| |
| err = bt_mesh_cdb_node_key_import(node, cfg.dev_key); |
| if (err) { |
| FAIL("Unable to import the target node device key (err: %d)", err); |
| } |
| } |
| |
| /* 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_size(0); |
| |
| /* 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; |
| uint16_t offset = 0, prev_len = 0; |
| |
| NET_BUF_SIMPLE_DEFINE(local_comp, CONFIG_BT_MESH_COMP_PST_BUF_SIZE); |
| NET_BUF_SIMPLE_DEFINE(srv_rsp_comp_1, 500); |
| NET_BUF_SIMPLE_DEFINE(srv_rsp_comp_2, 500); |
| 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_page == 0 || comp_page == 128) ? &comp_1 : &comp_2); |
| bt_mesh_comp2_register(&comp_p2); |
| prov_and_conf(cli_cfg); |
| target_node_alloc((comp_page == 0 || comp_page == 128) ? comp_1 : comp_2, srv_cfg); |
| |
| /* Get local data */ |
| err = bt_mesh_comp_data_get_page(&local_comp, comp_page, 0); |
| /* 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 %d: %d", err, comp_page); |
| } |
| |
| uint16_t total_size = bt_mesh_comp_page_size(comp_page); |
| |
| /* Verify that the total comp page size is not larger than the provided buffer */ |
| ASSERT_TRUE(total_size <= CONFIG_BT_MESH_COMP_PST_BUF_SIZE); |
| |
| /* Wait a bit until the server is ready to respond*/ |
| k_sleep(K_SECONDS(2)); |
| |
| /* Get first server composition data sample and verify data */ |
| ASSERT_OK(bt_mesh_large_comp_data_get(0, SRV_ADDR, comp_page, offset, &srv_rsp_1)); |
| rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp_1, &local_comp, 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, comp_page, offset, &srv_rsp_2)); |
| rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp_2, &local_comp, 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_page == 0 || comp_page == 128) ? &comp_1 : &comp_2); |
| bt_mesh_comp2_register(&comp_p2); |
| prov_and_conf(srv_cfg); |
| |
| /* Simulate an update of composition data */ |
| if (comp_changed) { |
| bt_mesh_comp_change_prepare(); |
| atomic_set_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY); |
| } |
| |
| /* 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_args_f = test_args_parse, \ |
| .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; |
| } |