/** * @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 #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. * * @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 " "[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"); 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 && parseAuthConfig (&cfg, "Client", NULL, &username, &password) != 0) { 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; }