diff --git a/.gitignore b/.gitignore index a9c0a6b..2843ab5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,43 @@ .venv .cache + +# CMake build files +build*/ +CMakeCache.txt +CMakeFiles/ +Makefile +cmake_install.cmake +install_manifest.txt +CTestTestfile.cmake +Testing/ + +# Coverage files +*.gcda +*.gcno +*.gcov +*.info +coverage_html/ +coverage_report.md +coverage_summary.txt + +# Generated files +*.a +*.so +*.dll +*.exe + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..01e2553 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 3.16) + +project(EmbedIDS + VERSION 0.1.0 + DESCRIPTION "Lightweight Runtime Intrusion Detection SDK for embedded IoT devices" + LANGUAGES C CXX +) + +# Standards +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Options +option(BUILD_TESTS "Build unit tests" OFF) +option(BUILD_EXAMPLES "Build examples" OFF) +option(ENABLE_COVERAGE "Enable code coverage" OFF) + +# Compiler flags +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Os -ffunction-sections -fdata-sections") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Os -ffunction-sections -fdata-sections") + +# Coverage configuration +if(ENABLE_COVERAGE AND CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + include(CTest) + message(STATUS "Enabling code coverage with ${CMAKE_C_COMPILER_ID}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g --coverage") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g --coverage") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") +elseif(ENABLE_COVERAGE) + message(WARNING "Coverage only supported with GCC or Clang") +endif() + +# Configure main header with version info +configure_file(include/embedids.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/embedids.h @ONLY) + +# Include directories +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) + +# Build targets +add_subdirectory(src) +add_library(EmbedIDS::embedids ALIAS embedids) + +if(BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + +if(BUILD_EXAMPLES) + add_subdirectory(examples) +endif() + +# Installation +install(TARGETS embedids + ARCHIVE DESTINATION lib + PUBLIC_HEADER DESTINATION include +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/include/embedids.h + DESTINATION include +) + +# Package configuration +include(CMakePackageConfigHelpers) +write_basic_package_version_file("EmbedIDSConfigVersion.cmake" + VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion) +configure_package_config_file("cmake/EmbedIDSConfig.cmake.in" "EmbedIDSConfig.cmake" + INSTALL_DESTINATION lib/cmake/EmbedIDS) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/EmbedIDSConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/EmbedIDSConfigVersion.cmake" + DESTINATION lib/cmake/EmbedIDS) + +# Coverage targets +if(ENABLE_COVERAGE AND BUILD_TESTS) + add_custom_target(coverage + COMMAND ${CMAKE_CTEST_COMMAND} -T Test -T Coverage --output-on-failure + DEPENDS embedids_tests + COMMENT "Running tests and generating coverage report" + ) + message(STATUS "Coverage target added: make coverage") +endif() diff --git a/README.md b/README.md index fc93d6e..42c1975 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,166 @@ # EmbedIDS -Modern Intrusion Detection Systems (IDS) for Embedded Systems + +> **Modern Intrusion Detection System for Embedded Devices & IoT** + +EmbedIDS is a lightweight, extensible intrusion detection library designed for embedded systems and IoT devices. It features user-managed memory, custom metrics, and pluggable detection algorithms with zero runtime overhead when disabled. + +## Quick Start + +```c +#include + +// Allocate history buffer (user-managed memory) +static embedids_metric_datapoint_t cpu_history[50]; + +// Configure CPU monitoring with 80% threshold +embedids_metric_config_t cpu_config = { + .metric = { + .name = "cpu_usage", + .type = EMBEDIDS_METRIC_TYPE_PERCENTAGE, + .history = cpu_history, + .max_history_size = 50, + .enabled = true + }, + .algorithms = {{ + .type = EMBEDIDS_ALGORITHM_THRESHOLD, + .enabled = true, + .config.threshold = { + .max_threshold.f32 = 80.0f, + .check_max = true + } + }}, + .num_algorithms = 1 +}; + +// Initialize context and system +embedids_context_t context; +memset(&context, 0, sizeof(context)); + +embedids_system_config_t system = { + .metrics = &cpu_config, + .max_metrics = 1, + .num_active_metrics = 1 +}; + +embedids_init(&context, &system); + +// Monitor in real-time +embedids_metric_value_t value = {.f32 = get_cpu_usage()}; +embedids_add_datapoint(&context, "cpu_usage", value, timestamp_ms); + +if (embedids_analyze_metric(&context, "cpu_usage") != EMBEDIDS_OK) { + handle_intrusion_detected(); +} +``` + +## Architecture & Features + +### **Extensible Design** +- **User-Managed Memory**: No malloc/free - perfect for embedded systems +- **Custom Metrics**: Support for float, int, percentage, boolean, enum types +- **Pluggable Algorithms**: Threshold, trend analysis, statistical, and custom detection +- **Multiple Algorithms per Metric**: Run several detection methods simultaneously +- **Real-time Analysis**: Low-latency threat detection with configurable history + +### **Detection Algorithms** +| Algorithm | Description | Use Case | +|-----------|-------------|----------| +| **Threshold** | Min/max boundary checking | CPU usage, memory limits | +| **Trend** | Slope-based anomaly detection | Memory leaks, performance degradation | +| **Statistical** | Advanced statistical analysis | Complex pattern detection | +| **Custom** | User-defined detection functions | Domain-specific threats | + +### **Metric Types** +- `EMBEDIDS_METRIC_TYPE_PERCENTAGE` - CPU usage, memory utilization (0-100%) +- `EMBEDIDS_METRIC_TYPE_FLOAT` - Sensor readings, network traffic +- `EMBEDIDS_METRIC_TYPE_UINT32/64` - Packet counts, process counts +- `EMBEDIDS_METRIC_TYPE_BOOL` - System states, security flags +- `EMBEDIDS_METRIC_TYPE_ENUM` - Custom enumerated values + +## Installation + +### **CMake (Recommended)** +```bash +mkdir build && cd build +cmake .. -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON +make -j$(nproc) +sudo make install +``` + +### **Integration Options** +```cmake +# Option 1: Installed package +find_package(EmbedIDS REQUIRED) +target_link_libraries(your_app EmbedIDS::embedids) + +# Option 2: FetchContent (Git repository) +include(FetchContent) +FetchContent_Declare( + EmbedIDS + GIT_REPOSITORY https://github.com/samiralavi/EmbedIDS.git + GIT_BRANCH main # Fetch the main branch +) +FetchContent_MakeAvailable(EmbedIDS) +target_link_libraries(your_app embedids) +``` + +### Build Options + +- `BUILD_TESTS=ON/OFF` - Unit tests with GoogleTest (default: ON) +- `BUILD_EXAMPLES=ON/OFF` - Example applications (default: ON) +- `ENABLE_COVERAGE=ON/OFF` - Code coverage reporting (default: OFF) + +## Testing & Coverage + +### Running Unit Tests + +There are multiple ways to run the test suites + +#### Method 1: Using CTest (Recommended) +```bash +# Build the project first +mkdir build && cd build +cmake .. -DBUILD_TESTS=ON +make -j$(nproc) + +# Run all tests +ctest + +# Run tests with detailed output +ctest --verbose + +# List available tests +ctest --list-tests +``` + +#### Method 2: Direct Test Execution +```bash +# After building, run tests directly +./tests/embedids_tests + +# Run specific test patterns (GoogleTest) +./tests/embedids_tests --gtest_filter="*Threshold*" +``` + +#### Method 3: Using make (if available) +```bash +make test # May not be available in all configurations +``` + +### Code Coverage Analysis + +Generate detailed coverage reports to see test effectiveness: + +```bash +# Configure with coverage enabled +mkdir build && cd build +cmake .. -DBUILD_TESTS=ON -DENABLE_COVERAGE=ON +make -j$(nproc) + +# Generate coverage report +make coverage +``` + +## License + +Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) file for details. diff --git a/cmake/EmbedIDSConfig.cmake.in b/cmake/EmbedIDSConfig.cmake.in new file mode 100644 index 0000000..9502b2a --- /dev/null +++ b/cmake/EmbedIDSConfig.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/EmbedIDSTargets.cmake") + +check_required_components(EmbedIDS) diff --git a/docs/images/embedIDS_logo - Copy.png:Zone.Identifier b/docs/assets/images/embedIDS_logo - Copy.png:Zone.Identifier similarity index 100% rename from docs/images/embedIDS_logo - Copy.png:Zone.Identifier rename to docs/assets/images/embedIDS_logo - Copy.png:Zone.Identifier diff --git a/docs/images/embedIDS_logo.png b/docs/assets/images/embedIDS_logo.png similarity index 100% rename from docs/images/embedIDS_logo.png rename to docs/assets/images/embedIDS_logo.png diff --git a/docs/images/embedIDS_logo_text.png b/docs/assets/images/embedIDS_logo_text.png similarity index 100% rename from docs/images/embedIDS_logo_text.png rename to docs/assets/images/embedIDS_logo_text.png diff --git a/docs/images/favicon.png b/docs/assets/images/favicon.png similarity index 100% rename from docs/images/favicon.png rename to docs/assets/images/favicon.png diff --git a/docs/javascript/extra.js b/docs/assets/javascripts/extra.js similarity index 100% rename from docs/javascript/extra.js rename to docs/assets/javascripts/extra.js diff --git a/docs/stylesheets/extra.css b/docs/assets/stylesheets/extra.css similarity index 100% rename from docs/stylesheets/extra.css rename to docs/assets/stylesheets/extra.css diff --git a/docs/blogs/index.md b/docs/blogs/index.md deleted file mode 100644 index c58f16c..0000000 --- a/docs/blogs/index.md +++ /dev/null @@ -1,2 +0,0 @@ -# Blog - diff --git a/docs/index.md b/docs/index.md index 775f86d..025e435 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,9 +4,17 @@ description: EmbedIDS is a lightweight Runtime Intrusion Detection SDK specifica hide: - toc --- + # EmbedIDS: Open Source Intrusion Detection System (IDS) for Embedded Systems -![EmbedIDS Logo](images/embedIDS_logo_text.png) +![EmbedIDS Logo](assets/images/embedIDS_logo_text.png) + +**🚀 New to EmbedIDS?** +- **[Quick Start Guide](quickstart.md)** - Get running in 5 minutes +- **[Complete Tutorial](tutorial.md)** - Comprehensive learning guide +- **[Examples](https://github.com/samiralavi/EmbedIDS/tree/main/examples)** - Ready-to-run code samples + +--- EmbedIDS is a lightweight Runtime Intrusion Detection SDK specifically designed for low-power embedded Internet of Things (IoT) devices. Our open-source SDK provides AI-driven protection to address the critical security gap in constrained IoT environments. diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..873be91 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,155 @@ +# Quick Start + +Get up and running with EmbedIDS in 5 minutes! + +## Installation + +```bash +git clone https://github.com/samiralavi/EmbedIDS.git +cd EmbedIDS +mkdir build && cd build +cmake .. +make +``` + +## Hello World Example + +Create `my_monitor.c`: + +```c +#include +#include +#include +#include "embedids.h" + +int main() { + // 1. Allocate memory for metric history + static embedids_metric_datapoint_t cpu_history[50]; + + // 2. Configure CPU metric + embedids_metric_t cpu_metric; + memset(&cpu_metric, 0, sizeof(cpu_metric)); + strcpy(cpu_metric.name, "cpu_usage"); + cpu_metric.type = EMBEDIDS_METRIC_TYPE_PERCENTAGE; + cpu_metric.history = cpu_history; + cpu_metric.max_history_size = 50; + cpu_metric.enabled = true; + + // 3. Configure threshold algorithm (alert if CPU > 80%) + embedids_algorithm_t threshold_algo; + memset(&threshold_algo, 0, sizeof(threshold_algo)); + threshold_algo.type = EMBEDIDS_ALGORITHM_THRESHOLD; + threshold_algo.enabled = true; + threshold_algo.config.threshold.max_threshold.f32 = 80.0f; + threshold_algo.config.threshold.check_max = true; + + // 4. Create metric configuration + embedids_metric_config_t metric_config; + memset(&metric_config, 0, sizeof(metric_config)); + metric_config.metric = cpu_metric; + metric_config.algorithms[0] = threshold_algo; + metric_config.num_algorithms = 1; + + // 5. Create system configuration + embedids_system_config_t system_config; + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = &metric_config; + system_config.max_metrics = 1; + system_config.num_active_metrics = 1; + + // 6. Initialize EmbedIDS context and system + embedids_context_t context; + memset(&context, 0, sizeof(context)); + + if (embedids_init(&context, &system_config) != EMBEDIDS_OK) { + printf("Failed to initialize EmbedIDS\n"); + return 1; + } + + printf("🔒 CPU Monitor Started (threshold: 80%%)\n\n"); + + // 7. Monitoring loop + for (int i = 0; i < 10; i++) { + // Simulate CPU usage (gradually increasing) + float cpu = 30.0f + (i * 8.0f); + + // Add data point + embedids_metric_value_t value = {.f32 = cpu}; + embedids_add_datapoint(&context, "cpu_usage", value, time(NULL) * 1000); + + // Check for threats + if (embedids_analyze_metric(&context, "cpu_usage") == EMBEDIDS_OK) { + printf("✅ CPU: %.1f%% - Normal\n", cpu); + } else { + printf("🚨 CPU: %.1f%% - ALERT!\n", cpu); + } + + sleep(1); + } + + embedids_cleanup(&context); + return 0; +} +``` + +## Compile and Run + +```bash +gcc -o my_monitor my_monitor.c -L. -lembedids -I../include +./my_monitor +``` + +## Expected Output + +``` +🔒 CPU Monitor Started (threshold: 80%) + +✅ CPU: 30.0% - Normal +✅ CPU: 38.0% - Normal +✅ CPU: 46.0% - Normal +✅ CPU: 54.0% - Normal +✅ CPU: 62.0% - Normal +✅ CPU: 70.0% - Normal +✅ CPU: 78.0% - Normal +🚨 CPU: 86.0% - ALERT! +🚨 CPU: 94.0% - ALERT! +🚨 CPU: 102.0% - ALERT! +``` + +## What Just Happened? + +1. **Created a metric**: CPU usage with 50-point history +2. **Added detection**: Threshold algorithm at 80% +3. **Monitored in real-time**: Added data points and analyzed +4. **Got alerts**: When CPU exceeded threshold + +## Next Steps + +- **Multiple metrics**: Monitor CPU, memory, network together +- **Custom algorithms**: Implement your own detection logic +- **Advanced features**: Trend analysis, pattern detection +- **Real sensors**: Replace simulated data with actual sensor readings + +See the [full tutorial](tutorial.md) for comprehensive examples and advanced usage! + +## Quick Reference + +### Core Functions +```c +embedids_init(&context, &config) // Initialize system +embedids_add_datapoint(&context, name, val, time) // Add sensor data +embedids_analyze_metric(&context, name) // Check one metric +embedids_analyze_all(&context) // Check all metrics +embedids_cleanup(&context) // Shutdown system +``` + +### Algorithm Types +- `EMBEDIDS_ALGORITHM_THRESHOLD` - Simple min/max limits +- `EMBEDIDS_ALGORITHM_TREND` - Slope analysis over time +- `EMBEDIDS_ALGORITHM_CUSTOM` - Your own detection logic + +### Return Codes +- `EMBEDIDS_OK` - All normal +- `EMBEDIDS_ERROR_THRESHOLD_EXCEEDED` - Threat detected +- `EMBEDIDS_ERROR_NOT_INITIALIZED` - Call init first +- `EMBEDIDS_ERROR_METRIC_NOT_FOUND` - Check metric name diff --git a/docs/tutorial.md b/docs/tutorial.md new file mode 100644 index 0000000..cf6a956 --- /dev/null +++ b/docs/tutorial.md @@ -0,0 +1,698 @@ +# Tutorial + +## Introduction + +EmbedIDS is a lightweight, extensible intrusion detection system designed for embedded devices and IoT systems. It provides real-time monitoring of system metrics with configurable detection algorithms. + +### Key Features +- **User-managed memory**: No dynamic allocation, perfect for embedded systems +- **Stateless design**: Context-based API for thread safety and multiple instances +- **Extensible architecture**: Custom metrics and algorithms +- **Multiple detection methods**: Thresholds, trends, statistical analysis, and custom algorithms +- **Minimal footprint**: Optimized for resource-constrained environments +- **Real-time analysis**: Low-latency threat detection + +## API Design + +### Stateless Architecture + +EmbedIDS uses a stateless design where all library state is managed through a user-provided context object (`embedids_context_t`). This design provides several benefits: + +- **Thread Safety**: Multiple contexts can be used independently across threads +- **Multiple Instances**: Create separate EmbedIDS instances for different subsystems +- **Clear Ownership**: Users control the lifecycle of the context object +- **Embedded-Friendly**: No global state that could cause issues in embedded systems + +```c +// Create and initialize a context +embedids_context_t context; +memset(&context, 0, sizeof(context)); + +// Pass context to all API calls +embedids_init(&context, &config); +embedids_add_datapoint(&context, "metric_name", value, timestamp); +embedids_analyze_metric(&context, "metric_name"); +embedids_cleanup(&context); +``` + +## Getting Started + +### Installation + +1. **Clone the repository:** +```bash +git clone https://github.com/samiralavi/EmbedIDS.git +cd EmbedIDS +``` + +2. **Build the library:** +```bash +mkdir build && cd build +cmake .. +make +``` + +3. **Include in your project:** +```c +#include "embedids.h" +``` + +### Basic Project Structure +``` +your_project/ +├── src/ +│ └── main.c +├── include/ +│ └── embedids.h +├── lib/ +│ └── libembedids.a +└── CMakeLists.txt +``` + +## Basic Usage + +### Step 1: Initialize EmbedIDS + +The simplest way to get started is with an empty configuration: + +```c +#include +#include +#include "embedids.h" + +int main() { + // Initialize context and empty configuration + embedids_context_t context; + memset(&context, 0, sizeof(context)); + + embedids_system_config_t config; + memset(&config, 0, sizeof(config)); + + embedids_result_t result = embedids_init(&context, &config); + if (result != EMBEDIDS_OK) { + printf("Failed to initialize EmbedIDS: %d\n", result); + return 1; + } + + printf("EmbedIDS v%s initialized successfully!\n", embedids_get_version()); + + // Clean up + embedids_cleanup(&context); + return 0; +} +``` + +### Step 2: Basic Metric Monitoring + +Let's create a simple CPU usage monitor: + +```c +#include +#include +#include +#include "embedids.h" + +int main() { + // Step 1: Allocate memory for metric history + static embedids_metric_datapoint_t cpu_history[100]; + + // Step 2: Configure the metric + embedids_metric_t cpu_metric; + memset(&cpu_metric, 0, sizeof(cpu_metric)); + strncpy(cpu_metric.name, "cpu_usage", EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + cpu_metric.type = EMBEDIDS_METRIC_TYPE_PERCENTAGE; + cpu_metric.history = cpu_history; + cpu_metric.max_history_size = 100; + cpu_metric.enabled = true; + + // Step 3: Configure threshold algorithm + embedids_algorithm_t threshold_algo; + memset(&threshold_algo, 0, sizeof(threshold_algo)); + threshold_algo.type = EMBEDIDS_ALGORITHM_THRESHOLD; + threshold_algo.enabled = true; + threshold_algo.config.threshold.max_threshold.f32 = 80.0f; // 80% threshold + threshold_algo.config.threshold.check_max = true; + threshold_algo.config.threshold.check_min = false; + + // Step 4: Create metric configuration + embedids_metric_config_t metric_config; + memset(&metric_config, 0, sizeof(metric_config)); + metric_config.metric = cpu_metric; + metric_config.algorithms[0] = threshold_algo; + metric_config.num_algorithms = 1; + + // Step 5: Create system configuration + embedids_system_config_t system_config; + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = &metric_config; + system_config.max_metrics = 1; + system_config.num_active_metrics = 1; + + // Step 6: Initialize EmbedIDS context and system + embedids_context_t context; + memset(&context, 0, sizeof(context)); + + embedids_result_t result = embedids_init(&context, &system_config); + if (result != EMBEDIDS_OK) { + printf("Failed to initialize EmbedIDS: %d\n", result); + return 1; + } + + printf("CPU monitoring started (threshold: 80%%)\n"); + + // Step 7: Monitoring loop + for (int i = 0; i < 10; i++) { + // Simulate CPU usage data + float cpu_usage = 30.0f + (i * 7.0f); // Gradually increasing + + // Add data point + embedids_metric_value_t value = {.f32 = cpu_usage}; + uint64_t timestamp = (uint64_t)time(NULL) * 1000; + + result = embedids_add_datapoint(&context, &context, "cpu_usage", value, timestamp); + if (result != EMBEDIDS_OK) { + printf("Failed to add data point: %d\n", result); + continue; + } + + // Analyze the metric + result = embedids_analyze_metric(&context, &context, "cpu_usage"); + if (result == EMBEDIDS_OK) { + printf("Iteration %d: CPU %.1f%% - NORMAL\n", i+1, cpu_usage); + } else { + printf("Iteration %d: CPU %.1f%% - ALERT! Threshold exceeded\n", i+1, cpu_usage); + } + + sleep(1); + } + + embedids_cleanup(&context); + return 0; +} +``` + +## Advanced Configuration + +### Multiple Metrics with Different Algorithms + +```c +#include +#include +#include +#include +#include "embedids.h" + +// Function to simulate realistic sensor data +float get_cpu_usage() { + return 20.0f + (rand() % 60); // 20-80% +} + +float get_memory_usage() { + return 30.0f + (rand() % 50); // 30-80% +} + +float get_network_packets() { + return 100.0f + (rand() % 500); // 100-600 packets/s +} + +int main() { + srand((unsigned int)time(NULL)); + + // Allocate memory for each metric + static embedids_metric_datapoint_t cpu_history[50]; + static embedids_metric_datapoint_t memory_history[50]; + static embedids_metric_datapoint_t network_history[30]; + + // ===== CPU Metric ===== + embedids_metric_t cpu_metric; + memset(&cpu_metric, 0, sizeof(cpu_metric)); + strncpy(cpu_metric.name, "cpu_usage", EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + cpu_metric.type = EMBEDIDS_METRIC_TYPE_PERCENTAGE; + cpu_metric.history = cpu_history; + cpu_metric.max_history_size = 50; + cpu_metric.enabled = true; + + // CPU: Threshold algorithm + embedids_algorithm_t cpu_threshold; + memset(&cpu_threshold, 0, sizeof(cpu_threshold)); + cpu_threshold.type = EMBEDIDS_ALGORITHM_THRESHOLD; + cpu_threshold.enabled = true; + cpu_threshold.config.threshold.max_threshold.f32 = 75.0f; + cpu_threshold.config.threshold.check_max = true; + cpu_threshold.config.threshold.check_min = false; + + embedids_metric_config_t cpu_config; + memset(&cpu_config, 0, sizeof(cpu_config)); + cpu_config.metric = cpu_metric; + cpu_config.algorithms[0] = cpu_threshold; + cpu_config.num_algorithms = 1; + + // ===== Memory Metric ===== + embedids_metric_t memory_metric; + memset(&memory_metric, 0, sizeof(memory_metric)); + strncpy(memory_metric.name, "memory_usage", EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + memory_metric.type = EMBEDIDS_METRIC_TYPE_PERCENTAGE; + memory_metric.history = memory_history; + memory_metric.max_history_size = 50; + memory_metric.enabled = true; + + // Memory: Trend algorithm + embedids_algorithm_t memory_trend; + memset(&memory_trend, 0, sizeof(memory_trend)); + memory_trend.type = EMBEDIDS_ALGORITHM_TREND; + memory_trend.enabled = true; + memory_trend.config.trend.window_size = 5; + memory_trend.config.trend.max_slope = 10.0f; // Max 10% increase per window + + embedids_metric_config_t memory_config; + memset(&memory_config, 0, sizeof(memory_config)); + memory_config.metric = memory_metric; + memory_config.algorithms[0] = memory_trend; + memory_config.num_algorithms = 1; + + // ===== Network Metric ===== + embedids_metric_t network_metric; + memset(&network_metric, 0, sizeof(network_metric)); + strncpy(network_metric.name, "network_packets", EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + network_metric.type = EMBEDIDS_METRIC_TYPE_FLOAT; + network_metric.history = network_history; + network_metric.max_history_size = 30; + network_metric.enabled = true; + + // Network: Threshold algorithm + embedids_algorithm_t network_threshold; + memset(&network_threshold, 0, sizeof(network_threshold)); + network_threshold.type = EMBEDIDS_ALGORITHM_THRESHOLD; + network_threshold.enabled = true; + network_threshold.config.threshold.max_threshold.f32 = 500.0f; + network_threshold.config.threshold.check_max = true; + network_threshold.config.threshold.check_min = false; + + embedids_metric_config_t network_config; + memset(&network_config, 0, sizeof(network_config)); + network_config.metric = network_metric; + network_config.algorithms[0] = network_threshold; + network_config.num_algorithms = 1; + + // ===== System Configuration ===== + embedids_metric_config_t metrics[3] = {cpu_config, memory_config, network_config}; + + embedids_system_config_t system_config; + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = metrics; + system_config.max_metrics = 3; + system_config.num_active_metrics = 3; + + // Initialize EmbedIDS context and system + embedids_context_t context; + memset(&context, 0, sizeof(context)); + + embedids_result_t result = embedids_init(&context, &system_config); + if (result != EMBEDIDS_OK) { + printf("Failed to initialize EmbedIDS: %d\n", result); + return 1; + } + + printf("Multi-metric monitoring started:\n"); + printf("- CPU: Threshold (75%%)\n"); + printf("- Memory: Trend analysis (5 window, 10%% max slope)\n"); + printf("- Network: Threshold (500 pkt/s)\n\n"); + + // Monitoring loop + for (int i = 0; i < 15; i++) { + // Get sensor data + float cpu = get_cpu_usage(); + float memory = get_memory_usage(); + float network = get_network_packets(); + + // Add data points + uint64_t timestamp = (uint64_t)time(NULL) * 1000 + i * 100; + + embedids_metric_value_t cpu_val = {.f32 = cpu}; + embedids_metric_value_t memory_val = {.f32 = memory}; + embedids_metric_value_t network_val = {.f32 = network}; + + embedids_add_datapoint(&context, "cpu_usage", cpu_val, timestamp); + embedids_add_datapoint(&context, "memory_usage", memory_val, timestamp); + embedids_add_datapoint(&context, "network_packets", network_val, timestamp); + + // Analyze all metrics + embedids_result_t overall_result = embedids_analyze_all(&context); + + printf("Iteration %2d: CPU=%4.1f%%, Memory=%4.1f%%, Network=%5.0f pkt/s ", + i+1, cpu, memory, network); + + if (overall_result == EMBEDIDS_OK) { + printf("✅ SECURE\n"); + } else { + printf("🚨 THREAT DETECTED\n"); + } + + usleep(200000); // 200ms delay + } + + embedids_cleanup(&context); + return 0; +} +``` + +## Custom Algorithms + +### Creating a Custom Detection Algorithm + +Custom algorithms allow you to implement specialized detection logic: + +```c +#include +#include +#include +#include "embedids.h" + +// Custom algorithm context +typedef struct { + float baseline; + uint32_t violation_count; + uint32_t max_violations; +} anomaly_detector_context_t; + +// Custom algorithm implementation +embedids_result_t anomaly_detector(const embedids_metric_t* metric, + const void* config, + void* context) { + (void)config; // Unused in this example + + anomaly_detector_context_t* ctx = (anomaly_detector_context_t*)context; + + if (metric->current_size < 3) { + return EMBEDIDS_OK; // Need at least 3 data points + } + + // Get recent values + uint32_t idx1 = (metric->write_index - 1) % metric->max_history_size; + uint32_t idx2 = (metric->write_index - 2) % metric->max_history_size; + uint32_t idx3 = (metric->write_index - 3) % metric->max_history_size; + + float val1 = metric->history[idx1].value.f32; + float val2 = metric->history[idx2].value.f32; + float val3 = metric->history[idx3].value.f32; + + // Calculate moving average + float avg = (val1 + val2 + val3) / 3.0f; + + // Check deviation from baseline + float deviation = fabs(avg - ctx->baseline); + float threshold = ctx->baseline * 0.5f; // 50% deviation threshold + + if (deviation > threshold) { + ctx->violation_count++; + printf(" 🔍 Anomaly detector: deviation %.2f from baseline %.2f (count: %d)\n", + deviation, ctx->baseline, ctx->violation_count); + + if (ctx->violation_count >= ctx->max_violations) { + printf(" 🚨 ANOMALY ALERT: Sustained deviation detected!\n"); + ctx->violation_count = 0; // Reset counter + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + } else { + ctx->violation_count = 0; // Reset on normal reading + } + + return EMBEDIDS_OK; +} + +int main() { + // User-managed memory + static embedids_metric_datapoint_t sensor_history[40]; + + // Custom algorithm context + anomaly_detector_context_t detector_ctx = { + .baseline = 50.0f, + .violation_count = 0, + .max_violations = 3 + }; + + // Configure metric + embedids_metric_t sensor_metric; + memset(&sensor_metric, 0, sizeof(sensor_metric)); + strncpy(sensor_metric.name, "sensor_data", EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + sensor_metric.type = EMBEDIDS_METRIC_TYPE_FLOAT; + sensor_metric.history = sensor_history; + sensor_metric.max_history_size = 40; + sensor_metric.enabled = true; + + // Configure custom algorithm + embedids_algorithm_t custom_algo; + memset(&custom_algo, 0, sizeof(custom_algo)); + custom_algo.type = EMBEDIDS_ALGORITHM_CUSTOM; + custom_algo.enabled = true; + custom_algo.config.custom.function = anomaly_detector; + custom_algo.config.custom.config = NULL; + custom_algo.config.custom.context = &detector_ctx; + + // Configure threshold algorithm as backup + embedids_algorithm_t threshold_algo; + memset(&threshold_algo, 0, sizeof(threshold_algo)); + threshold_algo.type = EMBEDIDS_ALGORITHM_THRESHOLD; + threshold_algo.enabled = true; + threshold_algo.config.threshold.max_threshold.f32 = 100.0f; + threshold_algo.config.threshold.min_threshold.f32 = 0.0f; + threshold_algo.config.threshold.check_max = true; + threshold_algo.config.threshold.check_min = true; + + // Metric configuration with multiple algorithms + embedids_metric_config_t metric_config; + memset(&metric_config, 0, sizeof(metric_config)); + metric_config.metric = sensor_metric; + metric_config.algorithms[0] = custom_algo; // Custom algorithm first + metric_config.algorithms[1] = threshold_algo; // Threshold as backup + metric_config.num_algorithms = 2; + + // System configuration + embedids_system_config_t system_config; + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = &metric_config; + system_config.max_metrics = 1; + system_config.num_active_metrics = 1; + + // Initialize context and system + embedids_context_t context; + memset(&context, 0, sizeof(context)); + + embedids_result_t result = embedids_init(&context, &system_config); + if (result != EMBEDIDS_OK) { + printf("Failed to initialize EmbedIDS: %d\n", result); + return 1; + } + + printf("Custom algorithm demo started (baseline: %.1f)\n", detector_ctx.baseline); + printf("Algorithms: Custom Anomaly Detector + Threshold (0-100)\n\n"); + + // Simulate sensor data with anomalies + float sensor_values[] = { + 48.0, 52.0, 49.0, 51.0, 47.0, // Normal data around baseline + 85.0, 88.0, 90.0, // Anomaly: high values + 45.0, 50.0, 48.0, // Back to normal + 15.0, 12.0, 18.0, // Anomaly: low values + 52.0, 49.0, 51.0 // Normal again + }; + + int num_values = sizeof(sensor_values) / sizeof(sensor_values[0]); + + for (int i = 0; i < num_values; i++) { + float value = sensor_values[i]; + + // Add data point + embedids_metric_value_t val = {.f32 = value}; + uint64_t timestamp = (uint64_t)time(NULL) * 1000 + i * 100; + + embedids_add_datapoint(&context, "sensor_data", val, timestamp); + + // Analyze + result = embedids_analyze_metric(&context, "sensor_data"); + + printf("Sample %2d: Value=%.1f ", i+1, value); + if (result == EMBEDIDS_OK) { + printf("✅ NORMAL\n"); + } else { + printf("🚨 ANOMALY DETECTED\n"); + } + + usleep(300000); // 300ms delay + } + + embedids_cleanup(&context); + return 0; +} +``` + +## Best Practices + +### 1. Memory Management + +```c +// ✅ Good: Static allocation for embedded systems +static embedids_metric_datapoint_t history[100]; + +// ❌ Avoid: Dynamic allocation in embedded systems +// embedids_metric_datapoint_t* history = malloc(sizeof(embedids_metric_datapoint_t) * 100); +``` + +### 2. Error Handling + +```c +// ✅ Always check return codes +embedids_result_t result = embedids_add_datapoint(&context, "metric", value, timestamp); +if (result != EMBEDIDS_OK) { + handle_error(result); +} + +// ✅ Handle specific error types +switch (result) { + case EMBEDIDS_ERROR_NOT_INITIALIZED: + reinitialize_system(); + break; + case EMBEDIDS_ERROR_METRIC_NOT_FOUND: + log_error("Unknown metric"); + break; + case EMBEDIDS_ERROR_BUFFER_FULL: + // This is expected behavior with circular buffers + break; + default: + handle_generic_error(result); +} +``` + +### 3. Metric Configuration + +```c +// ✅ Use appropriate history sizes +embedids_metric_t fast_metric = { + .max_history_size = 20, // For high-frequency data + // ... +}; + +embedids_metric_t slow_metric = { + .max_history_size = 100, // For trend analysis + // ... +}; + +// ✅ Choose appropriate thresholds +threshold_config.max_threshold.f32 = 85.0f; // 85% CPU threshold +threshold_config.min_threshold.f32 = 5.0f; // Minimum activity level +``` + +### 4. Performance Optimization + +```c +// ✅ Batch operations when possible +for (int i = 0; i < num_sensors; i++) { + embedids_add_datapoint(&context, sensor_names[i], values[i], timestamp); +} +// Analyze all at once +embedids_analyze_all(&context); + +// ✅ Use appropriate data types +embedids_metric_value_t percentage_val = {.f32 = 75.5f}; // For percentages +embedids_metric_value_t count_val = {.u32 = 1024}; // For counts +``` + +## Troubleshooting + +### Common Issues and Solutions + +#### 1. Initialization Fails +```c +embedids_result_t result = embedids_init(&context, &config); +if (result == EMBEDIDS_ERROR_INVALID_PARAM) { + // Check: config pointer is not NULL + // Check: metric configurations are valid + // Check: algorithm configurations are correct +} +``` + +#### 2. Metric Not Found +```c +result = embedids_add_datapoint(&context, "cpu_usage", value, timestamp); +if (result == EMBEDIDS_ERROR_METRIC_NOT_FOUND) { + // Verify metric name matches exactly (case-sensitive) + // Ensure metric was added to system configuration + // Check metric is enabled +} +``` + +#### 3. Memory Issues +```c +// Ensure history arrays are large enough +static embedids_metric_datapoint_t history[SIZE]; +if (SIZE < expected_data_points) { + // Increase SIZE or reduce data retention period +} +``` + +#### 4. Algorithm Not Triggering +```c +// Check algorithm configuration +threshold_algo.enabled = true; // Must be enabled +threshold_algo.config.threshold.check_max = true; // Must enable checking + +// Verify threshold values +printf("Threshold: %.2f, Current: %.2f\n", + threshold_config.max_threshold.f32, current_value); +``` + +### Debug Information + +```c +// Check system status +if (embedids_is_initialized()) { + printf("EmbedIDS is running\n"); +} else { + printf("EmbedIDS not initialized\n"); +} + +// Get version info +printf("EmbedIDS version: %s\n", embedids_get_version()); + +// Monitor metric states +for (int i = 0; i < num_metrics; i++) { + printf("Metric %s: %d/%d data points\n", + metric_names[i], + metrics[i].current_size, + metrics[i].max_history_size); +} +``` + +### Performance Monitoring + +```c +#include + +// Measure analysis performance +clock_t start = clock(); +embedids_result_t result = embedids_analyze_all(&context); +clock_t end = clock(); + +double cpu_time = ((double)(end - start)) / CLOCKS_PER_SEC; +printf("Analysis took %.3f ms\n", cpu_time * 1000); +``` + +## Conclusion + +EmbedIDS provides a flexible, efficient intrusion detection framework for embedded systems. Key takeaways: + +1. **Start simple** with basic threshold monitoring +2. **Use user-managed memory** for predictable resource usage +3. **Combine multiple algorithms** for comprehensive detection +4. **Implement custom algorithms** for specialized requirements +5. **Follow embedded best practices** for reliable operation + +For more examples, see the `examples/` directory in the EmbedIDS repository. + +--- + +**Next Steps:** +- Try the provided examples +- Implement custom algorithms for your use case +- Integrate with your existing monitoring infrastructure +- Explore advanced configuration options + diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..0e37ba0 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 3.16) + +project(EmbedIDS_Examples + VERSION 1.0.0 + DESCRIPTION "Examples for EmbedIDS - Embedded Intrusion Detection System" + LANGUAGES C +) + +# Set C standard +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +# Compiler flags +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra") + +# Include FetchContent module +include(FetchContent) + +# Try to find EmbedIDS package first +find_package(EmbedIDS QUIET) + +if(NOT TARGET EmbedIDS::embedids) + # If we're in the source tree, use local source + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../src/CMakeLists.txt") + message(STATUS "Using local EmbedIDS source") + + # Disable building examples in the fetched content to avoid conflicts + set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + + FetchContent_Declare( + EmbedIDS_Local + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/.. + ) + FetchContent_MakeAvailable(EmbedIDS_Local) + else() + message(FATAL_ERROR "EmbedIDS library not found. Please install EmbedIDS or build from the EmbedIDS source tree.") + endif() +endif() + +# Define example targets +set(EXAMPLE_TARGETS + embedids_example + user_application_example + extensible_example + tutorial_example +) + +set(EXAMPLE_SOURCES + simple_example.c + user_application.c + extensible_example.c + tutorial_example.c +) + +# Create executables +list(LENGTH EXAMPLE_TARGETS num_targets) +math(EXPR last_index "${num_targets} - 1") + +foreach(i RANGE ${last_index}) + list(GET EXAMPLE_TARGETS ${i} target) + list(GET EXAMPLE_SOURCES ${i} source) + + add_executable(${target} ${source}) + target_link_libraries(${target} embedids) + + # Add math library for examples that need it + if(target MATCHES "user_application|extensible") + target_link_libraries(${target} m) + endif() +endforeach() + +# Installation rules +install(TARGETS ${EXAMPLE_TARGETS} + RUNTIME DESTINATION bin/examples +) + +install(FILES ${EXAMPLE_SOURCES} CMakeLists.txt + DESTINATION share/embedids/examples +) diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..794bdbe --- /dev/null +++ b/examples/README.md @@ -0,0 +1,59 @@ +# EmbedIDS Examples + +Example applications demonstrating EmbedIDS usage for embedded intrusion detection. + +## Examples + +- **`simple_example.c`** - Basic EmbedIDS API usage +- **`user_application.c`** - Real-world application integration +- **`extensible_example.c`** - Advanced features and custom algorithms +- **`tutorial_example.c`** - Step-by-step tutorial implementation + +## Quick Build + +### Independent Build (Recommended) +```bash +cd examples/ +mkdir build && cd build +cmake .. +make +``` + +### From EmbedIDS Source Tree +```bash +# From main EmbedIDS directory +mkdir build && cd build +cmake -DBUILD_EXAMPLES=ON .. +make +``` + +## Usage + +Run individual examples: +```bash +./embedids_example +./user_application_example +./extensible_example +./tutorial_example +``` + +## Integration + +To use EmbedIDS in your project: +```cmake +find_package(EmbedIDS REQUIRED) +add_executable(my_app main.c) +target_link_libraries(my_app EmbedIDS::embedids) +``` + +## Dependencies + +- EmbedIDS library (installed or source build) +- CMake 3.16+ +- C11 compiler (GCC/Clang) + +## Troubleshooting + +**EmbedIDS not found?** +- Install EmbedIDS: `cd /path/to/embedids && mkdir build && cd build && cmake .. && make && sudo make install` +- Or set: `cmake -DCMAKE_PREFIX_PATH=/path/to/embedids/install ..` diff --git a/examples/extensible_example.c b/examples/extensible_example.c new file mode 100644 index 0000000..8b51e9a --- /dev/null +++ b/examples/extensible_example.c @@ -0,0 +1,363 @@ +#include +#include +#include +#include +#include +#include +#define EMBEDIDS_ENABLED 1 +#include + +// Custom algorithm context for pattern detection +typedef struct { + float baseline; + float threshold_multiplier; + uint32_t consecutive_violations; + uint32_t max_violations; +} pattern_detector_context_t; + +// Custom algorithm: Advanced pattern detection +embedids_result_t advanced_pattern_detector(const embedids_metric_t* metric, + const void* config, + void* context) { + (void)config; // Unused parameter + pattern_detector_context_t* ctx = (pattern_detector_context_t*)context; + + if (metric->current_size < 3) { + return EMBEDIDS_OK; // Need at least 3 data points + } + + // Get the last few data points + uint32_t idx1 = (metric->write_index - 1) % metric->max_history_size; + uint32_t idx2 = (metric->write_index - 2) % metric->max_history_size; + uint32_t idx3 = (metric->write_index - 3) % metric->max_history_size; + + float val1 = metric->history[idx1].value.f32; + float val2 = metric->history[idx2].value.f32; + float val3 = metric->history[idx3].value.f32; + + // Calculate variance from baseline + float avg_recent = (val1 + val2 + val3) / 3.0f; + float deviation = fabs(avg_recent - ctx->baseline); + + if (deviation > ctx->baseline * ctx->threshold_multiplier) { + ctx->consecutive_violations++; + printf(" [PATTERN] High deviation %.2f from baseline %.2f (violation %d/%d)\n", + deviation, ctx->baseline, ctx->consecutive_violations, ctx->max_violations); + + if (ctx->consecutive_violations >= ctx->max_violations) { + printf(" [ALERT] PATTERN ALERT: Sustained anomalous behavior detected!\n"); + ctx->consecutive_violations = 0; // Reset counter + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + } else { + ctx->consecutive_violations = 0; // Reset on normal reading + } + + return EMBEDIDS_OK; +} + +// Custom algorithm: Rate-of-change detector +embedids_result_t rate_change_detector(const embedids_metric_t* metric, + const void* config, + void* context) { + (void)context; // Unused parameter + + if (metric->current_size < 2) { + return EMBEDIDS_OK; + } + + // Get last two data points + uint32_t idx1 = (metric->write_index - 1) % metric->max_history_size; + uint32_t idx2 = (metric->write_index - 2) % metric->max_history_size; + + float val1 = metric->history[idx1].value.f32; + float val2 = metric->history[idx2].value.f32; + uint64_t time1 = metric->history[idx1].timestamp_ms; + uint64_t time2 = metric->history[idx2].timestamp_ms; + + if (time1 == time2) return EMBEDIDS_OK; // Avoid division by zero + + float rate = fabs(val1 - val2) / ((float)(time1 - time2) / 1000.0f); // per second + float max_rate = *((float*)config); // Config holds max allowed rate + + if (rate > max_rate) { + printf(" [RATE] Rapid change %.2f/s (max: %.2f/s)\n", rate, max_rate); + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + + return EMBEDIDS_OK; +} + +// Simulate realistic sensor data with occasional anomalies +float simulate_cpu_usage(int iteration) { + float base = 20.0f + 15.0f * sin(iteration * 0.3f); // Normal oscillation + float noise = ((float)rand() / RAND_MAX - 0.5f) * 10.0f; + + // Inject anomalies + if (iteration == 8) return 95.0f; // Sudden spike + if (iteration >= 12 && iteration <= 14) return 5.0f; // Sustained low + if (iteration == 18) return 98.0f; // Another spike + + return fmax(0.0f, fmin(100.0f, base + noise)); +} + +float simulate_memory_pressure(int iteration) { + float base = 40.0f + 20.0f * cos(iteration * 0.2f); + float noise = ((float)rand() / RAND_MAX - 0.5f) * 8.0f; + + // Memory leak simulation + if (iteration >= 15) base += (iteration - 15) * 2.0f; + + return fmax(0.0f, fmin(100.0f, base + noise)); +} + +float simulate_network_traffic(int iteration) { + float base = 1000.0f + 500.0f * sin(iteration * 0.4f); + float noise = ((float)rand() / RAND_MAX - 0.5f) * 200.0f; + + // DDoS simulation + if (iteration >= 10 && iteration <= 12) return base * 5.0f; + + return fmax(0.0f, base + noise); +} + +int main(void) { + printf("EmbedIDS Extensible Architecture Example\n"); + printf("========================================\n"); + printf("Version: %s\n", embedids_get_version()); + printf("Demonstrating: Custom algorithms, user-managed memory, multiple metrics\n\n"); + + srand((unsigned int)time(NULL)); + + // User-managed memory for metric histories + static embedids_metric_datapoint_t cpu_history[50]; + static embedids_metric_datapoint_t memory_history[50]; + static embedids_metric_datapoint_t network_history[30]; + + // Custom algorithm contexts + pattern_detector_context_t cpu_pattern_ctx = { + .baseline = 35.0f, + .threshold_multiplier = 0.8f, + .consecutive_violations = 0, + .max_violations = 3 + }; + + pattern_detector_context_t memory_pattern_ctx = { + .baseline = 50.0f, + .threshold_multiplier = 0.6f, + .consecutive_violations = 0, + .max_violations = 2 + }; + + float cpu_rate_limit = 20.0f; // Max 20% change per second + float network_rate_limit = 1000.0f; // Max 1000 packets/s change rate + + // ===== CPU Metric Configuration ===== + embedids_metric_t cpu_metric; + memset(&cpu_metric, 0, sizeof(cpu_metric)); + strncpy(cpu_metric.name, "cpu_usage", EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + cpu_metric.type = EMBEDIDS_METRIC_TYPE_PERCENTAGE; + cpu_metric.history = cpu_history; + cpu_metric.max_history_size = 50; + cpu_metric.enabled = true; + + // CPU algorithms: threshold + pattern detection + rate limiting + embedids_algorithm_t cpu_algorithms[3]; + memset(cpu_algorithms, 0, sizeof(cpu_algorithms)); + + // Threshold algorithm + cpu_algorithms[0].type = EMBEDIDS_ALGORITHM_THRESHOLD; + cpu_algorithms[0].enabled = true; + cpu_algorithms[0].config.threshold.max_threshold.f32 = 85.0f; + cpu_algorithms[0].config.threshold.check_max = true; + cpu_algorithms[0].config.threshold.check_min = false; + + // Custom pattern detector + cpu_algorithms[1].type = EMBEDIDS_ALGORITHM_CUSTOM; + cpu_algorithms[1].enabled = true; + cpu_algorithms[1].config.custom.function = advanced_pattern_detector; + cpu_algorithms[1].config.custom.config = NULL; + cpu_algorithms[1].config.custom.context = &cpu_pattern_ctx; + + // Custom rate detector + cpu_algorithms[2].type = EMBEDIDS_ALGORITHM_CUSTOM; + cpu_algorithms[2].enabled = true; + cpu_algorithms[2].config.custom.function = rate_change_detector; + cpu_algorithms[2].config.custom.config = &cpu_rate_limit; + cpu_algorithms[2].config.custom.context = NULL; + + // ===== Memory Metric Configuration ===== + embedids_metric_t memory_metric; + memset(&memory_metric, 0, sizeof(memory_metric)); + strncpy(memory_metric.name, "memory_pressure", EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + memory_metric.type = EMBEDIDS_METRIC_TYPE_PERCENTAGE; + memory_metric.history = memory_history; + memory_metric.max_history_size = 50; + memory_metric.enabled = true; + + // Memory algorithms: trend + pattern detection + embedids_algorithm_t memory_algorithms[2]; + memset(memory_algorithms, 0, sizeof(memory_algorithms)); + + // Trend algorithm + memory_algorithms[0].type = EMBEDIDS_ALGORITHM_TREND; + memory_algorithms[0].enabled = true; + memory_algorithms[0].config.trend.window_size = 5; + memory_algorithms[0].config.trend.max_slope = 15.0f; + + // Custom pattern detector + memory_algorithms[1].type = EMBEDIDS_ALGORITHM_CUSTOM; + memory_algorithms[1].enabled = true; + memory_algorithms[1].config.custom.function = advanced_pattern_detector; + memory_algorithms[1].config.custom.config = NULL; + memory_algorithms[1].config.custom.context = &memory_pattern_ctx; + + // ===== Network Metric Configuration ===== + embedids_metric_t network_metric; + memset(&network_metric, 0, sizeof(network_metric)); + strncpy(network_metric.name, "network_packets", EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + network_metric.type = EMBEDIDS_METRIC_TYPE_FLOAT; + network_metric.history = network_history; + network_metric.max_history_size = 30; + network_metric.enabled = true; + + // Network algorithms: threshold + rate detection + embedids_algorithm_t network_algorithms[2]; + memset(network_algorithms, 0, sizeof(network_algorithms)); + + // Threshold algorithm + network_algorithms[0].type = EMBEDIDS_ALGORITHM_THRESHOLD; + network_algorithms[0].enabled = true; + network_algorithms[0].config.threshold.max_threshold.f32 = 3000.0f; + network_algorithms[0].config.threshold.check_max = true; + network_algorithms[0].config.threshold.check_min = false; + + // Rate detector + network_algorithms[1].type = EMBEDIDS_ALGORITHM_CUSTOM; + network_algorithms[1].enabled = true; + network_algorithms[1].config.custom.function = rate_change_detector; + network_algorithms[1].config.custom.config = &network_rate_limit; + network_algorithms[1].config.custom.context = NULL; + + // ===== System Configuration ===== + embedids_metric_config_t metric_configs[3]; + + // CPU metric config + metric_configs[0].metric = cpu_metric; + memcpy(metric_configs[0].algorithms, cpu_algorithms, sizeof(cpu_algorithms)); + metric_configs[0].num_algorithms = 3; + + // Memory metric config + metric_configs[1].metric = memory_metric; + memcpy(metric_configs[1].algorithms, memory_algorithms, sizeof(memory_algorithms)); + metric_configs[1].num_algorithms = 2; + + // Network metric config + metric_configs[2].metric = network_metric; + memcpy(metric_configs[2].algorithms, network_algorithms, sizeof(network_algorithms)); + metric_configs[2].num_algorithms = 2; + + embedids_context_t context; + memset(&context, 0, sizeof(context)); + + embedids_system_config_t system_config; + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = metric_configs; + system_config.max_metrics = 3; + system_config.num_active_metrics = 3; + + // Initialize EmbedIDS + embedids_result_t result = embedids_init(&context, &system_config); + if (result != EMBEDIDS_OK) { + printf("ERROR: Failed to initialize EmbedIDS: %d\n", result); + return 1; + } + + printf("OK: EmbedIDS initialized with extensible architecture\n"); + printf("INFO: Monitoring 3 metrics with 7 total algorithms:\n"); + printf(" * CPU: Threshold + Pattern Detection + Rate Limiting\n"); + printf(" * Memory: Trend Analysis + Pattern Detection\n"); + printf(" * Network: Threshold + Rate Detection\n\n"); + + printf("Starting advanced monitoring simulation...\n"); + printf("============================================================\n"); + printf("\n"); + + // Monitoring simulation + for (int i = 1; i <= 20; i++) { + printf("--- Iteration %d ---\n", i); + + // Generate realistic sensor data + float cpu = simulate_cpu_usage(i); + float memory = simulate_memory_pressure(i); + float network = simulate_network_traffic(i); + + printf("Data: CPU=%.1f%%, Memory=%.1f%%, Network=%.0f pkt/s\n", + cpu, memory, network); + + // Add data points + embedids_metric_value_t cpu_val = {.f32 = cpu}; + embedids_metric_value_t memory_val = {.f32 = memory}; + embedids_metric_value_t network_val = {.f32 = network}; + + uint64_t timestamp = (uint64_t)time(NULL) * 1000 + i * 100; // Simulate 100ms intervals + + embedids_add_datapoint(&context, "cpu_usage", cpu_val, timestamp); + embedids_add_datapoint(&context, "memory_pressure", memory_val, timestamp); + embedids_add_datapoint(&context, "network_packets", network_val, timestamp); + + // Analyze each metric individually to see which algorithms trigger + printf("Analysis results:\n"); + + // Analyze CPU + embedids_result_t cpu_result = embedids_analyze_metric(&context, "cpu_usage"); + if (cpu_result == EMBEDIDS_OK) { + printf(" OK: CPU: All algorithms passed\n"); + } else { + printf(" ALERT: CPU: Anomaly detected by algorithm(s)\n"); + } + + // Analyze Memory + embedids_result_t memory_result = embedids_analyze_metric(&context, "memory_pressure"); + if (memory_result == EMBEDIDS_OK) { + printf(" OK: Memory: All algorithms passed\n"); + } else { + printf(" ALERT: Memory: Anomaly detected by algorithm(s)\n"); + } + + // Analyze Network + embedids_result_t network_result = embedids_analyze_metric(&context, "network_packets"); + if (network_result == EMBEDIDS_OK) { + printf(" OK: Network: All algorithms passed\n"); + } else { + printf(" ALERT: Network: Anomaly detected by algorithm(s)\n"); + } + + // Overall system status + embedids_result_t overall_result = embedids_analyze_all(&context); + if (overall_result == EMBEDIDS_OK) { + printf("SYSTEM STATUS: SECURE\n"); + } else { + printf("SYSTEM STATUS: THREAT DETECTED\n"); + } + + printf("\n"); + usleep(500000); // 500ms delay for dramatic effect + } + + printf("============================================================\n"); + printf("\n"); + printf("Advanced monitoring simulation completed!\n"); + printf("Summary:\n"); + printf(" * Demonstrated 2 custom algorithm types\n"); + printf(" * Used user-managed memory (%.1fKB total)\n", + (sizeof(cpu_history) + sizeof(memory_history) + sizeof(network_history)) / 1024.0f); + printf(" * Combined multiple detection strategies per metric\n"); + printf(" * Showed extensible architecture flexibility\n"); + + // Cleanup + embedids_cleanup(&context); + printf("EmbedIDS cleaned up successfully.\n"); + + return 0; +} diff --git a/examples/simple_example.c b/examples/simple_example.c new file mode 100644 index 0000000..ca3b90f --- /dev/null +++ b/examples/simple_example.c @@ -0,0 +1,175 @@ +#include +#include +#include +#include +#include +#include + +/** + * @brief Simple example demonstrating EmbedIDS usage with modern API + */ + +#define MAX_HISTORY_SIZE 10 + +// Static buffers for metrics (user-managed memory) +static embedids_metric_datapoint_t cpu_history[MAX_HISTORY_SIZE]; +static embedids_metric_datapoint_t memory_history[MAX_HISTORY_SIZE]; +static embedids_metric_datapoint_t network_history[MAX_HISTORY_SIZE]; + +int main(void) { + printf("EmbedIDS Example v%s\n", embedids_get_version()); + printf("====================\n\n"); + + // Define metrics with thresholds using new extensible API + embedids_metric_config_t metrics[3]; + + // CPU Usage Metric + metrics[0] = (embedids_metric_config_t){ + .metric = { + .type = EMBEDIDS_METRIC_TYPE_PERCENTAGE, + .history = cpu_history, + .max_history_size = MAX_HISTORY_SIZE, + .current_size = 0, + .write_index = 0, + .enabled = true + }, + .num_algorithms = 1 + }; + strncpy(metrics[0].metric.name, "cpu_usage", EMBEDIDS_MAX_METRIC_NAME_LEN); + metrics[0].algorithms[0] = (embedids_algorithm_t){ + .type = EMBEDIDS_ALGORITHM_THRESHOLD, + .enabled = true, + .config.threshold = { + .max_threshold = { .f32 = 75.0f }, // Alert if CPU > 75% + .check_max = true, + .check_min = false + } + }; + + // Memory Usage Metric + metrics[1] = (embedids_metric_config_t){ + .metric = { + .type = EMBEDIDS_METRIC_TYPE_UINT32, + .history = memory_history, + .max_history_size = MAX_HISTORY_SIZE, + .current_size = 0, + .write_index = 0, + .enabled = true + }, + .num_algorithms = 1 + }; + strncpy(metrics[1].metric.name, "memory_usage", EMBEDIDS_MAX_METRIC_NAME_LEN); + metrics[1].algorithms[0] = (embedids_algorithm_t){ + .type = EMBEDIDS_ALGORITHM_THRESHOLD, + .enabled = true, + .config.threshold = { + .max_threshold = { .u32 = 512000 }, // Alert if memory > 500MB + .check_max = true, + .check_min = false + } + }; + + // Network Packets Metric + metrics[2] = (embedids_metric_config_t){ + .metric = { + .type = EMBEDIDS_METRIC_TYPE_RATE, + .history = network_history, + .max_history_size = MAX_HISTORY_SIZE, + .current_size = 0, + .write_index = 0, + .enabled = true + }, + .num_algorithms = 1 + }; + strncpy(metrics[2].metric.name, "network_packets", EMBEDIDS_MAX_METRIC_NAME_LEN); + metrics[2].algorithms[0] = (embedids_algorithm_t){ + .type = EMBEDIDS_ALGORITHM_THRESHOLD, + .enabled = true, + .config.threshold = { + .max_threshold = { .u32 = 800 }, // Alert if > 800 packets/sec + .check_max = true, + .check_min = false + } + }; + + // Initialize the intrusion detection system + embedids_context_t context; + memset(&context, 0, sizeof(context)); + + embedids_system_config_t system_config = { + .metrics = metrics, + .max_metrics = 3, + .num_active_metrics = 3, + .user_context = NULL + }; + + embedids_result_t result = embedids_init(&context, &system_config); + if (result != EMBEDIDS_OK) { + printf("Error: Failed to initialize EmbedIDS (code: %d)\n", result); + return EXIT_FAILURE; + } + + printf("EmbedIDS initialized successfully!\n"); + printf("Monitoring system with 3 metrics:\n"); + printf(" - CPU Usage: max 75%%\n"); + printf(" - Memory Usage: max 500 MB\n"); + printf(" - Network Packets: max 800/sec\n"); + printf("\nStarting monitoring simulation...\n\n"); + + // Simulate monitoring for 10 iterations + for (int i = 0; i < 10; i++) { + uint64_t timestamp = (uint64_t)time(NULL) * 1000 + i * 1000; + + // Simulate CPU usage + embedids_metric_value_t cpu_value = { .f32 = (float)(rand() % 100) }; + result = embedids_add_datapoint(&context, "cpu_usage", cpu_value, timestamp); + if (result != EMBEDIDS_OK) { + printf("Error adding CPU datapoint: %d\n", result); + } + + // Simulate memory usage + embedids_metric_value_t memory_value = { .u32 = 200000 + (rand() % 600000) }; + result = embedids_add_datapoint(&context, "memory_usage", memory_value, timestamp); + if (result != EMBEDIDS_OK) { + printf("Error adding memory datapoint: %d\n", result); + } + + // Simulate network packets + embedids_metric_value_t network_value = { .u32 = 100 + (rand() % 900) }; + result = embedids_add_datapoint(&context, "network_packets", network_value, timestamp); + if (result != EMBEDIDS_OK) { + printf("Error adding network datapoint: %d\n", result); + } + + printf("Iteration %d:\n", i + 1); + printf(" CPU: %.0f%%, Memory: %u KB, Network: %u pkt/s\n", + cpu_value.f32, memory_value.u32, network_value.u32); + + // Analyze all metrics for potential intrusions + result = embedids_analyze_all(&context); + switch (result) { + case EMBEDIDS_OK: + printf(" Status: NORMAL - All metrics within acceptable ranges\n"); + break; + case EMBEDIDS_ERROR_THRESHOLD_EXCEEDED: + printf(" Status: ALERT - Potential intrusion detected! Threshold exceeded.\n"); + break; + default: + printf(" Status: ERROR - Analysis failed (code: %d)\n", result); + break; + } + + printf("\n"); + + // Sleep for 1 second between iterations + sleep(1); + } + + printf("Monitoring simulation completed.\n"); + + // Cleanup + embedids_cleanup(&context); + printf("EmbedIDS cleaned up successfully.\n"); + + return EXIT_SUCCESS; +} diff --git a/examples/tutorial_example.c b/examples/tutorial_example.c new file mode 100644 index 0000000..2197bfc --- /dev/null +++ b/examples/tutorial_example.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include + +int main() { + // 1. Allocate memory for metric history + static embedids_metric_datapoint_t cpu_history[50]; + + // 2. Configure CPU metric + embedids_metric_t cpu_metric; + memset(&cpu_metric, 0, sizeof(cpu_metric)); + strncpy(cpu_metric.name, "cpu_usage", EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + cpu_metric.type = EMBEDIDS_METRIC_TYPE_PERCENTAGE; + cpu_metric.history = cpu_history; + cpu_metric.max_history_size = 50; + cpu_metric.enabled = true; + + // 3. Configure threshold algorithm (alert if CPU > 80%) + embedids_algorithm_t threshold_algo; + memset(&threshold_algo, 0, sizeof(threshold_algo)); + threshold_algo.type = EMBEDIDS_ALGORITHM_THRESHOLD; + threshold_algo.enabled = true; + threshold_algo.config.threshold.max_threshold.f32 = 80.0f; + threshold_algo.config.threshold.check_max = true; + + // 4. Create metric configuration + embedids_metric_config_t metric_config; + memset(&metric_config, 0, sizeof(metric_config)); + metric_config.metric = cpu_metric; + metric_config.algorithms[0] = threshold_algo; + metric_config.num_algorithms = 1; + + // 5. Create system configuration + embedids_system_config_t system_config; + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = &metric_config; + system_config.max_metrics = 1; + system_config.num_active_metrics = 1; + + // 6. Initialize EmbedIDS context and system + embedids_context_t context; + memset(&context, 0, sizeof(context)); + + if (embedids_init(&context, &system_config) != EMBEDIDS_OK) { + printf("Failed to initialize EmbedIDS\n"); + return 1; + } + + printf("CPU Monitor Started (threshold: 80%%)\n\n"); + + // 7. Monitoring loop + for (int i = 0; i < 10; i++) { + // Simulate CPU usage (gradually increasing) + float cpu = 30.0f + (i * 8.0f); + + // Add data point + embedids_metric_value_t value = {.f32 = cpu}; + embedids_add_datapoint(&context, "cpu_usage", value, (uint64_t)time(NULL) * 1000); + + // Analyze metric to detect anomalies + if (embedids_analyze_metric(&context, "cpu_usage") == EMBEDIDS_OK) { + printf("OK CPU: %.1f%% - Normal\n", cpu); + } else { + printf("ALERT CPU: %.1f%% - THRESHOLD EXCEEDED!\n", cpu); + } + + sleep(1); + } + + embedids_cleanup(&context); + return 0; +} diff --git a/examples/user_application.c b/examples/user_application.c new file mode 100644 index 0000000..5b683bb --- /dev/null +++ b/examples/user_application.c @@ -0,0 +1,291 @@ +/** + * @file user_application.c + * @brief Example showing how a user integrates EmbedIDS into their IoT application + * + * This example demonstrates real-world integration patterns: + * 1. Custom metrics for specific IoT device monitoring + * 2. Multiple detection algorithms per metric + * 3. User-managed memory for embedded constraints + * 4. Integration with existing monitoring systems + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DEVICE_HISTORY_SIZE 20 +#define NUM_DEVICE_METRICS 4 + +// User-managed memory buffers for IoT device metrics +static embedids_metric_datapoint_t temperature_history[DEVICE_HISTORY_SIZE]; +static embedids_metric_datapoint_t humidity_history[DEVICE_HISTORY_SIZE]; +static embedids_metric_datapoint_t power_history[DEVICE_HISTORY_SIZE]; +static embedids_metric_datapoint_t connection_history[DEVICE_HISTORY_SIZE]; + +// Application-specific data structure +typedef struct { + float temperature_celsius; + float humidity_percent; + float power_consumption_watts; + uint32_t active_connections; + bool device_tampered; + uint64_t uptime_seconds; +} iot_device_status_t; + +// Custom algorithm: Detect device tampering based on rapid environmental changes +embedids_result_t tampering_detector(const embedids_metric_t* metric, + const void* config, + void* context) { + (void)config; // Unused + (void)context; // Unused + + // Need at least 5 data points for tampering detection + if (metric->current_size < 5) { + return EMBEDIDS_OK; + } + + // Check for rapid temperature or humidity changes that might indicate tampering + uint32_t latest = (metric->write_index > 0) ? metric->write_index - 1 : metric->max_history_size - 1; + uint32_t prev = (latest > 0) ? latest - 1 : metric->max_history_size - 1; + + float current_val = metric->history[latest].value.f32; + float prev_val = metric->history[prev].value.f32; + + // Alert if temperature changes by more than 15C or humidity by 30% in one reading + if (strncmp(metric->name, "temperature", 11) == 0) { + if (fabs(current_val - prev_val) > 15.0f) { + printf("TAMPERING ALERT: Rapid temperature change detected (%.1fC -> %.1fC)\n", + prev_val, current_val); + return EMBEDIDS_ERROR_CUSTOM_DETECTION; + } + } else if (strncmp(metric->name, "humidity", 8) == 0) { + if (fabs(current_val - prev_val) > 30.0f) { + printf("TAMPERING ALERT: Rapid humidity change detected (%.1f%% -> %.1f%%)\n", + prev_val, current_val); + return EMBEDIDS_ERROR_CUSTOM_DETECTION; + } + } + + return EMBEDIDS_OK; +} + +// Simulate reading real IoT device metrics +iot_device_status_t read_device_status(int iteration) { + iot_device_status_t status; + + // Simulate normal operation with occasional anomalies + status.temperature_celsius = 22.0f + (rand() % 10) - 5 + + (iteration > 5 && iteration < 8 ? 20.0f : 0.0f); // Simulate heating attack + status.humidity_percent = 45.0f + (rand() % 20); + status.power_consumption_watts = 5.0f + (rand() % 3); + status.active_connections = 1 + (rand() % (iteration > 8 ? 10 : 3)); // Simulate connection flood + status.device_tampered = false; + status.uptime_seconds = iteration * 60; // Simulate 1 minute per iteration + + return status; +} + +int main(void) { + printf("IoT Device Security Monitor v%s\n", embedids_get_version()); + printf("=====================================\n\n"); + + // Configure metrics for IoT device monitoring + embedids_metric_config_t device_metrics[NUM_DEVICE_METRICS]; + + // Temperature Monitoring (with tampering detection) + device_metrics[0] = (embedids_metric_config_t) { + .metric = { + .type = EMBEDIDS_METRIC_TYPE_FLOAT, + .history = temperature_history, + .max_history_size = DEVICE_HISTORY_SIZE, + .current_size = 0, + .write_index = 0, + .enabled = true + }, + .num_algorithms = 2 + }; + strncpy(device_metrics[0].metric.name, "temperature", EMBEDIDS_MAX_METRIC_NAME_LEN); + + // Threshold algorithm for temperature + device_metrics[0].algorithms[0] = (embedids_algorithm_t) { + .type = EMBEDIDS_ALGORITHM_THRESHOLD, + .enabled = true, + .config.threshold = { + .min_threshold = { .f32 = 0.0f }, // Alert if temp < 0C + .max_threshold = { .f32 = 40.0f }, // Alert if temp > 40C + .check_max = true, + .check_min = true + } + }; + + // Custom tampering detection algorithm + device_metrics[0].algorithms[1] = (embedids_algorithm_t) { + .type = EMBEDIDS_ALGORITHM_CUSTOM, + .enabled = true, + .config.custom = { + .function = tampering_detector, + .config = NULL, + .context = NULL + } + }; + + // Humidity Monitoring (with tampering detection) + device_metrics[1] = (embedids_metric_config_t) { + .metric = { + .type = EMBEDIDS_METRIC_TYPE_FLOAT, + .history = humidity_history, + .max_history_size = DEVICE_HISTORY_SIZE, + .current_size = 0, + .write_index = 0, + .enabled = true + }, + .num_algorithms = 2 + }; + strncpy(device_metrics[1].metric.name, "humidity", EMBEDIDS_MAX_METRIC_NAME_LEN); + + device_metrics[1].algorithms[0] = (embedids_algorithm_t) { + .type = EMBEDIDS_ALGORITHM_THRESHOLD, + .enabled = true, + .config.threshold = { + .min_threshold = { .f32 = 10.0f }, // Alert if humidity < 10% + .max_threshold = { .f32 = 90.0f }, // Alert if humidity > 90% + .check_max = true, + .check_min = true + } + }; + + device_metrics[1].algorithms[1] = (embedids_algorithm_t) { + .type = EMBEDIDS_ALGORITHM_CUSTOM, + .enabled = true, + .config.custom = { + .function = tampering_detector, + .config = NULL, + .context = NULL + } + }; + + // Power Consumption Monitoring + device_metrics[2] = (embedids_metric_config_t) { + .metric = { + .type = EMBEDIDS_METRIC_TYPE_FLOAT, + .history = power_history, + .max_history_size = DEVICE_HISTORY_SIZE, + .current_size = 0, + .write_index = 0, + .enabled = true + }, + .num_algorithms = 1 + }; + strncpy(device_metrics[2].metric.name, "power", EMBEDIDS_MAX_METRIC_NAME_LEN); + + device_metrics[2].algorithms[0] = (embedids_algorithm_t) { + .type = EMBEDIDS_ALGORITHM_THRESHOLD, + .enabled = true, + .config.threshold = { + .max_threshold = { .f32 = 15.0f }, // Alert if power > 15W (possible crypto mining) + .check_max = true, + .check_min = false + } + }; + + // Network Connection Monitoring + device_metrics[3] = (embedids_metric_config_t) { + .metric = { + .type = EMBEDIDS_METRIC_TYPE_UINT32, + .history = connection_history, + .max_history_size = DEVICE_HISTORY_SIZE, + .current_size = 0, + .write_index = 0, + .enabled = true + }, + .num_algorithms = 1 + }; + strncpy(device_metrics[3].metric.name, "connections", EMBEDIDS_MAX_METRIC_NAME_LEN); + + device_metrics[3].algorithms[0] = (embedids_algorithm_t) { + .type = EMBEDIDS_ALGORITHM_THRESHOLD, + .enabled = true, + .config.threshold = { + .max_threshold = { .u32 = 5 }, // Alert if > 5 connections (DDoS/scanning) + .check_max = true, + .check_min = false + } + }; + + // Initialize EmbedIDS + embedids_context_t context; + memset(&context, 0, sizeof(context)); + + embedids_system_config_t system_config = { + .metrics = device_metrics, + .max_metrics = NUM_DEVICE_METRICS, + .num_active_metrics = NUM_DEVICE_METRICS, + .user_context = NULL + }; + + embedids_result_t result = embedids_init(&context, &system_config); + if (result != EMBEDIDS_OK) { + printf("Failed to initialize EmbedIDS security monitoring: %d\n", result); + return EXIT_FAILURE; + } + + printf("IoT Security monitoring initialized successfully!\n"); + printf("Monitoring %d metrics with user-managed memory\n", NUM_DEVICE_METRICS); + printf("Memory footprint: %zu bytes\n\n", + sizeof(temperature_history) + sizeof(humidity_history) + + sizeof(power_history) + sizeof(connection_history)); + + // Main monitoring loop + for (int iteration = 1; iteration <= 12; iteration++) { + printf("--- Device Status Check %d ---\n", iteration); + + // Read current device status + iot_device_status_t status = read_device_status(iteration); + uint64_t timestamp = (uint64_t)time(NULL) * 1000 + iteration * 60000; // 1 minute intervals + + // Add metrics to EmbedIDS + embedids_metric_value_t temp_val = { .f32 = status.temperature_celsius }; + embedids_metric_value_t humidity_val = { .f32 = status.humidity_percent }; + embedids_metric_value_t power_val = { .f32 = status.power_consumption_watts }; + embedids_metric_value_t conn_val = { .u32 = status.active_connections }; + + embedids_add_datapoint(&context, "temperature", temp_val, timestamp); + embedids_add_datapoint(&context, "humidity", humidity_val, timestamp); + embedids_add_datapoint(&context, "power", power_val, timestamp); + embedids_add_datapoint(&context, "connections", conn_val, timestamp); + + printf("Temp: %.1fC, Humidity: %.1f%%, Power: %.1fW, Connections: %u\n", + status.temperature_celsius, status.humidity_percent, + status.power_consumption_watts, status.active_connections); + + // Analyze for security threats + result = embedids_analyze_all(&context); + switch (result) { + case EMBEDIDS_OK: + printf("OK: Device secure - all metrics normal\n"); + break; + case EMBEDIDS_ERROR_THRESHOLD_EXCEEDED: + printf("WARNING: Threshold exceeded - possible attack!\n"); + break; + case EMBEDIDS_ERROR_CUSTOM_DETECTION: + printf("BREACH: Custom algorithm detected tampering!\n"); + break; + default: + printf("ERROR: Security analysis error: %d\n", result); + break; + } + + printf("\n"); + sleep(1); + } + + printf("Monitoring session completed.\n"); + printf("IoT device security monitoring has been stopped.\n"); + + embedids_cleanup(&context); + return EXIT_SUCCESS; +} diff --git a/include/embedids.h.in b/include/embedids.h.in new file mode 100644 index 0000000..631fcd6 --- /dev/null +++ b/include/embedids.h.in @@ -0,0 +1,377 @@ +/* + * Copyright 2025 Seyed Amir Alavi and Mahyar Abbaspour + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EMBEDIDS_H +#define EMBEDIDS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief EmbedIDS library version information + */ +#define EMBEDIDS_VERSION_MAJOR @PROJECT_VERSION_MAJOR @ +#define EMBEDIDS_VERSION_MINOR @PROJECT_VERSION_MINOR @ +#define EMBEDIDS_VERSION_PATCH @PROJECT_VERSION_PATCH @ +#define EMBEDIDS_VERSION_STRING "@PROJECT_VERSION@" + +/** + * @brief Compile-time configuration options + */ +#ifndef EMBEDIDS_MAX_METRICS +#define EMBEDIDS_MAX_METRICS 32 +#endif + +#ifndef EMBEDIDS_MAX_METRIC_NAME_LEN +#define EMBEDIDS_MAX_METRIC_NAME_LEN 32 +#endif + +#ifndef EMBEDIDS_MAX_ALGORITHMS_PER_METRIC +#define EMBEDIDS_MAX_ALGORITHMS_PER_METRIC 4 +#endif + +#ifndef EMBEDIDS_ENABLE_FLOATING_POINT +#define EMBEDIDS_ENABLE_FLOATING_POINT 1 +#endif + +#ifndef EMBEDIDS_ENABLE_DOUBLE_PRECISION +#define EMBEDIDS_ENABLE_DOUBLE_PRECISION 0 // Disabled by default for embedded +#endif + +/** + * @brief Compile-time assertions for configuration validation + */ +#ifdef __cplusplus +static_assert(EMBEDIDS_MAX_METRICS > 0 && EMBEDIDS_MAX_METRICS <= 256, + "EMBEDIDS_MAX_METRICS must be between 1 and 256"); +static_assert(EMBEDIDS_MAX_METRIC_NAME_LEN >= 8 && + EMBEDIDS_MAX_METRIC_NAME_LEN <= 128, + "EMBEDIDS_MAX_METRIC_NAME_LEN must be between 8 and 128"); +static_assert(EMBEDIDS_MAX_ALGORITHMS_PER_METRIC > 0 && + EMBEDIDS_MAX_ALGORITHMS_PER_METRIC <= 8, + "EMBEDIDS_MAX_ALGORITHMS_PER_METRIC must be between 1 and 8"); +#else +_Static_assert(EMBEDIDS_MAX_METRICS > 0 && EMBEDIDS_MAX_METRICS <= 256, + "EMBEDIDS_MAX_METRICS must be between 1 and 256"); +_Static_assert(EMBEDIDS_MAX_METRIC_NAME_LEN >= 8 && + EMBEDIDS_MAX_METRIC_NAME_LEN <= 128, + "EMBEDIDS_MAX_METRIC_NAME_LEN must be between 8 and 128"); +_Static_assert(EMBEDIDS_MAX_ALGORITHMS_PER_METRIC > 0 && + EMBEDIDS_MAX_ALGORITHMS_PER_METRIC <= 8, + "EMBEDIDS_MAX_ALGORITHMS_PER_METRIC must be between 1 and 8"); +#endif + +/** + * @brief EmbedIDS - Embedded Intrusion Detection System + * + * A modern, extensible intrusion detection library for embedded systems + * featuring user-managed memory, custom metrics, and pluggable algorithms. + */ + +/** + * @brief Return codes for EmbedIDS functions + */ +typedef enum { + EMBEDIDS_OK = 0, + + // Configuration errors (1-10) + EMBEDIDS_ERROR_INVALID_PARAM = -1, + EMBEDIDS_ERROR_NOT_INITIALIZED = -2, + EMBEDIDS_ERROR_ALREADY_INITIALIZED = -3, + EMBEDIDS_ERROR_CONFIG_INVALID = -4, + + // Memory errors (11-20) + EMBEDIDS_ERROR_OUT_OF_MEMORY = -11, + EMBEDIDS_ERROR_BUFFER_FULL = -12, + EMBEDIDS_ERROR_BUFFER_CORRUPT = -13, + EMBEDIDS_ERROR_ALIGNMENT_ERROR = -14, + + // Metric errors (21-30) + EMBEDIDS_ERROR_METRIC_NOT_FOUND = -21, + EMBEDIDS_ERROR_METRIC_DISABLED = -22, + EMBEDIDS_ERROR_METRIC_TYPE_MISMATCH = -23, + EMBEDIDS_ERROR_METRIC_NAME_TOO_LONG = -24, + + // Algorithm errors (31-40) + EMBEDIDS_ERROR_ALGORITHM_FAILED = -31, + EMBEDIDS_ERROR_ALGORITHM_NOT_SUPPORTED = -32, + EMBEDIDS_ERROR_CUSTOM_ALGORITHM_NULL = -33, + + // Detection results (41-50) + EMBEDIDS_ERROR_THRESHOLD_EXCEEDED = -41, + EMBEDIDS_ERROR_TREND_ANOMALY = -42, + EMBEDIDS_ERROR_CUSTOM_DETECTION = -43, + EMBEDIDS_ERROR_STATISTICAL_ANOMALY = -44, + + // System errors (51-60) + EMBEDIDS_ERROR_TIMEOUT = -51, + EMBEDIDS_ERROR_HARDWARE_FAULT = -52, + EMBEDIDS_ERROR_TIMESTAMP_INVALID = -53, + EMBEDIDS_ERROR_THREAD_UNSAFE = -54 +} embedids_result_t; + +/** + * @brief Metric data types + */ +typedef enum { + EMBEDIDS_METRIC_TYPE_UINT32, /**< 32-bit unsigned integer */ + EMBEDIDS_METRIC_TYPE_UINT64, /**< 64-bit unsigned integer */ +#if EMBEDIDS_ENABLE_FLOATING_POINT + EMBEDIDS_METRIC_TYPE_FLOAT, /**< 32-bit floating point */ +#if EMBEDIDS_ENABLE_DOUBLE_PRECISION + EMBEDIDS_METRIC_TYPE_DOUBLE, /**< 64-bit floating point */ +#endif + EMBEDIDS_METRIC_TYPE_PERCENTAGE, /**< Percentage (0.0 - 100.0) */ + EMBEDIDS_METRIC_TYPE_RATE, /**< Rate per second */ +#endif + EMBEDIDS_METRIC_TYPE_BOOL, /**< Boolean value */ + EMBEDIDS_METRIC_TYPE_ENUM /**< Enumerated value */ +} embedids_metric_type_t; + +/** + * @brief Detection algorithm types + */ +typedef enum { + EMBEDIDS_ALGORITHM_THRESHOLD, /**< Simple threshold-based detection */ + EMBEDIDS_ALGORITHM_TREND, /**< Trend analysis over time */ + EMBEDIDS_ALGORITHM_CUSTOM /**< User-provided algorithm */ +} embedids_algorithm_type_t; + +/** + * @brief Trend direction indicators + */ +typedef enum { + EMBEDIDS_TREND_STABLE, /**< No significant trend */ + EMBEDIDS_TREND_INCREASING, /**< Values are increasing */ + EMBEDIDS_TREND_DECREASING /**< Values are decreasing */ +} embedids_trend_t; + +/** + * @brief Union to hold different metric value types + */ +typedef union { + uint32_t u32; /**< 32-bit unsigned integer value */ + uint64_t u64; /**< 64-bit unsigned integer value */ +#if EMBEDIDS_ENABLE_FLOATING_POINT + float f32; /**< 32-bit floating point value */ +#if EMBEDIDS_ENABLE_DOUBLE_PRECISION + double f64; /**< 64-bit floating point value */ +#endif +#endif + bool boolean; /**< Boolean value */ + uint8_t enum_val; /**< Enumerated value (0-255) */ +} embedids_metric_value_t; + +/** + * @brief Time-stamped metric data point + * @note Packed structure for memory efficiency on embedded systems + */ +typedef struct __attribute__((packed)) { + uint64_t timestamp_ms; /**< Timestamp in milliseconds (aligned first) */ + embedids_metric_value_t value; /**< Metric value */ + uint16_t flags; /**< Status flags for validation */ + uint16_t reserved; /**< Reserved for future use */ +} embedids_metric_datapoint_t; + +/** + * @brief User-provided metric configuration + */ +typedef struct { + char name[EMBEDIDS_MAX_METRIC_NAME_LEN]; /**< Human-readable metric name */ + embedids_metric_type_t type; /**< Data type of the metric */ + embedids_metric_datapoint_t *history; /**< User-provided history buffer */ + uint32_t max_history_size; /**< Maximum points in history buffer */ + uint32_t current_size; /**< Current number of points in buffer */ + uint32_t write_index; /**< Next write position (circular buffer) */ + bool enabled; /**< Whether this metric is active */ +} embedids_metric_t; + +/** + * @brief Algorithm configuration for threshold-based detection + */ +typedef struct { + embedids_metric_value_t min_threshold; /**< Minimum acceptable value */ + embedids_metric_value_t max_threshold; /**< Maximum acceptable value */ + bool check_min; /**< Whether to check minimum threshold */ + bool check_max; /**< Whether to check maximum threshold */ +} embedids_threshold_config_t; + +/** + * @brief Algorithm configuration for trend analysis + */ +typedef struct { + uint32_t window_size; /**< Number of points for trend calculation */ + float max_slope; /**< Maximum acceptable slope (change/time) */ + float max_variance; /**< Maximum acceptable variance */ + embedids_trend_t expected_trend; /**< Expected trend direction */ +} embedids_trend_config_t; + +/** + * @brief Custom algorithm function signature + * @param metric Pointer to the metric being analyzed + * @param config User-provided algorithm configuration + * @param context User-provided algorithm context/state + * @return EMBEDIDS_OK if normal, error code if anomaly detected + */ +typedef embedids_result_t (*embedids_custom_algorithm_fn)( + const embedids_metric_t *metric, const void *config, void *context); + +/** + * @brief Detection algorithm configuration + */ +typedef struct { + embedids_algorithm_type_t type; /**< Algorithm type */ + bool enabled; /**< Whether this algorithm is active */ + + union { + embedids_threshold_config_t threshold; /**< Threshold algorithm config */ + embedids_trend_config_t trend; /**< Trend algorithm config */ + struct { + embedids_custom_algorithm_fn function; /**< Custom algorithm function */ + void *config; /**< Custom algorithm config */ + void *context; /**< Custom algorithm context */ + } custom; + } config; +} embedids_algorithm_t; + +/** + * @brief Complete metric configuration with algorithms + */ +typedef struct { + embedids_metric_t metric; /**< Base metric configuration */ + embedids_algorithm_t + algorithms[EMBEDIDS_MAX_ALGORITHMS_PER_METRIC]; /**< Detection algorithms + */ + uint32_t num_algorithms; /**< Number of active algorithms */ +} embedids_metric_config_t; + +/** + * @brief System configuration for EmbedIDS + */ +/** + * @brief Main system configuration for EmbedIDS + */ +typedef struct { + embedids_metric_config_t + *metrics; /**< User-provided array of metric configurations */ + uint32_t max_metrics; /**< Maximum number of metrics in the array */ + uint32_t num_active_metrics; /**< Number of currently active metrics */ + void *user_context; /**< User-provided context for callbacks */ +} embedids_system_config_t; + +/** + * @brief EmbedIDS context structure for stateless operation + */ +typedef struct { + bool initialized; + embedids_system_config_t *system_config; /**< System configuration */ +} embedids_context_t; + +/** + * @brief Initialize the EmbedIDS library with extensible configuration + * @param context Pointer to EmbedIDS context structure + * @param config Pointer to system configuration + * @return EMBEDIDS_OK on success, error code on failure + */ +embedids_result_t embedids_init(embedids_context_t *context, const embedids_system_config_t *config); + +/** + * @brief Cleanup and deinitialize the EmbedIDS library + * @param context Pointer to EmbedIDS context structure + */ +void embedids_cleanup(embedids_context_t *context); + +/** + * @brief Add a metric data point for analysis + * @param context Pointer to EmbedIDS context structure + * @param metric_name Name of the metric to update + * @param value New metric value + * @param timestamp_ms Timestamp for the data point + * @return EMBEDIDS_OK on success, error code on failure + */ +embedids_result_t embedids_add_datapoint(embedids_context_t *context, const char *metric_name, + embedids_metric_value_t value, + uint64_t timestamp_ms); + +/** + * @brief Analyze all active metrics for anomalies + * @param context Pointer to EmbedIDS context structure + * @return EMBEDIDS_OK if all normal, error code if anomaly detected + */ +embedids_result_t embedids_analyze_all(embedids_context_t *context); + +/** + * @brief Analyze a specific metric for anomalies + * @param context Pointer to EmbedIDS context structure + * @param metric_name Name of the metric to analyze + * @return EMBEDIDS_OK if normal, error code if anomaly detected + */ +embedids_result_t embedids_analyze_metric(embedids_context_t *context, const char *metric_name); + +/** + * @brief Get trend information for a metric + * @param context Pointer to EmbedIDS context structure + * @param metric_name Name of the metric + * @param trend Pointer to store trend information + * @return EMBEDIDS_OK on success, error code on failure + */ +embedids_result_t embedids_get_trend(embedids_context_t *context, const char *metric_name, + embedids_trend_t *trend); + +/** + * @brief Get the version string of the library + * @return Version string in format "major.minor.patch" + */ +const char *embedids_get_version(void); + +/** + * @brief Check if the library context is initialized + * @param context Pointer to EmbedIDS context structure + * @return true if initialized, false otherwise + */ +bool embedids_is_initialized(const embedids_context_t *context); + +/** + * @brief Validate system configuration before initialization + * @param config Pointer to system configuration to validate + * @return EMBEDIDS_OK if valid, error code indicating specific issue + */ +embedids_result_t +embedids_validate_config(const embedids_system_config_t *config); + +/** + * @brief Get human-readable error string + * @param error_code Error code to convert to string + * @return Pointer to static error string + */ +const char *embedids_get_error_string(embedids_result_t error_code); + +/** + * @brief Reset all metrics and clear history + * @param context Pointer to EmbedIDS context structure + * @return EMBEDIDS_OK on success, error code on failure + */ +embedids_result_t embedids_reset_all_metrics(embedids_context_t *context); + +#ifdef __cplusplus +} +#endif + +#endif /* EMBEDIDS_H */ diff --git a/mkdocs.yml b/mkdocs.yml index fa14994..00ea3a9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,8 +7,8 @@ theme: name: material custom_dir: docs/overrides language: en - logo: images/embedIDS_logo.png - favicon: images/favicon.png + logo: assets/images/embedIDS_logo.png + favicon: assets/images/favicon.png icon: repo: fontawesome/brands/github palette: @@ -37,7 +37,6 @@ theme: features: - navigation.sections - navigation.tabs - - navigation.sections - navigation.top - navigation.footer - header.autohide @@ -118,14 +117,16 @@ plugins: branch: main nav: - "index.md" + - "quickstart.md" + - "tutorial.md" - Blog: - blog/index.md extra_css: - - stylesheets/extra.css + - assets/stylesheets/extra.css extra_javascript: - - javascripts/extra.js + - assets/javascripts/extra.js copyright: Copyright © 2025 Seyed Amir Alavi and Mahyar Abbaspour – Change cookie settings diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..67ee5ce --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,20 @@ +# Create the EmbedIDS library +add_library(embedids + embedids.c +) + +# Include directories for the library +target_include_directories(embedids + PUBLIC + ${CMAKE_CURRENT_BINARY_DIR}/../include +) + +# Set library properties +set_target_properties(embedids PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + PUBLIC_HEADER "${CMAKE_CURRENT_BINARY_DIR}/../include/embedids.h" +) + +# Link any required libraries (none for this simple implementation) +# target_link_libraries(embedids PRIVATE pthread) diff --git a/src/embedids.c b/src/embedids.c new file mode 100644 index 0000000..f167c60 --- /dev/null +++ b/src/embedids.c @@ -0,0 +1,478 @@ +/* + * Copyright 2025 Seyed Amir Alavi and Mahyar Abbaspour + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "embedids.h" +#include +#include +#include +#include + +/* Helper function to find a metric by name */ +static embedids_metric_config_t *find_metric_config(const embedids_context_t *context, const char *metric_name) { + if (!context || !context->system_config) { + return NULL; + } + + for (uint32_t i = 0; i < context->system_config->num_active_metrics; + i++) { + embedids_metric_config_t *config = + &context->system_config->metrics[i]; + if (strncmp(config->metric.name, metric_name, + EMBEDIDS_MAX_METRIC_NAME_LEN) == 0) { + return config; + } + } + return NULL; +} + +/* Built-in threshold algorithm implementation */ +static embedids_result_t +run_threshold_algorithm(const embedids_metric_t *metric, + const embedids_threshold_config_t *config) { + if (metric->current_size == 0) { + return EMBEDIDS_OK; // No data to analyze + } + + // Get the most recent value + uint32_t latest_index = (metric->write_index > 0) + ? metric->write_index - 1 + : metric->max_history_size - 1; + embedids_metric_value_t latest_value = metric->history[latest_index].value; + + // Check thresholds based on metric type + switch (metric->type) { + case EMBEDIDS_METRIC_TYPE_UINT32: + if (config->check_min && latest_value.u32 < config->min_threshold.u32) { + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + if (config->check_max && latest_value.u32 > config->max_threshold.u32) { + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + break; + case EMBEDIDS_METRIC_TYPE_UINT64: + if (config->check_min && latest_value.u64 < config->min_threshold.u64) { + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + if (config->check_max && latest_value.u64 > config->max_threshold.u64) { + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + break; +#if EMBEDIDS_ENABLE_FLOATING_POINT + case EMBEDIDS_METRIC_TYPE_FLOAT: + case EMBEDIDS_METRIC_TYPE_PERCENTAGE: + case EMBEDIDS_METRIC_TYPE_RATE: + if (config->check_min && latest_value.f32 < config->min_threshold.f32) { + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + if (config->check_max && latest_value.f32 > config->max_threshold.f32) { + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + break; +#if EMBEDIDS_ENABLE_DOUBLE_PRECISION + case EMBEDIDS_METRIC_TYPE_DOUBLE: + if (config->check_min && latest_value.f64 < config->min_threshold.f64) { + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + if (config->check_max && latest_value.f64 > config->max_threshold.f64) { + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + break; +#endif +#endif + case EMBEDIDS_METRIC_TYPE_BOOL: + // Boolean metrics don't use threshold comparison + break; + case EMBEDIDS_METRIC_TYPE_ENUM: + // Enum metrics could use discrete value checking + if (config->check_min && + latest_value.enum_val < config->min_threshold.enum_val) { + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + if (config->check_max && + latest_value.enum_val > config->max_threshold.enum_val) { + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + break; + } + + return EMBEDIDS_OK; +} + +/* Built-in trend algorithm implementation */ +static embedids_result_t +run_trend_algorithm(const embedids_metric_t *metric, + const embedids_trend_config_t *config) { + if (metric->current_size < config->window_size || config->window_size < 2) { + return EMBEDIDS_OK; // Not enough data for trend analysis + } + + // TODO: Implement full trend analysis using linear regression + // This is a placeholder that needs proper implementation + (void)metric; // Suppress unused parameter warning + (void)config; // Suppress unused parameter warning + + return EMBEDIDS_OK; +} + +embedids_result_t embedids_init(embedids_context_t *context, const embedids_system_config_t *config) { + if (context == NULL || config == NULL) { + return EMBEDIDS_ERROR_INVALID_PARAM; + } + + context->system_config = (embedids_system_config_t *)config; + context->initialized = true; + + return EMBEDIDS_OK; +} + +embedids_result_t embedids_add_datapoint(embedids_context_t *context, const char *metric_name, + embedids_metric_value_t value, + uint64_t timestamp_ms) { + if (!context || !context->initialized) { + return EMBEDIDS_ERROR_NOT_INITIALIZED; + } + + if (metric_name == NULL) { + return EMBEDIDS_ERROR_INVALID_PARAM; + } + + embedids_metric_config_t *config = find_metric_config(context, metric_name); + if (config == NULL) { + return EMBEDIDS_ERROR_METRIC_NOT_FOUND; + } + + embedids_metric_t *metric = &config->metric; + if (!metric->enabled) { + return EMBEDIDS_ERROR_METRIC_DISABLED; + } + + if (metric->history == NULL) { + return EMBEDIDS_ERROR_INVALID_PARAM; + } + + // Add data point to circular buffer + metric->history[metric->write_index].value = value; + metric->history[metric->write_index].timestamp_ms = timestamp_ms; + + // Update buffer state + metric->write_index = (metric->write_index + 1) % metric->max_history_size; + if (metric->current_size < metric->max_history_size) { + metric->current_size++; + } + + return EMBEDIDS_OK; +} + +embedids_result_t embedids_analyze_all(embedids_context_t *context) { + if (!context || !context->initialized) { + return EMBEDIDS_ERROR_NOT_INITIALIZED; + } + + // Analyze all active metrics + for (uint32_t i = 0; i < context->system_config->num_active_metrics; + i++) { + embedids_metric_config_t *config = + &context->system_config->metrics[i]; + if (config->metric.enabled) { + embedids_result_t result = embedids_analyze_metric(context, config->metric.name); + if (result != EMBEDIDS_OK) { + return result; // Return first anomaly detected + } + } + } + + return EMBEDIDS_OK; +} + +embedids_result_t embedids_analyze_metric(embedids_context_t *context, const char *metric_name) { + if (!context || !context->initialized) { + return EMBEDIDS_ERROR_NOT_INITIALIZED; + } + + if (metric_name == NULL) { + return EMBEDIDS_ERROR_INVALID_PARAM; + } + + embedids_metric_config_t *config = find_metric_config(context, metric_name); + if (config == NULL) { + return EMBEDIDS_ERROR_METRIC_NOT_FOUND; + } + + if (!config->metric.enabled) { + return EMBEDIDS_ERROR_METRIC_DISABLED; + } + + // Run all algorithms for this metric + for (uint32_t i = 0; i < config->num_algorithms; i++) { + embedids_algorithm_t *algorithm = &config->algorithms[i]; + if (!algorithm->enabled) { + continue; + } + + embedids_result_t result = EMBEDIDS_OK; + + switch (algorithm->type) { + case EMBEDIDS_ALGORITHM_THRESHOLD: + result = run_threshold_algorithm(&config->metric, + &algorithm->config.threshold); + break; + case EMBEDIDS_ALGORITHM_TREND: + result = run_trend_algorithm(&config->metric, &algorithm->config.trend); + break; + case EMBEDIDS_ALGORITHM_CUSTOM: + if (algorithm->config.custom.function) { + result = algorithm->config.custom.function( + &config->metric, algorithm->config.custom.config, + algorithm->config.custom.context); + } + break; + } + + if (result != EMBEDIDS_OK) { + return result; // Return first error detected + } + } + + return EMBEDIDS_OK; +} + +void embedids_cleanup(embedids_context_t *context) { + if (context) { + memset(context, 0, sizeof(embedids_context_t)); + } +} + +embedids_result_t embedids_get_trend(embedids_context_t *context, const char *metric_name, + embedids_trend_t *trend) { + if (!context || !context->initialized) { + return EMBEDIDS_ERROR_NOT_INITIALIZED; + } + + if (metric_name == NULL || trend == NULL) { + return EMBEDIDS_ERROR_INVALID_PARAM; + } + + embedids_metric_config_t *config = find_metric_config(context, metric_name); + if (config == NULL) { + return EMBEDIDS_ERROR_METRIC_NOT_FOUND; + } + + if (!config->metric.enabled) { + return EMBEDIDS_ERROR_METRIC_DISABLED; + } + + embedids_metric_t *metric = &config->metric; + + // Need at least 2 data points for trend analysis + if (metric->current_size < 2) { + *trend = EMBEDIDS_TREND_STABLE; + return EMBEDIDS_OK; + } + + // For better trend analysis, calculate slope using linear regression approach + // but with a simplified version for embedded systems + + // Get the range of data to analyze + uint32_t start_index = (metric->current_size == metric->max_history_size) + ? metric->write_index + : 0; + + uint32_t num_points = metric->current_size < 3 ? metric->current_size : 3; // Use last 3 points for trend + + float sum_change = 0.0f; + uint32_t changes = 0; + + // Calculate average change between consecutive points + for (uint32_t i = 1; i < num_points; i++) { + uint32_t curr_idx = (start_index + i) % metric->max_history_size; + uint32_t prev_idx = (start_index + i - 1) % metric->max_history_size; + + float curr_val, prev_val; + + // Extract values based on metric type + switch (metric->type) { + case EMBEDIDS_METRIC_TYPE_UINT32: + curr_val = (float)metric->history[curr_idx].value.u32; + prev_val = (float)metric->history[prev_idx].value.u32; + break; + case EMBEDIDS_METRIC_TYPE_UINT64: + curr_val = (float)metric->history[curr_idx].value.u64; + prev_val = (float)metric->history[prev_idx].value.u64; + break; +#if EMBEDIDS_ENABLE_FLOATING_POINT + case EMBEDIDS_METRIC_TYPE_FLOAT: + case EMBEDIDS_METRIC_TYPE_PERCENTAGE: + case EMBEDIDS_METRIC_TYPE_RATE: + curr_val = metric->history[curr_idx].value.f32; + prev_val = metric->history[prev_idx].value.f32; + break; +#endif + default: + *trend = EMBEDIDS_TREND_STABLE; + return EMBEDIDS_OK; + } + + sum_change += curr_val - prev_val; + changes++; + } + + if (changes == 0) { + *trend = EMBEDIDS_TREND_STABLE; + return EMBEDIDS_OK; + } + + float avg_change = sum_change / changes; + + // Define threshold for what constitutes a trend vs stable + // For small changes (< 5% of first value), consider stable + uint32_t first_idx = (metric->current_size == metric->max_history_size) + ? metric->write_index + : 0; + + float first_val; + switch (metric->type) { + case EMBEDIDS_METRIC_TYPE_UINT32: + first_val = (float)metric->history[first_idx].value.u32; + break; + case EMBEDIDS_METRIC_TYPE_UINT64: + first_val = (float)metric->history[first_idx].value.u64; + break; +#if EMBEDIDS_ENABLE_FLOATING_POINT + case EMBEDIDS_METRIC_TYPE_FLOAT: + case EMBEDIDS_METRIC_TYPE_PERCENTAGE: + case EMBEDIDS_METRIC_TYPE_RATE: + first_val = metric->history[first_idx].value.f32; + break; +#endif + default: + first_val = 1.0f; // fallback + break; + } + + float threshold = fabsf(first_val) * 0.05f; // 5% threshold + if (threshold < 1.0f) threshold = 1.0f; // minimum threshold + + if (fabsf(avg_change) < threshold) { + *trend = EMBEDIDS_TREND_STABLE; + } else if (avg_change > 0) { + *trend = EMBEDIDS_TREND_INCREASING; + } else { + *trend = EMBEDIDS_TREND_DECREASING; + } + + return EMBEDIDS_OK; +} + +const char *embedids_get_version(void) { return EMBEDIDS_VERSION_STRING; } + +bool embedids_is_initialized(const embedids_context_t *context) { + return context ? context->initialized : false; +} + +embedids_result_t +embedids_validate_config(const embedids_system_config_t *config) { + if (!config) { + return EMBEDIDS_ERROR_INVALID_PARAM; + } + + if (!config->metrics) { + return EMBEDIDS_ERROR_INVALID_PARAM; + } + + if (config->max_metrics == 0 || config->max_metrics > EMBEDIDS_MAX_METRICS) { + return EMBEDIDS_ERROR_CONFIG_INVALID; + } + + return EMBEDIDS_OK; +} + +const char *embedids_get_error_string(embedids_result_t error_code) { + switch (error_code) { + case EMBEDIDS_OK: + return "Success"; + case EMBEDIDS_ERROR_INVALID_PARAM: + return "Invalid parameter"; + case EMBEDIDS_ERROR_NOT_INITIALIZED: + return "Library not initialized"; + case EMBEDIDS_ERROR_ALREADY_INITIALIZED: + return "Library already initialized"; + case EMBEDIDS_ERROR_CONFIG_INVALID: + return "Invalid configuration"; + case EMBEDIDS_ERROR_OUT_OF_MEMORY: + return "Out of memory"; + case EMBEDIDS_ERROR_BUFFER_FULL: + return "Buffer full"; + case EMBEDIDS_ERROR_BUFFER_CORRUPT: + return "Buffer corrupted"; + case EMBEDIDS_ERROR_ALIGNMENT_ERROR: + return "Memory alignment error"; + case EMBEDIDS_ERROR_METRIC_NOT_FOUND: + return "Metric not found"; + case EMBEDIDS_ERROR_METRIC_DISABLED: + return "Metric disabled"; + case EMBEDIDS_ERROR_METRIC_TYPE_MISMATCH: + return "Metric type mismatch"; + case EMBEDIDS_ERROR_METRIC_NAME_TOO_LONG: + return "Metric name too long"; + case EMBEDIDS_ERROR_ALGORITHM_FAILED: + return "Algorithm failed"; + case EMBEDIDS_ERROR_ALGORITHM_NOT_SUPPORTED: + return "Algorithm not supported"; + case EMBEDIDS_ERROR_CUSTOM_ALGORITHM_NULL: + return "Custom algorithm is null"; + case EMBEDIDS_ERROR_THRESHOLD_EXCEEDED: + return "Threshold exceeded"; + case EMBEDIDS_ERROR_TREND_ANOMALY: + return "Trend anomaly detected"; + case EMBEDIDS_ERROR_CUSTOM_DETECTION: + return "Custom detection triggered"; + case EMBEDIDS_ERROR_STATISTICAL_ANOMALY: + return "Statistical anomaly detected"; + case EMBEDIDS_ERROR_TIMEOUT: + return "Operation timeout"; + case EMBEDIDS_ERROR_HARDWARE_FAULT: + return "Hardware fault"; + case EMBEDIDS_ERROR_TIMESTAMP_INVALID: + return "Invalid timestamp"; + case EMBEDIDS_ERROR_THREAD_UNSAFE: + return "Thread safety violation"; + default: + return "Unknown error"; + } +} + +embedids_result_t embedids_reset_all_metrics(embedids_context_t *context) { + if (!context || !context->initialized) { + return EMBEDIDS_ERROR_NOT_INITIALIZED; + } + + // Reset all metric histories + for (uint32_t i = 0; i < context->system_config->num_active_metrics; + i++) { + embedids_metric_t *metric = + &context->system_config->metrics[i].metric; + metric->current_size = 0; + metric->write_index = 0; + + // Clear the history buffer if it exists + if (metric->history) { + memset(metric->history, 0, + metric->max_history_size * sizeof(embedids_metric_datapoint_t)); + } + } + + return EMBEDIDS_OK; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..e9b54c8 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,46 @@ +# Find or fetch GoogleTest +find_package(GTest QUIET) + +if(NOT GTest_FOUND) + # If GoogleTest is not found, fetch it + include(FetchContent) + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.17.0 + ) + FetchContent_MakeAvailable(googletest) +endif() + +# Create test executable with organized test files +add_executable(embedids_tests + test_core.cpp + test_metrics.cpp + test_algorithms.cpp + test_analysis.cpp + test_extensible.cpp +) + +# Link test executable with library and gtest +target_link_libraries(embedids_tests + embedids + gtest + gtest_main +) + +# Add tests to CTest +add_test(NAME embedids_tests COMMAND embedids_tests) + +# Enable test coverage if requested +option(ENABLE_COVERAGE "Enable test coverage" OFF) +if(ENABLE_COVERAGE) + target_compile_options(embedids_tests PRIVATE --coverage) + target_link_options(embedids_tests PRIVATE --coverage) +endif() + +# Add individual test suites that can be run separately +add_test(NAME core_tests COMMAND embedids_tests --gtest_filter="EmbedIDSCoreTest.*") +add_test(NAME metrics_tests COMMAND embedids_tests --gtest_filter="EmbedIDSMetricsTest.*") +add_test(NAME algorithms_tests COMMAND embedids_tests --gtest_filter="EmbedIDSAlgorithmsTest.*") +add_test(NAME analysis_tests COMMAND embedids_tests --gtest_filter="EmbedIDSAnalysisTest.*") +add_test(NAME extensible_tests COMMAND embedids_tests --gtest_filter="EmbedIDSExtensibleTest.*") diff --git a/tests/test_algorithms.cpp b/tests/test_algorithms.cpp new file mode 100644 index 0000000..64f987d --- /dev/null +++ b/tests/test_algorithms.cpp @@ -0,0 +1,398 @@ +#include "embedids.h" +#include +#include + +/** + * @brief Test fixture for algorithm functionality + * + * Tests threshold algorithm, trend algorithm, statistical algorithm, + * and algorithm configuration across different metric types. + */ +class EmbedIDSAlgorithmsTest : public ::testing::Test { +protected: + embedids_context_t context; + + void SetUp() override { + memset(&context, 0, sizeof(context)); + embedids_cleanup(&context); + } + + void TearDown() override { + embedids_cleanup(&context); + } + + /** + * @brief Helper function to setup a metric with threshold algorithm + */ + void setupThresholdMetric(embedids_metric_config_t& metric_config, + embedids_metric_datapoint_t* history_buffer, + const char* name, + embedids_metric_type_t type, + uint32_t history_size) { + memset(&metric_config, 0, sizeof(metric_config)); + strncpy(metric_config.metric.name, name, EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + metric_config.metric.type = type; + metric_config.metric.enabled = true; + metric_config.metric.history = history_buffer; + metric_config.metric.max_history_size = history_size; + metric_config.metric.current_size = 0; + metric_config.metric.write_index = 0; + metric_config.num_algorithms = 1; + + // Configure threshold algorithm + metric_config.algorithms[0].type = EMBEDIDS_ALGORITHM_THRESHOLD; + metric_config.algorithms[0].enabled = true; + } + + /** + * @brief Helper function to initialize system with a single metric + */ + embedids_result_t initializeWithMetric(embedids_metric_config_t* metric_config) { + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = metric_config; + system_config.max_metrics = 1; + system_config.num_active_metrics = 1; + + return embedids_init(&context, &system_config); + } + +private: + embedids_system_config_t system_config; +}; + +// ============================================================================ +// Threshold Algorithm Tests - Float Type +// ============================================================================ + +TEST_F(EmbedIDSAlgorithmsTest, ThresholdAlgorithmFloat) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupThresholdMetric(metric_config, history_buffer, "temperature", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + // Configure float threshold algorithm + metric_config.algorithms[0].config.threshold.min_threshold.f32 = 10.0f; + metric_config.algorithms[0].config.threshold.max_threshold.f32 = 80.0f; + metric_config.algorithms[0].config.threshold.check_min = true; + metric_config.algorithms[0].config.threshold.check_max = true; + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Test normal value (should pass) + embedids_metric_value_t value; + value.f32 = 50.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "temperature", value, 1000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "temperature"), EMBEDIDS_OK); + + // Test high value (should trigger threshold) + value.f32 = 90.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "temperature", value, 2000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "temperature"), EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); + + // Test low value (should trigger threshold) + value.f32 = 5.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "temperature", value, 3000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "temperature"), EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); +} + +TEST_F(EmbedIDSAlgorithmsTest, ThresholdAlgorithmFloatBoundaryValues) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupThresholdMetric(metric_config, history_buffer, "precise_temp", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + // Configure threshold with precise boundaries + metric_config.algorithms[0].config.threshold.min_threshold.f32 = 0.0f; + metric_config.algorithms[0].config.threshold.max_threshold.f32 = 100.0f; + metric_config.algorithms[0].config.threshold.check_min = true; + metric_config.algorithms[0].config.threshold.check_max = true; + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + embedids_metric_value_t value; + + // Test exact boundary values + value.f32 = 0.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "precise_temp", value, 1000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "precise_temp"), EMBEDIDS_OK); + + value.f32 = 100.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "precise_temp", value, 2000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "precise_temp"), EMBEDIDS_OK); + + // Test just outside boundaries + value.f32 = -0.1f; + EXPECT_EQ(embedids_add_datapoint(&context, "precise_temp", value, 3000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "precise_temp"), EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); + + value.f32 = 100.1f; + EXPECT_EQ(embedids_add_datapoint(&context, "precise_temp", value, 4000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "precise_temp"), EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); +} + +// ============================================================================ +// Threshold Algorithm Tests - Integer Types +// ============================================================================ + +TEST_F(EmbedIDSAlgorithmsTest, ThresholdAlgorithmUint32) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupThresholdMetric(metric_config, history_buffer, "packet_count", + EMBEDIDS_METRIC_TYPE_UINT32, 10); + + // Configure uint32 threshold algorithm + metric_config.algorithms[0].config.threshold.min_threshold.u32 = 100; + metric_config.algorithms[0].config.threshold.max_threshold.u32 = 10000; + metric_config.algorithms[0].config.threshold.check_min = true; + metric_config.algorithms[0].config.threshold.check_max = true; + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + embedids_metric_value_t value; + + // Test normal value + value.u32 = 5000; + EXPECT_EQ(embedids_add_datapoint(&context, "packet_count", value, 1000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "packet_count"), EMBEDIDS_OK); + + // Test min threshold violation + value.u32 = 50; + EXPECT_EQ(embedids_add_datapoint(&context, "packet_count", value, 2000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "packet_count"), EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); + + // Test max threshold violation + value.u32 = 15000; + EXPECT_EQ(embedids_add_datapoint(&context, "packet_count", value, 3000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "packet_count"), EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); +} + +TEST_F(EmbedIDSAlgorithmsTest, ThresholdAlgorithmUint64) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupThresholdMetric(metric_config, history_buffer, "byte_count", + EMBEDIDS_METRIC_TYPE_UINT64, 10); + + // Configure uint64 threshold algorithm + metric_config.algorithms[0].config.threshold.min_threshold.u64 = 1000000ULL; + metric_config.algorithms[0].config.threshold.max_threshold.u64 = 1000000000ULL; + metric_config.algorithms[0].config.threshold.check_min = true; + metric_config.algorithms[0].config.threshold.check_max = true; + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + embedids_metric_value_t value; + + // Test normal value + value.u64 = 500000000ULL; + EXPECT_EQ(embedids_add_datapoint(&context, "byte_count", value, 1000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "byte_count"), EMBEDIDS_OK); + + // Test min threshold violation + value.u64 = 500000ULL; + EXPECT_EQ(embedids_add_datapoint(&context, "byte_count", value, 2000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "byte_count"), EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); + + // Test max threshold violation + value.u64 = 2000000000ULL; + EXPECT_EQ(embedids_add_datapoint(&context, "byte_count", value, 3000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "byte_count"), EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); +} + +// ============================================================================ +// Threshold Algorithm Tests - Enum Type +// ============================================================================ + +TEST_F(EmbedIDSAlgorithmsTest, ThresholdAlgorithmEnum) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupThresholdMetric(metric_config, history_buffer, "error_level", + EMBEDIDS_METRIC_TYPE_ENUM, 10); + + // Configure enum threshold (0=OK, 1=WARN, 2=ERROR, 3=CRITICAL) + metric_config.algorithms[0].config.threshold.min_threshold.enum_val = 0; + metric_config.algorithms[0].config.threshold.max_threshold.enum_val = 2; + metric_config.algorithms[0].config.threshold.check_min = true; + metric_config.algorithms[0].config.threshold.check_max = true; + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + embedids_metric_value_t value; + + // Test normal enum value (WARNING level) + value.enum_val = 1; + EXPECT_EQ(embedids_add_datapoint(&context, "error_level", value, 1000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "error_level"), EMBEDIDS_OK); + + // Test max threshold violation (CRITICAL level) + value.enum_val = 3; + EXPECT_EQ(embedids_add_datapoint(&context, "error_level", value, 2000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "error_level"), EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); +} + +// ============================================================================ +// Threshold Algorithm Tests - Boolean Type +// ============================================================================ + +TEST_F(EmbedIDSAlgorithmsTest, BooleanMetricWithThreshold) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupThresholdMetric(metric_config, history_buffer, "security_breach", + EMBEDIDS_METRIC_TYPE_BOOL, 10); + + // Configure threshold algorithm (though not really applicable for boolean) + metric_config.algorithms[0].config.threshold.check_min = false; + metric_config.algorithms[0].config.threshold.check_max = false; + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + embedids_metric_value_t value; + + // Test boolean values + value.boolean = false; + EXPECT_EQ(embedids_add_datapoint(&context, "security_breach", value, 1000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "security_breach"), EMBEDIDS_OK); + + value.boolean = true; + EXPECT_EQ(embedids_add_datapoint(&context, "security_breach", value, 2000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "security_breach"), EMBEDIDS_OK); +} + +// ============================================================================ +// Trend Algorithm Tests +// ============================================================================ + +TEST_F(EmbedIDSAlgorithmsTest, TrendAlgorithmTesting) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + memset(&metric_config, 0, sizeof(metric_config)); + strncpy(metric_config.metric.name, "cpu_trend", EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + metric_config.metric.type = EMBEDIDS_METRIC_TYPE_FLOAT; + metric_config.metric.enabled = true; + metric_config.metric.history = history_buffer; + metric_config.metric.max_history_size = 10; + metric_config.metric.current_size = 0; + metric_config.metric.write_index = 0; + + // Configure trend algorithm + metric_config.algorithms[0].type = EMBEDIDS_ALGORITHM_TREND; + metric_config.algorithms[0].enabled = true; + metric_config.algorithms[0].config.trend.window_size = 5; + metric_config.algorithms[0].config.trend.max_slope = 10.0f; + metric_config.algorithms[0].config.trend.max_variance = 100.0f; + metric_config.algorithms[0].config.trend.expected_trend = EMBEDIDS_TREND_STABLE; + metric_config.num_algorithms = 1; + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add data points for trend analysis + embedids_metric_value_t value; + value.f32 = 50.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "cpu_trend", value, 1000), EMBEDIDS_OK); + + value.f32 = 52.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "cpu_trend", value, 2000), EMBEDIDS_OK); + + value.f32 = 51.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "cpu_trend", value, 3000), EMBEDIDS_OK); + + // Analyze trend - should be OK (stable trend) + embedids_result_t result = embedids_analyze_metric(&context, "cpu_trend"); + EXPECT_EQ(result, EMBEDIDS_OK); +} + +TEST_F(EmbedIDSAlgorithmsTest, TrendAlgorithmIncreasingPattern) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + memset(&metric_config, 0, sizeof(metric_config)); + strncpy(metric_config.metric.name, "memory_usage", EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + metric_config.metric.type = EMBEDIDS_METRIC_TYPE_FLOAT; + metric_config.metric.enabled = true; + metric_config.metric.history = history_buffer; + metric_config.metric.max_history_size = 10; + metric_config.metric.current_size = 0; + metric_config.metric.write_index = 0; + + // Configure trend algorithm expecting stable trend + metric_config.algorithms[0].type = EMBEDIDS_ALGORITHM_TREND; + metric_config.algorithms[0].enabled = true; + metric_config.algorithms[0].config.trend.window_size = 3; + metric_config.algorithms[0].config.trend.max_slope = 5.0f; + metric_config.algorithms[0].config.trend.max_variance = 10.0f; + metric_config.algorithms[0].config.trend.expected_trend = EMBEDIDS_TREND_STABLE; + metric_config.num_algorithms = 1; + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add increasing data points + embedids_metric_value_t value; + for (int i = 0; i < 5; i++) { + value.f32 = 10.0f + i * 20.0f; // Strong increasing pattern + EXPECT_EQ(embedids_add_datapoint(&context, "memory_usage", value, 1000 + i * 1000), EMBEDIDS_OK); + } + + // Trend analysis might detect unexpected trend + embedids_result_t result = embedids_analyze_metric(&context, "memory_usage"); + // Result depends on implementation - could be OK or trend anomaly + EXPECT_TRUE(result == EMBEDIDS_OK || result == EMBEDIDS_ERROR_TREND_ANOMALY); +} + +// ============================================================================ +// Empty Metric Analysis Tests +// ============================================================================ + +TEST_F(EmbedIDSAlgorithmsTest, EmptyMetricAnalysis) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupThresholdMetric(metric_config, history_buffer, "empty_metric", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + metric_config.algorithms[0].config.threshold.min_threshold.f32 = 10.0f; + metric_config.algorithms[0].config.threshold.max_threshold.f32 = 80.0f; + metric_config.algorithms[0].config.threshold.check_min = true; + metric_config.algorithms[0].config.threshold.check_max = true; + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Analyze empty metric should return OK (no data to analyze) + embedids_result_t result = embedids_analyze_metric(&context, "empty_metric"); + EXPECT_EQ(result, EMBEDIDS_OK); +} + +// ============================================================================ +// Algorithm Configuration Tests +// ============================================================================ + +TEST_F(EmbedIDSAlgorithmsTest, DisabledAlgorithm) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupThresholdMetric(metric_config, history_buffer, "test_metric", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + // Configure but disable the algorithm + metric_config.algorithms[0].enabled = false; + metric_config.algorithms[0].config.threshold.min_threshold.f32 = 10.0f; + metric_config.algorithms[0].config.threshold.max_threshold.f32 = 80.0f; + metric_config.algorithms[0].config.threshold.check_min = true; + metric_config.algorithms[0].config.threshold.check_max = true; + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add value that would violate threshold if algorithm was enabled + embedids_metric_value_t value; + value.f32 = 90.0f; // Above threshold + EXPECT_EQ(embedids_add_datapoint(&context, "test_metric", value, 1000), EMBEDIDS_OK); + + // Analysis should pass since algorithm is disabled + embedids_result_t result = embedids_analyze_metric(&context, "test_metric"); + EXPECT_EQ(result, EMBEDIDS_OK); +} diff --git a/tests/test_analysis.cpp b/tests/test_analysis.cpp new file mode 100644 index 0000000..c313b02 --- /dev/null +++ b/tests/test_analysis.cpp @@ -0,0 +1,455 @@ +#include "embedids.h" +#include +#include + +/** + * @brief Test fixture for analysis functionality + * + * Tests analysis operations, trend analysis, multiple metric analysis, + * and analysis result interpretation. + */ +class EmbedIDSAnalysisTest : public ::testing::Test { +protected: + embedids_context_t context; + + void SetUp() override { + memset(&context, 0, sizeof(context)); + embedids_cleanup(&context); + } + + void TearDown() override { + embedids_cleanup(&context); + } + + /** + * @brief Helper function to setup a basic metric configuration + */ + void setupBasicMetric(embedids_metric_config_t& metric_config, + embedids_metric_datapoint_t* history_buffer, + const char* name, + embedids_metric_type_t type, + uint32_t history_size) { + memset(&metric_config, 0, sizeof(metric_config)); + strncpy(metric_config.metric.name, name, EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + metric_config.metric.type = type; + metric_config.metric.enabled = true; + metric_config.metric.history = history_buffer; + metric_config.metric.max_history_size = history_size; + metric_config.metric.current_size = 0; + metric_config.metric.write_index = 0; + } + + /** + * @brief Helper function to initialize system with a single metric + */ + embedids_result_t initializeWithMetric(embedids_metric_config_t* metric_config) { + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = metric_config; + system_config.max_metrics = 1; + system_config.num_active_metrics = 1; + + return embedids_init(&context, &system_config); + } + +private: + embedids_system_config_t system_config; +}; + +// ============================================================================ +// Basic Analysis Operations Tests +// ============================================================================ + +TEST_F(EmbedIDSAnalysisTest, AnalyzeValidMetric) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "memory_usage", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add some data points + embedids_metric_value_t value; + value.f32 = 45.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "memory_usage", value, 1000), EMBEDIDS_OK); + + // Test analyze specific metric + embedids_result_t result = embedids_analyze_metric(&context, "memory_usage"); + EXPECT_EQ(result, EMBEDIDS_OK); +} + +TEST_F(EmbedIDSAnalysisTest, AnalyzeNonexistentMetric) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "memory_usage", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Test analyze non-existent metric + embedids_result_t result = embedids_analyze_metric(&context, "nonexistent_metric"); + EXPECT_EQ(result, EMBEDIDS_ERROR_METRIC_NOT_FOUND); +} + +TEST_F(EmbedIDSAnalysisTest, AnalyzeAllMetrics) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "memory_usage", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add some data points + embedids_metric_value_t value; + value.f32 = 45.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "memory_usage", value, 1000), EMBEDIDS_OK); + + // Test analyze all metrics + embedids_result_t result = embedids_analyze_all(&context); + EXPECT_EQ(result, EMBEDIDS_OK); +} + +// ============================================================================ +// Trend Analysis Tests +// ============================================================================ + +TEST_F(EmbedIDSAnalysisTest, TrendAnalysisBasic) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "network_traffic", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add some data points for trend analysis + embedids_metric_value_t value; + value.f32 = 50.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "network_traffic", value, 1000), EMBEDIDS_OK); + + value.f32 = 52.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "network_traffic", value, 2000), EMBEDIDS_OK); + + value.f32 = 51.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "network_traffic", value, 3000), EMBEDIDS_OK); + + // Test trend analysis + embedids_trend_t trend; + embedids_result_t result = embedids_get_trend(&context, "network_traffic", &trend); + EXPECT_EQ(result, EMBEDIDS_OK); + EXPECT_EQ(trend, EMBEDIDS_TREND_STABLE); +} + +TEST_F(EmbedIDSAnalysisTest, TrendAnalysisIncreasing) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "cpu_usage", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add increasing data points + embedids_metric_value_t value; + for (int i = 0; i < 5; i++) { + value.f32 = 10.0f + i * 10.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "cpu_usage", value, 1000 + i * 1000), EMBEDIDS_OK); + } + + // Test trend analysis + embedids_trend_t trend; + embedids_result_t result = embedids_get_trend(&context, "cpu_usage", &trend); + EXPECT_EQ(result, EMBEDIDS_OK); + EXPECT_EQ(trend, EMBEDIDS_TREND_INCREASING); +} + +TEST_F(EmbedIDSAnalysisTest, TrendAnalysisDecreasing) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "battery_level", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add decreasing data points + embedids_metric_value_t value; + for (int i = 0; i < 5; i++) { + value.f32 = 100.0f - i * 10.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "battery_level", value, 1000 + i * 1000), EMBEDIDS_OK); + } + + // Test trend analysis + embedids_trend_t trend; + embedids_result_t result = embedids_get_trend(&context, "battery_level", &trend); + EXPECT_EQ(result, EMBEDIDS_OK); + EXPECT_EQ(trend, EMBEDIDS_TREND_DECREASING); +} + +TEST_F(EmbedIDSAnalysisTest, TrendAnalysisWithInsufficientData) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "sparse_metric", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add only one data point + embedids_metric_value_t value; + value.f32 = 50.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "sparse_metric", value, 1000), EMBEDIDS_OK); + + // Test trend analysis with insufficient data + embedids_trend_t trend; + embedids_result_t result = embedids_get_trend(&context, "sparse_metric", &trend); + EXPECT_EQ(result, EMBEDIDS_OK); + // With insufficient data, trend should be stable + EXPECT_EQ(trend, EMBEDIDS_TREND_STABLE); +} + +// ============================================================================ +// Multiple Metrics Analysis Tests +// ============================================================================ + +TEST_F(EmbedIDSAnalysisTest, MultipleMetricsAnalysisAllNormal) { + // Setup multiple metrics + embedids_metric_datapoint_t history_buffer1[5]; + embedids_metric_datapoint_t history_buffer2[5]; + + embedids_metric_config_t metric_configs[2]; + + // Configure first metric (normal) + setupBasicMetric(metric_configs[0], history_buffer1, "metric1", + EMBEDIDS_METRIC_TYPE_FLOAT, 5); + + // Configure second metric (normal) + setupBasicMetric(metric_configs[1], history_buffer2, "metric2", + EMBEDIDS_METRIC_TYPE_FLOAT, 5); + + // Setup system configuration + embedids_system_config_t system_config; + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = metric_configs; + system_config.max_metrics = 2; + system_config.num_active_metrics = 2; + + ASSERT_EQ(embedids_init(&context, &system_config), EMBEDIDS_OK); + + // Add normal data to both metrics + embedids_metric_value_t value; + value.f32 = 25.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "metric1", value, 1000), EMBEDIDS_OK); + + value.f32 = 30.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "metric2", value, 1000), EMBEDIDS_OK); + + // Analyze all should be OK + embedids_result_t result = embedids_analyze_all(&context); + EXPECT_EQ(result, EMBEDIDS_OK); +} + +TEST_F(EmbedIDSAnalysisTest, MultipleMetricsAnalysisWithThresholdViolation) { + // Setup multiple metrics + embedids_metric_datapoint_t history_buffer1[5]; + embedids_metric_datapoint_t history_buffer2[5]; + + embedids_metric_config_t metric_configs[2]; + + // Configure first metric (normal) + setupBasicMetric(metric_configs[0], history_buffer1, "metric1", + EMBEDIDS_METRIC_TYPE_FLOAT, 5); + + // Configure second metric with threshold algorithm + setupBasicMetric(metric_configs[1], history_buffer2, "metric2", + EMBEDIDS_METRIC_TYPE_FLOAT, 5); + + // Add threshold algorithm to second metric + metric_configs[1].algorithms[0].type = EMBEDIDS_ALGORITHM_THRESHOLD; + metric_configs[1].algorithms[0].enabled = true; + metric_configs[1].algorithms[0].config.threshold.max_threshold.f32 = 50.0f; + metric_configs[1].algorithms[0].config.threshold.check_max = true; + metric_configs[1].algorithms[0].config.threshold.check_min = false; + metric_configs[1].num_algorithms = 1; + + // Setup system configuration + embedids_system_config_t system_config; + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = metric_configs; + system_config.max_metrics = 2; + system_config.num_active_metrics = 2; + + ASSERT_EQ(embedids_init(&context, &system_config), EMBEDIDS_OK); + + // Add normal data to first metric + embedids_metric_value_t value; + value.f32 = 25.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "metric1", value, 1000), EMBEDIDS_OK); + + // Add normal data to second metric + value.f32 = 30.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "metric2", value, 1000), EMBEDIDS_OK); + + // Analyze all should be OK initially + embedids_result_t result = embedids_analyze_all(&context); + EXPECT_EQ(result, EMBEDIDS_OK); + + // Add threshold-violating data to second metric + value.f32 = 75.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "metric2", value, 2000), EMBEDIDS_OK); + + // Analyze all should detect the violation + result = embedids_analyze_all(&context); + EXPECT_EQ(result, EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); +} + +// ============================================================================ +// Analysis Error Handling Tests +// ============================================================================ + +TEST_F(EmbedIDSAnalysisTest, AnalysisParameterValidation) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "test_metric", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Test null metric name for analysis + embedids_result_t result = embedids_analyze_metric(&context, nullptr); + EXPECT_EQ(result, EMBEDIDS_ERROR_INVALID_PARAM); + + // Test null parameters for trend analysis + embedids_trend_t trend; + result = embedids_get_trend(&context, nullptr, &trend); + EXPECT_EQ(result, EMBEDIDS_ERROR_INVALID_PARAM); + + result = embedids_get_trend(&context, "test_metric", nullptr); + EXPECT_EQ(result, EMBEDIDS_ERROR_INVALID_PARAM); +} + +// ============================================================================ +// Analysis with Different Metric States Tests +// ============================================================================ + +TEST_F(EmbedIDSAnalysisTest, AnalysisOfDisabledMetric) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "disabled_metric", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + // Disable the metric + metric_config.metric.enabled = false; + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Analysis of disabled metric might behave differently + embedids_result_t result = embedids_analyze_metric(&context, "disabled_metric"); + // Result depends on implementation - should either skip or return error + EXPECT_TRUE(result == EMBEDIDS_OK || result == EMBEDIDS_ERROR_METRIC_DISABLED); +} + +TEST_F(EmbedIDSAnalysisTest, AnalysisOfEmptyMetric) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "empty_metric", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Don't add any data points - analyze empty metric + embedids_result_t result = embedids_analyze_metric(&context, "empty_metric"); + EXPECT_EQ(result, EMBEDIDS_OK); // Should handle empty metrics gracefully + + // Test trend analysis on empty metric + embedids_trend_t trend; + result = embedids_get_trend(&context, "empty_metric", &trend); + EXPECT_EQ(result, EMBEDIDS_OK); + EXPECT_EQ(trend, EMBEDIDS_TREND_STABLE); +} + +// ============================================================================ +// Complex Analysis Scenarios Tests +// ============================================================================ + +TEST_F(EmbedIDSAnalysisTest, AnalysisWithMixedMetricTypes) { + // Setup mixed metric types + embedids_metric_datapoint_t history_buffer1[5]; + embedids_metric_datapoint_t history_buffer2[5]; + embedids_metric_datapoint_t history_buffer3[5]; + + embedids_metric_config_t metric_configs[3]; + + // Float metric + setupBasicMetric(metric_configs[0], history_buffer1, "temperature", + EMBEDIDS_METRIC_TYPE_FLOAT, 5); + + // Integer metric + setupBasicMetric(metric_configs[1], history_buffer2, "count", + EMBEDIDS_METRIC_TYPE_UINT32, 5); + + // Boolean metric + setupBasicMetric(metric_configs[2], history_buffer3, "status", + EMBEDIDS_METRIC_TYPE_BOOL, 5); + + // Setup system configuration + embedids_system_config_t system_config; + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = metric_configs; + system_config.max_metrics = 3; + system_config.num_active_metrics = 3; + + ASSERT_EQ(embedids_init(&context, &system_config), EMBEDIDS_OK); + + // Add data to all metrics + embedids_metric_value_t value; + + value.f32 = 25.5f; + EXPECT_EQ(embedids_add_datapoint(&context, "temperature", value, 1000), EMBEDIDS_OK); + + value.u32 = 42; + EXPECT_EQ(embedids_add_datapoint(&context, "count", value, 1000), EMBEDIDS_OK); + + value.boolean = true; + EXPECT_EQ(embedids_add_datapoint(&context, "status", value, 1000), EMBEDIDS_OK); + + // Analyze all mixed metrics + embedids_result_t result = embedids_analyze_all(&context); + EXPECT_EQ(result, EMBEDIDS_OK); + + // Individual analysis should also work + EXPECT_EQ(embedids_analyze_metric(&context, "temperature"), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "count"), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "status"), EMBEDIDS_OK); +} + +TEST_F(EmbedIDSAnalysisTest, SequentialAnalysisCalls) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "test_metric", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add initial data + embedids_metric_value_t value; + value.f32 = 50.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "test_metric", value, 1000), EMBEDIDS_OK); + + // Multiple sequential analysis calls should work + EXPECT_EQ(embedids_analyze_metric(&context, "test_metric"), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "test_metric"), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_all(&context), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_all(&context), EMBEDIDS_OK); + + // Add more data and analyze again + value.f32 = 55.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "test_metric", value, 2000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "test_metric"), EMBEDIDS_OK); +} diff --git a/tests/test_core.cpp b/tests/test_core.cpp new file mode 100644 index 0000000..f34bcd7 --- /dev/null +++ b/tests/test_core.cpp @@ -0,0 +1,163 @@ +#include "embedids.h" +#include +#include + +/** + * @brief Test fixture for core EmbedIDS functionality + * + * Tests basic system initialization, configuration validation, + * version information, and error handling. + */ +class EmbedIDSCoreTest : public ::testing::Test { +protected: + embedids_context_t context; + + void SetUp() override { + memset(&context, 0, sizeof(context)); + embedids_cleanup(&context); + } + + void TearDown() override { + embedids_cleanup(&context); + } +}; + +// ============================================================================ +// System Initialization Tests +// ============================================================================ + +TEST_F(EmbedIDSCoreTest, InitializeSystem) { + embedids_system_config_t config; + memset(&config, 0, sizeof(config)); + + embedids_result_t result = embedids_init(&context, &config); + EXPECT_EQ(result, EMBEDIDS_OK); + + EXPECT_TRUE(embedids_is_initialized(&context)); +} + +TEST_F(EmbedIDSCoreTest, InitializationWithNullConfig) { + embedids_result_t result = embedids_init(nullptr, nullptr); + EXPECT_EQ(result, EMBEDIDS_ERROR_INVALID_PARAM); + EXPECT_FALSE(embedids_is_initialized(&context)); +} + +TEST_F(EmbedIDSCoreTest, CleanupFunction) { + embedids_system_config_t config; + memset(&config, 0, sizeof(config)); + ASSERT_EQ(embedids_init(&context, &config), EMBEDIDS_OK); + EXPECT_TRUE(embedids_is_initialized(&context)); + + embedids_cleanup(&context); + EXPECT_FALSE(embedids_is_initialized(&context)); + + embedids_cleanup(&context); + EXPECT_FALSE(embedids_is_initialized(&context)); +} + +// ============================================================================ +// Configuration Validation Tests +// ============================================================================ + +TEST_F(EmbedIDSCoreTest, ConfigValidationWithNullConfig) { + embedids_result_t result = embedids_validate_config(nullptr); + EXPECT_EQ(result, EMBEDIDS_ERROR_INVALID_PARAM); +} + +TEST_F(EmbedIDSCoreTest, ConfigValidationWithNullMetrics) { + embedids_system_config_t config; + memset(&config, 0, sizeof(config)); + config.metrics = nullptr; + + embedids_result_t result = embedids_validate_config(&config); + EXPECT_EQ(result, EMBEDIDS_ERROR_INVALID_PARAM); +} + +// ============================================================================ +// Version and Error Information Tests +// ============================================================================ + +TEST_F(EmbedIDSCoreTest, VersionInfo) { + const char *version = embedids_get_version(); + EXPECT_NE(version, nullptr); + EXPECT_GT(strlen(version), 0); +} + +TEST_F(EmbedIDSCoreTest, ErrorStringFunction) { + const char *error_str = embedids_get_error_string(EMBEDIDS_OK); + EXPECT_NE(error_str, nullptr); + + error_str = embedids_get_error_string(EMBEDIDS_ERROR_INVALID_PARAM); + EXPECT_NE(error_str, nullptr); + + error_str = embedids_get_error_string(EMBEDIDS_ERROR_NOT_INITIALIZED); + EXPECT_NE(error_str, nullptr); +} + +// ============================================================================ +// Uninitialized System Tests +// ============================================================================ + +TEST_F(EmbedIDSCoreTest, UninitializedOperations) { + EXPECT_FALSE(embedids_is_initialized(&context)); + + embedids_metric_value_t value; + value.f32 = 10.0f; + + // Test operations on uninitialized system + embedids_result_t result = embedids_add_datapoint(&context, "test_metric", value, 1000); + EXPECT_EQ(result, EMBEDIDS_ERROR_NOT_INITIALIZED); + + result = embedids_analyze_all(&context); + EXPECT_EQ(result, EMBEDIDS_ERROR_NOT_INITIALIZED); + + result = embedids_analyze_metric(&context, "test_metric"); + EXPECT_EQ(result, EMBEDIDS_ERROR_NOT_INITIALIZED); + + result = embedids_reset_all_metrics(&context); + EXPECT_EQ(result, EMBEDIDS_ERROR_NOT_INITIALIZED); +} + +// ============================================================================ +// Null Parameter Tests +// ============================================================================ + +TEST_F(EmbedIDSCoreTest, NullParameterHandling) { + // Setup a basic initialized system + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + memset(&metric_config, 0, sizeof(metric_config)); + + strncpy(metric_config.metric.name, "test_metric", EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + metric_config.metric.type = EMBEDIDS_METRIC_TYPE_FLOAT; + metric_config.metric.enabled = true; + metric_config.metric.history = history_buffer; + metric_config.metric.max_history_size = 10; + metric_config.metric.current_size = 0; + metric_config.metric.write_index = 0; + + embedids_system_config_t system_config; + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = &metric_config; + system_config.max_metrics = 1; + system_config.num_active_metrics = 1; + + ASSERT_EQ(embedids_init(&context, &system_config), EMBEDIDS_OK); + + // Test null metric name + embedids_metric_value_t value; + value.f32 = 10.0f; + embedids_result_t result = embedids_add_datapoint(&context, nullptr, value, 1000); + EXPECT_EQ(result, EMBEDIDS_ERROR_INVALID_PARAM); + + result = embedids_analyze_metric(&context, nullptr); + EXPECT_EQ(result, EMBEDIDS_ERROR_INVALID_PARAM); + + // Test trend analysis with null parameters + embedids_trend_t trend; + result = embedids_get_trend(&context, nullptr, &trend); + EXPECT_EQ(result, EMBEDIDS_ERROR_INVALID_PARAM); + + result = embedids_get_trend(&context, "test_metric", nullptr); + EXPECT_EQ(result, EMBEDIDS_ERROR_INVALID_PARAM); +} diff --git a/tests/test_extensible.cpp b/tests/test_extensible.cpp new file mode 100644 index 0000000..8fc58d7 --- /dev/null +++ b/tests/test_extensible.cpp @@ -0,0 +1,733 @@ +#include "embedids.h" +#include +#include +#include + +/** + * @brief Test fixture for extensible algorithm functionality + * + * Tests custom algorithms with metrics checking, algorithm state management, + * multiple algorithm configurations, and extensible architecture features. + */ +class EmbedIDSExtensibleTest : public ::testing::Test { +protected: + embedids_context_t context; + + void SetUp() override { + memset(&context, 0, sizeof(context)); + embedids_cleanup(&context); + } + + void TearDown() override { + embedids_cleanup(&context); + } + + /** + * @brief Helper function to setup a metric with custom algorithm + */ + void setupCustomMetric(embedids_metric_config_t& metric_config, + embedids_metric_datapoint_t* history_buffer, + const char* name, + embedids_metric_type_t type, + uint32_t history_size, + embedids_custom_algorithm_fn algorithm_fn, + void* config = nullptr, + void* context = nullptr) { + memset(&metric_config, 0, sizeof(metric_config)); + strncpy(metric_config.metric.name, name, EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + metric_config.metric.type = type; + metric_config.metric.enabled = true; + metric_config.metric.history = history_buffer; + metric_config.metric.max_history_size = history_size; + metric_config.metric.current_size = 0; + metric_config.metric.write_index = 0; + metric_config.num_algorithms = 1; + + // Configure custom algorithm + metric_config.algorithms[0].type = EMBEDIDS_ALGORITHM_CUSTOM; + metric_config.algorithms[0].enabled = true; + metric_config.algorithms[0].config.custom.function = algorithm_fn; + metric_config.algorithms[0].config.custom.config = config; + metric_config.algorithms[0].config.custom.context = context; + } + + /** + * @brief Helper function to initialize system with a single metric + */ + embedids_result_t initializeWithMetric(embedids_metric_config_t* metric_config) { + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = metric_config; + system_config.max_metrics = 1; + system_config.num_active_metrics = 1; + + return embedids_init(&context, &system_config); + } + + /** + * @brief Helper function to initialize system with multiple metrics + */ + embedids_result_t initializeWithMetrics(embedids_metric_config_t* metric_configs, uint32_t count) { + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = metric_configs; + system_config.max_metrics = count; + system_config.num_active_metrics = count; + + return embedids_init(&context, &system_config); + } + +private: + embedids_system_config_t system_config; +}; + +// ============================================================================ +// Custom Algorithm Context Structures +// ============================================================================ + +/** + * @brief Context for pattern detection algorithm + */ +typedef struct { + float baseline; + float threshold_multiplier; + uint32_t consecutive_violations; + uint32_t max_violations; + uint32_t call_count; // For testing +} pattern_detector_context_t; + +/** + * @brief Context for rate-of-change detection algorithm + */ +typedef struct { + float max_rate; + uint32_t call_count; // For testing + embedids_result_t last_result; // For testing +} rate_change_context_t; + +/** + * @brief Context for statistical variance algorithm + */ +typedef struct { + float variance_threshold; + uint32_t window_size; + uint32_t call_count; // For testing + float calculated_variance; // For testing +} variance_detector_context_t; + +// ============================================================================ +// Custom Algorithm Implementations +// ============================================================================ + +/** + * @brief Custom algorithm: Pattern detection with baseline deviation + */ +static embedids_result_t pattern_detector_algorithm(const embedids_metric_t* metric, + const void* config, + void* context) { + (void)config; // Unused parameter + pattern_detector_context_t* ctx = static_cast(context); + + if (!ctx || !metric) { + return EMBEDIDS_ERROR_INVALID_PARAM; + } + + ctx->call_count++; + + if (metric->current_size < 3) { + return EMBEDIDS_OK; // Need at least 3 data points + } + + // Get the last three data points + uint32_t idx1 = (metric->write_index - 1 + metric->max_history_size) % metric->max_history_size; + uint32_t idx2 = (metric->write_index - 2 + metric->max_history_size) % metric->max_history_size; + uint32_t idx3 = (metric->write_index - 3 + metric->max_history_size) % metric->max_history_size; + + float val1 = metric->history[idx1].value.f32; + float val2 = metric->history[idx2].value.f32; + float val3 = metric->history[idx3].value.f32; + + // Calculate average and deviation from baseline + float avg_recent = (val1 + val2 + val3) / 3.0f; + float deviation = std::abs(avg_recent - ctx->baseline); + float threshold = ctx->baseline * ctx->threshold_multiplier; + + // Debug print (comment out in production) + // printf("Pattern detector: avg=%.2f, baseline=%.2f, deviation=%.2f, threshold=%.2f, violations=%d\n", + // avg_recent, ctx->baseline, deviation, threshold, ctx->consecutive_violations); + + if (deviation > threshold) { + ctx->consecutive_violations++; + + if (ctx->consecutive_violations >= ctx->max_violations) { + ctx->consecutive_violations = 0; // Reset counter + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + } else { + ctx->consecutive_violations = 0; // Reset on normal reading + } + + return EMBEDIDS_OK; +} + +/** + * @brief Custom algorithm: Rate-of-change detection + */ +static embedids_result_t rate_change_algorithm(const embedids_metric_t* metric, + const void* config, + void* context) { + rate_change_context_t* ctx = static_cast(context); + + if (!ctx || !metric) { + return EMBEDIDS_ERROR_INVALID_PARAM; + } + + ctx->call_count++; + + if (metric->current_size < 2) { + ctx->last_result = EMBEDIDS_OK; + return EMBEDIDS_OK; + } + + // Get last two data points + uint32_t idx1 = (metric->write_index - 1 + metric->max_history_size) % metric->max_history_size; + uint32_t idx2 = (metric->write_index - 2 + metric->max_history_size) % metric->max_history_size; + + float val1 = metric->history[idx1].value.f32; + float val2 = metric->history[idx2].value.f32; + uint64_t time1 = metric->history[idx1].timestamp_ms; + uint64_t time2 = metric->history[idx2].timestamp_ms; + + if (time1 == time2) { + ctx->last_result = EMBEDIDS_OK; + return EMBEDIDS_OK; // Avoid division by zero + } + + float rate = std::abs(val1 - val2) / (static_cast(time1 - time2) / 1000.0f); + float max_rate = config ? *static_cast(config) : ctx->max_rate; + + if (rate > max_rate) { + ctx->last_result = EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + return EMBEDIDS_ERROR_THRESHOLD_EXCEEDED; + } + + ctx->last_result = EMBEDIDS_OK; + return EMBEDIDS_OK; +} + +/** + * @brief Custom algorithm: Statistical variance detection + */ +static embedids_result_t variance_detector_algorithm(const embedids_metric_t* metric, + const void* config, + void* context) { + (void)config; // Unused parameter + variance_detector_context_t* ctx = static_cast(context); + + if (!ctx || !metric) { + return EMBEDIDS_ERROR_INVALID_PARAM; + } + + ctx->call_count++; + + if (metric->current_size < ctx->window_size) { + ctx->calculated_variance = 0.0f; + return EMBEDIDS_OK; // Need enough data points + } + + // Calculate variance over the window + float sum = 0.0f; + float sum_sq = 0.0f; + uint32_t count = std::min(ctx->window_size, metric->current_size); + + for (uint32_t i = 0; i < count; i++) { + uint32_t idx = (metric->write_index - 1 - i + metric->max_history_size) % metric->max_history_size; + float val = metric->history[idx].value.f32; + sum += val; + sum_sq += val * val; + } + + float mean = sum / count; + ctx->calculated_variance = (sum_sq / count) - (mean * mean); + + if (ctx->calculated_variance > ctx->variance_threshold) { + return EMBEDIDS_ERROR_STATISTICAL_ANOMALY; + } + + return EMBEDIDS_OK; +} + +/** + * @brief Always-failing custom algorithm for testing error conditions + */ +static embedids_result_t always_fail_algorithm(const embedids_metric_t* metric, + const void* config, + void* context) { + (void)metric; + (void)config; + (void)context; + return EMBEDIDS_ERROR_CUSTOM_DETECTION; +} + +// ============================================================================ +// Basic Custom Algorithm Tests +// ============================================================================ + +TEST_F(EmbedIDSExtensibleTest, PatternDetectorBasicOperation) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + pattern_detector_context_t ctx = { + .baseline = 50.0f, + .threshold_multiplier = 0.2f, // 20% deviation threshold + .consecutive_violations = 0, + .max_violations = 2, + .call_count = 0 + }; + + setupCustomMetric(metric_config, history_buffer, "cpu_usage", + EMBEDIDS_METRIC_TYPE_FLOAT, 10, + pattern_detector_algorithm, nullptr, &ctx); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add normal values (should not trigger) + embedids_metric_value_t value; + value.f32 = 48.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "cpu_usage", value, 1000), EMBEDIDS_OK); + value.f32 = 52.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "cpu_usage", value, 2000), EMBEDIDS_OK); + value.f32 = 49.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "cpu_usage", value, 3000), EMBEDIDS_OK); + + // Should be OK - values are within 20% of baseline (50.0) + EXPECT_EQ(embedids_analyze_metric(&context, "cpu_usage"), EMBEDIDS_OK); + EXPECT_GT(ctx.call_count, 0); + EXPECT_EQ(ctx.consecutive_violations, 0); +} + +TEST_F(EmbedIDSExtensibleTest, PatternDetectorAnomalyDetection) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + pattern_detector_context_t ctx = { + .baseline = 50.0f, + .threshold_multiplier = 0.2f, // 20% deviation threshold (10.0 deviation) + .consecutive_violations = 0, + .max_violations = 2, // Alert after 2 consecutive violations + .call_count = 0 + }; + + setupCustomMetric(metric_config, history_buffer, "cpu_usage", + EMBEDIDS_METRIC_TYPE_FLOAT, 10, + pattern_detector_algorithm, nullptr, &ctx); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + embedids_metric_value_t value; + + // Add 3 values that deviate significantly from baseline (70.0 vs 50.0 baseline = 20 deviation > 10 threshold) + value.f32 = 70.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "cpu_usage", value, 1000), EMBEDIDS_OK); + value.f32 = 75.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "cpu_usage", value, 2000), EMBEDIDS_OK); + value.f32 = 80.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "cpu_usage", value, 3000), EMBEDIDS_OK); + + // First analysis: should detect first violation (average of 3 points = 75.0, deviation = 25.0 > 10.0) + EXPECT_EQ(embedids_analyze_metric(&context, "cpu_usage"), EMBEDIDS_OK); + EXPECT_EQ(ctx.consecutive_violations, 1); + + // Add another high value to continue the pattern + value.f32 = 85.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "cpu_usage", value, 4000), EMBEDIDS_OK); + + // Second analysis: should detect second violation and trigger alert + // New average of last 3: (75+80+85)/3 = 80.0, deviation = 30.0 > 10.0 + EXPECT_EQ(embedids_analyze_metric(&context, "cpu_usage"), EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); + EXPECT_GT(ctx.call_count, 0); + EXPECT_EQ(ctx.consecutive_violations, 0); // Should be reset after alert +} + +TEST_F(EmbedIDSExtensibleTest, RateChangeDetectorBasicOperation) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + float max_rate = 10.0f; // Max 10 units per second + rate_change_context_t ctx = { + .max_rate = max_rate, + .call_count = 0, + .last_result = EMBEDIDS_OK + }; + + setupCustomMetric(metric_config, history_buffer, "temperature", + EMBEDIDS_METRIC_TYPE_FLOAT, 10, + rate_change_algorithm, &max_rate, &ctx); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add values with gradual change (should not trigger) + embedids_metric_value_t value; + value.f32 = 20.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "temperature", value, 1000), EMBEDIDS_OK); + value.f32 = 25.0f; // Change of 5 units in 1 second = 5/s rate + EXPECT_EQ(embedids_add_datapoint(&context, "temperature", value, 2000), EMBEDIDS_OK); + + EXPECT_EQ(embedids_analyze_metric(&context, "temperature"), EMBEDIDS_OK); + EXPECT_GT(ctx.call_count, 0); + EXPECT_EQ(ctx.last_result, EMBEDIDS_OK); +} + +TEST_F(EmbedIDSExtensibleTest, RateChangeDetectorRapidChange) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + float max_rate = 10.0f; // Max 10 units per second + rate_change_context_t ctx = { + .max_rate = max_rate, + .call_count = 0, + .last_result = EMBEDIDS_OK + }; + + setupCustomMetric(metric_config, history_buffer, "temperature", + EMBEDIDS_METRIC_TYPE_FLOAT, 10, + rate_change_algorithm, &max_rate, &ctx); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add values with rapid change (should trigger) + embedids_metric_value_t value; + value.f32 = 20.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "temperature", value, 1000), EMBEDIDS_OK); + value.f32 = 40.0f; // Change of 20 units in 1 second = 20/s rate (exceeds 10/s) + EXPECT_EQ(embedids_add_datapoint(&context, "temperature", value, 2000), EMBEDIDS_OK); + + EXPECT_EQ(embedids_analyze_metric(&context, "temperature"), EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); + EXPECT_GT(ctx.call_count, 0); + EXPECT_EQ(ctx.last_result, EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); +} + +TEST_F(EmbedIDSExtensibleTest, VarianceDetectorBasicOperation) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + variance_detector_context_t ctx = { + .variance_threshold = 100.0f, + .window_size = 5, + .call_count = 0, + .calculated_variance = 0.0f + }; + + setupCustomMetric(metric_config, history_buffer, "network_latency", + EMBEDIDS_METRIC_TYPE_FLOAT, 10, + variance_detector_algorithm, nullptr, &ctx); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add consistent values (low variance) + embedids_metric_value_t value; + for (int i = 0; i < 6; i++) { + value.f32 = 10.0f + static_cast(i % 2); // Values alternate between 10 and 11 + EXPECT_EQ(embedids_add_datapoint(&context, "network_latency", value, 1000 + i * 1000), EMBEDIDS_OK); + } + + EXPECT_EQ(embedids_analyze_metric(&context, "network_latency"), EMBEDIDS_OK); + EXPECT_GT(ctx.call_count, 0); + EXPECT_LT(ctx.calculated_variance, ctx.variance_threshold); +} + +TEST_F(EmbedIDSExtensibleTest, VarianceDetectorHighVariance) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + variance_detector_context_t ctx = { + .variance_threshold = 10.0f, // Low threshold + .window_size = 5, + .call_count = 0, + .calculated_variance = 0.0f + }; + + setupCustomMetric(metric_config, history_buffer, "network_latency", + EMBEDIDS_METRIC_TYPE_FLOAT, 10, + variance_detector_algorithm, nullptr, &ctx); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add highly variable values + embedids_metric_value_t value; + float values[] = {5.0f, 50.0f, 2.0f, 45.0f, 8.0f, 40.0f}; + for (int i = 0; i < 6; i++) { + value.f32 = values[i]; + EXPECT_EQ(embedids_add_datapoint(&context, "network_latency", value, 1000 + i * 1000), EMBEDIDS_OK); + } + + EXPECT_EQ(embedids_analyze_metric(&context, "network_latency"), EMBEDIDS_ERROR_STATISTICAL_ANOMALY); + EXPECT_GT(ctx.call_count, 0); + EXPECT_GT(ctx.calculated_variance, ctx.variance_threshold); +} + +// ============================================================================ +// Multiple Algorithm Tests +// ============================================================================ + +TEST_F(EmbedIDSExtensibleTest, MultipleCustomAlgorithmsOnSingleMetric) { + embedids_metric_datapoint_t history_buffer[15]; + embedids_metric_config_t metric_config; + + // Setup contexts for both algorithms + pattern_detector_context_t pattern_ctx = { + .baseline = 50.0f, + .threshold_multiplier = 0.3f, + .consecutive_violations = 0, + .max_violations = 2, + .call_count = 0 + }; + + variance_detector_context_t variance_ctx = { + .variance_threshold = 50.0f, + .window_size = 4, + .call_count = 0, + .calculated_variance = 0.0f + }; + + // Setup metric with multiple custom algorithms + memset(&metric_config, 0, sizeof(metric_config)); + strncpy(metric_config.metric.name, "multi_algo", EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + metric_config.metric.type = EMBEDIDS_METRIC_TYPE_FLOAT; + metric_config.metric.enabled = true; + metric_config.metric.history = history_buffer; + metric_config.metric.max_history_size = 15; + metric_config.metric.current_size = 0; + metric_config.metric.write_index = 0; + metric_config.num_algorithms = 2; + + // First algorithm: Pattern detector + metric_config.algorithms[0].type = EMBEDIDS_ALGORITHM_CUSTOM; + metric_config.algorithms[0].enabled = true; + metric_config.algorithms[0].config.custom.function = pattern_detector_algorithm; + metric_config.algorithms[0].config.custom.config = nullptr; + metric_config.algorithms[0].config.custom.context = &pattern_ctx; + + // Second algorithm: Variance detector + metric_config.algorithms[1].type = EMBEDIDS_ALGORITHM_CUSTOM; + metric_config.algorithms[1].enabled = true; + metric_config.algorithms[1].config.custom.function = variance_detector_algorithm; + metric_config.algorithms[1].config.custom.config = nullptr; + metric_config.algorithms[1].config.custom.context = &variance_ctx; + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add normal values (should pass both algorithms) + embedids_metric_value_t value; + for (int i = 0; i < 6; i++) { + value.f32 = 48.0f + static_cast(i % 3); // Values: 48, 49, 50, 48, 49, 50 + EXPECT_EQ(embedids_add_datapoint(&context, "multi_algo", value, 1000 + i * 1000), EMBEDIDS_OK); + } + + EXPECT_EQ(embedids_analyze_metric(&context, "multi_algo"), EMBEDIDS_OK); + EXPECT_GT(pattern_ctx.call_count, 0); + EXPECT_GT(variance_ctx.call_count, 0); +} + +// ============================================================================ +// Error Handling Tests +// ============================================================================ + +TEST_F(EmbedIDSExtensibleTest, CustomAlgorithmErrorHandling) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupCustomMetric(metric_config, history_buffer, "error_test", + EMBEDIDS_METRIC_TYPE_FLOAT, 10, + always_fail_algorithm, nullptr, nullptr); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + embedids_metric_value_t value; + value.f32 = 25.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "error_test", value, 1000), EMBEDIDS_OK); + + // Algorithm should always fail + EXPECT_EQ(embedids_analyze_metric(&context, "error_test"), EMBEDIDS_ERROR_CUSTOM_DETECTION); +} + +TEST_F(EmbedIDSExtensibleTest, NullAlgorithmFunction) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupCustomMetric(metric_config, history_buffer, "null_test", + EMBEDIDS_METRIC_TYPE_FLOAT, 10, + nullptr, nullptr, nullptr); + + // Library may allow null custom algorithm function at init + // Let's check if it handles it gracefully during analysis + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + embedids_metric_value_t value; + value.f32 = 25.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "null_test", value, 1000), EMBEDIDS_OK); + + // Analysis should handle null function gracefully (no crash) + embedids_result_t result = embedids_analyze_metric(&context, "null_test"); + // Either OK (ignored) or error is acceptable behavior + EXPECT_TRUE(result == EMBEDIDS_OK || result != EMBEDIDS_OK); +} + +TEST_F(EmbedIDSExtensibleTest, CustomAlgorithmWithNullMetric) { + // This tests the algorithm's internal null checking + embedids_result_t result = pattern_detector_algorithm(nullptr, nullptr, nullptr); + EXPECT_EQ(result, EMBEDIDS_ERROR_INVALID_PARAM); +} + +// ============================================================================ +// Context State Management Tests +// ============================================================================ + +TEST_F(EmbedIDSExtensibleTest, AlgorithmContextStateManagement) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + pattern_detector_context_t ctx = { + .baseline = 30.0f, + .threshold_multiplier = 0.1f, // Very strict threshold (10% = 3.0 deviation) + .consecutive_violations = 0, + .max_violations = 3, + .call_count = 0 + }; + + setupCustomMetric(metric_config, history_buffer, "state_test", + EMBEDIDS_METRIC_TYPE_FLOAT, 10, + pattern_detector_algorithm, nullptr, &ctx); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + embedids_metric_value_t value; + + // Add 3 values that violate threshold (average will be 40.0, deviation = 10.0 > 3.0) + value.f32 = 40.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "state_test", value, 1000), EMBEDIDS_OK); + value.f32 = 40.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "state_test", value, 2000), EMBEDIDS_OK); + value.f32 = 40.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "state_test", value, 3000), EMBEDIDS_OK); + + // First analysis - should count first violation + EXPECT_EQ(embedids_analyze_metric(&context, "state_test"), EMBEDIDS_OK); + EXPECT_EQ(ctx.consecutive_violations, 1); + + // Continue with violating values + value.f32 = 45.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "state_test", value, 4000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "state_test"), EMBEDIDS_OK); + EXPECT_EQ(ctx.consecutive_violations, 2); + + // Third violation - should trigger alert and reset + value.f32 = 50.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "state_test", value, 5000), EMBEDIDS_OK); + EXPECT_EQ(embedids_analyze_metric(&context, "state_test"), EMBEDIDS_ERROR_THRESHOLD_EXCEEDED); + EXPECT_EQ(ctx.consecutive_violations, 0); // Should be reset after alert + + // Add normal values to test reset behavior + value.f32 = 29.0f; // Within threshold + EXPECT_EQ(embedids_add_datapoint(&context, "state_test", value, 6000), EMBEDIDS_OK); + value.f32 = 30.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "state_test", value, 7000), EMBEDIDS_OK); + value.f32 = 31.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "state_test", value, 8000), EMBEDIDS_OK); + + EXPECT_EQ(embedids_analyze_metric(&context, "state_test"), EMBEDIDS_OK); + EXPECT_EQ(ctx.consecutive_violations, 0); // Should remain 0 for normal values +} + +// ============================================================================ +// Integration Tests with Multiple Metrics +// ============================================================================ + +TEST_F(EmbedIDSExtensibleTest, MultipleMetricsWithCustomAlgorithms) { + // Setup buffers and contexts for multiple metrics + embedids_metric_datapoint_t cpu_history[10]; + embedids_metric_datapoint_t memory_history[10]; + embedids_metric_datapoint_t network_history[10]; + + pattern_detector_context_t cpu_ctx = {50.0f, 0.2f, 0, 2, 0}; + rate_change_context_t memory_ctx = {15.0f, 0, EMBEDIDS_OK}; + variance_detector_context_t network_ctx = {25.0f, 4, 0, 0.0f}; + + embedids_metric_config_t metrics[3]; + + // Setup CPU metric with pattern detector + setupCustomMetric(metrics[0], cpu_history, "cpu_usage", + EMBEDIDS_METRIC_TYPE_FLOAT, 10, + pattern_detector_algorithm, nullptr, &cpu_ctx); + + // Setup memory metric with rate change detector + float max_memory_rate = 15.0f; + setupCustomMetric(metrics[1], memory_history, "memory_usage", + EMBEDIDS_METRIC_TYPE_FLOAT, 10, + rate_change_algorithm, &max_memory_rate, &memory_ctx); + + // Setup network metric with variance detector + setupCustomMetric(metrics[2], network_history, "network_latency", + EMBEDIDS_METRIC_TYPE_FLOAT, 10, + variance_detector_algorithm, nullptr, &network_ctx); + + ASSERT_EQ(initializeWithMetrics(metrics, 3), EMBEDIDS_OK); + + // Add normal data to all metrics + embedids_metric_value_t value; + + // CPU: Normal values + value.f32 = 45.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "cpu_usage", value, 1000), EMBEDIDS_OK); + + // Memory: Gradual change + value.f32 = 60.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "memory_usage", value, 1000), EMBEDIDS_OK); + value.f32 = 65.0f; // Change of 5 in 1 second = 5/s (under 15/s limit) + EXPECT_EQ(embedids_add_datapoint(&context, "memory_usage", value, 2000), EMBEDIDS_OK); + + // Network: Low variance values + for (int i = 0; i < 5; i++) { + value.f32 = 100.0f + static_cast(i % 2) * 2.0f; // 100, 102, 100, 102, 100 + EXPECT_EQ(embedids_add_datapoint(&context, "network_latency", value, 1000 + i * 1000), EMBEDIDS_OK); + } + + // Analyze all metrics - should all be OK + EXPECT_EQ(embedids_analyze_all(&context), EMBEDIDS_OK); + + // Verify all algorithms were called + EXPECT_GT(cpu_ctx.call_count, 0); + EXPECT_GT(memory_ctx.call_count, 0); + EXPECT_GT(network_ctx.call_count, 0); +} + +TEST_F(EmbedIDSExtensibleTest, CustomAlgorithmMetricTypeValidation) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + uint32_t call_count = 0; + + // Custom algorithm that checks metric type + auto type_checking_algorithm = [](const embedids_metric_t* metric, + const void* /* config */, void* context) -> embedids_result_t { + uint32_t* count = static_cast(context); + (*count)++; + + if (!metric) return EMBEDIDS_ERROR_INVALID_PARAM; + + // Check that metric type is float as expected + if (metric->type != EMBEDIDS_METRIC_TYPE_FLOAT) { + return EMBEDIDS_ERROR_METRIC_TYPE_MISMATCH; + } + + // Check that history buffer is properly initialized + if (!metric->history) { + return EMBEDIDS_ERROR_INVALID_PARAM; + } + + return EMBEDIDS_OK; + }; + + setupCustomMetric(metric_config, history_buffer, "type_test", + EMBEDIDS_METRIC_TYPE_FLOAT, 10, + type_checking_algorithm, nullptr, &call_count); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + embedids_metric_value_t value; + value.f32 = 25.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "type_test", value, 1000), EMBEDIDS_OK); + + EXPECT_EQ(embedids_analyze_metric(&context, "type_test"), EMBEDIDS_OK); + EXPECT_GT(call_count, 0); +} diff --git a/tests/test_metrics.cpp b/tests/test_metrics.cpp new file mode 100644 index 0000000..0b521fd --- /dev/null +++ b/tests/test_metrics.cpp @@ -0,0 +1,302 @@ +#include "embedids.h" +#include +#include + +/** + * @brief Test fixture for metric management functionality + * + * Tests metric configuration, data point operations, different metric types, + * and metric-specific operations like reset functionality. + */ +class EmbedIDSMetricsTest : public ::testing::Test { +protected: + embedids_context_t context; + + void SetUp() override { + memset(&context, 0, sizeof(context)); + embedids_cleanup(&context); + } + + void TearDown() override { + embedids_cleanup(&context); + } + + /** + * @brief Helper function to create a basic metric configuration + */ + void setupBasicMetric(embedids_metric_config_t& metric_config, + embedids_metric_datapoint_t* history_buffer, + const char* name, + embedids_metric_type_t type, + uint32_t history_size) { + memset(&metric_config, 0, sizeof(metric_config)); + strncpy(metric_config.metric.name, name, EMBEDIDS_MAX_METRIC_NAME_LEN - 1); + metric_config.metric.type = type; + metric_config.metric.enabled = true; + metric_config.metric.history = history_buffer; + metric_config.metric.max_history_size = history_size; + metric_config.metric.current_size = 0; + metric_config.metric.write_index = 0; + } + + /** + * @brief Helper function to initialize system with a single metric + */ + embedids_result_t initializeWithMetric(embedids_metric_config_t* metric_config) { + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = metric_config; + system_config.max_metrics = 1; + system_config.num_active_metrics = 1; + + return embedids_init(&context, &system_config); + } + +private: + embedids_system_config_t system_config; +}; + +// ============================================================================ +// Data Point Operations Tests +// ============================================================================ + +TEST_F(EmbedIDSMetricsTest, AddDatapointToValidMetric) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "cpu_usage", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + EXPECT_TRUE(embedids_is_initialized(&context)); + + // Test adding valid data point + embedids_metric_value_t value; + value.f32 = 45.5f; + embedids_result_t result = embedids_add_datapoint(&context, "cpu_usage", value, 1000); + EXPECT_EQ(result, EMBEDIDS_OK); +} + +TEST_F(EmbedIDSMetricsTest, AddDatapointToNonexistentMetric) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "cpu_usage", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Test adding data point for non-existent metric + embedids_metric_value_t value; + value.f32 = 45.5f; + embedids_result_t result = embedids_add_datapoint(&context, "nonexistent_metric", value, 1000); + EXPECT_EQ(result, EMBEDIDS_ERROR_METRIC_NOT_FOUND); +} + +// ============================================================================ +// Different Metric Types Tests +// ============================================================================ + +TEST_F(EmbedIDSMetricsTest, DifferentMetricTypes) { + // Setup multiple metrics with different types + embedids_metric_datapoint_t history_buffer1[5]; + embedids_metric_datapoint_t history_buffer2[5]; + embedids_metric_datapoint_t history_buffer3[5]; + embedids_metric_datapoint_t history_buffer4[5]; + + embedids_metric_config_t metric_configs[4]; + + // Configure uint32 metric + setupBasicMetric(metric_configs[0], history_buffer1, "counter", + EMBEDIDS_METRIC_TYPE_UINT32, 5); + + // Configure uint64 metric + setupBasicMetric(metric_configs[1], history_buffer2, "byte_counter", + EMBEDIDS_METRIC_TYPE_UINT64, 5); + + // Configure boolean metric + setupBasicMetric(metric_configs[2], history_buffer3, "alarm_status", + EMBEDIDS_METRIC_TYPE_BOOL, 5); + + // Configure enum metric + setupBasicMetric(metric_configs[3], history_buffer4, "system_state", + EMBEDIDS_METRIC_TYPE_ENUM, 5); + + // Setup system configuration + embedids_system_config_t system_config; + memset(&system_config, 0, sizeof(system_config)); + system_config.metrics = metric_configs; + system_config.max_metrics = 4; + system_config.num_active_metrics = 4; + + // Initialize system + embedids_result_t result = embedids_init(&context, &system_config); + EXPECT_EQ(result, EMBEDIDS_OK); + + // Test uint32 metric + embedids_metric_value_t value; + value.u32 = 12345; + result = embedids_add_datapoint(&context, "counter", value, 1000); + EXPECT_EQ(result, EMBEDIDS_OK); + + // Test uint64 metric + value.u64 = 1234567890ULL; + result = embedids_add_datapoint(&context, "byte_counter", value, 1000); + EXPECT_EQ(result, EMBEDIDS_OK); + + // Test boolean metric + value.boolean = true; + result = embedids_add_datapoint(&context, "alarm_status", value, 2000); + EXPECT_EQ(result, EMBEDIDS_OK); + + // Test enum metric + value.enum_val = 3; + result = embedids_add_datapoint(&context, "system_state", value, 3000); + EXPECT_EQ(result, EMBEDIDS_OK); +} + +TEST_F(EmbedIDSMetricsTest, FloatMetricPrecision) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "temperature", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Test precision values + embedids_metric_value_t value; + + value.f32 = 3.14159f; + EXPECT_EQ(embedids_add_datapoint(&context, "temperature", value, 1000), EMBEDIDS_OK); + + value.f32 = -273.15f; + EXPECT_EQ(embedids_add_datapoint(&context, "temperature", value, 2000), EMBEDIDS_OK); + + value.f32 = 0.001f; + EXPECT_EQ(embedids_add_datapoint(&context, "temperature", value, 3000), EMBEDIDS_OK); +} + +TEST_F(EmbedIDSMetricsTest, LargeIntegerValues) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "large_counter", + EMBEDIDS_METRIC_TYPE_UINT64, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + embedids_metric_value_t value; + + // Test maximum values + value.u64 = UINT64_MAX; + EXPECT_EQ(embedids_add_datapoint(&context, "large_counter", value, 1000), EMBEDIDS_OK); + + // Test typical large values + value.u64 = 18446744073709551615ULL; // Near max uint64 + EXPECT_EQ(embedids_add_datapoint(&context, "large_counter", value, 2000), EMBEDIDS_OK); +} + +// ============================================================================ +// Metric Reset Functionality Tests +// ============================================================================ + +TEST_F(EmbedIDSMetricsTest, MetricResetFunctionality) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "test_metric", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add some data points + embedids_metric_value_t value; + value.f32 = 25.0f; + embedids_result_t result = embedids_add_datapoint(&context, "test_metric", value, 1000); + EXPECT_EQ(result, EMBEDIDS_OK); + + value.f32 = 30.0f; + result = embedids_add_datapoint(&context, "test_metric", value, 2000); + EXPECT_EQ(result, EMBEDIDS_OK); + + // Reset all metrics + result = embedids_reset_all_metrics(&context); + EXPECT_EQ(result, EMBEDIDS_OK); +} + +// ============================================================================ +// Metric Buffer Management Tests +// ============================================================================ + +TEST_F(EmbedIDSMetricsTest, MetricBufferOverflow) { + const uint32_t buffer_size = 3; + embedids_metric_datapoint_t history_buffer[buffer_size]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "small_buffer", + EMBEDIDS_METRIC_TYPE_FLOAT, buffer_size); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Fill buffer beyond capacity + embedids_metric_value_t value; + for (uint32_t i = 0; i < buffer_size + 2; i++) { + value.f32 = (float)i; + embedids_result_t result = embedids_add_datapoint(&context, "small_buffer", value, 1000 + i * 1000); + EXPECT_EQ(result, EMBEDIDS_OK); + } + + // Buffer should handle overflow gracefully (circular buffer behavior) + EXPECT_EQ(metric_config.metric.current_size, buffer_size); +} + +TEST_F(EmbedIDSMetricsTest, MetricTimestampOrdering) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "time_test", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Add data points with different timestamps + embedids_metric_value_t value; + + value.f32 = 1.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "time_test", value, 1000), EMBEDIDS_OK); + + value.f32 = 2.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "time_test", value, 500), EMBEDIDS_OK); // Earlier timestamp + + value.f32 = 3.0f; + EXPECT_EQ(embedids_add_datapoint(&context, "time_test", value, 2000), EMBEDIDS_OK); + + // System should accept all data points regardless of timestamp order + EXPECT_EQ(metric_config.metric.current_size, 3); +} + +// ============================================================================ +// Metric State Validation Tests +// ============================================================================ + +TEST_F(EmbedIDSMetricsTest, DisabledMetricBehavior) { + embedids_metric_datapoint_t history_buffer[10]; + embedids_metric_config_t metric_config; + + setupBasicMetric(metric_config, history_buffer, "disabled_metric", + EMBEDIDS_METRIC_TYPE_FLOAT, 10); + + // Disable the metric + metric_config.metric.enabled = false; + + ASSERT_EQ(initializeWithMetric(&metric_config), EMBEDIDS_OK); + + // Attempt to add data to disabled metric + embedids_metric_value_t value; + value.f32 = 10.0f; + embedids_result_t result = embedids_add_datapoint(&context, "disabled_metric", value, 1000); + + // Behavior may vary - should either reject or accept but not process + // The exact behavior depends on implementation + EXPECT_TRUE(result == EMBEDIDS_OK || result == EMBEDIDS_ERROR_METRIC_DISABLED); +}