diff options
| author | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-17 11:57:52 +0100 |
|---|---|---|
| committer | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-17 11:57:52 +0100 |
| commit | 48a9df043df64887cb99e03d7613379c947d11d8 (patch) | |
| tree | 897d94bcc55c481a82878c5d2de5ec3369df33ed | |
| parent | a54421dd976fd8081e96c11c2621076876c9986b (diff) | |
| download | BobinkCOpcUa-48a9df043df64887cb99e03d7613379c947d11d8.tar.gz BobinkCOpcUa-48a9df043df64887cb99e03d7613379c947d11d8.zip | |
Add configurable log level as optional CLI argument
All three programs now accept an optional second argument [log-level]
(trace, debug, info, warning, error, fatal) defaulting to info. The
level is applied by setting the logger context pointer directly,
avoiding a memory leak that would occur from overwriting the
heap-allocated logger struct. Also documents the ASan leak-check
workflow in CLAUDE.md.
| -rw-r--r-- | CLAUDE.md | 19 | ||||
| -rw-r--r-- | src/client_find_servers.c | 17 | ||||
| -rw-r--r-- | src/common.c | 20 | ||||
| -rw-r--r-- | src/common.h | 11 | ||||
| -rw-r--r-- | src/server_lds.c | 21 | ||||
| -rw-r--r-- | src/server_register.c | 19 | ||||
| -rwxr-xr-x | tests/run_test.sh | 46 |
7 files changed, 123 insertions, 30 deletions
@@ -61,7 +61,7 @@ Existing certs live in `certs/`. Only regenerate if missing. ## Running -Each program takes a single argument: a configuration file. Example config files are in `config/`. Start programs in order in separate terminals from the project root: +Each program takes a configuration file and an optional log level: `<program> <config-file> [log-level]`. The log level defaults to `info`. Valid levels: `trace`, `debug`, `info`, `warning`, `error`, `fatal`. Example config files are in `config/`. Start programs in order in separate terminals from the project root: **1. Local Discovery Server (LDS)** @@ -163,6 +163,23 @@ Each test starts ServerLDS (port 14840) and ServerRegister (port 14841), runs Cl To add a new test case: create a directory under `tests/` with 3 config files (`server_lds.conf`, `server_register.conf`, `client_find_servers.conf`), then add a `"name;ExpectedPolicy"` entry to the `INTEGRATION_TESTS` list in `CMakeLists.txt`. +### Memory Leak Check (ASan) + +Rebuild with AddressSanitizer, then run the tests: + +```sh +cmake -B build -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" +cmake --build build --parallel +ASAN_OPTIONS="detect_leaks=1" ctest --test-dir build --output-on-failure +``` + +Reconfigure without the flags afterwards to return to a normal build: + +```sh +cmake -B build -DCMAKE_C_FLAGS="" -DCMAKE_EXE_LINKER_FLAGS="" +cmake --build build --parallel +``` + ## Project Structure | Path | Purpose | diff --git a/src/client_find_servers.c b/src/client_find_servers.c index e50623f..a85b63f 100644 --- a/src/client_find_servers.c +++ b/src/client_find_servers.c @@ -211,10 +211,21 @@ readServerTime (UA_Client *client, int main (int argc, char **argv) { - if (argc != 2) + if (argc < 2 || argc > 3) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Usage: %s <config-file>", argv[0]); + "Usage: %s <config-file> [log-level]", argv[0]); + return EXIT_FAILURE; + } + + const char *logLevelStr = (argc == 3) ? argv[2] : "info"; + int logLevel = parseLogLevel (logLevelStr); + if (logLevel < 0) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown log level: %s " + "(expected trace, debug, info, warning, error, fatal)", + logLevelStr); return EXIT_FAILURE; } @@ -302,6 +313,8 @@ main (int argc, char **argv) configFree (&cfg); return EXIT_FAILURE; } + UA_ClientConfig *clientConfig = UA_Client_getConfig (client); + clientConfig->logging->context = (void *)(uintptr_t)logLevel; UA_ApplicationDescription *applicationDescriptionArray = NULL; size_t applicationDescriptionArraySize = 0; diff --git a/src/common.c b/src/common.c index d102868..7d378f1 100644 --- a/src/common.c +++ b/src/common.c @@ -59,6 +59,26 @@ loadFile (const char *const path) * Security Helpers * ======================================================================== */ +int +parseLogLevel (const char *name) +{ + static const struct + { + const char *name; + UA_LogLevel level; + } levels[] = { + { "trace", UA_LOGLEVEL_TRACE }, { "debug", UA_LOGLEVEL_DEBUG }, + { "info", UA_LOGLEVEL_INFO }, { "warning", UA_LOGLEVEL_WARNING }, + { "error", UA_LOGLEVEL_ERROR }, { "fatal", UA_LOGLEVEL_FATAL }, + }; + for (size_t i = 0; i < sizeof (levels) / sizeof (levels[0]); i++) + { + if (strcmp (name, levels[i].name) == 0) + return (int)levels[i].level; + } + return -1; +} + UA_MessageSecurityMode parseSecurityMode (const char *name) { diff --git a/src/common.h b/src/common.h index e3d2f4c..e8c0c78 100644 --- a/src/common.h +++ b/src/common.h @@ -44,6 +44,17 @@ UA_Server *createSecureServer (UA_UInt16 port, const char *applicationUri, UA_StatusCode *retval); /** + * @brief Parses a log-level name into the corresponding UA_LogLevel value. + * + * Accepted names (case-sensitive): "trace", "debug", "info", "warning", + * "error", "fatal". + * + * @param name Log-level name string. + * @return The matching UA_LogLevel, or -1 if the name is not recognized. + */ +int parseLogLevel (const char *name); + +/** * @brief Parses a security mode name into the corresponding enum value. * * Accepted names: "None", "Sign", "SignAndEncrypt". diff --git a/src/server_lds.c b/src/server_lds.c index c6960d5..2fe508f 100644 --- a/src/server_lds.c +++ b/src/server_lds.c @@ -35,10 +35,21 @@ main (int argc, char *argv[]) signal (SIGINT, stopHandler); signal (SIGTERM, stopHandler); - if (argc != 2) + if (argc < 2 || argc > 3) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Usage: %s <config-file>", argv[0]); + "Usage: %s <config-file> [log-level]", argv[0]); + return EXIT_FAILURE; + } + + const char *logLevelStr = (argc == 3) ? argv[2] : "info"; + int logLevel = parseLogLevel (logLevelStr); + if (logLevel < 0) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown log level: %s " + "(expected trace, debug, info, warning, error, fatal)", + logLevelStr); return EXIT_FAILURE; } @@ -116,6 +127,12 @@ main (int argc, char *argv[]) } UA_ServerConfig *serverConfig = UA_Server_getConfig (server); + serverConfig->logging->context = (void *)(uintptr_t)logLevel; + + /* Some OPC UA stacks omit the timestamp in the request header. The + default behaviour rejects these requests with BadInvalidTimestamp. + Downgrade to a warning so third-party servers can still register. */ + serverConfig->verifyRequestTimestamp = UA_RULEHANDLING_WARN; /* Configure access control after server creation because UA_ServerConfig_setDefaultWithSecurityPolicies (called by diff --git a/src/server_register.c b/src/server_register.c index ae8e959..5bdba8e 100644 --- a/src/server_register.c +++ b/src/server_register.c @@ -42,10 +42,21 @@ main (int argc, char **argv) signal (SIGINT, stopHandler); signal (SIGTERM, stopHandler); - if (argc != 2) + if (argc < 2 || argc > 3) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Usage: %s <config-file>", argv[0]); + "Usage: %s <config-file> [log-level]", argv[0]); + return EXIT_FAILURE; + } + + const char *logLevelStr = (argc == 3) ? argv[2] : "info"; + int logLevel = parseLogLevel (logLevelStr); + if (logLevel < 0) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown log level: %s " + "(expected trace, debug, info, warning, error, fatal)", + logLevelStr); return EXIT_FAILURE; } @@ -182,6 +193,7 @@ main (int argc, char **argv) } UA_ServerConfig *serverConfig = UA_Server_getConfig (server); + serverConfig->logging->context = (void *)(uintptr_t)logLevel; /* Configure access control after server creation because UA_ServerConfig_setDefaultWithSecurityPolicies (called by @@ -224,6 +236,7 @@ main (int argc, char **argv) configFree (&cfg); return EXIT_FAILURE; } + clientConfig.logging->context = (void *)(uintptr_t)logLevel; if (clientUsername) UA_ClientConfig_setAuthenticationUsername (&clientConfig, clientUsername, clientPassword); @@ -254,6 +267,7 @@ main (int argc, char **argv) trustPaths, trustSize, securityMode, securityPolicyUri); if (retval == UA_STATUSCODE_GOOD) { + clientConfig.logging->context = (void *)(uintptr_t)logLevel; if (clientUsername) UA_ClientConfig_setAuthenticationUsername ( &clientConfig, clientUsername, clientPassword); @@ -278,6 +292,7 @@ main (int argc, char **argv) trustSize, securityMode, securityPolicyUri); if (retval == UA_STATUSCODE_GOOD) { + clientConfig.logging->context = (void *)(uintptr_t)logLevel; if (clientUsername) UA_ClientConfig_setAuthenticationUsername ( &clientConfig, clientUsername, clientPassword); diff --git a/tests/run_test.sh b/tests/run_test.sh index 5173fa3..8a87824 100755 --- a/tests/run_test.sh +++ b/tests/run_test.sh @@ -29,18 +29,18 @@ FAILURES=0 # ── cleanup ──────────────────────────────────────────────────── cleanup() { - [ -n "$LDS_PID" ] && kill "$LDS_PID" 2>/dev/null && wait "$LDS_PID" 2>/dev/null - [ -n "$SR_PID" ] && kill "$SR_PID" 2>/dev/null && wait "$SR_PID" 2>/dev/null - [ -n "$TMPFILE" ] && rm -f "$TMPFILE" + [ -n "$LDS_PID" ] && kill "$LDS_PID" 2>/dev/null && wait "$LDS_PID" 2>/dev/null + [ -n "$SR_PID" ] && kill "$SR_PID" 2>/dev/null && wait "$SR_PID" 2>/dev/null + [ -n "$TMPFILE" ] && rm -f "$TMPFILE" } trap cleanup EXIT # ── port check ───────────────────────────────────────────────── for port in $LDS_PORT $SR_PORT; do - if ss -tlnp 2>/dev/null | grep -q ":${port} "; then - echo "FAIL: port $port is already in use" - exit 1 - fi + if ss -tlnp 2>/dev/null | grep -q ":${port} "; then + echo "FAIL: port $port is already in use" + exit 1 + fi done # ── start LDS ────────────────────────────────────────────────── @@ -48,8 +48,8 @@ build/ServerLDS "$CONFIG_DIR/server_lds.conf" >/dev/null 2>&1 & LDS_PID=$! sleep 2 if ! kill -0 "$LDS_PID" 2>/dev/null; then - echo "FAIL: ServerLDS exited prematurely" - exit 1 + echo "FAIL: ServerLDS exited prematurely" + exit 1 fi # ── start ServerRegister ─────────────────────────────────────── @@ -57,8 +57,8 @@ build/ServerRegister "$CONFIG_DIR/server_register.conf" >/dev/null 2>&1 & SR_PID=$! sleep 3 if ! kill -0 "$SR_PID" 2>/dev/null; then - echo "FAIL: ServerRegister exited prematurely" - exit 1 + echo "FAIL: ServerRegister exited prematurely" + exit 1 fi # ── run client ───────────────────────────────────────────────── @@ -70,13 +70,13 @@ CLIENT_OUTPUT=$(<"$TMPFILE") # ── validation checks ───────────────────────────────────────── check() { - local label="$1" result="$2" - if [ "$result" -eq 0 ]; then - echo "PASS: $label" - else - echo "FAIL: $label" - FAILURES=$((FAILURES + 1)) - fi + local label="$1" result="$2" + if [ "$result" -eq 0 ]; then + echo "PASS: $label" + else + echo "FAIL: $label" + FAILURES=$((FAILURES + 1)) + fi } # 1. Exit code @@ -97,10 +97,10 @@ check "endpoint contains $EXPECTED_POLICY" $? # ── result ───────────────────────────────────────────────────── if [ "$FAILURES" -ne 0 ]; then - echo "" - echo "--- client output ---" - echo "$CLIENT_OUTPUT" - echo "--- end ---" - exit 1 + echo "" + echo "--- client output ---" + echo "$CLIENT_OUTPUT" + echo "--- end ---" + exit 1 fi exit 0 |
