/** * @file common.c * @brief Implements shared helpers declared in common.h. */ #include "common.h" #include #include #include #include #include #include #include /* ======================================================================== * 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; }