From 5d8eb7a723f0087bd68f99ea6940b090484c596e Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 15 May 2026 11:24:45 -0700 Subject: [PATCH] Add refactored client NVM tests --- test-refactor/README.md | 17 +- test-refactor/client-server/wh_test_counter.c | 177 ++++++++++ test-refactor/client-server/wh_test_nvm_dma.c | 199 ++++++++++++ test-refactor/client-server/wh_test_nvm_ops.c | 301 ++++++++++++++++++ test-refactor/wh_test_list.c | 6 + 5 files changed, 696 insertions(+), 4 deletions(-) create mode 100644 test-refactor/client-server/wh_test_counter.c create mode 100644 test-refactor/client-server/wh_test_nvm_dma.c create mode 100644 test-refactor/client-server/wh_test_nvm_ops.c diff --git a/test-refactor/README.md b/test-refactor/README.md index 8d1a0a52a..b52058fd1 100644 --- a/test-refactor/README.md +++ b/test-refactor/README.md @@ -48,9 +48,15 @@ For running the more substantial client tests only, no server modifications are 4. Optionally call `whTestGroup_Misc()` ## Adding a multi-port test -1. Create a new function which returns `int` (0 for success) with a context argument (`whClientContext*` for client tests, `whServerContext*` for server tests, or none for misc tests). -2. In wh_test_list.c, add a line with `WH_TEST_DECL()` -3. In wh_test_list.c, add the function to the appropriate `whTestCase` array. +Tests are organized as a single registered parent function per source file, which dispatches to one or more `static` sub-tests within that file. + +If adding a new test file and/or a new group of tests: +1. Create a function which returns `int` (0 for success) with a context argument (`whClientContext*` for client tests, `whServerContext*` for server tests, or none for misc tests). Inside it, call each sub-test with `WH_TEST_RETURN_ON_FAIL`. +2. In `wh_test_list.c`, add a `WH_TEST_DECL()` line. +3. In `wh_test_list.c`, add the entry-point to the appropriate `whTestCase` array. + +To add an individual test to an existing group: +1. Add sub-tests as `static` functions in the same file. Name them with a leading underscore (e.g. `_whTest_`). **Note**: if the test is specific to a platform, do not add it to the common list as shown above. Port-specific tests live within the port (not this directory), and are called from the port-specific code. @@ -75,6 +81,9 @@ Translated tests: | `wh_test_cert.c::whTest_CertRamSim` | `server/wh_test_cert.c::whTest_CertVerify` | Server | remove ramsim coupling and migrate to server group | | `wh_test_crypto.c::whTest_Crypto` | `client-server/wh_test_crypto.c::{whTest_CryptoSha256, whTest_CryptoAes, whTest_CryptoEcc256}` | Client | Subset only; remaining cases listed below | | `wh_test_clientserver.c` (echo and server-info paths) | `client-server/wh_test_echo.c::whTest_Echo`, `client-server/wh_test_server_info.c::whTest_ServerInfo` | Client | pthread test ported, sequential test dropped | +| `wh_test_clientserver.c` (NVM CRUD + OOB read clamping paths) | `client-server/wh_test_nvm_ops.c::{whTest_NvmCrud, whTest_NvmReadOob}` | Client | each test cleans up its own slots; OOB test covers UINT16_MAX overflow regression | +| `wh_test_clientserver.c` (NVM DMA CRUD path) | `client-server/wh_test_nvm_dma.c::whTest_NvmCrudDma` | Client | gated on `WOLFHSM_CFG_DMA` | +| `wh_test_clientserver.c::_testClientCounter` | `client-server/wh_test_counter.c::whTest_Counter` | Client | exercises saturate-on-overflow and slot-leak detection | | `wh_test_wolfcrypt_test.c::whTest_WolfCryptTest` | `client-server/wh_test_wolfcrypt.c::whTest_WolfCryptTest` | Client | | | `wh_test_flash_ramsim.c::whTest_Flash_RamSim` | `posix/wh_test_flash_ramsim.c::{whTest_FlashWriteLock, whTest_FlashEraseProgramVerify, whTest_FlashUnitOps}` | POSIX port-specific (`whTestGroup_RunOne`) | remove ramsim coupling and migrate to server group | | `wh_test_nvm_flash.c::whTest_NvmFlash` | `posix/wh_test_nvm_flash.c::whTest_NvmAddOverwriteDestroy` | POSIX port-specific (`whTestGroup_RunOne`) | remove ramsim coupling and migrate to server group | @@ -85,7 +94,7 @@ Not yet migrated (still live in `wolfHSM/test/`): | Legacy (`wolfHSM/test/`) | Notes | |---|---| | `wh_test_comm.c::whTest_Comm` | | -| `wh_test_clientserver.c::whTest_ClientServer` | Pthread variant: remaining client-side coverage (NVM ops, etc.) still needs to be split out as new tests. The sequential test is dropped | +| `wh_test_clientserver.c::whTest_ClientServer` | Pthread variant: remaining coverage is the custom-callback round-trip (`_testCallbacks`) and the server-side DMA register/copy/allowlist exercise (`_testDma`). The sequential test is dropped, as is the FLASH_LOG NVM matrix variant. | | `wh_test_crypto.c::whTest_Crypto` | RNG, key cache, key-cache enforcement, RSA, CMAC, Curve25519, ML-DSA, key usage policies, key revocation | | `wh_test_crypto_affinity.c::whTest_CryptoAffinity` | | | `wh_test_keywrap.c::whTest_KeyWrapClientConfig` | | diff --git a/test-refactor/client-server/wh_test_counter.c b/test-refactor/client-server/wh_test_counter.c new file mode 100644 index 000000000..ab99ff7bc --- /dev/null +++ b/test-refactor/client-server/wh_test_counter.c @@ -0,0 +1,177 @@ +/* + * 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/client-server/wh_test_counter.c + * + * Exercise the persistent NVM counter API: init/reset, + * sequential increment past WOLFHSM_CFG_NVM_OBJECT_COUNT (catches + * slot leaks), saturate-on-overflow at UINT32_MAX, and + * reset+destroy across many slots. + */ + +#include +#include + +#include "wolfhsm/wh_settings.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_client.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + + +/* + * Reset to 0, increment 2x WOLFHSM_CFG_NVM_OBJECT_COUNT times, + * verify each read-back matches. The 2x iteration count is what + * surfaces a per-increment slot leak in the server -- a leaking + * implementation exhausts NVM long before the loop ends. + */ +static int _whTest_CounterSequentialIncrement(whClientContext* ctx) +{ + const whNvmId counterId = 1; + const size_t NUM_INCREMENTS = 2u * WOLFHSM_CFG_NVM_OBJECT_COUNT; + size_t i; + uint32_t counter = 0; + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterReset(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == 0); + + for (i = 0; i < NUM_INCREMENTS; i++) { + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterIncrement(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == (uint32_t)(i + 1)); + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterRead(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == (uint32_t)(i + 1)); + } + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterReset(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == 0); + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterDestroy(ctx, counterId)); + + return WH_ERROR_OK; +} + + +/* + * Init at UINT32_MAX, increment twice, value must remain + * UINT32_MAX (no roll-over). The saturate guarantee is the + * security-relevant invariant for anti-rollback / nonce uses. + */ +static int _whTest_CounterSaturate(whClientContext* ctx) +{ + const whNvmId counterId = 1; + const uint32_t MAX_COUNTER = 0xFFFFFFFFu; + uint32_t counter = MAX_COUNTER; + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterInit(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == MAX_COUNTER); + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterIncrement(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == MAX_COUNTER); + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterIncrement(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == MAX_COUNTER); + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterRead(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == MAX_COUNTER); + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterReset(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == 0); + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterDestroy(ctx, counterId)); + + return WH_ERROR_OK; +} + + +/* + * Reset+destroy across many slots: catches leaks in the destroy + * path and confirms that a destroyed counter reads back as + * NOTFOUND. + */ +static int _whTest_CounterDestroyMany(whClientContext* ctx) +{ + const size_t NUM_SLOTS = 2u * WOLFHSM_CFG_NVM_OBJECT_COUNT; + size_t i; + uint32_t counter; + + for (i = 1; i < NUM_SLOTS; i++) { + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterReset(ctx, (whNvmId)i, &counter)); + WH_TEST_ASSERT_RETURN(counter == 0); + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterDestroy(ctx, (whNvmId)i)); + + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + wh_Client_CounterRead(ctx, (whNvmId)i, &counter)); + } + + return WH_ERROR_OK; +} + + +int whTest_Counter(whClientContext* ctx) +{ + int32_t server_rc = 0; + uint32_t client_id = 0; + uint32_t server_id = 0; + uint32_t avail_size = 0; + uint32_t reclaim_size = 0; + whNvmId avail_objects = 0; + whNvmId reclaim_objects = 0; + whNvmId baseline = 0; + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmInit( + ctx, &server_rc, &client_id, &server_id)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetAvailable( + ctx, &server_rc, &avail_size, &avail_objects, + &reclaim_size, &reclaim_objects)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + baseline = avail_objects; + + WH_TEST_RETURN_ON_FAIL(_whTest_CounterSequentialIncrement(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CounterSaturate(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CounterDestroyMany(ctx)); + + /* No object slots leaked: available count is back where we + * started before the test ran. */ + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetAvailable( + ctx, &server_rc, &avail_size, &avail_objects, + &reclaim_size, &reclaim_objects)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(avail_objects == baseline); + + return WH_ERROR_OK; +} diff --git a/test-refactor/client-server/wh_test_nvm_dma.c b/test-refactor/client-server/wh_test_nvm_dma.c new file mode 100644 index 000000000..2e770f873 --- /dev/null +++ b/test-refactor/client-server/wh_test_nvm_dma.c @@ -0,0 +1,199 @@ +/* + * 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/client-server/wh_test_nvm_dma.c + * + * NVM object lifecycle tests using the DMA add/read APIs. + * Mirrors the blocking CRUD test but exercises the DMA path + * where the server reads/writes the client buffer directly + * instead of round-tripping the payload through the comm + * channel. + */ + +#include "wolfhsm/wh_settings.h" + +#ifdef WOLFHSM_CFG_DMA + +#include +#include +#include + +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_client.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#define NVM_DMA_OBJECT_COUNT 5 +#define NVM_DMA_ID_BASE 40 + + +static int _nvmDmaIdInRange(whNvmId id) +{ + return (id >= NVM_DMA_ID_BASE) && + (id < (whNvmId)(NVM_DMA_ID_BASE + NVM_DMA_OBJECT_COUNT)); +} + + +static int _destroyNvmDmaId(whClientContext* ctx, whNvmId id) +{ + int32_t server_rc = 0; + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmDestroyObjects( + ctx, 1, &id, &server_rc)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetMetadata( + ctx, id, &server_rc, NULL, NULL, NULL, NULL, 0, NULL)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_NOTFOUND); + + return WH_ERROR_OK; +} + + +static int _whTest_NvmDmaCrud(whClientContext* ctx) +{ + int32_t server_rc = 0; + uint32_t client_id = 0; + uint32_t server_id = 0; + uint32_t avail_size = 0; + uint32_t reclaim_size = 0; + whNvmId avail_objects = 0; + whNvmId reclaim_objects = 0; + whNvmId baseline = 0; + int i; + + char send_buf[WOLFHSM_CFG_COMM_DATA_LEN]; + char recv_buf[WOLFHSM_CFG_COMM_DATA_LEN]; + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmInit( + ctx, &server_rc, &client_id, &server_id)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetAvailable( + ctx, &server_rc, &avail_size, &avail_objects, + &reclaim_size, &reclaim_objects)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + baseline = avail_objects; + + for (i = 0; i < NVM_DMA_OBJECT_COUNT; i++) { + whNvmMetadata meta = { + .id = (whNvmId)(NVM_DMA_ID_BASE + i), + .access = WH_NVM_ACCESS_ANY, + .flags = WH_NVM_FLAGS_NONE, + .len = 0, + .label = {0}, + }; + whNvmSize len; + whNvmId prev_avail = avail_objects; + + whNvmId gid = 0; + whNvmAccess gaccess = 0; + whNvmFlags gflags = 0; + whNvmSize glen = 0; + char glabel[WH_NVM_LABEL_LEN] = {0}; + + snprintf((char*)meta.label, sizeof(meta.label), + "Label:%d", meta.id); + len = (whNvmSize)snprintf(send_buf, sizeof(send_buf), + "Data:%d Counter:%d", meta.id, i); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmAddObjectDma( + ctx, &meta, len, (const uint8_t*)send_buf, + &server_rc)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetAvailable( + ctx, &server_rc, &avail_size, &avail_objects, + &reclaim_size, &reclaim_objects)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(prev_avail - 1 == avail_objects); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetMetadata( + ctx, meta.id, &server_rc, + &gid, &gaccess, &gflags, &glen, + sizeof(glabel), (uint8_t*)glabel)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(gid == meta.id); + WH_TEST_ASSERT_RETURN(gaccess == meta.access); + WH_TEST_ASSERT_RETURN(gflags == meta.flags); + WH_TEST_ASSERT_RETURN(glen == len); + + memset(recv_buf, 0, sizeof(recv_buf)); + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmReadDma( + ctx, meta.id, 0, glen, + (uint8_t*)recv_buf, &server_rc)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(memcmp(send_buf, recv_buf, len) == 0); + } + + { + whNvmAccess list_access = WH_NVM_ACCESS_ANY; + whNvmFlags list_flags = WH_NVM_FLAGS_NONE; + whNvmId list_id = 0; + whNvmId list_count = 0; + whNvmId found = 0; + + do { + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmList( + ctx, list_access, list_flags, list_id, + &server_rc, &list_count, &list_id)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + if ((list_count > 0) && _nvmDmaIdInRange(list_id)) { + found++; + } + } while (list_count > 0); + + WH_TEST_ASSERT_RETURN(found == NVM_DMA_OBJECT_COUNT); + } + + for (i = 0; i < NVM_DMA_OBJECT_COUNT; i++) { + WH_TEST_RETURN_ON_FAIL(_destroyNvmDmaId( + ctx, (whNvmId)(NVM_DMA_ID_BASE + i))); + } + + /* Cleanup → Init round-trip leaves the NVM in a usable + * state with no leaked objects. */ + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmCleanup(ctx, &server_rc)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmInit( + ctx, &server_rc, &client_id, &server_id)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetAvailable( + ctx, &server_rc, &avail_size, &avail_objects, + &reclaim_size, &reclaim_objects)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(avail_objects == baseline); + + return WH_ERROR_OK; +} + + +int whTest_NvmDma(whClientContext* ctx) +{ + WH_TEST_RETURN_ON_FAIL(_whTest_NvmDmaCrud(ctx)); + + return WH_ERROR_OK; +} + +#endif /* WOLFHSM_CFG_DMA */ diff --git a/test-refactor/client-server/wh_test_nvm_ops.c b/test-refactor/client-server/wh_test_nvm_ops.c new file mode 100644 index 000000000..14be804e3 --- /dev/null +++ b/test-refactor/client-server/wh_test_nvm_ops.c @@ -0,0 +1,301 @@ +/* + * 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/client-server/wh_test_nvm_ops.c + * + * NVM object lifecycle tests over the blocking client API. + * Each test owns its own slot range and destroys what it + * created, so reset hooks can stay no-ops. + */ + +#include +#include +#include + +#include "wolfhsm/wh_settings.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_client.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#define NVM_CRUD_OBJECT_COUNT 5 +#define NVM_CRUD_ID_BASE 20 +#define NVM_OOB_ID 30 + + +static int _nvmIdInRange(whNvmId id, whNvmId base, whNvmId count) +{ + return (id >= base) && (id < (whNvmId)(base + count)); +} + + +static int _destroyNvmId(whClientContext* ctx, whNvmId id) +{ + int32_t server_rc = 0; + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmDestroyObjects( + ctx, 1, &id, &server_rc)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetMetadata( + ctx, id, &server_rc, NULL, NULL, NULL, NULL, 0, NULL)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_NOTFOUND); + + return WH_ERROR_OK; +} + + +/* + * Add → GetAvailable → GetMetadata → Read for 5 objects, then + * destroy them via the List/Destroy loop. Ends with an + * NvmCleanup → NvmInit round-trip so we also catch the case + * where Cleanup leaves the NVM unusable. + */ +static int _whTest_NvmOpsCrud(whClientContext* ctx) +{ + int32_t server_rc = 0; + uint32_t client_id = 0; + uint32_t server_id = 0; + uint32_t avail_size = 0; + uint32_t reclaim_size = 0; + whNvmId avail_objects = 0; + whNvmId reclaim_objects = 0; + whNvmId baseline = 0; + int i; + + char send_buf[WOLFHSM_CFG_COMM_DATA_LEN]; + char recv_buf[WOLFHSM_CFG_COMM_DATA_LEN]; + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmInit( + ctx, &server_rc, &client_id, &server_id)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + /* Capture the starting available count so we don't assume + * the suite is running on a virgin NVM. The CRUD test runs + * after other client tests that may have added (and ideally + * cleaned up) their own objects. */ + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetAvailable( + ctx, &server_rc, &avail_size, &avail_objects, + &reclaim_size, &reclaim_objects)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + baseline = avail_objects; + + for (i = 0; i < NVM_CRUD_OBJECT_COUNT; i++) { + whNvmId id = (whNvmId)(NVM_CRUD_ID_BASE + i); + whNvmAccess access = WH_NVM_ACCESS_ANY; + whNvmFlags flags = WH_NVM_FLAGS_NONE; + char label[WH_NVM_LABEL_LEN] = {0}; + whNvmSize label_len; + whNvmSize len; + + whNvmId gid = 0; + whNvmAccess gaccess = 0; + whNvmFlags gflags = 0; + whNvmSize glen = 0; + char glabel[WH_NVM_LABEL_LEN] = {0}; + whNvmSize rlen = 0; + whNvmId prev_avail = avail_objects; + + label_len = (whNvmSize)snprintf(label, sizeof(label), + "Label:%d", id); + len = (whNvmSize)snprintf(send_buf, sizeof(send_buf), + "Data:%d Counter:%d", id, i); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmAddObject( + ctx, id, access, flags, + label_len, (uint8_t*)label, + len, (uint8_t*)send_buf, + &server_rc)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetAvailable( + ctx, &server_rc, &avail_size, &avail_objects, + &reclaim_size, &reclaim_objects)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(prev_avail - 1 == avail_objects); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetMetadata( + ctx, id, &server_rc, + &gid, &gaccess, &gflags, &glen, + sizeof(glabel), (uint8_t*)glabel)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(gid == id); + WH_TEST_ASSERT_RETURN(gaccess == access); + WH_TEST_ASSERT_RETURN(gflags == flags); + WH_TEST_ASSERT_RETURN(glen == len); + WH_TEST_ASSERT_RETURN( + memcmp(glabel, label, label_len) == 0); + + memset(recv_buf, 0, sizeof(recv_buf)); + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmRead( + ctx, id, 0, glen, + &server_rc, &rlen, (uint8_t*)recv_buf)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(rlen == len); + WH_TEST_ASSERT_RETURN(memcmp(send_buf, recv_buf, len) == 0); + } + + /* Verify List can enumerate what we added without assuming + * this test owns unrelated objects that may already exist. */ + { + whNvmAccess list_access = WH_NVM_ACCESS_ANY; + whNvmFlags list_flags = WH_NVM_FLAGS_NONE; + whNvmId list_id = 0; + whNvmId list_count = 0; + whNvmId found = 0; + + do { + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmList( + ctx, list_access, list_flags, list_id, + &server_rc, &list_count, &list_id)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + if ((list_count > 0) && _nvmIdInRange( + list_id, NVM_CRUD_ID_BASE, NVM_CRUD_OBJECT_COUNT)) { + found++; + } + } while (list_count > 0); + + WH_TEST_ASSERT_RETURN(found == NVM_CRUD_OBJECT_COUNT); + } + + for (i = 0; i < NVM_CRUD_OBJECT_COUNT; i++) { + WH_TEST_RETURN_ON_FAIL(_destroyNvmId( + ctx, (whNvmId)(NVM_CRUD_ID_BASE + i))); + } + + /* Cleanup → Init round-trip leaves NVM in a usable state + * with no leaked objects. */ + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmCleanup(ctx, &server_rc)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmInit( + ctx, &server_rc, &client_id, &server_id)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetAvailable( + ctx, &server_rc, &avail_size, &avail_objects, + &reclaim_size, &reclaim_objects)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(avail_objects == baseline); + + return WH_ERROR_OK; +} + + +/* + * Exercise NvmRead's offset/length clamping and overflow + * safety. Adds a single object, runs the boundary cases, then + * destroys the object. + */ +static int _whTest_NvmOpsReadOob(whClientContext* ctx) +{ + const whNvmId id = NVM_OOB_ID; + int32_t server_rc = 0; + uint32_t client_id = 0; + uint32_t server_id = 0; + uint8_t buf[WOLFHSM_CFG_COMM_DATA_LEN]; + char label[WH_NVM_LABEL_LEN] = {0}; + char data[] = "OOB read clamping payload"; + whNvmSize label_len; + whNvmSize data_len = (whNvmSize)sizeof(data); + whNvmSize out_len; + whNvmId gid; + whNvmAccess gaccess; + whNvmFlags gflags; + whNvmSize meta_len; + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmInit( + ctx, &server_rc, &client_id, &server_id)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + label_len = (whNvmSize)snprintf(label, sizeof(label), "OOB:%u", id); + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmAddObject( + ctx, id, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONE, + label_len, (uint8_t*)label, + data_len, (const uint8_t*)data, + &server_rc)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + + /* Confirm metadata length so we can phrase the rest of the + * checks against meta_len rather than the raw write size. */ + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetMetadata( + ctx, id, &server_rc, + &gid, &gaccess, &gflags, &meta_len, + 0, NULL)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(meta_len == data_len); + + /* len = meta_len + 1 → clamp to meta_len */ + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmRead( + ctx, id, 0, (whNvmSize)(meta_len + 1), + &server_rc, &out_len, buf)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(out_len == meta_len); + + /* off=1, len=meta_len → clamp to meta_len - 1 */ + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmRead( + ctx, id, 1, meta_len, + &server_rc, &out_len, buf)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(out_len == (whNvmSize)(meta_len - 1)); + + /* off=meta_len-1, len=meta_len → clamp to 1 */ + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmRead( + ctx, id, (whNvmSize)(meta_len - 1), meta_len, + &server_rc, &out_len, buf)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(out_len == 1); + + /* off == meta_len, len = 0 → BADARGS (no readable bytes) */ + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmRead( + ctx, id, meta_len, 0, + &server_rc, &out_len, buf)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_BADARGS); + + /* off == UINT16_MAX → BADARGS. Regression for integer + * overflow in the offset+len bound check. */ + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmRead( + ctx, id, (whNvmSize)UINT16_MAX, 1, + &server_rc, &out_len, buf)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_BADARGS); + + /* off=meta_len/2, len=meta_len → clamp to meta_len - off. + * Verifies the overflow-safe form of the bounds check. */ + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmRead( + ctx, id, (whNvmSize)(meta_len / 2), meta_len, + &server_rc, &out_len, buf)); + WH_TEST_ASSERT_RETURN(server_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN( + out_len == (whNvmSize)(meta_len - (meta_len / 2))); + + return _destroyNvmId(ctx, id); +} + + +int whTest_NvmOps(whClientContext* ctx) +{ + WH_TEST_RETURN_ON_FAIL(_whTest_NvmOpsCrud(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_NvmOpsReadOob(ctx)); + + return WH_ERROR_OK; +} diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index 2867e82ef..92698c4c8 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -34,10 +34,13 @@ /* Test declarations and weak skip implementations. */ WH_TEST_DECL(whTest_Dma); WH_TEST_DECL(whTest_CertVerify); +WH_TEST_DECL(whTest_Counter); WH_TEST_DECL(whTest_CryptoAes); WH_TEST_DECL(whTest_CryptoEcc256); WH_TEST_DECL(whTest_CryptoSha256); WH_TEST_DECL(whTest_Echo); +WH_TEST_DECL(whTest_NvmDma); +WH_TEST_DECL(whTest_NvmOps); WH_TEST_DECL(whTest_ServerInfo); WH_TEST_DECL(whTest_WolfCryptTest); @@ -52,10 +55,13 @@ const whTestCase whTestsServer[] = { const size_t whTestsServerCount = sizeof(whTestsServer) / sizeof(whTestsServer[0]); const whTestCase whTestsClient[] = { + { "whTest_Counter", whTest_Counter }, { "whTest_CryptoAes", whTest_CryptoAes }, { "whTest_CryptoEcc256", whTest_CryptoEcc256 }, { "whTest_CryptoSha256", whTest_CryptoSha256 }, { "whTest_Echo", whTest_Echo }, + { "whTest_NvmDma", whTest_NvmDma }, + { "whTest_NvmOps", whTest_NvmOps }, { "whTest_ServerInfo", whTest_ServerInfo }, { "whTest_WolfCryptTest", whTest_WolfCryptTest }, };