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