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
53 changes: 32 additions & 21 deletions backends/open62541/src/DataTypeImporter.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,35 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2020 (c) Matthias Konnerth
* Copyright 2026 (c) SICK AG (author: Joerg Fischer)
*/

#include "internal.h"

#include <assert.h>

static UA_NodeId
getBinaryEncodingId(const NL_DataTypeNode *node) {
UA_NodeId encodingRefType = UA_NODEID_NUMERIC(0, 38);
for(NL_Reference *ref = node->refs; ref; ref = ref->next) {
getEncodingId(const NL_DataTypeNode *node, const UA_String *encodingName) {
const UA_NodeId encodingRefType = UA_NODEID_NUMERIC(0, 38);

for(const NL_Reference *ref = node->refs; ref; ref = ref->next) {
if(!UA_NodeId_equal(&encodingRefType, &ref->refType))
continue;
if(!ref->targetPtr)
continue;
UA_String binaryStr = UA_STRING("Default Binary");
if(!UA_String_equal(&ref->targetPtr->browseName.name, &binaryStr))
if(!UA_String_equal(&ref->targetPtr->browseName.name, encodingName))
continue;
UA_NodeId idCopy;
UA_NodeId_copy(&ref->target, &idCopy);
return idCopy;
}
return UA_NODEID_NULL;
}

static UA_NodeId
getXmlEncodingId(const NL_DataTypeNode *node) {
UA_NodeId encodingRefType = UA_NODEID_NUMERIC(0, 38);
for(NL_Reference *ref = node->refs; ref; ref = ref->next) {
for(const NL_Reference *ref = node->inverseRefs; ref; ref = ref->next) {
if(!UA_NodeId_equal(&encodingRefType, &ref->refType))
continue;
if(!ref->targetPtr)
continue;
UA_String xmlStr = UA_STRING("Default XML");
if(!UA_String_equal(&ref->targetPtr->browseName.name, &xmlStr))
if(!UA_String_equal(&ref->targetPtr->browseName.name, encodingName))
continue;
UA_NodeId idCopy;
UA_NodeId_copy(&ref->target, &idCopy);
Expand All @@ -45,6 +40,18 @@ getXmlEncodingId(const NL_DataTypeNode *node) {
return UA_NODEID_NULL;
}

static UA_NodeId
getBinaryEncodingId(const NL_DataTypeNode *node) {
const UA_String binaryStr = UA_STRING("Default Binary");
return getEncodingId(node, &binaryStr);
}

static UA_NodeId
getXmlEncodingId(const NL_DataTypeNode *node) {
const UA_String xmlStr = UA_STRING("Default XML");
return getEncodingId(node, &xmlStr);
}

static const UA_DataType *
getDataType(AddNodeContext *ctx, const UA_NodeId *id) {
return UA_Server_findDataType(ctx->server, id);
Expand All @@ -62,11 +69,16 @@ addDataTypeMembers(AddNodeContext *ctx, UA_DataType *type,
if(parentType)
memberSize += parentType->membersSize;

// Allocate the members
type->members = (UA_DataTypeMember *)
calloc(memberSize, sizeof(UA_DataTypeMember));
if(!type->members)
return UA_STATUSCODE_BADOUTOFMEMORY;
// Allocate the members (memberSize may be 0 for empty structs)
if(memberSize > 0) {
type->members = (UA_DataTypeMember *)
calloc(memberSize, sizeof(UA_DataTypeMember));
if(!type->members)
return UA_STATUSCODE_BADOUTOFMEMORY;
}
else {
type->members = NULL;
}
type->membersSize = (unsigned char)memberSize;

// Copy over members from the parent
Expand Down Expand Up @@ -239,14 +251,13 @@ addCustomDataType(AddNodeContext *ctx, const NL_DataTypeNode *node) {
UA_StatusCode res;
UA_ExtensionObject eo;
UA_NodeId parent = getParentId(ctx, (const NL_Node*)node, NULL);

if(node->definition &&
(node->definition->isEnum ||
node->definition->isOptionSet)) {
// Enum and OptionSet
res = EnumDataType_init(ctx, &type, node, &parent);
} else if(node->definition && node->definition->fieldCnt > 0) {
// Structure and Union
} else if(node->definition) {
// Structure and Union (including empty structs with zero fields)
res = StructureDataType_init(ctx, &type, node, &parent);
} else {
// Opaque subtype
Expand Down
39 changes: 39 additions & 0 deletions backends/open62541/src/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Copyright 2020 (c) Matthias Konnerth
* copyright 2021 (c) jan murzyn
* copyright 2025 (c) fraunhofer iosb (author: julius pfrommer)
* Copyright 2026 (c) SICK AG (author: Joerg Fischer)
*/

#include <open62541/server.h>
Expand Down Expand Up @@ -479,6 +480,35 @@ addAllRefs(AddNodeContext *context, NL_Node *node) {
return true;
}

static bool
addInverseRefForNode(void *context, NL_Node *node) {
(void)context;
for(NL_Reference *ref = node->refs; ref != NULL; ref = ref->next) {
if(ref->isForward)
continue;
if(!ref->targetPtr)
continue;

NL_Reference *inverseRef = (NL_Reference *)calloc(1, sizeof(NL_Reference));
if(!inverseRef)
return false;

inverseRef->isForward = true;
inverseRef->targetPtr = node;
if(UA_NodeId_copy(&ref->refType, &inverseRef->refType) != UA_STATUSCODE_GOOD ||
UA_NodeId_copy(&node->id, &inverseRef->target) != UA_STATUSCODE_GOOD) {
UA_NodeId_clear(&inverseRef->refType);
UA_NodeId_clear(&inverseRef->target);
free(inverseRef);
return false;
}

inverseRef->next = ref->targetPtr->inverseRefs;
ref->targetPtr->inverseRefs = inverseRef;
}
return true;
}

static bool
addNodes(NodesetLoader *loader, AddNodeContext *anc) {

Expand All @@ -497,6 +527,12 @@ addNodes(NodesetLoader *loader, AddNodeContext *anc) {
return true;
}

static bool
addInverseReferences(NodesetLoader *loader) {
return NodesetLoader_forEachNode(loader, NULL,
(NodesetLoader_forEachNode_Func)addInverseRefForNode);
}

bool
NodesetLoader_loadFile(struct UA_Server *server, const char *path,
NodesetLoader_ExtensionInterface *extensionHandling) {
Expand Down Expand Up @@ -528,8 +564,11 @@ NodesetLoader_loadFile(struct UA_Server *server, const char *path,
logger->log(logger->context, NODESETLOADER_LOGLEVEL_DEBUG,
"Start import nodeset: %s", path);
bool status = NodesetLoader_importFile(loader, &handler);

if(status)
status = NodesetLoader_sort(loader);
if(status)
status = addInverseReferences(loader);
if(status)
status = addNodes(loader, &ctx);
if(!status)
Expand Down
7 changes: 7 additions & 0 deletions backends/open62541/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ add_test(NAME issue_246_2_Test
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND issue_246_2 ${CMAKE_CURRENT_SOURCE_DIR}/issue_246_2.xml)

add_executable(loading_order_fix loading_order_fix.c)
target_include_directories(loading_order_fix PRIVATE ${CHECK_INCLUDE_DIR})
target_link_libraries(loading_order_fix PRIVATE NodesetLoader open62541::open62541 ${CHECK_LIBRARIES} ${CHECK_LIBRARIES} ${PTHREAD_LIB})
add_test(NAME loading_order_fix_Test
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND loading_order_fix ${CMAKE_CURRENT_SOURCE_DIR}/loading_order_fix.xml)

add_executable(issue_266 issue_266_testdata.c)
target_include_directories(issue_266 PRIVATE ${CHECK_INCLUDE_DIR})
target_link_libraries(issue_266 PRIVATE NodesetLoader open62541::open62541 ${CHECK_LIBRARIES} ${CHECK_LIBRARIES} ${PTHREAD_LIB})
Expand Down
16 changes: 16 additions & 0 deletions backends/open62541/tests/dataTypeImport/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,19 @@ target_link_libraries(bytestring PRIVATE NodesetLoader open62541::open62541 ${CH
add_test(NAME bytestring_Test
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND bytestring ${CMAKE_CURRENT_SOURCE_DIR}/bytestring.xml)

# Test for inverse HasEncoding references (regression test for issue where
# encoding references stored as inverse in NodeSet XML were not discovered)
add_executable(compareInverseHasEncoding compareInverseHasEncoding.c)
target_include_directories(compareInverseHasEncoding PRIVATE ${CHECK_INCLUDE_DIR})
target_link_libraries(compareInverseHasEncoding PRIVATE NodesetLoader open62541::open62541 ${CHECK_LIBRARIES} ${PTHREAD_LIB})
add_test(NAME compareInverseHasEncoding_Test
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND compareInverseHasEncoding ${CMAKE_CURRENT_SOURCE_DIR}/inverseHasEncoding.xml)

add_executable(emptyStructs emptyStructs.c)
target_include_directories(emptyStructs PRIVATE ${CHECK_INCLUDE_DIR})
target_link_libraries(emptyStructs PRIVATE NodesetLoader open62541::open62541 ${CHECK_LIBRARIES} ${PTHREAD_LIB})
add_test(NAME emptyStructs_Test
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND emptyStructs ${CMAKE_CURRENT_SOURCE_DIR}/emptyStructs.xml)
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/types.h>

#include <check.h>
#include <stdio.h>

#include <NodesetLoader/backendOpen62541.h>

static UA_Server *server;
static char *nodesetPath = NULL;
static UA_UInt16 nsIdx = 0;

static void setup(void)
{
server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefault(config);

// First, register the namespace URI BEFORE loading the nodeset
nsIdx = UA_Server_addNamespace(server,
"http://open62541.org/test/inverseEncoding/");
ck_assert_uint_lt(0, nsIdx);

ck_assert(NodesetLoader_loadFile(server, nodesetPath, NULL));
}

static void teardown(void) { UA_Server_delete(server); }

START_TEST(forwardHasEncodingIds)
{

/* Namespace index 1 is used for custom namespace in the nodeset */
const UA_NodeId forwardOnlyTypeId = UA_NODEID_NUMERIC(nsIdx, 5001);
const UA_NodeId binaryId = UA_NODEID_NUMERIC(nsIdx, 5002);

const UA_DataType *customStructType =
UA_Server_findDataType(server, &forwardOnlyTypeId);
ck_assert_msg(customStructType != NULL,
"CustomStructType (ns=x;i=5001) should exist after import");

ck_assert(UA_NodeId_equal(&customStructType->binaryEncodingId, &binaryId));
}
END_TEST

START_TEST(inverseHasEncodingIds)
{
const UA_NodeId inverseOnlyTypeId = UA_NODEID_NUMERIC(nsIdx, 5010);
const UA_NodeId inverseOnlyBinaryId = UA_NODEID_NUMERIC(nsIdx, 5011);

const UA_DataType *inverseOnlyType =
UA_Server_findDataType(server, &inverseOnlyTypeId);
ck_assert_msg(inverseOnlyType != NULL,
"InverseOnlyStructType (ns=x;i=5010) should exist after import");

ck_assert(UA_NodeId_equal(&inverseOnlyType->binaryEncodingId,
&inverseOnlyBinaryId));
}
END_TEST

static Suite *testSuite_InverseHasEncoding(void)
{
Suite *s = suite_create("InverseHasEncoding Test");
TCase *tc_inverse = tcase_create("inverse encoding");
tcase_add_unchecked_fixture(tc_inverse, setup, teardown);
tcase_add_test(tc_inverse, forwardHasEncodingIds);
tcase_add_test(tc_inverse, inverseHasEncodingIds);
suite_add_tcase(s, tc_inverse);
return s;
}

int main(int argc, char *argv[])
{
if (!(argc > 1))
{
fprintf(stderr, "Usage: %s <path_to_inverseHasEncoding.xml>\n",
argv[0]);
return EXIT_FAILURE;
}
nodesetPath = argv[1];

Suite *s = testSuite_InverseHasEncoding();
SRunner *sr = srunner_create(s);
srunner_set_fork_status(sr, CK_NOFORK);
srunner_run_all(sr, CK_NORMAL);
int number_failed = srunner_ntests_failed(sr);
srunner_free(sr);
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
90 changes: 90 additions & 0 deletions backends/open62541/tests/dataTypeImport/emptyStructs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/types.h>

#include <check.h>
#include <stdio.h>

#include <NodesetLoader/backendOpen62541.h>

static UA_Server *server;
static char *nodesetPath = NULL;
static UA_UInt16 nsIdx = 0;

static void setup(void)
{
server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefault(config);

nsIdx = UA_Server_addNamespace(server,
"http://open62541.org/test/emptyStructs/");
ck_assert_uint_lt(0, nsIdx);

ck_assert(NodesetLoader_loadFile(server, nodesetPath, NULL));
}

static void teardown(void)
{
UA_Server_delete(server);
}

START_TEST(emptyStructsImport)
{
const UA_NodeId emptyStructAId = UA_NODEID_NUMERIC(nsIdx, 6001);
const UA_NodeId emptyStructBId = UA_NODEID_NUMERIC(nsIdx, 6002);
const UA_NodeId nonEmptyStructCId = UA_NODEID_NUMERIC(nsIdx, 6003);

const UA_DataType *typeA = UA_Server_findDataType(server, &emptyStructAId);
const UA_DataType *typeB = UA_Server_findDataType(server, &emptyStructBId);
const UA_DataType *typeC = UA_Server_findDataType(server,
&nonEmptyStructCId);

ck_assert(typeA != NULL);
ck_assert(typeB != NULL);
ck_assert(typeC != NULL);
ck_assert_ptr_ne(typeA, typeB);
ck_assert_ptr_ne(typeA, typeC);
ck_assert_ptr_ne(typeB, typeC);

ck_assert_uint_eq(UA_DATATYPEKIND_STRUCTURE, typeA->typeKind);
ck_assert_uint_eq(0, typeA->membersSize);
ck_assert_uint_eq(UA_DATATYPEKIND_STRUCTURE, typeB->typeKind);
ck_assert_uint_eq(0, typeB->membersSize);
ck_assert_uint_eq(UA_DATATYPEKIND_STRUCTURE, typeC->typeKind);
ck_assert_uint_eq(1, typeC->membersSize);
ck_assert_ptr_ne(NULL, typeC->members);
}
END_TEST

static Suite *testSuite_EmptyStructs(void)
{
Suite *s = suite_create("EmptyStructs Test");
TCase *tc_empty = tcase_create("empty structs");
tcase_add_unchecked_fixture(tc_empty, setup, teardown);
tcase_add_test(tc_empty, emptyStructsImport);
suite_add_tcase(s, tc_empty);
return s;
}

int main(int argc, char *argv[])
{
if (!(argc > 1)) {
fprintf(stderr, "Usage: %s <path_to_emptyStructs.xml>\n", argv[0]);
return EXIT_FAILURE;
}
nodesetPath = argv[1];

Suite *s = testSuite_EmptyStructs();
SRunner *sr = srunner_create(s);
srunner_set_fork_status(sr, CK_NOFORK);
srunner_run_all(sr, CK_NORMAL);
int number_failed = srunner_ntests_failed(sr);
srunner_free(sr);
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}

Loading
Loading