From 827e90e0daabe32e058e08dd2a253425898a7e7a Mon Sep 17 00:00:00 2001 From: Thomas Vanbesien Date: Tue, 17 Feb 2026 19:06:22 +0100 Subject: Replace ClientFindServers with unified Client, use trust store directories Replace the single-purpose ClientFindServers program with a unified Client that supports three operations via CLI: find-servers, get-endpoints, and read-time. This simplifies the architecture by using one client binary with a single config file instead of a monolithic program that did everything in one run. Split the ServerRegister config into separate server and client config files so the LDS-registration credentials are isolated from the server's own settings. The discovery URL moves from config to a CLI argument. Replace repeated trustList config entries with a single trustStore directory path. Each program now points to a directory under certs/trust/ containing .der files, so adding or removing trust is a file-copy operation rather than editing every config file. Add loadTrustStore()/freeTrustStore() to common.c and remove the now-unused configGetAll() from the config parser. Simplify the test matrix from 6 to 4 cases (security and auth are orthogonal, so the full 3x2 matrix is unnecessary). Update run_test.sh to invoke the new Client three times and use port-polling instead of sleep. --- CMakeLists.txt | 18 +- config/client.conf | 23 ++ config/client_find_servers.conf | 50 --- config/server_lds.conf | 5 +- config/server_register.conf | 54 +-- config/server_register_client.conf | 26 ++ src/client.c | 328 +++++++++++++++ src/client_find_servers.c | 438 --------------------- src/common.c | 114 +++++- src/common.h | 25 ++ src/config.c | 40 -- src/config.h | 21 +- src/server_lds.c | 19 +- src/server_register.c | 227 +++++++---- tests/aes128_anon/client_find_servers.conf | 20 - tests/aes128_anon/server_lds.conf | 12 - tests/aes128_anon/server_register.conf | 21 - tests/aes128_user/client.conf | 11 + tests/aes128_user/client_find_servers.conf | 24 -- tests/aes128_user/server_lds.conf | 3 +- tests/aes128_user/server_register.conf | 25 +- tests/aes128_user/server_register_client.conf | 14 + tests/basic256sha256_anon/client.conf | 9 + tests/basic256sha256_anon/client_find_servers.conf | 20 - tests/basic256sha256_anon/server_lds.conf | 3 +- tests/basic256sha256_anon/server_register.conf | 19 +- .../server_register_client.conf | 12 + tests/basic256sha256_user/client_find_servers.conf | 24 -- tests/basic256sha256_user/server_lds.conf | 14 - tests/basic256sha256_user/server_register.conf | 25 -- tests/none_anon/client.conf | 9 + tests/none_anon/client_find_servers.conf | 20 - tests/none_anon/server_lds.conf | 3 +- tests/none_anon/server_register.conf | 19 +- tests/none_anon/server_register_client.conf | 12 + tests/none_user/client.conf | 11 + tests/none_user/client_find_servers.conf | 24 -- tests/none_user/server_lds.conf | 3 +- tests/none_user/server_register.conf | 25 +- tests/none_user/server_register_client.conf | 14 + tests/run_test.sh | 94 +++-- 41 files changed, 876 insertions(+), 1002 deletions(-) create mode 100644 config/client.conf delete mode 100644 config/client_find_servers.conf create mode 100644 config/server_register_client.conf create mode 100644 src/client.c delete mode 100644 src/client_find_servers.c delete mode 100644 tests/aes128_anon/client_find_servers.conf delete mode 100644 tests/aes128_anon/server_lds.conf delete mode 100644 tests/aes128_anon/server_register.conf create mode 100644 tests/aes128_user/client.conf delete mode 100644 tests/aes128_user/client_find_servers.conf create mode 100644 tests/aes128_user/server_register_client.conf create mode 100644 tests/basic256sha256_anon/client.conf delete mode 100644 tests/basic256sha256_anon/client_find_servers.conf create mode 100644 tests/basic256sha256_anon/server_register_client.conf delete mode 100644 tests/basic256sha256_user/client_find_servers.conf delete mode 100644 tests/basic256sha256_user/server_lds.conf delete mode 100644 tests/basic256sha256_user/server_register.conf create mode 100644 tests/none_anon/client.conf delete mode 100644 tests/none_anon/client_find_servers.conf create mode 100644 tests/none_anon/server_register_client.conf create mode 100644 tests/none_user/client.conf delete mode 100644 tests/none_user/client_find_servers.conf create mode 100644 tests/none_user/server_register_client.conf diff --git a/CMakeLists.txt b/CMakeLists.txt index f126d30..ab6f4fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,7 @@ # # Builds three programs that demonstrate OPC UA discovery. ServerLDS runs a # Local Discovery Server. ServerRegister periodically registers itself with the -# LDS. ClientFindServers queries the LDS for registered servers and their -# endpoints. +# LDS. Client queries servers for discovery info, endpoints, or current time. # # All programs link against DiscoveryCommon (shared helpers and config parser) # which in turn depends on open62541. @@ -27,9 +26,9 @@ include(cmake/BuildDeps.cmake) add_library(DiscoveryCommon STATIC src/common.c src/config.c) target_link_libraries(DiscoveryCommon open62541::open62541) -# Queries the LDS for registered servers and their endpoints. -add_executable(ClientFindServers src/client_find_servers.c) -target_link_libraries(ClientFindServers DiscoveryCommon) +# Unified client: find-servers, get-endpoints, read-time. +add_executable(Client src/client.c) +target_link_libraries(Client DiscoveryCommon) # Runs the Local Discovery Server that other servers register with. add_executable(ServerLDS src/server_lds.c) @@ -61,17 +60,16 @@ endif() # Each test exercises a combination of security mode/policy and authentication # method. _test_names and _test_policies are parallel lists: the Nth name uses # the Nth policy. Each name corresponds to a config directory under tests/ -# containing server_lds.conf, server_register.conf, client_find_servers.conf. +# containing server_lds.conf, server_register.conf, server_register_client.conf, +# client.conf. enable_testing() set(_test_script "${CMAKE_SOURCE_DIR}/tests/run_test.sh") -set(_test_names none_anon none_user basic256sha256_anon basic256sha256_user - aes128_anon aes128_user) +set(_test_names none_anon none_user basic256sha256_anon aes128_user) -set(_test_policies None None Basic256Sha256 Basic256Sha256 - Aes128_Sha256_RsaOaep Aes128_Sha256_RsaOaep) +set(_test_policies None None Basic256Sha256 Aes128_Sha256_RsaOaep) foreach(_name _policy IN ZIP_LISTS _test_names _test_policies) add_test(NAME "${_name}" COMMAND bash "${_test_script}" "tests/${_name}" diff --git a/config/client.conf b/config/client.conf new file mode 100644 index 0000000..1d3fe1b --- /dev/null +++ b/config/client.conf @@ -0,0 +1,23 @@ +# Client configuration +# +# Keys: +# applicationUri OPC UA application URI +# certificate Path to client certificate (.der) +# privateKey Path to client private key (.der) +# securityMode None, Sign, or SignAndEncrypt +# securityPolicy None, Basic256Sha256, Aes256_Sha256_RsaPss, +# Aes128_Sha256_RsaOaep, or ECC_nistP256 +# authMode "anonymous" or "user" (read-time only) +# username Username (required when authMode = user) +# password Password (required when authMode = user) +# trustStore Directory containing trusted certificates (.der) + +applicationUri = urn:bobink.ClientFindServers +certificate = certs/ClientFindServers_cert.der +privateKey = certs/ClientFindServers_key.der +securityMode = SignAndEncrypt +securityPolicy = Aes256_Sha256_RsaPss +authMode = user +username = user +password = password +trustStore = certs/trust/client diff --git a/config/client_find_servers.conf b/config/client_find_servers.conf deleted file mode 100644 index 5ab15d5..0000000 --- a/config/client_find_servers.conf +++ /dev/null @@ -1,50 +0,0 @@ -# ClientFindServers configuration -# -# Shared keys: -# discoveryEndpoint LDS endpoint URL (e.g. opc.tcp://localhost:4840) -# applicationUri OPC UA application URI -# -# Discovery-side keys (LDS connection): -# discoveryCertificate Path to certificate for LDS connections (.der) -# discoveryPrivateKey Path to private key for LDS connections (.der) -# discoverySecurityMode None, Sign, or SignAndEncrypt -# discoverySecurityPolicy None, Basic256Sha256, Aes256_Sha256_RsaPss, -# Aes128_Sha256_RsaOaep, or ECC_nistP256 -# discoveryAuthMode "anonymous" or "user" -# discoveryUsername Username (required when discoveryAuthMode = user) -# discoveryPassword Password (required when discoveryAuthMode = user) -# discoveryTrustList Trusted certificate path (repeat for multiple) -# -# Server-side keys (connections to discovered servers): -# serverCertificate Path to certificate for server connections (.der) -# serverPrivateKey Path to private key for server connections (.der) -# serverSecurityMode None, Sign, or SignAndEncrypt -# serverSecurityPolicy None, Basic256Sha256, Aes256_Sha256_RsaPss, -# Aes128_Sha256_RsaOaep, or ECC_nistP256 -# serverAuthMode "anonymous" or "user" -# serverUsername Username (required when serverAuthMode = user) -# serverPassword Password (required when serverAuthMode = user) -# serverTrustList Trusted certificate path (repeat for multiple) - -discoveryEndpoint = opc.tcp://localhost:4840 -applicationUri = urn:bobink.ClientFindServers - -# Discovery (LDS) side -discoveryCertificate = certs/ClientFindServers_cert.der -discoveryPrivateKey = certs/ClientFindServers_key.der -discoverySecurityMode = SignAndEncrypt -discoverySecurityPolicy = Aes256_Sha256_RsaPss -discoveryAuthMode = user -discoveryUsername = user -discoveryPassword = password -discoveryTrustList = certs/ServerLDS_cert.der - -# Server side -serverCertificate = certs/ClientFindServers_cert.der -serverPrivateKey = certs/ClientFindServers_key.der -serverSecurityMode = SignAndEncrypt -serverSecurityPolicy = Aes256_Sha256_RsaPss -serverAuthMode = user -serverUsername = user -serverPassword = password -serverTrustList = certs/ServerRegister_cert.der diff --git a/config/server_lds.conf b/config/server_lds.conf index a30106c..7382dbe 100644 --- a/config/server_lds.conf +++ b/config/server_lds.conf @@ -9,7 +9,7 @@ # authMode "anonymous" or "user" # username Username (required when authMode = user) # password Password (required when authMode = user) -# trustList Trusted certificate path (repeat for multiple) +# trustStore Directory containing trusted certificates (.der) port = 4840 applicationUri = urn:bobink.ServerLDS @@ -21,5 +21,4 @@ authMode = user username = user password = password -trustList = certs/ServerRegisterClient_cert.der -trustList = certs/ClientFindServers_cert.der +trustStore = certs/trust/server_lds diff --git a/config/server_register.conf b/config/server_register.conf index c32c61e..ddacbac 100644 --- a/config/server_register.conf +++ b/config/server_register.conf @@ -1,47 +1,25 @@ -# ServerRegister configuration +# ServerRegister — server configuration # # Keys: -# port Server port number -# applicationUri OPC UA application URI -# serverCertificate Path to server certificate (.der) -# serverPrivateKey Path to server private key (.der) -# clientCertificate Path to client certificate for LDS connection (.der) -# clientPrivateKey Path to client private key for LDS connection (.der) -# discoveryEndpoint LDS endpoint URL (e.g. opc.tcp://localhost:4840) -# registerInterval Seconds between re-registrations with the LDS -# securityMode None, Sign, or SignAndEncrypt -# securityPolicy None, Basic256Sha256, Aes256_Sha256_RsaPss, -# Aes128_Sha256_RsaOaep, or ECC_nistP256 -# serverAuthMode Auth mode for clients connecting to this server: -# "anonymous" or "user" -# serverUsername Username (required when serverAuthMode = user) -# serverPassword Password (required when serverAuthMode = user) -# clientAuthMode Auth mode for connecting to the LDS: -# "anonymous" or "user" -# clientUsername Username (required when clientAuthMode = user) -# clientPassword Password (required when clientAuthMode = user) -# trustList Trusted certificate path (repeat for multiple) +# port Server port number +# applicationUri OPC UA application URI +# certificate Path to server certificate (.der) +# privateKey Path to server private key (.der) +# registerInterval Seconds between re-registrations with the LDS +# authMode "anonymous" or "user" +# username Username (required when authMode = user) +# password Password (required when authMode = user) +# trustStore Directory containing trusted certificates (.der) port = 4841 applicationUri = urn:bobink.ServerRegister -serverCertificate = certs/ServerRegister_cert.der -serverPrivateKey = certs/ServerRegister_key.der -clientCertificate = certs/ServerRegisterClient_cert.der -clientPrivateKey = certs/ServerRegisterClient_key.der +certificate = certs/ServerRegister_cert.der +privateKey = certs/ServerRegister_key.der -discoveryEndpoint = opc.tcp://localhost:4840 registerInterval = 10 -securityMode = SignAndEncrypt -securityPolicy = Aes256_Sha256_RsaPss +authMode = user +username = user +password = password -serverAuthMode = user -serverUsername = user -serverPassword = password - -clientAuthMode = user -clientUsername = user -clientPassword = password - -trustList = certs/ServerLDS_cert.der -trustList = certs/ClientFindServers_cert.der +trustStore = certs/trust/server_register diff --git a/config/server_register_client.conf b/config/server_register_client.conf new file mode 100644 index 0000000..e4598a9 --- /dev/null +++ b/config/server_register_client.conf @@ -0,0 +1,26 @@ +# ServerRegister — client configuration for LDS registration +# +# Keys: +# applicationUri OPC UA application URI +# certificate Path to client certificate (.der) +# privateKey Path to client private key (.der) +# securityMode None, Sign, or SignAndEncrypt +# securityPolicy None, Basic256Sha256, Aes256_Sha256_RsaPss, +# Aes128_Sha256_RsaOaep, or ECC_nistP256 +# authMode "anonymous" or "user" +# username Username (required when authMode = user) +# password Password (required when authMode = user) +# trustStore Directory containing trusted certificates (.der) + +applicationUri = urn:bobink.ServerRegister +certificate = certs/ServerRegisterClient_cert.der +privateKey = certs/ServerRegisterClient_key.der + +securityMode = SignAndEncrypt +securityPolicy = Aes256_Sha256_RsaPss + +authMode = user +username = user +password = password + +trustStore = certs/trust/server_register_client diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..8234963 --- /dev/null +++ b/src/client.c @@ -0,0 +1,328 @@ +/** + * @file client.c + * @brief Unified OPC UA client for discovery and server interaction. + * + * Supports three operations selected via CLI: + * find-servers — queries a server's FindServers service + * get-endpoints — queries a server's GetEndpoints service + * read-time — connects to a server and reads the current time + */ + +#include "common.h" +#include "config.h" + +#include +#include + +#include +#include + +/* ======================================================================== + * Operation Dispatch + * ======================================================================== */ + +typedef enum +{ + OP_FIND_SERVERS, + OP_GET_ENDPOINTS, + OP_READ_TIME, + OP_INVALID +} Operation; + +static Operation +parseOperation (const char *name) +{ + if (strcmp (name, "find-servers") == 0) + return OP_FIND_SERVERS; + if (strcmp (name, "get-endpoints") == 0) + return OP_GET_ENDPOINTS; + if (strcmp (name, "read-time") == 0) + return OP_READ_TIME; + return OP_INVALID; +} + +/* ======================================================================== + * Operations + * ======================================================================== */ + +/** + * Calls the FindServers service and prints all discovered servers. + * + * @return EXIT_SUCCESS on success, EXIT_FAILURE otherwise. + */ +static int +opFindServers (UA_Client *client, const char *url) +{ + size_t arraySize = 0; + UA_ApplicationDescription *array = NULL; + + UA_StatusCode retval = UA_Client_findServers (client, url, 0, NULL, 0, NULL, + &arraySize, &array); + if (retval != UA_STATUSCODE_GOOD) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, + "FindServers failed: %s", UA_StatusCode_name (retval)); + return EXIT_FAILURE; + } + + for (size_t i = 0; i < arraySize; i++) + printApplicationDescription (&array[i], i); + + UA_Array_delete (array, arraySize, + &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]); + return EXIT_SUCCESS; +} + +/** + * Calls the GetEndpoints service and prints all endpoints. + * + * @return EXIT_SUCCESS on success, EXIT_FAILURE otherwise. + */ +static int +opGetEndpoints (UA_Client *client, const char *url) +{ + size_t arraySize = 0; + UA_EndpointDescription *array = NULL; + + UA_StatusCode retval + = UA_Client_getEndpoints (client, url, &arraySize, &array); + if (retval != UA_STATUSCODE_GOOD) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, + "GetEndpoints failed: %s", UA_StatusCode_name (retval)); + return EXIT_FAILURE; + } + + for (size_t i = 0; i < arraySize; i++) + printEndpoint (&array[i], i); + + UA_Array_delete (array, arraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); + return EXIT_SUCCESS; +} + +/** + * Connects to a server and reads the current time node. + * + * @param username Username for session auth, or NULL for anonymous. + * @param password Password for session auth (ignored when username is NULL). + * @return EXIT_SUCCESS on success, EXIT_FAILURE otherwise. + */ +static int +opReadTime (UA_Client *client, const char *url, const char *username, + const char *password) +{ + UA_StatusCode retval; + if (username) + retval = UA_Client_connectUsername (client, url, username, password); + else + retval = UA_Client_connect (client, url); + + if (retval != UA_STATUSCODE_GOOD) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, + "Could not connect: %s", UA_StatusCode_name (retval)); + return EXIT_FAILURE; + } + + UA_Variant value; + UA_Variant_init (&value); + + const UA_NodeId nodeId = UA_NS0ID (SERVER_SERVERSTATUS_CURRENTTIME); + retval = UA_Client_readValueAttribute (client, nodeId, &value); + + int rc = EXIT_SUCCESS; + if (retval == UA_STATUSCODE_GOOD + && UA_Variant_hasScalarType (&value, &UA_TYPES[UA_TYPES_DATETIME])) + { + UA_DateTime raw_date = *(UA_DateTime *)value.data; + UA_DateTimeStruct dts = UA_DateTime_toStruct (raw_date); + UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, + "date is: %u-%u-%u %u:%u:%u.%03u", dts.day, dts.month, + dts.year, dts.hour, dts.min, dts.sec, dts.milliSec); + } + else + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, + "Could not read current time: %s", + UA_StatusCode_name (retval)); + rc = EXIT_FAILURE; + } + + UA_Variant_clear (&value); + UA_Client_disconnect (client); + return rc; +} + +/* ======================================================================== + * Main + * ======================================================================== */ + +int +main (int argc, char **argv) +{ + if (argc < 4 || argc > 5) + { + UA_LOG_FATAL ( + UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Usage: %s [log-level]", + argv[0]); + return EXIT_FAILURE; + } + + Operation op = parseOperation (argv[2]); + if (op == OP_INVALID) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown operation: %s " + "(expected find-servers, get-endpoints, read-time)", + argv[2]); + return EXIT_FAILURE; + } + + const char *endpointUrl = argv[3]; + + const char *logLevelStr = (argc == 5) ? argv[4] : "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; + } + + Config cfg; + if (configLoad (argv[1], &cfg) != 0) + return EXIT_FAILURE; + + /* ---- Common config keys ---- */ + + const char *applicationUri + = configRequire (&cfg, "applicationUri", "Client"); + const char *certPath = configRequire (&cfg, "certificate", "Client"); + const char *keyPath = configRequire (&cfg, "privateKey", "Client"); + const char *secModeStr = configRequire (&cfg, "securityMode", "Client"); + const char *secPolStr = configRequire (&cfg, "securityPolicy", "Client"); + + if (!applicationUri || !certPath || !keyPath || !secModeStr || !secPolStr) + { + configFree (&cfg); + return EXIT_FAILURE; + } + + UA_MessageSecurityMode secMode = parseSecurityMode (secModeStr); + if (secMode == UA_MESSAGESECURITYMODE_INVALID) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown security mode: %s", secModeStr); + configFree (&cfg); + return EXIT_FAILURE; + } + + const char *secPolUri = resolveSecurityPolicyUri (secPolStr); + if (!secPolUri) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown security policy: %s", secPolStr); + configFree (&cfg); + return EXIT_FAILURE; + } + + /* ---- Auth config (read-time only) ---- */ + + const char *username = NULL, *password = NULL; + + if (op == OP_READ_TIME) + { + const char *authMode = configRequire (&cfg, "authMode", "Client"); + if (!authMode) + { + configFree (&cfg); + return EXIT_FAILURE; + } + + if (strcmp (authMode, "anonymous") == 0) + { + /* No credentials needed. */ + } + else if (strcmp (authMode, "user") == 0) + { + username = configRequire (&cfg, "username", "Client"); + password = configRequire (&cfg, "password", "Client"); + if (!username || !password) + { + configFree (&cfg); + return EXIT_FAILURE; + } + } + else + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown auth mode: %s " + "(expected 'anonymous' or 'user')", + authMode); + configFree (&cfg); + return EXIT_FAILURE; + } + } + + /* ---- Trust store ---- */ + + const char *trustStore = configRequire (&cfg, "trustStore", "Client"); + if (!trustStore) + { + configFree (&cfg); + return EXIT_FAILURE; + } + + char **trustPaths = NULL; + size_t trustSize = 0; + if (loadTrustStore (trustStore, &trustPaths, &trustSize) != 0) + { + configFree (&cfg); + return EXIT_FAILURE; + } + + /* ---- Create client ---- */ + + UA_Client *client = UA_Client_new (); + UA_StatusCode retval = createSecureClientConfig ( + UA_Client_getConfig (client), applicationUri, certPath, keyPath, + trustPaths, trustSize, secMode, secPolUri); + if (retval != UA_STATUSCODE_GOOD) + { + UA_Client_delete (client); + freeTrustStore (trustPaths, trustSize); + configFree (&cfg); + return EXIT_FAILURE; + } + UA_Client_getConfig (client)->logging->context = (void *)(uintptr_t)logLevel; + + /* ---- Dispatch operation ---- */ + + int rc; + switch (op) + { + case OP_FIND_SERVERS: + rc = opFindServers (client, endpointUrl); + break; + case OP_GET_ENDPOINTS: + rc = opGetEndpoints (client, endpointUrl); + break; + case OP_READ_TIME: + rc = opReadTime (client, endpointUrl, username, password); + break; + default: + rc = EXIT_FAILURE; + break; + } + + /* ---- Cleanup ---- */ + + UA_Client_delete (client); + freeTrustStore (trustPaths, trustSize); + configFree (&cfg); + + return rc; +} diff --git a/src/client_find_servers.c b/src/client_find_servers.c deleted file mode 100644 index 2212026..0000000 --- a/src/client_find_servers.c +++ /dev/null @@ -1,438 +0,0 @@ -/** - * @file client_find_servers.c - * @brief OPC UA client that queries a Local Discovery Server for registered - * servers. - * - * This program connects to an LDS and calls the FindServers service to - * retrieve all registered servers. It then queries each server's endpoints - * using the GetEndpoints service and displays the results in a human-readable - * format. - */ - -#include "common.h" -#include "config.h" - -#include -#include - -#include -#include - -/* ======================================================================== - * Discovery Service Calls - * ======================================================================== */ - -/** - * Calls the FindServers service on the LDS and prints all discovered servers. - * - * @param client The OPC UA client instance. - * @param discoveryServerEndpoint The LDS endpoint URL. - * @param applicationDescriptionArraySize Output: number of servers found. - * @param applicationDescriptionArray Output: array of server descriptions. - * @return UA_STATUSCODE_GOOD on success, error code otherwise. - */ -static UA_StatusCode -findServers (UA_Client *client, const char *discoveryServerEndpoint, - size_t *applicationDescriptionArraySize, - UA_ApplicationDescription **applicationDescriptionArray) -{ - UA_StatusCode retval = UA_Client_findServers ( - client, discoveryServerEndpoint, 0, NULL, 0, NULL, - applicationDescriptionArraySize, applicationDescriptionArray); - - if (retval != UA_STATUSCODE_GOOD) - { - UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "Could not call FindServers service. " - "Is the discovery server started? StatusCode %s", - UA_StatusCode_name (retval)); - return retval; - } - - for (size_t i = 0; i < *applicationDescriptionArraySize; i++) - printApplicationDescription (&(*applicationDescriptionArray)[i], i); - - return UA_STATUSCODE_GOOD; -} - -/** - * Queries endpoints for each discovered server using the GetEndpoints service. - * - * For each server in the applicationDescriptionArray, this function extracts - * the first discovery URL and calls GetEndpoints to retrieve all available - * endpoints. Results are logged via UA_LOG_INFO. - * - * @param client The OPC UA client instance. - * @param applicationDescriptionArray Array of server descriptions from - * FindServers. - * @param applicationDescriptionArraySize Number of servers in the array. - */ -static void -getServersEndpoints (UA_Client *client, - UA_ApplicationDescription *applicationDescriptionArray, - size_t applicationDescriptionArraySize) -{ - for (size_t i = 0; i < applicationDescriptionArraySize; i++) - { - UA_ApplicationDescription *description = &applicationDescriptionArray[i]; - if (description->discoveryUrlsSize == 0) - { - UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, - "[GetEndpoints] Server %.*s did not provide any " - "discovery urls. Skipping.", - (int)description->applicationUri.length, - description->applicationUri.data); - continue; - } - - /* UA_String is not null-terminated; build a C string for the API. */ - char *discoveryUrl = (char *)UA_malloc ( - sizeof (char) * description->discoveryUrls[0].length + 1); - memcpy (discoveryUrl, description->discoveryUrls[0].data, - description->discoveryUrls[0].length); - discoveryUrl[description->discoveryUrls[0].length] = '\0'; - - UA_EndpointDescription *endpointArray = NULL; - size_t endpointArraySize = 0; - UA_StatusCode retval = UA_Client_getEndpoints ( - client, discoveryUrl, &endpointArraySize, &endpointArray); - UA_free (discoveryUrl); - if (retval != UA_STATUSCODE_GOOD) - { - UA_Client_disconnect (client); - break; - } - - UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, - "Endpoints for Server[%lu]: %.*s", (unsigned long)i, - (int)description->applicationUri.length, - description->applicationUri.data); - for (size_t j = 0; j < endpointArraySize; j++) - printEndpoint (&endpointArray[j], j); - - UA_Array_delete (endpointArray, endpointArraySize, - &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); - } -} - -/* ======================================================================== - * Node Reading - * ======================================================================== */ - -/** - * Connects to each non-discovery server and reads the current time node. - * - * For each server that is not a DiscoveryServer, this function establishes a - * secure session, reads the Server_ServerStatus_CurrentTime variable, prints - * the result, and disconnects. - * - * @param client The OPC UA client instance. - * @param applicationDescriptionArray Array of server descriptions from - * FindServers. - * @param applicationDescriptionArraySize Number of servers in the array. - * @param username Username for session auth, or NULL for anonymous. - * @param password Password for session auth (ignored when username is NULL). - */ -static void -readServerTime (UA_Client *client, - UA_ApplicationDescription *applicationDescriptionArray, - size_t applicationDescriptionArraySize, const char *username, - const char *password) -{ - for (size_t i = 0; i < applicationDescriptionArraySize; i++) - { - UA_ApplicationDescription *desc = &applicationDescriptionArray[i]; - - if (desc->applicationType == UA_APPLICATIONTYPE_DISCOVERYSERVER) - continue; - - if (desc->discoveryUrlsSize == 0) - { - UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, - "[ReadTime] Server %.*s has no discovery URLs. " - "Skipping.", - (int)desc->applicationUri.length, - desc->applicationUri.data); - continue; - } - - /* UA_String is not null-terminated; build a C string for the API. */ - char *url = (char *)UA_malloc (desc->discoveryUrls[0].length + 1); - memcpy (url, desc->discoveryUrls[0].data, desc->discoveryUrls[0].length); - url[desc->discoveryUrls[0].length] = '\0'; - - UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, - "Connecting to %s to read current time...", url); - - UA_StatusCode retval; - if (username) - retval = UA_Client_connectUsername (client, url, username, password); - else - retval = UA_Client_connect (client, url); - UA_free (url); - if (retval != UA_STATUSCODE_GOOD) - { - UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, - "Could not connect: %s", UA_StatusCode_name (retval)); - continue; - } - - UA_Variant value; - UA_Variant_init (&value); - - const UA_NodeId nodeId = UA_NS0ID (SERVER_SERVERSTATUS_CURRENTTIME); - retval = UA_Client_readValueAttribute (client, nodeId, &value); - - if (retval == UA_STATUSCODE_GOOD - && UA_Variant_hasScalarType (&value, &UA_TYPES[UA_TYPES_DATETIME])) - { - UA_DateTime raw_date = *(UA_DateTime *)value.data; - UA_DateTimeStruct dts = UA_DateTime_toStruct (raw_date); - UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, - "date is: %u-%u-%u %u:%u:%u.%03u", dts.day, dts.month, - dts.year, dts.hour, dts.min, dts.sec, dts.milliSec); - } - else - { - UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, - "Could not read current time: %s", - UA_StatusCode_name (retval)); - } - - UA_Variant_clear (&value); - UA_Client_disconnect (client); - } -} - -/* ======================================================================== - * Main - * ======================================================================== */ - -int -main (int argc, char **argv) -{ - if (argc < 2 || argc > 3) - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Usage: %s [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; - } - - Config cfg; - if (configLoad (argv[1], &cfg) != 0) - return EXIT_FAILURE; - - /* ---- Shared keys ---- */ - - const char *discoveryServerEndpoint - = configRequire (&cfg, "discoveryEndpoint", "ClientFindServers"); - const char *applicationUri - = configRequire (&cfg, "applicationUri", "ClientFindServers"); - - if (!discoveryServerEndpoint || !applicationUri) - { - configFree (&cfg); - return EXIT_FAILURE; - } - - /* ---- Discovery-side config (LDS connection) ---- */ - - const char *discCertPath - = configRequire (&cfg, "discoveryCertificate", "ClientFindServers"); - const char *discKeyPath - = configRequire (&cfg, "discoveryPrivateKey", "ClientFindServers"); - const char *discSecModeStr - = configRequire (&cfg, "discoverySecurityMode", "ClientFindServers"); - const char *discSecPolStr - = configRequire (&cfg, "discoverySecurityPolicy", "ClientFindServers"); - const char *discAuthMode - = configRequire (&cfg, "discoveryAuthMode", "ClientFindServers"); - - if (!discCertPath || !discKeyPath || !discSecModeStr || !discSecPolStr - || !discAuthMode) - { - configFree (&cfg); - return EXIT_FAILURE; - } - - UA_MessageSecurityMode discSecMode = parseSecurityMode (discSecModeStr); - if (discSecMode == UA_MESSAGESECURITYMODE_INVALID) - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown discovery security mode: %s", discSecModeStr); - configFree (&cfg); - return EXIT_FAILURE; - } - - const char *discSecPolUri = resolveSecurityPolicyUri (discSecPolStr); - if (!discSecPolUri) - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown discovery security policy: %s", discSecPolStr); - configFree (&cfg); - return EXIT_FAILURE; - } - - /* ---- Server-side config (connections to discovered servers) ---- */ - - const char *srvCertPath - = configRequire (&cfg, "serverCertificate", "ClientFindServers"); - const char *srvKeyPath - = configRequire (&cfg, "serverPrivateKey", "ClientFindServers"); - const char *srvSecModeStr - = configRequire (&cfg, "serverSecurityMode", "ClientFindServers"); - const char *srvSecPolStr - = configRequire (&cfg, "serverSecurityPolicy", "ClientFindServers"); - const char *srvAuthMode - = configRequire (&cfg, "serverAuthMode", "ClientFindServers"); - - if (!srvCertPath || !srvKeyPath || !srvSecModeStr || !srvSecPolStr - || !srvAuthMode) - { - configFree (&cfg); - return EXIT_FAILURE; - } - - UA_MessageSecurityMode srvSecMode = parseSecurityMode (srvSecModeStr); - if (srvSecMode == UA_MESSAGESECURITYMODE_INVALID) - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown server security mode: %s", srvSecModeStr); - configFree (&cfg); - return EXIT_FAILURE; - } - - const char *srvSecPolUri = resolveSecurityPolicyUri (srvSecPolStr); - if (!srvSecPolUri) - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown server security policy: %s", srvSecPolStr); - configFree (&cfg); - return EXIT_FAILURE; - } - - /* ---- Server-side auth ---- */ - - const char *srvUsername = NULL, *srvPassword = NULL; - - if (strcmp (srvAuthMode, "anonymous") == 0) - { - /* No credentials needed */ - } - else if (strcmp (srvAuthMode, "user") == 0) - { - srvUsername - = configRequire (&cfg, "serverUsername", "ClientFindServers"); - srvPassword - = configRequire (&cfg, "serverPassword", "ClientFindServers"); - if (!srvUsername || !srvPassword) - { - configFree (&cfg); - return EXIT_FAILURE; - } - } - else - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown server auth mode: %s " - "(expected 'anonymous' or 'user')", - srvAuthMode); - configFree (&cfg); - return EXIT_FAILURE; - } - - /* ---- Trust lists ---- */ - - char **discTrustPaths = NULL; - size_t discTrustSize = 0; - configGetAll (&cfg, "discoveryTrustList", &discTrustPaths, &discTrustSize); - - char **srvTrustPaths = NULL; - size_t srvTrustSize = 0; - configGetAll (&cfg, "serverTrustList", &srvTrustPaths, &srvTrustSize); - - /* ---- Create discovery client (LDS) ---- */ - - UA_Client *discoveryClient = UA_Client_new (); - UA_StatusCode retval = createSecureClientConfig ( - UA_Client_getConfig (discoveryClient), applicationUri, discCertPath, - discKeyPath, discTrustPaths, discTrustSize, discSecMode, discSecPolUri); - if (retval != UA_STATUSCODE_GOOD) - { - UA_Client_delete (discoveryClient); - free (discTrustPaths); - free (srvTrustPaths); - configFree (&cfg); - return EXIT_FAILURE; - } - UA_Client_getConfig (discoveryClient)->logging->context - = (void *)(uintptr_t)logLevel; - - /* ---- Create server client (discovered servers) ---- */ - - UA_Client *serverClient = UA_Client_new (); - retval = createSecureClientConfig ( - UA_Client_getConfig (serverClient), applicationUri, srvCertPath, - srvKeyPath, srvTrustPaths, srvTrustSize, srvSecMode, srvSecPolUri); - if (retval != UA_STATUSCODE_GOOD) - { - UA_Client_delete (discoveryClient); - UA_Client_delete (serverClient); - free (discTrustPaths); - free (srvTrustPaths); - configFree (&cfg); - return EXIT_FAILURE; - } - UA_Client_getConfig (serverClient)->logging->context - = (void *)(uintptr_t)logLevel; - - /* ---- Discovery calls (use discoveryClient) ---- */ - - UA_ApplicationDescription *applicationDescriptionArray = NULL; - size_t applicationDescriptionArraySize = 0; - - retval = findServers (discoveryClient, discoveryServerEndpoint, - &applicationDescriptionArraySize, - &applicationDescriptionArray); - if (retval != UA_STATUSCODE_GOOD) - { - UA_Client_delete (discoveryClient); - UA_Client_delete (serverClient); - free (discTrustPaths); - free (srvTrustPaths); - configFree (&cfg); - return EXIT_FAILURE; - } - - getServersEndpoints (discoveryClient, applicationDescriptionArray, - applicationDescriptionArraySize); - - /* ---- Server calls (use serverClient) ---- */ - - readServerTime (serverClient, applicationDescriptionArray, - applicationDescriptionArraySize, srvUsername, srvPassword); - - /* ---- Cleanup ---- */ - - UA_Client_delete (discoveryClient); - UA_Client_delete (serverClient); - UA_Array_delete (applicationDescriptionArray, - applicationDescriptionArraySize, - &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]); - free (discTrustPaths); - free (srvTrustPaths); - configFree (&cfg); - - return EXIT_SUCCESS; -} diff --git a/src/common.c b/src/common.c index 7d378f1..568e4d0 100644 --- a/src/common.c +++ b/src/common.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -55,6 +56,102 @@ loadFile (const char *const path) return fileContents; } +/* ======================================================================== + * Trust Store + * ======================================================================== */ + +int +loadTrustStore (const char *dirPath, char ***outPaths, size_t *outSize) +{ + *outPaths = NULL; + *outSize = 0; + + DIR *dir = opendir (dirPath); + if (!dir) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Cannot open trust store directory '%s'", dirPath); + return -1; + } + + size_t capacity = 8; + size_t count = 0; + char **paths = malloc (capacity * sizeof (char *)); + if (!paths) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "loadTrustStore: out of memory"); + closedir (dir); + return -1; + } + + struct dirent *entry; + while ((entry = readdir (dir)) != NULL) + { + const char *name = entry->d_name; + size_t nameLen = strlen (name); + /* Skip entries that are not *.der files. 5 = strlen("x.der"). */ + if (nameLen < 5 || strcmp (name + nameLen - 4, ".der") != 0) + continue; + + /* Build full path: dirPath/name */ + size_t dirLen = strlen (dirPath); + size_t fullLen = dirLen + 1 + nameLen + 1; + char *full = malloc (fullLen); + if (!full) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "loadTrustStore: out of memory"); + for (size_t i = 0; i < count; i++) + free (paths[i]); + free (paths); + closedir (dir); + return -1; + } + snprintf (full, fullLen, "%s/%s", dirPath, name); + + if (count == capacity) + { + capacity *= 2; + char **tmp = realloc (paths, capacity * sizeof (char *)); + if (!tmp) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "loadTrustStore: out of memory"); + free (full); + for (size_t i = 0; i < count; i++) + free (paths[i]); + free (paths); + closedir (dir); + return -1; + } + paths = tmp; + } + + paths[count++] = full; + } + + closedir (dir); + + if (count == 0) + { + free (paths); + return 0; + } + + *outPaths = paths; + *outSize = count; + return 0; +} + +void +freeTrustStore (char **paths, size_t size) +{ + for (size_t i = 0; i < size; i++) + free (paths[i]); + free (paths); +} + /* ======================================================================== * Security Helpers * ======================================================================== */ @@ -223,18 +320,12 @@ createSecureServer (UA_UInt16 port, const char *applicationUri, for (size_t i = 0; i < trustSize; i++) trustList[i] = loadFile (trustPaths[i]); - /* Issuer and revocation lists are unused in this demo. */ - size_t issuerListSize = 0; - UA_ByteString *issuerList = NULL; - UA_ByteString *revocationList = NULL; - size_t revocationListSize = 0; - UA_Server *server = UA_Server_new (); UA_ServerConfig *config = UA_Server_getConfig (server); *retval = UA_ServerConfig_setDefaultWithSecurityPolicies ( - config, port, &certificate, &privateKey, trustList, trustSize, - issuerList, issuerListSize, revocationList, revocationListSize); + config, port, &certificate, &privateKey, trustList, trustSize, NULL, 0, + NULL, 0); UA_ByteString_clear (&certificate); UA_ByteString_clear (&privateKey); @@ -269,13 +360,8 @@ createSecureClientConfig (UA_ClientConfig *cc, const char *applicationUri, for (size_t i = 0; i < trustSize; i++) trustList[i] = loadFile (trustPaths[i]); - /* Revocation list is unused in this demo. */ - UA_ByteString *revocationList = NULL; - size_t revocationListSize = 0; - UA_StatusCode retval = UA_ClientConfig_setDefaultEncryption ( - cc, certificate, privateKey, trustList, trustSize, revocationList, - revocationListSize); + cc, certificate, privateKey, trustList, trustSize, NULL, 0); UA_ByteString_clear (&certificate); UA_ByteString_clear (&privateKey); diff --git a/src/common.h b/src/common.h index e8c0c78..7290181 100644 --- a/src/common.h +++ b/src/common.h @@ -13,6 +13,8 @@ #include #include +#include + /** * @brief Loads a DER-encoded certificate or key file into a UA_ByteString. * @@ -21,6 +23,29 @@ */ UA_ByteString loadFile (const char *const path); +/** + * @brief Collects all *.der file paths from a trust store directory. + * + * Opens the directory, finds every file ending in ".der", and builds + * heap-allocated full paths (dirPath/filename). The caller must free + * the result with freeTrustStore(). + * + * @param dirPath Path to the trust store directory. + * @param outPaths Output: heap-allocated array of heap-allocated strings. + * Set to NULL when the directory is empty. + * @param outSize Output: number of entries in outPaths. + * @return 0 on success, -1 on error (logged via UA_LOG_ERROR). + */ +int loadTrustStore (const char *dirPath, char ***outPaths, size_t *outSize); + +/** + * @brief Frees the array returned by loadTrustStore(). + * + * @param paths The array of strings (may be NULL). + * @param size Number of entries. + */ +void freeTrustStore (char **paths, size_t size); + /** * @brief Creates a UA_Server configured with security policies and encryption. * diff --git a/src/config.c b/src/config.c index 163f601..2165821 100644 --- a/src/config.c +++ b/src/config.c @@ -199,46 +199,6 @@ configRequireInt (const Config *cfg, const char *key, const char *program) 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) { diff --git a/src/config.h b/src/config.h index 649278d..2c5e364 100644 --- a/src/config.h +++ b/src/config.h @@ -7,8 +7,6 @@ * * Parses configuration files with one key=value pair per line. * Lines starting with '#' are comments. Blank lines are ignored. - * Repeated keys are allowed (used for list-valued settings like - * trustList). */ #include @@ -28,8 +26,7 @@ typedef struct /** * @brief A parsed configuration file. * - * Holds a dynamic array of ConfigEntry items. Duplicate keys are - * allowed (used for list-valued settings like trustList). + * Holds a dynamic array of ConfigEntry items. */ typedef struct { @@ -91,22 +88,6 @@ const char *configRequire (const Config *cfg, const char *key, */ int configRequireInt (const Config *cfg, const char *key, const char *program); -/** - * @brief Collects all values for a repeated key. - * - * Allocates an array of char* pointers to the values stored - * in @p cfg. The caller must free the array itself (but not - * the strings, which are owned by cfg). - * - * @param cfg The parsed configuration. - * @param key The key to collect (e.g. "trustList"). - * @param out Output: heap-allocated array of string pointers. - * Set to NULL if the key is not present. - * @param size Output: number of entries in @p out. - */ -void configGetAll (const Config *cfg, const char *key, char ***out, - size_t *size); - /** * @brief Frees all memory owned by a Config structure. * diff --git a/src/server_lds.c b/src/server_lds.c index 2fe508f..a9a68bc 100644 --- a/src/server_lds.c +++ b/src/server_lds.c @@ -111,9 +111,20 @@ main (int argc, char *argv[]) return EXIT_FAILURE; } + const char *trustStore = configRequire (&cfg, "trustStore", "ServerLDS"); + if (!trustStore) + { + configFree (&cfg); + return EXIT_FAILURE; + } + char **trustPaths = NULL; size_t trustSize = 0; - configGetAll (&cfg, "trustList", &trustPaths, &trustSize); + if (loadTrustStore (trustStore, &trustPaths, &trustSize) != 0) + { + configFree (&cfg); + return EXIT_FAILURE; + } UA_StatusCode retval; UA_Server *server @@ -121,7 +132,7 @@ main (int argc, char *argv[]) trustPaths, trustSize, &retval); if (!server) { - free (trustPaths); + freeTrustStore (trustPaths, trustSize); configFree (&cfg); return EXIT_FAILURE; } @@ -147,7 +158,7 @@ main (int argc, char *argv[]) if (retval != UA_STATUSCODE_GOOD) { UA_Server_delete (server); - free (trustPaths); + freeTrustStore (trustPaths, trustSize); configFree (&cfg); return EXIT_FAILURE; } @@ -164,7 +175,7 @@ main (int argc, char *argv[]) retval = UA_Server_run (server, &running); UA_Server_delete (server); - free (trustPaths); + freeTrustStore (trustPaths, trustSize); configFree (&cfg); return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/src/server_register.c b/src/server_register.c index d259961..6e1eb6d 100644 --- a/src/server_register.c +++ b/src/server_register.c @@ -42,14 +42,18 @@ main (int argc, char **argv) signal (SIGINT, stopHandler); signal (SIGTERM, stopHandler); - if (argc < 2 || argc > 3) + if (argc < 4 || argc > 5) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Usage: %s [log-level]", argv[0]); + "Usage: %s " + " [log-level]", + argv[0]); return EXIT_FAILURE; } - const char *logLevelStr = (argc == 3) ? argv[2] : "info"; + const char *discoveryEndpoint = argv[3]; + + const char *logLevelStr = (argc == 5) ? argv[4] : "info"; int logLevel = parseLogLevel (logLevelStr); if (logLevel < 0) { @@ -60,58 +64,28 @@ main (int argc, char **argv) return EXIT_FAILURE; } - Config cfg; - if (configLoad (argv[1], &cfg) != 0) + /* ── Load server config ─────────────────────────────────────── */ + + Config serverCfg; + if (configLoad (argv[1], &serverCfg) != 0) return EXIT_FAILURE; - int port = configRequireInt (&cfg, "port", "ServerRegister"); + int port = configRequireInt (&serverCfg, "port", "ServerRegister"); const char *applicationUri - = configRequire (&cfg, "applicationUri", "ServerRegister"); + = configRequire (&serverCfg, "applicationUri", "ServerRegister"); const char *serverCertPath - = configRequire (&cfg, "serverCertificate", "ServerRegister"); + = configRequire (&serverCfg, "certificate", "ServerRegister"); const char *serverKeyPath - = configRequire (&cfg, "serverPrivateKey", "ServerRegister"); - const char *clientCertPath - = configRequire (&cfg, "clientCertificate", "ServerRegister"); - const char *clientKeyPath - = configRequire (&cfg, "clientPrivateKey", "ServerRegister"); - const char *discoveryEndpoint - = configRequire (&cfg, "discoveryEndpoint", "ServerRegister"); + = configRequire (&serverCfg, "privateKey", "ServerRegister"); int registerInterval - = configRequireInt (&cfg, "registerInterval", "ServerRegister"); - const char *securityModeStr - = configRequire (&cfg, "securityMode", "ServerRegister"); - const char *securityPolicyStr - = configRequire (&cfg, "securityPolicy", "ServerRegister"); + = configRequireInt (&serverCfg, "registerInterval", "ServerRegister"); const char *serverAuthMode - = configRequire (&cfg, "serverAuthMode", "ServerRegister"); - const char *clientAuthMode - = configRequire (&cfg, "clientAuthMode", "ServerRegister"); - - if (!applicationUri || !serverCertPath || !serverKeyPath || !clientCertPath - || !clientKeyPath || !discoveryEndpoint || !securityModeStr - || !securityPolicyStr || !serverAuthMode || !clientAuthMode || port < 0 - || registerInterval < 0) - { - configFree (&cfg); - return EXIT_FAILURE; - } + = configRequire (&serverCfg, "authMode", "ServerRegister"); - UA_MessageSecurityMode securityMode = parseSecurityMode (securityModeStr); - if (securityMode == UA_MESSAGESECURITYMODE_INVALID) + if (!applicationUri || !serverCertPath || !serverKeyPath || !serverAuthMode + || port < 0 || registerInterval < 0) { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown security mode: %s", securityModeStr); - configFree (&cfg); - return EXIT_FAILURE; - } - - const char *securityPolicyUri = resolveSecurityPolicyUri (securityPolicyStr); - if (!securityPolicyUri) - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown security policy: %s", securityPolicyStr); - configFree (&cfg); + configFree (&serverCfg); return EXIT_FAILURE; } @@ -129,12 +103,12 @@ main (int argc, char **argv) { serverAllowAnonymous = false; serverUsername - = configRequire (&cfg, "serverUsername", "ServerRegister"); + = configRequire (&serverCfg, "username", "ServerRegister"); serverPassword - = configRequire (&cfg, "serverPassword", "ServerRegister"); + = configRequire (&serverCfg, "password", "ServerRegister"); if (!serverUsername || !serverPassword) { - configFree (&cfg); + configFree (&serverCfg); return EXIT_FAILURE; } } @@ -144,7 +118,78 @@ main (int argc, char **argv) "Unknown server auth mode: %s " "(expected 'anonymous' or 'user')", serverAuthMode); - configFree (&cfg); + configFree (&serverCfg); + return EXIT_FAILURE; + } + + const char *serverTrustStore + = configRequire (&serverCfg, "trustStore", "ServerRegister"); + if (!serverTrustStore) + { + configFree (&serverCfg); + return EXIT_FAILURE; + } + + char **serverTrustPaths = NULL; + size_t serverTrustSize = 0; + if (loadTrustStore (serverTrustStore, &serverTrustPaths, &serverTrustSize) + != 0) + { + configFree (&serverCfg); + return EXIT_FAILURE; + } + + /* ── Load client config ─────────────────────────────────────── */ + + Config clientCfg; + if (configLoad (argv[2], &clientCfg) != 0) + { + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&serverCfg); + return EXIT_FAILURE; + } + + const char *clientAppUri + = configRequire (&clientCfg, "applicationUri", "ServerRegister"); + const char *clientCertPath + = configRequire (&clientCfg, "certificate", "ServerRegister"); + const char *clientKeyPath + = configRequire (&clientCfg, "privateKey", "ServerRegister"); + const char *securityModeStr + = configRequire (&clientCfg, "securityMode", "ServerRegister"); + const char *securityPolicyStr + = configRequire (&clientCfg, "securityPolicy", "ServerRegister"); + const char *clientAuthMode + = configRequire (&clientCfg, "authMode", "ServerRegister"); + + if (!clientAppUri || !clientCertPath || !clientKeyPath || !securityModeStr + || !securityPolicyStr || !clientAuthMode) + { + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); + return EXIT_FAILURE; + } + + UA_MessageSecurityMode securityMode = parseSecurityMode (securityModeStr); + if (securityMode == UA_MESSAGESECURITYMODE_INVALID) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown security mode: %s", securityModeStr); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); + return EXIT_FAILURE; + } + + const char *securityPolicyUri = resolveSecurityPolicyUri (securityPolicyStr); + if (!securityPolicyUri) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown security policy: %s", securityPolicyStr); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_FAILURE; } @@ -159,12 +204,14 @@ main (int argc, char **argv) else if (strcmp (clientAuthMode, "user") == 0) { clientUsername - = configRequire (&cfg, "clientUsername", "ServerRegister"); + = configRequire (&clientCfg, "username", "ServerRegister"); clientPassword - = configRequire (&cfg, "clientPassword", "ServerRegister"); + = configRequire (&clientCfg, "password", "ServerRegister"); if (!clientUsername || !clientPassword) { - configFree (&cfg); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_FAILURE; } } @@ -174,22 +221,45 @@ main (int argc, char **argv) "Unknown client auth mode: %s " "(expected 'anonymous' or 'user')", clientAuthMode); - configFree (&cfg); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); + return EXIT_FAILURE; + } + + const char *clientTrustStore + = configRequire (&clientCfg, "trustStore", "ServerRegister"); + if (!clientTrustStore) + { + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); + return EXIT_FAILURE; + } + + char **clientTrustPaths = NULL; + size_t clientTrustSize = 0; + if (loadTrustStore (clientTrustStore, &clientTrustPaths, &clientTrustSize) + != 0) + { + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_FAILURE; } - char **trustPaths = NULL; - size_t trustSize = 0; - configGetAll (&cfg, "trustList", &trustPaths, &trustSize); + /* ── Create and configure server ────────────────────────────── */ UA_StatusCode retval; - UA_Server *server - = createSecureServer ((UA_UInt16)port, applicationUri, serverCertPath, - serverKeyPath, trustPaths, trustSize, &retval); + UA_Server *server = createSecureServer ( + (UA_UInt16)port, applicationUri, serverCertPath, serverKeyPath, + serverTrustPaths, serverTrustSize, &retval); if (!server) { - free (trustPaths); - configFree (&cfg); + freeTrustStore (clientTrustPaths, clientTrustSize); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_FAILURE; } @@ -209,8 +279,10 @@ main (int argc, char **argv) if (retval != UA_STATUSCODE_GOOD) { UA_Server_delete (server); - free (trustPaths); - configFree (&cfg); + freeTrustStore (clientTrustPaths, clientTrustSize); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_FAILURE; } } @@ -227,14 +299,16 @@ main (int argc, char **argv) UA_ClientConfig clientConfig; memset (&clientConfig, 0, sizeof (UA_ClientConfig)); retval = createSecureClientConfig ( - &clientConfig, applicationUri, clientCertPath, clientKeyPath, trustPaths, - trustSize, securityMode, securityPolicyUri); + &clientConfig, clientAppUri, clientCertPath, clientKeyPath, + clientTrustPaths, clientTrustSize, securityMode, securityPolicyUri); if (retval != UA_STATUSCODE_GOOD) { UA_Server_run_shutdown (server); UA_Server_delete (server); - free (trustPaths); - configFree (&cfg); + freeTrustStore (clientTrustPaths, clientTrustSize); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_FAILURE; } clientConfig.logging->context = (void *)(uintptr_t)logLevel; @@ -263,9 +337,10 @@ main (int argc, char **argv) if (now - lastRegister >= registerInterval) { memset (&clientConfig, 0, sizeof (UA_ClientConfig)); - retval = createSecureClientConfig ( - &clientConfig, applicationUri, clientCertPath, clientKeyPath, - trustPaths, trustSize, securityMode, securityPolicyUri); + retval = createSecureClientConfig (&clientConfig, clientAppUri, + clientCertPath, clientKeyPath, + clientTrustPaths, clientTrustSize, + securityMode, securityPolicyUri); if (retval == UA_STATUSCODE_GOOD) { clientConfig.logging->context = (void *)(uintptr_t)logLevel; @@ -289,8 +364,8 @@ main (int argc, char **argv) our entry immediately rather than waiting for the cleanup timeout. */ memset (&clientConfig, 0, sizeof (UA_ClientConfig)); retval = createSecureClientConfig ( - &clientConfig, applicationUri, clientCertPath, clientKeyPath, trustPaths, - trustSize, securityMode, securityPolicyUri); + &clientConfig, clientAppUri, clientCertPath, clientKeyPath, + clientTrustPaths, clientTrustSize, securityMode, securityPolicyUri); if (retval == UA_STATUSCODE_GOOD) { clientConfig.logging->context = (void *)(uintptr_t)logLevel; @@ -308,7 +383,9 @@ main (int argc, char **argv) UA_Server_run_shutdown (server); UA_Server_delete (server); - free (trustPaths); - configFree (&cfg); + freeTrustStore (clientTrustPaths, clientTrustSize); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_SUCCESS; } diff --git a/tests/aes128_anon/client_find_servers.conf b/tests/aes128_anon/client_find_servers.conf deleted file mode 100644 index 2cc096d..0000000 --- a/tests/aes128_anon/client_find_servers.conf +++ /dev/null @@ -1,20 +0,0 @@ -# ClientFindServers — test: aes128_anon - -discoveryEndpoint = opc.tcp://localhost:14840 -applicationUri = urn:bobink.ClientFindServers - -# Discovery (LDS) side -discoveryCertificate = certs/ClientFindServers_cert.der -discoveryPrivateKey = certs/ClientFindServers_key.der -discoverySecurityMode = SignAndEncrypt -discoverySecurityPolicy = Aes128_Sha256_RsaOaep -discoveryAuthMode = anonymous -discoveryTrustList = certs/ServerLDS_cert.der - -# Server side -serverCertificate = certs/ClientFindServers_cert.der -serverPrivateKey = certs/ClientFindServers_key.der -serverSecurityMode = SignAndEncrypt -serverSecurityPolicy = Aes128_Sha256_RsaOaep -serverAuthMode = anonymous -serverTrustList = certs/ServerRegister_cert.der diff --git a/tests/aes128_anon/server_lds.conf b/tests/aes128_anon/server_lds.conf deleted file mode 100644 index e8601d0..0000000 --- a/tests/aes128_anon/server_lds.conf +++ /dev/null @@ -1,12 +0,0 @@ -# ServerLDS — test: aes128_anon - -port = 14840 -applicationUri = urn:bobink.ServerLDS -certificate = certs/ServerLDS_cert.der -privateKey = certs/ServerLDS_key.der -cleanupTimeout = 60 - -authMode = anonymous - -trustList = certs/ServerRegisterClient_cert.der -trustList = certs/ClientFindServers_cert.der diff --git a/tests/aes128_anon/server_register.conf b/tests/aes128_anon/server_register.conf deleted file mode 100644 index 8a8d1d1..0000000 --- a/tests/aes128_anon/server_register.conf +++ /dev/null @@ -1,21 +0,0 @@ -# ServerRegister — test: aes128_anon - -port = 14841 -applicationUri = urn:bobink.ServerRegister -serverCertificate = certs/ServerRegister_cert.der -serverPrivateKey = certs/ServerRegister_key.der -clientCertificate = certs/ServerRegisterClient_cert.der -clientPrivateKey = certs/ServerRegisterClient_key.der - -discoveryEndpoint = opc.tcp://localhost:14840 -registerInterval = 10 - -securityMode = SignAndEncrypt -securityPolicy = Aes128_Sha256_RsaOaep - -serverAuthMode = anonymous - -clientAuthMode = anonymous - -trustList = certs/ServerLDS_cert.der -trustList = certs/ClientFindServers_cert.der diff --git a/tests/aes128_user/client.conf b/tests/aes128_user/client.conf new file mode 100644 index 0000000..026cd99 --- /dev/null +++ b/tests/aes128_user/client.conf @@ -0,0 +1,11 @@ +# Client — test: aes128_user + +applicationUri = urn:bobink.ClientFindServers +certificate = certs/ClientFindServers_cert.der +privateKey = certs/ClientFindServers_key.der +securityMode = SignAndEncrypt +securityPolicy = Aes128_Sha256_RsaOaep +authMode = user +username = user +password = password +trustStore = certs/trust/client diff --git a/tests/aes128_user/client_find_servers.conf b/tests/aes128_user/client_find_servers.conf deleted file mode 100644 index 4ecff56..0000000 --- a/tests/aes128_user/client_find_servers.conf +++ /dev/null @@ -1,24 +0,0 @@ -# ClientFindServers — test: aes128_user - -discoveryEndpoint = opc.tcp://localhost:14840 -applicationUri = urn:bobink.ClientFindServers - -# Discovery (LDS) side -discoveryCertificate = certs/ClientFindServers_cert.der -discoveryPrivateKey = certs/ClientFindServers_key.der -discoverySecurityMode = SignAndEncrypt -discoverySecurityPolicy = Aes128_Sha256_RsaOaep -discoveryAuthMode = user -discoveryUsername = user -discoveryPassword = password -discoveryTrustList = certs/ServerLDS_cert.der - -# Server side -serverCertificate = certs/ClientFindServers_cert.der -serverPrivateKey = certs/ClientFindServers_key.der -serverSecurityMode = SignAndEncrypt -serverSecurityPolicy = Aes128_Sha256_RsaOaep -serverAuthMode = user -serverUsername = user -serverPassword = password -serverTrustList = certs/ServerRegister_cert.der diff --git a/tests/aes128_user/server_lds.conf b/tests/aes128_user/server_lds.conf index 484512a..c440ea4 100644 --- a/tests/aes128_user/server_lds.conf +++ b/tests/aes128_user/server_lds.conf @@ -10,5 +10,4 @@ authMode = user username = user password = password -trustList = certs/ServerRegisterClient_cert.der -trustList = certs/ClientFindServers_cert.der +trustStore = certs/trust/server_lds diff --git a/tests/aes128_user/server_register.conf b/tests/aes128_user/server_register.conf index e100129..b0ce271 100644 --- a/tests/aes128_user/server_register.conf +++ b/tests/aes128_user/server_register.conf @@ -1,25 +1,14 @@ -# ServerRegister — test: aes128_user +# ServerRegister server config — test: aes128_user port = 14841 applicationUri = urn:bobink.ServerRegister -serverCertificate = certs/ServerRegister_cert.der -serverPrivateKey = certs/ServerRegister_key.der -clientCertificate = certs/ServerRegisterClient_cert.der -clientPrivateKey = certs/ServerRegisterClient_key.der +certificate = certs/ServerRegister_cert.der +privateKey = certs/ServerRegister_key.der -discoveryEndpoint = opc.tcp://localhost:14840 registerInterval = 10 -securityMode = SignAndEncrypt -securityPolicy = Aes128_Sha256_RsaOaep +authMode = user +username = user +password = password -serverAuthMode = user -serverUsername = user -serverPassword = password - -clientAuthMode = user -clientUsername = user -clientPassword = password - -trustList = certs/ServerLDS_cert.der -trustList = certs/ClientFindServers_cert.der +trustStore = certs/trust/server_register diff --git a/tests/aes128_user/server_register_client.conf b/tests/aes128_user/server_register_client.conf new file mode 100644 index 0000000..c919f7e --- /dev/null +++ b/tests/aes128_user/server_register_client.conf @@ -0,0 +1,14 @@ +# ServerRegister client config — test: aes128_user + +applicationUri = urn:bobink.ServerRegister +certificate = certs/ServerRegisterClient_cert.der +privateKey = certs/ServerRegisterClient_key.der + +securityMode = SignAndEncrypt +securityPolicy = Aes128_Sha256_RsaOaep + +authMode = user +username = user +password = password + +trustStore = certs/trust/server_register_client diff --git a/tests/basic256sha256_anon/client.conf b/tests/basic256sha256_anon/client.conf new file mode 100644 index 0000000..e46bb4c --- /dev/null +++ b/tests/basic256sha256_anon/client.conf @@ -0,0 +1,9 @@ +# Client — test: basic256sha256_anon + +applicationUri = urn:bobink.ClientFindServers +certificate = certs/ClientFindServers_cert.der +privateKey = certs/ClientFindServers_key.der +securityMode = SignAndEncrypt +securityPolicy = Basic256Sha256 +authMode = anonymous +trustStore = certs/trust/client diff --git a/tests/basic256sha256_anon/client_find_servers.conf b/tests/basic256sha256_anon/client_find_servers.conf deleted file mode 100644 index 332c3da..0000000 --- a/tests/basic256sha256_anon/client_find_servers.conf +++ /dev/null @@ -1,20 +0,0 @@ -# ClientFindServers — test: basic256sha256_anon - -discoveryEndpoint = opc.tcp://localhost:14840 -applicationUri = urn:bobink.ClientFindServers - -# Discovery (LDS) side -discoveryCertificate = certs/ClientFindServers_cert.der -discoveryPrivateKey = certs/ClientFindServers_key.der -discoverySecurityMode = SignAndEncrypt -discoverySecurityPolicy = Basic256Sha256 -discoveryAuthMode = anonymous -discoveryTrustList = certs/ServerLDS_cert.der - -# Server side -serverCertificate = certs/ClientFindServers_cert.der -serverPrivateKey = certs/ClientFindServers_key.der -serverSecurityMode = SignAndEncrypt -serverSecurityPolicy = Basic256Sha256 -serverAuthMode = anonymous -serverTrustList = certs/ServerRegister_cert.der diff --git a/tests/basic256sha256_anon/server_lds.conf b/tests/basic256sha256_anon/server_lds.conf index 7da2fd6..add5f46 100644 --- a/tests/basic256sha256_anon/server_lds.conf +++ b/tests/basic256sha256_anon/server_lds.conf @@ -8,5 +8,4 @@ cleanupTimeout = 60 authMode = anonymous -trustList = certs/ServerRegisterClient_cert.der -trustList = certs/ClientFindServers_cert.der +trustStore = certs/trust/server_lds diff --git a/tests/basic256sha256_anon/server_register.conf b/tests/basic256sha256_anon/server_register.conf index 798bf31..a5f904c 100644 --- a/tests/basic256sha256_anon/server_register.conf +++ b/tests/basic256sha256_anon/server_register.conf @@ -1,21 +1,12 @@ -# ServerRegister — test: basic256sha256_anon +# ServerRegister server config — test: basic256sha256_anon port = 14841 applicationUri = urn:bobink.ServerRegister -serverCertificate = certs/ServerRegister_cert.der -serverPrivateKey = certs/ServerRegister_key.der -clientCertificate = certs/ServerRegisterClient_cert.der -clientPrivateKey = certs/ServerRegisterClient_key.der +certificate = certs/ServerRegister_cert.der +privateKey = certs/ServerRegister_key.der -discoveryEndpoint = opc.tcp://localhost:14840 registerInterval = 10 -securityMode = SignAndEncrypt -securityPolicy = Basic256Sha256 +authMode = anonymous -serverAuthMode = anonymous - -clientAuthMode = anonymous - -trustList = certs/ServerLDS_cert.der -trustList = certs/ClientFindServers_cert.der +trustStore = certs/trust/server_register diff --git a/tests/basic256sha256_anon/server_register_client.conf b/tests/basic256sha256_anon/server_register_client.conf new file mode 100644 index 0000000..3a80d21 --- /dev/null +++ b/tests/basic256sha256_anon/server_register_client.conf @@ -0,0 +1,12 @@ +# ServerRegister client config — test: basic256sha256_anon + +applicationUri = urn:bobink.ServerRegister +certificate = certs/ServerRegisterClient_cert.der +privateKey = certs/ServerRegisterClient_key.der + +securityMode = SignAndEncrypt +securityPolicy = Basic256Sha256 + +authMode = anonymous + +trustStore = certs/trust/server_register_client diff --git a/tests/basic256sha256_user/client_find_servers.conf b/tests/basic256sha256_user/client_find_servers.conf deleted file mode 100644 index 403dfa4..0000000 --- a/tests/basic256sha256_user/client_find_servers.conf +++ /dev/null @@ -1,24 +0,0 @@ -# ClientFindServers — test: basic256sha256_user - -discoveryEndpoint = opc.tcp://localhost:14840 -applicationUri = urn:bobink.ClientFindServers - -# Discovery (LDS) side -discoveryCertificate = certs/ClientFindServers_cert.der -discoveryPrivateKey = certs/ClientFindServers_key.der -discoverySecurityMode = SignAndEncrypt -discoverySecurityPolicy = Basic256Sha256 -discoveryAuthMode = user -discoveryUsername = user -discoveryPassword = password -discoveryTrustList = certs/ServerLDS_cert.der - -# Server side -serverCertificate = certs/ClientFindServers_cert.der -serverPrivateKey = certs/ClientFindServers_key.der -serverSecurityMode = SignAndEncrypt -serverSecurityPolicy = Basic256Sha256 -serverAuthMode = user -serverUsername = user -serverPassword = password -serverTrustList = certs/ServerRegister_cert.der diff --git a/tests/basic256sha256_user/server_lds.conf b/tests/basic256sha256_user/server_lds.conf deleted file mode 100644 index 6841bb6..0000000 --- a/tests/basic256sha256_user/server_lds.conf +++ /dev/null @@ -1,14 +0,0 @@ -# ServerLDS — test: basic256sha256_user - -port = 14840 -applicationUri = urn:bobink.ServerLDS -certificate = certs/ServerLDS_cert.der -privateKey = certs/ServerLDS_key.der -cleanupTimeout = 60 - -authMode = user -username = user -password = password - -trustList = certs/ServerRegisterClient_cert.der -trustList = certs/ClientFindServers_cert.der diff --git a/tests/basic256sha256_user/server_register.conf b/tests/basic256sha256_user/server_register.conf deleted file mode 100644 index 636edd8..0000000 --- a/tests/basic256sha256_user/server_register.conf +++ /dev/null @@ -1,25 +0,0 @@ -# ServerRegister — test: basic256sha256_user - -port = 14841 -applicationUri = urn:bobink.ServerRegister -serverCertificate = certs/ServerRegister_cert.der -serverPrivateKey = certs/ServerRegister_key.der -clientCertificate = certs/ServerRegisterClient_cert.der -clientPrivateKey = certs/ServerRegisterClient_key.der - -discoveryEndpoint = opc.tcp://localhost:14840 -registerInterval = 10 - -securityMode = SignAndEncrypt -securityPolicy = Basic256Sha256 - -serverAuthMode = user -serverUsername = user -serverPassword = password - -clientAuthMode = user -clientUsername = user -clientPassword = password - -trustList = certs/ServerLDS_cert.der -trustList = certs/ClientFindServers_cert.der diff --git a/tests/none_anon/client.conf b/tests/none_anon/client.conf new file mode 100644 index 0000000..32eea7c --- /dev/null +++ b/tests/none_anon/client.conf @@ -0,0 +1,9 @@ +# Client — test: none_anon + +applicationUri = urn:bobink.ClientFindServers +certificate = certs/ClientFindServers_cert.der +privateKey = certs/ClientFindServers_key.der +securityMode = None +securityPolicy = None +authMode = anonymous +trustStore = certs/trust/client diff --git a/tests/none_anon/client_find_servers.conf b/tests/none_anon/client_find_servers.conf deleted file mode 100644 index 6ea7d2d..0000000 --- a/tests/none_anon/client_find_servers.conf +++ /dev/null @@ -1,20 +0,0 @@ -# ClientFindServers — test: none_anon - -discoveryEndpoint = opc.tcp://localhost:14840 -applicationUri = urn:bobink.ClientFindServers - -# Discovery (LDS) side -discoveryCertificate = certs/ClientFindServers_cert.der -discoveryPrivateKey = certs/ClientFindServers_key.der -discoverySecurityMode = None -discoverySecurityPolicy = None -discoveryAuthMode = anonymous -discoveryTrustList = certs/ServerLDS_cert.der - -# Server side -serverCertificate = certs/ClientFindServers_cert.der -serverPrivateKey = certs/ClientFindServers_key.der -serverSecurityMode = None -serverSecurityPolicy = None -serverAuthMode = anonymous -serverTrustList = certs/ServerRegister_cert.der diff --git a/tests/none_anon/server_lds.conf b/tests/none_anon/server_lds.conf index 705b51f..cf1ae45 100644 --- a/tests/none_anon/server_lds.conf +++ b/tests/none_anon/server_lds.conf @@ -8,5 +8,4 @@ cleanupTimeout = 60 authMode = anonymous -trustList = certs/ServerRegisterClient_cert.der -trustList = certs/ClientFindServers_cert.der +trustStore = certs/trust/server_lds diff --git a/tests/none_anon/server_register.conf b/tests/none_anon/server_register.conf index 349b6c7..e89fd59 100644 --- a/tests/none_anon/server_register.conf +++ b/tests/none_anon/server_register.conf @@ -1,21 +1,12 @@ -# ServerRegister — test: none_anon +# ServerRegister server config — test: none_anon port = 14841 applicationUri = urn:bobink.ServerRegister -serverCertificate = certs/ServerRegister_cert.der -serverPrivateKey = certs/ServerRegister_key.der -clientCertificate = certs/ServerRegisterClient_cert.der -clientPrivateKey = certs/ServerRegisterClient_key.der +certificate = certs/ServerRegister_cert.der +privateKey = certs/ServerRegister_key.der -discoveryEndpoint = opc.tcp://localhost:14840 registerInterval = 10 -securityMode = None -securityPolicy = None +authMode = anonymous -serverAuthMode = anonymous - -clientAuthMode = anonymous - -trustList = certs/ServerLDS_cert.der -trustList = certs/ClientFindServers_cert.der +trustStore = certs/trust/server_register diff --git a/tests/none_anon/server_register_client.conf b/tests/none_anon/server_register_client.conf new file mode 100644 index 0000000..83bf961 --- /dev/null +++ b/tests/none_anon/server_register_client.conf @@ -0,0 +1,12 @@ +# ServerRegister client config — test: none_anon + +applicationUri = urn:bobink.ServerRegister +certificate = certs/ServerRegisterClient_cert.der +privateKey = certs/ServerRegisterClient_key.der + +securityMode = None +securityPolicy = None + +authMode = anonymous + +trustStore = certs/trust/server_register_client diff --git a/tests/none_user/client.conf b/tests/none_user/client.conf new file mode 100644 index 0000000..18419f0 --- /dev/null +++ b/tests/none_user/client.conf @@ -0,0 +1,11 @@ +# Client — test: none_user + +applicationUri = urn:bobink.ClientFindServers +certificate = certs/ClientFindServers_cert.der +privateKey = certs/ClientFindServers_key.der +securityMode = None +securityPolicy = None +authMode = user +username = user +password = password +trustStore = certs/trust/client diff --git a/tests/none_user/client_find_servers.conf b/tests/none_user/client_find_servers.conf deleted file mode 100644 index b538952..0000000 --- a/tests/none_user/client_find_servers.conf +++ /dev/null @@ -1,24 +0,0 @@ -# ClientFindServers — test: none_user - -discoveryEndpoint = opc.tcp://localhost:14840 -applicationUri = urn:bobink.ClientFindServers - -# Discovery (LDS) side -discoveryCertificate = certs/ClientFindServers_cert.der -discoveryPrivateKey = certs/ClientFindServers_key.der -discoverySecurityMode = None -discoverySecurityPolicy = None -discoveryAuthMode = user -discoveryUsername = user -discoveryPassword = password -discoveryTrustList = certs/ServerLDS_cert.der - -# Server side -serverCertificate = certs/ClientFindServers_cert.der -serverPrivateKey = certs/ClientFindServers_key.der -serverSecurityMode = None -serverSecurityPolicy = None -serverAuthMode = user -serverUsername = user -serverPassword = password -serverTrustList = certs/ServerRegister_cert.der diff --git a/tests/none_user/server_lds.conf b/tests/none_user/server_lds.conf index eb6c2ff..12b2e4d 100644 --- a/tests/none_user/server_lds.conf +++ b/tests/none_user/server_lds.conf @@ -10,5 +10,4 @@ authMode = user username = user password = password -trustList = certs/ServerRegisterClient_cert.der -trustList = certs/ClientFindServers_cert.der +trustStore = certs/trust/server_lds diff --git a/tests/none_user/server_register.conf b/tests/none_user/server_register.conf index 890790e..81cf046 100644 --- a/tests/none_user/server_register.conf +++ b/tests/none_user/server_register.conf @@ -1,25 +1,14 @@ -# ServerRegister — test: none_user +# ServerRegister server config — test: none_user port = 14841 applicationUri = urn:bobink.ServerRegister -serverCertificate = certs/ServerRegister_cert.der -serverPrivateKey = certs/ServerRegister_key.der -clientCertificate = certs/ServerRegisterClient_cert.der -clientPrivateKey = certs/ServerRegisterClient_key.der +certificate = certs/ServerRegister_cert.der +privateKey = certs/ServerRegister_key.der -discoveryEndpoint = opc.tcp://localhost:14840 registerInterval = 10 -securityMode = None -securityPolicy = None +authMode = user +username = user +password = password -serverAuthMode = user -serverUsername = user -serverPassword = password - -clientAuthMode = user -clientUsername = user -clientPassword = password - -trustList = certs/ServerLDS_cert.der -trustList = certs/ClientFindServers_cert.der +trustStore = certs/trust/server_register diff --git a/tests/none_user/server_register_client.conf b/tests/none_user/server_register_client.conf new file mode 100644 index 0000000..14f409b --- /dev/null +++ b/tests/none_user/server_register_client.conf @@ -0,0 +1,14 @@ +# ServerRegister client config — test: none_user + +applicationUri = urn:bobink.ServerRegister +certificate = certs/ServerRegisterClient_cert.der +privateKey = certs/ServerRegisterClient_key.der + +securityMode = None +securityPolicy = None + +authMode = user +username = user +password = password + +trustStore = certs/trust/server_register_client diff --git a/tests/run_test.sh b/tests/run_test.sh index 8a87824..ef359ef 100755 --- a/tests/run_test.sh +++ b/tests/run_test.sh @@ -5,7 +5,8 @@ # Usage: tests/run_test.sh # # config_dir — directory containing server_lds.conf, -# server_register.conf, client_find_servers.conf +# server_register.conf, server_register_client.conf, +# client.conf # expected_policy — security-policy string that must appear in # the client's endpoint listing (e.g. # "Basic256Sha256", "Aes128_Sha256_RsaOaep", @@ -35,6 +36,24 @@ cleanup() { } trap cleanup EXIT +# ── helpers ──────────────────────────────────────────────────── +wait_for_port() { + local port="$1" pid="$2" label="$3" i=0 + while [ $i -lt 50 ]; do + if ! kill -0 "$pid" 2>/dev/null; then + echo "FAIL: $label exited prematurely" + exit 1 + fi + if ss -tlnp 2>/dev/null | grep -q ":${port} "; then + return 0 + fi + sleep 0.1 + i=$((i + 1)) + done + echo "FAIL: $label did not listen on port $port within 5 s" + exit 1 +} + # ── port check ───────────────────────────────────────────────── for port in $LDS_PORT $SR_PORT; do if ss -tlnp 2>/dev/null | grep -q ":${port} "; then @@ -46,29 +65,14 @@ done # ── start LDS ────────────────────────────────────────────────── 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 -fi +wait_for_port "$LDS_PORT" "$LDS_PID" "ServerLDS" # ── start ServerRegister ─────────────────────────────────────── -build/ServerRegister "$CONFIG_DIR/server_register.conf" >/dev/null 2>&1 & +build/ServerRegister "$CONFIG_DIR/server_register.conf" "$CONFIG_DIR/server_register_client.conf" "opc.tcp://localhost:$LDS_PORT" >/dev/null 2>&1 & SR_PID=$! -sleep 3 -if ! kill -0 "$SR_PID" 2>/dev/null; then - echo "FAIL: ServerRegister exited prematurely" - exit 1 -fi +wait_for_port "$SR_PORT" "$SR_PID" "ServerRegister" -# ── run client ───────────────────────────────────────────────── -TMPFILE=$(mktemp) -# UA_Log_Stdout writes to stdout; capture both stdout and stderr. -build/ClientFindServers "$CONFIG_DIR/client_find_servers.conf" >"$TMPFILE" 2>&1 -CLIENT_RC=$? -CLIENT_OUTPUT=$(<"$TMPFILE") - -# ── validation checks ───────────────────────────────────────── +# ── validation helper ───────────────────────────────────────── check() { local label="$1" result="$2" if [ "$result" -eq 0 ]; then @@ -79,27 +83,49 @@ check() { fi } -# 1. Exit code -[ "$CLIENT_RC" -eq 0 ] -check "client exit code is 0 (got $CLIENT_RC)" $? +# ── FindServers ─────────────────────────────────────────────── +TMPFILE=$(mktemp) +build/Client "$CONFIG_DIR/client.conf" find-servers "opc.tcp://localhost:$LDS_PORT" >"$TMPFILE" 2>&1 +FS_RC=$? +FS_OUTPUT=$(<"$TMPFILE") + +[ "$FS_RC" -eq 0 ] +check "find-servers exit code is 0 (got $FS_RC)" $? + +echo "$FS_OUTPUT" | grep -q "urn:bobink.ServerRegister" +check "find-servers contains urn:bobink.ServerRegister" $? + +# ── GetEndpoints ────────────────────────────────────────────── +build/Client "$CONFIG_DIR/client.conf" get-endpoints "opc.tcp://localhost:$SR_PORT" >"$TMPFILE" 2>&1 +GE_RC=$? +GE_OUTPUT=$(<"$TMPFILE") + +[ "$GE_RC" -eq 0 ] +check "get-endpoints exit code is 0 (got $GE_RC)" $? + +echo "$GE_OUTPUT" | grep -q "$EXPECTED_POLICY" +check "get-endpoints contains $EXPECTED_POLICY" $? -# 2. FindServers returned the registered server -echo "$CLIENT_OUTPUT" | grep -q "urn:bobink.ServerRegister" -check "FindServers contains urn:bobink.ServerRegister" $? +# ── ReadTime ────────────────────────────────────────────────── +build/Client "$CONFIG_DIR/client.conf" read-time "opc.tcp://localhost:$SR_PORT" >"$TMPFILE" 2>&1 +RT_RC=$? +RT_OUTPUT=$(<"$TMPFILE") -# 3. Client read the current time -echo "$CLIENT_OUTPUT" | grep -q "date is:" -check "client read current time" $? +[ "$RT_RC" -eq 0 ] +check "read-time exit code is 0 (got $RT_RC)" $? -# 4. Endpoint lists expected security policy -echo "$CLIENT_OUTPUT" | grep -q "$EXPECTED_POLICY" -check "endpoint contains $EXPECTED_POLICY" $? +echo "$RT_OUTPUT" | grep -q "date is:" +check "read-time output contains 'date is:'" $? # ── result ───────────────────────────────────────────────────── if [ "$FAILURES" -ne 0 ]; then echo "" - echo "--- client output ---" - echo "$CLIENT_OUTPUT" + echo "--- find-servers output ---" + echo "$FS_OUTPUT" + echo "--- get-endpoints output ---" + echo "$GE_OUTPUT" + echo "--- read-time output ---" + echo "$RT_OUTPUT" echo "--- end ---" exit 1 fi -- cgit v1.2.3