#include <open62541/client_highlevel.h>
#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) {
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;
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;
}
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;
if(UA_findDataTypeWithCustom(&rd->nodeId.nodeId,
client->config.customDataTypes))
continue;
if(ZIP_FIND(NodeIdTree, tree, &rd->nodeId.nodeId))
continue;
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)++;
res = browseDataTypesRecursive(client, tree, treeSize, rd->nodeId.nodeId);
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;
}
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;
}
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);
UA_StatusCode res = UA_STATUSCODE_GOOD;
if(dataTypesNodesSize == 0) {
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;
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 {
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;
}
}
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;
}
res = getRemoteDataTypes(client, &req, customTypes);
UA_ReadRequest_clear(&req);
return res;
}