/** * @file client_find_servers.c * @brief OPC UA client that queries a Local Discovery Server for registered * servers. * * This program connects to an LDS and calls the FindServers service to * retrieve all registered servers. It then queries each server's endpoints * using the GetEndpoints service and displays the results in a human-readable * format. */ #include "common.h" #include #include #include #include /* ======================================================================== * Discovery Service Calls * ======================================================================== */ /** * Calls the FindServers service on the LDS and prints all discovered servers. * * @param client The OPC UA client instance. * @param discoveryServerEndpoint The LDS endpoint URL. * @param applicationDescriptionArraySize Output: number of servers found. * @param applicationDescriptionArray Output: array of server descriptions. * @return UA_STATUSCODE_GOOD on success, error code otherwise. */ static UA_StatusCode findServers (UA_Client *client, const char *discoveryServerEndpoint, size_t *applicationDescriptionArraySize, UA_ApplicationDescription **applicationDescriptionArray) { UA_StatusCode retval = UA_Client_findServers ( client, discoveryServerEndpoint, 0, NULL, 0, NULL, applicationDescriptionArraySize, applicationDescriptionArray); if (retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not call FindServers service. " "Is the discovery server started? StatusCode %s", UA_StatusCode_name (retval)); return retval; } for (size_t i = 0; i < *applicationDescriptionArraySize; i++) printApplicationDescription (&(*applicationDescriptionArray)[i], i); return UA_STATUSCODE_GOOD; } /** * Queries endpoints for each discovered server using the GetEndpoints service. * * For each server in the applicationDescriptionArray, this function extracts * the first discovery URL and calls GetEndpoints to retrieve all available * endpoints. Results are logged via UA_LOG_INFO. * * @param client The OPC UA client instance. * @param applicationDescriptionArray Array of server descriptions from * FindServers. * @param applicationDescriptionArraySize Number of servers in the array. */ static void getServersEndpoints (UA_Client *client, UA_ApplicationDescription *applicationDescriptionArray, size_t applicationDescriptionArraySize) { for (size_t i = 0; i < applicationDescriptionArraySize; i++) { UA_ApplicationDescription *description = &applicationDescriptionArray[i]; if (description->discoveryUrlsSize == 0) { UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "[GetEndpoints] Server %.*s did not provide any " "discovery urls. Skipping.", (int)description->applicationUri.length, description->applicationUri.data); continue; } /* UA_String is not null-terminated; build a C string for the API. */ char *discoveryUrl = (char *)UA_malloc ( sizeof (char) * description->discoveryUrls[0].length + 1); memcpy (discoveryUrl, description->discoveryUrls[0].data, description->discoveryUrls[0].length); discoveryUrl[description->discoveryUrls[0].length] = '\0'; UA_EndpointDescription *endpointArray = NULL; size_t endpointArraySize = 0; UA_StatusCode retval = UA_Client_getEndpoints ( client, discoveryUrl, &endpointArraySize, &endpointArray); UA_free (discoveryUrl); if (retval != UA_STATUSCODE_GOOD) { UA_Client_disconnect (client); break; } UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, "Endpoints for Server[%lu]: %.*s", (unsigned long)i, (int)description->applicationUri.length, description->applicationUri.data); for (size_t j = 0; j < endpointArraySize; j++) printEndpoint (&endpointArray[j], j); UA_Array_delete (endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); } } /* ======================================================================== * Node Reading * ======================================================================== */ /** * Connects to each non-discovery server and reads the current time node. * * For each server that is not a DiscoveryServer, this function establishes a * secure session, reads the Server_ServerStatus_CurrentTime variable, prints * the result, and disconnects. * * @param client The OPC UA client instance. * @param applicationDescriptionArray Array of server descriptions from * FindServers. * @param applicationDescriptionArraySize Number of servers in the array. */ static void readServerTime (UA_Client *client, UA_ApplicationDescription *applicationDescriptionArray, size_t applicationDescriptionArraySize) { for (size_t i = 0; i < applicationDescriptionArraySize; i++) { UA_ApplicationDescription *desc = &applicationDescriptionArray[i]; if (desc->applicationType == UA_APPLICATIONTYPE_DISCOVERYSERVER) continue; if (desc->discoveryUrlsSize == 0) { UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "[ReadTime] Server %.*s has no discovery URLs. " "Skipping.", (int)desc->applicationUri.length, desc->applicationUri.data); continue; } /* UA_String is not null-terminated; build a C string for the API. */ char *url = (char *)UA_malloc (desc->discoveryUrls[0].length + 1); memcpy (url, desc->discoveryUrls[0].data, desc->discoveryUrls[0].length); url[desc->discoveryUrls[0].length] = '\0'; UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Connecting to %s to read current time...", url); UA_StatusCode retval = UA_Client_connectUsername (client, url, "user", "password"); UA_free (url); if (retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Could not connect: %s", UA_StatusCode_name (retval)); continue; } UA_Variant value; UA_Variant_init (&value); const UA_NodeId nodeId = UA_NS0ID (SERVER_SERVERSTATUS_CURRENTTIME); retval = UA_Client_readValueAttribute (client, nodeId, &value); 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)); } UA_Variant_clear (&value); UA_Client_disconnect (client); } } /* ======================================================================== * Main * ======================================================================== */ int main (int argc, char **argv) { if (argc < 7) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Usage: %s \n" " \n" " \n" " \n" " [, ...]\n" "\n" "Security modes : None, Sign, SignAndEncrypt\n" "Security policies: None, Basic256Sha256, " "Aes256_Sha256_RsaPss,\n" " Aes128_Sha256_RsaOaep, ECC_nistP256", 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]; UA_MessageSecurityMode securityMode = parseSecurityMode (argv[5]); if (securityMode == UA_MESSAGESECURITYMODE_INVALID) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown security mode: %s", argv[5]); return EXIT_FAILURE; } const char *securityPolicyUri = resolveSecurityPolicyUri (argv[6]); if (!securityPolicyUri) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown security policy: %s", argv[6]); return EXIT_FAILURE; } char **trustPaths = argv + 7; size_t trustSize = (argc > 7) ? (size_t)argc - 7 : 0; UA_Client *client = UA_Client_new (); UA_StatusCode retval = createSecureClientConfig ( UA_Client_getConfig (client), applicationUri, certPath, keyPath, trustPaths, trustSize, securityMode, securityPolicyUri); if (retval != UA_STATUSCODE_GOOD) { UA_Client_delete (client); return EXIT_FAILURE; } UA_ApplicationDescription *applicationDescriptionArray = NULL; size_t applicationDescriptionArraySize = 0; retval = findServers (client, discoveryServerEndpoint, &applicationDescriptionArraySize, &applicationDescriptionArray); if (retval != UA_STATUSCODE_GOOD) { UA_Client_delete (client); return EXIT_FAILURE; } getServersEndpoints (client, applicationDescriptionArray, applicationDescriptionArraySize); readServerTime (client, applicationDescriptionArray, applicationDescriptionArraySize); UA_Client_delete (client); UA_Array_delete (applicationDescriptionArray, applicationDescriptionArraySize, &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]); return EXIT_SUCCESS; }