diff --git a/benchmark/config/wolfhsm_cfg.h b/benchmark/config/wolfhsm_cfg.h index 4c1463758..f28963237 100644 --- a/benchmark/config/wolfhsm_cfg.h +++ b/benchmark/config/wolfhsm_cfg.h @@ -25,6 +25,10 @@ #ifndef WOLFHSM_CFG_H_ #define WOLFHSM_CFG_H_ +#include "port/posix/posix_time.h" + +#define WOLFHSM_CFG_PORT_GETTIME posixGetTime + #define WOLFHSM_CFG_ENABLE_CLIENT #define WOLFHSM_CFG_ENABLE_SERVER diff --git a/benchmark/wh_bench_ops.c b/benchmark/wh_bench_ops.c index dd5a478c2..509c7726e 100644 --- a/benchmark/wh_bench_ops.c +++ b/benchmark/wh_bench_ops.c @@ -19,10 +19,6 @@ #include #include /* For memset, memcpy */ -#if defined(WOLFHSM_CFG_TEST_POSIX) -#include /* For gettimeofday and struct timeval */ -#endif - #include "wolfhsm/wh_settings.h" #include "wolfhsm/wh_error.h" #include "wh_bench_ops.h" @@ -47,22 +43,7 @@ static uint64_t _benchGetTimeUsDefault(void); /* Default implementation for getting current time in microseconds */ static uint64_t _benchGetTimeUsDefault(void) { - uint64_t timeUs = 0; - -#if defined(WOLFHSM_CFG_TEST_POSIX) - struct timeval tv; - gettimeofday(&tv, NULL); - timeUs = (uint64_t)tv.tv_sec * 1000000 + (uint64_t)tv.tv_usec; -#else - /* Default implementation - should be overridden for actual platform */ - /* This is just a placeholder that returns a monotonically increasing value - */ - static uint64_t fakeTime = 0; - fakeTime += 1000; /* Increment by a fake 1ms each call */ - timeUs = fakeTime; -#endif - - return timeUs; + return WH_GETTIME_US(); } #endif /* !(WOLFHSM_CFG_BENCH_CUSTOM_TIME_FUNC) */ diff --git a/examples/posix/wh_posix_client/wolfhsm_cfg.h b/examples/posix/wh_posix_client/wolfhsm_cfg.h index 688569058..a07936b83 100644 --- a/examples/posix/wh_posix_client/wolfhsm_cfg.h +++ b/examples/posix/wh_posix_client/wolfhsm_cfg.h @@ -25,6 +25,10 @@ #ifndef WOLFHSM_CFG_H_ #define WOLFHSM_CFG_H_ +#include "port/posix/posix_time.h" + +#define WOLFHSM_CFG_PORT_GETTIME posixGetTime + /** wolfHSM settings */ #define WOLFHSM_CFG_ENABLE_CLIENT #define WOLFHSM_CFG_HEXDUMP diff --git a/examples/posix/wh_posix_server/wolfhsm_cfg.h b/examples/posix/wh_posix_server/wolfhsm_cfg.h index 526f6c27e..af8282595 100644 --- a/examples/posix/wh_posix_server/wolfhsm_cfg.h +++ b/examples/posix/wh_posix_server/wolfhsm_cfg.h @@ -25,6 +25,10 @@ #ifndef WOLFHSM_CFG_H_ #define WOLFHSM_CFG_H_ +#include "port/posix/posix_time.h" + +#define WOLFHSM_CFG_PORT_GETTIME posixGetTime + /** wolfHSM settings. Simple overrides to show they work */ #define WOLFHSM_CFG_ENABLE_SERVER diff --git a/port/posix/posix_log_file.c b/port/posix/posix_log_file.c new file mode 100644 index 000000000..0c086d670 --- /dev/null +++ b/port/posix/posix_log_file.c @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2024 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 . + */ +/* + * port/posix/posix_log_file.c + * + * POSIX file-based logging backend with thread-safe access + */ + +#include /* For NULL */ +#include /* For snprintf, FILE operations */ +#include /* For O_xxxx */ +#include /* For off_t */ +#include /* For fstat */ +#include /* For open, close, write, ftruncate, lseek */ +#include /* For errno */ +#include /* For memset, strncpy, strlen */ +#include /* For clock_gettime */ + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_log.h" + +#include "posix_log_file.h" + +#ifdef WOLFHSM_CFG_LOGGING + +/* Helper function to convert log level to string */ +static const char* posixLogFile_LevelToString(whLogLevel level) +{ + switch (level) { + case WH_LOG_LEVEL_INFO: + return "INFO"; + case WH_LOG_LEVEL_ERROR: + return "ERROR"; + case WH_LOG_LEVEL_SECEVENT: + return "SECEVENT"; + default: + return "UNKNOWN"; + } +} + +/* Helper function to convert string to log level */ +static whLogLevel posixLogFile_StringToLevel(const char* str) +{ + if (strcmp(str, "INFO") == 0) { + return WH_LOG_LEVEL_INFO; + } + else if (strcmp(str, "ERROR") == 0) { + return WH_LOG_LEVEL_ERROR; + } + else if (strcmp(str, "SECEVENT") == 0) { + return WH_LOG_LEVEL_SECEVENT; + } + return WH_LOG_LEVEL_INFO; /* Default */ +} + +int posixLogFile_Init(void* c, const void* cf) +{ + posixLogFileContext* context = c; + const posixLogFileConfig* config = cf; + int rc = 0; + + if ((context == NULL) || (config == NULL) || (config->filename == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Initialize context */ + memset(context, 0, sizeof(*context)); + context->fd = -1; + + /* Initialize mutex */ + rc = pthread_mutex_init(&context->mutex, NULL); + if (rc != 0) { + return WH_ERROR_ABORTED; + } + + /* Copy filename */ + strncpy(context->filename, config->filename, sizeof(context->filename) - 1); + context->filename[sizeof(context->filename) - 1] = '\0'; + + /* Open log file for append/create */ + context->fd = + open(context->filename, O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); + if (context->fd < 0) { + pthread_mutex_destroy(&context->mutex); + return WH_ERROR_ABORTED; + } + + context->initialized = 1; + return WH_ERROR_OK; +} + +int posixLogFile_Cleanup(void* c) +{ + posixLogFileContext* context = c; + + if (context == NULL) { + return WH_ERROR_BADARGS; + } + + if (context->initialized) { + if (context->fd >= 0) { + close(context->fd); + context->fd = -1; + } + pthread_mutex_destroy(&context->mutex); + context->initialized = 0; + } + + return WH_ERROR_OK; +} + +int posixLogFile_AddEntry(void* c, const whLogEntry* entry) +{ + posixLogFileContext* context = c; + char buffer[1024]; + int len; + ssize_t bytes_written; + + if ((context == NULL) || (entry == NULL)) { + return WH_ERROR_BADARGS; + } + + if (!context->initialized || context->fd < 0) { + return WH_ERROR_ABORTED; + } + + /* Lock mutex */ + pthread_mutex_lock(&context->mutex); + + /* Format log entry: TIMESTAMP|LEVEL|FILE:LINE|FUNCTION|MESSAGE\n */ + len = snprintf(buffer, sizeof(buffer), "%llu|%s|%s:%u|%s|%.*s\n", + (unsigned long long)entry->timestamp, + posixLogFile_LevelToString(entry->level), + entry->file ? entry->file : "", entry->line, + entry->function ? entry->function : "", (int)entry->msg_len, + entry->msg); + + if (len < 0 || (size_t)len >= sizeof(buffer)) { + pthread_mutex_unlock(&context->mutex); + return WH_ERROR_ABORTED; + } + + /* Write to file */ + bytes_written = write(context->fd, buffer, len); + + /* Unlock mutex */ + pthread_mutex_unlock(&context->mutex); + + if (bytes_written != len) { + return WH_ERROR_ABORTED; + } + + return WH_ERROR_OK; +} + +int posixLogFile_Export(void* c, void* export_arg) +{ + posixLogFileContext* context = c; + FILE* out_fp = (FILE*)export_arg; + FILE* in_fp = NULL; + char line[2048]; + int ret = 0; + + if (context == NULL) { + return WH_ERROR_BADARGS; + } + + if (!context->initialized || context->fd < 0) { + return WH_ERROR_ABORTED; + } + + /* Default to stdout if no FILE* provided */ + if (out_fp == NULL) { + out_fp = stdout; + } + + /* Lock mutex */ + pthread_mutex_lock(&context->mutex); + + /* Flush any pending writes */ + if (fsync(context->fd) != 0) { + pthread_mutex_unlock(&context->mutex); + return WH_ERROR_ABORTED; + } + + /* Open file for reading (using fdopen with dup'd fd) */ + int fd_dup = dup(context->fd); + if (fd_dup < 0) { + pthread_mutex_unlock(&context->mutex); + return WH_ERROR_ABORTED; + } + + /* Seek to beginning */ + lseek(fd_dup, 0, SEEK_SET); + + in_fp = fdopen(fd_dup, "r"); + if (in_fp == NULL) { + close(fd_dup); + pthread_mutex_unlock(&context->mutex); + return WH_ERROR_ABORTED; + } + + /* Read and write each line to output */ + while (fgets(line, sizeof(line), in_fp) != NULL) { + if (fputs(line, out_fp) == EOF) { + ret = WH_ERROR_ABORTED; + break; + } + } + + fclose(in_fp); /* Also closes fd_dup */ + + /* Unlock mutex */ + pthread_mutex_unlock(&context->mutex); + + return ret; +} + +int posixLogFile_Iterate(void* c, whLogIterateCb iterate_cb, void* iterate_arg) +{ + posixLogFileContext* context = c; + FILE* fp = NULL; + char line[2048]; + int ret = 0; + + if ((context == NULL) || (iterate_cb == NULL)) { + return WH_ERROR_BADARGS; + } + + if (!context->initialized || context->fd < 0) { + return WH_ERROR_ABORTED; + } + + /* Lock mutex */ + pthread_mutex_lock(&context->mutex); + + /* Flush any pending writes */ + if (fsync(context->fd) != 0) { + pthread_mutex_unlock(&context->mutex); + return WH_ERROR_ABORTED; + } + + /* Open file for reading (using fdopen with dup'd fd) */ + int fd_dup = dup(context->fd); + if (fd_dup < 0) { + pthread_mutex_unlock(&context->mutex); + return WH_ERROR_ABORTED; + } + + /* Seek to beginning */ + lseek(fd_dup, 0, SEEK_SET); + + fp = fdopen(fd_dup, "r"); + if (fp == NULL) { + close(fd_dup); + pthread_mutex_unlock(&context->mutex); + return WH_ERROR_ABORTED; + } + + /* Read and parse each line */ + while (fgets(line, sizeof(line), fp) != NULL) { + whLogEntry entry; + char level_str[32]; + char file_buf[256]; + char func_buf[256]; + char msg_buf[WOLFHSM_CFG_LOG_MSG_MAX]; + unsigned long long timestamp; + unsigned int line_num; + + memset(&entry, 0, sizeof(entry)); + + /* Parse: TIMESTAMP|LEVEL|FILE:LINE|FUNCTION|MESSAGE\n */ + int parsed = sscanf(line, "%llu|%31[^|]|%255[^:]:%u|%255[^|]|%255[^\n]", + ×tamp, level_str, file_buf, &line_num, + func_buf, msg_buf); + + /* Minimum number of fields to parse is 5, msg is optional */ + if (parsed >= 5) { + entry.timestamp = timestamp; + entry.level = posixLogFile_StringToLevel(level_str); + entry.file = file_buf; + entry.function = func_buf; + entry.line = line_num; + entry.msg_len = strlen(msg_buf); + if (entry.msg_len >= WOLFHSM_CFG_LOG_MSG_MAX) { + entry.msg_len = WOLFHSM_CFG_LOG_MSG_MAX - 1; + } + memcpy(entry.msg, msg_buf, entry.msg_len); + entry.msg[entry.msg_len] = '\0'; + + /* Invoke callback */ + ret = iterate_cb(iterate_arg, &entry); + if (ret != 0) { + break; + } + } + } + + fclose(fp); /* Also closes fd_dup */ + + /* Unlock mutex */ + pthread_mutex_unlock(&context->mutex); + + return ret; +} + +int posixLogFile_Clear(void* c) +{ + posixLogFileContext* context = c; + int ret = 0; + + if (context == NULL) { + return WH_ERROR_BADARGS; + } + + if (!context->initialized || context->fd < 0) { + return WH_ERROR_ABORTED; + } + + /* Lock mutex */ + pthread_mutex_lock(&context->mutex); + + /* Truncate file to zero length */ + if (ftruncate(context->fd, 0) != 0) { + ret = WH_ERROR_ABORTED; + } + + /* Seek to beginning */ + if (ret == 0) { + if (lseek(context->fd, 0, SEEK_SET) < 0) { + ret = WH_ERROR_ABORTED; + } + } + + /* Unlock mutex */ + pthread_mutex_unlock(&context->mutex); + + return ret; +} + +#endif /* WOLFHSM_CFG_LOGGING */ diff --git a/port/posix/posix_log_file.h b/port/posix/posix_log_file.h new file mode 100644 index 000000000..7a2e4da03 --- /dev/null +++ b/port/posix/posix_log_file.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 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 . + */ +/* + * port/posix/posix_log_file.h + * POSIX file-based logging backend with thread-safe access using mutex + * protection. Logs are written as flat text files with one entry per line. + */ + +#ifndef PORT_POSIX_POSIX_LOG_FILE_H_ +#define PORT_POSIX_POSIX_LOG_FILE_H_ + +#include + +#include "wolfhsm/wh_log.h" + +/* In memory context structure for POSIX file-based logging */ +typedef struct posixLogFileContext_t { + pthread_mutex_t mutex; /* Global file access mutex */ + int fd; /* File descriptor (-1 if not initialized) */ + char filename[256]; /* Fixed-size filename buffer */ + int initialized; /* Initialization flag */ +} posixLogFileContext; + +/* In memory configuration structure for POSIX file-based logging */ +typedef struct posixLogFileConfig_t { + const char* filename; /* Log file path (null terminated) */ +} posixLogFileConfig; + +/* Callback functions */ +int posixLogFile_Init(void* context, const void* config); +int posixLogFile_Cleanup(void* context); +int posixLogFile_AddEntry(void* context, const whLogEntry* entry); +/* Export log entries to FILE* specified by export_arg. + * @param context posixLogFileContext + * @param export_arg FILE* to write to, or NULL to write to stdout + * @return 0 on success, error code on failure */ +int posixLogFile_Export(void* context, void* export_arg); +/* Iterate log entries by parsing file and invoking callback for each entry. + * @param context posixLogFileContext + * @param iterate_cb User callback invoked for each parsed entry + * @param iterate_arg User argument passed to callback + * @return 0 on success, non-zero if callback stops iteration early */ +int posixLogFile_Iterate(void* context, whLogIterateCb iterate_cb, + void* iterate_arg); +int posixLogFile_Clear(void* context); + +/* Convenience macro for callback table initialization */ +/* clang-format off */ +#define POSIX_LOG_FILE_CB \ + { \ + .Init = posixLogFile_Init, \ + .Cleanup = posixLogFile_Cleanup, \ + .AddEntry = posixLogFile_AddEntry, \ + .Export = posixLogFile_Export, \ + .Iterate = posixLogFile_Iterate, \ + .Clear = posixLogFile_Clear, \ + } +/* clang-format on */ + +#endif /* !PORT_POSIX_POSIX_LOG_FILE_H_ */ diff --git a/port/posix/posix_time.c b/port/posix/posix_time.c new file mode 100644 index 000000000..3bb11a172 --- /dev/null +++ b/port/posix/posix_time.c @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2025 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 . + */ +/* + * port/posix/posix_time.c + * + * POSIX time helper returning the current time in microseconds. + */ + +#include + +#include "port/posix/posix_time.h" + +uint64_t posixGetTime(void) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_REALTIME, &ts) != 0) { + return 0; + } + + return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)(ts.tv_nsec / 1000); +} diff --git a/port/posix/posix_time.h b/port/posix/posix_time.h new file mode 100644 index 000000000..8dc64df91 --- /dev/null +++ b/port/posix/posix_time.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2025 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 . + */ +/* + * port/posix/posix_time.h + * + * POSIX time helper returning the current time in microseconds. + */ + +#ifndef PORT_POSIX_POSIX_TIME_H_ +#define PORT_POSIX_POSIX_TIME_H_ + +#include + +uint64_t posixGetTime(void); + +#endif /* PORT_POSIX_POSIX_TIME_H_ */ diff --git a/src/wh_log.c b/src/wh_log.c new file mode 100644 index 000000000..cb059009d --- /dev/null +++ b/src/wh_log.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2024 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 . + */ +/* + * src/wh_log.c + * + * Generic logging frontend implementation + */ + +/* Pick up compile-time configuration */ +#include "wolfhsm/wh_settings.h" + +#include +#include +#include +#include +#include + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_log.h" + +#ifdef WOLFHSM_CFG_LOGGING + +void wh_Log_AddMsg(whLogContext* ctx, whLogLevel level, const char* file, + const char* function, uint32_t line, const char* src, + size_t src_len) +{ + uint64_t timestamp = WH_GETTIME_US(); + size_t max_len = + (WOLFHSM_CFG_LOG_MSG_MAX > 0) ? (WOLFHSM_CFG_LOG_MSG_MAX - 1) : 0; + size_t copy_len = (src_len < max_len) ? src_len : max_len; + whLogEntry entry = {.timestamp = timestamp, + .level = level, + .file = file, + .function = function, + .line = line, + .msg_len = (uint32_t)copy_len}; + + if ((src != NULL) && (copy_len > 0)) { + memcpy(entry.msg, src, copy_len); + } + /* Zero-pad remainder of message buffer to prevent information leakage */ + memset(&entry.msg[copy_len], 0, WOLFHSM_CFG_LOG_MSG_MAX - copy_len); + + wh_Log_AddEntry(ctx, &entry); +} + +void wh_Log_AddMsgF(whLogContext* ctx, whLogLevel level, const char* file, + const char* function, uint32_t line, const char* fmt, ...) +{ + char buf[WOLFHSM_CFG_LOG_MSG_MAX]; + va_list args; + int ret; + size_t formatted_len; + + va_start(args, fmt); + ret = vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + /* vsnprintf returns the number of characters that would have been written + * (excluding null terminator) if the buffer was large enough. + * If ret < 0, there was an error; treat as empty string. + * If ret >= sizeof(buf), output was truncated. */ + if (ret < 0) { + formatted_len = 0; + } + else if ((size_t)ret >= sizeof(buf)) { + /* Truncated - actual length is sizeof(buf) - 1 */ + formatted_len = sizeof(buf) - 1; + } + else { + formatted_len = (size_t)ret; + } + + wh_Log_AddMsg(ctx, level, file, function, line, buf, formatted_len); +} + +int wh_Log_Init(whLogContext* ctx, const whLogConfig* config) +{ + int rc = 0; + + if ((ctx == NULL) || (config == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Callback table is required */ + if (config->cb == NULL) { + return WH_ERROR_BADARGS; + } + + ctx->cb = config->cb; + ctx->context = config->context; + + /* Backend doesn't support this operation. OK to fail here if desired */ + if (config->cb->Init == NULL) { + return WH_ERROR_NOTIMPL; + } + + /* Init the backend */ + rc = ctx->cb->Init(ctx->context, config->config); + if (rc != WH_ERROR_OK) { + /* Init failed, deinitialized context */ + ctx->cb = NULL; + ctx->context = NULL; + } + + return rc; +} + +int wh_Log_Cleanup(whLogContext* ctx) +{ + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + /* Init hasn't been called yet */ + if (ctx->cb == NULL) { + return WH_ERROR_ABORTED; + } + + /* Backend doesn't support this operation */ + if (ctx->cb->Cleanup == NULL) { + return WH_ERROR_NOTIMPL; + } + + return ctx->cb->Cleanup(ctx->context); +} + +int wh_Log_AddEntry(whLogContext* ctx, const whLogEntry* entry) +{ + if ((ctx == NULL) || (entry == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Init hasn't been called yet */ + if (ctx->cb == NULL) { + return WH_ERROR_ABORTED; + } + + /* Backend doesn't support this operation */ + if (ctx->cb->AddEntry == NULL) { + return WH_ERROR_NOTIMPL; + } + + return ctx->cb->AddEntry(ctx->context, entry); +} + +int wh_Log_Export(whLogContext* ctx, void* export_arg) +{ + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + /* Init hasn't been called yet */ + if (ctx->cb == NULL) { + return WH_ERROR_ABORTED; + } + + /* Backend doesn't support this operation */ + if (ctx->cb->Export == NULL) { + return WH_ERROR_NOTIMPL; + } + + return ctx->cb->Export(ctx->context, export_arg); +} + +int wh_Log_Iterate(whLogContext* ctx, whLogIterateCb iterate_cb, + void* iterate_arg) +{ + if ((ctx == NULL) || (iterate_cb == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Init hasn't been called yet */ + if (ctx->cb == NULL) { + return WH_ERROR_ABORTED; + } + + /* Backend doesn't support this operation */ + if (ctx->cb->Iterate == NULL) { + return WH_ERROR_NOTIMPL; + } + + return ctx->cb->Iterate(ctx->context, iterate_cb, iterate_arg); +} + +int wh_Log_Clear(whLogContext* ctx) +{ + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + /* Init hasn't been called yet */ + if (ctx->cb == NULL) { + return WH_ERROR_ABORTED; + } + + /* Backend doesn't support this operation */ + if (ctx->cb->Clear == NULL) { + return WH_ERROR_NOTIMPL; + } + + return ctx->cb->Clear(ctx->context); +} + +#endif /* WOLFHSM_CFG_LOGGING */ diff --git a/src/wh_log_ringbuf.c b/src/wh_log_ringbuf.c new file mode 100644 index 000000000..9a0edc0a3 --- /dev/null +++ b/src/wh_log_ringbuf.c @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2024 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 . + */ +/* + * src/wh_log_ringbuf.c + * + * Ring buffer logging backend implementation + */ + +#include /* For NULL */ +#include /* For memset, memcpy */ + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_log.h" +#include "wolfhsm/wh_log_ringbuf.h" + +#ifdef WOLFHSM_CFG_LOGGING + +int whLogRingbuf_Init(void* c, const void* cf) +{ + whLogRingbufContext* context = (whLogRingbufContext*)c; + const whLogRingbufConfig* config = (const whLogRingbufConfig*)cf; + size_t capacity; + + if (context == NULL || config == NULL || config->buffer == NULL || + config->buffer_size < sizeof(whLogEntry)) { + return WH_ERROR_BADARGS; + } + + /* Calculate capacity (number of complete entries that fit in buffer) */ + capacity = config->buffer_size / sizeof(whLogEntry); + /* Capacity must be able to hold at least one log entry, specifically to + * prevent divide-by-zeros in the rollover logic */ + if (capacity == 0) { + return WH_ERROR_BADARGS; + } + + /* Initialize context */ + memset(context, 0, sizeof(*context)); + context->entries = (whLogEntry*)config->buffer; + context->capacity = capacity; + context->count = 0; + context->initialized = 1; + + return WH_ERROR_OK; +} + +int whLogRingbuf_Cleanup(void* c) +{ + whLogRingbufContext* context = (whLogRingbufContext*)c; + + if (context == NULL) { + return WH_ERROR_BADARGS; + } + + if (context->initialized) { + (void)whLogRingbuf_Clear(context); + context->initialized = 0; + } + + return WH_ERROR_OK; +} + +int whLogRingbuf_AddEntry(void* c, const whLogEntry* entry) +{ + whLogRingbufContext* context = (whLogRingbufContext*)c; + size_t head; + + if ((context == NULL) || (entry == NULL)) { + return WH_ERROR_BADARGS; + } + + if (!context->initialized) { + return WH_ERROR_ABORTED; + } + + /* Calculate head position from count */ + head = context->count % context->capacity; + + /* Copy entry to ring buffer at head position */ + memcpy(&context->entries[head], entry, sizeof(whLogEntry)); + + /* Increment count freely to track total messages written */ + context->count++; + + return WH_ERROR_OK; +} + +int whLogRingbuf_Export(void* c, void* export_arg) +{ + (void)c; + (void)export_arg; + return WH_ERROR_OK; +} + +int whLogRingbuf_Iterate(void* c, whLogIterateCb iterate_cb, void* iterate_arg) +{ + whLogRingbufContext* context = (whLogRingbufContext*)c; + size_t capacity; + size_t num_entries; + size_t start_idx; + size_t i; + int ret = 0; + + if ((context == NULL) || (iterate_cb == NULL)) { + return WH_ERROR_BADARGS; + } + + if (!context->initialized) { + return WH_ERROR_ABORTED; + } + + /* If buffer is empty, nothing to iterate */ + if (context->count == 0) { + return WH_ERROR_OK; + } + + capacity = context->capacity; + + /* Calculate actual number of entries in buffer (capped at capacity) */ + num_entries = (context->count < capacity) ? context->count : capacity; + + /* Determine starting index for iteration: + * - If not full: start at 0 (oldest entry) + * - If full: start at head (oldest entry, about to be overwritten) + * head = count % capacity + */ + if (context->count < capacity) { + start_idx = 0; + } + else { + start_idx = context->count % capacity; + } + + /* Iterate through entries in chronological order */ + for (i = 0; i < num_entries; i++) { + size_t idx = (start_idx + i) % capacity; + ret = iterate_cb(iterate_arg, &context->entries[idx]); + if (ret != 0) { + /* User callback requested early termination */ + break; + } + } + + return ret; +} + +int whLogRingbuf_Clear(void* c) +{ + whLogRingbufContext* context = (whLogRingbufContext*)c; + + if (context == NULL) { + return WH_ERROR_BADARGS; + } + + /* Reset ring buffer state */ + context->count = 0; + + /* Zero the log entries */ + memset(context->entries, 0, context->capacity * sizeof(whLogEntry)); + + return WH_ERROR_OK; +} + +#endif /* WOLFHSM_CFG_LOGGING */ diff --git a/src/wh_server.c b/src/wh_server.c index 3ca4fa3e5..cf450db73 100644 --- a/src/wh_server.c +++ b/src/wh_server.c @@ -90,6 +90,16 @@ int wh_Server_Init(whServerContext* server, whServerConfig* config) #endif #endif +#ifdef WOLFHSM_CFG_LOGGING + if (config->logConfig != NULL) { + rc = wh_Log_Init(&server->log, config->logConfig); + if (rc != WH_ERROR_OK) { + (void)wh_Server_Cleanup(server); + return WH_ERROR_ABORTED; + } + } +#endif /* WOLFHSM_CFG_LOGGING */ + rc = wh_CommServer_Init(server->comm, config->comm_config, wh_Server_SetConnectedCb, (void*)server); if (rc != 0) { @@ -105,6 +115,9 @@ int wh_Server_Init(whServerContext* server, whServerConfig* config) } #endif /* WOLFHSM_CFG_DMA */ + /* Log the server startup */ + WH_LOG(&server->log, WH_LOG_LEVEL_INFO, "Server Initialized"); + return rc; } @@ -116,6 +129,13 @@ int wh_Server_Cleanup(whServerContext* server) (void)wh_CommServer_Cleanup(server->comm); + /* Log the server cleanup */ + WH_LOG(&server->log, WH_LOG_LEVEL_INFO, "Server Cleanup"); + +#ifdef WOLFHSM_CFG_LOGGING + (void)wh_Log_Cleanup(&server->log); +#endif /* WOLFHSM_CFG_LOGGING */ + memset(server, 0, sizeof(*server)); return WH_ERROR_OK; @@ -202,6 +222,10 @@ static int _wh_Server_HandleCommRequest(whServerContext* server, resp.client_id = server->comm->client_id; resp.server_id = server->comm->server_id; + WH_LOG_F(&server->log, WH_LOG_LEVEL_INFO, + "CommInit: client_id=0x%08X, server_id=0x%08X", req.client_id, + resp.server_id); + /* Convert the response struct */ wh_MessageComm_TranslateInitResponse(magic, &resp, (whMessageCommInitResponse*)resp_packet); @@ -249,6 +273,9 @@ static int _wh_Server_HandleCommRequest(whServerContext* server, /* Process the close action */ wh_Server_SetConnected(server, WH_COMM_DISCONNECTED); *out_resp_size = 0; + + WH_LOG_F(&server->log, WH_LOG_LEVEL_INFO, "CommClose: client_id=0x%08X", + server->comm->client_id); }; break; case WH_MESSAGE_COMM_ACTION_ECHO: @@ -403,6 +430,10 @@ int wh_Server_HandleRequestMessage(whServerContext* server) size, data); } while (rc == WH_ERROR_NOTREADY); } + WH_LOG_ON_ERROR_F( + &server->log, WH_LOG_LEVEL_ERROR, rc, + "Request Handler for (group=%d, action=%d) Returned Error: %d", + group, action, rc); } return rc; diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index dcfd24799..d8960cfa4 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -41,6 +41,7 @@ #include "wolfhsm/wh_message_keystore.h" #include "wolfhsm/wh_utils.h" #include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_log.h" #ifdef WOLFHSM_CFG_SHE_EXTENSION #include "wolfhsm/wh_server_she.h" @@ -1007,8 +1008,15 @@ static int _HandleKeyWrapRequest(whServerContext* server, memcpy(&metadata, reqData, sizeof(metadata)); memcpy(key, reqData + sizeof(metadata), req->keySz); + /* Ensure the cipher type in the response matches the request */ + resp->cipherType = req->cipherType; + /* Wrapped key size is only passed back to the client on success */ + resp->wrappedKeySz = 0; + /* Ensure the keyId in the wrapped metadata has the wrapped flag set */ if (!WH_KEYID_ISWRAPPED(metadata.id)) { + WH_LOG_F(&server->log, WH_LOG_LEVEL_ERROR, + "KeyWrapRequest: keyId:0x%08X is not wrapped", metadata.id); return WH_ERROR_BADARGS; } @@ -1017,11 +1025,6 @@ static int _HandleKeyWrapRequest(whServerContext* server, server->comm->client_id, req->serverKeyId); - /* Ensure the cipher type in the response matches the request */ - resp->cipherType = req->cipherType; - /* Wrapped key size is only passed back to the client on success */ - resp->wrappedKeySz = 0; - /* Store the wrapped key in the response data */ wrappedKey = respData; @@ -1461,13 +1464,12 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, meta->access = WH_NVM_ACCESS_ANY; meta->flags = req.flags; meta->len = req.sz; - /* validate label sz */ + /* truncate label if it's too large */ if (req.labelSz > WH_NVM_LABEL_LEN) { - ret = WH_ERROR_BADARGS; - } - else { - memcpy(meta->label, req.label, req.labelSz); + req.labelSz = WH_NVM_LABEL_LEN; } + memcpy(meta->label, req.label, req.labelSz); + /* get a new id if one wasn't provided */ if (WH_KEYID_ISERASED(meta->id)) { ret = wh_Server_KeystoreGetUniqueId(server, &meta->id); @@ -1510,14 +1512,11 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, meta->access = WH_NVM_ACCESS_ANY; meta->flags = req.flags; meta->len = req.key.sz; - - /* validate label sz */ + /* truncate label if it's too large */ if (req.labelSz > WH_NVM_LABEL_LEN) { - ret = WH_ERROR_BADARGS; - } - else { - memcpy(meta->label, req.label, req.labelSz); + req.labelSz = WH_NVM_LABEL_LEN; } + memcpy(meta->label, req.label, req.labelSz); /* get a new id if one wasn't provided */ if (WH_KEYID_ISERASED(meta->id)) { @@ -1633,6 +1632,9 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, ret = WH_ERROR_ACCESS; /* Clear any key data that may have been read */ memset(out, 0, keySz); + WH_LOG_F(&server->log, WH_LOG_LEVEL_SECEVENT, + "Export attempt for non-exportable keyId 0x%08X", + meta->id); } resp.rc = ret; diff --git a/test/config/wolfhsm_cfg.h b/test/config/wolfhsm_cfg.h index 9a0d19154..6c8b926f0 100644 --- a/test/config/wolfhsm_cfg.h +++ b/test/config/wolfhsm_cfg.h @@ -25,6 +25,9 @@ #ifndef WOLFHSM_CFG_H_ #define WOLFHSM_CFG_H_ +#include "port/posix/posix_time.h" +#define WOLFHSM_CFG_PORT_GETTIME posixGetTime + /** wolfHSM settings. Simple overrides to show they work */ /* #define WOLFHSM_CFG_NO_CRYPTO */ @@ -35,6 +38,9 @@ /* Enable global keys feature for testing */ #define WOLFHSM_CFG_GLOBAL_KEYS +/* Enable logging feature for testing */ +#define WOLFHSM_CFG_LOGGING + #define WOLFHSM_CFG_NVM_OBJECT_COUNT 30 #define WOLFHSM_CFG_SERVER_KEYCACHE_COUNT 9 #define WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE 300 @@ -59,6 +65,7 @@ #define WOLFHSM_CFG_CANCEL_API #endif +/* Test log-based NVM flash backend */ #define WOLFHSM_CFG_SERVER_NVM_FLASH_LOG #endif /* WOLFHSM_CFG_H_ */ diff --git a/test/wh_test.c b/test/wh_test.c index dad3f9865..d5d7eb1b4 100644 --- a/test/wh_test.c +++ b/test/wh_test.c @@ -38,6 +38,7 @@ #include "wh_test_clientserver.h" #include "wh_test_keywrap.h" #include "wh_test_multiclient.h" +#include "wh_test_log.h" #if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) #include "wh_test_cert.h" @@ -66,6 +67,9 @@ int whTest_Unit(void) /* Component Tests */ WH_TEST_ASSERT(0 == whTest_Flash_RamSim()); WH_TEST_ASSERT(0 == whTest_NvmFlash()); +#ifdef WOLFHSM_CFG_LOGGING + WH_TEST_ASSERT(0 == whTest_Log()); +#endif #if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && !defined(WOLFHSM_CFG_NO_CRYPTO) WH_TEST_ASSERT(0 == whTest_CertRamSim(WH_NVM_TEST_BACKEND_FLASH)); diff --git a/test/wh_test_log.c b/test/wh_test_log.c new file mode 100644 index 000000000..f2da3ee3a --- /dev/null +++ b/test/wh_test_log.c @@ -0,0 +1,1586 @@ +/* + * Copyright (C) 2024 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/wh_test_log.c + * + * Unit tests for logging module + */ + +#include +#include +#include + +#include "wolfhsm/wh_settings.h" +#include "wh_test_common.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_log.h" +#include "wolfhsm/wh_log_ringbuf.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_transport_mem.h" + +#if defined(WOLFHSM_CFG_ENABLE_CLIENT) +#include "wolfhsm/wh_client.h" +#endif + +#if defined(WOLFHSM_CFG_ENABLE_SERVER) +#include "wolfhsm/wh_server.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 + +#if defined(WOLFHSM_CFG_TEST_POSIX) +#include +#include +#include "port/posix/posix_log_file.h" +#endif + +#include "wh_test_log.h" + +#ifdef WOLFHSM_CFG_LOGGING + +#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 2 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) { + printf(" 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) { + printf("ERROR: 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[numLogEntries]; + + /* 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); + + /* Initialize callback table */ + whLogCb ringbufCb = WH_LOG_RINGBUF_CB; + + 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; +} + +#if defined(WOLFHSM_CFG_TEST_POSIX) + +/* POSIX file backend tests */ +static int whTest_LogPosixFile(void) +{ + 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; + + /* 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 */ + FILE* 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; + char line[2048]; + 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, iterateCallbackCount, &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; +} + +static int whTest_LogPosixFileConcurrent(void) +{ + 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; + + /* 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 */ + FILE* 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; + char line[2048]; + 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; +} + +#endif /* WOLFHSM_CFG_TEST_POSIX */ + +/* Generic backend tests using test harness */ + +/* 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); +} + +#if defined(WOLFHSM_CFG_TEST_POSIX) +/* POSIX file backend generic tests */ +static int whTest_LogPosixFile_Generic(void) +{ + posixLogFileContext posixCtx; + posixLogFileConfig posixCfg; + whTestLogBackendTestConfig testCfg; + whLogCb posixCb; + const char* test_log_file = "/tmp/wolfhsm_test_generic.log"; + + /* 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); +} +#endif /* WOLFHSM_CFG_TEST_POSIX */ + +#if defined(WOLFHSM_CFG_TEST_POSIX) && 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) +{ + /* 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 */ + FILE* log_file = fopen(WH_LOG_TEST_SERVER_LOG_FILE, "r"); + WH_TEST_ASSERT(log_file != NULL); + + /* Basic smoke test: Check that there is at least one log entry in server + * log file */ + size_t entry_count = 0; + char line[1024]; + + /* Ensure there are at least 3 log entries and that they are somewhat sanely + * ordered */ + 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); + } + 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); + } + else { + break; + } + } + fclose(log_file); + + /* Ensure we have at least the number of expected log entries */ + WH_TEST_ASSERT(entry_count >= 3); + + 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)); + + /* Placeholder for future log-oriented client actions. */ + 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); +} + +static int whTest_LogClientServerMemTransport(void) +{ + 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]; + whNvmContext nvm[1] = {{0}}; + + WH_TEST_RETURN_ON_FAIL(whTest_NvmCfgBackend( + WH_NVM_TEST_BACKEND_FLASH, &nvm_setup, n_conf, fc_conf, fc, fcb)); + +#ifndef WOLFHSM_CFG_NO_CRYPTO + whServerCryptoContext crypto[1] = {{ + .devId = INVALID_DEVID, + }}; +#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, + }}; + + unlink(WH_LOG_TEST_SERVER_LOG_FILE); + + whServerConfig s_conf[1] = {{ + .comm_config = cs_conf, + .nvm = nvm, +#ifndef WOLFHSM_CFG_NO_CRYPTO + .crypto = crypto, + .devId = INVALID_DEVID, +#endif + .logConfig = logConfig, + }}; + + 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, crypto->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; +} +#endif /* WOLFHSM_CFG_TEST_POSIX && WOLFHSM_CFG_ENABLE_CLIENT && \ + WOLFHSM_CFG_ENABLE_SERVER */ + +/* Main test entry point */ +int whTest_Log(void) +{ + printf("Testing log frontend API...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogFrontend()); + + printf("Testing log macros...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogMacros()); + + printf("Testing formatted log macros...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogFormattedMacros()); + + printf("Running Generic Backend Tests...\n"); + + printf("Testing mock log backend in generic harness...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogMock_Generic()); + + printf("Testing ringbuf backend in generic harness...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogRingbuf_Generic()); + +#if defined(WOLFHSM_CFG_TEST_POSIX) + printf("Testing posix file backend in generic harness...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogPosixFile_Generic()); +#endif + + printf("Running Backend-Specific Tests...\n"); + + printf("Testing ring buffer backend...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogRingbuf()); + +#if defined(WOLFHSM_CFG_TEST_POSIX) + printf("Testing POSIX file backend...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogPosixFile()); + + printf("Testing POSIX file backend with concurrent access...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogPosixFileConcurrent()); + +#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && defined(WOLFHSM_CFG_ENABLE_SERVER) + printf("Testing log client/server over mem transport...\n"); + WH_TEST_RETURN_ON_FAIL(whTest_LogClientServerMemTransport()); +#endif +#endif + + printf("Log tests PASSED\n"); + + return WH_ERROR_OK; +} + +#endif /* WOLFHSM_CFG_LOGGING */ diff --git a/test/wh_test_log.h b/test/wh_test_log.h new file mode 100644 index 000000000..5606da795 --- /dev/null +++ b/test/wh_test_log.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 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/wh_test_log.h + * + */ + +#ifndef TEST_WH_TEST_LOG_H_ +#define TEST_WH_TEST_LOG_H_ + +#include + +#include "wolfhsm/wh_log.h" + +/* + * Configuration structure for backend testing + */ +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 config 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 for the built-in backends. + * Returns 0 on success, non-zero on failure. + */ +int whTest_LogBackend_RunAll(whTestLogBackendTestConfig* cfg); + + +/* + * Runs all logging module tests including frontend API, macros, and + * POSIX file backend tests (if WOLFHSM_CFG_TEST_POSIX is defined). + * + * Returns 0 on success and a non-zero error code on failure + */ +int whTest_Log(void); + +#endif /* TEST_WH_TEST_LOG_H_ */ diff --git a/tools/whnvmtool/Makefile b/tools/whnvmtool/Makefile index b82faed1e..fe3ead8b7 100644 --- a/tools/whnvmtool/Makefile +++ b/tools/whnvmtool/Makefile @@ -50,7 +50,7 @@ LIBS = \ LIB_DIRS = CFLAGS = -Wall $(INCLUDE_DIRS) -CFLAGS += -DWOLFSSL_USER_SETTINGS -DWOLFHSM_CFG_ENABLE_SERVER +CFLAGS += -DWOLFSSL_USER_SETTINGS -DWOLFHSM_CFG_ENABLE_SERVER -DWOLFHSM_CFG_NO_SYS_TIME CFLAGS += -std=c90 -D_GNU_SOURCE -Wno-cpp ifneq ($(NOCRYPTO),1) diff --git a/tools/whnvmtool/test/Makefile b/tools/whnvmtool/test/Makefile index 3a4d45bf5..f8d66ccda 100644 --- a/tools/whnvmtool/test/Makefile +++ b/tools/whnvmtool/test/Makefile @@ -44,7 +44,7 @@ LIBS = \ LIB_DIRS = CFLAGS = -Wall $(INCLUDE_DIRS) -CFLAGS += -DWOLFSSL_USER_SETTINGS -DWOLFHSM_CFG_ENABLE_SERVER +CFLAGS += -DWOLFSSL_USER_SETTINGS -DWOLFHSM_CFG_ENABLE_SERVER -DWOLFHSM_CFG_NO_SYS_TIME CFLAGS_EXTRA = # Additional CFLAGS from the command line LDFLAGS = $(LIB_DIRS) $(LIBS) OUT = $(TARGET) # Output executable name diff --git a/wolfhsm/wh_log.h b/wolfhsm/wh_log.h new file mode 100644 index 000000000..85d610722 --- /dev/null +++ b/wolfhsm/wh_log.h @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2024 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 . + */ +/* + * wolfhsm/wh_log.h + * + * Generic logging frontend API with callback backend interface + */ + +#ifndef WOLFHSM_WH_LOG_H_ +#define WOLFHSM_WH_LOG_H_ + +/* Pick up compile-time configuration */ +#include "wolfhsm/wh_settings.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_utils.h" + +#include +#include +#include +#include + +/* Log levels */ +typedef enum { + WH_LOG_LEVEL_INFO = 0, /* Informational message */ + WH_LOG_LEVEL_ERROR = 1, /* Error message */ + WH_LOG_LEVEL_SECEVENT = 2 /* Security event */ +} whLogLevel; + +/* Log entry structure with fixed-size message buffer */ +typedef struct { + uint64_t timestamp; /* Unix timestamp (microseconds) */ + whLogLevel level; /* Log level */ + const char* file; /* Source file (__FILE__) */ + const char* function; /* Function name (__func__) */ + uint32_t line; /* Line number (__LINE__) */ + uint32_t msg_len; /* Actual message length (excluding + * null terminator) */ + char msg[WOLFHSM_CFG_LOG_MSG_MAX]; /* Fixed buffer with null + * terminator */ +} whLogEntry; + +/* User-provided callback for iterating log entries. + * Return 0 to continue iteration, non-zero to stop early. */ +typedef int (*whLogIterateCb)(void* arg, const whLogEntry* entry); + +/* Backend callback interface */ +typedef struct { + int (*Init)(void* context, const void* config); + int (*Cleanup)(void* context); + int (*AddEntry)(void* context, const whLogEntry* entry); + int (*Export)(void* context, void* export_arg); + int (*Iterate)(void* context, whLogIterateCb iterate_cb, void* iterate_arg); + int (*Clear)(void* context); +} whLogCb; + +/* Frontend context */ +typedef struct { + whLogCb* cb; /* Callback table */ + void* context; /* Opaque backend context */ +} whLogContext; + +/** Frontend configuration */ +typedef struct { + whLogCb* cb; /* Callback table */ + void* context; /* Pre-allocated backend context */ + void* config; /* Backend-specific config */ +} whLogConfig; + +/* Frontend API */ + +/** + * @brief Initialize the logging context with a backend configuration. + * + * This function initializes a logging context by setting up the callback + * interface and initializing the backend logging system. The context must + * be properly initialized before any other logging operations can be performed. + * + * @param ctx Pointer to the logging context to initialize. Must not be NULL. + * @param config Pointer to the logging configuration containing the callback + * table, backend context, and backend-specific configuration. + * + * @return WH_ERROR_OK on success. + * @return WH_ERROR_BADARGS if ctx or config is NULL, or if config->cb is NULL. + * @return WH_ERROR_NOTIMPL if the backend does not support initialization. + * @return Other error codes may be returned by the backend Init callback. + */ +int wh_Log_Init(whLogContext* ctx, const whLogConfig* config); + +/** Cleanup and deinitialize the logging context. + * + * This function performs cleanup operations on the logging context, including + * calling the backend cleanup callback. After cleanup, the context should not + * be used until reinitialized with wh_Log_Init(). + * + * @param ctx Pointer to the logging context to cleanup. Must not be NULL. + * + * @return WH_ERROR_OK on success. + * @return WH_ERROR_BADARGS if ctx is NULL. + * @return WH_ERROR_ABORTED if the context has not been initialized. + * @return WH_ERROR_NOTIMPL if the backend does not support cleanup. + * @return Other error codes may be returned by the backend Cleanup callback. + */ +int wh_Log_Cleanup(whLogContext* ctx); + +/** + * @brief Add a complete log entry to the logging system. + * + * This function adds a fully-formed log entry to the logging backend. The + * entry must contain all required fields including timestamp, level, source + * location, and message. This is a low-level function typically used by + * higher-level logging functions like wh_Log_AddMsg(). + * + * @param ctx Pointer to the initialized logging context. Must not be NULL. + * @param entry Pointer to the log entry structure containing all log + * information. Must not be NULL. The entry->msg buffer must be + * properly formatted with a null terminator, and entry->msg_len + * must reflect the actual message length. + * + * @return WH_ERROR_OK on success. + * @return WH_ERROR_BADARGS if ctx or entry is NULL. + * @return WH_ERROR_ABORTED if the context has not been initialized. + * @return WH_ERROR_NOTIMPL if the backend does not support adding entries. + * @return Other error codes may be returned by the backend AddEntry callback. + * + * @note Message truncation will occur if the message length exceeds + * WOLFHSM_CFG_LOG_MSG_MAX. + */ +int wh_Log_AddEntry(whLogContext* ctx, const whLogEntry* entry); + +/** + * @brief Add a log message with source location information. + * + * This function creates a log entry from a string message and automatically + * captures the current timestamp. The message is copied into the log entry + * buffer, with truncation if necessary. The remainder of the message buffer + * is zero-padded to prevent information leakage. + * + * @param ctx Pointer to the initialized logging context. Must not be NULL. + * @param level Log level for the message (WH_LOG_LEVEL_INFO, + * WH_LOG_LEVEL_ERROR, or WH_LOG_LEVEL_SECEVENT). + * @param file Source file name (typically __FILE__). May be NULL. + * @param function Function name (typically __func__). May be NULL. + * @param line Line number (typically __LINE__). + * @param msg Pointer to the message string to log. May be NULL, in which case + * an empty message is logged. + * @param msg_len Length of the message string in bytes, excluding the null + * terminator. If msg_len is 0, an empty message is logged. + * + * @note The message is truncated to WOLFHSM_CFG_LOG_MSG_MAX - 1 bytes if + * msg_len exceeds the maximum. + * @note The WH_LOG() macro provides a more convenient interface that + * automatically captures __FILE__, __func__, and __LINE__. + */ +void wh_Log_AddMsg(whLogContext* ctx, whLogLevel level, const char* file, + const char* function, uint32_t line, const char* msg, + size_t msg_len); + +/** Add a formatted log message with source location information. + * + * This function creates a log entry from a printf-style format string and + * variadic arguments. The formatted message is generated using vsnprintf() + * and then passed to wh_Log_AddMsg(). This allows for dynamic message + * construction with variable arguments. + * + * @param ctx Pointer to the initialized logging context. Must not be NULL. + * @param level Log level for the message (WH_LOG_LEVEL_INFO, + * WH_LOG_LEVEL_ERROR, or WH_LOG_LEVEL_SECEVENT). + * @param file Source file name (typically __FILE__). May be NULL. + * @param function Function name (typically __func__). May be NULL. + * @param line Line number (typically __LINE__). + * @param fmt printf-style format string. Must not be NULL. + * @param ... Variadic arguments matching the format specifiers in fmt. + * + * @note The formatted message is truncated to WOLFHSM_CFG_LOG_MSG_MAX - 1 bytes + * if the formatted output exceeds the maximum. + * @note The WH_LOG_F() macro provides a more convenient interface that + * automatically captures __FILE__, __func__, and __LINE__. + */ +void wh_Log_AddMsgF(whLogContext* ctx, whLogLevel level, const char* file, + const char* function, uint32_t line, const char* fmt, ...); + +/** Export log entries from the logging backend. + * + * This function triggers an export operation in the logging backend, allowing + * log entries to be exported to an external format or location. The exact + * behavior and format of the export is backend-specific + * + * @param ctx Pointer to the initialized logging context. Must not be NULL. + * @param export_arg Backend-specific argument for the export operation. + * The interpretation of this parameter depends on the + * backend implementation. May be NULL if the backend + * does not require additional arguments. + * + * @return WH_ERROR_OK on success. + * @return WH_ERROR_BADARGS if ctx is NULL. + * @return WH_ERROR_ABORTED if the context has not been initialized. + * @return WH_ERROR_NOTIMPL if the backend does not support export. + * @return Other error codes may be returned by the backend Export callback. + * + * @note The export operation is backend-specific. Consult the backend + * documentation for details on the export_arg parameter and export + * format. + */ +int wh_Log_Export(whLogContext* ctx, void* export_arg); + +/** + * @brief Iterate over log entries using a callback function. + * + * This function iterates through all log entries in the logging backend, + * calling the provided callback function for each entry. The iteration + * continues until all entries have been processed or the callback returns + * a non-zero value to stop early. + * + * @param ctx Pointer to the initialized logging context. Must not be NULL. + * @param iterate_cb Callback function to call for each log entry. Must not be + * NULL. The callback receives iterate_arg and a pointer to + * the current log entry. Returns 0 to continue iteration, + * non-zero to stop early. + * @param iterate_arg User-provided argument passed to the callback function + * on each invocation. May be NULL. + * + * @return WH_ERROR_OK on success (all entries processed or iteration stopped + * by callback). + * @return WH_ERROR_BADARGS if ctx or iterate_cb is NULL. + * @return WH_ERROR_ABORTED if the context has not been initialized. + * @return WH_ERROR_NOTIMPL if the backend does not support iteration. + * @return Other error codes may be returned by the backend Iterate callback. + * + * @note The order of iteration is backend-specific and may not be guaranteed + * to be chronological or any particular order + */ +int wh_Log_Iterate(whLogContext* ctx, whLogIterateCb iterate_cb, + void* iterate_arg); + +/** Clear all log entries from the logging backend. + * + * This function removes all log entries from the logging backend, effectively + * resetting the log to an empty state. After clearing, no log entries will + * be available until new entries are added. + * + * @param ctx Pointer to the initialized logging context. Must not be NULL. + * + * @return WH_ERROR_OK on success. + * @return WH_ERROR_BADARGS if ctx is NULL. + * @return WH_ERROR_ABORTED if the context has not been initialized. + * @return WH_ERROR_NOTIMPL if the backend does not support clearing. + * @return Other error codes may be returned by the backend Clear callback. + * + * @note The behavior of this function is backend-specific. Some backends may + * immediately free storage, while others may mark entries for deletion. + */ +int wh_Log_Clear(whLogContext* ctx); + +/* Logging helper macros. These should be used as the primary API to the logging + * interface */ +#ifdef WOLFHSM_CFG_LOGGING + +/* String literal logging macro */ +#define WH_LOG(ctx, lvl, message) \ + do { \ + wh_Log_AddMsg((ctx), (lvl), __FILE__, __func__, __LINE__, (message), \ + sizeof(message) - 1); \ + } while (0) + +/* Formatted logging macro (printf-style variadic arguments) using vsnprintf */ +#if !defined(__CCRH__) +#define WH_LOG_F(ctx, lvl, fmt, ...) \ + do { \ + wh_Log_AddMsgF((ctx), (lvl), __FILE__, __func__, __LINE__, (fmt), \ + ##__VA_ARGS__); \ + } while (0) +#else +/* CCRH workaround for empty __VA_ARGS__ */ +#define WH_LOG_F(ctx, lvl, ...) WH_LOG_F2((ctx), (lvl), __VA_ARGS__, "") +#define WH_LOG_F2(ctx, lvl, fmt, ...) \ + do { \ + wh_Log_AddMsgF((ctx), (lvl), __FILE__, __func__, __LINE__, (fmt), \ + ##__VA_ARGS__); \ + } while (0) +#endif + +/* Assertion logging helpers: + * - Log only when (cond) is false, then return (retcode) + * - Variants for string literal message, and formatted C string message + */ +#define WH_LOG_ASSERT(ctx, lvl, cond, message) \ + do { \ + if (!(cond)) { \ + wh_Log_AddMsg((ctx), (lvl), __FILE__, __func__, __LINE__, \ + (message), sizeof(message) - 1); \ + } \ + } while (0) + + +/* Supports printf-style formatting with vsnprintf */ +#define WH_LOG_ASSERT_F(ctx, lvl, cond, fmt, ...) \ + do { \ + if (!(cond)) { \ + WH_LOG_F((ctx), (lvl), (fmt), ##__VA_ARGS__); \ + } \ + } while (0) + +/* Log on error helpers: + * - Log only when (rc) is not equal to WH_ERROR_OK + * - Variants for string literal message, and formatted C string message + */ +#define WH_LOG_ON_ERROR(ctx, lvl, rc, message) \ + do { \ + if ((rc) != WH_ERROR_OK) { \ + wh_Log_AddMsg((ctx), (lvl), __FILE__, __func__, __LINE__, \ + (message), sizeof(message) - 1); \ + } \ + } while (0) + +#define WH_LOG_ON_ERROR_F(ctx, lvl, rc, fmt, ...) \ + do { \ + if ((rc) != WH_ERROR_OK) { \ + WH_LOG_F((ctx), (lvl), (fmt), ##__VA_ARGS__); \ + } \ + } while (0) + + +#else /* !WOLFHSM_CFG_LOGGING */ + +/* Stub macros that compile to nothing when logging is disabled */ +#define WH_LOG(ctx, lvl, message) \ + do { \ + } while (0) +#define WH_LOG_F(ctx, lvl, fmt, ...) \ + do { \ + } while (0) + +#define WH_LOG_ASSERT(ctx, lvl, cond, message) \ + do { \ + } while (0) +#define WH_LOG_ASSERT_F(ctx, lvl, cond, fmt, ...) \ + do { \ + } while (0) + +#define WH_LOG_ON_ERROR(ctx, lvl, rc, message) \ + do { \ + } while (0) +#define WH_LOG_ON_ERROR_F(ctx, lvl, rc, fmt, ...) \ + do { \ + } while (0) + +#endif /* WOLFHSM_CFG_LOGGING */ + +#endif /* WOLFHSM_WH_LOG_H_ */ diff --git a/wolfhsm/wh_log_ringbuf.h b/wolfhsm/wh_log_ringbuf.h new file mode 100644 index 000000000..d7f04548f --- /dev/null +++ b/wolfhsm/wh_log_ringbuf.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 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 . + */ +/* + * wolfhsm/wh_log_ringbuf.h + * + * Ring buffer logging backend with fixed capacity in RAM. Simple, portable, + * and not thread-safe. Overwrites oldest entries when full. + */ + +#ifndef WOLFHSM_WH_LOG_RINGBUF_H_ +#define WOLFHSM_WH_LOG_RINGBUF_H_ + +#include "wolfhsm/wh_settings.h" +#include "wolfhsm/wh_log.h" + +#include + +/* Ring buffer configuration structure */ +typedef struct whLogRingbufConfig_t { + void* buffer; /* User-supplied buffer */ + size_t buffer_size; /* Size of buffer in bytes */ +} whLogRingbufConfig; + +/* Ring buffer context structure */ +typedef struct whLogRingbufContext_t { + whLogEntry* entries; /* Pointer to user-supplied buffer */ + size_t capacity; /* Number of entries buffer can hold */ + size_t count; /* Total entries ever written (head = count % capacity) */ + int initialized; /* Initialization flag */ +} whLogRingbufContext; + +/* Callback functions */ +int whLogRingbuf_Init(void* context, const void* config); +int whLogRingbuf_Cleanup(void* context); +int whLogRingbuf_AddEntry(void* context, const whLogEntry* entry); +int whLogRingbuf_Export(void* context, void* export_arg); +int whLogRingbuf_Iterate(void* context, whLogIterateCb iterate_cb, + void* iterate_arg); +int whLogRingbuf_Clear(void* context); + +/* Convenience macro for callback table initialization. + */ +/* clang-format off */ +#define WH_LOG_RINGBUF_CB \ + { \ + .Init = whLogRingbuf_Init, \ + .Cleanup = whLogRingbuf_Cleanup, \ + .AddEntry = whLogRingbuf_AddEntry, \ + .Export = whLogRingbuf_Export, \ + .Iterate = whLogRingbuf_Iterate, \ + .Clear = whLogRingbuf_Clear, \ + } +/* clang-format on */ + +#endif /* !WOLFHSM_WH_LOG_RINGBUF_H_ */ diff --git a/wolfhsm/wh_server.h b/wolfhsm/wh_server.h index 2c3dc02a3..4e9226583 100644 --- a/wolfhsm/wh_server.h +++ b/wolfhsm/wh_server.h @@ -41,6 +41,7 @@ typedef struct whServerContext_t whServerContext; #include "wolfhsm/wh_keycache.h" #include "wolfhsm/wh_nvm.h" #include "wolfhsm/wh_message_customcb.h" +#include "wolfhsm/wh_log.h" #ifdef WOLFHSM_CFG_DMA #include "wolfhsm/wh_dma.h" #endif /* WOLFHSM_CFG_DMA */ @@ -164,6 +165,9 @@ typedef struct whServerConfig_t { #ifdef WOLFHSM_CFG_DMA whServerDmaConfig* dmaConfig; #endif /* WOLFHSM_CFG_DMA */ +#ifdef WOLFHSM_CFG_LOGGING + whLogConfig* logConfig; +#endif /* WOLFHSM_CFG_LOGGING */ } whServerConfig; @@ -186,6 +190,9 @@ struct whServerContext_t { #ifdef WOLFHSM_CFG_CANCEL_API uint16_t cancelSeq; #endif +#ifdef WOLFHSM_CFG_LOGGING + whLogContext log; +#endif /* WOLFHSM_CFG_LOGGING */ }; diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h index 981444774..6bac3c915 100644 --- a/wolfhsm/wh_settings.h +++ b/wolfhsm/wh_settings.h @@ -108,6 +108,17 @@ * Example custom definition: * #define WOLFHSM_CFG_PRINTF my_custom_printf * + * WOLFHSM_CFG_NO_SYS_TIME - If defined, all internal calls to obtain the + system + * time will return 0, removing the need to provide + WOLFHSM_CFG_PORT_GETTIME + * for the active port. Note that this will result in nonsensical benchmark + * results and log timestamps. + * + * WOLFHSM_CFG_PORT_GETTIME - Function-like macro returning the current system + * time in microseconds as a uint64_t. Must be defined in wolfhsm_cfg.h for + * the active port UNLESS WOLFHSM_CFG_NO_SYS_TIME is defined + * Overridable porting functions: * * XMEMFENCE() - Create a sequential memory consistency sync point. Note this @@ -119,21 +130,23 @@ * XCACHELINE - Size in bytes of a cache line * Default: 32 * - * #ifndef XCACHEFLUSH(ptr) - Flush the cache line including ptr + * XCACHEFLUSH(ptr) - Flush the cache line including ptr * DefaultL (void)(ptr) * - * #ifndef XCACHEFLUSHBLK(ptr, n) - Flush the cache lines starting at ptr for + * XCACHEFLUSHBLK(ptr, n) - Flush the cache lines starting at ptr for * at least n bytes * DefaultL wh_Utils_CacheFlush(ptr, n) * - * #ifndef XCACHEINVLD(ptr) - Invalidate the cache line including ptr + * XCACHEINVLD(ptr) - Invalidate the cache line including ptr * DefaultL (void)(ptr) * - * #ifndef XCACHEINVLDBLK(ptr, n) - Invalidate the cache lines starting at ptr + * XCACHEINVLDBLK(ptr, n) - Invalidate the cache lines starting at ptr * for at least n bytes * DefaultL wh_Utils_CacheInvalidate(ptr, n) * * + * + * */ #ifndef WOLFHSM_WH_SETTINGS_H_ @@ -143,6 +156,8 @@ #include "wolfhsm_cfg.h" #endif +#include + #ifndef WOLFHSM_CFG_NO_CRYPTO #ifdef WOLFSSL_USER_SETTINGS #include "user_settings.h" @@ -153,6 +168,18 @@ #endif #endif /* !WOLFHSM_CFG_NO_CRYPTO */ +/* Platform system time access */ +#if !defined WOLFHSM_CFG_NO_SYS_TIME && !defined(WOLFHSM_CFG_PORT_GETTIME) +#error \ + "WOLFHSM_CFG_PORT_GETTIME must be defined to a function returning current time in microseconds" +#endif + +#if defined(WOLFHSM_CFG_NO_SYS_TIME) +#define WH_GETTIME_US() ((uint64_t)0) +#else +#define WH_GETTIME_US() ((uint64_t)(WOLFHSM_CFG_PORT_GETTIME)()) +#endif + /** Default shares configurations */ /* Maximum length of the data portion of a request/reply message */ #ifndef WOLFHSM_CFG_COMM_DATA_LEN @@ -221,6 +248,13 @@ #define WOLFHSM_CFG_CUSTOMCB_LEN 256 #endif +/* WOLFHSM_CFG_LOG_MSG_MAX - Maximum size of a log message including null + * terminator. + * Default: 256 */ +#ifndef WOLFHSM_CFG_LOG_MSG_MAX +#define WOLFHSM_CFG_LOG_MSG_MAX 256 +#endif + /* Maximum size of a certificate */ #ifndef WOLFHSM_CFG_MAX_CERT_SIZE #ifndef WOLFHSM_CFG_DMA