/** * @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 * * Encryption is optional: when certificate, privateKey, and trustStore are * provided, the client uses the configured security policy; otherwise it * connects without encryption. */ #include "common.h" #include "config.h" #include #include #include #include #include /* ======================================================================== * Operation Dispatch * ======================================================================== */ typedef enum { OP_FIND_SERVERS, OP_GET_ENDPOINTS, OP_READ_TIME, OP_INVALID } Operation; static Operation parseOperation (const char *name) { if (strcmp (name, "find-servers") == 0) return OP_FIND_SERVERS; if (strcmp (name, "get-endpoints") == 0) return OP_GET_ENDPOINTS; if (strcmp (name, "read-time") == 0) return OP_READ_TIME; return OP_INVALID; } /* ======================================================================== * Operations * ======================================================================== */ /** * Calls the FindServers service and prints all discovered servers. * * @return EXIT_SUCCESS on success, EXIT_FAILURE otherwise. */ static int opFindServers (UA_Client *client, const char *url) { size_t arraySize = 0; UA_ApplicationDescription *array = NULL; UA_StatusCode retval = UA_Client_findServers (client, url, 0, NULL, 0, NULL, &arraySize, &array); if (retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "FindServers failed: %s", UA_StatusCode_name (retval)); return EXIT_FAILURE; } for (size_t i = 0; i < arraySize; i++) printApplicationDescription (&array[i], i); UA_Array_delete (array, arraySize, &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]); return EXIT_SUCCESS; } /** * Calls the GetEndpoints service and prints all endpoints. * * @return EXIT_SUCCESS on success, EXIT_FAILURE otherwise. */ static int opGetEndpoints (UA_Client *client, const char *url) { size_t arraySize = 0; UA_EndpointDescription *array = NULL; UA_StatusCode retval = UA_Client_getEndpoints (client, url, &arraySize, &array); if (retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "GetEndpoints failed: %s", UA_StatusCode_name (retval)); return EXIT_FAILURE; } for (size_t i = 0; i < arraySize; i++) printEndpoint (&array[i], i); UA_Array_delete (array, arraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); return EXIT_SUCCESS; } /** * Connects to a server and reads the current time node. * * Authentication (anonymous, username/password, or X509 certificate) is * configured in the client config before this function is called. * * @return EXIT_SUCCESS on success, EXIT_FAILURE otherwise. */ static int opReadTime (UA_Client *client, const char *url) { UA_StatusCode retval = UA_Client_connect (client, url); if (retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Could not connect: %s", UA_StatusCode_name (retval)); return EXIT_FAILURE; } UA_Variant value; UA_Variant_init (&value); const UA_NodeId nodeId = UA_NS0ID (SERVER_SERVERSTATUS_CURRENTTIME); retval = UA_Client_readValueAttribute (client, nodeId, &value); int rc = EXIT_SUCCESS; if (retval == UA_STATUSCODE_GOOD && UA_Variant_hasScalarType (&value, &UA_TYPES[UA_TYPES_DATETIME])) { UA_DateTime raw_date = *(UA_DateTime *)value.data; UA_DateTimeStruct dts = UA_DateTime_toStruct (raw_date); UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, "date is: %u-%u-%u %u:%u:%u.%03u", dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec); } else { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Could not read current time: %s", UA_StatusCode_name (retval)); rc = EXIT_FAILURE; } UA_Variant_clear (&value); UA_Client_disconnect (client); return rc; } /* ======================================================================== * Main * ======================================================================== */ int main (int argc, char **argv) { if (argc < 4 || argc > 5) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Usage: %s " "[log-level]\n" "Operations: find-servers, get-endpoints, read-time", 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"); if (!applicationUri) { configFree (&cfg); return EXIT_FAILURE; } SecurityConfig sec; if (parseSecurityConfig (&cfg, "Client", true, &sec) != 0) { configFree (&cfg); return EXIT_FAILURE; } /* ---- Auth config (read-time only) ---- */ AuthConfig auth = { .mode = AUTH_ANONYMOUS }; if (op == OP_READ_TIME && parseAuthConfig (&cfg, "Client", &auth) != 0) { freeTrustStore (sec.trustPaths, sec.trustSize); configFree (&cfg); return EXIT_FAILURE; } /* ---- Create client ---- */ UA_Client *client = UA_Client_new (); UA_StatusCode retval; if (sec.certPath) retval = createSecureClientConfig (UA_Client_getConfig (client), applicationUri, &sec, &auth); else retval = createUnsecureClientConfig (UA_Client_getConfig (client), applicationUri, &auth); if (retval != UA_STATUSCODE_GOOD) { UA_Client_delete (client); freeTrustStore (sec.trustPaths, sec.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); break; default: rc = EXIT_FAILURE; break; } /* ---- Cleanup ---- */ UA_Client_delete (client); freeTrustStore (sec.trustPaths, sec.trustSize); configFree (&cfg); return rc; }