/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2025 (c) Fraunhofer IOSB (Author: Julius Pfrommer) */ #include #include "ua_client_internal.h" #include "ziptree.h" typedef struct NodeIdTreeEntry { ZIP_ENTRY(NodeIdTreeEntry) zipfields; UA_NodeId nodeId; UA_String typeName; } NodeIdTreeEntry; static enum ZIP_CMP cmpNodeIdTreeEntry(const void *a, const void *b) { const UA_NodeId *aa = (const UA_NodeId*)a; const UA_NodeId *bb = (const UA_NodeId*)b; return (enum ZIP_CMP)UA_NodeId_order(aa, bb); } typedef ZIP_HEAD(NodeIdTree, NodeIdTreeEntry) NodeIdTree; ZIP_FUNCTIONS(NodeIdTree, NodeIdTreeEntry, zipfields, UA_NodeId, nodeId, cmpNodeIdTreeEntry) static void * deleteNodeIdEntry(void *context, NodeIdTreeEntry *elm) { UA_NodeId_clear(&elm->nodeId); UA_String_clear(&elm->typeName); UA_free(elm); return NULL; } static UA_StatusCode browseDataTypesRecursive(UA_Client *client, NodeIdTree *tree, size_t *treeSize, UA_NodeId typeNode) { /* Set up the browse request */ UA_BrowseDescription bd; UA_BrowseDescription_init(&bd); bd.nodeId = typeNode; bd.browseDirection = UA_BROWSEDIRECTION_FORWARD; bd.referenceTypeId = UA_NS0ID(HASSUBTYPE); bd.nodeClassMask = UA_NODECLASS_DATATYPE; /* Browse */ UA_BrowseResult br = UA_Client_browse(client, NULL, 0, &bd); UA_StatusCode res = br.statusCode; if(res != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, "Could not browse the DataTypeNodes with status code %s", UA_StatusCode_name(res)); UA_BrowseResult_clear(&br); return res; } /* Check which types are unknown, add them and recurse */ for(size_t i = 0; i < br.referencesSize && res == UA_STATUSCODE_GOOD; i++) { UA_ReferenceDescription *rd = &br.references[i]; if(!UA_ExpandedNodeId_isLocal(&rd->nodeId)) continue; /* Skip known types */ if(UA_findDataTypeWithCustom(&rd->nodeId.nodeId, client->config.customDataTypes)) continue; if(ZIP_FIND(NodeIdTree, tree, &rd->nodeId.nodeId)) continue; /* Create an entry */ NodeIdTreeEntry *entry = (NodeIdTreeEntry*)UA_malloc(sizeof(NodeIdTreeEntry)); if(!entry) { res = UA_STATUSCODE_BADOUTOFMEMORY; break; } *entry = (NodeIdTreeEntry){{NULL, NULL}, rd->nodeId.nodeId, rd->displayName.text}; ZIP_INSERT(NodeIdTree, tree, entry); (*treeSize)++; /* Recurse */ res = browseDataTypesRecursive(client, tree, treeSize, rd->nodeId.nodeId); /* Don't double-free */ UA_NodeId_init(&rd->nodeId.nodeId); UA_String_init(&rd->displayName.text); } UA_BrowseResult_clear(&br); return res; } static UA_StatusCode getRemoteDataTypes(UA_Client *client, UA_ReadRequest *req, UA_DataTypeArray **outCustomTypes) { UA_ReadResponse rr = UA_Client_Service_read(client, *req); UA_StatusCode res = rr.responseHeader.serviceResult; if(res == UA_STATUSCODE_GOOD && rr.resultsSize != req->nodesToReadSize) res = UA_STATUSCODE_BADINTERNALERROR; if(res != UA_STATUSCODE_GOOD) { UA_ReadResponse_clear(&rr); return res; } /* Allocate the arrays */ size_t typesSize = req->nodesToReadSize / 2; UA_DataTypeArray *dta = (UA_DataTypeArray*) UA_calloc(1, sizeof(UA_DataTypeArray)); if(!dta) { UA_ReadResponse_clear(&rr); return UA_STATUSCODE_BADOUTOFMEMORY; } dta->cleanup = true; dta->types = (UA_DataType*)UA_calloc(typesSize, sizeof(UA_DataType)); if(!dta->types) { UA_ReadResponse_clear(&rr); UA_cleanupDataTypeWithCustom(dta); return UA_STATUSCODE_BADOUTOFMEMORY; } /* Create the new types */ for(size_t i = 0; i < typesSize; i++) { UA_DataValue *typeDef = &rr.results[i]; if(typeDef->hasStatus && typeDef->status != UA_STATUSCODE_GOOD) continue; if(!typeDef->hasValue || !UA_Variant_hasScalarType(&typeDef->value, &UA_TYPES[UA_TYPES_STRUCTUREDEFINITION])) continue; UA_StructureDefinition *sd = (UA_StructureDefinition*)typeDef->value.data; UA_DataValue *typeName = &rr.results[i+typesSize]; if(typeName->hasStatus && typeName->status != UA_STATUSCODE_GOOD) continue; if(!typeName->hasValue || !UA_Variant_hasScalarType(&typeName->value, &UA_TYPES[UA_TYPES_QUALIFIEDNAME])) continue; UA_StructureDescription descr; descr.structureDefinition = *sd; descr.dataTypeId = req->nodesToRead[i].nodeId; descr.name = *(UA_QualifiedName*)typeName->value.data; UA_ExtensionObject eo; UA_ExtensionObject_setValue(&eo, &descr, &UA_TYPES[UA_TYPES_STRUCTUREDESCRIPTION]); UA_DataTypeArray lookupTypes = *dta; lookupTypes.next = client->config.customDataTypes; res = UA_DataType_fromDescription(&dta->types[dta->typesSize], &eo, &lookupTypes); if(res != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, "Could not add the DataType %Q (%N) with status code %s", descr.name, req->nodesToRead[i].nodeId, UA_StatusCode_name(res)); continue; } UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, "Added DataType %Q (%N) to the DataTypeArray", descr.name, req->nodesToRead[i].nodeId); dta->typesSize++; } *outCustomTypes = dta; UA_ReadResponse_clear(&rr); UA_ReadRequest_clear(req); if(dta->typesSize == 0) { UA_free(dta->types); dta->types = NULL; } return UA_STATUSCODE_GOOD; } static void * setNodesToRead(void *context, NodeIdTreeEntry *elm) { UA_ReadValueId **rvi = (UA_ReadValueId**)context; (*rvi)->nodeId = elm->nodeId; (*rvi)->attributeId = UA_ATTRIBUTEID_DATATYPEDEFINITION; (*rvi)++; UA_free(elm); return NULL; } UA_StatusCode UA_EXPORT UA_THREADSAFE UA_Client_getRemoteDataTypes(UA_Client *client, size_t dataTypesNodesSize, const UA_NodeId *dataTypesNodes, UA_DataTypeArray **customTypes) { if(!customTypes) return UA_STATUSCODE_BADINVALIDARGUMENT; UA_ReadRequest req; UA_ReadRequest_init(&req); /* req.nodesToRead has twice the normal length. Because we read the * DataTypeDescription and the DisplayName in the same request. */ UA_StatusCode res = UA_STATUSCODE_GOOD; if(dataTypesNodesSize == 0) { /* Browse to find unknown datatypes in the information model */ /* Get all unknown DataType NodeIds */ NodeIdTree tree; ZIP_INIT(&tree); size_t treeSize = 0; res = browseDataTypesRecursive(client, &tree, &treeSize, UA_NS0ID(STRUCTURE)); if(res != UA_STATUSCODE_GOOD) { ZIP_ITER(NodeIdTree, &tree, deleteNodeIdEntry, NULL); return res; } if(treeSize == 0) return UA_STATUSCODE_GOOD; /* Read the descriptions of the unknown types */ UA_ReadValueId *nodesToRead = (UA_ReadValueId*) UA_calloc(treeSize * 2, sizeof(UA_ReadValueId)); if(!nodesToRead) { ZIP_ITER(NodeIdTree, &tree, deleteNodeIdEntry, NULL); return UA_STATUSCODE_BADOUTOFMEMORY; } UA_ReadValueId *tmp = nodesToRead; ZIP_ITER(NodeIdTree, &tree, setNodesToRead, &tmp); req.nodesToRead = nodesToRead; req.nodesToReadSize = treeSize; } else { /* Prepare the ReadRequest for the provided NodeIds */ req.nodesToRead = (UA_ReadValueId*) UA_Array_new(dataTypesNodesSize * 2, &UA_TYPES[UA_TYPES_READVALUEID]); if(!req.nodesToRead) return UA_STATUSCODE_BADOUTOFMEMORY; req.nodesToReadSize = dataTypesNodesSize; for(size_t i = 0; i < dataTypesNodesSize; i++) { req.nodesToRead[i].attributeId = UA_ATTRIBUTEID_DATATYPEDEFINITION; res |= UA_NodeId_copy(&dataTypesNodes[i], &req.nodesToRead[i].nodeId); } if(res != UA_STATUSCODE_GOOD) { UA_ReadRequest_clear(&req); return res; } } /* Prepare reading the BrowseName attributes additionally */ for(size_t i = 0; i < req.nodesToReadSize; i++) { res |= UA_NodeId_copy(&req.nodesToRead[i].nodeId, &req.nodesToRead[i + req.nodesToReadSize].nodeId); req.nodesToRead[i + req.nodesToReadSize].attributeId = UA_ATTRIBUTEID_BROWSENAME; } req.nodesToReadSize *= 2; if(res != UA_STATUSCODE_GOOD) { UA_ReadRequest_clear(&req); return res; } /* Read and convert the DataTypeDefinitions */ res = getRemoteDataTypes(client, &req, customTypes); UA_ReadRequest_clear(&req); return res; }