aboutsummaryrefslogtreecommitdiffstats
path: root/src/common.c
diff options
context:
space:
mode:
authorThomas Vanbesien <tvanbesi@proton.me>2026-02-17 02:27:51 +0100
committerThomas Vanbesien <tvanbesi@proton.me>2026-02-17 02:27:51 +0100
commitc35eb35bb63a97b7c46e879819757a9cb48165b5 (patch)
treeabc7f07740fae388f4ff6776585b56f56ec558c9 /src/common.c
downloadBobinkCOpcUa-c35eb35bb63a97b7c46e879819757a9cb48165b5.tar.gz
BobinkCOpcUa-c35eb35bb63a97b7c46e879819757a9cb48165b5.zip
Initial commit: OPC UA discovery project
CMake-based C project using open62541 for OPC UA discovery. Includes Local Discovery Server, register server, and find servers client with OpenSSL encryption support.
Diffstat (limited to 'src/common.c')
-rw-r--r--src/common.c280
1 files changed, 280 insertions, 0 deletions
diff --git a/src/common.c b/src/common.c
new file mode 100644
index 0000000..d102868
--- /dev/null
+++ b/src/common.c
@@ -0,0 +1,280 @@
+/**
+ * @file common.c
+ * @brief Implements shared helpers declared in common.h.
+ */
+
+#include "common.h"
+
+#include <open62541/client_config_default.h>
+#include <open62541/plugin/log_stdout.h>
+#include <open62541/server_config_default.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* ========================================================================
+ * File Loading
+ * ======================================================================== */
+
+UA_ByteString
+loadFile (const char *const path)
+{
+ UA_ByteString fileContents = UA_STRING_NULL;
+
+ FILE *fp = fopen (path, "rb");
+ if (!fp)
+ {
+ /* fopen sets errno on failure. Callers like createSecureServer use
+ loadFile for optional trustlist entries where a missing file is not
+ an error. Clear errno so open62541's logging does not pick up
+ a stale value and emit misleading error messages. */
+ errno = 0;
+ return fileContents;
+ }
+
+ fseek (fp, 0, SEEK_END);
+ fileContents.length = (size_t)ftell (fp);
+ fileContents.data
+ = (UA_Byte *)UA_malloc (fileContents.length * sizeof (UA_Byte));
+ if (fileContents.data)
+ {
+ fseek (fp, 0, SEEK_SET);
+ size_t read = fread (fileContents.data, sizeof (UA_Byte),
+ fileContents.length, fp);
+ if (read != fileContents.length)
+ UA_ByteString_clear (&fileContents);
+ }
+ else
+ {
+ fileContents.length = 0;
+ }
+ fclose (fp);
+
+ return fileContents;
+}
+
+/* ========================================================================
+ * Security Helpers
+ * ======================================================================== */
+
+UA_MessageSecurityMode
+parseSecurityMode (const char *name)
+{
+ if (strcmp (name, "None") == 0)
+ return UA_MESSAGESECURITYMODE_NONE;
+ if (strcmp (name, "Sign") == 0)
+ return UA_MESSAGESECURITYMODE_SIGN;
+ if (strcmp (name, "SignAndEncrypt") == 0)
+ return UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
+ return UA_MESSAGESECURITYMODE_INVALID;
+}
+
+const char *
+resolveSecurityPolicyUri (const char *shortName)
+{
+ static const struct
+ {
+ const char *name;
+ const char *uri;
+ } policies[] = {
+ { "None", "http://opcfoundation.org/UA/SecurityPolicy#None" },
+ { "Basic256Sha256",
+ "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256" },
+ { "Aes256_Sha256_RsaPss",
+ "http://opcfoundation.org/UA/SecurityPolicy#Aes256_Sha256_RsaPss" },
+ { "Aes128_Sha256_RsaOaep",
+ "http://opcfoundation.org/UA/SecurityPolicy#Aes128_Sha256_RsaOaep" },
+ { "ECC_nistP256",
+ "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256" },
+ };
+ for (size_t i = 0; i < sizeof (policies) / sizeof (policies[0]); i++)
+ {
+ if (strcmp (shortName, policies[i].name) == 0)
+ return policies[i].uri;
+ }
+ return NULL;
+}
+
+/* ========================================================================
+ * Output Formatting
+ * ======================================================================== */
+
+void
+printApplicationDescription (const UA_ApplicationDescription *description,
+ size_t index)
+{
+ const char *type = "Unknown";
+ switch (description->applicationType)
+ {
+ case UA_APPLICATIONTYPE_SERVER:
+ type = "Server";
+ break;
+ case UA_APPLICATIONTYPE_CLIENT:
+ type = "Client";
+ break;
+ case UA_APPLICATIONTYPE_CLIENTANDSERVER:
+ type = "Client and Server";
+ break;
+ case UA_APPLICATIONTYPE_DISCOVERYSERVER:
+ type = "Discovery Server";
+ break;
+ default:
+ break;
+ }
+
+ UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, "Server[%lu]: %.*s",
+ (unsigned long)index, (int)description->applicationUri.length,
+ description->applicationUri.data);
+ UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, " Name: %.*s",
+ (int)description->applicationName.text.length,
+ description->applicationName.text.data);
+ UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION,
+ " Application URI: %.*s",
+ (int)description->applicationUri.length,
+ description->applicationUri.data);
+ UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION,
+ " Product URI: %.*s", (int)description->productUri.length,
+ description->productUri.data);
+ UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, " Type: %s", type);
+ for (size_t j = 0; j < description->discoveryUrlsSize; j++)
+ {
+ UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION,
+ " Discovery URL[%lu]: %.*s", (unsigned long)j,
+ (int)description->discoveryUrls[j].length,
+ description->discoveryUrls[j].data);
+ }
+}
+
+void
+printEndpoint (const UA_EndpointDescription *endpoint, size_t index)
+{
+ const char *mode = "Unknown";
+ switch (endpoint->securityMode)
+ {
+ case UA_MESSAGESECURITYMODE_NONE:
+ mode = "None";
+ break;
+ case UA_MESSAGESECURITYMODE_SIGN:
+ mode = "Sign";
+ break;
+ case UA_MESSAGESECURITYMODE_SIGNANDENCRYPT:
+ mode = "SignAndEncrypt";
+ break;
+ default:
+ break;
+ }
+
+ /* Extract policy name after the '#' */
+ const char *policy = (const char *)endpoint->securityPolicyUri.data;
+ size_t policyLen = endpoint->securityPolicyUri.length;
+ for (size_t k = 0; k < endpoint->securityPolicyUri.length; k++)
+ {
+ if (endpoint->securityPolicyUri.data[k] == '#')
+ {
+ policy = (const char *)&endpoint->securityPolicyUri.data[k + 1];
+ policyLen = endpoint->securityPolicyUri.length - k - 1;
+ break;
+ }
+ }
+
+ UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION,
+ " [%4lu] %.*s | Level: %2d | %-14s | %.*s",
+ (unsigned long)index, (int)endpoint->endpointUrl.length,
+ endpoint->endpointUrl.data, endpoint->securityLevel, mode,
+ (int)policyLen, policy);
+}
+
+/* ========================================================================
+ * Factory Functions
+ * ======================================================================== */
+
+UA_Server *
+createSecureServer (UA_UInt16 port, const char *applicationUri,
+ const char *certPath, const char *keyPath,
+ char **trustPaths, size_t trustSize, UA_StatusCode *retval)
+{
+ UA_ByteString certificate = loadFile (certPath);
+ UA_ByteString privateKey = loadFile (keyPath);
+
+ /* +1: UA_STACKARRAY requires a strictly positive size for VLA. */
+ UA_STACKARRAY (UA_ByteString, trustList, trustSize + 1);
+ 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);
+
+ UA_ByteString_clear (&certificate);
+ UA_ByteString_clear (&privateKey);
+ for (size_t i = 0; i < trustSize; i++)
+ UA_ByteString_clear (&trustList[i]);
+
+ if (*retval != UA_STATUSCODE_GOOD)
+ {
+ UA_Server_delete (server);
+ return NULL;
+ }
+
+ UA_String_clear (&config->applicationDescription.applicationUri);
+ config->applicationDescription.applicationUri
+ = UA_String_fromChars (applicationUri);
+
+ return server;
+}
+
+UA_StatusCode
+createSecureClientConfig (UA_ClientConfig *cc, const char *applicationUri,
+ const char *certPath, const char *keyPath,
+ char **trustPaths, size_t trustSize,
+ UA_MessageSecurityMode securityMode,
+ const char *securityPolicyUri)
+{
+ UA_ByteString certificate = loadFile (certPath);
+ UA_ByteString privateKey = loadFile (keyPath);
+
+ /* +1: UA_STACKARRAY requires a strictly positive size for VLA. */
+ UA_STACKARRAY (UA_ByteString, trustList, trustSize + 1);
+ 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);
+
+ UA_ByteString_clear (&certificate);
+ UA_ByteString_clear (&privateKey);
+ for (size_t i = 0; i < trustSize; i++)
+ UA_ByteString_clear (&trustList[i]);
+
+ if (retval != UA_STATUSCODE_GOOD)
+ {
+ UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION,
+ "Failed to set client encryption. StatusCode %s",
+ UA_StatusCode_name (retval));
+ return retval;
+ }
+
+ UA_String_clear (&cc->clientDescription.applicationUri);
+ cc->clientDescription.applicationUri = UA_String_fromChars (applicationUri);
+
+ cc->securityMode = securityMode;
+ cc->securityPolicyUri = UA_String_fromChars (securityPolicyUri);
+
+ return retval;
+}