diff --git a/test-refactor/README.md b/test-refactor/README.md index 88e068270..48c7d452f 100644 --- a/test-refactor/README.md +++ b/test-refactor/README.md @@ -84,6 +84,7 @@ Translated tests: | `wh_test_crypto.c::whTest_Crypto` | `client-server/wh_test_crypto_{aes,cmac,curve25519,ecc,ed25519,kdf,keypolicy,mldsa,rng,rsa,sha}.c::whTest_Crypto_*` | Client | Split into per-algorithm suites; key revocation is gated by `WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS`. Legacy ran FLASH and FLASH_LOG backends; the port runs the plain flash backend only -- FLASH_LOG re-run pending (see Known coverage gaps) | | `wh_test_crypto.c::whTest_CryptoKeyUsagePolicies` (AES CTR/ECB/GCM subset) | `client-server/wh_test_crypto_aes.c::whTest_CryptoAesKeyUsagePolicies` | Client | AES-CTR/ECB/GCM key usage enforcement (non-DMA and DMA variants) | | `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::_testClientCounter` | `client-server/wh_test_counter.c::whTest_Counter` | Client | NVM counter API (reset/init/increment/read/destroy, saturation, slot reuse). Config-agnostic: the counter messages do not use the cryptocb, so it runs unchanged under the DMA and non-DMA builds. The legacy global-NVM-empty check is replaced by an object census bracketing the test, so it is robust to the shared server holding other tests' objects | | `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, whTest_NvmFlash_Recovery}` | `posix/wh_test_nvm_flash.c::{whTest_NvmAddOverwriteDestroy, whTest_NvmFlashLog, whTest_NvmRecovery}` | POSIX port-specific (`whTestGroup_RunOne`) | remove ramsim coupling and migrate to server group; flash-log backend exercised by `whTest_NvmFlashLog` (skipped unless `WOLFHSM_CFG_SERVER_NVM_FLASH_LOG`) | @@ -99,7 +100,7 @@ Not yet migrated (still live in `wolfHSM/test/`): | Legacy (`wolfHSM/test/`) | Notes | |---|---| | `wh_test_comm.c::whTest_Comm` | Pthread mem/tcp/shmem variants only; sequential mem variant has been ported | -| `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 client-side coverage (NVM object add/read/list/destroy, including the DMA slice) still needs to be split out as new tests. The counter API is ported (see `whTest_Counter`); the sequential test is dropped | | `wh_test_crypto.c::whTest_Crypto` | Remaining crypto coverage not yet split out: the AES async family (comm-buffer `whTest_CryptoAesAsync`/`AesAsyncKat` + DMA `whTest_CryptoAesDmaAsync`/`AesDmaAsyncKat`, round-trip & KAT). ECC DMA export-public and the ML-DSA wolfCrypt-API path are now migrated. | | `wh_test_crypto.c::whTest_KeyCache`, `whTest_NonExportableKeystore` | Keystore tests (key-cache lifecycle and non-exportable-flag enforcement) dispatched from the legacy `whTest_Crypto`. The per-algorithm suites use `wh_Client_KeyCache`, but these dedicated keystore tests are not yet split out. | | `wh_test_crypto_affinity.c::whTest_CryptoAffinity` | | 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..d67fa0cd9 --- /dev/null +++ b/test-refactor/client-server/wh_test_counter.c @@ -0,0 +1,162 @@ +/* + * 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 + * + * NVM monotonic counter round-trips routed through the server. Covers + * reset/init/increment/read/destroy, increment saturation at the uint32_t + * max, and slot reuse across more counters than the NVM directory holds. + * + * The counter API is carried by dedicated counter messages, not the + * cryptocb, so it is identical across the DMA and non-DMA builds. The + * WOLFHSM_CFG_DMA build option is covered by compiling and running this + * test under that configuration, not by toggling the client DMA mode. + */ + +#include + +#include "wolfhsm/wh_settings.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_client.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +/* Increment well past the NVM directory size to confirm a single counter + * reuses one slot rather than leaking an object per increment. */ +#define WH_TEST_COUNTER_INCREMENTS (2 * WOLFHSM_CFG_NVM_OBJECT_COUNT) + +/* Reads the current count of free NVM objects, failing on a server error. */ +static int _whTest_CounterAvailObjects(whClientContext* ctx, + whNvmId* outAvailObjects) +{ + int32_t serverRc = 0; + uint32_t availSize = 0; + uint32_t reclaimSize = 0; + whNvmId availObjects = 0; + whNvmId reclaimObjects = 0; + + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmGetAvailable( + ctx, &serverRc, &availSize, &availObjects, &reclaimSize, + &reclaimObjects)); + WH_TEST_ASSERT_RETURN(serverRc == WH_ERROR_OK); + + *outAvailObjects = availObjects; + return WH_ERROR_OK; +} + +/* Reset, increment past the directory size, and saturate a single counter. */ +static int _whTest_CounterIncrement(whClientContext* ctx) +{ + const whNvmId counterId = 1; + const uint32_t maxCounterVal = 0xFFFFFFFF; + size_t i = 0; + uint32_t counter = 0; + + /* A fresh counter starts at zero. */ + WH_TEST_RETURN_ON_FAIL(wh_Client_CounterReset(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == 0); + + /* Each increment advances by one and read-back matches, with no object + * leak across more increments than the directory could hold. */ + for (i = 0; i < WH_TEST_COUNTER_INCREMENTS; i++) { + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterIncrement(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == i + 1); + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterRead(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == i + 1); + } + + WH_TEST_RETURN_ON_FAIL(wh_Client_CounterReset(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == 0); + + /* Init near the max and confirm increments saturate instead of rolling. */ + counter = maxCounterVal; + WH_TEST_RETURN_ON_FAIL(wh_Client_CounterInit(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == maxCounterVal); + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterIncrement(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == maxCounterVal); + + WH_TEST_RETURN_ON_FAIL( + wh_Client_CounterIncrement(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == maxCounterVal); + + WH_TEST_RETURN_ON_FAIL(wh_Client_CounterRead(ctx, counterId, &counter)); + WH_TEST_ASSERT_RETURN(counter == maxCounterVal); + + 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; +} + +/* Create and destroy counters across many ids, confirming destroy frees the + * slot and a destroyed counter can no longer be read. */ +static int _whTest_CounterDestroy(whClientContext* ctx) +{ + size_t i = 0; + uint32_t counter = 0; + + for (i = 1; i < WH_TEST_COUNTER_INCREMENTS; 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)); + + /* A destroyed counter must not be readable. */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_NOTFOUND == + wh_Client_CounterRead(ctx, (whNvmId)i, &counter)); + } + + return WH_ERROR_OK; +} + +/* + * NVM counter API test. Brackets the sub-tests with an NVM object census so + * the no-leak guarantee holds regardless of any objects other tests left + * behind in the shared server. + */ +int whTest_Counter(whClientContext* ctx) +{ + whNvmId baselineObjects = 0; + whNvmId finalObjects = 0; + + WH_TEST_PRINT("Testing NVM counters...\n"); + + WH_TEST_RETURN_ON_FAIL( + _whTest_CounterAvailObjects(ctx, &baselineObjects)); + + WH_TEST_RETURN_ON_FAIL(_whTest_CounterIncrement(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CounterDestroy(ctx)); + + /* Reset and destroy must not leak NVM objects. */ + WH_TEST_RETURN_ON_FAIL(_whTest_CounterAvailObjects(ctx, &finalObjects)); + WH_TEST_ASSERT_RETURN(finalObjects == baselineObjects); + + return WH_ERROR_OK; +} diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index 109536f2b..e3e9cfe58 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -67,6 +67,7 @@ WH_TEST_DECL(whTest_CryptoSha256); WH_TEST_DECL(whTest_She); WH_TEST_DECL(whTest_SheMasterEcuKeyFallback); WH_TEST_DECL(whTest_SheReqSizeChecking); +WH_TEST_DECL(whTest_Counter); WH_TEST_DECL(whTest_Echo); WH_TEST_DECL(whTest_ServerInfo); WH_TEST_DECL(whTest_WolfCryptTest); @@ -118,6 +119,7 @@ const whTestCase whTestsClient[] = { { "whTest_CryptoRsaBufferTooSmall", whTest_CryptoRsaBufferTooSmall }, { "whTest_CryptoSha256", whTest_CryptoSha256 }, { "whTest_She", whTest_She }, + { "whTest_Counter", whTest_Counter }, { "whTest_Echo", whTest_Echo }, { "whTest_ServerInfo", whTest_ServerInfo }, { "whTest_WolfCryptTest", whTest_WolfCryptTest },