Browse Source

remove code duplication for node editing; add the delete references service; delete references properly during node removal

Julius Pfrommer 9 years ago
parent
commit
6836308aff

+ 5 - 5
CMakeLists.txt

@@ -225,9 +225,9 @@ if(ENABLE_EXTERNAL_NAMESPACES)
 endif()
 
 ## enable dynamic nodeset
-option(ENABLE_ADDNODES "Enable dynamic addition of nodes" ON)
-if(ENABLE_ADDNODES)
-    add_definitions(-DENABLE_ADDNODES )
+option(ENABLE_NODEMANAGEMENT "Enable dynamic addition and removal of nodes" ON)
+if(ENABLE_NODEMANAGEMENT)
+    add_definitions(-DENABLE_NODEMANAGEMENT)
 endif()
 
 ## set the precompiler flags
@@ -429,8 +429,8 @@ if(BUILD_EXAMPLES)
 					           ${PROJECT_SOURCE_DIR}/tools/pyUANamespace/NodeID_Blacklist_FullNS0.txt
 					           ${PROJECT_SOURCE_DIR}/examples/server_nodeset.xml)
 					   
-	add_executable(server_nodeset ${PROJECT_SOURCE_DIR}/examples/server_nodeset.c ${PROJECT_BINARY_DIR}/src_generated/nodeset.c $<TARGET_OBJECTS:open62541-object>)
-	target_link_libraries(server_nodeset ${LIBS})
+	# add_executable(server_nodeset ${PROJECT_SOURCE_DIR}/examples/server_nodeset.c ${PROJECT_BINARY_DIR}/src_generated/nodeset.c $<TARGET_OBJECTS:open62541-object>)
+	# target_link_libraries(server_nodeset ${LIBS})
 
 	if(ENABLE_METHODCALLS)
 	  add_executable(server_method ${PROJECT_SOURCE_DIR}/examples/server_method.c $<TARGET_OBJECTS:open62541-object>)

+ 2 - 2
doc/building.rst

@@ -119,8 +119,8 @@ ENABLE_* group
 
 This group contains build options related to the supported OPC UA features.
 
-**ENABLE_ADDNODES**
-   AddNodes services in sever and client
+**ENABLE_NODEMANAGEMENT**
+   Node management services (adding and removing nodes and references) in server and client
 **ENABLE_AMALGAMATION**
    Compile a single-file release files open62541.c and open62541.h
 **ENABLE_COVERAGE**

+ 1 - 1
examples/client.c

@@ -157,7 +157,7 @@ int main(int argc, char *argv[]) {
 
 #endif
 
-#ifdef ENABLE_ADDNODES 
+#ifdef ENABLE_NODEMANAGEMENT 
     /* Create a new object type node */
     // New ReferenceType
     UA_AddNodesResponse *adResp = UA_Client_createReferenceTypeNode(client,

+ 5 - 4
examples/server_variable.c

@@ -47,16 +47,17 @@ int main(int argc, char** argv) {
     UA_VariableAttributes attr;
     UA_VariableAttributes_init(&attr);
     UA_Int32 myInteger = 42;
-    UA_Variant_setScalarCopy(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
+    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
     attr.description = UA_LOCALIZEDTEXT("en_US","the answer");
     attr.displayName = UA_LOCALIZEDTEXT("en_US","the answer");
     UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
     UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer");
     UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
     UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
-    UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
-                              parentReferenceNodeId, myIntegerName,
-                              UA_NODEID_NULL, attr);
+    UA_AddNodesResult res = UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
+                                                      parentReferenceNodeId, myIntegerName,
+                                                      UA_NODEID_NULL, attr);
+    UA_AddNodesResult_deleteMembers(&res);
 
     UA_ValueCallback callback = {(void*)7, onRead, onWrite};
     UA_Server_setAttribute_value_callback(server, myIntegerNodeId, callback);

+ 7 - 4
src/server/ua_server_binary.c

@@ -324,9 +324,6 @@ static void processMSG(UA_Connection *connection, UA_Server *server, const UA_By
     case UA_NS0ID_BROWSENEXTREQUEST:
         INVOKE_SERVICE(BrowseNext, UA_TYPES_BROWSENEXTRESPONSE);
         break;
-    case UA_NS0ID_ADDREFERENCESREQUEST:
-        INVOKE_SERVICE(AddReferences, UA_TYPES_ADDREFERENCESRESPONSE);
-        break;
     case UA_NS0ID_REGISTERNODESREQUEST:
         INVOKE_SERVICE(RegisterNodes, UA_TYPES_REGISTERNODESRESPONSE);
         break;
@@ -361,13 +358,19 @@ static void processMSG(UA_Connection *connection, UA_Server *server, const UA_By
         INVOKE_SERVICE(Call, UA_TYPES_CALLRESPONSE);
 	break;
 #endif
-#ifdef ENABLE_ADDNODES 
+#ifdef ENABLE_NODEMANAGEMENT
     case UA_NS0ID_ADDNODESREQUEST:
         INVOKE_SERVICE(AddNodes, UA_TYPES_ADDNODESRESPONSE);
         break;
+    case UA_NS0ID_ADDREFERENCESREQUEST:
+        INVOKE_SERVICE(AddReferences, UA_TYPES_ADDREFERENCESRESPONSE);
+        break;
     case UA_NS0ID_DELETENODESREQUEST:
       INVOKE_SERVICE(DeleteNodes, UA_TYPES_DELETENODESRESPONSE);
       break;
+    case UA_NS0ID_DELETEREFERENCESREQUEST:
+      INVOKE_SERVICE(DeleteReferences, UA_TYPES_DELETEREFERENCESRESPONSE);
+      break;
 #endif
     default: {
         if(requestType.namespaceIndex == 0 && requestType.identifier.numeric==787)

+ 7 - 0
src/server/ua_server_internal.h

@@ -74,6 +74,13 @@ struct UA_Server {
 #endif
 };
 
+typedef UA_StatusCode (*UA_EditNodeCallback)(UA_Server *server, UA_Session*, UA_Node*, void*);
+
+/* Calls callback on the node. In the multithreaded case, the node is copied before and replaced in
+   the nodestore. */
+UA_StatusCode UA_Server_editNode(UA_Server *server, UA_Session *session, const UA_NodeId *nodeId,
+                                 UA_EditNodeCallback callback, void *data);
+
 void UA_Server_processBinaryMessage(UA_Server *server, UA_Connection *connection, UA_ByteString *msg);
 
 UA_StatusCode UA_Server_addDelayedJob(UA_Server *server, UA_Job job);

+ 2 - 0
src/server/ua_services.h

@@ -130,6 +130,8 @@ UA_StatusCode Service_DeleteNodes_single(UA_Server *server, UA_Session *session,
 /** Used to delete one or more References of a Node. */
 void Service_DeleteReferences(UA_Server *server, UA_Session *session, const UA_DeleteReferencesRequest *request,
                               UA_DeleteReferencesResponse *response);
+UA_StatusCode Service_DeleteReferences_single(UA_Server *server, UA_Session *session,
+                                              const UA_DeleteReferencesItem *item);
 
 /** @} */
 

+ 72 - 67
src/server/ua_services_attribute.c

@@ -405,6 +405,36 @@ void Service_Read(UA_Server *server, UA_Session *session, const UA_ReadRequest *
 /* Write Attribute */
 /*******************/
 
+UA_StatusCode UA_Server_editNode(UA_Server *server, UA_Session *session, const UA_NodeId *nodeId,
+                                 UA_EditNodeCallback callback, void *data) {
+    UA_StatusCode retval;
+    do {
+        retval = UA_STATUSCODE_GOOD;
+        const UA_Node *node = UA_NodeStore_get(server->nodestore, nodeId);
+        if(!node)
+            return UA_STATUSCODE_BADNODEIDUNKNOWN;
+#ifndef UA_MULTITHREADING
+        retval = callback(server, session, (UA_Node*)(uintptr_t)node, data);
+        UA_NodeStore_release(node);
+        return retval;
+#else
+        UA_Node *copy = UA_Node_copyAnyNodeClass(node);
+        UA_NodeStore_release(node);
+        if(!copy)
+            return UA_STATUSCODE_BADOUTOFMEMORY;
+        retval = callback(server, session, copy, data);
+        if(retval != UA_STATUSCODE_GOOD) {
+            UA_Node_deleteAnyNodeClass(copy);
+            return retval;
+        }
+        retval = UA_NodeStore_replace(server->nodestore, orig, copy, UA_NULL);
+        if(retval != UA_STATUSCODE_GOOD)
+            UA_Node_deleteAnyNodeClass(copy);
+#endif
+    } while(retval != UA_STATUSCODE_GOOD);
+    return UA_STATUSCODE_GOOD;
+}
+
 #define CHECK_DATATYPE(EXP_DT)                                          \
     if(!wvalue->value.hasValue ||                                       \
        &UA_TYPES[UA_TYPES_##EXP_DT] != wvalue->value.value.type ||      \
@@ -414,7 +444,7 @@ void Service_Read(UA_Server *server, UA_Session *session, const UA_ReadRequest *
     }
 
 #define CHECK_NODECLASS_WRITE(CLASS)                                    \
-    if((copy->nodeClass & (CLASS)) == 0) {                              \
+    if((node->nodeClass & (CLASS)) == 0) {                              \
         retval = UA_STATUSCODE_BADNODECLASSINVALID;                     \
         break;                                                          \
     }
@@ -444,8 +474,7 @@ Service_Write_single_ValueDataSource(UA_Server *server, UA_Session *session, con
 
 /* In the multithreaded case, node is a copy */
 static UA_StatusCode
-Service_Write_single_Value(UA_Server *server, UA_Session *session, UA_VariableNode *node,
-                           UA_WriteValue *wvalue) {
+MoveValueIntoNode(UA_Server *server, UA_Session *session, UA_VariableNode *node, UA_WriteValue *wvalue) {
     UA_assert(wvalue->attributeId == UA_ATTRIBUTEID_VALUE);
     UA_assert(node->nodeClass == UA_NODECLASS_VARIABLE || node->nodeClass == UA_NODECLASS_VARIABLETYPE);
     UA_assert(node->valueSource == UA_VALUESOURCE_VARIANT);
@@ -505,34 +534,9 @@ Service_Write_single_Value(UA_Server *server, UA_Session *session, UA_VariableNo
     return retval;
 }
 
-UA_StatusCode Service_Write_single(UA_Server *server, UA_Session *session, UA_WriteValue *wvalue) {
-    if(!wvalue->value.hasValue || !wvalue->value.value.data)
-        return UA_STATUSCODE_BADNODATA; // TODO: is this the right return code?
-    const UA_Node *orig;
-    UA_StatusCode retval;
-
- retryWriteSingle:
-    retval = UA_STATUSCODE_GOOD;
-    orig = UA_NodeStore_get(server->nodestore, &wvalue->nodeId);
-    if(!orig)
-        return UA_STATUSCODE_BADNODEIDUNKNOWN;
-
-    if(wvalue->attributeId == UA_ATTRIBUTEID_VALUE && 
-       orig->nodeClass & (UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLE) &&
-       ((const UA_VariableNode*)orig)->valueSource == UA_VALUESOURCE_DATASOURCE) {
-        return Service_Write_single_ValueDataSource(server, session, (const UA_VariableNode*)orig, wvalue);
-    }
-    
-#ifndef UA_MULTITHREADING
-    UA_Node *copy = (UA_Node*)(uintptr_t)orig;
-#else
-    UA_Node *copy = UA_Node_copyAnyNodeClass(orig);
-    if(!copy) {
-        UA_NodeStore_release(orig);
-        return UA_STATUSCODE_BADOUTOFMEMORY;
-    }
-#endif
-
+static UA_StatusCode
+MoveAttributeIntoNode(UA_Server *server, UA_Session *session, UA_Node *node, UA_WriteValue *wvalue) {
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
     void *value = wvalue->value.value.data;
 	switch(wvalue->attributeId) {
     case UA_ATTRIBUTEID_NODEID:
@@ -542,45 +546,45 @@ UA_StatusCode Service_Write_single(UA_Server *server, UA_Session *session, UA_Wr
 		break;
 	case UA_ATTRIBUTEID_BROWSENAME:
 		CHECK_DATATYPE(QUALIFIEDNAME);
-		UA_QualifiedName_deleteMembers(&copy->browseName);
-        copy->browseName = *(UA_QualifiedName*)value;
+		UA_QualifiedName_deleteMembers(&node->browseName);
+        node->browseName = *(UA_QualifiedName*)value;
         UA_QualifiedName_init((UA_QualifiedName*)value);
 		break;
 	case UA_ATTRIBUTEID_DISPLAYNAME:
 		CHECK_DATATYPE(LOCALIZEDTEXT);
-		UA_LocalizedText_deleteMembers(&copy->displayName);
-        copy->displayName = *(UA_LocalizedText*)value;
+		UA_LocalizedText_deleteMembers(&node->displayName);
+        node->displayName = *(UA_LocalizedText*)value;
 		UA_LocalizedText_init((UA_LocalizedText*)value);
 		break;
 	case UA_ATTRIBUTEID_DESCRIPTION:
 		CHECK_DATATYPE(LOCALIZEDTEXT);
-		UA_LocalizedText_deleteMembers(&copy->description);
-        copy->description = *(UA_LocalizedText*)value;
+		UA_LocalizedText_deleteMembers(&node->description);
+        node->description = *(UA_LocalizedText*)value;
 		UA_LocalizedText_init((UA_LocalizedText*)value);
 		break;
 	case UA_ATTRIBUTEID_WRITEMASK:
 		CHECK_DATATYPE(UINT32);
-		copy->writeMask = *(UA_UInt32*)value;
+		node->writeMask = *(UA_UInt32*)value;
 		break;
 	case UA_ATTRIBUTEID_USERWRITEMASK:
 		CHECK_DATATYPE(UINT32);
-		copy->userWriteMask = *(UA_UInt32*)value;
+		node->userWriteMask = *(UA_UInt32*)value;
 		break;    
 	case UA_ATTRIBUTEID_ISABSTRACT:
 		CHECK_NODECLASS_WRITE(UA_NODECLASS_OBJECTTYPE | UA_NODECLASS_REFERENCETYPE |
                               UA_NODECLASS_VARIABLETYPE | UA_NODECLASS_DATATYPE);
 		CHECK_DATATYPE(BOOLEAN);
-		((UA_ObjectTypeNode*)copy)->isAbstract = *(UA_Boolean*)value;
+		((UA_ObjectTypeNode*)node)->isAbstract = *(UA_Boolean*)value;
 		break;
 	case UA_ATTRIBUTEID_SYMMETRIC:
 		CHECK_NODECLASS_WRITE(UA_NODECLASS_REFERENCETYPE);
 		CHECK_DATATYPE(BOOLEAN);
-		((UA_ReferenceTypeNode*)copy)->symmetric = *(UA_Boolean*)value;
+		((UA_ReferenceTypeNode*)node)->symmetric = *(UA_Boolean*)value;
 		break;
 	case UA_ATTRIBUTEID_INVERSENAME:
 		CHECK_NODECLASS_WRITE(UA_NODECLASS_REFERENCETYPE);
 		CHECK_DATATYPE(LOCALIZEDTEXT);
-        UA_ReferenceTypeNode *n = (UA_ReferenceTypeNode*)copy;
+        UA_ReferenceTypeNode *n = (UA_ReferenceTypeNode*)node;
 		UA_LocalizedText_deleteMembers(&n->inverseName);
         n->inverseName = *(UA_LocalizedText*)value;
 		UA_LocalizedText_init((UA_LocalizedText*)value);
@@ -588,70 +592,71 @@ UA_StatusCode Service_Write_single(UA_Server *server, UA_Session *session, UA_Wr
 	case UA_ATTRIBUTEID_CONTAINSNOLOOPS:
 		CHECK_NODECLASS_WRITE(UA_NODECLASS_VIEW);
 		CHECK_DATATYPE(BOOLEAN);
-        ((UA_ViewNode*)copy)->containsNoLoops = *(UA_Boolean*)value;
+        ((UA_ViewNode*)node)->containsNoLoops = *(UA_Boolean*)value;
 		break;
 	case UA_ATTRIBUTEID_EVENTNOTIFIER:
 		CHECK_NODECLASS_WRITE(UA_NODECLASS_VIEW | UA_NODECLASS_OBJECT);
 		CHECK_DATATYPE(BYTE);
-        ((UA_ViewNode*)copy)->eventNotifier = *(UA_Byte*)value;
+        ((UA_ViewNode*)node)->eventNotifier = *(UA_Byte*)value;
 		break;
 	case UA_ATTRIBUTEID_VALUE:
 		CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
-        retval = Service_Write_single_Value(server, session, (UA_VariableNode*)copy, wvalue);
+        retval = MoveValueIntoNode(server, session, (UA_VariableNode*)node, wvalue);
 		break;
 	case UA_ATTRIBUTEID_ACCESSLEVEL:
 		CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
 		CHECK_DATATYPE(BYTE);
-		((UA_VariableNode*)copy)->accessLevel = *(UA_Byte*)value;
+		((UA_VariableNode*)node)->accessLevel = *(UA_Byte*)value;
 		break;
 	case UA_ATTRIBUTEID_USERACCESSLEVEL:
 		CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
 		CHECK_DATATYPE(BYTE);
-		((UA_VariableNode*)copy)->userAccessLevel = *(UA_Byte*)value;
+		((UA_VariableNode*)node)->userAccessLevel = *(UA_Byte*)value;
 		break;
 	case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL:
 		CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
 		CHECK_DATATYPE(DOUBLE);
-		((UA_VariableNode*)copy)->minimumSamplingInterval = *(UA_Double*)value;
+		((UA_VariableNode*)node)->minimumSamplingInterval = *(UA_Double*)value;
 		break;
 	case UA_ATTRIBUTEID_HISTORIZING:
 		CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
 		CHECK_DATATYPE(BOOLEAN);
-		((UA_VariableNode*)copy)->historizing = *(UA_Boolean*)value;
+		((UA_VariableNode*)node)->historizing = *(UA_Boolean*)value;
 		break;
 	case UA_ATTRIBUTEID_EXECUTABLE:
 		CHECK_NODECLASS_WRITE(UA_NODECLASS_METHOD);
 		CHECK_DATATYPE(BOOLEAN);
-		((UA_MethodNode*)copy)->executable = *(UA_Boolean*)value;
+		((UA_MethodNode*)node)->executable = *(UA_Boolean*)value;
 		break;
 	case UA_ATTRIBUTEID_USEREXECUTABLE:
 		CHECK_NODECLASS_WRITE(UA_NODECLASS_METHOD);
 		CHECK_DATATYPE(BOOLEAN);
-		((UA_MethodNode*)copy)->userExecutable = *(UA_Boolean*)value;
+		((UA_MethodNode*)node)->userExecutable = *(UA_Boolean*)value;
 		break;
 	default:
 		retval = UA_STATUSCODE_BADATTRIBUTEIDINVALID;
 		break;
 	}
+    return retval;
+}
 
-#ifdef UA_MULTITHREADING
-    if(retval != UA_STATUSCODE_GOOD) {
+UA_StatusCode Service_Write_single(UA_Server *server, UA_Session *session, UA_WriteValue *wvalue) {
+    if(!wvalue->value.hasValue || !wvalue->value.value.data)
+        return UA_STATUSCODE_BADNODATA; // TODO: is this the right return code?
+    if(wvalue->attributeId == UA_ATTRIBUTEID_VALUE) {
+        const UA_Node *orig = UA_NodeStore_get(server->nodestore, &wvalue->nodeId);
+        if(!orig)
+            return UA_STATUSCODE_BADNODEIDUNKNOWN;
+        if(orig->nodeClass & (UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLE) &&
+           ((const UA_VariableNode*)orig)->valueSource == UA_VALUESOURCE_DATASOURCE) {
+            UA_StatusCode retval = Service_Write_single_ValueDataSource(server, session, (const UA_VariableNode*)orig, wvalue);
+            UA_NodeStore_release(orig);
+            return retval;
+        }
         UA_NodeStore_release(orig);
-        UA_Node_deleteAnyNodeClass(copy);
-        return retval;
-    }
-       
-    retval = UA_NodeStore_replace(server->nodestore, orig, copy, UA_NULL);
-	UA_NodeStore_release(orig);
-    if(retval != UA_STATUSCODE_GOOD) {
-        /* The node was replaced under our feet. Retry. */
-        UA_Node_deleteAnyNodeClass(copy);
-        goto retryWriteSingle;
     }
-#else
-	UA_NodeStore_release(orig);
-#endif
-	return retval;
+    return UA_Server_editNode(server, session, &wvalue->nodeId,
+                              (UA_EditNodeCallback)MoveAttributeIntoNode, wvalue);
 }
 
 void Service_Write(UA_Server *server, UA_Session *session, const UA_WriteRequest *request,

+ 70 - 165
src/server/ua_services_nodemanagement.c

@@ -6,25 +6,6 @@
 #include "ua_session.h"
 #include "ua_types_generated_encoding_binary.h"
 
-/**
- * Information Model Consistency
- *
- * The following consistency assertions *always* hold:
- *
- * - There are no directed cycles of hierarchical references
- * - All nodes have a hierarchical relation to at least one father node
- * - Variables and Objects contain all mandatory children according to their type
- *
- * The following consistency assertions *eventually* hold:
- *
- * - All references (except those pointing to external servers with an expandednodeid) are two-way
- *   (present in the source and the target node)
- * - The target of all references exists in the information model
- *
- */
-
-// TOOD: Ensure that the consistency guarantuees hold until v0.2
-
 /************/
 /* Add Node */
 /************/
@@ -565,81 +546,24 @@ UA_Server_addMethodNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
 
 /* Adds a one-way reference to the local nodestore */
 static UA_StatusCode
-addOneWayReferenceWithSession(UA_Server *server, UA_Session *session, const UA_AddReferencesItem *item) {
-    const UA_Node *node = UA_NodeStore_get(server->nodestore, &item->sourceNodeId);
-    if(!node)
-        return UA_STATUSCODE_BADINTERNALERROR;
-	UA_StatusCode retval = UA_STATUSCODE_GOOD;
-#ifndef UA_MULTITHREADING
+addOneWayReference(UA_Server *server, UA_Session *session, UA_Node *node, const UA_AddReferencesItem *item) {
 	size_t i = node->referencesSize;
 	if(node->referencesSize < 0)
 		i = 0;
     size_t refssize = (i+1) | 3; // so the realloc is not necessary every time
 	UA_ReferenceNode *new_refs = UA_realloc(node->references, sizeof(UA_ReferenceNode) * refssize);
 	if(!new_refs)
-		retval = UA_STATUSCODE_BADOUTOFMEMORY;
-	else {
-		UA_ReferenceNode_init(&new_refs[i]);
-		retval = UA_NodeId_copy(&item->referenceTypeId, &new_refs[i].referenceTypeId);
-		new_refs[i].isInverse = !item->isForward;
-		retval |= UA_ExpandedNodeId_copy(&item->targetNodeId, &new_refs[i].targetId);
-		/* hack. be careful! possible only in the single-threaded case. */
-		UA_Node *mutable_node = (UA_Node*)(uintptr_t)node;
-		mutable_node->references = new_refs;
-		if(retval != UA_STATUSCODE_GOOD) {
-			UA_NodeId_deleteMembers(&new_refs[node->referencesSize].referenceTypeId);
-			UA_ExpandedNodeId_deleteMembers(&new_refs[node->referencesSize].targetId);
-		} else
-			mutable_node->referencesSize = i+1;
-	}
-	UA_NodeStore_release(node);
-	return retval;
-#else
-    UA_Node *newNode = UA_Node_copyAnyNodeClass(node);
-    if(!newNode) {
-        UA_NodeStore_release(node);
-        return UA_STATUSCODE_BADINTERNALERROR;
-    }
-
-    UA_Int32 count = node->referencesSize;
-    if(count < 0)
-        count = 0;
-    UA_ReferenceNode *old_refs = newNode->references;
-    UA_ReferenceNode *new_refs = UA_malloc(sizeof(UA_ReferenceNode)*(count+1));
-    if(!new_refs) {
-        UA_Node_deleteAnyNodeClass(newNode);
-        UA_NodeStore_release(node);
-        return UA_STATUSCODE_BADOUTOFMEMORY;
-    }
-
-    // insert the new reference
-    UA_memcpy(new_refs, old_refs, sizeof(UA_ReferenceNode)*count);
-    UA_ReferenceNode_init(&new_refs[count]);
-    retval = UA_NodeId_copy(&item->referenceTypeId, &new_refs[count].referenceTypeId);
-    new_refs[count].isInverse = !item->isForward;
-    retval |= UA_ExpandedNodeId_copy(&item->targetNodeId, &new_refs[count].targetId);
-    if(retval != UA_STATUSCODE_GOOD) {
-        UA_Array_delete(new_refs, &UA_TYPES[UA_TYPES_REFERENCENODE], ++count);
-        newNode->references = UA_NULL;
-        newNode->referencesSize = 0;
-        UA_Node_deleteAnyNodeClass(newNode);
-        UA_NodeStore_release(node);
-        return UA_STATUSCODE_BADOUTOFMEMORY;
-    }
-
-    UA_free(old_refs);
-    newNode->references = new_refs;
-    newNode->referencesSize = ++count;
-    retval = UA_NodeStore_replace(server->nodestore, node, newNode, UA_NULL);
-	UA_NodeStore_release(node);
-	if(retval == UA_STATUSCODE_BADINTERNALERROR) {
-		/* presumably because the node was replaced and an old version was updated at the same time.
-           just try again */
-        UA_Node_deleteAnyNodeClass(newNode);
-		return addOneWayReferenceWithSession(server, session, item);
-	}
+		return UA_STATUSCODE_BADOUTOFMEMORY;
+    node->references = new_refs;
+    UA_ReferenceNode_init(&new_refs[i]);
+    UA_StatusCode retval = UA_NodeId_copy(&item->referenceTypeId, &new_refs[i].referenceTypeId);
+    retval |= UA_ExpandedNodeId_copy(&item->targetNodeId, &new_refs[i].targetId);
+    new_refs[i].isInverse = !item->isForward;
+    if(retval == UA_STATUSCODE_GOOD) 
+        node->referencesSize = i+1;
+    else
+        UA_ReferenceNode_deleteMembers(&new_refs[i]);
 	return retval;
-#endif
 }
 
 UA_StatusCode Service_AddReferences_single(UA_Server *server, UA_Session *session,
@@ -647,25 +571,11 @@ UA_StatusCode Service_AddReferences_single(UA_Server *server, UA_Session *sessio
     if(item->targetServerUri.length > 0)
         return UA_STATUSCODE_BADNOTIMPLEMENTED; // currently no expandednodeids are allowed
 
-    UA_StatusCode retval = UA_STATUSCODE_GOOD;
-
-#ifdef UA_EXTERNAL_NAMESPACES
-    UA_ExternalNodeStore *ensFirst = UA_NULL;
-    UA_ExternalNodeStore *ensSecond = UA_NULL;
-    for(size_t j = 0;j<server->externalNamespacesSize && (!ensFirst || !ensSecond);j++) {
-        if(item->sourceNodeId.namespaceIndex == server->externalNamespaces[j].index)
-            ensFirst = &server->externalNamespaces[j].externalNodeStore;
-        if(item->targetNodeId.nodeId.namespaceIndex == server->externalNamespaces[j].index)
-            ensSecond = &server->externalNamespaces[j].externalNodeStore;
-    }
-
-    if(ensFirst) {
-        // todo: use external nodestore
-    } else
-#endif
-        retval = addOneWayReferenceWithSession(server, session, item);
-
-    if(retval)
+    /* cast away the const to loop the call through UA_Server_editNode */
+    UA_StatusCode retval = UA_Server_editNode(server, session, &item->sourceNodeId,
+                                              (UA_EditNodeCallback)addOneWayReference,
+                                              (void*)(uintptr_t)item);
+    if(retval != UA_STATUSCODE_GOOD)
         return retval;
 
     UA_AddReferencesItem secondItem;
@@ -673,12 +583,8 @@ UA_StatusCode Service_AddReferences_single(UA_Server *server, UA_Session *sessio
     secondItem.targetNodeId.nodeId = item->sourceNodeId;
     secondItem.sourceNodeId = item->targetNodeId.nodeId;
     secondItem.isForward = !item->isForward;
-#ifdef UA_EXTERNAL_NAMESPACES
-    if(ensSecond) {
-        // todo: use external nodestore
-    } else
-#endif
-        retval = addOneWayReferenceWithSession (server, session, &secondItem);
+    retval = UA_Server_editNode(server, session, &secondItem.sourceNodeId,
+                                (UA_EditNodeCallback)addOneWayReference, &secondItem);
 
     // todo: remove reference if the second direction failed
     return retval;
@@ -697,7 +603,6 @@ void Service_AddReferences(UA_Server *server, UA_Session *session, const UA_AddR
 		return;
 	}
 	response->resultsSize = size;
-	UA_memset(response->results, UA_STATUSCODE_GOOD, sizeof(UA_StatusCode) * size);
 
 #ifdef UA_EXTERNAL_NAMESPACES
 #ifdef NO_ALLOCA
@@ -749,15 +654,17 @@ UA_StatusCode Service_DeleteNodes_single(UA_Server *server, UA_Session *session,
   
     // Find and remove all References to this node if so requested.
     if(deleteReferences == UA_TRUE) {
-        UA_DeleteReferencesItem *delItem = UA_DeleteReferencesItem_new();
-        delItem->deleteBidirectional = UA_TRUE; // WARNING: Current semantics in deleteOneWayReference is 'delete forward or inverse'
-        UA_NodeId_copy(&nodeId, &delItem->targetNodeId.nodeId);
-    
+        UA_DeleteReferencesItem delItem;
+        UA_DeleteReferencesItem_init(&delItem);
+        delItem.deleteBidirectional = UA_FALSE;
+        UA_NodeId_copy(&nodeId, &delItem.targetNodeId.nodeId);
         for(int i=0; i<delNode->referencesSize; i++) {
-            UA_NodeId_copy(&delNode->references[i].targetId.nodeId, &delItem->sourceNodeId);
-            UA_NodeId_deleteMembers(&delItem->sourceNodeId);
+            delItem.sourceNodeId = delNode->references[i].targetId.nodeId;
+            delItem.isForward = delNode->references[i].isInverse;
+            Service_DeleteReferences_single(server, session, &delItem);
         }
-        UA_DeleteReferencesItem_delete(delItem);
+        UA_NodeId_init(&delItem.sourceNodeId);
+        UA_DeleteReferencesItem_deleteMembers(&delItem);
     }
   
     UA_NodeStore_release(delNode);
@@ -766,6 +673,10 @@ UA_StatusCode Service_DeleteNodes_single(UA_Server *server, UA_Session *session,
 
 void Service_DeleteNodes(UA_Server *server, UA_Session *session, const UA_DeleteNodesRequest *request,
                          UA_DeleteNodesResponse *response) {
+    if(request->nodesToDeleteSize <= 0) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
+        return;
+    }
     response->results = UA_malloc(sizeof(UA_StatusCode) * request->nodesToDeleteSize);
     if(!response->results) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;;
@@ -782,63 +693,57 @@ void Service_DeleteNodes(UA_Server *server, UA_Session *session, const UA_Delete
 /* Delete References */
 /*********************/
 
-// TODO: Add to service
-
 static UA_StatusCode
-deleteOneWayReference(UA_Server *server, UA_Session *session, const UA_DeleteReferencesItem *item) {
-    const UA_Node *orig;
- repeat_deleteref_oneway:
-    orig = UA_NodeStore_get(server->nodestore, &item->sourceNodeId);
-    if(!orig)
-        return UA_STATUSCODE_BADNODEIDUNKNOWN;
-
-#ifndef UA_MULTITHREADING
-    /* We cheat if multithreading is not enabled and treat the node as mutable. */
-    UA_Node *editable = (UA_Node*)(uintptr_t)orig;
-#else
-    UA_Node *editable = UA_Node_copyAnyNodeClass(orig);
-    UA_Boolean edited = UA_FALSE;;
-#endif
-
-    for(UA_Int32 i = editable->referencesSize - 1; i >= 0; i--) {
-        if(!UA_NodeId_equal(&item->targetNodeId.nodeId, &editable->references[i].targetId.nodeId))
+deleteOneWayReference(UA_Server *server, UA_Session *session, UA_Node *node, const UA_DeleteReferencesItem *item) {
+    UA_Boolean edited = UA_FALSE;
+    for(UA_Int32 i = node->referencesSize - 1; i >= 0; i--) {
+        if(!UA_NodeId_equal(&item->targetNodeId.nodeId, &node->references[i].targetId.nodeId))
             continue;
-        if(!UA_NodeId_equal(&item->referenceTypeId, &editable->references[i].referenceTypeId))
+        if(!UA_NodeId_equal(&item->referenceTypeId, &node->references[i].referenceTypeId))
             continue;
-        if(item->isForward == editable->references[i].isInverse)
+        if(item->isForward == node->references[i].isInverse)
             continue;
         /* move the last entry to override the current position */
-        UA_ReferenceNode_deleteMembers(&editable->references[i]);
-        editable->references[i] = editable->references[editable->referencesSize-1];
-        editable->referencesSize--;
-
-#ifdef UA_MULTITHREADING
+        UA_ReferenceNode_deleteMembers(&node->references[i]);
+        node->references[i] = node->references[node->referencesSize-1];
+        node->referencesSize--;
         edited = UA_TRUE;
-#endif
     }
-
+    if(!edited)
+        return UA_STATUSCODE_UNCERTAINREFERENCENOTDELETED;
     /* we removed the last reference */
-    if(editable->referencesSize <= 0 && editable->references)
-        UA_free(editable->references);
-    
-#ifdef UA_MULTITHREADING
-    if(!edited) {
-        UA_Node_deleteAnyNodeClass(editable);
-    } else if(UA_NodeStore_replace(server->nodestore, orig, editable, UA_NULL) != UA_STATUSCODE_GOOD) {
-        /* the node was changed by another thread. repeat. */
-        UA_Node_deleteAnyNodeClass(editable);
-        UA_NodeStore_release(orig);
-        goto repeat_deleteref_oneway;
-    }
-#endif
-
-    UA_NodeStore_release(orig);
+    if(node->referencesSize <= 0 && node->references)
+        UA_free(node->references);
     return UA_STATUSCODE_GOOD;;
 }
 
+UA_StatusCode
+Service_DeleteReferences_single(UA_Server *server, UA_Session *session, const UA_DeleteReferencesItem *item) {
+    UA_StatusCode retval = UA_Server_editNode(server, session, &item->sourceNodeId,
+                                       (UA_EditNodeCallback)deleteOneWayReference, (void*)(uintptr_t)item);
+    if(!item->deleteBidirectional || item->targetNodeId.serverIndex != 0)
+        return retval;
+    UA_DeleteReferencesItem secondItem;
+    UA_DeleteReferencesItem_init(&secondItem);
+    secondItem.isForward = !item->isForward;
+    secondItem.sourceNodeId = item->targetNodeId.nodeId;
+    secondItem.targetNodeId.nodeId = item->sourceNodeId;
+    return UA_Server_editNode(server, session, &secondItem.sourceNodeId,
+                              (UA_EditNodeCallback)deleteOneWayReference, &secondItem);
+}
 
 void Service_DeleteReferences(UA_Server *server, UA_Session *session, const UA_DeleteReferencesRequest *request,
                               UA_DeleteReferencesResponse *response) {
-    UA_StatusCode retval = UA_STATUSCODE_BADSERVICEUNSUPPORTED;
-    response->responseHeader.serviceResult = retval;
+    if(request->referencesToDeleteSize <= 0) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
+        return;
+    }
+    response->results = UA_malloc(sizeof(UA_StatusCode) * request->referencesToDeleteSize);
+    if(!response->results) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;;
+        return;
+    }
+    response->resultsSize = request->referencesToDeleteSize;
+    for(int i=0; i<request->referencesToDeleteSize; i++)
+        response->results[i] = Service_DeleteReferences_single(server, session, &request->referencesToDelete[i]);
 }