Browse Source

Merge branch '1.0'

Julius Pfrommer 5 years ago
parent
commit
acbac3ee0b

+ 1 - 1
CMakeLists.txt

@@ -575,6 +575,7 @@ set(exported_headers ${exported_headers}
                      ${PROJECT_SOURCE_DIR}/include/open62541/plugin/securitypolicy.h
                      ${PROJECT_SOURCE_DIR}/include/open62541/server_pubsub.h
                      ${PROJECT_SOURCE_DIR}/include/open62541/plugin/pubsub.h
+                     ${PROJECT_SOURCE_DIR}/deps/ziptree.h
                      ${PROJECT_SOURCE_DIR}/include/open62541/plugin/nodestore.h
                      ${historizing_exported_headers}
                      ${PROJECT_SOURCE_DIR}/include/open62541/server_config.h
@@ -585,7 +586,6 @@ set(exported_headers ${exported_headers}
                      ${PROJECT_SOURCE_DIR}/include/open62541/client_highlevel_async.h)
 
 set(internal_headers ${PROJECT_SOURCE_DIR}/deps/open62541_queue.h
-                     ${PROJECT_SOURCE_DIR}/deps/ziptree.h
                      ${PROJECT_SOURCE_DIR}/deps/pcg_basic.h
                      ${PROJECT_SOURCE_DIR}/deps/libc_time.h
                      ${PROJECT_SOURCE_DIR}/deps/base64.h

+ 15 - 2
include/open62541/plugin/nodestore.h

@@ -18,6 +18,7 @@
  * / OPC UA services to interact with the information model. */
 
 #include <open62541/server.h>
+#include "ziptree.h"
 
 _UA_BEGIN_DECLS
 
@@ -65,12 +66,24 @@ struct UA_MonitoredItem;
  * not known or not important. The ``nodeClass`` attribute is used to ensure the
  * correctness of casting from ``UA_Node`` to a specific node type. */
 
+/* Ordered tree structure for fast member check */
+typedef struct UA_ReferenceTarget {
+    ZIP_ENTRY(UA_ReferenceTarget) zipfields;
+    UA_UInt32 targetHash; /* Hash of the target nodeid */
+    UA_ExpandedNodeId target;
+} UA_ReferenceTarget;
+
+ZIP_HEAD(UA_ReferenceTargetHead, UA_ReferenceTarget);
+typedef struct UA_ReferenceTargetHead UA_ReferenceTargetHead;
+ZIP_PROTTYPE(UA_ReferenceTargetHead, UA_ReferenceTarget, UA_ReferenceTarget)
+
 /* List of reference targets with the same reference type and direction */
 typedef struct {
     UA_NodeId referenceTypeId;
     UA_Boolean isInverse;
-    size_t targetIdsSize;
-    UA_ExpandedNodeId *targetIds;
+    size_t refTargetsSize;
+    UA_ReferenceTarget *refTargets;
+    UA_ReferenceTargetHead refTargetsTree;
 } UA_NodeReferenceKind;
 
 #define UA_NODE_BASEATTRIBUTES                  \

+ 131 - 59
src/server/ua_nodes.c

@@ -17,6 +17,20 @@
 /* There is no UA_Node_new() method here. Creating nodes is part of the
  * NodeStore layer */
 
+static enum ZIP_CMP
+cmpRefTarget(const void *a, const void *b) {
+    const UA_ReferenceTarget *aa = (const UA_ReferenceTarget*)a;
+    const UA_ReferenceTarget *bb = (const UA_ReferenceTarget*)b;
+    if(aa->targetHash < bb->targetHash)
+        return ZIP_CMP_LESS;
+    if(aa->targetHash > bb->targetHash)
+        return ZIP_CMP_MORE;
+    return (enum ZIP_CMP)UA_ExpandedNodeId_order(&aa->target, &bb->target);
+}
+
+ZIP_IMPL(UA_ReferenceTargetHead, UA_ReferenceTarget, zipfields,
+         UA_ReferenceTarget, zipfields, cmpRefTarget)
+
 void UA_Node_deleteMembers(UA_Node *node) {
     /* Delete standard content */
     UA_NodeId_deleteMembers(&node->nodeId);
@@ -179,16 +193,39 @@ UA_Node_copy(const UA_Node *src, UA_Node *dst) {
             UA_NodeReferenceKind *srefs = &src->references[i];
             UA_NodeReferenceKind *drefs = &dst->references[i];
             drefs->isInverse = srefs->isInverse;
+            ZIP_INIT(&drefs->refTargetsTree);
             retval = UA_NodeId_copy(&srefs->referenceTypeId, &drefs->referenceTypeId);
             if(retval != UA_STATUSCODE_GOOD)
                 break;
-            retval = UA_Array_copy(srefs->targetIds, srefs->targetIdsSize,
-                                    (void**)&drefs->targetIds,
-                                    &UA_TYPES[UA_TYPES_EXPANDEDNODEID]);
+            drefs->refTargets = (UA_ReferenceTarget*)
+                UA_malloc(srefs->refTargetsSize* sizeof(UA_ReferenceTarget));
+            if(!drefs->refTargets) {
+                UA_NodeId_deleteMembers(&drefs->referenceTypeId);
+                break;
+            }
+            uintptr_t arraydiff = (uintptr_t)drefs->refTargets - (uintptr_t)srefs->refTargets;
+            for(size_t j = 0; j < srefs->refTargetsSize; j++) {
+                retval |= UA_ExpandedNodeId_copy(&srefs->refTargets[j].target,
+                                                 &drefs->refTargets[j].target);
+                drefs->refTargets[j].targetHash = srefs->refTargets[j].targetHash;
+                drefs->refTargets[j].zipfields.zip_right = NULL;
+                if(srefs->refTargets[j].zipfields.zip_right)
+                    *(uintptr_t*)&drefs->refTargets[j].zipfields.zip_left =
+                        (uintptr_t)srefs->refTargets[j].zipfields.zip_right + arraydiff;
+                drefs->refTargets[j].zipfields.zip_left = NULL;
+                if(srefs->refTargets[j].zipfields.zip_left)
+                    *(uintptr_t*)&drefs->refTargets[j].zipfields.zip_left =
+                        (uintptr_t)srefs->refTargets[j].zipfields.zip_left + arraydiff;
+            }
+            srefs->refTargetsTree.zip_root = NULL;
+            if(drefs->refTargetsTree.zip_root)
+                *(uintptr_t*)&drefs->refTargetsTree.zip_root =
+                    (uintptr_t)srefs->refTargetsTree.zip_root + arraydiff;
+            drefs->refTargetsSize= srefs->refTargetsSize;
             if(retval != UA_STATUSCODE_GOOD)
                 break;
-            drefs->targetIdsSize = srefs->targetIdsSize;
         }
+
         if(retval != UA_STATUSCODE_GOOD) {
             UA_Node_deleteMembers(dst);
             return retval;
@@ -456,75 +493,100 @@ UA_Node_setAttributes(UA_Node *node, const void *attributes,
 /*********************/
 
 static UA_StatusCode
-addReferenceTarget(UA_NodeReferenceKind *refs, const UA_ExpandedNodeId *target) {
-    UA_ExpandedNodeId *targets =
-        (UA_ExpandedNodeId*) UA_realloc(refs->targetIds,
-                                        sizeof(UA_ExpandedNodeId) * (refs->targetIdsSize+1));
+addReferenceTarget(UA_NodeReferenceKind *refs, const UA_ExpandedNodeId *target,
+                   UA_UInt32 targetHash) {
+    UA_ReferenceTarget *targets = (UA_ReferenceTarget*)
+        UA_realloc(refs->refTargets, (refs->refTargetsSize + 1) * sizeof(UA_ReferenceTarget));
     if(!targets)
         return UA_STATUSCODE_BADOUTOFMEMORY;
 
-    refs->targetIds = targets;
-    UA_StatusCode retval =
-        UA_ExpandedNodeId_copy(target, &refs->targetIds[refs->targetIdsSize]);
-
-    if(retval == UA_STATUSCODE_GOOD) {
-        refs->targetIdsSize++;
-    } else if(refs->targetIdsSize == 0) {
-        /* We had zero references before (realloc was a malloc) */
-        UA_free(refs->targetIds);
-        refs->targetIds = NULL;
+    /* Repair the pointers in the tree for the realloced array */
+    uintptr_t arraydiff = (uintptr_t)targets - (uintptr_t)refs->refTargets;
+    if(arraydiff != 0) {
+        for(size_t i = 0; i < refs->refTargetsSize; i++) {
+            if(targets[i].zipfields.zip_left)
+                *(uintptr_t*)&targets[i].zipfields.zip_left += arraydiff;
+            if(targets[i].zipfields.zip_right)
+                *(uintptr_t*)&targets[i].zipfields.zip_right += arraydiff;
+        }
     }
-    return retval;
+
+    if(refs->refTargetsTree.zip_root)
+        *(uintptr_t*)&refs->refTargetsTree.zip_root += arraydiff;
+    refs->refTargets = targets;
+
+    UA_ReferenceTarget *entry = &refs->refTargets[refs->refTargetsSize];
+    UA_StatusCode retval = UA_ExpandedNodeId_copy(target, &entry->target);
+    if(retval != UA_STATUSCODE_GOOD) {
+        if(refs->refTargetsSize== 0) {
+            /* We had zero references before (realloc was a malloc) */
+            UA_free(refs->refTargets);
+            refs->refTargets = NULL;
+        }
+        return retval;
+    }
+
+    entry->targetHash = targetHash;
+    ZIP_INSERT(UA_ReferenceTargetHead, &refs->refTargetsTree,
+               entry, ZIP_FFS32(UA_UInt32_random()));
+    refs->refTargetsSize++;
+    return UA_STATUSCODE_GOOD;
 }
 
 static UA_StatusCode
 addReferenceKind(UA_Node *node, const UA_AddReferencesItem *item) {
-    UA_NodeReferenceKind *refs =
-        (UA_NodeReferenceKind*)UA_realloc(node->references,
-                                          sizeof(UA_NodeReferenceKind) * (node->referencesSize+1));
+    UA_NodeReferenceKind *refs = (UA_NodeReferenceKind*)
+        UA_realloc(node->references, sizeof(UA_NodeReferenceKind) * (node->referencesSize+1));
     if(!refs)
         return UA_STATUSCODE_BADOUTOFMEMORY;
     node->references = refs;
     UA_NodeReferenceKind *newRef = &refs[node->referencesSize];
     memset(newRef, 0, sizeof(UA_NodeReferenceKind));
 
+    ZIP_INIT(&newRef->refTargetsTree);
     newRef->isInverse = !item->isForward;
     UA_StatusCode retval = UA_NodeId_copy(&item->referenceTypeId, &newRef->referenceTypeId);
-    retval |= addReferenceTarget(newRef, &item->targetNodeId);
+    UA_UInt32 targetHash = UA_ExpandedNodeId_hash(&item->targetNodeId);
+    retval |= addReferenceTarget(newRef, &item->targetNodeId, targetHash);
 
-    if(retval == UA_STATUSCODE_GOOD) {
-        node->referencesSize++;
-    } else {
+    if(retval != UA_STATUSCODE_GOOD) {
         UA_NodeId_deleteMembers(&newRef->referenceTypeId);
         if(node->referencesSize == 0) {
             UA_free(node->references);
             node->references = NULL;
         }
+        return retval;
     }
-    return retval;
+
+    node->referencesSize++;
+    return UA_STATUSCODE_GOOD;
 }
 
 UA_StatusCode
 UA_Node_addReference(UA_Node *node, const UA_AddReferencesItem *item) {
+    /* Find the matching refkind */
     UA_NodeReferenceKind *existingRefs = NULL;
     for(size_t i = 0; i < node->referencesSize; ++i) {
         UA_NodeReferenceKind *refs = &node->references[i];
-        if(refs->isInverse != item->isForward
-                && UA_NodeId_equal(&refs->referenceTypeId, &item->referenceTypeId)) {
+        if(refs->isInverse != item->isForward &&
+           UA_NodeId_equal(&refs->referenceTypeId, &item->referenceTypeId)) {
             existingRefs = refs;
             break;
         }
     }
-    if(existingRefs != NULL) {
-        for(size_t i = 0; i < existingRefs->targetIdsSize; i++) {
-            if(UA_ExpandedNodeId_equal(&existingRefs->targetIds[i],
-                                       &item->targetNodeId)) {
-                return UA_STATUSCODE_BADDUPLICATEREFERENCENOTALLOWED;
-            }
-        }
-        return addReferenceTarget(existingRefs, &item->targetNodeId);
-    }
-    return addReferenceKind(node, item);
+
+    if(!existingRefs)
+        return addReferenceKind(node, item);
+
+    UA_ReferenceTarget tmpTarget;
+    tmpTarget.target = item->targetNodeId;
+    tmpTarget.targetHash = UA_ExpandedNodeId_hash(&item->targetNodeId);
+
+    UA_ReferenceTarget *found =
+        ZIP_FIND(UA_ReferenceTargetHead, &existingRefs->refTargetsTree, &tmpTarget);
+    if(found)
+        return UA_STATUSCODE_BADDUPLICATEREFERENCENOTALLOWED;
+    return addReferenceTarget(existingRefs, &item->targetNodeId, tmpTarget.targetHash);
 }
 
 UA_StatusCode
@@ -536,33 +598,40 @@ UA_Node_deleteReference(UA_Node *node, const UA_DeleteReferencesItem *item) {
         if(!UA_NodeId_equal(&item->referenceTypeId, &refs->referenceTypeId))
             continue;
 
-        for(size_t j = refs->targetIdsSize; j > 0; --j) {
-            UA_ExpandedNodeId *target = &refs->targetIds[j-1];
-            if(!UA_NodeId_equal(&item->targetNodeId.nodeId, &target->nodeId))
+        for(size_t j = refs->refTargetsSize; j > 0; --j) {
+            UA_ReferenceTarget *target = &refs->refTargets[j-1];
+            if(!UA_NodeId_equal(&item->targetNodeId.nodeId, &target->target.nodeId))
                 continue;
 
             /* Ok, delete the reference */
-            UA_ExpandedNodeId_deleteMembers(target);
-            refs->targetIdsSize--;
+            ZIP_REMOVE(UA_ReferenceTargetHead, &refs->refTargetsTree, target);
+            UA_ExpandedNodeId_deleteMembers(&target->target);
+            refs->refTargetsSize--;
 
             /* One matching target remaining */
-            if(refs->targetIdsSize > 0) {
-                if(j-1 != refs->targetIdsSize) // avoid valgrind error: Source
-                                               // and destination overlap in
-                                               // memcpy
-                    *target = refs->targetIds[refs->targetIdsSize];
+            if(refs->refTargetsSize > 0) {
+                if(j-1 != refs->refTargetsSize) {
+                    /* avoid valgrind error: Source and destination overlap in
+                     * memcpy */
+                    ZIP_REMOVE(UA_ReferenceTargetHead, &refs->refTargetsTree,
+                               &refs->refTargets[refs->refTargetsSize]);
+                    *target = refs->refTargets[refs->refTargetsSize];
+                    ZIP_INSERT(UA_ReferenceTargetHead, &refs->refTargetsTree,
+                               target, ZIP_RANK(target, zipfields));
+                }
                 return UA_STATUSCODE_GOOD;
             }
 
             /* No target for the ReferenceType remaining. Remove entry. */
-            UA_free(refs->targetIds);
+            UA_free(refs->refTargets);
             UA_NodeId_deleteMembers(&refs->referenceTypeId);
             node->referencesSize--;
             if(node->referencesSize > 0) {
-                if(i-1 != node->referencesSize) // avoid valgrind error: Source
-                                                // and destination overlap in
-                                                // memcpy
+                if(i-1 != node->referencesSize) {
+                    /* avoid valgrind error: Source and destination overlap in
+                     * memcpy */
                     node->references[i-1] = node->references[node->referencesSize];
+                }
                 return UA_STATUSCODE_GOOD;
             }
 
@@ -597,7 +666,9 @@ UA_Node_deleteReferencesSubset(UA_Node *node, size_t referencesSkipSize,
             continue;
 
         /* Remove references */
-        UA_Array_delete(refs->targetIds, refs->targetIdsSize, &UA_TYPES[UA_TYPES_EXPANDEDNODEID]);
+        for(size_t j = 0; j < refs->refTargetsSize; j++)
+            UA_ExpandedNodeId_deleteMembers(&refs->refTargets[j].target);
+        UA_free(refs->refTargets);
         UA_NodeId_deleteMembers(&refs->referenceTypeId);
         node->referencesSize--;
 
@@ -607,17 +678,18 @@ UA_Node_deleteReferencesSubset(UA_Node *node, size_t referencesSkipSize,
         node->references[i-1] = node->references[node->referencesSize];
     }
 
-    if(node->referencesSize == 0) {
-        /* The array is empty. Remove. */
-        UA_free(node->references);
-        node->references = NULL;
-    } else {
+    if(node->referencesSize > 0) {
         /* Realloc to save memory */
         UA_NodeReferenceKind *refs = (UA_NodeReferenceKind*)
             UA_realloc(node->references, sizeof(UA_NodeReferenceKind) * node->referencesSize);
         if(refs) /* Do nothing if realloc fails */
             node->references = refs;
+        return;
     }
+
+    /* The array is empty. Remove. */
+    UA_free(node->references);
+    node->references = NULL;
 }
 
 void UA_Node_deleteReferences(UA_Node *node) {

+ 2 - 2
src/server/ua_server.c

@@ -147,9 +147,9 @@ UA_Server_forEachChildNodeCall(UA_Server *server, UA_NodeId parentNodeId,
     UA_StatusCode retval = UA_STATUSCODE_GOOD;
     for(size_t i = parentCopy->referencesSize; i > 0; --i) {
         UA_NodeReferenceKind *ref = &parentCopy->references[i - 1];
-        for(size_t j = 0; j<ref->targetIdsSize; j++) {
+        for(size_t j = 0; j<ref->refTargetsSize; j++) {
             UA_UNLOCK(server->serviceMutex);
-            retval = callback(ref->targetIds[j].nodeId, ref->isInverse,
+            retval = callback(ref->refTargets[j].target.nodeId, ref->isInverse,
                               ref->referenceTypeId, handle);
             UA_LOCK(server->serviceMutex);
             if(retval != UA_STATUSCODE_GOOD)

+ 7 - 7
src/server/ua_server_utils.c

@@ -57,7 +57,7 @@ isNodeInTreeNoCircular(void *nsCtx, const UA_NodeId *leafNode, const UA_NodeId *
             continue;
 
         /* Match the targets or recurse */
-        for(size_t j = 0; j < refs->targetIdsSize; ++j) {
+        for(size_t j = 0; j < refs->refTargetsSize; ++j) {
             /* Check if we already have seen the referenced node and skip to
              * avoid endless recursion. Do this only at every 5th depth to save
              * effort. Circular dependencies are rare and forbidden for most
@@ -66,7 +66,7 @@ isNodeInTreeNoCircular(void *nsCtx, const UA_NodeId *leafNode, const UA_NodeId *
                 struct ref_history *last = visitedRefs;
                 UA_Boolean skip = false;
                 while(!skip && last) {
-                    if(UA_NodeId_equal(last->id, &refs->targetIds[j].nodeId))
+                    if(UA_NodeId_equal(last->id, &refs->refTargets[j].target.nodeId))
                         skip = true;
                     last = last->parent;
                 }
@@ -75,13 +75,13 @@ isNodeInTreeNoCircular(void *nsCtx, const UA_NodeId *leafNode, const UA_NodeId *
             }
 
             /* Stack-allocate the visitedRefs structure for the next depth */
-            struct ref_history nextVisitedRefs = {visitedRefs, &refs->targetIds[j].nodeId,
+            struct ref_history nextVisitedRefs = {visitedRefs, &refs->refTargets[j].target.nodeId,
                                                   (UA_UInt16)(visitedRefs->depth+1)};
 
             /* Recurse */
             UA_Boolean foundRecursive =
-                isNodeInTreeNoCircular(nsCtx, &refs->targetIds[j].nodeId, nodeToFind, &nextVisitedRefs,
-                                       referenceTypeIds, referenceTypeIdsSize);
+                isNodeInTreeNoCircular(nsCtx, &refs->refTargets[j].target.nodeId, nodeToFind,
+                                       &nextVisitedRefs, referenceTypeIds, referenceTypeIdsSize);
             if(foundRecursive) {
                 UA_Nodestore_releaseNode(nsCtx, node);
                 return true;
@@ -136,8 +136,8 @@ getNodeType(UA_Server *server, const UA_Node *node) {
             continue;
         if(!UA_NodeId_equal(&node->references[i].referenceTypeId, &parentRef))
             continue;
-        UA_assert(node->references[i].targetIdsSize > 0);
-        const UA_NodeId *targetId = &node->references[i].targetIds[0].nodeId;
+        UA_assert(node->references[i].refTargetsSize> 0);
+        const UA_NodeId *targetId = &node->references[i].refTargets[0].target.nodeId;
         const UA_Node *type = UA_Nodestore_getNode(server->nsCtx, targetId);
         if(!type)
             continue;

+ 4 - 4
src/server/ua_services_method.c

@@ -32,9 +32,9 @@ getArgumentsVariableNode(UA_Server *server, const UA_MethodNode *ofMethod,
         if(!UA_NodeId_equal(&hasProperty, &rk->referenceTypeId))
             continue;
 
-        for(size_t j = 0; j < rk->targetIdsSize; ++j) {
+        for(size_t j = 0; j < rk->refTargetsSize; ++j) {
             const UA_Node *refTarget =
-                UA_Nodestore_getNode(server->nsCtx, &rk->targetIds[j].nodeId);
+                UA_Nodestore_getNode(server->nsCtx, &rk->refTargets[j].target.nodeId);
             if(!refTarget)
                 continue;
             if(refTarget->nodeClass == UA_NODECLASS_VARIABLE &&
@@ -149,8 +149,8 @@ callWithMethodAndObject(UA_Server *server, UA_Session *session,
         if(!isNodeInTree(server->nsCtx, &rk->referenceTypeId,
                          &hasComponentNodeId, &hasSubTypeNodeId, 1))
             continue;
-        for(size_t j = 0; j < rk->targetIdsSize; ++j) {
-            if(UA_NodeId_equal(&rk->targetIds[j].nodeId, &request->methodId)) {
+        for(size_t j = 0; j < rk->refTargetsSize; ++j) {
+            if(UA_NodeId_equal(&rk->refTargets[j].target.nodeId, &request->methodId)) {
                 found = true;
                 break;
             }

+ 5 - 5
src/server/ua_services_nodemanagement.c

@@ -414,8 +414,8 @@ isMandatoryChild(UA_Server *server, UA_Session *session,
             continue;
         if(refs->isInverse)
             continue;
-        for(size_t j = 0; j < refs->targetIdsSize; ++j) {
-            if(UA_NodeId_equal(&mandatoryId, &refs->targetIds[j].nodeId)) {
+        for(size_t j = 0; j < refs->refTargetsSize; ++j) {
+            if(UA_NodeId_equal(&mandatoryId, &refs->refTargets[j].target.nodeId)) {
                 UA_Nodestore_releaseNode(server->nsCtx, child);
                 return true;
             }
@@ -1431,8 +1431,8 @@ removeIncomingReferences(UA_Server *server, UA_Session *session,
         UA_NodeReferenceKind *refs = &node->references[i];
         item.isForward = refs->isInverse;
         item.referenceTypeId = refs->referenceTypeId;
-        for(size_t j = 0; j < refs->targetIdsSize; ++j) {
-            item.sourceNodeId = refs->targetIds[j].nodeId;
+        for(size_t j = 0; j < refs->refTargetsSize; ++j) {
+            item.sourceNodeId = refs->refTargets[j].target.nodeId;
             Operation_deleteReference(server, session, NULL, &item, &dummy);
         }
     }
@@ -1463,7 +1463,7 @@ multipleHierarchies(size_t hierarchicalRefsSize, UA_ExpandedNodeId *hierarchical
         if(!hierarchical)
             continue;
 
-        incomingRefs += k->targetIdsSize;
+        incomingRefs += k->refTargetsSize;
         if(incomingRefs > 1)
             return true;
     }

+ 10 - 9
src/server/ua_services_view.c

@@ -176,8 +176,8 @@ addRelevantReferences(UA_Server *server, RefTree *rt, const UA_NodeId *nodeId,
         if(!relevantReference(&rk->referenceTypeId, refTypesSize, refTypes))
             continue;
 
-        for(size_t k = 0; k < rk->targetIdsSize; k++) {
-            retval = RefTree_add(rt ,&rk->targetIds[k]);
+        for(size_t k = 0; k < rk->refTargetsSize; k++) {
+            retval = RefTree_add(rt, &rk->refTargets[k].target);
             if(retval != UA_STATUSCODE_GOOD)
                 goto cleanup;
         }
@@ -473,13 +473,14 @@ browseReferences(UA_Server *server, const UA_Node *node,
             continue;
 
         /* Loop over the targets */
-        for(; targetIndex < rk->targetIdsSize; ++targetIndex) {
+        for(; targetIndex < rk->refTargetsSize; ++targetIndex) {
             target = NULL;
 
             /* Get the node if it is not a remote reference */
-            if(rk->targetIds[targetIndex].serverIndex == 0 &&
-               rk->targetIds[targetIndex].namespaceUri.data == NULL) {
-                target = UA_Nodestore_getNode(server->nsCtx, &rk->targetIds[targetIndex].nodeId);
+            if(rk->refTargets[targetIndex].target.serverIndex == 0 &&
+               rk->refTargets[targetIndex].target.namespaceUri.data == NULL) {
+                target = UA_Nodestore_getNode(server->nsCtx,
+                                              &rk->refTargets[targetIndex].target.nodeId);
 
                 /* Test if the node class matches */
                 if(target && !matchClassMask(target, bd->nodeClassMask)) {
@@ -500,7 +501,7 @@ browseReferences(UA_Server *server, const UA_Node *node,
 
             /* Copy the node description. Target is on top of the stack */
             retval = addReferenceDescription(server, rr, rk, bd->resultMask,
-                                             &rk->targetIds[targetIndex], target);
+                                             &rk->refTargets[targetIndex].target, target);
             UA_Nodestore_releaseNode(server->nsCtx, target);
             if(retval != UA_STATUSCODE_GOOD)
                 return retval;
@@ -815,8 +816,8 @@ walkBrowsePathElementReferenceTargets(UA_BrowsePathResult *result, size_t *targe
                                       UA_NodeId **next, size_t *nextSize, size_t *nextCount,
                                       UA_UInt32 elemDepth, const UA_NodeReferenceKind *rk) {
     /* Loop over the targets */
-    for(size_t i = 0; i < rk->targetIdsSize; i++) {
-        UA_ExpandedNodeId *targetId = &rk->targetIds[i];
+    for(size_t i = 0; i < rk->refTargetsSize; i++) {
+        UA_ExpandedNodeId *targetId = &rk->refTargets[i].target;
 
         /* Does the reference point to an external server? Then add to the
          * targets with the right path depth. */

+ 65 - 77
src/ua_types_encoding_json.c

@@ -1567,6 +1567,18 @@ UA_calcSizeJson(const void *src, const UA_DataType *type,
 /* decode without moving the token index */
 #define DECODE_DIRECT_JSON(DST, TYPE) TYPE##_decodeJson((UA_##TYPE*)DST, NULL, ctx, parseCtx, false)
 
+/* If parseCtx->index points to the beginning of an object, move the index to
+ * the next token after this object. Attention! The index can be moved after the
+ * last parsed token. So the array length has to be checked afterwards. */
+static void
+skipObject(ParseCtx *parseCtx) {
+    int end = parseCtx->tokenArray[parseCtx->index].end;
+    do {
+        parseCtx->index++;
+    } while(parseCtx->index < parseCtx->tokenCount &&
+            parseCtx->tokenArray[parseCtx->index].start < end);
+}
+
 static status
 Array_decodeJson(void *dst, const UA_DataType *type, CtxJson *ctx, 
         ParseCtx *parseCtx, UA_Boolean moveToken);
@@ -1587,14 +1599,6 @@ getJsmnType(const ParseCtx *parseCtx) {
     return parseCtx->tokenArray[parseCtx->index].type;
 }
 
-static UA_Boolean
-isJsonTokenNull(const CtxJson *ctx, jsmntok_t *token) {
-    if(token->type != JSMN_PRIMITIVE)
-        return false;
-    char* elem = (char*)(ctx->pos + token->start);
-    return (elem[0] == 'n' && elem[1] == 'u' && elem[2] == 'l' && elem[3] == 'l');
-}
-
 UA_Boolean
 isJsonNull(const CtxJson *ctx, const ParseCtx *parseCtx) {
     if(parseCtx->index >= parseCtx->tokenCount)
@@ -2613,77 +2617,59 @@ DECODE_JSON(Variant) {
     /* First search for the variant type in the json object. */
     size_t searchResultType = 0;
     status ret = lookAheadForKey(UA_JSONKEY_TYPE, ctx, parseCtx, &searchResultType);
-    if(ret != UA_STATUSCODE_GOOD)
-        return UA_STATUSCODE_BADDECODINGERROR;
+    if(ret != UA_STATUSCODE_GOOD) {
+        skipObject(parseCtx);
+        return UA_STATUSCODE_GOOD;
+    }
 
-    size_t size = (size_t)(parseCtx->tokenArray[searchResultType].end - parseCtx->tokenArray[searchResultType].start);
+    size_t size = (size_t)(parseCtx->tokenArray[searchResultType].end -
+                           parseCtx->tokenArray[searchResultType].start);
 
     /* check if size is zero or the type is not a number */
-    if(size < 1 || parseCtx->tokenArray[searchResultType].type != JSMN_PRIMITIVE) {
+    if(size < 1 || parseCtx->tokenArray[searchResultType].type != JSMN_PRIMITIVE)
         return UA_STATUSCODE_BADDECODINGERROR;
-    }
     
-    /*Parse the type*/
+    /* Parse the type */
     UA_UInt64 idTypeDecoded = 0;
     char *idTypeEncoded = (char*)(ctx->pos + parseCtx->tokenArray[searchResultType].start);
     status typeDecodeStatus = atoiUnsigned(idTypeEncoded, size, &idTypeDecoded);
-    
-    /* value is not a valid number */
-    if(typeDecodeStatus != UA_STATUSCODE_GOOD) {
+    if(typeDecodeStatus != UA_STATUSCODE_GOOD)
         return typeDecodeStatus;
+
+    /* A NULL Variant */
+    if(idTypeDecoded == 0) {
+        skipObject(parseCtx);
+        return UA_STATUSCODE_GOOD;
     }
 
-    /*Set the type, Get the Type by nodeID!*/
+    /* Set the type */
     UA_NodeId typeNodeId = UA_NODEID_NUMERIC(0, (UA_UInt32)idTypeDecoded);
-    const UA_DataType *bodyType = UA_findDataType(&typeNodeId);
-    if(bodyType == NULL) {
+    dst->type = UA_findDataType(&typeNodeId);
+    if(!dst->type)
         return UA_STATUSCODE_BADDECODINGERROR;
-    }
-    
-    /*Set the type*/
-    dst->type = bodyType;
-    
-    /* LookAhead BODY */
-    /* Does the variant contain an array? */
-    UA_Boolean isArray = false;
-    UA_Boolean isBodyNull = false;
     
     /* Search for body */
     size_t searchResultBody = 0;
     ret = lookAheadForKey(UA_JSONKEY_BODY, ctx, parseCtx, &searchResultBody);
-    if(ret == UA_STATUSCODE_GOOD) { /* body found */
-        /* get body token */
-        jsmntok_t bodyToken = parseCtx->tokenArray[searchResultBody];
-        
-        /*BODY is null?*/
-        if(isJsonTokenNull(ctx, &bodyToken)) {
-            dst->data = NULL;
-            isBodyNull = true;
-        }
-        
-        if(bodyToken.type == JSMN_ARRAY) {
-            isArray = true;
-            
-            size_t arraySize = 0;
-            arraySize = (size_t)parseCtx->tokenArray[searchResultBody].size;
-            dst->arrayLength = arraySize;
-        }
-    } else {
+    if(ret != UA_STATUSCODE_GOOD) {
         /*TODO: no body? set value NULL?*/
         return UA_STATUSCODE_BADDECODINGERROR;
     }
-    
-    /* LookAhead DIMENSION */
-    UA_Boolean hasDimension = false;
+
+    /* value is an array? */
+    UA_Boolean isArray = false;
+    if(parseCtx->tokenArray[searchResultBody].type == JSMN_ARRAY) {
+        isArray = true;
+        dst->arrayLength = (size_t)parseCtx->tokenArray[searchResultBody].size;
+    }
 
     /* Has the variant dimension? */
+    UA_Boolean hasDimension = false;
     size_t searchResultDim = 0;
     ret = lookAheadForKey(UA_JSONKEY_DIMENSION, ctx, parseCtx, &searchResultDim);
     if(ret == UA_STATUSCODE_GOOD) {
         hasDimension = true;
-        size_t dimensionSize = 0;
-        dimensionSize = (size_t)parseCtx->tokenArray[searchResultDim].size;
-        dst->arrayDimensionsSize = dimensionSize;
+        dst->arrayDimensionsSize = (size_t)parseCtx->tokenArray[searchResultDim].size;
     }
     
     /* no array but has dimension. error? */
@@ -2691,45 +2677,47 @@ DECODE_JSON(Variant) {
         return UA_STATUSCODE_BADDECODINGERROR;
     
     /* Get the datatype of the content. The type must be a builtin data type.
-    * All not-builtin types are wrapped in an ExtensionObject. */
-    if(bodyType->typeKind > UA_TYPES_DIAGNOSTICINFO)
+     * All not-builtin types are wrapped in an ExtensionObject. */
+    if(dst->type->typeKind > UA_TYPES_DIAGNOSTICINFO)
         return UA_STATUSCODE_BADDECODINGERROR;
 
     /* A variant cannot contain a variant. But it can contain an array of
-        * variants */
-    if(bodyType->typeKind == UA_DATATYPEKIND_VARIANT && !isArray)
+     * variants */
+    if(dst->type->typeKind == UA_DATATYPEKIND_VARIANT && !isArray)
         return UA_STATUSCODE_BADDECODINGERROR;
     
+    /* Decode an array */
     if(isArray) {
         DecodeEntry entries[3] = {
             {UA_JSONKEY_TYPE, NULL, NULL, false, NULL},
             {UA_JSONKEY_BODY, &dst->data, (decodeJsonSignature) Array_decodeJson, false, NULL},
             {UA_JSONKEY_DIMENSION, &dst->arrayDimensions,
              (decodeJsonSignature) VariantDimension_decodeJson, false, NULL}};
-
         if(!hasDimension) {
-            ret = decodeFields(ctx, parseCtx, entries, 2, bodyType); /*use first 2 fields*/
+            ret = decodeFields(ctx, parseCtx, entries, 2, dst->type); /*use first 2 fields*/
         } else {
-            ret = decodeFields(ctx, parseCtx, entries, 3, bodyType); /*use all fields*/
+            ret = decodeFields(ctx, parseCtx, entries, 3, dst->type); /*use all fields*/
         }      
-    } else if(bodyType->typeKind != UA_DATATYPEKIND_EXTENSIONOBJECT) {
-        /* Allocate Memory for Body */
-        if(!isBodyNull) {
-            dst->data = UA_new(bodyType);
-            if(!dst->data)
-                return UA_STATUSCODE_BADOUTOFMEMORY;
-        }
-        
-        DecodeEntry entries[2] = {{UA_JSONKEY_TYPE, NULL, NULL, false, NULL},
-            {UA_JSONKEY_BODY, dst->data, (decodeJsonSignature) decodeJsonInternal, false, NULL}};
-        ret = decodeFields(ctx, parseCtx, entries, 2, bodyType);
-    } else { /* extensionObject */
-        DecodeEntry entries[2] = {{UA_JSONKEY_TYPE, NULL, NULL, false, NULL},
-            {UA_JSONKEY_BODY, dst,
-             (decodeJsonSignature) Variant_decodeJsonUnwrapExtensionObject, false, NULL}};
-        ret = decodeFields(ctx, parseCtx, entries, 2, bodyType);
+        return ret;
     }
-    return ret;
+
+    /* Decode a value wrapped in an ExtensionObject */
+    if(dst->type->typeKind == UA_DATATYPEKIND_EXTENSIONOBJECT) {
+        DecodeEntry entries[2] =
+            {{UA_JSONKEY_TYPE, NULL, NULL, false, NULL},
+             {UA_JSONKEY_BODY, dst,
+              (decodeJsonSignature)Variant_decodeJsonUnwrapExtensionObject, false, NULL}};
+        return decodeFields(ctx, parseCtx, entries, 2, dst->type);
+    }
+
+    /* Allocate Memory for Body */
+    dst->data = UA_new(dst->type);
+    if(!dst->data)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    DecodeEntry entries[2] =
+        {{UA_JSONKEY_TYPE, NULL, NULL, false, NULL},
+         {UA_JSONKEY_BODY, dst->data, (decodeJsonSignature) decodeJsonInternal, false, NULL}};
+    return decodeFields(ctx, parseCtx, entries, 2, dst->type);
 }
 
 DECODE_JSON(DataValue) {

+ 4 - 0
tests/CMakeLists.txt

@@ -329,6 +329,10 @@ add_executable(check_server_readspeed server/check_server_readspeed.c $<TARGET_O
 target_link_libraries(check_server_readspeed ${LIBS})
 add_test_valgrind(server_readspeed ${TESTS_BINARY_DIR}/check_server_readspeed)
 
+add_executable(check_server_speed_addnodes server/check_server_speed_addnodes.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+target_link_libraries(check_server_speed_addnodes ${LIBS})
+add_test_valgrind(server_speed_addnodes ${TESTS_BINARY_DIR}/check_server_speed_addnodes)
+
 if(UA_ENABLE_SUBSCRIPTIONS)
     add_executable(check_server_monitoringspeed server/check_server_monitoringspeed.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
     target_link_libraries(check_server_monitoringspeed ${LIBS})

+ 24 - 67
tests/check_types_builtin_json.c

@@ -4348,14 +4348,12 @@ START_TEST(UA_ByteString_null_json_decode) {
     UA_Variant out;
     UA_Variant_init(&out);
     UA_ByteString buf = UA_STRING("{\"Type\":15,\"Body\":null}");
-    // when
-    
     UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT]);
-    // then
     ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_int_eq(out.type->typeIndex, UA_TYPES_BYTESTRING);
-    ck_assert_ptr_eq(out.data, NULL);
-    
+    UA_ByteString *outData = (UA_ByteString*)out.data;
+    ck_assert_ptr_ne(outData, NULL);
+    ck_assert_ptr_eq(outData->data, NULL);
     UA_Variant_deleteMembers(&out);
 }
 END_TEST
@@ -4577,26 +4575,18 @@ START_TEST(UA_QualifiedName_json_decode) {
 }
 END_TEST
 
-
 START_TEST(UA_QualifiedName_null_json_decode) {
-    
     UA_Variant out;
     UA_Variant_init(&out);
     UA_ByteString buf = UA_STRING("{\"Type\":20,\"Body\":null}");
-    // when
-    
     UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT]);
-    // then
     ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_int_eq(out.type->typeIndex, UA_TYPES_QUALIFIEDNAME);
-    ck_assert_ptr_eq(out.data, NULL);
-    
+    ck_assert_ptr_ne(out.data, NULL);
     UA_Variant_deleteMembers(&out);
 }
 END_TEST
 
-
-/* --------LocalizedText------------ */
 START_TEST(UA_LocalizedText_json_decode) {
     // given
     UA_LocalizedText out;
@@ -4637,17 +4627,13 @@ START_TEST(UA_LocalizedText_missing_json_decode) {
 END_TEST
 
 START_TEST(UA_LocalizedText_null_json_decode) {
-    
     UA_Variant out;
     UA_Variant_init(&out);
     UA_ByteString buf = UA_STRING("{\"Type\":21,\"Body\":null}");
-    // when
-    
     UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT]);
-    // then
     ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_int_eq(out.type->typeIndex, UA_TYPES_LOCALIZEDTEXT);
-    ck_assert_ptr_eq(out.data, NULL);
+    ck_assert_ptr_ne(out.data, NULL);
     UA_Variant_deleteMembers(&out);
 }
 END_TEST
@@ -4986,26 +4972,19 @@ START_TEST(UA_DiagnosticInfo_json_decode) {
 END_TEST
 
 START_TEST(UA_DiagnosticInfo_null_json_decode) {
-    
     UA_Variant out;
     UA_Variant_init(&out);
     UA_ByteString buf = UA_STRING("{\"Type\":25,\"Body\":null}");
-    // when
-    
     UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT]);
-    // then
     ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_int_eq(out.type->typeIndex, UA_TYPES_DIAGNOSTICINFO);
-    ck_assert_ptr_eq(out.data, NULL);
-    
-    //ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasAdditionalInfo, 0);
-    //ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasInnerDiagnosticInfo, 0);
-    //ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasInnerStatusCode, 0);
-    //ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasLocale, 0);
-    //ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasLocalizedText, 0);
-    //ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasNamespaceUri, 0);
-    //ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasSymbolicId, 0);
-    
+    ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasAdditionalInfo, 0);
+    ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasInnerDiagnosticInfo, 0);
+    ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasInnerStatusCode, 0);
+    ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasLocale, 0);
+    ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasLocalizedText, 0);
+    ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasNamespaceUri, 0);
+    ck_assert_uint_eq(((UA_DiagnosticInfo*)out.data)->hasSymbolicId, 0);
     UA_Variant_deleteMembers(&out);
 }
 END_TEST
@@ -5064,20 +5043,12 @@ START_TEST(UA_DataValueMissingFields_json_decode) {
 END_TEST
 
 START_TEST(UA_DataValue_null_json_decode) {
-    // given
-    
     UA_Variant out;
     UA_Variant_init(&out);
     UA_ByteString buf = UA_STRING("{\"Type\":23,\"Body\":null}");
-
-    // when
-    
     UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT]);
-    //UA_DiagnosticInfo inner = *out.innerDiagnosticInfo;
-
-    // then
     ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
-    ck_assert_ptr_eq(out.data, NULL);
+    ck_assert_ptr_ne(out.data, NULL);
     UA_Variant_deleteMembers(&out);
 }
 END_TEST
@@ -5213,6 +5184,15 @@ START_TEST(UA_VariantBoolNull_json_decode) {
 }
 END_TEST
 
+START_TEST(UA_VariantNull_json_decode) {
+    UA_Variant out;
+    UA_Variant_init(&out);
+    UA_ByteString buf = UA_STRING("{\"Type\":0}");
+    UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT]);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
+}
+END_TEST
+
 START_TEST(UA_VariantStringArray_json_decode) {
     // given
     
@@ -5601,34 +5581,14 @@ START_TEST(UA_Variant_Bad_Type2_decode) {
 }
 END_TEST
 
-START_TEST(UA_Variant_Null_decode) {
-    for(int i = 0; i < 100; i++){
-        UA_Variant out;
-        UA_Variant_init(&out);
-        char str[80];
-        sprintf(str, "{\"Type\":%d, \"Body:\":null}", i);
-        UA_ByteString buf = UA_STRING(str);
-        // when
-
-        UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT]);
-        // then
-        ck_assert_int_eq(retval, retval);
-        UA_Variant_deleteMembers(&out);
-    }
-}
-END_TEST
-
 START_TEST(UA_Variant_Malformed_decode) {
-    for(int i = 0; i < 100; i++){
+    for(int i = 1; i < 100; i++) {
         UA_Variant out;
         UA_Variant_init(&out);
         char str[80];
         sprintf(str, "{\"Type\":%d, \"Body:\"}", i);
         UA_ByteString buf = UA_STRING(str);
-        // when
-
         UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT]);
-        // then
         ck_assert_int_eq(retval, UA_STATUSCODE_BADDECODINGERROR);
         UA_Variant_deleteMembers(&out);
     }
@@ -5988,6 +5948,7 @@ static Suite *testSuite_builtin_json(void) {
     //Variant
     tcase_add_test(tc_json_decode, UA_VariantBool_json_decode);
     tcase_add_test(tc_json_decode, UA_VariantBoolNull_json_decode);
+    tcase_add_test(tc_json_decode, UA_VariantNull_json_decode);
     tcase_add_test(tc_json_decode, UA_VariantStringArray_json_decode);
     tcase_add_test(tc_json_decode, UA_VariantStringArrayNull_json_decode);
     tcase_add_test(tc_json_decode, UA_VariantLocalizedTextArrayNull_json_decode);
@@ -6025,13 +5986,9 @@ static Suite *testSuite_builtin_json(void) {
     tcase_add_test(tc_json_decode, UA_Variant_Bad_Type_decode);
     tcase_add_test(tc_json_decode, UA_Variant_Bad_Type2_decode);
 
-    tcase_add_test(tc_json_decode, UA_Variant_Null_decode);
-
-
     tcase_add_test(tc_json_decode, UA_Variant_Malformed_decode);
     tcase_add_test(tc_json_decode, UA_Variant_Malformed2_decode);
 
-
     suite_add_tcase(s, tc_json_decode);
     
     TCase *tc_json_helper = tcase_create("json_helper");

+ 96 - 0
tests/server/check_server_speed_addnodes.c

@@ -0,0 +1,96 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+/* This example is just to see how fast we can add nodes. The server does not
+ * open a TCP port. */
+
+#include <open62541/server_config_default.h>
+
+#include "server/ua_services.h"
+#include "ua_server_internal.h"
+#include "ua_types_encoding_binary.h"
+
+#include <check.h>
+#include <time.h>
+
+#include "testing_networklayers.h"
+#include "testing_policy.h"
+
+static UA_SecureChannel testChannel;
+static UA_SecurityPolicy dummyPolicy;
+static UA_Connection testingConnection;
+static funcs_called funcsCalled;
+static key_sizes keySizes;
+static UA_Server *server;
+
+static void setup(void) {
+    server = UA_Server_new();
+    UA_ServerConfig_setDefault(UA_Server_getConfig(server));
+    TestingPolicy(&dummyPolicy, UA_BYTESTRING_NULL, &funcsCalled, &keySizes);
+    UA_SecureChannel_init(&testChannel);
+    UA_SecureChannel_setSecurityPolicy(&testChannel, &dummyPolicy, &UA_BYTESTRING_NULL);
+    testingConnection = createDummyConnection(65535, NULL);
+    UA_Connection_attachSecureChannel(&testingConnection, &testChannel);
+    testChannel.connection = &testingConnection;
+
+    UA_ServerConfig *config = UA_Server_getConfig(server);
+    config->logger.log = NULL;
+}
+
+static void teardown(void) {
+    UA_SecureChannel_close(&testChannel);
+    UA_SecureChannel_deleteMembers(&testChannel);
+    dummyPolicy.deleteMembers(&dummyPolicy);
+    testingConnection.close(&testingConnection);
+    UA_Server_delete(server);
+}
+
+START_TEST(addVariable) {
+    /* add a variable node to the address space */
+    UA_VariableAttributes attr = UA_VariableAttributes_default;
+    UA_Int32 myInteger = 42;
+    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_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);
+
+    clock_t begin, finish;
+    begin = clock();
+    for(int i = 0; i < 10000; i++) {
+        UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId,
+                                  parentReferenceNodeId, myIntegerName,
+                                  UA_NODEID_NULL, attr, NULL, NULL);
+
+        if(i % 1000 == 0) {
+            finish = clock();
+            double time_spent = (double)(finish - begin) / CLOCKS_PER_SEC;
+            printf("%i nodes:\t Duration was %f s\n", i, time_spent);
+            begin = clock();
+        }
+    }
+}
+END_TEST
+
+static Suite * service_speed_suite (void) {
+    Suite *s = suite_create ("Service Speed");
+
+    TCase* tc_addnodes = tcase_create ("AddNodes");
+    tcase_add_checked_fixture(tc_addnodes, setup, teardown);
+    tcase_add_test(tc_addnodes, addVariable);
+    suite_add_tcase(s, tc_addnodes);
+
+    return s;
+}
+
+int main (void) {
+    int number_failed = 0;
+    Suite *s = service_speed_suite();
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr,CK_NOFORK);
+    srunner_run_all(sr, CK_NORMAL);
+    number_failed += srunner_ntests_failed (sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}