Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions test-refactor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(<function>)`
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(<entry-point>)` 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_<Group><Case>`).

**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.

Expand All @@ -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 |
Expand All @@ -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` | |
Expand Down
177 changes: 177 additions & 0 deletions test-refactor/client-server/wh_test_counter.c
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
/*
* 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 <stdint.h>
#include <stddef.h>

#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;
}
Loading
Loading