mirror of
https://github.com/open62541/open62541.git
synced 2026-10-16 20:33:18 +08:00
1209 lines
48 KiB
C
1209 lines
48 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/.
|
|
*
|
|
* Copyright 2019 (c) basysKom GmbH <opensource@basyskom.com> (Author: Frank Meerkötter)
|
|
* Copyright 2026 (c) o6 Automation GmbH (Author: Andreas Ebner)
|
|
*/
|
|
|
|
#include <open62541/server.h>
|
|
#include <open62541/server_config_default.h>
|
|
#include <open62541/client.h>
|
|
#include <open62541/client_config_default.h>
|
|
#include <open62541/types.h>
|
|
#include <open62541/transport_generated.h>
|
|
#include <open62541/plugin/accesscontrol_default.h>
|
|
|
|
#include "server/ua_server_internal.h"
|
|
#include "server/ua_services.h"
|
|
#include "test_helpers.h"
|
|
#include "ua_types_encoding_binary.h"
|
|
#include "testing_networklayers.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
|
|
#include "check.h"
|
|
|
|
static UA_Server *server = NULL;
|
|
|
|
#define MAX_NOTIFICATIONS 16
|
|
static UA_ApplicationNotificationType receivedNotifications[MAX_NOTIFICATIONS];
|
|
static size_t receivedNotificationsCount = 0;
|
|
|
|
static void
|
|
serverNotificationCallback(UA_Server *server, UA_ApplicationNotificationType type,
|
|
const UA_KeyValueMap payload) {
|
|
if(receivedNotificationsCount < MAX_NOTIFICATIONS)
|
|
receivedNotifications[receivedNotificationsCount++] = type;
|
|
}
|
|
|
|
static void setup(void) {
|
|
server = UA_Server_newForUnitTest();
|
|
UA_ServerConfig *cfg = UA_Server_getConfig(server);
|
|
cfg->globalNotificationCallback = serverNotificationCallback;
|
|
ck_assert(server != NULL);
|
|
receivedNotificationsCount = 0;
|
|
}
|
|
|
|
static void teardown(void) {
|
|
UA_Server_delete(server);
|
|
}
|
|
|
|
START_TEST(checkGetConfig) {
|
|
ck_assert_ptr_eq(UA_Server_getConfig(NULL), NULL);
|
|
UA_ServerConfig *config = UA_Server_getConfig(server);
|
|
ck_assert_ptr_ne(config, NULL);
|
|
ck_assert(config->tcpEnabled);
|
|
} END_TEST
|
|
|
|
START_TEST(checkTcpDisabled) {
|
|
UA_ServerConfig *config = UA_Server_getConfig(server);
|
|
config->tcpEnabled = false;
|
|
|
|
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(config->applicationDescription.discoveryUrlsSize, 0);
|
|
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
typedef struct {
|
|
UA_Driver driver;
|
|
UA_StatusCode startResult;
|
|
unsigned starts;
|
|
unsigned stops;
|
|
} TestDriver;
|
|
|
|
static UA_StatusCode
|
|
testDriverStart(UA_Driver *drv) {
|
|
TestDriver *test = (TestDriver*)drv;
|
|
test->starts++;
|
|
ck_assert_ptr_eq(drv->server, server);
|
|
ck_assert_ptr_eq(UA_Server_getDrivers(server), drv);
|
|
if(test->startResult == UA_STATUSCODE_GOOD)
|
|
drv->state = UA_LIFECYCLESTATE_STARTED;
|
|
return test->startResult;
|
|
}
|
|
|
|
static void
|
|
testDriverStop(UA_Driver *drv) {
|
|
((TestDriver*)drv)->stops++;
|
|
drv->state = UA_LIFECYCLESTATE_STOPPED;
|
|
}
|
|
|
|
START_TEST(addDriverPropagatesStartFailure) {
|
|
UA_Server_getConfig(server)->tcpEnabled = false;
|
|
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
|
|
UA_Driver *previous = UA_Server_getDrivers(server);
|
|
TestDriver test;
|
|
memset(&test, 0, sizeof(test));
|
|
test.driver.start = testDriverStart;
|
|
test.driver.stop = testDriverStop;
|
|
test.startResult = UA_STATUSCODE_BADCONFIGURATIONERROR;
|
|
|
|
ck_assert_uint_eq(UA_Server_addDriver(server, &test.driver),
|
|
UA_STATUSCODE_BADCONFIGURATIONERROR);
|
|
ck_assert_uint_eq(test.starts, 1);
|
|
ck_assert_ptr_eq(UA_Server_getDrivers(server), previous);
|
|
ck_assert_ptr_eq(test.driver.next, NULL);
|
|
ck_assert_ptr_eq(test.driver.server, server);
|
|
ck_assert_uint_eq(UA_Server_removeDriver(server, &test.driver),
|
|
UA_STATUSCODE_BADNOTFOUND);
|
|
|
|
/* The same instance can be retried after correcting its configuration. */
|
|
test.startResult = UA_STATUSCODE_GOOD;
|
|
ck_assert_uint_eq(UA_Server_addDriver(server, &test.driver), UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(test.starts, 2);
|
|
ck_assert_ptr_eq(UA_Server_getDrivers(server), &test.driver);
|
|
ck_assert_ptr_eq(test.driver.next, previous);
|
|
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(test.stops, 1);
|
|
ck_assert_uint_eq(UA_Server_removeDriver(server, &test.driver), UA_STATUSCODE_GOOD);
|
|
ck_assert_ptr_eq(UA_Server_getDrivers(server), previous);
|
|
} END_TEST
|
|
|
|
START_TEST(checkGetNamespaceByName) {
|
|
size_t notFoundIndex = 62541;
|
|
UA_StatusCode notFound = UA_Server_getNamespaceByName(server, UA_STRING("http://opcfoundation.org/UA/invalid"), ¬FoundIndex);
|
|
ck_assert_uint_eq(notFoundIndex, 62541); // not changed
|
|
ck_assert_uint_eq(notFound, UA_STATUSCODE_BADNOTFOUND);
|
|
|
|
size_t foundIndex = 62541;
|
|
UA_StatusCode found = UA_Server_getNamespaceByName(server, UA_STRING("http://opcfoundation.org/UA/"), &foundIndex);
|
|
ck_assert_uint_eq(foundIndex, 0); // this namespace always has index 0 (defined by the standard)
|
|
ck_assert_uint_eq(found, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
START_TEST(checkGetNamespaceById) {
|
|
UA_String searchResultNamespace;
|
|
UA_StatusCode notFound = UA_Server_getNamespaceByIndex(server, 10, &searchResultNamespace);
|
|
ck_assert_uint_eq(notFound, UA_STATUSCODE_BADNOTFOUND);
|
|
|
|
UA_StatusCode found1 = UA_Server_getNamespaceByIndex(server, 1, &searchResultNamespace);
|
|
ck_assert_uint_eq(found1, UA_STATUSCODE_GOOD);
|
|
UA_String_clear(&searchResultNamespace);
|
|
|
|
UA_StatusCode notFound2 = UA_Server_getNamespaceByIndex(server, 2, &searchResultNamespace);
|
|
ck_assert_uint_eq(notFound2, UA_STATUSCODE_BADNOTFOUND);
|
|
|
|
UA_String compareNamespace = UA_STRING("http://opcfoundation.org/UA/");
|
|
UA_StatusCode found = UA_Server_getNamespaceByIndex(server, 0, &searchResultNamespace);
|
|
ck_assert(UA_String_equal(&compareNamespace, &searchResultNamespace));
|
|
ck_assert_uint_eq(found, UA_STATUSCODE_GOOD);
|
|
UA_String_clear(&searchResultNamespace);
|
|
} END_TEST
|
|
|
|
static void timedCallbackHandler(UA_Server *s, void *data) {
|
|
*((UA_Boolean*)data) = false; // stop the server via a timedCallback
|
|
}
|
|
|
|
START_TEST(checkServer_run) {
|
|
UA_Boolean running = true;
|
|
// 0 is in the past so the server will terminate on the first iteration
|
|
UA_StatusCode ret;
|
|
ret = UA_Server_addTimedCallback(server, &timedCallbackHandler, &running, 0, NULL);
|
|
ck_assert_int_eq(ret, UA_STATUSCODE_GOOD);
|
|
ret = UA_Server_run(server, &running);
|
|
ck_assert_int_eq(ret, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
START_TEST(checkGetStatistics) {
|
|
/* Get server statistics without running - should return valid stats */
|
|
UA_ServerStatistics stats = UA_Server_getStatistics(server);
|
|
|
|
/* Initial stats should be zero/empty */
|
|
ck_assert_uint_eq(stats.ss.currentSessionCount, 0);
|
|
ck_assert_uint_eq(stats.ss.cumulatedSessionCount, 0);
|
|
ck_assert_uint_eq(stats.ss.securityRejectedSessionCount, 0);
|
|
ck_assert_uint_eq(stats.ss.rejectedSessionCount, 0);
|
|
ck_assert_uint_eq(stats.ss.sessionTimeoutCount, 0);
|
|
ck_assert_uint_eq(stats.ss.sessionAbortCount, 0);
|
|
|
|
/* SecureChannel stats should also be initialized */
|
|
ck_assert_uint_eq(stats.scs.currentChannelCount, 0);
|
|
ck_assert_uint_eq(stats.scs.cumulatedChannelCount, 0);
|
|
ck_assert_uint_eq(stats.scs.rejectedChannelCount, 0);
|
|
ck_assert_uint_eq(stats.scs.channelTimeoutCount, 0);
|
|
ck_assert_uint_eq(stats.scs.channelAbortCount, 0);
|
|
ck_assert_uint_eq(stats.scs.channelPurgeCount, 0);
|
|
} END_TEST
|
|
|
|
START_TEST(checkGetLifecycleState) {
|
|
/* Before startup, server should be in stopped state */
|
|
UA_LifecycleState state = UA_Server_getLifecycleState(server);
|
|
ck_assert_int_eq(state, UA_LIFECYCLESTATE_STOPPED);
|
|
ck_assert_int_eq(UA_Server_getConfig(server)->eventLoop->state,
|
|
UA_EVENTLOOPSTATE_FRESH);
|
|
|
|
/* After startup, server should be in started state */
|
|
UA_StatusCode ret = UA_Server_run_startup(server);
|
|
ck_assert_int_eq(ret, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(UA_Server_getConfig(server)->eventLoop->state,
|
|
UA_EVENTLOOPSTATE_STARTED);
|
|
|
|
state = UA_Server_getLifecycleState(server);
|
|
ck_assert_int_eq(state, UA_LIFECYCLESTATE_STARTED);
|
|
|
|
/* After shutdown, server should transition back to stopped */
|
|
ret = UA_Server_run_shutdown(server);
|
|
ck_assert_int_eq(ret, UA_STATUSCODE_GOOD);
|
|
|
|
state = UA_Server_getLifecycleState(server);
|
|
ck_assert_int_eq(state, UA_LIFECYCLESTATE_STOPPED);
|
|
} END_TEST
|
|
|
|
START_TEST(checkSharedExternalEventLoop) {
|
|
UA_ServerConfig *firstConfig = UA_Server_getConfig(server);
|
|
UA_EventLoop *el = firstConfig->eventLoop;
|
|
firstConfig->externalEventLoop = true;
|
|
firstConfig->tcpEnabled = false;
|
|
|
|
UA_ServerConfig secondConfig = {0};
|
|
secondConfig.eventLoop = el;
|
|
secondConfig.externalEventLoop = true;
|
|
ck_assert_uint_eq(UA_ServerConfig_setMinimal(&secondConfig, 0, NULL),
|
|
UA_STATUSCODE_GOOD);
|
|
secondConfig.tcpEnabled = false;
|
|
UA_Server *second = UA_Server_newWithConfig(&secondConfig);
|
|
ck_assert_ptr_ne(second, NULL);
|
|
|
|
UA_ClientConfig clientConfig = {0};
|
|
clientConfig.eventLoop = el;
|
|
clientConfig.externalEventLoop = true;
|
|
ck_assert_uint_eq(UA_ClientConfig_setDefault(&clientConfig),
|
|
UA_STATUSCODE_GOOD);
|
|
UA_Client *client = UA_Client_newWithConfig(&clientConfig);
|
|
ck_assert_ptr_ne(client, NULL);
|
|
|
|
/* Configuring all three users must not start the shared EventLoop. */
|
|
ck_assert_int_eq(el->state, UA_EVENTLOOPSTATE_FRESH);
|
|
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(el->state, UA_EVENTLOOPSTATE_STARTED);
|
|
ck_assert_uint_eq(UA_Server_run_startup(second), UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(UA_Client_run_iterate(client, 0), UA_STATUSCODE_GOOD);
|
|
|
|
/* Shutting down one server and deleting the client must leave the shared
|
|
* EventLoop available to the other server. */
|
|
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(el->state, UA_EVENTLOOPSTATE_STARTED);
|
|
UA_Client_delete(client);
|
|
ck_assert_int_eq(el->state, UA_EVENTLOOPSTATE_STARTED);
|
|
UA_Server_run_iterate(second, false);
|
|
ck_assert_int_eq(UA_Server_getLifecycleState(second),
|
|
UA_LIFECYCLESTATE_STARTED);
|
|
|
|
ck_assert_uint_eq(UA_Server_run_shutdown(second), UA_STATUSCODE_GOOD);
|
|
UA_Server_delete(second);
|
|
ck_assert_int_eq(el->state, UA_EVENTLOOPSTATE_STARTED);
|
|
|
|
/* The fixture's last server can now stop and free the EventLoop. */
|
|
firstConfig->externalEventLoop = false;
|
|
} END_TEST
|
|
|
|
static void
|
|
checkLifecycleNotifications(void) {
|
|
ck_assert_uint_eq(receivedNotificationsCount, 3);
|
|
ck_assert_int_eq(receivedNotifications[0], UA_APPLICATIONNOTIFICATIONTYPE_LIFECYCLE_STARTED);
|
|
ck_assert_int_eq(receivedNotifications[1], UA_APPLICATIONNOTIFICATIONTYPE_LIFECYCLE_STOPPING);
|
|
ck_assert_int_eq(receivedNotifications[2], UA_APPLICATIONNOTIFICATIONTYPE_LIFECYCLE_STOPPED);
|
|
}
|
|
|
|
static void
|
|
stopExternalEventLoop(void) {
|
|
UA_ServerConfig *config = UA_Server_getConfig(server);
|
|
UA_EventLoop *el = config->eventLoop;
|
|
ck_assert_int_eq(el->state, UA_EVENTLOOPSTATE_STARTED);
|
|
el->stop(el);
|
|
for(unsigned i = 0; i < 32 && el->state != UA_EVENTLOOPSTATE_STOPPED; i++)
|
|
ck_assert_uint_eq(el->run(el, 0), UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(el->state, UA_EVENTLOOPSTATE_STOPPED);
|
|
/* Return ownership for the normal config cleanup, including the logger. */
|
|
config->externalEventLoop = false;
|
|
}
|
|
|
|
static void
|
|
iterateShutdownServer(UA_Boolean driveEventLoop) {
|
|
if(!driveEventLoop) {
|
|
UA_Server_run_iterate(server, false);
|
|
return;
|
|
}
|
|
UA_EventLoop *el = UA_Server_getConfig(server)->eventLoop;
|
|
ck_assert_uint_eq(el->run(el, 0), UA_STATUSCODE_GOOD);
|
|
if(UA_Server_getLifecycleState(server) == UA_LIFECYCLESTATE_STOPPING)
|
|
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
|
|
}
|
|
|
|
START_TEST(checkExternalShutdownTcp) {
|
|
UA_ServerConfig *config = UA_Server_getConfig(server);
|
|
config->externalEventLoop = true;
|
|
ck_assert_uint_eq(config->serverUrlsSize, 1);
|
|
UA_String_clear(&config->serverUrls[0]);
|
|
config->serverUrls[0] = UA_STRING_ALLOC("opc.tcp://127.0.0.1:0");
|
|
|
|
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_gt(config->applicationDescription.discoveryUrlsSize, 0);
|
|
for(UA_Driver *drv = UA_Server_getDrivers(server); drv; drv = drv->next)
|
|
ck_assert_int_eq(drv->state, UA_LIFECYCLESTATE_STARTED);
|
|
|
|
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(UA_Server_getLifecycleState(server), UA_LIFECYCLESTATE_STOPPING);
|
|
for(unsigned i = 0; i < 32 &&
|
|
UA_Server_getLifecycleState(server) != UA_LIFECYCLESTATE_STOPPED; i++)
|
|
iterateShutdownServer((UA_Boolean)_i);
|
|
ck_assert_int_eq(UA_Server_getLifecycleState(server), UA_LIFECYCLESTATE_STOPPED);
|
|
for(UA_Driver *drv = UA_Server_getDrivers(server); drv; drv = drv->next)
|
|
ck_assert_int_eq(drv->state, UA_LIFECYCLESTATE_STOPPED);
|
|
iterateShutdownServer((UA_Boolean)_i);
|
|
iterateShutdownServer((UA_Boolean)_i);
|
|
checkLifecycleNotifications();
|
|
stopExternalEventLoop();
|
|
ck_assert_uint_eq(UA_Server_delete(server), UA_STATUSCODE_GOOD);
|
|
server = NULL;
|
|
} END_TEST
|
|
|
|
typedef struct {
|
|
UA_Driver driver;
|
|
UA_DelayedCallback callback;
|
|
UA_Boolean mayStop;
|
|
unsigned turns;
|
|
unsigned stops;
|
|
} DelayedStopDriver;
|
|
|
|
static void
|
|
delayedDriverFinish(void *application, void *context) {
|
|
DelayedStopDriver *test = (DelayedStopDriver*)context;
|
|
UA_Server *s = (UA_Server*)application;
|
|
UA_EventLoop *el = UA_Server_getConfig(s)->eventLoop;
|
|
lockServer(s);
|
|
test->turns++;
|
|
if(test->mayStop && test->turns >= 2)
|
|
test->driver.state = UA_LIFECYCLESTATE_STOPPED;
|
|
else
|
|
el->addDelayedCallback(el, &test->callback);
|
|
unlockServer(s);
|
|
}
|
|
|
|
static void
|
|
delayedDriverStop(UA_Driver *drv) {
|
|
DelayedStopDriver *test = (DelayedStopDriver*)drv;
|
|
test->stops++;
|
|
drv->state = UA_LIFECYCLESTATE_STOPPING;
|
|
test->callback.callback = delayedDriverFinish;
|
|
test->callback.application = drv->server;
|
|
test->callback.context = test;
|
|
UA_EventLoop *el = UA_Server_getConfig(drv->server)->eventLoop;
|
|
el->addDelayedCallback(el, &test->callback);
|
|
}
|
|
|
|
START_TEST(checkShutdownDelayedDriver) {
|
|
UA_ServerConfig *config = UA_Server_getConfig(server);
|
|
config->tcpEnabled = false;
|
|
config->externalEventLoop = (_i != 0);
|
|
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
|
|
|
|
DelayedStopDriver test;
|
|
memset(&test, 0, sizeof(test));
|
|
test.driver.state = UA_LIFECYCLESTATE_STARTED;
|
|
test.driver.stop = delayedDriverStop;
|
|
test.mayStop = !config->externalEventLoop;
|
|
ck_assert_uint_eq(UA_Server_addDriver(server, &test.driver), UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
|
|
|
|
if(config->externalEventLoop) {
|
|
ck_assert_uint_eq(test.turns, 0);
|
|
/* EventLoops may process delayed callbacks more than once per iteration. */
|
|
for(unsigned i = 0; i < 3; i++) {
|
|
unsigned turns = test.turns;
|
|
iterateShutdownServer(_i == 2);
|
|
ck_assert_uint_gt(test.turns, turns);
|
|
ck_assert_int_eq(UA_Server_getLifecycleState(server), UA_LIFECYCLESTATE_STOPPING);
|
|
}
|
|
ck_assert_uint_eq(receivedNotificationsCount, 2);
|
|
/* The outer loop can process the work that permits the driver to stop. */
|
|
unsigned turnsBeforeStop = test.turns;
|
|
test.mayStop = true;
|
|
iterateShutdownServer(_i == 2);
|
|
ck_assert_uint_eq(test.turns, turnsBeforeStop + 1);
|
|
iterateShutdownServer(_i == 2);
|
|
stopExternalEventLoop();
|
|
} else {
|
|
ck_assert_uint_eq(test.turns, 2);
|
|
ck_assert_int_eq(config->eventLoop->state, UA_EVENTLOOPSTATE_STOPPED);
|
|
}
|
|
ck_assert_int_eq(UA_Server_getLifecycleState(server), UA_LIFECYCLESTATE_STOPPED);
|
|
ck_assert_uint_eq(test.stops, 1);
|
|
checkLifecycleNotifications();
|
|
ck_assert_uint_eq(UA_Server_removeDriver(server, &test.driver), UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(UA_Server_delete(server), UA_STATUSCODE_GOOD);
|
|
server = NULL;
|
|
} END_TEST
|
|
|
|
static size_t
|
|
countNoneEndpointTokens(UA_UserTokenType tokenType) {
|
|
UA_GetEndpointsRequest request;
|
|
UA_GetEndpointsRequest_init(&request);
|
|
request.endpointUrl = UA_STRING("opc.tcp://localhost:4840");
|
|
UA_GetEndpointsResponse response;
|
|
UA_GetEndpointsResponse_init(&response);
|
|
lockServer(server);
|
|
Service_GetEndpoints(server, &server->adminSession, &request, &response);
|
|
unlockServer(server);
|
|
ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
|
|
|
size_t count = 0;
|
|
for(size_t i = 0; i < response.endpointsSize; i++) {
|
|
const UA_EndpointDescription *ep = &response.endpoints[i];
|
|
if(!UA_String_equal(&ep->securityPolicyUri, &UA_SECURITY_POLICY_NONE_URI))
|
|
continue;
|
|
for(size_t j = 0; j < ep->userIdentityTokensSize; j++) {
|
|
if(ep->userIdentityTokens[j].tokenType == tokenType)
|
|
count++;
|
|
}
|
|
}
|
|
UA_GetEndpointsResponse_clear(&response);
|
|
return count;
|
|
}
|
|
|
|
/* Without an encrypting SecurityPolicy, ActivateSession rejects x509 and
|
|
* password tokens on #None. So the #None endpoint does not offer them. */
|
|
START_TEST(checkNoneEndpointUserTokens) {
|
|
UA_ServerConfig *config = UA_Server_getConfig(server);
|
|
UA_UsernamePasswordLogin login = {UA_STRING_STATIC("user"),
|
|
UA_STRING_STATIC("password")};
|
|
ck_assert_uint_eq(UA_AccessControl_default(config, true, NULL, 1, &login),
|
|
UA_STATUSCODE_GOOD);
|
|
|
|
/* The AccessControl offers x509, as the session PKI accepts all */
|
|
UA_Boolean haveX509 = false;
|
|
for(size_t i = 0; i < config->accessControl.userTokenPoliciesSize; i++) {
|
|
if(config->accessControl.userTokenPolicies[i].tokenType ==
|
|
UA_USERTOKENTYPE_CERTIFICATE)
|
|
haveX509 = true;
|
|
}
|
|
ck_assert(haveX509);
|
|
|
|
config->allowNonePolicyPassword = false;
|
|
ck_assert_uint_gt(countNoneEndpointTokens(UA_USERTOKENTYPE_ANONYMOUS), 0);
|
|
ck_assert_uint_eq(countNoneEndpointTokens(UA_USERTOKENTYPE_USERNAME), 0);
|
|
ck_assert_uint_eq(countNoneEndpointTokens(UA_USERTOKENTYPE_CERTIFICATE), 0);
|
|
|
|
/* Passwords can be allowed without encryption, x509 tokens cannot */
|
|
config->allowNonePolicyPassword = true;
|
|
ck_assert_uint_gt(countNoneEndpointTokens(UA_USERTOKENTYPE_USERNAME), 0);
|
|
ck_assert_uint_eq(countNoneEndpointTokens(UA_USERTOKENTYPE_CERTIFICATE), 0);
|
|
} END_TEST
|
|
|
|
static void
|
|
setLoopbackUrl(UA_ServerConfig *config, UA_UInt16 port) {
|
|
char url[64];
|
|
snprintf(url, sizeof(url), "opc.tcp://127.0.0.1:%u", (unsigned)port);
|
|
UA_Array_delete(config->serverUrls, config->serverUrlsSize,
|
|
&UA_TYPES[UA_TYPES_STRING]);
|
|
config->serverUrls = UA_String_new();
|
|
ck_assert_ptr_ne(config->serverUrls, NULL);
|
|
config->serverUrlsSize = 1;
|
|
config->serverUrls[0] = UA_STRING_ALLOC(url);
|
|
config->tcpReuseAddr = false;
|
|
}
|
|
|
|
/* The second server cannot bind the port of the first one. Without a server
|
|
* socket its startup fails and leaves it stopped. */
|
|
START_TEST(checkStartupFailsWithoutServerSocket) {
|
|
/* Let the OS choose an unused port for the first server */
|
|
UA_ServerConfig *config = UA_Server_getConfig(server);
|
|
setLoopbackUrl(config, 0);
|
|
ck_assert_uint_eq(UA_Server_run_startup(server), UA_STATUSCODE_GOOD);
|
|
|
|
UA_UInt16 port = 0;
|
|
UA_ApplicationDescription *ad = &config->applicationDescription;
|
|
for(size_t i = 0; i < ad->discoveryUrlsSize; i++) {
|
|
UA_String hostname;
|
|
UA_UInt16 urlPort = 0;
|
|
if(UA_parseEndpointUrl(&ad->discoveryUrls[i], &hostname,
|
|
&urlPort, NULL) == UA_STATUSCODE_GOOD &&
|
|
urlPort != 0)
|
|
port = urlPort;
|
|
}
|
|
ck_assert_uint_ne(port, 0);
|
|
|
|
UA_Server *second = UA_Server_newForUnitTest();
|
|
ck_assert_ptr_ne(second, NULL);
|
|
setLoopbackUrl(UA_Server_getConfig(second), port);
|
|
UA_Server_getConfig(second)->globalNotificationCallback =
|
|
serverNotificationCallback;
|
|
receivedNotificationsCount = 0;
|
|
ck_assert_uint_ne(UA_Server_run_startup(second), UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(UA_Server_getLifecycleState(second),
|
|
UA_LIFECYCLESTATE_STOPPED);
|
|
ck_assert_uint_eq(receivedNotificationsCount, 2);
|
|
ck_assert_int_eq(receivedNotifications[0],
|
|
UA_APPLICATIONNOTIFICATIONTYPE_LIFECYCLE_STOPPING);
|
|
ck_assert_int_eq(receivedNotifications[1],
|
|
UA_APPLICATIONNOTIFICATIONTYPE_LIFECYCLE_STOPPED);
|
|
|
|
/* Once the port is free, the second server can be started */
|
|
ck_assert_uint_eq(UA_Server_run_shutdown(server), UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(UA_Server_run_startup(second), UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(UA_Server_getLifecycleState(second),
|
|
UA_LIFECYCLESTATE_STARTED);
|
|
ck_assert_uint_eq(UA_Server_run_shutdown(second), UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(UA_Server_delete(second), UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
/* ---- Additional coverage tests ---- */
|
|
|
|
START_TEST(checkGetDataTypes) {
|
|
const UA_DataTypeArray *types = UA_Server_getDataTypes(server);
|
|
/* Server always has at least the built-in types chain */
|
|
(void)types; /* may be NULL if no custom types, that's fine */
|
|
} END_TEST
|
|
|
|
START_TEST(checkFindDataType) {
|
|
/* Find a well-known built-in type: Int32 */
|
|
UA_NodeId int32Id = UA_NODEID_NUMERIC(0, UA_NS0ID_INT32);
|
|
const UA_DataType *dt = UA_Server_findDataType(server, &int32Id);
|
|
/* Built-in types are found via the default DataTypeArray */
|
|
if(dt) {
|
|
ck_assert_uint_eq(dt->memSize, sizeof(UA_Int32));
|
|
}
|
|
|
|
/* Non-existing type should return NULL */
|
|
UA_NodeId fakeId = UA_NODEID_NUMERIC(0, 99999);
|
|
const UA_DataType *dtFake = UA_Server_findDataType(server, &fakeId);
|
|
ck_assert_ptr_eq(dtFake, NULL);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerReadValue) {
|
|
/* Read the ServerStatus CurrentTime value from the server */
|
|
UA_Variant value;
|
|
UA_StatusCode retval = UA_Server_readValue(server,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME), &value);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert(UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_DATETIME]));
|
|
UA_Variant_clear(&value);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerReadDisplayName) {
|
|
UA_LocalizedText displayName;
|
|
UA_StatusCode retval = UA_Server_readDisplayName(server,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), &displayName);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert(displayName.text.length > 0);
|
|
UA_LocalizedText_clear(&displayName);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerReadDescription) {
|
|
UA_LocalizedText desc;
|
|
UA_StatusCode retval = UA_Server_readDescription(server,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), &desc);
|
|
/* May or may not have description, but shouldn't fail */
|
|
ck_assert(retval == UA_STATUSCODE_GOOD || retval == UA_STATUSCODE_BADATTRIBUTEIDINVALID);
|
|
if(retval == UA_STATUSCODE_GOOD)
|
|
UA_LocalizedText_clear(&desc);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerReadWriteRank) {
|
|
UA_StatusCode retval;
|
|
|
|
/* Read browse name */
|
|
UA_QualifiedName browseName;
|
|
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);
|
|
|
|
/* Read node class */
|
|
UA_NodeClass nodeClass;
|
|
retval = UA_Server_readNodeClass(server,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), &nodeClass);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(nodeClass, UA_NODECLASS_OBJECT);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerReadNodeId) {
|
|
/* Read the NodeId attribute of a node */
|
|
UA_NodeId outNodeId;
|
|
UA_StatusCode retval = UA_Server_readNodeId(server,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), &outNodeId);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(outNodeId.identifier.numeric, UA_NS0ID_OBJECTSFOLDER);
|
|
UA_NodeId_clear(&outNodeId);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerAddRemoveNamespace) {
|
|
/* Add a namespace */
|
|
UA_UInt16 nsIdx = UA_Server_addNamespace(server, "http://test.example.com");
|
|
ck_assert_uint_gt(nsIdx, 0);
|
|
|
|
/* Find it */
|
|
size_t foundIdx = 0;
|
|
UA_StatusCode retval = UA_Server_getNamespaceByName(server,
|
|
UA_STRING("http://test.example.com"), &foundIdx);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(foundIdx, nsIdx);
|
|
|
|
/* Add the same namespace again - should return same index */
|
|
UA_UInt16 nsIdx2 = UA_Server_addNamespace(server, "http://test.example.com");
|
|
ck_assert_uint_eq(nsIdx, nsIdx2);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerWriteValue) {
|
|
/* Add a variable node */
|
|
UA_VariableAttributes attr = UA_VariableAttributes_default;
|
|
UA_Int32 myInt = 42;
|
|
UA_Variant_setScalar(&attr.value, &myInt, &UA_TYPES[UA_TYPES_INT32]);
|
|
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
|
|
|
|
UA_NodeId myNodeId = UA_NODEID_STRING(1, "test.writevar");
|
|
UA_StatusCode retval = UA_Server_addVariableNode(server, myNodeId,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
UA_QUALIFIEDNAME(1, "TestWriteVar"),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
|
|
attr, NULL, NULL);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
|
|
/* Write a new value */
|
|
UA_Int32 newVal = 100;
|
|
UA_Variant writeVal;
|
|
UA_Variant_setScalar(&writeVal, &newVal, &UA_TYPES[UA_TYPES_INT32]);
|
|
retval = UA_Server_writeValue(server, myNodeId, writeVal);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
|
|
/* Read back */
|
|
UA_Variant readVal;
|
|
retval = UA_Server_readValue(server, myNodeId, &readVal);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(*(UA_Int32*)readVal.data, 100);
|
|
UA_Variant_clear(&readVal);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerAddObjectNode) {
|
|
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
|
|
oAttr.displayName = UA_LOCALIZEDTEXT("en-US", "TestObject");
|
|
UA_NodeId objId = UA_NODEID_STRING(1, "test.object");
|
|
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, "TestObject"),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
|
|
oAttr, NULL, NULL);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
|
|
/* Verify node class */
|
|
UA_NodeClass nc;
|
|
retval = UA_Server_readNodeClass(server, objId, &nc);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(nc, UA_NODECLASS_OBJECT);
|
|
|
|
/* Delete the node */
|
|
retval = UA_Server_deleteNode(server, objId, true);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
|
|
/* Verify deletion */
|
|
retval = UA_Server_readNodeClass(server, objId, &nc);
|
|
ck_assert_uint_ne(retval, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerBrowse) {
|
|
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(checkServerReadNonExistingNode) {
|
|
UA_Variant value;
|
|
UA_NodeId badId = UA_NODEID_NUMERIC(0, 99999);
|
|
UA_StatusCode retval = UA_Server_readValue(server, badId, &value);
|
|
ck_assert_uint_ne(retval, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerTranslateBrowsePath) {
|
|
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(checkServerAddMethodNode) {
|
|
UA_MethodAttributes methAttr = UA_MethodAttributes_default;
|
|
methAttr.displayName = UA_LOCALIZEDTEXT("en-US", "TestMethod");
|
|
methAttr.executable = true;
|
|
methAttr.userExecutable = true;
|
|
|
|
UA_NodeId methId = UA_NODEID_STRING(1, "test.method");
|
|
UA_StatusCode retval = UA_Server_addMethodNode(server, methId,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
|
|
UA_QUALIFIEDNAME(1, "TestMethod"),
|
|
methAttr, NULL, 0, NULL, 0, NULL, NULL, NULL);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
|
|
/* Verify it's a method */
|
|
UA_NodeClass nc;
|
|
retval = UA_Server_readNodeClass(server, methId, &nc);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(nc, UA_NODECLASS_METHOD);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerAddReference) {
|
|
/* Add two objects, then add a reference between them */
|
|
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
|
|
UA_NodeId obj1 = UA_NODEID_STRING(1, "test.ref1");
|
|
UA_NodeId obj2 = UA_NODEID_STRING(1, "test.ref2");
|
|
|
|
UA_Server_addObjectNode(server, obj1,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
UA_QUALIFIEDNAME(1, "RefObj1"),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
|
|
oAttr, NULL, NULL);
|
|
UA_Server_addObjectNode(server, obj2,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
UA_QUALIFIEDNAME(1, "RefObj2"),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
|
|
oAttr, NULL, NULL);
|
|
|
|
/* Add reference */
|
|
UA_StatusCode retval = UA_Server_addReference(server, obj1,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
UA_EXPANDEDNODEID_STRING(1, "test.ref2"), true);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
|
|
/* Delete reference */
|
|
retval = UA_Server_deleteReference(server, obj1,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), true,
|
|
UA_EXPANDEDNODEID_STRING(1, "test.ref2"), true);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerAddReferenceToRemoteTarget) {
|
|
UA_ExpandedNodeId target = UA_EXPANDEDNODEID_NUMERIC(1, 1000);
|
|
target.serverIndex = 1;
|
|
|
|
UA_StatusCode retval = UA_Server_addReference(
|
|
server, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), target, true);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
|
|
retval = UA_Server_deleteReference(
|
|
server, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), true, target, true);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerReadDataType) {
|
|
/* Read the DataType attribute of the CurrentTime variable */
|
|
UA_NodeId dataType;
|
|
UA_StatusCode retval = UA_Server_readDataType(server,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME),
|
|
&dataType);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
/* Should be UtcTime (ns=0;i=294) which is a subtype of DateTime */
|
|
ck_assert_uint_eq(dataType.namespaceIndex, 0);
|
|
ck_assert_uint_gt(dataType.identifier.numeric, 0);
|
|
UA_NodeId_clear(&dataType);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerRepeatedCallback) {
|
|
UA_Boolean triggered = false;
|
|
UA_UInt64 callbackId = 0;
|
|
UA_StatusCode ret = UA_Server_addRepeatedCallback(server,
|
|
timedCallbackHandler, &triggered, 100.0, &callbackId);
|
|
ck_assert_uint_eq(ret, UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_gt(callbackId, 0);
|
|
|
|
/* Remove the callback */
|
|
UA_Server_removeCallback(server, callbackId);
|
|
} END_TEST
|
|
|
|
static UA_StatusCode
|
|
childIteratorCallback(UA_NodeId childId, UA_Boolean isInverse,
|
|
UA_NodeId referenceTypeId, void *handle) {
|
|
(*(int*)handle)++;
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
START_TEST(checkForEachChildNodeCall) {
|
|
/* Objects folder should have children */
|
|
int count = 0;
|
|
UA_StatusCode retval = UA_Server_forEachChildNodeCall(
|
|
server, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
childIteratorCallback, &count);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_gt(count, 0);
|
|
} END_TEST
|
|
|
|
static UA_StatusCode
|
|
childIteratorCallbackAbort(UA_NodeId childId, UA_Boolean isInverse,
|
|
UA_NodeId referenceTypeId, void *handle) {
|
|
(*(int*)handle)++;
|
|
/* Abort after first child */
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
START_TEST(checkForEachChildNodeCall_abort) {
|
|
/* Test early abort via callback returning error */
|
|
int count = 0;
|
|
UA_StatusCode retval = UA_Server_forEachChildNodeCall(
|
|
server, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
childIteratorCallbackAbort, &count);
|
|
ck_assert_uint_ne(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(count, 1);
|
|
} END_TEST
|
|
|
|
START_TEST(checkForEachChildNodeCall_invalidNode) {
|
|
int count = 0;
|
|
UA_StatusCode retval = UA_Server_forEachChildNodeCall(
|
|
server, UA_NODEID_NUMERIC(0, 99999),
|
|
childIteratorCallback, &count);
|
|
ck_assert_uint_ne(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(count, 0);
|
|
} END_TEST
|
|
|
|
START_TEST(checkChangeRepeatedCallbackInterval) {
|
|
UA_Boolean triggered = false;
|
|
UA_UInt64 callbackId = 0;
|
|
UA_StatusCode ret = UA_Server_addRepeatedCallback(server,
|
|
timedCallbackHandler, &triggered, 500.0, &callbackId);
|
|
ck_assert_uint_eq(ret, UA_STATUSCODE_GOOD);
|
|
|
|
/* Change interval */
|
|
ret = UA_Server_changeRepeatedCallbackInterval(server, callbackId, 1000.0);
|
|
ck_assert_uint_eq(ret, UA_STATUSCODE_GOOD);
|
|
|
|
/* Remove */
|
|
UA_Server_removeCallback(server, callbackId);
|
|
} END_TEST
|
|
|
|
START_TEST(checkMultipleNamespaces) {
|
|
/* Add multiple namespaces in sequence */
|
|
UA_UInt16 ns1 = UA_Server_addNamespace(server, "http://ns1.example.com");
|
|
UA_UInt16 ns2 = UA_Server_addNamespace(server, "http://ns2.example.com");
|
|
UA_UInt16 ns3 = UA_Server_addNamespace(server, "http://ns3.example.com");
|
|
|
|
ck_assert_uint_gt(ns1, 0);
|
|
ck_assert_uint_gt(ns2, ns1);
|
|
ck_assert_uint_gt(ns3, ns2);
|
|
|
|
/* Verify they can be found */
|
|
size_t foundIdx;
|
|
UA_StatusCode ret = UA_Server_getNamespaceByName(server,
|
|
UA_STRING("http://ns2.example.com"), &foundIdx);
|
|
ck_assert_uint_eq(ret, UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(foundIdx, ns2);
|
|
|
|
/* Verify namespace by index */
|
|
UA_String nsUri;
|
|
ret = UA_Server_getNamespaceByIndex(server, ns3, &nsUri);
|
|
ck_assert_uint_eq(ret, UA_STATUSCODE_GOOD);
|
|
UA_String expected = UA_STRING("http://ns3.example.com");
|
|
ck_assert(UA_String_equal(&nsUri, &expected));
|
|
UA_String_clear(&nsUri);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerLifecycleCallbackNotification) {
|
|
/* Verify that the notification callback fires the distinct STARTED,
|
|
* STOPPING and STOPPED notifications -- not STOPPING twice -- during a
|
|
* normal startup/shutdown cycle. */
|
|
UA_StatusCode ret = UA_Server_run_startup(server);
|
|
ck_assert_uint_eq(ret, UA_STATUSCODE_GOOD);
|
|
|
|
UA_LifecycleState state = UA_Server_getLifecycleState(server);
|
|
ck_assert_int_eq(state, UA_LIFECYCLESTATE_STARTED);
|
|
|
|
/* Get stats while running */
|
|
UA_ServerStatistics stats = UA_Server_getStatistics(server);
|
|
(void)stats;
|
|
|
|
ret = UA_Server_run_shutdown(server);
|
|
ck_assert_uint_eq(ret, UA_STATUSCODE_GOOD);
|
|
|
|
ck_assert_uint_eq(receivedNotificationsCount, 3);
|
|
ck_assert_int_eq(receivedNotifications[0], UA_APPLICATIONNOTIFICATIONTYPE_LIFECYCLE_STARTED);
|
|
ck_assert_int_eq(receivedNotifications[1], UA_APPLICATIONNOTIFICATIONTYPE_LIFECYCLE_STOPPING);
|
|
ck_assert_int_eq(receivedNotifications[2], UA_APPLICATIONNOTIFICATIONTYPE_LIFECYCLE_STOPPED);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerAddVariableTypeNode) {
|
|
/* Add a custom VariableType node */
|
|
UA_VariableTypeAttributes vtAttr = UA_VariableTypeAttributes_default;
|
|
vtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "TestVariableType");
|
|
vtAttr.dataType = UA_NODEID_NUMERIC(0, UA_NS0ID_INT32);
|
|
vtAttr.valueRank = UA_VALUERANK_SCALAR;
|
|
|
|
UA_NodeId vtId = UA_NODEID_STRING(1, "test.vartype");
|
|
UA_StatusCode retval = UA_Server_addVariableTypeNode(
|
|
server, vtId,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
|
|
UA_QUALIFIEDNAME(1, "TestVariableType"),
|
|
UA_NODEID_NULL, vtAttr, NULL, NULL);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
|
|
/* Verify node class */
|
|
UA_NodeClass nc;
|
|
retval = UA_Server_readNodeClass(server, vtId, &nc);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(nc, UA_NODECLASS_VARIABLETYPE);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerAddViewNode) {
|
|
UA_ViewAttributes vAttr = UA_ViewAttributes_default;
|
|
vAttr.displayName = UA_LOCALIZEDTEXT("en-US", "TestView");
|
|
|
|
UA_NodeId viewId = UA_NODEID_STRING(1, "test.view");
|
|
UA_StatusCode retval = UA_Server_addViewNode(
|
|
server, viewId,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_VIEWSFOLDER),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
UA_QUALIFIEDNAME(1, "TestView"),
|
|
vAttr, NULL, NULL);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
|
|
/* Verify node class */
|
|
UA_NodeClass nc;
|
|
retval = UA_Server_readNodeClass(server, viewId, &nc);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(nc, UA_NODECLASS_VIEW);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerAddDataTypeNode) {
|
|
UA_DataTypeAttributes dtAttr = UA_DataTypeAttributes_default;
|
|
dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "TestDataType");
|
|
|
|
UA_NodeId dtId = UA_NODEID_STRING(1, "test.datatype");
|
|
UA_StatusCode retval = UA_Server_addDataTypeNode(
|
|
server, dtId,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_STRUCTURE),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
|
|
UA_QUALIFIEDNAME(1, "TestDataType"),
|
|
dtAttr, NULL, NULL);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
|
|
UA_NodeClass nc;
|
|
retval = UA_Server_readNodeClass(server, dtId, &nc);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(nc, UA_NODECLASS_DATATYPE);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerAddReferenceTypeNode) {
|
|
UA_ReferenceTypeAttributes rtAttr = UA_ReferenceTypeAttributes_default;
|
|
rtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "TestRefType");
|
|
rtAttr.inverseName = UA_LOCALIZEDTEXT("en-US", "InverseTestRefType");
|
|
|
|
UA_NodeId rtId = UA_NODEID_STRING(1, "test.reftype");
|
|
UA_StatusCode retval = UA_Server_addReferenceTypeNode(
|
|
server, rtId,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_NONHIERARCHICALREFERENCES),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
|
|
UA_QUALIFIEDNAME(1, "TestRefType"),
|
|
rtAttr, NULL, NULL);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
|
|
UA_NodeClass nc;
|
|
retval = UA_Server_readNodeClass(server, rtId, &nc);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(nc, UA_NODECLASS_REFERENCETYPE);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerAddObjectTypeNode) {
|
|
UA_ObjectTypeAttributes otAttr = UA_ObjectTypeAttributes_default;
|
|
otAttr.displayName = UA_LOCALIZEDTEXT("en-US", "TestObjectType");
|
|
|
|
UA_NodeId otId = UA_NODEID_STRING(1, "test.objtype");
|
|
UA_StatusCode retval = UA_Server_addObjectTypeNode(
|
|
server, otId,
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
|
|
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
|
|
UA_QUALIFIEDNAME(1, "TestObjectType"),
|
|
otAttr, NULL, NULL);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
|
|
UA_NodeClass nc;
|
|
retval = UA_Server_readNodeClass(server, otId, &nc);
|
|
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
|
ck_assert_int_eq(nc, UA_NODECLASS_OBJECTTYPE);
|
|
} END_TEST
|
|
|
|
START_TEST(checkServerConfig_returnsLivePointer) {
|
|
UA_ServerConfig *config1 = UA_Server_getConfig(server);
|
|
UA_ServerConfig *config2 = UA_Server_getConfig(server);
|
|
ck_assert_ptr_ne(config1, NULL);
|
|
ck_assert_ptr_eq(config1, config2);
|
|
|
|
/* Mutations through the returned pointer must persist */
|
|
UA_UInt32 original = config1->maxNodesPerRead;
|
|
config1->maxNodesPerRead = original + 17;
|
|
ck_assert_uint_eq(UA_Server_getConfig(server)->maxNodesPerRead,
|
|
original + 17);
|
|
config1->maxNodesPerRead = original;
|
|
} END_TEST
|
|
|
|
static int callbackCounter = 0;
|
|
static void testRepeatedCallback(UA_Server *s, void *data) {
|
|
(void)s; (void)data;
|
|
callbackCounter++;
|
|
}
|
|
|
|
START_TEST(checkCallbackManagement) {
|
|
UA_UInt64 callbackId = 0;
|
|
|
|
UA_StatusCode ret = UA_Server_run_startup(server);
|
|
ck_assert_int_eq(ret, UA_STATUSCODE_GOOD);
|
|
|
|
ret = UA_Server_addRepeatedCallback(server, testRepeatedCallback, NULL,
|
|
1000.0, &callbackId);
|
|
ck_assert_int_eq(ret, UA_STATUSCODE_GOOD);
|
|
ck_assert(callbackId != 0);
|
|
|
|
ret = UA_Server_changeRepeatedCallbackInterval(server, callbackId, 2000.0);
|
|
ck_assert_int_eq(ret, UA_STATUSCODE_GOOD);
|
|
|
|
UA_Server_removeCallback(server, callbackId);
|
|
|
|
UA_Server_removeCallback(server, 99999);
|
|
|
|
ret = UA_Server_run_shutdown(server);
|
|
ck_assert_int_eq(ret, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
static unsigned closeCount;
|
|
static UA_StatusCode
|
|
closeConnection(UA_ConnectionManager *cm, uintptr_t connectionId) {
|
|
(void)cm;
|
|
ck_assert_uint_eq(connectionId, 2);
|
|
closeCount++;
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
static void
|
|
checkHello(size_t urlLength, size_t trailing, UA_UInt32 version, UA_StatusCode expected) {
|
|
UA_ConnectionManager *cm = TestConnectionManager_new("tcp", NULL);
|
|
ck_assert_ptr_ne(cm, NULL);
|
|
cm->eventSource.eventLoop = UA_Server_getConfig(server)->eventLoop;
|
|
cm->closeConnection = closeConnection;
|
|
UA_Driver *bpm = server->binaryDriver;
|
|
ck_assert_ptr_ne(bpm, NULL);
|
|
closeCount = 0;
|
|
void *listener = NULL;
|
|
serverNetworkCallback(cm, 1, bpm, &listener, UA_CONNECTIONSTATE_ESTABLISHED,
|
|
&UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
|
|
ck_assert_ptr_ne(listener, NULL);
|
|
|
|
UA_TcpHelloMessage hello;
|
|
UA_TcpHelloMessage_init(&hello);
|
|
hello.protocolVersion = version;
|
|
hello.receiveBufferSize = hello.sendBufferSize = 65536;
|
|
ck_assert_uint_eq(UA_ByteString_allocBuffer(&hello.endpointUrl, urlLength),
|
|
UA_STATUSCODE_GOOD);
|
|
memset(hello.endpointUrl.data, 'x', urlLength);
|
|
UA_ByteString body = UA_BYTESTRING_NULL;
|
|
ck_assert_uint_eq(UA_encodeBinary(&hello, &UA_TRANSPORT[UA_TRANSPORT_TCPHELLOMESSAGE],
|
|
&body, NULL), UA_STATUSCODE_GOOD);
|
|
UA_TcpHelloMessage_clear(&hello);
|
|
UA_ByteString frame;
|
|
ck_assert_uint_eq(UA_ByteString_allocBuffer(&frame, 8 + body.length + trailing),
|
|
UA_STATUSCODE_GOOD);
|
|
UA_TcpMessageHeader header = {UA_MESSAGETYPE_HEL + UA_CHUNKTYPE_FINAL,
|
|
(UA_UInt32)frame.length};
|
|
UA_ByteString headerBuf = {8, frame.data};
|
|
ck_assert_uint_eq(UA_encodeBinary(&header, &UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER],
|
|
&headerBuf, NULL), UA_STATUSCODE_GOOD);
|
|
memcpy(frame.data + 8, body.data, body.length);
|
|
memset(frame.data + 8 + body.length, 0xab, trailing);
|
|
UA_ByteString_clear(&body);
|
|
|
|
void *connection = listener;
|
|
serverNetworkCallback(cm, 2, bpm, &connection, UA_CONNECTIONSTATE_ESTABLISHED,
|
|
&UA_KEYVALUEMAP_NULL, frame);
|
|
const UA_ByteString *response = TestConnectionManager_getLastSent(cm);
|
|
size_t offset = 0;
|
|
ck_assert_uint_eq(UA_decodeBinaryInternal(response, &offset, &header,
|
|
&UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], NULL), UA_STATUSCODE_GOOD);
|
|
if(expected == UA_STATUSCODE_GOOD) {
|
|
ck_assert_uint_eq(header.messageTypeAndChunkType, UA_MESSAGETYPE_ACK + UA_CHUNKTYPE_FINAL);
|
|
UA_TcpAcknowledgeMessage ack;
|
|
ck_assert_uint_eq(UA_decodeBinaryInternal(response, &offset, &ack,
|
|
&UA_TRANSPORT[UA_TRANSPORT_TCPACKNOWLEDGEMESSAGE], NULL), UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(ack.protocolVersion, 0);
|
|
ck_assert_uint_eq(closeCount, 0);
|
|
} else {
|
|
ck_assert_uint_eq(header.messageTypeAndChunkType, UA_MESSAGETYPE_ERR + UA_CHUNKTYPE_FINAL);
|
|
UA_TcpErrorMessage error;
|
|
ck_assert_uint_eq(UA_decodeBinaryInternal(response, &offset, &error,
|
|
&UA_TRANSPORT[UA_TRANSPORT_TCPERRORMESSAGE], NULL), UA_STATUSCODE_GOOD);
|
|
ck_assert_uint_eq(error.error, expected);
|
|
ck_assert_uint_eq(closeCount, 1);
|
|
UA_TcpErrorMessage_clear(&error);
|
|
}
|
|
serverNetworkCallback(cm, 2, bpm, &connection, UA_CONNECTIONSTATE_CLOSING,
|
|
&UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
|
|
serverNetworkCallback(cm, 1, bpm, &listener, UA_CONNECTIONSTATE_CLOSING,
|
|
&UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
|
|
cm->eventSource.free(&cm->eventSource);
|
|
UA_ByteString_clear(&frame);
|
|
}
|
|
|
|
START_TEST(helloEndpointUrlLimit) {
|
|
checkHello(32, 0, 0, UA_STATUSCODE_GOOD);
|
|
checkHello(4095, 0, 0, UA_STATUSCODE_GOOD);
|
|
checkHello(4096, 0, 0, UA_STATUSCODE_BADTCPENDPOINTURLINVALID);
|
|
checkHello(4097, 0, 0, UA_STATUSCODE_BADTCPENDPOINTURLINVALID);
|
|
checkHello(5000, 0, 0, UA_STATUSCODE_BADTCPENDPOINTURLINVALID);
|
|
checkHello(32, 0, 0xdeadbeef, UA_STATUSCODE_GOOD);
|
|
} END_TEST
|
|
|
|
START_TEST(helloTrailingData) {
|
|
checkHello(32, 1, 0, UA_STATUSCODE_BADDECODINGERROR);
|
|
checkHello(32, 8, 0, UA_STATUSCODE_BADDECODINGERROR);
|
|
} END_TEST
|
|
|
|
int main(void) {
|
|
Suite *s = suite_create("server");
|
|
|
|
TCase *tc_call = tcase_create("server - basics");
|
|
tcase_add_checked_fixture(tc_call, setup, teardown);
|
|
tcase_add_test(tc_call, checkGetConfig);
|
|
tcase_add_test(tc_call, checkTcpDisabled);
|
|
tcase_add_test(tc_call, addDriverPropagatesStartFailure);
|
|
tcase_add_test(tc_call, checkGetNamespaceByName);
|
|
tcase_add_test(tc_call, checkGetNamespaceById);
|
|
tcase_add_test(tc_call, checkServer_run);
|
|
tcase_add_test(tc_call, checkGetStatistics);
|
|
tcase_add_test(tc_call, checkGetLifecycleState);
|
|
tcase_add_test(tc_call, checkSharedExternalEventLoop);
|
|
tcase_add_loop_test(tc_call, checkExternalShutdownTcp, 0, 2);
|
|
tcase_add_loop_test(tc_call, checkShutdownDelayedDriver, 0, 3);
|
|
tcase_add_test(tc_call, helloEndpointUrlLimit);
|
|
tcase_add_test(tc_call, helloTrailingData);
|
|
tcase_add_test(tc_call, checkNoneEndpointUserTokens);
|
|
tcase_add_test(tc_call, checkStartupFailsWithoutServerSocket);
|
|
suite_add_tcase(s, tc_call);
|
|
|
|
TCase *tc_ext = tcase_create("server - extended");
|
|
tcase_add_checked_fixture(tc_ext, setup, teardown);
|
|
tcase_add_test(tc_ext, checkGetDataTypes);
|
|
tcase_add_test(tc_ext, checkFindDataType);
|
|
tcase_add_test(tc_ext, checkServerReadValue);
|
|
tcase_add_test(tc_ext, checkServerReadDisplayName);
|
|
tcase_add_test(tc_ext, checkServerReadDescription);
|
|
tcase_add_test(tc_ext, checkServerReadWriteRank);
|
|
tcase_add_test(tc_ext, checkServerReadNodeId);
|
|
|
|
tcase_add_test(tc_ext, checkServerAddRemoveNamespace);
|
|
tcase_add_test(tc_ext, checkServerWriteValue);
|
|
tcase_add_test(tc_ext, checkServerAddObjectNode);
|
|
tcase_add_test(tc_ext, checkServerBrowse);
|
|
tcase_add_test(tc_ext, checkServerReadNonExistingNode);
|
|
tcase_add_test(tc_ext, checkServerTranslateBrowsePath);
|
|
tcase_add_test(tc_ext, checkServerAddMethodNode);
|
|
tcase_add_test(tc_ext, checkServerAddReference);
|
|
tcase_add_test(tc_ext, checkServerAddReferenceToRemoteTarget);
|
|
tcase_add_test(tc_ext, checkServerReadDataType);
|
|
tcase_add_test(tc_ext, checkServerRepeatedCallback);
|
|
tcase_add_test(tc_ext, checkCallbackManagement);
|
|
tcase_add_test(tc_ext, checkForEachChildNodeCall);
|
|
tcase_add_test(tc_ext, checkForEachChildNodeCall_abort);
|
|
tcase_add_test(tc_ext, checkForEachChildNodeCall_invalidNode);
|
|
tcase_add_test(tc_ext, checkChangeRepeatedCallbackInterval);
|
|
tcase_add_test(tc_ext, checkMultipleNamespaces);
|
|
tcase_add_test(tc_ext, checkServerLifecycleCallbackNotification);
|
|
tcase_add_test(tc_ext, checkServerAddVariableTypeNode);
|
|
tcase_add_test(tc_ext, checkServerAddViewNode);
|
|
tcase_add_test(tc_ext, checkServerAddDataTypeNode);
|
|
tcase_add_test(tc_ext, checkServerAddReferenceTypeNode);
|
|
tcase_add_test(tc_ext, checkServerAddObjectTypeNode);
|
|
tcase_add_test(tc_ext, checkServerConfig_returnsLivePointer);
|
|
suite_add_tcase(s, tc_ext);
|
|
|
|
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;
|
|
}
|