1 /*
2  * Copyright (c) 2022 GARDENA GmbH
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include "lwm2m_engine.h"
8 #include "lwm2m_util.h"
9 
10 #include <zephyr/kernel.h>
11 #include <zephyr/ztest.h>
12 
13 #define TEST_VERBOSE 0
14 
15 #if TEST_VERBOSE
16 #define TEST_VERBOSE_PRINT(fmt, ...) TC_PRINT(fmt, ##__VA_ARGS__)
17 #else
18 #define TEST_VERBOSE_PRINT(fmt, ...)
19 #endif
20 
lwm2m_path_object_equal_upto(struct lwm2m_obj_path * path,struct lwm2m_obj_path * compare_path,uint8_t level)21 static bool lwm2m_path_object_equal_upto(struct lwm2m_obj_path *path,
22 					 struct lwm2m_obj_path *compare_path, uint8_t level)
23 {
24 
25 	if (level >= LWM2M_PATH_LEVEL_OBJECT && path->obj_id != compare_path->obj_id) {
26 		return false;
27 	}
28 
29 	if (level >= LWM2M_PATH_LEVEL_OBJECT_INST &&
30 		path->obj_inst_id != compare_path->obj_inst_id) {
31 		return false;
32 	}
33 
34 	if (level >= LWM2M_PATH_LEVEL_RESOURCE && path->res_id != compare_path->res_id) {
35 		return false;
36 	}
37 
38 	if (level >= LWM2M_PATH_LEVEL_RESOURCE_INST &&
39 		path->res_inst_id != compare_path->res_inst_id) {
40 		return false;
41 	}
42 
43 	return true;
44 }
45 
assert_path_list_order(sys_slist_t * lwm2m_path_list)46 static void assert_path_list_order(sys_slist_t *lwm2m_path_list)
47 {
48 
49 	struct lwm2m_obj_path_list *prev = NULL;
50 	struct lwm2m_obj_path_list *entry, *tmp;
51 
52 	uint16_t obj_id_max = 0;
53 	uint16_t obj_inst_id_max = 0;
54 	uint16_t res_id_max = 0;
55 	uint16_t res_inst_id_max = 0;
56 
57 	if (sys_slist_is_empty(lwm2m_path_list)) {
58 		return;
59 	}
60 
61 	SYS_SLIST_FOR_EACH_CONTAINER_SAFE(lwm2m_path_list, entry, tmp, node) {
62 		if (prev) {
63 			if (entry->path.level > prev->path.level) {
64 
65 				bool is_after = false;
66 
67 				if (prev->path.level >= LWM2M_PATH_LEVEL_OBJECT) {
68 					is_after = entry->path.obj_id >= prev->path.obj_id;
69 				}
70 				if (is_after && prev->path.level >= LWM2M_PATH_LEVEL_OBJECT_INST &&
71 					entry->path.obj_id == prev->path.obj_id) {
72 					is_after =
73 						entry->path.obj_inst_id >= prev->path.obj_inst_id;
74 				}
75 
76 				if (is_after && prev->path.level >= LWM2M_PATH_LEVEL_RESOURCE &&
77 					entry->path.obj_inst_id == prev->path.obj_inst_id) {
78 					is_after = entry->path.res_id >= prev->path.res_id;
79 				}
80 
81 				if (is_after &&
82 					prev->path.level >= LWM2M_PATH_LEVEL_RESOURCE_INST &&
83 					entry->path.res_id == prev->path.res_id) {
84 					is_after =
85 						entry->path.res_inst_id >= prev->path.res_inst_id;
86 				}
87 
88 				zassert_true(is_after, "Next element %p must be before previous %p",
89 						 entry, prev);
90 			} else if (entry->path.level == prev->path.level) {
91 
92 				if (entry->path.level >= LWM2M_PATH_LEVEL_OBJECT) {
93 					zassert_true(entry->path.obj_id >= obj_id_max,
94 							 "Next element has object %d which is smaller "
95 							 "than previous max object %d",
96 							 entry->path.obj_id, obj_id_max);
97 				}
98 
99 				if (entry->path.level >= LWM2M_PATH_LEVEL_OBJECT_INST) {
100 					if (!lwm2m_path_object_equal_upto(
101 							&entry->path, &prev->path,
102 							LWM2M_PATH_LEVEL_OBJECT)) {
103 						/* reset max id when path changed */
104 						obj_inst_id_max = 0;
105 					}
106 
107 					zassert_true(entry->path.obj_inst_id >= obj_inst_id_max,
108 							 "Next element has object instance %d which is "
109 							 "smaller "
110 							 "than previous max object instance %d",
111 							 entry->path.obj_inst_id, obj_inst_id_max);
112 				}
113 
114 				if (entry->path.level >= LWM2M_PATH_LEVEL_RESOURCE) {
115 					if (!lwm2m_path_object_equal_upto(
116 							&entry->path, &prev->path,
117 							LWM2M_PATH_LEVEL_OBJECT_INST)) {
118 						/* reset max id when path changed */
119 						res_id_max = 0;
120 					}
121 
122 					zassert_true(
123 						entry->path.res_id >= res_id_max,
124 						"Next element has resource %d which is smaller "
125 						"than previous max resource %d",
126 						entry->path.res_id, res_id_max);
127 				}
128 
129 				if (entry->path.level >= LWM2M_PATH_LEVEL_RESOURCE_INST) {
130 					if (!lwm2m_path_object_equal_upto(
131 							&entry->path, &prev->path,
132 							LWM2M_PATH_LEVEL_RESOURCE)) {
133 						/* reset max id when path changed */
134 						res_inst_id_max = 0;
135 					}
136 					zassert_true(entry->path.res_inst_id >= res_inst_id_max,
137 							 "Next element has resource instance %d which "
138 							 "is smaller "
139 							 "than previous max resource instance %d",
140 							 entry->path.res_inst_id, res_inst_id_max);
141 				}
142 
143 			} else { /* entry->path.level < prev->path.level */
144 				if (entry->path.level >= LWM2M_PATH_LEVEL_OBJECT) {
145 					zassert_true(entry->path.obj_id >= obj_id_max,
146 							 "Next element has object %d which is smaller "
147 							 "than previous max object %d",
148 							 entry->path.obj_id, obj_id_max);
149 				}
150 
151 				if (entry->path.level >= LWM2M_PATH_LEVEL_OBJECT_INST) {
152 					zassert_true(entry->path.obj_inst_id >= obj_inst_id_max,
153 							 "Next element has object instance %d which is "
154 							 "smaller "
155 							 "than previous max object instance %d",
156 							 entry->path.obj_inst_id, obj_inst_id_max);
157 				}
158 
159 				if (entry->path.level >= LWM2M_PATH_LEVEL_RESOURCE) {
160 					zassert_true(
161 						entry->path.res_id >= res_id_max,
162 						"Next element has resource %d which is smaller "
163 						"than previous max resource %d",
164 						entry->path.res_id, res_id_max);
165 				}
166 
167 				if (entry->path.level >= LWM2M_PATH_LEVEL_RESOURCE_INST) {
168 					zassert_true(entry->path.res_inst_id >= res_inst_id_max,
169 							 "Next element has resource instance %d which "
170 							 "is bigger "
171 							 "than previous max resource instance %d",
172 							 entry->path.res_inst_id, res_inst_id_max);
173 				}
174 
175 				zassert_true(!lwm2m_path_object_equal_upto(
176 						&entry->path, &prev->path, entry->path.level),
177 						 "Next element equals previous up to level %d "
178 						 "and thus must be before previous",
179 						 entry->path.level);
180 			}
181 		}
182 
183 		if (entry->path.level >= LWM2M_PATH_LEVEL_OBJECT) {
184 			obj_id_max = entry->path.obj_id;
185 		} else {
186 			obj_id_max = 0;
187 		}
188 
189 		if (entry->path.level >= LWM2M_PATH_LEVEL_OBJECT_INST &&
190 			(prev == NULL || entry->path.obj_id == prev->path.obj_id)) {
191 			obj_inst_id_max = entry->path.obj_inst_id;
192 		} else {
193 			obj_inst_id_max = 0;
194 		}
195 
196 		if (entry->path.level >= LWM2M_PATH_LEVEL_RESOURCE &&
197 			(prev == NULL || entry->path.obj_id == prev->path.obj_id) &&
198 			(prev == NULL || entry->path.obj_inst_id == prev->path.obj_inst_id)) {
199 			res_id_max = entry->path.res_id;
200 		} else {
201 			res_id_max = 0;
202 		}
203 
204 		if (entry->path.level >= LWM2M_PATH_LEVEL_RESOURCE_INST &&
205 			(prev == NULL || entry->path.obj_id == prev->path.obj_id) &&
206 			(prev == NULL || entry->path.obj_inst_id == prev->path.obj_inst_id) &&
207 			(prev == NULL || entry->path.res_id == prev->path.res_id)) {
208 			res_inst_id_max = entry->path.res_inst_id;
209 		} else {
210 			res_inst_id_max = 0;
211 		}
212 
213 		prev = entry;
214 	}
215 }
216 
run_insertion_test(char const * insert_path_str[],int insertions_count,char const * expected_path_str[])217 static void run_insertion_test(char const *insert_path_str[], int insertions_count,
218 				   char const *expected_path_str[])
219 {
220 	/* GIVEN: different paths */
221 	struct lwm2m_obj_path_list lwm2m_path_list_buf[insertions_count];
222 	sys_slist_t lwm2m_path_list;
223 	sys_slist_t lwm2m_path_free_list;
224 	int ret;
225 
226 	lwm2m_engine_path_list_init(&lwm2m_path_list, &lwm2m_path_free_list, lwm2m_path_list_buf,
227 					insertions_count);
228 
229 	/* WHEN: inserting each path */
230 	struct lwm2m_obj_path insert_path;
231 
232 	for (int i = 0; i < insertions_count; ++i) {
233 		ret = lwm2m_string_to_path(insert_path_str[i], &insert_path, '/');
234 		zassert_true(ret >= 0, "Conversion to path #%d failed", i);
235 
236 		ret = lwm2m_engine_add_path_to_list(&lwm2m_path_list, &lwm2m_path_free_list,
237 							&insert_path);
238 
239 		zassert_true(ret >= 0, "Insertion #%d failed", i);
240 		/* THEN: path order is maintained */
241 		assert_path_list_order(&lwm2m_path_list);
242 	}
243 
244 	/* AND: final list matches expectation */
245 	struct lwm2m_obj_path_list *entry, *tmp;
246 	int path_num = 0;
247 
248 	SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&lwm2m_path_list, entry, tmp, node) {
249 		struct lwm2m_obj_path expected_path;
250 
251 		lwm2m_string_to_path(expected_path_str[path_num++], &expected_path, '/');
252 		zassert_mem_equal(&entry->path, &expected_path, sizeof(struct lwm2m_obj_path),
253 				  "Path #%d did not match expectation", path_num);
254 	}
255 }
256 
ZTEST(lwm2m_observation,test_add_path_to_list)257 ZTEST(lwm2m_observation, test_add_path_to_list)
258 {
259 	/* clang-format off */
260 	char const *insert_path_str[] = {
261 		LWM2M_PATH(2),
262 		LWM2M_PATH(1),
263 		LWM2M_PATH(1, 2),
264 		LWM2M_PATH(1, 1),
265 		LWM2M_PATH(1, 1, 10),
266 		LWM2M_PATH(1, 1, 10, 10),
267 		LWM2M_PATH(1, 1, 10, 9),
268 		LWM2M_PATH(1, 2, 10, 11),
269 
270 		LWM2M_PATH(100),
271 
272 		LWM2M_PATH(41),
273 		LWM2M_PATH(43, 3),
274 		LWM2M_PATH(45, 2, 2),
275 		LWM2M_PATH(47, 1, 1, 1),
276 
277 		LWM2M_PATH(57, 1, 1, 1),
278 		LWM2M_PATH(55, 2, 2),
279 		LWM2M_PATH(53, 3),
280 		LWM2M_PATH(51),
281 	};
282 
283 	char const *expected_path_str[] = {
284 		LWM2M_PATH(1),
285 		LWM2M_PATH(1, 1),
286 		LWM2M_PATH(1, 1, 10),
287 		LWM2M_PATH(1, 1, 10, 9),
288 		LWM2M_PATH(1, 1, 10, 10),
289 		LWM2M_PATH(1, 2),
290 		LWM2M_PATH(1, 2, 10, 11),
291 		LWM2M_PATH(2),
292 		LWM2M_PATH(41),
293 		LWM2M_PATH(43, 3),
294 		LWM2M_PATH(45, 2, 2),
295 		LWM2M_PATH(47, 1, 1, 1),
296 		LWM2M_PATH(51),
297 		LWM2M_PATH(53, 3),
298 		LWM2M_PATH(55, 2, 2),
299 		LWM2M_PATH(57, 1, 1, 1),
300 		LWM2M_PATH(100),
301 	};
302 	/* clang-format on */
303 
304 	run_insertion_test(insert_path_str, ARRAY_SIZE(insert_path_str), expected_path_str);
305 }
306 
ZTEST(lwm2m_observation,test_add_path_to_list_inverse_non_overlapping)307 ZTEST(lwm2m_observation, test_add_path_to_list_inverse_non_overlapping)
308 {
309 	/* clang-format off */
310 	char const *insert_path_str[] = {
311 		LWM2M_PATH(41),
312 		LWM2M_PATH(43, 3),
313 		LWM2M_PATH(45, 2, 2),
314 		LWM2M_PATH(47, 1, 1, 1),
315 	};
316 
317 	char const *expected_path_str[] = {
318 		LWM2M_PATH(41),
319 		LWM2M_PATH(43, 3),
320 		LWM2M_PATH(45, 2, 2),
321 		LWM2M_PATH(47, 1, 1, 1),
322 	};
323 	/* clang-format on */
324 
325 	run_insertion_test(insert_path_str, ARRAY_SIZE(insert_path_str), expected_path_str);
326 }
327 
ZTEST(lwm2m_observation,test_add_path_to_list_inverse_non_overlapping_2)328 ZTEST(lwm2m_observation, test_add_path_to_list_inverse_non_overlapping_2)
329 {
330 	/* clang-format off */
331 	char const *insert_path_str[] = {
332 		LWM2M_PATH(57, 1, 1, 1),
333 		LWM2M_PATH(55, 2, 2),
334 		LWM2M_PATH(53, 3),
335 		LWM2M_PATH(51),
336 	};
337 
338 	char const *expected_path_str[] = {
339 		LWM2M_PATH(51),
340 		LWM2M_PATH(53, 3),
341 		LWM2M_PATH(55, 2, 2),
342 		LWM2M_PATH(57, 1, 1, 1),
343 	};
344 	/* clang-format on */
345 
346 	run_insertion_test(insert_path_str, ARRAY_SIZE(insert_path_str), expected_path_str);
347 }
348 
ZTEST(lwm2m_observation,test_add_path_to_list_object_before_resource_inst)349 ZTEST(lwm2m_observation, test_add_path_to_list_object_before_resource_inst)
350 {
351 	/* clang-format off */
352 	char const *insert_path_str[] = {
353 		LWM2M_PATH(1, 1, 1, 1),
354 		LWM2M_PATH(1),
355 	};
356 
357 	char const *expected_path_str[6] = {
358 		LWM2M_PATH(1),
359 		LWM2M_PATH(1, 1, 1, 1),
360 	};
361 	/* clang-format on */
362 
363 	run_insertion_test(insert_path_str, ARRAY_SIZE(insert_path_str), expected_path_str);
364 }
365 
ZTEST(lwm2m_observation,test_add_path_to_list_object_inst_before_resource_inst)366 ZTEST(lwm2m_observation, test_add_path_to_list_object_inst_before_resource_inst)
367 {
368 	/* clang-format off */
369 	char const *insert_path_str[] = {
370 		LWM2M_PATH(1, 1, 1, 1),
371 		LWM2M_PATH(1, 1),
372 	};
373 
374 	char const *expected_path_str[6] = {
375 		LWM2M_PATH(1, 1),
376 		LWM2M_PATH(1, 1, 1, 1),
377 	};
378 	/* clang-format on */
379 
380 	run_insertion_test(insert_path_str, ARRAY_SIZE(insert_path_str), expected_path_str);
381 }
382 
ZTEST(lwm2m_observation,test_add_path_to_list_resource_before_resource_inst)383 ZTEST(lwm2m_observation, test_add_path_to_list_resource_before_resource_inst)
384 {
385 	/* clang-format off */
386 	char const *insert_path_str[] = {
387 		LWM2M_PATH(1, 1, 1, 1),
388 		LWM2M_PATH(1, 1, 1),
389 	};
390 
391 	char const *expected_path_str[] = {
392 		LWM2M_PATH(1, 1, 1),
393 		LWM2M_PATH(1, 1, 1, 1),
394 	};
395 	/* clang-format on */
396 
397 	run_insertion_test(insert_path_str, ARRAY_SIZE(insert_path_str), expected_path_str);
398 }
399 
ZTEST(lwm2m_observation,test_add_path_to_list_resource_order)400 ZTEST(lwm2m_observation, test_add_path_to_list_resource_order)
401 {
402 	/* clang-format off */
403 	char const *insert_path_str[] = {
404 		LWM2M_PATH(32765, 1, 6, 0),
405 		LWM2M_PATH(32765, 1, 6, 1),
406 		LWM2M_PATH(32765, 1, 6),
407 		LWM2M_PATH(32765, 1, 5),
408 		LWM2M_PATH(32765, 1, 5, 2),
409 		LWM2M_PATH(32765, 1, 5, 1),
410 	};
411 
412 	char const *expected_path_str[] = {
413 		LWM2M_PATH(32765, 1, 5),
414 		LWM2M_PATH(32765, 1, 5, 1),
415 		LWM2M_PATH(32765, 1, 5, 2),
416 		LWM2M_PATH(32765, 1, 6),
417 		LWM2M_PATH(32765, 1, 6, 0),
418 		LWM2M_PATH(32765, 1, 6, 1),
419 	};
420 	/* clang-format on */
421 
422 	run_insertion_test(insert_path_str, ARRAY_SIZE(insert_path_str), expected_path_str);
423 }
424 
ZTEST(lwm2m_observation,test_add_path_to_list_resource_before_instance)425 ZTEST(lwm2m_observation, test_add_path_to_list_resource_before_instance)
426 {
427 	/* clang-format off */
428 	char const *insert_path_str[] = {
429 		LWM2M_PATH(32765, 1, 6, 0),
430 		LWM2M_PATH(32765, 1, 6, 1),
431 		LWM2M_PATH(32765, 1, 6),
432 	};
433 
434 	char const *expected_path_str[6] = {
435 		LWM2M_PATH(32765, 1, 6),
436 		LWM2M_PATH(32765, 1, 6, 0),
437 		LWM2M_PATH(32765, 1, 6, 1),
438 	};
439 	/* clang-format on */
440 
441 	run_insertion_test(insert_path_str, ARRAY_SIZE(insert_path_str), expected_path_str);
442 }
443 
ZTEST(lwm2m_observation,test_add_path_to_list_resource_inverse)444 ZTEST(lwm2m_observation, test_add_path_to_list_resource_inverse)
445 {
446 	/* clang-format off */
447 	char const *insert_path_str[] = {
448 		LWM2M_PATH(1, 1, 1, 1),
449 		LWM2M_PATH(1, 1, 1),
450 		LWM2M_PATH(1, 1),
451 		LWM2M_PATH(1),
452 	};
453 
454 	char const *expected_path_str[] = {
455 		LWM2M_PATH(1),
456 		LWM2M_PATH(1, 1),
457 		LWM2M_PATH(1, 1, 1),
458 		LWM2M_PATH(1, 1, 1, 1),
459 	};
460 	/* clang-format on */
461 
462 	run_insertion_test(insert_path_str, ARRAY_SIZE(insert_path_str), expected_path_str);
463 }
464 
ZTEST(lwm2m_observation,test_add_path_to_list_obj_after_resource)465 ZTEST(lwm2m_observation, test_add_path_to_list_obj_after_resource)
466 {
467 	/* clang-format off */
468 	char const *insert_path_str[] = {
469 		LWM2M_PATH(1),
470 		LWM2M_PATH(1, 1),
471 		LWM2M_PATH(1, 1, 1),
472 		LWM2M_PATH(1, 2),
473 	};
474 
475 	char const *expected_path_str[] = {
476 		LWM2M_PATH(1),
477 		LWM2M_PATH(1, 1),
478 		LWM2M_PATH(1, 1, 1),
479 		LWM2M_PATH(1, 2),
480 	};
481 	/* clang-format on */
482 
483 	run_insertion_test(insert_path_str, ARRAY_SIZE(insert_path_str), expected_path_str);
484 }
485 
ZTEST(lwm2m_observation,test_add_path_to_list_duplicate)486 ZTEST(lwm2m_observation, test_add_path_to_list_duplicate)
487 {
488 	/* clang-format off */
489 	char const *insert_path_str[] = {
490 		LWM2M_PATH(1),
491 		LWM2M_PATH(1, 1),
492 		LWM2M_PATH(1),
493 	};
494 
495 	char const *expected_path_str[] = {
496 		LWM2M_PATH(1),
497 		LWM2M_PATH(1, 1),
498 	};
499 	/* clang-format on */
500 
501 	run_insertion_test(insert_path_str, ARRAY_SIZE(insert_path_str), expected_path_str);
502 }
503 
504 ZTEST_SUITE(lwm2m_observation, NULL, NULL, NULL, NULL, NULL);
505