diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index fa8d5580..d5fba001 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -32,6 +32,23 @@ jobs: repository: wolfssl/wolfssl path: wolfssl + # Verify the committed wh_test_list.c matches what gen_test_list.py + # would produce for the current set of WH_TEST_* annotations. If a + # contributor adds/renames/removes a test without regenerating, this + # fails with a diff and a regeneration hint. + - name: Verify wh_test_list.c is up to date + run: | + cd test-refactor + python3 gen_test_list.py --output wh_test_list.c misc server client-server + if ! git diff --exit-code wh_test_list.c; then + echo "::error::wh_test_list.c is stale. Regenerate with:" + echo "::error:: cd test-refactor && python3 gen_test_list.py --output wh_test_list.c misc server client-server" + exit 1 + fi + + - name: Build and test refactor + run: cd test-refactor/posix && make clean && make -j WOLFSSL_DIR=../../wolfssl && make run + # Build and test standard build - name: Build and test run: cd test && make clean && make -j WOLFSSL_DIR=../wolfssl && make run diff --git a/.gitignore b/.gitignore index 3da94679..62f601d6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ tools/static-analysis/reports/ *.gcda *.gcno coverage/ + +# Test driver log (automake-style; produced by `make run` in test-refactor) +test-suite.log diff --git a/test-refactor/README.md b/test-refactor/README.md new file mode 100644 index 00000000..1dd7e7d2 --- /dev/null +++ b/test-refactor/README.md @@ -0,0 +1,297 @@ +# test-refactor + +Prototype of the refactored wolfHSM test infrastructure. + +## Key differences from `test/` + +- **Groups** (`wh_test_groups.h/c`): three portable entry + points (`whTestGroup_Misc` / `whTestGroup_Server` / + `whTestGroup_Client`) that `main()` invokes. Each test + receives the corresponding context pointer + (`whClientContext*`, `whServerContext*`, or `NULL` for + misc), pre-populated by the port. +- **Automatic test registry**: tests are individual functions + tagged with an annotation macro. +- **App-owned init**: the port's `main()` brings up the server + and client once at startup (mirroring real firmware boot) and + hands the live contexts to the group functions. +- **Concurrent server + client during the client group**: the + POSIX port runs server and client on separate threads. The + server thread runs the server group first, then enters a + `HandleRequestMessage` loop; the client thread runs the + client group against that live server. No per-test + sequencing. +- **Separation of port and tests**: the multi-threaded POSIX + port lives alongside the test code but is cleanly separated + from it, serving as the reference implementation for other + platforms/targets. + +## Build and run (POSIX) + +``` +cd posix +make run +make run DEBUG=1 +make run THREADSAFE=1 # enables stress test gate +``` + +The `check` (or `run`) target sends the full test output (including any +verbose prints from individual tests) to `test-suite.log` in +the POSIX build dir, and shows only the per-test result lines +and the final tally on the terminal -- mirroring the +`make check` convention in autotools-based wolfssl. Look at +`test-suite.log` for the verbose output on failure. The log +file is gitignored. + +## Writing a test + +Tag the function with one of `WH_TEST_MISC`, `WH_TEST_SERVER`, +or `WH_TEST_CLIENT`, and declare it with the context type that +matches its group -- no internal cast required: + +```c +#include "wh_test_list.h" + +WH_TEST_SERVER int whTest_CertVerify(whServerContext* server) +{ + /* ...use `server` directly... */ + return 0; +} + +WH_TEST_CLIENT int whTest_Echo(whClientContext* client) { ... } + +WH_TEST_MISC int whTest_FlashWriteLock(void* ctx) +{ + (void)ctx; /* misc group has no context; ctx is NULL */ + /* ... */ + return 0; +} +``` + +Why this works: the generator emits the weak skip stub as +`int name(void* ctx)` in `wh_test_list.c`, while the real +definition lives in a separate TU with its typed signature. +The compiler never sees both together, so there's no +conflicting-prototype error. The linker matches symbols by +name (strong overrides weak); the runner calls through +`int (*)(void*)`, and pointer arguments use the same ABI +regardless of pointee type, so the typed function sees the +context it expects. + +Return `0` on success, any other non-zero value on failure. +Don't return `WH_TEST_SKIPPED` yourself -- that sentinel is +reserved for the weak stub that replaces a test whose feature +gate is off. + +Name the function `whTest_CamelCase` to match the convention +in `test/` and the other tests in this directory. + +After adding, renaming, or removing a test, regenerate the +registry: + +``` +cd test-refactor +python3 gen_test_list.py --output wh_test_list.c \ + misc server client-server +``` + +The POSIX Makefile regenerates automatically on build. CI +verifies the committed `wh_test_list.c` is in sync with the +annotations; see the `Verify wh_test_list.c is up to date` +step in `.github/workflows/build-and-test.yml`. + +### Generated code and gen_test_list.py + +The framework supports bottom-up test declaration: each test +associates itself with a group (client, server, or misc) via +its own annotation tag (e.g. `WH_TEST_CLIENT`), without +changing any upper layer. No header, no registration call, +no central switch statement -- the tag alone is enough. + +To support this across toolchains without requiring link-time +tricks (ELF sections, `__start_`/`__stop_` symbols, etc.) +that aren't universally available, a small pre-processing +script scans the test sources for the tags and emits +`wh_test_list.c`. That file defines three registry arrays -- +`whTestsMisc[]`, `whTestsServer[]`, `whTestsClient[]` -- plus +their counts, which the group runners in `wh_test_groups.c` +iterate directly. + +**The generated file is checked in.** Two reasons: first, +some embedded toolchains don't have a convenient Python +runtime on the build host, and baking the generator into +the build would either force an extra dependency or a +hand-maintained fallback; committing the output sidesteps +both. Second, a committed registry makes test additions +reviewable in the diff -- a PR that adds a new test also +shows the corresponding registry change, which catches +forgotten regenerations and makes the CI "is this file in +sync" check pass by construction once the author runs the +script. + +**Conditional compilation (`#ifdef`s) is handled by +link-time override of weak symbols, not by mirroring guards +in the registry.** Every discovered test gets an +unconditional entry in the registry and a weak stub that +returns `WH_TEST_SKIPPED`. When a test's feature gate is on, +the test source compiles to a strong symbol that overrides +the stub at link time and the real test runs. When the gate +is off, the test source compiles to an empty translation +unit, the stub wins, and the test surfaces as "test skipped" +at runtime. The generator never looks at `#if` context; all +the gating lives in the test's own source file, exactly once. + +**Re-run the script whenever the set of tagged tests +changes:** adding a test, removing a test, or renaming a +tagged function. Changing a test's body or tweaking its +feature gate does *not* require regeneration -- only the +set of function names and their groups is captured in +`wh_test_list.c`. If the committed file and the annotations +drift, CI will flag it. + +## Feature gating + +Wrap the body of the test source in the feature's `#if` as +usual: + +```c +#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) \ + && !defined(WOLFHSM_CFG_NO_CRYPTO) + +WH_TEST_SERVER int whTest_CertVerify(void* ctx) { ... } + +#endif +``` + +The generator ignores preprocessor context and always emits +an entry and a weak stub. If the gate is off, the real test's +TU is empty, the stub's `WH_TEST_SKIPPED` propagates at +runtime, and the test shows up as `test skipped` in the +output rather than silently vanishing. + +## Tests implemented so far + +| Test | Group | Description | +|----------------------------------|----------------|---------------------------------------------------------------------------------| +| `whTest_FlashWriteLock` | misc | Flash write-lock behavior | +| `whTest_FlashEraseProgramVerify` | misc | Flash erase, program, verify, blank-check | +| `whTest_FlashUnitOps` | misc | Flash unit ops | +| `whTest_NvmAddOverwriteDestroy` | misc | NVM add / overwrite / destroy / reclaim | +| `whTest_CertVerify` | server | Server-side cert add / verify / chain / erase | +| `whTest_Echo` | client | Echo round-trip | +| `whTest_ServerInfo` | client | Server info query | +| `whTest_CryptoSha256` | client | SHA256 via server | +| `whTest_CryptoAes` | client | AES-CBC via server | +| `whTest_CryptoEcc256` | client | ECC256 via server | +| ThreadSafe Stress (POSIX only) | -- | Phased multi-thread contention, invoked directly by the POSIX port (not in the registry) | + +## Remaining tests to port + +| Test | Group | Description | +|-------------------|----------------|-----------------------------------------------------------------| +| Comm | client-server | Transport layer (mem, TCP, SHM) | +| Crypto (rest) | client-server | RSA, CMAC, curve25519, ed25519, etc. | +| Crypto Affinity | client-server | Device ID operation routing | +| SHE | client-server | SHE key load, crypto, secure boot | +| Keywrap | client-server | Key wrap/unwrap operations | +| Log | misc | Logging frontend, ringbuf, POSIX file backends | +| Lock | misc | Lock primitives with POSIX backend | +| DMA | misc | DMA address translation and allow-list | +| Server Img Mgr | server | Image manager verify/install/erase | +| Timeout | client-server | POSIX timeout enforcement | +| wolfCrypt Test | client-server | wolfCrypt test suite via wolfHSM transport | +| MultiClient | client-server | 2 CS pairs, shared NVM, global/local key isolation | + +## Platforms requiring update + +Each platform with test infrastructure needs its own +`wh_test_helpers_server_.c`, +`wh_test_helpers_client_.c`, and +`wh_test_main_.c` (see "Porting" below). + +| Platform | Vendor | Test files | +|-------------|-----------|----------------------------------------------------------------| +| POSIX | wolfSSL | `test-refactor/posix/` (done) | +| Bernina | STMicro | `bernina-server/src/bh_test.c` | +| SR6 | STMicro | (no test files found) | +| TC3xx | Infineon | `port/client/wolfhsm_tests.c`, `port/server/ccb_tests.c` | +| RH850 F1KM | Renesas | `rh850_test2_1/`, `rh850_test2_2/` | +| PIC32CZ | Microchip | `czhsm-client/tests/`, `czhsm-server/` | +| TDA4VH | TI | (no test files found) | +| New Eagle | Customer | (no test files found) | + +## File layout + +``` +Portable (ships in wolfHSM): + wh_test_list.h - annotation markers, whTestCase, + WH_TEST_WEAK, WH_TEST_DECL, + WH_TEST_SKIPPED, extern decls for + whTestsMisc/Server/Client[] + wh_test_list.c - GENERATED registry (three per-group + arrays + weak skip stubs). Run + gen_test_list.py to rebuild. + wh_test_groups.h/c - Misc/Server/Client entry points, + runner, pass/skip/fail counters, + whTestGroup_Summary() + gen_test_list.py - registry generator + server/wh_test_*.c - server-only test modules + client-server/wh_test_*.c - client-server test modules + misc/wh_test_*.c - standalone test modules + +Platform-specific (one directory per platform, e.g. posix/): + /wh_test_helpers_server_.h/c - server bringup + /wh_test_helpers_client_.h/c - client bringup + /wh_test_main_.c - init, group + dispatch, reset + hooks, summary + /Makefile - build rules +``` + +## Toolchain support + +The weak-symbol attribute in `WH_TEST_WEAK(name)` is mapped +per toolchain in `wh_test_list.h`: + +| Toolchain(s) | Spelling | +|---------------------------------------|----------------------------------| +| GCC, Clang, armclang, armcc, TI | `__attribute__((weak))` | +| IAR | `__weak` | +| Renesas CC-RH / CC-RL / CC-RX | `_Pragma("weak ")` | + +Other toolchains trigger a `#error` -- add a case rather than +falling back silently, since a no-op expansion would make the +weak stub strong and either cause a multiple-definition link +error or (worse) let the stub win over the real test. + +## Porting to other platforms + +1. Implement the init helpers for the side(s) the target + needs. These stand in for what your firmware's normal + boot flow already does -- if it's simpler to call your + existing init code directly from main, that works too: + - `whTestHelperPosix_Server_Init/Cleanup` (reference): + bring up flash/NVM/crypto/transport/server. + - `whTestHelperPosix_Client_Init/Cleanup` (reference): + bring up client comm + handshake. On single-process + targets, the server runs in its own thread and pumps + `HandleRequestMessage` itself. +2. Provide a `main()` that: + - Calls `whTestGroup_Misc()` for standalone tests. + - Brings up the server/client contexts once. + - Calls `whTestGroup_Server(&server)` and/or + `whTestGroup_Client(&client)` with the live handles. + - Tears the contexts down. + - Calls `whTestGroup_Summary()` at the end to print the + final wolfCrypt-style tally and return a non-zero exit + code on failure. + - Implements `whTestGroup_ResetServer` and/or + `whTestGroup_ResetClient` -- called between tests to + scrub persistent state. +3. Add the portable `.c` files and your port files to your + build system. Either regenerate `wh_test_list.c` from the + build (`python3 gen_test_list.py ...`) or rely on the + committed copy. + +See `wh_test_main_posix.c` and the two `*_posix.c` helpers as +a reference implementation. diff --git a/test-refactor/client-server/wh_test_crypto.c b/test-refactor/client-server/wh_test_crypto.c new file mode 100644 index 00000000..82565958 --- /dev/null +++ b/test-refactor/client-server/wh_test_crypto.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_crypto.c + * + * Basic crypto test suite. Minimal SHA256 and AES-CBC + * round-trips routed through the server via WH_DEV_ID. + */ + +#include "wolfhsm/wh_settings.h" + +#if !defined(WOLFHSM_CFG_NO_CRYPTO) + +#include +#include + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/aes.h" +#include "wolfssl/wolfcrypt/ecc.h" +#include "wolfssl/wolfcrypt/random.h" +#include "wolfssl/wolfcrypt/sha256.h" + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_client.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#ifndef NO_SHA256 +WH_TEST_CLIENT int whTest_CryptoSha256(whClientContext* ctx) +{ + int devId = WH_DEV_ID; + int ret = WH_ERROR_OK; + wc_Sha256 sha256[1]; + uint8_t out[WC_SHA256_DIGEST_SIZE]; + /* Vector exactly one block size in length */ + const char inOne[] = + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + const uint8_t expectedOutOne[WC_SHA256_DIGEST_SIZE] = { + 0xff, 0xe0, 0x54, 0xfe, 0x7a, 0xe0, 0xcb, 0x6d, 0xc6, 0x5c, 0x3a, + 0xf9, 0xb6, 0x1d, 0x52, 0x09, 0xf4, 0x39, 0x85, 0x1d, 0xb4, 0x3d, + 0x0b, 0xa5, 0x99, 0x73, 0x37, 0xdf, 0x15, 0x46, 0x68, 0xeb}; + + (void)ctx; + + /* Initialize SHA256 structure */ + ret = wc_InitSha256_ex(sha256, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitSha256 on devId 0x%X: %d\n", devId, + ret); + } else { + /* Single-block update should trigger a server transaction */ + ret = wc_Sha256Update(sha256, + (const byte*)inOne, + WC_SHA256_BLOCK_SIZE); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_Sha256Update %d\n", ret); + } else { + /* Finalize should trigger a server transaction with empty buffer */ + ret = wc_Sha256Final(sha256, out); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_Sha256Final %d\n", ret); + } else { + /* Compare the computed hash with the expected output */ + if (memcmp(out, expectedOutOne, WC_SHA256_DIGEST_SIZE) != 0) { + WH_ERROR_PRINT("SHA256 hash does not match expected.\n"); + ret = -1; + } + } + } + (void)wc_Sha256Free(sha256); + } + if (ret == 0) { + WH_TEST_PRINT("SHA256 DEVID=0x%X SUCCESS\n", devId); + } + return ret; +} +#endif /* !NO_SHA256 */ + + +#if !defined(NO_AES) && defined(HAVE_AES_CBC) +WH_TEST_CLIENT int whTest_CryptoAes(whClientContext* ctx) +{ + int devId = WH_DEV_ID; + int ret = 0; + Aes aes[1]; + uint8_t cipher[AES_BLOCK_SIZE] = {0}; + uint8_t plainOut[AES_BLOCK_SIZE] = {0}; + /* NIST SP 800-38B test vectors (same k128 / m used by the CMAC test + * in test/wh_test_crypto.c). Using a fixed vector keeps this suite + * self-contained, no RNG needed. */ + const uint8_t key[] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c}; + const uint8_t iv[AES_BLOCK_SIZE] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; + const uint8_t plainIn[AES_BLOCK_SIZE] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a}; + + (void)ctx; + + /* test aes CBC with client side key */ + ret = wc_AesInit(aes, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_AesInit %d\n", ret); + } else { + ret = wc_AesSetKey(aes, key, sizeof(key), iv, AES_ENCRYPTION); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_AesSetKey %d\n", ret); + } else { + ret = wc_AesCbcEncrypt(aes, cipher, plainIn, + sizeof(plainIn)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_AesCbcEncrypt %d\n", ret); + } else { + ret = wc_AesSetKey(aes, key, sizeof(key), iv, + AES_DECRYPTION); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_AesSetKey %d\n", ret); + } else { + ret = wc_AesCbcDecrypt(aes, plainOut, cipher, + sizeof(cipher)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_AesCbcDecrypt %d\n", + ret); + } else { + if (memcmp(plainIn, plainOut, sizeof(plainIn)) != + 0) { + WH_ERROR_PRINT("Failed to match AES-CBC\n"); + ret = -1; + } + } + } + } + } + (void)wc_AesFree(aes); + } + if (ret == 0) { + WH_TEST_PRINT("AES CBC DEVID=0x%X SUCCESS\n", devId); + } + return ret; +} +#endif /* !NO_AES && HAVE_AES_CBC */ + + +#if defined(HAVE_ECC) && defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY) +WH_TEST_CLIENT int whTest_CryptoEcc256(whClientContext* ctx) +{ + int devId = WH_DEV_ID; + int ret = 0; + WC_RNG rng[1]; + ecc_key key[1]; + uint8_t hash[32] = {0}; + uint8_t sig[ECC_MAX_SIG_SIZE] = {0}; + word32 sigLen = sizeof(sig); + int verify = 0; + + (void)ctx; + + /* Minimal P-256 sign/verify round-trip routed through the server via + * WH_DEV_ID. Key size 32 selects SECP256R1 as wolfCrypt's default. */ + ret = wc_InitRng_ex(rng, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); + } else { + ret = wc_ecc_init_ex(key, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_ecc_init_ex %d\n", ret); + } else { + ret = wc_ecc_make_key(rng, 32, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_ecc_make_key %d\n", ret); + } else { + ret = wc_ecc_sign_hash(hash, sizeof(hash), + sig, &sigLen, rng, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_ecc_sign_hash %d\n", + ret); + } else { + ret = wc_ecc_verify_hash(sig, sigLen, + hash, sizeof(hash), &verify, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_ecc_verify_hash %d\n", + ret); + } else if (verify != 1) { + WH_ERROR_PRINT("ECC256 verify mismatch\n"); + ret = -1; + } + } + } + (void)wc_ecc_free(key); + } + (void)wc_FreeRng(rng); + } + if (ret == 0) { + WH_TEST_PRINT("ECC256 DEVID=0x%X SUCCESS\n", devId); + } + return ret; +} +#endif /* HAVE_ECC && HAVE_ECC_SIGN && HAVE_ECC_VERIFY */ + +#endif /* !WOLFHSM_CFG_NO_CRYPTO */ diff --git a/test-refactor/client-server/wh_test_echo.c b/test-refactor/client-server/wh_test_echo.c new file mode 100644 index 00000000..dd0845c0 --- /dev/null +++ b/test-refactor/client-server/wh_test_echo.c @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_echo.c + * + * Echo round-trip test. Uses blocking client APIs; the port + * is responsible for pumping the server in parallel. + */ + +#include +#include + +#include "wolfhsm/wh_settings.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_client.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#define REPEAT_COUNT 10 + +/* + * Echo a message to the server and verify the response + * matches. Repeats several times with different payloads. + */ +WH_TEST_CLIENT int whTest_Echo(whClientContext* ctx) +{ + char send_buf[WOLFHSM_CFG_COMM_DATA_LEN]; + char recv_buf[WOLFHSM_CFG_COMM_DATA_LEN]; + uint16_t send_len = 0; + uint16_t recv_len = 0; + int i; + + for (i = 0; i < REPEAT_COUNT; i++) { + send_len = snprintf(send_buf, sizeof(send_buf), + "Echo test %d", i); + + recv_len = 0; + memset(recv_buf, 0, sizeof(recv_buf)); + + WH_TEST_RETURN_ON_FAIL( + wh_Client_Echo(ctx, + send_len, send_buf, + &recv_len, recv_buf)); + + WH_TEST_ASSERT_RETURN(recv_len == send_len); + WH_TEST_ASSERT_RETURN( + memcmp(recv_buf, send_buf, recv_len) == 0); + } + + return 0; +} diff --git a/test-refactor/client-server/wh_test_server_info.c b/test-refactor/client-server/wh_test_server_info.c new file mode 100644 index 00000000..07b505f3 --- /dev/null +++ b/test-refactor/client-server/wh_test_server_info.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_server_info.c + * + * Server info query test. Uses blocking client APIs; the port + * is responsible for pumping the server in parallel. + */ + +#include + +#include "wolfhsm/wh_settings.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_message_comm.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + + +/* + * Query server info and verify the response contains + * valid data. + */ +WH_TEST_CLIENT int whTest_ServerInfo(whClientContext* ctx) +{ + uint8_t version[WH_INFO_VERSION_LEN + 1] = {0}; + uint8_t build[WH_INFO_VERSION_LEN + 1] = {0}; + uint32_t comm_data_len = 0; + uint32_t nvm_object_count = 0; + uint32_t keycache_count = 0; + uint32_t keycache_bufsize = 0; + uint32_t keycache_bigcount = 0; + uint32_t keycache_bigbufsz = 0; + uint32_t customcb_count = 0; + uint32_t dmaaddr_count = 0; + uint32_t debug_state = 0; + uint32_t boot_state = 0; + uint32_t lifecycle_state = 0; + uint32_t nvm_state = 0; + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CommInfo(ctx, + version, build, + &comm_data_len, + &nvm_object_count, + &keycache_count, + &keycache_bufsize, + &keycache_bigcount, + &keycache_bigbufsz, + &customcb_count, + &dmaaddr_count, + &debug_state, + &boot_state, + &lifecycle_state, + &nvm_state)); + + /* Comm data length must be nonzero */ + WH_TEST_ASSERT_RETURN(comm_data_len > 0); + + return 0; +} diff --git a/test-refactor/gen_test_list.py b/test-refactor/gen_test_list.py new file mode 100755 index 00000000..38cbfb3d --- /dev/null +++ b/test-refactor/gen_test_list.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfHSM. +# +# wolfHSM is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +""" +Generate wh_test_list.c from WH_TEST_{MISC,CLIENT,SERVER} annotations. + +For every test found it emits: + - A `WH_TEST_DECL(name);` line, which expands (see + wh_test_list.h) to a forward prototype plus a weak skip + implementation returning WH_TEST_SKIPPED. When the real + test's feature gate is on, its strong definition overrides + this stub at link time; otherwise the stub wins and the + test surfaces as SKIPPED at runtime. + - A row in whTests[]. + +Preprocessor conditionals around the annotation are deliberately +ignored: the link-time weak override handles the gating. All the +script needs is the set of annotated function names and the group +each belongs to. + +Usage: + gen_test_list.py --output ... +""" + +import argparse +import os +import re +import sys + +ANNOTATION_RE = re.compile( + r'^\s*WH_TEST_(MISC|CLIENT|SERVER)\s+int\s+(\w+)\s*\(', + re.MULTILINE, +) + +# Strip // line comments and /* ... */ block comments so an +# annotation appearing in commented-out code doesn't register a +# phantom test. Strings aren't stripped; an annotation inside a +# string literal would be an exotic footgun we're not solving. +LINE_COMMENT_RE = re.compile(r'//[^\n]*') +BLOCK_COMMENT_RE = re.compile(r'/\*.*?\*/', re.DOTALL) + +# Each group emits its own whTests[] registry in the +# generated file. Order here fixes the section ordering in +# wh_test_list.c. +GROUPS = [ + ('MISC', 'whTestsMisc', 'whTestsMiscCount'), + ('SERVER', 'whTestsServer', 'whTestsServerCount'), + ('CLIENT', 'whTestsClient', 'whTestsClientCount'), +] + + +def strip_comments(src): + src = BLOCK_COMMENT_RE.sub('', src) + src = LINE_COMMENT_RE.sub('', src) + return src + + +def scan_file(path): + """Return [(group, fn_name)] from one source file.""" + with open(path, 'r') as f: + src = strip_comments(f.read()) + return [(m.group(1), m.group(2)) for m in ANNOTATION_RE.finditer(src)] + + +def discover(dirs): + """Walk dirs, scan .c files, return sorted unique tests with stable group-major order.""" + found = [] + seen_names = {} + for d in dirs: + for root, _, files in os.walk(d): + for name in sorted(files): + if not name.endswith('.c'): + continue + path = os.path.join(root, name) + for group, fn in scan_file(path): + if fn in seen_names: + prev = seen_names[fn] + sys.stderr.write( + "gen_test_list.py: duplicate test name '{}' " + "(in {} and {})\n".format(fn, prev, path)) + sys.exit(1) + seen_names[fn] = path + found.append((group, fn, path)) + # Stable order: group (misc/server/client), then discovery order. + group_order = {g[0]: i for i, g in enumerate(GROUPS)} + found.sort(key=lambda t: (group_order[t[0]], t[2], t[1])) + return found + + +HEADER = """\ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ + +/* + ************************************************************************* + * wh_test_list.c -- GENERATED BY gen_test_list.py. DO NOT EDIT. + ************************************************************************* + */ + +/* + * Registry of every test function found via WH_TEST_{MISC, + * CLIENT,SERVER} annotations. Each discovered test gets a weak + * stub that returns WH_TEST_SKIPPED; the real test, when + * compiled in, provides a strong symbol that the linker picks + * instead. + */ + +#include "wh_test_list.h" + +""" + + +def render(tests): + out = [HEADER] + + # Declarations and weak skip implementations -- one per test. + # WH_TEST_DECL expands to a forward prototype plus a weak + # stub returning WH_TEST_SKIPPED; the real test's strong + # definition overrides it at link time when the feature gate + # is on. + out.append( + '/* Test declarations and weak skip implementations. ' + '*/\n') + for group, fn, _ in tests: + out.append('WH_TEST_DECL({name});\n'.format(name=fn)) + out.append('\n') + + # Per-group registration tables. + by_group = {g[0]: [] for g in GROUPS} + for group, fn, _ in tests: + by_group[group].append(fn) + + for i, (group, array, count) in enumerate(GROUPS): + fns = by_group[group] + if i > 0: + out.append('\n') + if not fns: + # C forbids zero-length arrays at file scope; emit a + # single NULL placeholder and a hardcoded count of 0 + # so the runner treats this group as empty. + out.append( + 'const whTestCase {array}[] = ' + '{{ {{ NULL, NULL }} }};\n'.format(array=array)) + out.append('const size_t {count} = 0;\n'.format(count=count)) + continue + out.append('const whTestCase {array}[] = {{\n'.format(array=array)) + for fn in fns: + out.append( + ' {{ "{name}", {name} }},\n'.format(name=fn)) + out.append('};\n') + out.append( + 'const size_t {count} = ' + 'sizeof({array}) / sizeof({array}[0]);\n'.format( + array=array, count=count)) + + return ''.join(out) + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('--output', required=True, + help='path to wh_test_list.c to (re)generate') + ap.add_argument('sources', nargs='+', + help='test source directories to scan') + args = ap.parse_args() + + tests = discover(args.sources) + if not tests: + sys.stderr.write('gen_test_list.py: no WH_TEST_* annotations found\n') + sys.exit(1) + + new_content = render(tests) + + # Only rewrite when content actually changes, so make doesn't + # rebuild the world on every invocation. + try: + with open(args.output, 'r') as f: + if f.read() == new_content: + return + except IOError: + pass + + with open(args.output, 'w') as f: + f.write(new_content) + + +if __name__ == '__main__': + main() diff --git a/test-refactor/misc/wh_test_flash_ramsim.c b/test-refactor/misc/wh_test_flash_ramsim.c new file mode 100644 index 00000000..fe184055 --- /dev/null +++ b/test-refactor/misc/wh_test_flash_ramsim.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_flash_ramsim.c + * + * Flash RamSim test suite. Exercises the RAM-based flash simulator + * through init, write-lock, erase, program, verify, and blank-check + * operations. No setup/cleanup needed -- the test manages its own + * flash context internally. + */ + +#include +#include +#include + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_flash_ramsim.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#define TEST_FLASH_SIZE (1024 * 1024) +#define TEST_SECTOR_SIZE (4096) +#define TEST_PAGE_SIZE (256) + + +static void _fillTestData(uint8_t* buf, uint32_t size, + uint32_t base) +{ + uint32_t i; + for (i = 0; i < size; i++) { + buf[i] = (uint8_t)(base + i); + } +} + + +/* + * Verify that write-lock prevents erase and program, and that + * unlock restores access. + */ +WH_TEST_MISC int whTest_FlashWriteLock(void* ctx) +{ + int ret; + whFlashRamsimCtx fctx; + static uint8_t memory[TEST_FLASH_SIZE]; + uint8_t page[TEST_PAGE_SIZE] = {0}; + whFlashRamsimCfg cfg = { + .size = TEST_FLASH_SIZE, + .sectorSize = TEST_SECTOR_SIZE, + .pageSize = TEST_PAGE_SIZE, + .erasedByte = 0xFF, + .memory = memory, + }; + + (void)ctx; + + WH_TEST_RETURN_ON_FAIL(whFlashRamsim_Init(&fctx, &cfg)); + + if (fctx.writeLocked == 1) { + WH_ERROR_PRINT("RamSim locked on init\n"); + whFlashRamsim_Cleanup(&fctx); + return WH_TEST_FAIL; + } + + /* Lock sector 0 */ + ret = whFlashRamsim_WriteLock(&fctx, 0, cfg.sectorSize); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("WriteLock failed: ret=%d\n", ret); + whFlashRamsim_Cleanup(&fctx); + return ret; + } + + /* Erase and program must fail while locked */ + ret = whFlashRamsim_Erase(&fctx, 0, cfg.sectorSize); + if (ret != WH_ERROR_LOCKED) { + WH_ERROR_PRINT("Erase not blocked by lock: ret=%d\n", + ret); + whFlashRamsim_Cleanup(&fctx); + return WH_TEST_FAIL; + } + + ret = whFlashRamsim_Program(&fctx, 0, TEST_PAGE_SIZE, page); + if (ret != WH_ERROR_LOCKED) { + WH_ERROR_PRINT("Program not blocked by lock: ret=%d\n", + ret); + whFlashRamsim_Cleanup(&fctx); + return WH_TEST_FAIL; + } + + /* Unlock and verify access is restored */ + ret = whFlashRamsim_WriteUnlock(&fctx, 0, cfg.sectorSize); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("WriteUnlock failed: ret=%d\n", ret); + whFlashRamsim_Cleanup(&fctx); + return ret; + } + + whFlashRamsim_Cleanup(&fctx); + return 0; +} + + +/* + * Erase every sector, program every page with known data, + * verify, then erase again and blank-check. + */ +WH_TEST_MISC int whTest_FlashEraseProgramVerify(void* ctx) +{ + int ret; + whFlashRamsimCtx fctx; + static uint8_t memory[TEST_FLASH_SIZE]; + uint8_t testData[TEST_PAGE_SIZE]; + uint32_t sector; + whFlashRamsimCfg cfg = { + .size = TEST_FLASH_SIZE, + .sectorSize = TEST_SECTOR_SIZE, + .pageSize = TEST_PAGE_SIZE, + .erasedByte = 0xFF, + .memory = memory, + }; + + (void)ctx; + + WH_TEST_RETURN_ON_FAIL(whFlashRamsim_Init(&fctx, &cfg)); + + for (sector = 0; sector < cfg.size / cfg.sectorSize; + sector++) { + uint32_t sOff = sector * cfg.sectorSize; + uint32_t page; + + /* Erase sector */ + ret = whFlashRamsim_Erase(&fctx, sOff, + cfg.sectorSize); + if (ret != 0) { + WH_ERROR_PRINT("Erase sector %u failed: %d\n", + (unsigned)sector, ret); + whFlashRamsim_Cleanup(&fctx); + return ret; + } + + /* Blank check after erase */ + ret = whFlashRamsim_BlankCheck(&fctx, sOff, + cfg.sectorSize); + if (ret != 0) { + WH_ERROR_PRINT("BlankCheck sector %u failed: %d\n", + (unsigned)sector, ret); + whFlashRamsim_Cleanup(&fctx); + return ret; + } + + /* Program and verify each page */ + for (page = 0; + page < cfg.sectorSize / cfg.pageSize; + page++) { + uint32_t pOff = sOff + page * cfg.pageSize; + + _fillTestData(testData, cfg.pageSize, page); + + ret = whFlashRamsim_Program(&fctx, pOff, + cfg.pageSize, testData); + if (ret != 0) { + WH_ERROR_PRINT("Program page %u sector %u" + " failed: %d\n", + (unsigned)page, (unsigned)sector, ret); + whFlashRamsim_Cleanup(&fctx); + return ret; + } + + ret = whFlashRamsim_Verify(&fctx, pOff, + cfg.pageSize, testData); + if (ret != 0) { + WH_ERROR_PRINT("Verify page %u sector %u" + " failed: %d\n", + (unsigned)page, (unsigned)sector, ret); + whFlashRamsim_Cleanup(&fctx); + return ret; + } + } + + /* Sector should no longer be blank */ + ret = whFlashRamsim_BlankCheck(&fctx, sOff, + cfg.sectorSize); + if (ret != WH_ERROR_NOTBLANK) { + WH_ERROR_PRINT("Sector %u blank after program:" + " %d\n", (unsigned)sector, ret); + whFlashRamsim_Cleanup(&fctx); + return WH_TEST_FAIL; + } + + /* Erase and confirm blank */ + ret = whFlashRamsim_Erase(&fctx, sOff, + cfg.sectorSize); + if (ret != 0) { + WH_ERROR_PRINT("Re-erase sector %u failed: %d\n", + (unsigned)sector, ret); + whFlashRamsim_Cleanup(&fctx); + return ret; + } + + ret = whFlashRamsim_BlankCheck(&fctx, sOff, + cfg.sectorSize); + if (ret != 0) { + WH_ERROR_PRINT("Sector %u not blank after" + " re-erase: %d\n", (unsigned)sector, ret); + whFlashRamsim_Cleanup(&fctx); + return ret; + } + } + + whFlashRamsim_Cleanup(&fctx); + return 0; +} diff --git a/test-refactor/misc/wh_test_nvm_flash.c b/test-refactor/misc/wh_test_nvm_flash.c new file mode 100644 index 00000000..1e5e9eb8 --- /dev/null +++ b/test-refactor/misc/wh_test_nvm_flash.c @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_nvm_flash.c + * + * NVM flash test suite. The fixture (flash + NVM stack) is + * owned by the caller; these tests just consume it. Exercises + * flash unit ops and NVM add/read/overwrite/destroy/reclaim + * through the callback interface. + */ + +#include +#include + +#include "wolfhsm/wh_settings.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_flash.h" +#include "wolfhsm/wh_flash_ramsim.h" +#include "wolfhsm/wh_flash_unit.h" +#include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_nvm_flash.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#define NVM_FLASH_SIZE (1024 * 1024) +#define NVM_FLASH_SECTOR_SZ (4096) +#define NVM_FLASH_PAGE_SZ (8) + + +/* + * Module-private fixture. A single file-static instance holds + * all ramsim/NVM state the tests poke at; _setup populates it, + * _cleanup is a placeholder for symmetry. + */ +typedef struct { + uint8_t memory[NVM_FLASH_SIZE]; + whFlashRamsimCtx flashCtx; + whFlashRamsimCfg flashCfg; + whFlashCb flashCb; + + whNvmFlashContext nvmFlashCtx; + whNvmFlashConfig nvmFlashCfg; + whNvmCb nvmCb; +} whTestNvmFlashCtx; + +static whTestNvmFlashCtx _ctx; + + +/* + * Populate the module-private fixture. Called from the top + * of each test so every test starts against a fresh state. + */ +static void _setup(void) +{ + whTestNvmFlashCtx* c = &_ctx; + const whFlashCb initFlashCb[1] = {WH_FLASH_RAMSIM_CB}; + whNvmCb initNvmCb[1] = {WH_NVM_FLASH_CB}; + + memset(c, 0, sizeof(*c)); + + c->flashCb = initFlashCb[0]; + c->flashCfg.size = NVM_FLASH_SIZE; + c->flashCfg.sectorSize = NVM_FLASH_SECTOR_SZ; + c->flashCfg.pageSize = NVM_FLASH_PAGE_SZ; + c->flashCfg.erasedByte = 0; + c->flashCfg.memory = c->memory; + + c->nvmFlashCfg.cb = &c->flashCb; + c->nvmFlashCfg.context = &c->flashCtx; + c->nvmFlashCfg.config = &c->flashCfg; + + c->nvmCb = initNvmCb[0]; +} + + +/* ---- Flash unit operations ---- */ + +/* + * Exercises flash unit program/read/erase/blank-check + * and byte-level read/write including unaligned access. + */ +WH_TEST_MISC int whTest_FlashUnitOps(void* ctx) +{ + whTestNvmFlashCtx* c = &_ctx; + uint8_t write_bytes[8] = { + 0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87 + }; + uint8_t read_bytes[8] = {0}; + whFlashUnit write_buf[4] = {0}; + whFlashUnit read_buf[4] = {0}; + uint32_t partition_units = 0; + + (void)ctx; + _setup(); + + WH_TEST_RETURN_ON_FAIL( + c->flashCb.Init(&c->flashCtx, &c->flashCfg)); + + partition_units = wh_FlashUnit_Bytes2Units( + c->flashCb.PartitionSize(&c->flashCtx)); + + /* Unlock + erase + blank check */ + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_WriteUnlock( + &c->flashCb, &c->flashCtx, 0, partition_units)); + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_Erase( + &c->flashCb, &c->flashCtx, 0, partition_units)); + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_BlankCheck( + &c->flashCb, &c->flashCtx, 0, partition_units)); + + /* Program + read back at unit granularity */ + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_Program( + &c->flashCb, &c->flashCtx, 0, 1, write_buf)); + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_Program( + &c->flashCb, &c->flashCtx, 1, 2, write_buf)); + + memset(read_buf, 0, sizeof(read_buf)); + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_Read( + &c->flashCb, &c->flashCtx, 0, 1, read_buf)); + WH_TEST_ASSERT_RETURN(0 == memcmp( + write_buf, read_buf, 1 * WHFU_BYTES_PER_UNIT)); + + /* Program + read back at byte granularity */ + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_ProgramBytes( + &c->flashCb, &c->flashCtx, + 10 * WHFU_BYTES_PER_UNIT, 8, write_bytes)); + + memset(read_bytes, 0, sizeof(read_bytes)); + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_ReadBytes( + &c->flashCb, &c->flashCtx, + 10 * WHFU_BYTES_PER_UNIT, 8, read_bytes)); + WH_TEST_ASSERT_RETURN( + 0 == memcmp(write_bytes, read_bytes, 8)); + + /* Unaligned read (exercises offset_rem path) */ + { + uint8_t pattern[WHFU_BYTES_PER_UNIT * 4]; + uint8_t readback[WHFU_BYTES_PER_UNIT * 4]; + uint32_t base = 20; + uint32_t i; + + for (i = 0; i < sizeof(pattern); i++) { + pattern[i] = (uint8_t)(0x10 + i); + } + + WH_TEST_RETURN_ON_FAIL( + wh_FlashUnit_ProgramBytes( + &c->flashCb, &c->flashCtx, + base * WHFU_BYTES_PER_UNIT, + sizeof(pattern), pattern)); + + memset(readback, 0, sizeof(readback)); + WH_TEST_RETURN_ON_FAIL( + wh_FlashUnit_ReadBytes( + &c->flashCb, &c->flashCtx, + base * WHFU_BYTES_PER_UNIT + 3, + 5, readback)); + WH_TEST_ASSERT_RETURN( + 0 == memcmp(readback, &pattern[3], 5)); + + memset(readback, 0, sizeof(readback)); + WH_TEST_RETURN_ON_FAIL( + wh_FlashUnit_ReadBytes( + &c->flashCb, &c->flashCtx, + base * WHFU_BYTES_PER_UNIT + 2, + 21, readback)); + WH_TEST_ASSERT_RETURN( + 0 == memcmp(readback, &pattern[2], 21)); + } + + /* Erase + lock */ + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_Erase( + &c->flashCb, &c->flashCtx, 0, partition_units)); + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_BlankCheck( + &c->flashCb, &c->flashCtx, 0, partition_units)); + WH_TEST_RETURN_ON_FAIL(wh_FlashUnit_WriteLock( + &c->flashCb, &c->flashCtx, 0, partition_units)); + + WH_TEST_RETURN_ON_FAIL( + c->flashCb.Cleanup(&c->flashCtx)); + + return 0; +} + + +/* ---- NVM operations ---- */ + +static int _addAndCheck(const whNvmCb* cb, void* context, + whNvmMetadata* meta, whNvmSize len, const uint8_t* data) +{ + whNvmMetadata readMeta = {0}; + uint8_t readBuf[256]; + + WH_TEST_RETURN_ON_FAIL( + cb->AddObject(context, meta, len, data)); + WH_TEST_RETURN_ON_FAIL( + cb->Read(context, meta->id, 0, len, readBuf)); + WH_TEST_RETURN_ON_FAIL( + cb->GetMetadata(context, meta->id, &readMeta)); + WH_TEST_ASSERT_RETURN(meta->id == readMeta.id); + WH_TEST_ASSERT_RETURN(0 == memcmp(data, readBuf, len)); + + return 0; +} + + +/* + * Add objects, overwrite, reclaim, destroy, verify + * data integrity throughout. + */ +WH_TEST_MISC int whTest_NvmAddOverwriteDestroy(void* ctx) +{ + whTestNvmFlashCtx* c = &_ctx; + const whNvmCb* cb = &c->nvmCb; + + (void)ctx; + _setup(); + uint8_t data1[] = "Data1"; + uint8_t data2[] = "Data2"; + uint8_t data3[] = "Data3"; + uint8_t update1[] = "Update1fdsafdasfdsafdsafdsafdsaf"; + uint8_t update2[] = "Update2fdafdafdafdsafdsafdasfd"; + whNvmId ids[] = {100, 400, 300}; + + whNvmMetadata meta1 = {.id = ids[0], .label = "L1"}; + whNvmMetadata meta2 = {.id = ids[1], .label = "L2"}; + whNvmMetadata meta3 = {.id = ids[2], .label = "L3"}; + + whNvmMetadata readMeta = {0}; + uint8_t readBuf[256]; + size_t i; + + WH_TEST_RETURN_ON_FAIL( + cb->Init(&c->nvmFlashCtx, &c->nvmFlashCfg)); + + /* Add 3 objects */ + WH_TEST_RETURN_ON_FAIL( + _addAndCheck(cb, &c->nvmFlashCtx, + &meta1, sizeof(data1), data1)); + WH_TEST_RETURN_ON_FAIL( + _addAndCheck(cb, &c->nvmFlashCtx, + &meta2, sizeof(data2), data2)); + WH_TEST_RETURN_ON_FAIL( + _addAndCheck(cb, &c->nvmFlashCtx, + &meta3, sizeof(data3), data3)); + + /* Overwrite objects */ + WH_TEST_RETURN_ON_FAIL( + _addAndCheck(cb, &c->nvmFlashCtx, + &meta1, sizeof(update1), update1)); + WH_TEST_RETURN_ON_FAIL( + _addAndCheck(cb, &c->nvmFlashCtx, + &meta2, sizeof(update2), update2)); + + /* Reclaim space */ + WH_TEST_RETURN_ON_FAIL( + cb->DestroyObjects(&c->nvmFlashCtx, 0, NULL)); + + /* Verify all objects survived reclaim */ + for (i = 0; i < sizeof(ids) / sizeof(ids[0]); i++) { + memset(&readMeta, 0, sizeof(readMeta)); + WH_TEST_RETURN_ON_FAIL(cb->GetMetadata( + &c->nvmFlashCtx, ids[i], &readMeta)); + WH_TEST_RETURN_ON_FAIL(cb->Read( + &c->nvmFlashCtx, ids[i], 0, + readMeta.len, readBuf)); + } + + /* Destroy first object, verify it's gone */ + WH_TEST_RETURN_ON_FAIL( + cb->DestroyObjects(&c->nvmFlashCtx, 1, ids)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_NOTFOUND == cb->Read( + &c->nvmFlashCtx, ids[0], 0, + sizeof(readBuf), readBuf)); + + /* Destroy remaining */ + WH_TEST_RETURN_ON_FAIL( + cb->DestroyObjects(&c->nvmFlashCtx, + sizeof(ids) / sizeof(ids[0]), ids)); + + WH_TEST_RETURN_ON_FAIL( + cb->Cleanup(&c->nvmFlashCtx)); + + return 0; +} diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile new file mode 100644 index 00000000..aefdaf35 --- /dev/null +++ b/test-refactor/posix/Makefile @@ -0,0 +1,190 @@ +## Makefile for wolfHSM test-refactor POSIX port +## Mirrors test/Makefile conventions with minimal sources + +# Use bash so the `run` target can read ${PIPESTATUS[0]} to get +# the test binary's exit status through `tee`. +SHELL := /bin/bash + +## Project name +BIN = wh_test_refactor + +## Important directories +PROJECT_DIR ?= . +REFACTOR_DIR ?= $(PROJECT_DIR)/.. +CONFIG_DIR ?= $(REFACTOR_DIR)/../test/config +WOLFSSL_DIR ?= ../../../wolfssl +WOLFHSM_DIR ?= $(REFACTOR_DIR)/.. +WOLFHSM_PORT_DIR ?= $(WOLFHSM_DIR)/port/posix +TEST_DIR ?= $(WOLFHSM_DIR)/test + +BUILD_DIR ?= $(PROJECT_DIR)/Build + +# Includes +INC = -I$(PROJECT_DIR) \ + -I$(REFACTOR_DIR) \ + -I$(REFACTOR_DIR)/server \ + -I$(REFACTOR_DIR)/client-server \ + -I$(REFACTOR_DIR)/misc \ + -I$(CONFIG_DIR) \ + -I$(TEST_DIR) \ + -I$(WOLFSSL_DIR) \ + -I$(WOLFHSM_DIR) \ + -I$(WOLFHSM_PORT_DIR) + +# POSIX requires C source be defined before any header +DEF += -D_POSIX_C_SOURCE=200809L + +# Library configuration defines for user-supplied settings +DEF += -DWOLFSSL_USER_SETTINGS -DWOLFHSM_CFG + +# Enable POSIX test features and server +DEF += -DWOLFHSM_CFG_TEST_POSIX +DEF += -DWOLFHSM_CFG_ENABLE_CLIENT +DEF += -DWOLFHSM_CFG_ENABLE_SERVER + +# C standard +CSTD ?= -std=c90 + +# Compiler flags +CFLAGS_EXTRA = -Werror -Wall -Wextra +CFLAGS_EXTRA += -ffunction-sections -fdata-sections +CFLAGS_EXTRA += -MMD -MP + +ARCHFLAGS ?= +CFLAGS ?= $(ARCHFLAGS) $(CSTD) $(CFLAGS_EXTRA) +LDFLAGS ?= $(ARCHFLAGS) + +# Dead-strip unused sections +OS_NAME := $(shell uname -s | tr A-Z a-z) +ifeq ($(OS_NAME),darwin) + LDFLAGS += -Wl,-dead_strip +else + LDFLAGS += -Wl,--gc-sections +endif + +## Optional flags (same as test/Makefile) + +ifeq ($(DEBUG),1) + DBGFLAGS = -ggdb -g3 + CFLAGS += $(DBGFLAGS) + LDFLAGS += $(DBGFLAGS) + DEF += -DWOLFHSM_CFG_DEBUG +endif + +ifeq ($(DEBUG_VERBOSE),1) + DBGFLAGS = -ggdb -g3 + CFLAGS += $(DBGFLAGS) + LDFLAGS += $(DBGFLAGS) + DEF += -DWOLFHSM_CFG_DEBUG -DWOLFHSM_CFG_DEBUG_VERBOSE +endif + +ifeq ($(ASAN),1) + CFLAGS += -fsanitize=address + LDFLAGS += -fsanitize=address +endif + +## Source files + +# wolfCrypt +SRC_C += $(wildcard $(WOLFSSL_DIR)/wolfcrypt/src/*.c) + +# wolfSSL TLS (needed by cert manager APIs) +SRC_C += $(wildcard $(WOLFSSL_DIR)/src/*.c) + +# wolfHSM library +SRC_C += $(wildcard $(WOLFHSM_DIR)/src/*.c) + +# POSIX port (timestamps, flash file, etc.) +SRC_C += $(wildcard $(WOLFHSM_PORT_DIR)/*.c) + +# Portable test-refactor sources (groups + generated registry). +# wh_test_list.c is generated by gen_test_list.py; listing it +# explicitly means first-time builds work even before wildcard +# expansion can see it. +SRC_C += $(REFACTOR_DIR)/wh_test_groups.c +SRC_C += $(REFACTOR_DIR)/wh_test_list.c + +# Grouped test sources +SRC_C += $(wildcard $(REFACTOR_DIR)/server/*.c) +SRC_C += $(wildcard $(REFACTOR_DIR)/client-server/*.c) +SRC_C += $(wildcard $(REFACTOR_DIR)/misc/*.c) + +# POSIX-specific sources (main, helpers) +SRC_C += $(wildcard $(PROJECT_DIR)/*.c) + +# Legacy POSIX stress test pulled from test/; the .c file is +# self-gated on WOLFHSM_CFG_THREADSAFE et al. so it compiles to +# an empty TU when the gate isn't satisfied. +SRC_C += $(TEST_DIR)/wh_test_posix_threadsafe_stress.c + + +## Build rules + +FILENAMES_C = $(notdir $(SRC_C)) +OBJS_C = $(addprefix $(BUILD_DIR)/, $(FILENAMES_C:.c=.o)) +vpath %.c $(dir $(SRC_C)) + +# Regenerate the test registry from WH_TEST_* annotations. The +# script is idempotent (rewrites only on content change) so it's +# safe to run unconditionally, but we still list its inputs so +# make picks up new/removed test functions. +TEST_LIST_SCRIPT = $(REFACTOR_DIR)/gen_test_list.py +TEST_LIST_OUT = $(REFACTOR_DIR)/wh_test_list.c +TEST_LIST_DIRS = $(REFACTOR_DIR)/misc \ + $(REFACTOR_DIR)/server \ + $(REFACTOR_DIR)/client-server +TEST_LIST_INPUTS = $(wildcard $(REFACTOR_DIR)/misc/*.c) \ + $(wildcard $(REFACTOR_DIR)/server/*.c) \ + $(wildcard $(REFACTOR_DIR)/client-server/*.c) + +$(TEST_LIST_OUT): $(TEST_LIST_SCRIPT) $(TEST_LIST_INPUTS) + @echo "Generating: $(notdir $@)" + python3 $(TEST_LIST_SCRIPT) --output $@ $(TEST_LIST_DIRS) + +.PHONY: build_app clean run gen_test_list + +gen_test_list: $(TEST_LIST_OUT) + +build_app: $(BUILD_DIR) $(TEST_LIST_OUT) $(BUILD_DIR)/$(BIN).elf + @echo Build complete. + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +# Workaround: pre-existing warnings in upstream files that +# -Werror promotes to errors. +$(BUILD_DIR)/internal.o: CFLAGS += -Wno-error=implicit-function-declaration +$(BUILD_DIR)/wh_client_crypto.o: CFLAGS += -Wno-error=sign-compare + +$(BUILD_DIR)/%.o: %.c + @echo "Compiling: $(notdir $<)" + $(CC) $(CFLAGS) $(DEF) $(INC) -c -o $@ $< + +-include $(OBJS_C:.o=.d) + +$(BUILD_DIR)/$(BIN).elf: $(OBJS_C) + @echo "Linking: $(notdir $@)" + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +clean: + @echo "Cleaning build files" + @rm -rf $(BUILD_DIR) + @rm -f test-suite.log + +# Send the full test output to test-suite.log (automake-style) and +# show only the runner's per-test lines + the final tally on the +# console. Verbose prints from individual test bodies are still +# captured in the log file, just filtered out of the terminal so +# the summary is the signal. Uses PIPESTATUS to surface the test +# binary's real exit code through the pipeline. +check run: build_app + @rm -f test-suite.log + @$(BUILD_DIR)/$(BIN).elf 2>&1 \ + | tee test-suite.log \ + | grep --line-buffered -E \ + '^whTest_|^All [0-9]+ tests passed|^[0-9]+ passed, [0-9]+ skipped'; \ + rc=$${PIPESTATUS[0]}; \ + if [ $$rc -ne 0 ]; then \ + echo "test run exited $$rc (see test-suite.log for full output)"; \ + fi; \ + exit $$rc diff --git a/test-refactor/posix/wh_test_helpers_client_posix.c b/test-refactor/posix/wh_test_helpers_client_posix.c new file mode 100644 index 00000000..79179187 --- /dev/null +++ b/test-refactor/posix/wh_test_helpers_client_posix.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_helpers_client_posix.c + * + * POSIX client-side init. Wires the client onto the shared + * mem-transport buffers published by the server helper; the + * port's server thread is responsible for pumping requests. + */ + +#include +#include + +#include "wolfhsm/wh_settings.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_transport_mem.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_client.h" + +#include "wh_test_common.h" +#include "wh_test_helpers_server_posix.h" +#include "wh_test_helpers_client_posix.h" + + +/* Client-side transport state (buffers are shared with the + * server via whTestHelperPosix_Server_GetTransportConfig) */ +static whTransportMemClientContext _tmClientCtx; +static whCommClientConfig _commCfg; + +static const whTransportClientCb _tcCb = { + .Init = wh_TransportMem_InitClear, + .Send = wh_TransportMem_SendRequest, + .Recv = wh_TransportMem_RecvResponse, + .Cleanup = wh_TransportMem_Cleanup, +}; + + +int whTestHelperPosix_Client_Init(whClientContext* client) +{ + whClientConfig cCfg; + whTransportMemConfig* tmCfg; + uint32_t clientId = 0; + uint32_t serverId = 0; + + if (client == NULL) { + return WH_ERROR_BADARGS; + } + + tmCfg = whTestHelperPosix_Server_GetTransportConfig(); + if (tmCfg == NULL) { + return WH_ERROR_BADARGS; + } + + memset(&_commCfg, 0, sizeof(_commCfg)); + _commCfg.transport_cb = &_tcCb; + _commCfg.transport_context = (void*)&_tmClientCtx; + _commCfg.transport_config = (void*)tmCfg; + _commCfg.client_id = 1; + + memset(&cCfg, 0, sizeof(cCfg)); + cCfg.comm = &_commCfg; + + WH_TEST_RETURN_ON_FAIL(wh_Client_Init(client, &cCfg)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_CommInit(client, &clientId, &serverId)); + + return 0; +} + + +int whTestHelperPosix_Client_Cleanup(whClientContext* client) +{ + if (client == NULL) { + return 0; + } + wh_Client_Cleanup(client); + return 0; +} diff --git a/test-refactor/posix/wh_test_helpers_client_posix.h b/test-refactor/posix/wh_test_helpers_client_posix.h new file mode 100644 index 00000000..1f437c05 --- /dev/null +++ b/test-refactor/posix/wh_test_helpers_client_posix.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_helpers_client_posix.h + * + * POSIX client-side init helper. Stands in for the normal + * boot-time transport+client init a real firmware would do. + * Wires the client onto the mem-transport buffers exposed by + * the server helper and performs the CommInit handshake. The + * port's main is responsible for running a server thread that + * pumps HandleRequestMessage -- this helper does not touch the + * server context. + */ + +#ifndef WH_TEST_HELPERS_CLIENT_POSIX_H_ +#define WH_TEST_HELPERS_CLIENT_POSIX_H_ + +#include "wolfhsm/wh_client.h" + +/* + * Initialize the client context plus transport state and + * perform the CommInit handshake. The server helper must have + * been initialized first (its transport config supplies the + * shared request/response buffers) and a server thread must be + * actively processing requests when this is called. + */ +int whTestHelperPosix_Client_Init(whClientContext* client); + +int whTestHelperPosix_Client_Cleanup(whClientContext* client); + +#endif /* WH_TEST_HELPERS_CLIENT_POSIX_H_ */ diff --git a/test-refactor/posix/wh_test_helpers_server_posix.c b/test-refactor/posix/wh_test_helpers_server_posix.c new file mode 100644 index 00000000..f76d0c80 --- /dev/null +++ b/test-refactor/posix/wh_test_helpers_server_posix.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_helpers_server_posix.c + * + * POSIX server-side init. Stand-in for what a real firmware's + * boot flow would do: configure flash/NVM, init crypto, wire + * up a transport, and bring up a server context. + */ + +#include +#include + +#include "wolfhsm/wh_settings.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_flash.h" +#include "wolfhsm/wh_flash_ramsim.h" +#include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_nvm_flash.h" +#include "wolfhsm/wh_transport_mem.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_server.h" + +#ifndef WOLFHSM_CFG_NO_CRYPTO +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/random.h" +#endif + +#include "wh_test_common.h" +#include "wh_test_helpers_server_posix.h" + +#define POSIX_FLASH_SIZE (1024 * 1024) +#define POSIX_FLASH_SECTOR_SZ (128 * 1024) +#define POSIX_FLASH_PAGE_SZ (8) +#define POSIX_TRANSPORT_BUF_SZ (4096) + + +/* Flash (ramsim backing) */ +static uint8_t _flashMem[POSIX_FLASH_SIZE]; +static whFlashRamsimCtx _flashCtx; +static whFlashRamsimCfg _flashCfg; +static const whFlashCb _flashCb = WH_FLASH_RAMSIM_CB; + +/* NVM wrapping the flash */ +static whNvmFlashContext _nvmFlashCtx; +static whNvmFlashConfig _nvmFlashCfg; +static whNvmCb _nvmCb = WH_NVM_FLASH_CB; +static whNvmContext _nvm; + +#ifndef WOLFHSM_CFG_NO_CRYPTO +static whServerCryptoContext _crypto; +#endif + +/* Mem transport -- buffers and server-side state. + * The client helper re-uses these buffers via + * whTestHelperPosix_Server_GetTransportConfig. */ +static uint8_t _req[POSIX_TRANSPORT_BUF_SZ]; +static uint8_t _resp[POSIX_TRANSPORT_BUF_SZ]; +static whTransportMemConfig _tmCfg; +static whTransportMemServerContext _tmServerCtx; +static const whTransportServerCb _tsCb = WH_TRANSPORT_MEM_SERVER_CB; +static whCommServerConfig _commCfg; + + +int whTestHelperPosix_Server_Init(whServerContext* server) +{ + whNvmConfig nvmCfg; + whServerConfig sCfg; + + if (server == NULL) { + return WH_ERROR_BADARGS; + } + + /* Flash backend */ + memset(&_flashCtx, 0, sizeof(_flashCtx)); + memset(&_flashCfg, 0, sizeof(_flashCfg)); + _flashCfg.size = POSIX_FLASH_SIZE; + _flashCfg.sectorSize = POSIX_FLASH_SECTOR_SZ; + _flashCfg.pageSize = POSIX_FLASH_PAGE_SZ; + _flashCfg.erasedByte = 0xFF; + _flashCfg.memory = _flashMem; + + /* Transport */ + memset(&_tmCfg, 0, sizeof(_tmCfg)); + _tmCfg.req = (whTransportMemCsr*)_req; + _tmCfg.req_size = sizeof(_req); + _tmCfg.resp = (whTransportMemCsr*)_resp; + _tmCfg.resp_size = sizeof(_resp); + + memset(&_commCfg, 0, sizeof(_commCfg)); + _commCfg.transport_cb = &_tsCb; + _commCfg.transport_context = (void*)&_tmServerCtx; + _commCfg.transport_config = (void*)&_tmCfg; + _commCfg.server_id = 1; + + /* NVM -- flash ctx/cfg/cb wired by pointer */ + memset(&_nvmFlashCfg, 0, sizeof(_nvmFlashCfg)); + _nvmFlashCfg.cb = &_flashCb; + _nvmFlashCfg.context = &_flashCtx; + _nvmFlashCfg.config = &_flashCfg; + + memset(&nvmCfg, 0, sizeof(nvmCfg)); + nvmCfg.cb = &_nvmCb; + nvmCfg.context = &_nvmFlashCtx; + nvmCfg.config = &_nvmFlashCfg; + + WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(&_nvm, &nvmCfg)); + +#ifndef WOLFHSM_CFG_NO_CRYPTO + WH_TEST_RETURN_ON_FAIL(wolfCrypt_Init()); + WH_TEST_RETURN_ON_FAIL( + wc_InitRng_ex(_crypto.rng, NULL, INVALID_DEVID)); +#endif + + memset(&sCfg, 0, sizeof(sCfg)); + sCfg.comm_config = &_commCfg; + sCfg.nvm = &_nvm; +#ifndef WOLFHSM_CFG_NO_CRYPTO + sCfg.crypto = &_crypto; +#endif + + return wh_Server_Init(server, &sCfg); +} + + +int whTestHelperPosix_Server_Cleanup(whServerContext* server) +{ + if (server == NULL) { + return 0; + } + + wh_Server_Cleanup(server); + wh_Nvm_Cleanup(&_nvm); + +#ifndef WOLFHSM_CFG_NO_CRYPTO + wc_FreeRng(_crypto.rng); + wolfCrypt_Cleanup(); +#endif + + return 0; +} + + +whTransportMemConfig* whTestHelperPosix_Server_GetTransportConfig(void) +{ + return &_tmCfg; +} diff --git a/test-refactor/posix/wh_test_helpers_server_posix.h b/test-refactor/posix/wh_test_helpers_server_posix.h new file mode 100644 index 00000000..a494d964 --- /dev/null +++ b/test-refactor/posix/wh_test_helpers_server_posix.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_helpers_server_posix.h + * + * POSIX server-side init helper. Allocates the NVM, crypto, + * transport, and server backing state the test main needs, + * and wires them into a whServerContext. A real target would + * do this in its normal boot flow; this file stands in for + * that flow in the POSIX test harness. + * + * Also exposes the shared mem-transport config so the POSIX + * client helper can wire its side onto the same + * request/response buffers. + */ + +#ifndef WH_TEST_HELPERS_SERVER_POSIX_H_ +#define WH_TEST_HELPERS_SERVER_POSIX_H_ + +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_transport_mem.h" + +/* + * Initialize the server context plus all backing state + * (flash, NVM, crypto, transport). Caller owns `server`. + */ +int whTestHelperPosix_Server_Init(whServerContext* server); + +/* + * Tear down the server context plus backing state. Matches + * Server_Init one-for-one. + */ +int whTestHelperPosix_Server_Cleanup(whServerContext* server); + +/* + * Returns the shared mem-transport config (buffers + sizes). + * Used by the POSIX client helper to wire its side onto the + * same buffers the server publishes through. + */ +whTransportMemConfig* whTestHelperPosix_Server_GetTransportConfig(void); + +#endif /* WH_TEST_HELPERS_SERVER_POSIX_H_ */ diff --git a/test-refactor/posix/wh_test_main_posix.c b/test-refactor/posix/wh_test_main_posix.c new file mode 100644 index 00000000..e76b5cc4 --- /dev/null +++ b/test-refactor/posix/wh_test_main_posix.c @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_main_posix.c + * + * POSIX threaded test driver. Runs the misc group inline, then + * spawns a server thread and a client thread. The server thread + * runs the server-only group first (no client is active yet), + * then marks itself connected, signals the client thread, and + * enters a request-handling loop. The client thread waits for + * that signal, runs the client-only group against the live + * server, and calls CommClose -- the server processes the close + * message, drops its connected state, and the loop exits on the + * next iteration. Suite dispatch is delegated to the portable + * group entry points in wh_test_groups.c; this file also + * implements the reset hooks they invoke between suites. + */ + +#include + +#include "wolfhsm/wh_settings.h" +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_server.h" + +#include "wh_test_common.h" +#include "wh_test_groups.h" + +#include "wh_test_helpers_client_posix.h" +#include "wh_test_helpers_server_posix.h" + +/* POSIX-only thread-safety stress test. Called directly rather + * than through the suite runner: the legacy test owns its own + * client/server threads and doesn't fit the runner's context + * model. Kept out of the portable client group since it's not + * meaningful (or portable) on bare-metal client targets. */ +#if defined(WOLFHSM_CFG_THREADSAFE) \ + && defined(WOLFHSM_CFG_GLOBAL_KEYS) \ + && !defined(WOLFHSM_CFG_NO_CRYPTO) +#include "wh_test_posix_threadsafe_stress.h" +#endif + +/* + * Port-owned contexts. The thread functions fill these in and + * hand them to the group functions, paralleling the firmware + * pattern where these handles come from the normal init flow. + */ +static whServerContext _server; +static whClientContext _client; + +/* + * Server-ready gate. The client thread blocks on this until + * the server finishes its group and enters the request loop, + * so the client never issues a request against a server that + * isn't listening yet. + */ +static pthread_mutex_t _readyMtx = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t _readyCond = PTHREAD_COND_INITIALIZER; +static int _serverReady = 0; + +/* + * Per-thread return codes. main collects these after join so a + * failure in either thread propagates to the process exit + * status. + */ +static int _serverRc = 0; +static int _clientRc = 0; + + +static void _signalServerReady(void) +{ + pthread_mutex_lock(&_readyMtx); + _serverReady = 1; + pthread_cond_broadcast(&_readyCond); + pthread_mutex_unlock(&_readyMtx); +} + + +static void _waitServerReady(void) +{ + pthread_mutex_lock(&_readyMtx); + while (_serverReady == 0) { + pthread_cond_wait(&_readyCond, &_readyMtx); + } + pthread_mutex_unlock(&_readyMtx); +} + + +static void* _serverThread(void* arg) +{ + whCommConnected connected = WH_COMM_CONNECTED; + int rc; + + (void)arg; + + rc = whTestHelperPosix_Server_Init(&_server); + if (rc != 0) { + _serverRc = rc; + /* Release the client thread so it doesn't stall on the + * ready gate waiting for a server that never came up. */ + _signalServerReady(); + return NULL; + } + + rc = whTestGroup_Server(&_server); + if (rc != 0) { + _serverRc = rc; + (void)whTestHelperPosix_Server_Cleanup(&_server); + _signalServerReady(); + return NULL; + } + + /* Mark connected before signaling -- otherwise the client's + * CommInit request can arrive before the server is willing + * to handle it and HandleRequestMessage short-circuits to + * NOTREADY. */ + rc = wh_Server_SetConnected(&_server, WH_COMM_CONNECTED); + if (rc != 0) { + _serverRc = rc; + (void)whTestHelperPosix_Server_Cleanup(&_server); + _signalServerReady(); + return NULL; + } + + _signalServerReady(); + + /* Handle requests until the server's CommClose handler flips + * us to DISCONNECTED in response to the client's CommClose + * message. */ + while (1) { + rc = wh_Server_HandleRequestMessage(&_server); + if (rc != WH_ERROR_OK && rc != WH_ERROR_NOTREADY) { + _serverRc = rc; + break; + } + (void)wh_Server_GetConnected(&_server, &connected); + if (connected == WH_COMM_DISCONNECTED) { + break; + } + } + + (void)whTestHelperPosix_Server_Cleanup(&_server); + return NULL; +} + + +static void* _clientThread(void* arg) +{ + int rc; + + (void)arg; + + _waitServerReady(); + + /* Server init or the server-only group may have failed. + * Don't try to talk to a server that never entered the + * request loop; propagate the server's error instead. */ + if (_serverRc != 0) { + _clientRc = _serverRc; + return NULL; + } + + rc = whTestHelperPosix_Client_Init(&_client); + if (rc != 0) { + _clientRc = rc; + return NULL; + } + + rc = whTestGroup_Client(&_client); + if (rc != 0) { + _clientRc = rc; + } + +#if defined(WOLFHSM_CFG_THREADSAFE) \ + && defined(WOLFHSM_CFG_GLOBAL_KEYS) \ + && !defined(WOLFHSM_CFG_NO_CRYPTO) + /* Run the POSIX-only stress test after the portable client + * group so a failure there doesn't mask earlier results. */ + if (_clientRc == 0) { + rc = whTest_ThreadSafeStress(); + if (rc != 0) { + _clientRc = rc; + } + } +#endif + + /* CommClose triggers the server-side SetConnected(DISCONNECTED) + * inside HandleRequestMessage, which is what lets the server + * thread exit its loop. Always attempt it so the server can + * shut down even when a test failed. */ + (void)wh_Client_CommClose(&_client); + (void)whTestHelperPosix_Client_Cleanup(&_client); + return NULL; +} + + +int main(void) +{ + pthread_t sthread; + pthread_t cthread; + int rc; + int miscRc; + + /* Run everything to the end so the summary reflects the + * whole suite; a misc failure doesn't skip the client or + * server groups. */ + miscRc = whTestGroup_Misc(); + + rc = pthread_create(&sthread, NULL, _serverThread, NULL); + if (rc != 0) { + (void)whTestGroup_Summary(); + return rc; + } + + rc = pthread_create(&cthread, NULL, _clientThread, NULL); + if (rc != 0) { + /* Drop the server out of its loop so the best-effort + * join below doesn't block forever. */ + (void)wh_Server_SetConnected(&_server, + WH_COMM_DISCONNECTED); + (void)pthread_join(sthread, NULL); + (void)whTestGroup_Summary(); + return rc; + } + + (void)pthread_join(cthread, NULL); + (void)pthread_join(sthread, NULL); + + (void)whTestGroup_Summary(); + + if (miscRc != 0) { + return miscRc; + } + if (_serverRc != 0) { + return _serverRc; + } + return _clientRc; +} + + +/* + * Reset hooks invoked by the group functions between suites. + * Placeholder implementations -- once suites drop their own + * setup/cleanup and run against the live contexts, these get + * filled in to scrub persistent state (key cache, NVM, etc.). + */ +int whTestGroup_ResetServer(whServerContext* server) +{ + (void)server; + return 0; +} + + +int whTestGroup_ResetClient(whClientContext* client) +{ + (void)client; + return 0; +} diff --git a/test-refactor/server/wh_test_cert.c b/test-refactor/server/wh_test_cert.c new file mode 100644 index 00000000..78b60b75 --- /dev/null +++ b/test-refactor/server/wh_test_cert.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_cert.c + * + * Server-side certificate test suite. Exercises the cert + * manager through direct server API calls. Uses the shared + * server helper for setup/cleanup. + */ + +#include "wolfhsm/wh_settings.h" + +#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) \ + && !defined(WOLFHSM_CFG_NO_CRYPTO) + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_server_cert.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" +#include "wh_test_cert_data.h" + + +/* + * Add trusted roots, verify valid and invalid certs/chains, + * then remove roots. + */ +WH_TEST_SERVER int whTest_CertVerify(whServerContext* ctx) +{ + whServerContext* server = (whServerContext*)ctx; + const whNvmId rootA = 1; + const whNvmId rootB = 2; + + WH_TEST_RETURN_ON_FAIL(wh_Server_CertInit(server)); + + /* Add trusted roots */ + WH_TEST_RETURN_ON_FAIL(wh_Server_CertAddTrusted( + server, rootA, WH_NVM_ACCESS_ANY, + WH_NVM_FLAGS_NONMODIFIABLE, + NULL, 0, ROOT_A_CERT, ROOT_A_CERT_len)); + + WH_TEST_RETURN_ON_FAIL(wh_Server_CertAddTrusted( + server, rootB, WH_NVM_ACCESS_ANY, + WH_NVM_FLAGS_NONMODIFIABLE, + NULL, 0, ROOT_B_CERT, ROOT_B_CERT_len)); + + /* Valid single cert (intermediate against its root) */ + WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify( + server, INTERMEDIATE_A_CERT, INTERMEDIATE_A_CERT_len, + rootA, WH_CERT_FLAGS_NONE, + WH_NVM_FLAGS_USAGE_ANY, NULL)); + + /* Invalid: leaf without intermediate -- must fail */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_CERT_VERIFY == wh_Server_CertVerify( + server, LEAF_A_CERT, LEAF_A_CERT_len, + rootA, WH_CERT_FLAGS_NONE, + WH_NVM_FLAGS_USAGE_ANY, NULL)); + + /* Invalid: intermediate against wrong root */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_CERT_VERIFY == wh_Server_CertVerify( + server, INTERMEDIATE_B_CERT, + INTERMEDIATE_B_CERT_len, + rootA, WH_CERT_FLAGS_NONE, + WH_NVM_FLAGS_USAGE_ANY, NULL)); + + /* Valid chains */ + WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify( + server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, + rootA, WH_CERT_FLAGS_NONE, + WH_NVM_FLAGS_USAGE_ANY, NULL)); + + WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify( + server, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, + rootB, WH_CERT_FLAGS_NONE, + WH_NVM_FLAGS_USAGE_ANY, NULL)); + + /* Cross-chain: must fail */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_CERT_VERIFY == wh_Server_CertVerify( + server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, + rootB, WH_CERT_FLAGS_NONE, + WH_NVM_FLAGS_USAGE_ANY, NULL)); + + WH_TEST_ASSERT_RETURN( + WH_ERROR_CERT_VERIFY == wh_Server_CertVerify( + server, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, + rootA, WH_CERT_FLAGS_NONE, + WH_NVM_FLAGS_USAGE_ANY, NULL)); + + /* Remove trusted roots */ + WH_TEST_RETURN_ON_FAIL( + wh_Server_CertEraseTrusted(server, rootA)); + WH_TEST_RETURN_ON_FAIL( + wh_Server_CertEraseTrusted(server, rootB)); + + return 0; +} + +#endif diff --git a/test-refactor/wh_test_groups.c b/test-refactor/wh_test_groups.c new file mode 100644 index 00000000..7aaa8251 --- /dev/null +++ b/test-refactor/wh_test_groups.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_groups.c + * + * Portable group entry points. Each walks its slice of the + * generated test registry (see wh_test_list.c) -- one array per + * group. Tests whose feature gate is off are resolved by the + * linker to the weak skip stub and surface as SKIPPED at + * runtime. + * + * Output format follows wolfCrypt convention: + * whTest_Foo test passed! + * whTest_Bar test skipped + * whTest_Baz test FAILED (rc=-5) + * with a final whTestGroup_Summary() tally. + */ + +#include + +#include "wolfhsm/wh_settings.h" + +#include "wh_test_common.h" +#include "wh_test_groups.h" +#include "wh_test_list.h" + +#ifndef WH_TEST_RUNNER_PRINTF +#include +#define WH_TEST_RUNNER_PRINTF printf +#endif + +/* Column at which "test passed!" / "test skipped" / "test FAILED" + * starts. Pad the test name with spaces to line these up. Pick + * something a bit wider than the longest current name so future + * tests don't force a reformat. */ +#define WH_TEST_NAME_COL 40 + +/* Per-process tallies. The POSIX port runs the groups + * sequentially (misc inline, then server, then client), so no + * locking is needed. Ports that invoke groups from concurrent + * threads must serialize the calls or add their own lock. */ +static int whTestPassed = 0; +static int whTestSkipped = 0; +static int whTestFailed = 0; + +static void whTest_PrintResult(const char* name, int rc) +{ + int pad = WH_TEST_NAME_COL - (int)strlen(name); + if (pad < 1) { + pad = 1; + } + WH_TEST_RUNNER_PRINTF("%s%*s", name, pad, ""); + + if (rc == 0) { + WH_TEST_RUNNER_PRINTF("test passed!\n"); + whTestPassed++; + } + else if (rc == WH_TEST_SKIPPED) { + WH_TEST_RUNNER_PRINTF("test skipped\n"); + whTestSkipped++; + } + else { + WH_TEST_RUNNER_PRINTF("test FAILED (rc=%d)\n", rc); + whTestFailed++; + } +} + +static int whTest_Run(const whTestCase* tests, size_t count, void* ctx) +{ + int overall = 0; + size_t i; + + for (i = 0; i < count; i++) { + int rc = tests[i].fn(ctx); + whTest_PrintResult(tests[i].name, rc); + + if (rc != 0 && rc != WH_TEST_SKIPPED && overall == 0) { + overall = rc; + } + } + + return overall; +} + + +int whTestGroup_Misc(void) +{ + return whTest_Run(whTestsMisc, whTestsMiscCount, NULL); +} + +int whTestGroup_Server(whServerContext* server) +{ + return whTest_Run(whTestsServer, whTestsServerCount, server); +} + +int whTestGroup_Client(whClientContext* client) +{ + return whTest_Run(whTestsClient, whTestsClientCount, client); +} + +int whTestGroup_Summary(void) +{ + int total = whTestPassed + whTestSkipped + whTestFailed; + + if (whTestFailed == 0 && whTestSkipped == 0) { + WH_TEST_RUNNER_PRINTF("All %d tests passed!\n", total); + } + else { + WH_TEST_RUNNER_PRINTF( + "%d passed, %d skipped, %d failed of %d tests\n", + whTestPassed, whTestSkipped, whTestFailed, total); + } + + return whTestFailed == 0 ? 0 : -1; +} diff --git a/test-refactor/wh_test_groups.h b/test-refactor/wh_test_groups.h new file mode 100644 index 00000000..7038951b --- /dev/null +++ b/test-refactor/wh_test_groups.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_groups.h + * + * Portable entry points for the three test groups. The port's + * main() owns the client/server contexts and hands them to the + * group functions, which run every suite that belongs to the + * group (gated by the applicable compile-time config flags). + * + * - Misc: standalone suites, no client or server needed + * - Server: server-side suites; takes a whServerContext* + * - Client: client-side suites; takes a whClientContext* + * (the server must already be running -- on + * single-process ports the port sets it up before + * calling into this group) + * + * A client-only port calls Client (and optionally Misc). + * A server-only port calls Server (and optionally Misc). + * A combined port calls all three. + */ + +#ifndef WH_TEST_GROUPS_H_ +#define WH_TEST_GROUPS_H_ + +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_server.h" + + +int whTestGroup_Misc(void); +int whTestGroup_Server(whServerContext* server); +int whTestGroup_Client(whClientContext* client); + +/* + * Print a wolfCrypt-style tally ("All N tests passed!" or + * "N passed, M skipped, K failed of T tests") using the + * counters accumulated by whTestGroup_{Misc,Server,Client}. + * Call once from main after the last group returns. + * Returns 0 if no failures, non-zero otherwise. + */ +int whTestGroup_Summary(void); + + +/* + * Caller-implemented reset hooks. The group functions invoke + * these between suites so the caller can scrub any persistent + * state (NVM objects, key cache, connection state, ...) that + * one suite may have mutated before the next one runs. The + * caller owns the context, so only it knows how to reset it. + */ +int whTestGroup_ResetServer(whServerContext* server); +int whTestGroup_ResetClient(whClientContext* client); + +#endif /* WH_TEST_GROUPS_H_ */ diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c new file mode 100644 index 00000000..fd2519b1 --- /dev/null +++ b/test-refactor/wh_test_list.c @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ + +/* + ************************************************************************* + * wh_test_list.c -- GENERATED BY gen_test_list.py. DO NOT EDIT. + ************************************************************************* + */ + +/* + * Registry of every test function found via WH_TEST_{MISC, + * CLIENT,SERVER} annotations. Each discovered test gets a weak + * stub that returns WH_TEST_SKIPPED; the real test, when + * compiled in, provides a strong symbol that the linker picks + * instead. + */ + +#include "wh_test_list.h" + +/* Test declarations and weak skip implementations. */ +WH_TEST_DECL(whTest_FlashEraseProgramVerify); +WH_TEST_DECL(whTest_FlashWriteLock); +WH_TEST_DECL(whTest_FlashUnitOps); +WH_TEST_DECL(whTest_NvmAddOverwriteDestroy); +WH_TEST_DECL(whTest_CertVerify); +WH_TEST_DECL(whTest_CryptoAes); +WH_TEST_DECL(whTest_CryptoEcc256); +WH_TEST_DECL(whTest_CryptoSha256); +WH_TEST_DECL(whTest_Echo); +WH_TEST_DECL(whTest_ServerInfo); + +const whTestCase whTestsMisc[] = { + { "whTest_FlashEraseProgramVerify", whTest_FlashEraseProgramVerify }, + { "whTest_FlashWriteLock", whTest_FlashWriteLock }, + { "whTest_FlashUnitOps", whTest_FlashUnitOps }, + { "whTest_NvmAddOverwriteDestroy", whTest_NvmAddOverwriteDestroy }, +}; +const size_t whTestsMiscCount = sizeof(whTestsMisc) / sizeof(whTestsMisc[0]); + +const whTestCase whTestsServer[] = { + { "whTest_CertVerify", whTest_CertVerify }, +}; +const size_t whTestsServerCount = sizeof(whTestsServer) / sizeof(whTestsServer[0]); + +const whTestCase whTestsClient[] = { + { "whTest_CryptoAes", whTest_CryptoAes }, + { "whTest_CryptoEcc256", whTest_CryptoEcc256 }, + { "whTest_CryptoSha256", whTest_CryptoSha256 }, + { "whTest_Echo", whTest_Echo }, + { "whTest_ServerInfo", whTest_ServerInfo }, +}; +const size_t whTestsClientCount = sizeof(whTestsClient) / sizeof(whTestsClient[0]); diff --git a/test-refactor/wh_test_list.h b/test-refactor/wh_test_list.h new file mode 100644 index 00000000..5114b257 --- /dev/null +++ b/test-refactor/wh_test_list.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +#ifndef WH_TEST_LIST_H_ +#define WH_TEST_LIST_H_ + +#include + +/* + * Empty annotation macros. Test functions tag themselves with one + * of these so the pre-processing script (gen_test_list.py) can + * discover them and emit wh_test_list.c. The macros expand to + * nothing at compile time; they exist purely as textual markers. + */ +#define WH_TEST_MISC +#define WH_TEST_CLIENT +#define WH_TEST_SERVER + +/* + * Portable weak-linkage attribute. The generated wh_test_list.c + * emits a weak stub for every discovered test; the real test's + * strong definition (compiled in when its feature gate is on) + * overrides the stub at link time. When the gate is off the test + * source compiles to nothing and the weak stub wins, returning + * WH_TEST_SKIPPED at runtime. + * + * Usage: + * WH_TEST_WEAK(foo) int foo(void* ctx) { ... } + * + * The name argument is only used by toolchains whose weak-symbol + * mechanism is a pragma rather than a prefix attribute; others + * ignore it. Spellings covered: + * GCC, Clang, armclang, armcc, TI -> __attribute__((weak)) + * IAR -> __weak + * Renesas CC-RH / CC-RL / CC-RX -> _Pragma("weak ") + * + * If a port's toolchain isn't covered here, add it rather than + * silently falling back -- a no-op WH_TEST_WEAK makes the stub + * strong and will cause a multiple-definition link error (or, + * worse, the stub wins over the real test). + */ +#define WH_TEST_WEAK_STR_(s) #s +#if defined(__GNUC__) || defined(__clang__) || \ + defined(__ARMCC_VERSION) || defined(__CC_ARM) || \ + defined(__TI_COMPILER_VERSION__) +#define WH_TEST_WEAK(name) __attribute__((weak)) +#elif defined(__IAR_SYSTEMS_ICC__) +#define WH_TEST_WEAK(name) __weak +#elif defined(__CCRH__) || defined(__CCRL__) || defined(__CCRX__) +#define WH_TEST_WEAK(name) _Pragma(WH_TEST_WEAK_STR_(weak name)) +#else +#error "WH_TEST_WEAK: add a weak-symbol spelling for this toolchain" +#endif + +/* + * Sentinel returned by the weak skip stubs. Picked to be distinct + * from any error code a real test would return and from 0 (pass). + */ +#define WH_TEST_SKIPPED (-777) + +/* + * One-shot declaration of a test's forward prototype AND its weak + * skip implementation. The generator emits `WH_TEST_DECL(name);` + * for every discovered test. If the real test's feature gate is + * on, its strong definition in the test's .c file overrides this + * stub at link time; otherwise the stub wins and the test + * surfaces as SKIPPED. + * + * The trailing "struct ... whTest_decl_dummy_" lets callers end + * the invocation with a ';' (a bare "};" after a function body + * is ill-formed under strict C90); the dummy tag swallows it. + */ +#define WH_TEST_DECL(name) \ + WH_TEST_WEAK(name) int name(void* ctx) \ + { (void)ctx; return WH_TEST_SKIPPED; } \ + struct whTest_decl_dummy_##name + +typedef int (*whTestFn)(void* ctx); + +typedef struct whTestCase { + const char* name; + whTestFn fn; +} whTestCase; + +/* + * Per-group registries, populated by the generated wh_test_list.c. + * Each group gets its own array so the runner walks only the + * relevant tests and doesn't carry a group tag per row. + */ +extern const whTestCase whTestsMisc[]; +extern const size_t whTestsMiscCount; + +extern const whTestCase whTestsServer[]; +extern const size_t whTestsServerCount; + +extern const whTestCase whTestsClient[]; +extern const size_t whTestsClientCount; + +#endif /* WH_TEST_LIST_H_ */