diff --git a/src/wh_nvm.c b/src/wh_nvm.c
index ae6c662d1..2ea7d7c82 100644
--- a/src/wh_nvm.c
+++ b/src/wh_nvm.c
@@ -192,7 +192,7 @@ int wh_Nvm_DestroyObjects(whNvmContext* context, whNvmId list_count,
int wh_Nvm_Read(whNvmContext* context, whNvmId id, whNvmSize offset,
- whNvmSize data_len, uint8_t* data)
+ whNvmSize data_len, uint8_t* data)
{
if ( (context == NULL) ||
(context->cb == NULL) ) {
diff --git a/src/wh_nvm_flash.c b/src/wh_nvm_flash.c
index aa7a95b86..3d83ab476 100644
--- a/src/wh_nvm_flash.c
+++ b/src/wh_nvm_flash.c
@@ -133,8 +133,8 @@ static int nfObject_Program(whNvmFlashContext* context, int partition,
int object_index, uint32_t epoch, whNvmMetadata* meta, uint32_t start,
const uint8_t* data);
static int nfObject_ReadDataBytes(whNvmFlashContext* context, int partition,
- int object_index, uint32_t byte_offset, uint32_t byte_count,
- uint8_t* out_data);
+ int object_index, uint32_t byte_offset,
+ uint32_t byte_count, uint8_t* out_data);
static int nfObject_Copy(whNvmFlashContext* context, int object_index,
int partition, uint32_t *inout_next_object, uint32_t *inout_next_data);
@@ -652,8 +652,8 @@ static int nfObject_Program(whNvmFlashContext* context, int partition,
}
static int nfObject_ReadDataBytes(whNvmFlashContext* context, int partition,
- int object_index,
- uint32_t byte_offset, uint32_t byte_count, uint8_t* out_data)
+ int object_index, uint32_t byte_offset,
+ uint32_t byte_count, uint8_t* out_data)
{
int start = 0;
uint32_t startOffset = 0;
@@ -676,18 +676,16 @@ static int nfObject_ReadDataBytes(whNvmFlashContext* context, int partition,
}
/* Ensure we don't read off the end of the active partition */
- if (WH_ERROR_OK != nfPartition_CheckDataRange(context, partition,
- startOffset * WHFU_BYTES_PER_UNIT + byte_offset,
- byte_count)) {
+ if (WH_ERROR_OK != nfPartition_CheckDataRange(
+ context, partition,
+ startOffset * WHFU_BYTES_PER_UNIT + byte_offset,
+ byte_count)) {
return WH_ERROR_BADARGS;
}
return wh_FlashUnit_ReadBytes(
- context->cb,
- context->flash,
- startOffset * WHFU_BYTES_PER_UNIT + byte_offset,
- byte_count,
- out_data);
+ context->cb, context->flash,
+ startOffset * WHFU_BYTES_PER_UNIT + byte_offset, byte_count, out_data);
}
static int nfObject_Copy(whNvmFlashContext* context, int object_index,
@@ -728,13 +726,8 @@ static int nfObject_Copy(whNvmFlashContext* context, int object_index,
}
/* Read the data from the old object. */
- ret = nfObject_ReadDataBytes(
- context,
- context->active,
- object_index,
- data_offset,
- this_len,
- buffer);
+ ret = nfObject_ReadDataBytes(context, context->active, object_index,
+ data_offset, this_len, buffer);
if (ret != 0) return ret;
/* Write the data to the new object. */
@@ -1243,30 +1236,23 @@ int wh_NvmFlash_DestroyObjects(void* c, whNvmId list_count,
}
/* Read the data of the object starting at the byte offset */
-int wh_NvmFlash_Read(void* c, whNvmId id, whNvmSize offset,
- whNvmSize data_len, uint8_t* data)
+int wh_NvmFlash_Read(void* c, whNvmId id, whNvmSize offset, whNvmSize data_len,
+ uint8_t* data)
{
whNvmFlashContext* context = c;
int ret = 0;
- int object_index = -1;
+ int object_index = -1;
if ( (context == NULL) ||
((data_len > 0) && (data == NULL)) ){
return WH_ERROR_BADARGS;
}
- ret = nfMemDirectory_FindObjectIndexById(
- &context->directory,
- id,
- &object_index);
+ ret = nfMemDirectory_FindObjectIndexById(&context->directory, id,
+ &object_index);
if (ret == 0) {
- ret = nfObject_ReadDataBytes(
- context,
- context->active,
- object_index,
- offset,
- data_len,
- data);
+ ret = nfObject_ReadDataBytes(context, context->active, object_index,
+ offset, data_len, data);
}
return ret;
}
diff --git a/src/wh_nvm_flash_log.c b/src/wh_nvm_flash_log.c
new file mode 100644
index 000000000..e0ca8d489
--- /dev/null
+++ b/src/wh_nvm_flash_log.c
@@ -0,0 +1,698 @@
+/*
+ * 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 .
+ */
+
+/*
+ * wh NVM Flash Layer
+ *
+ * This NVM layer provides secure, atomic storage of data on flash devices
+ * with large write granularity (e.g., 64 bytes). The layer manages two
+ * equal-sized partitions in flash, with only one partition active at any time.
+ * All objects metadata are cached in memory for fast access.
+ *
+ * Atomicity Guarantee:
+ * On every modification, the inactive partition is erased, all objects are
+ * written to it, and only then is the partition header (containing an
+ * incremented epoch counter) programmed. At initialization, the layer selects
+ * the partition with the highest epoch as active, ensuring that after any
+ * interruption, either the state before or after the write is valid.
+ *
+ * Object Storage Format:
+ * Objects are stored back-to-back in the partition, each consisting of a
+ * whNvmMetadata structure immediately followed by the object data.
+ *
+ * Write Padding:
+ * All writes are padded to the flash's write granularity.
+ *
+ * Flash backend:
+ * This layer relies on the same flash backend as wh_Flash, using the whFlashCb
+ * interface.
+ *
+ * Limitations and performance considerations:
+ *
+ * The implementation favors simplicity over both speed and space.
+ *
+ * Regarding space:
+ * - An area of RAM as big as one partition is allocated to cache the
+ * partition.
+ *
+ * Regarding speed:
+ * - Each updates to the NVM (create, write, delete) requires copying all the
+ * objects from one partition of the flash to the other + erase operations.
+ *
+ * Possible future improvements:
+ *
+ * - cache only the metadata in RAM, access data on the FLASH as needed
+ * - use a true append log format to avoid copying all objects on each update
+ *
+ * Right now the implementation works well for read-heavy workloads with few
+ * updates.
+ *
+ * Alignment consideration:
+ *
+ * The implementation assure that writes are aligned to WRITE_GRANULARITY in
+ * FLASH memory space. The source data passed on the flash layer might not be
+ * aligned.
+ */
+
+#include "wolfhsm/wh_settings.h"
+
+#if defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG)
+
+#include
+#include
+#include
+
+#include "wolfhsm/wh_common.h"
+#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_flash.h"
+
+#include "wolfhsm/wh_nvm_flash_log.h"
+
+#define PAD_SIZE(size) \
+ (((size) + WH_NVM_FLASH_LOG_WRITE_GRANULARITY - 1) & \
+ ~(WH_NVM_FLASH_LOG_WRITE_GRANULARITY - 1))
+
+typedef struct {
+ union {
+ whNvmMetadata meta;
+ uint8_t WH_PAD[PAD_SIZE(sizeof(whNvmMetadata))];
+ };
+} whNvmFlashLogMetadata;
+
+/* do a blank check + program + verify */
+static int nfl_FlashProgramHelper(whNvmFlashLogContext* ctx, uint32_t off,
+ const uint8_t* data, uint32_t len)
+{
+ int ret;
+
+ if (ctx == NULL || (data == NULL && len > 0))
+ return WH_ERROR_BADARGS;
+
+ ret = ctx->flash_cb->BlankCheck(ctx->flash_ctx, off, len);
+ if (ret != 0)
+ return ret;
+ ret = ctx->flash_cb->Program(ctx->flash_ctx, off, len, data);
+ if (ret != 0)
+ return ret;
+ ret = ctx->flash_cb->Verify(ctx->flash_ctx, off, len, data);
+ if (ret != 0)
+ return ret;
+ return WH_ERROR_OK;
+}
+
+/* do a erase + blank check */
+static int nfl_FlashEraseHelper(whNvmFlashLogContext* ctx, uint32_t off,
+ uint32_t len)
+{
+ int ret;
+
+ if (ctx == NULL)
+ return WH_ERROR_BADARGS;
+ ret = ctx->flash_cb->Erase(ctx->flash_ctx, off, len);
+ if (ret != 0)
+ return ret;
+ ret = ctx->flash_cb->BlankCheck(ctx->flash_ctx, off, len);
+ if (ret != 0)
+ return ret;
+
+ return WH_ERROR_OK;
+}
+
+static whNvmFlashLogMetadata* nfl_ObjNext(whNvmFlashLogContext* ctx,
+ whNvmFlashLogMetadata* obj)
+{
+ if (obj == NULL || ctx == NULL)
+ return NULL;
+ uint8_t* next =
+ (uint8_t*)obj + sizeof(whNvmFlashLogMetadata) + PAD_SIZE(obj->meta.len);
+ if (next >= ctx->directory.data + ctx->directory.header.size)
+ return NULL;
+ return (whNvmFlashLogMetadata*)next;
+}
+
+static int nfl_PartitionErase(whNvmFlashLogContext* ctx, uint32_t partition)
+{
+ uint32_t off;
+
+ if (ctx == NULL || partition > 1)
+ return WH_ERROR_BADARGS;
+
+ off = partition * ctx->partition_size;
+ return nfl_FlashEraseHelper(ctx, off, ctx->partition_size);
+}
+
+static int nfl_PartitionWrite(whNvmFlashLogContext* ctx, uint32_t partition)
+{
+ uint32_t off;
+ int ret;
+
+ if (ctx == NULL || partition > 1)
+ return WH_ERROR_BADARGS;
+
+ off = partition * ctx->partition_size;
+
+ if (ctx->directory.header.size % WH_NVM_FLASH_LOG_WRITE_GRANULARITY != 0)
+ return WH_ERROR_ABORTED;
+
+ if (ctx->directory.header.size > 0) {
+ ret = nfl_FlashProgramHelper(
+ ctx, off + sizeof(whNvmFlashLogPartitionHeader),
+ ctx->directory.data, ctx->directory.header.size);
+ if (ret != 0)
+ return ret;
+ }
+
+ return WH_ERROR_OK;
+}
+
+static int nfl_PartitionCommit(whNvmFlashLogContext* ctx, uint32_t partition)
+{
+ const whFlashCb* f_cb;
+ uint32_t off;
+ int ret;
+
+ if (ctx == NULL || partition > 1)
+ return WH_ERROR_BADARGS;
+
+ off = partition * ctx->partition_size;
+ f_cb = ctx->flash_cb;
+ ret = f_cb->BlankCheck(ctx->flash_ctx, off,
+ sizeof(whNvmFlashLogPartitionHeader));
+ if (ret != 0)
+ return ret;
+
+ ret = nfl_FlashProgramHelper(ctx, off, (uint8_t*)&ctx->directory.header,
+ sizeof(whNvmFlashLogPartitionHeader));
+ if (ret != 0)
+ return ret;
+
+ return WH_ERROR_OK;
+}
+
+static int nfl_PartitionChoose(whNvmFlashLogContext* ctx)
+{
+ whNvmFlashLogPartitionHeader header0, header1;
+ const whFlashCb* f_cb;
+ uint32_t part1_offset;
+ int part0_blank, part1_blank;
+ int ret;
+
+ if (ctx == NULL)
+ return WH_ERROR_BADARGS;
+
+ part1_offset = ctx->partition_size;
+ f_cb = ctx->flash_cb;
+ ret = f_cb->BlankCheck(ctx->flash_ctx, 0, sizeof(header0));
+ if (ret != 0 && ret != WH_ERROR_NOTBLANK) {
+ return ret;
+ }
+ part0_blank = (ret == 0);
+
+ ret = f_cb->BlankCheck(ctx->flash_ctx, part1_offset, sizeof(header0));
+ if (ret != 0 && ret != WH_ERROR_NOTBLANK) {
+ return ret;
+ }
+ part1_blank = (ret == 0);
+
+ if (part0_blank && part1_blank) {
+ /* Both partitions headers are blank, start with partition 0 */
+ ret = nfl_PartitionErase(ctx, 0);
+ if (ret != 0)
+ return ret;
+ ret = nfl_PartitionCommit(ctx, 0);
+ if (ret != 0)
+ return ret;
+ return WH_ERROR_OK;
+ }
+
+ if (part0_blank) {
+ ctx->active_partition = 1;
+ return WH_ERROR_OK;
+ }
+
+ if (part1_blank) {
+ ctx->active_partition = 0;
+ return WH_ERROR_OK;
+ }
+
+ /* both partition are programmed */
+ ret = f_cb->Read(ctx->flash_ctx, 0, sizeof(header0), (uint8_t*)&header0);
+ if (ret != 0) {
+ return ret;
+ }
+ ret = f_cb->Read(ctx->flash_ctx, part1_offset, sizeof(header1),
+ (uint8_t*)&header1);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if (header0.partition_epoch > header1.partition_epoch) {
+ ctx->active_partition = 0;
+ }
+ else {
+ ctx->active_partition = 1;
+ }
+
+ return WH_ERROR_OK;
+}
+
+static whNvmFlashLogMetadata* nfl_ObjectFindById(whNvmFlashLogContext* ctx,
+ whNvmId id)
+{
+ whNvmFlashLogMetadata* obj;
+
+ if (ctx == NULL || id == WH_NVM_ID_INVALID)
+ return NULL;
+
+ obj = (whNvmFlashLogMetadata*)ctx->directory.data;
+ while (obj != NULL && obj->meta.id != WH_NVM_ID_INVALID) {
+ if (obj->meta.id == id) {
+ return obj;
+ }
+ obj = nfl_ObjNext(ctx, obj);
+ }
+ return NULL;
+}
+
+static int nfl_ObjectDestroy(whNvmFlashLogContext* ctx, whNvmId id)
+{
+ whNvmFlashLogMetadata* obj;
+ uint32_t len;
+ uint32_t off;
+ uint32_t tail;
+
+ if (ctx == NULL || id == WH_NVM_ID_INVALID)
+ return WH_ERROR_BADARGS;
+
+ obj = nfl_ObjectFindById(ctx, id);
+ if (obj == NULL)
+ return WH_ERROR_OK;
+
+ len = sizeof(whNvmFlashLogMetadata) + PAD_SIZE(obj->meta.len);
+ off = (uint8_t*)obj - ctx->directory.data;
+ tail = ctx->directory.header.size - (off + len);
+ memmove(obj, (uint8_t*)obj + len, tail);
+ /* be sure to clean-up moved objects from memory */
+ memset((uint8_t*)obj + tail, 0, len);
+ ctx->directory.header.size -= len;
+ return WH_ERROR_OK;
+}
+
+static int nfl_ObjectCount(whNvmFlashLogContext* ctx,
+ whNvmFlashLogMetadata* startObj)
+{
+ int count = 0;
+
+ if (ctx == NULL)
+ return 0;
+
+ if (startObj == NULL) {
+ startObj = (whNvmFlashLogMetadata*)ctx->directory.data;
+ }
+
+ if ((uint8_t*)startObj < ctx->directory.data ||
+ (uint8_t*)startObj >=
+ ctx->directory.data + ctx->directory.header.size) {
+ return 0;
+ }
+
+ while (startObj != NULL) {
+ if (startObj->meta.id == WH_NVM_ID_INVALID) {
+ break;
+ }
+ count++;
+ startObj = nfl_ObjNext(ctx, startObj);
+ }
+
+ return count;
+}
+
+static int nfl_PartitionRead(whNvmFlashLogContext* ctx)
+{
+ const whFlashCb* f_cb;
+ uint32_t off;
+ int ret;
+
+ if (ctx == NULL)
+ return WH_ERROR_BADARGS;
+
+ f_cb = ctx->flash_cb;
+ off = ctx->active_partition * ctx->partition_size;
+
+ ret = f_cb->Read(ctx->flash_ctx, off, sizeof(whNvmFlashLogPartitionHeader),
+ (uint8_t*)&ctx->directory.header);
+ if (ret != 0)
+ return ret;
+
+ if (ctx->directory.header.size >
+ ctx->partition_size - sizeof(whNvmFlashLogPartitionHeader)) {
+ return WH_ERROR_ABORTED;
+ }
+
+ if (ctx->directory.header.size > 0) {
+ ret = f_cb->Read(ctx->flash_ctx,
+ off + sizeof(whNvmFlashLogPartitionHeader),
+ ctx->directory.header.size, ctx->directory.data);
+ if (ret != 0)
+ return ret;
+ }
+
+ return WH_ERROR_OK;
+}
+
+static int nfl_PartitionNewEpoch(whNvmFlashLogContext* ctx)
+{
+ int next_active;
+ int ret;
+
+ if (ctx == NULL)
+ return WH_ERROR_BADARGS;
+
+ next_active = (ctx->active_partition == 0) ? 1 : 0;
+ ctx->directory.header.partition_epoch++;
+ ret = nfl_PartitionErase(ctx, next_active);
+ if (ret != 0)
+ return ret;
+ ret = nfl_PartitionWrite(ctx, next_active);
+ if (ret != 0)
+ return ret;
+ ret = nfl_PartitionCommit(ctx, next_active);
+ if (ret != 0)
+ return ret;
+ ctx->active_partition = next_active;
+ return WH_ERROR_OK;
+}
+
+static int nfl_PartitionNewEpochOrFallback(whNvmFlashLogContext* ctx)
+{
+ int ret;
+
+ if (ctx == NULL)
+ return WH_ERROR_BADARGS;
+ ret = nfl_PartitionNewEpoch(ctx);
+
+ if (ret != WH_ERROR_OK) {
+ /* swtiching to new partition failed for a reason, try to restore
+ * back active partition. */
+ nfl_PartitionRead(ctx);
+ }
+
+ /* erase in-active partition, this ensure objects are truly destroyed */
+ nfl_PartitionErase(ctx, ctx->active_partition == 0 ? 1 : 0);
+
+ return ret;
+}
+
+/* Initialization function */
+int wh_NvmFlashLog_Init(void* c, const void* cf)
+{
+ whNvmFlashLogContext* context = (whNvmFlashLogContext*)c;
+ const whNvmFlashLogConfig* config = (const whNvmFlashLogConfig*)cf;
+ int ret;
+
+ if (context == NULL || config == NULL || config->flash_cb == NULL ||
+ config->flash_ctx == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+ if (config->flash_cb->PartitionSize == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+ memset(context, 0, sizeof(*context));
+
+ ret = 0;
+ if (config->flash_cb->Init != NULL)
+ ret = config->flash_cb->Init(config->flash_ctx, config->flash_cfg);
+ if (ret != 0)
+ return ret;
+
+ context->flash_cb = config->flash_cb;
+ context->flash_ctx = config->flash_ctx;
+ context->partition_size =
+ context->flash_cb->PartitionSize(context->flash_ctx);
+
+ if (context->partition_size != WH_NVM_FLASH_LOG_PARTITION_SIZE ||
+ context->partition_size % WH_NVM_FLASH_LOG_WRITE_GRANULARITY != 0) {
+ return WH_ERROR_BADARGS;
+ }
+
+ /* unlock partitions */
+ if (context->flash_cb->WriteUnlock != NULL) {
+ ret = context->flash_cb->WriteUnlock(context->flash_ctx, 0,
+ context->partition_size);
+ if (ret != 0)
+ return ret;
+ ret = context->flash_cb->WriteUnlock(context->flash_ctx,
+ context->partition_size,
+ context->partition_size);
+ if (ret != 0)
+ return ret;
+ }
+
+ ret = nfl_PartitionChoose(context);
+ if (ret != 0)
+ return ret;
+ ret = nfl_PartitionRead(context);
+ if (ret != 0)
+ return ret;
+ ret = nfl_PartitionErase(context, (context->active_partition == 0) ? 1 : 0);
+ if (ret != 0)
+ return ret;
+
+ context->is_initialized = 1;
+ return WH_ERROR_OK;
+}
+
+int wh_NvmFlashLog_Cleanup(void* c)
+{
+ whNvmFlashLogContext* context = (whNvmFlashLogContext*)c;
+ int ret0, ret1;
+
+ if (context == NULL || !context->is_initialized)
+ return WH_ERROR_BADARGS;
+
+ context->is_initialized = 0;
+
+ /* lock partitions */
+ if (context->flash_cb->WriteLock == NULL)
+ return WH_ERROR_OK;
+
+ ret0 = context->flash_cb->WriteLock(context->flash_ctx, 0,
+ context->partition_size);
+ ret1 = context->flash_cb->WriteLock(
+ context->flash_ctx, context->partition_size, context->partition_size);
+
+ if (ret0 != WH_ERROR_OK)
+ return ret0;
+ if (ret1 != WH_ERROR_OK)
+ return ret1;
+
+ return WH_ERROR_OK;
+}
+
+/* List objects */
+int wh_NvmFlashLog_List(void* c, whNvmAccess access, whNvmFlags flags,
+ whNvmId start_id, whNvmId* out_count, whNvmId* out_id)
+{
+ whNvmFlashLogContext* ctx = (whNvmFlashLogContext*)c;
+ whNvmFlashLogMetadata *next_obj = NULL, *start_obj = NULL;
+ uint32_t count = 0;
+
+ /* TODO: Implement access and flag matching */
+ (void)access;
+ (void)flags;
+
+ if (ctx == NULL || !ctx->is_initialized)
+ return WH_ERROR_BADARGS;
+
+ /* list all obects if start_id is WH_NVM_ID_INVALID */
+ if (start_id == WH_NVM_ID_INVALID) {
+ next_obj = (whNvmFlashLogMetadata*)ctx->directory.data;
+ }
+ else {
+ start_obj = nfl_ObjectFindById(ctx, start_id);
+ if (start_obj != NULL && start_obj->meta.id != WH_NVM_ID_INVALID)
+ next_obj = nfl_ObjNext(ctx, start_obj);
+ }
+
+ if (next_obj == NULL || next_obj->meta.id == WH_NVM_ID_INVALID) {
+ if (out_count != NULL)
+ *out_count = 0;
+ if (out_id != NULL)
+ *out_id = WH_NVM_ID_INVALID;
+ return WH_ERROR_OK;
+ }
+
+ count = nfl_ObjectCount(ctx, next_obj);
+ if (out_count != NULL)
+ *out_count = count;
+ if (out_id != NULL)
+ *out_id = next_obj->meta.id;
+
+ return WH_ERROR_OK;
+}
+
+/* Get available space/objects */
+int wh_NvmFlashLog_GetAvailable(void* c, uint32_t* out_avail_size,
+ whNvmId* out_avail_objects,
+ uint32_t* out_reclaim_size,
+ whNvmId* out_reclaim_objects)
+{
+ whNvmFlashLogContext* ctx = (whNvmFlashLogContext*)c;
+ uint8_t count;
+
+ if (ctx == NULL || !ctx->is_initialized)
+ return WH_ERROR_BADARGS;
+ if (out_avail_size != NULL) {
+ *out_avail_size = ctx->partition_size -
+ sizeof(whNvmFlashLogPartitionHeader) -
+ ctx->directory.header.size;
+ }
+
+ if (out_avail_objects != NULL) {
+ count = nfl_ObjectCount(ctx, NULL);
+ *out_avail_objects = WOLFHSM_CFG_NVM_OBJECT_COUNT - count;
+ }
+
+ /* No reclaim in this simple implementation */
+ if (out_reclaim_size != NULL) {
+ *out_reclaim_size = 0;
+ }
+ if (out_reclaim_objects != NULL) {
+ *out_reclaim_objects = 0;
+ }
+
+ return WH_ERROR_OK;
+}
+
+/* Get metadata for an object */
+int wh_NvmFlashLog_GetMetadata(void* c, whNvmId id, whNvmMetadata* meta)
+{
+ whNvmFlashLogContext* ctx = (whNvmFlashLogContext*)c;
+ whNvmFlashLogMetadata* obj;
+
+ if (ctx == NULL || !ctx->is_initialized)
+ return WH_ERROR_BADARGS;
+
+ obj = nfl_ObjectFindById(ctx, id);
+ if (obj == NULL) {
+ return WH_ERROR_NOTFOUND;
+ }
+
+ if (meta != NULL)
+ memcpy(meta, &obj->meta, sizeof(*meta));
+ return WH_ERROR_OK;
+}
+
+int wh_NvmFlashLog_AddObject(void* c, whNvmMetadata* meta, whNvmSize data_len,
+ const uint8_t* data)
+{
+ whNvmFlashLogContext* ctx = (whNvmFlashLogContext*)c;
+ whNvmFlashLogMetadata *obj, *old_obj;
+ uint32_t available_space;
+ int ret;
+ uint32_t count;
+
+ if (ctx == NULL || !ctx->is_initialized || meta == NULL ||
+ (data_len > 0 && data == NULL))
+ return WH_ERROR_BADARGS;
+
+ count = nfl_ObjectCount(ctx, NULL);
+ available_space = ctx->partition_size -
+ sizeof(whNvmFlashLogPartitionHeader) -
+ ctx->directory.header.size;
+
+ old_obj = nfl_ObjectFindById(ctx, meta->id);
+ if (old_obj != NULL) {
+ available_space +=
+ sizeof(whNvmFlashLogMetadata) + PAD_SIZE(old_obj->meta.len);
+ count -= 1;
+ }
+
+ if (PAD_SIZE(data_len) + sizeof(whNvmFlashLogMetadata) > available_space)
+ return WH_ERROR_NOSPACE;
+
+ if (count >= WOLFHSM_CFG_NVM_OBJECT_COUNT)
+ return WH_ERROR_NOSPACE;
+
+ if (old_obj) {
+ ret = nfl_ObjectDestroy(ctx, meta->id);
+ if (ret != WH_ERROR_OK)
+ return ret;
+ }
+
+ obj = (whNvmFlashLogMetadata*)(ctx->directory.data +
+ ctx->directory.header.size);
+ meta->len = data_len;
+ memcpy(&obj->meta, meta, sizeof(*meta));
+ memcpy((uint8_t*)obj + sizeof(whNvmFlashLogMetadata), data, data_len);
+ ctx->directory.header.size +=
+ sizeof(whNvmFlashLogMetadata) + PAD_SIZE(data_len);
+
+ return nfl_PartitionNewEpochOrFallback(ctx);
+}
+
+/* Destroy objects by id list */
+int wh_NvmFlashLog_DestroyObjects(void* c, whNvmId list_count,
+ const whNvmId* id_list)
+{
+ whNvmFlashLogContext* ctx = (whNvmFlashLogContext*)c;
+ int i;
+ int ret;
+
+ if (ctx == NULL || !ctx->is_initialized ||
+ (list_count > 0 && id_list == NULL))
+ return WH_ERROR_BADARGS;
+
+ if (list_count == 0)
+ return WH_ERROR_OK;
+
+ for (i = 0; i < list_count; i++) {
+ ret = nfl_ObjectDestroy(ctx, id_list[i]);
+ if (ret != WH_ERROR_OK)
+ return ret;
+ }
+
+ return nfl_PartitionNewEpochOrFallback(ctx);
+}
+
+/* Read object data */
+int wh_NvmFlashLog_Read(void* c, whNvmId id, whNvmSize offset,
+ whNvmSize data_len, uint8_t* data)
+{
+ whNvmFlashLogContext* ctx = (whNvmFlashLogContext*)c;
+ whNvmFlashLogMetadata* obj;
+ uint8_t* obj_data;
+
+ if (ctx == NULL || !ctx->is_initialized || (data_len > 0 && data == NULL))
+ return WH_ERROR_BADARGS;
+
+ obj = nfl_ObjectFindById(ctx, id);
+ if (obj == NULL)
+ return WH_ERROR_NOTFOUND;
+
+ if (offset + data_len > obj->meta.len)
+ return WH_ERROR_BADARGS;
+
+ obj_data = (uint8_t*)obj + sizeof(whNvmFlashLogMetadata) + offset;
+ memcpy(data, obj_data, data_len);
+
+ return WH_ERROR_OK;
+}
+
+#endif /* WOLFHSM_CFG_SERVER_NVM_FLASH_LOG */
diff --git a/test/Makefile b/test/Makefile
index 051212dcb..ad6393e34 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -123,6 +123,7 @@ else
DEF += -DWOLFHSM_CFG_IS_TEST_SERVER
endif
+
## Source files
# Assembly source files
SRC_ASM +=
diff --git a/test/config/wolfhsm_cfg.h b/test/config/wolfhsm_cfg.h
index cf9068221..4a0762140 100644
--- a/test/config/wolfhsm_cfg.h
+++ b/test/config/wolfhsm_cfg.h
@@ -56,4 +56,6 @@
#define WOLFHSM_CFG_CANCEL_API
#endif
+#define WOLFHSM_CFG_SERVER_NVM_FLASH_LOG
+
#endif /* WOLFHSM_CFG_H_ */
diff --git a/test/wh_test.c b/test/wh_test.c
index 6c33f5bda..31124a6e4 100644
--- a/test/wh_test.c
+++ b/test/wh_test.c
@@ -66,7 +66,12 @@ int whTest_Unit(void)
WH_TEST_ASSERT(0 == whTest_Flash_RamSim());
WH_TEST_ASSERT(0 == whTest_NvmFlash());
#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && !defined(WOLFHSM_CFG_NO_CRYPTO)
- WH_TEST_ASSERT(0 == whTest_CertRamSim());
+ WH_TEST_ASSERT(0 == whTest_CertRamSim(WH_NVM_TEST_BACKEND_FLASH));
+
+#if defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG)
+ WH_TEST_ASSERT(0 == whTest_CertRamSim(WH_NVM_TEST_BACKEND_FLASH_LOG));
+#endif /* WOLFHSM_CFG_SERVER_NVM_FLASH_LOG */
+
#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER && !WOLFHSM_CFG_NO_CRYPTO */
@@ -80,7 +85,12 @@ int whTest_Unit(void)
#if defined(WOLFHSM_CFG_SERVER_IMG_MGR) && !defined(WOLFHSM_CFG_NO_CRYPTO)
/* Image Manager Tests */
- WH_TEST_ASSERT(0 == whTest_ServerImgMgr());
+ WH_TEST_ASSERT(0 == whTest_ServerImgMgr(WH_NVM_TEST_BACKEND_FLASH));
+
+#if defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG)
+ WH_TEST_ASSERT(0 == whTest_ServerImgMgr(WH_NVM_TEST_BACKEND_FLASH_LOG));
+#endif
+
#endif /* WOLFHSM_CFG_SERVER_IMG_MGR && !WOLFHSM_CFG_NO_CRYPTO */
#if defined(WOLFHSM_CFG_SHE_EXTENSION)
diff --git a/test/wh_test_cert.c b/test/wh_test_cert.c
index 8c2e0d4fc..b48cda9f9 100644
--- a/test/wh_test_cert.c
+++ b/test/wh_test_cert.c
@@ -578,7 +578,7 @@ static int whTest_CertNonExportable(whClientContext* client)
#endif /* WOLFHSM_CFG_ENABLE_CLIENT */
#ifdef WOLFHSM_CFG_ENABLE_SERVER
-int whTest_CertRamSim(void)
+int whTest_CertRamSim(whTestNvmBackendType nvmType)
{
int rc = WH_ERROR_OK;
const uint32_t BUFFER_SIZE = 1024;
@@ -614,21 +614,13 @@ int whTest_CertRamSim(void)
}};
const whFlashCb fcb[1] = {WH_FLASH_RAMSIM_CB};
- /* NVM Flash Configuration using RamSim HAL Flash */
- whNvmFlashConfig nf_conf[1] = {{
- .cb = fcb,
- .context = fc,
- .config = fc_conf,
- }};
- whNvmFlashContext nfc[1] = {0};
- whNvmCb nfcb[1] = {WH_NVM_FLASH_CB};
-
- whNvmConfig n_conf[1] = {{
- .cb = nfcb,
- .context = nfc,
- .config = nf_conf,
- }};
+ whTestNvmBackendUnion nvm_setup;
+ whNvmConfig n_conf[1];
whNvmContext nvm[1] = {{0}};
+
+ WH_TEST_RETURN_ON_FAIL(
+ whTest_NvmCfgBackend(nvmType, &nvm_setup, n_conf, fc_conf, fc, fcb));
+
#ifndef WOLFHSM_CFG_NO_CRYPTO
whServerCryptoContext crypto[1] = {{
.devId = INVALID_DEVID,
diff --git a/test/wh_test_cert.h b/test/wh_test_cert.h
index 5deecaa69..d5d002eb6 100644
--- a/test/wh_test_cert.h
+++ b/test/wh_test_cert.h
@@ -26,8 +26,10 @@
#include "wolfhsm/wh_server.h"
#include "wolfhsm/wh_client.h"
+#include "wh_test_common.h"
+
/* Run certificate configuration tests */
-int whTest_CertRamSim(void);
+int whTest_CertRamSim(whTestNvmBackendType nvmType);
int whTest_CertServerCfg(whServerConfig* serverCfg);
int whTest_CertClient(whClientContext* client);
#if defined(WOLFHSM_CFG_DMA)
@@ -41,4 +43,4 @@ int whTest_CertClientAcertDma_ClientServerTestInternal(whClientContext* client);
#endif /* WOLFHSM_CFG_DMA */
#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT */
-#endif /* !WOLFHSM_WH_TEST_CERT_H_ */
\ No newline at end of file
+#endif /* !WOLFHSM_WH_TEST_CERT_H_ */
diff --git a/test/wh_test_clientserver.c b/test/wh_test_clientserver.c
index 808598431..c91d67581 100644
--- a/test/wh_test_clientserver.c
+++ b/test/wh_test_clientserver.c
@@ -87,7 +87,6 @@ typedef struct {
static int _testNonExportableNvmAccess(whClientContext* client);
#endif
-
#ifdef WOLFHSM_CFG_ENABLE_SERVER
/* Pointer to a local server context so a connect callback can access it. Should
* be set before calling wh_ClientInit() */
@@ -727,7 +726,7 @@ static int _testOutOfBoundsNvmReads(whClientContext* client,
return WH_ERROR_OK;
}
-int whTest_ClientServerSequential(void)
+int whTest_ClientServerSequential(whTestNvmBackendType nvmType)
{
int ret = 0;
@@ -780,21 +779,13 @@ int whTest_ClientServerSequential(void)
}};
const whFlashCb fcb[1] = {WH_FLASH_RAMSIM_CB};
- /* NVM Flash Configuration using RamSim HAL Flash */
- whNvmFlashConfig nf_conf[1] = {{
- .cb = fcb,
- .context = fc,
- .config = fc_conf,
- }};
- whNvmFlashContext nfc[1] = {0};
- whNvmCb nfcb[1] = {WH_NVM_FLASH_CB};
-
- whNvmConfig n_conf[1] = {{
- .cb = nfcb,
- .context = nfc,
- .config = nf_conf,
- }};
+ whTestNvmBackendUnion nvm_setup;
+ whNvmConfig n_conf[1];
whNvmContext nvm[1] = {{0}};
+
+ WH_TEST_RETURN_ON_FAIL(
+ whTest_NvmCfgBackend(nvmType, &nvm_setup, n_conf, fc_conf, fc, fcb));
+
#ifndef WOLFHSM_CFG_NO_CRYPTO
whServerCryptoContext crypto[1] = {{
.devId = INVALID_DEVID,
@@ -1766,7 +1757,7 @@ static void _whClientServerThreadTest(whClientConfig* c_conf,
}
}
-static int wh_ClientServer_MemThreadTest(void)
+static int wh_ClientServer_MemThreadTest(whTestNvmBackendType nvmType)
{
uint8_t req[BUFFER_SIZE] = {0};
uint8_t resp[BUFFER_SIZE] = {0};
@@ -1811,21 +1802,12 @@ static int wh_ClientServer_MemThreadTest(void)
}};
const whFlashCb fcb[1] = {WH_FLASH_RAMSIM_CB};
- /* NVM Flash Configuration using RamSim HAL Flash */
- whNvmFlashConfig nf_conf[1] = {{
- .cb = fcb,
- .context = fc,
- .config = fc_conf,
- }};
- whNvmFlashContext nfc[1] = {0};
- whNvmCb nfcb[1] = {WH_NVM_FLASH_CB};
+ whTestNvmBackendUnion nvm_setup;
+ whNvmConfig n_conf[1] = {0};
+ whNvmContext nvm[1] = {{0}};
- whNvmConfig n_conf[1] = {{
- .cb = nfcb,
- .context = nfc,
- .config = nf_conf,
- }};
- whNvmContext nvm[1] = {{0}};
+ WH_TEST_RETURN_ON_FAIL(
+ whTest_NvmCfgBackend(nvmType, &nvm_setup, n_conf, fc_conf, fc, fcb));
#ifndef WOLFHSM_CFG_NO_CRYPTO
/* Crypto context */
@@ -1863,7 +1845,7 @@ static int wh_ClientServer_MemThreadTest(void)
}
-static int wh_ClientServer_PosixMemMapThreadTest(void)
+static int wh_ClientServer_PosixMemMapThreadTest(whTestNvmBackendType nvmType)
{
posixTransportShmConfig tmcf[1] = {{
.name = "/wh_test_clientserver_shm",
@@ -1905,22 +1887,13 @@ static int wh_ClientServer_PosixMemMapThreadTest(void)
}};
const whFlashCb fcb[1] = {WH_FLASH_RAMSIM_CB};
- /* NVM Flash Configuration using RamSim HAL Flash */
- whNvmFlashConfig nf_conf[1] = {{
- .cb = fcb,
- .context = fc,
- .config = fc_conf,
- }};
- whNvmFlashContext nfc[1] = {0};
- whNvmCb nfcb[1] = {WH_NVM_FLASH_CB};
-
- whNvmConfig n_conf[1] = {{
- .cb = nfcb,
- .context = nfc,
- .config = nf_conf,
- }};
+ whTestNvmBackendUnion nvm_setup;
+ whNvmConfig n_conf[1];
whNvmContext nvm[1] = {{0}};
+ WH_TEST_RETURN_ON_FAIL(
+ whTest_NvmCfgBackend(nvmType, &nvm_setup, n_conf, fc_conf, fc, fcb));
+
#ifndef WOLFHSM_CFG_NO_CRYPTO
/* Crypto context */
whServerCryptoContext crypto[1] = {{
@@ -1960,14 +1933,33 @@ static int wh_ClientServer_PosixMemMapThreadTest(void)
int whTest_ClientServer(void)
{
printf("Testing client/server sequential: mem...\n");
- WH_TEST_ASSERT(0 == whTest_ClientServerSequential());
+ WH_TEST_ASSERT(0 == whTest_ClientServerSequential(WH_NVM_TEST_BACKEND_FLASH));
+
+#if defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG)
+ printf("Testing client/server sequential: mem + flash log...\n");
+ WH_TEST_ASSERT(0 ==
+ whTest_ClientServerSequential(WH_NVM_TEST_BACKEND_FLASH_LOG));
+#endif /* defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG) */
#if defined(WOLFHSM_CFG_TEST_POSIX)
printf("Testing client/server: (pthread) mem...\n");
- WH_TEST_ASSERT(0 == wh_ClientServer_MemThreadTest());
+ WH_TEST_ASSERT(0 == wh_ClientServer_MemThreadTest(WH_NVM_TEST_BACKEND_FLASH));
printf("Testing client/server: (pthread) POSIX shared memory ...\n");
- WH_TEST_ASSERT(0 == wh_ClientServer_PosixMemMapThreadTest());
+ WH_TEST_ASSERT(
+ 0 == wh_ClientServer_PosixMemMapThreadTest(WH_NVM_TEST_BACKEND_FLASH));
+
+#if defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG)
+ printf("Testing client/server: (pthread) mem + flash log...\n");
+ WH_TEST_ASSERT(0 ==
+ wh_ClientServer_MemThreadTest(WH_NVM_TEST_BACKEND_FLASH_LOG));
+
+ printf("Testing client/server: (pthread) POSIX shared memory + flash "
+ "log...\n");
+ WH_TEST_ASSERT(
+ 0 == wh_ClientServer_PosixMemMapThreadTest(WH_NVM_TEST_BACKEND_FLASH_LOG));
+#endif /* defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG) */
+
#endif /* defined(WOLFHSM_CFG_TEST_POSIX) */
return 0;
diff --git a/test/wh_test_common.c b/test/wh_test_common.c
new file mode 100644
index 000000000..2cac0aebb
--- /dev/null
+++ b/test/wh_test_common.c
@@ -0,0 +1,92 @@
+/*
+ * 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 .
+ */
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "wh_test_common.h"
+
+
+/**
+ * Helper function to configure and select an NVM backend for testing.
+ *
+ * @param type The type of NVM backend to configure (see
+ * NvmTestBackendType).
+ * @param nvmSetup Pointer to a union of NVM backend setup structures (output).
+ * @param nvmCfg Pointer to the NVM configuration structure to populate
+ * (output).
+ * @param fCfg Pointer to the RamSim flash configuration structure.
+ * @param fCtx Pointer to the RamSim flash context structure.
+ * @param fCb Pointer to the RamSim flash callback structure.
+ *
+ * @return WH_ERROR_OK on success, or WH_ERROR_BADARGS on failure.
+ */
+int whTest_NvmCfgBackend(whTestNvmBackendType type,
+ whTestNvmBackendUnion* nvmSetup, whNvmConfig* nvmCfg,
+ whFlashRamsimCfg* fCfg, whFlashRamsimCtx* fCtx,
+ const whFlashCb* fCb)
+{
+
+ WH_TEST_ASSERT(nvmSetup != NULL);
+ WH_TEST_ASSERT(nvmCfg != NULL);
+ WH_TEST_ASSERT(fCfg != NULL);
+
+ switch (type) {
+#if defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG)
+ case WH_NVM_TEST_BACKEND_FLASH_LOG:
+ nvmSetup->nvmFlashLogCfg.flash_cb = fCb;
+ /* restrict simulated flash partition to nvm_flash_log_partition */
+ WH_TEST_ASSERT(fCfg->size >= WH_NVM_FLASH_LOG_PARTITION_SIZE * 2);
+ fCfg->sectorSize = WH_NVM_FLASH_LOG_PARTITION_SIZE;
+ nvmSetup->nvmFlashLogCfg.flash_cfg = fCfg;
+ nvmSetup->nvmFlashLogCfg.flash_ctx = fCtx;
+ memset(&nvmSetup->nvmFlashLogCtx, 0,
+ sizeof(nvmSetup->nvmFlashLogCtx));
+ static whNvmCb nflcb[1] = {WH_NVM_FLASH_LOG_CB};
+
+ nvmCfg->cb = nflcb;
+ nvmCfg->context = &nvmSetup->nvmFlashLogCtx;
+ nvmCfg->config = &nvmSetup->nvmFlashLogCfg;
+ break;
+#endif
+ case WH_NVM_TEST_BACKEND_FLASH:
+ /* NVM Flash Configuration using RamSim HAL Flash */
+ nvmSetup->nvmFlashCfg.cb = fCb;
+ nvmSetup->nvmFlashCfg.context = fCtx;
+ nvmSetup->nvmFlashCfg.config = fCfg;
+
+ memset(&nvmSetup->nvmFlashCtx, 0, sizeof(nvmSetup->nvmFlashCtx));
+ static whNvmCb nfcb[1] = {WH_NVM_FLASH_CB};
+
+ nvmCfg->cb = nfcb;
+ nvmCfg->context = &nvmSetup->nvmFlashCtx;
+ nvmCfg->config = &nvmSetup->nvmFlashCfg;
+ break;
+
+ default:
+ return WH_ERROR_BADARGS;
+ }
+
+ return 0;
+}
diff --git a/test/wh_test_common.h b/test/wh_test_common.h
index a6ba3ff2e..c1bc5a08f 100644
--- a/test/wh_test_common.h
+++ b/test/wh_test_common.h
@@ -26,6 +26,10 @@
#include
#endif
+#include
+#include
+#include
+#include
#define WH_TEST_FAIL (-1)
#define WH_TEST_SUCCESS (0)
@@ -106,4 +110,32 @@
} while (0)
+typedef enum {
+ WH_NVM_TEST_BACKEND_FLASH = 0,
+#if defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG)
+ WH_NVM_TEST_BACKEND_FLASH_LOG,
+#endif
+ WH_NVM_TEST_BACKEND_COUNT
+} whTestNvmBackendType;
+
+/* union helper struct to be able to test more than one NVM implementation */
+typedef struct {
+ union {
+ struct {
+ whNvmFlashContext nvmFlashCtx;
+ whNvmFlashConfig nvmFlashCfg;
+ };
+#if defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG)
+ struct {
+ whNvmFlashLogContext nvmFlashLogCtx;
+ whNvmFlashLogConfig nvmFlashLogCfg;
+ };
+#endif /* WOLFHSM_CFG_SERVER_NVM_FLASH_LOG */
+ };
+} whTestNvmBackendUnion;
+
+int whTest_NvmCfgBackend(whTestNvmBackendType type,
+ whTestNvmBackendUnion* nvmSetup, whNvmConfig* nvmCfg,
+ whFlashRamsimCfg* fCfg, whFlashRamsimCtx* fCtx,
+ const whFlashCb* fCb);
#endif /* WH_TEST_COMMON_H_ */
diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c
index 7da2aaec9..f6556046e 100644
--- a/test/wh_test_crypto.c
+++ b/test/wh_test_crypto.c
@@ -3614,7 +3614,8 @@ static void _whClientServerThreadTest(whClientConfig* c_conf,
}
}
-static int wh_ClientServer_MemThreadTest(void)
+
+static int wh_ClientServer_MemThreadTest(whTestNvmBackendType nvmType)
{
uint8_t req[BUFFER_SIZE] = {0};
uint8_t resp[BUFFER_SIZE] = {0};
@@ -3671,22 +3672,13 @@ static int wh_ClientServer_MemThreadTest(void)
}};
const whFlashCb fcb[1] = {WH_FLASH_RAMSIM_CB};
- /* NVM Flash Configuration using RamSim HAL Flash */
- whNvmFlashConfig nf_conf[1] = {{
- .cb = fcb,
- .context = fc,
- .config = fc_conf,
- }};
- whNvmFlashContext nfc[1] = {0};
- whNvmCb nfcb[1] = {WH_NVM_FLASH_CB};
-
- whNvmConfig n_conf[1] = {{
- .cb = nfcb,
- .context = nfc,
- .config = nf_conf,
- }};
+ whTestNvmBackendUnion nvm_setup;
+ whNvmConfig n_conf[1];
whNvmContext nvm[1] = {{0}};
+ WH_TEST_RETURN_ON_FAIL(
+ whTest_NvmCfgBackend(nvmType, &nvm_setup, n_conf, fc_conf, fc, fcb));
+
/* Crypto context */
whServerCryptoContext crypto[1] = {{
.devId = INVALID_DEVID,
@@ -3720,7 +3712,15 @@ static int wh_ClientServer_MemThreadTest(void)
int whTest_Crypto(void)
{
printf("Testing crypto: (pthread) mem...\n");
- WH_TEST_RETURN_ON_FAIL(wh_ClientServer_MemThreadTest());
+ WH_TEST_RETURN_ON_FAIL(
+ wh_ClientServer_MemThreadTest(WH_NVM_TEST_BACKEND_FLASH));
+
+#if defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG)
+ printf("Testing crypto: (pthread) mem (flash log)...\n");
+ WH_TEST_RETURN_ON_FAIL(
+ wh_ClientServer_MemThreadTest(WH_NVM_TEST_BACKEND_FLASH_LOG));
+#endif
+
return 0;
}
#endif /* WOLFHSM_CFG_TEST_POSIX && WOLFHSM_CFG_ENABLE_CLIENT && \
diff --git a/test/wh_test_nvm_flash.c b/test/wh_test_nvm_flash.c
index f6bfd69e6..7ddc41845 100644
--- a/test/wh_test_nvm_flash.c
+++ b/test/wh_test_nvm_flash.c
@@ -31,6 +31,7 @@
#include "wolfhsm/wh_error.h"
#include "wolfhsm/wh_nvm.h"
#include "wolfhsm/wh_nvm_flash.h"
+#include "wolfhsm/wh_nvm_flash_log.h"
#include "wolfhsm/wh_flash_unit.h"
/* NVM simulator backends to use for testing NVM module */
@@ -155,8 +156,7 @@ static void _ShowList(const whNvmCb* cb, void* context)
#endif
-static int addObjectWithReadBackCheck(const whNvmCb* cb,
- whNvmFlashContext* context,
+static int addObjectWithReadBackCheck(const whNvmCb* cb, void* context,
whNvmMetadata* meta, whNvmSize data_len,
const uint8_t* data)
@@ -316,10 +316,8 @@ int whTest_Flash(const whFlashCb* fcb, void* fctx, const void* cfg)
return 0;
}
-int whTest_NvmFlashCfg(whNvmFlashConfig* cfg)
+int whTest_NvmFlashCfg(void* cfg, void* context, const whNvmCb* cb)
{
- const whNvmCb cb[1] = {WH_NVM_FLASH_CB};
- whNvmFlashContext context[1] = {0};
int ret = 0;
WH_TEST_RETURN_ON_FAIL(cb->Init(context, cfg));
@@ -426,7 +424,8 @@ int whTest_NvmFlashCfg(whNvmFlashConfig* cfg)
goto cleanup;
}
- if ((ret = cb->Read(context, ids[i], 0, metaBuf.len, dataBuf)) != 0) {
+ if ((ret = cb->Read(context, ids[i], 0, metaBuf.len, dataBuf)) !=
+ 0) {
WH_ERROR_PRINT("Read after reclaim returned %d\n", ret);
goto cleanup;
}
@@ -495,9 +494,24 @@ int whTest_NvmFlash_RamSim(void)
.context = myHalFlashCtx,
.config = myHalFlashCfg,
};
+ whNvmFlashContext nvmFlashCtx[1] = {0};
+ const whNvmCb nvmFlashCb[1] = {WH_NVM_FLASH_CB};
+ WH_TEST_RETURN_ON_FAIL(
+ whTest_NvmFlashCfg(&myNvmCfg, nvmFlashCtx, nvmFlashCb));
- return whTest_NvmFlashCfg(&myNvmCfg);
+#if defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG)
+ whNvmFlashLogConfig myLogCfg = {
+ .flash_cb = myCb,
+ .flash_ctx = myHalFlashCtx,
+ .flash_cfg = myHalFlashCfg,
+ };
+ whNvmFlashLogContext nvmLogCtx[1] = {0};
+ const whNvmCb nvmLogCb[1] = {WH_NVM_FLASH_LOG_CB};
+ WH_TEST_RETURN_ON_FAIL(whTest_NvmFlashCfg(&myLogCfg, nvmLogCtx, nvmLogCb));
+#endif /* WOLFHSM_CFG_SERVER_NVM_FLASH_LOG */
+
+ return 0;
}
static int
@@ -629,8 +643,31 @@ int whTest_NvmFlash_PosixFileSim(void)
.context = myHalFlashContext,
.config = myHalFlashConfig,
};
+ whNvmFlashContext nvmFlashCtx[1] = {0};
+ const whNvmCb nvmFlashCb[1] = {WH_NVM_FLASH_CB};
+
+ WH_TEST_ASSERT(0 == whTest_NvmFlashCfg(&myNvmCfg, nvmFlashCtx, nvmFlashCb));
+
+
+ unlink(myHalFlashConfig[0].filename);
+
+#if defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG)
+ WH_TEST_ASSERT(myHalFlashConfig[0].partition_size >=
+ WH_NVM_FLASH_LOG_PARTITION_SIZE);
+ myHalFlashConfig[0].partition_size = WH_NVM_FLASH_LOG_PARTITION_SIZE;
+
+ memset(myHalFlashContext, 0, sizeof(myHalFlashContext));
+
+ whNvmFlashLogConfig myLogCfg = {
+ .flash_cb = myCb,
+ .flash_ctx = myHalFlashContext,
+ .flash_cfg = myHalFlashConfig,
+ };
+ whNvmFlashLogContext nvmLogCtx[1] = {0};
+ const whNvmCb nvmLogCb[1] = {WH_NVM_FLASH_LOG_CB};
- WH_TEST_ASSERT(0 == whTest_NvmFlashCfg(&myNvmCfg));
+ WH_TEST_RETURN_ON_FAIL(whTest_NvmFlashCfg(&myLogCfg, nvmLogCtx, nvmLogCb));
+#endif /* WOLFHSM_CFG_SERVER_NVM_FLASH_LOG */
/* Remove the configured file on success*/
unlink(myHalFlashConfig[0].filename);
diff --git a/test/wh_test_server_img_mgr.c b/test/wh_test_server_img_mgr.c
index 45b50d4b8..ec2cfd7ca 100644
--- a/test/wh_test_server_img_mgr.c
+++ b/test/wh_test_server_img_mgr.c
@@ -1192,7 +1192,7 @@ static int whTest_ServerImgMgrServerCfgRsa2048(whServerConfig* serverCfg)
}
#endif /* !NO_RSA */
-int whTest_ServerImgMgr(void)
+int whTest_ServerImgMgr(whTestNvmBackendType nvmType)
{
int rc = 0;
const uint32_t BUFFER_SIZE = 1024;
@@ -1229,22 +1229,13 @@ int whTest_ServerImgMgr(void)
}};
const whFlashCb fcb[1] = {WH_FLASH_RAMSIM_CB};
- /* NVM Flash Configuration using RamSim HAL Flash */
- whNvmFlashConfig nf_conf[1] = {{
- .cb = fcb,
- .context = fc,
- .config = fc_conf,
- }};
- whNvmFlashContext nfc[1] = {0};
- whNvmCb nfcb[1] = {WH_NVM_FLASH_CB};
-
- whNvmConfig n_conf[1] = {{
- .cb = nfcb,
- .context = nfc,
- .config = nf_conf,
- }};
+ whTestNvmBackendUnion nvm_setup;
+ whNvmConfig n_conf[1];
whNvmContext nvm[1] = {{0}};
+ WH_TEST_RETURN_ON_FAIL(
+ whTest_NvmCfgBackend(nvmType, &nvm_setup, n_conf, fc_conf, fc, fcb));
+
whServerCryptoContext crypto[1] = {{
.devId = INVALID_DEVID,
}};
diff --git a/test/wh_test_server_img_mgr.h b/test/wh_test_server_img_mgr.h
index 576422ea3..1ba8d41eb 100644
--- a/test/wh_test_server_img_mgr.h
+++ b/test/wh_test_server_img_mgr.h
@@ -20,6 +20,8 @@
#ifndef WH_TEST_SERVER_IMG_MGR_H
#define WH_TEST_SERVER_IMG_MGR_H
-int whTest_ServerImgMgr(void);
+#include "wh_test_common.h"
-#endif /* WH_TEST_SERVER_IMG_MGR_H */
\ No newline at end of file
+int whTest_ServerImgMgr(whTestNvmBackendType nvmType);
+
+#endif /* WH_TEST_SERVER_IMG_MGR_H */
diff --git a/wolfhsm/wh_nvm.h b/wolfhsm/wh_nvm.h
index f52e3e99b..a0c8cefd1 100644
--- a/wolfhsm/wh_nvm.h
+++ b/wolfhsm/wh_nvm.h
@@ -82,8 +82,8 @@ typedef struct {
const whNvmId* id_list);
/* Read the data of the object starting at the byte offset */
- int (*Read)(void* context, whNvmId id, whNvmSize offset,
- whNvmSize data_len, uint8_t* data);
+ int (*Read)(void* context, whNvmId id, whNvmSize offset, whNvmSize data_len,
+ uint8_t* data);
} whNvmCb;
@@ -126,6 +126,6 @@ int wh_Nvm_DestroyObjects(whNvmContext* context, whNvmId list_count,
const whNvmId* id_list);
int wh_Nvm_Read(whNvmContext* context, whNvmId id, whNvmSize offset,
- whNvmSize data_len, uint8_t* data);
+ whNvmSize data_len, uint8_t* data);
#endif /* !WOLFHSM_WH_NVM_H_ */
diff --git a/wolfhsm/wh_nvm_flash.h b/wolfhsm/wh_nvm_flash.h
index 622f5c484..b928a59c7 100644
--- a/wolfhsm/wh_nvm_flash.h
+++ b/wolfhsm/wh_nvm_flash.h
@@ -100,8 +100,8 @@ int wh_NvmFlash_AddObject(void* c, whNvmMetadata* meta,
whNvmSize data_len, const uint8_t* data);
int wh_NvmFlash_DestroyObjects(void* c, whNvmId list_count,
const whNvmId* id_list);
-int wh_NvmFlash_Read(void* c, whNvmId id, whNvmSize offset,
- whNvmSize data_len, uint8_t* data);
+int wh_NvmFlash_Read(void* c, whNvmId id, whNvmSize offset, whNvmSize data_len,
+ uint8_t* data);
#define WH_NVM_FLASH_CB \
{ \
diff --git a/wolfhsm/wh_nvm_flash_log.h b/wolfhsm/wh_nvm_flash_log.h
new file mode 100644
index 000000000..42d879623
--- /dev/null
+++ b/wolfhsm/wh_nvm_flash_log.h
@@ -0,0 +1,100 @@
+/*
+ * 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 .
+ */
+#ifndef WOLFHSM_WH_NVM_FLASH_LOG_H_
+#define WOLFHSM_WH_NVM_FLASH_LOG_H_
+
+#if defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG)
+
+#include "wolfhsm/wh_settings.h"
+
+#include "wolfhsm/wh_common.h"
+#include "wolfhsm/wh_flash.h"
+
+#ifndef WH_NVM_FLASH_LOG_WRITE_GRANULARITY
+#define WH_NVM_FLASH_LOG_WRITE_GRANULARITY 64
+#endif
+
+#ifndef WOLFHSM_CFG_NVM_OBJECT_COUNT
+#define WOLFHSM_CFG_NVM_OBJECT_COUNT 32
+#endif
+
+#ifndef WH_NVM_FLASH_LOG_PARTITION_SIZE
+#define WH_NVM_FLASH_LOG_PARTITION_SIZE (4 * 1024)
+#endif
+
+typedef struct {
+ uint32_t partition_epoch;
+ uint32_t size;
+ uint8_t WH_PAD[WH_NVM_FLASH_LOG_WRITE_GRANULARITY - sizeof(uint32_t) * 2];
+} whNvmFlashLogPartitionHeader;
+
+/* In-memory representation of a partition */
+typedef struct {
+ whNvmFlashLogPartitionHeader header;
+ uint8_t data[WH_NVM_FLASH_LOG_PARTITION_SIZE];
+} whNvmFlashLogMemPartition;
+
+/* Flash log backend context structure */
+typedef struct {
+ const whFlashCb* flash_cb; /* Flash callback interface */
+ void* flash_ctx; /* Flash context */
+ uint32_t partition_size;
+ uint32_t active_partition; /* 0 or 1 */
+ int is_initialized;
+ whNvmFlashLogMemPartition directory;
+} whNvmFlashLogContext;
+
+/* Flash log backend config structure */
+typedef struct {
+ const whFlashCb* flash_cb; /* Flash callback interface */
+ void* flash_ctx; /* Flash context */
+ const void* flash_cfg; /* Config to be passed to cb->Init */
+} whNvmFlashLogConfig;
+
+int wh_NvmFlashLog_Init(void* c, const void* cf);
+int wh_NvmFlashLog_Cleanup(void* c);
+int wh_NvmFlashLog_List(void* c, whNvmAccess access, whNvmFlags flags,
+ whNvmId start_id, whNvmId* out_avail_objects,
+ whNvmId* out_id);
+int wh_NvmFlashLog_GetAvailable(void* c, uint32_t* out_avail_size,
+ whNvmId* out_avail_objects,
+ uint32_t* out_reclaim_size,
+ whNvmId* out_reclaim_objects);
+int wh_NvmFlashLog_GetMetadata(void* c, whNvmId id, whNvmMetadata* meta);
+int wh_NvmFlashLog_AddObject(void* c, whNvmMetadata* meta, whNvmSize data_len,
+ const uint8_t* data);
+int wh_NvmFlashLog_DestroyObjects(void* c, whNvmId list_count,
+ const whNvmId* id_list);
+int wh_NvmFlashLog_Read(void* c, whNvmId id, whNvmSize offset,
+ whNvmSize data_len, uint8_t* data);
+
+#define WH_NVM_FLASH_LOG_CB \
+ { \
+ .Init = wh_NvmFlashLog_Init, .Cleanup = wh_NvmFlashLog_Cleanup, \
+ .List = wh_NvmFlashLog_List, \
+ .GetAvailable = wh_NvmFlashLog_GetAvailable, \
+ .GetMetadata = wh_NvmFlashLog_GetMetadata, \
+ .AddObject = wh_NvmFlashLog_AddObject, \
+ .DestroyObjects = wh_NvmFlashLog_DestroyObjects, \
+ .Read = wh_NvmFlashLog_Read, \
+ }
+
+#endif /* WOLFHSM_CFG_SERVER_NVM_FLASH_LOG */
+
+#endif /* WOLFHSM_WH_NVM_FLASH_LOG_H_ */