Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 56 additions & 2 deletions backends/open62541/src/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,36 @@ AddNodeContext_init(AddNodeContext *ctx,
UA_Array_append((void**)&ctx->parentRefTypes,
&ctx->parentRefTypesSize, &hasChildExp,
&UA_TYPES[UA_TYPES_EXPANDEDNODEID]);

// Get all hierarchical ReferenceTypes (subtypes of HierarchicalReferences).
// Used by the topological sort to distinguish ordering dependencies
// from non-hierarchical cross-references.
UA_BrowseDescription bd2;
UA_BrowseDescription_init(&bd2);
bd2.browseDirection = UA_BROWSEDIRECTION_FORWARD;
bd2.referenceTypeId = UA_NS0ID(HASSUBTYPE);
bd2.nodeId = UA_NS0ID(HIERARCHICALREFERENCES);

UA_Server_browseRecursive(server, &bd2,
&ctx->hierarchicalRefTypesSize,
&ctx->hierarchicalRefTypes);

// Include HierarchicalReferences itself
UA_ExpandedNodeId hierRefExp;
UA_ExpandedNodeId_init(&hierRefExp);
hierRefExp.nodeId = UA_NS0ID(HIERARCHICALREFERENCES);
UA_Array_append((void**)&ctx->hierarchicalRefTypes,
&ctx->hierarchicalRefTypesSize, &hierRefExp,
&UA_TYPES[UA_TYPES_EXPANDEDNODEID]);
}

static void
AddNodeContext_clear(AddNodeContext *ctx) {
UA_NamespaceMapping_clear(&ctx->nsMapping);
UA_Array_delete(ctx->parentRefTypes, ctx->parentRefTypesSize,
&UA_TYPES[UA_TYPES_EXPANDEDNODEID]);
UA_Array_delete(ctx->hierarchicalRefTypes, ctx->hierarchicalRefTypesSize,
&UA_TYPES[UA_TYPES_EXPANDEDNODEID]);
}

static inline UA_Boolean isValTrue(const char *s) {
Expand Down Expand Up @@ -252,8 +275,24 @@ handleVariableNode(const NL_VariableNode *node, UA_NodeId *id,
}
}

// this case is only needed for the euromap83 comparison, think the nodeset
// is not valid
/* Some companion specs (e.g. IOLink, PNENC, PNRIO) declare
* ValueRank=1 (one-dimensional array) but provide a scalar default
* value in the XML (e.g. <String> instead of <ListOfString>). This is
* a known nodeset authoring bug -- ValueRank=1 is correct per the spec
* (confirmed by the base OPC UA nodeset's own StaticNumericNodeIdRange
* instance i=15963). Wrap the scalar into a one-element array so that
* the value passes the server's ValueRank compatibility check. */
if(attr.valueRank >= 1 &&
UA_Variant_isScalar(&attr.value) && attr.value.data != NULL) {
context->logger->log(context->logger->context,
NODESETLOADER_LOGLEVEL_WARNING,
"Node %s: ValueRank=%d but the XML value is "
"scalar. Auto-wrapping into a one-element array.",
buf, attr.valueRank);
attr.value.arrayLength = 1;
}

// Ensure ArrayDimensions is set when ValueRank=1
UA_UInt32 arrayDims;
if (attr.arrayDimensions == NULL && attr.valueRank == 1) {
attr.arrayDimensionsSize = 1;
Expand Down Expand Up @@ -497,6 +536,20 @@ addNodes(NodesetLoader *loader, AddNodeContext *anc) {
return true;
}

/* Callback for the topological sort: check whether a reference type is
* hierarchical (subtype of HierarchicalReferences i=33). Only hierarchical
* inverse references create parent-child ordering dependencies. */
static bool
isHierarchicalRef(void *userContext, const UA_NodeId *refType) {
AddNodeContext *ctx = (AddNodeContext *)userContext;
for(size_t i = 0; i < ctx->hierarchicalRefTypesSize; i++) {
if(UA_NodeId_equal(refType,
&ctx->hierarchicalRefTypes[i].nodeId))
return true;
}
return false;
}

bool
NodesetLoader_loadFile(struct UA_Server *server, const char *path,
NodesetLoader_ExtensionInterface *extensionHandling) {
Expand Down Expand Up @@ -524,6 +577,7 @@ NodesetLoader_loadFile(struct UA_Server *server, const char *path,
handler.file = path;
handler.extensionHandling = extensionHandling;
handler.nsMapping = &ctx.nsMapping; // Provide the pre-filled mapping
handler.isHierarchicalRef = isHierarchicalRef;

logger->log(logger->context, NODESETLOADER_LOGLEVEL_DEBUG,
"Start import nodeset: %s", path);
Expand Down
6 changes: 6 additions & 0 deletions backends/open62541/src/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ typedef struct {
// Inherited from HasChild.
size_t parentRefTypesSize;
UA_ExpandedNodeId *parentRefTypes;

// All hierarchical ReferenceTypes (subtypes of HierarchicalReferences).
// Used by the topological sort to distinguish ordering dependencies
// from non-hierarchical cross-references.
size_t hierarchicalRefTypesSize;
UA_ExpandedNodeId *hierarchicalRefTypes;
} AddNodeContext;

UA_NodeId
Expand Down
8 changes: 8 additions & 0 deletions include/NodesetLoader/NodesetLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,20 @@ typedef void (*NL_addNamespaceCallback)(void *userContext,
UA_String *localNamespaceUris,
UA_NamespaceMapping *nsMapping);

/* Callback to check if a reference type is hierarchical (subtype of
* HierarchicalReferences i=33). Used by the topological sort to determine
* ordering dependencies. If NULL, all inverse references are conservatively
* treated as ordering dependencies. */
typedef bool (*NL_isHierarchicalRefCallback)(void *userContext,
const UA_NodeId *refType);

typedef struct NL_FileContext {
void *userContext;
const char *file;
NL_addNamespaceCallback addNamespace;
NodesetLoader_ExtensionInterface *extensionHandling;
UA_NamespaceMapping *nsMapping;
NL_isHierarchicalRefCallback isHierarchicalRef;
} NL_FileContext;

struct NodesetLoader;
Expand Down
40 changes: 36 additions & 4 deletions src/Nodeset.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,50 @@ Nodeset_findByNodeId(Nodeset *nodeset, const UA_NodeId *key) {

static UA_NodeId hasTypeDef = {0, UA_NODEIDTYPE_NUMERIC, {40}};

/* Check whether a reference creates an ordering dependency for the
* topological sort. Only two kinds of references require the target
* to exist before the source node can be added:
*
* 1. Forward HasTypeDefinition -- the type node must exist first.
* 2. Inverse hierarchical references -- the parent node must exist first.
* For example, an Object with an inverse HasComponent to its parent
* cannot be added before the parent exists.
*
* Non-hierarchical references (like HasCondition or HasSubStateMachine)
* do NOT create ordering dependencies. If they are treated as such,
* they cause deadlocks when two nodes in the same nodeset reference
* each other via a non-hierarchical reference type.
*
* Whether a reference type is hierarchical is determined via the
* isHierarchicalRef callback in the NL_FileContext. The open62541
* backend sets this callback using UA_Server_browseRecursive to
* discover all subtypes of HierarchicalReferences (i=33) from the
* server's reference type hierarchy. If no callback is set, all
* inverse references are conservatively treated as dependencies. */
static bool
nodeRefsReady(NL_Node *node) {
nodeRefsReady(Nodeset *nodeset, NL_Node *node) {
NL_isHierarchicalRefCallback isHierCb = NULL;
void *userCtx = NULL;
if(nodeset->fc) {
isHierCb = nodeset->fc->isHierarchicalRef;
userCtx = nodeset->fc->userContext;
}
for(NL_Reference *ref = node->refs; ref != NULL; ref = ref->next) {
if(!ref->targetPtr)
continue;
if(ref->targetPtr->isDone)
continue;
if(UA_NodeId_equal(&hasTypeDef, &ref->refType)) {
/* Forward HasTypeDefinition: the type must exist first */
if(ref->isForward)
return false;
} else {
if(!ref->isForward)
} else if(!ref->isForward) {
/* Inverse reference: only hierarchical refs create a
* parent-child ordering dependency. */
bool isHier = true; /* Conservative default */
if(isHierCb)
isHier = isHierCb(userCtx, &ref->refType);
if(isHier)
return false;
}
}
Expand All @@ -166,7 +198,7 @@ Nodeset_sortNodeClass(Nodeset *nodeset, NL_NodeClass nodeClass) {
oldSize = nc->size;
for(size_t i = 0; i < nc->size; i++) {
NL_Node *node = nc->nodes[i];
if(!nodeRefsReady(node))
if(!nodeRefsReady(nodeset, node))
continue;
NodeContainer_add(&nodeset->sortedNodes, node);
NodeContainer_remove(nc, i);
Expand Down
Loading