From d900f569304282bf809df38f68d3d36db177fe38 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 19 Dec 2023 15:31:50 +0100 Subject: [PATCH] feat(util): Enable RelativePath parsing to use non-standard ReferenceTypes by their BrowseName --- include/open62541/util.h | 7 ++++++ src/util/ua_types_lex.c | 19 +++++++++++--- src/util/ua_types_lex.re | 19 +++++++++++--- src/util/ua_util.c | 49 +++++++++++++++++++++++++++++++------ src/util/ua_util_internal.h | 2 +- tests/check_types_parse.c | 30 +++++++++++++++++++++++ 6 files changed, 110 insertions(+), 16 deletions(-) diff --git a/include/open62541/util.h b/include/open62541/util.h index c40f8edd0d9..528a9e28a94 100644 --- a/include/open62541/util.h +++ b/include/open62541/util.h @@ -221,6 +221,13 @@ UA_readNumberWithBase(const UA_Byte *buf, size_t buflen, #ifdef UA_ENABLE_PARSING UA_EXPORT UA_StatusCode UA_RelativePath_parse(UA_RelativePath *rp, const UA_String str); + +/* Supports the lookup of non-standard ReferenceTypes by their browse name in + * the information model of a server. The first matching result in the + * ReferenceType hierarchy is used. */ +UA_EXPORT UA_StatusCode +UA_RelativePath_parseWithServer(UA_Server *server, UA_RelativePath *rp, + const UA_String str); #endif /** diff --git a/src/util/ua_types_lex.c b/src/util/ua_types_lex.c index b98642fd089..2deb3e290c2 100644 --- a/src/util/ua_types_lex.c +++ b/src/util/ua_types_lex.c @@ -700,7 +700,10 @@ parse_refpath_qn(UA_QualifiedName *qn, const char *pos, const char *end) { } static UA_StatusCode -parse_relativepath(UA_RelativePath *rp, const char *pos, const char *end) { +parse_relativepath(UA_Server *server, UA_RelativePath *rp, const UA_String str) { + const char *pos = (const char*)str.data; + const char *end = (const char*)(str.data + str.length); + LexContext context; memset(&context, 0, sizeof(LexContext)); const char *begin = NULL, *finish = NULL; @@ -802,7 +805,7 @@ parse_relativepath(UA_RelativePath *rp, const char *pos, const char *end) { } UA_QualifiedName refqn; res |= parse_refpath_qn(&refqn, begin, finish); - res |= lookupRefType(&refqn, ¤t.referenceTypeId); + res |= lookupRefType(server, &refqn, ¤t.referenceTypeId); UA_QualifiedName_clear(&refqn); goto reftype_target; } @@ -884,8 +887,16 @@ parse_relativepath(UA_RelativePath *rp, const char *pos, const char *end) { UA_StatusCode UA_RelativePath_parse(UA_RelativePath *rp, const UA_String str) { - UA_StatusCode res = - parse_relativepath(rp, (const char*)str.data, (const char*)str.data+str.length); + UA_StatusCode res = parse_relativepath(NULL, rp, str); + if(res != UA_STATUSCODE_GOOD) + UA_RelativePath_clear(rp); + return res; +} + +UA_StatusCode +UA_RelativePath_parseWithServer(UA_Server *server, UA_RelativePath *rp, + const UA_String str) { + UA_StatusCode res = parse_relativepath(server, rp, str); if(res != UA_STATUSCODE_GOOD) UA_RelativePath_clear(rp); return res; diff --git a/src/util/ua_types_lex.re b/src/util/ua_types_lex.re index 311c2f94607..65a38d0e4e0 100644 --- a/src/util/ua_types_lex.re +++ b/src/util/ua_types_lex.re @@ -307,7 +307,10 @@ parse_refpath_qn(UA_QualifiedName *qn, const char *pos, const char *end) { } static UA_StatusCode -parse_relativepath(UA_RelativePath *rp, const char *pos, const char *end) { +parse_relativepath(UA_Server *server, UA_RelativePath *rp, const UA_String str) { + const char *pos = (const char*)str.data; + const char *end = (const char*)(str.data + str.length); + LexContext context; memset(&context, 0, sizeof(LexContext)); const char *begin = NULL, *finish = NULL; @@ -344,7 +347,7 @@ parse_relativepath(UA_RelativePath *rp, const char *pos, const char *end) { } UA_QualifiedName refqn; res |= parse_refpath_qn(&refqn, begin, finish); - res |= lookupRefType(&refqn, ¤t.referenceTypeId); + res |= lookupRefType(server, &refqn, ¤t.referenceTypeId); UA_QualifiedName_clear(&refqn); goto reftype_target; } @@ -381,8 +384,16 @@ parse_relativepath(UA_RelativePath *rp, const char *pos, const char *end) { UA_StatusCode UA_RelativePath_parse(UA_RelativePath *rp, const UA_String str) { - UA_StatusCode res = - parse_relativepath(rp, (const char*)str.data, (const char*)str.data+str.length); + UA_StatusCode res = parse_relativepath(NULL, rp, str); + if(res != UA_STATUSCODE_GOOD) + UA_RelativePath_clear(rp); + return res; +} + +UA_StatusCode +UA_RelativePath_parseWithServer(UA_Server *server, UA_RelativePath *rp, + const UA_String str) { + UA_StatusCode res = parse_relativepath(server, rp, str); if(res != UA_STATUSCODE_GOOD) UA_RelativePath_clear(rp); return res; diff --git a/src/util/ua_util.c b/src/util/ua_util.c index b73860d2469..09e63defe0e 100644 --- a/src/util/ua_util.c +++ b/src/util/ua_util.c @@ -12,6 +12,7 @@ #define UA_INLINABLE_IMPL 1 #include +#include #include #include "ua_util_internal.h" @@ -538,15 +539,49 @@ static const RefTypeName knownRefTypes[KNOWNREFTYPES] = { }; UA_StatusCode -lookupRefType(UA_QualifiedName *qn, UA_NodeId *outRefTypeId) { - if(qn->namespaceIndex != 0) - return UA_STATUSCODE_BADNOTFOUND; - for(size_t i = 0; i < KNOWNREFTYPES; i++) { - if(UA_String_equal(&qn->name, &knownRefTypes[i].browseName)) { - *outRefTypeId = UA_NODEID_NUMERIC(0, knownRefTypes[i].identifier); - return UA_STATUSCODE_GOOD; +lookupRefType(UA_Server *server, UA_QualifiedName *qn, UA_NodeId *outRefTypeId) { + /* Check well-known ReferenceTypes first */ + if(qn->namespaceIndex == 0) { + for(size_t i = 0; i < KNOWNREFTYPES; i++) { + if(UA_String_equal(&qn->name, &knownRefTypes[i].browseName)) { + *outRefTypeId = UA_NODEID_NUMERIC(0, knownRefTypes[i].identifier); + return UA_STATUSCODE_GOOD; + } } } + + /* Browse the information model. Return the first results if the browse name + * in the hierarchy is ambiguous. */ + if(server) { + UA_BrowseDescription bd; + UA_BrowseDescription_init(&bd); + bd.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_REFERENCES); + bd.browseDirection = UA_BROWSEDIRECTION_FORWARD; + bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE); + bd.nodeClassMask = UA_NODECLASS_REFERENCETYPE; + + size_t resultsSize = 0; + UA_ExpandedNodeId *results = NULL; + UA_StatusCode res = + UA_Server_browseRecursive(server, &bd, &resultsSize, &results); + if(res != UA_STATUSCODE_GOOD) + return res; + for(size_t i = 0; i < resultsSize; i++) { + UA_QualifiedName bn; + UA_Server_readBrowseName(server, results[i].nodeId, &bn); + if(UA_QualifiedName_equal(qn, &bn)) { + UA_QualifiedName_clear(&bn); + *outRefTypeId = results[i].nodeId; + UA_NodeId_clear(&results[i].nodeId); + UA_Array_delete(results, resultsSize, &UA_TYPES[UA_TYPES_NODEID]); + return UA_STATUSCODE_GOOD; + } + UA_QualifiedName_clear(&bn); + } + + UA_Array_delete(results, resultsSize, &UA_TYPES[UA_TYPES_NODEID]); + } + return UA_STATUSCODE_BADNOTFOUND; } diff --git a/src/util/ua_util_internal.h b/src/util/ua_util_internal.h index 6bcb8700361..39fc60f64df 100644 --- a/src/util/ua_util_internal.h +++ b/src/util/ua_util_internal.h @@ -57,7 +57,7 @@ typedef UA_StatusCode status; /* Well-known ReferenceTypes */ UA_StatusCode -lookupRefType(UA_QualifiedName *qn, UA_NodeId *outRefTypeId); +lookupRefType(UA_Server *server, UA_QualifiedName *qn, UA_NodeId *outRefTypeId); /** * Error checking macros diff --git a/tests/check_types_parse.c b/tests/check_types_parse.c index 3a72b65b0df..6527a8b1257 100644 --- a/tests/check_types_parse.c +++ b/tests/check_types_parse.c @@ -4,6 +4,7 @@ #include #include +#include "test_helpers.h" #include "open62541/util.h" #include @@ -209,6 +210,34 @@ START_TEST(parseRelativePath) { UA_RelativePath_clear(&rp); } END_TEST +START_TEST(parseRelativePathWithServer) { + UA_Server *server = UA_Server_newForUnitTest(); + + /* Add a custom non-hierarchical reference type */ + UA_NodeId refTypeId; + UA_ReferenceTypeAttributes refattr = UA_ReferenceTypeAttributes_default; + refattr.displayName = UA_LOCALIZEDTEXT(NULL, "MyRef"); + refattr.inverseName = UA_LOCALIZEDTEXT(NULL, "RefMy"); + UA_QualifiedName browseName = UA_QUALIFIEDNAME(1, "MyRef"); + UA_StatusCode res = + UA_Server_addReferenceTypeNode(server, UA_NODEID_NULL, + UA_NODEID_NUMERIC(0, UA_NS0ID_NONHIERARCHICALREFERENCES), + UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), + browseName, refattr, NULL, &refTypeId); + ck_assert_int_eq(res, UA_STATUSCODE_GOOD); + + /* Use the browse name in the path string. Expect the NodeId of the reference type */ + UA_RelativePath rp; + res = UA_RelativePath_parseWithServer(server, &rp, + UA_STRING("/1:Boiler<1:MyRef>1:HeatSensor")); + ck_assert_int_eq(res, UA_STATUSCODE_GOOD); + ck_assert_uint_eq(rp.elementsSize, 2); + ck_assert(UA_NodeId_equal(&rp.elements[1].referenceTypeId, &refTypeId)); + UA_RelativePath_clear(&rp); + + UA_Server_delete(server); +} END_TEST + int main(void) { Suite *s = suite_create("Test Builtin Type Parsing"); TCase *tc = tcase_create("test cases"); @@ -226,6 +255,7 @@ int main(void) { tcase_add_test(tc, parseExpandedNodeIdIntegerFailNSU); tcase_add_test(tc, parseExpandedNodeIdIntegerFailNSU2); tcase_add_test(tc, parseRelativePath); + tcase_add_test(tc, parseRelativePathWithServer); suite_add_tcase(s, tc); SRunner *sr = srunner_create(s);