diff --git a/Makefile.rules b/Makefile.rules index a9fc87f2e..47c622c9d 100644 --- a/Makefile.rules +++ b/Makefile.rules @@ -32,6 +32,8 @@ ifeq ($(ENABLE_DYNAMIC_ALLOC), 1) SDK_SOURCE_PATH += lib_alloc endif +SDK_SOURCE_PATH += lib_c_list + ifneq ($(IS_STANDARD_APP),1) ifneq ($(DISABLE_OS_IO_STACK_USE), 1) ifeq (,$(filter $(DEFINES),HAVE_LEGACY_PID)) diff --git a/doc/Doxyfile b/doc/Doxyfile index 0d16f0695..faaaf1666 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -830,6 +830,8 @@ INPUT = \ lib_cxng/doc \ lib_cxng/include \ lib_cxng/src \ + lib_c_list \ + lib_c_list/doc \ lib_nbgl/doc \ lib_nbgl/include \ lib_nbgl/src \ diff --git a/doc/mainpage.dox b/doc/mainpage.dox index 4e6fb41db..416625c30 100644 --- a/doc/mainpage.dox +++ b/doc/mainpage.dox @@ -13,6 +13,10 @@ The @subpage ble_mainpage page contains all information necessary to interact wi The @subpage cxng_mainpage page contains all information necessary to understand Cryptographic Library +@section list_presentation Generic Linked List Library + +The @subpage c_list_mainpage page contains all information necessary to use the Generic Linked List library. + @section mem_presentation Dynamic Memory Allocator Interface The @subpage mem_alloc_mainpage page contains all information necessary to interact with Dynamic Memory Allocator. diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt index cdeb3b107..03e1e391c 100644 --- a/fuzzing/CMakeLists.txt +++ b/fuzzing/CMakeLists.txt @@ -118,6 +118,7 @@ if(NOT ${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) secure_sdk INTERFACE macros alloc + c_list cxng io nbgl diff --git a/fuzzing/README.md b/fuzzing/README.md index bc729b56c..4e2849371 100644 --- a/fuzzing/README.md +++ b/fuzzing/README.md @@ -3,6 +3,7 @@ ## Manual usage based on Ledger container ### About Fuzzing Framework + The code is divided into the following folders: ```bash @@ -49,23 +50,22 @@ export BOLOS_SDK=/app cd fuzzing # You must run it from the fuzzing folder -./local_run.sh --build=1 --fuzzer=build/fuzz_bip32 --j=4 --run-fuzzer=1 --compute-coverage=1 --BOLOS_SDK=${BOLOS_SDK} +./local_run.sh --BOLOS_SDK=${BOLOS_SDK} --j=4 --build=1 --fuzzer=build/fuzz_bip32 --run-fuzzer=1 --compute-coverage=1 ``` ### About local_run.sh -| Parameter | Type | Description | -| :--------------------- | :------------------ | :------------------------------------------------------------------- | -| `--BOLOS_SDK` | `PATH TO BOLOS SDK` | **Required**. Path to the BOLOS SDK | -| `--build` | `bool` | **Optional**. Whether to build the project (default: 0) | -| `--fuzzer` | `PATH` | **Required**. Path to the fuzzer binary | -| `--compute-coverage` | `bool` | **Optional**. Whether to compute coverage after fuzzing (default: 0) | -| `--run-fuzzer` | `bool` | **Optional**. Whether to run or not the fuzzer (default: 0) | -| `--run-crash` | `FILENAME` | **Optional**. Run the fuzzer on a specific crash input file (default: 0) | -| `--sanitizer` | `address or memory` | **Optional**. Compile fuzzer with sanitizer (default: address) | +| Parameter | Type | Description | +| :--------------------- | :------------------ | :------------------------------------------------------------------- ---------| +| `--BOLOS_SDK` | `PATH TO BOLOS SDK` | **Required**. Path to the BOLOS SDK | +| `--build` | `bool` | **Optional**. Whether to build the project (default: 0) | +| `--fuzzer` | `PATH` | **Required**. Path to the fuzzer binary | +| `--compute-coverage` | `bool` | **Optional**. Whether to compute coverage after fuzzing (default: 0) | +| `--run-fuzzer` | `bool` | **Optional**. Whether to run or not the fuzzer (default: 0) | +| `--run-crash` | `FILENAME` | **Optional**. Run the fuzzer on a specific crash input file (default: 0) | +| `--sanitizer` | `address or memory` | **Optional**. Compile fuzzer with sanitizer (default: address) | | `--j` | `int` | **Optional**. Number of parallel jobs/CPUs for build and fuzzing (default: 1) | -| `--help` | | **Optional**. Display help message | - +| `--help` | | **Optional**. Display help message | ### Writing your Harness @@ -138,6 +138,7 @@ cmake -S . -B build -DCMAKE_C_COMPILER=clang -DSANITIZER=address -G Ninja -DTARG ./build/fuzz_apdu_parser ./build/fuzz_base58 ./build/fuzz_bip32 +./build/fuzz_c_list ./build/fuzz_qrcodegen ./build/fuzz_alloc ./build/fuzz_alloc_utils diff --git a/fuzzing/docs/fuzz_c_list.md b/fuzzing/docs/fuzz_c_list.md new file mode 100644 index 000000000..80efe0a39 --- /dev/null +++ b/fuzzing/docs/fuzz_c_list.md @@ -0,0 +1,227 @@ +# Fuzzing lib_c_list + +## Overview + +This fuzzer tests the generic linked list library (`lib_c_list`) for memory safety issues, edge cases, and potential crashes. It exercises both **forward lists** (`c_flist_*`) and **doubly-linked lists** (`c_dlist_*`) with all available operations including insertion, removal, sorting, reversing, and traversal. + +## List Type Selection + +The **first byte** determines which type of list to test: + +- **Bit 7 = 0**: Forward list (singly-linked) +- **Bit 7 = 1**: Doubly-linked list + +This allows the fuzzer to test both implementations independently within a single harness. + +## Operations Tested + +### Forward List Operations (c_flist_*) + +The fuzzer uses the lower 4 bits of each input byte to select operations: + +| Op Code | Operation | Description | +|---------|-------------------|------------------------------------------------| +| 0x00 | `push_front` | Add node at the beginning (O(1)) | +| 0x01 | `push_back` | Add node at the end (O(n)) | +| 0x02 | `pop_front` | Remove first node (O(1)) | +| 0x03 | `pop_back` | Remove last node (O(n)) | +| 0x04 | `insert_after` | Insert node after reference node (O(1)) | +| 0x05 | `remove` | Remove specific node (O(n)) | +| 0x06 | `clear` | Remove all nodes (O(n)) | +| 0x07 | `sort` | Sort list by node ID (O(n²)) | +| 0x08 | `size` | Get list size (O(n)) | +| 0x09 | `reverse` | Reverse list order (O(n)) | +| 0x0A | `empty` | Check if list is empty (O(1)) | + +### Doubly-Linked List Operations (c_dlist_*) + +| Op Code | Operation | Description | +|---------|-------------------|------------------------------------------------| +| 0x00 | `push_front` | Add node at the beginning (O(1)) | +| 0x01 | `push_back` | Add node at the end (O(1) ⚡) | +| 0x02 | `pop_front` | Remove first node (O(1) ⚡) | +| 0x03 | `pop_back` | Remove last node (O(1) ⚡) | +| 0x04 | `insert_after` | Insert node after reference (O(1)) | +| 0x05 | `insert_before` | Insert node before reference (O(1) ⚡) | +| 0x06 | `remove` | Remove specific node (O(1) ⚡) | +| 0x07 | `clear` | Remove all nodes (O(n)) | +| 0x08 | `sort` | Sort list by node ID (O(n²)) | +| 0x09 | `size` | Get list size (O(n)) | +| 0x0A | `reverse` | Reverse list order (O(n)) | +| 0x0B | `empty` | Check if list is empty (O(1)) | + +## Features + +### Dual List Testing + +The fuzzer tests both list implementations: + +- **Forward Lists** (`c_flist_node_t`): Singly-linked, minimal memory overhead +- **Doubly-Linked Lists** (`c_dlist_node_t`): Bidirectional traversal, O(1) operations + +### Safety Checks + +- **Type safety**: Separate tracking for forward and doubly-linked nodes +- **Node tracking**: All allocated nodes are tracked for cleanup +- **Memory leak prevention**: Automatic cleanup at end of fuzzing iteration +- **Size limits**: Prevents excessive memory usage + +### Test Data + +Each node contains: + +- Unique ID (auto-incremented) +- 16 bytes of fuzzer-provided data +- Standard list node structure (with or without prev pointer) + +### Limits + +- `MAX_NODES`: 1000 (prevents excessive memory usage) +- `MAX_TRACKERS`: 100 (limits number of tracked nodes) +- Minimum input length: 2 bytes (1 for type selection, 1+ for operations) + +## Building + +From the SDK root: + +```bash +cd fuzzing +mkdir -p build && cd build +cmake -S .. -B . -DCMAKE_C_COMPILER=clang -DSANITIZER=address -DBOLOS_SDK=/path/to/sdk +cmake --build . --target fuzz_c_list +``` + +## Running + +### Basic run + +```bash +./fuzz_c_list +``` + +### With specific options + +```bash +# Run for 10000 iterations +./fuzz_c_list -runs=10000 + +# Limit input size to 128 bytes +./fuzz_c_list -max_len=128 + +# Use corpus directory +./fuzz_c_list corpus/ + +# Timeout per input (in seconds) +./fuzz_c_list -timeout=10 +``` + +### Using the helper script + +```bash +cd /path/to/sdk/fuzzing +./local_run.sh --build=1 --fuzzer=build/fuzz_c_list --j=4 --run-fuzzer=1 --BOLOS_SDK=/path/to/sdk +``` + +## Corpus + +Initial corpus files can be placed in: + +```bash +fuzzing/harness/fuzz_c_list/ +``` + +Example corpus files: + +**Forward list operations** (first byte < 0x80): + +```bash +# Simple forward list operations +00 00 <16 bytes> # push_front +00 01 <16 bytes> # push_back +00 02 # pop_front + +# Complex forward list sequence +00 00 <16 bytes> 01 <16 bytes> 07 09 # push_front, push_back, sort, reverse +``` + +**Doubly-linked list operations** (first byte >= 0x80): + +```bash +# Simple doubly-linked operations +80 00 <16 bytes> # push_front +80 01 <16 bytes> # push_back (O(1) - fast!) +80 05 00 <16 bytes> # insert_before (unique to doubly-linked) + +# Complex doubly-linked sequence +80 00 <16 bytes> 01 <16 bytes> 08 0A # push_front, push_back, sort, reverse +``` + +## Input Format + +```bash +Byte 0: [1 bit: list type] [7 bits: unused] + - Bit 7 = 0: Forward list + - Bit 7 = 1: Doubly-linked list + +Byte 1+: [Operation code] [Optional parameters] + - Lower 4 bits: operation type (0x00-0x0F) + - Upper 4 bits: unused + +Parameters: +- Node creation: 16 bytes of data +- Node reference: 1 byte index (for insert/remove operations) +``` + +## Debugging + +Enable debug output by uncommenting `#define DEBUG_CRASH` in `fuzzer_c_list.c`: + +```c +#define DEBUG_CRASH +``` + +This will print: + +- Node creation/deletion +- Operation execution +- List size changes +- Cleanup operations + +## Crash Analysis + +If a crash is found: + +1. The fuzzer will save the crashing input to `crash-*` or `leak-*` files +2. Reproduce the crash: + + ```bash + ./fuzz_c_list crash-HASH + ``` + +3. Debug with AddressSanitizer output +4. Fix the issue in `lib_c_list/c_list.c` +5. Verify fix by re-running fuzzer + +## Expected Behavior + +The fuzzer should: + +- ✅ Handle all operations safely +- ✅ Prevent memory leaks (all nodes cleaned up) +- ✅ Detect invalid operations (return false) +- ✅ Handle edge cases (empty list, single node, etc.) +- ✅ Maintain list integrity (no cycles, no corruption) + +## Known Issues + +None currently. If you find a crash, please report it! + +## Coverage + +To generate coverage report: + +```bash +./local_run.sh --build=1 --fuzzer=build/fuzz_c_list --compute-coverage=1 --BOLOS_SDK=/path/to/sdk +``` + +Coverage files will be in `fuzzing/out/coverage/`. diff --git a/fuzzing/extra/lib_c_list.cmake b/fuzzing/extra/lib_c_list.cmake new file mode 100644 index 000000000..1b62efc3a --- /dev/null +++ b/fuzzing/extra/lib_c_list.cmake @@ -0,0 +1,13 @@ +include_guard() + +# Include required libraries +include(${BOLOS_SDK}/fuzzing/libs/lib_c_list.cmake) +include(${BOLOS_SDK}/fuzzing/mock/mock.cmake) + +# Define the executable and its properties here +add_executable(fuzz_c_list ${BOLOS_SDK}/fuzzing/harness/fuzzer_c_list.c) +target_compile_options(fuzz_c_list PUBLIC ${COMPILATION_FLAGS}) +target_link_options(fuzz_c_list PUBLIC ${LINK_FLAGS}) + +# Link with required libraries +target_link_libraries(fuzz_c_list PUBLIC c_list mock) diff --git a/fuzzing/harness/fuzzer_c_list.c b/fuzzing/harness/fuzzer_c_list.c new file mode 100644 index 000000000..44e6094dd --- /dev/null +++ b/fuzzing/harness/fuzzer_c_list.c @@ -0,0 +1,502 @@ +#include +#include +#include +#include + +#include "c_list.h" + +#define MAX_NODES 1000 +#define MAX_TRACKERS 100 + +// #define DEBUG_CRASH + +// Test node structures for both list types +typedef struct fuzz_flist_node_s { + c_flist_node_t node; + uint32_t id; + uint8_t data[16]; +} fuzz_flist_node_t; + +typedef struct fuzz_dlist_node_s { + c_dlist_node_t node; + uint32_t id; + uint8_t data[16]; +} fuzz_dlist_node_t; + +// Track allocated nodes for cleanup +struct node_tracker { + void *ptr; + uint32_t id; + bool in_list; + bool is_doubly; // Track which type of list +}; + +static struct node_tracker trackers[MAX_TRACKERS] = {0}; +static uint32_t next_id = 1; + +// Helper to create a new tracked forward list node +fuzz_flist_node_t *create_tracked_flist_node(const uint8_t *data, size_t len) +{ + fuzz_flist_node_t *node = malloc(sizeof(fuzz_flist_node_t)); + if (!node) { + return NULL; + } + + node->node.next = NULL; + node->id = next_id++; + + size_t copy_len = len < sizeof(node->data) ? len : sizeof(node->data); + memcpy(node->data, data, copy_len); + + // Track the node + for (size_t i = 0; i < MAX_TRACKERS; i++) { + if (trackers[i].ptr == NULL) { + trackers[i].ptr = node; + trackers[i].id = node->id; + trackers[i].in_list = false; + trackers[i].is_doubly = false; +#ifdef DEBUG_CRASH + printf("[CREATE FLIST] node id=%u ptr=%p\n", node->id, (void *) node); +#endif + return node; + } + } + + free(node); + return NULL; +} + +// Helper to create a new tracked doubly-linked list node +fuzz_dlist_node_t *create_tracked_dlist_node(const uint8_t *data, size_t len) +{ + fuzz_dlist_node_t *node = malloc(sizeof(fuzz_dlist_node_t)); + if (!node) { + return NULL; + } + + node->node._list.next = NULL; + node->node.prev = NULL; + node->id = next_id++; + + size_t copy_len = len < sizeof(node->data) ? len : sizeof(node->data); + memcpy(node->data, data, copy_len); + + // Track the node + for (size_t i = 0; i < MAX_TRACKERS; i++) { + if (trackers[i].ptr == NULL) { + trackers[i].ptr = node; + trackers[i].id = node->id; + trackers[i].in_list = false; + trackers[i].is_doubly = true; +#ifdef DEBUG_CRASH + printf("[CREATE DLIST] node id=%u ptr=%p\n", node->id, (void *) node); +#endif + return node; + } + } + + free(node); + return NULL; +} + +// Mark node as in list +void mark_in_list(void *node) +{ + for (size_t i = 0; i < MAX_TRACKERS; i++) { + if (trackers[i].ptr == node) { + trackers[i].in_list = true; + return; + } + } +} + +// Mark node as removed from list +void mark_removed_from_list(void *node) +{ + for (size_t i = 0; i < MAX_TRACKERS; i++) { + if (trackers[i].ptr == node) { + trackers[i].in_list = false; + return; + } + } +} + +// Get a tracked node by index (for operations requiring existing nodes) +void *get_tracked_node(uint8_t index, bool must_be_in_list, bool is_doubly) +{ + size_t idx = index % MAX_TRACKERS; + if (trackers[idx].ptr == NULL) { + return NULL; + } + if (trackers[idx].is_doubly != is_doubly) { + return NULL; // Type mismatch + } + if (must_be_in_list && !trackers[idx].in_list) { + return NULL; + } + return trackers[idx].ptr; +} + +// Deletion callback for forward lists +void delete_flist_node(c_flist_node_t *node) +{ + if (!node) { + return; + } + + fuzz_flist_node_t *fnode = (fuzz_flist_node_t *) node; + +#ifdef DEBUG_CRASH + printf("[DELETE FLIST] node id=%u ptr=%p\n", fnode->id, (void *) fnode); +#endif + + for (size_t i = 0; i < MAX_TRACKERS; i++) { + if (trackers[i].ptr == fnode) { + trackers[i].ptr = NULL; + trackers[i].id = 0; + trackers[i].in_list = false; + trackers[i].is_doubly = false; + break; + } + } + + free(fnode); +} + +// Deletion callback for doubly-linked lists +void delete_dlist_node(c_flist_node_t *node) +{ + if (!node) { + return; + } + + fuzz_dlist_node_t *dnode = (fuzz_dlist_node_t *) node; + +#ifdef DEBUG_CRASH + printf("[DELETE DLIST] node id=%u ptr=%p\n", dnode->id, (void *) dnode); +#endif + + for (size_t i = 0; i < MAX_TRACKERS; i++) { + if (trackers[i].ptr == dnode) { + trackers[i].ptr = NULL; + trackers[i].id = 0; + trackers[i].in_list = false; + trackers[i].is_doubly = false; + break; + } + } + + free(dnode); +} + +// Comparison functions +bool compare_flist_nodes(const c_flist_node_t *a, const c_flist_node_t *b) +{ + const fuzz_flist_node_t *na = (const fuzz_flist_node_t *) a; + const fuzz_flist_node_t *nb = (const fuzz_flist_node_t *) b; + return na->id <= nb->id; +} + +bool compare_dlist_nodes(const c_flist_node_t *a, const c_flist_node_t *b) +{ + const fuzz_dlist_node_t *na = (const fuzz_dlist_node_t *) a; + const fuzz_dlist_node_t *nb = (const fuzz_dlist_node_t *) b; + return na->id <= nb->id; +} + +// Cleanup all tracked nodes +void cleanup_all_flist(c_flist_node_t **list) +{ + if (list && *list) { + c_flist_clear(list, delete_flist_node); + } + + for (size_t i = 0; i < MAX_TRACKERS; i++) { + if (trackers[i].ptr && !trackers[i].in_list && !trackers[i].is_doubly) { +#ifdef DEBUG_CRASH + printf("[CLEANUP FLIST] node id=%u ptr=%p\n", trackers[i].id, trackers[i].ptr); +#endif + free(trackers[i].ptr); + trackers[i].ptr = NULL; + trackers[i].id = 0; + trackers[i].is_doubly = false; + } + } +} + +void cleanup_all_dlist(c_dlist_node_t **list) +{ + if (list && *list) { + c_dlist_clear(list, delete_dlist_node); + } + + for (size_t i = 0; i < MAX_TRACKERS; i++) { + if (trackers[i].ptr && !trackers[i].in_list && trackers[i].is_doubly) { +#ifdef DEBUG_CRASH + printf("[CLEANUP DLIST] node id=%u ptr=%p\n", trackers[i].id, trackers[i].ptr); +#endif + free(trackers[i].ptr); + trackers[i].ptr = NULL; + trackers[i].id = 0; + trackers[i].is_doubly = false; + } + } +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size < 2) { + return 0; + } + + // First byte determines list type: 0 = forward list, 1 = doubly-linked list + bool use_doubly = (data[0] & 0x80) != 0; + + c_flist_node_t *flist = NULL; + c_dlist_node_t *dlist = NULL; + + memset(trackers, 0, sizeof(trackers)); + next_id = 1; + +#ifdef DEBUG_CRASH + printf("\n[FUZZING] Starting %s list with %zu bytes\n", + use_doubly ? "DOUBLY-LINKED" : "FORWARD", + size); +#endif + + size_t offset = 1; + while (offset < size) { + uint8_t op = data[offset++]; + +#ifdef DEBUG_CRASH + size_t current_size = use_doubly ? c_dlist_size(&dlist) : c_flist_size(&flist); + printf("[OP] offset=%zu op=0x%02x list_size=%zu\n", offset - 1, op, current_size); +#endif + + if (use_doubly) { + // Doubly-linked list operations + switch (op & 0x0F) { + case 0x00: { // push_front + if (offset + 16 > size) { + goto cleanup; + } + fuzz_dlist_node_t *node = create_tracked_dlist_node(&data[offset], 16); + offset += 16; + if (node && c_dlist_push_front(&dlist, &node->node)) { + mark_in_list(node); + } + break; + } + + case 0x01: { // push_back + if (offset + 16 > size) { + goto cleanup; + } + fuzz_dlist_node_t *node = create_tracked_dlist_node(&data[offset], 16); + offset += 16; + if (node && c_dlist_push_back(&dlist, &node->node)) { + mark_in_list(node); + } + break; + } + + case 0x02: { // pop_front + c_dlist_pop_front(&dlist, delete_dlist_node); + break; + } + + case 0x03: { // pop_back + c_dlist_pop_back(&dlist, delete_dlist_node); + break; + } + + case 0x04: { // insert_after + if (offset + 17 > size) { + goto cleanup; + } + uint8_t ref_idx = data[offset++]; + fuzz_dlist_node_t *ref = get_tracked_node(ref_idx, true, true); + if (ref) { + fuzz_dlist_node_t *node = create_tracked_dlist_node(&data[offset], 16); + if (node && c_dlist_insert_after(&dlist, &ref->node, &node->node)) { + mark_in_list(node); + } + } + offset += 16; + break; + } + + case 0x05: { // insert_before + if (offset + 17 > size) { + goto cleanup; + } + uint8_t ref_idx = data[offset++]; + fuzz_dlist_node_t *ref = get_tracked_node(ref_idx, true, true); + if (ref) { + fuzz_dlist_node_t *node = create_tracked_dlist_node(&data[offset], 16); + if (node && c_dlist_insert_before(&dlist, &ref->node, &node->node)) { + mark_in_list(node); + } + } + offset += 16; + break; + } + + case 0x06: { // remove + if (offset >= size) { + goto cleanup; + } + uint8_t node_idx = data[offset++]; + fuzz_dlist_node_t *node = get_tracked_node(node_idx, true, true); + if (node) { + c_dlist_remove(&dlist, &node->node, delete_dlist_node); + } + break; + } + + case 0x07: { // clear + c_dlist_clear(&dlist, delete_dlist_node); + break; + } + + case 0x08: { // sort + c_dlist_sort(&dlist, compare_dlist_nodes); + break; + } + + case 0x09: { // size + size_t s = c_dlist_size(&dlist); + (void) s; + break; + } + + case 0x0A: { // reverse + c_dlist_reverse(&dlist); + break; + } + + case 0x0B: { // empty + bool empty = c_dlist_empty(&dlist); + (void) empty; + break; + } + + default: + break; + } + + if (c_dlist_size(&dlist) > MAX_NODES) { + goto cleanup; + } + } + else { + // Forward list operations + switch (op & 0x0F) { + case 0x00: { // push_front + if (offset + 16 > size) { + goto cleanup; + } + fuzz_flist_node_t *node = create_tracked_flist_node(&data[offset], 16); + offset += 16; + if (node && c_flist_push_front(&flist, &node->node)) { + mark_in_list(node); + } + break; + } + + case 0x01: { // push_back + if (offset + 16 > size) { + goto cleanup; + } + fuzz_flist_node_t *node = create_tracked_flist_node(&data[offset], 16); + offset += 16; + if (node && c_flist_push_back(&flist, &node->node)) { + mark_in_list(node); + } + break; + } + + case 0x02: { // pop_front + c_flist_pop_front(&flist, delete_flist_node); + break; + } + + case 0x03: { // pop_back + c_flist_pop_back(&flist, delete_flist_node); + break; + } + + case 0x04: { // insert_after + if (offset + 17 > size) { + goto cleanup; + } + uint8_t ref_idx = data[offset++]; + fuzz_flist_node_t *ref = get_tracked_node(ref_idx, true, false); + if (ref) { + fuzz_flist_node_t *node = create_tracked_flist_node(&data[offset], 16); + if (node && c_flist_insert_after(&flist, &ref->node, &node->node)) { + mark_in_list(node); + } + } + offset += 16; + break; + } + + case 0x05: { // remove + if (offset >= size) { + goto cleanup; + } + uint8_t node_idx = data[offset++]; + fuzz_flist_node_t *node = get_tracked_node(node_idx, true, false); + if (node) { + c_flist_remove(&flist, &node->node, delete_flist_node); + } + break; + } + + case 0x06: { // clear + c_flist_clear(&flist, delete_flist_node); + break; + } + + case 0x07: { // sort + c_flist_sort(&flist, compare_flist_nodes); + break; + } + + case 0x08: { // size + size_t s = c_flist_size(&flist); + (void) s; + break; + } + + case 0x09: { // reverse + c_flist_reverse(&flist); + break; + } + + case 0x0A: { // empty + bool empty = c_flist_empty(&flist); + (void) empty; + break; + } + + default: + break; + } + + if (c_flist_size(&flist) > MAX_NODES) { + goto cleanup; + } + } + } + +cleanup: + if (use_doubly) { + cleanup_all_dlist(&dlist); + } + else { + cleanup_all_flist(&flist); + } + return 0; +} diff --git a/fuzzing/libs/lib_c_list.cmake b/fuzzing/libs/lib_c_list.cmake new file mode 100644 index 000000000..e55c72a8f --- /dev/null +++ b/fuzzing/libs/lib_c_list.cmake @@ -0,0 +1,11 @@ +include_guard() +include(${BOLOS_SDK}/fuzzing/macros/macros.cmake) + +file(GLOB LIB_C_LIST_SOURCES "${BOLOS_SDK}/lib_c_list/*.c") + +add_library(c_list ${LIB_C_LIST_SOURCES}) +target_link_libraries(c_list PUBLIC macros) +target_compile_options(c_list PUBLIC ${COMPILATION_FLAGS}) +target_include_directories( + c_list PUBLIC "${BOLOS_SDK}/include/" "${BOLOS_SDK}/lib_c_list/" + "${BOLOS_SDK}/target/${TARGET}/include") diff --git a/lib_c_list/c_list.c b/lib_c_list/c_list.c new file mode 100644 index 000000000..486757a82 --- /dev/null +++ b/lib_c_list/c_list.c @@ -0,0 +1,432 @@ +/** + * @file + * @brief Generic linked list implementation + * + * Provides both singly-linked (c_flist) and doubly-linked (c_list) implementations. + */ + +#include "c_list.h" +#include "os_helpers.h" + +// ============================================================================ +// Internal shared functions with strict validation +// ============================================================================ + +static bool push_front_internal(c_flist_node_t **list, c_flist_node_t *node, bool doubly_linked) +{ + if ((list == NULL) || (node == NULL)) { + return false; + } + node->next = *list; + *list = node; + if (doubly_linked) { + if (node->next != NULL) { + ((c_dlist_node_t *) node->next)->prev = (c_dlist_node_t *) node; + } + ((c_dlist_node_t *) node)->prev = NULL; + } + return true; +} + +static bool pop_front_internal(c_flist_node_t **list, c_list_node_del del_func, bool doubly_linked) +{ + c_flist_node_t *tmp; + + if ((list == NULL) || (*list == NULL)) { + return false; + } + tmp = *list; + *list = tmp->next; + if (del_func != NULL) { + del_func(tmp); + } + if (doubly_linked) { + if (*list != NULL) { + (*(c_dlist_node_t **) list)->prev = NULL; + } + } + return true; +} + +static bool push_back_internal(c_flist_node_t **list, c_flist_node_t *node, bool doubly_linked) +{ + c_flist_node_t *tmp; + + if ((list == NULL) || (node == NULL)) { + return false; + } + node->next = NULL; + if (*list == NULL) { + if (doubly_linked) { + ((c_dlist_node_t *) node)->prev = NULL; + } + *list = node; + } + else { + tmp = *list; + while (tmp->next != NULL) { + tmp = tmp->next; + } + if (doubly_linked) { + ((c_dlist_node_t *) node)->prev = (c_dlist_node_t *) tmp; + } + tmp->next = node; + } + return true; +} + +static bool insert_after_internal(c_flist_node_t *ref, c_flist_node_t *node, bool doubly_linked) +{ + if ((ref == NULL) || (node == NULL)) { + return false; + } + if (doubly_linked) { + if (ref->next != NULL) { + ((c_dlist_node_t *) (ref->next))->prev = (c_dlist_node_t *) node; + } + ((c_dlist_node_t *) node)->prev = (c_dlist_node_t *) ref; + } + node->next = ref->next; + ref->next = node; + return true; +} + +static bool remove_internal(c_flist_node_t **list, + c_flist_node_t *node, + c_list_node_del del_func, + bool doubly_linked) +{ + c_flist_node_t *it; + c_flist_node_t *tmp; + + if ((list == NULL) || (node == NULL) || (*list == NULL)) { + return false; + } + if (node == *list) { + // first element + return pop_front_internal(list, del_func, doubly_linked); + } + it = *list; + while ((it->next != node) && (it->next != NULL)) { + it = it->next; + } + if (it->next == NULL) { + // node not found + return false; + } + tmp = it->next->next; + if (doubly_linked) { + if (tmp != NULL) { + ((c_dlist_node_t *) tmp)->prev = ((c_dlist_node_t *) node)->prev; + } + } + if (del_func != NULL) { + del_func(it->next); + } + it->next = tmp; + return true; +} + +static size_t remove_if_internal(c_flist_node_t **list, + c_list_node_pred pred_func, + c_list_node_del del_func, + bool doubly_linked) +{ + c_flist_node_t *node; + c_flist_node_t *tmp; + size_t count = 0; + + if ((list == NULL) || (pred_func == NULL)) { + return 0; + } + node = *list; + while (node != NULL) { + tmp = node->next; + if (pred_func(node)) { + if (remove_internal(list, node, del_func, doubly_linked)) { + count += 1; + } + } + node = tmp; + } + return count; +} + +static bool sort_internal(c_flist_node_t **list, c_list_node_cmp cmp_func, bool doubly_linked) +{ + c_flist_node_t **tmp; + c_flist_node_t *a, *b; + bool sorted; + + if ((list == NULL) || (cmp_func == NULL)) { + return false; + } + do { + sorted = true; + for (tmp = list; (*tmp != NULL) && ((*tmp)->next != NULL); tmp = &(*tmp)->next) { + a = *tmp; + b = a->next; + if (cmp_func(a, b) == false) { + *tmp = b; + a->next = b->next; + b->next = a; + if (doubly_linked) { + ((c_dlist_node_t *) b)->prev = ((c_dlist_node_t *) a)->prev; + ((c_dlist_node_t *) a)->prev = (c_dlist_node_t *) b; + if (a->next != NULL) { + ((c_dlist_node_t *) a->next)->prev = (c_dlist_node_t *) a; + } + } + sorted = false; + } + } + } while (!sorted); + return true; +} + +static size_t unique_internal(c_flist_node_t **list, + c_list_node_bin_pred pred_func, + c_list_node_del del_func, + bool doubly_linked) +{ + size_t count = 0; + + if ((list == NULL) || (pred_func == NULL)) { + return 0; + } + for (c_flist_node_t *ref = *list; ref != NULL; ref = ref->next) { + c_flist_node_t *node = ref->next; + while ((node != NULL) && (pred_func(ref, node))) { + c_flist_node_t *tmp = node->next; + if (remove_internal(list, node, del_func, doubly_linked)) { + count += 1; + } + node = tmp; + } + } + return count; +} + +static bool reverse_internal(c_flist_node_t **list, bool doubly_linked) +{ + c_flist_node_t *node; + c_flist_node_t *prev = NULL; + c_flist_node_t *next; + + if (list == NULL) { + return false; + } + node = *list; + while (node != NULL) { + next = node->next; + node->next = prev; + if (doubly_linked) { + ((c_dlist_node_t *) node)->prev = (c_dlist_node_t *) next; + } + *list = node; + prev = node; + node = next; + } + return true; +} + +// ============================================================================ +// Forward list (singly-linked) functions +// ============================================================================ + +bool c_flist_push_front(c_flist_node_t **list, c_flist_node_t *node) +{ + return push_front_internal(list, node, false); +} + +bool c_flist_pop_front(c_flist_node_t **list, c_list_node_del del_func) +{ + return pop_front_internal(list, del_func, false); +} + +bool c_flist_push_back(c_flist_node_t **list, c_flist_node_t *node) +{ + return push_back_internal(list, node, false); +} + +bool c_flist_pop_back(c_flist_node_t **list, c_list_node_del del_func) +{ + c_flist_node_t *tmp; + + if ((list == NULL) || (*list == NULL)) { + return false; + } + tmp = *list; + // only one element + if (tmp->next == NULL) { + return c_flist_pop_front(list, del_func); + } + while (tmp->next->next != NULL) { + tmp = tmp->next; + } + if (del_func != NULL) { + del_func(tmp->next); + } + tmp->next = NULL; + return true; +} + +bool c_flist_insert_after(c_flist_node_t **list, c_flist_node_t *ref, c_flist_node_t *node) +{ + UNUSED(list); + return insert_after_internal(ref, node, false); +} + +bool c_flist_remove(c_flist_node_t **list, c_flist_node_t *node, c_list_node_del del_func) +{ + return remove_internal(list, node, del_func, false); +} + +size_t c_flist_remove_if(c_flist_node_t **list, + c_list_node_pred pred_func, + c_list_node_del del_func) +{ + return remove_if_internal(list, pred_func, del_func, false); +} + +bool c_flist_clear(c_flist_node_t **list, c_list_node_del del_func) +{ + c_flist_node_t *tmp; + c_flist_node_t *next; + + if (list == NULL) { + return false; + } + tmp = *list; + while (tmp != NULL) { + next = tmp->next; + if (del_func != NULL) { + del_func(tmp); + } + tmp = next; + } + *list = NULL; + return true; +} + +size_t c_flist_size(c_flist_node_t *const *list) +{ + size_t size = 0; + + if (list != NULL) { + for (c_flist_node_t *tmp = *list; tmp != NULL; tmp = tmp->next) { + size += 1; + } + } + return size; +} + +bool c_flist_empty(c_flist_node_t *const *list) +{ + return c_flist_size(list) == 0; +} + +bool c_flist_sort(c_flist_node_t **list, c_list_node_cmp cmp_func) +{ + return sort_internal(list, cmp_func, false); +} + +size_t c_flist_unique(c_flist_node_t **list, + c_list_node_bin_pred pred_func, + c_list_node_del del_func) +{ + return unique_internal(list, pred_func, del_func, false); +} + +bool c_flist_reverse(c_flist_node_t **list) +{ + return reverse_internal(list, false); +} + +// ============================================================================ +// Doubly-linked list functions +// ============================================================================ + +bool c_dlist_push_front(c_dlist_node_t **list, c_dlist_node_t *node) +{ + return push_front_internal((c_flist_node_t **) list, (c_flist_node_t *) node, true); +} + +bool c_dlist_pop_front(c_dlist_node_t **list, c_list_node_del del_func) +{ + return pop_front_internal((c_flist_node_t **) list, del_func, true); +} + +bool c_dlist_push_back(c_dlist_node_t **list, c_dlist_node_t *node) +{ + return push_back_internal((c_flist_node_t **) list, (c_flist_node_t *) node, true); +} + +bool c_dlist_pop_back(c_dlist_node_t **list, c_list_node_del del_func) +{ + return c_flist_pop_back((c_flist_node_t **) list, del_func); +} + +bool c_dlist_insert_before(c_dlist_node_t **list, c_dlist_node_t *ref, c_dlist_node_t *node) +{ + if ((ref == NULL) || (node == NULL)) { + return false; + } + if (ref->prev == NULL) { + if ((list != NULL) && (*list == ref)) { + return c_dlist_push_front(list, node); + } + return false; + } + return c_dlist_insert_after(list, ref->prev, node); +} + +bool c_dlist_insert_after(c_dlist_node_t **list, c_dlist_node_t *ref, c_dlist_node_t *node) +{ + UNUSED(list); + return insert_after_internal((c_flist_node_t *) ref, (c_flist_node_t *) node, true); +} + +bool c_dlist_remove(c_dlist_node_t **list, c_dlist_node_t *node, c_list_node_del del_func) +{ + return remove_internal((c_flist_node_t **) list, (c_flist_node_t *) node, del_func, true); +} + +size_t c_dlist_remove_if(c_dlist_node_t **list, + c_list_node_pred pred_func, + c_list_node_del del_func) +{ + return remove_if_internal((c_flist_node_t **) list, pred_func, del_func, true); +} + +bool c_dlist_clear(c_dlist_node_t **list, c_list_node_del del_func) +{ + return c_flist_clear((c_flist_node_t **) list, del_func); +} + +size_t c_dlist_size(c_dlist_node_t *const *list) +{ + return c_flist_size((c_flist_node_t **) list); +} + +bool c_dlist_empty(c_dlist_node_t *const *list) +{ + return c_dlist_size(list) == 0; +} + +bool c_dlist_sort(c_dlist_node_t **list, c_list_node_cmp cmp_func) +{ + return sort_internal((c_flist_node_t **) list, cmp_func, true); +} + +size_t c_dlist_unique(c_dlist_node_t **list, + c_list_node_bin_pred pred_func, + c_list_node_del del_func) +{ + return unique_internal((c_flist_node_t **) list, pred_func, del_func, true); +} + +bool c_dlist_reverse(c_dlist_node_t **list) +{ + return reverse_internal((c_flist_node_t **) list, true); +} diff --git a/lib_c_list/c_list.h b/lib_c_list/c_list.h new file mode 100644 index 000000000..fbad7136f --- /dev/null +++ b/lib_c_list/c_list.h @@ -0,0 +1,367 @@ +/** + * @file + * @brief Generic linked list implementation (singly and doubly-linked) + * + * This file provides both forward lists (singly-linked) and doubly-linked lists. + * Based on app-ethereum implementation. + */ + +#pragma once + +#include +#include + +/** + * @struct c_flist_node_s + * @brief Forward list node structure (singly-linked) + * + * This structure represents a node in a forward list (singly-linked list). + * It contains only a pointer to the next node, making it memory-efficient (4-8 bytes per node). + * + * @note Memory footprint: 4 bytes (32-bit) or 8 bytes (64-bit) + * @see c_dlist_node_t for doubly-linked list with backward traversal support + */ +typedef struct c_flist_node_s { + struct c_flist_node_s *next; /**< Pointer to the next node (NULL if last) */ +} c_flist_node_t; + +/** + * @struct c_dlist_node_s + * @brief Doubly-linked list node structure + * + * This structure represents a node in a doubly-linked list. + * It embeds a c_flist_node_t and adds a previous pointer for bidirectional traversal. + * + * @note Memory footprint: 8 bytes (32-bit) or 16 bytes (64-bit) + * @note The _list member provides forward traversal compatibility + * @see c_flist_node_t for lighter singly-linked alternative + */ +typedef struct c_dlist_node_s { + c_flist_node_t _list; /**< Forward list node (contains next pointer) */ + struct c_dlist_node_s *prev; /**< Pointer to the previous node (NULL if first) */ +} c_dlist_node_t; + +/** + * @typedef c_list_node_del + * @brief Callback function to delete/free a node + * + * This function is called when a node needs to be deleted from the list. + * It should free any resources associated with the node and the node itself. + * + * @param[in] node The node to delete (never NULL when called by list functions) + * + * @note If NULL is passed to list functions, nodes are removed but not freed + * @note This function should handle the complete node lifecycle cleanup + */ +typedef void (*c_list_node_del)(c_flist_node_t *node); + +/** + * @typedef c_list_node_cmp + * @brief Callback function to compare two nodes for sorting + * + * This function is used by c_flist_sort() and c_dlist_sort() to determine node order. + * It should return true if node 'a' should come before node 'b' in the sorted list. + * + * @param[in] a First node to compare + * @param[in] b Second node to compare + * @return true if a < b (a should come before b), false otherwise + * + * @note Both parameters are never NULL when called by list functions + * @note Use consistent comparison logic for stable sorting + */ +typedef bool (*c_list_node_cmp)(const c_flist_node_t *a, const c_flist_node_t *b); + +/** + * @typedef c_list_node_pred + * @brief Callback function to test a single node (unary predicate) + * + * This function is used by c_flist_remove_if() and c_dlist_remove_if() to determine + * which nodes should be removed from the list. + * + * @param[in] node The node to test + * @return true if the node matches the condition (should be removed), false otherwise + * + * @note The parameter is never NULL when called by list functions + */ +typedef bool (*c_list_node_pred)(const c_flist_node_t *node); + +/** + * @typedef c_list_node_bin_pred + * @brief Callback function to test two nodes for equality (binary predicate) + * + * This function is used by c_flist_unique() and c_dlist_unique() to determine + * if two consecutive nodes are considered equal and should be deduplicated. + * + * @param[in] a First node to compare + * @param[in] b Second node to compare + * @return true if nodes are equal (b should be removed), false otherwise + * + * @note Both parameters are never NULL when called by list functions + * @note Typically used after sorting to remove consecutive duplicates + */ +typedef bool (*c_list_node_bin_pred)(const c_flist_node_t *a, const c_flist_node_t *b); + +// ============================================================================ +// Forward list (singly-linked) functions +// ============================================================================ + +/** + * @brief Add a node at the beginning of the forward list + * @param[in,out] list Pointer to the list head + * @param[in] node Node to add (must have node->next == NULL) + * @return true on success, false on error + * @note Time complexity: O(1) + */ +bool c_flist_push_front(c_flist_node_t **list, c_flist_node_t *node); + +/** + * @brief Remove and delete the first node from the forward list + * @param[in,out] list Pointer to the list head + * @param[in] del_func Function to delete the node (can be NULL) + * @return true on success, false if list is empty or NULL + * @note Time complexity: O(1) + */ +bool c_flist_pop_front(c_flist_node_t **list, c_list_node_del del_func); + +/** + * @brief Add a node at the end of the forward list + * @param[in,out] list Pointer to the list head + * @param[in] node Node to add (must have node->next == NULL) + * @return true on success, false on error + * @note Time complexity: O(n) + */ +bool c_flist_push_back(c_flist_node_t **list, c_flist_node_t *node); + +/** + * @brief Remove and delete the last node from the forward list + * @param[in,out] list Pointer to the list head + * @param[in] del_func Function to delete the node (can be NULL) + * @return true on success, false if list is empty or NULL + * @note Time complexity: O(n) + */ +bool c_flist_pop_back(c_flist_node_t **list, c_list_node_del del_func); + +/** + * @brief Insert a node after a reference node in the forward list + * @param[in,out] list Pointer to the list head + * @param[in] ref Reference node (must be in list) + * @param[in] node Node to insert (must have node->next == NULL) + * @return true on success, false on error + * @note Time complexity: O(1) + */ +bool c_flist_insert_after(c_flist_node_t **list, c_flist_node_t *ref, c_flist_node_t *node); + +/** + * @brief Remove and delete a specific node from the forward list + * @param[in,out] list Pointer to the list head + * @param[in] node Node to remove (must be in list) + * @param[in] del_func Function to delete the node (can be NULL) + * @return true on success, false if node not found or error + * @note Time complexity: O(n) + */ +bool c_flist_remove(c_flist_node_t **list, c_flist_node_t *node, c_list_node_del del_func); + +/** + * @brief Remove all nodes matching a predicate from the forward list + * @param[in,out] list Pointer to the list head + * @param[in] pred_func Predicate function to test each node + * @param[in] del_func Function to delete removed nodes (can be NULL) + * @return Number of nodes removed + * @note Time complexity: O(n) + */ +size_t c_flist_remove_if(c_flist_node_t **list, + c_list_node_pred pred_func, + c_list_node_del del_func); + +/** + * @brief Remove and delete all nodes from the forward list + * @param[in,out] list Pointer to the list head + * @param[in] del_func Function to delete each node (can be NULL) + * @return true on success, false on error + * @note Time complexity: O(n) + */ +bool c_flist_clear(c_flist_node_t **list, c_list_node_del del_func); + +/** + * @brief Get the number of nodes in the forward list + * @param[in] list Pointer to the list head + * @return Number of nodes (0 if list is NULL or empty) + * @note Time complexity: O(n) + */ +size_t c_flist_size(c_flist_node_t *const *list); + +/** + * @brief Check if the forward list is empty + * @param[in] list Pointer to the list head + * @return true if empty or NULL, false otherwise + * @note Time complexity: O(1) + */ +bool c_flist_empty(c_flist_node_t *const *list); + +/** + * @brief Sort the forward list using a comparison function + * @param[in,out] list Pointer to the list head + * @param[in] cmp_func Comparison function (returns true if a < b) + * @return true on success, false on error + * @note Time complexity: O(n log n) - merge sort algorithm + */ +bool c_flist_sort(c_flist_node_t **list, c_list_node_cmp cmp_func); + +/** + * @brief Remove consecutive duplicate nodes from the forward list + * @param[in,out] list Pointer to the list head + * @param[in] pred_func Binary predicate to test equality (returns true if a == b) + * @param[in] del_func Function to delete removed nodes (can be NULL) + * @return Number of nodes removed + * @note Time complexity: O(n) + * @note List should be sorted first for best results + */ +size_t c_flist_unique(c_flist_node_t **list, + c_list_node_bin_pred pred_func, + c_list_node_del del_func); + +/** + * @brief Reverse the order of nodes in the forward list + * @param[in,out] list Pointer to the list head + * @return true on success, false on error + * @note Time complexity: O(n) + */ +bool c_flist_reverse(c_flist_node_t **list); + +// ============================================================================ +// Doubly-linked list functions +// ============================================================================ + +/** + * @brief Add a node at the beginning of the doubly-linked list + * @param[in,out] list Pointer to the list head + * @param[in] node Node to add (must have node->_list.next == NULL and node->prev == NULL) + * @return true on success, false on error + * @note Time complexity: O(1) + */ +bool c_dlist_push_front(c_dlist_node_t **list, c_dlist_node_t *node); + +/** + * @brief Remove and delete the first node from the doubly-linked list + * @param[in,out] list Pointer to the list head + * @param[in] del_func Function to delete the node (can be NULL) + * @return true on success, false if list is empty or NULL + * @note Time complexity: O(1) + */ +bool c_dlist_pop_front(c_dlist_node_t **list, c_list_node_del del_func); + +/** + * @brief Add a node at the end of the doubly-linked list + * @param[in,out] list Pointer to the list head + * @param[in] node Node to add (must have node->_list.next == NULL and node->prev == NULL) + * @return true on success, false on error + * @note Time complexity: O(n) + */ +bool c_dlist_push_back(c_dlist_node_t **list, c_dlist_node_t *node); + +/** + * @brief Remove and delete the last node from the doubly-linked list + * @param[in,out] list Pointer to the list head + * @param[in] del_func Function to delete the node (can be NULL) + * @return true on success, false if list is empty or NULL + * @note Time complexity: O(n) + */ +bool c_dlist_pop_back(c_dlist_node_t **list, c_list_node_del del_func); + +/** + * @brief Insert a node before a reference node in the doubly-linked list + * @param[in,out] list Pointer to the list head + * @param[in] ref Reference node (must be in list) + * @param[in] node Node to insert (must have node->_list.next == NULL and node->prev == NULL) + * @return true on success, false on error + * @note Time complexity: O(1) + * @note This function is only available for doubly-linked lists + */ +bool c_dlist_insert_before(c_dlist_node_t **list, c_dlist_node_t *ref, c_dlist_node_t *node); + +/** + * @brief Insert a node after a reference node in the doubly-linked list + * @param[in,out] list Pointer to the list head + * @param[in] ref Reference node (must be in list) + * @param[in] node Node to insert (must have node->_list.next == NULL and node->prev == NULL) + * @return true on success, false on error + * @note Time complexity: O(1) + */ +bool c_dlist_insert_after(c_dlist_node_t **list, c_dlist_node_t *ref, c_dlist_node_t *node); + +/** + * @brief Remove and delete a specific node from the doubly-linked list + * @param[in,out] list Pointer to the list head + * @param[in] node Node to remove (must be in list) + * @param[in] del_func Function to delete the node (can be NULL) + * @return true on success, false if node not found or error + * @note Time complexity: O(n) + */ +bool c_dlist_remove(c_dlist_node_t **list, c_dlist_node_t *node, c_list_node_del del_func); + +/** + * @brief Remove all nodes matching a predicate from the doubly-linked list + * @param[in,out] list Pointer to the list head + * @param[in] pred_func Predicate function to test each node + * @param[in] del_func Function to delete removed nodes (can be NULL) + * @return Number of nodes removed + * @note Time complexity: O(n) + */ +size_t c_dlist_remove_if(c_dlist_node_t **list, + c_list_node_pred pred_func, + c_list_node_del del_func); + +/** + * @brief Remove and delete all nodes from the doubly-linked list + * @param[in,out] list Pointer to the list head + * @param[in] del_func Function to delete each node (can be NULL) + * @return true on success, false on error + * @note Time complexity: O(n) + */ +bool c_dlist_clear(c_dlist_node_t **list, c_list_node_del del_func); + +/** + * @brief Get the number of nodes in the doubly-linked list + * @param[in] list Pointer to the list head + * @return Number of nodes (0 if list is NULL or empty) + * @note Time complexity: O(n) + */ +size_t c_dlist_size(c_dlist_node_t *const *list); + +/** + * @brief Check if the doubly-linked list is empty + * @param[in] list Pointer to the list head + * @return true if empty or NULL, false otherwise + * @note Time complexity: O(1) + */ +bool c_dlist_empty(c_dlist_node_t *const *list); + +/** + * @brief Sort the doubly-linked list using a comparison function + * @param[in,out] list Pointer to the list head + * @param[in] cmp_func Comparison function (returns true if a < b) + * @return true on success, false on error + * @note Time complexity: O(n log n) - merge sort algorithm + */ +bool c_dlist_sort(c_dlist_node_t **list, c_list_node_cmp cmp_func); + +/** + * @brief Remove consecutive duplicate nodes from the doubly-linked list + * @param[in,out] list Pointer to the list head + * @param[in] pred_func Binary predicate to test equality (returns true if a == b) + * @param[in] del_func Function to delete removed nodes (can be NULL) + * @return Number of nodes removed + * @note Time complexity: O(n) + * @note List should be sorted first for best results + */ +size_t c_dlist_unique(c_dlist_node_t **list, + c_list_node_bin_pred pred_func, + c_list_node_del del_func); + +/** + * @brief Reverse the order of nodes in the doubly-linked list + * @param[in,out] list Pointer to the list head + * @return true on success, false on error + * @note Time complexity: O(n) + */ +bool c_dlist_reverse(c_dlist_node_t **list); diff --git a/lib_c_list/doc/mainpage.dox b/lib_c_list/doc/mainpage.dox new file mode 100644 index 000000000..3f1821e50 --- /dev/null +++ b/lib_c_list/doc/mainpage.dox @@ -0,0 +1,719 @@ +/** @page c_list_mainpage Generic Linked List Library + +@section c_list_mainpage_intro Introduction + +This page describes the Generic Linked List library available for Applications. + +The `lib_c_list` provides two types of linked list implementations: +- **Forward lists** (`c_flist_*`): Singly-linked lists with minimal memory overhead +- **Doubly-linked lists** (`c_list_*`): Bidirectional lists with previous pointers + +Both implementations share a common architecture with generic node structures that can +be embedded in any data structure. This design is based on the app-ethereum implementation +and is optimized for embedded systems like Ledger devices. + +@section c_list_node_structure Node Structures + +@subsection c_flist_node Forward List Node (Singly-Linked) + +The forward list node contains only a next pointer: + +@code{.c} +typedef struct c_flist_node_s { + struct c_flist_node_s *next; +} c_flist_node_t; +@endcode + +**Usage:** Embed `c_flist_node_t` as the first member of your structure: + +@code{.c} +typedef struct my_data_s { + c_flist_node_t node; // Must be first member + int value; + char name[32]; +} my_data_t; + +c_flist_node_t *my_flist = NULL; // Empty forward list +@endcode + +**Memory overhead:** 4-8 bytes per node (one pointer) + +@subsection c_list_node Doubly-Linked List Node + +The doubly-linked node contains both next and previous pointers: + +@code{.c} +typedef struct c_dlist_node_s { + c_flist_node_t _list; // Contains next pointer + struct c_dlist_node_s *prev; // Previous pointer +} c_dlist_node_t; +@endcode + +**Usage:** Embed `c_dlist_node_t` as the first member of your structure: + +@code{.c} +typedef struct my_data_s { + c_dlist_node_t node; // Must be first member + int value; + char name[32]; +} my_data_t; + +c_dlist_node_t *my_list = NULL; // Empty doubly-linked list +@endcode + +**Memory overhead:** 8-16 bytes per node (two pointers) + +@subsection c_list_choosing Choosing Between Forward Lists and Doubly-Linked Lists + +| Feature | Forward List (`c_flist`) | Doubly-Linked (`c_list`) | +|--------------------------|--------------------------|--------------------------| +| Memory per node | 4-8 bytes | 8-16 bytes | +| Insert/remove at front | O(1) | O(1) | +| Insert/remove at back | O(n) | O(1) ⚡ | +| Insert after node | O(1) | O(1) | +| Insert before node | Not available | O(1) ⚡ | +| Backward traversal | No | Yes ⚡ | +| Reverse operation | O(n) | O(n) | + +**Best practice:** Use forward lists when memory is critical and you don't need backward +traversal or frequent tail operations. Use doubly-linked lists when you need bidirectional +access or O(1) tail operations. + +@section c_list_complexity Understanding Time Complexity + +The documentation uses **Big O notation** to describe algorithm performance: + +- **O(1) - Constant time**: The operation takes the same time regardless of list size + - Example: Adding to the front of the list always requires the same steps + - Performance: Excellent for embedded systems ✓ + +- **O(n) - Linear time**: Time increases proportionally with the number of elements (n) + - Example: Finding the last element requires traversing all n elements + - Performance: Acceptable for small to medium lists + +- **O(n²) - Quadratic time**: Time increases with the square of the number of elements + - Example: Bubble sort compares elements multiple times + - Performance: Avoid for large lists in embedded systems + +**Concrete examples:** +| List Size | O(1) | O(n) | O(n²) | +|-----------|-----------|----------------|--------------------| +| 10 items | 1 step | 10 steps | 100 steps | +| 100 items | 1 step | 100 steps | 10,000 steps | +| 1000 items| 1 step | 1,000 steps | 1,000,000 steps | + +**Best practice:** Always prefer O(1) operations when possible for optimal performance. + +@section c_list_operations Basic Operations + +All operations are available for both forward lists and doubly-linked lists with similar APIs: +- Forward list functions: `c_flist_*` +- Doubly-linked list functions: `c_list_*` + +All mutating functions return `bool` to indicate success or failure. + +@subsection c_list_insertion Insertion Operations + +**Push Front** - Add a node at the beginning (O(1) for both types): + +@code{.c} +// Forward list example +my_data_t *data = malloc(sizeof(my_data_t)); +data->node.next = NULL; // Must be NULL before insertion +data->value = 42; + +if (c_flist_push_front(&my_flist, &data->node)) { + // Successfully inserted +} + +// Doubly-linked list example +my_data_t *data2 = malloc(sizeof(my_data_t)); +data2->node._list.next = NULL; +data2->node.prev = NULL; // Both pointers must be NULL +data2->value = 43; + +if (c_dlist_push_front(&my_list, &data2->node)) { + // Successfully inserted +} +@endcode + +**Push Back** - Add a node at the end: +- Forward list: O(n) - must traverse entire list +- Doubly-linked: O(1) - direct access via prev pointer ⚡ + +@code{.c} +// Forward list: O(n) +if (c_flist_push_back(&my_flist, &data->node)) { + // Successfully inserted +} + +// Doubly-linked: O(1) - faster! +if (c_dlist_push_back(&my_list, &data->node)) { + // Successfully inserted +} +@endcode + +**Insert After** - Insert a node after a reference node (O(1) for both): + +@code{.c} +my_data_t *new_data = malloc(sizeof(my_data_t)); +new_data->node._list.next = NULL; +new_data->node.prev = NULL; + +if (c_dlist_insert_after(&my_list, &ref_node, &new_data->node)) { + // Successfully inserted after ref_node +} +@endcode + +**Insert Before** - Insert a node before a reference node: +- Forward list: Not available (requires backward traversal) +- Doubly-linked: O(1) - direct access via prev pointer ⚡ + +@code{.c} +// Only available for doubly-linked lists +my_data_t *new_data = malloc(sizeof(my_data_t)); +new_data->node._list.next = NULL; +new_data->node.prev = NULL; + +if (c_dlist_insert_before(&my_list, &ref_node, &new_data->node)) { + // Successfully inserted before ref_node +} +@endcode + +@subsection c_list_removal Removal Operations + +All removal functions return `bool` to indicate success or failure (except `remove_if`). +They accept an optional deletion callback to clean up node data. + +**Pop Front** - Remove the first node (O(1) for both): + +@code{.c} +void my_delete_func(c_flist_node_t *node) { + my_data_t *data = (my_data_t *)node; + // Clean up data if needed + free(data); +} + +// Forward list +if (c_flist_pop_front(&my_flist, my_delete_func)) { + // First node was removed and freed +} + +// Doubly-linked list +if (c_dlist_pop_front(&my_list, (c_list_node_del)my_delete_func)) { + // First node was removed and freed +} +@endcode + +**Pop Back** - Remove the last node: +- Forward list: O(n) - must find second-to-last node +- Doubly-linked: O(1) - direct access via prev pointer ⚡ + +@code{.c} +// Forward list: O(n) +if (c_flist_pop_back(&my_flist, my_delete_func)) { + // Last node was removed +} + +// Doubly-linked: O(1) - faster! +if (c_dlist_pop_back(&my_list, (c_list_node_del)my_delete_func)) { + // Last node was removed +} +@endcode + +**Remove** - Remove a specific node (O(n) for forward, O(1) for doubly-linked): + +@code{.c} +// Forward list: O(n) - must find previous node +if (c_flist_remove(&my_flist, &node_to_remove, my_delete_func)) { + // Node was found and removed +} + +// Doubly-linked: O(1) - direct access via prev pointer ⚡ +if (c_dlist_remove(&my_list, &node_to_remove, (c_list_node_del)my_delete_func)) { + // Node was removed instantly +} +@endcode + +**Remove If** - Remove all nodes matching a predicate (O(n) for both): + +@code{.c} +bool is_negative(const c_flist_node_t *node) { + const my_data_t *data = (const my_data_t *)node; + return data->value < 0; +} + +// Returns the number of removed nodes +size_t removed = c_flist_remove_if(&my_flist, is_negative, my_delete_func); +printf("Removed %zu negative values\n", removed); +@endcode + +**Clear** - Remove all nodes (O(n) for both): + +@code{.c} +if (c_flist_clear(&my_flist, my_delete_func)) { + // All nodes were removed and list is now empty +} +@endcode + +@subsection c_list_utilities Utility Operations + +**Get Size** - Count the number of nodes (O(n) for both): + +@code{.c} +size_t count = c_flist_size(&my_flist); +size_t count2 = c_dlist_size(&my_list); +@endcode + +**Check Empty** - Test if list is empty (O(1) for both): + +@code{.c} +if (c_flist_empty(&my_flist)) { + printf("Forward list is empty\n"); +} + +if (c_dlist_empty(&my_list)) { + printf("Doubly-linked list is empty\n"); +} +@endcode + +**Sort** - Sort the list using a comparison function (O(n²) for both): + +@code{.c} +bool my_compare_func(const c_flist_node_t *a, const c_flist_node_t *b) { + my_data_t *data_a = (my_data_t *)a; + my_data_t *data_b = (my_data_t *)b; + return data_a->value <= data_b->value; // true if in correct order +} + +// Forward list +if (c_flist_sort(&my_flist, my_compare_func)) { + // List is now sorted +} + +// Doubly-linked list +if (c_dlist_sort(&my_list, my_compare_func)) { + // List is now sorted +} +@endcode + +**Unique** - Remove consecutive duplicate nodes (O(n) for both): + +@code{.c} +bool are_equal(const c_flist_node_t *a, const c_flist_node_t *b) { + const my_data_t *data_a = (const my_data_t *)a; + const my_data_t *data_b = (const my_data_t *)b; + return data_a->value == data_b->value; +} + +// Returns the number of removed duplicates +size_t removed = c_flist_unique(&my_flist, are_equal, my_delete_func); +printf("Removed %zu duplicates\n", removed); +@endcode + +**Reverse** - Reverse the order of nodes (O(n) for both): + +@code{.c} +if (c_flist_reverse(&my_flist)) { + // List is now reversed +} + +if (c_dlist_reverse(&my_list)) { + // List is now reversed +} +@endcode + +@section c_list_traversal List Traversal + +@subsection c_flist_traversal Forward List Traversal (Forward Only) + +@code{.c} +// Forward iteration only +for (c_flist_node_t *node = my_flist; node != NULL; node = node->next) { + my_data_t *data = (my_data_t *)node; + printf("Value: %d\n", data->value); +} +@endcode + +@subsection c_dlist_traversal Doubly-Linked List Traversal (Both Directions) + +@code{.c} +// Forward iteration +for (c_dlist_node_t *node = my_list; node != NULL; node = (c_dlist_node_t *)node->_list.next) { + my_data_t *data = (my_data_t *)node; + printf("Value: %d\n", data->value); +} + +// Backward iteration - only possible with doubly-linked lists! +c_dlist_node_t *tail = my_list; +if (tail != NULL) { + // Find the tail + while (tail->_list.next != NULL) { + tail = (c_dlist_node_t *)tail->_list.next; + } + // Traverse backward + for (c_dlist_node_t *node = tail; node != NULL; node = node->prev) { + my_data_t *data = (my_data_t *)node; + printf("Value: %d\n", data->value); + } +} +@endcode + +@section c_list_safety Safety and Error Handling + +The library implements several safety checks: + +1. **NULL pointer checks**: All functions validate their parameters +2. **Node state validation**: + - Forward list: Insertion functions verify that `node->next == NULL` + - Doubly-linked: Insertion functions verify that both `node->_list.next` and `node->prev` are NULL + - This prevents: + - Creating cycles in the list + - Accidentally linking nodes from different lists + - Losing track of existing node chains + +3. **Return value checking**: All mutating operations return `bool`: + - `true`: Operation succeeded + - `false`: Invalid parameters or operation failed (e.g., node not found) + +4. **Counter operations**: `remove_if`, `unique`, and `size` return `size_t` with count information + +**Common error scenarios:** + +@code{.c} +// Forward list error +my_data_t *data = malloc(sizeof(my_data_t)); +data->node.next = some_other_node; // Already linked! + +if (!c_flist_push_front(&my_flist, &data->node)) { + // ERROR: node->next was not NULL + // This prevents accidentally breaking another list +} + +// Doubly-linked list error +my_data_t *data2 = malloc(sizeof(my_data_t)); +data2->node.prev = some_node; // Already linked! + +if (!c_dlist_push_front(&my_list, &data2->node)) { + // ERROR: node->prev was not NULL +} +@endcode + +@section c_list_best_practices Best Practices + +1. **Always initialize node pointers to NULL** before insertion: + @code{.c} + // Forward list + my_data_t *data = malloc(sizeof(my_data_t)); + data->node.next = NULL; // Critical! + + // Doubly-linked list + my_data_t *data2 = malloc(sizeof(my_data_t)); + data2->node._list.next = NULL; // Critical! + data2->node.prev = NULL; // Critical! + @endcode + +2. **Check return values** to detect errors: + @code{.c} + if (!c_flist_push_front(&my_flist, &data->node)) { + // Handle error + } + @endcode + +3. **Use the deletion callback** to prevent memory leaks: + @code{.c} + c_flist_clear(&my_flist, my_delete_func); + @endcode + +4. **Choose the right list type:** + - Use `c_flist` (forward list) for minimal memory usage + - Use `c_list` (doubly-linked) for bidirectional access or frequent tail operations + +5. **Prefer front operations** when order doesn't matter: + - `c_flist_push_front` is O(1) + - `c_dlist_push_front` is O(1) + - Both are faster than push_back for forward lists + +6. **Don't reuse nodes** across multiple lists without proper unlinking + +@section c_list_examples Complete Examples + +@subsection c_list_example_basic Forward List Basic Usage + +@code{.c} +#include "c_list.h" +#include +#include + +typedef struct person_s { + c_flist_node_t node; + char name[32]; + int age; +} person_t; + +void delete_person(c_flist_node_t *node) { + person_t *person = (person_t *)node; + printf("Deleting: %s\n", person->name); + free(person); +} + +int main(void) { + c_flist_node_t *people = NULL; + + // Add some people + person_t *alice = malloc(sizeof(person_t)); + alice->node.next = NULL; + strcpy(alice->name, "Alice"); + alice->age = 30; + c_flist_push_front(&people, &alice->node); + + person_t *bob = malloc(sizeof(person_t)); + bob->node.next = NULL; + strcpy(bob->name, "Bob"); + bob->age = 25; + c_flist_push_back(&people, &bob->node); + + // Print all people + for (c_flist_node_t *node = people; node != NULL; node = node->next) { + person_t *p = (person_t *)node; + printf("%s is %d years old\n", p->name, p->age); + } + + // Clean up + c_flist_clear(&people, delete_person); + + return 0; +} +@endcode + +@subsection c_list_example_doubly Doubly-Linked List with Backward Traversal + +@code{.c} +#include "c_list.h" +#include +#include + +typedef struct person_s { + c_dlist_node_t node; + char name[32]; + int age; +} person_t; + +void delete_person_dl(c_flist_node_t *node) { + person_t *person = (person_t *)node; + printf("Deleting: %s\n", person->name); + free(person); +} + +int main(void) { + c_dlist_node_t *people = NULL; + + // Add people (O(1) for both front and back!) + person_t *alice = malloc(sizeof(person_t)); + alice->node._list.next = NULL; + alice->node.prev = NULL; + strcpy(alice->name, "Alice"); + alice->age = 30; + c_dlist_push_back(&people, &alice->node); // O(1) - fast! + + person_t *bob = malloc(sizeof(person_t)); + bob->node._list.next = NULL; + bob->node.prev = NULL; + strcpy(bob->name, "Bob"); + bob->age = 25; + c_dlist_push_back(&people, &bob->node); // O(1) - fast! + + // Forward traversal + printf("Forward:\n"); + for (c_dlist_node_t *node = people; node != NULL; node = (c_dlist_node_t *)node->_list.next) { + person_t *p = (person_t *)node; + printf(" %s is %d years old\n", p->name, p->age); + } + + // Backward traversal - only possible with doubly-linked! + printf("Backward:\n"); + c_dlist_node_t *tail = people; + while (tail && tail->_list.next) { + tail = (c_dlist_node_t *)tail->_list.next; + } + for (c_dlist_node_t *node = tail; node != NULL; node = node->prev) { + person_t *p = (person_t *)node; + printf(" %s is %d years old\n", p->name, p->age); + } + + // Clean up + c_dlist_clear(&people, delete_person_dl); + + return 0; +} +@endcode + +@subsection c_list_example_sort Sorting Example + +@code{.c} +bool compare_age(const c_flist_node_t *a, const c_flist_node_t *b) { + const person_t *pa = (const person_t *)a; + const person_t *pb = (const person_t *)b; + return pa->age <= pb->age; // Sort by age ascending +} + +// Sort forward list +if (c_flist_sort(&people, compare_age)) { + printf("Forward list sorted by age\n"); +} + +// Sort doubly-linked list +if (c_dlist_sort(&people_dl, compare_age)) { + printf("Doubly-linked list sorted by age\n"); +} +@endcode + +@subsection c_list_example_advanced Advanced: Remove Duplicates and Reverse + +@code{.c} +// Remove consecutive duplicates after sorting +bool are_equal(const c_flist_node_t *a, const c_flist_node_t *b) { + const person_t *pa = (const person_t *)a; + const person_t *pb = (const person_t *)b; + return pa->age == pb->age; +} + +c_flist_sort(&people, compare_age); +size_t removed = c_flist_unique(&people, are_equal, delete_person); +printf("Removed %zu duplicates\n", removed); + +// Reverse the list +if (c_flist_reverse(&people)) { + printf("List reversed (now descending order)\n"); +} + +// Remove all minors +bool is_minor(const c_flist_node_t *node) { + const person_t *p = (const person_t *)node; + return p->age < 18; +} + +size_t removed_minors = c_flist_remove_if(&people, is_minor, delete_person); +printf("Removed %zu minors\n", removed_minors); +@endcode + +@section c_list_performance Performance Characteristics + +@subsection c_flist_performance Forward List Performance + +| Operation | Time Complexity | Notes | +|---------------------------|-----------------|----------------------------------| +| `c_flist_push_front` | O(1) | Constant time | +| `c_flist_pop_front` | O(1) | Constant time | +| `c_flist_push_back` | O(n) | Must traverse entire list | +| `c_flist_pop_back` | O(n) | Must find second-to-last node | +| `c_flist_insert_after` | O(1) | Constant time if reference known | +| `c_flist_remove` | O(n) | Must find previous node | +| `c_flist_remove_if` | O(n) | Traverses list once | +| `c_flist_clear` | O(n) | Must visit all nodes | +| `c_flist_size` | O(n) | Must count all nodes | +| `c_flist_empty` | O(1) | Just checks if head is NULL | +| `c_flist_sort` | O(n²) | Bubble sort implementation | +| `c_flist_unique` | O(n) | Single pass after sorting | +| `c_flist_reverse` | O(n) | Single pass, reverses pointers | + +**Memory overhead:** 4-8 bytes per node (one pointer) + +@subsection c_dlist_performance Doubly-Linked List Performance + +| Operation | Time Complexity | Notes | +|---------------------------|-----------------|-----------------------------------| +| `c_dlist_push_front` | O(1) ⚡ | Constant time | +| `c_dlist_pop_front` | O(1) ⚡ | Constant time | +| `c_dlist_push_back` | O(1) ⚡ | **Direct access via prev!** | +| `c_dlist_pop_back` | O(1) ⚡ | **Direct access via prev!** | +| `c_dlist_insert_before` | O(1) ⚡ | **Direct access via prev!** | +| `c_dlist_insert_after` | O(1) ⚡ | Constant time | +| `c_dlist_remove` | O(1) ⚡ | **Direct access via prev!** | +| `c_dlist_remove_if` | O(n) | Traverses list once | +| `c_dlist_clear` | O(n) | Must visit all nodes | +| `c_dlist_size` | O(n) | Must count all nodes | +| `c_dlist_empty` | O(1) ⚡ | Just checks if head is NULL | +| `c_dlist_sort` | O(n²) | Bubble sort implementation | +| `c_dlist_unique` | O(n) | Single pass after sorting | +| `c_dlist_reverse` | O(n) | Single pass, swaps pointers | + +**Memory overhead:** 8-16 bytes per node (two pointers) + +@subsection c_list_performance_comparison Performance Comparison + +**When to use forward lists (`c_flist`):** +- Memory is critical (saves 4-8 bytes per node) +- Only need forward traversal +- Rarely need tail operations +- Example: Stack-like data structures, one-directional iteration + +**When to use doubly-linked lists (`c_list`):** +- Need frequent tail operations (O(1) vs O(n) ⚡) +- Need to insert/remove at known positions efficiently +- Need bidirectional traversal +- Memory overhead is acceptable +- Example: LRU caches, undo/redo systems, navigation histories + +@section c_list_limitations Limitations + +@subsection c_flist_limitations Forward List Limitations + +1. **No backward traversal**: Cannot iterate backward through the list. + +2. **No tail pointer**: `push_back` and `pop_back` are O(n). For frequent tail operations, + use doubly-linked lists instead. + +3. **No insert_before**: Cannot insert before a node without traversing from head. + Use doubly-linked lists for O(1) insert_before. + +4. **Bubble sort**: The sort implementation is O(n²). For large lists, consider using + an external sorting algorithm. + +@subsection c_dlist_limitations Doubly-Linked List Limitations + +1. **Memory overhead**: Uses twice the memory per node compared to forward lists + (two pointers instead of one). + +2. **Bubble sort**: The sort implementation is O(n²). For large lists, consider using + an external sorting algorithm. + +3. **No tail caching**: While tail operations are O(1), finding the tail initially + requires traversal. For repeated tail access, cache the tail pointer in your application. + +@subsection c_list_common_limitations Common Limitations (Both Types) + +1. **No built-in search**: Searching requires manual traversal. Implement application-specific + search functions. + +2. **No thread safety**: The library is not thread-safe. Implement external synchronization + if needed. + +3. **No automatic memory management**: Applications must manage node allocation and provide + deletion callbacks. + +@section c_list_alternatives When to Use Alternatives + +Consider alternatives if you need: + +**Instead of forward lists (`c_flist`):** +- **Frequent tail operations**: Use doubly-linked list (`c_list`) for O(1) tail access ⚡ +- **Bidirectional traversal**: Use doubly-linked list (`c_list`) +- **Insert before operation**: Use doubly-linked list (`c_list`) for O(1) insert_before ⚡ + +**Instead of doubly-linked lists (`c_list`):** +- **Minimal memory usage**: Use forward list (`c_flist`) to save 4-8 bytes per node +- **Simple one-directional access**: Use forward list (`c_flist`) for simplicity + +**Instead of any linked list:** +- **Fast random access**: Use array-based structures (indexing is O(1) vs O(n)) +- **Efficient sorting of large datasets**: Pre-sort data before insertion or use array + qsort +- **Memory profiling**: Use `lib_alloc` for allocation tracking and debugging +- **Fixed-size collections**: Use static arrays for better cache locality + +**Hybrid approaches:** +- Maintain a separate tail pointer with forward lists for O(1) tail access +- Use ring buffers for fixed-size FIFO/LIFO operations +- Combine with hash tables for O(1) lookup + ordered iteration + +*/ diff --git a/unit-tests/lib_c_list/CMakeLists.txt b/unit-tests/lib_c_list/CMakeLists.txt new file mode 100644 index 000000000..e9e0efc68 --- /dev/null +++ b/unit-tests/lib_c_list/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 3.10) + +if(${CMAKE_VERSION} VERSION_LESS 3.10) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +endif() + +# project information +project(unit_tests + VERSION 0.1 + DESCRIPTION "Unit tests for Generic Linked List Library" + LANGUAGES C) + + +# guard against bad build-type strings +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Debug") +endif() + +include(CTest) +ENABLE_TESTING() + +# specify C standard +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED True) + +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall ${DEFINES} -g -O0 --coverage") + +set(GCC_COVERAGE_LINK_FLAGS "--coverage -lgcov") +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}") + +# guard against in-source builds +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) + message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt. ") +endif() + +add_compile_definitions(TEST) +set(SDK_SRC ../..) + +include_directories(.) +include_directories(${SDK_SRC}/target/stax/include) +include_directories(${SDK_SRC}/include) +include_directories(${SDK_SRC}/lib_c_list) + +add_executable(test_c_list + test_c_list.c + ${SDK_SRC}/lib_c_list/c_list.c +) + +target_link_libraries(test_c_list PUBLIC cmocka gcov) + +add_test(test_c_list test_c_list) diff --git a/unit-tests/lib_c_list/README.md b/unit-tests/lib_c_list/README.md new file mode 100644 index 000000000..18be787bb --- /dev/null +++ b/unit-tests/lib_c_list/README.md @@ -0,0 +1,174 @@ +# Generic Linked List Unit Tests + +## Prerequisites + +- CMake >= 3.10 +- CMocka >= 1.1.5 +- lcov >= 1.14 (for code coverage) + +## Overview + +This test suite covers all functions provided by the Generic Linked List library (`c_list.c`), which supports both: + +- **Forward lists** (`c_flist_*`) - Singly-linked lists (4-8 bytes/node) +- **Doubly-linked lists** (`c_dlist_*`) - Doubly-linked lists (8-16 bytes/node) + +### Test Coverage + +The test suite includes **24 comprehensive tests** covering: + +**Forward Lists (c_flist_*) - 11 tests:** + +- `test_c_flist_push_front` - Add nodes at the beginning +- `test_c_flist_push_back` - Add nodes at the end +- `test_c_flist_pop_front` - Remove nodes from the beginning +- `test_c_flist_pop_back` - Remove nodes from the end +- `test_c_flist_insert_after` - Insert after a reference node +- `test_c_flist_remove` - Remove specific nodes +- `test_c_flist_remove_if` - Remove nodes matching predicate +- `test_c_flist_clear` - Clear entire list +- `test_c_flist_sort` - Sort list with comparison function +- `test_c_flist_unique` - Remove duplicate consecutive nodes +- `test_c_flist_reverse` - Reverse list order + +**Doubly-Linked Lists (c_dlist_*) - 13 tests:** + +- `test_c_dlist_push_front` - Add nodes at the beginning +- `test_c_dlist_push_back` - Add nodes at the end +- `test_c_dlist_pop_front` - Remove nodes from the beginning +- `test_c_dlist_pop_back` - Remove nodes from the end +- `test_c_dlist_insert_after` - Insert after a reference node +- `test_c_dlist_insert_before` - Insert before a reference node +- `test_c_dlist_remove` - Remove specific nodes +- `test_c_dlist_remove_if` - Remove nodes matching predicate +- `test_c_dlist_clear` - Clear entire list +- `test_c_dlist_sort` - Sort list with comparison function +- `test_c_dlist_unique` - Remove duplicate consecutive nodes +- `test_c_dlist_reverse` - Reverse list order +- `test_c_dlist_backward_traversal` - Traverse list backward using prev pointers + +## Building and Running Tests + +### Quick Start + +```bash +cd unit-tests/lib_c_list +mkdir build && cd build +cmake -DCMAKE_C_COMPILER=/usr/bin/gcc .. +make +``` + +### Run Tests + +```bash +# Run all tests +make test + +# Or run directly with verbose output +./test_c_list +``` + +### Generate Code Coverage + +```bash +# Generate coverage data +lcov --capture --directory . --output-file coverage.info + +# Filter out system headers +lcov --remove coverage.info '/usr/*' --output-file coverage.info + +# Generate HTML report +genhtml coverage.info --output-directory coverage_html + +# View report +xdg-open coverage_html/index.html +``` + +## Test Details + +### Safety Features Tested + +1. **NULL pointer validation** - All functions check for NULL parameters +2. **Node state validation** - Insertion functions verify `node->next == NULL` (flist) or `node->_list.next == NULL && node->prev == NULL` (dlist) +3. **Return value checking** - All mutating operations return bool for success/failure +4. **Empty list operations** - Safe handling of operations on empty lists +5. **Predicate filtering** - remove_if tests validate callback-based filtering +6. **Duplicate removal** - unique tests validate consecutive duplicate handling +7. **Bidirectional traversal** - dlist tests validate both forward and backward traversal + +### Example Test Output + +```bash +[==========] Running 24 test(s). +[ RUN ] test_c_flist_push_front +[ OK ] test_c_flist_push_front +[ RUN ] test_c_flist_push_back +[ OK ] test_c_flist_push_back +[ RUN ] test_c_flist_pop_front +[ OK ] test_c_flist_pop_front +... +[ RUN ] test_c_dlist_backward_traversal +[ OK ] test_c_dlist_backward_traversal +[==========] 24 test(s) run. +[ PASSED ] 24 test(s). +``` + +## Memory Safety + +All tests use cmocka's memory leak detection to ensure: + +- No memory leaks in list operations +- Proper cleanup with deletion callbacks +- Safe handling of node allocation/deallocation + +## Continuous Integration + +These tests should be run: + +- Before committing changes to lib_c_list +- As part of the CI/CD pipeline +- When modifying list-related code in applications + +## Adding New Tests + +To add new tests: + +1. Define test node structures for the appropriate list type: + +```c +// For forward lists +typedef struct test_flist_node_s { + c_flist_node_t _list; + int value; +} test_flist_node_t; + +// For doubly-linked lists +typedef struct test_dlist_node_s { + c_dlist_node_t _list; + int value; +} test_dlist_node_t; +``` + +2. Add test function following the pattern: + +```c +static void test_new_feature(void **state) +{ + (void) state; + // Test implementation +} +``` + +3. Register in main(): + +```c +cmocka_unit_test(test_new_feature), +``` + +4. Rebuild and run tests + +## Known Limitations + +- Tests use malloc/free for simplicity +- Real applications may use different memory allocators +- Performance tests are not included (focus on correctness) diff --git a/unit-tests/lib_c_list/test_c_list.c b/unit-tests/lib_c_list/test_c_list.c new file mode 100644 index 000000000..2721170e5 --- /dev/null +++ b/unit-tests/lib_c_list/test_c_list.c @@ -0,0 +1,799 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "c_list.h" + +// ============================================================================ +// Forward list test structures and helpers +// ============================================================================ + +typedef struct test_flist_node_s { + c_flist_node_t node; + int value; +} test_flist_node_t; + +static test_flist_node_t *create_flist_node(int value) +{ + test_flist_node_t *node = malloc(sizeof(test_flist_node_t)); + assert_non_null(node); + node->node.next = NULL; + node->value = value; + return node; +} + +static void delete_flist_node(c_flist_node_t *node) +{ + if (node != NULL) { + free(node); + } +} + +// ============================================================================ +// Doubly-linked list test structures and helpers +// ============================================================================ + +typedef struct test_dlist_node_s { + c_dlist_node_t node; + int value; +} test_dlist_node_t; + +static test_dlist_node_t *create_dlist_node(int value) +{ + test_dlist_node_t *node = malloc(sizeof(test_dlist_node_t)); + assert_non_null(node); + node->node._list.next = NULL; + node->node.prev = NULL; + node->value = value; + return node; +} + +static void delete_dlist_node(c_flist_node_t *node) +{ + if (node != NULL) { + free(node); + } +} + +// ============================================================================ +// Comparison and predicate functions +// ============================================================================ + +static bool compare_ascending_flist(const c_flist_node_t *a, const c_flist_node_t *b) +{ + const test_flist_node_t *node_a = (const test_flist_node_t *) a; + const test_flist_node_t *node_b = (const test_flist_node_t *) b; + return node_a->value <= node_b->value; +} + +static bool are_equal_flist(const c_flist_node_t *a, const c_flist_node_t *b) +{ + const test_flist_node_t *node_a = (const test_flist_node_t *) a; + const test_flist_node_t *node_b = (const test_flist_node_t *) b; + return node_a->value == node_b->value; +} + +static bool is_negative_flist(const c_flist_node_t *node) +{ + const test_flist_node_t *test_node = (const test_flist_node_t *) node; + return test_node->value < 0; +} + +// Doubly-linked list comparison and predicate functions +static bool compare_ascending_dlist(const c_flist_node_t *a, const c_flist_node_t *b) +{ + const test_dlist_node_t *node_a = (const test_dlist_node_t *) a; + const test_dlist_node_t *node_b = (const test_dlist_node_t *) b; + return node_a->value <= node_b->value; +} + +static bool are_equal_dlist(const c_flist_node_t *a, const c_flist_node_t *b) +{ + const test_dlist_node_t *node_a = (const test_dlist_node_t *) a; + const test_dlist_node_t *node_b = (const test_dlist_node_t *) b; + return node_a->value == node_b->value; +} + +static bool is_even_dlist(const c_flist_node_t *node) +{ + const test_dlist_node_t *test_node = (const test_dlist_node_t *) node; + return (test_node->value % 2) == 0; +} + +// ============================================================================ +// Forward list tests +// ============================================================================ + +// Test: flist push_front +static void test_flist_push_front(void **state) +{ + (void) state; + c_flist_node_t *list = NULL; + test_flist_node_t *node1, *node2, *node3; + + node1 = create_flist_node(1); + assert_true(c_flist_push_front(&list, &node1->node)); + assert_ptr_equal(list, &node1->node); + assert_int_equal(c_flist_size(&list), 1); + + node2 = create_flist_node(2); + assert_true(c_flist_push_front(&list, &node2->node)); + assert_ptr_equal(list, &node2->node); + assert_int_equal(c_flist_size(&list), 2); + + node3 = create_flist_node(3); + assert_true(c_flist_push_front(&list, &node3->node)); + assert_ptr_equal(list, &node3->node); + assert_int_equal(c_flist_size(&list), 3); + + // Verify order: 3 -> 2 -> 1 + test_flist_node_t *current = (test_flist_node_t *) list; + assert_int_equal(current->value, 3); + current = (test_flist_node_t *) current->node.next; + assert_int_equal(current->value, 2); + current = (test_flist_node_t *) current->node.next; + assert_int_equal(current->value, 1); + + c_flist_clear(&list, delete_flist_node); +} + +// Test: flist push_back +static void test_flist_push_back(void **state) +{ + (void) state; + c_flist_node_t *list = NULL; + test_flist_node_t *node1, *node2, *node3; + + node1 = create_flist_node(1); + assert_true(c_flist_push_back(&list, &node1->node)); + assert_int_equal(c_flist_size(&list), 1); + + node2 = create_flist_node(2); + assert_true(c_flist_push_back(&list, &node2->node)); + assert_int_equal(c_flist_size(&list), 2); + + node3 = create_flist_node(3); + assert_true(c_flist_push_back(&list, &node3->node)); + assert_int_equal(c_flist_size(&list), 3); + + // Verify order: 1 -> 2 -> 3 + test_flist_node_t *current = (test_flist_node_t *) list; + assert_int_equal(current->value, 1); + current = (test_flist_node_t *) current->node.next; + assert_int_equal(current->value, 2); + current = (test_flist_node_t *) current->node.next; + assert_int_equal(current->value, 3); + + c_flist_clear(&list, delete_flist_node); +} + +// Test: flist pop_front +static void test_flist_pop_front(void **state) +{ + (void) state; + c_flist_node_t *list = NULL; + + test_flist_node_t *node1 = create_flist_node(1); + test_flist_node_t *node2 = create_flist_node(2); + test_flist_node_t *node3 = create_flist_node(3); + c_flist_push_back(&list, &node1->node); + c_flist_push_back(&list, &node2->node); + c_flist_push_back(&list, &node3->node); + + assert_true(c_flist_pop_front(&list, delete_flist_node)); + assert_int_equal(c_flist_size(&list), 2); + assert_int_equal(((test_flist_node_t *) list)->value, 2); + + assert_true(c_flist_pop_front(&list, delete_flist_node)); + assert_int_equal(c_flist_size(&list), 1); + assert_int_equal(((test_flist_node_t *) list)->value, 3); + + assert_true(c_flist_pop_front(&list, delete_flist_node)); + assert_int_equal(c_flist_size(&list), 0); + assert_null(list); + + assert_false(c_flist_pop_front(&list, delete_flist_node)); +} + +// Test: flist pop_back +static void test_flist_pop_back(void **state) +{ + (void) state; + c_flist_node_t *list = NULL; + + test_flist_node_t *node1 = create_flist_node(1); + test_flist_node_t *node2 = create_flist_node(2); + test_flist_node_t *node3 = create_flist_node(3); + c_flist_push_back(&list, &node1->node); + c_flist_push_back(&list, &node2->node); + c_flist_push_back(&list, &node3->node); + + assert_true(c_flist_pop_back(&list, delete_flist_node)); + assert_int_equal(c_flist_size(&list), 2); + + assert_true(c_flist_pop_back(&list, delete_flist_node)); + assert_int_equal(c_flist_size(&list), 1); + assert_int_equal(((test_flist_node_t *) list)->value, 1); + + assert_true(c_flist_pop_back(&list, delete_flist_node)); + assert_int_equal(c_flist_size(&list), 0); + assert_null(list); + + assert_false(c_flist_pop_back(&list, delete_flist_node)); +} + +// Test: flist insert_after +static void test_flist_insert_after(void **state) +{ + (void) state; + c_flist_node_t *list = NULL; + + test_flist_node_t *node1 = create_flist_node(1); + test_flist_node_t *node3 = create_flist_node(3); + c_flist_push_back(&list, &node1->node); + c_flist_push_back(&list, &node3->node); + + test_flist_node_t *node2 = create_flist_node(2); + assert_true(c_flist_insert_after(&list, &node1->node, &node2->node)); + assert_int_equal(c_flist_size(&list), 3); + + // Verify order: 1 -> 2 -> 3 + test_flist_node_t *current = (test_flist_node_t *) list; + assert_int_equal(current->value, 1); + current = (test_flist_node_t *) current->node.next; + assert_int_equal(current->value, 2); + current = (test_flist_node_t *) current->node.next; + assert_int_equal(current->value, 3); + + c_flist_clear(&list, delete_flist_node); +} + +// Test: flist remove +static void test_flist_remove(void **state) +{ + (void) state; + c_flist_node_t *list = NULL; + + test_flist_node_t *node1 = create_flist_node(1); + test_flist_node_t *node2 = create_flist_node(2); + test_flist_node_t *node3 = create_flist_node(3); + c_flist_push_back(&list, &node1->node); + c_flist_push_back(&list, &node2->node); + c_flist_push_back(&list, &node3->node); + + assert_true(c_flist_remove(&list, &node2->node, delete_flist_node)); + assert_int_equal(c_flist_size(&list), 2); + + // Verify order: 1 -> 3 + test_flist_node_t *current = (test_flist_node_t *) list; + assert_int_equal(current->value, 1); + current = (test_flist_node_t *) current->node.next; + assert_int_equal(current->value, 3); + + c_flist_clear(&list, delete_flist_node); +} + +// Test: flist remove_if +static void test_flist_remove_if(void **state) +{ + (void) state; + c_flist_node_t *list = NULL; + + // Create list: -2, -1, 0, 1, 2, 3 + int values[] = {-2, -1, 0, 1, 2, 3}; + for (int i = 0; i < 6; i++) { + test_flist_node_t *node = create_flist_node(values[i]); + c_flist_push_back(&list, &node->node); + } + + // Remove all negative values + size_t removed = c_flist_remove_if(&list, is_negative_flist, delete_flist_node); + assert_int_equal(removed, 2); + assert_int_equal(c_flist_size(&list), 4); + + // Verify remaining: 0, 1, 2, 3 + test_flist_node_t *current = (test_flist_node_t *) list; + for (int i = 0; i < 4; i++) { + assert_non_null(current); + assert_int_equal(current->value, i); + current = (test_flist_node_t *) current->node.next; + } + + c_flist_clear(&list, delete_flist_node); +} + +// Test: flist unique +static void test_flist_unique(void **state) +{ + (void) state; + c_flist_node_t *list = NULL; + + // Create list with duplicates: 1, 1, 2, 2, 2, 3, 3 + int values[] = {1, 1, 2, 2, 2, 3, 3}; + for (int i = 0; i < 7; i++) { + test_flist_node_t *node = create_flist_node(values[i]); + c_flist_push_back(&list, &node->node); + } + + size_t removed = c_flist_unique(&list, are_equal_flist, delete_flist_node); + assert_int_equal(removed, 4); // Removed 4 duplicates + assert_int_equal(c_flist_size(&list), 3); + + // Verify remaining: 1, 2, 3 + test_flist_node_t *current = (test_flist_node_t *) list; + for (int i = 1; i <= 3; i++) { + assert_non_null(current); + assert_int_equal(current->value, i); + current = (test_flist_node_t *) current->node.next; + } + + c_flist_clear(&list, delete_flist_node); +} + +// Test: flist reverse +static void test_flist_reverse(void **state) +{ + (void) state; + c_flist_node_t *list = NULL; + + // Create list: 1, 2, 3, 4, 5 + for (int i = 1; i <= 5; i++) { + test_flist_node_t *node = create_flist_node(i); + c_flist_push_back(&list, &node->node); + } + + assert_true(c_flist_reverse(&list)); + + // Verify reversed: 5, 4, 3, 2, 1 + test_flist_node_t *current = (test_flist_node_t *) list; + for (int i = 5; i >= 1; i--) { + assert_non_null(current); + assert_int_equal(current->value, i); + current = (test_flist_node_t *) current->node.next; + } + + c_flist_clear(&list, delete_flist_node); +} + +// Test: flist empty +static void test_flist_empty(void **state) +{ + (void) state; + c_flist_node_t *list = NULL; + + assert_true(c_flist_empty(&list)); + + test_flist_node_t *node = create_flist_node(1); + c_flist_push_front(&list, &node->node); + assert_false(c_flist_empty(&list)); + + c_flist_clear(&list, delete_flist_node); + assert_true(c_flist_empty(&list)); +} + +// Test: flist sort +static void test_flist_sort(void **state) +{ + (void) state; + c_flist_node_t *list = NULL; + + // Create unsorted list: 3, 1, 4, 2 + int values[] = {3, 1, 4, 2}; + for (int i = 0; i < 4; i++) { + test_flist_node_t *node = create_flist_node(values[i]); + c_flist_push_back(&list, &node->node); + } + + assert_true(c_flist_sort(&list, compare_ascending_flist)); + + // Verify sorted: 1, 2, 3, 4 + test_flist_node_t *current = (test_flist_node_t *) list; + for (int i = 1; i <= 4; i++) { + assert_non_null(current); + assert_int_equal(current->value, i); + current = (test_flist_node_t *) current->node.next; + } + + c_flist_clear(&list, delete_flist_node); +} + +// ============================================================================ +// Doubly-linked list tests +// ============================================================================ + +// Test: dlist push_front +static void test_dlist_push_front(void **state) +{ + (void) state; + c_dlist_node_t *list = NULL; + test_dlist_node_t *node1, *node2, *node3; + + node1 = create_dlist_node(1); + assert_true(c_dlist_push_front(&list, &node1->node)); + assert_ptr_equal(list, &node1->node); + assert_int_equal(c_dlist_size(&list), 1); + + node2 = create_dlist_node(2); + assert_true(c_dlist_push_front(&list, &node2->node)); + assert_ptr_equal(list, &node2->node); + assert_int_equal(c_dlist_size(&list), 2); + + node3 = create_dlist_node(3); + assert_true(c_dlist_push_front(&list, &node3->node)); + assert_ptr_equal(list, &node3->node); + assert_int_equal(c_dlist_size(&list), 3); + + // Verify order: 3 -> 2 -> 1 + test_dlist_node_t *current = (test_dlist_node_t *) list; + assert_int_equal(current->value, 3); + current = (test_dlist_node_t *) current->node._list.next; + assert_int_equal(current->value, 2); + current = (test_dlist_node_t *) current->node._list.next; + assert_int_equal(current->value, 1); + + c_dlist_clear(&list, delete_dlist_node); +} + +// Test: dlist push_back (O(1) - fast!) +static void test_dlist_push_back(void **state) +{ + (void) state; + c_dlist_node_t *list = NULL; + test_dlist_node_t *node1, *node2, *node3; + + node1 = create_dlist_node(1); + assert_true(c_dlist_push_back(&list, &node1->node)); + assert_int_equal(c_dlist_size(&list), 1); + + node2 = create_dlist_node(2); + assert_true(c_dlist_push_back(&list, &node2->node)); + assert_int_equal(c_dlist_size(&list), 2); + + node3 = create_dlist_node(3); + assert_true(c_dlist_push_back(&list, &node3->node)); + assert_int_equal(c_dlist_size(&list), 3); + + // Verify order: 1 -> 2 -> 3 + test_dlist_node_t *current = (test_dlist_node_t *) list; + assert_int_equal(current->value, 1); + current = (test_dlist_node_t *) current->node._list.next; + assert_int_equal(current->value, 2); + current = (test_dlist_node_t *) current->node._list.next; + assert_int_equal(current->value, 3); + + c_dlist_clear(&list, delete_dlist_node); +} + +// Test: dlist pop_front +static void test_dlist_pop_front(void **state) +{ + (void) state; + c_dlist_node_t *list = NULL; + + test_dlist_node_t *node1 = create_dlist_node(1); + test_dlist_node_t *node2 = create_dlist_node(2); + test_dlist_node_t *node3 = create_dlist_node(3); + c_dlist_push_back(&list, &node1->node); + c_dlist_push_back(&list, &node2->node); + c_dlist_push_back(&list, &node3->node); + + assert_true(c_dlist_pop_front(&list, delete_dlist_node)); + assert_int_equal(c_dlist_size(&list), 2); + assert_int_equal(((test_dlist_node_t *) list)->value, 2); + + assert_true(c_dlist_pop_front(&list, delete_dlist_node)); + assert_int_equal(c_dlist_size(&list), 1); + assert_int_equal(((test_dlist_node_t *) list)->value, 3); + + assert_true(c_dlist_pop_front(&list, delete_dlist_node)); + assert_int_equal(c_dlist_size(&list), 0); + assert_null(list); + + assert_false(c_dlist_pop_front(&list, delete_dlist_node)); +} + +// Test: dlist pop_back (O(1) - fast!) +static void test_dlist_pop_back(void **state) +{ + (void) state; + c_dlist_node_t *list = NULL; + + test_dlist_node_t *node1 = create_dlist_node(1); + test_dlist_node_t *node2 = create_dlist_node(2); + test_dlist_node_t *node3 = create_dlist_node(3); + c_dlist_push_back(&list, &node1->node); + c_dlist_push_back(&list, &node2->node); + c_dlist_push_back(&list, &node3->node); + + assert_true(c_dlist_pop_back(&list, delete_dlist_node)); + assert_int_equal(c_dlist_size(&list), 2); + + assert_true(c_dlist_pop_back(&list, delete_dlist_node)); + assert_int_equal(c_dlist_size(&list), 1); + assert_int_equal(((test_dlist_node_t *) list)->value, 1); + + assert_true(c_dlist_pop_back(&list, delete_dlist_node)); + assert_int_equal(c_dlist_size(&list), 0); + assert_null(list); + + assert_false(c_dlist_pop_back(&list, delete_dlist_node)); +} + +// Test: dlist insert_before (O(1) - unique to doubly-linked!) +static void test_dlist_insert_before(void **state) +{ + (void) state; + c_dlist_node_t *list = NULL; + + test_dlist_node_t *node1 = create_dlist_node(1); + test_dlist_node_t *node3 = create_dlist_node(3); + c_dlist_push_back(&list, &node1->node); + c_dlist_push_back(&list, &node3->node); + + test_dlist_node_t *node2 = create_dlist_node(2); + assert_true(c_dlist_insert_before(&list, &node3->node, &node2->node)); + assert_int_equal(c_dlist_size(&list), 3); + + // Verify order: 1 -> 2 -> 3 + test_dlist_node_t *current = (test_dlist_node_t *) list; + assert_int_equal(current->value, 1); + current = (test_dlist_node_t *) current->node._list.next; + assert_int_equal(current->value, 2); + current = (test_dlist_node_t *) current->node._list.next; + assert_int_equal(current->value, 3); + + // Insert before head + test_dlist_node_t *node0 = create_dlist_node(0); + assert_true(c_dlist_insert_before(&list, &node1->node, &node0->node)); + assert_ptr_equal(list, &node0->node); + + c_dlist_clear(&list, delete_dlist_node); +} + +// Test: dlist insert_after +static void test_dlist_insert_after(void **state) +{ + (void) state; + c_dlist_node_t *list = NULL; + + test_dlist_node_t *node1 = create_dlist_node(1); + test_dlist_node_t *node3 = create_dlist_node(3); + c_dlist_push_back(&list, &node1->node); + c_dlist_push_back(&list, &node3->node); + + test_dlist_node_t *node2 = create_dlist_node(2); + assert_true(c_dlist_insert_after(&list, &node1->node, &node2->node)); + assert_int_equal(c_dlist_size(&list), 3); + + // Verify order: 1 -> 2 -> 3 + test_dlist_node_t *current = (test_dlist_node_t *) list; + assert_int_equal(current->value, 1); + current = (test_dlist_node_t *) current->node._list.next; + assert_int_equal(current->value, 2); + current = (test_dlist_node_t *) current->node._list.next; + assert_int_equal(current->value, 3); + + c_dlist_clear(&list, delete_dlist_node); +} + +// Test: dlist remove (O(1) - fast!) +static void test_dlist_remove(void **state) +{ + (void) state; + c_dlist_node_t *list = NULL; + + test_dlist_node_t *node1 = create_dlist_node(1); + test_dlist_node_t *node2 = create_dlist_node(2); + test_dlist_node_t *node3 = create_dlist_node(3); + c_dlist_push_back(&list, &node1->node); + c_dlist_push_back(&list, &node2->node); + c_dlist_push_back(&list, &node3->node); + + // Remove middle node (O(1)) + assert_true(c_dlist_remove(&list, &node2->node, delete_dlist_node)); + assert_int_equal(c_dlist_size(&list), 2); + + // Verify order: 1 -> 3 + test_dlist_node_t *current = (test_dlist_node_t *) list; + assert_int_equal(current->value, 1); + current = (test_dlist_node_t *) current->node._list.next; + assert_int_equal(current->value, 3); + + c_dlist_clear(&list, delete_dlist_node); +} + +// Test: dlist remove_if +static void test_dlist_remove_if(void **state) +{ + (void) state; + c_dlist_node_t *list = NULL; + + // Create list: 1, 2, 3, 4, 5, 6 + for (int i = 1; i <= 6; i++) { + test_dlist_node_t *node = create_dlist_node(i); + c_dlist_push_back(&list, &node->node); + } + + // Remove all even values + size_t removed = c_dlist_remove_if(&list, is_even_dlist, delete_dlist_node); + assert_int_equal(removed, 3); // Removed 2, 4, 6 + assert_int_equal(c_dlist_size(&list), 3); + + // Verify remaining: 1, 3, 5 + test_dlist_node_t *current = (test_dlist_node_t *) list; + int expected[] = {1, 3, 5}; + for (int i = 0; i < 3; i++) { + assert_non_null(current); + assert_int_equal(current->value, expected[i]); + current = (test_dlist_node_t *) current->node._list.next; + } + + c_dlist_clear(&list, delete_dlist_node); +} + +// Test: dlist unique +static void test_dlist_unique(void **state) +{ + (void) state; + c_dlist_node_t *list = NULL; + + // Create list with duplicates: 1, 1, 2, 3, 3, 3, 4 + int values[] = {1, 1, 2, 3, 3, 3, 4}; + for (int i = 0; i < 7; i++) { + test_dlist_node_t *node = create_dlist_node(values[i]); + c_dlist_push_back(&list, &node->node); + } + + size_t removed = c_dlist_unique(&list, are_equal_dlist, delete_dlist_node); + assert_int_equal(removed, 3); // Removed 3 duplicates + assert_int_equal(c_dlist_size(&list), 4); + + // Verify remaining: 1, 2, 3, 4 + test_dlist_node_t *current = (test_dlist_node_t *) list; + for (int i = 1; i <= 4; i++) { + assert_non_null(current); + assert_int_equal(current->value, i); + current = (test_dlist_node_t *) current->node._list.next; + } + + c_dlist_clear(&list, delete_dlist_node); +} + +// Test: dlist reverse +static void test_dlist_reverse(void **state) +{ + (void) state; + c_dlist_node_t *list = NULL; + + // Create list: 1, 2, 3, 4, 5 + for (int i = 1; i <= 5; i++) { + test_dlist_node_t *node = create_dlist_node(i); + c_dlist_push_back(&list, &node->node); + } + + assert_true(c_dlist_reverse(&list)); + + // Verify reversed: 5, 4, 3, 2, 1 + test_dlist_node_t *current = (test_dlist_node_t *) list; + for (int i = 5; i >= 1; i--) { + assert_non_null(current); + assert_int_equal(current->value, i); + current = (test_dlist_node_t *) current->node._list.next; + } + + c_dlist_clear(&list, delete_dlist_node); +} + +// Test: dlist empty +static void test_dlist_empty(void **state) +{ + (void) state; + c_dlist_node_t *list = NULL; + + assert_true(c_dlist_empty(&list)); + + test_dlist_node_t *node = create_dlist_node(1); + c_dlist_push_front(&list, &node->node); + assert_false(c_dlist_empty(&list)); + + c_dlist_clear(&list, delete_dlist_node); + assert_true(c_dlist_empty(&list)); +} + +// Test: dlist sort +static void test_dlist_sort(void **state) +{ + (void) state; + c_dlist_node_t *list = NULL; + + // Create unsorted list: 4, 1, 3, 2, 5 + int values[] = {4, 1, 3, 2, 5}; + for (int i = 0; i < 5; i++) { + test_dlist_node_t *node = create_dlist_node(values[i]); + c_dlist_push_back(&list, &node->node); + } + + assert_true(c_dlist_sort(&list, compare_ascending_dlist)); + + // Verify sorted: 1, 2, 3, 4, 5 + test_dlist_node_t *current = (test_dlist_node_t *) list; + for (int i = 1; i <= 5; i++) { + assert_non_null(current); + assert_int_equal(current->value, i); + current = (test_dlist_node_t *) current->node._list.next; + } + + c_dlist_clear(&list, delete_dlist_node); +} + +// Test: dlist backward traversal (unique to doubly-linked!) +static void test_dlist_backward_traversal(void **state) +{ + (void) state; + c_dlist_node_t *list = NULL; + + // Create list: 1, 2, 3, 4, 5 + for (int i = 1; i <= 5; i++) { + test_dlist_node_t *node = create_dlist_node(i); + c_dlist_push_back(&list, &node->node); + } + + // Find tail + c_dlist_node_t *tail = list; + while (tail && tail->_list.next) { + tail = (c_dlist_node_t *) tail->_list.next; + } + + // Traverse backward from tail + test_dlist_node_t *current = (test_dlist_node_t *) tail; + for (int i = 5; i >= 1; i--) { + assert_non_null(current); + assert_int_equal(current->value, i); + current = (test_dlist_node_t *) current->node.prev; + } + assert_null(current); // Should reach NULL after first node + + c_dlist_clear(&list, delete_dlist_node); +} + +// ============================================================================ +// Main test runner +// ============================================================================ + +int main(void) +{ + const struct CMUnitTest tests[] = { + // Forward list tests + cmocka_unit_test(test_flist_push_front), + cmocka_unit_test(test_flist_push_back), + cmocka_unit_test(test_flist_pop_front), + cmocka_unit_test(test_flist_pop_back), + cmocka_unit_test(test_flist_insert_after), + cmocka_unit_test(test_flist_remove), + cmocka_unit_test(test_flist_remove_if), + cmocka_unit_test(test_flist_unique), + cmocka_unit_test(test_flist_reverse), + cmocka_unit_test(test_flist_empty), + cmocka_unit_test(test_flist_sort), + + // Doubly-linked list tests + cmocka_unit_test(test_dlist_push_front), + cmocka_unit_test(test_dlist_push_back), + cmocka_unit_test(test_dlist_pop_front), + cmocka_unit_test(test_dlist_pop_back), + cmocka_unit_test(test_dlist_insert_before), + cmocka_unit_test(test_dlist_insert_after), + cmocka_unit_test(test_dlist_remove), + cmocka_unit_test(test_dlist_remove_if), + cmocka_unit_test(test_dlist_unique), + cmocka_unit_test(test_dlist_reverse), + cmocka_unit_test(test_dlist_empty), + cmocka_unit_test(test_dlist_sort), + cmocka_unit_test(test_dlist_backward_traversal), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +}