blob: bbae3b11f3d9f34ba26b47c40bb0f9939fd24126 [file] [log] [blame]
/* 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_config_default.h>
#include "server/ua_server_internal.h"
#include "server/ua_services.h"
#include <check.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
static UA_Server *server = NULL;
static UA_Int32 handleCalled = 0;
static UA_StatusCode
globalInstantiationMethod(UA_Server *server_,
const UA_NodeId *sessionId, void *sessionContext,
const UA_NodeId *nodeId, void **nodeContext) {
handleCalled++;
return UA_STATUSCODE_GOOD;
}
static void setup(void) {
server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefault(config);
UA_GlobalNodeLifecycle lifecycle;
lifecycle.constructor = globalInstantiationMethod;
lifecycle.destructor = NULL;
lifecycle.createOptionalChild = NULL;
lifecycle.generateChildNodeId = NULL;
config->nodeLifecycle = lifecycle;
}
static void teardown(void) {
UA_Server_delete(server);
}
START_TEST(AddVariableNode) {
/* Add a variable node to the address space */
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_Int32 myInteger = 42;
UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
attr.description = UA_LOCALIZEDTEXT("en-US","the answer");
attr.displayName = UA_LOCALIZEDTEXT("en-US","the answer");
UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer");
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
UA_StatusCode res =
UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
parentReferenceNodeId, myIntegerName,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, NULL);
ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
} END_TEST
START_TEST(AddVariableNode_Matrix) {
/* Add a variable node to the address space */
UA_VariableAttributes attr = UA_VariableAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT("en-US", "Double Matrix");
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
attr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
attr.valueRank = UA_VALUERANK_TWO_DIMENSIONS;
UA_UInt32 arrayDims[2] = {2,2};
attr.arrayDimensions = arrayDims;
attr.arrayDimensionsSize = 2;
UA_Double zero[4] = {0.0, 0.0, 0.0, 0.0};
UA_Variant_setArray(&attr.value, zero, 4, &UA_TYPES[UA_TYPES_DOUBLE]);
attr.value.arrayDimensions = arrayDims;
attr.value.arrayDimensionsSize = 2;
UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "double.matrix");
UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "double matrix");
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
UA_StatusCode res =
UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
parentReferenceNodeId, myIntegerName,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, NULL);
ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
} END_TEST
START_TEST(AddVariableNode_ExtensionObject) {
/* Add a variable node to the address space */
UA_VariableAttributes attr = UA_VariableAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT("en-US","the extensionobject");
/* Set an ExtensionObject with an unknown binary encoding */
UA_ExtensionObject myExtensionObject;
UA_ExtensionObject_init(&myExtensionObject);
myExtensionObject.encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
myExtensionObject.content.encoded.typeId = UA_NODEID_NUMERIC(5, 1234);
UA_ByteString byteString = UA_BYTESTRING("String Payload as a ByteString extension");
myExtensionObject.content.encoded.body = byteString;
UA_Variant_setScalar(&attr.value, &myExtensionObject, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]);
UA_NodeId myEONodeId = UA_NODEID_STRING(1, "the.extensionobject");
UA_QualifiedName myEOName = UA_QUALIFIEDNAME(1, "the extensionobject");
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
UA_StatusCode res =
UA_Server_addVariableNode(server, myEONodeId, parentNodeId,
parentReferenceNodeId, myEOName,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, NULL);
ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
} END_TEST
static UA_NodeId pointTypeId;
static void
addVariableTypeNode(void) {
UA_VariableTypeAttributes vtAttr = UA_VariableTypeAttributes_default;
vtAttr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
vtAttr.valueRank = UA_VALUERANK_ONE_DIMENSION;
UA_UInt32 arrayDims[1] = {2};
vtAttr.arrayDimensions = arrayDims;
vtAttr.arrayDimensionsSize = 1;
vtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "2DPoint Type");
/* a matching default value is required */
UA_Double zero[2] = {0.0, 0.0};
UA_Variant_setArray(&vtAttr.value, zero, 2, &UA_TYPES[UA_TYPES_DOUBLE]);
UA_StatusCode res =
UA_Server_addVariableTypeNode(server, UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
UA_QUALIFIEDNAME(1, "2DPoint Type"), UA_NODEID_NULL,
vtAttr, NULL, &pointTypeId);
ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
}
START_TEST(InstantiateVariableTypeNode) {
addVariableTypeNode();
/* Prepare the node attributes */
UA_UInt32 arrayDims[1] = {2};
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
vAttr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
vAttr.valueRank = UA_VALUERANK_ONE_DIMENSION;
vAttr.arrayDimensions = arrayDims;
vAttr.arrayDimensionsSize = 1;
vAttr.displayName = UA_LOCALIZEDTEXT("en-US", "2DPoint Variable");
vAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
/* vAttr.value is left empty, the server instantiates with the default value */
/* Add the node */
UA_NodeId pointVariableId;
UA_StatusCode res =
UA_Server_addVariableNode(server, UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "2DPoint Type"), pointTypeId,
vAttr, NULL, &pointVariableId);
ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
/* Was the value instantiated? */
UA_Variant val;
UA_Server_readValue(server, pointVariableId, &val);
ck_assert(val.type != NULL);
UA_Variant_deleteMembers(&val);
} END_TEST
START_TEST(InstantiateVariableTypeNodeWrongDims) {
addVariableTypeNode();
/* Prepare the node attributes */
UA_UInt32 arrayDims[1] = {3}; /* This will fail as the dimensions are too big */
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
vAttr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
vAttr.valueRank = UA_VALUERANK_ONE_DIMENSION;
vAttr.arrayDimensions = arrayDims;
vAttr.arrayDimensionsSize = 1;
vAttr.displayName = UA_LOCALIZEDTEXT("en-US", "2DPoint Variable");
vAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
/* vAttr.value is left empty, the server instantiates with the default value */
/* Add the node */
UA_StatusCode res =
UA_Server_addVariableNode(server, UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "2DPoint Type"), pointTypeId,
vAttr, NULL, NULL);
ck_assert_int_eq(UA_STATUSCODE_BADTYPEMISMATCH, res);
} END_TEST
START_TEST(InstantiateVariableTypeNodeLessDims) {
addVariableTypeNode();
/* Prepare the node attributes */
UA_UInt32 arrayDims[1] = {1}; /* This will match as the dimension constraints are an upper bound */
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
vAttr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
vAttr.valueRank = UA_VALUERANK_ONE_DIMENSION;
vAttr.arrayDimensions = arrayDims;
vAttr.arrayDimensionsSize = 1;
vAttr.displayName = UA_LOCALIZEDTEXT("en-US", "2DPoint Variable");
vAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
/* vAttr.value is left empty, the server instantiates with the default value */
/* Add the node */
UA_StatusCode res =
UA_Server_addVariableNode(server, UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "2DPoint Type"), pointTypeId,
vAttr, NULL, NULL);
ck_assert_int_eq(UA_STATUSCODE_BADTYPEMISMATCH, res);
} END_TEST
START_TEST(AddComplexTypeWithInheritance) {
/* add a variable node to the address space */
/* Node UA_NS0ID_SERVERTYPE is not available in the minimal NS0 */
#ifdef UA_GENERATED_NAMESPACE_ZERO
UA_ObjectAttributes attr = UA_ObjectAttributes_default;
attr.description = UA_LOCALIZEDTEXT("en-US","fakeServerStruct");
attr.displayName = UA_LOCALIZEDTEXT("en-US","fakeServerStruct");
UA_NodeId myObjectNodeId = UA_NODEID_STRING(1, "the.fake.Server.Struct");
UA_QualifiedName myObjectName = UA_QUALIFIEDNAME(1, "the.fake.Server.Struct");
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
UA_StatusCode res =
UA_Server_addObjectNode(server, myObjectNodeId, parentNodeId,
parentReferenceNodeId, myObjectName,
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERTYPE), attr,
&handleCalled, NULL);
ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
ck_assert_int_gt(handleCalled, 0); // Should be 58, but may depend on NS0 XML detail
#endif
} END_TEST
START_TEST(AddNodeTwiceGivesError) {
/* add a variable node to the address space */
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_Int32 myInteger = 42;
UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
attr.description = UA_LOCALIZEDTEXT("en-US","the answer");
attr.displayName = UA_LOCALIZEDTEXT("en-US","the answer");
UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer");
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
UA_StatusCode res =
UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
parentReferenceNodeId, myIntegerName,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, NULL);
ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
res = UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
parentReferenceNodeId, myIntegerName,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, NULL);
ck_assert_int_eq(res, UA_STATUSCODE_BADNODEIDEXISTS);
} END_TEST
static UA_Boolean constructorCalled = false;
static UA_StatusCode
objectConstructor(UA_Server *server_,
const UA_NodeId *sessionId, void *sessionContext,
const UA_NodeId *typeId, void *typeContext,
const UA_NodeId *nodeId, void **nodeContext) {
constructorCalled = true;
return UA_STATUSCODE_GOOD;
}
START_TEST(AddObjectWithConstructor) {
/* Add an object type */
UA_NodeId objecttypeid = UA_NODEID_NUMERIC(0, 13371337);
UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT("en-US","my objecttype");
UA_StatusCode res =
UA_Server_addObjectTypeNode(server, objecttypeid,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
UA_QUALIFIEDNAME(0, "myobjecttype"), attr,
NULL, NULL);
ck_assert_int_eq(res, UA_STATUSCODE_GOOD);
/* Add a constructor to the object type */
UA_NodeTypeLifecycle lifecycle;
lifecycle.constructor = objectConstructor;
lifecycle.destructor = NULL;
res = UA_Server_setNodeTypeLifecycle(server, objecttypeid, lifecycle);
ck_assert_int_eq(res, UA_STATUSCODE_GOOD);
/* Add an object of the type */
UA_ObjectAttributes attr2 = UA_ObjectAttributes_default;
attr2.displayName = UA_LOCALIZEDTEXT("en-US","my object");
res = UA_Server_addObjectNode(server, UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(0, "MyObjectNode"), objecttypeid,
attr2, NULL, NULL);
ck_assert_int_eq(res, UA_STATUSCODE_GOOD);
/* Verify that the constructor was called */
ck_assert_int_eq(constructorCalled, true);
} END_TEST
static UA_Boolean destructorCalled = false;
static void
objectDestructor(UA_Server *server_,
const UA_NodeId *sessionId, void *sessionContext,
const UA_NodeId *typeId, void *typeContext,
const UA_NodeId *nodeId, void **nodeContext) {
destructorCalled = true;
}
START_TEST(DeleteObjectWithDestructor) {
/* Add an object type */
UA_NodeId objecttypeid = UA_NODEID_NUMERIC(0, 13371337);
UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT("en-US","my objecttype");
UA_StatusCode res =
UA_Server_addObjectTypeNode(server, objecttypeid,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
UA_QUALIFIEDNAME(0, "myobjecttype"), attr, NULL, NULL);
ck_assert_int_eq(res, UA_STATUSCODE_GOOD);
/* Add a constructor to the object type */
UA_NodeTypeLifecycle lifecycle;
lifecycle.constructor = NULL;
lifecycle.destructor = objectDestructor;
res = UA_Server_setNodeTypeLifecycle(server, objecttypeid, lifecycle);
ck_assert_int_eq(res, UA_STATUSCODE_GOOD);
/* Add an object of the type */
UA_NodeId objectid = UA_NODEID_NUMERIC(0, 23372337);
UA_ObjectAttributes attr2 = UA_ObjectAttributes_default;
attr2.displayName = UA_LOCALIZEDTEXT("en-US","my object");
res = UA_Server_addObjectNode(server, objectid,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(0, "MyObject"), objecttypeid,
attr2, NULL, NULL);
ck_assert_int_eq(res, UA_STATUSCODE_GOOD);
/* Delete the object */
UA_Server_deleteNode(server, objectid, true);
/* Verify that the destructor was called */
ck_assert_int_eq(destructorCalled, true);
} END_TEST
START_TEST(DeleteObjectAndReferences) {
/* Add an object of the type */
UA_ObjectAttributes attr = UA_ObjectAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT("en-US","my object");
UA_NodeId objectid = UA_NODEID_NUMERIC(0, 23372337);
UA_StatusCode res;
res = UA_Server_addObjectNode(server, objectid,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(0, "MyObject"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
attr, NULL, NULL);
ck_assert_int_eq(res, UA_STATUSCODE_GOOD);
/* Verify that we have a reference to the node from the objects folder */
UA_BrowseDescription bd;
UA_BrowseDescription_init(&bd);
bd.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
bd.browseDirection = UA_BROWSEDIRECTION_FORWARD;
UA_BrowseResult br = UA_Server_browse(server, 0, &bd);
ck_assert_int_eq(br.statusCode, UA_STATUSCODE_GOOD);
size_t refCount = 0;
for(size_t i = 0; i < br.referencesSize; ++i) {
if(UA_NodeId_equal(&br.references[i].nodeId.nodeId, &objectid))
refCount++;
}
ck_assert_int_eq(refCount, 1);
UA_BrowseResult_deleteMembers(&br);
/* Delete the object */
UA_Server_deleteNode(server, objectid, true);
/* Browse again, this time we expect that no reference is found */
br = UA_Server_browse(server, 0, &bd);
ck_assert_int_eq(br.statusCode, UA_STATUSCODE_GOOD);
refCount = 0;
for(size_t i = 0; i < br.referencesSize; ++i) {
if(UA_NodeId_equal(&br.references[i].nodeId.nodeId, &objectid))
refCount++;
}
ck_assert_int_eq(refCount, 0);
UA_BrowseResult_deleteMembers(&br);
/* Add an object the second time */
attr = UA_ObjectAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT("en-US","my object");
objectid = UA_NODEID_NUMERIC(0, 23372337);
res = UA_Server_addObjectNode(server, objectid,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(0, "MyObject"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
attr, NULL, NULL);
ck_assert_int_eq(res, UA_STATUSCODE_GOOD);
/* Browse again, this time we expect that a single reference to the node is found */
refCount = 0;
br = UA_Server_browse(server, 0, &bd);
ck_assert_int_eq(br.statusCode, UA_STATUSCODE_GOOD);
for(size_t i = 0; i < br.referencesSize; ++i) {
if(UA_NodeId_equal(&br.references[i].nodeId.nodeId, &objectid))
refCount++;
}
ck_assert_int_eq(refCount, 1);
UA_BrowseResult_deleteMembers(&br);
} END_TEST
/* Example taken from tutorial_server_object.c */
START_TEST(InstantiateObjectType) {
/* Define the object type */
UA_NodeId pumpTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};
UA_StatusCode retval;
/* Define the object type for "Device" */
UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "DeviceType");
retval = UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
UA_QUALIFIEDNAME(1, "DeviceType"), dtAttr,
NULL, &deviceTypeId);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ManufacturerName");
UA_NodeId manufacturerNameId;
retval = UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "ManufacturerName"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
mnAttr, NULL, &manufacturerNameId);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
/* UA_NS0ID_MODELLINGRULE_MANDATORY is not available in Minimal Nodeset */
#ifdef UA_GENERATED_NAMESPACE_ZERO
/* Make the manufacturer name mandatory */
retval = UA_Server_addReference(server, manufacturerNameId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
#endif
UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ModelName");
retval = UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "ModelName"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
modelAttr, NULL, NULL);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
/* Define the object type for "Pump" */
UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "PumpType");
retval = UA_Server_addObjectTypeNode(server, pumpTypeId, deviceTypeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
UA_QUALIFIEDNAME(1, "PumpType"), ptAttr,
NULL, NULL);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
statusAttr.valueRank = UA_VALUERANK_SCALAR;
UA_NodeId statusId;
retval = UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "Status"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
statusAttr, NULL, &statusId);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
/* UA_NS0ID_MODELLINGRULE_MANDATORY is not available in Minimal Nodeset */
#ifdef UA_GENERATED_NAMESPACE_ZERO
/* Make the status variable mandatory */
retval = UA_Server_addReference(server, statusId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
#endif
UA_VariableAttributes rpmAttr = UA_VariableAttributes_default;
rpmAttr.displayName = UA_LOCALIZEDTEXT("en-US", "MotorRPM");
rpmAttr.valueRank = UA_VALUERANK_SCALAR;
retval = UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "MotorRPMs"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
rpmAttr, NULL, NULL);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
/* Instantiate the variable */
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
oAttr.displayName = UA_LOCALIZEDTEXT("en-US", "MyPump");
retval = UA_Server_addObjectNode(server, UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "MyPump"),
pumpTypeId, /* this refers to the object type
identifier */
oAttr, NULL, NULL);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
} END_TEST
static UA_NodeId
findReference(const UA_NodeId sourceId, const UA_NodeId refTypeId) {
UA_BrowseDescription * bDesc = UA_BrowseDescription_new();
UA_NodeId_copy(&sourceId, &bDesc->nodeId);
bDesc->browseDirection = UA_BROWSEDIRECTION_FORWARD;
bDesc->includeSubtypes = true;
bDesc->resultMask = UA_BROWSERESULTMASK_REFERENCETYPEID;
UA_BrowseResult bRes = UA_Server_browse(server, 0, bDesc);
ck_assert(bRes.statusCode == UA_STATUSCODE_GOOD);
UA_NodeId outNodeId = UA_NODEID_NULL;
for(size_t i = 0; i < bRes.referencesSize; i++) {
UA_ReferenceDescription rDesc = bRes.references[i];
if(UA_NodeId_equal(&rDesc.referenceTypeId, &refTypeId)) {
UA_NodeId_copy(&rDesc.nodeId.nodeId, &outNodeId);
break;
}
}
UA_BrowseDescription_deleteMembers(bDesc);
UA_BrowseDescription_delete(bDesc);
UA_BrowseResult_deleteMembers(&bRes);
return outNodeId;
}
static UA_NodeId
registerRefType(char *forwName, char *invName) {
UA_NodeId outNodeId;
UA_ReferenceTypeAttributes refattr = UA_ReferenceTypeAttributes_default;
refattr.displayName = UA_LOCALIZEDTEXT(NULL, forwName);
refattr.inverseName = UA_LOCALIZEDTEXT(NULL, invName );
UA_QualifiedName browseName = UA_QUALIFIEDNAME(1, forwName);
UA_StatusCode st =
UA_Server_addReferenceTypeNode(server, UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_NONHIERARCHICALREFERENCES),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
browseName, refattr, NULL, &outNodeId);
ck_assert(st == UA_STATUSCODE_GOOD);
return outNodeId;
}
static UA_NodeId
addObjInstance(const UA_NodeId parentNodeId, char *dispName) {
UA_NodeId outNodeId;
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
oAttr.displayName = UA_LOCALIZEDTEXT(NULL, dispName);
UA_QualifiedName browseName = UA_QUALIFIEDNAME(1, dispName);
UA_StatusCode st =
UA_Server_addObjectNode(server, UA_NODEID_NULL,
parentNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
browseName, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
oAttr, NULL, &outNodeId);
ck_assert(st == UA_STATUSCODE_GOOD);
return outNodeId;
}
START_TEST(AddDoubleReference) {
// create two different reference types
UA_NodeId ref1TypeId = registerRefType("HasRef1", "IsRefOf1");
UA_NodeId ref2TypeId = registerRefType("HasRef2", "IsRefOf2");
// create two different object instances
UA_NodeId objectsNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
UA_NodeId sourceId = addObjInstance(objectsNodeId, "obj1");
UA_NodeId targetId = addObjInstance(objectsNodeId, "obj2");
// connect them twice, one time per reference type
UA_ExpandedNodeId targetExpId;
targetExpId.nodeId = targetId;
targetExpId.namespaceUri = UA_STRING_NULL;
targetExpId.serverIndex = 0;
UA_StatusCode st;
st = UA_Server_addReference(server, sourceId, ref1TypeId, targetExpId, true);
ck_assert(st == UA_STATUSCODE_GOOD);
st = UA_Server_addReference(server, sourceId, ref2TypeId, targetExpId, true);
ck_assert(st == UA_STATUSCODE_GOOD);
/* repetition fails */
st = UA_Server_addReference(server, sourceId, ref2TypeId, targetExpId, true);
ck_assert(st != UA_STATUSCODE_GOOD);
// check references where added
UA_NodeId targetCheckId;
targetCheckId = findReference(sourceId, ref1TypeId);
ck_assert(UA_NodeId_equal(&targetCheckId, &targetId));
targetCheckId = findReference(sourceId, ref2TypeId);
ck_assert(UA_NodeId_equal(&targetCheckId, &targetId));
} END_TEST
int main(void) {
Suite *s = suite_create("services_nodemanagement");
TCase *tc_addnodes = tcase_create("addnodes");
tcase_add_checked_fixture(tc_addnodes, setup, teardown);
tcase_add_test(tc_addnodes, AddVariableNode);
tcase_add_test(tc_addnodes, AddVariableNode_Matrix);
tcase_add_test(tc_addnodes, AddVariableNode_ExtensionObject);
tcase_add_test(tc_addnodes, InstantiateVariableTypeNode);
tcase_add_test(tc_addnodes, InstantiateVariableTypeNodeWrongDims);
tcase_add_test(tc_addnodes, InstantiateVariableTypeNodeLessDims);
tcase_add_test(tc_addnodes, AddComplexTypeWithInheritance);
tcase_add_test(tc_addnodes, AddNodeTwiceGivesError);
tcase_add_test(tc_addnodes, AddObjectWithConstructor);
tcase_add_test(tc_addnodes, InstantiateObjectType);
suite_add_tcase(s, tc_addnodes);
TCase *tc_deletenodes = tcase_create("deletenodes");
tcase_add_checked_fixture(tc_deletenodes, setup, teardown);
tcase_add_test(tc_deletenodes, DeleteObjectWithDestructor);
tcase_add_test(tc_deletenodes, DeleteObjectAndReferences);
suite_add_tcase(s, tc_deletenodes);
TCase *tc_addreferences = tcase_create("addreferences");
tcase_add_checked_fixture(tc_addreferences, setup, teardown);
tcase_add_test(tc_addreferences, AddDoubleReference);
suite_add_tcase(s, tc_addreferences);
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;
}