diff options
| author | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-17 11:07:37 +0100 |
|---|---|---|
| committer | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-17 11:07:37 +0100 |
| commit | a54421dd976fd8081e96c11c2621076876c9986b (patch) | |
| tree | a7614934364bc692dd94ee13a3ec6d242521194b /src/config.c | |
| parent | d1e229c80a6e51ccc5b21d001271c41d6cda30bf (diff) | |
| download | BobinkCOpcUa-a54421dd976fd8081e96c11c2621076876c9986b.tar.gz BobinkCOpcUa-a54421dd976fd8081e96c11c2621076876c9986b.zip | |
Replace CLI arguments with config-file parser and add integration tests
Introduce a reusable key=value config parser (config.h/c) and convert
all three programs to read their settings from config files instead of
positional command-line arguments. Add example config files in config/
and 6 CTest integration tests covering None/Basic256Sha256/Aes128 with
anonymous and user authentication. Remove the now-obsolete launch.sh.
Diffstat (limited to 'src/config.c')
| -rw-r--r-- | src/config.c | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..163f601 --- /dev/null +++ b/src/config.c @@ -0,0 +1,252 @@ +/** + * @file config.c + * @brief Simple key=value configuration file parser implementation. + */ + +#include "config.h" + +#include <open62541/plugin/log_stdout.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* Enough for typical config files; doubles on overflow. */ +#define CONFIG_INITIAL_CAPACITY 16 + +/* Sufficient for file paths and URIs. */ +#define CONFIG_LINE_MAX 1024 + +/* ======================================================================== + * Static Helpers + * ======================================================================== */ + +/** + * @brief Trims leading and trailing whitespace in place. + * + * Modifies the string by writing a NUL terminator after the last + * non-whitespace character and returns a pointer past any leading + * whitespace. + */ +static char * +trim (char *s) +{ + while (*s == ' ' || *s == '\t') + s++; + size_t len = strlen (s); + while (len > 0 && (s[len - 1] == ' ' || s[len - 1] == '\t')) + len--; + s[len] = '\0'; + return s; +} + +/** + * @brief Appends a key-value pair to the config's dynamic array. + * + * Grows the array by doubling capacity when full. + * + * @return 0 on success, -1 on allocation failure. + */ +static int +configAppend (Config *cfg, const char *key, const char *value) +{ + if (cfg->count == cfg->capacity) + { + size_t newCap = cfg->capacity * 2; + ConfigEntry *tmp = realloc (cfg->entries, newCap * sizeof (ConfigEntry)); + if (!tmp) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Config: out of memory"); + return -1; + } + cfg->entries = tmp; + cfg->capacity = newCap; + } + + cfg->entries[cfg->count].key = strdup (key); + cfg->entries[cfg->count].value = strdup (value); + if (!cfg->entries[cfg->count].key || !cfg->entries[cfg->count].value) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Config: out of memory"); + return -1; + } + cfg->count++; + return 0; +} + +/* ======================================================================== + * Public API + * ======================================================================== */ + +int +configLoad (const char *path, Config *cfg) +{ + memset (cfg, 0, sizeof (Config)); + + cfg->entries = malloc (CONFIG_INITIAL_CAPACITY * sizeof (ConfigEntry)); + if (!cfg->entries) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Config: out of memory"); + return -1; + } + cfg->capacity = CONFIG_INITIAL_CAPACITY; + + FILE *fp = fopen (path, "r"); + if (!fp) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Config: cannot open '%s'", path); + free (cfg->entries); + memset (cfg, 0, sizeof (Config)); + return -1; + } + + char line[CONFIG_LINE_MAX]; + int lineNum = 0; + + while (fgets (line, sizeof (line), fp)) + { + lineNum++; + + /* Strip trailing newline / carriage return. */ + size_t len = strlen (line); + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) + line[--len] = '\0'; + + /* Skip blank lines and comments. */ + char *trimmed = trim (line); + if (*trimmed == '\0' || *trimmed == '#') + continue; + + /* Find the '=' separator. */ + char *eq = strchr (trimmed, '='); + if (!eq) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Config: syntax error at %s:%d " + "(missing '=')", + path, lineNum); + fclose (fp); + configFree (cfg); + return -1; + } + + *eq = '\0'; + char *key = trim (trimmed); + char *value = trim (eq + 1); + + if (*key == '\0') + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Config: empty key at %s:%d", path, lineNum); + fclose (fp); + configFree (cfg); + return -1; + } + + if (configAppend (cfg, key, value) != 0) + { + fclose (fp); + configFree (cfg); + return -1; + } + } + + fclose (fp); + return 0; +} + +const char * +configGet (const Config *cfg, const char *key) +{ + for (size_t i = 0; i < cfg->count; i++) + { + if (strcmp (cfg->entries[i].key, key) == 0) + return cfg->entries[i].value; + } + return NULL; +} + +const char * +configRequire (const Config *cfg, const char *key, const char *program) +{ + const char *val = configGet (cfg, key); + if (!val) + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "%s: missing required config key '%s'", program, key); + return val; +} + +int +configRequireInt (const Config *cfg, const char *key, const char *program) +{ + const char *val = configRequire (cfg, key, program); + if (!val) + return -1; + + char *endptr; + long num = strtol (val, &endptr, 10); + if (*endptr != '\0') + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "%s: config key '%s' is not a valid integer: '%s'", + program, key, val); + return -1; + } + return (int)num; +} + +void +configGetAll (const Config *cfg, const char *key, char ***out, size_t *size) +{ + /* First pass: count matches. */ + size_t count = 0; + for (size_t i = 0; i < cfg->count; i++) + { + if (strcmp (cfg->entries[i].key, key) == 0) + count++; + } + + if (count == 0) + { + *out = NULL; + *size = 0; + return; + } + + /* Second pass: collect pointers. */ + char **arr = malloc (count * sizeof (char *)); + if (!arr) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Config: out of memory"); + *out = NULL; + *size = 0; + return; + } + + size_t idx = 0; + for (size_t i = 0; i < cfg->count; i++) + { + if (strcmp (cfg->entries[i].key, key) == 0) + arr[idx++] = cfg->entries[i].value; + } + + *out = arr; + *size = count; +} + +void +configFree (Config *cfg) +{ + for (size_t i = 0; i < cfg->count; i++) + { + free (cfg->entries[i].key); + free (cfg->entries[i].value); + } + free (cfg->entries); + memset (cfg, 0, sizeof (Config)); +} |
