Skip to content

Commit 2996b58

Browse files
committed
Add unit tests
1 parent b747574 commit 2996b58

5 files changed

Lines changed: 249 additions & 3 deletions

File tree

include/wolfboot/wolfboot.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ int wolfBoot_get_failure_count(void);
647647
int wolfBoot_get_failure(int index, struct wolfBoot_failure_record *out);
648648
int wolfBoot_clear_failures(void);
649649

650-
#ifdef __WOLFBOOT
650+
#if defined(__WOLFBOOT) || defined(UNIT_TEST)
651651
/* Internal API */
652652
int wolfBoot_record_failure(uint8_t phase, uint8_t cause, uint8_t partition,
653653
uint32_t fw_version);

src/libwolfboot.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,7 +1085,7 @@ static int RAMFUNCTION diag_erase(haladdr_t addr, uint32_t len)
10851085
#endif
10861086
}
10871087

1088-
#ifdef __WOLFBOOT
1088+
#if defined(__WOLFBOOT) || defined(UNIT_TEST)
10891089
static int RAMFUNCTION diag_write(haladdr_t addr, const void *buf, uint32_t len)
10901090
{
10911091
#if DIAG_IS_EXT
@@ -1175,7 +1175,7 @@ int RAMFUNCTION wolfBoot_record_failure(uint8_t phase, uint8_t cause,
11751175
diag_lock();
11761176
return ret;
11771177
}
1178-
#endif /* __WOLFBOOT */
1178+
#endif /* __WOLFBOOT || UNIT_TEST */
11791179

11801180
int wolfBoot_get_failure_count(void)
11811181
{

tools/unit-tests/Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128
6464
unit-keygen-xmss-params
6565
TESTS+=unit-tpm-check-rot-auth
6666
TESTS+=unit-tpm-api-names
67+
TESTS+=unit-diagnostics
6768
TESTS+=unit-fit-gzip unit-fit-nogzip
6869
TESTS+=unit-fit-fpga
6970
TESTS+=unit-mpusize
@@ -133,6 +134,7 @@ unit-parser:CFLAGS+=-DNVM_FLASH_WRITEONCE
133134
unit-fdt:CFLAGS+=-DWOLFBOOT_FDT
134135
unit-nvm:CFLAGS+=-DNVM_FLASH_WRITEONCE -DMOCK_PARTITIONS
135136
unit-nvm-flagshome:CFLAGS+=-DNVM_FLASH_WRITEONCE -DMOCK_PARTITIONS -DFLAGS_HOME
137+
unit-diagnostics:CFLAGS+=-DMOCK_PARTITIONS
136138
unit-enc-nvm:CFLAGS+=-DNVM_FLASH_WRITEONCE -DMOCK_PARTITIONS -DEXT_ENCRYPTED \
137139
-DENCRYPT_WITH_CHACHA -DEXT_FLASH -DHAVE_CHACHA
138140
unit-enc-nvm:WOLFCRYPT_SRC+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/chacha.c
@@ -445,6 +447,9 @@ unit-nvm: ../../include/target.h unit-nvm.c
445447
unit-nvm-flagshome: ../../include/target.h unit-nvm.c
446448
gcc -o $@ unit-nvm.c $(CFLAGS) $(LDFLAGS)
447449

450+
unit-diagnostics: ../../include/target.h unit-diagnostics.c
451+
gcc -o $@ unit-diagnostics.c $(CFLAGS) $(LDFLAGS)
452+
448453
unit-enc-nvm: ../../include/target.h unit-enc-nvm.c
449454
gcc -o $@ $(WOLFCRYPT_SRC) unit-enc-nvm.c $(CFLAGS) $(WOLFCRYPT_CFLAGS) $(LDFLAGS)
450455

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/* unit-diagnostics.c
2+
*
3+
* Unit tests for the persistent failure diagnostics store.
4+
*
5+
* Copyright (C) 2026 wolfSSL Inc.
6+
*
7+
* This file is part of wolfBoot.
8+
*
9+
* wolfBoot is free software; you can redistribute it and/or modify
10+
* it under the terms of the GNU General Public License as published by
11+
* the Free Software Foundation; either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* wolfBoot is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with this program; if not, write to the Free Software
21+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
22+
*/
23+
#define WOLFBOOT_HASH_SHA256
24+
#define IMAGE_HEADER_SIZE 256
25+
#define WOLFBOOT_PERSIST_FAILURE_STATUS
26+
#define WOLFBOOT_DIAGNOSTICS_ADDRESS 0xD0000000
27+
#define WOLFBOOT_DIAGNOSTICS_SECTORS 2
28+
#define WC_RSA_BLINDING
29+
#define ECC_TIMING_RESISTANT
30+
#include <stdio.h>
31+
#include "libwolfboot.c"
32+
#include <fcntl.h>
33+
#include <unistd.h>
34+
#include <sys/mman.h>
35+
#include <check.h>
36+
37+
#include "unit-mock-flash.c"
38+
39+
Suite *wolfboot_suite(void);
40+
41+
#define DIAG_REGION_SIZE (WOLFBOOT_DIAGNOSTICS_SECTORS * WOLFBOOT_SECTOR_SIZE)
42+
43+
static void diag_mmap(const char *path)
44+
{
45+
int ret = mmap_file(path, (void *)(uintptr_t)WOLFBOOT_DIAGNOSTICS_ADDRESS,
46+
DIAG_REGION_SIZE, NULL);
47+
ck_assert(ret >= 0);
48+
}
49+
50+
static void record_one(uint8_t phase, uint8_t cause, uint8_t part, uint32_t ver)
51+
{
52+
int ret = wolfBoot_record_failure(phase, cause, part, ver);
53+
ck_assert_int_eq(ret, 0);
54+
}
55+
56+
START_TEST(test_record_and_read_newest_first)
57+
{
58+
struct wolfBoot_failure_record rec, oldest;
59+
60+
diag_mmap("/tmp/wolfboot-unit-diag-basic.bin");
61+
ck_assert_int_eq(wolfBoot_clear_failures(), 0);
62+
63+
/* empty store */
64+
ck_assert_int_eq(wolfBoot_get_failure_count(), 0);
65+
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), -1);
66+
67+
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
68+
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 11);
69+
record_one(WOLFBOOT_FAILURE_PHASE_BOOT,
70+
WOLFBOOT_FAILURE_CAUSE_SIGNATURE, PART_BOOT, 22);
71+
record_one(WOLFBOOT_FAILURE_PHASE_ROLLBACK,
72+
WOLFBOOT_FAILURE_CAUSE_NOT_CONFIRMED, PART_BOOT, 33);
73+
74+
ck_assert_int_eq(wolfBoot_get_failure_count(), 3);
75+
76+
/* index 0 is the most recent record */
77+
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), 0);
78+
ck_assert_uint_eq(rec.phase, WOLFBOOT_FAILURE_PHASE_ROLLBACK);
79+
ck_assert_uint_eq(rec.cause, WOLFBOOT_FAILURE_CAUSE_NOT_CONFIRMED);
80+
ck_assert_uint_eq(rec.partition, PART_BOOT);
81+
ck_assert_uint_eq(rec.fw_version, 33);
82+
83+
ck_assert_int_eq(wolfBoot_get_failure(1, &rec), 0);
84+
ck_assert_uint_eq(rec.phase, WOLFBOOT_FAILURE_PHASE_BOOT);
85+
ck_assert_uint_eq(rec.cause, WOLFBOOT_FAILURE_CAUSE_SIGNATURE);
86+
ck_assert_uint_eq(rec.fw_version, 22);
87+
88+
ck_assert_int_eq(wolfBoot_get_failure(2, &oldest), 0);
89+
ck_assert_uint_eq(oldest.phase, WOLFBOOT_FAILURE_PHASE_UPDATE);
90+
ck_assert_uint_eq(oldest.cause, WOLFBOOT_FAILURE_CAUSE_HASH);
91+
ck_assert_uint_eq(oldest.partition, PART_UPDATE);
92+
ck_assert_uint_eq(oldest.fw_version, 11);
93+
94+
/* sequence numbers increase from oldest to newest */
95+
wolfBoot_get_failure(0, &rec);
96+
ck_assert_uint_gt(rec.seq, oldest.seq);
97+
98+
/* invalid arguments */
99+
ck_assert_int_eq(wolfBoot_get_failure(3, &rec), -1);
100+
ck_assert_int_eq(wolfBoot_get_failure(-1, &rec), -1);
101+
ck_assert_int_eq(wolfBoot_get_failure(0, NULL), -1);
102+
}
103+
END_TEST
104+
105+
START_TEST(test_ring_wrap_and_ordering)
106+
{
107+
struct wolfBoot_failure_record rec;
108+
int slots = (int)DIAG_SLOTS_PER_SECTOR;
109+
int total = 2 * slots + 1; /* force a full wrap back onto the first sector */
110+
int count, i;
111+
112+
diag_mmap("/tmp/wolfboot-unit-diag-wrap.bin");
113+
ck_assert_int_eq(wolfBoot_clear_failures(), 0);
114+
115+
for (i = 1; i <= total; i++)
116+
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
117+
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, (uint32_t)i);
118+
119+
/* With two sectors, after wrapping we keep the most recent full sector plus
120+
* the one record written into the freshly recycled sector. */
121+
count = wolfBoot_get_failure_count();
122+
ck_assert_int_eq(count, slots + 1);
123+
124+
/* newest is the last record written */
125+
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), 0);
126+
ck_assert_uint_eq(rec.fw_version, (uint32_t)total);
127+
128+
/* oldest retained record */
129+
ck_assert_int_eq(wolfBoot_get_failure(count - 1, &rec), 0);
130+
ck_assert_uint_eq(rec.fw_version, (uint32_t)(total - count + 1));
131+
132+
/* records are returned strictly newest-first and contiguous */
133+
for (i = 0; i < count; i++) {
134+
ck_assert_int_eq(wolfBoot_get_failure(i, &rec), 0);
135+
ck_assert_uint_eq(rec.fw_version, (uint32_t)(total - i));
136+
}
137+
}
138+
END_TEST
139+
140+
START_TEST(test_crc_rejection)
141+
{
142+
struct wolfBoot_failure_record rec;
143+
uint8_t *p;
144+
145+
diag_mmap("/tmp/wolfboot-unit-diag-crc.bin");
146+
ck_assert_int_eq(wolfBoot_clear_failures(), 0);
147+
148+
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
149+
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 1);
150+
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
151+
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 2);
152+
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
153+
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 3);
154+
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
155+
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 4);
156+
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
157+
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 5);
158+
ck_assert_int_eq(wolfBoot_get_failure_count(), 5);
159+
160+
/* Corrupt the 3rd record (slot 2) of the active sector. Records are read
161+
* contiguously, so the count truncates at the first invalid slot. */
162+
p = (uint8_t *)(uintptr_t)(DIAG_SECTOR_ADDR(0) + DIAG_HDR_SIZE +
163+
2 * DIAG_RECORD_SIZE);
164+
p[0] ^= 0xFF;
165+
ck_assert_int_eq(wolfBoot_get_failure_count(), 2);
166+
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), 0);
167+
ck_assert_uint_eq(rec.fw_version, 2);
168+
169+
/* Corrupt the sector header: the whole sector is rejected. */
170+
p = (uint8_t *)(uintptr_t)DIAG_SECTOR_ADDR(0);
171+
p[0] ^= 0xFF;
172+
ck_assert_int_eq(wolfBoot_get_failure_count(), 0);
173+
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), -1);
174+
}
175+
END_TEST
176+
177+
START_TEST(test_clear)
178+
{
179+
struct wolfBoot_failure_record rec;
180+
181+
diag_mmap("/tmp/wolfboot-unit-diag-clear.bin");
182+
ck_assert_int_eq(wolfBoot_clear_failures(), 0);
183+
184+
record_one(WOLFBOOT_FAILURE_PHASE_BOOT,
185+
WOLFBOOT_FAILURE_CAUSE_HEADER, PART_BOOT, 1);
186+
record_one(WOLFBOOT_FAILURE_PHASE_BOOT,
187+
WOLFBOOT_FAILURE_CAUSE_HASH, PART_BOOT, 2);
188+
ck_assert_int_eq(wolfBoot_get_failure_count(), 2);
189+
190+
ck_assert_int_eq(wolfBoot_clear_failures(), 0);
191+
ck_assert_int_eq(wolfBoot_get_failure_count(), 0);
192+
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), -1);
193+
194+
/* recording works again after a clear; the sequence restarts */
195+
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
196+
WOLFBOOT_FAILURE_CAUSE_SIGNATURE, PART_UPDATE, 7);
197+
ck_assert_int_eq(wolfBoot_get_failure_count(), 1);
198+
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), 0);
199+
ck_assert_uint_eq(rec.fw_version, 7);
200+
ck_assert_uint_eq(rec.seq, 1);
201+
}
202+
END_TEST
203+
204+
Suite *wolfboot_suite(void)
205+
{
206+
Suite *s = suite_create("wolfboot-diagnostics");
207+
TCase *diag = tcase_create("Failure diagnostics");
208+
tcase_add_test(diag, test_record_and_read_newest_first);
209+
tcase_add_test(diag, test_ring_wrap_and_ordering);
210+
tcase_add_test(diag, test_crc_rejection);
211+
tcase_add_test(diag, test_clear);
212+
suite_add_tcase(s, diag);
213+
return s;
214+
}
215+
216+
int main(int argc, char *argv[])
217+
{
218+
int fails;
219+
argv0 = strdup(argv[0]);
220+
Suite *s = wolfboot_suite();
221+
SRunner *sr = srunner_create(s);
222+
srunner_run_all(sr, CK_NORMAL);
223+
fails = srunner_ntests_failed(sr);
224+
srunner_free(sr);
225+
return fails;
226+
}

tools/unit-tests/unit-mock-flash.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ int hal_flash_write(haladdr_t address, const uint8_t *data, int len)
7474
a[i] = data[i];
7575
}
7676
}
77+
#endif
78+
#ifdef WOLFBOOT_DIAGNOSTICS_ADDRESS
79+
if ((address >= (haladdr_t)WOLFBOOT_DIAGNOSTICS_ADDRESS) &&
80+
(address < (haladdr_t)WOLFBOOT_DIAGNOSTICS_ADDRESS +
81+
WOLFBOOT_DIAGNOSTICS_SECTORS * WOLFBOOT_SECTOR_SIZE)) {
82+
for (i = 0; i < len; i++) {
83+
a[i] = data[i];
84+
}
85+
}
7786
#endif
7887
return 0;
7988
}
@@ -107,6 +116,12 @@ int hal_flash_erase(haladdr_t address, int len)
107116
printf("Erasing vault from %p : %p bytes\n", address, len);
108117
erased_vault++;
109118
memset((void *)(uintptr_t)address, 0xFF, len);
119+
#endif
120+
#ifdef WOLFBOOT_DIAGNOSTICS_ADDRESS
121+
} else if ((address >= (haladdr_t)WOLFBOOT_DIAGNOSTICS_ADDRESS) &&
122+
(address < (haladdr_t)WOLFBOOT_DIAGNOSTICS_ADDRESS +
123+
WOLFBOOT_DIAGNOSTICS_SECTORS * WOLFBOOT_SECTOR_SIZE)) {
124+
memset((void *)(uintptr_t)address, 0xFF, len);
110125
#endif
111126
} else {
112127
fail("Invalid address\n");

0 commit comments

Comments
 (0)