10#include <zephyr/ztest.h>
21SAFE_CONTAINER_DEFINE(safe_payload,
struct payload);
23static struct safe_payload sp;
25static void reset_to(uint32_t a, uint16_t b, uint8_t c)
27 struct payload v = { .a = a, .b = b, .c = c };
47 zassert_ok(
SAFE_VERIFY(&sp),
"freshly sealed data must verify");
65 reset_to(0xDEADBEEF, 0xCAFE, 0x42);
67 zassert_equal(out.a, 0xDEADBEEF);
68 zassert_equal(out.b, 0xCAFE);
69 zassert_equal(out.c, 0x42);
88 struct payload v = { .a = 7, .b = 8, .c = 9 };
95 zassert_equal(out.a, 7);
98static int mut_add(
void *
payload,
void *user)
102 p->a += *(uint32_t *)user;
106static int mut_abort(
void *
payload,
void *user)
133 zassert_equal(out.a, 15);
155 zassert_equal(ret, -ERANGE,
"mutator's error must propagate");
160 if (IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
161 zassert_ok(
SAFE_VERIFY(&sp),
"abort must roll back cleanly");
163 zassert_equal(out.a, 10,
"payload must be the pre-update value");
183#if defined(CONFIG_SAFE_DATA_GNU_EXTENSIONS)
191 zassert_ok(
SAFE_VERIFY(&sp),
"section must reseal on exit");
193 zassert_equal(out.a, 101);
194 zassert_equal(out.c, 0xAB);
214 reset_to(100, 200, 30);
217 sp.payload.a = 0x12345678;
219 if (IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
226 zassert_equal(sp.payload.a, 0x12345678,
227 "verify must not modify the payload");
231 zassert_equal(out.a, 100,
"payload must be restored from shadow");
232 zassert_equal(sp.payload.a, 100);
252 if (!IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
256 reset_to(100, 200, 30);
257 sp.payload.a = 0x12345678;
263 sp._crc, _SAFE_SHADOW(&sp)),
265 zassert_equal(sp.payload.a, 100,
"repair must restore the payload");
284#if !defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT)
298 zassert_ok(
SAFE_VERIFY(&sp),
"commit must reseal over current contents");
300 zassert_equal(out.a, 77);
323 if (!IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
332 sp._crc ^= 0xA5A5A5A5u;
334 "consistent data with a stale tag must be recoverable");
338 zassert_equal(out.a, 5);
339 zassert_ok(
SAFE_VERIFY(&sp),
"tag must be resealed after recovery");
359#if CONFIG_SAFE_DATA_REDUNDANT
365 sp.payload.a = 0x11111111u;
366 sp._shadow[0] ^= 0xAAu;
368 "a double fault must be reported as unrecoverable");
374static int mut_noop(
void *
payload,
void *user)
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);
403 zassert_equal(
safe_data_init(NULL, NULL, n, &crc, NULL), -EINVAL);
407 "NULL lock must be accepted");
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);
423 zassert_equal(
safe_data_write(NULL, NULL, n, &crc, NULL, in), -EINVAL);
425 zassert_equal(
safe_data_write(NULL, buf, n, &crc, NULL, NULL), -EINVAL,
426 "write needs an input buffer");
435 -EINVAL,
"update needs a mutator");
439#if defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT)
452 zassert_mem_equal(out, in, n);
454#if defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT)
460#if !defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT)
466 uint32_t *crc,
void *shadow) __attribute__((weak));
484 int (*
volatile commit_fn)(
struct k_mutex *,
void *, size_t, uint32_t *,
486#if defined(SAFE_COMMIT)
487 const bool commit_macro =
true;
489 const bool commit_macro =
false;
491#if defined(SAFE_SECTION)
492 const bool section_macro =
true;
494 const bool section_macro =
false;
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);
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");
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");
527SAFE_CONTAINER_DEFINE(safe_cc,
struct cc_payload);
528static struct safe_cc cc;
532static atomic_t cc_breaks;
533static atomic_t cc_reads;
535static int cc_advance(
void *
payload,
void *user)
538 uint32_t n = ++s->seq;
546 s->sum = s->a + s->b;
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;
555static void cc_producer(
void *a,
void *b,
void *c)
557 ARG_UNUSED(a); ARG_UNUSED(b); ARG_UNUSED(c);
559 for (
int i = 0; i < CC_ITERS; i++) {
565static void cc_consumer(
void *a,
void *b,
void *c)
569 ARG_UNUSED(a); ARG_UNUSED(b); ARG_UNUSED(c);
571 for (
int i = 0; i < CC_ITERS; i++) {
573 if (snap.sum != snap.a + snap.b) {
574 atomic_inc(&cc_breaks);
576 atomic_inc(&cc_reads);
599 atomic_clear(&cc_breaks);
600 atomic_clear(&cc_reads);
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);
609 zassert_ok(k_thread_join(&cc_prod, K_FOREVER));
610 zassert_ok(k_thread_join(&cc_cons, K_FOREVER));
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");
635#if defined(CONFIG_SAFE_DATA_SELFTEST)
655#if defined(CONFIG_SAFE_DATA_STATS)
662 if (IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
677#if CONFIG_SAFE_DATA_REDUNDANT
678 sp.payload.a ^= 0x1111u;
679 sp._shadow[0] ^= 0xAAu;
681 sp.payload.a ^= 0x1111u;
694static atomic_t cb_count;
700 atomic_inc(&cb_count);
723 atomic_clear(&cb_count);
726 -EALREADY,
"second handler must be rejected");
728 "re-registering the same handler is idempotent");
732 if (IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
736 sp.payload.a = 0xBAD;
738 zassert_true(atomic_get(&cb_count) >= 1);
739 zassert_equal(cb_last.kind,
744#if CONFIG_SAFE_DATA_REDUNDANT
745 sp.payload.a ^= 0x2222u;
746 sp._shadow[1] ^= 0x55u;
748 sp.payload.a ^= 0x2222u;
752 zassert_equal(cb_last.len,
sizeof(
struct payload));
757static int mut_positive(
void *
payload,
void *user)
780 zassert_equal(
SAFE_UPDATE(&sp, mut_positive, NULL), -EINVAL,
781 "positive mutator return must be clamped to -EINVAL");
783 if (IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
786 zassert_ok(
SAFE_READ(&sp, &out),
"clamp must abort + roll back");
787 zassert_equal(out.a, 10);
793#if defined(CONFIG_SAFE_DATA_STATS)
817#if defined(CONFIG_SAFE_DATA_WRITE_CHECKS_OLD) && defined(CONFIG_SAFE_DATA_STATS)
818 struct payload v = { .a = 1, .b = 2, .c = 3 };
821 reset_to(100, 200, 30);
827 sp.payload.a = 0xBADF00D;
831 "overwritten corruption must be observed");
838#if CONFIG_SAFE_DATA_LOCK_TIMEOUT_MS > 0 && defined(CONFIG_SAFE_DATA_LOCKING)
839static int mut_hold(
void *
payload,
void *user)
843 k_sleep(K_MSEC(10 * CONFIG_SAFE_DATA_LOCK_TIMEOUT_MS));
847static K_THREAD_STACK_DEFINE(holder_stack, 2048);
848static struct k_thread holder_thread;
850static void holder_fn(
void *a,
void *b,
void *c)
852 ARG_UNUSED(a); ARG_UNUSED(b); ARG_UNUSED(c);
873#if CONFIG_SAFE_DATA_LOCK_TIMEOUT_MS > 0 && defined(CONFIG_SAFE_DATA_LOCKING)
875 struct payload in = { .a = 2, .b = 2, .c = 2 };
877 uint32_t blocked = 0;
878#if defined(CONFIG_SAFE_DATA_STATS)
884#if defined(CONFIG_SAFE_DATA_STATS)
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);
894 k_sleep(K_MSEC(CONFIG_SAFE_DATA_LOCK_TIMEOUT_MS));
896 zassert_equal(
SAFE_READ(&sp, &out), -ETIMEDOUT,
897 "blocked read must fail within the configured bound");
899 zassert_equal(
SAFE_WRITE(&sp, &in), -ETIMEDOUT,
900 "blocked write must fail within the configured bound");
902 zassert_equal(
SAFE_UPDATE(&sp, mut_add, &delta), -ETIMEDOUT,
903 "blocked update must fail within the configured bound");
905#if defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT)
907 "blocked commit must fail within the configured bound");
910#if defined(CONFIG_SAFE_DATA_GNU_EXTENSIONS)
912 bool entered =
false;
918 zassert_false(entered,
919 "a blocked SAFE_SECTION must skip its block");
924#if defined(CONFIG_SAFE_DATA_STATS)
927 "each timeout must be observable");
931 zassert_ok(k_thread_join(&holder_thread, K_FOREVER));
932 zassert_ok(
SAFE_READ(&sp, &out),
"container must be usable again");
948ZTEST_SUITE(safe_data, NULL, NULL, NULL, NULL, NULL);
#define SAFE_READ(_c, _out_ptr)
#define SAFE_SECTION(_c, _pvar)
#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.
void test_detects_corruption(void)
Payload corruption detected; verification is pure.
void test_update_clamps_positive_mutator_return(void)
Positive mutator return is clamped and aborted.
void test_verify_repair_fixes_in_place(void)
verify_repair restores the payload in place
void test_double_fault_unrecoverable(void)
Double fault is unrecoverable.
void test_section_scope(void)
SAFE_SECTION reseals at scope exit.
void test_selftest(void)
Self-test reports a healthy mechanism.
void test_write_reseals(void)
Write replaces the payload and reseals.
void test_commit_reseals_external_write(void)
Unchecked commit reseals an external modification.
void test_reduced_api_surface(void)
Optional API surface matches the configuration.
void test_null_args(void)
NULL and zero-size arguments are rejected.
void test_fault_handler_events(void)
Registered handler receives all event kinds.
void test_read_roundtrip(void)
Read returns the sealed value.
void test_update_abort_keeps_integrity(void)
Aborted update leaves a consistent container.
void test_update_commits(void)
Update commits a mutator transaction.
void test_write_observes_overwritten_corruption(void)
Write observes corruption before overwriting.
void test_stale_tag_recovers(void)
Stale tag with consistent data recovers.
void test_lock_timeout_is_bounded_and_reported(void)
Blocked access fails within the lock bound.
void test_init_and_verify(void)
Init seals and verification passes.
void test_stats_count_events(void)
Statistics count every integrity event.
uint32_t recovered_payload