Safe Data API Test Specification
Annotated ztest sources of the Safe Data API
v0.0-dev+gbb519af
Loading...
Searching...
No Matches
main.c
1/*
2 * Copyright (c) 2026 The safety-toolbox contributors
3 * SPDX-License-Identifier: Apache-2.0
4 *
5 * Unit tests for the Safe Data API. Built with CONFIG_SAFE_DATA_REDUNDANT=y so
6 * the recovery paths are exercised; redundancy-specific assertions are guarded
7 * with IS_ENABLED().
8 */
9
10#include <zephyr/ztest.h>
11#include <string.h>
12
13#include <safe_data/safe_data.h>
14
15struct payload {
16 uint32_t a;
17 uint16_t b;
18 uint8_t c;
19};
20
21SAFE_CONTAINER_DEFINE(safe_payload, struct payload);
22
23static struct safe_payload sp;
24
25static void reset_to(uint32_t a, uint16_t b, uint8_t c)
26{
27 struct payload v = { .a = a, .b = b, .c = c };
28
29 zassert_ok(SAFE_INIT(&sp));
30 zassert_ok(SAFE_WRITE(&sp, &v));
31}
32
44ZTEST(safe_data, test_init_and_verify)
45{
46 reset_to(1, 2, 3);
47 zassert_ok(SAFE_VERIFY(&sp), "freshly sealed data must verify");
48}
49
61ZTEST(safe_data, test_read_roundtrip)
62{
63 struct payload out;
64
65 reset_to(0xDEADBEEF, 0xCAFE, 0x42);
66 zassert_ok(SAFE_READ(&sp, &out));
67 zassert_equal(out.a, 0xDEADBEEF);
68 zassert_equal(out.b, 0xCAFE);
69 zassert_equal(out.c, 0x42);
70}
71
86ZTEST(safe_data, test_write_reseals)
87{
88 struct payload v = { .a = 7, .b = 8, .c = 9 };
89 struct payload out;
90
91 reset_to(0, 0, 0);
92 zassert_ok(SAFE_WRITE(&sp, &v));
93 zassert_ok(SAFE_VERIFY(&sp));
94 zassert_ok(SAFE_READ(&sp, &out));
95 zassert_equal(out.a, 7);
96}
97
98static int mut_add(void *payload, void *user)
99{
100 struct payload *p = payload;
101
102 p->a += *(uint32_t *)user;
103 return 0;
104}
105
106static int mut_abort(void *payload, void *user)
107{
108 struct payload *p = payload;
109
110 ARG_UNUSED(user);
111 p->a = 0xFFFFFFFF; /* must be rolled back logically (tag not resealed) */
112 return -ERANGE;
113}
114
125ZTEST(safe_data, test_update_commits)
126{
127 uint32_t delta = 5;
128 struct payload out;
129
130 reset_to(10, 0, 0);
131 zassert_ok(SAFE_UPDATE(&sp, mut_add, &delta));
132 zassert_ok(SAFE_READ(&sp, &out));
133 zassert_equal(out.a, 15);
134}
135
148{
149 int ret;
150
151 struct payload out;
152
153 reset_to(10, 0, 0);
154 ret = SAFE_UPDATE(&sp, mut_abort, NULL);
155 zassert_equal(ret, -ERANGE, "mutator's error must propagate");
156
157 /* The mutator wrote a->0xFFFFFFFF then aborted. With the shadow copy
158 * the transaction rolls the payload back, leaving the container clean.
159 */
160 if (IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
161 zassert_ok(SAFE_VERIFY(&sp), "abort must roll back cleanly");
162 zassert_ok(SAFE_READ(&sp, &out));
163 zassert_equal(out.a, 10, "payload must be the pre-update value");
164 } else {
165 /* No rollback buffer: the partial write is detectable. */
166 zassert_equal(SAFE_VERIFY(&sp), -EILSEQ);
167 }
168}
169
181ZTEST(safe_data, test_section_scope)
182{
183#if defined(CONFIG_SAFE_DATA_GNU_EXTENSIONS)
184 struct payload out;
185
186 reset_to(1, 0, 0);
187 SAFE_SECTION(&sp, p) {
188 p->a += 100;
189 p->c = 0xAB;
190 }
191 zassert_ok(SAFE_VERIFY(&sp), "section must reseal on exit");
192 zassert_ok(SAFE_READ(&sp, &out));
193 zassert_equal(out.a, 101);
194 zassert_equal(out.c, 0xAB);
195#else
196 ztest_test_skip(); /* SAFE_SECTION not in the configured API surface */
197#endif
198}
199
212ZTEST(safe_data, test_detects_corruption)
213{
214 reset_to(100, 200, 30);
215
216 /* Corrupt the live payload behind the API's back. */
217 sp.payload.a = 0x12345678;
218
219 if (IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
220 /* shadow still good -> recoverable; verify is PURE and must
221 * not have repaired anything yet.
222 */
223 struct payload out;
224
225 zassert_equal(SAFE_VERIFY(&sp), SAFE_DATA_RECOVERED);
226 zassert_equal(sp.payload.a, 0x12345678,
227 "verify must not modify the payload");
228
229 /* The locked read repairs in place and reseals. */
230 zassert_ok(SAFE_READ(&sp, &out));
231 zassert_equal(out.a, 100, "payload must be restored from shadow");
232 zassert_equal(sp.payload.a, 100);
233 } else {
234 zassert_equal(SAFE_VERIFY(&sp), -EILSEQ);
235 }
236}
237
251{
252 if (!IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
253 ztest_test_skip(); /* repair requires the shadow copy */
254 }
255
256 reset_to(100, 200, 30);
257 sp.payload.a = 0x12345678;
258
259 /* verify_repair (here with external mutual exclusion: single thread)
260 * restores the payload; the tag is still stale until a reseal.
261 */
262 zassert_equal(safe_data_verify_repair(&sp.payload, sizeof(sp.payload),
263 sp._crc, _SAFE_SHADOW(&sp)),
265 zassert_equal(sp.payload.a, 100, "repair must restore the payload");
266 /* The restored payload matches the stored tag again. */
267 zassert_ok(SAFE_VERIFY(&sp));
268}
269
283{
284#if !defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT)
285 ztest_test_skip(); /* SAFE_COMMIT not in the configured API surface */
286#else
287 struct payload out;
288
289 reset_to(1, 2, 3);
290
291 /* Modify the storage directly (as an audited low-level path might),
292 * then SAFE_COMMIT reseals the tag (and refreshes the shadow) over the
293 * new contents. commit does not verify first, so this works the same
294 * with or without the redundant copy.
295 */
296 sp.payload.a = 77;
297 zassert_ok(SAFE_COMMIT(&sp));
298 zassert_ok(SAFE_VERIFY(&sp), "commit must reseal over current contents");
299 zassert_ok(SAFE_READ(&sp, &out));
300 zassert_equal(out.a, 77);
301#endif /* CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT */
302}
303
319ZTEST(safe_data, test_stale_tag_recovers)
320{
321 struct payload out;
322
323 if (!IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
324 ztest_test_skip(); /* recovery requires the shadow copy */
325 }
326
327 reset_to(5, 6, 7);
328
329 /* Corrupt ONLY the stored tag; payload and shadow stay consistent.
330 * verify() must notice the data itself is fine and report RECOVERED.
331 */
332 sp._crc ^= 0xA5A5A5A5u;
333 zassert_equal(SAFE_VERIFY(&sp), SAFE_DATA_RECOVERED,
334 "consistent data with a stale tag must be recoverable");
335
336 /* A read makes the recovery durable (reseals the tag). */
337 zassert_ok(SAFE_READ(&sp, &out));
338 zassert_equal(out.a, 5);
339 zassert_ok(SAFE_VERIFY(&sp), "tag must be resealed after recovery");
340}
341
354{
355 /* The shadow field only exists when redundancy is compiled in, so this
356 * test body must be guarded at compile time (a runtime skip would still
357 * try to compile the sp._shadow reference).
358 */
359#if CONFIG_SAFE_DATA_REDUNDANT
360 reset_to(9, 9, 9);
361
362 /* Corrupt the payload AND the shadow inconsistently: neither copy can
363 * vouch for the other, so the fault is unrecoverable.
364 */
365 sp.payload.a = 0x11111111u;
366 sp._shadow[0] ^= 0xAAu;
367 zassert_equal(SAFE_VERIFY(&sp), -EILSEQ,
368 "a double fault must be reported as unrecoverable");
369#else
370 ztest_test_skip(); /* the double-fault path only exists with a shadow */
371#endif
372}
373
374static int mut_noop(void *payload, void *user)
375{
376 ARG_UNUSED(payload);
377 ARG_UNUSED(user);
378 return 0;
379}
380
394ZTEST(safe_data, test_null_args)
395{
396 uint32_t crc = 0;
397 uint8_t buf[4] = {0};
398 uint8_t in[4] = {1, 2, 3, 4};
399 uint8_t out[4] = {0};
400 const size_t n = sizeof(buf);
401
402 /* init: payload and crc required, len > 0; a NULL lock is accepted. */
403 zassert_equal(safe_data_init(NULL, NULL, n, &crc, NULL), -EINVAL);
404 zassert_equal(safe_data_init(NULL, buf, n, NULL, NULL), -EINVAL);
405 zassert_equal(safe_data_init(NULL, buf, 0, &crc, NULL), -EINVAL);
406 zassert_ok(safe_data_init(NULL, buf, n, &crc, NULL),
407 "NULL lock must be accepted");
408
409 /* verify and verify_repair: payload required, len > 0. */
410 zassert_equal(safe_data_verify(NULL, n, crc, NULL), -EINVAL);
411 zassert_equal(safe_data_verify(buf, 0, crc, NULL), -EINVAL);
412 zassert_equal(safe_data_verify_repair(NULL, n, crc, NULL), -EINVAL);
413 zassert_equal(safe_data_verify_repair(buf, 0, crc, NULL), -EINVAL);
414
415 /* read: payload, crc and out required, len > 0. */
416 zassert_equal(safe_data_read(NULL, NULL, n, &crc, NULL, out), -EINVAL);
417 zassert_equal(safe_data_read(NULL, buf, n, NULL, NULL, out), -EINVAL);
418 zassert_equal(safe_data_read(NULL, buf, n, &crc, NULL, NULL), -EINVAL,
419 "read needs an out buffer");
420 zassert_equal(safe_data_read(NULL, buf, 0, &crc, NULL, out), -EINVAL);
421
422 /* write: payload, crc and in required, len > 0. */
423 zassert_equal(safe_data_write(NULL, NULL, n, &crc, NULL, in), -EINVAL);
424 zassert_equal(safe_data_write(NULL, buf, n, NULL, NULL, in), -EINVAL);
425 zassert_equal(safe_data_write(NULL, buf, n, &crc, NULL, NULL), -EINVAL,
426 "write needs an input buffer");
427 zassert_equal(safe_data_write(NULL, buf, 0, &crc, NULL, in), -EINVAL);
428
429 /* update: payload, crc and mutator required, len > 0. */
430 zassert_equal(safe_data_update(NULL, NULL, n, &crc, NULL, mut_noop,
431 NULL), -EINVAL);
432 zassert_equal(safe_data_update(NULL, buf, n, NULL, NULL, mut_noop,
433 NULL), -EINVAL);
434 zassert_equal(safe_data_update(NULL, buf, n, &crc, NULL, NULL, NULL),
435 -EINVAL, "update needs a mutator");
436 zassert_equal(safe_data_update(NULL, buf, 0, &crc, NULL, mut_noop,
437 NULL), -EINVAL);
438
439#if defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT)
440 /* commit: payload and crc required, len > 0. */
441 zassert_equal(safe_data_commit(NULL, NULL, n, &crc, NULL), -EINVAL);
442 zassert_equal(safe_data_commit(NULL, buf, n, NULL, NULL), -EINVAL);
443 zassert_equal(safe_data_commit(NULL, buf, 0, &crc, NULL), -EINVAL);
444#endif
445
446 /* stats_get: out required (with or without CONFIG_SAFE_DATA_STATS). */
447 zassert_equal(safe_data_stats_get(NULL), -EINVAL);
448
449 /* A NULL lock selects the lock-free mode: valid calls succeed. */
450 zassert_ok(safe_data_write(NULL, buf, n, &crc, NULL, in));
451 zassert_ok(safe_data_read(NULL, buf, n, &crc, NULL, out));
452 zassert_mem_equal(out, in, n);
453 zassert_ok(safe_data_update(NULL, buf, n, &crc, NULL, mut_noop, NULL));
454#if defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT)
455 zassert_ok(safe_data_commit(NULL, buf, n, &crc, NULL));
456#endif
457 zassert_ok(safe_data_verify(buf, n, crc, NULL));
458}
459
460#if !defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT)
461/* The header has no prototype here. A weak reference resolves to NULL when
462 * the image links no definition, so the test can show that the function is
463 * not part of the build without a use that fails to compile.
464 */
465extern int safe_data_commit(struct k_mutex *lock, void *payload, size_t len,
466 uint32_t *crc, void *shadow) __attribute__((weak));
467#endif
468
483{
484 int (*volatile commit_fn)(struct k_mutex *, void *, size_t, uint32_t *,
485 void *) = safe_data_commit;
486#if defined(SAFE_COMMIT)
487 const bool commit_macro = true;
488#else
489 const bool commit_macro = false;
490#endif
491#if defined(SAFE_SECTION)
492 const bool section_macro = true;
493#else
494 const bool section_macro = false;
495#endif
496 const bool commit_cfg = IS_ENABLED(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT);
497 const bool section_cfg = IS_ENABLED(CONFIG_SAFE_DATA_GNU_EXTENSIONS);
498
499 zassert_equal(commit_macro, commit_cfg,
500 "SAFE_COMMIT must exist exactly when it is configured");
501 zassert_equal(commit_fn != NULL, commit_cfg,
502 "safe_data_commit() must be linked exactly when configured");
503 zassert_equal(section_macro, section_cfg,
504 "SAFE_SECTION must exist exactly when it is configured");
505
506 if (IS_ENABLED(CONFIG_SAFE_DATA_LEVEL_STRICT)) {
507 zassert_false(commit_macro, "Strict: no SAFE_COMMIT");
508 zassert_is_null(commit_fn, "Strict: no safe_data_commit()");
509 zassert_false(section_macro, "Strict: no SAFE_SECTION");
510 }
511}
512
513/* ----------------------------------------------------------------------- *
514 * Concurrency: two threads share one container. The mutex inside the
515 * container must serialise the producer's read-modify-write against the
516 * consumer's reads, so the consumer never observes a torn (internally
517 * inconsistent) snapshot. The producer deliberately writes the dependent
518 * fields one at a time with yields between them to widen the race window.
519 * ----------------------------------------------------------------------- */
521 uint32_t seq;
522 uint32_t a;
523 uint32_t b;
524 uint32_t sum; /* invariant: sum == a + b */
525};
526
527SAFE_CONTAINER_DEFINE(safe_cc, struct cc_payload);
528static struct safe_cc cc;
529
530#define CC_ITERS 1000
531
532static atomic_t cc_breaks;
533static atomic_t cc_reads;
534
535static int cc_advance(void *payload, void *user)
536{
537 struct cc_payload *s = payload;
538 uint32_t n = ++s->seq;
539
540 ARG_UNUSED(user);
541
542 s->a = n;
543 k_yield(); /* momentarily inconsistent - but under the lock */
544 s->b = n * 3U;
545 k_yield();
546 s->sum = s->a + s->b;
547 return 0;
548}
549
550static K_THREAD_STACK_DEFINE(cc_prod_stack, 2048);
551static K_THREAD_STACK_DEFINE(cc_cons_stack, 2048);
552static struct k_thread cc_prod;
553static struct k_thread cc_cons;
554
555static void cc_producer(void *a, void *b, void *c)
556{
557 ARG_UNUSED(a); ARG_UNUSED(b); ARG_UNUSED(c);
558
559 for (int i = 0; i < CC_ITERS; i++) {
560 (void)SAFE_UPDATE(&cc, cc_advance, NULL);
561 k_yield();
562 }
563}
564
565static void cc_consumer(void *a, void *b, void *c)
566{
567 struct cc_payload snap;
568
569 ARG_UNUSED(a); ARG_UNUSED(b); ARG_UNUSED(c);
570
571 for (int i = 0; i < CC_ITERS; i++) {
572 if (SAFE_READ(&cc, &snap) == 0) {
573 if (snap.sum != snap.a + snap.b) {
574 atomic_inc(&cc_breaks);
575 }
576 atomic_inc(&cc_reads);
577 }
578 k_yield();
579 }
580}
581
594{
595 struct cc_payload init = {0};
596
597 zassert_ok(SAFE_INIT(&cc));
598 zassert_ok(SAFE_WRITE(&cc, &init));
599 atomic_clear(&cc_breaks);
600 atomic_clear(&cc_reads);
601
602 k_thread_create(&cc_prod, cc_prod_stack,
603 K_THREAD_STACK_SIZEOF(cc_prod_stack), cc_producer,
604 NULL, NULL, NULL, 5, 0, K_NO_WAIT);
605 k_thread_create(&cc_cons, cc_cons_stack,
606 K_THREAD_STACK_SIZEOF(cc_cons_stack), cc_consumer,
607 NULL, NULL, NULL, 5, 0, K_NO_WAIT);
608
609 zassert_ok(k_thread_join(&cc_prod, K_FOREVER));
610 zassert_ok(k_thread_join(&cc_cons, K_FOREVER));
611
612 zassert_true(atomic_get(&cc_reads) > 0, "consumer made no reads");
613 zassert_equal(atomic_get(&cc_breaks), 0,
614 "lock failed to serialise: a torn snapshot was observed");
615}
616
617/* ----------------------------------------------------------------------- *
618 * Diagnostics: self-test, statistics, fault-handler events, contract
619 * enforcement, bounded locking.
620 * ----------------------------------------------------------------------- */
621
633ZTEST(safe_data, test_selftest)
634{
635#if defined(CONFIG_SAFE_DATA_SELFTEST)
636 zassert_ok(safe_data_selftest(), "integrity mechanism must be healthy");
637#else
638 ztest_test_skip();
639#endif
640}
641
653ZTEST(safe_data, test_stats_count_events)
654{
655#if defined(CONFIG_SAFE_DATA_STATS)
656 struct safe_data_stats st;
657 struct payload out;
658
660 reset_to(1, 2, 3);
661
662 if (IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
663 /* Corrupt payload -> locked read repairs from the shadow. */
664 sp.payload.a = 99;
665 zassert_ok(SAFE_READ(&sp, &out));
666 zassert_ok(safe_data_stats_get(&st));
667 zassert_equal(st.recovered_payload, 1);
668
669 /* Corrupt only the stored tag -> recovered by resealing. */
670 sp._crc ^= 0xFFu;
671 zassert_ok(SAFE_READ(&sp, &out));
672 zassert_ok(safe_data_stats_get(&st));
673 zassert_equal(st.recovered_tag, 1);
674 }
675
676 /* Unrecoverable corruption must be counted too. */
677#if CONFIG_SAFE_DATA_REDUNDANT
678 sp.payload.a ^= 0x1111u;
679 sp._shadow[0] ^= 0xAAu;
680#else
681 sp.payload.a ^= 0x1111u;
682#endif
683 zassert_equal(SAFE_VERIFY(&sp), -EILSEQ);
684 zassert_ok(safe_data_stats_get(&st));
685 zassert_equal(st.unrecoverable, 1);
686#else
687 struct safe_data_stats st;
688
689 zassert_equal(safe_data_stats_get(&st), -ENOTSUP);
690 ztest_test_skip();
691#endif
692}
693
694static atomic_t cb_count;
695static struct safe_data_fault_info cb_last;
696
697static void fault_event_cb(const struct safe_data_fault_info *info)
698{
699 cb_last = *info;
700 atomic_inc(&cb_count);
701}
702
703static void fault_event_cb_other(const struct safe_data_fault_info *info)
704{
705 ARG_UNUSED(info);
706}
707
722{
723 atomic_clear(&cb_count);
724 zassert_ok(safe_data_fault_handler_register(fault_event_cb));
725 zassert_equal(safe_data_fault_handler_register(fault_event_cb_other),
726 -EALREADY, "second handler must be rejected");
727 zassert_ok(safe_data_fault_handler_register(fault_event_cb),
728 "re-registering the same handler is idempotent");
729
730 reset_to(4, 5, 6);
731
732 if (IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
733 struct payload out;
734
735 /* Recovery must be observable, not silent. */
736 sp.payload.a = 0xBAD;
737 zassert_ok(SAFE_READ(&sp, &out));
738 zassert_true(atomic_get(&cb_count) >= 1);
739 zassert_equal(cb_last.kind,
741 }
742
743 /* Unrecoverable corruption must reach the handler too. */
744#if CONFIG_SAFE_DATA_REDUNDANT
745 sp.payload.a ^= 0x2222u;
746 sp._shadow[1] ^= 0x55u;
747#else
748 sp.payload.a ^= 0x2222u;
749#endif
750 zassert_equal(SAFE_VERIFY(&sp), -EILSEQ);
751 zassert_equal(cb_last.kind, SAFE_DATA_FAULT_KIND_UNRECOVERABLE);
752 zassert_equal(cb_last.len, sizeof(struct payload));
753
754 zassert_ok(safe_data_fault_handler_register(NULL), "unregister");
755}
756
757static int mut_positive(void *payload, void *user)
758{
759 struct payload *p = payload;
760
761 ARG_UNUSED(user);
762 p->a = 0xDEAD;
763 return 7; /* contract violation: positive return */
764}
765
778{
779 reset_to(10, 0, 0);
780 zassert_equal(SAFE_UPDATE(&sp, mut_positive, NULL), -EINVAL,
781 "positive mutator return must be clamped to -EINVAL");
782
783 if (IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
784 struct payload out;
785
786 zassert_ok(SAFE_READ(&sp, &out), "clamp must abort + roll back");
787 zassert_equal(out.a, 10);
788 } else {
789 /* No rollback buffer: the partial write is detectable. */
790 zassert_equal(SAFE_VERIFY(&sp), -EILSEQ);
791 }
792
793#if defined(CONFIG_SAFE_DATA_STATS)
794 {
795 struct safe_data_stats st;
796
797 zassert_ok(safe_data_stats_get(&st));
798 zassert_true(st.mutator_clamped >= 1);
799 }
800#endif
801}
802
816{
817#if defined(CONFIG_SAFE_DATA_WRITE_CHECKS_OLD) && defined(CONFIG_SAFE_DATA_STATS)
818 struct payload v = { .a = 1, .b = 2, .c = 3 };
819 struct safe_data_stats st;
820
821 reset_to(100, 200, 30);
823
824 /* Corrupt the old contents, then overwrite: the write must succeed
825 * but the destroyed evidence must be counted.
826 */
827 sp.payload.a = 0xBADF00D;
828 zassert_ok(SAFE_WRITE(&sp, &v));
829 zassert_ok(safe_data_stats_get(&st));
830 zassert_equal(st.overwritten, 1,
831 "overwritten corruption must be observed");
832 zassert_ok(SAFE_VERIFY(&sp));
833#else
834 ztest_test_skip();
835#endif
836}
837
838#if CONFIG_SAFE_DATA_LOCK_TIMEOUT_MS > 0 && defined(CONFIG_SAFE_DATA_LOCKING)
839static int mut_hold(void *payload, void *user)
840{
841 ARG_UNUSED(payload);
842 ARG_UNUSED(user);
843 k_sleep(K_MSEC(10 * CONFIG_SAFE_DATA_LOCK_TIMEOUT_MS));
844 return 0;
845}
846
847static K_THREAD_STACK_DEFINE(holder_stack, 2048);
848static struct k_thread holder_thread;
849
850static void holder_fn(void *a, void *b, void *c)
851{
852 ARG_UNUSED(a); ARG_UNUSED(b); ARG_UNUSED(c);
853 (void)SAFE_UPDATE(&sp, mut_hold, NULL);
854}
855#endif
856
872{
873#if CONFIG_SAFE_DATA_LOCK_TIMEOUT_MS > 0 && defined(CONFIG_SAFE_DATA_LOCKING)
874 struct payload out;
875 struct payload in = { .a = 2, .b = 2, .c = 2 };
876 uint32_t delta = 1;
877 uint32_t blocked = 0;
878#if defined(CONFIG_SAFE_DATA_STATS)
879 struct safe_data_stats before;
880 struct safe_data_stats after;
881#endif
882
883 reset_to(1, 1, 1);
884#if defined(CONFIG_SAFE_DATA_STATS)
885 zassert_ok(safe_data_stats_get(&before));
886#endif
887
888 k_thread_create(&holder_thread, holder_stack,
889 K_THREAD_STACK_SIZEOF(holder_stack), holder_fn,
890 NULL, NULL, NULL, 5, 0, K_NO_WAIT);
891 /* Let the holder take the container lock. It keeps the lock for ten
892 * bounds; the blocked operations below use one bound each.
893 */
894 k_sleep(K_MSEC(CONFIG_SAFE_DATA_LOCK_TIMEOUT_MS));
895
896 zassert_equal(SAFE_READ(&sp, &out), -ETIMEDOUT,
897 "blocked read must fail within the configured bound");
898 blocked++;
899 zassert_equal(SAFE_WRITE(&sp, &in), -ETIMEDOUT,
900 "blocked write must fail within the configured bound");
901 blocked++;
902 zassert_equal(SAFE_UPDATE(&sp, mut_add, &delta), -ETIMEDOUT,
903 "blocked update must fail within the configured bound");
904 blocked++;
905#if defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT)
906 zassert_equal(SAFE_COMMIT(&sp), -ETIMEDOUT,
907 "blocked commit must fail within the configured bound");
908 blocked++;
909#endif
910#if defined(CONFIG_SAFE_DATA_GNU_EXTENSIONS)
911 {
912 bool entered = false;
913
914 SAFE_SECTION(&sp, p) {
915 entered = true;
916 p->a = 0;
917 }
918 zassert_false(entered,
919 "a blocked SAFE_SECTION must skip its block");
920 blocked++;
921 }
922#endif
923
924#if defined(CONFIG_SAFE_DATA_STATS)
925 zassert_ok(safe_data_stats_get(&after));
926 zassert_equal(after.lock_timeouts - before.lock_timeouts, blocked,
927 "each timeout must be observable");
928#endif
929 ARG_UNUSED(blocked);
930
931 zassert_ok(k_thread_join(&holder_thread, K_FOREVER));
932 zassert_ok(SAFE_READ(&sp, &out), "container must be usable again");
933#else
934 ztest_test_skip(); /* needs CONFIG_SAFE_DATA_LOCK_TIMEOUT_MS > 0 */
935#endif
936}
937
943
948ZTEST_SUITE(safe_data, NULL, NULL, NULL, NULL, NULL);
#define SAFE_COMMIT(_c)
#define SAFE_READ(_c, _out_ptr)
#define SAFE_INIT(_c)
#define SAFE_SECTION(_c, _pvar)
#define SAFE_VERIFY(_c)
#define SAFE_UPDATE(_c, _fn, _user)
#define SAFE_WRITE(_c, _in_ptr)
int safe_data_verify_repair(void *payload, size_t len, uint32_t crc, void *shadow)
int safe_data_update(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow, safe_data_mutator_t fn, void *user)
#define SAFE_DATA_RECOVERED
int safe_data_read(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow, void *out)
int safe_data_verify(const void *payload, size_t len, uint32_t crc, const void *shadow)
int safe_data_write(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow, const void *in)
int safe_data_init(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow)
int safe_data_commit(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow)
void safe_data_stats_reset(void)
int safe_data_stats_get(struct safe_data_stats *out)
int safe_data_selftest(void)
int safe_data_fault_handler_register(safe_data_fault_cb_t cb)
SAFE_DATA_FAULT_KIND_UNRECOVERABLE
SAFE_DATA_FAULT_KIND_RECOVERED_PAYLOAD
void test_concurrent_access_is_serialised(void)
Concurrent access never yields torn snapshots.
Definition main.c:593
void test_detects_corruption(void)
Payload corruption detected; verification is pure.
Definition main.c:212
void test_update_clamps_positive_mutator_return(void)
Positive mutator return is clamped and aborted.
Definition main.c:777
void test_verify_repair_fixes_in_place(void)
verify_repair restores the payload in place
Definition main.c:250
void test_double_fault_unrecoverable(void)
Double fault is unrecoverable.
Definition main.c:353
void test_section_scope(void)
SAFE_SECTION reseals at scope exit.
Definition main.c:181
void test_selftest(void)
Self-test reports a healthy mechanism.
Definition main.c:633
void test_write_reseals(void)
Write replaces the payload and reseals.
Definition main.c:86
void test_commit_reseals_external_write(void)
Unchecked commit reseals an external modification.
Definition main.c:282
void test_reduced_api_surface(void)
Optional API surface matches the configuration.
Definition main.c:482
void test_null_args(void)
NULL and zero-size arguments are rejected.
Definition main.c:394
void test_fault_handler_events(void)
Registered handler receives all event kinds.
Definition main.c:721
void test_read_roundtrip(void)
Read returns the sealed value.
Definition main.c:61
void test_update_abort_keeps_integrity(void)
Aborted update leaves a consistent container.
Definition main.c:147
void test_update_commits(void)
Update commits a mutator transaction.
Definition main.c:125
void test_write_observes_overwritten_corruption(void)
Write observes corruption before overwriting.
Definition main.c:815
void test_stale_tag_recovers(void)
Stale tag with consistent data recovers.
Definition main.c:319
void test_lock_timeout_is_bounded_and_reported(void)
Blocked access fails within the lock bound.
Definition main.c:871
void test_init_and_verify(void)
Init seals and verification passes.
Definition main.c:44
void test_stats_count_events(void)
Statistics count every integrity event.
Definition main.c:653
Definition main.c:15
uint32_t unrecoverable
uint32_t overwritten
uint32_t lock_timeouts
uint32_t recovered_tag
uint32_t recovered_payload
uint32_t mutator_clamped