diff --git a/test-refactor/README.md b/test-refactor/README.md index 54fac5cb6..8037ae079 100644 --- a/test-refactor/README.md +++ b/test-refactor/README.md @@ -99,6 +99,8 @@ Translated tests: | `wh_test_auth.c` (`whTest_AuthMEM` / `whTest_AuthTest` sub-tests) | `client-server/wh_test_auth.c::{whTest_AuthBadArgs, whTest_AuthLogin, whTest_AuthLogout, whTest_AuthAddUser, whTest_AuthDeleteUser, whTest_AuthSetPermissions, whTest_AuthSetCredentials, whTest_AuthRequestAuthorization}` | Client | Under `WOLFHSM_CFG_ENABLE_AUTHENTICATION` the POSIX server installs an auth context + admin user and the client logs in as admin at connect, so the ordinary client tests run authorized; each auth test brackets its own session (logout to start clean, restore admin on exit). Uses the blocking client API; the legacy own-server setup and single-thread manual-pump are dropped. Build with `make AUTH=1`. The TCP/client-only variant (`whTest_AuthTCP`) is not ported | | `wh_test_she.c` (`whTest_SheMasterEcuKeyFallback`, `whTest_SheReqSizeChecking`) | `server/wh_test_she_server.c::{whTest_SheMasterEcuKeyFallback, whTest_SheReqSizeChecking}` | Server | server-internal checks reworked to use the shared server context; the POSIX server config gains a `whServerSheContext` under `WOLFHSM_CFG_SHE_EXTENSION` | | `wh_test_she.c::whTest_She` (client flows) | `client-server/wh_test_she.c::whTest_She` | Client | SHE UID/secure-boot state is one-shot per server lifetime, so the three legacy client flows are folded into one test that does `SetUid` plus a single comm-boundary-sized secure boot, then the load-key vectors, UID handling, RND, ECB/CBC/MAC, and write-protect rejection -- all of which only need UID set and secure boot complete. Build with `make SHE=1` | +| `wh_test_log.c::whTest_Log` (frontend, macros, ring buffer, mock/ringbuf generic harness) | `misc/wh_test_log.c::whTest_Log` | Misc | Portable subset: frontend API, log macros, formatted macros, ring buffer backend, and the backend-agnostic harness against the mock and ring buffer backends. The harness itself lives in `misc/wh_test_log_backend.h` so the POSIX port reuses it | +| `wh_test_log.c` (POSIX file backend, concurrent, client/server) | `posix/wh_test_log_posix.c::{whTest_LogPosixFile_Generic, whTest_LogPosixFile, whTest_LogPosixFileConcurrent, whTest_LogClientServerMemTransport}` | POSIX port-specific (`whTestGroup_RunOne`) | POSIX file backend (via the shared harness and directly), multi-threaded concurrent writers, and a client/server log smoke test over the mem transport. Need pthreads and the host file system, so they run from the port main, not the portable registry | Not yet migrated (still live in `wolfHSM/test/`): @@ -111,7 +113,6 @@ Not yet migrated (still live in `wolfHSM/test/`): | `wh_test_crypto_affinity.c::whTest_CryptoAffinity` | | | `wh_test_keywrap.c::whTest_KeyWrapClientConfig` | | | `wh_test_lock.c::whTest_LockConfig`, `whTest_LockPosix` | `whTest_LockConfig` to be reworked to fit the Misc group, likely with a context param. | -| `wh_test_log.c::whTest_Log`, `whTest_LogBackend_RunAll` | `whTest_LogBackend_RunAll` to be reworked to fit the Misc group, likely with a context param. | | `wh_test_timeout.c::whTest_TimeoutPosix` | | | `wh_test_server_img_mgr.c::whTest_ServerImgMgr` | | | `wh_test_nvmflags.c::whTest_NvmFlags` | | diff --git a/test-refactor/misc/wh_test_log.c b/test-refactor/misc/wh_test_log.c new file mode 100644 index 000000000..f3ab5d9ff --- /dev/null +++ b/test-refactor/misc/wh_test_log.c @@ -0,0 +1,1141 @@ +/* + * 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/misc/wh_test_log.c + * + * Portable logging tests from legacy test/wh_test_log.c: frontend API, + * log macros, the ring buffer backend, and the backend-agnostic harness + * run against the mock and ring buffer backends. The POSIX file backend, + * concurrent, and client/server log tests live in the POSIX port. + */ + +#include "wolfhsm/wh_settings.h" + +#ifdef WOLFHSM_CFG_LOGGING + +#include +#include +#include + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_log.h" +#include "wolfhsm/wh_log_ringbuf.h" +#include "wolfhsm/wh_log_printf.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" +#include "wh_test_log_backend.h" + +#define ITERATE_STOP_MAGIC 99 +#define ITERATE_STOP_COUNT 3 + +/* Mock log backend definitions */ + +#define MOCK_LOG_MAX_ENTRIES 16 + +typedef struct { + whLogEntry entries[MOCK_LOG_MAX_ENTRIES]; + int count; + int init_called; + int cleanup_called; +} mockLogContext; + +static int mockLog_Init(void* context, const void* config) +{ + mockLogContext* ctx = (mockLogContext*)context; + (void)config; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + memset(ctx, 0, sizeof(*ctx)); + ctx->init_called = 1; + return 0; +} + +static int mockLog_Cleanup(void* context) +{ + mockLogContext* ctx = (mockLogContext*)context; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + ctx->cleanup_called = 1; + return 0; +} + +static int mockLog_AddEntry(void* context, const whLogEntry* entry) +{ + mockLogContext* ctx = (mockLogContext*)context; + + if (ctx == NULL || entry == NULL) { + return WH_ERROR_BADARGS; + } + + if (ctx->count >= MOCK_LOG_MAX_ENTRIES) { + return WH_ERROR_NOSPACE; + } + + memcpy(&ctx->entries[ctx->count], entry, sizeof(whLogEntry)); + ctx->count++; + return 0; +} + +/* Test-specific export structure for mock backend */ +typedef struct { + int (*callback)(void* arg, const whLogEntry* entry); + void* callback_arg; +} mockLogExportArg; + +static int mockLog_Export(void* context, void* export_arg) +{ + mockLogContext* ctx = (mockLogContext*)context; + mockLogExportArg* args = (mockLogExportArg*)export_arg; + int i; + int ret; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + /* If no export args or callback, just succeed */ + if (args == NULL || args->callback == NULL) { + return 0; + } + + /* Iterate and call user's callback */ + for (i = 0; i < ctx->count; i++) { + ret = args->callback(args->callback_arg, &ctx->entries[i]); + if (ret != 0) { + return ret; + } + } + + return 0; +} + +static int mockLog_Iterate(void* context, whLogIterateCb iterate_cb, + void* iterate_arg) +{ + mockLogContext* ctx = (mockLogContext*)context; + int i; + int ret; + + if (ctx == NULL || iterate_cb == NULL) { + return WH_ERROR_BADARGS; + } + + /* Iterate and call user's callback */ + for (i = 0; i < ctx->count; i++) { + ret = iterate_cb(iterate_arg, &ctx->entries[i]); + if (ret != 0) { + return ret; + } + } + + return 0; +} + +static int mockLog_Clear(void* context) +{ + mockLogContext* ctx = (mockLogContext*)context; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + ctx->count = 0; + memset(ctx->entries, 0, sizeof(ctx->entries)); + return 0; +} + +static whLogCb mockLogCb = { + .Init = mockLog_Init, + .Cleanup = mockLog_Cleanup, + .AddEntry = mockLog_AddEntry, + .Export = mockLog_Export, + .Iterate = mockLog_Iterate, + .Clear = mockLog_Clear, +}; + + +/* Helper for iterate callback - counts entries */ +static int iterateCallbackCount(void* arg, const whLogEntry* entry) +{ + int* count = (int*)arg; + (void)entry; + (*count)++; + return 0; +} + +/* Helper callback - stops iteration after a fixed number of entries */ +static int iterateCallbackStopAt2(void* arg, const whLogEntry* entry) +{ + int* count = (int*)arg; + (void)entry; + (*count)++; + if (*count >= ITERATE_STOP_COUNT) { + /* Custom return code to test propagation */ + return ITERATE_STOP_MAGIC; + } + return WH_ERROR_OK; +} + +/* Helper for iterate callback - validates specific entries */ +typedef struct { + int count; + int valid; /* Set to 0 if entry doesn't match expected pattern */ +} iterateValidationArgs; + +static int iterateCallbackValidator(void* arg, const whLogEntry* entry) +{ + iterateValidationArgs* args = (iterateValidationArgs*)arg; + char expected[32]; + + /* Expect messages like "Entry 0", "Entry 1", etc. */ + snprintf(expected, sizeof(expected), "Entry %d", args->count); + + if (strncmp(entry->msg, expected, WOLFHSM_CFG_LOG_MSG_MAX) != 0) { + args->valid = 0; + } + if (entry->level != WH_LOG_LEVEL_INFO) { + args->valid = 0; + } + + args->count++; + return 0; +} + +/* Frontend API test using mock backend */ +static int whTest_LogFrontend(void) +{ + whLogContext logCtx; + mockLogContext mockCtx; + whLogConfig logConfig; + int iterate_count = 0; + int i; + mockLogExportArg exportArgs; + iterateValidationArgs valArgs; + whLogEntry entry = {0}; + + /* Setup */ + memset(&logCtx, 0, sizeof(logCtx)); + memset(&mockCtx, 0, sizeof(mockCtx)); + logConfig.cb = &mockLogCb; + logConfig.context = &mockCtx; + logConfig.config = NULL; + + /* Test: NULL input rejections */ + WH_TEST_ASSERT_RETURN(wh_Log_Init(NULL, &logConfig) == WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(wh_Log_Init(&logCtx, NULL) == WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(wh_Log_AddEntry(NULL, &entry) == WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(wh_Log_AddEntry(&logCtx, NULL) == WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(wh_Log_Cleanup(NULL) == WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(wh_Log_Clear(NULL) == WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(wh_Log_Export(NULL, &exportArgs) == WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(wh_Log_Iterate(NULL, iterateCallbackCount, + &iterate_count) == WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(wh_Log_Iterate(&logCtx, NULL, &iterate_count) == + WH_ERROR_BADARGS); + + /* Initialize the log context */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig)); + WH_TEST_ASSERT_RETURN(mockCtx.init_called == 1); + + /* Test: Fill buffer completely and verify all entries */ + for (i = 0; i < MOCK_LOG_MAX_ENTRIES; i++) { + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Entry %d", i); + } + WH_TEST_ASSERT_RETURN(mockCtx.count == MOCK_LOG_MAX_ENTRIES); + + /* Verify each entry has correct content */ + for (i = 0; i < MOCK_LOG_MAX_ENTRIES; i++) { + char expected[32]; + snprintf(expected, sizeof(expected), "Entry %d", i); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[i].msg, expected, + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[i].level == WH_LOG_LEVEL_INFO); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[i].file, __FILE__, + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[i].function, __func__, + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[i].line > 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[i].timestamp > 0); + } + + /* Test: Export works */ + iterate_count = 0; + exportArgs.callback = iterateCallbackCount; + exportArgs.callback_arg = &iterate_count; + WH_TEST_RETURN_ON_FAIL(wh_Log_Export(&logCtx, &exportArgs)); + WH_TEST_ASSERT_RETURN(iterate_count == MOCK_LOG_MAX_ENTRIES); + + /* Test: Iterate works and iterates over expected elements */ + valArgs.count = 0; + valArgs.valid = 1; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackValidator, &valArgs)); + WH_TEST_ASSERT_RETURN(valArgs.count == MOCK_LOG_MAX_ENTRIES); + WH_TEST_ASSERT_RETURN(valArgs.valid == 1); + + /* Test: Clear works */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx)); + WH_TEST_ASSERT_RETURN(mockCtx.count == 0); + + /* Verify buffer is actually empty via iterate */ + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == 0); + + /* Test: Can write after clear */ + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 0"); + WH_TEST_ASSERT_RETURN(mockCtx.count == 1); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].msg, "Entry 0", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + + /* Cleanup */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx)); + WH_TEST_ASSERT_RETURN(mockCtx.cleanup_called == 1); + + return 0; +} + +/* Test helper macros using mock backend */ +static int whTest_LogMacros(void) +{ + whLogContext logCtx; + mockLogContext mockCtx; + whLogConfig logConfig; + + /* Setup */ + memset(&logCtx, 0, sizeof(logCtx)); + memset(&mockCtx, 0, sizeof(mockCtx)); + logConfig.cb = &mockLogCb; + logConfig.context = &mockCtx; + logConfig.config = NULL; + + WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig)); + + /* Test: WH_LOG_INFO creates proper entry with __FILE__/__LINE__ */ + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Info message"); + WH_TEST_ASSERT_RETURN(mockCtx.count == 1); + WH_TEST_ASSERT_RETURN(mockCtx.entries[0].level == WH_LOG_LEVEL_INFO); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].msg, "Info message", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].file, __FILE__, + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].function, __func__, + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[0].line > 0); + + /* Test: WH_LOG_ERROR creates proper entry */ + WH_LOG(&logCtx, WH_LOG_LEVEL_ERROR, "Error message"); + WH_TEST_ASSERT_RETURN(mockCtx.count == 2); + WH_TEST_ASSERT_RETURN(mockCtx.entries[1].level == WH_LOG_LEVEL_ERROR); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[1].msg, "Error message", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + + /* Test: WH_LOG_SECEVENT creates proper entry */ + WH_LOG(&logCtx, WH_LOG_LEVEL_SECEVENT, "Security event"); + WH_TEST_ASSERT_RETURN(mockCtx.count == 3); + WH_TEST_ASSERT_RETURN(mockCtx.entries[2].level == WH_LOG_LEVEL_SECEVENT); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[2].msg, "Security event", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + + /* Test: WH_LOG_F creates proper entry with runtime string */ + { + const char* runtime_info = "Runtime info message"; + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "%s", runtime_info); + WH_TEST_ASSERT_RETURN(mockCtx.count == 4); + WH_TEST_ASSERT_RETURN(mockCtx.entries[3].level == WH_LOG_LEVEL_INFO); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[3].msg, runtime_info, + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[3].file, __FILE__, + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[3].function, __func__, + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[3].line > 0); + } + + /* Test: WH_LOG_F with empty string */ + { + const char* empty_str = ""; + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "%s", empty_str); + WH_TEST_ASSERT_RETURN(mockCtx.count == 5); + WH_TEST_ASSERT_RETURN(mockCtx.entries[4].level == WH_LOG_LEVEL_INFO); + WH_TEST_ASSERT_RETURN( + strncmp(mockCtx.entries[4].msg, "", WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[4].msg_len == 0); + } + + /* Test: WH_LOG_F with string exactly at max length */ + { + char exact_msg[WOLFHSM_CFG_LOG_MSG_MAX]; + int i; + /* Fill with 'B' characters, leaving room for null terminator */ + for (i = 0; i < WOLFHSM_CFG_LOG_MSG_MAX - 1; i++) { + exact_msg[i] = 'B'; + } + exact_msg[WOLFHSM_CFG_LOG_MSG_MAX - 1] = '\0'; + + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "%s", exact_msg); + WH_TEST_ASSERT_RETURN(mockCtx.count == 6); + WH_TEST_ASSERT_RETURN(mockCtx.entries[5].level == WH_LOG_LEVEL_INFO); + /* Message should be exactly max length - 1 */ + WH_TEST_ASSERT_RETURN(mockCtx.entries[5].msg_len == + WOLFHSM_CFG_LOG_MSG_MAX - 1); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[5].msg, exact_msg, + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + /* Should be null-terminated */ + WH_TEST_ASSERT_RETURN( + mockCtx.entries[5].msg[mockCtx.entries[5].msg_len] == '\0'); + } + + /* Test: Log assert with true condition doesn't add log entry */ + WH_LOG_ASSERT(&logCtx, WH_LOG_LEVEL_ERROR, 1, "Assert Message"); + WH_TEST_ASSERT_RETURN(mockCtx.count == 6); + + /* Test: Log assert with false condition adds log entry */ + WH_LOG_ASSERT(&logCtx, WH_LOG_LEVEL_ERROR, 0, "Assert Message"); + WH_TEST_ASSERT_RETURN(mockCtx.count == 7); + WH_TEST_ASSERT_RETURN(mockCtx.entries[6].level == WH_LOG_LEVEL_ERROR); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[6].msg, "Assert Message", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + + /* Test: Timestamp is populated */ + WH_TEST_ASSERT_RETURN(mockCtx.entries[0].timestamp > 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[1].timestamp >= + mockCtx.entries[0].timestamp); + + /* Cleanup */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx)); + + return 0; +} + + +static int whTest_LogFormattedMacros(void) +{ + whLogContext logCtx; + mockLogContext mockCtx; + whLogConfig logConfig; + + /* Setup */ + memset(&logCtx, 0, sizeof(logCtx)); + memset(&mockCtx, 0, sizeof(mockCtx)); + logConfig.cb = &mockLogCb; + logConfig.context = &mockCtx; + logConfig.config = NULL; + + WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig)); + + /* Test: WH_LOG_INFO_F with single integer */ + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Value: %d", 42); + WH_TEST_ASSERT_RETURN(mockCtx.count == 1); + WH_TEST_ASSERT_RETURN(mockCtx.entries[0].level == WH_LOG_LEVEL_INFO); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].msg, "Value: 42", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[0].msg_len == 9); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].file, __FILE__, + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].function, __func__, + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[0].line > 0); + + /* Test: WH_LOG_ERROR_F with multiple integers */ + WH_LOG_F(&logCtx, WH_LOG_LEVEL_ERROR, "x=%d, y=%d", 10, 20); + WH_TEST_ASSERT_RETURN(mockCtx.count == 2); + WH_TEST_ASSERT_RETURN(mockCtx.entries[1].level == WH_LOG_LEVEL_ERROR); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[1].msg, "x=10, y=20", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[1].msg_len == 10); + + /* Test: WH_LOG_SECEVENT_F with string formatting */ + WH_LOG_F(&logCtx, WH_LOG_LEVEL_SECEVENT, "User: %s", "admin"); + WH_TEST_ASSERT_RETURN(mockCtx.count == 3); + WH_TEST_ASSERT_RETURN(mockCtx.entries[2].level == WH_LOG_LEVEL_SECEVENT); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[2].msg, "User: admin", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[2].msg_len == 11); + + /* Test: WH_LOG_INFO_F with mixed types */ + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Status %d: %s", 404, "Not Found"); + WH_TEST_ASSERT_RETURN(mockCtx.count == 4); + WH_TEST_ASSERT_RETURN(mockCtx.entries[3].level == WH_LOG_LEVEL_INFO); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[3].msg, + "Status 404: Not Found", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[3].msg_len == 21); + + /* Test: WH_LOG_ERROR_F with hex formatting */ + WH_LOG_F(&logCtx, WH_LOG_LEVEL_ERROR, "Addr: 0x%08x", 0xDEADBEEF); + WH_TEST_ASSERT_RETURN(mockCtx.count == 5); + WH_TEST_ASSERT_RETURN(mockCtx.entries[4].level == WH_LOG_LEVEL_ERROR); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[4].msg, "Addr: 0xdeadbeef", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[4].msg_len == 16); + + /* Test: WH_LOG_F generic macro with level */ + WH_LOG_F(&logCtx, WH_LOG_LEVEL_ERROR, "val=%d", 123); + WH_TEST_ASSERT_RETURN(mockCtx.count == 6); + WH_TEST_ASSERT_RETURN(mockCtx.entries[5].level == WH_LOG_LEVEL_ERROR); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[5].msg, "val=123", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[5].msg_len == 7); + + /* Test: Long formatted output (truncation) */ + { + char expected[WOLFHSM_CFG_LOG_MSG_MAX]; + char longStr[WOLFHSM_CFG_LOG_MSG_MAX + 100]; + + /* Create a string longer than the log buffer limit and pass via %s */ + memset(longStr, 'X', sizeof(longStr) - 1); + longStr[sizeof(longStr) - 1] = '\0'; + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "%s", longStr); + + WH_TEST_ASSERT_RETURN(mockCtx.count == 7); + WH_TEST_ASSERT_RETURN(mockCtx.entries[6].level == WH_LOG_LEVEL_INFO); + /* Message should be truncated to WOLFHSM_CFG_LOG_MSG_MAX - 1 */ + WH_TEST_ASSERT_RETURN(mockCtx.entries[6].msg_len == + WOLFHSM_CFG_LOG_MSG_MAX - 1); + /* Should be null-terminated */ + WH_TEST_ASSERT_RETURN( + mockCtx.entries[6].msg[mockCtx.entries[6].msg_len] == '\0'); + + /* Verify the beginning of the truncated message */ + memset(expected, 'X', WOLFHSM_CFG_LOG_MSG_MAX - 1); + expected[WOLFHSM_CFG_LOG_MSG_MAX - 1] = '\0'; + /* Compare up to the truncated length */ + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[6].msg, expected, + WOLFHSM_CFG_LOG_MSG_MAX - 1) == 0); + } + + /* Test: Format with multiple argument types */ + WH_LOG_F(&logCtx, WH_LOG_LEVEL_SECEVENT, "ID=%u, Name=%s, Code=0x%04x", 100, + "test", 0xABCD); + WH_TEST_ASSERT_RETURN(mockCtx.count == 8); + WH_TEST_ASSERT_RETURN(mockCtx.entries[7].level == WH_LOG_LEVEL_SECEVENT); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[7].msg, + "ID=100, Name=test, Code=0xabcd", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[7].msg_len == 30); + + /* Test: Pointer formatting */ + { + void* ptr = (void*)0x1234; + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Pointer: %p", ptr); + WH_TEST_ASSERT_RETURN(mockCtx.count == 9); + WH_TEST_ASSERT_RETURN(mockCtx.entries[8].level == WH_LOG_LEVEL_INFO); + /* Just verify message contains "Pointer:" and is non-empty */ + WH_TEST_ASSERT_RETURN(mockCtx.entries[8].msg_len > 9); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[8].msg, "Pointer: ", 9) == + 0); + } + + /* Test: Character formatting */ + WH_LOG_F(&logCtx, WH_LOG_LEVEL_ERROR, "Char: %c", 'X'); + WH_TEST_ASSERT_RETURN(mockCtx.count == 10); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[9].msg, "Char: X", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[9].msg_len == 7); + + + /* Test assert formatting with simple int args */ + WH_LOG_ASSERT_F(&logCtx, WH_LOG_LEVEL_ERROR, 0, "Assert Message: %d", 42); + WH_TEST_ASSERT_RETURN(mockCtx.count == 11); + WH_TEST_ASSERT_RETURN(mockCtx.entries[10].level == WH_LOG_LEVEL_ERROR); + WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[10].msg, "Assert Message: 42", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + + /* Test: Timestamp is populated */ + WH_TEST_ASSERT_RETURN(mockCtx.entries[0].timestamp > 0); + WH_TEST_ASSERT_RETURN(mockCtx.entries[1].timestamp >= + mockCtx.entries[0].timestamp); + + /* Cleanup */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx)); + + return 0; +} + + +/* + * Generic backend test - smoke test for basic operations + */ +static int whTest_LogBackend_BasicOperations(whTestLogBackendTestConfig* cfg) +{ + whLogContext logCtx; + whLogConfig logConfig; + void* backend_context; + int iterate_count; + + /* Use driver-provided backend context */ + backend_context = cfg->backend_context; + WH_TEST_ASSERT_RETURN(backend_context != NULL); + memset(backend_context, 0, cfg->config_size); + + /* Setup log configuration */ + memset(&logCtx, 0, sizeof(logCtx)); + logConfig.cb = cfg->cb; + logConfig.context = backend_context; + logConfig.config = cfg->config; + + /* Test: Init with valid config */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig)); + + /* Test: Add single entry */ + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Single entry"); + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == 1); + + /* Test: Add multiple entries (3) */ + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 0"); + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 1"); + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 2"); + + /* Verify count via Iterate */ + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == 4); /* 1 + 3 */ + + /* Test: Clear and verify empty */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx)); + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == 0); + + /* Test: Cleanup */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx)); + + return WH_ERROR_OK; +} + +/* + * Generic backend test - Capacity Handling + * Tests behavior when buffer reaches capacity + */ +static int whTest_LogBackend_CapacityHandling(whTestLogBackendTestConfig* cfg) +{ + whLogContext logCtx; + whLogConfig logConfig; + void* backend_context; + int iterate_count; + int i; + + /* Skip if capacity is unlimited */ + if (cfg->expected_capacity < 0) { + WH_TEST_PRINT(" Skipped (unlimited capacity)\n"); + return WH_ERROR_OK; + } + + /* Use driver-provided backend context */ + backend_context = cfg->backend_context; + WH_TEST_ASSERT_RETURN(backend_context != NULL); + memset(backend_context, 0, cfg->config_size); + + /* Setup and init */ + memset(&logCtx, 0, sizeof(logCtx)); + logConfig.cb = cfg->cb; + logConfig.context = backend_context; + logConfig.config = cfg->config; + WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig)); + + /* Test: Fill to capacity */ + for (i = 0; i < cfg->expected_capacity; i++) { + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Entry %d", i); + } + + /* Verify count == capacity */ + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == cfg->expected_capacity); + + /* Test: Add 10 more entries (overflow) */ + for (i = 0; i < 10; i++) { + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Overflow %d", i); + } + + /* Verify count still == capacity (overflow behavior) */ + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == cfg->expected_capacity); + + WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx)); + return WH_ERROR_OK; +} + +/* + * Generic backend test - Message Handling + * Tests various message sizes and special characters + */ +static int whTest_LogBackend_MessageHandling(whTestLogBackendTestConfig* cfg) +{ + whLogContext logCtx; + whLogConfig logConfig; + void* backend_context; + int iterate_count; + char maxMsg[WOLFHSM_CFG_LOG_MSG_MAX]; + + /* Use driver-provided backend context */ + backend_context = cfg->backend_context; + WH_TEST_ASSERT_RETURN(backend_context != NULL); + memset(backend_context, 0, cfg->config_size); + + /* Setup and init */ + memset(&logCtx, 0, sizeof(logCtx)); + logConfig.cb = cfg->cb; + logConfig.context = backend_context; + logConfig.config = cfg->config; + WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig)); + + /* Test: Empty message */ + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, ""); + + /* Test: Short message */ + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Hi"); + + /* Test: Max size message (255 chars + null) */ + memset(maxMsg, 'A', sizeof(maxMsg) - 1); + maxMsg[sizeof(maxMsg) - 1] = '\0'; + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "%s", maxMsg); + + + /* Verify all entries were added */ + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == 3); + + WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx)); + return WH_ERROR_OK; +} + +/* + * Generic backend test - Iteration + * Tests iteration behavior in various scenarios + */ +static int whTest_LogBackend_Iteration(whTestLogBackendTestConfig* cfg) +{ + whLogContext logCtx; + whLogConfig logConfig; + void* backend_context; + int iterate_count; + int ret; + + /* Use driver-provided backend context */ + backend_context = cfg->backend_context; + WH_TEST_ASSERT_RETURN(backend_context != NULL); + memset(backend_context, 0, cfg->config_size); + + /* Setup and init */ + memset(&logCtx, 0, sizeof(logCtx)); + logConfig.cb = cfg->cb; + logConfig.context = backend_context; + logConfig.config = cfg->config; + WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig)); + + /* Clear first to ensure clean state for persistent backends */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx)); + + /* Test: Iterate empty log */ + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == 0); + + /* Test: Iterate single entry */ + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Single"); + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == 1); + + /* Test: Iterate 3 entries */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx)); + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 1"); + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 2"); + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 3"); + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == 3); + + /* Test: Early termination (callback returns magic number after + * fixed number of entries) */ + iterate_count = 0; + ret = wh_Log_Iterate(&logCtx, iterateCallbackStopAt2, &iterate_count); + WH_TEST_ASSERT_RETURN(ret == ITERATE_STOP_MAGIC); + WH_TEST_ASSERT_RETURN(iterate_count == ITERATE_STOP_COUNT); + + /* Test: Iterate after clear */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx)); + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == 0); + + WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx)); + return WH_ERROR_OK; +} + +/* + * Generic backend tests - Main runner + * Executes all generic backend tests on a given backend test config + */ +int whTest_LogBackend_RunAll(whTestLogBackendTestConfig* cfg) +{ + int ret = 0; + + /* Call setup hook if provided */ + if (cfg->setup != NULL) { + if (cfg->setup(&cfg->test_context) != 0) { + WH_ERROR_PRINT("Setup hook failed\n"); + return WH_TEST_FAIL; + } + } + + /* Run all test suites */ + ret = whTest_LogBackend_BasicOperations(cfg); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("whTest_LogBackend_BasicOperations returned %d\n", ret); + } + + if (ret == WH_ERROR_OK) { + ret = whTest_LogBackend_CapacityHandling(cfg); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("whTest_LogBackend_CapacityHandling returned %d\n", + ret); + } + } + + if (ret == WH_ERROR_OK) { + ret = whTest_LogBackend_MessageHandling(cfg); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("whTest_LogBackend_MessageHandling returned %d\n", + ret); + } + } + + if (ret == WH_ERROR_OK) { + ret = whTest_LogBackend_Iteration(cfg); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("whTest_LogBackend_Iteration returned %d\n", ret); + } + } + + /* Call teardown hook if provided */ + if (cfg->teardown != NULL) { + if (cfg->teardown(cfg->test_context) != 0) { + WH_ERROR_PRINT("Teardown hook failed\n"); + return WH_TEST_FAIL; + } + } + + return ret; +} + + +/* Ring buffer backend tests */ +static int whTest_LogRingbuf(void) +{ + whLogContext logCtx; + whLogRingbufContext ringbufCtx; + whLogRingbufConfig ringbufConfig; + whLogConfig logConfig; + int i; + int iterate_count; + uint32_t capacity; + /* Backend storage for ring buffer */ + const size_t numLogEntries = 32; + whLogEntry ringbuf_buffer[32]; + whLogCb ringbufCb = WH_LOG_RINGBUF_CB; + + /* Setup ring buffer backend */ + memset(&logCtx, 0, sizeof(logCtx)); + memset(&ringbufCtx, 0, sizeof(ringbufCtx)); + memset(&ringbuf_buffer, 0, sizeof(ringbuf_buffer)); + + /* Configure ring buffer with user-supplied buffer */ + ringbufConfig.buffer = ringbuf_buffer; + ringbufConfig.buffer_size = sizeof(ringbuf_buffer); + + logConfig.cb = &ringbufCb; + logConfig.context = &ringbufCtx; + logConfig.config = &ringbufConfig; + + /* Test: Init with valid config */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig)); + WH_TEST_ASSERT_RETURN(ringbufCtx.initialized == 1); + WH_TEST_ASSERT_RETURN(ringbufCtx.count == 0); + + /* Get capacity from initialized context */ + capacity = ringbufCtx.capacity; + WH_TEST_ASSERT_RETURN(capacity == numLogEntries); + + /* Test: Add a few entries */ + for (i = 0; i < 5; i++) { + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Message %d", i); + } + + WH_TEST_ASSERT_RETURN(ringbufCtx.count == 5); + + /* Verify the entries are correct */ + WH_TEST_ASSERT_RETURN(ringbufCtx.count == 5); + for (i = 0; i < 5; i++) { + char expected[32]; + snprintf(expected, sizeof(expected), "Message %d", i); + WH_TEST_ASSERT_RETURN(strncmp(ringbufCtx.entries[i].msg, expected, + sizeof(expected)) == 0); + } + + /* Test: Clear buffer */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx)); + WH_TEST_ASSERT_RETURN(ringbufCtx.count == 0); + + /* Test: Fill buffer to capacity */ + for (i = 0; i < (int)capacity; i++) { + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Entry %d", i); + } + + WH_TEST_ASSERT_RETURN(ringbufCtx.count == capacity); + + /* Test: Wraparound - add more entries to overwrite oldest */ + for (i = 0; i < 5; i++) { + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Wrapped %d", i); + } + + /* Count increments freely to track total messages ever written */ + WH_TEST_ASSERT_RETURN(ringbufCtx.count == capacity + 5); + + /* Verify oldest entries were overwritten */ + WH_TEST_ASSERT_RETURN(strncmp(ringbufCtx.entries[0].msg, "Wrapped 0", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + WH_TEST_ASSERT_RETURN(strncmp(ringbufCtx.entries[4].msg, "Wrapped 4", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + + /* Verify some non-overwritten entries still exist */ + WH_TEST_ASSERT_RETURN(strncmp(ringbufCtx.entries[5].msg, "Entry 5", + WOLFHSM_CFG_LOG_MSG_MAX) == 0); + + /* Test: Iterate through ring buffer */ + /* Clear and add known entries for iteration test */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx)); + for (i = 0; i < 3; i++) { + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Iterate test %d", i); + } + + /* Count entries via iteration */ + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == 3); + + /* Test: Iterate when buffer is full and wrapped */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx)); + for (i = 0; i < (int)capacity + 5; i++) { + WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Wrap %d", i); + } + + /* Should iterate exactly capacity entries */ + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == (int)capacity); + + /* Cleanup */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx)); + WH_TEST_ASSERT_RETURN(ringbufCtx.initialized == 0); + + return 0; +} + +/* Mock backend generic tests */ +static int whTest_LogMock_Generic(void) +{ + mockLogContext mockCtx; + whTestLogBackendTestConfig testCfg; + + memset(&mockCtx, 0, sizeof(mockCtx)); + + testCfg.backend_name = "Mock"; + testCfg.cb = &mockLogCb; + testCfg.config = NULL; + testCfg.config_size = sizeof(mockLogContext); + testCfg.backend_context = &mockCtx; + testCfg.expected_capacity = MOCK_LOG_MAX_ENTRIES; + testCfg.supports_concurrent = 0; + testCfg.setup = NULL; + testCfg.teardown = NULL; + testCfg.test_context = NULL; + + return whTest_LogBackend_RunAll(&testCfg); +} + +/* Ring buffer backend generic tests */ +static int whTest_LogRingbuf_Generic(void) +{ + whLogRingbufContext ringbufCtx; + whLogRingbufConfig ringbufConfig; + whTestLogBackendTestConfig testCfg; + whLogCb ringbufCb; + const size_t numLogEntries = 32; + static whLogEntry ringbuf_buffer[32]; + + /* Setup ring buffer configuration with user-supplied buffer */ + memset(&ringbuf_buffer, 0, sizeof(ringbuf_buffer)); + memset(&ringbufCtx, 0, sizeof(ringbufCtx)); + ringbufConfig.buffer = ringbuf_buffer; + ringbufConfig.buffer_size = sizeof(ringbuf_buffer); + + /* Initialize callback table (C90 compatible) */ + memset(&ringbufCb, 0, sizeof(ringbufCb)); + ringbufCb.Init = whLogRingbuf_Init; + ringbufCb.Cleanup = whLogRingbuf_Cleanup; + ringbufCb.AddEntry = whLogRingbuf_AddEntry; + ringbufCb.Export = whLogRingbuf_Export; + ringbufCb.Iterate = whLogRingbuf_Iterate; + ringbufCb.Clear = whLogRingbuf_Clear; + + testCfg.backend_name = "RingBuffer"; + testCfg.cb = &ringbufCb; + testCfg.config = &ringbufConfig; + testCfg.config_size = sizeof(whLogRingbufContext); + testCfg.backend_context = &ringbufCtx; + testCfg.expected_capacity = numLogEntries; + testCfg.supports_concurrent = 0; + testCfg.setup = NULL; + testCfg.teardown = NULL; + testCfg.test_context = NULL; + + return whTest_LogBackend_RunAll(&testCfg); +} + +/* Printf backend tests. The printf backend is a write-only sink: it + * implements only Init and AddEntry, so the remaining frontend ops + * report NOTIMPL. Exercises both the "log always" and "drop unless + * debug" config paths and the bad-args/uninitialized rejections. */ +static int whTest_LogPrintf(void) +{ + whLogContext logCtx; + whLogPrintfContext printfCtx; + whLogPrintfConfig printfCfg; + whLogConfig logConfig; + whLogCb printfCb = WH_LOG_PRINTF_CB; + whLogEntry entry = {0}; + int iterate_count = 0; + + memset(&printfCtx, 0, sizeof(printfCtx)); + + /* Direct backend bad-args rejections */ + WH_TEST_ASSERT_RETURN(whLogPrintf_Init(NULL, NULL) == WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(whLogPrintf_AddEntry(NULL, &entry) == + WH_ERROR_BADARGS); + WH_TEST_ASSERT_RETURN(whLogPrintf_AddEntry(&printfCtx, NULL) == + WH_ERROR_BADARGS); + + /* Adding to an uninitialized backend is rejected */ + WH_TEST_ASSERT_RETURN(whLogPrintf_AddEntry(&printfCtx, &entry) == + WH_ERROR_ABORTED); + + /* Init with NULL config uses defaults (logIfNotDebug = 0) */ + memset(&logCtx, 0, sizeof(logCtx)); + memset(&printfCtx, 0, sizeof(printfCtx)); + logConfig.cb = &printfCb; + logConfig.context = &printfCtx; + logConfig.config = NULL; + WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig)); + WH_TEST_ASSERT_RETURN(printfCtx.initialized == 1); + WH_TEST_ASSERT_RETURN(printfCtx.logIfNotDebug == 0); + + /* With logIfNotDebug = 0 and a non-debug build, entries are dropped */ + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Dropped unless debug build"); + + /* Re-init with logIfNotDebug = 1 so entries are always printed */ + memset(&logCtx, 0, sizeof(logCtx)); + memset(&printfCtx, 0, sizeof(printfCtx)); + printfCfg.logIfNotDebug = 1; + logConfig.config = &printfCfg; + WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig)); + WH_TEST_ASSERT_RETURN(printfCtx.logIfNotDebug == 1); + + /* Exercise the print path for each known level */ + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Printf info"); + WH_LOG(&logCtx, WH_LOG_LEVEL_ERROR, "Printf error"); + WH_LOG(&logCtx, WH_LOG_LEVEL_SECEVENT, "Printf secevent"); + + /* An out-of-range level exercises the level-to-string default */ + entry.timestamp = 1; + entry.level = (whLogLevel)999; + entry.file = __FILE__; + entry.function = __func__; + entry.line = __LINE__; + entry.msg_len = 0; + WH_TEST_RETURN_ON_FAIL(wh_Log_AddEntry(&logCtx, &entry)); + + /* The printf backend implements no store, so the remaining frontend + * operations report NOTIMPL */ + WH_TEST_ASSERT_RETURN(wh_Log_Cleanup(&logCtx) == WH_ERROR_NOTIMPL); + WH_TEST_ASSERT_RETURN(wh_Log_Export(&logCtx, NULL) == WH_ERROR_NOTIMPL); + WH_TEST_ASSERT_RETURN( + wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count) == + WH_ERROR_NOTIMPL); + WH_TEST_ASSERT_RETURN(wh_Log_Clear(&logCtx) == WH_ERROR_NOTIMPL); + + return WH_ERROR_OK; +} + +/* Portable log test entry point (Misc group) */ +int whTest_Log(void* ctx) +{ + (void)ctx; + + WH_TEST_PRINT("Testing log frontend API...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogFrontend()); + + WH_TEST_PRINT("Testing log macros...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogMacros()); + + WH_TEST_PRINT("Testing formatted log macros...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogFormattedMacros()); + + WH_TEST_PRINT("Testing mock log backend in generic harness...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogMock_Generic()); + + WH_TEST_PRINT("Testing ringbuf backend in generic harness...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogRingbuf_Generic()); + + WH_TEST_PRINT("Testing ring buffer backend...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogRingbuf()); + + WH_TEST_PRINT("Testing printf backend...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogPrintf()); + + return WH_ERROR_OK; +} + +#endif /* WOLFHSM_CFG_LOGGING */ diff --git a/test-refactor/misc/wh_test_log_backend.h b/test-refactor/misc/wh_test_log_backend.h new file mode 100644 index 000000000..9732face0 --- /dev/null +++ b/test-refactor/misc/wh_test_log_backend.h @@ -0,0 +1,59 @@ +/* + * 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/misc/wh_test_log_backend.h + * + * Backend-agnostic log harness shared by the portable Misc log tests + * (mock, ring buffer) and the POSIX port log tests (file backend). The + * caller supplies a backend callback table and context; the harness runs + * the same suite against it. + */ + +#ifndef WH_TEST_LOG_BACKEND_H_ +#define WH_TEST_LOG_BACKEND_H_ + +#include + +#include "wolfhsm/wh_log.h" + +/* Configuration describing a backend under test. */ +typedef struct { + const char* backend_name; /* Backend name for test output */ + whLogCb* cb; /* Backend callback table */ + void* config; /* Backend-specific config */ + size_t config_size; /* Size of context structure */ + void* backend_context; /* Pre-allocated backend context */ + + /* Capabilities */ + int expected_capacity; /* Max entries (-1 = unlimited) */ + int supports_concurrent; /* supports multithreaded use */ + + /* Optional hooks */ + int (*setup)(void** context); /* Setup hook (optional) */ + int (*teardown)(void* context); /* Teardown hook (optional) */ + void* test_context; /* Context for setup/teardown */ +} whTestLogBackendTestConfig; + +/* + * Runs all generic test suites against the supplied backend. + * Returns WH_ERROR_OK on success, non-zero on failure. + */ +int whTest_LogBackend_RunAll(whTestLogBackendTestConfig* cfg); + +#endif /* WH_TEST_LOG_BACKEND_H_ */ diff --git a/test-refactor/posix/wh_test_log_posix.c b/test-refactor/posix/wh_test_log_posix.c new file mode 100644 index 000000000..8812d76fe --- /dev/null +++ b/test-refactor/posix/wh_test_log_posix.c @@ -0,0 +1,608 @@ +/* + * 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/posix/wh_test_log_posix.c + * + * POSIX-specific logging tests from legacy test/wh_test_log.c: the + * POSIX file backend (via the shared generic harness and directly), + * concurrent access from multiple threads, and a client/server log + * smoke test over the mem transport. These are invoked from the POSIX + * port main via whTestGroup_RunOne, not from the portable registry. + */ + +#include "wolfhsm/wh_settings.h" + +#include +#include +#include + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#if defined(WOLFHSM_CFG_LOGGING) + +#include +#include + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_log.h" + +#include "port/posix/posix_log_file.h" + +#include "wh_test_log_backend.h" + +#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && defined(WOLFHSM_CFG_ENABLE_SERVER) +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_transport_mem.h" +#include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_nvm_flash.h" +#include "wolfhsm/wh_flash_ramsim.h" +#ifndef WOLFHSM_CFG_NO_CRYPTO +#include "wolfhsm/wh_server_crypto.h" +#endif +#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER */ + + +/* Helper for iterate callback - counts entries */ +static int _logIterateCount(void* arg, const whLogEntry* entry) +{ + int* count = (int*)arg; + (void)entry; + (*count)++; + return 0; +} + + +/* POSIX file backend in the generic harness */ +int whTest_LogPosixFile_Generic(void* ctx) +{ + posixLogFileContext posixCtx; + posixLogFileConfig posixCfg; + whTestLogBackendTestConfig testCfg; + whLogCb posixCb; + const char* test_log_file = "/tmp/wolfhsm_test_generic.log"; + + (void)ctx; + + /* Initialize callback table (C90 compatible) */ + memset(&posixCtx, 0, sizeof(posixCtx)); + memset(&posixCb, 0, sizeof(posixCb)); + posixCb.Init = posixLogFile_Init; + posixCb.Cleanup = posixLogFile_Cleanup; + posixCb.AddEntry = posixLogFile_AddEntry; + posixCb.Export = posixLogFile_Export; + posixCb.Iterate = posixLogFile_Iterate; + posixCb.Clear = posixLogFile_Clear; + + /* Remove any existing test log file */ + unlink(test_log_file); + + posixCfg.filename = test_log_file; + + testCfg.backend_name = "PosixFile"; + testCfg.cb = &posixCb; + testCfg.config = &posixCfg; + testCfg.config_size = sizeof(posixLogFileContext); + testCfg.backend_context = &posixCtx; + testCfg.expected_capacity = -1; /* Unlimited */ + testCfg.supports_concurrent = 1; + testCfg.setup = NULL; + testCfg.teardown = NULL; + testCfg.test_context = NULL; + + return whTest_LogBackend_RunAll(&testCfg); +} + + +/* POSIX file backend tests */ +int whTest_LogPosixFile(void* ctx) +{ + whLogContext logCtx; + posixLogFileContext posixCtx; + posixLogFileConfig posixCfg; + whLogConfig logConfig; + whLogCb posixCb = POSIX_LOG_FILE_CB; + const char* test_log_file = "/tmp/wolfhsm_test_log.txt"; + int export_count; + int iterate_count; + FILE* export_fp; + char line[2048]; + + (void)ctx; + + /* Remove any existing test log file */ + unlink(test_log_file); + + /* Test: Create log file, add entries, verify file exists */ + memset(&logCtx, 0, sizeof(logCtx)); + memset(&posixCtx, 0, sizeof(posixCtx)); + posixCfg.filename = test_log_file; + + logConfig.cb = &posixCb; + logConfig.context = &posixCtx; + logConfig.config = &posixCfg; + + WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig)); + WH_TEST_ASSERT_RETURN(posixCtx.initialized == 1); + WH_TEST_ASSERT_RETURN(posixCtx.fd >= 0); + + /* Add some log entries */ + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "First info message"); + WH_LOG(&logCtx, WH_LOG_LEVEL_ERROR, "First error message"); + WH_LOG(&logCtx, WH_LOG_LEVEL_SECEVENT, "First security event"); + + /* Test: Export reads back all entries correctly */ + /* For POSIX backend, export to a temp file and count lines */ + export_fp = tmpfile(); + WH_TEST_ASSERT_RETURN(export_fp != NULL); + WH_TEST_RETURN_ON_FAIL(wh_Log_Export(&logCtx, export_fp)); + fflush(export_fp); + rewind(export_fp); + + export_count = 0; + while (fgets(line, sizeof(line), export_fp) != NULL) { + export_count++; + } + fclose(export_fp); + WH_TEST_ASSERT_RETURN(export_count == 3); + + /* Test: Append preserves existing entries */ + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Second info message"); + export_fp = tmpfile(); + WH_TEST_ASSERT_RETURN(export_fp != NULL); + WH_TEST_RETURN_ON_FAIL(wh_Log_Export(&logCtx, export_fp)); + fflush(export_fp); + rewind(export_fp); + + export_count = 0; + while (fgets(line, sizeof(line), export_fp) != NULL) { + export_count++; + } + fclose(export_fp); + WH_TEST_ASSERT_RETURN(export_count == 4); + + /* Test: Clear truncates file */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx)); + export_fp = tmpfile(); + WH_TEST_ASSERT_RETURN(export_fp != NULL); + WH_TEST_RETURN_ON_FAIL(wh_Log_Export(&logCtx, export_fp)); + fflush(export_fp); + rewind(export_fp); + + export_count = 0; + while (fgets(line, sizeof(line), export_fp) != NULL) { + export_count++; + } + fclose(export_fp); + WH_TEST_ASSERT_RETURN(export_count == 0); + + /* Test: Iterate functionality with parsing */ + /* Add entries for iterate test */ + WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Iterate message 1"); + WH_LOG(&logCtx, WH_LOG_LEVEL_ERROR, "Iterate message 2"); + WH_LOG(&logCtx, WH_LOG_LEVEL_SECEVENT, "Iterate message 3"); + + /* Count entries via iteration */ + iterate_count = 0; + WH_TEST_RETURN_ON_FAIL( + wh_Log_Iterate(&logCtx, _logIterateCount, &iterate_count)); + WH_TEST_ASSERT_RETURN(iterate_count == 3); + + /* Cleanup */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx)); + + /* Remove test log file */ + unlink(test_log_file); + + return 0; +} + + +/* Thread function for concurrent access test */ +typedef struct { + whLogContext* ctx; + int thread_id; + int iterations; +} thread_test_args; + +static void* threadTestFunc(void* arg) +{ + thread_test_args* args = (thread_test_args*)arg; + int i; + + for (i = 0; i < args->iterations; i++) { + WH_LOG_F(args->ctx, WH_LOG_LEVEL_INFO, "Thread %d iteration %d", + args->thread_id, i); + } + + return (void*)0; +} + +int whTest_LogPosixFileConcurrent(void* ctx) +{ + whLogContext logCtx; + posixLogFileContext posixCtx; + posixLogFileConfig posixCfg; + whLogConfig logConfig; + whLogCb posixCb = POSIX_LOG_FILE_CB; + const char* test_log_file = "/tmp/wolfhsm_test_log_concurrent.txt"; + int export_count; + const int NUM_THREADS = 4; + const int ITERATIONS_PER_THREAD = 10; + pthread_t threads[4]; + thread_test_args args[4]; + int i; + FILE* verify_fp; + char line[2048]; + + (void)ctx; + + /* Remove any existing test log file */ + unlink(test_log_file); + + /* Setup */ + memset(&logCtx, 0, sizeof(logCtx)); + memset(&posixCtx, 0, sizeof(posixCtx)); + posixCfg.filename = test_log_file; + + logConfig.cb = &posixCb; + logConfig.context = &posixCtx; + logConfig.config = &posixCfg; + + WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig)); + + /* Test: Concurrent access from multiple threads */ + for (i = 0; i < NUM_THREADS; i++) { + args[i].ctx = &logCtx; + args[i].thread_id = i; + args[i].iterations = ITERATIONS_PER_THREAD; + + if (pthread_create(&threads[i], NULL, threadTestFunc, &args[i]) != 0) { + WH_ERROR_PRINT("Failed to create thread %d\n", i); + return WH_TEST_FAIL; + } + } + + /* Wait for all threads */ + for (i = 0; i < NUM_THREADS; i++) { + void* result; + pthread_join(threads[i], &result); + if (result != (void*)0) { + WH_ERROR_PRINT("Thread %d failed\n", i); + return WH_TEST_FAIL; + } + } + + /* Verify all entries were written */ + /* For POSIX backend, export to a temp file and count lines */ + verify_fp = tmpfile(); + WH_TEST_ASSERT_RETURN(verify_fp != NULL); + WH_TEST_RETURN_ON_FAIL(wh_Log_Export(&logCtx, verify_fp)); + fflush(verify_fp); + rewind(verify_fp); + + /* Count lines in exported file */ + export_count = 0; + while (fgets(line, sizeof(line), verify_fp) != NULL) { + export_count++; + } + fclose(verify_fp); + WH_TEST_ASSERT_RETURN(export_count == NUM_THREADS * ITERATIONS_PER_THREAD); + + /* Cleanup */ + WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx)); + + /* Remove test log file */ + unlink(test_log_file); + + return 0; +} + + +#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && defined(WOLFHSM_CFG_ENABLE_SERVER) + +#define WH_LOG_TEST_FLASH_RAM_SIZE (1024 * 1024) +#define WH_LOG_TEST_FLASH_SECTOR_SIZE (128 * 1024) +#define WH_LOG_TEST_FLASH_PAGE_SIZE (8) +#define WH_LOG_TEST_SERVER_LOG_FILE "/tmp/wh_log_clientserver_posix.txt" + +enum { + WH_LOG_TEST_BUFFER_SIZE = sizeof(whTransportMemCsr) + sizeof(whCommHeader) + + WOLFHSM_CFG_COMM_DATA_LEN, +}; + +static int _clientServerLogSmokeTest(whClientContext* client) +{ + FILE* log_file = NULL; + size_t entry_count = 0; + char line[1024]; + size_t expected_entries = 3; + +#ifdef WOLFHSM_CFG_ENABLE_AUTHENTICATION + /* When authentication is compiled in but no auth context is configured, + * server init emits an extra SECEVENT warning after the startup INFO log */ + expected_entries++; +#endif + + /* Connect to the server, which should trigger an info log entry */ + WH_TEST_ASSERT(WH_ERROR_OK == wh_Client_CommInit(client, NULL, NULL)); + + /* Disconnect the server, which should trigger an info log entry */ + WH_TEST_RETURN_ON_FAIL(wh_Client_CommClose(client)); + + /* Now read the log file and verify that the log entries are present */ + log_file = fopen(WH_LOG_TEST_SERVER_LOG_FILE, "r"); + WH_TEST_ASSERT(log_file != NULL); + + while (fgets(line, sizeof(line), log_file) != NULL) { + entry_count++; + WH_TEST_PRINT("Log entry: %s", line); + + /* First log entry should be startup INFO log */ + if (entry_count == 1) { + WH_TEST_ASSERT(strstr(line, "INFO") != NULL); + } +#ifdef WOLFHSM_CFG_ENABLE_AUTHENTICATION + else if (entry_count == 2) { + /* Auth enabled with no auth context triggers a SECEVENT */ + WH_TEST_ASSERT(strstr(line, "SECEVENT") != NULL); + } + else if (entry_count == 3) { + WH_TEST_ASSERT(strstr(line, "INFO") != NULL); + } + else if (entry_count == 4) { + WH_TEST_ASSERT(strstr(line, "INFO") != NULL); + } +#else + else if (entry_count == 2) { + /* Second log entry should the connect INFO log */ + WH_TEST_ASSERT(strstr(line, "INFO") != NULL); + } + else if (entry_count == 3) { + /* Third log entry should be another INFO from comm close */ + WH_TEST_ASSERT(strstr(line, "INFO") != NULL); + } +#endif + else { + break; + } + } + fclose(log_file); + + /* Ensure we have at least the number of expected log entries */ + WH_TEST_ASSERT(entry_count >= expected_entries); + + return WH_ERROR_OK; +} + +static void* _whLogClientTask(void* cfg) +{ + whClientConfig* client_cfg = (whClientConfig*)cfg; + whClientContext client[1] = {{0}}; + + if (client_cfg == NULL) { + return NULL; + } + + WH_TEST_ASSERT(WH_ERROR_OK == wh_Client_Init(client, client_cfg)); + + WH_TEST_ASSERT(WH_ERROR_OK == _clientServerLogSmokeTest(client)); + + WH_TEST_ASSERT(WH_ERROR_OK == wh_Client_Cleanup(client)); + return NULL; +} + +static void* _whLogServerTask(void* cfg) +{ + whServerConfig* server_cfg = (whServerConfig*)cfg; + whServerContext server[1] = {{0}}; + whCommConnected connected = WH_COMM_CONNECTED; + int ret; + + if (server_cfg == NULL) { + return NULL; + } + + WH_TEST_ASSERT(WH_ERROR_OK == wh_Server_Init(server, server_cfg)); + WH_TEST_ASSERT(WH_ERROR_OK == wh_Server_SetConnected(server, connected)); + + while (connected == WH_COMM_CONNECTED) { + ret = wh_Server_HandleRequestMessage(server); + if ((ret != WH_ERROR_NOTREADY) && (ret != WH_ERROR_OK)) { + WH_ERROR_PRINT( + "[server] Failed to wh_Server_HandleRequestMessage ret=%d\n", + ret); + break; + } + wh_Server_GetConnected(server, &connected); + } + + WH_TEST_ASSERT(0 == wh_Server_Cleanup(server)); + return NULL; +} + +static void _whLogClientServerThreadTest(whClientConfig* c_conf, + whServerConfig* s_conf) +{ + pthread_t client_thread; + pthread_t server_thread; + void* retval = NULL; + int rc; + + rc = pthread_create(&server_thread, NULL, _whLogServerTask, s_conf); + WH_TEST_ASSERT(rc == 0); + + rc = pthread_create(&client_thread, NULL, _whLogClientTask, c_conf); + if (rc != 0) { + pthread_cancel(server_thread); + pthread_join(server_thread, &retval); + WH_TEST_ASSERT(rc == 0); + return; + } + + pthread_join(client_thread, &retval); + pthread_join(server_thread, &retval); +} + +int whTest_LogClientServerMemTransport(void* ctx) +{ + uint8_t req[WH_LOG_TEST_BUFFER_SIZE] = {0}; + uint8_t resp[WH_LOG_TEST_BUFFER_SIZE] = {0}; + whTransportMemConfig tmcf[1] = {{ + .req = (whTransportMemCsr*)req, + .req_size = sizeof(req), + .resp = (whTransportMemCsr*)resp, + .resp_size = sizeof(resp), + }}; + + /* Client configuration */ + whTransportClientCb tccb[1] = {WH_TRANSPORT_MEM_CLIENT_CB}; + whTransportMemClientContext tmcc[1] = {{0}}; + whCommClientConfig cc_conf[1] = {{ + .transport_cb = tccb, + .transport_context = (void*)tmcc, + .transport_config = (void*)tmcf, + .client_id = WH_TEST_DEFAULT_CLIENT_ID, + }}; + +#ifdef WOLFHSM_CFG_DMA + whClientDmaConfig clientDmaConfig = {0}; +#endif + + whClientConfig c_conf[1] = {{ + .comm = cc_conf, +#ifdef WOLFHSM_CFG_DMA + .dmaConfig = &clientDmaConfig, +#endif + }}; + + /* Server configuration */ + whTransportServerCb tscb[1] = {WH_TRANSPORT_MEM_SERVER_CB}; + whTransportMemServerContext tmsc[1] = {{0}}; + whCommServerConfig cs_conf[1] = {{ + .transport_cb = tscb, + .transport_context = (void*)tmsc, + .transport_config = (void*)tmcf, + .server_id = 124, + }}; + + uint8_t memory[WH_LOG_TEST_FLASH_RAM_SIZE] = {0}; + whFlashRamsimCtx fc[1] = {{0}}; + whFlashRamsimCfg fc_conf[1] = {{ + .size = WH_LOG_TEST_FLASH_RAM_SIZE, + .sectorSize = WH_LOG_TEST_FLASH_SECTOR_SIZE, + .pageSize = WH_LOG_TEST_FLASH_PAGE_SIZE, + .erasedByte = ~(uint8_t)0, + .memory = memory, + }}; + const whFlashCb fcb[1] = {WH_FLASH_RAMSIM_CB}; + + whTestNvmBackendUnion nvm_setup; + whNvmConfig n_conf[1] = {0}; + whNvmContext nvm[1] = {{0}}; + +#ifndef WOLFHSM_CFG_NO_CRYPTO + whServerCryptoContext crypto[1] = {0}; +#endif + + posixLogFileContext posixCtx[1] = {0}; + posixLogFileConfig posixCfg[1] = {{ + .filename = WH_LOG_TEST_SERVER_LOG_FILE, + }}; + whLogCb posixCb = POSIX_LOG_FILE_CB; + whLogConfig logConfig[1] = {{ + .cb = &posixCb, + .context = posixCtx, + .config = posixCfg, + }}; + + whServerConfig s_conf[1] = {{ + .comm_config = cs_conf, + .nvm = nvm, +#ifndef WOLFHSM_CFG_NO_CRYPTO + .crypto = crypto, + .devId = INVALID_DEVID, +#endif + .logConfig = logConfig, + }}; + + (void)ctx; + + WH_TEST_RETURN_ON_FAIL(whTest_NvmCfgBackend( + WH_NVM_TEST_BACKEND_FLASH, &nvm_setup, n_conf, fc_conf, fc, fcb)); + + unlink(WH_LOG_TEST_SERVER_LOG_FILE); + + WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(nvm, n_conf)); + +#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 + + _whLogClientServerThreadTest(c_conf, s_conf); + +#ifndef WOLFHSM_CFG_NO_CRYPTO + wc_FreeRng(crypto->rng); + wolfCrypt_Cleanup(); +#endif + wh_Nvm_Cleanup(nvm); + + return WH_ERROR_OK; +} + +#else /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER */ + +int whTest_LogClientServerMemTransport(void* ctx) +{ + (void)ctx; + return WH_TEST_SKIPPED; +} + +#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER */ + +#else /* WOLFHSM_CFG_LOGGING */ + +int whTest_LogPosixFile_Generic(void* ctx) +{ + (void)ctx; + return WH_TEST_SKIPPED; +} + +int whTest_LogPosixFile(void* ctx) +{ + (void)ctx; + return WH_TEST_SKIPPED; +} + +int whTest_LogPosixFileConcurrent(void* ctx) +{ + (void)ctx; + return WH_TEST_SKIPPED; +} + +int whTest_LogClientServerMemTransport(void* ctx) +{ + (void)ctx; + return WH_TEST_SKIPPED; +} + +#endif /* WOLFHSM_CFG_LOGGING */ diff --git a/test-refactor/posix/wh_test_posix_main.c b/test-refactor/posix/wh_test_posix_main.c index 756ed6041..d36f6fcd2 100644 --- a/test-refactor/posix/wh_test_posix_main.c +++ b/test-refactor/posix/wh_test_posix_main.c @@ -76,6 +76,16 @@ int whTest_NvmAddOverwriteDestroy(void* ctx); int whTest_NvmFlashLog(void* ctx); int whTest_NvmRecovery(void* ctx); +/* POSIX-specific logging tests. The portable log suite (frontend, + * macros, ring buffer, mock/ringbuf harness) runs in the Misc group; + * these exercise the POSIX file backend, concurrent writers, and a + * client/server log smoke test that need pthreads and the host file + * system, so they run from the port directly. */ +int whTest_LogPosixFile_Generic(void* ctx); +int whTest_LogPosixFile(void* ctx); +int whTest_LogPosixFileConcurrent(void* ctx); +int whTest_LogClientServerMemTransport(void* ctx); + /* * Port-owned contexts. The thread functions fill these in and * hand them to the group functions, paralleling the firmware @@ -284,6 +294,29 @@ int main(void) if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) { miscRc = rc; } + /* POSIX-specific log backend tests. Self-contained (each owns + * its own contexts and threads), so run inline before the port + * spins up its own server thread. */ + rc = whTestGroup_RunOne("whTest_LogPosixFile_Generic", + whTest_LogPosixFile_Generic, NULL); + if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) { + miscRc = rc; + } + rc = whTestGroup_RunOne("whTest_LogPosixFile", + whTest_LogPosixFile, NULL); + if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) { + miscRc = rc; + } + rc = whTestGroup_RunOne("whTest_LogPosixFileConcurrent", + whTest_LogPosixFileConcurrent, NULL); + if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) { + miscRc = rc; + } + rc = whTestGroup_RunOne("whTest_LogClientServerMemTransport", + whTest_LogClientServerMemTransport, NULL); + if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) { + miscRc = rc; + } } rc = pthread_create(&sthread, NULL, _serverThread, NULL); diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index 683555463..fdb70aee5 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -44,7 +44,11 @@ WH_TEST_DECL(whTest_ClientDevId); WH_TEST_DECL(whTest_Comm); WH_TEST_DECL(whTest_Dma); WH_TEST_DECL(whTest_KeystoreReqSize); +<<<<<<< HEAD WH_TEST_DECL(whTest_MultiClient); +======= +WH_TEST_DECL(whTest_Log); +>>>>>>> 8b3eb9b (Add refactored log tests, coverage identical) WH_TEST_DECL(whTest_CertVerify); WH_TEST_DECL(whTest_NvmOptional); WH_TEST_DECL(whTest_ClientCerts); @@ -86,11 +90,12 @@ WH_TEST_DECL(whTest_AuthSetCredentials); WH_TEST_DECL(whTest_AuthRequestAuthorization); const whTestCase whTestsMisc[] = { - { "whTest_ClientDevId", whTest_ClientDevId }, - { "whTest_Comm", whTest_Comm }, - { "whTest_Dma", whTest_Dma }, - { "whTest_KeystoreReqSize", whTest_KeystoreReqSize }, - { "whTest_MultiClient", whTest_MultiClient }, + { "whTest_ClientDevId", whTest_ClientDevId}, + { "whTest_Comm", whTest_Comm}, + { "whTest_Dma", whTest_Dma}, + { "whTest_KeystoreReqSize", whTest_KeystoreReqSize}, + { "whTest_MultiClient", whTest_MultiClient }, + { "whTest_Log", whTest_Log}, }; const size_t whTestsMiscCount = ARRAY_SIZE(whTestsMisc);