|
@@ -2,7 +2,7 @@
|
|
|
* 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-2019 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
|
|
|
* Copyright 2014-2017 (c) Florian Palm
|
|
|
* Copyright 2015-2016 (c) Sten Grüner
|
|
|
* Copyright 2015 (c) LEvertz
|
|
@@ -18,22 +18,161 @@
|
|
|
|
|
|
#include "ua_server_internal.h"
|
|
|
#include "ua_services.h"
|
|
|
+#include "ziptree.h"
|
|
|
+
|
|
|
+/***********/
|
|
|
+/* RefTree */
|
|
|
+/***********/
|
|
|
+
|
|
|
+/* References are browsed with a minimum number of copy operations. A RefTree
|
|
|
+ * holds a single array for both the NodeIds encountered during (recursive)
|
|
|
+ * browsing and the entries for a tree-structure to check for duplicates. Once
|
|
|
+ * the (recursive) browse has finished, the tree-structure part can be simply
|
|
|
+ * cut away. A single realloc operation (with some pointer repairing) can be
|
|
|
+ * used to increase the capacity of the RefTree.
|
|
|
+ *
|
|
|
+ * If an ExpandedNodeId is encountered, it has to be processed right away.
|
|
|
+ * Remote ExpandedNodeId are not put into the tree, since it is not possible to
|
|
|
+ * recurse into them anyway.
|
|
|
+ *
|
|
|
+ * The layout of the results array is as follows:
|
|
|
+ *
|
|
|
+ * | Targets [NodeId] | Tree [RefEntry] | */
|
|
|
+
|
|
|
+#define UA_BROWSE_INITIAL_SIZE 16
|
|
|
+
|
|
|
+typedef struct RefEntry {
|
|
|
+ ZIP_ENTRY(RefEntry) zipfields;
|
|
|
+ const UA_NodeId *target;
|
|
|
+ UA_UInt32 targetHash; /* Hash of the target nodeid */
|
|
|
+} RefEntry;
|
|
|
+
|
|
|
+static enum ZIP_CMP
|
|
|
+cmpTarget(const void *a, const void *b) {
|
|
|
+ const RefEntry *aa = (const RefEntry*)a;
|
|
|
+ const RefEntry *bb = (const RefEntry*)b;
|
|
|
+ if(aa->targetHash < bb->targetHash)
|
|
|
+ return ZIP_CMP_LESS;
|
|
|
+ if(aa->targetHash > bb->targetHash)
|
|
|
+ return ZIP_CMP_MORE;
|
|
|
+ return (enum ZIP_CMP)UA_NodeId_order(aa->target, bb->target);
|
|
|
+}
|
|
|
+
|
|
|
+ZIP_HEAD(RefHead, RefEntry);
|
|
|
+typedef struct RefHead RefHead;
|
|
|
+ZIP_PROTTYPE(RefHead, RefEntry, RefEntry)
|
|
|
+ZIP_IMPL(RefHead, RefEntry, zipfields, RefEntry, zipfields, cmpTarget)
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ UA_NodeId *targets;
|
|
|
+ RefHead head;
|
|
|
+ size_t capacity; /* available space */
|
|
|
+ size_t size; /* used space */
|
|
|
+} RefTree;
|
|
|
+
|
|
|
+static UA_StatusCode
|
|
|
+RefTree_init(RefTree *rt) {
|
|
|
+ size_t space = (sizeof(UA_NodeId) + sizeof(RefEntry)) * UA_BROWSE_INITIAL_SIZE;
|
|
|
+ rt->targets = (UA_NodeId*)UA_malloc(space);
|
|
|
+ if(!rt->targets)
|
|
|
+ return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
+ rt->capacity = UA_BROWSE_INITIAL_SIZE;
|
|
|
+ rt->size = 0;
|
|
|
+ ZIP_INIT(&rt->head);
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+static
|
|
|
+void RefTree_clear(RefTree *rt) {
|
|
|
+ for(size_t i = 0; i < rt->size; i++)
|
|
|
+ UA_NodeId_deleteMembers(&rt->targets[i]);
|
|
|
+ UA_free(rt->targets);
|
|
|
+}
|
|
|
+
|
|
|
+/* Double the capacity of the reftree */
|
|
|
+static UA_StatusCode
|
|
|
+RefTree_double(RefTree *rt) {
|
|
|
+ size_t capacity = rt->capacity * 2;
|
|
|
+ size_t space = (sizeof(UA_NodeId) + sizeof(RefEntry)) * capacity;
|
|
|
+ UA_NodeId *newTargets = (UA_NodeId*)realloc(rt->targets, space);
|
|
|
+ if(!newTargets)
|
|
|
+ return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
+
|
|
|
+ /* Repair the pointers for the realloced array+tree */
|
|
|
+ uintptr_t arraydiff = (uintptr_t)newTargets - (uintptr_t)rt->targets;
|
|
|
+ RefEntry *reArray = (RefEntry*)
|
|
|
+ ((uintptr_t)newTargets + (capacity * sizeof(UA_NodeId)));
|
|
|
+ uintptr_t entrydiff = (uintptr_t)reArray -
|
|
|
+ ((uintptr_t)rt->targets + (rt->capacity * sizeof(UA_NodeId)));
|
|
|
+ RefEntry *oldReArray = (RefEntry*)
|
|
|
+ ((uintptr_t)newTargets + (rt->capacity * sizeof(UA_NodeId)));
|
|
|
+ memmove(reArray, oldReArray, rt->size * sizeof(RefEntry));
|
|
|
+ for(size_t i = 0; i < rt->size; i++) {
|
|
|
+ if(reArray[i].zipfields.zip_left)
|
|
|
+ *(uintptr_t*)&reArray[i].zipfields.zip_left += entrydiff;
|
|
|
+ if(reArray[i].zipfields.zip_right)
|
|
|
+ *(uintptr_t*)&reArray[i].zipfields.zip_right += entrydiff;
|
|
|
+ *(uintptr_t*)&reArray[i].target += arraydiff;
|
|
|
+ }
|
|
|
+
|
|
|
+ rt->head.zip_root = (RefEntry*)((uintptr_t)rt->head.zip_root + entrydiff);
|
|
|
+ rt->capacity = capacity;
|
|
|
+ rt->targets = newTargets;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+static UA_StatusCode
|
|
|
+RefTree_add(RefTree *rt, const UA_NodeId *target) {
|
|
|
+ UA_StatusCode s = UA_STATUSCODE_GOOD;
|
|
|
+ if(rt->capacity <= rt->size) {
|
|
|
+ s = RefTree_double(rt);
|
|
|
+ if(s != UA_STATUSCODE_GOOD)
|
|
|
+ return s;
|
|
|
+ }
|
|
|
+ s = UA_NodeId_copy(target, &rt->targets[rt->size]);
|
|
|
+ if(s != UA_STATUSCODE_GOOD)
|
|
|
+ return s;
|
|
|
+ RefEntry *re = (RefEntry*)((uintptr_t)rt->targets +
|
|
|
+ (sizeof(UA_NodeId) * rt->capacity) +
|
|
|
+ (sizeof(RefEntry) * rt->size));
|
|
|
+ re->target = &rt->targets[rt->size];
|
|
|
+ re->targetHash = UA_NodeId_hash(target);
|
|
|
+ ZIP_INSERT(RefHead, &rt->head, re, ZIP_FFS32(UA_UInt32_random()));
|
|
|
+ rt->size++;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+/*********************/
|
|
|
+/* ContinuationPoint */
|
|
|
+/*********************/
|
|
|
|
|
|
struct ContinuationPoint {
|
|
|
ContinuationPoint *next;
|
|
|
- UA_ByteString identifier;
|
|
|
- UA_BrowseDescription browseDescription;
|
|
|
+ UA_Guid identifier;
|
|
|
UA_UInt32 maxReferences;
|
|
|
+ UA_BrowseDescription bd;
|
|
|
+ UA_Boolean recursive;
|
|
|
|
|
|
- /* The last point in the node references? */
|
|
|
- size_t referenceKindIndex;
|
|
|
- size_t targetIndex;
|
|
|
+ RefTree rt;
|
|
|
+ size_t n; /* Index of the node in the rt that is currently visited */
|
|
|
+ size_t nk; /* Index of the ReferenceKind in the node that is visited */
|
|
|
+ size_t nki; /* Index of the reference in the ReferenceKind that is visited */
|
|
|
};
|
|
|
|
|
|
+static UA_StatusCode
|
|
|
+ContinuationPoint_init(ContinuationPoint *cp, UA_UInt32 maxRefs,
|
|
|
+ UA_Boolean recursive) {
|
|
|
+ memset(cp, 0, sizeof(ContinuationPoint));
|
|
|
+ cp->identifier = UA_Guid_random();
|
|
|
+ cp->maxReferences = maxRefs;
|
|
|
+ cp->recursive = recursive;
|
|
|
+ return RefTree_init(&cp->rt);
|
|
|
+}
|
|
|
+
|
|
|
ContinuationPoint *
|
|
|
ContinuationPoint_clear(ContinuationPoint *cp) {
|
|
|
- UA_ByteString_deleteMembers(&cp->identifier);
|
|
|
- UA_BrowseDescription_deleteMembers(&cp->browseDescription);
|
|
|
+ UA_BrowseDescription_clear(&cp->bd);
|
|
|
+ RefTree_clear(&cp->rt);
|
|
|
return cp->next;
|
|
|
}
|
|
|
|
|
@@ -41,17 +180,72 @@ ContinuationPoint_clear(ContinuationPoint *cp) {
|
|
|
/* Browse */
|
|
|
/**********/
|
|
|
|
|
|
+/* The RefTree structure is kept across calls to Browse(Next). The RefResult
|
|
|
+ * structure is valid only for the current service call. Note that we can call
|
|
|
+ * Browse internally without a RefResult. Then, only the RefTree is set up with
|
|
|
+ * the target identifiers. */
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ size_t size;
|
|
|
+ size_t capacity;
|
|
|
+ UA_ReferenceDescription *descr;
|
|
|
+} RefResult;
|
|
|
+
|
|
|
+static UA_StatusCode
|
|
|
+RefResult_init(RefResult *rr, UA_UInt32 maxRefs) {
|
|
|
+ UA_UInt32 initialRes = UA_BROWSE_INITIAL_SIZE;
|
|
|
+ if(initialRes > maxRefs)
|
|
|
+ initialRes = maxRefs;
|
|
|
+ memset(rr, 0, sizeof(RefResult));
|
|
|
+ rr->descr = (UA_ReferenceDescription*)
|
|
|
+ UA_Array_new(initialRes, &UA_TYPES[UA_TYPES_REFERENCEDESCRIPTION]);
|
|
|
+ if(!rr->descr)
|
|
|
+ return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
+ rr->capacity = initialRes;
|
|
|
+ rr->size = 0;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+static UA_StatusCode
|
|
|
+RefResult_double(RefResult *rr, UA_UInt32 maxSize) {
|
|
|
+ size_t newSize = rr->capacity * 2;
|
|
|
+ if(newSize > maxSize)
|
|
|
+ newSize = maxSize;
|
|
|
+ UA_ReferenceDescription *rd = (UA_ReferenceDescription*)
|
|
|
+ UA_realloc(rr->descr, newSize * sizeof(UA_ReferenceDescription));
|
|
|
+ if(!rd)
|
|
|
+ return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
+ memset(&rd[rr->size], 0, sizeof(UA_ReferenceDescription) * (newSize - rr->size));
|
|
|
+ rr->descr = rd;
|
|
|
+ rr->capacity = newSize;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+RefResult_clear(RefResult *rr) {
|
|
|
+ UA_assert(rr->descr != NULL);
|
|
|
+ for(size_t i = 0; i < rr->size; i++)
|
|
|
+ UA_ReferenceDescription_clear(&rr->descr[i]);
|
|
|
+ UA_free(rr->descr);
|
|
|
+}
|
|
|
+
|
|
|
/* Target node on top of the stack */
|
|
|
static UA_StatusCode
|
|
|
-fillReferenceDescription(UA_Server *server, const UA_Node *curr,
|
|
|
- const UA_NodeReferenceKind *ref,
|
|
|
- UA_UInt32 mask, UA_ReferenceDescription *descr) {
|
|
|
- UA_ReferenceDescription_init(descr);
|
|
|
- UA_StatusCode retval = UA_NodeId_copy(&curr->nodeId, &descr->nodeId.nodeId);
|
|
|
+fillReferenceDescription(UA_Server *server, const UA_NodeReferenceKind *ref, UA_UInt32 mask,
|
|
|
+ const UA_ExpandedNodeId *nodeId, const UA_Node *curr,
|
|
|
+ UA_ReferenceDescription *descr) {
|
|
|
+ /* Fields without access to the actual node */
|
|
|
+ UA_StatusCode retval = UA_ExpandedNodeId_copy(nodeId, &descr->nodeId);
|
|
|
if(mask & UA_BROWSERESULTMASK_REFERENCETYPEID)
|
|
|
retval |= UA_NodeId_copy(&ref->referenceTypeId, &descr->referenceTypeId);
|
|
|
if(mask & UA_BROWSERESULTMASK_ISFORWARD)
|
|
|
descr->isForward = !ref->isInverse;
|
|
|
+
|
|
|
+ /* Remote references (ExpandedNodeId) are not further looked up here */
|
|
|
+ if(!curr)
|
|
|
+ return retval;
|
|
|
+
|
|
|
+ /* Fields that require the actual node */
|
|
|
if(mask & UA_BROWSERESULTMASK_NODECLASS)
|
|
|
retval |= UA_NodeClass_copy(&curr->nodeClass, &descr->nodeClass);
|
|
|
if(mask & UA_BROWSERESULTMASK_BROWSENAME)
|
|
@@ -89,255 +283,284 @@ matchClassMask(const UA_Node *node, UA_UInt32 nodeClassMask) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
-/* Returns whether the node / continuationpoint is done */
|
|
|
-static UA_Boolean
|
|
|
-browseReferences(UA_Server *server, const UA_Node *node,
|
|
|
- ContinuationPoint *cp, UA_BrowseResult *result) {
|
|
|
- UA_assert(cp != NULL);
|
|
|
- const UA_BrowseDescription *descr = &cp->browseDescription;
|
|
|
+static UA_StatusCode
|
|
|
+browseNodeRefKind(UA_Server *server, UA_Session *session, ContinuationPoint *cp,
|
|
|
+ RefResult *rr, UA_Boolean *maxed, const UA_NodeReferenceKind *rk,
|
|
|
+ const UA_ExpandedNodeId *target) {
|
|
|
+ /* Is the target already in the tree? */
|
|
|
+ RefEntry re;
|
|
|
+ re.target = &target->nodeId;
|
|
|
+ re.targetHash = UA_NodeId_hash(&target->nodeId);
|
|
|
+ if(ZIP_FIND(RefHead, &cp->rt.head, &re)) {
|
|
|
+ cp->nki++;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+ }
|
|
|
|
|
|
- /* If the node has no references, just return */
|
|
|
- if(node->referencesSize == 0) {
|
|
|
- result->referencesSize = 0;
|
|
|
- return true;
|
|
|
+ /* Get the target if it is not a remote node */
|
|
|
+ const UA_Node *node = NULL;
|
|
|
+ if(target->serverIndex == 0) {
|
|
|
+ node = UA_Nodestore_getNode(server->nsCtx, &target->nodeId);
|
|
|
+ if(node) {
|
|
|
+ /* Test if the node class matches */
|
|
|
+ UA_Boolean match = matchClassMask(node, cp->bd.nodeClassMask);
|
|
|
+ if(!match) {
|
|
|
+ UA_Nodestore_releaseNode(server->nsCtx, node);
|
|
|
+ cp->nki++;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- /* Follow all references? */
|
|
|
- UA_Boolean browseAll = UA_NodeId_isNull(&descr->referenceTypeId);
|
|
|
+ /* A match! */
|
|
|
|
|
|
- /* How many references can we return at most? */
|
|
|
- size_t maxrefs = cp->maxReferences;
|
|
|
- if(maxrefs == 0) {
|
|
|
- if(server->config.maxReferencesPerNode != 0) {
|
|
|
- maxrefs = server->config.maxReferencesPerNode;
|
|
|
- } else {
|
|
|
- maxrefs = UA_INT32_MAX;
|
|
|
- }
|
|
|
- } else {
|
|
|
- if(server->config.maxReferencesPerNode != 0 &&
|
|
|
- maxrefs > server->config.maxReferencesPerNode) {
|
|
|
- maxrefs = server->config.maxReferencesPerNode;
|
|
|
+ /* Don't return detailed results if not required */
|
|
|
+ UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
|
|
+ if(!rr)
|
|
|
+ goto cleanup;
|
|
|
+
|
|
|
+ /* Increase the capacity of the results array/tree if required */
|
|
|
+ if(rr->capacity <= rr->size) {
|
|
|
+ retval = RefResult_double(rr, cp->maxReferences);
|
|
|
+ /* Reached MaxReferences; Redo for this target */
|
|
|
+ if(retval != UA_STATUSCODE_GOOD)
|
|
|
+ goto cleanup;
|
|
|
+ if(rr->size == rr->capacity) {
|
|
|
+ *maxed = true;
|
|
|
+ goto cleanup;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /* Allocate the results array */
|
|
|
- size_t refs_size = 2; /* True size of the array */
|
|
|
- result->references = (UA_ReferenceDescription*)
|
|
|
- UA_Array_new(refs_size, &UA_TYPES[UA_TYPES_REFERENCEDESCRIPTION]);
|
|
|
- if(!result->references) {
|
|
|
- result->statusCode = UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
- return false;
|
|
|
- }
|
|
|
+ /* Fill the detailed results */
|
|
|
+ retval = fillReferenceDescription(server, rk, cp->bd.resultMask, target,
|
|
|
+ node, &rr->descr[rr->size]);
|
|
|
+ if(retval != UA_STATUSCODE_GOOD)
|
|
|
+ goto cleanup;
|
|
|
+ rr->size++;
|
|
|
|
|
|
- size_t referenceKindIndex = cp->referenceKindIndex;
|
|
|
- size_t targetIndex = cp->targetIndex;
|
|
|
+ cleanup:
|
|
|
+ if(!(*maxed) && retval == UA_STATUSCODE_GOOD) {
|
|
|
+ /* Proceed to the next target */
|
|
|
+ if(node)
|
|
|
+ retval = RefTree_add(&cp->rt, &target->nodeId);
|
|
|
+ cp->nki++;
|
|
|
+ }
|
|
|
+ if(node)
|
|
|
+ UA_Nodestore_releaseNode(server->nsCtx, node);
|
|
|
+ return retval;
|
|
|
+}
|
|
|
|
|
|
- /* Loop over the node's references */
|
|
|
- for(; referenceKindIndex < node->referencesSize; ++referenceKindIndex) {
|
|
|
- UA_NodeReferenceKind *rk = &node->references[referenceKindIndex];
|
|
|
+static UA_StatusCode
|
|
|
+browseNode(UA_Server *server, UA_Session *session,
|
|
|
+ ContinuationPoint *cp, RefResult *rr, UA_Boolean *maxed,
|
|
|
+ size_t referenceTypesSize, const UA_NodeId *referenceTypes,
|
|
|
+ const UA_Node *node) {
|
|
|
+ for(; cp->nk < node->referencesSize; cp->nk++, cp->nki = 0) {
|
|
|
+ UA_NodeReferenceKind *rk = &node->references[cp->nk];
|
|
|
|
|
|
/* Reference in the right direction? */
|
|
|
- if(rk->isInverse && descr->browseDirection == UA_BROWSEDIRECTION_FORWARD)
|
|
|
+ if(rk->isInverse && cp->bd.browseDirection == UA_BROWSEDIRECTION_FORWARD)
|
|
|
continue;
|
|
|
- if(!rk->isInverse && descr->browseDirection == UA_BROWSEDIRECTION_INVERSE)
|
|
|
+ if(!rk->isInverse && cp->bd.browseDirection == UA_BROWSEDIRECTION_INVERSE)
|
|
|
continue;
|
|
|
|
|
|
/* Is the reference part of the hierarchy of references we look for? */
|
|
|
- if(!browseAll && !relevantReference(server, descr->includeSubtypes,
|
|
|
- &descr->referenceTypeId, &rk->referenceTypeId))
|
|
|
+ UA_Boolean relevant = (referenceTypes == NULL);
|
|
|
+ for(size_t i = 0; i < referenceTypesSize && !relevant; i++)
|
|
|
+ relevant = UA_NodeId_equal(&rk->referenceTypeId, &referenceTypes[i]);
|
|
|
+ if(!relevant)
|
|
|
continue;
|
|
|
|
|
|
- /* Loop over the targets */
|
|
|
- for(; targetIndex < rk->targetIdsSize; ++targetIndex) {
|
|
|
- /* Get the node */
|
|
|
- const UA_Node *target =
|
|
|
- UA_Nodestore_getNode(server->nsCtx, &rk->targetIds[targetIndex].nodeId);
|
|
|
- if(!target)
|
|
|
- continue;
|
|
|
-
|
|
|
- /* Test if the node class matches */
|
|
|
- if(!matchClassMask(target, descr->nodeClassMask)) {
|
|
|
- UA_Nodestore_releaseNode(server->nsCtx, target);
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- /* A match! Can we return it? */
|
|
|
- if(result->referencesSize >= maxrefs) {
|
|
|
- /* There are references we could not return */
|
|
|
- cp->referenceKindIndex = referenceKindIndex;
|
|
|
- cp->targetIndex = targetIndex;
|
|
|
- UA_Nodestore_releaseNode(server->nsCtx, target);
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- /* Make enough space in the array */
|
|
|
- if(result->referencesSize >= refs_size) {
|
|
|
- refs_size *= 2;
|
|
|
- if(refs_size > maxrefs)
|
|
|
- refs_size = maxrefs;
|
|
|
- UA_ReferenceDescription *rd = (UA_ReferenceDescription*)
|
|
|
- UA_realloc(result->references, sizeof(UA_ReferenceDescription) * refs_size);
|
|
|
- if(!rd) {
|
|
|
- result->statusCode = UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
- UA_Nodestore_releaseNode(server->nsCtx, target);
|
|
|
- goto error_recovery;
|
|
|
- }
|
|
|
- result->references = rd;
|
|
|
- }
|
|
|
-
|
|
|
- /* Copy the node description. Target is on top of the stack */
|
|
|
- result->statusCode =
|
|
|
- fillReferenceDescription(server, target, rk, descr->resultMask,
|
|
|
- &result->references[result->referencesSize]);
|
|
|
-
|
|
|
- UA_Nodestore_releaseNode(server->nsCtx, target);
|
|
|
-
|
|
|
- if(result->statusCode != UA_STATUSCODE_GOOD)
|
|
|
- goto error_recovery;
|
|
|
-
|
|
|
- /* Increase the counter */
|
|
|
- result->referencesSize++;
|
|
|
+ /* cp->nki is increaed in browseNodeRefKind upon success */
|
|
|
+ for(; cp->nki < rk->targetIdsSize; ) {
|
|
|
+ UA_StatusCode retval = browseNodeRefKind(server, session, cp, rr, maxed,
|
|
|
+ rk, &rk->targetIds[cp->nki]);
|
|
|
+ if(*maxed || retval != UA_STATUSCODE_GOOD)
|
|
|
+ return retval;
|
|
|
}
|
|
|
-
|
|
|
- targetIndex = 0; /* Start at index 0 for the next reference kind */
|
|
|
}
|
|
|
|
|
|
- /* No relevant references, return array of length zero */
|
|
|
- if(result->referencesSize == 0) {
|
|
|
- UA_free(result->references);
|
|
|
- result->references = (UA_ReferenceDescription*)UA_EMPTY_ARRAY_SENTINEL;
|
|
|
- }
|
|
|
-
|
|
|
- /* The node is done */
|
|
|
- return true;
|
|
|
-
|
|
|
- error_recovery:
|
|
|
- if(result->referencesSize == 0)
|
|
|
- UA_free(result->references);
|
|
|
- else
|
|
|
- UA_Array_delete(result->references, result->referencesSize,
|
|
|
- &UA_TYPES[UA_TYPES_REFERENCEDESCRIPTION]);
|
|
|
- result->references = NULL;
|
|
|
- result->referencesSize = 0;
|
|
|
- return false;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
}
|
|
|
|
|
|
/* Results for a single browsedescription. This is the inner loop for both
|
|
|
* Browse and BrowseNext. The ContinuationPoint contains all the data used.
|
|
|
* Including the BrowseDescription. Returns whether there are remaining
|
|
|
* references. */
|
|
|
-static UA_Boolean
|
|
|
-browseWithContinuation(UA_Server *server, UA_Session *session,
|
|
|
- ContinuationPoint *cp, UA_BrowseResult *result) {
|
|
|
- const UA_BrowseDescription *descr = &cp->browseDescription;
|
|
|
-
|
|
|
+/* Results for a single browsedescription. Sets all NodeIds for the RefTree. */
|
|
|
+static UA_StatusCode
|
|
|
+browseWithCp(UA_Server *server, UA_Session *session, ContinuationPoint *cp,
|
|
|
+ RefResult *rr, UA_Boolean *maxed) {
|
|
|
/* Is the browsedirection valid? */
|
|
|
- if(descr->browseDirection != UA_BROWSEDIRECTION_BOTH &&
|
|
|
- descr->browseDirection != UA_BROWSEDIRECTION_FORWARD &&
|
|
|
- descr->browseDirection != UA_BROWSEDIRECTION_INVERSE) {
|
|
|
- result->statusCode = UA_STATUSCODE_BADBROWSEDIRECTIONINVALID;
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- /* Is the reference type valid? */
|
|
|
- if(!UA_NodeId_isNull(&descr->referenceTypeId)) {
|
|
|
- const UA_Node *reftype = UA_Nodestore_getNode(server->nsCtx, &descr->referenceTypeId);
|
|
|
- if(!reftype) {
|
|
|
- result->statusCode = UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
|
|
|
- return true;
|
|
|
- }
|
|
|
+ if(cp->bd.browseDirection != UA_BROWSEDIRECTION_BOTH &&
|
|
|
+ cp->bd.browseDirection != UA_BROWSEDIRECTION_FORWARD &&
|
|
|
+ cp->bd.browseDirection != UA_BROWSEDIRECTION_INVERSE) {
|
|
|
+ return UA_STATUSCODE_BADBROWSEDIRECTIONINVALID;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Set the list of references to check */
|
|
|
+ UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
|
|
+ UA_NodeId *referenceTypes = NULL; /* NULL -> all reference types are relevant */
|
|
|
+ size_t referenceTypesSize = 0;
|
|
|
+ if(!UA_NodeId_isNull(&cp->bd.referenceTypeId)) {
|
|
|
+ const UA_Node *reftype = UA_Nodestore_getNode(server->nsCtx, &cp->bd.referenceTypeId);
|
|
|
+ if(!reftype)
|
|
|
+ return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
|
|
|
|
|
|
UA_Boolean isRef = (reftype->nodeClass == UA_NODECLASS_REFERENCETYPE);
|
|
|
UA_Nodestore_releaseNode(server->nsCtx, reftype);
|
|
|
|
|
|
- if(!isRef) {
|
|
|
- result->statusCode = UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
|
|
|
- return true;
|
|
|
+ if(!isRef)
|
|
|
+ return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
|
|
|
+
|
|
|
+ if(!UA_NodeId_isNull(&cp->bd.referenceTypeId)) {
|
|
|
+ if(cp->bd.includeSubtypes) {
|
|
|
+ retval = getTypesHierarchy(server->nsCtx, &cp->bd.referenceTypeId, 1,
|
|
|
+ &referenceTypes, &referenceTypesSize, true);
|
|
|
+ if(retval != UA_STATUSCODE_GOOD)
|
|
|
+ return retval;
|
|
|
+ UA_assert(referenceTypesSize > 0); /* The original referenceTypeId has been mirrored back */
|
|
|
+ } else {
|
|
|
+ referenceTypes = &cp->bd.referenceTypeId;
|
|
|
+ referenceTypesSize = 1;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const UA_Node *node = UA_Nodestore_getNode(server->nsCtx, &descr->nodeId);
|
|
|
- if(!node) {
|
|
|
- result->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN;
|
|
|
- return true;
|
|
|
+ for(; cp->n < cp->rt.size; cp->n++, cp->nk = 0, cp->nki = 0) {
|
|
|
+ /* Get the node */
|
|
|
+ const UA_Node *node = UA_Nodestore_getNode(server->nsCtx, &cp->rt.targets[cp->n]);
|
|
|
+ if(!node)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ /* Browse the references in the current node */
|
|
|
+ retval = browseNode(server, session, cp, rr, maxed, referenceTypesSize, referenceTypes, node);
|
|
|
+ UA_Nodestore_releaseNode(server->nsCtx, node);
|
|
|
+ if(retval != UA_STATUSCODE_GOOD)
|
|
|
+ break;
|
|
|
+ if(!cp->recursive || *maxed)
|
|
|
+ break;
|
|
|
}
|
|
|
|
|
|
- /* Browse the references */
|
|
|
- UA_Boolean done = browseReferences(server, node, cp, result);
|
|
|
- UA_Nodestore_releaseNode(server->nsCtx, node);
|
|
|
- return done;
|
|
|
+ if(referenceTypes != &cp->bd.referenceTypeId)
|
|
|
+ UA_Array_delete(referenceTypes, referenceTypesSize, &UA_TYPES[UA_TYPES_NODEID]);
|
|
|
+
|
|
|
+ return retval;
|
|
|
}
|
|
|
|
|
|
/* Start to browse with no previous cp */
|
|
|
void
|
|
|
-Operation_Browse(UA_Server *server, UA_Session *session, const UA_UInt32 *maxrefs,
|
|
|
+Operation_Browse(UA_Server *server, UA_Session *session, const struct BrowseOpts *bo,
|
|
|
const UA_BrowseDescription *descr, UA_BrowseResult *result) {
|
|
|
- /* Stack-allocate a temporary cp */
|
|
|
- UA_STACKARRAY(ContinuationPoint, cp, 1);
|
|
|
- memset(cp, 0, sizeof(ContinuationPoint));
|
|
|
- cp->maxReferences = *maxrefs;
|
|
|
- cp->browseDescription = *descr; /* Shallow copy. Deep-copy later if we persist the cp. */
|
|
|
+ /* How many references can we return at most? */
|
|
|
+ UA_UInt32 maxRefs = bo->maxReferences;
|
|
|
+ if(maxRefs == 0) {
|
|
|
+ if(server->config.maxReferencesPerNode != 0) {
|
|
|
+ maxRefs = server->config.maxReferencesPerNode;
|
|
|
+ } else {
|
|
|
+ maxRefs = UA_INT32_MAX;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if(server->config.maxReferencesPerNode != 0 &&
|
|
|
+ maxRefs > server->config.maxReferencesPerNode) {
|
|
|
+ maxRefs = server->config.maxReferencesPerNode;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- UA_Boolean done = browseWithContinuation(server, session, cp, result);
|
|
|
+ /* Create the results array */
|
|
|
+ RefResult rr;
|
|
|
+ result->statusCode = RefResult_init(&rr, maxRefs);
|
|
|
+ if(result->statusCode != UA_STATUSCODE_GOOD)
|
|
|
+ return;
|
|
|
|
|
|
- /* Exit early if done or an error occurred */
|
|
|
- if(done || result->statusCode != UA_STATUSCODE_GOOD)
|
|
|
+ ContinuationPoint cp;
|
|
|
+ ContinuationPoint_init(&cp, maxRefs, bo->recursive);
|
|
|
+ cp.bd = *descr; /* Deep-copy only when the cp is persisted in the session */
|
|
|
+
|
|
|
+ /* Add the initial node to the RefTree */
|
|
|
+ result->statusCode = RefTree_add(&cp.rt, &descr->nodeId);
|
|
|
+ if(result->statusCode != UA_STATUSCODE_GOOD) {
|
|
|
+ RefTree_clear(&cp.rt);
|
|
|
+ RefResult_clear(&rr);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Recurse to get all references */
|
|
|
+ UA_Boolean maxed = false;
|
|
|
+ result->statusCode = browseWithCp(server, session, &cp, &rr, &maxed);
|
|
|
+ if(result->statusCode != UA_STATUSCODE_GOOD) {
|
|
|
+ RefTree_clear(&cp.rt);
|
|
|
+ RefResult_clear(&rr);
|
|
|
return;
|
|
|
+ }
|
|
|
|
|
|
- /* Persist the new continuation point */
|
|
|
+ /* No results */
|
|
|
+ if(rr.size == 0) {
|
|
|
+ result->references = (UA_ReferenceDescription*)UA_EMPTY_ARRAY_SENTINEL;
|
|
|
+ RefTree_clear(&cp.rt);
|
|
|
+ UA_free(rr.descr);
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- ContinuationPoint *cp2 = NULL;
|
|
|
- UA_Guid *ident = NULL;
|
|
|
- UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
|
|
+ /* Move results to the output */
|
|
|
+ result->references = rr.descr;
|
|
|
+ result->referencesSize = rr.size;
|
|
|
|
|
|
- /* Enough space for the continuation point? */
|
|
|
- if(session->availableContinuationPoints <= 0) {
|
|
|
- retval = UA_STATUSCODE_BADNOCONTINUATIONPOINTS;
|
|
|
- goto cleanup;
|
|
|
+ /* Nothing left for BrowseNext */
|
|
|
+ if(!maxed) {
|
|
|
+ RefTree_clear(&cp.rt);
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- /* Allocate and fill the data structure */
|
|
|
- cp2 = (ContinuationPoint*)UA_malloc(sizeof(ContinuationPoint));
|
|
|
- if(!cp2) {
|
|
|
+ /* Create a new continuation point. */
|
|
|
+ ContinuationPoint *newCp = (ContinuationPoint*)UA_malloc(sizeof(ContinuationPoint));
|
|
|
+ UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
|
|
+ UA_ByteString tmp;
|
|
|
+ if(!newCp) {
|
|
|
retval = UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
goto cleanup;
|
|
|
}
|
|
|
- memset(cp2, 0, sizeof(ContinuationPoint));
|
|
|
- cp2->referenceKindIndex = cp->referenceKindIndex;
|
|
|
- cp2->targetIndex = cp->targetIndex;
|
|
|
- cp2->maxReferences = cp->maxReferences;
|
|
|
- retval = UA_BrowseDescription_copy(descr, &cp2->browseDescription);
|
|
|
- if(retval != UA_STATUSCODE_GOOD)
|
|
|
- goto cleanup;
|
|
|
+ *newCp = cp;
|
|
|
|
|
|
- /* Create a random bytestring via a Guid */
|
|
|
- ident = UA_Guid_new();
|
|
|
- if(!ident) {
|
|
|
- retval = UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
+ /* Make a deep copy of the BrowseDescription */
|
|
|
+ retval = UA_BrowseDescription_copy(descr, &newCp->bd);
|
|
|
+ if(retval != UA_STATUSCODE_GOOD)
|
|
|
goto cleanup;
|
|
|
- }
|
|
|
- *ident = UA_Guid_random();
|
|
|
- cp2->identifier.data = (UA_Byte*)ident;
|
|
|
- cp2->identifier.length = sizeof(UA_Guid);
|
|
|
|
|
|
/* Return the cp identifier */
|
|
|
- retval = UA_ByteString_copy(&cp2->identifier, &result->continuationPoint);
|
|
|
+ tmp.length = sizeof(UA_Guid);
|
|
|
+ tmp.data = (UA_Byte*)&newCp->identifier;
|
|
|
+ retval = UA_ByteString_copy(&tmp, &result->continuationPoint);
|
|
|
if(retval != UA_STATUSCODE_GOOD)
|
|
|
goto cleanup;
|
|
|
|
|
|
+ /* Remove the oldest continuation point if required */
|
|
|
+ if(session->availableContinuationPoints <= 0) {
|
|
|
+ struct ContinuationPoint **prev = &session->continuationPoints;
|
|
|
+ struct ContinuationPoint *cp2 = session->continuationPoints;
|
|
|
+ while(cp2 && cp2->next) {
|
|
|
+ prev = &cp2->next;
|
|
|
+ cp2 = cp2->next;
|
|
|
+ }
|
|
|
+ if(cp2) {
|
|
|
+ *prev = NULL;
|
|
|
+ ContinuationPoint_clear(cp2);
|
|
|
+ UA_free(cp2);
|
|
|
+ ++session->availableContinuationPoints;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/* Attach the cp to the session */
|
|
|
- cp2->next = session->continuationPoints;
|
|
|
- session->continuationPoints = cp2;
|
|
|
+ newCp->next = session->continuationPoints;
|
|
|
+ session->continuationPoints = newCp;
|
|
|
--session->availableContinuationPoints;
|
|
|
return;
|
|
|
|
|
|
cleanup:
|
|
|
- if(cp2) {
|
|
|
- UA_ByteString_deleteMembers(&cp2->identifier);
|
|
|
- UA_BrowseDescription_deleteMembers(&cp2->browseDescription);
|
|
|
- UA_free(cp2);
|
|
|
+ UA_BrowseResult_deleteMembers(result); /* Holds the content that was in rr before */
|
|
|
+ if(newCp) {
|
|
|
+ ContinuationPoint_clear(newCp);
|
|
|
+ UA_free(newCp);
|
|
|
}
|
|
|
- UA_BrowseResult_deleteMembers(result);
|
|
|
result->statusCode = retval;
|
|
|
}
|
|
|
|
|
@@ -358,10 +581,11 @@ void Service_Browse(UA_Server *server, UA_Session *session,
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- UA_UInt32 requestedMaxReferencesPerNode = request->requestedMaxReferencesPerNode;
|
|
|
+ struct BrowseOpts bo;
|
|
|
+ bo.maxReferences = request->requestedMaxReferencesPerNode;
|
|
|
+ bo.recursive = false;
|
|
|
response->responseHeader.serviceResult =
|
|
|
- UA_Server_processServiceOperations(server, session, (UA_ServiceOperation)Operation_Browse,
|
|
|
- &requestedMaxReferencesPerNode,
|
|
|
+ UA_Server_processServiceOperations(server, session, (UA_ServiceOperation)Operation_Browse, &bo,
|
|
|
&request->nodesToBrowseSize, &UA_TYPES[UA_TYPES_BROWSEDESCRIPTION],
|
|
|
&response->resultsSize, &UA_TYPES[UA_TYPES_BROWSERESULT]);
|
|
|
}
|
|
@@ -371,7 +595,10 @@ UA_Server_browse(UA_Server *server, UA_UInt32 maxReferences,
|
|
|
const UA_BrowseDescription *bd) {
|
|
|
UA_BrowseResult result;
|
|
|
UA_BrowseResult_init(&result);
|
|
|
- Operation_Browse(server, &server->adminSession, &maxReferences, bd, &result);
|
|
|
+ struct BrowseOpts bo;
|
|
|
+ bo.maxReferences = maxReferences;
|
|
|
+ bo.recursive = false;
|
|
|
+ Operation_Browse(server, &server->adminSession, &bo, bd, &result);
|
|
|
return result;
|
|
|
}
|
|
|
|
|
@@ -381,8 +608,11 @@ Operation_BrowseNext(UA_Server *server, UA_Session *session,
|
|
|
const UA_ByteString *continuationPoint, UA_BrowseResult *result) {
|
|
|
/* Find the continuation point */
|
|
|
ContinuationPoint **prev = &session->continuationPoints, *cp;
|
|
|
+ UA_ByteString identifier;
|
|
|
while((cp = *prev)) {
|
|
|
- if(UA_ByteString_equal(&cp->identifier, continuationPoint))
|
|
|
+ identifier.length = sizeof(UA_Guid);
|
|
|
+ identifier.data = (UA_Byte*)&cp->identifier;
|
|
|
+ if(UA_ByteString_equal(&identifier, continuationPoint))
|
|
|
break;
|
|
|
prev = &cp->next;
|
|
|
}
|
|
@@ -399,22 +629,47 @@ Operation_BrowseNext(UA_Server *server, UA_Session *session,
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- /* Continue browsing */
|
|
|
- UA_Boolean done = browseWithContinuation(server, session, cp, result);
|
|
|
+ /* Allocate the results array */
|
|
|
+ RefResult rr;
|
|
|
+ result->statusCode = RefResult_init(&rr, cp->maxReferences);
|
|
|
+ if(result->statusCode != UA_STATUSCODE_GOOD)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /* Recurse to get all references */
|
|
|
+ UA_Boolean maxed = false;
|
|
|
+ result->statusCode = browseWithCp(server, session, cp, &rr, &maxed);
|
|
|
+ if(result->statusCode != UA_STATUSCODE_GOOD)
|
|
|
+ goto removeCp;
|
|
|
+
|
|
|
+ /* No results */
|
|
|
+ if(rr.size == 0) {
|
|
|
+ result->references = (UA_ReferenceDescription*)UA_EMPTY_ARRAY_SENTINEL;
|
|
|
+ goto removeCp;
|
|
|
+ }
|
|
|
|
|
|
- if(done) {
|
|
|
- /* Remove the cp if there are no references left */
|
|
|
+ if(maxed) {
|
|
|
+ /* Keep the cp */
|
|
|
+ result->statusCode = UA_ByteString_copy(&identifier, &result->continuationPoint);
|
|
|
+ if(result->statusCode != UA_STATUSCODE_GOOD)
|
|
|
+ goto removeCp;
|
|
|
+ } else {
|
|
|
+ /* All done, remove the cp */
|
|
|
*prev = ContinuationPoint_clear(cp);
|
|
|
UA_free(cp);
|
|
|
++session->availableContinuationPoints;
|
|
|
- } else {
|
|
|
- /* Return the cp identifier */
|
|
|
- UA_StatusCode retval = UA_ByteString_copy(&cp->identifier, &result->continuationPoint);
|
|
|
- if(retval != UA_STATUSCODE_GOOD) {
|
|
|
- UA_BrowseResult_deleteMembers(result);
|
|
|
- result->statusCode = retval;
|
|
|
- }
|
|
|
}
|
|
|
+
|
|
|
+ /* Move results to the output */
|
|
|
+ result->references = rr.descr;
|
|
|
+ result->referencesSize = rr.size;
|
|
|
+ return;
|
|
|
+
|
|
|
+ removeCp:
|
|
|
+ *prev = cp->next;
|
|
|
+ ContinuationPoint_clear(cp);
|
|
|
+ UA_free(cp);
|
|
|
+ session->availableContinuationPoints++;
|
|
|
+ RefResult_clear(&rr);
|
|
|
}
|
|
|
|
|
|
void
|