aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Vanbesien <tvanbesi@proton.me>2026-02-17 19:06:22 +0100
committerThomas Vanbesien <tvanbesi@proton.me>2026-02-17 19:06:22 +0100
commit827e90e0daabe32e058e08dd2a253425898a7e7a (patch)
treeecd3f31da63890ac029b7929eade88f38e078b3d
parente4ba24b3d24fdce36bc9dbd3c2c8f00b0ec23335 (diff)
downloadBobinkCOpcUa-827e90e0daabe32e058e08dd2a253425898a7e7a.tar.gz
BobinkCOpcUa-827e90e0daabe32e058e08dd2a253425898a7e7a.zip
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.
-rw-r--r--CMakeLists.txt18
-rw-r--r--config/client.conf23
-rw-r--r--config/client_find_servers.conf50
-rw-r--r--config/server_lds.conf5
-rw-r--r--config/server_register.conf54
-rw-r--r--config/server_register_client.conf26
-rw-r--r--src/client.c328
-rw-r--r--src/client_find_servers.c438
-rw-r--r--src/common.c114
-rw-r--r--src/common.h25
-rw-r--r--src/config.c40
-rw-r--r--src/config.h21
-rw-r--r--src/server_lds.c19
-rw-r--r--src/server_register.c227
-rw-r--r--tests/aes128_anon/client_find_servers.conf20
-rw-r--r--tests/aes128_anon/server_lds.conf12
-rw-r--r--tests/aes128_anon/server_register.conf21
-rw-r--r--tests/aes128_user/client.conf11
-rw-r--r--tests/aes128_user/client_find_servers.conf24
-rw-r--r--tests/aes128_user/server_lds.conf3
-rw-r--r--tests/aes128_user/server_register.conf25
-rw-r--r--tests/aes128_user/server_register_client.conf14
-rw-r--r--tests/basic256sha256_anon/client.conf9
-rw-r--r--tests/basic256sha256_anon/client_find_servers.conf20
-rw-r--r--tests/basic256sha256_anon/server_lds.conf3
-rw-r--r--tests/basic256sha256_anon/server_register.conf19
-rw-r--r--tests/basic256sha256_anon/server_register_client.conf12
-rw-r--r--tests/basic256sha256_user/client_find_servers.conf24
-rw-r--r--tests/basic256sha256_user/server_lds.conf14
-rw-r--r--tests/basic256sha256_user/server_register.conf25
-rw-r--r--tests/none_anon/client.conf9
-rw-r--r--tests/none_anon/client_find_servers.conf20
-rw-r--r--tests/none_anon/server_lds.conf3
-rw-r--r--tests/none_anon/server_register.conf19
-rw-r--r--tests/none_anon/server_register_client.conf12
-rw-r--r--tests/none_user/client.conf11
-rw-r--r--tests/none_user/client_find_servers.conf24
-rw-r--r--tests/none_user/server_lds.conf3
-rw-r--r--tests/none_user/server_register.conf25
-rw-r--r--tests/none_user/server_register_client.conf14
-rwxr-xr-xtests/run_test.sh94
41 files changed, 876 insertions, 1002 deletions
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 <open62541/client_highlevel.h>
+#include <open62541/plugin/log_stdout.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+/* ========================================================================
+ * 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 <config-file> <operation> <endpoint-url> [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 <open62541/client_highlevel.h>
-#include <open62541/plugin/log_stdout.h>
-
-#include <stdlib.h>
-#include <string.h>
-
-/* ========================================================================
- * 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 <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;
- }
-
- 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 <open62541/plugin/log_stdout.h>
#include <open62541/server_config_default.h>
+#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
@@ -56,6 +57,102 @@ loadFile (const char *const path)
}
/* ========================================================================
+ * 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 <open62541/server.h>
#include <open62541/types.h>
+#include <stddef.h>
+
/**
* @brief Loads a DER-encoded certificate or key file into a UA_ByteString.
*
@@ -22,6 +24,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.
*
* The server is initialized with the specified port, certificate, private key,
diff --git a/src/config.c b/src/config.c
index 163f601..2165821 100644
--- a/src/config.c
+++ b/src/config.c
@@ -200,46 +200,6 @@ configRequireInt (const Config *cfg, const char *key, const char *program)
}
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++)
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 <stddef.h>
@@ -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
{
@@ -92,22 +89,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.
*
* After this call the Config is zeroed and must not be used
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 <config-file> [log-level]", argv[0]);
+ "Usage: %s <server-config> <client-config> "
+ "<discovery-url> [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> <expected_policy>
#
# 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