mirror of
https://github.com/open62541/open62541.git
synced 2026-10-18 13:42:32 +08:00
335cead676
Use pointer-sized atomic run flags in threaded fixtures, including dynamically allocated discovery and browse flags. Pointer sizing supports the MSVC atomic implementation as well as C11 and GCC atomics. Serialize the client error-injection test with the server network callbacks before accessing the internal session and shared send buffer.
790 lines
29 KiB
C
790 lines
29 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/. */
|
|
|
|
/* Server node operation tests */
|
|
|
|
#include <open62541/client.h>
|
|
#include <open62541/client_config_default.h>
|
|
#include <open62541/client_highlevel.h>
|
|
#include <open62541/server.h>
|
|
#include <open62541/server_config_default.h>
|
|
|
|
#include "testing_clock.h"
|
|
#include "thread_wrapper.h"
|
|
#include "test_helpers.h"
|
|
#include <check.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
/* Shared server for server-only tests */
|
|
static UA_Server *server;
|
|
|
|
static void setup_serveronly(void) {
|
|
server = UA_Server_newForUnitTest();
|
|
ck_assert_ptr_ne(server, NULL);
|
|
}
|
|
static void teardown_serveronly(void) {
|
|
UA_Server_delete(server);
|
|
}
|
|
|
|
/* Server+client setup */
|
|
static UA_atomic(uintptr_t) running;
|
|
static THREAD_HANDLE server_thread;
|
|
THREAD_CALLBACK(serverloop) {
|
|
while(UA_atomic_load(&running))
|
|
UA_Server_run_iterate(server, true);
|
|
return 0;
|
|
}
|
|
|
|
static void setup(void) {
|
|
server = UA_Server_newForUnitTest();
|
|
ck_assert_ptr_ne(server, NULL);
|
|
UA_Server_run_startup(server);
|
|
UA_atomic_store(&running, true);
|
|
THREAD_CREATE(server_thread, serverloop);
|
|
}
|
|
|
|
static void teardown(void) {
|
|
UA_atomic_store(&running, false);
|
|
THREAD_JOIN(server_thread);
|
|
UA_Server_run_shutdown(server);
|
|
UA_Server_delete(server);
|
|
}
|
|
|
|
static UA_Client *connectClient(void) {
|
|
UA_Client *client = UA_Client_newForUnitTest();
|
|
UA_StatusCode res = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
return client;
|
|
}
|
|
|
|
static void disconnectClient(UA_Client *client) {
|
|
UA_Client_disconnect(client);
|
|
UA_Client_delete(client);
|
|
}
|
|
|
|
/* === Add View node === */
|
|
START_TEST(addViewNode) {
|
|
UA_ViewAttributes attr = UA_ViewAttributes_default;
|
|
attr.displayName = UA_LOCALIZEDTEXT("en", "TestView");
|
|
attr.description = UA_LOCALIZEDTEXT("en", "A test view");
|
|
|
|
UA_StatusCode res = UA_Server_addViewNode(server,
|
|
UA_NODEID_NUMERIC(1, 70001),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_VIEWSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
UA_QUALIFIEDNAME(1, "TestView"),
|
|
attr, NULL, NULL);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Read back */
|
|
UA_LocalizedText out;
|
|
UA_LocalizedText_init(&out);
|
|
res = UA_Server_readDisplayName(server, UA_NODEID_NUMERIC(1, 70001), &out);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
UA_LocalizedText_clear(&out);
|
|
|
|
/* Write to it to trigger copy */
|
|
UA_LocalizedText newName = UA_LOCALIZEDTEXT("en", "UpdatedView");
|
|
res = UA_Server_writeDisplayName(server, UA_NODEID_NUMERIC(1, 70001), newName);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
/* === Add ObjectType node === */
|
|
START_TEST(addObjectTypeNode) {
|
|
UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default;
|
|
attr.displayName = UA_LOCALIZEDTEXT("en", "TestObjectType");
|
|
attr.description = UA_LOCALIZEDTEXT("en", "A test object type");
|
|
|
|
UA_StatusCode res = UA_Server_addObjectTypeNode(server,
|
|
UA_NODEID_NUMERIC(1, 70002),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
|
|
UA_QUALIFIEDNAME(1, "TestObjectType"),
|
|
attr, NULL, NULL);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Write to it to trigger copy */
|
|
UA_LocalizedText newDesc = UA_LOCALIZEDTEXT("en", "Updated description");
|
|
res = UA_Server_writeDescription(server, UA_NODEID_NUMERIC(1, 70002), newDesc);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
/* === Add VariableType node === */
|
|
START_TEST(addVariableTypeNode) {
|
|
UA_VariableTypeAttributes attr = UA_VariableTypeAttributes_default;
|
|
attr.displayName = UA_LOCALIZEDTEXT("en", "TestVariableType");
|
|
attr.valueRank = UA_VALUERANK_SCALAR;
|
|
|
|
UA_StatusCode res = UA_Server_addVariableTypeNode(server,
|
|
UA_NODEID_NUMERIC(1, 70003),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
|
|
UA_QUALIFIEDNAME(1, "TestVariableType"),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
|
|
attr, NULL, NULL);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
/* === Add DataType node === */
|
|
START_TEST(addDataTypeNode) {
|
|
UA_DataTypeAttributes attr = UA_DataTypeAttributes_default;
|
|
attr.displayName = UA_LOCALIZEDTEXT("en", "TestDataType");
|
|
|
|
UA_StatusCode res = UA_Server_addDataTypeNode(server,
|
|
UA_NODEID_NUMERIC(1, 70004),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_STRUCTURE),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
|
|
UA_QUALIFIEDNAME(1, "TestDataType"),
|
|
attr, NULL, NULL);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
/* === Add ReferenceType node === */
|
|
START_TEST(addReferenceTypeNode) {
|
|
UA_ReferenceTypeAttributes attr = UA_ReferenceTypeAttributes_default;
|
|
attr.displayName = UA_LOCALIZEDTEXT("en", "TestRefType");
|
|
attr.inverseName = UA_LOCALIZEDTEXT("en", "InverseTestRefType");
|
|
|
|
UA_StatusCode res = UA_Server_addReferenceTypeNode(server,
|
|
UA_NODEID_NUMERIC(1, 70005),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_NONHIERARCHICALREFERENCES),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
|
|
UA_QUALIFIEDNAME(1, "TestRefType"),
|
|
attr, NULL, NULL);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
/* === Attribute read operations === */
|
|
START_TEST(readAttributes_variable) {
|
|
/* Add a variable */
|
|
UA_VariableAttributes vattr = UA_VariableAttributes_default;
|
|
vattr.displayName = UA_LOCALIZEDTEXT("en", "ReadTestVar");
|
|
vattr.description = UA_LOCALIZEDTEXT("en", "Test variable for reads");
|
|
UA_Int32 val = 42;
|
|
UA_Variant_setScalar(&vattr.value, &val, &UA_TYPES[UA_TYPES_INT32]);
|
|
vattr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
|
|
vattr.valueRank = UA_VALUERANK_SCALAR;
|
|
vattr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
|
|
|
|
UA_StatusCode res = UA_Server_addVariableNode(server,
|
|
UA_NODEID_NUMERIC(1, 70010),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
UA_QUALIFIEDNAME(1, "ReadTestVar"),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
|
|
vattr, NULL, NULL);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Read various attributes */
|
|
UA_NodeClass nc;
|
|
res = UA_Server_readNodeClass(server, UA_NODEID_NUMERIC(1, 70010), &nc);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(nc, UA_NODECLASS_VARIABLE);
|
|
|
|
UA_QualifiedName bn;
|
|
res = UA_Server_readBrowseName(server, UA_NODEID_NUMERIC(1, 70010), &bn);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
UA_QualifiedName_clear(&bn);
|
|
|
|
UA_LocalizedText dn;
|
|
res = UA_Server_readDisplayName(server, UA_NODEID_NUMERIC(1, 70010), &dn);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
UA_LocalizedText_clear(&dn);
|
|
|
|
UA_LocalizedText desc;
|
|
res = UA_Server_readDescription(server, UA_NODEID_NUMERIC(1, 70010), &desc);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
UA_LocalizedText_clear(&desc);
|
|
|
|
UA_UInt32 writeMask;
|
|
res = UA_Server_readWriteMask(server, UA_NODEID_NUMERIC(1, 70010), &writeMask);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
UA_NodeId dataType;
|
|
res = UA_Server_readDataType(server, UA_NODEID_NUMERIC(1, 70010), &dataType);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
UA_NodeId_clear(&dataType);
|
|
|
|
UA_Int32 valueRank;
|
|
res = UA_Server_readValueRank(server, UA_NODEID_NUMERIC(1, 70010), &valueRank);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
UA_Byte accessLevel;
|
|
res = UA_Server_readAccessLevel(server, UA_NODEID_NUMERIC(1, 70010), &accessLevel);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
UA_Double minSamplingInterval;
|
|
res = UA_Server_readMinimumSamplingInterval(server,
|
|
UA_NODEID_NUMERIC(1, 70010), &minSamplingInterval);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
UA_Boolean historizing;
|
|
res = UA_Server_readHistorizing(server, UA_NODEID_NUMERIC(1, 70010), &historizing);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
/* === Write attribute edge cases === */
|
|
START_TEST(writeAttributes_variable) {
|
|
/* Add a variable */
|
|
UA_VariableAttributes vattr = UA_VariableAttributes_default;
|
|
UA_Int32 val = 100;
|
|
UA_Variant_setScalar(&vattr.value, &val, &UA_TYPES[UA_TYPES_INT32]);
|
|
vattr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
|
|
vattr.writeMask = UA_WRITEMASK_DISPLAYNAME | UA_WRITEMASK_DESCRIPTION |
|
|
UA_WRITEMASK_WRITEMASK | UA_WRITEMASK_ACCESSLEVEL |
|
|
UA_WRITEMASK_VALUERANK | UA_WRITEMASK_MINIMUMSAMPLINGINTERVAL |
|
|
UA_WRITEMASK_HISTORIZING;
|
|
|
|
UA_Server_addVariableNode(server,
|
|
UA_NODEID_NUMERIC(1, 70011),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
UA_QUALIFIEDNAME(1, "WriteTestVar"),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
|
|
vattr, NULL, NULL);
|
|
|
|
/* Write display name */
|
|
UA_LocalizedText newName = UA_LOCALIZEDTEXT("en", "NewDisplayName");
|
|
UA_StatusCode res = UA_Server_writeDisplayName(server,
|
|
UA_NODEID_NUMERIC(1, 70011), newName);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Write description */
|
|
UA_LocalizedText newDesc = UA_LOCALIZEDTEXT("en", "New description");
|
|
res = UA_Server_writeDescription(server, UA_NODEID_NUMERIC(1, 70011), newDesc);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Write value */
|
|
UA_Int32 newVal = 200;
|
|
UA_Variant wv;
|
|
UA_Variant_setScalar(&wv, &newVal, &UA_TYPES[UA_TYPES_INT32]);
|
|
res = UA_Server_writeValue(server, UA_NODEID_NUMERIC(1, 70011), wv);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Write access level */
|
|
UA_Byte newAccess = UA_ACCESSLEVELMASK_READ;
|
|
res = UA_Server_writeAccessLevel(server, UA_NODEID_NUMERIC(1, 70011), newAccess);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Write minimum sampling interval */
|
|
UA_Double newInterval = 500.0;
|
|
res = UA_Server_writeMinimumSamplingInterval(server,
|
|
UA_NODEID_NUMERIC(1, 70011), newInterval);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Write historizing */
|
|
UA_Boolean newHist = true;
|
|
res = UA_Server_writeHistorizing(server, UA_NODEID_NUMERIC(1, 70011), newHist);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Write value rank */
|
|
UA_Int32 newVR = UA_VALUERANK_SCALAR;
|
|
res = UA_Server_writeValueRank(server, UA_NODEID_NUMERIC(1, 70011), newVR);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Write writemask */
|
|
UA_UInt32 newWM = 0;
|
|
res = UA_Server_writeWriteMask(server, UA_NODEID_NUMERIC(1, 70011), newWM);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
/* === Read nonexistent attribute === */
|
|
START_TEST(readAttribute_nonexistent) {
|
|
UA_Variant out;
|
|
UA_Variant_init(&out);
|
|
UA_StatusCode res = UA_Server_readValue(server,
|
|
UA_NODEID_NUMERIC(1, 99999), &out);
|
|
ck_assert(res != UA_STATUSCODE_GOOD);
|
|
UA_Variant_clear(&out);
|
|
} END_TEST
|
|
|
|
/* === Object node with many references === */
|
|
START_TEST(addNodeWithManyReferences) {
|
|
/* Add a parent object */
|
|
UA_ObjectAttributes oattr = UA_ObjectAttributes_default;
|
|
oattr.displayName = UA_LOCALIZEDTEXT("en", "RefTestParent");
|
|
UA_Server_addObjectNode(server,
|
|
UA_NODEID_NUMERIC(1, 70020),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
UA_QUALIFIEDNAME(1, "RefTestParent"),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
|
|
oattr, NULL, NULL);
|
|
|
|
/* Add many child variables to create many references */
|
|
for(int i = 0; i < 20; i++) {
|
|
UA_VariableAttributes vattr = UA_VariableAttributes_default;
|
|
UA_Int32 val = i;
|
|
UA_Variant_setScalar(&vattr.value, &val, &UA_TYPES[UA_TYPES_INT32]);
|
|
char name[32];
|
|
snprintf(name, sizeof(name), "Child%d", i);
|
|
vattr.displayName = UA_LOCALIZEDTEXT_ALLOC("en", name);
|
|
|
|
UA_QualifiedName bn = UA_QUALIFIEDNAME_ALLOC(1, name);
|
|
UA_Server_addVariableNode(server,
|
|
UA_NODEID_NUMERIC(1, (UA_UInt32)(70030 + i)),
|
|
UA_NODEID_NUMERIC(1, 70020),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
|
|
bn,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
|
|
vattr, NULL, NULL);
|
|
UA_QualifiedName_clear(&bn);
|
|
UA_LocalizedText_clear(&vattr.displayName);
|
|
}
|
|
|
|
/* Browse the parent to verify references */
|
|
UA_BrowseDescription bd;
|
|
UA_BrowseDescription_init(&bd);
|
|
bd.nodeId = UA_NODEID_NUMERIC(1, 70020);
|
|
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(br.referencesSize >= 20);
|
|
UA_BrowseResult_clear(&br);
|
|
} END_TEST
|
|
|
|
/* === Method node === */
|
|
static UA_StatusCode
|
|
testMethodCallback(UA_Server *methodServer,
|
|
const UA_NodeId *sessionId, void *sessionContext,
|
|
const UA_NodeId *methodId, void *methodContext,
|
|
const UA_NodeId *objectId, void *objectContext,
|
|
size_t inputSize, const UA_Variant *input,
|
|
size_t outputSize, UA_Variant *output) {
|
|
if(inputSize == 1 && input[0].type == &UA_TYPES[UA_TYPES_INT32]) {
|
|
UA_Int32 inVal = *(UA_Int32 *)input[0].data;
|
|
UA_Int32 outVal = inVal * 2;
|
|
UA_Variant_setScalarCopy(&output[0], &outVal, &UA_TYPES[UA_TYPES_INT32]);
|
|
}
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
START_TEST(addMethodNode_withCallback) {
|
|
UA_Argument inputArg;
|
|
UA_Argument_init(&inputArg);
|
|
inputArg.name = UA_STRING("Input");
|
|
inputArg.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
|
|
inputArg.valueRank = UA_VALUERANK_SCALAR;
|
|
|
|
UA_Argument outputArg;
|
|
UA_Argument_init(&outputArg);
|
|
outputArg.name = UA_STRING("Output");
|
|
outputArg.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
|
|
outputArg.valueRank = UA_VALUERANK_SCALAR;
|
|
|
|
UA_MethodAttributes mattr = UA_MethodAttributes_default;
|
|
mattr.displayName = UA_LOCALIZEDTEXT("en", "TestMethod");
|
|
mattr.executable = true;
|
|
mattr.userExecutable = true;
|
|
|
|
UA_StatusCode res = UA_Server_addMethodNode(server,
|
|
UA_NODEID_NUMERIC(1, 70060),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
|
|
UA_QUALIFIEDNAME(1, "TestMethod"),
|
|
mattr, testMethodCallback,
|
|
1, &inputArg, 1, &outputArg,
|
|
NULL, NULL);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Call the method */
|
|
UA_Int32 inVal = 5;
|
|
UA_Variant inVar;
|
|
UA_Variant_setScalar(&inVar, &inVal, &UA_TYPES[UA_TYPES_INT32]);
|
|
|
|
UA_CallMethodRequest callReq;
|
|
UA_CallMethodRequest_init(&callReq);
|
|
callReq.objectId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
|
|
callReq.methodId = UA_NODEID_NUMERIC(1, 70060);
|
|
callReq.inputArguments = &inVar;
|
|
callReq.inputArgumentsSize = 1;
|
|
|
|
UA_CallMethodResult callRes = UA_Server_call(server, &callReq);
|
|
ck_assert_uint_eq(callRes.statusCode, UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(callRes.outputArgumentsSize, 1);
|
|
ck_assert_int_eq(*(UA_Int32 *)callRes.outputArguments[0].data, 10);
|
|
UA_CallMethodResult_clear(&callRes);
|
|
|
|
/* Write executable to trigger method node copy */
|
|
UA_Boolean newExec = false;
|
|
res = UA_Server_writeExecutable(server, UA_NODEID_NUMERIC(1, 70060), newExec);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
/* === Server lifecycle tests === */
|
|
START_TEST(server_lifecycle) {
|
|
UA_Server *s = UA_Server_newForUnitTest();
|
|
ck_assert_ptr_ne(s, NULL);
|
|
|
|
UA_ServerConfig *config = UA_Server_getConfig(s);
|
|
ck_assert_ptr_ne(config, NULL);
|
|
|
|
/* Start up */
|
|
UA_StatusCode res = UA_Server_run_startup(s);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Run some iterations */
|
|
for(int i = 0; i < 5; i++)
|
|
UA_Server_run_iterate(s, false);
|
|
|
|
/* Shutdown */
|
|
res = UA_Server_run_shutdown(s);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
UA_Server_delete(s);
|
|
} END_TEST
|
|
|
|
/* === Discovery via client === */
|
|
START_TEST(findServers) {
|
|
UA_Client *client = connectClient();
|
|
|
|
UA_ApplicationDescription *appDescs = NULL;
|
|
size_t appDescsSize = 0;
|
|
UA_StatusCode res = UA_Client_findServers(client,
|
|
"opc.tcp://localhost:4840", 0, NULL, 0, NULL,
|
|
&appDescsSize, &appDescs);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
ck_assert(appDescsSize > 0);
|
|
UA_Array_delete(appDescs, appDescsSize, &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
|
|
|
|
disconnectClient(client);
|
|
} END_TEST
|
|
|
|
START_TEST(getEndpoints) {
|
|
UA_Client *client = connectClient();
|
|
|
|
UA_EndpointDescription *endpoints = NULL;
|
|
size_t endpointsSize = 0;
|
|
UA_StatusCode res = UA_Client_getEndpoints(client,
|
|
"opc.tcp://localhost:4840", &endpointsSize, &endpoints);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
ck_assert(endpointsSize > 0);
|
|
UA_Array_delete(endpoints, endpointsSize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
|
|
|
|
disconnectClient(client);
|
|
} END_TEST
|
|
|
|
START_TEST(registerUnregisterNodes) {
|
|
UA_Client *client = connectClient();
|
|
|
|
/* Register nodes */
|
|
UA_RegisterNodesRequest regReq;
|
|
UA_RegisterNodesRequest_init(®Req);
|
|
UA_NodeId nodeIds[2];
|
|
nodeIds[0] = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);
|
|
nodeIds[1] = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS);
|
|
regReq.nodesToRegister = nodeIds;
|
|
regReq.nodesToRegisterSize = 2;
|
|
|
|
UA_RegisterNodesResponse regResp = UA_Client_Service_registerNodes(client, regReq);
|
|
ck_assert_uint_eq(regResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(regResp.registeredNodeIdsSize, 2);
|
|
|
|
/* Unregister */
|
|
UA_UnregisterNodesRequest unregReq;
|
|
UA_UnregisterNodesRequest_init(&unregReq);
|
|
unregReq.nodesToUnregister = regResp.registeredNodeIds;
|
|
unregReq.nodesToUnregisterSize = regResp.registeredNodeIdsSize;
|
|
|
|
UA_UnregisterNodesResponse unregResp =
|
|
UA_Client_Service_unregisterNodes(client, unregReq);
|
|
ck_assert_uint_eq(unregResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
|
|
|
UA_RegisterNodesResponse_clear(®Resp);
|
|
UA_UnregisterNodesResponse_clear(&unregResp);
|
|
|
|
disconnectClient(client);
|
|
} END_TEST
|
|
|
|
/* === Translate browse paths === */
|
|
START_TEST(translateBrowsePaths) {
|
|
UA_Client *client = connectClient();
|
|
|
|
UA_BrowsePath bp;
|
|
UA_BrowsePath_init(&bp);
|
|
bp.startingNode = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
|
|
|
|
UA_RelativePathElement elem;
|
|
UA_RelativePathElement_init(&elem);
|
|
elem.targetName = UA_QUALIFIEDNAME(0, "Server");
|
|
elem.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
|
|
elem.includeSubtypes = true;
|
|
|
|
bp.relativePath.elements = &elem;
|
|
bp.relativePath.elementsSize = 1;
|
|
|
|
UA_TranslateBrowsePathsToNodeIdsRequest tbpReq;
|
|
UA_TranslateBrowsePathsToNodeIdsRequest_init(&tbpReq);
|
|
tbpReq.browsePaths = &bp;
|
|
tbpReq.browsePathsSize = 1;
|
|
|
|
UA_TranslateBrowsePathsToNodeIdsResponse tbpResp =
|
|
UA_Client_Service_translateBrowsePathsToNodeIds(client, tbpReq);
|
|
ck_assert_uint_eq(tbpResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(tbpResp.resultsSize, 1);
|
|
ck_assert_uint_eq(tbpResp.results[0].statusCode, UA_STATUSCODE_GOOD);
|
|
|
|
UA_TranslateBrowsePathsToNodeIdsResponse_clear(&tbpResp);
|
|
disconnectClient(client);
|
|
} END_TEST
|
|
|
|
/* === Browse with continuation === */
|
|
START_TEST(browseWithContinuation) {
|
|
UA_Client *client = connectClient();
|
|
|
|
/* Browse Objects folder expecting multiple refs */
|
|
UA_BrowseRequest bReq;
|
|
UA_BrowseRequest_init(&bReq);
|
|
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;
|
|
bReq.nodesToBrowse = &bd;
|
|
bReq.nodesToBrowseSize = 1;
|
|
bReq.requestedMaxReferencesPerNode = 2; /* Low limit to trigger continuation */
|
|
|
|
UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq);
|
|
ck_assert_uint_eq(bResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
|
ck_assert(bResp.resultsSize > 0);
|
|
|
|
/* If there is a continuation point, use BrowseNext */
|
|
if(bResp.results[0].continuationPoint.length > 0) {
|
|
UA_BrowseNextRequest bnReq;
|
|
UA_BrowseNextRequest_init(&bnReq);
|
|
bnReq.continuationPoints = &bResp.results[0].continuationPoint;
|
|
bnReq.continuationPointsSize = 1;
|
|
bnReq.releaseContinuationPoints = false;
|
|
|
|
UA_BrowseNextResponse bnResp = UA_Client_Service_browseNext(client, bnReq);
|
|
ck_assert_uint_eq(bnResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
|
|
|
/* Release continuation point */
|
|
if(bnResp.resultsSize > 0 &&
|
|
bnResp.results[0].continuationPoint.length > 0) {
|
|
UA_BrowseNextRequest relReq;
|
|
UA_BrowseNextRequest_init(&relReq);
|
|
relReq.continuationPoints = &bnResp.results[0].continuationPoint;
|
|
relReq.continuationPointsSize = 1;
|
|
relReq.releaseContinuationPoints = true;
|
|
UA_BrowseNextResponse relResp = UA_Client_Service_browseNext(client, relReq);
|
|
UA_BrowseNextResponse_clear(&relResp);
|
|
}
|
|
|
|
UA_BrowseNextResponse_clear(&bnResp);
|
|
}
|
|
|
|
UA_BrowseResponse_clear(&bResp);
|
|
disconnectClient(client);
|
|
} END_TEST
|
|
|
|
/* === Delete nodes === */
|
|
START_TEST(addAndDeleteObject) {
|
|
/* Add an object */
|
|
UA_ObjectAttributes oattr = UA_ObjectAttributes_default;
|
|
oattr.displayName = UA_LOCALIZEDTEXT("en", "DeleteMe");
|
|
UA_Server_addObjectNode(server,
|
|
UA_NODEID_NUMERIC(1, 70070),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
UA_QUALIFIEDNAME(1, "DeleteMe"),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
|
|
oattr, NULL, NULL);
|
|
|
|
/* Delete it */
|
|
UA_StatusCode res = UA_Server_deleteNode(server,
|
|
UA_NODEID_NUMERIC(1, 70070), true);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Verify it's gone */
|
|
UA_Variant out;
|
|
UA_Variant_init(&out);
|
|
res = UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 70070), &out);
|
|
ck_assert(res != UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
/* === Add/delete references === */
|
|
START_TEST(addDeleteReference) {
|
|
/* Add two objects */
|
|
UA_ObjectAttributes oattr = UA_ObjectAttributes_default;
|
|
oattr.displayName = UA_LOCALIZEDTEXT("en", "RefSrc");
|
|
UA_Server_addObjectNode(server,
|
|
UA_NODEID_NUMERIC(1, 70080),
|
|
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);
|
|
|
|
oattr.displayName = UA_LOCALIZEDTEXT("en", "RefDst");
|
|
UA_Server_addObjectNode(server,
|
|
UA_NODEID_NUMERIC(1, 70081),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
UA_QUALIFIEDNAME(1, "RefDst"),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
|
|
oattr, NULL, NULL);
|
|
|
|
/* Add a reference */
|
|
UA_StatusCode res = UA_Server_addReference(server,
|
|
UA_NODEID_NUMERIC(1, 70080),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
UA_EXPANDEDNODEID_NUMERIC(1, 70081), true);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Delete the reference */
|
|
res = UA_Server_deleteReference(server,
|
|
UA_NODEID_NUMERIC(1, 70080),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
true,
|
|
UA_EXPANDEDNODEID_NUMERIC(1, 70081), true);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
/* === Read/Write via client === */
|
|
START_TEST(clientReadWriteAttributes) {
|
|
UA_Client *client = connectClient();
|
|
|
|
/* Read display name of Objects folder */
|
|
UA_LocalizedText dn;
|
|
UA_StatusCode res = UA_Client_readDisplayNameAttribute(client,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), &dn);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
UA_LocalizedText_clear(&dn);
|
|
|
|
/* Read description */
|
|
UA_LocalizedText desc;
|
|
res = UA_Client_readDescriptionAttribute(client,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), &desc);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
UA_LocalizedText_clear(&desc);
|
|
|
|
/* Read browseName */
|
|
UA_QualifiedName bn;
|
|
res = UA_Client_readBrowseNameAttribute(client,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), &bn);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
UA_QualifiedName_clear(&bn);
|
|
|
|
/* Read NodeClass */
|
|
UA_NodeClass nc;
|
|
res = UA_Client_readNodeClassAttribute(client,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), &nc);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(nc, UA_NODECLASS_OBJECT);
|
|
|
|
/* Read NodeId */
|
|
UA_NodeId nodeId;
|
|
res = UA_Client_readNodeIdAttribute(client,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), &nodeId);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
UA_NodeId_clear(&nodeId);
|
|
|
|
/* Read WriteMask */
|
|
UA_UInt32 writeMask;
|
|
res = UA_Client_readWriteMaskAttribute(client,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), &writeMask);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Read UserWriteMask */
|
|
UA_UInt32 userWriteMask;
|
|
res = UA_Client_readUserWriteMaskAttribute(client,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), &userWriteMask);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Read EventNotifier */
|
|
UA_Byte eventNotifier;
|
|
res = UA_Client_readEventNotifierAttribute(client,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), &eventNotifier);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Read server status */
|
|
UA_Variant val;
|
|
UA_Variant_init(&val);
|
|
res = UA_Client_readValueAttribute(client,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS), &val);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
UA_Variant_clear(&val);
|
|
|
|
/* Read isAbstract on BaseObjectType */
|
|
UA_Boolean isAbstract;
|
|
res = UA_Client_readIsAbstractAttribute(client,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE), &isAbstract);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Read ContainsNoLoops on Organizes reftype.
|
|
* This attribute was removed from ReferenceType nodes in OPC UA 1.04.
|
|
* open62541 may return BadAttributeIdInvalid. */
|
|
UA_Boolean containsNoLoops;
|
|
res = UA_Client_readContainsNoLoopsAttribute(client,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), &containsNoLoops);
|
|
(void)res;
|
|
|
|
/* Read Symmetric on Organizes reftype */
|
|
UA_Boolean symmetric;
|
|
res = UA_Client_readSymmetricAttribute(client,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), &symmetric);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
|
|
/* Read InverseName */
|
|
UA_LocalizedText inverseName;
|
|
res = UA_Client_readInverseNameAttribute(client,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), &inverseName);
|
|
ck_assert_uint_eq(res, UA_STATUSCODE_GOOD);
|
|
UA_LocalizedText_clear(&inverseName);
|
|
|
|
disconnectClient(client);
|
|
} END_TEST
|
|
|
|
/* === Suite definition === */
|
|
static Suite *testSuite_serverOps(void) {
|
|
TCase *tc_nodes = tcase_create("NodeOps");
|
|
tcase_add_checked_fixture(tc_nodes, setup_serveronly, teardown_serveronly);
|
|
tcase_add_test(tc_nodes, addViewNode);
|
|
tcase_add_test(tc_nodes, addObjectTypeNode);
|
|
tcase_add_test(tc_nodes, addVariableTypeNode);
|
|
tcase_add_test(tc_nodes, addDataTypeNode);
|
|
tcase_add_test(tc_nodes, addReferenceTypeNode);
|
|
tcase_add_test(tc_nodes, readAttributes_variable);
|
|
tcase_add_test(tc_nodes, writeAttributes_variable);
|
|
tcase_add_test(tc_nodes, readAttribute_nonexistent);
|
|
tcase_add_test(tc_nodes, addNodeWithManyReferences);
|
|
tcase_add_test(tc_nodes, addMethodNode_withCallback);
|
|
tcase_add_test(tc_nodes, addAndDeleteObject);
|
|
tcase_add_test(tc_nodes, addDeleteReference);
|
|
|
|
TCase *tc_lifecycle = tcase_create("Lifecycle");
|
|
tcase_add_test(tc_lifecycle, server_lifecycle);
|
|
|
|
TCase *tc_client = tcase_create("ClientOps");
|
|
tcase_add_checked_fixture(tc_client, setup, teardown);
|
|
tcase_set_timeout(tc_client, 30);
|
|
tcase_add_test(tc_client, findServers);
|
|
tcase_add_test(tc_client, getEndpoints);
|
|
tcase_add_test(tc_client, registerUnregisterNodes);
|
|
tcase_add_test(tc_client, translateBrowsePaths);
|
|
tcase_add_test(tc_client, browseWithContinuation);
|
|
tcase_add_test(tc_client, clientReadWriteAttributes);
|
|
|
|
Suite *s = suite_create("Server Ops Extended");
|
|
suite_add_tcase(s, tc_nodes);
|
|
suite_add_tcase(s, tc_lifecycle);
|
|
suite_add_tcase(s, tc_client);
|
|
return s;
|
|
}
|
|
|
|
int main(void) {
|
|
int number_failed = 0;
|
|
Suite *s = testSuite_serverOps();
|
|
SRunner *sr = srunner_create(s);
|
|
srunner_set_fork_status(sr, CK_NOFORK);
|
|
srunner_run_all(sr, CK_NORMAL);
|
|
number_failed += srunner_ntests_failed(sr);
|
|
srunner_free(sr);
|
|
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
}
|