| /* 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 2016-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
| * Copyright 2016 (c) Lorenz Haas |
| * Copyright 2017 (c) frax2222 |
| * Copyright 2017 (c) Florian Palm |
| * Copyright 2017-2018 (c) Stefan Profanter, fortiss GmbH |
| * Copyright 2017 (c) Julian Grothoff |
| */ |
| |
| #include "ua_server_internal.h" |
| |
| #define UA_MAX_TREE_RECURSE 50 /* How deep up/down the tree do we recurse at most? */ |
| |
| /********************************/ |
| /* Information Model Operations */ |
| /********************************/ |
| |
| /* Keeps track of already visited nodes to detect circular references */ |
| struct ref_history { |
| struct ref_history *parent; /* the previous element */ |
| const UA_NodeId *id; /* the id of the node at this depth */ |
| UA_UInt16 depth; |
| }; |
| |
| static UA_Boolean |
| isNodeInTreeNoCircular(void *nsCtx, const UA_NodeId *leafNode, const UA_NodeId *nodeToFind, |
| struct ref_history *visitedRefs, const UA_NodeId *referenceTypeIds, |
| size_t referenceTypeIdsSize) { |
| if(UA_NodeId_equal(nodeToFind, leafNode)) |
| return true; |
| |
| if(visitedRefs->depth >= UA_MAX_TREE_RECURSE) |
| return false; |
| |
| const UA_Node *node = UA_Nodestore_getNode(nsCtx, leafNode); |
| if(!node) |
| return false; |
| |
| for(size_t i = 0; i < node->referencesSize; ++i) { |
| UA_NodeReferenceKind *refs = &node->references[i]; |
| /* Search upwards in the tree */ |
| if(!refs->isInverse) |
| continue; |
| |
| /* Consider only the indicated reference types */ |
| UA_Boolean match = false; |
| for(size_t j = 0; j < referenceTypeIdsSize; ++j) { |
| if(UA_NodeId_equal(&refs->referenceTypeId, &referenceTypeIds[j])) { |
| match = true; |
| break; |
| } |
| } |
| if(!match) |
| continue; |
| |
| /* Match the targets or recurse */ |
| for(size_t j = 0; j < refs->targetIdsSize; ++j) { |
| /* Check if we already have seen the referenced node and skip to |
| * avoid endless recursion. Do this only at every 5th depth to save |
| * effort. Circular dependencies are rare and forbidden for most |
| * reference types. */ |
| if(visitedRefs->depth % 5 == 4) { |
| struct ref_history *last = visitedRefs; |
| UA_Boolean skip = false; |
| while(!skip && last) { |
| if(UA_NodeId_equal(last->id, &refs->targetIds[j].nodeId)) |
| skip = true; |
| last = last->parent; |
| } |
| if(skip) |
| continue; |
| } |
| |
| /* Stack-allocate the visitedRefs structure for the next depth */ |
| struct ref_history nextVisitedRefs = {visitedRefs, &refs->targetIds[j].nodeId, |
| (UA_UInt16)(visitedRefs->depth+1)}; |
| |
| /* Recurse */ |
| UA_Boolean foundRecursive = |
| isNodeInTreeNoCircular(nsCtx, &refs->targetIds[j].nodeId, nodeToFind, &nextVisitedRefs, |
| referenceTypeIds, referenceTypeIdsSize); |
| if(foundRecursive) { |
| UA_Nodestore_releaseNode(nsCtx, node); |
| return true; |
| } |
| } |
| } |
| |
| UA_Nodestore_releaseNode(nsCtx, node); |
| return false; |
| } |
| |
| UA_Boolean |
| isNodeInTree(void *nsCtx, const UA_NodeId *leafNode, const UA_NodeId *nodeToFind, |
| const UA_NodeId *referenceTypeIds, size_t referenceTypeIdsSize) { |
| struct ref_history visitedRefs = {NULL, leafNode, 0}; |
| return isNodeInTreeNoCircular(nsCtx, leafNode, nodeToFind, &visitedRefs, |
| referenceTypeIds, referenceTypeIdsSize); |
| } |
| |
| const UA_Node * |
| getNodeType(UA_Server *server, const UA_Node *node) { |
| /* The reference to the parent is different for variable and variabletype */ |
| UA_NodeId parentRef; |
| UA_Boolean inverse; |
| UA_NodeClass typeNodeClass; |
| switch(node->nodeClass) { |
| case UA_NODECLASS_OBJECT: |
| parentRef = UA_NODEID_NUMERIC(0, UA_NS0ID_HASTYPEDEFINITION); |
| inverse = false; |
| typeNodeClass = UA_NODECLASS_OBJECTTYPE; |
| break; |
| case UA_NODECLASS_VARIABLE: |
| parentRef = UA_NODEID_NUMERIC(0, UA_NS0ID_HASTYPEDEFINITION); |
| inverse = false; |
| typeNodeClass = UA_NODECLASS_VARIABLETYPE; |
| break; |
| case UA_NODECLASS_OBJECTTYPE: |
| case UA_NODECLASS_VARIABLETYPE: |
| case UA_NODECLASS_REFERENCETYPE: |
| case UA_NODECLASS_DATATYPE: |
| parentRef = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE); |
| inverse = true; |
| typeNodeClass = node->nodeClass; |
| break; |
| default: |
| return NULL; |
| } |
| |
| /* Return the first matching candidate */ |
| for(size_t i = 0; i < node->referencesSize; ++i) { |
| if(node->references[i].isInverse != inverse) |
| continue; |
| if(!UA_NodeId_equal(&node->references[i].referenceTypeId, &parentRef)) |
| continue; |
| UA_assert(node->references[i].targetIdsSize > 0); |
| const UA_NodeId *targetId = &node->references[i].targetIds[0].nodeId; |
| const UA_Node *type = UA_Nodestore_getNode(server->nsCtx, targetId); |
| if(!type) |
| continue; |
| if(type->nodeClass == typeNodeClass) |
| return type; |
| UA_Nodestore_releaseNode(server->nsCtx, type); |
| } |
| |
| return NULL; |
| } |
| |
| UA_Boolean |
| UA_Node_hasSubTypeOrInstances(const UA_Node *node) { |
| const UA_NodeId hasSubType = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE); |
| const UA_NodeId hasTypeDefinition = UA_NODEID_NUMERIC(0, UA_NS0ID_HASTYPEDEFINITION); |
| for(size_t i = 0; i < node->referencesSize; ++i) { |
| if(node->references[i].isInverse == false && |
| UA_NodeId_equal(&node->references[i].referenceTypeId, &hasSubType)) |
| return true; |
| if(node->references[i].isInverse == true && |
| UA_NodeId_equal(&node->references[i].referenceTypeId, &hasTypeDefinition)) |
| return true; |
| } |
| return false; |
| } |
| |
| static const UA_NodeId hasInterfaceNodeId = |
| {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASINTERFACE}}; |
| |
| UA_StatusCode |
| getParentTypeAndInterfaceHierarchy(UA_Server *server, const UA_NodeId *typeNode, |
| UA_NodeId **typeHierarchy, size_t *typeHierarchySize) { |
| UA_ExpandedNodeId *subTypes = NULL; |
| size_t subTypesSize = 0; |
| UA_StatusCode retval = browseRecursive(server, 1, typeNode, 1, &subtypeId, |
| UA_BROWSEDIRECTION_INVERSE, false, |
| &subTypesSize, &subTypes); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| UA_assert(subTypesSize < 1000); |
| |
| UA_ExpandedNodeId *interfaces = NULL; |
| size_t interfacesSize = 0; |
| retval = browseRecursive(server, 1, typeNode, 1, &hasInterfaceNodeId, |
| UA_BROWSEDIRECTION_FORWARD, false, |
| &interfacesSize, &interfaces); |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_Array_delete(subTypes, subTypesSize, &UA_TYPES[UA_TYPES_NODEID]); |
| return retval; |
| } |
| |
| UA_assert(interfacesSize < 1000); |
| |
| UA_NodeId *hierarchy = (UA_NodeId*) |
| UA_malloc(sizeof(UA_NodeId) * (1 + subTypesSize + interfacesSize)); |
| if(!hierarchy) { |
| UA_Array_delete(subTypes, subTypesSize, &UA_TYPES[UA_TYPES_EXPANDEDNODEID]); |
| UA_Array_delete(interfaces, interfacesSize, &UA_TYPES[UA_TYPES_EXPANDEDNODEID]); |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| } |
| |
| retval = UA_NodeId_copy(typeNode, hierarchy); |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_free(hierarchy); |
| UA_Array_delete(subTypes, subTypesSize, &UA_TYPES[UA_TYPES_EXPANDEDNODEID]); |
| UA_Array_delete(interfaces, interfacesSize, &UA_TYPES[UA_TYPES_EXPANDEDNODEID]); |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| } |
| |
| for(size_t i = 0; i < subTypesSize; i++) { |
| hierarchy[i+1] = subTypes[i].nodeId; |
| UA_NodeId_init(&subTypes[i].nodeId); |
| } |
| for(size_t i = 0; i < interfacesSize; i++) { |
| hierarchy[i+1+subTypesSize] = interfaces[i].nodeId; |
| UA_NodeId_init(&interfaces[i].nodeId); |
| } |
| |
| *typeHierarchy = hierarchy; |
| *typeHierarchySize = subTypesSize + interfacesSize + 1; |
| |
| UA_assert(*typeHierarchySize < 1000); |
| |
| UA_Array_delete(subTypes, subTypesSize, &UA_TYPES[UA_TYPES_EXPANDEDNODEID]); |
| UA_Array_delete(interfaces, interfacesSize, &UA_TYPES[UA_TYPES_EXPANDEDNODEID]); |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* For mulithreading: make a copy of the node, edit and replace. |
| * For singlethreading: edit the original */ |
| UA_StatusCode |
| UA_Server_editNode(UA_Server *server, UA_Session *session, |
| const UA_NodeId *nodeId, UA_EditNodeCallback callback, |
| void *data) { |
| #ifndef UA_ENABLE_IMMUTABLE_NODES |
| /* Get the node and process it in-situ */ |
| const UA_Node *node = UA_Nodestore_getNode(server->nsCtx, nodeId); |
| if(!node) |
| return UA_STATUSCODE_BADNODEIDUNKNOWN; |
| UA_StatusCode retval = callback(server, session, (UA_Node*)(uintptr_t)node, data); |
| UA_Nodestore_releaseNode(server->nsCtx, node); |
| return retval; |
| #else |
| UA_StatusCode retval; |
| do { |
| /* Get an editable copy of the node */ |
| UA_Node *node; |
| retval = UA_Nodestore_getNodeCopy(server->nsCtx, nodeId, &node); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| /* Run the operation on the copy */ |
| retval = callback(server, session, node, data); |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_Nodestore_deleteNode(server->nsCtx, node); |
| return retval; |
| } |
| |
| /* Replace the node */ |
| retval = UA_Nodestore_replaceNode(server->nsCtx, node); |
| } while(retval != UA_STATUSCODE_GOOD); |
| return retval; |
| #endif |
| } |
| |
| UA_StatusCode |
| UA_Server_processServiceOperations(UA_Server *server, UA_Session *session, |
| UA_ServiceOperation operationCallback, |
| const void *context, const size_t *requestOperations, |
| const UA_DataType *requestOperationsType, |
| size_t *responseOperations, |
| const UA_DataType *responseOperationsType) { |
| size_t ops = *requestOperations; |
| if(ops == 0) |
| return UA_STATUSCODE_BADNOTHINGTODO; |
| |
| /* No padding after size_t */ |
| void **respPos = (void**)((uintptr_t)responseOperations + sizeof(size_t)); |
| *respPos = UA_Array_new(ops, responseOperationsType); |
| if(!(*respPos)) |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| |
| *responseOperations = ops; |
| uintptr_t respOp = (uintptr_t)*respPos; |
| /* No padding after size_t */ |
| uintptr_t reqOp = *(uintptr_t*)((uintptr_t)requestOperations + sizeof(size_t)); |
| for(size_t i = 0; i < ops; i++) { |
| operationCallback(server, session, context, (void*)reqOp, (void*)respOp); |
| reqOp += requestOperationsType->memSize; |
| respOp += responseOperationsType->memSize; |
| } |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* A few global NodeId definitions */ |
| const UA_NodeId subtypeId = {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASSUBTYPE}}; |
| const UA_NodeId hierarchicalReferences = {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HIERARCHICALREFERENCES}}; |
| |
| /*********************************/ |
| /* Default attribute definitions */ |
| /*********************************/ |
| |
| const UA_ObjectAttributes UA_ObjectAttributes_default = { |
| 0, /* specifiedAttributes */ |
| {{0, NULL}, {0, NULL}}, /* displayName */ |
| {{0, NULL}, {0, NULL}}, /* description */ |
| 0, 0, /* writeMask (userWriteMask) */ |
| 0 /* eventNotifier */ |
| }; |
| |
| const UA_VariableAttributes UA_VariableAttributes_default = { |
| 0, /* specifiedAttributes */ |
| {{0, NULL}, {0, NULL}}, /* displayName */ |
| {{0, NULL}, {0, NULL}}, /* description */ |
| 0, 0, /* writeMask (userWriteMask) */ |
| {NULL, UA_VARIANT_DATA, |
| 0, NULL, 0, NULL}, /* value */ |
| {0, UA_NODEIDTYPE_NUMERIC, |
| {UA_NS0ID_BASEDATATYPE}}, /* dataType */ |
| UA_VALUERANK_ANY, /* valueRank */ |
| 0, NULL, /* arrayDimensions */ |
| UA_ACCESSLEVELMASK_READ, 0, /* accessLevel (userAccessLevel) */ |
| 0.0, /* minimumSamplingInterval */ |
| false /* historizing */ |
| }; |
| |
| const UA_MethodAttributes UA_MethodAttributes_default = { |
| 0, /* specifiedAttributes */ |
| {{0, NULL}, {0, NULL}}, /* displayName */ |
| {{0, NULL}, {0, NULL}}, /* description */ |
| 0, 0, /* writeMask (userWriteMask) */ |
| true, true /* executable (userExecutable) */ |
| }; |
| |
| const UA_ObjectTypeAttributes UA_ObjectTypeAttributes_default = { |
| 0, /* specifiedAttributes */ |
| {{0, NULL}, {0, NULL}}, /* displayName */ |
| {{0, NULL}, {0, NULL}}, /* description */ |
| 0, 0, /* writeMask (userWriteMask) */ |
| false /* isAbstract */ |
| }; |
| |
| const UA_VariableTypeAttributes UA_VariableTypeAttributes_default = { |
| 0, /* specifiedAttributes */ |
| {{0, NULL}, {0, NULL}}, /* displayName */ |
| {{0, NULL}, {0, NULL}}, /* description */ |
| 0, 0, /* writeMask (userWriteMask) */ |
| {NULL, UA_VARIANT_DATA, |
| 0, NULL, 0, NULL}, /* value */ |
| {0, UA_NODEIDTYPE_NUMERIC, |
| {UA_NS0ID_BASEDATATYPE}}, /* dataType */ |
| UA_VALUERANK_ANY, /* valueRank */ |
| 0, NULL, /* arrayDimensions */ |
| false /* isAbstract */ |
| }; |
| |
| const UA_ReferenceTypeAttributes UA_ReferenceTypeAttributes_default = { |
| 0, /* specifiedAttributes */ |
| {{0, NULL}, {0, NULL}}, /* displayName */ |
| {{0, NULL}, {0, NULL}}, /* description */ |
| 0, 0, /* writeMask (userWriteMask) */ |
| false, /* isAbstract */ |
| false, /* symmetric */ |
| {{0, NULL}, {0, NULL}} /* inverseName */ |
| }; |
| |
| const UA_DataTypeAttributes UA_DataTypeAttributes_default = { |
| 0, /* specifiedAttributes */ |
| {{0, NULL}, {0, NULL}}, /* displayName */ |
| {{0, NULL}, {0, NULL}}, /* description */ |
| 0, 0, /* writeMask (userWriteMask) */ |
| false /* isAbstract */ |
| }; |
| |
| const UA_ViewAttributes UA_ViewAttributes_default = { |
| 0, /* specifiedAttributes */ |
| {{0, NULL}, {0, NULL}}, /* displayName */ |
| {{0, NULL}, {0, NULL}}, /* description */ |
| 0, 0, /* writeMask (userWriteMask) */ |
| false, /* containsNoLoops */ |
| 0 /* eventNotifier */ |
| }; |
| |