blob: 5f5aed90f2245e9e0d1a761b4fb009a70463923c [file] [log] [blame]
/* 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 2014-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright 2014-2017 (c) Florian Palm
* Copyright 2015-2016 (c) Sten GrĂ¼ner
* Copyright 2015-2016 (c) Chris Iatrou
* Copyright 2015-2016 (c) Oleksiy Vasylyev
* Copyright 2017 (c) Julian Grothoff
* Copyright 2016 (c) LEvertz
* Copyright 2016 (c) Lorenz Haas
* Copyright 2017 (c) frax2222
* Copyright 2017-2018 (c) Stefan Profanter, fortiss GmbH
* Copyright 2017 (c) Christian von Arnim
* Copyright 2017 (c) Henrik Norrman
*/
#include "ua_server_internal.h"
#include "ua_services.h"
#define UA_LOG_NODEID_WRAP(NODEID, LOG) { \
UA_String nodeIdStr = UA_STRING_NULL; \
UA_NodeId_toString(NODEID, &nodeIdStr); \
LOG; \
UA_String_deleteMembers(&nodeIdStr); \
}
/*********************/
/* Edit Node Context */
/*********************/
UA_StatusCode
UA_Server_getNodeContext(UA_Server *server, UA_NodeId nodeId,
void **nodeContext) {
const UA_Node *node = UA_Nodestore_getNode(server->nsCtx, &nodeId);
if(!node)
return UA_STATUSCODE_BADNODEIDUNKNOWN;
*nodeContext = node->context;
UA_Nodestore_releaseNode(server->nsCtx, node);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
setDeconstructedNode(UA_Server *server, UA_Session *session,
UA_Node *node, void *context) {
node->constructed = false;
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
setConstructedNodeContext(UA_Server *server, UA_Session *session,
UA_Node *node, void *context) {
node->context = context;
node->constructed = true;
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
editNodeContext(UA_Server *server, UA_Session* session,
UA_Node* node, void *context) {
node->context = context;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_Server_setNodeContext(UA_Server *server, UA_NodeId nodeId,
void *nodeContext) {
return UA_Server_editNode(server, &server->adminSession, &nodeId,
(UA_EditNodeCallback)editNodeContext, nodeContext);
}
/**********************/
/* Consistency Checks */
/**********************/
#define UA_PARENT_REFERENCES_COUNT 2
const UA_NodeId parentReferences[UA_PARENT_REFERENCES_COUNT] = {
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASSUBTYPE}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASCOMPONENT}}
};
/* Check if the requested parent node exists, has the right node class and is
* referenced with an allowed (hierarchical) reference type. For "type" nodes,
* only hasSubType references are allowed. */
static UA_StatusCode
checkParentReference(UA_Server *server, UA_Session *session, UA_NodeClass nodeClass,
const UA_NodeId *parentNodeId, const UA_NodeId *referenceTypeId) {
/* Objects do not need a parent (e.g. mandatory/optional modellingrules) */
/* Also, there are some variables which do not have parents, e.g. EnumStrings, EnumValues */
if((nodeClass == UA_NODECLASS_OBJECT || nodeClass == UA_NODECLASS_VARIABLE) &&
UA_NodeId_isNull(parentNodeId) && UA_NodeId_isNull(referenceTypeId))
return UA_STATUSCODE_GOOD;
/* See if the parent exists */
const UA_Node *parent = UA_Nodestore_getNode(server->nsCtx, parentNodeId);
if(!parent) {
UA_LOG_NODEID_WRAP(parentNodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Parent node %.*s not found",
(int)nodeIdStr.length, nodeIdStr.data));
return UA_STATUSCODE_BADPARENTNODEIDINVALID;
}
UA_NodeClass parentNodeClass = parent->nodeClass;
UA_Nodestore_releaseNode(server->nsCtx, parent);
/* Check the referencetype exists */
const UA_ReferenceTypeNode *referenceType = (const UA_ReferenceTypeNode*)
UA_Nodestore_getNode(server->nsCtx, referenceTypeId);
if(!referenceType) {
UA_LOG_NODEID_WRAP(referenceTypeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Reference type %.*s to the parent not found",
(int)nodeIdStr.length, nodeIdStr.data));
return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
}
/* Check if the referencetype is a reference type node */
if(referenceType->nodeClass != UA_NODECLASS_REFERENCETYPE) {
UA_LOG_NODEID_WRAP(referenceTypeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Reference type %.*s to the parent is not a ReferenceTypeNode",
(int)nodeIdStr.length, nodeIdStr.data));
UA_Nodestore_releaseNode(server->nsCtx, (const UA_Node*)referenceType);
return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
}
UA_Boolean referenceTypeIsAbstract = referenceType->isAbstract;
UA_Nodestore_releaseNode(server->nsCtx, (const UA_Node*)referenceType);
/* Check that the reference type is not abstract */
if(referenceTypeIsAbstract == true) {
UA_LOG_NODEID_WRAP(referenceTypeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Abstract reference type %.*s to the parent not allowed",
(int)nodeIdStr.length, nodeIdStr.data));
return UA_STATUSCODE_BADREFERENCENOTALLOWED;
}
/* Check hassubtype relation for type nodes */
if(nodeClass == UA_NODECLASS_DATATYPE ||
nodeClass == UA_NODECLASS_VARIABLETYPE ||
nodeClass == UA_NODECLASS_OBJECTTYPE ||
nodeClass == UA_NODECLASS_REFERENCETYPE) {
/* type needs hassubtype reference to the supertype */
if(!UA_NodeId_equal(referenceTypeId, &subtypeId)) {
UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Type nodes need to have a HasSubType "
"reference to the parent");
return UA_STATUSCODE_BADREFERENCENOTALLOWED;
}
/* supertype needs to be of the same node type */
if(parentNodeClass != nodeClass) {
UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Type nodes needs to be of the same node "
"type as their parent");
return UA_STATUSCODE_BADPARENTNODEIDINVALID;
}
return UA_STATUSCODE_GOOD;
}
/* Test if the referencetype is hierarchical */
if(!isNodeInTree(server->nsCtx, referenceTypeId,
&hierarchicalReferences, &subtypeId, 1)) {
UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Reference type to the parent is not hierarchical");
return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
}
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
typeCheckVariableNode(UA_Server *server, UA_Session *session,
const UA_VariableNode *node,
const UA_VariableTypeNode *vt) {
/* The value might come from a datasource, so we perform a
* regular read. */
UA_DataValue value;
UA_DataValue_init(&value);
UA_StatusCode retval = readValueAttribute(server, session, node, &value);
if(retval != UA_STATUSCODE_GOOD)
return retval;
UA_NodeId baseDataType = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE);
/* Check the datatype against the vt */
/* If the node does not have any value and the dataType is BaseDataType,
* then it's also fine. This is the default for empty nodes. */
if(!compatibleDataType(server, &node->dataType, &vt->dataType, false) &&
(value.hasValue || !UA_NodeId_equal(&node->dataType, &baseDataType))) {
UA_LOG_NODEID_WRAP(&node->nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: The value of %.*s is incompatible with "
"the datatype of the VariableType",
(int)nodeIdStr.length, nodeIdStr.data));
UA_DataValue_deleteMembers(&value);
return UA_STATUSCODE_BADTYPEMISMATCH;
}
/* Check valueRank against array dimensions */
if(!compatibleValueRankArrayDimensions(server, session, node->valueRank,
node->arrayDimensionsSize)) {
UA_LOG_NODEID_WRAP(&node->nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: The value rank of %.*s is incomatible "
"with its array dimensions", (int)nodeIdStr.length, nodeIdStr.data));
UA_DataValue_deleteMembers(&value);
return UA_STATUSCODE_BADTYPEMISMATCH;
}
/* Check valueRank against the vt */
if(!compatibleValueRanks(node->valueRank, vt->valueRank)) {
UA_LOG_NODEID_WRAP(&node->nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: The value rank of %.*s is incomatible "
"with the value rank of the VariableType",
(int)nodeIdStr.length, nodeIdStr.data));
UA_DataValue_deleteMembers(&value);
return UA_STATUSCODE_BADTYPEMISMATCH;
}
/* Check array dimensions against the vt */
if(!compatibleArrayDimensions(vt->arrayDimensionsSize, vt->arrayDimensions,
node->arrayDimensionsSize, node->arrayDimensions)) {
UA_LOG_NODEID_WRAP(&node->nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: The array dimensions of %.*s are "
"incomatible with the array dimensions of the VariableType",
(int)nodeIdStr.length, nodeIdStr.data));
UA_DataValue_deleteMembers(&value);
return UA_STATUSCODE_BADTYPEMISMATCH;
}
/* Typecheck the value */
if(value.hasValue && value.value.data) {
/* If the type-check failed write the same value again. The
* write-service tries to convert to the correct type... */
if(!compatibleValue(server, session, &node->dataType, node->valueRank,
node->arrayDimensionsSize, node->arrayDimensions,
&value.value, NULL)) {
retval = writeWithWriteValue(server, &node->nodeId, UA_ATTRIBUTEID_VALUE, &UA_TYPES[UA_TYPES_VARIANT], &value.value);
}
UA_DataValue_deleteMembers(&value);
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_NODEID_WRAP(&node->nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: The value of of %.*s is incomatible with the "
"variable definition", (int)nodeIdStr.length, nodeIdStr.data));
}
}
return retval;
}
/********************/
/* Instantiate Node */
/********************/
static const UA_NodeId baseDataVariableType =
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_BASEDATAVARIABLETYPE}};
static const UA_NodeId baseObjectType =
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_BASEOBJECTTYPE}};
static const UA_NodeId hasTypeDefinition =
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASTYPEDEFINITION}};
/* Use attributes from the variable type wherever required. Reload the node if
* changes were made. */
static UA_StatusCode
useVariableTypeAttributes(UA_Server *server, UA_Session *session,
const UA_VariableNode **node_ptr,
const UA_VariableTypeNode *vt) {
const UA_VariableNode *node = *node_ptr;
UA_Boolean modified = false;
/* If no value is set, see if the vt provides one and copy it. This needs to
* be done before copying the datatype from the vt, as setting the datatype
* triggers a typecheck. */
UA_DataValue orig;
UA_DataValue_init(&orig);
UA_StatusCode retval = readValueAttribute(server, session, node, &orig);
if(retval != UA_STATUSCODE_GOOD)
return retval;
if(orig.value.type) {
/* A value is present */
UA_DataValue_deleteMembers(&orig);
} else {
UA_LOG_DEBUG_SESSION(&server->config.logger, session,
"AddNodes: No value given; Copy the value "
"from the TypeDefinition");
UA_WriteValue v;
UA_WriteValue_init(&v);
retval = readValueAttribute(server, session, (const UA_VariableNode*)vt, &v.value);
if(retval == UA_STATUSCODE_GOOD && v.value.hasValue) {
v.nodeId = node->nodeId;
v.attributeId = UA_ATTRIBUTEID_VALUE;
retval = writeWithSession(server, session, &v);
modified = true;
}
UA_DataValue_deleteMembers(&v.value);
if(retval != UA_STATUSCODE_GOOD)
return retval;
}
/* If no datatype is given, use the datatype of the vt */
if(UA_NodeId_isNull(&node->dataType)) {
UA_LOG_INFO_SESSION(&server->config.logger, session, "AddNodes: "
"No datatype given; Copy the datatype attribute "
"from the TypeDefinition");
UA_WriteValue v;
UA_WriteValue_init(&v);
v.nodeId = node->nodeId;
v.attributeId = UA_ATTRIBUTEID_DATATYPE;
v.value.hasValue = true;
UA_Variant_setScalar(&v.value.value, (void*)(uintptr_t)&vt->dataType,
&UA_TYPES[UA_TYPES_NODEID]);
retval = writeWithSession(server, session, &v);
modified = true;
if(retval != UA_STATUSCODE_GOOD)
return retval;
}
/* Use the ArrayDimensions of the vt */
if(node->arrayDimensionsSize == 0 && vt->arrayDimensionsSize > 0) {
UA_WriteValue v;
UA_WriteValue_init(&v);
v.nodeId = node->nodeId;
v.attributeId = UA_ATTRIBUTEID_ARRAYDIMENSIONS;
v.value.hasValue = true;
UA_Variant_setArray(&v.value.value, vt->arrayDimensions,
vt->arrayDimensionsSize, &UA_TYPES[UA_TYPES_UINT32]);
retval = writeWithSession(server, session, &v);
modified = true;
if(retval != UA_STATUSCODE_GOOD)
return retval;
}
/* If the node was modified, update the pointer to the new version */
if(modified) {
const UA_VariableNode *updated = (const UA_VariableNode*)
UA_Nodestore_getNode(server->nsCtx, &node->nodeId);
if(!updated)
return UA_STATUSCODE_BADINTERNALERROR;
UA_Nodestore_releaseNode(server->nsCtx, (const UA_Node*)node);
*node_ptr = updated;
}
return UA_STATUSCODE_GOOD;
}
/* Search for an instance of "browseName" in node searchInstance. Used during
* copyChildNodes to find overwritable/mergable nodes. Does not touch
* outInstanceNodeId if no child is found. */
static UA_StatusCode
findChildByBrowsename(UA_Server *server, UA_Session *session,
const UA_NodeId *searchInstance,
const UA_QualifiedName *browseName,
UA_NodeId *outInstanceNodeId) {
UA_BrowseDescription bd;
UA_BrowseDescription_init(&bd);
bd.nodeId = *searchInstance;
bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_AGGREGATES);
bd.includeSubtypes = true;
bd.browseDirection = UA_BROWSEDIRECTION_FORWARD;
bd.nodeClassMask = UA_NODECLASS_OBJECT | UA_NODECLASS_VARIABLE | UA_NODECLASS_METHOD;
bd.resultMask = UA_BROWSERESULTMASK_BROWSENAME;
UA_BrowseResult br;
UA_BrowseResult_init(&br);
UA_UInt32 maxrefs = 0;
Operation_Browse(server, session, &maxrefs, &bd, &br);
if(br.statusCode != UA_STATUSCODE_GOOD)
return br.statusCode;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
for(size_t i = 0; i < br.referencesSize; ++i) {
UA_ReferenceDescription *rd = &br.references[i];
if(rd->browseName.namespaceIndex == browseName->namespaceIndex &&
UA_String_equal(&rd->browseName.name, &browseName->name)) {
retval = UA_NodeId_copy(&rd->nodeId.nodeId, outInstanceNodeId);
break;
}
}
UA_BrowseResult_deleteMembers(&br);
return retval;
}
static const UA_NodeId mandatoryId =
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_MODELLINGRULE_MANDATORY}};
static const UA_NodeId hasModellingRuleId =
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASMODELLINGRULE}};
static UA_Boolean
isMandatoryChild(UA_Server *server, UA_Session *session,
const UA_NodeId *childNodeId) {
/* Get the child */
const UA_Node *child = UA_Nodestore_getNode(server->nsCtx, childNodeId);
if(!child)
return false;
/* Look for the reference making the child mandatory */
for(size_t i = 0; i < child->referencesSize; ++i) {
UA_NodeReferenceKind *refs = &child->references[i];
if(!UA_NodeId_equal(&hasModellingRuleId, &refs->referenceTypeId))
continue;
if(refs->isInverse)
continue;
for(size_t j = 0; j < refs->targetIdsSize; ++j) {
if(UA_NodeId_equal(&mandatoryId, &refs->targetIds[j].nodeId)) {
UA_Nodestore_releaseNode(server->nsCtx, child);
return true;
}
}
}
UA_Nodestore_releaseNode(server->nsCtx, child);
return false;
}
static UA_StatusCode
copyAllChildren(UA_Server *server, UA_Session *session,
const UA_NodeId *source, const UA_NodeId *destination);
static UA_StatusCode
recursiveTypeCheckAddChildren(UA_Server *server, UA_Session *session,
const UA_Node **node, const UA_Node *type);
static void
Operation_addReference(UA_Server *server, UA_Session *session, void *context,
const UA_AddReferencesItem *item, UA_StatusCode *retval);
static UA_StatusCode
copyChild(UA_Server *server, UA_Session *session, const UA_NodeId *destinationNodeId,
const UA_ReferenceDescription *rd) {
/* Is there an existing child with the browsename? */
UA_NodeId existingChild = UA_NODEID_NULL;
UA_StatusCode retval = findChildByBrowsename(server, session, destinationNodeId,
&rd->browseName, &existingChild);
if(retval != UA_STATUSCODE_GOOD)
return retval;
/* Have a child with that browseName. Deep-copy missing members. */
if(!UA_NodeId_isNull(&existingChild)) {
if(rd->nodeClass == UA_NODECLASS_VARIABLE ||
rd->nodeClass == UA_NODECLASS_OBJECT)
retval = copyAllChildren(server, session, &rd->nodeId.nodeId, &existingChild);
UA_NodeId_deleteMembers(&existingChild);
return retval;
}
/* Is the child mandatory? If not, ask callback whether child should be instantiated.
* If not, skip. */
if(!isMandatoryChild(server, session, &rd->nodeId.nodeId)) {
if(!server->config.nodeLifecycle.createOptionalChild)
return UA_STATUSCODE_GOOD;
UA_UNLOCK(server->serviceMutex);
retval = server->config.nodeLifecycle.createOptionalChild(server,
&session->sessionId,
session->sessionHandle,
&rd->nodeId.nodeId,
destinationNodeId,
&rd->referenceTypeId);
UA_LOCK(server->serviceMutex);
if(retval == UA_FALSE) {
return UA_STATUSCODE_GOOD;
}
}
/* Child is a method -> create a reference */
if(rd->nodeClass == UA_NODECLASS_METHOD) {
UA_AddReferencesItem newItem;
UA_AddReferencesItem_init(&newItem);
newItem.sourceNodeId = *destinationNodeId;
newItem.referenceTypeId = rd->referenceTypeId;
newItem.isForward = true;
newItem.targetNodeId = rd->nodeId;
newItem.targetNodeClass = UA_NODECLASS_METHOD;
Operation_addReference(server, session, NULL, &newItem, &retval);
return retval;
}
/* Child is a variable or object */
if(rd->nodeClass == UA_NODECLASS_VARIABLE ||
rd->nodeClass == UA_NODECLASS_OBJECT) {
/* Make a copy of the node */
UA_Node *node;
retval = UA_Nodestore_getNodeCopy(server->nsCtx, &rd->nodeId.nodeId, &node);
if(retval != UA_STATUSCODE_GOOD)
return retval;
/* Remove the context of the copied node */
node->context = NULL;
node->constructed = false;
/* Reset the NodeId (random numeric id will be assigned in the nodestore) */
UA_NodeId_deleteMembers(&node->nodeId);
node->nodeId.namespaceIndex = destinationNodeId->namespaceIndex;
if (server->config.nodeLifecycle.generateChildNodeId) {
UA_UNLOCK(server->serviceMutex);
retval = server->config.nodeLifecycle.generateChildNodeId(server,
&session->sessionId, session->sessionHandle,
&rd->nodeId.nodeId,
destinationNodeId,
&rd->referenceTypeId,
&node->nodeId);
UA_LOCK(server->serviceMutex);
if(retval != UA_STATUSCODE_GOOD) {
UA_Nodestore_deleteNode(server->nsCtx, node);
return retval;
}
}
/* Remove references, they are re-created from scratch in addnode_finish */
/* TODO: Be more clever in removing references that are re-added during
* addnode_finish. That way, we can call addnode_finish also on children that were
* manually added by the user during addnode_begin and addnode_finish. */
/* For now we keep all the modelling rule references and delete all others */
UA_NodeId modellingRuleReferenceId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE);
UA_Node_deleteReferencesSubset(node, 1, &modellingRuleReferenceId);
/* Add the node to the nodestore */
UA_NodeId newNodeId;
retval = UA_Nodestore_insertNode(server->nsCtx, node, &newNodeId);
if(retval != UA_STATUSCODE_GOOD)
return retval;
/* Add the node references */
retval = AddNode_addRefs(server, session, &newNodeId, destinationNodeId,
&rd->referenceTypeId, &rd->typeDefinition.nodeId);
if(retval != UA_STATUSCODE_GOOD) {
UA_Nodestore_removeNode(server->nsCtx, &newNodeId);
return retval;
}
/* For the new child, recursively copy the members of the original. No
* typechecking is performed here. Assuming that the original is
* consistent. */
retval = copyAllChildren(server, session, &rd->nodeId.nodeId, &newNodeId);
}
return retval;
}
/* Copy any children of Node sourceNodeId to another node destinationNodeId. */
static UA_StatusCode
copyAllChildren(UA_Server *server, UA_Session *session,
const UA_NodeId *source, const UA_NodeId *destination) {
/* Browse to get all children of the source */
UA_BrowseDescription bd;
UA_BrowseDescription_init(&bd);
bd.nodeId = *source;
bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_AGGREGATES);
bd.includeSubtypes = true;
bd.browseDirection = UA_BROWSEDIRECTION_FORWARD;
bd.nodeClassMask = UA_NODECLASS_OBJECT | UA_NODECLASS_VARIABLE | UA_NODECLASS_METHOD;
bd.resultMask = UA_BROWSERESULTMASK_REFERENCETYPEID | UA_BROWSERESULTMASK_NODECLASS |
UA_BROWSERESULTMASK_BROWSENAME | UA_BROWSERESULTMASK_TYPEDEFINITION;
UA_BrowseResult br;
UA_BrowseResult_init(&br);
UA_UInt32 maxrefs = 0;
Operation_Browse(server, session, &maxrefs, &bd, &br);
if(br.statusCode != UA_STATUSCODE_GOOD)
return br.statusCode;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
for(size_t i = 0; i < br.referencesSize; ++i) {
UA_ReferenceDescription *rd = &br.references[i];
retval = copyChild(server, session, destination, rd);
if(retval != UA_STATUSCODE_GOOD)
return retval;
}
UA_BrowseResult_deleteMembers(&br);
return retval;
}
static UA_StatusCode
addTypeChildren(UA_Server *server, UA_Session *session,
const UA_Node *node, const UA_Node *type) {
/* Get the hierarchy of the type and all its supertypes */
UA_NodeId *hierarchy = NULL;
size_t hierarchySize = 0;
UA_StatusCode retval = getParentTypeAndInterfaceHierarchy(server, &type->nodeId,
&hierarchy, &hierarchySize);
if(retval != UA_STATUSCODE_GOOD)
return retval;
UA_assert(hierarchySize < 1000);
/* Copy members of the type and supertypes (and instantiate them) */
for(size_t i = 0; i < hierarchySize; ++i) {
retval = copyAllChildren(server, session, &hierarchy[i], &node->nodeId);
if(retval != UA_STATUSCODE_GOOD)
break;
}
UA_Array_delete(hierarchy, hierarchySize, &UA_TYPES[UA_TYPES_NODEID]);
return retval;
}
static UA_StatusCode
addRef(UA_Server *server, UA_Session *session, const UA_NodeId *nodeId,
const UA_NodeId *referenceTypeId, const UA_NodeId *parentNodeId,
UA_Boolean forward) {
UA_AddReferencesItem ref_item;
UA_AddReferencesItem_init(&ref_item);
ref_item.sourceNodeId = *nodeId;
ref_item.referenceTypeId = *referenceTypeId;
ref_item.isForward = forward;
ref_item.targetNodeId.nodeId = *parentNodeId;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
Operation_addReference(server, session, NULL, &ref_item, &retval);
return retval;
}
/************/
/* Add Node */
/************/
static const UA_NodeId hasSubtype = {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASSUBTYPE}};
UA_StatusCode
AddNode_addRefs(UA_Server *server, UA_Session *session, const UA_NodeId *nodeId,
const UA_NodeId *parentNodeId, const UA_NodeId *referenceTypeId,
const UA_NodeId *typeDefinitionId) {
/* Get the node */
const UA_Node *type = NULL;
const UA_Node *node = UA_Nodestore_getNode(server->nsCtx, nodeId);
if(!node)
return UA_STATUSCODE_BADNODEIDUNKNOWN;
/* Use the typeDefinition as parent for type-nodes */
if(node->nodeClass == UA_NODECLASS_VARIABLETYPE ||
node->nodeClass == UA_NODECLASS_OBJECTTYPE ||
node->nodeClass == UA_NODECLASS_REFERENCETYPE ||
node->nodeClass == UA_NODECLASS_DATATYPE) {
if(UA_NodeId_equal(referenceTypeId, &UA_NODEID_NULL))
referenceTypeId = &hasSubtype;
const UA_Node *parentNode = UA_Nodestore_getNode(server->nsCtx, parentNodeId);
if(parentNode) {
if(parentNode->nodeClass == node->nodeClass)
typeDefinitionId = parentNodeId;
UA_Nodestore_releaseNode(server->nsCtx, parentNode);
}
}
/* Check parent reference. Objects may have no parent. */
UA_StatusCode retval = checkParentReference(server, session, node->nodeClass,
parentNodeId, referenceTypeId);
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_NODEID_WRAP(nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: The parent reference for %.*s is invalid "
"with status code %s",
(int)nodeIdStr.length, nodeIdStr.data,
UA_StatusCode_name(retval)));
goto cleanup;
}
/* Replace empty typeDefinition with the most permissive default */
if((node->nodeClass == UA_NODECLASS_VARIABLE ||
node->nodeClass == UA_NODECLASS_OBJECT) &&
UA_NodeId_isNull(typeDefinitionId)) {
UA_LOG_NODEID_WRAP(nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: No TypeDefinition for %.*s; Use the default "
"TypeDefinition for the Variable/Object",
(int)nodeIdStr.length, nodeIdStr.data));
if(node->nodeClass == UA_NODECLASS_VARIABLE)
typeDefinitionId = &baseDataVariableType;
else
typeDefinitionId = &baseObjectType;
}
/* Get the node type. There must be a typedefinition for variables, objects
* and type-nodes. See the above checks. */
if(!UA_NodeId_isNull(typeDefinitionId)) {
/* Get the type node */
type = UA_Nodestore_getNode(server->nsCtx, typeDefinitionId);
if(!type) {
UA_LOG_NODEID_WRAP(typeDefinitionId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Node type %.*s not found",
(int)nodeIdStr.length, nodeIdStr.data));
retval = UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
goto cleanup;
}
UA_Boolean typeOk = false;
switch(node->nodeClass) {
case UA_NODECLASS_DATATYPE:
typeOk = type->nodeClass == UA_NODECLASS_DATATYPE;
break;
case UA_NODECLASS_METHOD:
typeOk = type->nodeClass == UA_NODECLASS_METHOD;
break;
case UA_NODECLASS_OBJECT:
typeOk = type->nodeClass == UA_NODECLASS_OBJECTTYPE;
break;
case UA_NODECLASS_OBJECTTYPE:
typeOk = type->nodeClass == UA_NODECLASS_OBJECTTYPE;
break;
case UA_NODECLASS_REFERENCETYPE:
typeOk = type->nodeClass == UA_NODECLASS_REFERENCETYPE;
break;
case UA_NODECLASS_VARIABLE:
typeOk = type->nodeClass == UA_NODECLASS_VARIABLETYPE;
break;
case UA_NODECLASS_VARIABLETYPE:
typeOk = type->nodeClass == UA_NODECLASS_VARIABLETYPE;
break;
case UA_NODECLASS_VIEW:
typeOk = type->nodeClass == UA_NODECLASS_VIEW;
break;
default:
typeOk = false;
}
if(!typeOk) {
UA_LOG_NODEID_WRAP(nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Type for %.*s does not match node class",
(int)nodeIdStr.length, nodeIdStr.data));
retval = UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
goto cleanup;
}
/* See if the type has the correct node class. For type-nodes, we know
* that type has the same nodeClass from checkParentReference. */
if(node->nodeClass == UA_NODECLASS_VARIABLE) {
if(((const UA_VariableTypeNode*)type)->isAbstract) {
/* Get subtypes of the parent reference types */
UA_NodeId *parentTypeHierarchy = NULL;
size_t parentTypeHierarchySize = 0;
retval |= referenceSubtypes(server, &parentReferences[0],
&parentTypeHierarchySize, &parentTypeHierarchy);
retval |= referenceSubtypes(server, &parentReferences[1],
&parentTypeHierarchySize, &parentTypeHierarchy);
if(retval != UA_STATUSCODE_GOOD) {
UA_Array_delete(parentTypeHierarchy, parentTypeHierarchySize,
&UA_TYPES[UA_TYPES_NODEID]);
goto cleanup;
}
/* Abstract variable is allowed if parent is a children of a
* base data variable. An abstract variable may be part of an
* object type which again is below BaseObjectType */
const UA_NodeId variableTypes = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);
const UA_NodeId objectTypes = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE);
if(!isNodeInTree(server->nsCtx, parentNodeId, &variableTypes,
parentTypeHierarchy, parentTypeHierarchySize) &&
!isNodeInTree(server->nsCtx, parentNodeId, &objectTypes,
parentTypeHierarchy, parentTypeHierarchySize)) {
UA_LOG_NODEID_WRAP(nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Type of variable node %.*s must "
"be VariableType and not cannot be abstract",
(int)nodeIdStr.length, nodeIdStr.data));
retval = UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
}
UA_Array_delete(parentTypeHierarchy, parentTypeHierarchySize,
&UA_TYPES[UA_TYPES_NODEID]);
if(retval != UA_STATUSCODE_GOOD)
goto cleanup;
}
}
if(node->nodeClass == UA_NODECLASS_OBJECT) {
if(((const UA_ObjectTypeNode*)type)->isAbstract) {
/* Get subtypes of the parent reference types */
UA_NodeId *parentTypeHierarchy = NULL;
size_t parentTypeHierarchySize = 0;
retval |= referenceSubtypes(server, &parentReferences[0],
&parentTypeHierarchySize, &parentTypeHierarchy);
retval |= referenceSubtypes(server, &parentReferences[1],
&parentTypeHierarchySize, &parentTypeHierarchy);
if(retval != UA_STATUSCODE_GOOD) {
UA_Array_delete(parentTypeHierarchy, parentTypeHierarchySize,
&UA_TYPES[UA_TYPES_NODEID]);
goto cleanup;
}
/* Object node created of an abstract ObjectType. Only allowed
* if within BaseObjectType folder or if it's an event (subType of BaseEventType) */
const UA_NodeId objectTypes = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE);
UA_Boolean isInBaseObjectType = isNodeInTree(server->nsCtx, parentNodeId, &objectTypes,
parentTypeHierarchy, parentTypeHierarchySize);
const UA_NodeId eventTypes = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE);
UA_Boolean isInBaseEventType = isNodeInTree(server->nsCtx, &type->nodeId, &eventTypes, &hasSubtype, 1);
if(!isInBaseObjectType && !(isInBaseEventType && UA_NodeId_isNull(parentNodeId))) {
UA_LOG_NODEID_WRAP(nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Type of object node %.*s must "
"be ObjectType and not be abstract",
(int)nodeIdStr.length, nodeIdStr.data));
retval = UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
}
UA_Array_delete(parentTypeHierarchy, parentTypeHierarchySize,
&UA_TYPES[UA_TYPES_NODEID]);
if(retval != UA_STATUSCODE_GOOD)
goto cleanup;
}
}
}
/* Add reference to the parent */
if(!UA_NodeId_isNull(parentNodeId)) {
if(UA_NodeId_isNull(referenceTypeId)) {
UA_LOG_NODEID_WRAP(nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Reference to parent of %.*s cannot be null",
(int)nodeIdStr.length, nodeIdStr.data));
retval = UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
goto cleanup;
}
retval = addRef(server, session, &node->nodeId, referenceTypeId, parentNodeId, false);
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_NODEID_WRAP(nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Adding reference to parent of %.*s failed",
(int)nodeIdStr.length, nodeIdStr.data));
goto cleanup;
}
}
/* Add a hasTypeDefinition reference */
if(node->nodeClass == UA_NODECLASS_VARIABLE ||
node->nodeClass == UA_NODECLASS_OBJECT) {
UA_assert(type != NULL); /* see above */
retval = addRef(server, session, &node->nodeId, &hasTypeDefinition, &type->nodeId, true);
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_NODEID_WRAP(nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Adding a reference to the type "
"definition of %.*s failed with error code %s",
(int)nodeIdStr.length, nodeIdStr.data,
UA_StatusCode_name(retval)));
}
}
cleanup:
UA_Nodestore_releaseNode(server->nsCtx, node);
if(type)
UA_Nodestore_releaseNode(server->nsCtx, type);
return retval;
}
/* Create the node and add it to the nodestore. But don't typecheck and add
* references so far */
UA_StatusCode
AddNode_raw(UA_Server *server, UA_Session *session, void *nodeContext,
const UA_AddNodesItem *item, UA_NodeId *outNewNodeId) {
/* Do not check access for server */
if(session != &server->adminSession && server->config.accessControl.allowAddNode) {
UA_UNLOCK(server->serviceMutex)
if (!server->config.accessControl.allowAddNode(server, &server->config.accessControl,
&session->sessionId, session->sessionHandle, item)) {
UA_LOCK(server->serviceMutex);
return UA_STATUSCODE_BADUSERACCESSDENIED;
}
UA_LOCK(server->serviceMutex);
}
/* Check the namespaceindex */
if(item->requestedNewNodeId.nodeId.namespaceIndex >= server->namespacesSize) {
UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Namespace invalid");
return UA_STATUSCODE_BADNODEIDINVALID;
}
if(item->nodeAttributes.encoding != UA_EXTENSIONOBJECT_DECODED &&
item->nodeAttributes.encoding != UA_EXTENSIONOBJECT_DECODED_NODELETE) {
UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Node attributes invalid");
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Create a node */
UA_Node *node = UA_Nodestore_newNode(server->nsCtx, item->nodeClass);
if(!node) {
UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Node could not create a node "
"in the nodestore");
return UA_STATUSCODE_BADOUTOFMEMORY;
}
/* Fill the node attributes */
node->context = nodeContext;
UA_StatusCode retval = UA_NodeId_copy(&item->requestedNewNodeId.nodeId, &node->nodeId);
if(retval != UA_STATUSCODE_GOOD)
goto create_error;
retval = UA_QualifiedName_copy(&item->browseName, &node->browseName);
if(retval != UA_STATUSCODE_GOOD)
goto create_error;
retval = UA_Node_setAttributes(node, item->nodeAttributes.content.decoded.data,
item->nodeAttributes.content.decoded.type);
if(retval != UA_STATUSCODE_GOOD)
goto create_error;
/* Add the node to the nodestore */
retval = UA_Nodestore_insertNode(server->nsCtx, node, outNewNodeId);
if(retval != UA_STATUSCODE_GOOD)
UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Node could not add the new node "
"to the nodestore with error code %s",
UA_StatusCode_name(retval));
return retval;
create_error:
UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Node could not create a node "
"with error code %s", UA_StatusCode_name(retval));
UA_Nodestore_deleteNode(server->nsCtx, node);
return retval;
}
/* Prepare the node, then add it to the nodestore */
static UA_StatusCode
Operation_addNode_begin(UA_Server *server, UA_Session *session, void *nodeContext,
const UA_AddNodesItem *item, const UA_NodeId *parentNodeId,
const UA_NodeId *referenceTypeId, UA_NodeId *outNewNodeId) {
/* Create a temporary NodeId if none is returned */
UA_NodeId newId;
if(!outNewNodeId) {
UA_NodeId_init(&newId);
outNewNodeId = &newId;
}
/* Create the node and add it to the nodestore */
UA_StatusCode retval = AddNode_raw(server, session, nodeContext, item, outNewNodeId);
if(retval != UA_STATUSCODE_GOOD)
return retval;
/* Typecheck and add references to parent and type definition */
retval = AddNode_addRefs(server, session, outNewNodeId, parentNodeId,
referenceTypeId, &item->typeDefinition.nodeId);
if(retval != UA_STATUSCODE_GOOD)
deleteNode(server, *outNewNodeId, true);
if(outNewNodeId == &newId)
UA_NodeId_deleteMembers(&newId);
return retval;
}
static UA_StatusCode
recursiveTypeCheckAddChildren(UA_Server *server, UA_Session *session,
const UA_Node **nodeptr, const UA_Node *type) {
UA_assert(type != NULL);
UA_StatusCode retval = UA_STATUSCODE_GOOD;
const UA_Node *node = *nodeptr;
/* Use attributes from the type. The value and value constraints are the
* same for the variable and variabletype attribute structs. */
if(node->nodeClass == UA_NODECLASS_VARIABLE ||
node->nodeClass == UA_NODECLASS_VARIABLETYPE) {
retval = useVariableTypeAttributes(server, session, (const UA_VariableNode**)nodeptr,
(const UA_VariableTypeNode*)type);
node = *nodeptr; /* If the node was replaced */
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_NODEID_WRAP(&node->nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Using attributes for %.*s from the variable type "
"failed with error code %s", (int)nodeIdStr.length,
nodeIdStr.data, UA_StatusCode_name(retval)));
return retval;
}
/* Check NodeClass for 'hasSubtype'. UA_NODECLASS_VARIABLE not allowed to have subtype */
if((node->nodeClass == UA_NODECLASS_VARIABLE) && (UA_NodeId_equal(
&node->references->referenceTypeId, &hasSubtype))) {
UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: VariableType not allowed to have HasSubType");
return UA_STATUSCODE_BADREFERENCENOTALLOWED;
}
/* Check if all attributes hold the constraints of the type now. The initial
* attributes must type-check. The constructor might change the attributes
* again. Then, the changes are type-checked by the normal write service. */
retval = typeCheckVariableNode(server, session, (const UA_VariableNode*)node,
(const UA_VariableTypeNode*)type);
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_NODEID_WRAP(&node->nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Type-checking the variable node %.*s "
"failed with error code %s", (int)nodeIdStr.length,
nodeIdStr.data, UA_StatusCode_name(retval)));
return retval;
}
}
/* Add (mandatory) child nodes from the type definition */
if(node->nodeClass == UA_NODECLASS_VARIABLE ||
node->nodeClass == UA_NODECLASS_OBJECT) {
retval = addTypeChildren(server, session, node, type);
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_NODEID_WRAP(&node->nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Adding child nodes of %.*s failed with error code %s",
(int)nodeIdStr.length, nodeIdStr.data, UA_StatusCode_name(retval)));
}
}
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
findDefaultInstanceBrowseNameNode(UA_Server *server,
UA_NodeId startingNode, UA_NodeId *foundId){
UA_NodeId_init(foundId);
UA_RelativePathElement rpe;
UA_RelativePathElement_init(&rpe);
rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY);
rpe.isInverse = false;
rpe.includeSubtypes = false;
rpe.targetName = UA_QUALIFIEDNAME(0, "DefaultInstanceBrowseName");
UA_BrowsePath bp;
UA_BrowsePath_init(&bp);
bp.startingNode = startingNode;
bp.relativePath.elementsSize = 1;
bp.relativePath.elements = &rpe;
UA_BrowsePathResult bpr =
translateBrowsePathToNodeIds(server, &bp);
UA_StatusCode retval = bpr.statusCode;
if (retval == UA_STATUSCODE_GOOD &&
bpr.targetsSize > 0) {
retval = UA_NodeId_copy(&bpr.targets[0].targetId.nodeId, foundId);
}
UA_BrowsePathResult_deleteMembers(&bpr);
return retval;
}
/* Check if we got a valid browse name for the new node.
* For object nodes the BrowseName may only be null if the parent type has a
* 'DefaultInstanceBrowseName' property.
* */
static UA_StatusCode
checkValidBrowseName(UA_Server *server, UA_Session *session,
const UA_Node *node, const UA_Node *type) {
UA_assert(type != NULL);
UA_StatusCode retval = UA_STATUSCODE_GOOD;
if(node->nodeClass != UA_NODECLASS_OBJECT) {
/* nodes other than Objects must have a browseName */
if (UA_QualifiedName_isNull(&node->browseName))
return UA_STATUSCODE_BADBROWSENAMEINVALID;
return UA_STATUSCODE_GOOD;
}
/* If the object node already has a browse name we are done here. */
if(!UA_QualifiedName_isNull(&node->browseName))
return UA_STATUSCODE_GOOD;
/* at this point we have an object with an empty browse name.
* Check the type node if it has a DefaultInstanceBrowseName property
*/
UA_NodeId defaultBrowseNameNode;
retval = findDefaultInstanceBrowseNameNode(server, type->nodeId, &defaultBrowseNameNode);
if (retval != UA_STATUSCODE_GOOD) {
if (retval == UA_STATUSCODE_BADNOMATCH)
/* the DefaultBrowseName property is not found, return the corresponding status code */
return UA_STATUSCODE_BADBROWSENAMEINVALID;
return retval;
}
UA_Variant defaultBrowseName;
retval = readWithReadValue(server, &defaultBrowseNameNode, UA_ATTRIBUTEID_VALUE, &defaultBrowseName);
if (retval != UA_STATUSCODE_GOOD)
return retval;
UA_QualifiedName *defaultValue = (UA_QualifiedName *) defaultBrowseName.data;
retval = writeWithWriteValue(server, &node->nodeId, UA_ATTRIBUTEID_BROWSENAME, &UA_TYPES[UA_TYPES_QUALIFIEDNAME], defaultValue);
UA_Variant_clear(&defaultBrowseName);
return retval;
}
/* Construct children first */
static UA_StatusCode
recursiveCallConstructors(UA_Server *server, UA_Session *session,
const UA_Node *node, const UA_Node *type) {
if(node->constructed)
return UA_STATUSCODE_GOOD;
/* Construct the children */
UA_BrowseDescription bd;
UA_BrowseDescription_init(&bd);
bd.nodeId = node->nodeId;
bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_AGGREGATES);
bd.includeSubtypes = true;
bd.browseDirection = UA_BROWSEDIRECTION_FORWARD;
UA_BrowseResult br;
UA_BrowseResult_init(&br);
UA_UInt32 maxrefs = 0;
Operation_Browse(server, session, &maxrefs, &bd, &br);
if(br.statusCode != UA_STATUSCODE_GOOD)
return br.statusCode;
/* Call the constructor for every unconstructed node */
UA_StatusCode retval = UA_STATUSCODE_GOOD;
for(size_t i = 0; i < br.referencesSize; ++i) {
UA_ReferenceDescription *rd = &br.references[i];
const UA_Node *target = UA_Nodestore_getNode(server->nsCtx, &rd->nodeId.nodeId);
if(!target)
continue;
if(target->constructed) {
UA_Nodestore_releaseNode(server->nsCtx, target);
continue;
}
const UA_Node *targetType = NULL;
if(node->nodeClass == UA_NODECLASS_VARIABLE ||
node->nodeClass == UA_NODECLASS_OBJECT) {
targetType = getNodeType(server, target);
if(!targetType) {
UA_Nodestore_releaseNode(server->nsCtx, target);
retval = UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
break;
}
}
retval = recursiveCallConstructors(server, session, target, targetType);
UA_Nodestore_releaseNode(server->nsCtx, target);
if(targetType)
UA_Nodestore_releaseNode(server->nsCtx, targetType);
if(retval != UA_STATUSCODE_GOOD)
break;
}
UA_BrowseResult_deleteMembers(&br);
/* If a child could not be constructed or the node is already constructed */
if(retval != UA_STATUSCODE_GOOD)
return retval;
/* Get the node type constructor */
const UA_NodeTypeLifecycle *lifecycle = NULL;
if(type && node->nodeClass == UA_NODECLASS_OBJECT) {
const UA_ObjectTypeNode *ot = (const UA_ObjectTypeNode*)type;
lifecycle = &ot->lifecycle;
} else if(type && node->nodeClass == UA_NODECLASS_VARIABLE) {
const UA_VariableTypeNode *vt = (const UA_VariableTypeNode*)type;
lifecycle = &vt->lifecycle;
}
/* Call the global constructor */
void *context = node->context;
if(server->config.nodeLifecycle.constructor) {
UA_UNLOCK(server->serviceMutex);
retval = server->config.nodeLifecycle.constructor(server, &session->sessionId,
session->sessionHandle,
&node->nodeId, &context);
UA_LOCK(server->serviceMutex);
}
/* Call the type constructor */
if(retval == UA_STATUSCODE_GOOD && lifecycle && lifecycle->constructor) {
UA_UNLOCK(server->serviceMutex)
retval = lifecycle->constructor(server, &session->sessionId,
session->sessionHandle, &type->nodeId,
type->context, &node->nodeId, &context);
UA_LOCK(server->serviceMutex);
}
if(retval != UA_STATUSCODE_GOOD)
goto fail1;
/* Set the context *and* mark the node as constructed */
if(retval == UA_STATUSCODE_GOOD)
retval = UA_Server_editNode(server, &server->adminSession, &node->nodeId,
(UA_EditNodeCallback)setConstructedNodeContext,
context);
/* All good, return */
if(retval == UA_STATUSCODE_GOOD)
return retval;
/* Fail. Call the destructors. */
if(lifecycle && lifecycle->destructor) {
UA_UNLOCK(server->serviceMutex);
lifecycle->destructor(server, &session->sessionId,
session->sessionHandle, &type->nodeId,
type->context, &node->nodeId, &context);
UA_LOCK(server->serviceMutex)
}
fail1:
if(server->config.nodeLifecycle.destructor) {
UA_UNLOCK(server->serviceMutex);
server->config.nodeLifecycle.destructor(server, &session->sessionId,
session->sessionHandle,
&node->nodeId, context);
UA_LOCK(server->serviceMutex);
}
return retval;
}
static void
recursiveDeconstructNode(UA_Server *server, UA_Session *session,
size_t hierarchicalReferencesSize,
UA_ExpandedNodeId *hierarchicalReferences,
const UA_Node *node);
static void
recursiveDeleteNode(UA_Server *server, UA_Session *session,
size_t hierarchicalReferencesSize,
UA_ExpandedNodeId *hierarchicalReferences,
const UA_Node *node, UA_Boolean removeTargetRefs);
/* Children, references, type-checking, constructors. */
UA_StatusCode
AddNode_finish(UA_Server *server, UA_Session *session, const UA_NodeId *nodeId) {
UA_StatusCode retval = UA_STATUSCODE_GOOD;
/* Get the node */
const UA_Node *node = UA_Nodestore_getNode(server->nsCtx, nodeId);
if(!node)
return UA_STATUSCODE_BADNODEIDUNKNOWN;
const UA_Node *type = NULL;
/* Instantiate variables and objects */
if(node->nodeClass == UA_NODECLASS_VARIABLE ||
node->nodeClass == UA_NODECLASS_VARIABLETYPE ||
node->nodeClass == UA_NODECLASS_OBJECT) {
/* Get the type node */
type = getNodeType(server, node);
if(!type) {
if(server->bootstrapNS0)
goto constructor;
UA_LOG_NODEID_WRAP(&node->nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Node type for %.*s not found",
(int)nodeIdStr.length, nodeIdStr.data));
retval = UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
goto cleanup;
}
retval = checkValidBrowseName(server, session, node, type);
if(retval != UA_STATUSCODE_GOOD)
goto cleanup;
retval = recursiveTypeCheckAddChildren(server, session, &node, type);
if(retval != UA_STATUSCODE_GOOD)
goto cleanup;
}
/* Call the constructor(s) */
constructor:
retval = recursiveCallConstructors(server, session, node, type);
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_NODEID_WRAP(&node->nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
"AddNodes: Calling the node constructor(s) of %.*s failed "
"with status code %s", (int)nodeIdStr.length,
nodeIdStr.data, UA_StatusCode_name(retval)));
}
cleanup:
if(type)
UA_Nodestore_releaseNode(server->nsCtx, type);
if(retval != UA_STATUSCODE_GOOD) {
recursiveDeconstructNode(server, session, 0, NULL, node);
recursiveDeleteNode(server, session, 0, NULL, node, true);
}
UA_Nodestore_releaseNode(server->nsCtx, node);
return retval;
}
static void
Operation_addNode(UA_Server *server, UA_Session *session, void *nodeContext,
const UA_AddNodesItem *item, UA_AddNodesResult *result) {
result->statusCode =
Operation_addNode_begin(server, session, nodeContext, item, &item->parentNodeId.nodeId,
&item->referenceTypeId, &result->addedNodeId);
if(result->statusCode != UA_STATUSCODE_GOOD)
return;
/* AddNodes_finish */
result->statusCode = AddNode_finish(server, session, &result->addedNodeId);
/* If finishing failed, the node was deleted */
if(result->statusCode != UA_STATUSCODE_GOOD)
UA_NodeId_deleteMembers(&result->addedNodeId);
}
void
Service_AddNodes(UA_Server *server, UA_Session *session,
const UA_AddNodesRequest *request,
UA_AddNodesResponse *response) {
UA_LOG_DEBUG_SESSION(&server->config.logger, session, "Processing AddNodesRequest");
UA_LOCK_ASSERT(server->serviceMutex, 1);
if(server->config.maxNodesPerNodeManagement != 0 &&
request->nodesToAddSize > server->config.maxNodesPerNodeManagement) {
response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYOPERATIONS;
return;
}
response->responseHeader.serviceResult =
UA_Server_processServiceOperations(server, session,
(UA_ServiceOperation)Operation_addNode, NULL,
&request->nodesToAddSize, &UA_TYPES[UA_TYPES_ADDNODESITEM],
&response->resultsSize, &UA_TYPES[UA_TYPES_ADDNODESRESULT]);
}
UA_StatusCode
addNode(UA_Server *server, const UA_NodeClass nodeClass,
const UA_NodeId *requestedNewNodeId,
const UA_NodeId *parentNodeId,
const UA_NodeId *referenceTypeId,
const UA_QualifiedName browseName,
const UA_NodeId *typeDefinition,
const UA_NodeAttributes *attr,
const UA_DataType *attributeType,
void *nodeContext, UA_NodeId *outNewNodeId) {
/* Create the AddNodesItem */
UA_AddNodesItem item;
UA_AddNodesItem_init(&item);
item.nodeClass = nodeClass;
item.requestedNewNodeId.nodeId = *requestedNewNodeId;
item.browseName = browseName;
item.parentNodeId.nodeId = *parentNodeId;
item.referenceTypeId = *referenceTypeId;
item.typeDefinition.nodeId = *typeDefinition;
item.nodeAttributes.encoding = UA_EXTENSIONOBJECT_DECODED_NODELETE;
item.nodeAttributes.content.decoded.type = attributeType;
item.nodeAttributes.content.decoded.data = (void*)(uintptr_t)attr;
/* Call the normal addnodes service */
UA_AddNodesResult result;
UA_AddNodesResult_init(&result);
Operation_addNode(server, &server->adminSession, nodeContext, &item, &result);
if(outNewNodeId)
*outNewNodeId = result.addedNodeId;
else
UA_NodeId_deleteMembers(&result.addedNodeId);
return result.statusCode;
}
UA_StatusCode
__UA_Server_addNode(UA_Server *server, const UA_NodeClass nodeClass,
const UA_NodeId *requestedNewNodeId,
const UA_NodeId *parentNodeId,
const UA_NodeId *referenceTypeId,
const UA_QualifiedName browseName,
const UA_NodeId *typeDefinition,
const UA_NodeAttributes *attr,
const UA_DataType *attributeType,
void *nodeContext, UA_NodeId *outNewNodeId) {
UA_LOCK(server->serviceMutex)
UA_StatusCode reval = addNode(server, nodeClass, requestedNewNodeId, parentNodeId,
referenceTypeId, browseName, typeDefinition, attr, attributeType, nodeContext, outNewNodeId);
UA_UNLOCK(server->serviceMutex);
return reval;
}
UA_StatusCode
UA_Server_addNode_begin(UA_Server *server, const UA_NodeClass nodeClass,
const UA_NodeId requestedNewNodeId,
const UA_NodeId parentNodeId,
const UA_NodeId referenceTypeId,
const UA_QualifiedName browseName,
const UA_NodeId typeDefinition,
const void *attr, const UA_DataType *attributeType,
void *nodeContext, UA_NodeId *outNewNodeId) {
UA_AddNodesItem item;
UA_AddNodesItem_init(&item);
item.nodeClass = nodeClass;
item.requestedNewNodeId.nodeId = requestedNewNodeId;
item.browseName = browseName;
item.typeDefinition.nodeId = typeDefinition;
item.nodeAttributes.encoding = UA_EXTENSIONOBJECT_DECODED_NODELETE;
item.nodeAttributes.content.decoded.type = attributeType;
item.nodeAttributes.content.decoded.data = (void*)(uintptr_t)attr;
UA_LOCK(server->serviceMutex);
UA_StatusCode retval = Operation_addNode_begin(server, &server->adminSession, nodeContext, &item,
&parentNodeId, &referenceTypeId, outNewNodeId);
UA_UNLOCK(server->serviceMutex);
return retval;
}
UA_StatusCode
UA_Server_addNode_finish(UA_Server *server, const UA_NodeId nodeId) {
UA_LOCK(server->serviceMutex);
UA_StatusCode retval = AddNode_finish(server, &server->adminSession, &nodeId);
UA_UNLOCK(server->serviceMutex);
return retval;
}
/****************/
/* Delete Nodes */
/****************/
static void
Operation_deleteReference(UA_Server *server, UA_Session *session, void *context,
const UA_DeleteReferencesItem *item, UA_StatusCode *retval);
/* Remove references to this node (in the other nodes) */
static void
removeIncomingReferences(UA_Server *server, UA_Session *session,
const UA_Node *node) {
UA_DeleteReferencesItem item;
UA_DeleteReferencesItem_init(&item);
item.targetNodeId.nodeId = node->nodeId;
item.deleteBidirectional = false;
UA_StatusCode dummy;
for(size_t i = 0; i < node->referencesSize; ++i) {
UA_NodeReferenceKind *refs = &node->references[i];
item.isForward = refs->isInverse;
item.referenceTypeId = refs->referenceTypeId;
for(size_t j = 0; j < refs->targetIdsSize; ++j) {
item.sourceNodeId = refs->targetIds[j].nodeId;
Operation_deleteReference(server, session, NULL, &item, &dummy);
}
}
}
/* A node can only be deleted if it has at most one incoming hierarchical
* reference. If hierarchicalReferences is NULL, always remove. */
static UA_Boolean
multipleHierarchies(size_t hierarchicalRefsSize, UA_ExpandedNodeId *hierarchicalRefs,
const UA_Node *node) {
if(!hierarchicalRefs)
return false;
size_t incomingRefs = 0;
for(size_t i = 0; i < node->referencesSize; i++) {
const UA_NodeReferenceKind *k = &node->references[i];
if(!k->isInverse)
continue;
UA_Boolean hierarchical = false;
for(size_t j = 0; j < hierarchicalRefsSize; j++) {
if(UA_NodeId_equal(&hierarchicalRefs[j].nodeId,
&k->referenceTypeId)) {
hierarchical = true;
break;
}
}
if(!hierarchical)
continue;
incomingRefs += k->targetIdsSize;
if(incomingRefs > 1)
return true;
}
return false;
}
/* Recursively call the destructors of this node and all child nodes.
* Deconstructs the parent before its children. */
static void
recursiveDeconstructNode(UA_Server *server, UA_Session *session,
size_t hierarchicalRefsSize,
UA_ExpandedNodeId *hierarchicalRefs,
const UA_Node *node) {
/* Was the constructor called for the node? */
if(!node->constructed)
return;
/* Call the type-level destructor */
void *context = node->context; /* No longer needed after this function */
if(node->nodeClass == UA_NODECLASS_OBJECT ||
node->nodeClass == UA_NODECLASS_VARIABLE) {
const UA_Node *type = getNodeType(server, node);
if(type) {
const UA_NodeTypeLifecycle *lifecycle;
if(node->nodeClass == UA_NODECLASS_OBJECT)
lifecycle = &((const UA_ObjectTypeNode*)type)->lifecycle;
else
lifecycle = &((const UA_VariableTypeNode*)type)->lifecycle;
if(lifecycle->destructor)
lifecycle->destructor(server,
&session->sessionId, session->sessionHandle,
&type->nodeId, type->context,
&node->nodeId, &context);
UA_Nodestore_releaseNode(server->nsCtx, type);
}
}
/* Call the global destructor */
if(server->config.nodeLifecycle.destructor)
server->config.nodeLifecycle.destructor(server, &session->sessionId,
session->sessionHandle,
&node->nodeId, context);
/* Set the constructed flag to false */
UA_Server_editNode(server, &server->adminSession, &node->nodeId,
(UA_EditNodeCallback)setDeconstructedNode, context);
/* Browse to get all children of the node */
UA_BrowseDescription bd;
UA_BrowseDescription_init(&bd);
bd.nodeId = node->nodeId;
bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_AGGREGATES);
bd.includeSubtypes = true;
bd.browseDirection = UA_BROWSEDIRECTION_FORWARD;
UA_BrowseResult br;
UA_BrowseResult_init(&br);
UA_UInt32 maxrefs = 0;
Operation_Browse(server, session, &maxrefs, &bd, &br);
if(br.statusCode != UA_STATUSCODE_GOOD)
return;
/* Deconstruct every child node */
for(size_t i = 0; i < br.referencesSize; ++i) {
UA_ReferenceDescription *rd = &br.references[i];
const UA_Node *child = UA_Nodestore_getNode(server->nsCtx, &rd->nodeId.nodeId);
if(!child)
continue;
/* Only delete child nodes that have no other parent */
if(!multipleHierarchies(hierarchicalRefsSize, hierarchicalRefs, child))
recursiveDeconstructNode(server, session, hierarchicalRefsSize,
hierarchicalRefs, child);
UA_Nodestore_releaseNode(server->nsCtx, child);
}
UA_BrowseResult_deleteMembers(&br);
}
static void
recursiveDeleteNode(UA_Server *server, UA_Session *session,
size_t hierarchicalRefsSize,
UA_ExpandedNodeId *hierarchicalRefs,
const UA_Node *node, UA_Boolean removeTargetRefs) {
/* Browse to get all children of the node */
UA_BrowseDescription bd;
UA_BrowseDescription_init(&bd);
bd.nodeId = node->nodeId;
bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_AGGREGATES);
bd.includeSubtypes = true;
bd.browseDirection = UA_BROWSEDIRECTION_FORWARD;
UA_BrowseResult br;
UA_BrowseResult_init(&br);
UA_UInt32 maxrefs = 0;
Operation_Browse(server, session, &maxrefs, &bd, &br);
if(br.statusCode != UA_STATUSCODE_GOOD)
return;
/* Remove every child */
for(size_t i = 0; i < br.referencesSize; ++i) {
UA_ReferenceDescription *rd = &br.references[i];
/* Check for self-reference to avoid endless loop */
if(UA_NodeId_equal(&node->nodeId, &rd->nodeId.nodeId))
continue;
const UA_Node *child = UA_Nodestore_getNode(server->nsCtx, &rd->nodeId.nodeId);
if(!child)
continue;
/* Only delete child nodes that have no other parent */
if(!multipleHierarchies(hierarchicalRefsSize, hierarchicalRefs, child))
recursiveDeleteNode(server, session, hierarchicalRefsSize,
hierarchicalRefs, child, true);
UA_Nodestore_releaseNode(server->nsCtx, child);
}
UA_BrowseResult_deleteMembers(&br);
if(removeTargetRefs)
removeIncomingReferences(server, session, node);
UA_Nodestore_removeNode(server->nsCtx, &node->nodeId);
}
static void
deleteNodeOperation(UA_Server *server, UA_Session *session, void *context,
const UA_DeleteNodesItem *item, UA_StatusCode *result) {
/* Do not check access for server */
if(session != &server->adminSession && server->config.accessControl.allowDeleteNode) {
UA_UNLOCK(server->serviceMutex);
if ( !server->config.accessControl.allowDeleteNode(server, &server->config.accessControl,
&session->sessionId, session->sessionHandle, item)) {
UA_LOCK(server->serviceMutex);
*result = UA_STATUSCODE_BADUSERACCESSDENIED;
return;
}
UA_LOCK(server->serviceMutex);
}
const UA_Node *node = UA_Nodestore_getNode(server->nsCtx, &item->nodeId);
if(!node) {
*result = UA_STATUSCODE_BADNODEIDUNKNOWN;
return;
}
if(UA_Node_hasSubTypeOrInstances(node)) {
UA_LOG_INFO_SESSION(&server->config.logger, session,
"Delete Nodes: Cannot delete a type node "
"with active instances or subtypes");
UA_Nodestore_releaseNode(server->nsCtx, node);
*result = UA_STATUSCODE_BADINTERNALERROR;
return;
}
/* TODO: Check if the information model consistency is violated */
/* TODO: Check if the node is a mandatory child of a parent */
/* A node can be referenced with hierarchical references from several
* parents in the information model. (But not in a circular way.) The
* hierarchical references are checked to see if a node can be deleted.
* Getting the type hierarchy can fail in case of low RAM. In that case the
* nodes are always deleted. */
UA_ExpandedNodeId *hierarchicalRefs = NULL;
size_t hierarchicalRefsSize = 0;
UA_NodeId hr = UA_NODEID_NUMERIC(0, UA_NS0ID_HIERARCHICALREFERENCES);
browseRecursive(server, 1, &hr, 1, &subtypeId, UA_BROWSEDIRECTION_FORWARD, true,
&hierarchicalRefsSize, &hierarchicalRefs);
if(!hierarchicalRefs) {
UA_LOG_WARNING_SESSION(&server->config.logger, session,
"Delete Nodes: Cannot test for hierarchical "
"references. Deleting the node and all child nodes.");
}
recursiveDeconstructNode(server, session, hierarchicalRefsSize, hierarchicalRefs, node);
recursiveDeleteNode(server, session, hierarchicalRefsSize, hierarchicalRefs, node,
item->deleteTargetReferences);
UA_Array_delete(hierarchicalRefs, hierarchicalRefsSize, &UA_TYPES[UA_TYPES_EXPANDEDNODEID]);
UA_Nodestore_releaseNode(server->nsCtx, node);
}
void Service_DeleteNodes(UA_Server *server, UA_Session *session,
const UA_DeleteNodesRequest *request,
UA_DeleteNodesResponse *response) {
UA_LOG_DEBUG_SESSION(&server->config.logger, session,
"Processing DeleteNodesRequest");
UA_LOCK_ASSERT(server->serviceMutex, 1);
if(server->config.maxNodesPerNodeManagement != 0 &&
request->nodesToDeleteSize > server->config.maxNodesPerNodeManagement) {
response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYOPERATIONS;
return;
}
response->responseHeader.serviceResult =
UA_Server_processServiceOperations(server, session,
(UA_ServiceOperation)deleteNodeOperation,
NULL, &request->nodesToDeleteSize,
&UA_TYPES[UA_TYPES_DELETENODESITEM],
&response->resultsSize, &UA_TYPES[UA_TYPES_STATUSCODE]);
}
UA_StatusCode
UA_Server_deleteNode(UA_Server *server, const UA_NodeId nodeId,
UA_Boolean deleteReferences) {
UA_LOCK(server->serviceMutex)
UA_StatusCode retval = deleteNode(server, nodeId, deleteReferences);
UA_UNLOCK(server->serviceMutex);
return retval;
}
UA_StatusCode
deleteNode(UA_Server *server, const UA_NodeId nodeId,
UA_Boolean deleteReferences) {
UA_DeleteNodesItem item;
item.deleteTargetReferences = deleteReferences;
item.nodeId = nodeId;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
deleteNodeOperation(server, &server->adminSession, NULL, &item, &retval);
return retval;
}
/******************/
/* Add References */
/******************/
static UA_StatusCode
addOneWayReference(UA_Server *server, UA_Session *session,
UA_Node *node, const UA_AddReferencesItem *item) {
return UA_Node_addReference(node, item);
}
static UA_StatusCode
deleteOneWayReference(UA_Server *server, UA_Session *session, UA_Node *node,
const UA_DeleteReferencesItem *item) {
return UA_Node_deleteReference(node, item);
}
static void
Operation_addReference(UA_Server *server, UA_Session *session, void *context,
const UA_AddReferencesItem *item, UA_StatusCode *retval) {
/* Do not check access for server */
if(session != &server->adminSession && server->config.accessControl.allowAddReference) {
UA_UNLOCK(server->serviceMutex);
if (!server->config.accessControl.
allowAddReference(server, &server->config.accessControl,
&session->sessionId, session->sessionHandle, item)) {
UA_LOCK(server->serviceMutex);
*retval = UA_STATUSCODE_BADUSERACCESSDENIED;
return;
}
UA_LOCK(server->serviceMutex);
}
/* Currently no expandednodeids are allowed */
if(item->targetServerUri.length > 0) {
*retval = UA_STATUSCODE_BADNOTIMPLEMENTED;
return;
}
/* Add the first direction */
*retval = UA_Server_editNode(server, session, &item->sourceNodeId,
(UA_EditNodeCallback)addOneWayReference,
/* cast away const because callback uses const anyway */
(UA_AddReferencesItem *)(uintptr_t)item);
UA_Boolean firstExisted = false;
if(*retval == UA_STATUSCODE_BADDUPLICATEREFERENCENOTALLOWED) {
*retval = UA_STATUSCODE_GOOD;
firstExisted = true;
} else if(*retval != UA_STATUSCODE_GOOD)
return;
/* Add the second direction */
UA_AddReferencesItem secondItem;
UA_AddReferencesItem_init(&secondItem);
secondItem.sourceNodeId = item->targetNodeId.nodeId;
secondItem.referenceTypeId = item->referenceTypeId;
secondItem.isForward = !item->isForward;
secondItem.targetNodeId.nodeId = item->sourceNodeId;
/* keep default secondItem.targetNodeClass = UA_NODECLASS_UNSPECIFIED */
*retval = UA_Server_editNode(server, session, &secondItem.sourceNodeId,
(UA_EditNodeCallback)addOneWayReference, &secondItem);
/* remove reference if the second direction failed */
UA_Boolean secondExisted = false;
if(*retval == UA_STATUSCODE_BADDUPLICATEREFERENCENOTALLOWED) {
*retval = UA_STATUSCODE_GOOD;
secondExisted = true;
} else if(*retval != UA_STATUSCODE_GOOD && !firstExisted) {
UA_DeleteReferencesItem deleteItem;
deleteItem.sourceNodeId = item->sourceNodeId;
deleteItem.referenceTypeId = item->referenceTypeId;
deleteItem.isForward = item->isForward;
deleteItem.targetNodeId = item->targetNodeId;
deleteItem.deleteBidirectional = false;
/* ignore returned status code */
UA_Server_editNode(server, session, &item->sourceNodeId,
(UA_EditNodeCallback)deleteOneWayReference, &deleteItem);
}
/* Calculate common duplicate reference not allowed result and set bad result
* if BOTH directions already existed */
if(firstExisted && secondExisted)
*retval = UA_STATUSCODE_BADDUPLICATEREFERENCENOTALLOWED;
}
void Service_AddReferences(UA_Server *server, UA_Session *session,
const UA_AddReferencesRequest *request,
UA_AddReferencesResponse *response) {
UA_LOG_DEBUG_SESSION(&server->config.logger, session,
"Processing AddReferencesRequest");
UA_LOCK_ASSERT(server->serviceMutex, 1);
if(server->config.maxNodesPerNodeManagement != 0 &&
request->referencesToAddSize > server->config.maxNodesPerNodeManagement) {
response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYOPERATIONS;
return;
}
response->responseHeader.serviceResult =
UA_Server_processServiceOperations(server, session,
(UA_ServiceOperation)Operation_addReference,
NULL, &request->referencesToAddSize,
&UA_TYPES[UA_TYPES_ADDREFERENCESITEM],
&response->resultsSize, &UA_TYPES[UA_TYPES_STATUSCODE]);
}
UA_StatusCode
UA_Server_addReference(UA_Server *server, const UA_NodeId sourceId,
const UA_NodeId refTypeId,
const UA_ExpandedNodeId targetId,
UA_Boolean isForward) {
UA_AddReferencesItem item;
UA_AddReferencesItem_init(&item);
item.sourceNodeId = sourceId;
item.referenceTypeId = refTypeId;
item.isForward = isForward;
item.targetNodeId = targetId;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_LOCK(server->serviceMutex);
Operation_addReference(server, &server->adminSession, NULL, &item, &retval);
UA_UNLOCK(server->serviceMutex)
return retval;
}
/*********************/
/* Delete References */
/*********************/
static void
Operation_deleteReference(UA_Server *server, UA_Session *session, void *context,
const UA_DeleteReferencesItem *item, UA_StatusCode *retval) {
/* Do not check access for server */
if(session != &server->adminSession && server->config.accessControl.allowDeleteReference) {
UA_UNLOCK(server->serviceMutex);
if (!server->config.accessControl.
allowDeleteReference(server, &server->config.accessControl,
&session->sessionId, session->sessionHandle, item)){
UA_LOCK(server->serviceMutex);
*retval = UA_STATUSCODE_BADUSERACCESSDENIED;
return;
}
UA_LOCK(server->serviceMutex)
}
// TODO: Check consistency constraints, remove the references.
*retval = UA_Server_editNode(server, session, &item->sourceNodeId,
(UA_EditNodeCallback)deleteOneWayReference,
/* cast away const qualifier because callback uses it anyway */
(UA_DeleteReferencesItem *)(uintptr_t)item);
if(*retval != UA_STATUSCODE_GOOD)
return;
if(!item->deleteBidirectional || item->targetNodeId.serverIndex != 0)
return;
UA_DeleteReferencesItem secondItem;
UA_DeleteReferencesItem_init(&secondItem);
secondItem.isForward = !item->isForward;
secondItem.sourceNodeId = item->targetNodeId.nodeId;
secondItem.targetNodeId.nodeId = item->sourceNodeId;
secondItem.referenceTypeId = item->referenceTypeId;
*retval = UA_Server_editNode(server, session, &secondItem.sourceNodeId,
(UA_EditNodeCallback)deleteOneWayReference,
&secondItem);
}
void
Service_DeleteReferences(UA_Server *server, UA_Session *session,
const UA_DeleteReferencesRequest *request,
UA_DeleteReferencesResponse *response) {
UA_LOG_DEBUG_SESSION(&server->config.logger, session,
"Processing DeleteReferencesRequest");
UA_LOCK_ASSERT(server->serviceMutex, 1);
if(server->config.maxNodesPerNodeManagement != 0 &&
request->referencesToDeleteSize > server->config.maxNodesPerNodeManagement) {
response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYOPERATIONS;
return;
}
response->responseHeader.serviceResult =
UA_Server_processServiceOperations(server, session,
(UA_ServiceOperation)Operation_deleteReference,
NULL, &request->referencesToDeleteSize,
&UA_TYPES[UA_TYPES_DELETEREFERENCESITEM],
&response->resultsSize, &UA_TYPES[UA_TYPES_STATUSCODE]);
}
UA_StatusCode
UA_Server_deleteReference(UA_Server *server, const UA_NodeId sourceNodeId,
const UA_NodeId referenceTypeId, UA_Boolean isForward,
const UA_ExpandedNodeId targetNodeId,
UA_Boolean deleteBidirectional) {
UA_DeleteReferencesItem item;
item.sourceNodeId = sourceNodeId;
item.referenceTypeId = referenceTypeId;
item.isForward = isForward;
item.targetNodeId = targetNodeId;
item.deleteBidirectional = deleteBidirectional;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_LOCK(server->serviceMutex);
Operation_deleteReference(server, &server->adminSession, NULL, &item, &retval);
UA_UNLOCK(server->serviceMutex);
return retval;
}
/**********************/
/* Set Value Callback */
/**********************/
static UA_StatusCode
setValueCallback(UA_Server *server, UA_Session *session,
UA_VariableNode *node, const UA_ValueCallback *callback) {
if(node->nodeClass != UA_NODECLASS_VARIABLE)
return UA_STATUSCODE_BADNODECLASSINVALID;
node->value.data.callback = *callback;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_Server_setVariableNode_valueCallback(UA_Server *server,
const UA_NodeId nodeId,
const UA_ValueCallback callback) {
UA_LOCK(server->serviceMutex);
UA_StatusCode retval = UA_Server_editNode(server, &server->adminSession, &nodeId,
(UA_EditNodeCallback)setValueCallback,
/* cast away const because callback uses const anyway */
(UA_ValueCallback *)(uintptr_t) &callback);
UA_UNLOCK(server->serviceMutex);
return retval;
}
/***************************************************/
/* Special Handling of Variables with Data Sources */
/***************************************************/
UA_StatusCode
UA_Server_addDataSourceVariableNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
const UA_NodeId parentNodeId, const UA_NodeId referenceTypeId,
const UA_QualifiedName browseName, const UA_NodeId typeDefinition,
const UA_VariableAttributes attr, const UA_DataSource dataSource,
void *nodeContext, UA_NodeId *outNewNodeId) {
UA_AddNodesItem item;
UA_AddNodesItem_init(&item);
item.nodeClass = UA_NODECLASS_VARIABLE;
item.requestedNewNodeId.nodeId = requestedNewNodeId;
item.browseName = browseName;
UA_ExpandedNodeId typeDefinitionId;
UA_ExpandedNodeId_init(&typeDefinitionId);
typeDefinitionId.nodeId = typeDefinition;
item.typeDefinition = typeDefinitionId;
item.nodeAttributes.encoding = UA_EXTENSIONOBJECT_DECODED_NODELETE;
item.nodeAttributes.content.decoded.data = (void*)(uintptr_t)&attr;
item.nodeAttributes.content.decoded.type = &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES];
UA_NodeId newNodeId;
if(!outNewNodeId) {
newNodeId = UA_NODEID_NULL;
outNewNodeId = &newNodeId;
}
UA_LOCK(server->serviceMutex);
/* Create the node and add it to the nodestore */
UA_StatusCode retval = AddNode_raw(server, &server->adminSession, nodeContext,
&item, outNewNodeId);
if(retval != UA_STATUSCODE_GOOD)
goto cleanup;
/* Set the data source */
retval = setVariableNode_dataSource(server, *outNewNodeId, dataSource);
if(retval != UA_STATUSCODE_GOOD)
goto cleanup;
/* Typecheck and add references to parent and type definition */
retval = AddNode_addRefs(server, &server->adminSession, outNewNodeId, &parentNodeId,
&referenceTypeId, &typeDefinition);
if(retval != UA_STATUSCODE_GOOD)
goto cleanup;
/* Call the constructors */
retval = AddNode_finish(server, &server->adminSession, outNewNodeId);
cleanup:
UA_UNLOCK(server->serviceMutex);
if(outNewNodeId == &newNodeId)
UA_NodeId_deleteMembers(&newNodeId);
return retval;
}
static UA_StatusCode
setDataSource(UA_Server *server, UA_Session *session,
UA_VariableNode* node, const UA_DataSource *dataSource) {
if(node->nodeClass != UA_NODECLASS_VARIABLE)
return UA_STATUSCODE_BADNODECLASSINVALID;
if(node->valueSource == UA_VALUESOURCE_DATA)
UA_DataValue_deleteMembers(&node->value.data.value);
node->value.dataSource = *dataSource;
node->valueSource = UA_VALUESOURCE_DATASOURCE;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
setVariableNode_dataSource(UA_Server *server, const UA_NodeId nodeId,
const UA_DataSource dataSource) {
return UA_Server_editNode(server, &server->adminSession, &nodeId,
(UA_EditNodeCallback)setDataSource,
/* casting away const because callback casts it back anyway */
(UA_DataSource *) (uintptr_t)&dataSource);
}
UA_StatusCode
UA_Server_setVariableNode_dataSource(UA_Server *server, const UA_NodeId nodeId,
const UA_DataSource dataSource) {
UA_LOCK(server->serviceMutex);
UA_StatusCode retval = setVariableNode_dataSource(server, nodeId, dataSource);
UA_UNLOCK(server->serviceMutex);
return retval;
}
/************************************/
/* Special Handling of Method Nodes */
/************************************/
#ifdef UA_ENABLE_METHODCALLS
static const UA_NodeId hasproperty = {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASPROPERTY}};
static const UA_NodeId propertytype = {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_PROPERTYTYPE}};
static UA_StatusCode
UA_Server_addMethodNodeEx_finish(UA_Server *server, const UA_NodeId nodeId, UA_MethodCallback method,
const size_t inputArgumentsSize, const UA_Argument *inputArguments,
const UA_NodeId inputArgumentsRequestedNewNodeId,
UA_NodeId *inputArgumentsOutNewNodeId,
const size_t outputArgumentsSize, const UA_Argument *outputArguments,
const UA_NodeId outputArgumentsRequestedNewNodeId,
UA_NodeId *outputArgumentsOutNewNodeId) {
/* Browse to see which argument nodes exist */
UA_BrowseDescription bd;
UA_BrowseDescription_init(&bd);
bd.nodeId = nodeId;
bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY);
bd.includeSubtypes = false;
bd.browseDirection = UA_BROWSEDIRECTION_FORWARD;
bd.nodeClassMask = UA_NODECLASS_VARIABLE;
bd.resultMask = UA_BROWSERESULTMASK_BROWSENAME;
UA_BrowseResult br;
UA_BrowseResult_init(&br);
UA_UInt32 maxrefs = 0;
Operation_Browse(server, &server->adminSession, &maxrefs, &bd, &br);
UA_StatusCode retval = br.statusCode;
if(retval != UA_STATUSCODE_GOOD) {
deleteNode(server, nodeId, true);
UA_BrowseResult_deleteMembers(&br);
return retval;
}
/* Filter out the argument nodes */
UA_NodeId inputArgsId = UA_NODEID_NULL;
UA_NodeId outputArgsId = UA_NODEID_NULL;
const UA_QualifiedName inputArgsName = UA_QUALIFIEDNAME(0, "InputArguments");
const UA_QualifiedName outputArgsName = UA_QUALIFIEDNAME(0, "OutputArguments");
for(size_t i = 0; i < br.referencesSize; i++) {
UA_ReferenceDescription *rd = &br.references[i];
if(rd->browseName.namespaceIndex == 0 &&
UA_String_equal(&rd->browseName.name, &inputArgsName.name))
inputArgsId = rd->nodeId.nodeId;
else if(rd->browseName.namespaceIndex == 0 &&
UA_String_equal(&rd->browseName.name, &outputArgsName.name))
outputArgsId = rd->nodeId.nodeId;
}
/* Add the Input Arguments VariableNode */
if(inputArgumentsSize > 0 && UA_NodeId_isNull(&inputArgsId)) {
UA_VariableAttributes attr = UA_VariableAttributes_default;
char *name = "InputArguments";
attr.displayName = UA_LOCALIZEDTEXT("", name);
attr.dataType = UA_TYPES[UA_TYPES_ARGUMENT].typeId;
attr.valueRank = UA_VALUERANK_ONE_DIMENSION;
UA_UInt32 inputArgsSize32 = (UA_UInt32)inputArgumentsSize;
attr.arrayDimensions = &inputArgsSize32;
attr.arrayDimensionsSize = 1;
UA_Variant_setArray(&attr.value, (void *)(uintptr_t)inputArguments,
inputArgumentsSize, &UA_TYPES[UA_TYPES_ARGUMENT]);
retval = addNode(server, UA_NODECLASS_VARIABLE, &inputArgumentsRequestedNewNodeId,
&nodeId, &hasproperty, UA_QUALIFIEDNAME(0, name),
&propertytype, (const UA_NodeAttributes*)&attr,
&UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES],
NULL, &inputArgsId);
if(retval != UA_STATUSCODE_GOOD)
goto error;
}
/* Add the Output Arguments VariableNode */
if(outputArgumentsSize > 0 && UA_NodeId_isNull(&outputArgsId)) {
UA_VariableAttributes attr = UA_VariableAttributes_default;
char *name = "OutputArguments";
attr.displayName = UA_LOCALIZEDTEXT("", name);
attr.dataType = UA_TYPES[UA_TYPES_ARGUMENT].typeId;
attr.valueRank = UA_VALUERANK_ONE_DIMENSION;
UA_UInt32 outputArgsSize32 = (UA_UInt32)outputArgumentsSize;
attr.arrayDimensions = &outputArgsSize32;
attr.arrayDimensionsSize = 1;
UA_Variant_setArray(&attr.value, (void *)(uintptr_t)outputArguments,
outputArgumentsSize, &UA_TYPES[UA_TYPES_ARGUMENT]);
retval = addNode(server, UA_NODECLASS_VARIABLE, &outputArgumentsRequestedNewNodeId,
&nodeId, &hasproperty, UA_QUALIFIEDNAME(0, name),
&propertytype, (const UA_NodeAttributes*)&attr,
&UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES],
NULL, &outputArgsId);
if(retval != UA_STATUSCODE_GOOD)
goto error;
}
retval = setMethodNode_callback(server, nodeId, method);
if(retval != UA_STATUSCODE_GOOD)
goto error;
/* Call finish to add the parent reference */
retval = AddNode_finish(server, &server->adminSession, &nodeId);
if(retval != UA_STATUSCODE_GOOD)
goto error;
if(inputArgumentsOutNewNodeId != NULL) {
UA_NodeId_copy(&inputArgsId, inputArgumentsOutNewNodeId);
}
if(outputArgumentsOutNewNodeId != NULL) {
UA_NodeId_copy(&outputArgsId, outputArgumentsOutNewNodeId);
}
UA_BrowseResult_deleteMembers(&br);
return retval;
error:
deleteNode(server, nodeId, true);
deleteNode(server, inputArgsId, true);
deleteNode(server, outputArgsId, true);
UA_BrowseResult_deleteMembers(&br);
return retval;
}
UA_StatusCode
UA_Server_addMethodNode_finish(UA_Server *server, const UA_NodeId nodeId,
UA_MethodCallback method,
size_t inputArgumentsSize, const UA_Argument* inputArguments,
size_t outputArgumentsSize, const UA_Argument* outputArguments) {
UA_LOCK(server->serviceMutex)
UA_StatusCode retval = UA_Server_addMethodNodeEx_finish(server, nodeId, method,
inputArgumentsSize, inputArguments, UA_NODEID_NULL, NULL,
outputArgumentsSize, outputArguments, UA_NODEID_NULL, NULL);
UA_UNLOCK(server->serviceMutex)
return retval;
}
UA_StatusCode
UA_Server_addMethodNodeEx(UA_Server *server, const UA_NodeId requestedNewNodeId,
const UA_NodeId parentNodeId,
const UA_NodeId referenceTypeId,
const UA_QualifiedName browseName,
const UA_MethodAttributes attr, UA_MethodCallback method,
size_t inputArgumentsSize, const UA_Argument *inputArguments,
const UA_NodeId inputArgumentsRequestedNewNodeId,
UA_NodeId *inputArgumentsOutNewNodeId,
size_t outputArgumentsSize, const UA_Argument *outputArguments,
const UA_NodeId outputArgumentsRequestedNewNodeId,
UA_NodeId *outputArgumentsOutNewNodeId,
void *nodeContext, UA_NodeId *outNewNodeId) {
UA_AddNodesItem item;
UA_AddNodesItem_init(&item);
item.nodeClass = UA_NODECLASS_METHOD;
item.requestedNewNodeId.nodeId = requestedNewNodeId;
item.browseName = browseName;
item.nodeAttributes.encoding = UA_EXTENSIONOBJECT_DECODED_NODELETE;
item.nodeAttributes.content.decoded.data = (void*)(uintptr_t)&attr;
item.nodeAttributes.content.decoded.type = &UA_TYPES[UA_TYPES_METHODATTRIBUTES];
UA_NodeId newId;
if(!outNewNodeId) {
UA_NodeId_init(&newId);
outNewNodeId = &newId;
}
UA_LOCK(server->serviceMutex);
UA_StatusCode retval = Operation_addNode_begin(server, &server->adminSession,
nodeContext, &item, &parentNodeId,
&referenceTypeId, outNewNodeId);
if(retval != UA_STATUSCODE_GOOD) {
UA_UNLOCK(server->serviceMutex);
return retval;
}
retval = UA_Server_addMethodNodeEx_finish(server, *outNewNodeId, method,
inputArgumentsSize, inputArguments,
inputArgumentsRequestedNewNodeId,
inputArgumentsOutNewNodeId,
outputArgumentsSize, outputArguments,
outputArgumentsRequestedNewNodeId,
outputArgumentsOutNewNodeId);
UA_UNLOCK(server->serviceMutex);
if(outNewNodeId == &newId)
UA_NodeId_deleteMembers(&newId);
return retval;
}
static UA_StatusCode
editMethodCallback(UA_Server *server, UA_Session* session,
UA_Node* node, void* handle) {
if(node->nodeClass != UA_NODECLASS_METHOD)
return UA_STATUSCODE_BADNODECLASSINVALID;
UA_MethodNode *mnode = (UA_MethodNode*) node;
mnode->method = (UA_MethodCallback)(uintptr_t)handle;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
setMethodNode_callback(UA_Server *server,
const UA_NodeId methodNodeId,
UA_MethodCallback methodCallback) {
return UA_Server_editNode(server, &server->adminSession, &methodNodeId,
(UA_EditNodeCallback)editMethodCallback,
(void*)(uintptr_t)methodCallback);
}
UA_StatusCode
UA_Server_setMethodNode_callback(UA_Server *server,
const UA_NodeId methodNodeId,
UA_MethodCallback methodCallback) {
UA_LOCK(server->serviceMutex);
UA_StatusCode retVal = setMethodNode_callback(server, methodNodeId, methodCallback);
UA_UNLOCK(server->serviceMutex);
return retVal;
}
#endif
/************************/
/* Lifecycle Management */
/************************/
static UA_StatusCode
setNodeTypeLifecycle(UA_Server *server, UA_Session *session,
UA_Node* node, UA_NodeTypeLifecycle *lifecycle) {
if(node->nodeClass == UA_NODECLASS_OBJECTTYPE) {
UA_ObjectTypeNode *ot = (UA_ObjectTypeNode*)node;
ot->lifecycle = *lifecycle;
return UA_STATUSCODE_GOOD;
}
if(node->nodeClass == UA_NODECLASS_VARIABLETYPE) {
UA_VariableTypeNode *vt = (UA_VariableTypeNode*)node;
vt->lifecycle = *lifecycle;
return UA_STATUSCODE_GOOD;
}
return UA_STATUSCODE_BADNODECLASSINVALID;
}
UA_StatusCode
UA_Server_setNodeTypeLifecycle(UA_Server *server, UA_NodeId nodeId,
UA_NodeTypeLifecycle lifecycle) {
return UA_Server_editNode(server, &server->adminSession, &nodeId,
(UA_EditNodeCallback)setNodeTypeLifecycle,
&lifecycle);
}