1 /*
2 * Copyright (c) 2023 Nordic Semiconductor
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 *
6 * Large Composition Data test
7 */
8
9 #include "mesh_test.h"
10
11 #include <stddef.h>
12 #include <string.h>
13
14 #include <zephyr/logging/log.h>
15
16 #include <mesh/access.h>
17 #include <mesh/net.h>
18 #include "argparse.h"
19
20 LOG_MODULE_REGISTER(test_lcd, LOG_LEVEL_INF);
21
22 #define CLI_ADDR 0x7728
23 #define SRV_ADDR 0x18f8
24 #define WAIT_TIME 60 /* seconds */
25
26 /* Length of additional status fields (offset, page and total size) */
27 #define LCD_STATUS_FIELDS_LEN 5
28 #define DUMMY_2_BYTE_OP BT_MESH_MODEL_OP_2(0xff, 0xff)
29 #define BT_MESH_LCD_PAYLOAD_MAX \
30 (BT_MESH_TX_SDU_MAX - BT_MESH_MODEL_OP_LEN(DUMMY_2_BYTE_OP) - \
31 LCD_STATUS_FIELDS_LEN - \
32 BT_MESH_MIC_SHORT) /* 378 bytes */
33
34 #define TEST_MODEL_CNT_CB(_dummy_op, _metadata) \
35 { \
36 .id = 0x1234, \
37 .pub = NULL, \
38 .keys = NULL, \
39 .keys_cnt = 0, \
40 .groups = NULL, \
41 .groups_cnt = 0, \
42 .op = _dummy_op, \
43 .cb = NULL, \
44 .user_data = NULL, \
45 .metadata = _metadata, \
46 }
47
48 const struct bt_mesh_model_op dummy_op[] = {
49 { 0xfeed, BT_MESH_LEN_MIN(1), NULL },
50 { 0xface, BT_MESH_LEN_MIN(1), NULL },
51 BT_MESH_MODEL_OP_END,
52 };
53
54 static const uint8_t elem_offset2[3] = {4, 5, 6};
55 static const uint8_t additional_data[2] = {100, 200}; /* A Mesh Profile may have additional data. */
56 static const struct bt_mesh_comp2_record comp_rec[40] = {
57 [0 ... 39] = {.id = 10,
58 .version.x = 20,
59 .version.y = 30,
60 .version.z = 40,
61 .elem_offset_cnt = sizeof(elem_offset2),
62 .elem_offset = elem_offset2,
63 .data_len = sizeof(additional_data),
64 .data = additional_data},
65 };
66 static const struct bt_mesh_comp2 comp_p2 = {.record_cnt = ARRAY_SIZE(comp_rec),
67 .record = comp_rec};
68
69 static int comp_page;
70 static bool comp_changed;
test_args_parse(int argc,char * argv[])71 static void test_args_parse(int argc, char *argv[])
72 {
73 bs_args_struct_t args_struct[] = {
74 {.dest = &comp_page,
75 .type = 'i',
76 .name = "{page}",
77 .option = "page",
78 .descript = "Current composition data page"},
79 {.dest = &comp_changed,
80 .type = 'b',
81 .name = "{0, 1}",
82 .option = "comp-changed-mode",
83 .descript = "Composition data has changed"},
84 };
85
86 bs_args_parse_all_cmd_line(argc, argv, args_struct);
87 }
88
89 static struct bt_mesh_models_metadata_entry *dummy_meta_entry[] = {};
90
91 /* Empty elements to create large composition/meta data */
92 #define DUMMY_ELEM(i, _) BT_MESH_ELEM((i) + 2, \
93 MODEL_LIST(TEST_MODEL_CNT_CB(dummy_op, dummy_meta_entry)), BT_MESH_MODEL_NONE)
94
95 static const struct bt_mesh_test_cfg cli_cfg = {
96 .addr = CLI_ADDR,
97 .dev_key = {0xaa},
98 };
99
100 static const struct bt_mesh_test_cfg srv_cfg = {
101 .addr = SRV_ADDR,
102 .dev_key = {0xab},
103 };
104
105 static struct bt_mesh_prov prov;
106 static struct bt_mesh_cfg_cli cfg_cli;
107 static struct bt_mesh_large_comp_data_cli lcd_cli;
108
109 /* Creates enough composition data to send a max SDU comp status message + 1 byte */
110 static struct bt_mesh_elem elements_1[] = {
111 BT_MESH_ELEM(1,
112 MODEL_LIST(BT_MESH_MODEL_CFG_SRV,
113 BT_MESH_MODEL_CFG_CLI(&cfg_cli),
114 BT_MESH_MODEL_LARGE_COMP_DATA_CLI(&lcd_cli),
115 BT_MESH_MODEL_LARGE_COMP_DATA_SRV),
116 BT_MESH_MODEL_NONE),
117 LISTIFY(88, DUMMY_ELEM, (,)),
118 };
119
120 /* Creates enough metadata to send a max SDU metadata status message + 1 byte */
121 static struct bt_mesh_elem elements_2[] = {
122 BT_MESH_ELEM(1,
123 MODEL_LIST(BT_MESH_MODEL_CFG_SRV,
124 BT_MESH_MODEL_CFG_CLI(&cfg_cli),
125 BT_MESH_MODEL_LARGE_COMP_DATA_CLI(&lcd_cli),
126 BT_MESH_MODEL_LARGE_COMP_DATA_SRV),
127 BT_MESH_MODEL_NONE),
128 LISTIFY(186, DUMMY_ELEM, (,)),
129 };
130
131 static const struct bt_mesh_comp comp_1 = {
132 .cid = TEST_VND_COMPANY_ID,
133 .vid = 0xabba,
134 .pid = 0xdead,
135 .elem = elements_1,
136 .elem_count = ARRAY_SIZE(elements_1),
137 };
138
139 static const struct bt_mesh_comp comp_2 = {
140 .cid = TEST_VND_COMPANY_ID,
141 .elem = elements_2,
142 .elem_count = ARRAY_SIZE(elements_2),
143 };
144
prov_and_conf(struct bt_mesh_test_cfg cfg)145 static void prov_and_conf(struct bt_mesh_test_cfg cfg)
146 {
147 uint8_t status;
148
149 ASSERT_OK(bt_mesh_provision(test_net_key, 0, 0, 0, cfg.addr, cfg.dev_key));
150
151 /* Check device key by adding appkey. */
152 ASSERT_OK(bt_mesh_cfg_cli_app_key_add(0, cfg.addr, 0, 0, test_app_key, &status));
153 ASSERT_OK(status);
154 }
155
156 /* Since nodes self-provision in this test and the LCD model uses device keys for crypto, the
157 * server node must be added to the client CDB manually.
158 */
target_node_alloc(struct bt_mesh_comp comp,struct bt_mesh_test_cfg cfg)159 static void target_node_alloc(struct bt_mesh_comp comp, struct bt_mesh_test_cfg cfg)
160 {
161 struct bt_mesh_cdb_node *node;
162 int err;
163
164 node = bt_mesh_cdb_node_alloc(test_va_uuid, cfg.addr, comp.elem_count, 0);
165 ASSERT_TRUE(node);
166
167 err = bt_mesh_cdb_node_key_import(node, cfg.dev_key);
168 if (err) {
169 FAIL("Unable to import the target node device key (err: %d)", err);
170 }
171 }
172
173 /* Assert equality between local data and merged sample data */
merge_and_compare_assert(struct net_buf_simple * sample1,struct net_buf_simple * sample2,struct net_buf_simple * local_data)174 static void merge_and_compare_assert(struct net_buf_simple *sample1, struct net_buf_simple *sample2,
175 struct net_buf_simple *local_data)
176 {
177 uint8_t merged_data[sample1->len + sample2->len];
178
179 memcpy(&merged_data[0], sample1->data, sample1->len);
180 memcpy(&merged_data[sample1->len], sample2->data, sample2->len);
181 ASSERT_TRUE(memcmp(local_data->data, merged_data, ARRAY_SIZE(merged_data)) == 0);
182 }
183
184 /* Assert that the received status fields are equal to local values. Buffer state is saved.
185 */
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)186 static void verify_status_fields(struct bt_mesh_large_comp_data_rsp *srv_rsp, uint8_t page_local,
187 uint16_t offset_local, uint16_t total_size_local)
188 {
189 ASSERT_EQUAL(page_local, srv_rsp->page);
190 ASSERT_EQUAL(offset_local, srv_rsp->offset);
191 ASSERT_EQUAL(total_size_local, srv_rsp->total_size);
192 }
193
194 /* Compare response data with local data.
195 * Note:
196 * * srv_rsp: Status field data (5 bytes) is removed form buffer.
197 * * local_data: state is preserved.
198 * * prev_len: Set to NULL if irrelevant. Used for split and merge testing.
199 */
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)200 static void rsp_equals_local_data_assert(uint16_t addr, struct bt_mesh_large_comp_data_rsp *srv_rsp,
201 struct net_buf_simple *local_data, uint8_t page,
202 uint16_t offset, uint16_t total_size, uint16_t *prev_len)
203 {
204 struct net_buf_simple_state local_state = {0};
205
206 /* Check that status field data matches local values. */
207 verify_status_fields(srv_rsp, page, offset, total_size);
208
209 net_buf_simple_save(local_data, &local_state);
210
211 if (prev_len != NULL) {
212 size_t len = *prev_len;
213
214 net_buf_simple_pull_mem(local_data, len);
215 }
216
217 /* Check that local and rsp data are equal */
218 ASSERT_TRUE(memcmp(srv_rsp->data->data, local_data->data, srv_rsp->data->len) == 0);
219
220 net_buf_simple_restore(local_data, &local_state);
221 }
222
test_srv_init(void)223 static void test_srv_init(void)
224 {
225 bt_mesh_test_cfg_set(&srv_cfg, WAIT_TIME);
226 }
227
test_cli_init(void)228 static void test_cli_init(void)
229 {
230 bt_mesh_test_cfg_set(&cli_cfg, WAIT_TIME);
231 }
232
test_cli_max_sdu_comp_data_request(void)233 static void test_cli_max_sdu_comp_data_request(void)
234 {
235 int err;
236 uint8_t page = 0;
237 uint16_t offset, total_size;
238
239 NET_BUF_SIMPLE_DEFINE(local_comp, 500);
240 NET_BUF_SIMPLE_DEFINE(srv_rsp_comp, 500);
241 net_buf_simple_init(&local_comp, 0);
242 net_buf_simple_init(&srv_rsp_comp, 0);
243
244 struct bt_mesh_large_comp_data_rsp srv_rsp = {
245 .data = &srv_rsp_comp,
246 };
247
248 bt_mesh_device_setup(&prov, &comp_1);
249 prov_and_conf(cli_cfg);
250 target_node_alloc(comp_1, srv_cfg);
251
252 /* Note: an offset of 3 is necessary with the status data to be exactly
253 * 380 bytes of access payload.
254 */
255 offset = 3;
256
257 /* Get local data */
258 err = bt_mesh_comp_data_get_page_0(&local_comp, offset);
259 /* Operation is successful even if all data cannot fit in the buffer (-E2BIG) */
260 if (err && err != -E2BIG) {
261 FAIL("CLIENT: Failed to get comp data Page 0: %d", err);
262 }
263 total_size = bt_mesh_comp_page_size(0);
264
265 /* Get server composition data and check integrity */
266 ASSERT_OK(bt_mesh_large_comp_data_get(0, SRV_ADDR, page, offset, &srv_rsp));
267 ASSERT_EQUAL(srv_rsp_comp.len, BT_MESH_LCD_PAYLOAD_MAX);
268 rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp, &local_comp, page, offset, total_size,
269 NULL);
270
271 PASS();
272 }
273
test_cli_split_comp_data_request(void)274 static void test_cli_split_comp_data_request(void)
275 {
276 int err;
277 uint16_t offset = 0, prev_len = 0;
278
279 NET_BUF_SIMPLE_DEFINE(local_comp, CONFIG_BT_MESH_COMP_PST_BUF_SIZE);
280 NET_BUF_SIMPLE_DEFINE(srv_rsp_comp_1, 500);
281 NET_BUF_SIMPLE_DEFINE(srv_rsp_comp_2, 500);
282 net_buf_simple_init(&local_comp, 0);
283 net_buf_simple_init(&srv_rsp_comp_1, 0);
284 net_buf_simple_init(&srv_rsp_comp_2, 0);
285
286 struct bt_mesh_large_comp_data_rsp srv_rsp_1 = {
287 .data = &srv_rsp_comp_1,
288 };
289 struct bt_mesh_large_comp_data_rsp srv_rsp_2 = {
290 .data = &srv_rsp_comp_2,
291 };
292
293 bt_mesh_device_setup(&prov, (comp_page == 0 || comp_page == 128) ? &comp_1 : &comp_2);
294 bt_mesh_comp2_register(&comp_p2);
295 prov_and_conf(cli_cfg);
296 target_node_alloc((comp_page == 0 || comp_page == 128) ? comp_1 : comp_2, srv_cfg);
297
298 /* Get local data */
299 err = bt_mesh_comp_data_get_page(&local_comp, comp_page, 0);
300 /* Operation is successful even if all data cannot fit in the buffer (-E2BIG) */
301 if (err && err != -E2BIG) {
302 FAIL("CLIENT: Failed to get comp data Page %d: %d", err, comp_page);
303 }
304
305 uint16_t total_size = bt_mesh_comp_page_size(comp_page);
306
307 /* Verify that the total comp page size is not larger than the provided buffer */
308 ASSERT_TRUE(total_size <= CONFIG_BT_MESH_COMP_PST_BUF_SIZE);
309
310 /* Wait a bit until the server is ready to respond*/
311 k_sleep(K_SECONDS(2));
312
313 /* Get first server composition data sample and verify data */
314 ASSERT_OK(bt_mesh_large_comp_data_get(0, SRV_ADDR, comp_page, offset, &srv_rsp_1));
315 rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp_1, &local_comp, comp_page, offset,
316 total_size, &prev_len);
317
318 prev_len = srv_rsp_comp_1.len;
319 offset = prev_len;
320
321 /* Get next server composition data sample */
322 ASSERT_OK(bt_mesh_large_comp_data_get(0, SRV_ADDR, comp_page, offset, &srv_rsp_2));
323 rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp_2, &local_comp, comp_page, offset,
324 total_size, &prev_len);
325
326 /* Check data integrity of merged sample data */
327 merge_and_compare_assert(&srv_rsp_comp_1, &srv_rsp_comp_2, &local_comp);
328
329 PASS();
330 }
331
test_cli_max_sdu_metadata_request(void)332 static void test_cli_max_sdu_metadata_request(void)
333 {
334 int err;
335 uint8_t page = 0;
336 uint16_t offset, total_size;
337
338 NET_BUF_SIMPLE_DEFINE(local_metadata, 500);
339 NET_BUF_SIMPLE_DEFINE(srv_rsp_metadata, 500);
340 net_buf_simple_init(&local_metadata, 0);
341 net_buf_simple_init(&srv_rsp_metadata, 0);
342
343 struct bt_mesh_large_comp_data_rsp srv_rsp = {
344 .data = &srv_rsp_metadata,
345 };
346
347 bt_mesh_device_setup(&prov, &comp_2);
348 prov_and_conf(cli_cfg);
349 target_node_alloc(comp_2, srv_cfg);
350
351 /* Note: an offset of 4 is necessary for the status data to be exactly
352 * 380 bytes of access payload.
353 */
354 offset = 4;
355
356 /* Get local data */
357 err = bt_mesh_metadata_get_page_0(&local_metadata, offset);
358 /* Operation is successful even if all data cannot fit in the buffer (-E2BIG) */
359 if (err && err != -E2BIG) {
360 FAIL("CLIENT: Failed to get Models Metadata Page 0: %d", err);
361 }
362 total_size = bt_mesh_metadata_page_0_size();
363
364 /* Get server metadata and check integrity */
365 ASSERT_OK(bt_mesh_models_metadata_get(0, SRV_ADDR, page, offset, &srv_rsp));
366 ASSERT_EQUAL(srv_rsp_metadata.len, BT_MESH_LCD_PAYLOAD_MAX);
367 rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp, &local_metadata, page, offset, total_size,
368 NULL);
369
370 PASS();
371 }
372
test_cli_split_metadata_request(void)373 static void test_cli_split_metadata_request(void)
374 {
375 uint8_t page = 0;
376 uint16_t offset, total_size, prev_len = 0;
377
378 NET_BUF_SIMPLE_DEFINE(local_metadata, 500);
379 NET_BUF_SIMPLE_DEFINE(srv_rsp_metadata_1, 64);
380 NET_BUF_SIMPLE_DEFINE(srv_rsp_metadata_2, 64);
381 net_buf_simple_init(&local_metadata, 0);
382 net_buf_simple_init(&srv_rsp_metadata_1, 0);
383 net_buf_simple_init(&srv_rsp_metadata_2, 0);
384
385 struct bt_mesh_large_comp_data_rsp srv_rsp_1 = {
386 .data = &srv_rsp_metadata_1,
387 };
388 struct bt_mesh_large_comp_data_rsp srv_rsp_2 = {
389 .data = &srv_rsp_metadata_2,
390 };
391
392 bt_mesh_device_setup(&prov, &comp_2);
393 prov_and_conf(cli_cfg);
394 target_node_alloc(comp_2, srv_cfg);
395
396 offset = 0;
397
398 /* Get local data */
399 int err = bt_mesh_metadata_get_page_0(&local_metadata, offset);
400 /* Operation is successful even if not all metadata could fit in the buffer (-E2BIG) */
401 if (err && err != -E2BIG) {
402 FAIL("CLIENT: Failed to get Models Metadata Page 0: %d", err);
403 }
404 total_size = bt_mesh_metadata_page_0_size();
405
406 /* Get first server composition data sample and check integrity */
407 ASSERT_OK(bt_mesh_models_metadata_get(0, SRV_ADDR, page, offset, &srv_rsp_1));
408 rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp_1, &local_metadata, page, offset,
409 total_size, &prev_len);
410
411 prev_len = srv_rsp_metadata_1.len;
412 offset += prev_len;
413
414 /* Get next server composition data sample and check integrity */
415 ASSERT_OK(bt_mesh_models_metadata_get(0, SRV_ADDR, page, offset, &srv_rsp_2));
416 rsp_equals_local_data_assert(SRV_ADDR, &srv_rsp_2, &local_metadata, page, offset,
417 total_size, &prev_len);
418
419 /* Check data integrity of merged sample data */
420 merge_and_compare_assert(&srv_rsp_metadata_1, &srv_rsp_metadata_2, &local_metadata);
421
422 PASS();
423 }
424
test_srv_comp_data_status_respond(void)425 static void test_srv_comp_data_status_respond(void)
426 {
427 bt_mesh_device_setup(&prov, (comp_page == 0 || comp_page == 128) ? &comp_1 : &comp_2);
428 bt_mesh_comp2_register(&comp_p2);
429 prov_and_conf(srv_cfg);
430
431 /* Simulate an update of composition data */
432 if (comp_changed) {
433 bt_mesh_comp_change_prepare();
434 atomic_set_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY);
435 }
436
437 /* No server callback available. Wait 10 sec for message to be recived */
438 k_sleep(K_SECONDS(10));
439
440 PASS();
441 }
442
test_srv_metadata_status_respond(void)443 static void test_srv_metadata_status_respond(void)
444 {
445 bt_mesh_device_setup(&prov, &comp_2);
446 prov_and_conf(srv_cfg);
447
448 if (atomic_test_bit(bt_mesh.flags, BT_MESH_METADATA_DIRTY)) {
449 FAIL("Metadata is dirty. Test is not suited for this purpose.");
450 }
451
452 /* No server callback available. Wait 10 sec for message to be recived */
453 k_sleep(K_SECONDS(10));
454
455 PASS();
456 }
457
458 #define TEST_CASE(role, name, description) \
459 { \
460 .test_id = "lcd_" #role "_" #name, \
461 .test_descr = description, \
462 .test_args_f = test_args_parse, \
463 .test_tick_f = bt_mesh_test_timeout, \
464 .test_post_init_f = test_##role##_init, \
465 .test_main_f = test_##role##_##name, \
466 }
467
468 static const struct bst_test_instance test_lcd[] = {
469 TEST_CASE(cli, max_sdu_comp_data_request, "Request comp data with max SDU length"),
470 TEST_CASE(cli, split_comp_data_request, "Request continuous comp data in two samples."),
471 TEST_CASE(cli, max_sdu_metadata_request, "Request metadata with max SDU length"),
472 TEST_CASE(cli, split_metadata_request, "Request continuous metadata in two samples."),
473
474 TEST_CASE(srv, comp_data_status_respond, "Process incoming GET LCD messages."),
475 TEST_CASE(srv, metadata_status_respond, "Process incoming GET metadata messages."),
476
477 BSTEST_END_MARKER};
478
test_lcd_install(struct bst_test_list * tests)479 struct bst_test_list *test_lcd_install(struct bst_test_list *tests)
480 {
481 tests = bst_add_tests(tests, test_lcd);
482 return tests;
483 }
484