Files
open62541/tests/server/check_server_utils.c
2026-07-18 09:45:45 +02:00

796 lines
31 KiB
C

/* 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 "server/ua_server_internal.h"
#include "test_helpers.h"
#include <check.h>
#include <stdio.h>
#include <stdlib.h>
static UA_Server *server = NULL;
static void setup(void) {
server = UA_Server_newForUnitTest();
ck_assert(server != NULL);
}
static void teardown(void) {
UA_Server_delete(server);
}
static UA_StatusCode
findNodeVersion(const UA_NodeId nodeId, UA_NodeId *outPropertyId) {
lockServer(server);
const UA_Node *node = UA_NODESTORE_GET(server, &nodeId);
ck_assert_ptr_ne(node, NULL);
UA_StatusCode res =
getNodeVersionProperty(server, &node->head, outPropertyId);
UA_NODESTORE_RELEASE(server, node);
unlockServer(server);
return res;
}
/* --- UA_Server_getDataTypes --- */
START_TEST(getDataTypes) {
const UA_DataTypeArray *dta = UA_Server_getDataTypes(server);
/* May be NULL if no custom types; that's fine. The function itself
* should not crash. */
(void)dta;
} END_TEST
/* --- UA_Server_findDataType --- */
START_TEST(findBuiltinDataType) {
/* Look up Boolean (ns=0;i=1) */
UA_NodeId boolId = UA_NODEID_NUMERIC(0, UA_NS0ID_BOOLEAN);
const UA_DataType *dt = UA_Server_findDataType(server, &boolId);
if(dt)
ck_assert_uint_eq(dt->memSize, sizeof(UA_Boolean));
} END_TEST
START_TEST(findNonExistentDataType) {
UA_NodeId fakeId = UA_NODEID_NUMERIC(0, 99999);
const UA_DataType *dt = UA_Server_findDataType(server, &fakeId);
ck_assert_ptr_eq(dt, NULL);
} END_TEST
/* --- UA_Server_addDataType --- */
START_TEST(addDataTypeBasic) {
/* Create a simple test DataType */
UA_DataType testType;
memset(&testType, 0, sizeof(UA_DataType));
testType.typeName = "TestType";
testType.typeId = UA_NODEID_NUMERIC(1, 50000);
testType.binaryEncodingId = UA_NODEID_NUMERIC(1, 50001);
testType.memSize = 8;
testType.typeKind = UA_DATATYPEKIND_STRUCTURE;
testType.pointerFree = true;
testType.overlayable = false;
testType.membersSize = 0;
/* Also add the type node in the address space */
UA_DataTypeAttributes dtattr = UA_DataTypeAttributes_default;
dtattr.displayName = UA_LOCALIZEDTEXT("en-US", "TestType");
UA_StatusCode retval = UA_Server_addDataTypeNode(server,
testType.typeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_STRUCTURE),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
UA_QUALIFIEDNAME(1, "TestType"),
dtattr, NULL, NULL);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
retval = UA_Server_addDataType(server,
UA_NODEID_NUMERIC(0, UA_NS0ID_STRUCTURE), &testType);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Find it */
const UA_DataType *found = UA_Server_findDataType(server, &testType.typeId);
ck_assert_ptr_ne(found, NULL);
} END_TEST
/* --- Server browsing utilities --- */
START_TEST(browseObjectsFolder) {
UA_BrowseDescription bd;
UA_BrowseDescription_init(&bd);
bd.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
bd.browseDirection = UA_BROWSEDIRECTION_FORWARD;
bd.resultMask = UA_BROWSERESULTMASK_ALL;
UA_BrowseResult br = UA_Server_browse(server, 0, &bd);
ck_assert_uint_eq(br.statusCode, UA_STATUSCODE_GOOD);
ck_assert_uint_gt(br.referencesSize, 0);
UA_BrowseResult_clear(&br);
} END_TEST
START_TEST(browseInverse) {
UA_BrowseDescription bd;
UA_BrowseDescription_init(&bd);
bd.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);
bd.browseDirection = UA_BROWSEDIRECTION_INVERSE;
bd.resultMask = UA_BROWSERESULTMASK_ALL;
UA_BrowseResult br = UA_Server_browse(server, 0, &bd);
ck_assert_uint_eq(br.statusCode, UA_STATUSCODE_GOOD);
/* Server object should have at least one inverse reference (from ObjectsFolder) */
ck_assert_uint_gt(br.referencesSize, 0);
UA_BrowseResult_clear(&br);
} END_TEST
START_TEST(browseBothDirections) {
UA_BrowseDescription bd;
UA_BrowseDescription_init(&bd);
bd.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);
bd.browseDirection = UA_BROWSEDIRECTION_BOTH;
bd.resultMask = UA_BROWSERESULTMASK_ALL;
UA_BrowseResult br = UA_Server_browse(server, 0, &bd);
ck_assert_uint_eq(br.statusCode, UA_STATUSCODE_GOOD);
ck_assert_uint_gt(br.referencesSize, 0);
UA_BrowseResult_clear(&br);
} END_TEST
START_TEST(browseNonExistentNode) {
UA_BrowseDescription bd;
UA_BrowseDescription_init(&bd);
bd.nodeId = UA_NODEID_NUMERIC(0, 99999);
bd.browseDirection = UA_BROWSEDIRECTION_FORWARD;
bd.resultMask = UA_BROWSERESULTMASK_ALL;
UA_BrowseResult br = UA_Server_browse(server, 0, &bd);
ck_assert_uint_ne(br.statusCode, UA_STATUSCODE_GOOD);
UA_BrowseResult_clear(&br);
} END_TEST
/* --- TranslateBrowsePath --- */
START_TEST(translateBrowsePathToServer) {
/* ObjectsFolder -> Server */
UA_RelativePathElement rpe;
UA_RelativePathElement_init(&rpe);
rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
rpe.isInverse = false;
rpe.includeSubtypes = true;
rpe.targetName = UA_QUALIFIEDNAME(0, "Server");
UA_BrowsePath bp;
UA_BrowsePath_init(&bp);
bp.startingNode = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
bp.relativePath.elementsSize = 1;
bp.relativePath.elements = &rpe;
UA_BrowsePathResult bpr = UA_Server_translateBrowsePathToNodeIds(server, &bp);
ck_assert_uint_eq(bpr.statusCode, UA_STATUSCODE_GOOD);
ck_assert_uint_gt(bpr.targetsSize, 0);
UA_BrowsePathResult_clear(&bpr);
} END_TEST
START_TEST(translateBrowsePathMultiHop) {
/* Root -> Objects -> Server (two hops) */
UA_RelativePathElement rpe[2];
UA_RelativePathElement_init(&rpe[0]);
rpe[0].referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
rpe[0].isInverse = false;
rpe[0].includeSubtypes = true;
rpe[0].targetName = UA_QUALIFIEDNAME(0, "Objects");
UA_RelativePathElement_init(&rpe[1]);
rpe[1].referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
rpe[1].isInverse = false;
rpe[1].includeSubtypes = true;
rpe[1].targetName = UA_QUALIFIEDNAME(0, "Server");
UA_BrowsePath bp;
UA_BrowsePath_init(&bp);
bp.startingNode = UA_NODEID_NUMERIC(0, UA_NS0ID_ROOTFOLDER);
bp.relativePath.elementsSize = 2;
bp.relativePath.elements = rpe;
UA_BrowsePathResult bpr = UA_Server_translateBrowsePathToNodeIds(server, &bp);
ck_assert_uint_eq(bpr.statusCode, UA_STATUSCODE_GOOD);
ck_assert_uint_gt(bpr.targetsSize, 0);
UA_BrowsePathResult_clear(&bpr);
} END_TEST
START_TEST(translateBrowsePathNotFound) {
UA_RelativePathElement rpe;
UA_RelativePathElement_init(&rpe);
rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
rpe.isInverse = false;
rpe.includeSubtypes = true;
rpe.targetName = UA_QUALIFIEDNAME(0, "NonExistentNode");
UA_BrowsePath bp;
UA_BrowsePath_init(&bp);
bp.startingNode = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
bp.relativePath.elementsSize = 1;
bp.relativePath.elements = &rpe;
UA_BrowsePathResult bpr = UA_Server_translateBrowsePathToNodeIds(server, &bp);
ck_assert_uint_ne(bpr.statusCode, UA_STATUSCODE_GOOD);
UA_BrowsePathResult_clear(&bpr);
} END_TEST
/* --- Node management tests --- */
START_TEST(addAndDeleteVariableNode) {
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_Int32 myVal = 42;
UA_Variant_setScalar(&attr.value, &myVal, &UA_TYPES[UA_TYPES_INT32]);
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_NodeId myId = UA_NODEID_STRING(1, "utils.test.var");
UA_StatusCode retval = UA_Server_addVariableNode(server, myId,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "UtilsTestVar"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, NULL);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Delete */
retval = UA_Server_deleteNode(server, myId, true);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Verify deletion */
UA_Variant readVal;
retval = UA_Server_readValue(server, myId, &readVal);
ck_assert_uint_ne(retval, UA_STATUSCODE_GOOD);
} END_TEST
START_TEST(addVariableTypeMismatch) {
/* Try to add a var node with incorrect data type vs type definition */
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_String myStr = UA_STRING("hello");
UA_Variant_setScalar(&attr.value, &myStr, &UA_TYPES[UA_TYPES_STRING]);
UA_NodeId myId = UA_NODEID_STRING(1, "utils.test.badtype");
UA_StatusCode retval = UA_Server_addVariableNode(server, myId,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "BadTypeVar"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, NULL);
/* Should succeed since BaseDataVariableType accepts any data type */
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
} END_TEST
START_TEST(deleteNonExistentNode) {
UA_NodeId fakeId = UA_NODEID_NUMERIC(1, 99999);
UA_StatusCode retval = UA_Server_deleteNode(server, fakeId, true);
ck_assert_uint_ne(retval, UA_STATUSCODE_GOOD);
} END_TEST
/* --- Default attribute validation --- */
START_TEST(checkDefaultAttributes) {
/* Verify VariableAttributes default */
UA_VariableAttributes vattr = UA_VariableAttributes_default;
ck_assert_int_eq(vattr.valueRank, UA_VALUERANK_ANY);
/* Verify ObjectAttributes default */
UA_ObjectAttributes oattr = UA_ObjectAttributes_default;
(void)oattr;
/* Verify MethodAttributes default */
UA_MethodAttributes mattr = UA_MethodAttributes_default;
ck_assert(mattr.executable == true);
ck_assert(mattr.userExecutable == true);
/* Verify ViewAttributes default */
UA_ViewAttributes viewattr = UA_ViewAttributes_default;
ck_assert(viewattr.containsNoLoops == false);
/* Verify DataTypeAttributes default */
UA_DataTypeAttributes dtattr = UA_DataTypeAttributes_default;
ck_assert(dtattr.isAbstract == false);
/* Verify ReferenceTypeAttributes default */
UA_ReferenceTypeAttributes rtattr = UA_ReferenceTypeAttributes_default;
ck_assert(rtattr.isAbstract == false);
ck_assert(rtattr.symmetric == false);
} END_TEST
/* --- Server read/write various attribute types --- */
START_TEST(readWriteMinimumSamplingInterval) {
/* Add a variable node */
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_Double val = 1.0;
UA_Variant_setScalar(&attr.value, &val, &UA_TYPES[UA_TYPES_DOUBLE]);
attr.minimumSamplingInterval = 500.0;
UA_NodeId myId = UA_NODEID_STRING(1, "utils.test.sampling");
UA_StatusCode retval = UA_Server_addVariableNode(server, myId,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "SamplingVar"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, NULL);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Read minimumSamplingInterval */
UA_Double readSampling = 0.0;
retval = UA_Server_readMinimumSamplingInterval(server, myId, &readSampling);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
ck_assert(readSampling == 500.0);
/* Write new minimumSamplingInterval */
retval = UA_Server_writeMinimumSamplingInterval(server, myId, 100.0);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
retval = UA_Server_readMinimumSamplingInterval(server, myId, &readSampling);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
ck_assert(readSampling == 100.0);
} END_TEST
START_TEST(readWriteAccessLevel) {
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_Int32 val = 10;
UA_Variant_setScalar(&attr.value, &val, &UA_TYPES[UA_TYPES_INT32]);
attr.accessLevel = UA_ACCESSLEVELMASK_READ;
UA_NodeId myId = UA_NODEID_STRING(1, "utils.test.access");
UA_StatusCode retval = UA_Server_addVariableNode(server, myId,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "AccessVar"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, NULL);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Read accessLevel */
UA_Byte accessLevel;
retval = UA_Server_readAccessLevel(server, myId, &accessLevel);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
ck_assert(accessLevel & UA_ACCESSLEVELMASK_READ);
/* Write new accessLevel */
retval = UA_Server_writeAccessLevel(server, myId,
UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
} END_TEST
START_TEST(readWriteDisplayName) {
/* Add a custom node so we can safely write its display name */
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
oAttr.displayName = UA_LOCALIZEDTEXT("en-US", "OrigDN");
UA_NodeId objId = UA_NODEID_STRING(1, "utils.test.dn");
UA_StatusCode retval = UA_Server_addObjectNode(server, objId,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "DN_Object"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
oAttr, NULL, NULL);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_LocalizedText displayName;
retval = UA_Server_readDisplayName(server, objId, &displayName);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_LocalizedText_clear(&displayName);
/* Write new display name */
retval = UA_Server_writeDisplayName(server, objId,
UA_LOCALIZEDTEXT("en-US", "MyObjects"));
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Read back */
retval = UA_Server_readDisplayName(server, objId, &displayName);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_String expected = UA_STRING("MyObjects");
ck_assert(UA_String_equal(&displayName.text, &expected));
UA_LocalizedText_clear(&displayName);
} END_TEST
START_TEST(readWriteDescription) {
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_Int32 val = 10;
UA_Variant_setScalar(&attr.value, &val, &UA_TYPES[UA_TYPES_INT32]);
attr.description = UA_LOCALIZEDTEXT("en-US", "OriginalDesc");
UA_NodeId myId = UA_NODEID_STRING(1, "utils.test.desc");
UA_StatusCode retval = UA_Server_addVariableNode(server, myId,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "DescVar"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, NULL);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Write new description */
retval = UA_Server_writeDescription(server, myId,
UA_LOCALIZEDTEXT("de-DE", "NeuerName"));
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Read back - description may match the latest write */
UA_LocalizedText desc;
retval = UA_Server_readDescription(server, myId, &desc);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Just verify we got something back */
ck_assert(desc.text.length > 0);
UA_LocalizedText_clear(&desc);
} END_TEST
START_TEST(readWriteBrowseName) {
UA_QualifiedName browseName;
UA_StatusCode retval = UA_Server_readBrowseName(server,
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), &browseName);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
ck_assert(browseName.name.length > 0);
UA_QualifiedName_clear(&browseName);
/* Write new browse name for a custom node */
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
UA_NodeId objId = UA_NODEID_STRING(1, "utils.test.bn");
retval = UA_Server_addObjectNode(server, objId,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "OrigName"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
oAttr, NULL, NULL);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Read the browse name we just set */
retval = UA_Server_readBrowseName(server, objId, &browseName);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_String origExpected = UA_STRING("OrigName");
ck_assert(UA_String_equal(&browseName.name, &origExpected));
UA_QualifiedName_clear(&browseName);
} END_TEST
/* --- Reference management --- */
START_TEST(addDeleteReference) {
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
UA_NodeId s1 = UA_NODEID_STRING(1, "utils.ref.src");
UA_NodeId t1 = UA_NODEID_STRING(1, "utils.ref.tgt");
UA_Server_addObjectNode(server, s1,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "RefSrc"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
oAttr, NULL, NULL);
UA_Server_addObjectNode(server, t1,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "RefTgt"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
oAttr, NULL, NULL);
/* Add a reference */
UA_StatusCode retval = UA_Server_addReference(server, s1,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_EXPANDEDNODEID_STRING(1, "utils.ref.tgt"), true);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Adding the same reference again should fail */
retval = UA_Server_addReference(server, s1,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_EXPANDEDNODEID_STRING(1, "utils.ref.tgt"), true);
ck_assert_uint_eq(retval, UA_STATUSCODE_BADDUPLICATEREFERENCENOTALLOWED);
/* Delete the reference */
retval = UA_Server_deleteReference(server, s1,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), true,
UA_EXPANDEDNODEID_STRING(1, "utils.ref.tgt"), true);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
} END_TEST
START_TEST(nodeVersionProperty) {
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
UA_NodeId objectId = UA_NODEID_STRING(1, "utils.nodeversion.object");
UA_StatusCode res = UA_Server_addObjectNode(
server, objectId, UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(ORGANIZES),
UA_QUALIFIEDNAME(1, "NodeVersionObject"), UA_NS0ID(BASEOBJECTTYPE),
oAttr, NULL, NULL);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
UA_NodeId found;
res = findNodeVersion(objectId, &found);
ck_assert_uint_eq(res, UA_STATUSCODE_BADNOTFOUND);
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
vAttr.displayName = UA_LOCALIZEDTEXT("", "NodeVersion");
vAttr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
vAttr.valueRank = UA_VALUERANK_SCALAR;
UA_NodeId propertyId = UA_NODEID_STRING(1, "utils.nodeversion.property");
res = UA_Server_addVariableNode(
server, propertyId, objectId, UA_NS0ID(HASPROPERTY),
UA_QUALIFIEDNAME(0, "NodeVersion"), UA_NS0ID(PROPERTYTYPE),
vAttr, NULL, NULL);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
res = findNodeVersion(objectId, &found);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
ck_assert(UA_NodeId_equal(&found, &propertyId));
UA_NodeId_clear(&found);
} END_TEST
START_TEST(nodeVersionPropertyViaHasPropertySubtype) {
UA_ReferenceTypeAttributes rtAttr = UA_ReferenceTypeAttributes_default;
rtAttr.displayName = UA_LOCALIZEDTEXT("", "HasVersionProperty");
rtAttr.inverseName = UA_LOCALIZEDTEXT("", "VersionPropertyOf");
UA_NodeId refTypeId = UA_NODEID_STRING(1, "utils.nodeversion.reftype");
UA_StatusCode res = UA_Server_addReferenceTypeNode(
server, refTypeId, UA_NS0ID(HASPROPERTY), UA_NS0ID(HASSUBTYPE),
UA_QUALIFIEDNAME(1, "HasVersionProperty"), rtAttr, NULL, NULL);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
UA_NodeId objectId = UA_NODEID_STRING(1, "utils.nodeversion.subtype.object");
res = UA_Server_addObjectNode(
server, objectId, UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(ORGANIZES),
UA_QUALIFIEDNAME(1, "NodeVersionSubtypeObject"),
UA_NS0ID(BASEOBJECTTYPE), oAttr, NULL, NULL);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
vAttr.displayName = UA_LOCALIZEDTEXT("", "NodeVersion");
vAttr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
vAttr.valueRank = UA_VALUERANK_SCALAR;
UA_NodeId propertyId =
UA_NODEID_STRING(1, "utils.nodeversion.subtype.property");
res = UA_Server_addVariableNode(
server, propertyId, objectId, refTypeId,
UA_QUALIFIEDNAME(0, "NodeVersion"), UA_NS0ID(PROPERTYTYPE),
vAttr, NULL, NULL);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
UA_NodeId found;
res = findNodeVersion(objectId, &found);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
ck_assert(UA_NodeId_equal(&found, &propertyId));
UA_NodeId_clear(&found);
} END_TEST
START_TEST(nodeVersionPropertyRejectsInvalidVariable) {
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
UA_NodeId objectId = UA_NODEID_STRING(1, "utils.nodeversion.invalid.object");
UA_StatusCode res = UA_Server_addObjectNode(
server, objectId, UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(ORGANIZES),
UA_QUALIFIEDNAME(1, "InvalidNodeVersionObject"),
UA_NS0ID(BASEOBJECTTYPE), oAttr, NULL, NULL);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
vAttr.displayName = UA_LOCALIZEDTEXT("", "NodeVersion");
vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
vAttr.valueRank = UA_VALUERANK_SCALAR;
res = UA_Server_addVariableNode(
server, UA_NODEID_STRING(1, "utils.nodeversion.invalid.property"),
objectId, UA_NS0ID(HASPROPERTY), UA_QUALIFIEDNAME(0, "NodeVersion"),
UA_NS0ID(PROPERTYTYPE), vAttr, NULL, NULL);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
UA_NodeId found;
res = findNodeVersion(objectId, &found);
ck_assert_uint_eq(res, UA_STATUSCODE_BADNOTFOUND);
} END_TEST
#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
static void
addVersionedObject(const UA_NodeId affected, const UA_NodeId propertyId) {
static UA_UInt32 counter = 0;
char name[32];
snprintf(name, sizeof(name), "VersionedObject%u", counter++);
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
UA_StatusCode res = UA_Server_addObjectNode(
server, affected, UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(ORGANIZES),
UA_QUALIFIEDNAME(1, name), UA_NS0ID(BASEOBJECTTYPE),
oAttr, NULL, NULL);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
UA_String initial = UA_STRING("initial");
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
vAttr.displayName = UA_LOCALIZEDTEXT("", "NodeVersion");
vAttr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
vAttr.valueRank = UA_VALUERANK_SCALAR;
UA_Variant_setScalar(&vAttr.value, &initial, &UA_TYPES[UA_TYPES_STRING]);
res = UA_Server_addVariableNode(
server, propertyId, affected, UA_NS0ID(HASPROPERTY),
UA_QUALIFIEDNAME(0, "NodeVersion"), UA_NS0ID(PROPERTYTYPE),
vAttr, NULL, NULL);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
}
static UA_StatusCode
recordModelChange(UA_ModelChangeAccumulator *acc, const UA_NodeId *affected,
UA_Byte verb) {
lockServer(server);
UA_StatusCode res = UA_ModelChangeAccumulator_record(
server, acc, affected, verb);
unlockServer(server);
return res;
}
START_TEST(modelChangeAccumulatorAllVerbs) {
UA_ModelChangeAccumulator acc;
UA_ModelChangeAccumulator_init(&acc);
UA_NodeId affected = UA_NODEID_STRING(1, "modelchange.affected");
addVersionedObject(affected,
UA_NODEID_STRING(1, "modelchange.affected.version"));
const UA_Byte verbs[] = {
UA_MODELCHANGESTRUCTUREVERBMASK_NODEADDED,
UA_MODELCHANGESTRUCTUREVERBMASK_NODEDELETED,
UA_MODELCHANGESTRUCTUREVERBMASK_REFERENCEADDED,
UA_MODELCHANGESTRUCTUREVERBMASK_REFERENCEDELETED,
UA_MODELCHANGESTRUCTUREVERBMASK_DATATYPECHANGED
};
for(size_t i = 0; i < sizeof(verbs) / sizeof(verbs[0]); ++i) {
UA_StatusCode res = recordModelChange(
&acc, &affected, verbs[i]);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
}
ck_assert_uint_eq(acc.changesSize, 1);
ck_assert(UA_NodeId_equal(&acc.changes[0].change.affected, &affected));
UA_NodeId expectedType = UA_NS0ID(BASEOBJECTTYPE);
ck_assert(UA_NodeId_equal(&acc.changes[0].change.affectedType,
&expectedType));
ck_assert_uint_eq(acc.changes[0].change.verb, 0x1Fu);
UA_ModelChangeAccumulator_clear(&acc);
ck_assert_uint_eq(acc.changesSize, 0);
ck_assert_uint_eq(acc.changesCapacity, 0);
ck_assert_ptr_eq(acc.changes, NULL);
} END_TEST
START_TEST(modelChangeAccumulatorMultipleNodes) {
UA_ModelChangeAccumulator acc;
UA_ModelChangeAccumulator_init(&acc);
for(UA_UInt32 i = 1; i <= 10; ++i) {
UA_NodeId affected = UA_NODEID_NUMERIC(1, 2000 + i);
UA_NodeId property = UA_NODEID_NUMERIC(1, 2100 + i);
addVersionedObject(affected, property);
UA_StatusCode res = recordModelChange(
&acc, &affected,
UA_MODELCHANGESTRUCTUREVERBMASK_REFERENCEADDED);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
}
ck_assert_uint_eq(acc.changesSize, 10);
ck_assert_uint_ge(acc.changesCapacity, acc.changesSize);
UA_NodeId expectedType = UA_NS0ID(BASEOBJECTTYPE);
for(UA_UInt32 i = 1; i <= 10; ++i) {
UA_NodeId expected = UA_NODEID_NUMERIC(1, 2000 + i);
ck_assert(UA_NodeId_equal(&acc.changes[i - 1].change.affected,
&expected));
ck_assert(UA_NodeId_equal(&acc.changes[i - 1].change.affectedType,
&expectedType));
}
UA_ModelChangeAccumulator_clear(&acc);
} END_TEST
START_TEST(modelChangeAccumulatorAffectedType) {
UA_ModelChangeAccumulator acc;
UA_ModelChangeAccumulator_init(&acc);
UA_NodeId affected = UA_NODEID_NUMERIC(1, 1000);
addVersionedObject(affected, UA_NODEID_NUMERIC(1, 1001));
UA_StatusCode res = recordModelChange(
&acc, &affected,
UA_MODELCHANGESTRUCTUREVERBMASK_NODEADDED);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
res = recordModelChange(
&acc, &affected,
UA_MODELCHANGESTRUCTUREVERBMASK_REFERENCEADDED);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
UA_NodeId expectedType = UA_NS0ID(BASEOBJECTTYPE);
ck_assert(UA_NodeId_equal(&acc.changes[0].change.affectedType,
&expectedType));
res = recordModelChange(
&acc, &affected,
UA_MODELCHANGESTRUCTUREVERBMASK_NODEDELETED);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
/* NodeDeleted writes the assigned version before the node is removed. */
UA_Variant version;
UA_Variant_init(&version);
res = UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 1001), &version);
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
ck_assert(UA_Variant_hasScalarType(&version, &UA_TYPES[UA_TYPES_STRING]));
UA_String expectedVersion = UA_STRING("1");
ck_assert(UA_String_equal((UA_String*)version.data, &expectedVersion));
UA_Variant_clear(&version);
ck_assert(UA_NodeId_equal(&acc.changes[0].change.affectedType,
&expectedType));
ck_assert_uint_eq(acc.changes[0].change.verb,
UA_MODELCHANGESTRUCTUREVERBMASK_NODEADDED |
UA_MODELCHANGESTRUCTUREVERBMASK_NODEDELETED |
UA_MODELCHANGESTRUCTUREVERBMASK_REFERENCEADDED);
UA_ModelChangeAccumulator_clear(&acc);
} END_TEST
START_TEST(modelChangeAccumulatorRejectsInvalidInput) {
UA_ModelChangeAccumulator acc;
UA_ModelChangeAccumulator_init(&acc);
UA_NodeId affected = UA_NODEID_NUMERIC(1, 1000);
ck_assert_uint_eq(recordModelChange(
&acc, NULL, UA_MODELCHANGESTRUCTUREVERBMASK_NODEADDED),
UA_STATUSCODE_BADINVALIDARGUMENT);
ck_assert_uint_eq(recordModelChange(
&acc, &UA_NODEID_NULL,
UA_MODELCHANGESTRUCTUREVERBMASK_NODEADDED),
UA_STATUSCODE_BADINVALIDARGUMENT);
ck_assert_uint_eq(recordModelChange(&acc, &affected, 0),
UA_STATUSCODE_BADINVALIDARGUMENT);
ck_assert_uint_eq(recordModelChange(&acc, &affected, 0x20),
UA_STATUSCODE_BADINVALIDARGUMENT);
ck_assert_uint_eq(acc.changesSize, 0);
UA_ModelChangeAccumulator_clear(&acc);
} END_TEST
#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
int main(void) {
Suite *s = suite_create("server_utils");
TCase *tc_types = tcase_create("DataTypes");
tcase_add_checked_fixture(tc_types, setup, teardown);
tcase_add_test(tc_types, getDataTypes);
tcase_add_test(tc_types, findBuiltinDataType);
tcase_add_test(tc_types, findNonExistentDataType);
tcase_add_test(tc_types, addDataTypeBasic);
suite_add_tcase(s, tc_types);
TCase *tc_browse = tcase_create("Browse");
tcase_add_checked_fixture(tc_browse, setup, teardown);
tcase_add_test(tc_browse, browseObjectsFolder);
tcase_add_test(tc_browse, browseInverse);
tcase_add_test(tc_browse, browseBothDirections);
tcase_add_test(tc_browse, browseNonExistentNode);
tcase_add_test(tc_browse, translateBrowsePathToServer);
tcase_add_test(tc_browse, translateBrowsePathMultiHop);
tcase_add_test(tc_browse, translateBrowsePathNotFound);
suite_add_tcase(s, tc_browse);
TCase *tc_nodes = tcase_create("NodeMgmt");
tcase_add_checked_fixture(tc_nodes, setup, teardown);
tcase_add_test(tc_nodes, addAndDeleteVariableNode);
tcase_add_test(tc_nodes, addVariableTypeMismatch);
tcase_add_test(tc_nodes, deleteNonExistentNode);
tcase_add_test(tc_nodes, checkDefaultAttributes);
tcase_add_test(tc_nodes, nodeVersionProperty);
tcase_add_test(tc_nodes, nodeVersionPropertyViaHasPropertySubtype);
tcase_add_test(tc_nodes, nodeVersionPropertyRejectsInvalidVariable);
#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
tcase_add_test(tc_nodes, modelChangeAccumulatorAllVerbs);
tcase_add_test(tc_nodes, modelChangeAccumulatorMultipleNodes);
tcase_add_test(tc_nodes, modelChangeAccumulatorAffectedType);
tcase_add_test(tc_nodes, modelChangeAccumulatorRejectsInvalidInput);
#endif
suite_add_tcase(s, tc_nodes);
TCase *tc_attrs = tcase_create("Attributes");
tcase_add_checked_fixture(tc_attrs, setup, teardown);
tcase_add_test(tc_attrs, readWriteMinimumSamplingInterval);
tcase_add_test(tc_attrs, readWriteAccessLevel);
tcase_add_test(tc_attrs, readWriteDisplayName);
tcase_add_test(tc_attrs, readWriteDescription);
tcase_add_test(tc_attrs, readWriteBrowseName);
tcase_add_test(tc_attrs, addDeleteReference);
suite_add_tcase(s, tc_attrs);
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;
}