aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Vanbesien <tvanbesi@proton.me>2026-02-17 11:07:37 +0100
committerThomas Vanbesien <tvanbesi@proton.me>2026-02-17 11:07:37 +0100
commita54421dd976fd8081e96c11c2621076876c9986b (patch)
treea7614934364bc692dd94ee13a3ec6d242521194b
parentd1e229c80a6e51ccc5b21d001271c41d6cda30bf (diff)
downloadBobinkCOpcUa-a54421dd976fd8081e96c11c2621076876c9986b.tar.gz
BobinkCOpcUa-a54421dd976fd8081e96c11c2621076876c9986b.zip
Replace CLI arguments with config-file parser and add integration tests
Introduce a reusable key=value config parser (config.h/c) and convert all three programs to read their settings from config files instead of positional command-line arguments. Add example config files in config/ and 6 CTest integration tests covering None/Basic256Sha256/Aes128 with anonymous and user authentication. Remove the now-obsolete launch.sh.
-rw-r--r--CLAUDE.md128
-rw-r--r--CMakeLists.txt31
-rw-r--r--config/client_find_servers.conf29
-rw-r--r--config/server_lds.conf25
-rw-r--r--config/server_register.conf47
-rw-r--r--src/client_find_servers.c80
-rw-r--r--src/config.c252
-rw-r--r--src/config.h120
-rw-r--r--src/server_lds.c74
-rw-r--r--src/server_register.c145
-rw-r--r--tests/aes128_anon/client_find_servers.conf14
-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_find_servers.conf16
-rw-r--r--tests/aes128_user/server_lds.conf14
-rw-r--r--tests/aes128_user/server_register.conf25
-rw-r--r--tests/basic256sha256_anon/client_find_servers.conf14
-rw-r--r--tests/basic256sha256_anon/server_lds.conf12
-rw-r--r--tests/basic256sha256_anon/server_register.conf21
-rw-r--r--tests/basic256sha256_user/client_find_servers.conf16
-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_find_servers.conf14
-rw-r--r--tests/none_anon/server_lds.conf12
-rw-r--r--tests/none_anon/server_register.conf21
-rw-r--r--tests/none_user/client_find_servers.conf16
-rw-r--r--tests/none_user/server_lds.conf14
-rw-r--r--tests/none_user/server_register.conf25
-rwxr-xr-xtests/run_test.sh106
-rwxr-xr-xtools/launch.sh148
30 files changed, 1179 insertions, 312 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index 4a6af57..4135924 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -61,77 +61,119 @@ Existing certs live in `certs/`. Only regenerate if missing.
## Running
-All three programs run from the project root. Start them in order in separate terminals:
+Each program takes a single argument: a configuration file. Example config files are in `config/`. Start programs in order in separate terminals from the project root:
**1. Local Discovery Server (LDS)**
```sh
-build/ServerLDS 4840 "urn:bobink.ServerLDS" \
- certs/ServerLDS_cert.der certs/ServerLDS_key.der \
- 60 \
- user user password \
- certs/ServerRegisterClient_cert.der certs/ClientFindServers_cert.der
+build/ServerLDS config/server_lds.conf
```
-Args: `<port> <applicationUri> <cert> <key> <cleanup-timeout-seconds> <auth-mode> [<username> <password>] [trustlist...]`
-
-The trustlist must include the client certs that will connect over encrypted channels: `ServerRegisterClient_cert.der` (used by `ServerRegister`) and `ClientFindServers_cert.der` (used by `ClientFindServers`). Cleanup timeout must be > 10.
-
**2. Register Server**
```sh
-build/ServerRegister 4841 "urn:bobink.ServerRegister" \
- certs/ServerRegister_cert.der certs/ServerRegister_key.der \
- certs/ServerRegisterClient_cert.der certs/ServerRegisterClient_key.der \
- "opc.tcp://localhost:4840" 10 \
- SignAndEncrypt Aes128_Sha256_RsaOaep \
- user user password \
- user user password \
- certs/ServerLDS_cert.der certs/ClientFindServers_cert.der
+build/ServerRegister config/server_register.conf
```
-Args: `<port> <applicationUri> <server-cert> <server-key> <client-cert> <client-key> <discovery-endpoint> <register-interval-seconds> <security-mode> <security-policy> <server-auth-mode> [<server-user> <server-pass>] <client-auth-mode> [<client-user> <client-pass>] [trustlist...]`
-
-Uses separate server/client certificate pairs. The client cert+key are for the secure channel to the LDS. Re-registers periodically at the given interval. Server auth mode controls what clients connecting to this server need; client auth mode controls how this server authenticates to the LDS. Trustlist should include the LDS cert and any client certs that will query this server's endpoints (e.g. `ClientFindServers_cert.der`).
-
**3. Find Servers Client**
```sh
-build/ClientFindServers "opc.tcp://localhost:4840" \
- "urn:bobink.ClientFindServers" \
- certs/ClientFindServers_cert.der certs/ClientFindServers_key.der \
- SignAndEncrypt Aes128_Sha256_RsaOaep \
- user user password \
- certs/ServerLDS_cert.der certs/ServerRegister_cert.der
+build/ClientFindServers config/client_find_servers.conf
```
-Args: `<discovery-server-endpoint> <applicationUri> <cert> <key> <security-mode> <security-policy> <auth-mode> [<username> <password>] [trustlist...]`
+## Configuration Files
+
+Config files use a simple `key = value` format, one entry per line. Lines starting with `#` are comments. Blank lines are ignored. Repeated keys are used for list values (e.g. multiple `trustList` entries).
+
+### ServerLDS Keys
+
+| Key | Required | Description |
+|-----|----------|-------------|
+| `port` | yes | Server port number |
+| `applicationUri` | yes | OPC UA application URI |
+| `certificate` | yes | Path to server certificate (.der) |
+| `privateKey` | yes | Path to server private key (.der) |
+| `cleanupTimeout` | yes | Seconds before stale registrations are removed (must be > 10) |
+| `authMode` | yes | `anonymous` or `user` |
+| `username` | if user | Username for authentication |
+| `password` | if user | Password for authentication |
+| `trustList` | no | Trusted certificate path (repeat for multiple) |
+
+### ServerRegister Keys
+
+| Key | Required | Description |
+|-----|----------|-------------|
+| `port` | yes | Server port number |
+| `applicationUri` | yes | OPC UA application URI |
+| `serverCertificate` | yes | Path to server certificate (.der) |
+| `serverPrivateKey` | yes | Path to server private key (.der) |
+| `clientCertificate` | yes | Path to client certificate for LDS connection (.der) |
+| `clientPrivateKey` | yes | Path to client private key for LDS connection (.der) |
+| `discoveryEndpoint` | yes | LDS endpoint URL (e.g. `opc.tcp://localhost:4840`) |
+| `registerInterval` | yes | Seconds between re-registrations |
+| `securityMode` | yes | `None`, `Sign`, or `SignAndEncrypt` |
+| `securityPolicy` | yes | `None`, `Basic256Sha256`, `Aes256_Sha256_RsaPss`, `Aes128_Sha256_RsaOaep`, or `ECC_nistP256` |
+| `serverAuthMode` | yes | Auth for clients connecting to this server: `anonymous` or `user` |
+| `serverUsername` | if user | Server-side username |
+| `serverPassword` | if user | Server-side password |
+| `clientAuthMode` | yes | Auth for LDS connection: `anonymous` or `user` |
+| `clientUsername` | if user | Client-side username (for LDS) |
+| `clientPassword` | if user | Client-side password (for LDS) |
+| `trustList` | no | Trusted certificate path (repeat for multiple) |
+
+### ClientFindServers Keys
+
+| Key | Required | Description |
+|-----|----------|-------------|
+| `discoveryEndpoint` | yes | LDS endpoint URL (e.g. `opc.tcp://localhost:4840`) |
+| `applicationUri` | yes | OPC UA application URI |
+| `certificate` | yes | Path to client certificate (.der) |
+| `privateKey` | yes | Path to client private key (.der) |
+| `securityMode` | yes | `None`, `Sign`, or `SignAndEncrypt` |
+| `securityPolicy` | yes | `None`, `Basic256Sha256`, `Aes256_Sha256_RsaPss`, `Aes128_Sha256_RsaOaep`, or `ECC_nistP256` |
+| `authMode` | yes | `anonymous` or `user` |
+| `username` | if user | Username for session auth |
+| `password` | if user | Password for session auth |
+| `trustList` | no | Trusted certificate path (repeat for multiple) |
+
+## Testing
+
+Integration tests use CTest and cover 6 combinations of security mode/policy and authentication:
+
+| Test | Security | Auth |
+|------|----------|------|
+| `none_anon` | None / None | anonymous |
+| `none_user` | None / None | user |
+| `basic256sha256_anon` | SignAndEncrypt / Basic256Sha256 | anonymous |
+| `basic256sha256_user` | SignAndEncrypt / Basic256Sha256 | user |
+| `aes128_anon` | SignAndEncrypt / Aes128_Sha256_RsaOaep | anonymous |
+| `aes128_user` | SignAndEncrypt / Aes128_Sha256_RsaOaep | user |
+
+Run all tests:
-Queries the LDS and prints all registered servers and their endpoints. Auth mode controls how the client authenticates when reading server time. The trustlist should include the certs of all servers whose endpoints will be queried.
-
-**Security and auth options** (for both ServerRegister and ClientFindServers):
+```sh
+ctest --test-dir build --output-on-failure
+```
-| Security modes | Security policies |
-|----------------|-------------------|
-| `None` | `None` |
-| `Sign` | `Basic256Sha256` |
-| `SignAndEncrypt` | `Aes256_Sha256_RsaPss` |
-| | `Aes128_Sha256_RsaOaep` |
-| | `ECC_nistP256` |
+Each test starts ServerLDS (port 14840) and ServerRegister (port 14841), runs ClientFindServers, then validates:
+1. Client exits with code 0
+2. FindServers output contains `urn:bobink.ServerRegister`
+3. Client read the current time (`date is:`)
+4. Endpoint listing contains the expected security policy
-| Auth modes | Description |
-|------------|-------------|
-| `anonymous` | No session credentials |
-| `user` | Username/password (requires two extra args) |
+To add a new test case: create a directory under `tests/` with 3 config files (`server_lds.conf`, `server_register.conf`, `client_find_servers.conf`), then add a `"name;ExpectedPolicy"` entry to the `INTEGRATION_TESTS` list in `CMakeLists.txt`.
## Project Structure
| Path | Purpose |
|------|---------|
| `src/common.h` / `src/common.c` | Shared helpers: `loadFile()`, `createSecureServer()`, `createSecureClientConfig()`, `parseSecurityMode()`, `resolveSecurityPolicyUri()` |
+| `src/config.h` / `src/config.c` | Config file parser: `configLoad()`, `configGet()`, `configRequire()`, `configRequireInt()`, `configGetAll()`, `configFree()` |
| `src/server_lds.c` | Local Discovery Server |
| `src/server_register.c` | Server that registers with LDS |
| `src/client_find_servers.c` | Client that queries LDS and displays endpoints |
+| `config/` | Example configuration files for all three programs |
+| `tests/` | Integration tests: `run_test.sh` helper and per-test config directories |
| `certs/` | TLS certificates for server, client, and LDS |
| `tools/` | Helper scripts |
| `cmake/BuildDeps.cmake` | Configures, builds, and installs open62541 |
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4075e49..93e472d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,7 +6,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include(cmake/BuildDeps.cmake)
-add_library(DiscoveryCommon STATIC src/common.c)
+add_library(DiscoveryCommon STATIC src/common.c src/config.c)
target_link_libraries(DiscoveryCommon open62541::open62541)
add_executable(ClientFindServers src/client_find_servers.c)
@@ -25,3 +25,32 @@ if(BUILD_DOC)
COMMENT "Building open62541 HTML documentation"
VERBATIM)
endif()
+
+# ── Integration tests ───────────────────────────────────────────
+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_policies
+ None
+ None
+ Basic256Sha256
+ Basic256Sha256
+ Aes128_Sha256_RsaOaep
+ Aes128_Sha256_RsaOaep)
+
+foreach (_name _policy IN ZIP_LISTS _test_names _test_policies)
+ add_test (NAME "${_name}"
+ COMMAND bash "${_test_script}" "tests/${_name}" "${_policy}")
+ set_tests_properties ("${_name}" PROPERTIES
+ WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+ TIMEOUT 30)
+endforeach ()
diff --git a/config/client_find_servers.conf b/config/client_find_servers.conf
new file mode 100644
index 0000000..a9e29c8
--- /dev/null
+++ b/config/client_find_servers.conf
@@ -0,0 +1,29 @@
+# ClientFindServers configuration
+#
+# Keys:
+# discoveryEndpoint LDS endpoint URL (e.g. opc.tcp://localhost:4840)
+# 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)
+# trustList Trusted certificate path (repeat for multiple)
+
+discoveryEndpoint = opc.tcp://localhost:4840
+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
+
+trustList = certs/ServerLDS_cert.der
+trustList = certs/ServerRegister_cert.der
diff --git a/config/server_lds.conf b/config/server_lds.conf
new file mode 100644
index 0000000..a30106c
--- /dev/null
+++ b/config/server_lds.conf
@@ -0,0 +1,25 @@
+# ServerLDS configuration
+#
+# Keys:
+# port Server port number
+# applicationUri OPC UA application URI
+# certificate Path to server certificate (.der)
+# privateKey Path to server private key (.der)
+# cleanupTimeout Seconds before stale registrations are removed (must be > 10)
+# authMode "anonymous" or "user"
+# username Username (required when authMode = user)
+# password Password (required when authMode = user)
+# trustList Trusted certificate path (repeat for multiple)
+
+port = 4840
+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/config/server_register.conf b/config/server_register.conf
new file mode 100644
index 0000000..3c905a5
--- /dev/null
+++ b/config/server_register.conf
@@ -0,0 +1,47 @@
+# ServerRegister 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 = 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
+
+discoveryEndpoint = opc.tcp://localhost:4840
+registerInterval = 10
+
+securityMode = SignAndEncrypt
+securityPolicy = Aes128_Sha256_RsaOaep
+
+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/src/client_find_servers.c b/src/client_find_servers.c
index 21d48ca..e50623f 100644
--- a/src/client_find_servers.c
+++ b/src/client_find_servers.c
@@ -10,6 +10,7 @@
*/
#include "common.h"
+#include "config.h"
#include <open62541/client_highlevel.h>
#include <open62541/plugin/log_stdout.h>
@@ -210,64 +211,71 @@ readServerTime (UA_Client *client,
int
main (int argc, char **argv)
{
- if (argc < 8)
+ if (argc != 2)
{
UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "Usage: %s <discovery-server-endpoint>\n"
- " <applicationUri>\n"
- " <certificate.der> <private-key.der>\n"
- " <security-mode> <security-policy>\n"
- " <auth-mode> [<username> <password>]\n"
- " [<trustlist1.der>, ...]\n"
- "\n"
- "Security modes : None, Sign, SignAndEncrypt\n"
- "Security policies: None, Basic256Sha256, "
- "Aes256_Sha256_RsaPss,\n"
- " Aes128_Sha256_RsaOaep, ECC_nistP256\n"
- "Auth modes : anonymous, user",
- argv[0]);
+ "Usage: %s <config-file>", argv[0]);
return EXIT_FAILURE;
}
- const char *discoveryServerEndpoint = argv[1];
- const char *applicationUri = argv[2];
- const char *certPath = argv[3];
- const char *keyPath = argv[4];
+ Config cfg;
+ if (configLoad (argv[1], &cfg) != 0)
+ return EXIT_FAILURE;
+
+ const char *discoveryServerEndpoint
+ = configRequire (&cfg, "discoveryEndpoint", "ClientFindServers");
+ const char *applicationUri
+ = configRequire (&cfg, "applicationUri", "ClientFindServers");
+ const char *certPath
+ = configRequire (&cfg, "certificate", "ClientFindServers");
+ const char *keyPath
+ = configRequire (&cfg, "privateKey", "ClientFindServers");
+ const char *securityModeStr
+ = configRequire (&cfg, "securityMode", "ClientFindServers");
+ const char *securityPolicyStr
+ = configRequire (&cfg, "securityPolicy", "ClientFindServers");
+ const char *authMode = configRequire (&cfg, "authMode", "ClientFindServers");
+
+ if (!discoveryServerEndpoint || !applicationUri || !certPath || !keyPath
+ || !securityModeStr || !securityPolicyStr || !authMode)
+ {
+ configFree (&cfg);
+ return EXIT_FAILURE;
+ }
- UA_MessageSecurityMode securityMode = parseSecurityMode (argv[5]);
+ UA_MessageSecurityMode securityMode = parseSecurityMode (securityModeStr);
if (securityMode == UA_MESSAGESECURITYMODE_INVALID)
{
UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "Unknown security mode: %s", argv[5]);
+ "Unknown security mode: %s", securityModeStr);
+ configFree (&cfg);
return EXIT_FAILURE;
}
- const char *securityPolicyUri = resolveSecurityPolicyUri (argv[6]);
+ const char *securityPolicyUri = resolveSecurityPolicyUri (securityPolicyStr);
if (!securityPolicyUri)
{
UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "Unknown security policy: %s", argv[6]);
+ "Unknown security policy: %s", securityPolicyStr);
+ configFree (&cfg);
return EXIT_FAILURE;
}
- int idx = 7;
- const char *authMode = argv[idx++];
const char *username = NULL, *password = NULL;
if (strcmp (authMode, "anonymous") == 0)
{
- /* No extra args needed */
+ /* No credentials needed */
}
else if (strcmp (authMode, "user") == 0)
{
- if (idx + 2 > argc)
+ username = configRequire (&cfg, "username", "ClientFindServers");
+ password = configRequire (&cfg, "password", "ClientFindServers");
+ if (!username || !password)
{
- UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "Auth mode 'user' requires <username> <password>");
+ configFree (&cfg);
return EXIT_FAILURE;
}
- username = argv[idx++];
- password = argv[idx++];
}
else
{
@@ -275,11 +283,13 @@ main (int argc, char **argv)
"Unknown auth mode: %s "
"(expected 'anonymous' or 'user')",
authMode);
+ configFree (&cfg);
return EXIT_FAILURE;
}
- char **trustPaths = argv + idx;
- size_t trustSize = (idx < argc) ? (size_t)(argc - idx) : 0;
+ char **trustPaths = NULL;
+ size_t trustSize = 0;
+ configGetAll (&cfg, "trustList", &trustPaths, &trustSize);
UA_Client *client = UA_Client_new ();
UA_StatusCode retval = createSecureClientConfig (
@@ -288,6 +298,8 @@ main (int argc, char **argv)
if (retval != UA_STATUSCODE_GOOD)
{
UA_Client_delete (client);
+ free (trustPaths);
+ configFree (&cfg);
return EXIT_FAILURE;
}
@@ -300,6 +312,8 @@ main (int argc, char **argv)
if (retval != UA_STATUSCODE_GOOD)
{
UA_Client_delete (client);
+ free (trustPaths);
+ configFree (&cfg);
return EXIT_FAILURE;
}
@@ -313,6 +327,8 @@ main (int argc, char **argv)
UA_Array_delete (applicationDescriptionArray,
applicationDescriptionArraySize,
&UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
+ free (trustPaths);
+ configFree (&cfg);
return EXIT_SUCCESS;
}
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..163f601
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,252 @@
+/**
+ * @file config.c
+ * @brief Simple key=value configuration file parser implementation.
+ */
+
+#include "config.h"
+
+#include <open62541/plugin/log_stdout.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Enough for typical config files; doubles on overflow. */
+#define CONFIG_INITIAL_CAPACITY 16
+
+/* Sufficient for file paths and URIs. */
+#define CONFIG_LINE_MAX 1024
+
+/* ========================================================================
+ * Static Helpers
+ * ======================================================================== */
+
+/**
+ * @brief Trims leading and trailing whitespace in place.
+ *
+ * Modifies the string by writing a NUL terminator after the last
+ * non-whitespace character and returns a pointer past any leading
+ * whitespace.
+ */
+static char *
+trim (char *s)
+{
+ while (*s == ' ' || *s == '\t')
+ s++;
+ size_t len = strlen (s);
+ while (len > 0 && (s[len - 1] == ' ' || s[len - 1] == '\t'))
+ len--;
+ s[len] = '\0';
+ return s;
+}
+
+/**
+ * @brief Appends a key-value pair to the config's dynamic array.
+ *
+ * Grows the array by doubling capacity when full.
+ *
+ * @return 0 on success, -1 on allocation failure.
+ */
+static int
+configAppend (Config *cfg, const char *key, const char *value)
+{
+ if (cfg->count == cfg->capacity)
+ {
+ size_t newCap = cfg->capacity * 2;
+ ConfigEntry *tmp = realloc (cfg->entries, newCap * sizeof (ConfigEntry));
+ if (!tmp)
+ {
+ UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Config: out of memory");
+ return -1;
+ }
+ cfg->entries = tmp;
+ cfg->capacity = newCap;
+ }
+
+ cfg->entries[cfg->count].key = strdup (key);
+ cfg->entries[cfg->count].value = strdup (value);
+ if (!cfg->entries[cfg->count].key || !cfg->entries[cfg->count].value)
+ {
+ UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Config: out of memory");
+ return -1;
+ }
+ cfg->count++;
+ return 0;
+}
+
+/* ========================================================================
+ * Public API
+ * ======================================================================== */
+
+int
+configLoad (const char *path, Config *cfg)
+{
+ memset (cfg, 0, sizeof (Config));
+
+ cfg->entries = malloc (CONFIG_INITIAL_CAPACITY * sizeof (ConfigEntry));
+ if (!cfg->entries)
+ {
+ UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Config: out of memory");
+ return -1;
+ }
+ cfg->capacity = CONFIG_INITIAL_CAPACITY;
+
+ FILE *fp = fopen (path, "r");
+ if (!fp)
+ {
+ UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Config: cannot open '%s'", path);
+ free (cfg->entries);
+ memset (cfg, 0, sizeof (Config));
+ return -1;
+ }
+
+ char line[CONFIG_LINE_MAX];
+ int lineNum = 0;
+
+ while (fgets (line, sizeof (line), fp))
+ {
+ lineNum++;
+
+ /* Strip trailing newline / carriage return. */
+ size_t len = strlen (line);
+ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+ line[--len] = '\0';
+
+ /* Skip blank lines and comments. */
+ char *trimmed = trim (line);
+ if (*trimmed == '\0' || *trimmed == '#')
+ continue;
+
+ /* Find the '=' separator. */
+ char *eq = strchr (trimmed, '=');
+ if (!eq)
+ {
+ UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Config: syntax error at %s:%d "
+ "(missing '=')",
+ path, lineNum);
+ fclose (fp);
+ configFree (cfg);
+ return -1;
+ }
+
+ *eq = '\0';
+ char *key = trim (trimmed);
+ char *value = trim (eq + 1);
+
+ if (*key == '\0')
+ {
+ UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Config: empty key at %s:%d", path, lineNum);
+ fclose (fp);
+ configFree (cfg);
+ return -1;
+ }
+
+ if (configAppend (cfg, key, value) != 0)
+ {
+ fclose (fp);
+ configFree (cfg);
+ return -1;
+ }
+ }
+
+ fclose (fp);
+ return 0;
+}
+
+const char *
+configGet (const Config *cfg, const char *key)
+{
+ for (size_t i = 0; i < cfg->count; i++)
+ {
+ if (strcmp (cfg->entries[i].key, key) == 0)
+ return cfg->entries[i].value;
+ }
+ return NULL;
+}
+
+const char *
+configRequire (const Config *cfg, const char *key, const char *program)
+{
+ const char *val = configGet (cfg, key);
+ if (!val)
+ UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "%s: missing required config key '%s'", program, key);
+ return val;
+}
+
+int
+configRequireInt (const Config *cfg, const char *key, const char *program)
+{
+ const char *val = configRequire (cfg, key, program);
+ if (!val)
+ return -1;
+
+ char *endptr;
+ long num = strtol (val, &endptr, 10);
+ if (*endptr != '\0')
+ {
+ UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "%s: config key '%s' is not a valid integer: '%s'",
+ program, key, val);
+ return -1;
+ }
+ return (int)num;
+}
+
+void
+configGetAll (const Config *cfg, const char *key, char ***out, size_t *size)
+{
+ /* First pass: count matches. */
+ size_t count = 0;
+ for (size_t i = 0; i < cfg->count; i++)
+ {
+ if (strcmp (cfg->entries[i].key, key) == 0)
+ count++;
+ }
+
+ if (count == 0)
+ {
+ *out = NULL;
+ *size = 0;
+ return;
+ }
+
+ /* Second pass: collect pointers. */
+ char **arr = malloc (count * sizeof (char *));
+ if (!arr)
+ {
+ UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Config: out of memory");
+ *out = NULL;
+ *size = 0;
+ return;
+ }
+
+ size_t idx = 0;
+ for (size_t i = 0; i < cfg->count; i++)
+ {
+ if (strcmp (cfg->entries[i].key, key) == 0)
+ arr[idx++] = cfg->entries[i].value;
+ }
+
+ *out = arr;
+ *size = count;
+}
+
+void
+configFree (Config *cfg)
+{
+ for (size_t i = 0; i < cfg->count; i++)
+ {
+ free (cfg->entries[i].key);
+ free (cfg->entries[i].value);
+ }
+ free (cfg->entries);
+ memset (cfg, 0, sizeof (Config));
+}
diff --git a/src/config.h b/src/config.h
new file mode 100644
index 0000000..ee29ada
--- /dev/null
+++ b/src/config.h
@@ -0,0 +1,120 @@
+/**
+ * @file config.h
+ * @brief Simple key=value configuration file parser.
+ *
+ * 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).
+ */
+
+#ifndef DISCOVERY_CONFIG_H
+#define DISCOVERY_CONFIG_H
+
+#include <stddef.h>
+
+/**
+ * @brief A single key=value entry from a config file.
+ *
+ * Both key and value are heap-allocated, null-terminated strings
+ * with leading/trailing whitespace trimmed.
+ */
+typedef struct
+{
+ char *key;
+ char *value;
+} ConfigEntry;
+
+/**
+ * @brief A parsed configuration file.
+ *
+ * Holds a dynamic array of ConfigEntry items. Duplicate keys are
+ * allowed (used for list-valued settings like trustList).
+ */
+typedef struct
+{
+ ConfigEntry *entries;
+ size_t count;
+ size_t capacity;
+} Config;
+
+/**
+ * @brief Parses a configuration file into a Config structure.
+ *
+ * Each non-blank, non-comment line must contain '=' separating key
+ * and value. Whitespace is trimmed around both key and value.
+ *
+ * @param path Path to the configuration file.
+ * @param cfg Output: the parsed configuration. Must be freed
+ * with configFree() when no longer needed.
+ * @return 0 on success, -1 on error (logged via UA_LOG_ERROR).
+ */
+int configLoad (const char *path, Config *cfg);
+
+/**
+ * @brief Looks up a single-valued key.
+ *
+ * Returns the value of the first entry matching @p key, or NULL
+ * if the key is not present.
+ *
+ * @param cfg The parsed configuration.
+ * @param key The key to search for.
+ * @return The value string (owned by cfg), or NULL.
+ */
+const char *configGet (const Config *cfg, const char *key);
+
+/**
+ * @brief Looks up a required single-valued key.
+ *
+ * Like configGet(), but logs a FATAL error and returns NULL when
+ * the key is missing.
+ *
+ * @param cfg The parsed configuration.
+ * @param key The key to search for.
+ * @param program Program name (for error messages).
+ * @return The value string (owned by cfg), or NULL if missing.
+ */
+const char *configRequire (const Config *cfg, const char *key,
+ const char *program);
+
+/**
+ * @brief Looks up a required integer-valued key.
+ *
+ * Parses the value of @p key as an integer. If the key is
+ * missing or the value is not a valid integer, logs a FATAL
+ * error and returns -1.
+ *
+ * @param cfg The parsed configuration.
+ * @param key The key to search for.
+ * @param program Program name (for error messages).
+ * @return The parsed integer, or -1 on error.
+ */
+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
+ * unless configLoad() is called again.
+ *
+ * @param cfg The configuration to free.
+ */
+void configFree (Config *cfg);
+
+#endif /* DISCOVERY_CONFIG_H */
diff --git a/src/server_lds.c b/src/server_lds.c
index fc51596..c6960d5 100644
--- a/src/server_lds.c
+++ b/src/server_lds.c
@@ -9,6 +9,7 @@
*/
#include "common.h"
+#include "config.h"
#include <open62541/plugin/accesscontrol_default.h>
#include <open62541/plugin/log_stdout.h>
@@ -17,6 +18,7 @@
#include <signal.h>
#include <stdlib.h>
+#include <string.h>
UA_Boolean running = true;
@@ -33,23 +35,31 @@ main (int argc, char *argv[])
signal (SIGINT, stopHandler);
signal (SIGTERM, stopHandler);
- if (argc < 7)
+ if (argc != 2)
{
UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "Usage: %s\n"
- " <port> <applicationUri>\n"
- " <server-certificate.der> <private-key.der>\n"
- " <cleanup-timeout-seconds>\n"
- " <auth-mode> [<username> <password>]\n"
- " [<trustlist1.der>, ...]\n"
- "\n"
- "Auth modes: anonymous, user",
- argv[0]);
+ "Usage: %s <config-file>", argv[0]);
return EXIT_FAILURE;
}
- UA_UInt16 port = (UA_UInt16)atoi (argv[1]);
- int cleanupTimeout = atoi (argv[5]);
+ Config cfg;
+ if (configLoad (argv[1], &cfg) != 0)
+ return EXIT_FAILURE;
+
+ int port = configRequireInt (&cfg, "port", "ServerLDS");
+ const char *applicationUri
+ = configRequire (&cfg, "applicationUri", "ServerLDS");
+ const char *certPath = configRequire (&cfg, "certificate", "ServerLDS");
+ const char *keyPath = configRequire (&cfg, "privateKey", "ServerLDS");
+ int cleanupTimeout = configRequireInt (&cfg, "cleanupTimeout", "ServerLDS");
+ const char *authMode = configRequire (&cfg, "authMode", "ServerLDS");
+
+ if (!applicationUri || !certPath || !keyPath || !authMode || port < 0
+ || cleanupTimeout < 0)
+ {
+ configFree (&cfg);
+ return EXIT_FAILURE;
+ }
/* The OPC UA specification requires the cleanup timeout to exceed the
register-server interval. open62541 enforces a floor of 10 seconds. */
@@ -58,13 +68,12 @@ main (int argc, char *argv[])
UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Cleanup timeout must be > 10 seconds (got %d)",
cleanupTimeout);
+ configFree (&cfg);
return EXIT_FAILURE;
}
- int idx = 6;
- const char *authMode = argv[idx++];
UA_Boolean allowAnonymous;
- char *username = NULL, *password = NULL;
+ const char *username = NULL, *password = NULL;
if (strcmp (authMode, "anonymous") == 0)
{
@@ -72,15 +81,14 @@ main (int argc, char *argv[])
}
else if (strcmp (authMode, "user") == 0)
{
- if (idx + 2 > argc)
+ allowAnonymous = false;
+ username = configRequire (&cfg, "username", "ServerLDS");
+ password = configRequire (&cfg, "password", "ServerLDS");
+ if (!username || !password)
{
- UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "Auth mode 'user' requires <username> <password>");
+ configFree (&cfg);
return EXIT_FAILURE;
}
- allowAnonymous = false;
- username = argv[idx++];
- password = argv[idx++];
}
else
{
@@ -88,16 +96,24 @@ main (int argc, char *argv[])
"Unknown auth mode: %s "
"(expected 'anonymous' or 'user')",
authMode);
+ configFree (&cfg);
return EXIT_FAILURE;
}
- size_t trustSize = (idx < argc) ? (size_t)(argc - idx) : 0;
+ char **trustPaths = NULL;
+ size_t trustSize = 0;
+ configGetAll (&cfg, "trustList", &trustPaths, &trustSize);
UA_StatusCode retval;
- UA_Server *server = createSecureServer (port, argv[2], argv[3], argv[4],
- argv + idx, trustSize, &retval);
+ UA_Server *server
+ = createSecureServer ((UA_UInt16)port, applicationUri, certPath, keyPath,
+ trustPaths, trustSize, &retval);
if (!server)
- return EXIT_FAILURE;
+ {
+ free (trustPaths);
+ configFree (&cfg);
+ return EXIT_FAILURE;
+ }
UA_ServerConfig *serverConfig = UA_Server_getConfig (server);
@@ -108,12 +124,14 @@ main (int argc, char *argv[])
if (!allowAnonymous)
{
UA_UsernamePasswordLogin logins[1];
- logins[0].username = UA_STRING (username);
- logins[0].password = UA_STRING (password);
+ logins[0].username = UA_STRING ((char *)username);
+ logins[0].password = UA_STRING ((char *)password);
retval = UA_AccessControl_default (serverConfig, false, NULL, 1, logins);
if (retval != UA_STATUSCODE_GOOD)
{
UA_Server_delete (server);
+ free (trustPaths);
+ configFree (&cfg);
return EXIT_FAILURE;
}
}
@@ -129,5 +147,7 @@ main (int argc, char *argv[])
retval = UA_Server_run (server, &running);
UA_Server_delete (server);
+ free (trustPaths);
+ configFree (&cfg);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
diff --git a/src/server_register.c b/src/server_register.c
index 60a4998..ae8e959 100644
--- a/src/server_register.c
+++ b/src/server_register.c
@@ -9,6 +9,7 @@
*/
#include "common.h"
+#include "config.h"
#include <open62541/client.h>
#include <open62541/client_config_default.h>
@@ -41,59 +42,73 @@ main (int argc, char **argv)
signal (SIGINT, stopHandler);
signal (SIGTERM, stopHandler);
- if (argc < 13)
+ if (argc != 2)
{
UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "Usage: %s\n"
- " <port> <applicationUri>\n"
- " <server-certificate.der> <server-private-key.der>\n"
- " <client-certificate.der> <client-private-key.der>\n"
- " <discovery-server-endpoint>\n"
- " <register-interval-seconds>\n"
- " <security-mode> <security-policy>\n"
- " <server-auth-mode> [<server-user> <server-pass>]\n"
- " <client-auth-mode> [<client-user> <client-pass>]\n"
- " [<trustlist1.der>, ...]\n"
- "\n"
- "Security modes : None, Sign, SignAndEncrypt\n"
- "Security policies: None, Basic256Sha256, "
- "Aes256_Sha256_RsaPss,\n"
- " Aes128_Sha256_RsaOaep, ECC_nistP256\n"
- "Auth modes : anonymous, user",
- argv[0]);
+ "Usage: %s <config-file>", argv[0]);
return EXIT_FAILURE;
}
- UA_UInt16 port = (UA_UInt16)atoi (argv[1]);
- const char *applicationUri = argv[2];
- const char *clientCertPath = argv[5];
- const char *clientKeyPath = argv[6];
- const char *discoveryEndpoint = argv[7];
- int registerInterval = atoi (argv[8]);
+ Config cfg;
+ if (configLoad (argv[1], &cfg) != 0)
+ return EXIT_FAILURE;
+
+ int port = configRequireInt (&cfg, "port", "ServerRegister");
+ const char *applicationUri
+ = configRequire (&cfg, "applicationUri", "ServerRegister");
+ const char *serverCertPath
+ = configRequire (&cfg, "serverCertificate", "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");
+ int registerInterval
+ = configRequireInt (&cfg, "registerInterval", "ServerRegister");
+ const char *securityModeStr
+ = configRequire (&cfg, "securityMode", "ServerRegister");
+ const char *securityPolicyStr
+ = configRequire (&cfg, "securityPolicy", "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;
+ }
- UA_MessageSecurityMode securityMode = parseSecurityMode (argv[9]);
+ UA_MessageSecurityMode securityMode = parseSecurityMode (securityModeStr);
if (securityMode == UA_MESSAGESECURITYMODE_INVALID)
{
UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "Unknown security mode: %s", argv[9]);
+ "Unknown security mode: %s", securityModeStr);
+ configFree (&cfg);
return EXIT_FAILURE;
}
- const char *securityPolicyUri = resolveSecurityPolicyUri (argv[10]);
+ const char *securityPolicyUri = resolveSecurityPolicyUri (securityPolicyStr);
if (!securityPolicyUri)
{
UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "Unknown security policy: %s", argv[10]);
+ "Unknown security policy: %s", securityPolicyStr);
+ configFree (&cfg);
return EXIT_FAILURE;
}
/* Parse server-side auth mode (what clients connecting to this server
need). "anonymous" allows unauthenticated sessions; "user" requires
a username/password pair. */
- int idx = 11;
- const char *serverAuthMode = argv[idx++];
UA_Boolean serverAllowAnonymous;
- char *serverUsername = NULL, *serverPassword = NULL;
+ const char *serverUsername = NULL, *serverPassword = NULL;
if (strcmp (serverAuthMode, "anonymous") == 0)
{
@@ -101,16 +116,16 @@ main (int argc, char **argv)
}
else if (strcmp (serverAuthMode, "user") == 0)
{
- if (idx + 2 > argc)
+ serverAllowAnonymous = false;
+ serverUsername
+ = configRequire (&cfg, "serverUsername", "ServerRegister");
+ serverPassword
+ = configRequire (&cfg, "serverPassword", "ServerRegister");
+ if (!serverUsername || !serverPassword)
{
- UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "Server auth mode 'user' requires "
- "<username> <password>");
+ configFree (&cfg);
return EXIT_FAILURE;
}
- serverAllowAnonymous = false;
- serverUsername = argv[idx++];
- serverPassword = argv[idx++];
}
else
{
@@ -118,34 +133,28 @@ main (int argc, char **argv)
"Unknown server auth mode: %s "
"(expected 'anonymous' or 'user')",
serverAuthMode);
+ configFree (&cfg);
return EXIT_FAILURE;
}
/* Parse client-side auth mode (how this server authenticates to the
LDS when registering). */
- if (idx >= argc)
- {
- UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "Missing client auth mode");
- return EXIT_FAILURE;
- }
- const char *clientAuthMode = argv[idx++];
- char *clientUsername = NULL, *clientPassword = NULL;
+ const char *clientUsername = NULL, *clientPassword = NULL;
if (strcmp (clientAuthMode, "anonymous") == 0)
{
}
else if (strcmp (clientAuthMode, "user") == 0)
{
- if (idx + 2 > argc)
+ clientUsername
+ = configRequire (&cfg, "clientUsername", "ServerRegister");
+ clientPassword
+ = configRequire (&cfg, "clientPassword", "ServerRegister");
+ if (!clientUsername || !clientPassword)
{
- UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
- "Client auth mode 'user' requires "
- "<username> <password>");
+ configFree (&cfg);
return EXIT_FAILURE;
}
- clientUsername = argv[idx++];
- clientPassword = argv[idx++];
}
else
{
@@ -153,16 +162,24 @@ main (int argc, char **argv)
"Unknown client auth mode: %s "
"(expected 'anonymous' or 'user')",
clientAuthMode);
+ configFree (&cfg);
return EXIT_FAILURE;
}
- size_t trustSize = (idx < argc) ? (size_t)(argc - idx) : 0;
+ char **trustPaths = NULL;
+ size_t trustSize = 0;
+ configGetAll (&cfg, "trustList", &trustPaths, &trustSize);
UA_StatusCode retval;
- UA_Server *server = createSecureServer (
- port, applicationUri, argv[3], argv[4], argv + idx, trustSize, &retval);
+ UA_Server *server
+ = createSecureServer ((UA_UInt16)port, applicationUri, serverCertPath,
+ serverKeyPath, trustPaths, trustSize, &retval);
if (!server)
- return EXIT_FAILURE;
+ {
+ free (trustPaths);
+ configFree (&cfg);
+ return EXIT_FAILURE;
+ }
UA_ServerConfig *serverConfig = UA_Server_getConfig (server);
@@ -173,12 +190,14 @@ main (int argc, char **argv)
if (!serverAllowAnonymous)
{
UA_UsernamePasswordLogin logins[1];
- logins[0].username = UA_STRING (serverUsername);
- logins[0].password = UA_STRING (serverPassword);
+ logins[0].username = UA_STRING ((char *)serverUsername);
+ logins[0].password = UA_STRING ((char *)serverPassword);
retval = UA_AccessControl_default (serverConfig, false, NULL, 1, logins);
if (retval != UA_STATUSCODE_GOOD)
{
UA_Server_delete (server);
+ free (trustPaths);
+ configFree (&cfg);
return EXIT_FAILURE;
}
}
@@ -195,12 +214,14 @@ main (int argc, char **argv)
UA_ClientConfig clientConfig;
memset (&clientConfig, 0, sizeof (UA_ClientConfig));
retval = createSecureClientConfig (
- &clientConfig, applicationUri, clientCertPath, clientKeyPath, argv + idx,
+ &clientConfig, applicationUri, clientCertPath, clientKeyPath, trustPaths,
trustSize, securityMode, securityPolicyUri);
if (retval != UA_STATUSCODE_GOOD)
{
UA_Server_run_shutdown (server);
UA_Server_delete (server);
+ free (trustPaths);
+ configFree (&cfg);
return EXIT_FAILURE;
}
if (clientUsername)
@@ -230,7 +251,7 @@ main (int argc, char **argv)
memset (&clientConfig, 0, sizeof (UA_ClientConfig));
retval = createSecureClientConfig (
&clientConfig, applicationUri, clientCertPath, clientKeyPath,
- argv + idx, trustSize, securityMode, securityPolicyUri);
+ trustPaths, trustSize, securityMode, securityPolicyUri);
if (retval == UA_STATUSCODE_GOOD)
{
if (clientUsername)
@@ -253,7 +274,7 @@ 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, argv + idx,
+ &clientConfig, applicationUri, clientCertPath, clientKeyPath, trustPaths,
trustSize, securityMode, securityPolicyUri);
if (retval == UA_STATUSCODE_GOOD)
{
@@ -271,5 +292,7 @@ main (int argc, char **argv)
UA_Server_run_shutdown (server);
UA_Server_delete (server);
+ free (trustPaths);
+ configFree (&cfg);
return EXIT_SUCCESS;
}
diff --git a/tests/aes128_anon/client_find_servers.conf b/tests/aes128_anon/client_find_servers.conf
new file mode 100644
index 0000000..87eed56
--- /dev/null
+++ b/tests/aes128_anon/client_find_servers.conf
@@ -0,0 +1,14 @@
+# ClientFindServers — test: aes128_anon
+
+discoveryEndpoint = opc.tcp://localhost:14840
+applicationUri = urn:bobink.ClientFindServers
+certificate = certs/ClientFindServers_cert.der
+privateKey = certs/ClientFindServers_key.der
+
+securityMode = SignAndEncrypt
+securityPolicy = Aes128_Sha256_RsaOaep
+
+authMode = anonymous
+
+trustList = certs/ServerLDS_cert.der
+trustList = certs/ServerRegister_cert.der
diff --git a/tests/aes128_anon/server_lds.conf b/tests/aes128_anon/server_lds.conf
new file mode 100644
index 0000000..e8601d0
--- /dev/null
+++ b/tests/aes128_anon/server_lds.conf
@@ -0,0 +1,12 @@
+# 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
new file mode 100644
index 0000000..8a8d1d1
--- /dev/null
+++ b/tests/aes128_anon/server_register.conf
@@ -0,0 +1,21 @@
+# 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_find_servers.conf b/tests/aes128_user/client_find_servers.conf
new file mode 100644
index 0000000..58d7bd1
--- /dev/null
+++ b/tests/aes128_user/client_find_servers.conf
@@ -0,0 +1,16 @@
+# ClientFindServers — test: aes128_user
+
+discoveryEndpoint = opc.tcp://localhost:14840
+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
+
+trustList = certs/ServerLDS_cert.der
+trustList = certs/ServerRegister_cert.der
diff --git a/tests/aes128_user/server_lds.conf b/tests/aes128_user/server_lds.conf
new file mode 100644
index 0000000..484512a
--- /dev/null
+++ b/tests/aes128_user/server_lds.conf
@@ -0,0 +1,14 @@
+# ServerLDS — test: aes128_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/aes128_user/server_register.conf b/tests/aes128_user/server_register.conf
new file mode 100644
index 0000000..e100129
--- /dev/null
+++ b/tests/aes128_user/server_register.conf
@@ -0,0 +1,25 @@
+# ServerRegister — 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
+
+discoveryEndpoint = opc.tcp://localhost:14840
+registerInterval = 10
+
+securityMode = SignAndEncrypt
+securityPolicy = Aes128_Sha256_RsaOaep
+
+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/basic256sha256_anon/client_find_servers.conf b/tests/basic256sha256_anon/client_find_servers.conf
new file mode 100644
index 0000000..fb3d9d4
--- /dev/null
+++ b/tests/basic256sha256_anon/client_find_servers.conf
@@ -0,0 +1,14 @@
+# ClientFindServers — test: basic256sha256_anon
+
+discoveryEndpoint = opc.tcp://localhost:14840
+applicationUri = urn:bobink.ClientFindServers
+certificate = certs/ClientFindServers_cert.der
+privateKey = certs/ClientFindServers_key.der
+
+securityMode = SignAndEncrypt
+securityPolicy = Basic256Sha256
+
+authMode = anonymous
+
+trustList = certs/ServerLDS_cert.der
+trustList = certs/ServerRegister_cert.der
diff --git a/tests/basic256sha256_anon/server_lds.conf b/tests/basic256sha256_anon/server_lds.conf
new file mode 100644
index 0000000..7da2fd6
--- /dev/null
+++ b/tests/basic256sha256_anon/server_lds.conf
@@ -0,0 +1,12 @@
+# ServerLDS — test: basic256sha256_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/basic256sha256_anon/server_register.conf b/tests/basic256sha256_anon/server_register.conf
new file mode 100644
index 0000000..798bf31
--- /dev/null
+++ b/tests/basic256sha256_anon/server_register.conf
@@ -0,0 +1,21 @@
+# ServerRegister — 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
+
+discoveryEndpoint = opc.tcp://localhost:14840
+registerInterval = 10
+
+securityMode = SignAndEncrypt
+securityPolicy = Basic256Sha256
+
+serverAuthMode = anonymous
+
+clientAuthMode = anonymous
+
+trustList = certs/ServerLDS_cert.der
+trustList = certs/ClientFindServers_cert.der
diff --git a/tests/basic256sha256_user/client_find_servers.conf b/tests/basic256sha256_user/client_find_servers.conf
new file mode 100644
index 0000000..93f511c
--- /dev/null
+++ b/tests/basic256sha256_user/client_find_servers.conf
@@ -0,0 +1,16 @@
+# ClientFindServers — test: basic256sha256_user
+
+discoveryEndpoint = opc.tcp://localhost:14840
+applicationUri = urn:bobink.ClientFindServers
+certificate = certs/ClientFindServers_cert.der
+privateKey = certs/ClientFindServers_key.der
+
+securityMode = SignAndEncrypt
+securityPolicy = Basic256Sha256
+
+authMode = user
+username = user
+password = password
+
+trustList = certs/ServerLDS_cert.der
+trustList = certs/ServerRegister_cert.der
diff --git a/tests/basic256sha256_user/server_lds.conf b/tests/basic256sha256_user/server_lds.conf
new file mode 100644
index 0000000..6841bb6
--- /dev/null
+++ b/tests/basic256sha256_user/server_lds.conf
@@ -0,0 +1,14 @@
+# 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
new file mode 100644
index 0000000..636edd8
--- /dev/null
+++ b/tests/basic256sha256_user/server_register.conf
@@ -0,0 +1,25 @@
+# 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_find_servers.conf b/tests/none_anon/client_find_servers.conf
new file mode 100644
index 0000000..8d874e7
--- /dev/null
+++ b/tests/none_anon/client_find_servers.conf
@@ -0,0 +1,14 @@
+# ClientFindServers — test: none_anon
+
+discoveryEndpoint = opc.tcp://localhost:14840
+applicationUri = urn:bobink.ClientFindServers
+certificate = certs/ClientFindServers_cert.der
+privateKey = certs/ClientFindServers_key.der
+
+securityMode = None
+securityPolicy = None
+
+authMode = anonymous
+
+trustList = certs/ServerLDS_cert.der
+trustList = certs/ServerRegister_cert.der
diff --git a/tests/none_anon/server_lds.conf b/tests/none_anon/server_lds.conf
new file mode 100644
index 0000000..705b51f
--- /dev/null
+++ b/tests/none_anon/server_lds.conf
@@ -0,0 +1,12 @@
+# ServerLDS — test: none_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/none_anon/server_register.conf b/tests/none_anon/server_register.conf
new file mode 100644
index 0000000..349b6c7
--- /dev/null
+++ b/tests/none_anon/server_register.conf
@@ -0,0 +1,21 @@
+# ServerRegister — 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
+
+discoveryEndpoint = opc.tcp://localhost:14840
+registerInterval = 10
+
+securityMode = None
+securityPolicy = None
+
+serverAuthMode = anonymous
+
+clientAuthMode = anonymous
+
+trustList = certs/ServerLDS_cert.der
+trustList = certs/ClientFindServers_cert.der
diff --git a/tests/none_user/client_find_servers.conf b/tests/none_user/client_find_servers.conf
new file mode 100644
index 0000000..10e9888
--- /dev/null
+++ b/tests/none_user/client_find_servers.conf
@@ -0,0 +1,16 @@
+# ClientFindServers — test: none_user
+
+discoveryEndpoint = opc.tcp://localhost:14840
+applicationUri = urn:bobink.ClientFindServers
+certificate = certs/ClientFindServers_cert.der
+privateKey = certs/ClientFindServers_key.der
+
+securityMode = None
+securityPolicy = None
+
+authMode = user
+username = user
+password = password
+
+trustList = certs/ServerLDS_cert.der
+trustList = certs/ServerRegister_cert.der
diff --git a/tests/none_user/server_lds.conf b/tests/none_user/server_lds.conf
new file mode 100644
index 0000000..eb6c2ff
--- /dev/null
+++ b/tests/none_user/server_lds.conf
@@ -0,0 +1,14 @@
+# ServerLDS — test: none_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/none_user/server_register.conf b/tests/none_user/server_register.conf
new file mode 100644
index 0000000..890790e
--- /dev/null
+++ b/tests/none_user/server_register.conf
@@ -0,0 +1,25 @@
+# ServerRegister — 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
+
+discoveryEndpoint = opc.tcp://localhost:14840
+registerInterval = 10
+
+securityMode = None
+securityPolicy = None
+
+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/run_test.sh b/tests/run_test.sh
new file mode 100755
index 0000000..5173fa3
--- /dev/null
+++ b/tests/run_test.sh
@@ -0,0 +1,106 @@
+#!/usr/bin/env bash
+# ---------------------------------------------------------------
+# Integration-test helper for OPC UA discovery.
+#
+# Usage: tests/run_test.sh <config_dir> <expected_policy>
+#
+# config_dir — directory containing server_lds.conf,
+# server_register.conf, client_find_servers.conf
+# expected_policy — security-policy string that must appear in
+# the client's endpoint listing (e.g.
+# "Basic256Sha256", "Aes128_Sha256_RsaOaep",
+# or "None")
+#
+# Exit: 0 when all checks pass, 1 on any failure.
+# ---------------------------------------------------------------
+set -uo pipefail
+# NOTE: we intentionally omit "set -e" so that every check runs
+# even if the client itself returns non-zero.
+
+CONFIG_DIR="${1:?Usage: $0 <config_dir> <expected_policy>}"
+EXPECTED_POLICY="${2:?Usage: $0 <config_dir> <expected_policy>}"
+
+LDS_PORT=14840
+SR_PORT=14841
+LDS_PID=""
+SR_PID=""
+TMPFILE=""
+FAILURES=0
+
+# ── cleanup ────────────────────────────────────────────────────
+cleanup() {
+ [ -n "$LDS_PID" ] && kill "$LDS_PID" 2>/dev/null && wait "$LDS_PID" 2>/dev/null
+ [ -n "$SR_PID" ] && kill "$SR_PID" 2>/dev/null && wait "$SR_PID" 2>/dev/null
+ [ -n "$TMPFILE" ] && rm -f "$TMPFILE"
+}
+trap cleanup EXIT
+
+# ── port check ─────────────────────────────────────────────────
+for port in $LDS_PORT $SR_PORT; do
+ if ss -tlnp 2>/dev/null | grep -q ":${port} "; then
+ echo "FAIL: port $port is already in use"
+ exit 1
+ fi
+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
+
+# ── start ServerRegister ───────────────────────────────────────
+build/ServerRegister "$CONFIG_DIR/server_register.conf" >/dev/null 2>&1 &
+SR_PID=$!
+sleep 3
+if ! kill -0 "$SR_PID" 2>/dev/null; then
+ echo "FAIL: ServerRegister exited prematurely"
+ exit 1
+fi
+
+# ── 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 ─────────────────────────────────────────
+check() {
+ local label="$1" result="$2"
+ if [ "$result" -eq 0 ]; then
+ echo "PASS: $label"
+ else
+ echo "FAIL: $label"
+ FAILURES=$((FAILURES + 1))
+ fi
+}
+
+# 1. Exit code
+[ "$CLIENT_RC" -eq 0 ]
+check "client exit code is 0 (got $CLIENT_RC)" $?
+
+# 2. FindServers returned the registered server
+echo "$CLIENT_OUTPUT" | grep -q "urn:bobink.ServerRegister"
+check "FindServers contains urn:bobink.ServerRegister" $?
+
+# 3. Client read the current time
+echo "$CLIENT_OUTPUT" | grep -q "date is:"
+check "client read current time" $?
+
+# 4. Endpoint lists expected security policy
+echo "$CLIENT_OUTPUT" | grep -q "$EXPECTED_POLICY"
+check "endpoint contains $EXPECTED_POLICY" $?
+
+# ── result ─────────────────────────────────────────────────────
+if [ "$FAILURES" -ne 0 ]; then
+ echo ""
+ echo "--- client output ---"
+ echo "$CLIENT_OUTPUT"
+ echo "--- end ---"
+ exit 1
+fi
+exit 0
diff --git a/tools/launch.sh b/tools/launch.sh
deleted file mode 100755
index 7691baf..0000000
--- a/tools/launch.sh
+++ /dev/null
@@ -1,148 +0,0 @@
-#!/bin/bash
-# launch.sh — Generate certificates and launch N RegisterServers + 1 LDS.
-#
-# Usage: tools/launch.sh [N] [extra_cert1.der ...]
-# N Number of RegisterServer instances to launch (default: 1).
-# extra certs Additional certificates to add to the LDS and every
-# RegisterServer trustlist (e.g. external client certs).
-
-set -euo pipefail
-
-SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
-PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
-BUILD_DIR="$PROJECT_DIR/build"
-CERTS_DIR="$PROJECT_DIR/certs"
-GEN_CERT="$SCRIPT_DIR/generate_certificate.sh"
-
-N="${1:-1}"
-
-if ! [[ "$N" =~ ^[1-9][0-9]*$ ]]; then
- echo "Usage: $0 [N] [extra_cert1.der ...]" >&2
- echo " N = number of RegisterServers (default 1)" >&2
- exit 1
-fi
-
-shift || true
-EXTRA_TRUST=("$@")
-
-LDS_PORT=4840
-BASE_REGISTER_PORT=4841
-
-# ------------------------------------------------------------------
-# Certificate generation (only creates missing ones)
-# ------------------------------------------------------------------
-
-generate_if_missing() {
- local name="$1"
- local uri="${2:-}"
- if [ ! -f "$CERTS_DIR/${name}_cert.der" ] ||
- [ ! -f "$CERTS_DIR/${name}_key.der" ]; then
- "$GEN_CERT" "$CERTS_DIR" "$name" ${uri:+"$uri"}
- fi
-}
-
-generate_if_missing "ServerLDS"
-generate_if_missing "ClientFindServers"
-
-for i in $(seq 1 "$N"); do
- generate_if_missing "ServerRegister${i}"
- # The client cert must carry the server's ApplicationUri so the LDS
- # can verify the certificate against the ApplicationDescription.
- generate_if_missing "ServerRegisterClient${i}" "urn:bobink.ServerRegister${i}"
-done
-
-# ------------------------------------------------------------------
-# Cleanup on exit
-# ------------------------------------------------------------------
-
-pids=()
-
-cleanup() {
- echo ""
- echo "Stopping all servers..."
- for pid in "${pids[@]}"; do
- kill "$pid" 2>/dev/null || true
- done
- wait 2>/dev/null
- echo "All servers stopped."
-}
-
-trap cleanup EXIT INT TERM
-
-# ------------------------------------------------------------------
-# Launch LDS
-# ------------------------------------------------------------------
-
-# LDS trustlist: every RegisterServer client cert + the FindServers client cert.
-lds_trustlist=()
-for i in $(seq 1 "$N"); do
- lds_trustlist+=("$CERTS_DIR/ServerRegisterClient${i}_cert.der")
-done
-lds_trustlist+=("$CERTS_DIR/ClientFindServers_cert.der")
-lds_trustlist+=(${EXTRA_TRUST[@]+"${EXTRA_TRUST[@]}"})
-
-echo "Starting LDS on port $LDS_PORT..."
-"$BUILD_DIR/ServerLDS" \
- "$LDS_PORT" \
- "urn:bobink.ServerLDS" \
- "$CERTS_DIR/ServerLDS_cert.der" \
- "$CERTS_DIR/ServerLDS_key.der" \
- 60 \
- "${lds_trustlist[@]}" &
-pids+=($!)
-sleep 1
-
-# ------------------------------------------------------------------
-# Launch RegisterServers
-# ------------------------------------------------------------------
-
-for i in $(seq 1 "$N"); do
- port=$((BASE_REGISTER_PORT + i - 1))
-
- echo "Starting ServerRegister${i} on port $port..."
- "$BUILD_DIR/ServerRegister" \
- "$port" \
- "urn:bobink.ServerRegister${i}" \
- "$CERTS_DIR/ServerRegister${i}_cert.der" \
- "$CERTS_DIR/ServerRegister${i}_key.der" \
- "$CERTS_DIR/ServerRegisterClient${i}_cert.der" \
- "$CERTS_DIR/ServerRegisterClient${i}_key.der" \
- "opc.tcp://localhost:$LDS_PORT" \
- 10 \
- SignAndEncrypt Aes128_Sha256_RsaOaep \
- "$CERTS_DIR/ServerLDS_cert.der" \
- "$CERTS_DIR/ClientFindServers_cert.der" \
- ${EXTRA_TRUST[@]+"${EXTRA_TRUST[@]}"} &
- pids+=($!)
- sleep 0.5
-done
-
-# ------------------------------------------------------------------
-# Summary
-# ------------------------------------------------------------------
-
-echo ""
-echo "=== All servers running ==="
-echo " LDS: port $LDS_PORT"
-for i in $(seq 1 "$N"); do
- port=$((BASE_REGISTER_PORT + i - 1))
- echo " ServerRegister${i}: port $port"
-done
-
-# Build the client command hint with the correct trustlist.
-client_trust="certs/ServerLDS_cert.der"
-for i in $(seq 1 "$N"); do
- client_trust="$client_trust certs/ServerRegister${i}_cert.der"
-done
-
-echo ""
-echo "Run the client with:"
-echo " build/ClientFindServers \"opc.tcp://localhost:$LDS_PORT\" \\"
-echo " \"urn:bobink.ClientFindServers\" \\"
-echo " certs/ClientFindServers_cert.der certs/ClientFindServers_key.der \\"
-echo " SignAndEncrypt Aes128_Sha256_RsaOaep \\"
-echo " $client_trust"
-echo ""
-echo "Press Ctrl+C to stop all servers."
-
-wait