/* 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 2021 (c) Christian von Arnim, ISW University of Stuttgart  (for VDW and umati)
 *    Copyright 2017 (c) Henrik Norrman
 *    Copyright 2021 (c) Fraunhofer IOSB (Author: Andreas Ebner)
 *    Copyright 2026 (c) o6 Automation GmbH (Author: Andreas Ebner)
 */

#include "ua_server_internal.h"
#include "ua_services.h"

/*********************/
/* Edit Node Context */
/*********************/

UA_StatusCode
UA_Server_getNodeContext(UA_Server *server, UA_NodeId nodeId,
                         void **nodeContext) {
    lockServer(server);
    UA_StatusCode retval = getNodeContext(server, nodeId, nodeContext);
    unlockServer(server);
    return retval;
}

UA_StatusCode
getNodeContext(UA_Server *server, UA_NodeId nodeId,
               void **nodeContext) {
    const UA_Node *node = UA_NODESTORE_GET(server, &nodeId);
    if(!node)
        return UA_STATUSCODE_BADNODEIDUNKNOWN;
    *nodeContext = node->head.context;
    UA_NODESTORE_RELEASE(server, node);
    return UA_STATUSCODE_GOOD;
}

static UA_StatusCode
setDeconstructedNode(UA_Server *server, UA_Session *session,
                     UA_NodeHead *head, void *context) {
    head->constructed = false;
    return UA_STATUSCODE_GOOD;
}

static UA_StatusCode
setConstructedNodeContext(UA_Server *server, UA_Session *session,
                          UA_NodeHead *head, void *context) {
    head->context = context;
    head->constructed = true;
    return UA_STATUSCODE_GOOD;
}

UA_StatusCode
setNodeContext(UA_Server *server, UA_NodeId nodeId, void *nodeContext) {
    UA_Node *node =
        UA_NODESTORE_GET_EDIT_SELECTIVE(server, &nodeId, UA_NODEATTRIBUTESMASK_NONE,
                                        UA_REFERENCETYPESET_NONE,
                                        UA_BROWSEDIRECTION_INVALID);
    if(!node)
        return UA_STATUSCODE_BADNODEIDINVALID;
    node->head.context = nodeContext;
    UA_NODESTORE_RELEASE(server, node);
    return UA_STATUSCODE_GOOD;
}

UA_StatusCode
UA_Server_setNodeContext(UA_Server *server, UA_NodeId nodeId,
                         void *nodeContext) {
    lockServer(server);
    UA_StatusCode retval = setNodeContext(server, nodeId, nodeContext);
    unlockServer(server);
    return retval;
}

static UA_StatusCode
checkSetIsDynamicVariable(UA_Server *server, UA_Session *session,
                          const UA_NodeId *nodeId);

/**********************/
/* Consistency Checks */
/**********************/

#define UA_PARENT_REFERENCES_COUNT 2

static const UA_NodeId parentReferences[UA_PARENT_REFERENCES_COUNT] = {
    {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASSUBTYPE}},
    {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASCOMPONENT}}
};

static void
logAddNode(const UA_Logger *logger, UA_Session *session,
           const UA_NodeId *nodeId, const char *msg) {
    UA_LOG_INFO_SESSION(logger, session, "AddNode (%N): %s", *nodeId, msg);
}

/* 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, const UA_NodeHead *head,
                     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((head->nodeClass == UA_NODECLASS_OBJECT ||
        head->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_GET(server, parentNodeId);
    if(!parent) {
        UA_LOG_INFO_SESSION(server->config.logging, session,
                            "AddNode (%N): Parent node %N not found",
                            head->nodeId, *parentNodeId);
        return UA_STATUSCODE_BADPARENTNODEIDINVALID;
    }

    UA_NodeClass parentNodeClass = parent->head.nodeClass;
    UA_NODESTORE_RELEASE(server, parent);

    /* Check the referencetype exists */
    const UA_Node *referenceType = UA_NODESTORE_GET(server, referenceTypeId);
    if(!referenceType) {
        UA_LOG_INFO_SESSION(server->config.logging, session,
                            "AddNode (%N): Reference type %N to the parent not found",
                            head->nodeId, *referenceTypeId);
        return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
    }

    /* Check if the referencetype is a reference type node */
    if(referenceType->head.nodeClass != UA_NODECLASS_REFERENCETYPE) {
        UA_LOG_INFO_SESSION(server->config.logging, session,
                            "AddNode (%N): Reference type %N to the parent is "
                            "not a ReferenceTypeNode", head->nodeId, *referenceTypeId);
        UA_NODESTORE_RELEASE(server, referenceType);
        return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
    }

    /* Check that the reference type is not abstract */
    UA_Boolean referenceTypeIsAbstract = referenceType->referenceTypeNode.isAbstract;
    UA_NODESTORE_RELEASE(server, referenceType);
    if(referenceTypeIsAbstract == true) {
        UA_LOG_INFO_SESSION(server->config.logging, session,
                            "AddNode (%N): Reference type %N to the parent is abstract",
                            head->nodeId, *referenceTypeId);
        return UA_STATUSCODE_BADREFERENCENOTALLOWED;
    }

    /* Check hassubtype relation for type nodes */
    if(head->nodeClass == UA_NODECLASS_DATATYPE ||
       head->nodeClass == UA_NODECLASS_VARIABLETYPE ||
       head->nodeClass == UA_NODECLASS_OBJECTTYPE ||
       head->nodeClass == UA_NODECLASS_REFERENCETYPE) {
        /* Type needs hassubtype reference to the supertype */
        if(referenceType->referenceTypeNode.referenceTypeIndex !=
           UA_REFERENCETYPEINDEX_HASSUBTYPE) {
            logAddNode(server->config.logging, session, &head->nodeId,
                       "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 != head->nodeClass) {
            logAddNode(server->config.logging, session, &head->nodeId,
                       "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 */
    const UA_NodeId hierarchRefs = UA_NS0ID(HIERARCHICALREFERENCES);
    if(!isNodeInTree_singleRef(server, referenceTypeId, &hierarchRefs,
                               UA_REFERENCETYPEINDEX_HASSUBTYPE)) {
        logAddNode(server->config.logging, session, &head->nodeId,
                   "Reference type to the parent is not hierarchical");
        return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
    }

    return UA_STATUSCODE_GOOD;
}

/* Only BaseDataType can have empty values. Generate a default value. */
static UA_StatusCode
setDefaultValue(UA_Server *server, const UA_VariableNode *node) {
    /* Get the DataType */
    UA_StatusCode res = UA_STATUSCODE_GOOD;
    const UA_DataType *type = UA_Server_findDataType(server, &node->dataType);
    if(!type) {
        /* No description for the DataType found. It is possible that an
         * abstract DataType is used, e.g. UInteger. Browse to see if there is a
         * non-abstract subtype that can be used for the default value.
         *
         * Look up and downwards in the hierarchy. Some data types (e.g.
         * UtcTime) are derived from a non-abstract data type. This is then used
         * for the actual value, Use the first match. */
        UA_ReferenceTypeSet refs = UA_REFTYPESET(UA_REFERENCETYPEINDEX_HASSUBTYPE);
        UA_ExpandedNodeId *typeCandidates = NULL;
        size_t typeCandidatesSize = 0;
        res = browseRecursive(server, 1, &node->dataType,
                              UA_BROWSEDIRECTION_BOTH, &refs,
                              UA_NODECLASS_DATATYPE, false,
                              &typeCandidatesSize, &typeCandidates);
        if(res != UA_STATUSCODE_GOOD)
            return res;

        for(size_t i = 0; i < typeCandidatesSize; i++) {
            /* Skip BaseDataType (Variant). This is the root of the DataType
             * hierarchy. Variables of BaseDataType can be empty. */
            if(UA_NodeId_equal(&UA_TYPES[UA_TYPES_VARIANT].typeId,
                               &typeCandidates[i].nodeId))
                continue;

            /* Skip ExtensionObject. Even though we can encode the type it is
             * abstract and nodes should not contain them as value. */
            if(UA_NodeId_equal(&UA_TYPES[UA_TYPES_EXTENSIONOBJECT].typeId,
                               &typeCandidates[i].nodeId))
                continue;

            /* Get the type encoding information */
            type = UA_Server_findDataType(server, &typeCandidates[i].nodeId);
            if(type)
                break;
        }

        UA_Array_delete(typeCandidates, typeCandidatesSize,
                        &UA_TYPES[UA_TYPES_EXPANDEDNODEID]);
        if(!type)
            return UA_STATUSCODE_BADTYPEMISMATCH;
    }

    /* Set up the value with the default content */
    UA_Variant val;
    UA_Variant_init(&val);
    if(node->valueRank < 0) {
        /* Set a scalar */
        void *data = UA_new(type);
        if(!data)
            return UA_STATUSCODE_BADOUTOFMEMORY;
        UA_Variant_setScalar(&val, data, type);
    } else {
        /* Set an array
         *
         * https://reference.opcfoundation.org/v104/Core/docs/Part3/5.6.2/#Table13
         * specifies ArrayDimensions as follows: This Attribute specifies the
         * maximum supported length of each dimension. If the maximum is unknown
         * the value shall be 0. The number of elements shall be equal to the
         * value of the ValueRank Attribute. This Attribute shall be null if
         * ValueRank <= 0.
         *
         * The (variant) values themselves cannot have ArrayDimensions with a
         * dimension length of zero. We however consider that empty arrays
         * (null-array or length zero) have implicit array dimensions [0,0,...].
         * With the appropriate number of dimensions. So they always match.
         *
         * We automatically create a null array during the node creation when
         * required for the type-checking..
         *
         * Also see the method 'compatibleValueArrayDimensions' where the
         * compatibility of the ArrayDimensions is checked. */
        UA_Variant_setArray(&val, NULL, 0, type);
    }

    /* Write the value */
    res = writeAttribute(server, &server->adminSession, &node->head.nodeId,
                         UA_ATTRIBUTEID_VALUE, &val, &UA_TYPES[UA_TYPES_VARIANT]);

    /* Clean up */
    UA_Variant_clear(&val);
    return res;
}

static UA_StatusCode
typeCheckVariableNode(UA_Server *server, UA_Session *session,
                      const UA_VariableNode *node,
                      const UA_VariableTypeNode *vt) {
    /* Check the datatype against the vt */
    if(!compatibleDataTypes(server, &node->dataType, &vt->dataType)) {
        logAddNode(server->config.logging, session, &node->head.nodeId,
                   "The value of is incompatible with "
                   "the datatype of the VariableType");
        return UA_STATUSCODE_BADTYPEMISMATCH;
    }

    /* Check valueRank against array dimensions */
    if(!compatibleValueRankArrayDimensions(server, session, node->valueRank,
                                           node->arrayDimensionsSize)) {
        logAddNode(server->config.logging, session, &node->head.nodeId,
                   "The value rank of is incompatible with its array dimensions");
        return UA_STATUSCODE_BADTYPEMISMATCH;
    }

    /* Check valueRank against the vt */
    if(!compatibleValueRanks(node->valueRank, vt->valueRank)) {
        logAddNode(server->config.logging, session, &node->head.nodeId,
                   "The value rank is incompatible "
                   "with the value rank of the VariableType");
        return UA_STATUSCODE_BADTYPEMISMATCH;
    }

    /* Check array dimensions against the vt */
    if(!compatibleArrayDimensions(vt->arrayDimensionsSize, vt->arrayDimensions,
                                  node->arrayDimensionsSize, node->arrayDimensions)) {
        logAddNode(server->config.logging, session, &node->head.nodeId,
                   "The array dimensions are incompatible with the "
                   "array dimensions of the VariableType");
        return UA_STATUSCODE_BADTYPEMISMATCH;
    }

    /* Typecheck the value. Less strict for NS0 -- we adjust nodes after loading
     * the generated definitions. */
    if(server->bootstrapNS0)
        return UA_STATUSCODE_GOOD;

    /* 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;

    /* We have a value. Write it back to perform checks and adjustments. */
    const char *reason;
    if(node->valueSourceType == UA_VALUESOURCETYPE_INTERNAL && value.hasValue) {
        if(!compatibleValue(server, session, &node->dataType, node->valueRank,
                            node->arrayDimensionsSize, node->arrayDimensions,
                            &value.value, NULL, &reason)) {
            retval = writeAttribute(server, session, &node->head.nodeId,
                                    UA_ATTRIBUTEID_VALUE, &value.value,
                                    &UA_TYPES[UA_TYPES_VARIANT]);
        }
        UA_DataValue_clear(&value);
        return retval;
    }

    /* Only BaseDataType (Variant) can have empty values */
    if(!value.hasValue &&
       !UA_NodeId_equal(&node->dataType, &UA_TYPES[UA_TYPES_VARIANT].typeId)) {
        /* Warn if that is configured */
        if(server->config.allowEmptyVariables != UA_RULEHANDLING_ACCEPT)
            UA_LOG_DEBUG_SESSION(server->config.logging, session,
                                 "AddNode (%N): The value is empty. "
                                 "But this is only allowed for BaseDataType. "
                                 "Create a matching default value.",
                                 node->head.nodeId);

        /* Abort if that is configured */
        if(server->config.allowEmptyVariables == UA_RULEHANDLING_ABORT)
            retval = UA_STATUSCODE_BADTYPEMISMATCH;

        /* Try to generate and write a default value */
        if(server->config.allowEmptyVariables == UA_RULEHANDLING_DEFAULT) {
            retval = setDefaultValue(server, node);
            if(retval != UA_STATUSCODE_GOOD) {
                UA_LOG_INFO_SESSION(server->config.logging, session,
                                    "AddNode (%N): Could not create a default value "
                                    "with StatusCode %s", node->head.nodeId,
                                    UA_StatusCode_name(retval));
            }
        }
        return retval;
    }

    /* Type-check the value */
    UA_Boolean compatible =
        compatibleValue(server, session, &node->dataType,
            node->valueRank, node->arrayDimensionsSize,
            node->arrayDimensions, &value.value, NULL, &reason);
    if(!compatible) {
        UA_LOG_INFO_SESSION(server->config.logging, session,
                            "AddNode (%N): The VariableNode value has "
                            "failed the type check with reason %s. ",
                            node->head.nodeId, reason);
        retval = UA_STATUSCODE_BADINTERNALERROR;
    }

    UA_DataValue_clear(&value);
    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,
                          const UA_VariableTypeNode *vt) {
    /* 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 origDv;
    UA_DataValue_init(&origDv);
    UA_StatusCode retval = readValueAttribute(server, session, node, &origDv);
    if(retval != UA_STATUSCODE_GOOD)
        return retval;

    if(origDv.hasValue && origDv.value.type) {
        /* A value is present */
        UA_DataValue_clear(&origDv);
    } else {
        UA_DataValue_clear(&origDv);
        UA_DataValue v;
        UA_DataValue_init(&v);
        retval = readValueAttribute(server, session, (const UA_VariableNode*)vt, &v);
        if(retval == UA_STATUSCODE_GOOD && v.hasValue) {
            retval = writeAttribute(server, session, &node->head.nodeId,
                                    UA_ATTRIBUTEID_VALUE, &v.value,
                                    &UA_TYPES[UA_TYPES_VARIANT]);
        }
        UA_DataValue_clear(&v);

        if(retval != UA_STATUSCODE_GOOD) {
            logAddNode(server->config.logging, session, &node->head.nodeId,
                       "The default content of the VariableType could "
                       "not be used. This may happen if the VariableNode "
                       "makes additional restrictions.");
            retval = UA_STATUSCODE_GOOD;
        }
    }

    /* If no datatype is given, use the datatype of the vt */
    if(UA_NodeId_isNull(&node->dataType)) {
        logAddNode(server->config.logging, session, &node->head.nodeId,
                   "No datatype given; Copy the datatype attribute "
                   "from the TypeDefinition");
        retval = writeAttribute(server, session, &node->head.nodeId,
                                UA_ATTRIBUTEID_DATATYPE, &vt->dataType,
                                &UA_TYPES[UA_TYPES_NODEID]);
        if(retval != UA_STATUSCODE_GOOD)
            return retval;
    }

    /* Use the ArrayDimensions of the vt */
    if(node->arrayDimensionsSize == 0 && vt->arrayDimensionsSize > 0) {
        UA_Variant v;
        UA_Variant_init(&v);
        UA_Variant_setArray(&v, vt->arrayDimensions, vt->arrayDimensionsSize,
                            &UA_TYPES[UA_TYPES_UINT32]);
        retval = writeAttribute(server, session, &node->head.nodeId,
                                UA_ATTRIBUTEID_ARRAYDIMENSIONS, &v,
                                &UA_TYPES[UA_TYPES_VARIANT]);
    }

    return retval;
}

struct findChildContext {
    UA_Server *server;
    UA_QualifiedName browseName;
    UA_UInt32 browseNameHash;
    UA_NodeClass nodeClassMask;
    UA_NodeId *outInstanceNodeId;
};

static void *
findChildCallback(void *context, UA_ReferenceTarget *t) {
    struct findChildContext *ctx = (struct findChildContext*)context;

    /* No ExpandedNodeId pointing to another server */
    if(!UA_NodePointer_isLocal(t->targetId))
        return NULL;

    /* Compare the hash (fast) */
    if(t->targetNameHash != ctx->browseNameHash)
        return NULL;

    /* Get the node to compare the full attributes */
    const UA_Node *refTarget =
        UA_NODESTORE_GETFROMREF_SELECTIVE(ctx->server, t->targetId,
                                          UA_NODEATTRIBUTESMASK_BROWSENAME,
                                          UA_REFERENCETYPESET_NONE,
                                          UA_BROWSEDIRECTION_INVALID);
    if(!refTarget)
        return NULL;

    /* Compare NodeClass and BrowseName */
    if(!(refTarget->head.nodeClass & ctx->nodeClassMask) ||
       !UA_QualifiedName_equal(&ctx->browseName, &refTarget->head.browseName)) {
        UA_NODESTORE_RELEASE(ctx->server, refTarget);
        return NULL;
    }

    UA_NODESTORE_RELEASE(ctx->server, refTarget);

    /* Copy the child NodeId and return a sentinel value */
    UA_NodeId origId = UA_NodePointer_toNodeId(t->targetId);
    UA_StatusCode res = UA_NodeId_copy(&origId, ctx->outInstanceNodeId);
    return (res == UA_STATUSCODE_GOOD) ? (void*)0x01 : (void*)0x02;
}

UA_StatusCode
findChildByBrowsename(UA_Server *server, UA_Session *session,
                      const UA_NodeId parentId, UA_NodeClass nodeClassMask,
                      const UA_Byte refType, const UA_NodeId refTypeId,
                      const UA_QualifiedName *browseName,
                      UA_NodeId *outChildNodeId) {
    /* Begin with HasComponent references only. This is the most common case as
     * a "fast path". If this fails also look for subtypes of HasComponent. */
    UA_Boolean subTypes = false;
    UA_ReferenceTypeSet refTypes = UA_REFTYPESET(refType);

    /* Setup the context */
    struct findChildContext ctx;
    ctx.server = server;
    ctx.browseName = *browseName;
    ctx.browseNameHash = UA_QualifiedName_hash(browseName);
    ctx.nodeClassMask = nodeClassMask;
    ctx.outInstanceNodeId = outChildNodeId;

    /* Get the parent node */
    void *found = NULL;
    const UA_Node *parent;
 get_parent:
    parent = UA_NODESTORE_GET_SELECTIVE(server, &parentId,
                                        UA_NODEATTRIBUTESMASK_NONE,
                                        refTypes, UA_BROWSEDIRECTION_FORWARD);
    if(!parent)
        return UA_STATUSCODE_BADNODEIDUNKNOWN;

    /* Loop over the references to find a match */
    for(size_t i = 0; i < parent->head.referencesSize; i++) {
        UA_NodeReferenceKind *rk = &parent->head.references[i];
        if(rk->isInverse)
            continue;
        if(!UA_ReferenceTypeSet_contains(&refTypes, rk->referenceTypeIndex))
            continue;
        found = UA_NodeReferenceKind_iterate(rk, findChildCallback, &ctx);
        if(found)
            break;
    }

    UA_NODESTORE_RELEASE(server, parent);

    /* Also consider subtypes of for references*/
    if(!found && !subTypes) {
        UA_StatusCode res =
            referenceTypeIndices(server, &refTypeId, &refTypes, true);
        if(res != UA_STATUSCODE_GOOD)
            return res;
        subTypes = true;
        goto get_parent;
    }

    /* Error */
    if(found == (void *)0x02)
        return UA_STATUSCODE_BADOUTOFMEMORY;

    /* Done */
    return (found) ? UA_STATUSCODE_GOOD : UA_STATUSCODE_BADNOTFOUND;
}

static const UA_ExpandedNodeId mandatoryId =
    {{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_MODELLINGRULE_MANDATORY}}, {0, NULL}, 0};

static UA_Boolean
isMandatoryChild(UA_Server *server, UA_Session *session,
                 const UA_NodeId *childNodeId) {
    /* Get the child */
    const UA_Node *child = UA_NODESTORE_GET(server, childNodeId);
    if(!child)
        return false;

    /* Look for the reference making the child mandatory */
    UA_Boolean found = false;
    for(size_t i = 0; i < child->head.referencesSize; ++i) {
        UA_NodeReferenceKind *rk = &child->head.references[i];
        if(rk->referenceTypeIndex != UA_REFERENCETYPEINDEX_HASMODELLINGRULE)
            continue;
        if(rk->isInverse)
            continue;

        if(UA_NodeReferenceKind_findTarget(rk, &mandatoryId)) {
            found = true;
            break;
        }
    }

    UA_NODESTORE_RELEASE(server, child);
    return found;
}

static UA_StatusCode
copyAllChildren(UA_Server *server, UA_Session *session,
                const UA_NodeId *source, const UA_NodeId *destination);

static void
Operation_addReference(UA_Server *server, UA_Session *session, void *context,
                       const UA_AddReferencesItem *item, UA_StatusCode *retval);

UA_StatusCode
addRefWithSession(UA_Server *server, UA_Session *session, const UA_NodeId *sourceId,
                  const UA_NodeId *referenceTypeId, const UA_NodeId *targetId,
                  UA_Boolean forward) {
    UA_AddReferencesItem ref_item;
    UA_AddReferencesItem_init(&ref_item);
    ref_item.sourceNodeId = *sourceId;
    ref_item.referenceTypeId = *referenceTypeId;
    ref_item.isForward = forward;
    ref_item.targetNodeId.nodeId = *targetId;

    UA_StatusCode retval = UA_STATUSCODE_GOOD;
    Operation_addReference(server, session, NULL, &ref_item, &retval);
    return retval;
}

UA_StatusCode
addRef(UA_Server *server, const UA_NodeId sourceId,
       const UA_NodeId referenceTypeId, const UA_NodeId targetId,
       UA_Boolean forward) {
    return addRefWithSession(server, &server->adminSession, &sourceId,
                             &referenceTypeId, &targetId, forward);
}

static UA_StatusCode
addInterfaceChildren(UA_Server *server, UA_Session *session,
                     const UA_NodeId *nodeId) {
    /* Get the hierarchy of the type and all its supertypes */
    UA_NodeId *hierarchy = NULL;
    size_t hierarchySize = 0;
    UA_StatusCode retval = getAllInterfaces(server, nodeId, &hierarchy, &hierarchySize);
    if(retval != UA_STATUSCODE_GOOD)
        return retval;

    /* Copy members of the type and supertypes (and instantiate them) */
    for(size_t i = 0; i < hierarchySize; ++i) {
        retval = copyAllChildren(server, session, &hierarchy[i], nodeId);
        if(retval != UA_STATUSCODE_GOOD) {
            UA_Array_delete(hierarchy, hierarchySize, &UA_TYPES[UA_TYPES_NODEID]);
            return retval;
        }
    }

    for(size_t i = 0; i < hierarchySize; ++i) {
        UA_NodeId refId = UA_NS0ID(HASINTERFACE);
        retval = addRef(server, *nodeId, refId, hierarchy[i], true);

        /* Don't add the original HasInterface reference to ObjectType sub nodes */
        if(retval == UA_STATUSCODE_BADDUPLICATEREFERENCENOTALLOWED) {
            retval = UA_STATUSCODE_GOOD;
        } else if(retval != UA_STATUSCODE_GOOD) {
            break;
        }
    }

    UA_Array_delete(hierarchy, hierarchySize, &UA_TYPES[UA_TYPES_NODEID]);
    return retval;
}

static UA_StatusCode
copyObjectVariableChild(UA_Server *server, UA_Session *session,
                        const UA_NodeId *destinationNodeId,
                        const UA_ReferenceDescription *rd) {
    /* Make a copy of the node */
    UA_Node *node;
    UA_StatusCode res = UA_NODESTORE_GETCOPY(server, &rd->nodeId.nodeId, &node);
    if(res != UA_STATUSCODE_GOOD)
        return res;

    /* Remove the context of the copied node */
    node->head.context = NULL;
    node->head.constructed = false;
#ifdef UA_ENABLE_SUBSCRIPTIONS
    node->head.monitoredItems = NULL;
#endif

    /* The value source callbacks are copied by default. But we don't want
     * to keep it here. */
    if(node->head.nodeClass == UA_NODECLASS_VARIABLE ||
       node->head.nodeClass == UA_NODECLASS_VARIABLETYPE) {
        if(node->variableNode.valueSourceType == UA_VALUESOURCETYPE_INTERNAL ||
           node->variableNode.valueSourceType == UA_VALUESOURCETYPE_EXTERNAL) {
            memset(&node->variableNode.valueSource.internal.notifications, 0,
                   sizeof(UA_ValueSourceNotifications));
        } else {
            memset(&node->variableNode.valueSource.callback, 0,
                   sizeof(UA_CallbackValueSource));

        }
        node->variableNode.valueSourceType = UA_VALUESOURCETYPE_INTERNAL;
    }

    /* Reset the NodeId (random numeric id will be assigned in the nodestore) */
    UA_NodeId_clear(&node->head.nodeId);
    node->head.nodeId.namespaceIndex = destinationNodeId->namespaceIndex;
    if(server->config.nodeLifecycle &&
       server->config.nodeLifecycle->generateChildNodeId) {
        res = server->config.nodeLifecycle->
            generateChildNodeId(server, &session->sessionId, session->context,
                                &rd->nodeId.nodeId, destinationNodeId,
                                &rd->referenceTypeId, &node->head.nodeId);
        if(res != UA_STATUSCODE_GOOD) {
            UA_NODESTORE_DELETE(server, node);
            return res;
        }
    }

    /* Remove references, they are re-created from scratch in
     * addnode_finish. For now we keep all the modelling rule references and
     * delete all others. */

    /* 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. */
    const UA_NodeId nodeId_typesFolder = UA_NS0ID(TYPESFOLDER);
    const UA_ReferenceTypeSet reftypes_aggregates =
        UA_REFTYPESET(UA_REFERENCETYPEINDEX_AGGREGATES);
    UA_ReferenceTypeSet reftypes_skipped =
        UA_REFTYPESET(UA_REFERENCETYPEINDEX_HASINTERFACE);
    if(server->config.modellingRulesOnInstances ||
       isNodeInTree(server, destinationNodeId,
                    &nodeId_typesFolder, &reftypes_aggregates)) {
        /* hasModellingRule-reference is required (configured or node in an
         * instance declaration) */
        UA_ReferenceTypeSet_add(&reftypes_skipped,
                                UA_REFERENCETYPEINDEX_HASMODELLINGRULE);
    }
    UA_Node_deleteReferencesSubset(node, &reftypes_skipped);

    /* Add the node to the nodestore */
    UA_NodeId newNodeId = UA_NODEID_NULL;
    res = UA_NODESTORE_INSERT(server, node, &newNodeId);
    /* node = NULL; The pointer is no longer valid */
    if(res != UA_STATUSCODE_GOOD)
        return res;

    /* Add the node references */
    res = addNode_addRefs(server, session, &newNodeId, destinationNodeId,
                          &rd->referenceTypeId, &rd->typeDefinition.nodeId);
    if(res != UA_STATUSCODE_GOOD)
        goto errout;

    if(rd->nodeClass == UA_NODECLASS_VARIABLE) {
        res = checkSetIsDynamicVariable(server, session, &newNodeId);
        if(res != UA_STATUSCODE_GOOD)
            goto errout;
    }

    /* For the new child, recursively copy the members of the original. No
     * typechecking is performed here. Assuming that the original is
     * consistent. */
    res = copyAllChildren(server, session, &rd->nodeId.nodeId, &newNodeId);
    if(res != UA_STATUSCODE_GOOD)
        goto errout;

    /* Check if its a dynamic variable, add all type and/or interface
     * children and call the constructor */
    res = addNode_finish(server, session, &newNodeId);
    if(res != UA_STATUSCODE_GOOD)
        goto errout;

 errout:
    /* Clean up */
    if(res != UA_STATUSCODE_GOOD)
        deleteNode(server, newNodeId, true);
    UA_NodeId_clear(&newNodeId);
    return res;
}

static UA_StatusCode
copyChild(UA_Server *server, UA_Session *session,
          const UA_NodeId *destinationNodeId,
          const UA_ReferenceDescription *rd) {
    UA_assert(session);
    UA_LOCK_ASSERT(&server->serviceMutex);

    /* Is there an existing child with the browsename? */
    UA_NodeId existingChild = UA_NODEID_NULL;
    UA_NodeClass childNodeClass = (UA_NodeClass)
        (UA_NODECLASS_OBJECT | UA_NODECLASS_VARIABLE | UA_NODECLASS_METHOD);
    UA_StatusCode retval = findChildByBrowsename(server, session,
                                                 *destinationNodeId, childNodeClass,
                                                 UA_REFERENCETYPEINDEX_AGGREGATES,
                                                 UA_NS0ID(AGGREGATES), &rd->browseName,
                                                 &existingChild);

    /* Existing child with that browseName. Deep-copy missing members. */
    if(retval == UA_STATUSCODE_GOOD) {
        if(rd->nodeClass == UA_NODECLASS_VARIABLE ||
           rd->nodeClass == UA_NODECLASS_OBJECT)
            retval = copyAllChildren(server, session, &rd->nodeId.nodeId, &existingChild);
        UA_NodeId_clear(&existingChild);
        return retval;
    }

    /* An error occurred (besides not finding an existing child) */
    if(retval != UA_STATUSCODE_BADNOTFOUND)
        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 ||
           !server->config.nodeLifecycle->createOptionalChild)
            return UA_STATUSCODE_GOOD;
        UA_Boolean createChild = server->config.nodeLifecycle->
            createOptionalChild(server, &session->sessionId, session->context,
                                &rd->nodeId.nodeId, destinationNodeId, &rd->referenceTypeId);
        if(!createChild)
            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) {
        retval = copyObjectVariableChild(server, session, destinationNodeId, rd);
    }

    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_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)
            break;
    }

    UA_BrowseResult_clear(&br);
    return retval;
}

static UA_StatusCode
addTypeChildren(UA_Server *server, UA_Session *session,
                const UA_NodeId *nodeId, const UA_NodeId *typeId) {
    /* Get the hierarchy of the type and all its supertypes */
    UA_NodeId *hierarchy = NULL;
    size_t hierarchySize = 0;
    UA_StatusCode retval =
        getTypeAndInterfaceHierarchy(server, typeId, true,
                                     &hierarchy, &hierarchySize);
    if(retval != UA_STATUSCODE_GOOD)
        return retval;

    /* Copy members of the type and supertypes (and instantiate them) */
    for(size_t i = 0; i < hierarchySize; ++i) {
        retval = copyAllChildren(server, session, &hierarchy[i], nodeId);
        if(retval != UA_STATUSCODE_GOOD)
            break;
    }

    UA_Array_delete(hierarchy, hierarchySize, &UA_TYPES[UA_TYPES_NODEID]);
    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_GET(server, nodeId);
    if(!node)
        return UA_STATUSCODE_BADNODEIDUNKNOWN;

    /* Use the typeDefinition as parent for type-nodes */
    const UA_NodeHead *head = &node->head;
    if(head->nodeClass == UA_NODECLASS_VARIABLETYPE ||
       head->nodeClass == UA_NODECLASS_OBJECTTYPE ||
       head->nodeClass == UA_NODECLASS_REFERENCETYPE ||
       head->nodeClass == UA_NODECLASS_DATATYPE) {
        if(UA_NodeId_equal(referenceTypeId, &UA_NODEID_NULL))
            referenceTypeId = &hasSubtype;
        const UA_Node *parentNode = UA_NODESTORE_GET(server, parentNodeId);
        if(parentNode) {
            if(parentNode->head.nodeClass == head->nodeClass)
                typeDefinitionId = parentNodeId;
            UA_NODESTORE_RELEASE(server, parentNode);
        }
    }

    UA_StatusCode retval;
    /* Make sure newly created node does not have itself as parent */
    if(UA_NodeId_equal(nodeId, parentNodeId)) {
        logAddNode(server->config.logging, session, nodeId,
                   "A node cannot have itself as parent");
        retval = UA_STATUSCODE_BADINVALIDARGUMENT;
        goto cleanup;
    }


    /* Check parent reference. Objects may have no parent. */
    retval = checkParentReference(server, session, head, parentNodeId, referenceTypeId);
    if(retval != UA_STATUSCODE_GOOD) {
        logAddNode(server->config.logging, session, nodeId,
                   "The parent reference is invalid");
        goto cleanup;
    }

    /* Replace empty typeDefinition with the most permissive default */
    if((head->nodeClass == UA_NODECLASS_VARIABLE ||
        head->nodeClass == UA_NODECLASS_OBJECT) &&
       UA_NodeId_isNull(typeDefinitionId)) {
        logAddNode(server->config.logging, session, nodeId,
                   "No TypeDefinition. Use the default "
                   "TypeDefinition for the Variable/Object");
        if(head->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_GET(server, typeDefinitionId);
        if(!type) {
            UA_LOG_INFO_SESSION(server->config.logging, session,
                                "AddNode (%N): Node type %N not found ",
                                *nodeId, *typeDefinitionId);
            retval = UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
            goto cleanup;
        }

        UA_Boolean typeOk = false;
        const UA_NodeHead *typeHead = &type->head;
        switch(head->nodeClass) {
            case UA_NODECLASS_DATATYPE:
                typeOk = typeHead->nodeClass == UA_NODECLASS_DATATYPE;
                break;
            case UA_NODECLASS_METHOD:
                typeOk = typeHead->nodeClass == UA_NODECLASS_METHOD;
                break;
            case UA_NODECLASS_OBJECT:
            case UA_NODECLASS_OBJECTTYPE:
                typeOk = typeHead->nodeClass == UA_NODECLASS_OBJECTTYPE;
                break;
            case UA_NODECLASS_REFERENCETYPE:
                typeOk = typeHead->nodeClass == UA_NODECLASS_REFERENCETYPE;
                break;
            case UA_NODECLASS_VARIABLE:
            case UA_NODECLASS_VARIABLETYPE:
                typeOk = typeHead->nodeClass == UA_NODECLASS_VARIABLETYPE;
                break;
            case UA_NODECLASS_VIEW:
                typeOk = typeHead->nodeClass == UA_NODECLASS_VIEW;
                break;
            default:
                typeOk = false;
        }
        if(!typeOk) {
            logAddNode(server->config.logging, session, nodeId,
                       "Type does not match the NodeClass");
            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(head->nodeClass == UA_NODECLASS_VARIABLE &&
           type->variableTypeNode.isAbstract) {
            /* Get subtypes of the parent reference types */
            UA_ReferenceTypeSet refTypes1, refTypes2;
            retval |= referenceTypeIndices(server, &parentReferences[0], &refTypes1, true);
            retval |= referenceTypeIndices(server, &parentReferences[1], &refTypes2, true);
            UA_ReferenceTypeSet refTypes = UA_ReferenceTypeSet_union(refTypes1, refTypes2);
            if(retval != UA_STATUSCODE_GOOD)
                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.
             * When the parent is not set (e.g. generated namespace code where
             * the parent reference is non-hierarchical), we skip the check. */
            const UA_NodeId variableTypes = UA_NS0ID(BASEDATAVARIABLETYPE);
            const UA_NodeId objectTypes = UA_NS0ID(BASEOBJECTTYPE);
            if(!UA_NodeId_isNull(parentNodeId) &&
               !isNodeInTree(server, parentNodeId, &variableTypes, &refTypes) &&
               !isNodeInTree(server, parentNodeId, &objectTypes, &refTypes)) {
                logAddNode(server->config.logging, session, nodeId,
                           "Type of variable node must be a "
                           "VariableType and cannot be abstract");
                retval = UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
                goto cleanup;
            }
        }

        if(head->nodeClass == UA_NODECLASS_OBJECT &&
           type->objectTypeNode.isAbstract) {
            /* Get subtypes of the parent reference types */
            UA_ReferenceTypeSet refTypes1, refTypes2;
            retval |= referenceTypeIndices(server, &parentReferences[0], &refTypes1, true);
            retval |= referenceTypeIndices(server, &parentReferences[1], &refTypes2, true);
            UA_ReferenceTypeSet refTypes = UA_ReferenceTypeSet_union(refTypes1, refTypes2);
            if(retval != UA_STATUSCODE_GOOD)
                goto cleanup;


            /* Object node created of an abstract ObjectType. Only allowed if
             * within BaseObjectType folder, or if it's an event (subType of
             * BaseEventType), or if the parent is not set (e.g. generated
             * namespace code where the parent reference is
             * non-hierarchical). */
            const UA_NodeId objectTypes = UA_NS0ID(BASEOBJECTTYPE);
            UA_Boolean isInBaseObjectType =
                isNodeInTree(server, parentNodeId, &objectTypes, &refTypes);

            if(!isInBaseObjectType && !UA_NodeId_isNull(parentNodeId)) {
                logAddNode(server->config.logging, session, nodeId,
                           "Type of ObjectNode must be ObjectType and not be abstract");
                retval = UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
                goto cleanup;
            }
        }
    }

    /* Add reference to the parent */
    if(!UA_NodeId_isNull(parentNodeId)) {
        if(UA_NodeId_isNull(referenceTypeId)) {
            logAddNode(server->config.logging, session, nodeId,
                       "Reference to parent cannot be null");
            retval = UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
            goto cleanup;
        }

        retval = addRefWithSession(server, session, &head->nodeId, referenceTypeId,
                                   parentNodeId, false);
        if(retval != UA_STATUSCODE_GOOD) {
            logAddNode(server->config.logging, session, nodeId,
                       "Adding reference to parent failed");
            goto cleanup;
        }
    }

    /* Add a hasTypeDefinition reference */
    if(head->nodeClass == UA_NODECLASS_VARIABLE ||
       head->nodeClass == UA_NODECLASS_OBJECT) {
        UA_assert(type != NULL); /* see above */
        retval = addRefWithSession(server, session, &head->nodeId, &hasTypeDefinition,
                                   &type->head.nodeId, true);
        if(retval != UA_STATUSCODE_GOOD) {
            logAddNode(server->config.logging, session, nodeId,
                       "Adding a reference to the type definition failed");
        }
    }

 cleanup:
    UA_NODESTORE_RELEASE(server, node);
    if(type)
        UA_NODESTORE_RELEASE(server, 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) {
    UA_LOCK_ASSERT(&server->serviceMutex);

    /* Do not check access for server */
    if(session != &server->adminSession && server->config.accessControl.allowAddNode) {
        if(!server->config.accessControl.
           allowAddNode(server, &server->config.accessControl,
                        &session->sessionId, session->context, item)) {
            return UA_STATUSCODE_BADUSERACCESSDENIED;
        }
    }

    /* Check the NamespaceIndex */
    if(item->requestedNewNodeId.nodeId.namespaceIndex >= server->namespacesSize) {
        UA_LOG_INFO_SESSION(server->config.logging, session,
                            "AddNode (%N): Namespace invalid",
                            item->requestedNewNodeId.nodeId);
        return UA_STATUSCODE_BADNODEIDINVALID;
    }

    if(item->nodeAttributes.encoding != UA_EXTENSIONOBJECT_DECODED &&
       item->nodeAttributes.encoding != UA_EXTENSIONOBJECT_DECODED_NODELETE) {
        UA_LOG_INFO_SESSION(server->config.logging, session,
                            "AddNode (%N): Node attributes invalid",
                            item->requestedNewNodeId.nodeId);
        return UA_STATUSCODE_BADINTERNALERROR;
    }

    /* Create a Node */
    UA_Node *node = UA_NODESTORE_NEW(server, item->nodeClass);
    if(!node) {
        UA_LOG_INFO_SESSION(server->config.logging, session,
                            "AddNode: Could not create a node "
                            "in the nodestore");
        return UA_STATUSCODE_BADOUTOFMEMORY;
    }

    UA_NodeId tmpOutId = UA_NODEID_NULL;

    /* Fill the node attributes. If an error occurs, the memory in the node is
     * cleaned up inside UA_NODESTORE_DELETE. */
    node->head.context = nodeContext;
    UA_StatusCode retval =
        UA_NodeId_copy(&item->requestedNewNodeId.nodeId, &node->head.nodeId);
    if(retval != UA_STATUSCODE_GOOD)
        goto create_error;

    retval = UA_QualifiedName_copy(&item->browseName, &node->head.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;

    /* Create a current source timestamp for values that don't have any */
    if(node->head.nodeClass == UA_NODECLASS_VARIABLE) {
        UA_VariableNode *vn = &node->variableNode;
        if(vn->valueSourceType == UA_VALUESOURCETYPE_INTERNAL &&
           !vn->valueSource.internal.value.hasSourceTimestamp) {
            UA_EventLoop *el = server->config.eventLoop;
            vn->valueSource.internal.value.sourceTimestamp = el->dateTime_now(el);
            vn->valueSource.internal.value.hasSourceTimestamp = true;
        }
    }

    /* Add the node to the Nodestore */
    if(!outNewNodeId)
        outNewNodeId = &tmpOutId;
    retval = UA_NODESTORE_INSERT(server, node, outNewNodeId);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_INFO_SESSION(server->config.logging, session,
                            "AddNode (%N): Could not add the new node "
                            "to the Nodestore with status %s",
                            item->requestedNewNodeId.nodeId,
                            UA_StatusCode_name(retval));
        return retval;
    }

    if(outNewNodeId == &tmpOutId)
        UA_NodeId_clear(&tmpOutId);

    return UA_STATUSCODE_GOOD;

create_error:
    UA_LOG_INFO_SESSION(server->config.logging, session,
                        "AddNode (%N): Could not create node "
                        "with status %s", node->head.nodeId,
                        UA_StatusCode_name(retval));
    UA_NODESTORE_DELETE(server, node);
    return retval;
}

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_NS0ID(HASPROPERTY);
    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_clear(&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
checkSetBrowseName(UA_Server *server, UA_Session *session, UA_AddNodesItem *item) {
    /* If the object node already has a browse name we are done here. */
    if(!UA_QualifiedName_isNull(&item->browseName))
        return UA_STATUSCODE_GOOD;

    /* Nodes other than Objects must have a BrowseName */
    if(item->nodeClass != UA_NODECLASS_OBJECT)
        return UA_STATUSCODE_BADBROWSENAMEINVALID;

    /* 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;
    UA_StatusCode retval =
        findDefaultInstanceBrowseNameNode(server, item->typeDefinition.nodeId,
                                          &defaultBrowseNameNode);
    if(retval != UA_STATUSCODE_GOOD)
        return UA_STATUSCODE_BADBROWSENAMEINVALID;

    UA_Variant defaultBrowseName;
    retval = readWithReadValue(server, &defaultBrowseNameNode,
                               UA_ATTRIBUTEID_VALUE, &defaultBrowseName);
    UA_NodeId_clear(&defaultBrowseNameNode);
    if(retval != UA_STATUSCODE_GOOD)
        return UA_STATUSCODE_BADBROWSENAMEINVALID;

    if(UA_Variant_hasScalarType(&defaultBrowseName, &UA_TYPES[UA_TYPES_QUALIFIEDNAME])) {
        item->browseName = *(UA_QualifiedName*)defaultBrowseName.data;
        UA_QualifiedName_init((UA_QualifiedName*)defaultBrowseName.data);
    } else {
        retval = UA_STATUSCODE_BADBROWSENAMEINVALID;
    }

    UA_Variant_clear(&defaultBrowseName);
    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;
    }

    /* Set the BrowsenName before adding to the Nodestore. The BrowseName is
     * immutable afterwards. */
    UA_Boolean noBrowseName = UA_QualifiedName_isNull(&item->browseName);
    UA_StatusCode retval =
        checkSetBrowseName(server, session, (UA_AddNodesItem*)(uintptr_t)item);
    if(retval != UA_STATUSCODE_GOOD)
        return retval;

    /* Create the node and add it to the nodestore */
    retval = addNode_raw(server, session, nodeContext, item, outNewNodeId);
    if(retval != UA_STATUSCODE_GOOD)
        goto cleanup;

    /* 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_clear(&newId);

 cleanup:
    if(noBrowseName)
        UA_QualifiedName_clear((UA_QualifiedName*)(uintptr_t)&item->browseName);
    return retval;
}

/* Construct children first */
static UA_StatusCode
recursiveCallConstructors(UA_Server *server, UA_Session *session,
                          const UA_NodeId *nodeId, const UA_Node *type) {
    UA_LOCK_ASSERT(&server->serviceMutex);

    /* Browse the children */
    UA_BrowseDescription bd;
    UA_BrowseDescription_init(&bd);
    bd.nodeId = *nodeId;
    bd.referenceTypeId = 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 child node */
    UA_StatusCode retval = UA_STATUSCODE_GOOD;
    for(size_t i = 0; i < br.referencesSize; ++i) {
        UA_ReferenceDescription *rd = &br.references[i];
        if(!UA_ExpandedNodeId_isLocal(&rd->nodeId))
            continue;
        const UA_Node *target = UA_NODESTORE_GET(server, &rd->nodeId.nodeId);
        if(!target)
            continue;
        if(target->head.constructed) {
            UA_NODESTORE_RELEASE(server, target);
            continue;
        }

        /* TODO: Do we need all attributes and references here?  */
        const UA_Node *targetType = NULL;
        if(target->head.nodeClass == UA_NODECLASS_VARIABLE ||
           target->head.nodeClass == UA_NODECLASS_OBJECT) {
            targetType = getNodeType(server, &target->head, ~(UA_UInt32)0,
                                     UA_REFERENCETYPESET_ALL,
                                     UA_BROWSEDIRECTION_BOTH);
            if(!targetType) {
                UA_NODESTORE_RELEASE(server, target);
                retval = UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
                break;
            }
        }

        UA_NODESTORE_RELEASE(server, target);
        retval = recursiveCallConstructors(server, session, &rd->nodeId.nodeId, targetType);

        if(targetType)
            UA_NODESTORE_RELEASE(server, targetType);
        if(retval != UA_STATUSCODE_GOOD)
            break;
    }

    UA_BrowseResult_clear(&br);

    /* If a child could not be constructed or the node is already constructed */
    if(retval != UA_STATUSCODE_GOOD)
        return retval;

    /* Get the node context */
    const UA_Node *node = UA_NODESTORE_GET(server, nodeId);
    if(!node)
        return UA_STATUSCODE_BADNODEIDUNKNOWN;
    void *context = node->head.context;
    UA_NODESTORE_RELEASE(server, node);

    /* Call the global constructor */
    if(server->config.nodeLifecycle &&
       server->config.nodeLifecycle->constructor) {
        retval = server->config.nodeLifecycle->
            constructor(server, &session->sessionId,
                        session->context, nodeId, &context);
        if(retval != UA_STATUSCODE_GOOD)
            return retval;
    }

    /* Call the local (per-type) constructor */
    const UA_NodeTypeLifecycle *lifecycle = NULL;
    if(type && node->head.nodeClass == UA_NODECLASS_OBJECT)
        lifecycle = &type->objectTypeNode.lifecycle;
    else if(type && node->head.nodeClass == UA_NODECLASS_VARIABLE)
        lifecycle = &type->variableTypeNode.lifecycle;
    if(lifecycle && lifecycle->constructor) {
        retval = lifecycle->constructor(server, &session->sessionId,
                                        session->context, &type->head.nodeId,
                                        type->head.context, nodeId, &context);
        if(retval != UA_STATUSCODE_GOOD)
            goto global_destructor;
    }

    /* Set the context *and* mark the node as constructed */
    retval = editNode(server, &server->adminSession, nodeId,
                      0, UA_REFERENCETYPESET_NONE, UA_BROWSEDIRECTION_INVALID,
                      (UA_EditNodeCallback)setConstructedNodeContext, context);
    if(retval != UA_STATUSCODE_GOOD)
        goto local_destructor;

    /* All good, return */
    return retval;

    /* Fail. Call the destructors. */
  local_destructor:
    if(lifecycle && lifecycle->destructor) {
        lifecycle->destructor(server, &session->sessionId, session->context,
                              &type->head.nodeId, type->head.context, nodeId, &context);
    }

  global_destructor:
    if(server->config.nodeLifecycle && server->config.nodeLifecycle->destructor) {
        server->config.nodeLifecycle->destructor(server, &session->sessionId,
                                                 session->context, nodeId, context);
    }
    return retval;
}

/* Add new ReferenceType to the subtypes bitfield */
static UA_StatusCode
addReferenceTypeSubtype(UA_Server *server, UA_Session *session,
                        UA_Node *node, void *context) {
    node->referenceTypeNode.subTypes =
        UA_ReferenceTypeSet_union(node->referenceTypeNode.subTypes,
                                  *(UA_ReferenceTypeSet*)context);
    return UA_STATUSCODE_GOOD;
}

static UA_StatusCode
setReferenceTypeSubtypes(UA_Server *server, const UA_ReferenceTypeNode *node) {
    /* Get the ReferenceTypes upwards in the hierarchy */
    size_t parentsSize = 0;
    UA_ExpandedNodeId *parents = NULL;
    UA_ReferenceTypeSet reftypes_subtype = UA_REFTYPESET(UA_REFERENCETYPEINDEX_HASSUBTYPE);
    UA_StatusCode res =
        browseRecursive(server, 1, &node->head.nodeId, UA_BROWSEDIRECTION_INVERSE,
                        &reftypes_subtype, UA_NODECLASS_UNSPECIFIED,
                        false, &parentsSize, &parents);
    if(res != UA_STATUSCODE_GOOD)
        return res;

    /* Add the ReferenceTypeIndex of this node */
    const UA_ReferenceTypeSet *newRefSet = &node->subTypes;
    for(size_t i = 0; i < parentsSize; i++) {
        editNode(server, &server->adminSession, &parents[i].nodeId,
                 0, UA_REFERENCETYPESET_NONE, UA_BROWSEDIRECTION_INVALID,
                 addReferenceTypeSubtype, (void*)(uintptr_t)newRefSet);
    }

    UA_Array_delete(parents, parentsSize, &UA_TYPES[UA_TYPES_EXPANDEDNODEID]);
    return UA_STATUSCODE_GOOD;
}

static UA_StatusCode
setVariableNodeDynamic(UA_Server *server, const UA_NodeId *nodeId,
                       UA_Boolean isDynamic) {
    UA_Node *node =
        UA_NODESTORE_GET_EDIT_SELECTIVE(server, nodeId, 0, UA_REFERENCETYPESET_NONE,
                                        UA_BROWSEDIRECTION_INVALID);
    if(!node)
        return UA_STATUSCODE_BADNODEIDINVALID;
    UA_StatusCode res = UA_STATUSCODE_GOOD;
    if(node->head.nodeClass == UA_NODECLASS_VARIABLE)
        ((UA_VariableNode*)node)->isDynamic = isDynamic;
    else
        res = UA_STATUSCODE_BADINTERNALERROR;
    UA_NODESTORE_RELEASE(server, node);
    return res;
}

static UA_StatusCode
checkSetIsDynamicVariable(UA_Server *server, UA_Session *session,
                          const UA_NodeId *nodeId) {
    /* Get all hierarchical reference types */
    UA_ReferenceTypeSet reftypes_hierarchical;
    UA_ReferenceTypeSet_init(&reftypes_hierarchical);
    UA_NodeId hierarchicalRefs = UA_NS0ID(HIERARCHICALREFERENCES);
    UA_StatusCode res =
        referenceTypeIndices(server, &hierarchicalRefs, &reftypes_hierarchical, true);
    if(res != UA_STATUSCODE_GOOD)
        return res;

    /* Is the variable under the server object? */
    UA_NodeId serverNodeId = UA_NS0ID(SERVER);
    if(isNodeInTree(server, nodeId, &serverNodeId, &reftypes_hierarchical))
        return UA_STATUSCODE_GOOD;

    /* Is the variable in the type hierarchy? */
    UA_NodeId typesNodeId = UA_NS0ID(TYPESFOLDER);
    if(isNodeInTree(server, nodeId, &typesNodeId, &reftypes_hierarchical))
        return UA_STATUSCODE_GOOD;

    /* Is the variable a property of a method node (InputArguments /
     * OutputArguments)? */
    UA_BrowseDescription bd;
    UA_BrowseDescription_init(&bd);
    bd.nodeId = *nodeId;
    bd.browseDirection = UA_BROWSEDIRECTION_INVERSE;
    bd.referenceTypeId = UA_NS0ID(HASPROPERTY);
    bd.includeSubtypes = false;
    bd.nodeClassMask = UA_NODECLASS_METHOD;
    UA_BrowseResult br;
    UA_BrowseResult_init(&br);
    UA_UInt32 maxrefs = 0;
    Operation_Browse(server, session, &maxrefs, &bd, &br);
    UA_Boolean hasParentMethod = (br.referencesSize > 0);
    UA_BrowseResult_clear(&br);
    if(hasParentMethod)
        return UA_STATUSCODE_GOOD;

    /* Set the variable to "dynamic" */
    return setVariableNodeDynamic(server, nodeId, true);
}

UA_StatusCode
UA_Server_setVariableNodeDynamic(UA_Server *server, const UA_NodeId nodeId,
                                 UA_Boolean isDynamic) {
    lockServer(server);
    UA_StatusCode res = setVariableNodeDynamic(server, &nodeId, isDynamic);
    unlockServer(server);
    return res;
}

/* Children, references, type-checking, constructors. */
UA_StatusCode
addNode_finish(UA_Server *server, UA_Session *session, const UA_NodeId *nodeId) {
    /* Get the node */
    const UA_Node *type = NULL;
    const UA_Node *node = UA_NODESTORE_GET(server, nodeId);
    if(!node)
        return UA_STATUSCODE_BADNODEIDUNKNOWN;

    /* Set the ReferenceTypesSet of subtypes in the ReferenceTypeNode */
    UA_StatusCode retval = UA_STATUSCODE_GOOD;
    if(node->head.nodeClass == UA_NODECLASS_REFERENCETYPE) {
        retval = setReferenceTypeSubtypes(server, &node->referenceTypeNode);
        if(retval != UA_STATUSCODE_GOOD)
            goto cleanup;
    }

    /* Check NodeClass for 'hasSubtype'. UA_NODECLASS_VARIABLE not allowed
     * to have subtype */
    if(node->head.nodeClass == UA_NODECLASS_VARIABLE) {
        for(size_t i = 0; i < node->head.referencesSize; i++) {
            if(node->head.references[i].referenceTypeIndex ==
               UA_REFERENCETYPEINDEX_HASSUBTYPE) {
                UA_LOG_INFO_SESSION(server->config.logging, session,
                                    "AddNode (%N): Variable not allowed "
                                    "to have HasSubType reference",
                                    node->head.nodeId);
                retval = UA_STATUSCODE_BADREFERENCENOTALLOWED;
                goto cleanup;
            }
        }
    }

    /* Set variables to dynamic (source and server timestamps are meaningful) if
     * they fulfill some conditions. (Do this before replacing the default value
     * in a VariableNode. So that the sourceTimestamp is set in the Write service.) */
    if(node->head.nodeClass == UA_NODECLASS_VARIABLE) {
        retval = checkSetIsDynamicVariable(server, session, nodeId);
        if(retval != UA_STATUSCODE_GOOD)
            goto cleanup;
    }

    /* Get the type node
     * TODO: Do we need all attributes and references here?  */
    if(node->head.nodeClass == UA_NODECLASS_VARIABLE ||
       node->head.nodeClass == UA_NODECLASS_VARIABLETYPE ||
       node->head.nodeClass == UA_NODECLASS_OBJECT) {
        type = getNodeType(server, &node->head, ~(UA_UInt32)0,
                           UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
        if(!type) {
            if(server->bootstrapNS0)
                goto constructor;
            logAddNode(server->config.logging, session, &node->head.nodeId,
                       "Node type not found");
            retval = UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
            goto cleanup;
        }
    }

    /* Initialize and typecheck the variable */
    if(node->head.nodeClass == UA_NODECLASS_VARIABLE ||
       node->head.nodeClass == UA_NODECLASS_VARIABLETYPE) {
        /* Use attributes from the type. The value and value constraints are the
         * same for the variable and variabletype attribute structs. */
        retval = useVariableTypeAttributes(server, session,
                                           &node->variableNode,
                                           &type->variableTypeNode);
        if(retval != UA_STATUSCODE_GOOD) {
            UA_LOG_INFO_SESSION(server->config.logging, session,
                                "AddNode (%N): Using attributes for from "
                                "the variable type failed with error code %s",
                                node->head.nodeId, UA_StatusCode_name(retval));
            goto cleanup;
        }

        /* Get a new pointer to the node. It might have been updated in the
         * background due to a 'Write' operation inside
         * useVariableTypeAttributes. */
        UA_NODESTORE_RELEASE(server, node);
        node = UA_NODESTORE_GET(server, nodeId);
        if(!node || (node->head.nodeClass != UA_NODECLASS_VARIABLE &&
                     node->head.nodeClass != UA_NODECLASS_VARIABLETYPE)) {
            retval = UA_STATUSCODE_BADINTERNALERROR;
            goto cleanup;
        }

        /* 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, &node->variableNode,
                                       &type->variableTypeNode);
        if(retval != UA_STATUSCODE_GOOD) {
            UA_LOG_INFO_SESSION(server->config.logging, session,
                                "AddNode (%N): Type-checking failed with error code %s",
                                node->head.nodeId, UA_StatusCode_name(retval));
            goto cleanup;
        }
    }

    /* Add (mandatory) child nodes from the type definition */
    if(node->head.nodeClass == UA_NODECLASS_VARIABLE ||
       node->head.nodeClass == UA_NODECLASS_OBJECT) {
        retval = addTypeChildren(server, session, nodeId, &type->head.nodeId);
        if(retval != UA_STATUSCODE_GOOD) {
            UA_LOG_INFO_SESSION(server->config.logging, session,
                                "AddNode (%N): Adding child nodes "
                                "failed with error code %s",
                                node->head.nodeId, UA_StatusCode_name(retval));
            goto cleanup;
        }
    }

    /* Add (mandatory) child nodes from the HasInterface references */
    if(node->head.nodeClass == UA_NODECLASS_OBJECT) {
        retval = addInterfaceChildren(server, session, nodeId);
        if(retval != UA_STATUSCODE_GOOD) {
            UA_LOG_INFO_SESSION(server->config.logging, session,
                                "AddNode (%N): Adding child nodes "
                                "interface failed with error code %s",
                                node->head.nodeId, UA_StatusCode_name(retval));
            goto cleanup;
        }
    }

    /* Call the constructor(s) */
 constructor:
    if(!node->head.constructed)
        retval = recursiveCallConstructors(server, session, nodeId, type);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_INFO_SESSION(server->config.logging, session,
                            "AddNode (%N): Calling the node constructor(s) "
                            "failed with error code %s",
                            node->head.nodeId, UA_StatusCode_name(retval));
    }

 cleanup:
    UA_NODESTORE_RELEASE(server, node);
    if(type)
        UA_NODESTORE_RELEASE(server, type);
    if(retval != UA_STATUSCODE_GOOD)
        deleteNode(server, *nodeId, true);
    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_clear(&result->addedNodeId);
}

UA_Boolean
Service_AddNodes(UA_Server *server, UA_Session *session,
                 const UA_AddNodesRequest *request,
                 UA_AddNodesResponse *response) {
    UA_LOG_DEBUG_SESSION(server->config.logging, session, "Processing AddNodesRequest");
    UA_LOCK_ASSERT(&server->serviceMutex);

    if(server->config.maxNodesPerNodeManagement != 0 &&
       request->nodesToAddSize > server->config.maxNodesPerNodeManagement) {
        response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYOPERATIONS;
        return true;
    }

    response->responseHeader.serviceResult =
        allocProcessServiceOperations(server, session,
                                      (UA_ServiceOperation)Operation_addNode, NULL,
                                      &request->nodesToAddSize,
                                      &UA_TYPES[UA_TYPES_ADDNODESITEM],
                                      &response->resultsSize,
                                      &UA_TYPES[UA_TYPES_ADDNODESRESULT]);
    return true;
}

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 void *attr, const UA_DataType *attributeType,
        void *nodeContext, UA_NodeId *outNewNodeId) {
    UA_LOCK_ASSERT(&server->serviceMutex);

    /* 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;
    UA_ExtensionObject_setValueNoDelete(&item.nodeAttributes,
                                        (void*)(uintptr_t)attr, attributeType);

    /* 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_clear(&result.addedNodeId);
    return result.statusCode;
}

static UA_StatusCode
__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) {
    lockServer(server);
    UA_StatusCode reval =
        addNode(server, nodeClass, *requestedNewNodeId, *parentNodeId,
                *referenceTypeId, browseName, *typeDefinition, attr,
                attributeType, nodeContext, outNewNodeId);
    unlockServer(server);
    return reval;
}

UA_StatusCode
UA_Server_addVariableNode(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, void *nodeContext,
                          UA_NodeId *outNewNodeId) {
    return __Server_addNode(server, UA_NODECLASS_VARIABLE, &requestedNewNodeId,
                            &parentNodeId, &referenceTypeId, browseName,
                            &typeDefinition, (const UA_NodeAttributes*)&attr,
                            &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES],
                            nodeContext, outNewNodeId);
}

UA_StatusCode
UA_Server_addVariableTypeNode(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_VariableTypeAttributes attr, void *nodeContext,
                              UA_NodeId *outNewNodeId) {
    return __Server_addNode(server, UA_NODECLASS_VARIABLETYPE,
                            &requestedNewNodeId, &parentNodeId, &referenceTypeId,
                            browseName, &typeDefinition,
                            (const UA_NodeAttributes*)&attr,
                            &UA_TYPES[UA_TYPES_VARIABLETYPEATTRIBUTES],
                            nodeContext, outNewNodeId);
}

UA_StatusCode
UA_Server_addObjectNode(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_ObjectAttributes attr, void *nodeContext,
                        UA_NodeId *outNewNodeId) {
    return __Server_addNode(server, UA_NODECLASS_OBJECT, &requestedNewNodeId,
                            &parentNodeId, &referenceTypeId, browseName,
                            &typeDefinition, (const UA_NodeAttributes*)&attr,
                            &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES],
                            nodeContext, outNewNodeId);
}

UA_StatusCode
UA_Server_addObjectTypeNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
                            const UA_NodeId parentNodeId, const UA_NodeId referenceTypeId,
                            const UA_QualifiedName browseName, const UA_ObjectTypeAttributes attr,
                            void *nodeContext, UA_NodeId *outNewNodeId) {
    return __Server_addNode(server, UA_NODECLASS_OBJECTTYPE, &requestedNewNodeId,
                            &parentNodeId, &referenceTypeId, browseName,
                            &UA_NODEID_NULL, (const UA_NodeAttributes*)&attr,
                            &UA_TYPES[UA_TYPES_OBJECTTYPEATTRIBUTES],
                            nodeContext, outNewNodeId);
}

UA_StatusCode
UA_Server_addViewNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
                      const UA_NodeId parentNodeId, const UA_NodeId referenceTypeId,
                      const UA_QualifiedName browseName, const UA_ViewAttributes attr,
                      void *nodeContext, UA_NodeId *outNewNodeId) {
    return __Server_addNode(server, UA_NODECLASS_VIEW, &requestedNewNodeId,
                            &parentNodeId, &referenceTypeId, browseName,
                            &UA_NODEID_NULL, (const UA_NodeAttributes*)&attr,
                            &UA_TYPES[UA_TYPES_VIEWATTRIBUTES],
                            nodeContext, outNewNodeId);
}

UA_StatusCode
UA_Server_addReferenceTypeNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
                               const UA_NodeId parentNodeId, const UA_NodeId referenceTypeId,
                               const UA_QualifiedName browseName,
                               const UA_ReferenceTypeAttributes attr, void *nodeContext,
                               UA_NodeId *outNewNodeId) {
    return __Server_addNode(server, UA_NODECLASS_REFERENCETYPE,
                            &requestedNewNodeId, &parentNodeId, &referenceTypeId,
                            browseName, &UA_NODEID_NULL,
                            (const UA_NodeAttributes*)&attr,
                            &UA_TYPES[UA_TYPES_REFERENCETYPEATTRIBUTES],
                            nodeContext, outNewNodeId);
}

UA_StatusCode
UA_Server_addDataTypeNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
                          const UA_NodeId parentNodeId, const UA_NodeId referenceTypeId,
                          const UA_QualifiedName browseName, const UA_DataTypeAttributes attr,
                          void *nodeContext, UA_NodeId *outNewNodeId) {
    return __Server_addNode(server, UA_NODECLASS_DATATYPE, &requestedNewNodeId,
                            &parentNodeId, &referenceTypeId, browseName,
                            &UA_NODEID_NULL, (const UA_NodeAttributes*)&attr,
                            &UA_TYPES[UA_TYPES_DATATYPEATTRIBUTES],
                            nodeContext, outNewNodeId);
}

UA_StatusCode
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;
    UA_ExtensionObject_setValueNoDelete(&item.nodeAttributes,
                                        (void*)(uintptr_t)attr, attributeType);
    return Operation_addNode_begin(server, &server->adminSession, nodeContext, &item,
                                   &parentNodeId, &referenceTypeId, outNewNodeId);
}

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) {
    lockServer(server);
    UA_StatusCode res =
        addNode_begin(server, nodeClass, requestedNewNodeId, parentNodeId,
                      referenceTypeId, browseName, typeDefinition, attr,
                      attributeType, nodeContext, outNewNodeId);
    unlockServer(server);
    return res;
}

UA_StatusCode
UA_Server_addNode_finish(UA_Server *server, const UA_NodeId nodeId) {
    lockServer(server);
    UA_StatusCode retval = addNode_finish(server, &server->adminSession, &nodeId);
    unlockServer(server);
    return retval;
}

/****************/
/* Delete Nodes */
/****************/

static void
Operation_deleteReference(UA_Server *server, UA_Session *session, void *context,
                          const UA_DeleteReferencesItem *item, UA_StatusCode *retval);

struct RemoveIncomingContext {
    UA_Server *server;
    UA_Session *session;
    UA_DeleteReferencesItem *item;
};

static void *
removeIncomingReferencesCallback(void *context, UA_ReferenceTarget *t) {
    struct RemoveIncomingContext *ctx = (struct RemoveIncomingContext *)context;
    if(!UA_NodePointer_isLocal(t->targetId))
        return NULL;
    UA_StatusCode dummy;
    ctx->item->sourceNodeId = UA_NodePointer_toNodeId(t->targetId);
    Operation_deleteReference(ctx->server, ctx->session, NULL, ctx->item, &dummy);
    return NULL;
}

/* Remove references to this node (in the other nodes) */
static void
removeIncomingReferences(UA_Server *server, UA_Session *session,
                         const UA_NodeHead *head) {
    UA_DeleteReferencesItem item;
    UA_DeleteReferencesItem_init(&item);
    item.targetNodeId.nodeId = head->nodeId;
    item.deleteBidirectional = false;

    struct RemoveIncomingContext ctx;
    ctx.server = server;
    ctx.session = session;
    ctx.item = &item;

    for(size_t i = 0; i < head->referencesSize; ++i) {
        UA_NodeReferenceKind *rk = &head->references[i];
        item.isForward = rk->isInverse;
        item.referenceTypeId =
            *UA_NODESTORE_GETREFERENCETYPEID(server, rk->referenceTypeIndex);
        UA_NodeReferenceKind_iterate(rk, removeIncomingReferencesCallback, &ctx);
    }
}

static void *
checkTargetInRefTree(void *context, UA_ReferenceTarget *t) {
    RefTree *refTree = (RefTree*)context;
    if(!UA_NodePointer_isLocal(t->targetId))
        return NULL;
    UA_NodeId tmpId = UA_NodePointer_toNodeId(t->targetId);
    if(!RefTree_containsNodeId(refTree, &tmpId))
        return (void*)0x1;
    return NULL;
}

/* A node is auto-deleted if all its hierarchical parents are being deleted */
static UA_Boolean
hasParentRef(const UA_NodeHead *head, const UA_ReferenceTypeSet *refSet,
             RefTree *refTree) {
    for(size_t i = 0; i < head->referencesSize; i++) {
        UA_NodeReferenceKind *rk = &head->references[i];
        if(!rk->isInverse)
            continue;
        if(!UA_ReferenceTypeSet_contains(refSet, rk->referenceTypeIndex))
            continue;
        if(UA_NodeReferenceKind_iterate(rk, checkTargetInRefTree, refTree) != NULL)
            return true;
    }
    return false;
}

static void
deconstructNodeSet(UA_Server *server, UA_Session *session,
                   UA_ReferenceTypeSet *hierarchRefsSet, RefTree *refTree) {
    UA_LOCK_ASSERT(&server->serviceMutex);

    /* Deconstruct the nodes based on the RefTree entries, parent nodes first */
    for(size_t i = 0; i < refTree->size; i++) {
        const UA_Node *member = UA_NODESTORE_GET(server, &refTree->targets[i].nodeId);
        if(!member)
            continue;

        /* Call the type-level destructor
         * TODO: Do we need all attributes and references here?  */
        void *context = member->head.context; /* No longer needed after this function */
        if(member->head.nodeClass == UA_NODECLASS_OBJECT ||
           member->head.nodeClass == UA_NODECLASS_VARIABLE) {
            const UA_Node *type =
                getNodeType(server, &member->head, ~(UA_UInt32)0,
                            UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
            if(type) {
               /* Get the lifecycle */
               const UA_NodeTypeLifecycle *lifecycle;
               if(member->head.nodeClass == UA_NODECLASS_OBJECT)
                  lifecycle = &type->objectTypeNode.lifecycle;
               else
                  lifecycle = &type->variableTypeNode.lifecycle;

               /* Call the destructor */
               if(lifecycle->destructor) {
                  lifecycle->destructor(server,
                                        &session->sessionId, session->context,
                                        &type->head.nodeId, type->head.context,
                                        &member->head.nodeId, &context);
               }

               /* Release the type node */
               UA_NODESTORE_RELEASE(server, type);
            }
        }

        /* Call the global destructor */
        if(server->config.nodeLifecycle &&
           server->config.nodeLifecycle->destructor) {
            server->config.nodeLifecycle->destructor(server, &session->sessionId,
                                                     session->context,
                                                     &member->head.nodeId, context);
        }

        /* Release the node. Don't access the node context from here on. */
        UA_NODESTORE_RELEASE(server, member);

        /* Set the constructed flag to false */
        editNode(server, &server->adminSession, &refTree->targets[i].nodeId,
                 0, UA_REFERENCETYPESET_NONE, UA_BROWSEDIRECTION_INVALID,
                 (UA_EditNodeCallback)setDeconstructedNode, NULL);
    }
}

struct DeleteChildrenContext {
    UA_Server *server;
    const UA_ReferenceTypeSet *hierarchRefsSet;
    RefTree *refTree;
    UA_StatusCode res;
};

static void *
deleteChildrenCallback(void *context, UA_ReferenceTarget *t) {
    struct DeleteChildrenContext *ctx = (struct DeleteChildrenContext*)context;

    /* Get the child */
    const UA_Node *child = UA_NODESTORE_GETFROMREF(ctx->server, t->targetId);
    if(!child)
        return NULL;

    /* Only delete child nodes that have no other parent */
    if(!hasParentRef(&child->head, ctx->hierarchRefsSet, ctx->refTree))
        ctx->res = RefTree_addNodeId(ctx->refTree, &child->head.nodeId, NULL);

    UA_NODESTORE_RELEASE(ctx->server, child);
    return (ctx->res == UA_STATUSCODE_GOOD) ? NULL : (void*)0x01;
}

/* The processNodeLayer function searches all children's of the head node and
 * adds the children node to the RefTree if all incoming references sources are
 * contained in the RefTree (No callback references to this node --> node can be
 * deleted) */
static UA_StatusCode
autoDeleteChildren(UA_Server *server, UA_Session *session, RefTree *refTree,
                   const UA_ReferenceTypeSet *hierarchRefsSet, const UA_NodeHead *head){
    struct DeleteChildrenContext ctx;
    ctx.server = server;
    ctx.hierarchRefsSet = hierarchRefsSet;
    ctx.refTree = refTree;
    ctx.res = UA_STATUSCODE_GOOD;

    for(size_t i = 0; i < head->referencesSize; ++i) {
        /* Check if the ReferenceType is hierarchical */
        UA_NodeReferenceKind *rk = &head->references[i];
        if(!UA_ReferenceTypeSet_contains(hierarchRefsSet, rk->referenceTypeIndex))
            continue;

        /* Check if the references are forward (to a child) */
        if(rk->isInverse)
            continue;

        /* Loop over the references */
        UA_NodeReferenceKind_iterate(rk, deleteChildrenCallback, &ctx);
        if(ctx.res != UA_STATUSCODE_GOOD)
            return ctx.res;
    }
    return UA_STATUSCODE_GOOD;
}

/* Build up an ordered set (tree) of all nodes that can be deleted. Step through
 * the ordered set in order to avoid recursion. */
static UA_StatusCode
buildDeleteNodeSet(UA_Server *server, UA_Session *session,
                   const UA_ReferenceTypeSet *hierarchRefsSet,
                   const UA_NodeId *initial, UA_Boolean removeTargetRefs,
                   RefTree *refTree) {
    /* Add the initial node to delete */
    UA_StatusCode res = RefTree_addNodeId(refTree, initial, NULL);
    if(res != UA_STATUSCODE_GOOD)
        return res;

    /* Find out which hierarchical children should also be deleted. We know
     * there are no "callback" ExpandedNodeId in the RefTree. */
    size_t pos = 0;
    while(pos < refTree->size) {
        const UA_Node *member = UA_NODESTORE_GET(server, &refTree->targets[pos].nodeId);
        pos++;
        if(!member)
            continue;
        res |= autoDeleteChildren(server, session, refTree, hierarchRefsSet, &member->head);
        UA_NODESTORE_RELEASE(server, member);
    }
    return res;
}

static void
deleteNodeSet(UA_Server *server, UA_Session *session,
              const UA_ReferenceTypeSet *hierarchRefsSet,
              UA_Boolean removeTargetRefs, RefTree *refTree) {
    /* Delete the nodes based on the RefTree entries */
    for(size_t i = refTree->size; i > 0; --i) {
        const UA_Node *member = UA_NODESTORE_GET(server, &refTree->targets[i-1].nodeId);
        if(!member)
            continue;
        UA_NODESTORE_RELEASE(server, member);
        if(removeTargetRefs)
            removeIncomingReferences(server, session, &member->head);
        UA_NODESTORE_REMOVE(server, &member->head.nodeId);
    }
}

static void
deleteNodeOperation(UA_Server *server, UA_Session *session, void *context,
                    const UA_DeleteNodesItem *item, UA_StatusCode *result) {
    UA_LOCK_ASSERT(&server->serviceMutex);

    /* Do not check access for server */
    if(session != &server->adminSession && server->config.accessControl.allowDeleteNode) {
        if(!server->config.accessControl.
           allowDeleteNode(server, &server->config.accessControl,
                           &session->sessionId, session->context, item)) {
            *result = UA_STATUSCODE_BADUSERACCESSDENIED;
            return;
        }
    }

    const UA_Node *node = UA_NODESTORE_GET(server, &item->nodeId);
    if(!node) {
        *result = UA_STATUSCODE_BADNODEIDUNKNOWN;
        return;
    }

    if(UA_Node_hasSubTypeOrInstances(&node->head)) {
        UA_LOG_INFO_SESSION(server->config.logging, session,
                            "DeleteNode (%N): Cannot delete a type node with "
                            "active instances or subtypes", node->head.nodeId);
        UA_NODESTORE_RELEASE(server, 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 */

    /* Relase the node. Don't access the pointer after this! */
    UA_NODESTORE_RELEASE(server, node);

    /* 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_ReferenceTypeSet hierarchRefsSet;
    UA_NodeId hr = UA_NS0ID(HIERARCHICALREFERENCES);
    *result = referenceTypeIndices(server, &hr, &hierarchRefsSet, true);
    if(*result != UA_STATUSCODE_GOOD)
        return;

    /* The list of childs is needed for the deconstructing and deleting phase.
     * Within the processNodeLayer we generate a RefTree based set of childs
     * which can be deleted beside the parent node. */
    RefTree refTree;
    *result = RefTree_init(&refTree);
    if(*result != UA_STATUSCODE_GOOD)
        return;
    *result = buildDeleteNodeSet(server, session, &hierarchRefsSet, &item->nodeId,
                                 item->deleteTargetReferences, &refTree);
    if(*result != UA_STATUSCODE_GOOD) {
        UA_LOG_WARNING_SESSION(server->config.logging, session,
                               "DeleteNode: Incomplete lookup of nodes. "
                               "Still deleting what we have.");
        /* Continue, so the RefTree is cleaned up. Return the error message
         * anyway. */
    }

    /* Deconstruct, then delete, then clean up the set */
    deconstructNodeSet(server, session, &hierarchRefsSet, &refTree);
    deleteNodeSet(server, session, &hierarchRefsSet,
                  item->deleteTargetReferences, &refTree);
    RefTree_clear(&refTree);
}

UA_Boolean
Service_DeleteNodes(UA_Server *server, UA_Session *session,
                    const UA_DeleteNodesRequest *request,
                    UA_DeleteNodesResponse *response) {
    UA_LOG_DEBUG_SESSION(server->config.logging, session,
                         "Processing DeleteNodesRequest");
    UA_LOCK_ASSERT(&server->serviceMutex);

    if(server->config.maxNodesPerNodeManagement != 0 &&
       request->nodesToDeleteSize > server->config.maxNodesPerNodeManagement) {
        response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYOPERATIONS;
        return true;
    }

    response->responseHeader.serviceResult =
        allocProcessServiceOperations(server, session,
                                      (UA_ServiceOperation)deleteNodeOperation,
                                      NULL, &request->nodesToDeleteSize,
                                      &UA_TYPES[UA_TYPES_DELETENODESITEM],
                                      &response->resultsSize,
                                      &UA_TYPES[UA_TYPES_STATUSCODE]);
    return true;
}

UA_StatusCode
UA_Server_deleteNode(UA_Server *server, const UA_NodeId nodeId,
                     UA_Boolean deleteReferences) {
    lockServer(server);
    UA_StatusCode retval = deleteNode(server, nodeId, deleteReferences);
    unlockServer(server);
    return retval;
}

UA_StatusCode
deleteNode(UA_Server *server, const UA_NodeId nodeId,
           UA_Boolean deleteReferences) {
    UA_LOCK_ASSERT(&server->serviceMutex);
    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 void
Operation_addReference(UA_Server *server, UA_Session *session, void *context,
                       const UA_AddReferencesItem *item, UA_StatusCode *retval) {
    (void)context;
    UA_assert(session);
    UA_LOCK_ASSERT(&server->serviceMutex);

    /* Check access rights */
    if(session != &server->adminSession && server->config.accessControl.allowAddReference) {
        if (!server->config.accessControl.
                allowAddReference(server, &server->config.accessControl,
                                  &session->sessionId, session->context, item)) {
            *retval = UA_STATUSCODE_BADUSERACCESSDENIED;
            return;
        }
    }

    /* TODO: Currently no expandednodeids are allowed */
    if(item->targetServerUri.length > 0) {
        *retval = UA_STATUSCODE_BADNOTIMPLEMENTED;
        return;
    }

    /* Check the ReferenceType and get the index */
    const UA_Node *refType = UA_NODESTORE_GET(server, &item->referenceTypeId);
    if(!refType) {
        UA_LOG_DEBUG_SESSION(server->config.logging, session,
                             "Cannot add reference - ReferenceType %N unknown",
                             item->referenceTypeId);
        *retval = UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
        return;
    }
    if(refType->head.nodeClass != UA_NODECLASS_REFERENCETYPE) {
        UA_LOG_DEBUG_SESSION(server->config.logging, session,
                             "Cannot add reference - ReferenceType %N with wrong NodeClass",
                             item->referenceTypeId);
        UA_NODESTORE_RELEASE(server, refType);
        *retval = UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
        return;
    }
    UA_Byte refTypeIndex = refType->referenceTypeNode.referenceTypeIndex;
    UA_NODESTORE_RELEASE(server, refType);

    /* Get the source and target node (editable). Include only the BrowseName
     * and the relevant ReferenceType and direction. Don't modify the target
     * node if it lives on a different server. */
    UA_Node *targetNode = NULL;
    if(UA_ExpandedNodeId_isLocal(&item->targetNodeId)) {
        if(UA_NodeId_equal(&item->targetNodeId.nodeId, &item->sourceNodeId)) {
            *retval = UA_STATUSCODE_GOOD;
            UA_LOG_INFO_SESSION(server->config.logging, session,
                                "Cannot add reference - source and target %N are identical",
                                item->targetNodeId.nodeId);
            return;
        }
        targetNode =
            UA_NODESTORE_GET_EDIT_SELECTIVE(server, &item->targetNodeId.nodeId,
                                            UA_NODEATTRIBUTESMASK_BROWSENAME,
                                            UA_REFTYPESET(refTypeIndex),
                                            (!item->isForward) ?
                                            UA_BROWSEDIRECTION_FORWARD : UA_BROWSEDIRECTION_INVERSE);
        if(!targetNode) {
            UA_LOG_DEBUG_SESSION(server->config.logging, session,
                                 "Cannot add reference - target %N does not exist",
                                 item->targetNodeId.nodeId);
            *retval = UA_STATUSCODE_BADTARGETNODEIDINVALID;
            return;
        }
    }

    UA_Node *sourceNode =
        UA_NODESTORE_GET_EDIT_SELECTIVE(server, &item->sourceNodeId,
                                        UA_NODEATTRIBUTESMASK_BROWSENAME,
                                        UA_REFTYPESET(refTypeIndex),
                                        item->isForward ?
                                        UA_BROWSEDIRECTION_FORWARD : UA_BROWSEDIRECTION_INVERSE);
    if(!sourceNode) {
        if(targetNode)
            UA_NODESTORE_RELEASE(server, targetNode);
        *retval = UA_STATUSCODE_BADSOURCENODEIDINVALID;
        return;
    }

    /* Add the first direction */
    UA_UInt32 targetNameHash = UA_QualifiedName_hash(&targetNode->head.browseName);
    *retval = UA_Node_addReference(sourceNode, refTypeIndex, item->isForward,
                                   &item->targetNodeId, targetNameHash);
    UA_Boolean firstExisted = false;
    if(*retval == UA_STATUSCODE_BADDUPLICATEREFERENCENOTALLOWED) {
        *retval = UA_STATUSCODE_GOOD;
        firstExisted = true;
    }
    if(*retval != UA_STATUSCODE_GOOD)
        goto cleanup;

    /* Add the second direction */
    if(targetNode) {
        UA_ExpandedNodeId expSourceId;
        UA_ExpandedNodeId_init(&expSourceId);
        expSourceId.nodeId = item->sourceNodeId;
        UA_UInt32 sourceNameHash = UA_QualifiedName_hash(&sourceNode->head.browseName);
        *retval = UA_Node_addReference(targetNode, refTypeIndex, !item->isForward,
                                       &expSourceId, sourceNameHash);

        /* Second direction existed already */
        if(*retval == UA_STATUSCODE_BADDUPLICATEREFERENCENOTALLOWED) {
            /* Set bad result if BOTH directions already existed.
             * But don't delete the reference from source to target. */
            if(!firstExisted)
                *retval = UA_STATUSCODE_GOOD;
            goto cleanup;
        }

        /* Remove first direction if the second direction failed */
        if(*retval != UA_STATUSCODE_GOOD)
            UA_Node_deleteReference(sourceNode, refTypeIndex, item->isForward, &item->targetNodeId);
    }

 cleanup:
    if(targetNode)
        UA_NODESTORE_RELEASE(server, targetNode);
    UA_NODESTORE_RELEASE(server, sourceNode);
}

UA_Boolean
Service_AddReferences(UA_Server *server, UA_Session *session,
                      const UA_AddReferencesRequest *request,
                      UA_AddReferencesResponse *response) {
    UA_LOG_DEBUG_SESSION(server->config.logging, session,
                         "Processing AddReferencesRequest");
    UA_LOCK_ASSERT(&server->serviceMutex);
    UA_assert(session);

    if(server->config.maxNodesPerNodeManagement != 0 &&
       request->referencesToAddSize > server->config.maxNodesPerNodeManagement) {
        response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYOPERATIONS;
        return true;
    }

    response->responseHeader.serviceResult =
        allocProcessServiceOperations(server, session,
                                      (UA_ServiceOperation)Operation_addReference,
                                      NULL, &request->referencesToAddSize,
                                      &UA_TYPES[UA_TYPES_ADDREFERENCESITEM],
                                      &response->resultsSize,
                                      &UA_TYPES[UA_TYPES_STATUSCODE]);
    return true;
}

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;
    lockServer(server);
    Operation_addReference(server, &server->adminSession, NULL, &item, &retval);
    unlockServer(server);
    return retval;
}

/*********************/
/* Delete References */
/*********************/

static void
Operation_deleteReference(UA_Server *server, UA_Session *session, void *context,
                          const UA_DeleteReferencesItem *item, UA_StatusCode *retval) {
    UA_LOCK_ASSERT(&server->serviceMutex);

    /* Do not check access for server */
    if(session != &server->adminSession &&
       server->config.accessControl.allowDeleteReference) {
        if(!server->config.accessControl.
           allowDeleteReference(server, &server->config.accessControl,
                                &session->sessionId, session->context, item)) {
            *retval = UA_STATUSCODE_BADUSERACCESSDENIED;
            return;
        }
    }

    /* Check the ReferenceType and get the RefTypeIndex */
    const UA_Node *refType =
        UA_NODESTORE_GET_SELECTIVE(server, &item->referenceTypeId,
                                   0, UA_REFERENCETYPESET_NONE, UA_BROWSEDIRECTION_INVALID);
    if(!refType) {
        *retval = UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
        return;
    }

    if(refType->head.nodeClass != UA_NODECLASS_REFERENCETYPE) {
        UA_NODESTORE_RELEASE(server, refType);
        *retval = UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
        return;
    }

    UA_Byte refTypeIndex = refType->referenceTypeNode.referenceTypeIndex;
    UA_NODESTORE_RELEASE(server, refType);

    // TODO: Check consistency constraints, remove the references.

    /* Delete the reference in this direction */
    UA_Node *firstNode =
        UA_NODESTORE_GET_EDIT_SELECTIVE(server, &item->sourceNodeId, 0,
                                        UA_REFTYPESET(refTypeIndex),
                                        item->isForward ?
                                        UA_BROWSEDIRECTION_FORWARD : UA_BROWSEDIRECTION_INVERSE);
    if(firstNode) {
        *retval = UA_Node_deleteReference(firstNode, refTypeIndex, item->isForward, &item->targetNodeId);
    } else {
        *retval = UA_STATUSCODE_BADNODEIDUNKNOWN;
    }
    UA_NODESTORE_RELEASE(server, firstNode);
    if(*retval != UA_STATUSCODE_GOOD)
        return;

    if(!item->deleteBidirectional || item->targetNodeId.serverIndex != 0)
        return;

    /* Remove the reference in the second direction */
    if(UA_ExpandedNodeId_isLocal(&item->targetNodeId)) {
        UA_ExpandedNodeId target2;
        UA_ExpandedNodeId_init(&target2);
        target2.nodeId = item->sourceNodeId;
        UA_Node *secondNode =
            UA_NODESTORE_GET_EDIT_SELECTIVE(server, &item->targetNodeId.nodeId, 0,
                                            UA_REFTYPESET(refTypeIndex),
                                            (!item->isForward) ?
                                            UA_BROWSEDIRECTION_FORWARD : UA_BROWSEDIRECTION_INVERSE);
        if(secondNode) {
            *retval = UA_Node_deleteReference(secondNode, refTypeIndex, !item->isForward, &target2);
            UA_NODESTORE_RELEASE(server, secondNode);
        }
    }
}

UA_Boolean
Service_DeleteReferences(UA_Server *server, UA_Session *session,
                         const UA_DeleteReferencesRequest *request,
                         UA_DeleteReferencesResponse *response) {
    UA_LOG_DEBUG_SESSION(server->config.logging, session,
                         "Processing DeleteReferencesRequest");
    UA_LOCK_ASSERT(&server->serviceMutex);

    if(server->config.maxNodesPerNodeManagement != 0 &&
       request->referencesToDeleteSize > server->config.maxNodesPerNodeManagement) {
        response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYOPERATIONS;
        return true;
    }

    response->responseHeader.serviceResult =
        allocProcessServiceOperations(server, session,
                                      (UA_ServiceOperation)Operation_deleteReference,
                                      NULL, &request->referencesToDeleteSize,
                                      &UA_TYPES[UA_TYPES_DELETEREFERENCESITEM],
                                      &response->resultsSize,
                                      &UA_TYPES[UA_TYPES_STATUSCODE]);
    return true;
}

UA_StatusCode
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;
    Operation_deleteReference(server, &server->adminSession, NULL, &item, &retval);
    return retval;
}

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) {
    lockServer(server);
    UA_StatusCode res = deleteReference(server, sourceNodeId, referenceTypeId,
                                        isForward, targetNodeId, deleteBidirectional);
    unlockServer(server);
    return res;
}

/*****************************/
/* Set Internal Value Source */
/*****************************/

struct SetInternalValueContext {
    const UA_DataValue *value;
    const UA_ValueSourceNotifications *notifications;
};

static UA_StatusCode
setInternalValueSourceCB(UA_Server *server, UA_Session *session,
                         UA_VariableNode *vn, const void *ctx) {
    const struct SetInternalValueContext *ivc =
        (const struct SetInternalValueContext*)ctx;

    /* Check the node class */
    if(vn->head.nodeClass != UA_NODECLASS_VARIABLE)
        return UA_STATUSCODE_BADNODECLASSINVALID;

    /* Make a copy of the supplied value */
    UA_DataValue val;
    UA_StatusCode res = UA_STATUSCODE_GOOD;
    if(ivc->value) {
        res = UA_DataValue_copy(ivc->value, &val);
        if(res != UA_STATUSCODE_GOOD)
            return res;
    }

    /* Replace the previous internal value */
    if(vn->valueSourceType == UA_VALUESOURCETYPE_INTERNAL) {
        if(ivc->value) {
            UA_DataValue_clear(&vn->valueSource.internal.value);
            vn->valueSource.internal.value = val;
        }
    } else {
        if(ivc->value)
            vn->valueSource.internal.value = val;
        else
            UA_DataValue_init(&vn->valueSource.internal.value);
        vn->valueSourceType = UA_VALUESOURCETYPE_INTERNAL;
    }

    /* Set the notification callbacks */
    if(ivc->notifications)
        vn->valueSource.internal.notifications = *ivc->notifications;
    else
        memset(&vn->valueSource.internal.notifications, 0,
               sizeof(UA_ValueSourceNotifications));

    return UA_STATUSCODE_GOOD;
}

UA_StatusCode
setVariableNode_internalValueSource(UA_Server *server, const UA_NodeId nodeId,
                                    const UA_DataValue *value,
                                    const UA_ValueSourceNotifications *notifications) {
    struct SetInternalValueContext ctx;
    ctx.value = value;
    ctx.notifications = notifications;
    return editNode(server, &server->adminSession, &nodeId,
                    UA_NODEATTRIBUTESMASK_VALUE, UA_REFERENCETYPESET_NONE,
                    UA_BROWSEDIRECTION_INVALID,
                    (UA_EditNodeCallback)setInternalValueSourceCB, &ctx);
}

UA_StatusCode
UA_Server_setVariableNode_internalValueSource(UA_Server *server, const UA_NodeId nodeId,
                                              const UA_DataValue *value,
                                              const UA_ValueSourceNotifications *notifications) {
    lockServer(server);
    UA_StatusCode res = setVariableNode_internalValueSource(server, nodeId, value, notifications);
    unlockServer(server);
    return res;
}

/*****************************/
/* Set External Value Source */
/*****************************/

struct SetExternalValueContext {
    UA_DataValue **value;
    const UA_ValueSourceNotifications *notifications;
};

static UA_StatusCode
setExternalValueSourceCB(UA_Server *server, UA_Session *session,
                         UA_VariableNode *vn, const void *ctx) {
    const struct SetExternalValueContext *evc =
        (const struct SetExternalValueContext*)ctx;

    /* Check the node class */
    if(vn->head.nodeClass != UA_NODECLASS_VARIABLE)
        return UA_STATUSCODE_BADNODECLASSINVALID;

    /* Clean the previous internal value */
    if(vn->valueSourceType == UA_VALUESOURCETYPE_INTERNAL && evc->value)
        UA_DataValue_clear(&vn->valueSource.internal.value);

    /* Set the value */
    vn->valueSourceType = UA_VALUESOURCETYPE_EXTERNAL;
    vn->valueSource.external.value = evc->value;
    if(evc->notifications)
        vn->valueSource.external.notifications = *evc->notifications;
    else
        memset(&vn->valueSource.external.notifications, 0,
               sizeof(UA_ValueSourceNotifications));

    return UA_STATUSCODE_GOOD;
}

UA_StatusCode
UA_Server_setVariableNode_externalValueSource(UA_Server *server, const UA_NodeId nodeId,
                                              UA_DataValue **value,
                                              const UA_ValueSourceNotifications *notifications) {
    if(!server || !value || !*value)
        return UA_STATUSCODE_BADINVALIDARGUMENT;
    lockServer(server);
    struct SetExternalValueContext ctx;
    ctx.value = value;
    ctx.notifications = notifications;
    UA_StatusCode res = editNode(server, &server->adminSession, &nodeId,
                                 UA_NODEATTRIBUTESMASK_VALUE, UA_REFERENCETYPESET_NONE,
                                 UA_BROWSEDIRECTION_INVALID,
                                 (UA_EditNodeCallback)setExternalValueSourceCB, &ctx);
    unlockServer(server);
    return res;
}

/*****************************/
/* Set Callback Value Source */
/*****************************/

static UA_StatusCode
setCallbackValueSourceCB(UA_Server *server, UA_Session *session,
                         UA_VariableNode *vn, const void *ctx) {
    const UA_CallbackValueSource *evs = (const UA_CallbackValueSource*)ctx;

    /* Check the node class */
    if(vn->head.nodeClass != UA_NODECLASS_VARIABLE)
        return UA_STATUSCODE_BADNODECLASSINVALID;

    /* Clean up the internal value */
    if(vn->valueSourceType == UA_VALUESOURCETYPE_INTERNAL)
        UA_DataValue_clear(&vn->valueSource.internal.value);

    /* Replace the value source */
    vn->valueSource.callback = *evs;
    vn->valueSourceType = UA_VALUESOURCETYPE_CALLBACK;

    return UA_STATUSCODE_GOOD;
}

UA_StatusCode
setVariableNode_callbackValueSource(UA_Server *server, const UA_NodeId nodeId,
                                    const UA_CallbackValueSource evs) {
    return editNode(server, &server->adminSession, &nodeId, UA_NODEATTRIBUTESMASK_VALUE,
                    UA_REFERENCETYPESET_NONE, UA_BROWSEDIRECTION_INVALID,
                    (UA_EditNodeCallback)setCallbackValueSourceCB,
                    (void*)(uintptr_t)&evs);
}

UA_StatusCode
UA_Server_setVariableNode_callbackValueSource(UA_Server *server,
                                              const UA_NodeId nodeId,
                                              const UA_CallbackValueSource evs) {
    lockServer(server);
    UA_StatusCode res = setVariableNode_callbackValueSource(server, nodeId, evs);
    unlockServer(server);
    return res;
}

UA_StatusCode
UA_Server_addCallbackValueSourceVariableNode(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_CallbackValueSource evs,
                                             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;
    UA_ExtensionObject_setValueNoDelete(&item.nodeAttributes, (void*)(uintptr_t)&attr,
                                        &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES]);
    UA_NodeId newNodeId;
    if(!outNewNodeId) {
        newNodeId = UA_NODEID_NULL;
        outNewNodeId = &newNodeId;
    }

    lockServer(server);

    /* 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 value source */
    retval = setVariableNode_callbackValueSource(server, *outNewNodeId, evs);
    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:
    unlockServer(server);
    if(outNewNodeId == &newNodeId)
        UA_NodeId_clear(&newNodeId);

    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_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_clear(&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, &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, &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_clear(&br);
    return retval;

error:
    deleteNode(server, nodeId, true);
    deleteNode(server, inputArgsId, true);
    deleteNode(server, outputArgsId, true);
    UA_BrowseResult_clear(&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) {
    lockServer(server);
    UA_StatusCode retval =
        UA_Server_addMethodNodeEx_finish(server, nodeId, method,
                                         inputArgumentsSize, inputArguments,
                                         UA_NODEID_NULL, NULL,
                                         outputArgumentsSize, outputArguments,
                                         UA_NODEID_NULL, NULL);
    unlockServer(server);
    return retval;
}

UA_StatusCode
addMethodNode(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;
    UA_ExtensionObject_setValueNoDelete(&item.nodeAttributes, (void*)(uintptr_t)attr,
                                        &UA_TYPES[UA_TYPES_METHODATTRIBUTES]);
    UA_NodeId newId;
    if(!outNewNodeId) {
        UA_NodeId_init(&newId);
        outNewNodeId = &newId;
    }
    UA_StatusCode retval = Operation_addNode_begin(server, &server->adminSession,
                                                   nodeContext, &item, &parentNodeId,
                                                   &referenceTypeId, outNewNodeId);
    if(retval != UA_STATUSCODE_GOOD)
        return retval;

    retval = UA_Server_addMethodNodeEx_finish(server, *outNewNodeId, method,
                                              inputArgumentsSize, inputArguments,
                                              inputArgumentsRequestedNewNodeId,
                                              inputArgumentsOutNewNodeId,
                                              outputArgumentsSize, outputArguments,
                                              outputArgumentsRequestedNewNodeId,
                                              outputArgumentsOutNewNodeId);
    if(outNewNodeId == &newId)
        UA_NodeId_clear(&newId);
    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) {
    lockServer(server);
    UA_StatusCode res = addMethodNode(server, requestedNewNodeId,
                                      parentNodeId, referenceTypeId,
                                      browseName, &attr, method,
                                      inputArgumentsSize, inputArguments,
                                      inputArgumentsRequestedNewNodeId,
                                      inputArgumentsOutNewNodeId,
                                      outputArgumentsSize,
                                      outputArguments,
                                      outputArgumentsRequestedNewNodeId,
                                      outputArgumentsOutNewNodeId,
                                      nodeContext, outNewNodeId);
    unlockServer(server);
    return res;
}

UA_StatusCode
UA_Server_addMethodNode(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,
                        size_t outputArgumentsSize, const UA_Argument *outputArguments,
                        void *nodeContext, UA_NodeId *outNewNodeId) {
    return UA_Server_addMethodNodeEx(server, requestedNewNodeId,  parentNodeId,
                                     referenceTypeId, browseName, attr, method,
                                     inputArgumentsSize, inputArguments,
                                     UA_NODEID_NULL, NULL,
                                     outputArgumentsSize, outputArguments,
                                     UA_NODEID_NULL, NULL,
                                     nodeContext, outNewNodeId);
}

static UA_StatusCode
editMethodCallback(UA_Server *server, UA_Session* session,
                   UA_Node *node, UA_MethodCallback methodCallback) {
    if(node->head.nodeClass != UA_NODECLASS_METHOD)
        return UA_STATUSCODE_BADNODECLASSINVALID;
    node->methodNode.method = methodCallback;
    return UA_STATUSCODE_GOOD;
}

UA_StatusCode
setMethodNode_callback(UA_Server *server,
                       const UA_NodeId methodNodeId,
                       UA_MethodCallback methodCallback) {
    UA_LOCK_ASSERT(&server->serviceMutex);
    return editNode(server, &server->adminSession, &methodNodeId,
                    0, UA_REFERENCETYPESET_NONE, UA_BROWSEDIRECTION_INVALID,
                    (UA_EditNodeCallback)editMethodCallback,
                    (void*)(uintptr_t)methodCallback);
}

UA_StatusCode
UA_Server_setMethodNodeCallback(UA_Server *server,
                                const UA_NodeId methodNodeId,
                                UA_MethodCallback methodCallback) {
    lockServer(server);
    UA_StatusCode retVal = setMethodNode_callback(server, methodNodeId, methodCallback);
    unlockServer(server);
    return retVal;
}

UA_StatusCode
UA_Server_getMethodNodeCallback(UA_Server *server,
                                const UA_NodeId methodNodeId,
                                UA_MethodCallback *outMethodCallback) {
    lockServer(server);
    const UA_Node *node = UA_NODESTORE_GET(server, &methodNodeId);
    if(!node) {
        unlockServer(server);
        return UA_STATUSCODE_BADNODEIDUNKNOWN;
    }

    if(node->head.nodeClass != UA_NODECLASS_METHOD) {
        UA_NODESTORE_RELEASE(server, node);
        unlockServer(server);
        return UA_STATUSCODE_BADNODECLASSINVALID;
    }

    *outMethodCallback = node->methodNode.method;
    UA_NODESTORE_RELEASE(server, node);
    unlockServer(server);
    return UA_STATUSCODE_GOOD;
}

#endif

/************************/
/* Lifecycle Management */
/************************/

void UA_EXPORT
UA_Server_setAdminSessionContext(UA_Server *server,
                                 void *context) {
    server->adminSession.context = context;
}

static UA_StatusCode
setNodeTypeLifecycleCallback(UA_Server *server, UA_Session *session,
                             UA_Node *node, UA_NodeTypeLifecycle *lifecycle) {
    if(node->head.nodeClass == UA_NODECLASS_OBJECTTYPE) {
        node->objectTypeNode.lifecycle = *lifecycle;
    } else if(node->head.nodeClass == UA_NODECLASS_VARIABLETYPE) {
        node->variableTypeNode.lifecycle = *lifecycle;
    } else {
        return UA_STATUSCODE_BADNODECLASSINVALID;
    }
    return UA_STATUSCODE_GOOD;
}

UA_StatusCode
setNodeTypeLifecycle(UA_Server *server, UA_NodeId nodeId,
                     UA_NodeTypeLifecycle lifecycle) {
    return editNode(server, &server->adminSession, &nodeId, 0,
                    UA_REFERENCETYPESET_NONE, UA_BROWSEDIRECTION_INVALID,
                    (UA_EditNodeCallback)setNodeTypeLifecycleCallback,
                    &lifecycle);
}

UA_StatusCode
UA_Server_setNodeTypeLifecycle(UA_Server *server, UA_NodeId nodeId,
                               UA_NodeTypeLifecycle lifecycle) {
    lockServer(server);
    UA_StatusCode retval = setNodeTypeLifecycle(server, nodeId, lifecycle);
    unlockServer(server);
    return retval;
}
