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