Merge branch 'master' into 1.3

This commit is contained in:
Julius Pfrommer 2021-12-01 11:08:34 +01:00 committed by GitHub
commit 2f2b9fea7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 5547 additions and 596 deletions

View File

@ -11,6 +11,12 @@ jobs:
- build_name: "Debug Build & Unit Tests (gcc)"
cmd_deps: ""
cmd_action: unit_tests
- build_name: "Debug Build & Unit Tests (gcc, 32bit)"
cmd_deps: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install -y -qq gcc-multilib libsubunit-dev:i386 check:i386
cmd_action: unit_tests_32
- build_name: "Debug Build & Unit Tests with multithreading (gcc)"
cmd_deps: ""
cmd_action: unit_tests_mt

32
.github/workflows/coverage.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: "Coverage Test"
on:
push:
branches: [ main, master ]
tags:
- v1.*
pull_request:
branches: [ main, master ]
tags:
- v1.*
jobs:
run:
runs-on: ubuntu-latest
steps:
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install -y -qq python3-sphinx graphviz check libmbedtls-dev
- name: Fetch
uses: actions/checkout@v2
with:
submodules: true
- name: Execute Tests
run: source tools/ci.sh && unit_tests_with_coverage
env:
ETHERNET_INTERFACE: eth0
- name: Debug print
run: |
tree .
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2

View File

@ -117,11 +117,14 @@ GET_PROPERTY(ua_architecture_sources GLOBAL PROPERTY UA_ARCHITECTURE_SOURCES)
set(ua_architecture_sources ${ua_architecture_sources}
${PROJECT_SOURCE_DIR}/arch/network_tcp.c
${PROJECT_SOURCE_DIR}/arch/eventloop_posix.c
${PROJECT_SOURCE_DIR}/arch/eventloop_posix_tcp.c
)
set(ua_architecture_headers ${ua_architecture_headers}
${PROJECT_SOURCE_DIR}/include/open62541/network_tcp.h
${PROJECT_SOURCE_DIR}/include/open62541/architecture_functions.h
${PROJECT_SOURCE_DIR}/arch/eventloop_posix.h
)
if(UA_ENABLE_WEBSOCKET_SERVER)
@ -289,8 +292,9 @@ if((UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS) AND (NOT (UA_ENABLE_SUBSCRIPTIONS
endif()
if(UA_ENABLE_COVERAGE)
set(CMAKE_BUILD_TYPE DEBUG)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
# We are using the scripts provided at for coverage testing: https://github.com/RWTH-HPC/CMake-codecov
set(ENABLE_COVERAGE ON)
find_package(codecov REQUIRED)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage -lgcov")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fprofile-arcs -ftest-coverage")
endif()
@ -673,7 +677,10 @@ if(NOT UA_FORCE_CPP AND (CMAKE_COMPILER_IS_GNUCC OR "x${CMAKE_C_COMPILER_ID}" ST
# Force 32bit build
if(UA_FORCE_32BIT)
check_add_cc_flag("-m32")
if(MSVC)
message(FATAL_ERROR "Select the 32bit (cross-) compiler instead of forcing compiler options")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32") # GCC and Clang, possibly more
endif()
if(NOT MINGW AND NOT UA_BUILD_OSS_FUZZ)
@ -856,6 +863,7 @@ set(exported_headers ${PROJECT_BINARY_DIR}/src_generated/open62541/config.h
${PROJECT_SOURCE_DIR}/include/open62541/plugin/pubsub.h
${PROJECT_SOURCE_DIR}/deps/ziptree.h
${PROJECT_SOURCE_DIR}/deps/aa_tree.h
${PROJECT_SOURCE_DIR}/include/open62541/plugin/eventloop.h
${PROJECT_SOURCE_DIR}/include/open62541/plugin/nodestore.h
${historizing_exported_headers}
${PROJECT_SOURCE_DIR}/include/open62541/server_pubsub.h
@ -875,7 +883,7 @@ set(internal_headers ${PROJECT_SOURCE_DIR}/deps/open62541_queue.h
${PROJECT_BINARY_DIR}/src_generated/open62541/transport_generated_handling.h
${PROJECT_SOURCE_DIR}/src/ua_connection_internal.h
${PROJECT_SOURCE_DIR}/src/ua_securechannel.h
${PROJECT_SOURCE_DIR}/src/ua_timer.h
${PROJECT_SOURCE_DIR}/arch/common/ua_timer.h
${PROJECT_SOURCE_DIR}/src/server/ua_session.h
${PROJECT_SOURCE_DIR}/src/server/ua_subscription.h
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage.h
@ -896,7 +904,7 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
${PROJECT_BINARY_DIR}/src_generated/open62541/transport_generated.c
${PROJECT_BINARY_DIR}/src_generated/open62541/statuscodes.c
${PROJECT_SOURCE_DIR}/src/ua_util.c
${PROJECT_SOURCE_DIR}/src/ua_timer.c
${PROJECT_SOURCE_DIR}/arch/common/ua_timer.c
${PROJECT_SOURCE_DIR}/src/ua_connection.c
${PROJECT_SOURCE_DIR}/src/ua_securechannel.c
${PROJECT_SOURCE_DIR}/src/ua_securechannel_crypto.c
@ -1360,8 +1368,11 @@ else()
open62541-generator-statuscode
open62541-generator-namespace
)
if(UA_ENABLE_COVERAGE)
add_coverage(open62541-object)
endif()
target_include_directories(open62541-object PRIVATE ${PROJECT_SOURCE_DIR}/src)
target_include_directories(open62541-object PRIVATE ${PROJECT_SOURCE_DIR}/arch) # TODO: Remove once the EventLoop is integrated
add_library(open62541-plugins OBJECT ${default_plugin_sources} ${ua_architecture_sources} ${exported_headers})
add_dependencies(open62541-plugins open62541-generator-types open62541-generator-transport open62541-generator-namespace)

View File

@ -103,6 +103,7 @@ The scope is optional, but recommended to be used. It should be the name of the
The following is the list of supported scopes:
- **arch**: Changes to specific architecture code in `root/arch`
- **el**: Changes to the eventloop and associated event sources (also networking)
- **client**: Changes only affecting client code
- **core**: Core functionality used by the client and server
- **ex**: Example code changes

View File

@ -6,7 +6,6 @@
* Copyright 2017 (c) Stefan Profanter, fortiss GmbH
*/
#include "ua_util_internal.h"
#include "ua_timer.h"
/* There may be several entries with the same nextTime in the tree. We give them
@ -273,6 +272,12 @@ UA_Timer_process(UA_Timer *t, UA_DateTime nowMonotonic,
return next;
}
UA_DateTime
UA_Timer_nextRepeatedTime(UA_Timer *t) {
UA_TimerEntry *first = (UA_TimerEntry*)aa_min(&t->root);
return (first) ? first->nextTime : UA_INT64_MAX;
}
void
UA_Timer_clear(UA_Timer *t) {
UA_LOCK(&t->timerMutex);

View File

@ -9,7 +9,8 @@
#ifndef UA_TIMER_H_
#define UA_TIMER_H_
#include "ua_util_internal.h"
#include <open62541/types.h>
#include <open62541/util.h>
#include "aa_tree.h"
_UA_BEGIN_DECLS
@ -55,6 +56,9 @@ typedef struct {
void
UA_Timer_init(UA_Timer *t);
UA_DateTime
UA_Timer_nextRepeatedTime(UA_Timer *t);
UA_StatusCode
UA_Timer_addTimedCallback(UA_Timer *t, UA_ApplicationCallback callback,
void *application, void *data, UA_DateTime date,

View File

@ -39,7 +39,8 @@
#define UA_WOULDBLOCK EWOULDBLOCK
#define UA_ERR_CONNECTION_PROGRESS EINPROGRESS
#define UA_getnameinfo getnameinfo
#define UA_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) \
getnameinfo(sa, salen, host, hostlen, serv, servlen, flags)
#define UA_send send
#define UA_recv recv
#define UA_sendto sendto

596
arch/eventloop_posix.c Normal file
View File

@ -0,0 +1,596 @@
/* 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 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes)
*/
#include "eventloop_posix.h"
#include "common/ua_timer.h"
typedef struct {
UA_FD fd;
short eventMask;
UA_EventSource *es;
UA_FDCallback callback;
void *fdcontext;
} UA_RegisteredFD;
struct UA_EventLoop {
UA_EventLoopState state;
const UA_Logger *logger;
/* Timer */
UA_Timer timer;
/* Linked List of Delayed Callbacks */
UA_DelayedCallback *delayedCallbacks;
/* Pointers to registered EventSources */
UA_EventSource *eventSources;
/* Registered file descriptors */
size_t fdsSize;
UA_RegisteredFD *fds;
/* Flag determining whether the eventloop is currently within the "run" method */
UA_Boolean executing;
#if UA_MULTITHREADING >= 100
UA_Lock elMutex;
#endif
};
/*********/
/* Timer */
/*********/
static UA_StatusCode
processFDs(UA_EventLoop *el, UA_DateTime usedTimeout);
static void
timerExecutionTrampoline(void *executionApplication, UA_ApplicationCallback cb,
void *callbackApplication, void *data) {
cb(callbackApplication, data);
}
UA_StatusCode
UA_EventLoop_addTimedCallback(UA_EventLoop *el, UA_Callback callback,
void *application, void *data, UA_DateTime date,
UA_UInt64 *callbackId) {
return UA_Timer_addTimedCallback(&el->timer, callback, application,
data, date, callbackId);
}
UA_StatusCode
UA_EventLoop_addCyclicCallback(UA_EventLoop *el, UA_Callback cb,
void *application, void *data, UA_Double interval_ms,
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy,
UA_UInt64 *callbackId) {
return UA_Timer_addRepeatedCallback(&el->timer, cb, application, data,
interval_ms, baseTime, timerPolicy, callbackId);
}
UA_StatusCode
UA_EventLoop_modifyCyclicCallback(UA_EventLoop *el, UA_UInt64 callbackId,
UA_Double interval_ms, UA_DateTime *baseTime,
UA_TimerPolicy timerPolicy) {
return UA_Timer_changeRepeatedCallback(&el->timer, callbackId, interval_ms,
baseTime, timerPolicy);
}
void
UA_EventLoop_removeCyclicCallback(UA_EventLoop *el, UA_UInt64 callbackId) {
UA_Timer_removeCallback(&el->timer, callbackId);
}
void
UA_EventLoop_addDelayedCallback(UA_EventLoop *el, UA_DelayedCallback *dc) {
UA_LOCK(&el->elMutex);
dc->next = el->delayedCallbacks;
el->delayedCallbacks = dc;
UA_UNLOCK(&el->elMutex);
}
/* Process and then free registered delayed callbacks */
static void
processDelayed(UA_EventLoop *el) {
UA_LOCK_ASSERT(&el->elMutex, 1);
while(el->delayedCallbacks) {
UA_DelayedCallback *dc = el->delayedCallbacks;
el->delayedCallbacks = dc->next;
/* Delayed Callbacks might have no cb pointer if all we want to do is
* free the memory */
if(dc->callback) {
UA_UNLOCK(&el->elMutex);
dc->callback(dc->application, dc->data);
UA_LOCK(&el->elMutex);
}
UA_free(dc);
}
}
/***********************/
/* EventLoop Lifecycle */
/***********************/
UA_EventLoop *
UA_EventLoop_new(const UA_Logger *logger) {
UA_EventLoop *el = (UA_EventLoop*)UA_malloc(sizeof(UA_EventLoop));
if(!el)
return NULL;
memset(el, 0, sizeof(UA_EventLoop));
UA_LOCK_INIT(&el->elMutex);
el->logger = logger;
UA_Timer_init(&el->timer);
return el;
}
UA_StatusCode
UA_EventLoop_delete(UA_EventLoop *el) {
UA_LOCK(&el->elMutex);
/* Check if the EventLoop can be deleted */
if(el->state != UA_EVENTLOOPSTATE_STOPPED &&
el->state != UA_EVENTLOOPSTATE_FRESH) {
UA_LOG_WARNING(el->logger, UA_LOGCATEGORY_EVENTLOOP,
"Cannot delete a running EventLoop");
UA_UNLOCK(&el->elMutex);
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Deregister and delete all the EventSources */
while(el->eventSources) {
UA_EventSource *es = el->eventSources;
UA_UNLOCK(&el->elMutex);
UA_EventLoop_deregisterEventSource(el, es);
UA_LOCK(&el->elMutex);
es->free(es);
}
/* Remove the repeated timed callbacks */
UA_Timer_clear(&el->timer);
/* Process remaining delayed callbacks */
processDelayed(el);
/* free the file descriptors */
UA_free(el->fds);
/* Clean up */
UA_UNLOCK(&el->elMutex);
UA_LOCK_DESTROY(&el->elMutex);
UA_free(el);
return UA_STATUSCODE_GOOD;
}
UA_EventLoopState
UA_EventLoop_getState(UA_EventLoop *el) {
return el->state;
}
UA_DateTime
UA_EventLoop_nextCyclicTime(UA_EventLoop *el) {
return UA_Timer_nextRepeatedTime(&el->timer);
}
UA_StatusCode
UA_EventLoop_start(UA_EventLoop *el) {
UA_LOCK(&el->elMutex);
if(el->state != UA_EVENTLOOPSTATE_FRESH &&
el->state != UA_EVENTLOOPSTATE_STOPPED) {
UA_UNLOCK(&el->elMutex);
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, "Starting the EventLoop");
UA_EventSource *es = el->eventSources;
UA_StatusCode res = UA_STATUSCODE_GOOD;
while(es) {
UA_UNLOCK(&el->elMutex);
res |= es->start(es);
UA_LOCK(&el->elMutex);
es = es->next;
}
el->state = UA_EVENTLOOPSTATE_STARTED;
UA_UNLOCK(&el->elMutex);
return res;
}
static void
checkClosed(UA_EventLoop *el) {
UA_EventSource *es = el->eventSources;
while(es) {
if(es->state != UA_EVENTSOURCESTATE_STOPPED)
return;
es = es->next;
}
UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, "The EventLoop has stopped");
el->state = UA_EVENTLOOPSTATE_STOPPED;
}
void
UA_EventLoop_stop(UA_EventLoop *el) {
UA_LOCK(&el->elMutex);
UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, "Stopping the EventLoop");
/* Shutdown all event sources. This will close open connections. */
UA_EventSource *es = el->eventSources;
while(es) {
if(es->state == UA_EVENTSOURCESTATE_STARTING ||
es->state == UA_EVENTSOURCESTATE_STARTED)
es->stop(es);
es = es->next;
}
UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP,
"All EventSources are stopped");
el->state = UA_EVENTLOOPSTATE_STOPPING;
checkClosed(el);
UA_UNLOCK(&el->elMutex);
}
/* After every select, reset the file-descriptors to listen on */
static UA_FD
setFDSets(UA_EventLoop *el, fd_set *readset, fd_set *writeset, fd_set *errset) {
FD_ZERO(readset);
FD_ZERO(writeset);
FD_ZERO(errset);
UA_FD highestfd = UA_INVALID_FD;
for(size_t i = 0; i < el->fdsSize; i++) {
UA_FD currentFD = el->fds[i].fd;
/* Add to the fd_sets */
if(el->fds[i].eventMask & UA_POSIX_EVENT_READ)
UA_fd_set(currentFD, readset);
if(el->fds[i].eventMask & UA_POSIX_EVENT_WRITE)
UA_fd_set(currentFD, writeset);
if(el->fds[i].eventMask & UA_POSIX_EVENT_ERR)
UA_fd_set(currentFD, errset);
/* Highest fd? */
if(currentFD > highestfd || highestfd == UA_INVALID_FD)
highestfd = currentFD;
}
return highestfd;
}
static UA_StatusCode
processFDs(UA_EventLoop *el, UA_DateTime usedTimeout) {
fd_set readset, writeset, errset;
UA_FD highestfd = setFDSets(el, &readset, &writeset, &errset);
/* Nothing to do? */
if(highestfd == UA_INVALID_FD) {
UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP,
"No valid FDs for processing");
return UA_STATUSCODE_GOOD;
}
struct timeval tmptv = {
#ifndef _WIN32
(time_t)(usedTimeout / UA_DATETIME_SEC),
(suseconds_t)((usedTimeout % UA_DATETIME_SEC) / UA_DATETIME_USEC)
#else
(long)(usedTimeout / UA_DATETIME_SEC),
(long)((usedTimeout % UA_DATETIME_SEC) / UA_DATETIME_USEC)
#endif
};
int selectStatus = UA_select(highestfd+1, &readset, &writeset, &errset, &tmptv);
if(selectStatus < 0) {
/* We will retry, only log the error */
UA_LOG_SOCKET_ERRNO_WRAP(
UA_LOG_WARNING(UA_EventLoop_getLogger(el),
UA_LOGCATEGORY_EVENTLOOP,
"Error during select: %s", errno_str));
el->executing = false;
return UA_STATUSCODE_GOODCALLAGAIN;
}
/* Loop over all registered FD to see if an event arrived. Yes, this is why
* select is slow for many open sockets. */
for(size_t i = 0; i < el->fdsSize; i++) {
UA_RegisteredFD *rfd = &el->fds[i];
UA_FD fd = rfd->fd;
UA_assert(fd > 0);
UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP,
"Processing fd: %u", (unsigned)fd);
/* Error Event */
if((rfd->eventMask & UA_POSIX_EVENT_ERR) && UA_fd_isset(fd, &errset)) {
UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP,
"Processing error event for fd: %u", (unsigned)fd);
UA_UNLOCK(&el->elMutex);
rfd->callback(rfd->es, fd, &rfd->fdcontext, UA_POSIX_EVENT_ERR);
UA_LOCK(&el->elMutex);
if(i == el->fdsSize || fd != el->fds[i].fd)
i--; /* The fd has removed itself */
continue;
}
/* Read Event */
if((rfd->eventMask & UA_POSIX_EVENT_READ) && UA_fd_isset(fd, &readset)) {
UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP,
"Processing read event for fd: %u", (unsigned)fd);
UA_UNLOCK(&el->elMutex);
rfd->callback(rfd->es, fd, &rfd->fdcontext, UA_POSIX_EVENT_READ);
UA_LOCK(&el->elMutex);
if(i == el->fdsSize || fd != el->fds[i].fd)
i--; /* The fd has removed itself */
continue;
}
/* Write Event */
if((rfd->eventMask & UA_POSIX_EVENT_WRITE) && UA_fd_isset(fd, &writeset)) {
UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP,
"Processing write event for fd: %u", (unsigned)fd);
UA_UNLOCK(&el->elMutex);
rfd->callback(rfd->es, fd, &rfd->fdcontext, UA_POSIX_EVENT_WRITE);
UA_LOCK(&el->elMutex);
if(i == el->fdsSize || fd != el->fds[i].fd)
i--; /* The fd has removed itself */
continue;
}
}
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) {
UA_LOCK(&el->elMutex);
UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, "iterate the EventLoop");
if(el->executing) {
UA_LOG_ERROR(el->logger,
UA_LOGCATEGORY_EVENTLOOP,
"Cannot run EventLoop from the run method itself");
UA_UNLOCK(&el->elMutex);
return UA_STATUSCODE_BADINTERNALERROR;
}
/* TODO: use check macros instead
UA_CHECK_ERROR(!el->executing, return UA_STATUSCODE_BADINTERNALERROR, el->logger,
UA_LOGCATEGORY_EVENTLOOP,
"Cannot run eventloop from the run method itself");
*/
el->executing = true;
if(el->state == UA_EVENTLOOPSTATE_FRESH ||
el->state == UA_EVENTLOOPSTATE_STOPPED) {
UA_LOG_WARNING(el->logger, UA_LOGCATEGORY_EVENTLOOP,
"Cannot iterate a stopped EventLoop");
el->executing = false;
UA_UNLOCK(&el->elMutex);
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Process cyclic callbacks */
UA_DateTime dateBeforeCallback = UA_DateTime_nowMonotonic();
UA_UNLOCK(&el->elMutex);
UA_DateTime dateOfNextCallback =
UA_Timer_process(&el->timer, dateBeforeCallback, timerExecutionTrampoline, NULL);
UA_LOCK(&el->elMutex);
UA_DateTime dateAfterCallback = UA_DateTime_nowMonotonic();
UA_DateTime processTimerDuration = dateAfterCallback - dateBeforeCallback;
UA_DateTime callbackTimeout = dateOfNextCallback - dateAfterCallback;
UA_DateTime maxTimeout = UA_MAX(timeout * UA_DATETIME_MSEC - processTimerDuration, 0);
UA_DateTime usedTimeout = UA_MIN(callbackTimeout, maxTimeout);
/* Listen on the active file-descriptors (sockets) from the ConnectionManagers */
UA_StatusCode rv = processFDs(el, usedTimeout);
if(rv == UA_STATUSCODE_GOODCALLAGAIN) {
UA_UNLOCK(&el->elMutex);
return UA_STATUSCODE_GOOD;
}
if(rv != UA_STATUSCODE_GOOD) {
UA_UNLOCK(&el->elMutex);
return rv;
}
/* Process and then free registered delayed callbacks */
processDelayed(el);
/* Check if the last EventSource was successfully stopped */
if(el->state == UA_EVENTLOOPSTATE_STOPPING)
checkClosed(el);
el->executing = false;
UA_UNLOCK(&el->elMutex);
return UA_STATUSCODE_GOOD;
}
/*****************************/
/* Registering Event Sources */
/*****************************/
UA_StatusCode
UA_EventLoop_registerEventSource(UA_EventLoop *el, UA_EventSource *es) {
/* Already registered? */
if(es->state != UA_EVENTSOURCESTATE_FRESH) {
UA_LOG_ERROR(UA_EventLoop_getLogger(el), UA_LOGCATEGORY_NETWORK,
"Cannot register the EventSource \"%.*s\": already registered",
(int)es->name.length, (char*)es->name.data);
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Add to linked list */
UA_LOCK(&el->elMutex);
es->next = el->eventSources;
el->eventSources = es;
UA_UNLOCK(&el->elMutex);
es->eventLoop = el;
es->state = UA_EVENTSOURCESTATE_STOPPED;
/* Start if the entire EventLoop is started */
if(el->state == UA_EVENTLOOPSTATE_STARTED)
return es->start(es);
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_EventLoop_deregisterEventSource(UA_EventLoop *el, UA_EventSource *es) {
if(es->state != UA_EVENTSOURCESTATE_STOPPED) {
UA_LOG_WARNING(el->logger, UA_LOGCATEGORY_EVENTLOOP,
"Cannot deregister the EventSource %.*s. Has to be stopped first",
(int)es->name.length, es->name.data);
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Remove from the linked list */
UA_LOCK(&el->elMutex);
UA_EventSource **s = &el->eventSources;
while(*s) {
if(*s == es) {
*s = es->next;
break;
}
s = &(*s)->next;
}
UA_UNLOCK(&el->elMutex);
/* Set the state to non-registered */
es->state = UA_EVENTSOURCESTATE_FRESH;
return UA_STATUSCODE_GOOD;
}
/********************************/
/* Registering File Descriptors */
/********************************/
UA_StatusCode
UA_EventLoop_registerFD(UA_EventLoop *el, UA_FD fd, short eventMask,
UA_FDCallback cb, UA_EventSource *es, void *fdcontext) {
UA_LOCK(&el->elMutex);
UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP,
"Registering fd: %u", (unsigned)fd);
/* Realloc */
UA_RegisteredFD *fds_tmp = (UA_RegisteredFD*)
UA_realloc(el->fds, sizeof(UA_RegisteredFD) * (el->fdsSize + 1));
if(!fds_tmp) {
UA_UNLOCK(&el->elMutex);
return UA_STATUSCODE_BADOUTOFMEMORY;
}
el->fds = fds_tmp;
/* Add to the last entry */
el->fds[el->fdsSize].callback = cb;
el->fds[el->fdsSize].eventMask = eventMask;
el->fds[el->fdsSize].es = es;
el->fds[el->fdsSize].fdcontext = fdcontext;
el->fds[el->fdsSize].fd = fd;
el->fdsSize++;
UA_UNLOCK(&el->elMutex);
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_EventLoop_modifyFD(UA_EventLoop *el, UA_FD fd, short eventMask,
UA_FDCallback cb, void *fdcontext) {
UA_LOCK(&el->elMutex);
/* Find the entry */
size_t i = 0;
for(; i < el->fdsSize; i++) {
if(el->fds[i].fd == fd)
break;
}
/* Not found? */
if(i == el->fdsSize) {
UA_UNLOCK(&el->elMutex);
return UA_STATUSCODE_BADNOTFOUND;
}
/* Modify */
el->fds[i].callback = cb;
el->fds[i].eventMask = eventMask;
el->fds[i].fdcontext = fdcontext;
UA_UNLOCK(&el->elMutex);
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_EventLoop_deregisterFD(UA_EventLoop *el, UA_FD fd) {
UA_LOCK(&el->elMutex);
UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP,
"Unregistering fd: %u", (unsigned)fd);
/* Find the entry */
size_t i = 0;
for(; i < el->fdsSize; i++) {
if(el->fds[i].fd == fd)
break;
}
/* Not found? */
if(i == el->fdsSize) {
UA_UNLOCK(&el->elMutex);
return UA_STATUSCODE_BADNOTFOUND;
}
if(el->fdsSize > 1) {
/* Move the last entry in the ith slot and realloc. */
el->fdsSize--;
el->fds[i] = el->fds[el->fdsSize];
UA_RegisteredFD *fds_tmp = (UA_RegisteredFD*)
UA_realloc(el->fds, sizeof(UA_RegisteredFD) * el->fdsSize);
/* if realloc fails the fds are still in a correct state with
* possibly lost memory, so failing silently here is ok */
if(fds_tmp)
el->fds = fds_tmp;
} else {
/* Remove the last entry */
UA_free(el->fds);
el->fds = NULL;
el->fdsSize = 0;
}
UA_UNLOCK(&el->elMutex);
return UA_STATUSCODE_GOOD;
}
void
UA_EventLoop_iterateFD(UA_EventLoop *el, UA_EventSource *es, UA_FDCallback cb) {
for(size_t i = 0; i < el->fdsSize; i++) {
if(el->fds[i].es != es)
continue;
UA_FD fd = el->fds[i].fd;
cb(es, fd, el->fds[i].fdcontext, 0);
if(i == el->fdsSize || fd != el->fds[i].fd)
i--; /* The fd has removed itself */
}
}
/* Helper Functions */
const UA_Logger *
UA_EventLoop_getLogger(UA_EventLoop *el) {
return el->logger;
}
void
UA_EventLoop_setLogger(UA_EventLoop *el, const UA_Logger *logger) {
el->logger = logger;
}

99
arch/eventloop_posix.h Normal file
View File

@ -0,0 +1,99 @@
/* 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 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes)
*/
#ifndef UA_EVENTLOOP_POSIX_H_
#define UA_EVENTLOOP_POSIX_H_
#include <open62541/config.h>
#include <open62541/plugin/eventloop.h>
/* A macro-forest to work around small differences between POSIX and
* nearly-POSIX architectures */
#if defined(_WIN32) /* Windows */
# include <winsock2.h>
# if !defined(__MINGW32__) || defined(__clang__)
# define UA_FD SOCKET /* On MSVC, a socket is a pointer and not an int */
# define UA_INVALID_FD INVALID_SOCKET
//# define UA_close(s) closesocket(s) /* closesocket() takes a SOCKET (and sock() an int) */
# endif
# define UA_ERRNO WSAGetLastError()
# define UA_INTERRUPTED WSAEINTR
# define UA_AGAIN WSAEWOULDBLOCK
# define UA_EAGAIN EAGAIN
# define UA_WOULDBLOCK WSAEWOULDBLOCK
# define UA_ERR_CONNECTION_PROGRESS WSAEWOULDBLOCK
#else /* Unix */
# include <sys/select.h>
#endif
/* Catch-all for the architectures that are "actually POSIX" */
#ifndef UA_FD
# define UA_FD int
# define UA_INVALID_FD -1
//# define UA_close(s) close(s)
#endif
#ifndef UA_ERRNO
# define UA_ERRNO errno
# define UA_INTERRUPTED EINTR
# define UA_AGAIN EAGAIN
# define UA_EAGAIN EAGAIN
# define UA_WOULDBLOCK EWOULDBLOCK
# define UA_ERR_CONNECTION_PROGRESS EINPROGRESS
#endif
/* Workaround a bug in early glibc. Additionally, some non-glibc implementations
* use a macro for FD_SET that triggers a cast-warning (e.g. early BSD libc or
* musl libc). */
#if (!defined(__GNU_LIBRARY__) && defined(FD_SET)) || \
(defined(__GNU_LIBRARY__) && (__GNU_LIBRARY__ <= 6) && \
(__GLIBC__ <= 2) && (__GLIBC_MINOR__ < 16))
# define UA_FD_SET(fd, fds) FD_SET((unsigned int)fd, fds)
# define UA_FD_ISSET(fd, fds) FD_ISSET((unsigned int)fd, fds)
#else
# define UA_FD_SET(fd, fds) FD_SET((UA_FD)fd, fds)
# define UA_FD_ISSET(fd, fds) FD_ISSET((UA_FD)fd, fds)
#endif
_UA_BEGIN_DECLS
/* POSIX events are based on sockets / file descriptors. The EventSources can
* register their fd in the EventLoop so that they are considered by the
* EventLoop dropping into "select" to wait for events. */
/* POSIX-select can listen for three types of events. It has to be selected
* for each registered fd which events they are interested in. */
#define UA_POSIX_EVENT_READ 1
#define UA_POSIX_EVENT_WRITE 2
#define UA_POSIX_EVENT_ERR 4
typedef void
(*UA_FDCallback)(UA_EventSource *es, UA_FD fd, void *fdcontext, short event);
UA_StatusCode
UA_EventLoop_registerFD(UA_EventLoop *el, UA_FD fd, short eventMask,
UA_FDCallback cb, UA_EventSource *es, void *fdcontext);
/* Change the fd settings (event mask, callback) in-place. Fails only if the fd
* no longer exists. */
UA_StatusCode
UA_EventLoop_modifyFD(UA_EventLoop *el, UA_FD fd, short eventMask,
UA_FDCallback cb, void *fdcontext);
/* During processing of an fd-event, the fd may deregister itself. But in the
* fd-callback they must not deregister another fd. */
UA_StatusCode
UA_EventLoop_deregisterFD(UA_EventLoop *el, UA_FD fd);
/* Call the callback for all fd that are registered from that event source */
void
UA_EventLoop_iterateFD(UA_EventLoop *el, UA_EventSource *es, UA_FDCallback cb);
_UA_END_DECLS
#endif /* UA_EVENTLOOP_POSIX_H_ */

788
arch/eventloop_posix_tcp.c Normal file
View File

@ -0,0 +1,788 @@
/* 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 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes)
*/
#include "eventloop_posix.h"
#define UA_MAXBACKLOG 100
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
#ifndef MSG_DONTWAIT
#define MSG_DONTWAIT 0
#endif
typedef struct {
UA_ConnectionManager cm;
size_t fdCount; /* Number of fd registered in the EventLoop */
size_t recvBufferSize;
} TCPConnectionManager;
static UA_StatusCode
TCP_allocNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId,
UA_ByteString *buf, size_t bufSize) {
return UA_ByteString_allocBuffer(buf, bufSize);
}
static void
TCP_freeNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId,
UA_ByteString *buf) {
UA_ByteString_clear(buf);
}
/* Set the socket non-blocking */
static UA_StatusCode
TCP_setNonBlocking(UA_FD sockfd) {
#ifndef _WIN32
int opts = fcntl(sockfd, F_GETFL);
if(opts < 0 || fcntl(sockfd, F_SETFL, opts | O_NONBLOCK) < 0)
return UA_STATUSCODE_BADINTERNALERROR;
#else
u_long iMode = 1;
if(ioctlsocket(sockfd, FIONBIO, &iMode) != NO_ERROR)
return UA_STATUSCODE_BADINTERNALERROR;
#endif
return UA_STATUSCODE_GOOD;
}
/* Don't have the socket create interrupt signals */
static UA_StatusCode
TCP_setNoSigPipe(UA_FD sockfd) {
#ifdef SO_NOSIGPIPE
int val = 1;
int res = UA_setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof(val));
if(res < 0)
return UA_STATUSCODE_BADINTERNALERROR;
#endif
return UA_STATUSCODE_GOOD;
}
/* Do not merge packets on the socket (disable Nagle's algorithm) */
static UA_StatusCode
TCP_setNoNagle(UA_FD sockfd) {
int val = 1;
int res = UA_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
if(res < 0)
return UA_STATUSCODE_BADINTERNALERROR;
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
TCP_close(UA_ConnectionManager *cm, UA_FD fd) {
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Closing connection", (unsigned)fd);
TCPConnectionManager *tcm = (TCPConnectionManager*)cm;
/* Close the fd and deregister */
int ret = UA_close(fd);
if(ret != 0)
return UA_STATUSCODE_BADINTERNALERROR;
UA_StatusCode sc = UA_EventLoop_deregisterFD(tcm->cm.eventSource.eventLoop, fd);
if(sc != UA_STATUSCODE_GOOD)
return sc;
/* Reduce the count */
UA_assert(tcm->fdCount > 0);
tcm->fdCount--;
UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK, "TCP #%u\t| Socket closed", (unsigned)fd);
/* Stopped? */
if(tcm->fdCount == 0 && cm->eventSource.state == UA_EVENTSOURCESTATE_STOPPING) {
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP\t| All sockets closed, the EventLoop has stopped");
cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPED;
}
return UA_STATUSCODE_GOOD;
}
/* Gets called when a connection socket opens, receives data or closes */
static void
TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd,
void **fdcontext, short event) {
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Activity on the socket", (unsigned)fd);
/* Write-Event, a new connection has opened. */
UA_StatusCode res = UA_STATUSCODE_GOOD;
if(event & UA_POSIX_EVENT_WRITE) {
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Opening a new connection", (unsigned)fd);
/* The socket has opened. Signal it to the application. */
cm->connectionCallback(cm, (uintptr_t)fd, fdcontext,
UA_STATUSCODE_GOOD, UA_BYTESTRING_NULL);
/* Now we are interested in read-events. */
UA_EventLoop_modifyFD(cm->eventSource.eventLoop, fd, UA_POSIX_EVENT_READ,
(UA_FDCallback)TCP_connectionSocketCallback, *fdcontext);
return;
}
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Allocate receive buffer", (unsigned)fd);
/* Allocate the receive-buffer */
UA_ByteString response;
TCPConnectionManager *tcm = (TCPConnectionManager*)cm;
res = UA_ByteString_allocBuffer(&response, tcm->recvBufferSize);
if(res != UA_STATUSCODE_GOOD)
return; /* Retry in the next iteration */
/* Receive */
#ifndef _WIN32
ssize_t ret = UA_recv(fd, (char*)response.data, response.length, MSG_DONTWAIT);
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| recv(...) returned %zd", (unsigned)fd, ret);
#else
int ret = UA_recv(fd, (char*)response.data, response.length, MSG_DONTWAIT);
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| recv(...) returned %d", (unsigned)fd, ret);
#endif
if(ret > 0) {
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Received message of size %u",
(unsigned)fd, (unsigned)ret);
/* Callback to the application layer */
response.length = (size_t)ret; /* Set the length of the received buffer */
cm->connectionCallback(cm, (uintptr_t)fd, fdcontext,
UA_STATUSCODE_GOOD, response);
} else if(UA_ERRNO != UA_INTERRUPTED && UA_ERRNO != UA_EAGAIN) {
/* Orderly shutdown of the connection. Signal to the application and
* then close the connection. We end up in this path after shutdown was
* called on the socket. Here, we then are in the next EventLoop
* iteration and the socket is known to be unused. */
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| recv signaled closed connection", (unsigned)fd);
/* Close the connection if not a temporary error on a nonblocking socket */
cm->connectionCallback(cm, (uintptr_t)fd, fdcontext,
UA_STATUSCODE_BADCONNECTIONCLOSED,
UA_BYTESTRING_NULL);
TCP_close(cm, fd);
}
UA_ByteString_clear(&response);
}
/* Gets called when a new connection opens or if the listenSocket is closed */
static void
TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd,
void **fdcontext, short event) {
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Callback on server socket", (unsigned)fd);
/* Try to accept a new connection */
struct sockaddr_storage remote;
socklen_t remote_size = sizeof(remote);
UA_FD newsockfd = UA_accept(fd, (struct sockaddr*)&remote, &remote_size);
if(newsockfd == UA_INVALID_FD) {
/* Temporary error -- retry */
if(UA_ERRNO == UA_INTERRUPTED)
return;
/* Close the listen socket */
if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPING) {
UA_LOG_SOCKET_ERRNO_WRAP(
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Error %s, closing the server socket",
(unsigned)fd, errno_str));
}
TCP_close(cm, fd);
return;
}
/* Log the name of the remote host */
#if UA_LOGLEVEL <= 300
char hoststr[256];
int get_res = UA_getnameinfo((struct sockaddr *)&remote, sizeof(remote),
hoststr, sizeof(hoststr), NULL, 0, 0);
if(get_res != 0) {
get_res = UA_getnameinfo((struct sockaddr *)&remote, sizeof(remote),
hoststr, sizeof(hoststr), NULL, 0, NI_NUMERICHOST);
if(get_res != 0) {
hoststr[0] = 0;
UA_LOG_SOCKET_ERRNO_WRAP(
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| getnameinfo(...) could not resolve the "
"hostname (%s)", (unsigned)fd, errno_str));
}
}
UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Connection opened from \"%s\" via the server socket #%u",
(unsigned)newsockfd, hoststr, (unsigned)fd);
#endif
/* Configure the new socket */
UA_StatusCode res = UA_STATUSCODE_GOOD;
res |= TCP_setNonBlocking(newsockfd); /* Set the socket non-blocking */
res |= TCP_setNoSigPipe(newsockfd); /* Supress interrupts from the socket */
res |= TCP_setNoNagle(newsockfd); /* Disable Nagle's algorithm */
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_SOCKET_ERRNO_WRAP(
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Error seeting the TCP options (%s), closing",
(unsigned)newsockfd, errno_str));
/* Close the new socket */
UA_close(newsockfd);
return;
}
void *ctx = cm->initialConnectionContext;
/* The socket has opened. Signal it to the application. The callback can
* switch out the context. So put it into a temp variable. */
cm->connectionCallback(cm, (uintptr_t)newsockfd, &ctx, UA_STATUSCODE_GOOD,
UA_BYTESTRING_NULL);
/* Register in the EventLoop. Signal to the user if registering failed. */
res = UA_EventLoop_registerFD(cm->eventSource.eventLoop, newsockfd,
UA_POSIX_EVENT_READ,
(UA_FDCallback)TCP_connectionSocketCallback,
&cm->eventSource, ctx);
if(res != UA_STATUSCODE_GOOD) {
cm->connectionCallback(cm, (uintptr_t)newsockfd, &ctx,
UA_STATUSCODE_BADINTERNALERROR, UA_BYTESTRING_NULL);
UA_close(newsockfd);
}
/* Increase the count of registered fd */
TCPConnectionManager *tcm = (TCPConnectionManager*)cm;
tcm->fdCount++;
}
static void
TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) {
/* Get logging information */
char hoststr[256];
char portstr[16];
int get_res = UA_getnameinfo(ai->ai_addr, ai->ai_addrlen,
hoststr, sizeof(hoststr),
portstr, sizeof(portstr), NI_NUMERICSERV);
if(get_res != 0) {
get_res = UA_getnameinfo(ai->ai_addr, ai->ai_addrlen,
hoststr, sizeof(hoststr),
portstr, sizeof(portstr),
NI_NUMERICHOST | NI_NUMERICSERV);
if(get_res != 0) {
hoststr[0] = 0;
portstr[0] = 0;
UA_LOG_SOCKET_ERRNO_WRAP(
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP\t| getnameinfo(...) could not resolve the hostname (%s)",
errno_str));
}
}
/* Create the server socket */
UA_FD listenSocket = UA_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if(listenSocket == UA_INVALID_SOCKET) {
UA_LOG_SOCKET_ERRNO_WRAP(
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Error opening the listen socket for "
"\"%s\" on port %s(%s)",
(unsigned)listenSocket, hoststr, portstr, errno_str));
return;
}
UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| New server socket for \"%s\" on port %s",
(unsigned)listenSocket, hoststr, portstr);
/* Some Linux distributions have net.ipv6.bindv6only not activated. So
* sockets can double-bind to IPv4 and IPv6. This leads to problems. Use
* AF_INET6 sockets only for IPv6. */
int optval = 1;
#if UA_IPV6
if(ai->ai_family == AF_INET6 &&
UA_setsockopt(listenSocket, IPPROTO_IPV6, IPV6_V6ONLY,
(const char*)&optval, sizeof(optval)) == -1) {
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Could not set an IPv6 socket to IPv6 only, closing",
(unsigned)listenSocket);
UA_close(listenSocket);
return;
}
#endif
/* Allow rebinding to the IP/port combination. Eg. to restart the server. */
if(UA_setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR,
(const char *)&optval, sizeof(optval)) == -1) {
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Could not make the socket reusable, closing",
(unsigned)listenSocket);
UA_close(listenSocket);
return;
}
/* Set the socket non-blocking */
if(TCP_setNonBlocking(listenSocket) != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Could not set the socket non-blocking, closing",
(unsigned)listenSocket);
UA_close(listenSocket);
return;
}
/* Supress interrupts from the socket */
if(TCP_setNoSigPipe(listenSocket) != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Could not disable SIGPIPE, closing",
(unsigned)listenSocket);
UA_close(listenSocket);
return;
}
/* Bind socket to address */
int ret = UA_bind(listenSocket, ai->ai_addr, (socklen_t)ai->ai_addrlen);
if(ret < 0) {
UA_LOG_SOCKET_ERRNO_WRAP(
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Error binding the socket to the address (%s), closing",
(unsigned)listenSocket, errno_str));
UA_close(listenSocket);
return;
}
/* Start listening */
if(UA_listen(listenSocket, UA_MAXBACKLOG) < 0) {
UA_LOG_SOCKET_ERRNO_WRAP(
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Error listening on the socket (%s), closing",
(unsigned)listenSocket, errno_str));
UA_close(listenSocket);
return;
}
/* Register the socket */
UA_StatusCode res =
UA_EventLoop_registerFD(cm->eventSource.eventLoop, listenSocket,
UA_POSIX_EVENT_READ,
(UA_FDCallback) TCP_listenSocketCallback,
&cm->eventSource, NULL);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Error registering the socket in the "
"EventLoop, closing", (unsigned)listenSocket);
UA_close(listenSocket);
return;
}
/* Increase the registered fd count */
TCPConnectionManager *tcm = (TCPConnectionManager*)cm;
tcm->fdCount++;
}
static UA_StatusCode
TCP_registerListenSocketDomainName(UA_ConnectionManager *cm, const char *hostname,
const char *port) {
/* Get all the interface and IPv4/6 combinations for the configured hostname */
struct addrinfo hints, *res;
memset(&hints, 0, sizeof hints);
#if UA_IPV6
hints.ai_family = AF_UNSPEC; /* Allow IPv4 and IPv6 */
#else
hints.ai_family = AF_INET; /* IPv4 only */
#endif
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
hints.ai_protocol = IPPROTO_TCP;
#ifdef AI_ADDRCONFIG
hints.ai_flags |= AI_ADDRCONFIG; /* Only return IPv4/IPv6 if at least one
* such address is configured */
#endif
int retcode = UA_getaddrinfo(hostname, port, &hints, &res);
if(retcode != 0) {
UA_LOG_SOCKET_ERRNO_GAI_WRAP(
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP\t| getaddrinfo lookup for \"%s\" on port %s failed (%s)",
hostname, port, errno_str));
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Add listen sockets */
struct addrinfo *ai = res;
while(ai) {
TCP_registerListenSocket(cm, ai);
ai = ai->ai_next;
}
UA_freeaddrinfo(res);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
TCP_shutdownConnection(UA_ConnectionManager *cm, uintptr_t connectionId) {
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Shutdown called", (unsigned)connectionId);
/* Shutdown, will be picked up by the next iteration of the event loop */
#ifndef _WIN32
int res = UA_shutdown((UA_FD)connectionId, SHUT_RDWR);
#else
int res = UA_shutdown((UA_FD)connectionId, SD_BOTH);
#endif
UA_StatusCode retval = UA_STATUSCODE_GOOD;
if(res != 0) {
UA_LOG_SOCKET_ERRNO_WRAP(
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Error shutting down the socket (%s), closing",
(unsigned)connectionId, errno_str));
retval = TCP_close(cm, (UA_FD)connectionId);
}
return retval;
}
static UA_StatusCode
TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId,
size_t paramsSize, UA_KeyValuePair *params,
UA_ByteString *buf) {
/* Prevent OS signals when sending to a closed socket */
int flags = MSG_NOSIGNAL;
/* Send the full buffer. This may require several calls to send */
size_t nWritten = 0;
do {
ssize_t n = 0;
do {
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Attempting to send", (unsigned)connectionId);
size_t bytes_to_send = buf->length - nWritten;
n = UA_send((UA_FD)connectionId,
(const char*)buf->data + nWritten,
bytes_to_send, flags);
if(n < 0 && UA_ERRNO != UA_INTERRUPTED && UA_ERRNO != UA_AGAIN) {
UA_LOG_SOCKET_ERRNO_GAI_WRAP(
UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| Send failed with error %s",
(unsigned)connectionId, errno_str));
TCP_shutdownConnection(cm, connectionId);
UA_ByteString_clear(buf);
return UA_STATUSCODE_BADCONNECTIONCLOSED;
}
} while(n < 0);
nWritten += (size_t)n;
} while(nWritten < buf->length);
/* Free the buffer */
UA_ByteString_clear(buf);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
TCP_openConnection(UA_ConnectionManager *cm,
size_t paramsSize, UA_KeyValuePair *params,
void *context) {
/* Get the connection parameters */
char hostname[256];
char portStr[16];
/* Prepare the port parameter as a string */
const UA_UInt16 *port = (const UA_UInt16*)
UA_KeyValueMap_getScalar(params, paramsSize,
UA_QUALIFIEDNAME(0, "target-port"),
&UA_TYPES[UA_TYPES_UINT16]);
if(!port) {
UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_EVENTLOOP,
"TCP\t| Open TCP Connection: No target port defined, aborting");
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_snprintf(portStr, 6, "%d", *port);
/* Prepare the hostname string */
const UA_String *host = (const UA_String*)
UA_KeyValueMap_getScalar(params, paramsSize,
UA_QUALIFIEDNAME(0, "target-hostname"),
&UA_TYPES[UA_TYPES_STRING]);
if(!host) {
UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_EVENTLOOP,
"TCP\t| Open TCP Connection: No target hostname defined, aborting");
return UA_STATUSCODE_BADINTERNALERROR;
}
if(host->length >= 256) {
UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_EVENTLOOP,
"TCP\t| Open TCP Connection: No target hostname too long, aborting");
return UA_STATUSCODE_BADINTERNALERROR;
}
strncpy(hostname, (const char*)host->data, host->length);
hostname[host->length] = 0;
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK, "TCP\t| Open a connection to \"%s\" on port %s",
hostname, portStr);
/* Create the socket description from the connectString
* TODO: Make this non-blocking */
struct addrinfo hints, *info;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
int error = getaddrinfo(hostname, portStr, &hints, &info);
if(error != 0) {
UA_LOG_SOCKET_ERRNO_GAI_WRAP(
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP\t| Lookup of %s failed with error %d - %s",
hostname, error, errno_str));
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Create a socket */
UA_FD newSock = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
if(newSock == UA_INVALID_SOCKET) {
freeaddrinfo(info);
UA_LOG_SOCKET_ERRNO_WRAP(
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP\t| Could not create socket to connect to %s (%s)",
hostname, errno_str));
return UA_STATUSCODE_BADDISCONNECT;
}
/* Set the socket options */
UA_StatusCode res = UA_STATUSCODE_GOOD;
res |= TCP_setNonBlocking(newSock);
res |= TCP_setNoSigPipe(newSock);
res |= TCP_setNoNagle(newSock);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_SOCKET_ERRNO_WRAP(
UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP\t| Could not set socket options: %s", errno_str));
freeaddrinfo(info);
UA_close(newSock);
return res;
}
/* Non-blocking connect */
error = UA_connect(newSock, info->ai_addr, info->ai_addrlen);
freeaddrinfo(info);
if(error != 0 && UA_ERRNO != UA_ERR_CONNECTION_PROGRESS) {
UA_LOG_SOCKET_ERRNO_WRAP(
UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP\t| Connecting the socket to %s failed (%s)",
hostname, errno_str));
return UA_STATUSCODE_BADDISCONNECT;
}
/* Register the fd to trigger when output is possible (the connection is open) */
res = UA_EventLoop_registerFD(cm->eventSource.eventLoop, newSock, UA_POSIX_EVENT_WRITE,
(UA_FDCallback)TCP_connectionSocketCallback,
&cm->eventSource, context);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP\t| Registering the socket to connect to %s failed", hostname);
UA_close(newSock);
return res;
}
/* Increase the count */
TCPConnectionManager *tcm = (TCPConnectionManager*)cm;
tcm->fdCount++;
UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK,
"TCP #%u\t| New connection to \"%s\" on port %s",
(unsigned)newSock, hostname, portStr);
return UA_STATUSCODE_GOOD;
}
/* Asynchronously register the listenSocket */
static UA_StatusCode
TCP_eventSourceStart(UA_ConnectionManager *cm) {
/* Check the state */
if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPED) {
UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_EVENTLOOP, "To start the TCP ConnectionManager, "
"it has to be registered in an EventLoop and not started");
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Initialize networking */
#ifdef _WIN32
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
/* Listening on a socket? */
const UA_UInt16 *port = (const UA_UInt16*)
UA_KeyValueMap_getScalar(cm->eventSource.params,
cm->eventSource.paramsSize,
UA_QUALIFIEDNAME(0, "listen-port"),
&UA_TYPES[UA_TYPES_UINT16]);
if(!port) {
UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_EVENTLOOP,
"TCP\t| No port configured, don't accept connections");
return UA_STATUSCODE_GOOD;
}
/* Prepare the port parameter as a string */
char portno[6];
UA_snprintf(portno, 6, "%d", *port);
/* Get the hostnames configuration */
const UA_Variant *hostNames =
UA_KeyValueMap_get(cm->eventSource.params,
cm->eventSource.paramsSize,
UA_QUALIFIEDNAME(0, "listen-hostnames"));
if(!hostNames) {
/* No hostnames configured */
UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK, "TCP\t| Listening on all interfaces");
TCP_registerListenSocketDomainName(cm, NULL, portno);
} else if(hostNames->type != &UA_TYPES[UA_TYPES_STRING]) {
/* Wrong datatype */
UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_EVENTLOOP,
"TCP\t| The hostnames have to be strings");
return UA_STATUSCODE_BADINTERNALERROR;
} else {
size_t interfaces = hostNames->arrayLength;
if(UA_Variant_isScalar(hostNames))
interfaces = 1;
if(interfaces == 0) {
UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_EVENTLOOP, "TCP\t| Listening on all interfaces");
TCP_registerListenSocketDomainName(cm, NULL, portno);
} else {
/* Iterate over the configured hostnames */
UA_String *hostStrings = (UA_String*)hostNames->data;
for(size_t i = 0; i < hostNames->arrayLength; i++) {
char hostname[512];
if(hostStrings[i].length >= sizeof(hostname))
continue;
memcpy(hostname, hostStrings[i].data, hostStrings->length);
hostname[hostStrings->length] = '\0';
TCP_registerListenSocketDomainName(cm, hostname, portno);
}
}
}
/* The receive buffersize was configured? */
const UA_UInt16 *bufSize = (const UA_UInt16*)
UA_KeyValueMap_getScalar(cm->eventSource.params,
cm->eventSource.paramsSize,
UA_QUALIFIEDNAME(0, "recv-bufsize"),
&UA_TYPES[UA_TYPES_UINT16]);
if(bufSize)
((TCPConnectionManager*)cm)->recvBufferSize = *bufSize;
/* Set the EventSource to the started state */
cm->eventSource.state = UA_EVENTSOURCESTATE_STARTED;
return UA_STATUSCODE_GOOD;
}
static void
TCP_shutdownCallback(UA_EventSource *es, UA_FD fd, void *fdcontext, short event) {
TCP_shutdownConnection((UA_ConnectionManager*)es, (uintptr_t)fd);
}
static void
TCP_eventSourceStop(UA_ConnectionManager *cm) {
UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK, "TCP\t| Shutting down the ConnectionManager");
/* Shut down all registered fd. The cm is set to "stopped" when the last fd
* is closed and deregistered in the callback from the EventLoop. */
UA_EventLoop_iterateFD(cm->eventSource.eventLoop, &cm->eventSource,
TCP_shutdownCallback);
cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPING;
TCPConnectionManager *tcm = (TCPConnectionManager*)cm;
/* Closed? */
if(tcm->fdCount == 0 && cm->eventSource.state == UA_EVENTSOURCESTATE_STOPPING)
cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPED;
UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_NETWORK, "TCP\t| EventSource successfully stopped");
}
static UA_StatusCode
TCP_eventSourceDelete(UA_ConnectionManager *cm) {
if(cm->eventSource.state >= UA_EVENTSOURCESTATE_STARTING) {
UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop),
UA_LOGCATEGORY_EVENTLOOP,
"TCP\t| The EventSource must be stopped before it can be deleted");
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_deinitialize_architecture_network();
/* Delete the parameters */
UA_Array_delete(cm->eventSource.params,
cm->eventSource.paramsSize,
&UA_TYPES[UA_TYPES_KEYVALUEPAIR]);
cm->eventSource.params = NULL;
cm->eventSource.paramsSize = 0;
UA_String_clear(&cm->eventSource.name);
UA_free(cm);
return UA_STATUSCODE_GOOD;
}
UA_ConnectionManager *
UA_ConnectionManager_TCP_new(const UA_String eventSourceName) {
TCPConnectionManager *cm = (TCPConnectionManager*)
UA_calloc(1, sizeof(TCPConnectionManager));
if(!cm)
return NULL;
UA_String_copy(&eventSourceName, &cm->cm.eventSource.name);
cm->cm.eventSource.start = (UA_StatusCode (*)(UA_EventSource *)) TCP_eventSourceStart;
cm->cm.eventSource.stop = (void (*)(UA_EventSource *))TCP_eventSourceStop;
cm->cm.eventSource.free = (UA_StatusCode (*)(UA_EventSource *))TCP_eventSourceDelete;
cm->cm.openConnection = TCP_openConnection;
cm->cm.allocNetworkBuffer = TCP_allocNetworkBuffer;
cm->cm.freeNetworkBuffer = TCP_freeNetworkBuffer;
cm->cm.sendWithConnection = TCP_sendWithConnection;
cm->cm.closeConnection = TCP_shutdownConnection;
cm->recvBufferSize = 1 << 14; /* TODO: Read from the config */
return &cm->cm;
}

View File

@ -72,7 +72,8 @@ void UA_sleep_ms(unsigned long ms);
#define UA_ENABLE_LOG_COLORS
#define UA_getnameinfo getnameinfo
#define UA_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) \
getnameinfo(sa, salen, host, hostlen, serv, servlen, flags)
#define UA_send send
#define UA_recv recv
#define UA_sendto sendto

View File

@ -64,7 +64,8 @@
#define UA_ENABLE_LOG_COLORS
#define UA_getnameinfo getnameinfo
#define UA_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) \
getnameinfo(sa, salen, host, hostlen, serv, servlen, flags)
#define UA_send send
#define UA_recv recv
#define UA_sendto sendto

View File

@ -83,7 +83,8 @@ void UA_sleep_ms(unsigned long ms);
#define UA_ERRNO WSAGetLastError()
#endif
#define UA_getnameinfo getnameinfo
#define UA_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) \
getnameinfo(sa, salen, host, hostlen, serv, servlen, flags)
#define UA_send(sockfd, buf, len, flags) send(sockfd, buf, (int)(len), flags)
#define UA_recv recv
#define UA_sendto(sockfd, buf, len, flags, dest_addr, addrlen) sendto(sockfd, (const char*)(buf), (int)(len), flags, dest_addr, (int) (addrlen))

View File

@ -90,7 +90,8 @@ void UA_sleep_ms(unsigned long ms);
# define errno
#endif
#define UA_getnameinfo getnameinfo
#define UA_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) \
getnameinfo(sa, (socklen_t)salen, host, (DWORD)hostlen, serv, (DWORD)servlen, flags)
#define UA_send(sockfd, buf, len, flags) send(sockfd, buf, (int)(len), flags)
#define UA_recv(sockfd, buf, len, flags) recv(sockfd, buf, (int)(len), flags)
#define UA_sendto(sockfd, buf, len, flags, dest_addr, addrlen) sendto(sockfd, (const char*)(buf), (int)(len), flags, dest_addr, (int) (addrlen))

2
deps/ua-nodeset vendored

@ -1 +1 @@
Subproject commit 393b633468a5d1d062dd253e1488d1d8ba335b6f
Subproject commit f71b3f411d5cb16097c3ae0c744f67ad45535ffb

View File

@ -165,6 +165,7 @@ add_example(server_loglevel server_loglevel.c)
if(UA_ENABLE_HISTORIZING)
add_example(tutorial_server_historicaldata tutorial_server_historicaldata.c)
add_example(tutorial_server_historicaldata_circular tutorial_server_historicaldata_circular.c)
endif()
if(UA_ENABLE_ENCRYPTION OR UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL")

View File

@ -108,14 +108,14 @@ setupOrFilter(UA_ContentFilterElement *element){
}
static void
setupOfTypeFilter(UA_ContentFilterElement *element, UA_UInt32 typeId){
setupOfTypeFilter(UA_ContentFilterElement *element, UA_UInt16 nsIndex, UA_UInt32 typeId){
element->filterOperands[0].content.decoded.type = &UA_TYPES[UA_TYPES_LITERALOPERAND];
element->filterOperands[0].encoding = UA_EXTENSIONOBJECT_DECODED;
UA_LiteralOperand *literalOperand = UA_LiteralOperand_new();
UA_LiteralOperand_init(literalOperand);
UA_NodeId *nodeId = UA_NodeId_new();
UA_NodeId_init(nodeId);
nodeId->namespaceIndex = 0;
nodeId->namespaceIndex = nsIndex;
nodeId->identifierType = UA_NODEIDTYPE_NUMERIC;
nodeId->identifier.numeric = typeId;
UA_Variant_setScalar(&literalOperand->value, nodeId, &UA_TYPES[UA_TYPES_NODEID]);
@ -133,6 +133,14 @@ setupOfTypeFilter(UA_ContentFilterElement *element, UA_UInt32 typeId){
*
* filterSelection 0:
* ( (OfType AUDITEVENTTYPE ) (or) (OfType EVENTQUEUEOVERFLOWEVENTTYPE) )
* filterSelection 2:
* ((EventTypeId == NodeID("StartScanEvent") ||
* (EventTypeId == NodeID("DcpScanFinishedEvent") ||
* (EventTypeId == NodeID("ScanFinishedEvent") ||
* (EventTypeId == NodeID("CancelScanEvent") ||
* (EventTypeId == NodeID("CancelScanFinishedEvent") ||
* (EventTypeId == NodeID("ShutdownEvent"))
*
*/
static UA_StatusCode
setupWhereClauses(UA_ContentFilter *contentFilter, UA_UInt16 whereClauseSize, UA_UInt16 filterSelection){
@ -147,7 +155,7 @@ setupWhereClauses(UA_ContentFilter *contentFilter, UA_UInt16 whereClauseSize, UA
}
UA_StatusCode result = UA_STATUSCODE_GOOD;
switch(filterSelection) {
case 0:
case 0: {
contentFilter->elements[0].filterOperator = UA_FILTEROPERATOR_OR;
contentFilter->elements[1].filterOperator = UA_FILTEROPERATOR_OFTYPE;
contentFilter->elements[2].filterOperator = UA_FILTEROPERATOR_OFTYPE;
@ -156,17 +164,77 @@ setupWhereClauses(UA_ContentFilter *contentFilter, UA_UInt16 whereClauseSize, UA
contentFilter->elements[2].filterOperandsSize = 1;
/* Setup Operand Arrays */
result = setupOperandArrays(contentFilter);
if(result != UA_STATUSCODE_GOOD){
if(result != UA_STATUSCODE_GOOD) {
UA_ContentFilter_clear(contentFilter);
return UA_STATUSCODE_BADCONFIGURATIONERROR;
}
/* first Element (OR) */
setupOrFilter(&contentFilter->elements[0]);
/* second Element (OfType) */
setupOfTypeFilter(&contentFilter->elements[1], 60443);
setupOfTypeFilter(&contentFilter->elements[1], 1, 5003);
/* third Element (OfType) */
setupOfTypeFilter(&contentFilter->elements[2], UA_NS0ID_EVENTQUEUEOVERFLOWEVENTTYPE);
setupOfTypeFilter(&contentFilter->elements[2], 0,
UA_NS0ID_EVENTQUEUEOVERFLOWEVENTTYPE);
break;
}
case 1: {
contentFilter->elements[0].filterOperator = UA_FILTEROPERATOR_OFTYPE;
UA_UInt32 placeholder_ScanFinishedEvent = 10000;
setupOfTypeFilter(&contentFilter->elements[0], 1,
placeholder_ScanFinishedEvent);
break;
}
case 2: {
contentFilter->elements[0].filterOperator = UA_FILTEROPERATOR_OR;
contentFilter->elements[1].filterOperator = UA_FILTEROPERATOR_OR;
contentFilter->elements[2].filterOperator = UA_FILTEROPERATOR_OR;
contentFilter->elements[3].filterOperator = UA_FILTEROPERATOR_OR;
contentFilter->elements[4].filterOperator = UA_FILTEROPERATOR_OR;
contentFilter->elements[5].filterOperator = UA_FILTEROPERATOR_OFTYPE;
contentFilter->elements[6].filterOperator = UA_FILTEROPERATOR_OFTYPE;
contentFilter->elements[7].filterOperator = UA_FILTEROPERATOR_OFTYPE;
contentFilter->elements[8].filterOperator = UA_FILTEROPERATOR_OFTYPE;
contentFilter->elements[9].filterOperator = UA_FILTEROPERATOR_OFTYPE;
contentFilter->elements[10].filterOperator = UA_FILTEROPERATOR_OFTYPE;
// init or clauses
setupOrFilter(&contentFilter->elements[0]);
setupOrFilter(&contentFilter->elements[1]);
setupOrFilter(&contentFilter->elements[2]);
setupOrFilter(&contentFilter->elements[3]);
setupOrFilter(&contentFilter->elements[4]);
// init oftype
UA_UInt32 placeholder_StartScanEvent = 10000;
UA_UInt32 placeholder_DcpScanFinishedEvent = 10001;
UA_UInt32 CancelScanEvent = 10003;
UA_UInt32 placeholder_CancelScanFinishedEvent = 10004;
UA_UInt32 placeholder_ShutdownEvent = 10005;
UA_UInt32 placeholder_ShutdownEvent_1 = 10006;
setupOfTypeFilter(&contentFilter->elements[5], 1, placeholder_StartScanEvent);
setupOfTypeFilter(&contentFilter->elements[6], 1,
placeholder_DcpScanFinishedEvent);
setupOfTypeFilter(&contentFilter->elements[7], 1,
placeholder_ShutdownEvent_1);
setupOfTypeFilter(&contentFilter->elements[8], 1, CancelScanEvent);
setupOfTypeFilter(&contentFilter->elements[9], 1,
placeholder_CancelScanFinishedEvent);
setupOfTypeFilter(&contentFilter->elements[10], 1, placeholder_ShutdownEvent);
break;
}
case 3:{
UA_UInt32 placeholder_ShutdownEvent_2 = 10000;
contentFilter->elements[0].filterOperator = UA_FILTEROPERATOR_OFTYPE;
setupOfTypeFilter(&contentFilter->elements[0], 1, placeholder_ShutdownEvent_2);
break;
}
case 4: {
contentFilter->elements[0].filterOperator = UA_FILTEROPERATOR_AND;
contentFilter->elements[1].filterOperator = UA_FILTEROPERATOR_OFTYPE;
contentFilter->elements[2].filterOperator = UA_FILTEROPERATOR_AND;
contentFilter->elements[2].filterOperator = UA_FILTEROPERATOR_EQUALS;
contentFilter->elements[2].filterOperator = UA_FILTEROPERATOR_EQUALS;
break;
}
default:
UA_ContentFilter_clear(contentFilter);
return UA_STATUSCODE_BADCONFIGURATIONERROR;
@ -175,11 +243,10 @@ setupWhereClauses(UA_ContentFilter *contentFilter, UA_UInt16 whereClauseSize, UA
}
static void
handler_events(UA_Client *client, UA_UInt32 subId, void *subContext,
handler_events_filter(UA_Client *client, UA_UInt32 subId, void *subContext,
UA_UInt32 monId, void *monContext,
size_t nEventFields, UA_Variant *eventFields) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Notification");
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Received Event Notification (Filter passed)");
for(size_t i = 0; i < nEventFields; ++i) {
if(UA_Variant_hasScalarType(&eventFields[i], &UA_TYPES[UA_TYPES_UINT16])) {
UA_UInt16 severity = *(UA_UInt16 *)eventFields[i].data;
@ -188,8 +255,7 @@ handler_events(UA_Client *client, UA_UInt32 subId, void *subContext,
UA_LocalizedText *lt = (UA_LocalizedText *)eventFields[i].data;
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Message: '%.*s'", (int)lt->text.length, lt->text.data);
}
else {
} else {
#ifdef UA_ENABLE_TYPEDESCRIPTION
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Don't know how to handle type: '%s'", eventFields[i].type->typeName);
@ -262,11 +328,11 @@ int main(int argc, char *argv[]) {
UA_MonitoredItemCreateResult result =
UA_Client_MonitoredItems_createEvent(client, subId,
UA_TIMESTAMPSTORETURN_BOTH, item,
&monId, handler_events, NULL);
&monId, handler_events_filter, NULL);
if(result.statusCode != UA_STATUSCODE_GOOD) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Could not add the MonitoredItem with %s", UA_StatusCode_name(retval));
"Could not add the MonitoredItem with %s", UA_StatusCode_name(result.statusCode));
goto cleanup;
} else {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
@ -276,7 +342,7 @@ int main(int argc, char *argv[]) {
monId = result.monitoredItemId;
while(running)
retval = UA_Client_run_iterate(client, 100);
UA_Client_run_iterate(client, 100);
/* Delete the subscription */
cleanup:

View File

@ -48,27 +48,27 @@ addSampleEventTypes(UA_Server *server) {
UA_Array_new(SAMPLE_EVENT_TYPES_COUNT, &UA_TYPES[UA_TYPES_NODEID]);
UA_StatusCode retval = addEventType(server, "SampleBaseEventType",
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE),
UA_NODEID_NULL,
UA_NODEID_NUMERIC(1, 5000),
&eventTypes[0]);
if (retval != UA_STATUSCODE_GOOD) return retval;
retval = addEventType(server, "SampleDeviceFailureEventType",
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE),
UA_NODEID_NULL,
UA_NODEID_NUMERIC(1, 5001),
&eventTypes[1]);
if (retval != UA_STATUSCODE_GOOD) return retval;
retval = addEventType(server, "SampleEventQueueOverflowEventType",
UA_NODEID_NUMERIC(0, UA_NS0ID_EVENTQUEUEOVERFLOWEVENTTYPE),
UA_NODEID_NULL,
UA_NODEID_NUMERIC(1, 5002),
&eventTypes[2]);
if (retval != UA_STATUSCODE_GOOD) return retval;
retval = addEventType(server, "SampleProgressEventType",
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE),
UA_NODEID_NULL,
UA_NODEID_NUMERIC(1, 5003),
&eventTypes[3]);
if (retval != UA_STATUSCODE_GOOD) return retval;
retval = addEventType(server, "SampleAuditSecurityEventType",
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE),
UA_NODEID_NUMERIC(0,60443),
UA_NODEID_NUMERIC(1, 5004),
&eventTypes[4]);
if (retval != UA_STATUSCODE_GOOD) return retval;
return UA_STATUSCODE_GOOD;

View File

@ -91,7 +91,7 @@ if(UA_NAMESPACE_ZERO STREQUAL "FULL")
ua_generate_nodeset_and_datatypes(
NAME "plc"
# PLCopen does not define custom types. Only generate the nodeset
FILE_NS "${FILE_NS_DIRPREFIX}/PLCopen/Opc.Ua.Plc.NodeSet2.xml"
FILE_NS "${FILE_NS_DIRPREFIX}/PLCopen/Opc.Ua.PLCopen.NodeSet2_V1.02.xml"
# PLCopen depends on the di nodeset, which must be generated before
DEPENDS "di"
INTERNAL

View File

@ -206,9 +206,9 @@ changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
/* Remove the callback added for cyclic repetition */
static void
removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId){
if(callbackId && (pthread_join(callbackId, NULL) != 0))
if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0))
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Pthread Join Failed thread: %ld\n", callbackId);
"Pthread Join Failed thread: %lu\n", (long unsigned)callbackId);
}
/**
@ -705,8 +705,8 @@ int main(int argc, char **argv) {
size_t pubLoopVariable = 0;
for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher;
pubLoopVariable++) {
fprintf(fpPublisher, "%ld,%ld.%09ld,%lf\n",
publishCounterValue[pubLoopVariable],
fprintf(fpPublisher, "%lu,%ld.%09ld,%lf\n",
(long unsigned)publishCounterValue[pubLoopVariable],
publishTimestamp[pubLoopVariable].tv_sec,
publishTimestamp[pubLoopVariable].tv_nsec,
pressureValues[pubLoopVariable]);
@ -717,8 +717,8 @@ int main(int argc, char **argv) {
size_t pubLoopVariable = 0;
for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher;
pubLoopVariable++) {
printf("%ld,%ld.%09ld,%lf\n",
publishCounterValue[pubLoopVariable],
printf("%lu,%ld.%09ld,%lf\n",
(long unsigned)publishCounterValue[pubLoopVariable],
publishTimestamp[pubLoopVariable].tv_sec,
publishTimestamp[pubLoopVariable].tv_nsec,
pressureValues[pubLoopVariable]);

View File

@ -194,9 +194,9 @@ changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt
/* Remove the callback added for cyclic repetition */
static void
removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) {
if(callbackId && (pthread_join(callbackId, NULL) != 0))
if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0))
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Pthread Join Failed thread: %ld\n", callbackId);
"Pthread Join Failed thread: %lu\n", (long unsigned)callbackId);
}
/**
@ -665,8 +665,8 @@ int main(int argc, char **argv) {
size_t subLoopVariable = 0;
for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber;
subLoopVariable++) {
fprintf(fpSubscriber, "%ld,%ld.%09ld,%lf\n",
subscribeCounterValue[subLoopVariable],
fprintf(fpSubscriber, "%lu,%ld.%09ld,%lf\n",
(long unsigned)subscribeCounterValue[subLoopVariable],
subscribeTimestamp[subLoopVariable].tv_sec,
subscribeTimestamp[subLoopVariable].tv_nsec,
pressureValues[subLoopVariable]);
@ -677,8 +677,8 @@ int main(int argc, char **argv) {
size_t subLoopVariable = 0;
for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber;
subLoopVariable++) {
fprintf(fpSubscriber, "%ld,%ld.%09ld,%lf\n",
subscribeCounterValue[subLoopVariable],
fprintf(fpSubscriber, "%lu,%ld.%09ld,%lf\n",
(long unsigned)subscribeCounterValue[subLoopVariable],
subscribeTimestamp[subLoopVariable].tv_sec,
subscribeTimestamp[subLoopVariable].tv_nsec,
pressureValues[subLoopVariable]);

View File

@ -79,6 +79,7 @@
#include <open62541/types_generated.h>
#include <open62541/plugin/pubsub_ethernet.h>
#include <open62541/plugin/securitypolicy_default.h>
#include <linux/if_link.h>
#include <linux/if_xdp.h>
@ -103,12 +104,10 @@ UA_DataSetReaderConfig readerConfig;
/* Qbv offset */
#define DEFAULT_QBV_OFFSET 125
#define DEFAULT_SOCKET_PRIORITY 3
#if defined(PUBLISHER)
#define PUBLISHER_ID 2235
#define WRITER_GROUP_ID 100
#define DATA_SET_WRITER_ID 62541
#define DEFAULT_PUBLISHING_MAC_ADDRESS "opc.eth://01-00-5E-00-00-01:8.3"
#endif
#if defined(SUBSCRIBER)
#define PUBLISHER_ID_SUB 2234
#define WRITER_GROUP_ID_SUB 101
@ -125,9 +124,11 @@ UA_DataSetReaderConfig readerConfig;
#define MILLI_SECONDS 1000 * 1000
#define SECONDS 1000 * 1000 * 1000
#define SECONDS_SLEEP 5
#if defined(PUBLISHER)
/* Publisher will sleep for 60% of cycle time and then prepares the */
/* transmission packet within 40% */
static UA_Double pubWakeupPercentage = 0.6;
#endif
/* Subscriber will wakeup only during start of cycle and check whether */
/* the packets are received */
static UA_Double subWakeupPercentage = 0;
@ -150,6 +151,24 @@ static UA_Double userAppWakeupPercentage = 0.3;
#define CLOCKID CLOCK_TAI
#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
#define UA_AES128CTR_SIGNING_KEY_LENGTH 32
#define UA_AES128CTR_KEY_LENGTH 16
#define UA_AES128CTR_KEYNONCE_LENGTH 4
#if defined(PUBLISHER)
UA_Byte signingKeyPub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0};
UA_Byte encryptingKeyPub[UA_AES128CTR_KEY_LENGTH] = {0};
UA_Byte keyNoncePub[UA_AES128CTR_KEYNONCE_LENGTH] = {0};
#endif
#if defined(SUBSCRIBER)
UA_Byte signingKeySub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0};
UA_Byte encryptingKeySub[UA_AES128CTR_KEY_LENGTH] = {0};
UA_Byte keyNonceSub[UA_AES128CTR_KEYNONCE_LENGTH] = {0};
#endif
#endif
/* If the Hardcoded publisher/subscriber MAC addresses need to be changed,
* change PUBLISHING_MAC_ADDRESS and SUBSCRIBING_MAC_ADDRESS
*/
@ -334,7 +353,7 @@ addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
char threadNamePub[10] = "Publisher";
*callbackId = threadCreation((UA_Int16)pubPriority, (size_t)pubCore, publisherETF, threadNamePub, threadArguments);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Publisher thread callback Id: %ld\n", *callbackId);
"Publisher thread callback Id: %lu\n", (long unsigned)*callbackId);
#endif
}
else {
@ -343,7 +362,7 @@ addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
char threadNameSub[11] = "Subscriber";
*callbackId = threadCreation((UA_Int16)subPriority,(size_t)subCore, subscriber, threadNameSub, threadArguments);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Subscriber thread callback Id: %ld\n", *callbackId);
"Subscriber thread callback Id: %lu\n", (long unsigned)*callbackId);
#endif
}
@ -363,9 +382,9 @@ changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
/* Remove the callback added for cyclic repetition */
static void
removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) {
if(callbackId && (pthread_join(callbackId, NULL) != 0))
if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0))
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Pthread Join Failed thread: %ld\n", callbackId);
"Pthread Join Failed thread: %lu\n", (long unsigned)callbackId);
}
@ -454,12 +473,29 @@ addReaderGroup(UA_Server *server) {
readerGroupConfig.timeout = 0; //Blocking socket
}
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
/* Encryption settings */
UA_ServerConfig *config = UA_Server_getConfig(server);
readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
#endif
readerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback;
readerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback;
readerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback;
UA_Server_addReaderGroup(server, connectionIdentSubscriber, &readerGroupConfig,
&readerGroupIdentifier);
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
/* Add the encryption key informaton */
UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeySub};
UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeySub};
UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNonceSub};
// TODO security token not necessary for readergroup (extracted from security-header)
UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn);
#endif
}
/* Set SubscribedDataSet type to TargetVariables data type
@ -811,6 +847,12 @@ addWriterGroup(UA_Server *server) {
writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE];
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
UA_ServerConfig *config = UA_Server_getConfig(server);
writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[1];
#endif
/* The configuration flags for the messages are encapsulated inside the
* message- and transport settings extension objects. These extension
* objects are defined by the standard. e.g.
@ -827,6 +869,14 @@ addWriterGroup(UA_Server *server) {
UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent);
UA_Server_setWriterGroupOperational(server, writerGroupIdent);
UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage);
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
/* Add the encryption key informaton */
UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub};
UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub};
UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub};
UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn);
#endif
}
/* DataSetWriter handling */
@ -859,7 +909,8 @@ updateMeasurementsPublisher(struct timespec start_time,
}
if(consolePrint)
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Pub:%ld,%ld.%09ld\n", counterValue, start_time.tv_sec, start_time.tv_nsec);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Pub:%lu,%ld.%09ld\n",
(long unsigned)counterValue, start_time.tv_sec, start_time.tv_nsec);
if (signalTerm != UA_TRUE){
publishTimestamp[measurementsPublisher] = start_time;
@ -884,7 +935,8 @@ updateMeasurementsSubscriber(struct timespec receive_time, UA_UInt64 counterValu
}
if(consolePrint)
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Sub:%ld,%ld.%09ld\n", counterValue, receive_time.tv_sec, receive_time.tv_nsec);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Sub:%lu,%ld.%09ld\n",
(long unsigned)counterValue, receive_time.tv_sec, receive_time.tv_nsec);
if (signalTerm != UA_TRUE){
subscribeTimestamp[measurementsSubscriber] = receive_time;
@ -894,6 +946,7 @@ updateMeasurementsSubscriber(struct timespec receive_time, UA_UInt64 counterValu
}
#endif
#if defined(PUBLISHER)
/**
* **Publisher thread routine**
*
@ -972,6 +1025,7 @@ void *publisherETF(void *arg) {
runningServer = UA_FALSE;
return (void*)NULL;
}
#endif
#if defined(SUBSCRIBER)
/**
@ -1028,6 +1082,9 @@ void *subscriber(void *arg) {
if (*runningSub == UA_FALSE)
signalTerm = UA_TRUE;
#if defined(SUBSCRIBER) && !defined(PUBLISHER)
runningServer = UA_FALSE;
#endif
UA_free(threadArgumentsSubscriber);
return (void*)NULL;
}
@ -1058,7 +1115,11 @@ void *userApplicationPubSub(void *arg) {
nextnanosleeptimeUserApplication.tv_nsec = threadBaseTime.tv_nsec + (__syscall_slong_t)(cycleTimeInMsec * MILLI_SECONDS * userAppWakeupPercentage);
nanoSecondFieldConversion(&nextnanosleeptimeUserApplication);
#if defined(PUBLISHER) && defined(SUBSCRIBER)
while (*runningSub || *runningPub) {
#else
while (*runningSub) {
#endif
/* The User application threads wakes up at the configured userApp wake up percentage (30%) of each cycle */
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL);
#if defined(SUBSCRIBER)
@ -1144,7 +1205,7 @@ static pthread_t threadCreation(UA_Int16 threadPriority, size_t coreAffinity, vo
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName);
if (CPU_ISSET(coreAffinity, &cpuset))
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %ld\n", applicationName, coreAffinity);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %lu\n", applicationName, (long unsigned)coreAffinity);
return threadID;
}
@ -1259,7 +1320,6 @@ static void removeServerNodes(UA_Server *server) {
}
UA_Server_deleteNode(server, runningPubStatusNodeID, UA_TRUE);
UA_NodeId_clear(&runningPubStatusNodeID);
UA_Server_deleteNode(server, subNodeID, UA_TRUE);
UA_NodeId_clear(&subNodeID);
for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++)
@ -1463,6 +1523,24 @@ int main(int argc, char **argv) {
UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL);
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
#if defined(PUBLISHER) && defined(SUBSCRIBER)
/* Instantiate the PubSub SecurityPolicy */
config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*)
UA_calloc(2, sizeof(UA_PubSubSecurityPolicy));
config->pubSubConfig.securityPoliciesSize = 2;
#else
config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*)
UA_malloc(sizeof(UA_PubSubSecurityPolicy));
config->pubSubConfig.securityPoliciesSize = 1;
#endif
#endif
#if defined(UA_ENABLE_PUBSUB_ENCRYPTION) && defined(PUBLISHER)
UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[1],
&config->logger);
#endif
#if defined(PUBLISHER)
UA_NetworkAddressUrlDataType networkAddressUrlPub;
#endif
@ -1512,6 +1590,10 @@ if (enableCsvLog)
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
#endif
#if defined(UA_ENABLE_PUBSUB_ENCRYPTION) && defined(SUBSCRIBER)
UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[0],
&config->logger);
#endif
#if defined (PUBLISHER) && defined(SUBSCRIBER)
UA_ServerConfig_addPubSubTransportLayer(config, UA_PubSubTransportLayerEthernet());
#endif
@ -1537,7 +1619,10 @@ if (enableCsvLog)
#endif
retval |= UA_Server_run(server, &runningServer);
#if defined(SUBSCRIBER)
UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier);
#endif
#if defined(PUBLISHER) || defined(SUBSCRIBER)
returnValue = pthread_join(userThreadID, NULL);
if (returnValue != 0)
@ -1550,8 +1635,8 @@ if (enableCsvLog)
size_t pubLoopVariable = 0;
for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher;
pubLoopVariable++) {
fprintf(fpPublisher, "%ld,%ld.%09ld\n",
publishCounterValue[pubLoopVariable],
fprintf(fpPublisher, "%lu,%ld.%09ld\n",
(long unsigned)publishCounterValue[pubLoopVariable],
publishTimestamp[pubLoopVariable].tv_sec,
publishTimestamp[pubLoopVariable].tv_nsec);
}
@ -1561,8 +1646,8 @@ if (enableCsvLog)
size_t subLoopVariable = 0;
for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber;
subLoopVariable++) {
fprintf(fpSubscriber, "%ld,%ld.%09ld\n",
subscribeCounterValue[subLoopVariable],
fprintf(fpSubscriber, "%lu,%ld.%09ld\n",
(long unsigned)subscribeCounterValue[subLoopVariable],
subscribeTimestamp[subLoopVariable].tv_sec,
subscribeTimestamp[subLoopVariable].tv_nsec);
}
@ -1574,6 +1659,7 @@ if (enableCsvLog)
UA_Server_delete(server);
UA_free(serverConfig);
#endif
#if defined(PUBLISHER)
UA_free(runningPub);
UA_free(pubCounterData);

View File

@ -80,6 +80,8 @@
#include <open62541/types_generated.h>
#include <open62541/plugin/pubsub_ethernet.h>
#include <open62541/plugin/securitypolicy_default.h>
#include "ua_pubsub.h"
#include <linux/if_link.h>
@ -110,12 +112,10 @@ UA_DataSetReaderConfig readerConfig;
#define DATA_SET_WRITER_ID 62541
#define DEFAULT_PUBLISHING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.3"
#endif
#if defined(SUBSCRIBER)
#define PUBLISHER_ID_SUB 2235
#define WRITER_GROUP_ID_SUB 100
#define DATA_SET_WRITER_ID_SUB 62541
#define DEFAULT_SUBSCRIBING_MAC_ADDRESS "opc.eth://01-00-5E-00-00-01:8.3"
#endif
#define REPEATED_NODECOUNTS 2 // Default to publish 64 bytes
#define PORT_NUMBER 62541
#define DEFAULT_XDP_QUEUE 2
@ -129,9 +129,11 @@ UA_DataSetReaderConfig readerConfig;
/* Publisher will sleep for 60% of cycle time and then prepares the */
/* transmission packet within 40% */
static UA_Double pubWakeupPercentage = 0.6;
#if defined(SUBSCRIBER)
/* Subscriber will wakeup only during start of cycle and check whether */
/* the packets are received */
static UA_Double subWakeupPercentage = 0;
#endif
/* User application Pub/Sub will wakeup at the 30% of cycle time and handles the */
/* user data such as read and write in Information model */
static UA_Double userAppWakeupPercentage = 0.3;
@ -153,6 +155,24 @@ static UA_Double userAppWakeupPercentage = 0.3;
#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"
#define LATENCY_CSV_FILE_NAME "latencyT1toT8.csv"
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
#define UA_AES128CTR_SIGNING_KEY_LENGTH 32
#define UA_AES128CTR_KEY_LENGTH 16
#define UA_AES128CTR_KEYNONCE_LENGTH 4
#if defined(PUBLISHER)
UA_Byte signingKeyPub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0};
UA_Byte encryptingKeyPub[UA_AES128CTR_KEY_LENGTH] = {0};
UA_Byte keyNoncePub[UA_AES128CTR_KEYNONCE_LENGTH] = {0};
#endif
#if defined(SUBSCRIBER)
UA_Byte signingKeySub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0};
UA_Byte encryptingKeySub[UA_AES128CTR_KEY_LENGTH] = {0};
UA_Byte keyNonceSub[UA_AES128CTR_KEYNONCE_LENGTH] = {0};
#endif
#endif
/* If the Hardcoded publisher/subscriber MAC addresses need to be changed,
* change PUBLISHING_MAC_ADDRESS and SUBSCRIBING_MAC_ADDRESS
*/
@ -336,7 +356,7 @@ addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
char threadNamePub[10] = "Publisher";
*callbackId = threadCreation((UA_Int16)pubPriority, (size_t)pubCore, publisherETF, threadNamePub, threadArguments);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Publisher thread callback Id: %ld\n", *callbackId);
"Publisher thread callback Id: %lu\n", (unsigned long)*callbackId);
#endif
}
else {
@ -345,7 +365,7 @@ addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
char threadNameSub[11] = "Subscriber";
*callbackId = threadCreation((UA_Int16)subPriority, (size_t)subCore, subscriber, threadNameSub, threadArguments);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Subscriber thread callback Id: %ld\n", *callbackId);
"Subscriber thread callback Id: %lu\n", (unsigned long)*callbackId);
#endif
}
@ -365,9 +385,9 @@ changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
/* Remove the callback added for cyclic repetition */
static void
removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) {
if(callbackId && (pthread_join(callbackId, NULL) != 0))
if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0))
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Pthread Join Failed thread: %ld\n", callbackId);
"Pthread Join Failed thread: %lu\n", (unsigned long)callbackId);
}
/**
@ -456,14 +476,31 @@ addReaderGroup(UA_Server *server) {
readerGroupConfig.timeout = 0; //Blocking socket
}
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
/* Encryption settings */
UA_ServerConfig *config = UA_Server_getConfig(server);
readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[1];
#endif
readerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback;
readerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback;
readerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback;
UA_Server_addReaderGroup(server, connectionIdentSubscriber, &readerGroupConfig,
&readerGroupIdentifier);
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
/* Add the encryption key informaton */
UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeySub};
UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeySub};
UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNonceSub};
// TODO security token not necessary for readergroup (extracted from security-header)
UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn);
#endif
}
/* Set SubscribedDataSet type to TargetVariables data type
* Add SubscriberCounter variable to the DataSetReader */
static void addSubscribedVariables (UA_Server *server) {
@ -817,6 +854,13 @@ addWriterGroup(UA_Server *server) {
writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE];
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
UA_ServerConfig *config = UA_Server_getConfig(server);
writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
#endif
/* The configuration flags for the messages are encapsulated inside the
* message- and transport settings extension objects. These extension
* objects are defined by the standard. e.g.
@ -833,6 +877,14 @@ addWriterGroup(UA_Server *server) {
UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent);
UA_Server_setWriterGroupOperational(server, writerGroupIdent);
UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage);
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
/* Add the encryption key informaton */
UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub};
UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub};
UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub};
UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn);
#endif
}
/* DataSetWriter handling */
@ -864,7 +916,8 @@ updateMeasurementsPublisher(struct timespec start_time,
}
if(consolePrint)
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Pub:%ld,%ld.%09ld\n", counterValue, start_time.tv_sec, start_time.tv_nsec);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Pub:%lu,%ld.%09ld\n",
(long unsigned)counterValue, start_time.tv_sec, start_time.tv_nsec);
if (signalTerm != UA_TRUE){
@ -890,7 +943,8 @@ updateMeasurementsSubscriber(struct timespec receive_time,
}
if(consolePrint)
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Sub:%ld,%ld.%09ld\n", counterValue, receive_time.tv_sec, receive_time.tv_nsec);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Sub:%lu,%ld.%09ld\n",
(long unsigned)counterValue, receive_time.tv_sec, receive_time.tv_nsec);
if (signalTerm != UA_TRUE)
{
@ -969,6 +1023,9 @@ void *publisherETF(void *arg) {
nanoSecondFieldConversion(&nextnanosleeptime);
}
#if defined(PUBLISHER) && !defined(SUBSCRIBER)
runningServer = UA_FALSE;
#endif
UA_free(threadArgumentsPublisher);
return (void*)NULL;
}
@ -1066,7 +1123,11 @@ void *userApplicationPubSub(void *arg) {
*repeatedCounterData[iterator] = repeatedCounterValue;
}
#if defined(PUBLISHER) && defined(SUBSCRIBER)
while (*runningPub || *runningSub) {
#else
while (*runningPub) {
#endif
/* The User application threads wakes up at the configured userApp wake up percentage (30%) of each cycle */
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL);
#if defined(PUBLISHER)
@ -1150,7 +1211,7 @@ static pthread_t threadCreation(UA_Int16 threadPriority, size_t coreAffinity, vo
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName);
if (CPU_ISSET(coreAffinity, &cpuset))
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %ld\n", applicationName, coreAffinity);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %lu\n", applicationName, (unsigned long)coreAffinity);
return threadID;
}
@ -1263,7 +1324,6 @@ static void removeServerNodes(UA_Server *server) {
}
UA_Server_deleteNode(server, runningPubStatusNodeID, UA_TRUE);
UA_NodeId_clear(&runningPubStatusNodeID);
UA_Server_deleteNode(server, subNodeID, UA_TRUE);
UA_NodeId_clear(&subNodeID);
for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++)
@ -1274,7 +1334,7 @@ static void removeServerNodes(UA_Server *server) {
UA_Server_deleteNode(server, runningSubStatusNodeID, UA_TRUE);
UA_NodeId_clear(&runningSubStatusNodeID);
}
#if defined (PUBLISHER) && defined(SUBSCRIBER)
/**
* **Time Difference Calculation**
*
@ -1345,8 +1405,8 @@ static void computeLatencyAndGenerateCsv(char *latencyFileName) {
if(((latencyCharIndex - prevLatencyCharIndex) + latencyCharIndex + 3) < MAX_MEASUREMENTS_FILEWRITE) {
latencyCharIndex += (UA_UInt64)sprintf(&latency_measurements[latencyCharIndex],
"%0.3f, %ld, %ld\n",
finalTime, missed_counter, repeated_counter);
"%0.3f, %lu, %lu\n",
finalTime, (unsigned long)missed_counter, (unsigned long)repeated_counter);
}
else {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
@ -1358,10 +1418,10 @@ static void computeLatencyAndGenerateCsv(char *latencyFileName) {
}
/* Write into the latency file */
fwrite(&latency_measurements[0], prevLatencyCharIndex, 1, fp_latency);
fwrite(&latency_measurements[0], (size_t)prevLatencyCharIndex, 1, fp_latency);
fclose(fp_latency);
}
#endif
/**
* **Usage function**
*
@ -1557,6 +1617,23 @@ int main(int argc, char **argv) {
}
UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL);
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
#if defined(PUBLISHER) && defined(SUBSCRIBER)
/* Instantiate the PubSub SecurityPolicy */
config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*)
UA_calloc(2, sizeof(UA_PubSubSecurityPolicy));
config->pubSubConfig.securityPoliciesSize = 2;
#else
config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*)
UA_malloc(sizeof(UA_PubSubSecurityPolicy));
config->pubSubConfig.securityPoliciesSize = 1;
#endif
#endif
#if defined(UA_ENABLE_PUBSUB_ENCRYPTION) && defined(PUBLISHER)
UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[0],
&config->logger);
#endif
#if defined(PUBLISHER)
UA_NetworkAddressUrlDataType networkAddressUrlPub;
@ -1606,6 +1683,11 @@ if (enableCsvLog) {
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
#endif
#if defined(UA_ENABLE_PUBSUB_ENCRYPTION) && defined(SUBSCRIBER)
UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[1],
&config->logger);
#endif
#if defined (PUBLISHER) && defined(SUBSCRIBER)
UA_ServerConfig_addPubSubTransportLayer(config, UA_PubSubTransportLayerEthernet());
#endif
@ -1631,23 +1713,24 @@ if (enableCsvLog) {
#endif
retval |= UA_Server_run(server, &runningServer);
#if defined(SUBSCRIBER)
UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier);
#endif
#if defined(PUBLISHER) || defined(SUBSCRIBER)
returnValue = pthread_join(userThreadID, NULL);
if (returnValue != 0)
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for User thread:%d\n", returnValue);
#endif
if (enableCsvLog) {
#if defined(PUBLISHER)
/* Write the published data in the publisher_T1.csv file */
size_t pubLoopVariable = 0;
for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher;
pubLoopVariable++) {
fprintf(fpPublisher, "%ld,%ld.%09ld\n",
publishCounterValue[pubLoopVariable],
publishTimestamp[pubLoopVariable].tv_sec,
publishTimestamp[pubLoopVariable].tv_nsec);
fprintf(fpPublisher, "%lu,%lu.%09lu\n",
(long unsigned)publishCounterValue[pubLoopVariable],
(long unsigned)publishTimestamp[pubLoopVariable].tv_sec,
(long unsigned)publishTimestamp[pubLoopVariable].tv_nsec);
}
#endif
#if defined(SUBSCRIBER)
@ -1655,17 +1738,19 @@ if (enableCsvLog) {
size_t subLoopVariable = 0;
for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber;
subLoopVariable++) {
fprintf(fpSubscriber, "%ld,%ld.%09ld\n",
subscribeCounterValue[subLoopVariable],
subscribeTimestamp[subLoopVariable].tv_sec,
subscribeTimestamp[subLoopVariable].tv_nsec);
fprintf(fpSubscriber, "%lu,%lu.%09lu\n",
(long unsigned)subscribeCounterValue[subLoopVariable],
(long unsigned)subscribeTimestamp[subLoopVariable].tv_sec,
(long unsigned)subscribeTimestamp[subLoopVariable].tv_nsec);
}
#endif
}
if(enableLatencyCsvLog) {
#if defined (PUBLISHER) && defined(SUBSCRIBER)
char *latencyCsvName = LATENCY_CSV_FILE_NAME;
computeLatencyAndGenerateCsv(latencyCsvName);
#endif
}
#if defined(PUBLISHER) || defined(SUBSCRIBER)
@ -1688,7 +1773,6 @@ if (enableCsvLog) {
if (enableCsvLog)
fclose(fpPublisher);
#endif
#if defined(SUBSCRIBER)
UA_free(runningSub);
UA_free(subCounterData);

View File

@ -0,0 +1,133 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
*
* Copyright 2019 (c) basysKom GmbH <opensource@basyskom.com> (Author: Peter Rustler)
* Copyright 2021 (c) luibass92 <luibass92@live.it> (Author: Luigi Bassetta)
*/
#include <open62541/plugin/historydata/history_data_backend_memory.h>
#include <open62541/plugin/historydata/history_data_gathering_default.h>
#include <open62541/plugin/historydata/history_database_default.h>
#include <open62541/plugin/historydatabase.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <signal.h>
#include <stdlib.h>
static volatile UA_Boolean running = true;
static void stopHandler(int sign) {
(void)sign;
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = false;
}
int main(void) {
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
UA_Server *server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefault(config);
/* We need a gathering for the plugin to constuct.
* The UA_HistoryDataGathering is responsible to collect data and store it to the database.
* We will use this gathering for one node, only. initialNodeIdStoreSize = 1
* The store will NOT automatically grow if you register more than one node will return a UA_STATUS_BADOUTOFMEMORY. */
UA_HistoryDataGathering gathering = UA_HistoryDataGathering_Circular(1);
/* We set the responsible plugin in the configuration. UA_HistoryDatabase is
* the main plugin which handles the historical data service. */
config->historyDatabase = UA_HistoryDatabase_default(gathering);
/* Define the attribute of the uint32 variable node */
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_UInt32 myUint32 = 40;
UA_Variant_setScalar(&attr.value, &myUint32, &UA_TYPES[UA_TYPES_UINT32]);
attr.description = UA_LOCALIZEDTEXT("en-US","myUintValue");
attr.displayName = UA_LOCALIZEDTEXT("en-US","myUintValue");
attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
/* We set the access level to also support history read
* This is what will be reported to clients */
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE | UA_ACCESSLEVELMASK_HISTORYREAD;
/* We also set this node to historizing, so the server internals also know from it. */
attr.historizing = true;
/* Add the variable node to the information model */
UA_NodeId uint32NodeId = UA_NODEID_STRING(1, "myUintValue");
UA_QualifiedName uint32Name = UA_QUALIFIEDNAME(1, "myUintValue");
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
UA_NodeId outNodeId;
UA_NodeId_init(&outNodeId);
UA_StatusCode retval = UA_Server_addVariableNode(server,
uint32NodeId,
parentNodeId,
parentReferenceNodeId,
uint32Name,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr,
NULL,
&outNodeId);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
"UA_Server_addVariableNode %s", UA_StatusCode_name(retval));
/* Now we define the settings for our node */
UA_HistorizingNodeIdSettings setting;
/* There is a memory based database plugin. We will use that. We just
* reserve space for 3 nodes with 10 values each. This will NOT automatically grow
* but will store data as a circular buffer of size 10. The 11th value will be
* stored replacing the oldest one and the process will continue like that. */
setting.historizingBackend = UA_HistoryDataBackend_Memory_Circular(3, 10);
/* We want the server to serve a maximum of 100 values per request. This
* value depend on the plattform you are running the server. A big server
* can serve more values, smaller ones less. */
setting.maxHistoryDataResponseSize = 100;
/* If we have a sensor which do not report updates
* and need to be polled we change the setting like that.
* The polling interval in ms.
*
setting.pollingInterval = 100;
*
* Set the update strategie to polling.
*
setting.historizingUpdateStrategy = UA_HISTORIZINGUPDATESTRATEGY_POLL;
*/
/* If you want to insert the values to the database yourself, we can set the user strategy here.
* This is useful if you for example want a value stored, if a defined delta is reached.
* Then you should use a local monitored item with a fuzziness and store the value in the callback.
*
setting.historizingUpdateStrategy = UA_HISTORIZINGUPDATESTRATEGY_USER;
*/
/* We want the values stored in the database, when the nodes value is
* set. */
setting.historizingUpdateStrategy = UA_HISTORIZINGUPDATESTRATEGY_VALUESET;
/* At the end we register the node for gathering data in the database. */
retval = gathering.registerNodeId(server, gathering.context, &outNodeId, setting);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "registerNodeId %s", UA_StatusCode_name(retval));
/* If you use UA_HISTORIZINGUPDATESTRATEGY_POLL, then start the polling.
*
retval = gathering.startPoll(server, gathering.context, &outNodeId);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "startPoll %s", UA_StatusCode_name(retval));
*/
retval = UA_Server_run(server, &running);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "UA_Server_run %s", UA_StatusCode_name(retval));
/*
* If you use UA_HISTORIZINGUPDATESTRATEGY_POLL, then stop the polling.
*
retval = gathering.stopPoll(server, gathering.context, &outNodeId);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "stopPoll %s", UA_StatusCode_name(retval));
*/
UA_Server_delete(server);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@ -27,6 +27,7 @@
#include <open62541/plugin/log.h>
#include <open62541/plugin/network.h>
#include <open62541/plugin/securitypolicy.h>
#include <open62541/plugin/eventloop.h>
_UA_BEGIN_DECLS
@ -119,6 +120,9 @@ typedef struct {
* up together with the
* configuration. So it is possible
* to allocate them on ROM. */
/* EventLoop */
UA_EventLoop *eventLoop;
UA_Boolean externalEventLoop; /* The EventLoop is not deleted with the config */
/* Available SecurityPolicies */
size_t securityPoliciesSize;

View File

@ -0,0 +1,302 @@
/* 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 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes)
*/
#ifndef UA_EVENTLOOP_H_
#define UA_EVENTLOOP_H_
#include <open62541/types.h>
#include <open62541/types_generated.h>
#include <open62541/util.h>
#include <open62541/plugin/log.h>
_UA_BEGIN_DECLS
struct UA_EventLoop;
typedef struct UA_EventLoop UA_EventLoop;
struct UA_EventSource;
typedef struct UA_EventSource UA_EventSource;
/**
* Event Loop Subsystem
* ====================
*
* An OPC UA-enabled application can have several clients and servers. And
* server can serve different transport-level protocols for OPC UA. The
* EventLoop is a central module that provides a unified control-flow for all of
* these. Hence, several applications can share an EventLoop.
*
* The EventLoop and the ConnectionManager implementation is
* architecture-specific. The goal is to have a single call to "select" (epoll,
* kqueue, ...) in the EventLoop that covers all ConnectionManagers. Hence the
* EventLoop plugin implementation must know implementation details of the
* ConnectionManager implementations. So the EventLoop can extract socket
* information, etc. from the ConnectionManagers.
*
* Event Loop
* ----------
* The EventLoop implementation is part of the selected architecture. For
* example, "Win32/POSIX" stands for a Windows environment with an EventLoop
* that uses the POSIX API. Several EventLoops can be instantiated in parallel.
* But the globally defined functions are the same everywhere. */
typedef void (*UA_Callback)(void *application, void *context);
/* To be executed in the next EventLoop cycle */
typedef struct UA_DelayedCallback {
struct UA_DelayedCallback *next; /* Singly-linked list */
UA_Callback callback;
void *application;
void *data;
} UA_DelayedCallback;
typedef enum {
UA_EVENTLOOPSTATE_FRESH = 0,
UA_EVENTLOOPSTATE_STARTED,
UA_EVENTLOOPSTATE_STOPPING, /* stopping in progress, needs EventLoop
* cycles to finish */
UA_EVENTLOOPSTATE_STOPPED
} UA_EventLoopState;
/**
* EventLoop Lifecycle
* ~~~~~~~~~~~~~~~~~~~ */
UA_EXPORT UA_EventLoop *
UA_EventLoop_new(const UA_Logger *logger);
/* Clean up the EventLoop and free allocated memory. Can fail if the EventLoop
* is not stopped. */
UA_EXPORT UA_StatusCode
UA_EventLoop_delete(UA_EventLoop *el);
UA_EXPORT UA_EventLoopState
UA_EventLoop_getState(UA_EventLoop *el);
UA_EXPORT UA_StatusCode
UA_EventLoop_start(UA_EventLoop *el);
/* Stop all EventSources. This is asynchronous and might need a few
* iterations of the main-loop to succeed. */
UA_EXPORT void
UA_EventLoop_stop(UA_EventLoop *el);
/* Process events for at most "timeout" ms or until an unrecoverable error
* occurs. If timeout==0, then only already received events are processed. */
UA_EXPORT UA_StatusCode
UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout);
/* Time of the next cyclic callback. Returns the max DateTime if no cyclic
* callback is registered. */
UA_EXPORT UA_DateTime
UA_EventLoop_nextCyclicTime(UA_EventLoop *el);
/**
* Cyclic and Delayed Callbacks
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Cyclic callbacks are executed regularly with an interval. A delayed callback
* is executed in the next cycle of the EventLoop. The memory for the delayed
* callback is freed after the execution. */
/* The execution interval is in ms. Returns the callbackId if the pointer is
* non-NULL. */
UA_EXPORT UA_StatusCode
UA_EventLoop_addCyclicCallback(UA_EventLoop *el, UA_Callback cb,
void *application, void *data, UA_Double interval_ms,
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy,
UA_UInt64 *callbackId);
UA_EXPORT UA_StatusCode
UA_EventLoop_addTimedCallback(UA_EventLoop *el, UA_Callback callback,
void *application, void *data, UA_DateTime date,
UA_UInt64 *callbackId);
UA_EXPORT UA_StatusCode
UA_EventLoop_modifyCyclicCallback(UA_EventLoop *el, UA_UInt64 callbackId,
UA_Double interval_ms, UA_DateTime *baseTime,
UA_TimerPolicy timerPolicy);
UA_EXPORT void
UA_EventLoop_removeCyclicCallback(UA_EventLoop *el, UA_UInt64 callbackId);
UA_EXPORT void
UA_EventLoop_addDelayedCallback(UA_EventLoop *el, UA_DelayedCallback *dc);
/* Helper Functions */
UA_EXPORT const UA_Logger *
UA_EventLoop_getLogger(UA_EventLoop *el);
UA_EXPORT void
UA_EventLoop_setLogger(UA_EventLoop *el, const UA_Logger *logger);
/**
* Event Source
* ------------ */
typedef enum {
UA_EVENTSOURCESTATE_FRESH = 0,
UA_EVENTSOURCESTATE_STOPPED, /* Registered but stopped */
UA_EVENTSOURCESTATE_STARTING,
UA_EVENTSOURCESTATE_STARTED,
UA_EVENTSOURCESTATE_STOPPING /* Stopping in progress, needs
* EventLoop cycles to finish */
} UA_EventSourceState;
struct UA_EventSource {
struct UA_EventSource *next; /* Singly-linked list for use by the
* application that registered the ES */
/* Configuration
* ~~~~~~~~~~~~~ */
UA_String name; /* Unique name of the ES for logging */
void *application; /* Application to which the ES belongs */
size_t paramsSize;
UA_KeyValuePair *params; /* Configuration parameters */
/* Lifecycle
* ~~~~~~~~~ */
UA_EventSourceState state;
UA_EventLoop *eventLoop; /* EventLoop where the ES is registered */
UA_StatusCode (*start)(UA_EventSource *es);
void (*stop)(UA_EventSource *es); /* Asynchronous. Iterate theven EventLoop
* until the EventSource is stopped. */
UA_StatusCode (*free)(UA_EventSource *es);
};
/* Register the ES. Immediately starts the ES if the EventLoop is already
* started. Otherwise the ES is started together with the EventLoop. */
UA_EXPORT UA_StatusCode
UA_EventLoop_registerEventSource(UA_EventLoop *el,
UA_EventSource *es);
/* If still registered, call _stop (but not _clear) on the CM and deregister. */
UA_EXPORT UA_StatusCode
UA_EventLoop_deregisterEventSource(UA_EventLoop *el,
UA_EventSource *es);
/**
* Connection Manager
* ------------------
* Every Connection is created by a ConnectionManager. Every ConnectionManager
* belongs to just one application. A ConnectionManager can act purely as a
* passive "Factory" for Connections. But it can also be stateful. For example,
* it can keep a session to an MQTT broker open which is used by individual
* connections that are each bound to an MQTT topic. */
struct UA_ConnectionManager;
typedef struct UA_ConnectionManager UA_ConnectionManager;
/**
* The ConnectionCallback is the only interface from the connection back to the
* application. The connectionId is announced to the application when it is
* first used for the callback. The context is a double-pointer so the context
* can be overwritten by the application */
typedef void
(*UA_ConnectionCallback)(UA_ConnectionManager *cm, uintptr_t connectionId,
void **connectionContext, UA_StatusCode status,
UA_ByteString msg);
struct UA_ConnectionManager {
/* Every ConnectionManager is treated like an EventSource from the
* perspective of the EventLoop. */
UA_EventSource eventSource;
/* Passively listen for new connections
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Some ConnectionManagers passively listen to open new Connections. The
* configuration parameters stored in the EventSource are used during
* "start" of the EventSource to set this up. The "connectionCallback"
* callback is used to indicate that a new connection has been are created
* (status==Good, msg=empty).
*
* The callback depends on the application and has to be manually
* configured. */
UA_ConnectionCallback connectionCallback;
void *initialConnectionContext;
/* Actively Open a Connection
* ~~~~~~~~~~~~~~~~~~~~~~~~~~
* Some ConnectionManagers can actively open a new Connection. Connecting is
* asynchronous. cm->connectionCallback is called when the connection is
* open (status=GOOD) or aborted (status!=GOOD) when connecting failed.
*
* The parameters describe the connection. For example hostname and port
* (for TCP). Other protocols (e.g. MQTT, AMQP, etc.) may required
* additional arguments to open a connection.
*
* The connection is opened asynchronously. The ConnectionCallback is
* triggered when the connection is fully opened (UA_STATUSCODE_GOOD) or has
* failed (with an error code). */
UA_StatusCode
(*openConnection)(UA_ConnectionManager *cm,
size_t paramsSize, UA_KeyValuePair *params,
void *context);
/* Connection Activities
* ~~~~~~~~~~~~~~~~~~~~~
* The following are activities to be performed on an open connection.
*
* Each ConnectionManager allocates and frees his own memory for the network
* buffers. This enables, for example, zero-copy neworking mechanisms. The
* connectionId is part of the API to enable cases where memory is
* statically allocated for every connection */
UA_StatusCode
(*allocNetworkBuffer)(UA_ConnectionManager *cm, uintptr_t connectionId,
UA_ByteString *buf, size_t bufSize);
void
(*freeNetworkBuffer)(UA_ConnectionManager *cm, uintptr_t connectionId,
UA_ByteString *buf);
/* Send a message. Sending is asynchronous. That is, the function returns
* before the message is ACKed from remote. The memory for the buffer is
* expected to be allocated with allocNetworkBuffer and is released
* internally (also if sending fails).
*
* Some ConnectionManagers can accept additional parameters for sending. For
* example a tx-time for sending in time-synchronized TSN settings. */
UA_StatusCode
(*sendWithConnection)(UA_ConnectionManager *cm, uintptr_t connectionId,
size_t paramsSize, UA_KeyValuePair *params,
UA_ByteString *buf);
/* When a connection is closed, cm->connectionCallback is called with
* (status=BadConnectionClosed, msg=empty). Then the connection is cleared
* up inside the ConnectionManager. This is the case both for connections
* that are actively closed and those that are closed remotely. The return
* code is non-good only if the connection is already closed. */
UA_StatusCode
(*closeConnection)(UA_ConnectionManager *cm, uintptr_t connectionId);
};
/**
* TCP Connection Manager
* ~~~~~~~~~~~~~~~~~~~~~~
* Listens on the network and manages TCP connections. The configuration
* parameters have to set before calling _start to take effect.
*
* Configuration Parameters:
* - 0:listen-port [uint16]: Port to listen for new connections (default: do not
* listen on any port).
* - 0:listen-hostnames [string | string array]: Hostnames of the devices to
* listen on (default: listen on
* all devices).
* - 0:recv-bufsize [uint16]: Size of the buffer that is allocated for receiving
* messages (default 16kB).
*
* Open Connection Parameters:
* - 0:target-hostname [string]: Hostname (or IPv4/IPv6 address) of the target
* (required).
* - 0:target-port [uint16]: Port of the target host (required).
*
* Send Parameters:
* No additional parameters for sending over an established TCP socket defined. */
UA_EXPORT UA_ConnectionManager *
UA_ConnectionManager_TCP_new(const UA_String eventSourceName);
_UA_END_DECLS
#endif /* UA_EVENTLOOP_H_ */

View File

@ -37,6 +37,8 @@ typedef enum {
UA_LOGLEVEL_FATAL
} UA_LogLevel;
#define UA_LOGCATEGORIES 8
typedef enum {
UA_LOGCATEGORY_NETWORK = 0,
UA_LOGCATEGORY_SECURECHANNEL,
@ -44,7 +46,8 @@ typedef enum {
UA_LOGCATEGORY_SERVER,
UA_LOGCATEGORY_CLIENT,
UA_LOGCATEGORY_USERLAND,
UA_LOGCATEGORY_SECURITYPOLICY
UA_LOGCATEGORY_SECURITYPOLICY,
UA_LOGCATEGORY_EVENTLOOP
} UA_LogCategory;
typedef struct {

View File

@ -209,18 +209,18 @@ typedef struct {
/* The maximum number of ReferrenceTypes. Must be a multiple of 32. */
#define UA_REFERENCETYPESET_MAX 128
typedef struct { UA_UInt32 bits[UA_REFERENCETYPESET_MAX / 32]; } UA_ReferenceTypeSet;
typedef struct {
UA_UInt32 bits[UA_REFERENCETYPESET_MAX / 32];
} UA_ReferenceTypeSet;
UA_EXPORT extern const UA_ReferenceTypeSet UA_REFERENCETYPESET_NONE;
UA_EXPORT extern const UA_ReferenceTypeSet UA_REFERENCETYPESET_ALL;
static UA_INLINE void
UA_ReferenceTypeSet_init(UA_ReferenceTypeSet *set) {
memset(set, 0, sizeof(UA_ReferenceTypeSet));
}
static UA_INLINE void
UA_ReferenceTypeSet_any(UA_ReferenceTypeSet *set) {
memset(set, -1, sizeof(UA_ReferenceTypeSet));
}
static UA_INLINE UA_ReferenceTypeSet
UA_REFTYPESET(UA_Byte index) {
UA_Byte i = index / 32, j = index % 32;
@ -959,10 +959,35 @@ typedef struct {
void (*deleteNode)(void *nsCtx, UA_Node *node);
/* ``Get`` returns a pointer to an immutable node. ``Release`` indicates
* that the pointer is no longer accessed afterwards. */
const UA_Node * (*getNode)(void *nsCtx, const UA_NodeId *nodeId);
/* ``Get`` returns a pointer to an immutable node. Call ``releaseNode`` to
* indicate when the pointer is no longer accessed.
*
* It can be indicated if only a subset of the attributes and referencs need
* to be accessed. That is relevant when the nodestore accesses a slow
* storage backend for the attributes. The attribute mask is a bitfield with
* ORed entries from UA_NodeAttributesMask.
*
* The returned node always contains the context-pointer and other fields
* specific to open626541 (not official attributes).
*
* The NodeStore does not complain if attributes and references that don't
* exist (for that node) are requested. Attributes and references in
* addition to those specified can be returned. For example, if the full
* node already is kept in memory by the Nodestore. */
const UA_Node * (*getNode)(void *nsCtx, const UA_NodeId *nodeId,
UA_UInt32 attributeMask,
UA_ReferenceTypeSet references,
UA_BrowseDirection referenceDirections);
/* Similar to the normal ``getNode``. But it can take advantage of the
* NodePointer structure, e.g. if it contains a direct pointer. */
const UA_Node * (*getNodeFromPtr)(void *nsCtx, UA_NodePointer ptr,
UA_UInt32 attributeMask,
UA_ReferenceTypeSet references,
UA_BrowseDirection referenceDirections);
/* Release a node that has been retrieved with ``getNode`` or
* ``getNodeFromPtr``. */
void (*releaseNode)(void *nsCtx, const UA_Node *node);
/* Returns an editable copy of a node (needs to be deleted with the

View File

@ -24,6 +24,7 @@
#include <open62541/types_generated.h>
#include <open62541/types_generated_handling.h>
#include <open62541/plugin/eventloop.h>
#include <open62541/plugin/securitypolicy.h>
#include <open62541/plugin/accesscontrol.h>
#include <open62541/plugin/nodestore.h>
@ -119,6 +120,10 @@ struct UA_ServerConfig {
* with custom data types are provided in
* ``/examples/custom_datatype/``. */
/* EventLoop */
UA_EventLoop *eventLoop;
UA_Boolean externalEventLoop; /* The EventLoop is not deleted with the config */
/* Networking */
size_t networkLayersSize;
UA_ServerNetworkLayer *networkLayers;
@ -424,28 +429,29 @@ UA_Server_closeSession(UA_Server *server, const UA_NodeId *sessionId);
UA_EXPORT UA_StatusCode UA_THREADSAFE
UA_Server_setSessionParameter(UA_Server *server, const UA_NodeId *sessionId,
const char *name, const UA_Variant *parameter);
const UA_QualifiedName key,
const UA_Variant *value);
UA_EXPORT void UA_THREADSAFE
UA_Server_deleteSessionParameter(UA_Server *server, const UA_NodeId *sessionId,
const char *name);
const UA_QualifiedName key);
/* Returns NULL if the session or the parameter are not defined. Returns a deep
* copy otherwise */
UA_EXPORT UA_StatusCode UA_THREADSAFE
UA_Server_getSessionParameter(UA_Server *server, const UA_NodeId *sessionId,
const char *name, UA_Variant *outParameter);
const UA_QualifiedName key,
UA_Variant *outValue);
/* Returns NULL if the parameter is not defined or not of the right datatype */
/* Returns NULL if the parameter is not defined or not a scalar or not of the
* right datatype. Otherwise a deep copy of the scalar value is filled at the
* target location of the void pointer. */
UA_EXPORT UA_StatusCode UA_THREADSAFE
UA_Server_getSessionScalarParameter(UA_Server *server, const UA_NodeId *sessionId,
const char *name, const UA_DataType *type,
UA_Variant *outParameter);
UA_EXPORT UA_StatusCode UA_THREADSAFE
UA_Server_getSessionArrayParameter(UA_Server *server, const UA_NodeId *sessionId,
const char *name, const UA_DataType *type,
UA_Variant *outParameter);
UA_Server_getSessionParameter_scalar(UA_Server *server,
const UA_NodeId *sessionId,
const UA_QualifiedName key,
const UA_DataType *type,
void *outValue);
/**
* Reading and Writing Node Attributes

View File

@ -1106,7 +1106,10 @@ void UA_EXPORT UA_delete(void *p, const UA_DataType *type);
*
* @param p The memory location of the variable
* @param type The datatype description of the variable
* @param output A string that is memory-allocated for the pretty-printed output
* @param output A string that is used for the pretty-printed output. If the
* memory for string is already allocated, we try to use the existing
* string (the length is adjusted). If the string is empty, memory
* is allocated for it.
* @return Indicates whether the operation succeeded*/
#ifdef UA_ENABLE_TYPEDESCRIPTION
UA_StatusCode UA_EXPORT

View File

@ -47,44 +47,26 @@ typedef enum {
* invalidates pointers into the previous array. If the key exists already, the
* value is overwritten. */
UA_EXPORT UA_StatusCode
UA_KeyValueMap_setQualified(UA_KeyValuePair **map, size_t *mapSize,
const UA_QualifiedName *key,
const UA_Variant *value);
/* Simplified version that assumes the key is in namespace 0 */
UA_EXPORT UA_StatusCode
UA_KeyValueMap_set(UA_KeyValuePair **map, size_t *mapSize,
const char *key, const UA_Variant *value);
const UA_QualifiedName key,
const UA_Variant *value);
/* Returns a pointer into underlying array or NULL if the key is not found.*/
UA_EXPORT const UA_Variant *
UA_KeyValueMap_getQualified(UA_KeyValuePair *map, size_t mapSize,
const UA_QualifiedName *key);
/* Simplified version that assumes the key is in namespace 0 */
/* Returns a pointer to the value or NULL if the key is not found.*/
UA_EXPORT const UA_Variant *
UA_KeyValueMap_get(UA_KeyValuePair *map, size_t mapSize,
const char *key);
const UA_QualifiedName key);
/* Returns NULL if the value for the key is not defined or not of the right
* datatype and scalar/array */
UA_EXPORT const UA_Variant *
UA_EXPORT const void *
UA_KeyValueMap_getScalar(UA_KeyValuePair *map, size_t mapSize,
const char *key, const UA_DataType *type);
UA_EXPORT const UA_Variant *
UA_KeyValueMap_getArray(UA_KeyValuePair *map, size_t mapSize,
const char *key, const UA_DataType *type);
const UA_QualifiedName key,
const UA_DataType *type);
/* Remove a single entry. To delete the entire map, use UA_Array_delete. */
UA_EXPORT void
UA_KeyValueMap_deleteQualified(UA_KeyValuePair **map, size_t *mapSize,
const UA_QualifiedName *key);
/* Simplified version that assumes the key is in namespace 0 */
UA_EXPORT void
UA_KeyValueMap_delete(UA_KeyValuePair **map, size_t *mapSize,
const char *key);
const UA_QualifiedName key);
/**
* Endpoint URL Parser

View File

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2018 (c) basysKom GmbH <opensource@basyskom.com> (Author: Peter Rustler)
* Copyright 2021 (c) luibass92 <luibass92@live.it> (Author: Luigi Bassetta)
*/
#include <open62541/plugin/historydata/history_data_backend_memory.h>
@ -26,6 +27,8 @@ typedef struct {
UA_DataValueMemoryStoreItem **dataStore;
size_t storeEnd;
size_t storeSize;
/* New field useful for circular buffer management */
size_t lastInserted;
} UA_NodeIdStoreContextItem_backend_memory;
static void
@ -603,3 +606,261 @@ UA_HistoryDataBackend_Memory_clear(UA_HistoryDataBackend *backend)
UA_MemoryStoreContext_delete(ctx);
memset(backend, 0, sizeof(UA_HistoryDataBackend));
}
/* Circular buffer implementation */
static UA_NodeIdStoreContextItem_backend_memory *
getNewNodeIdContext_backend_memory_Circular(UA_MemoryStoreContext *context,
UA_Server *server,
const UA_NodeId *nodeId) {
UA_MemoryStoreContext *ctx = (UA_MemoryStoreContext *)context;
if(ctx->storeEnd >= ctx->storeSize) {
return NULL;
}
UA_NodeIdStoreContextItem_backend_memory *item = &ctx->dataStore[ctx->storeEnd];
UA_NodeId_copy(nodeId, &item->nodeId);
UA_DataValueMemoryStoreItem **store = (UA_DataValueMemoryStoreItem **)UA_calloc(ctx->initialStoreSize, sizeof(UA_DataValueMemoryStoreItem *));
if(!store) {
UA_NodeIdStoreContextItem_clear(item);
return NULL;
}
item->dataStore = store;
item->storeSize = ctx->initialStoreSize;
item->storeEnd = 0;
++ctx->storeEnd;
return item;
}
static UA_NodeIdStoreContextItem_backend_memory *
getNodeIdStoreContextItem_backend_memory_Circular(UA_MemoryStoreContext *context,
UA_Server *server,
const UA_NodeId *nodeId) {
for(size_t i = 0; i < context->storeEnd; ++i) {
if(UA_NodeId_equal(nodeId, &context->dataStore[i].nodeId)) {
return &context->dataStore[i];
}
}
return getNewNodeIdContext_backend_memory_Circular(context, server, nodeId);
}
static UA_StatusCode
serverSetHistoryData_backend_memory_Circular(UA_Server *server,
void *context,
const UA_NodeId *sessionId,
void *sessionContext,
const UA_NodeId *nodeId,
UA_Boolean historizing,
const UA_DataValue *value) {
UA_NodeIdStoreContextItem_backend_memory *item = getNodeIdStoreContextItem_backend_memory_Circular((UA_MemoryStoreContext *)context, server, nodeId);
if(item == NULL) {
return UA_STATUSCODE_BADOUTOFMEMORY;
}
if(item->lastInserted >= item->storeSize) {
/* If the buffer size is overcomed, push new elements from the start of the buffer */
item->lastInserted = 0;
}
UA_DateTime timestamp = 0;
if(value->hasSourceTimestamp) {
timestamp = value->sourceTimestamp;
} else if(value->hasServerTimestamp) {
timestamp = value->serverTimestamp;
} else {
timestamp = UA_DateTime_now();
}
UA_DataValueMemoryStoreItem *newItem = (UA_DataValueMemoryStoreItem *)UA_calloc(1, sizeof(UA_DataValueMemoryStoreItem));
newItem->timestamp = timestamp;
UA_DataValue_copy(value, &newItem->value);
/* This implementation does NOT sort values by timestamp */
if(item->dataStore[item->lastInserted] != NULL) {
UA_DataValueMemoryStoreItem_clear(item->dataStore[item->lastInserted]);
UA_free(item->dataStore[item->lastInserted]);
}
item->dataStore[item->lastInserted] = newItem;
++item->lastInserted;
if(item->storeEnd < item->storeSize) {
++item->storeEnd;
}
return UA_STATUSCODE_GOOD;
}
static size_t
getResultSize_service_Circular(const UA_HistoryDataBackend *backend, UA_Server *server,
const UA_NodeId *sessionId, void *sessionContext,
const UA_NodeId *nodeId, UA_DateTime start,
UA_DateTime end, UA_UInt32 numValuesPerNode,
UA_Boolean returnBounds, size_t *startIndex,
size_t *endIndex, UA_Boolean *addFirst,
UA_Boolean *addLast, UA_Boolean *reverse) {
*startIndex = 0;
*endIndex = backend->lastIndex(server, backend->context, sessionId, sessionContext, nodeId);
*addFirst = false;
*addLast = false;
if(end == LLONG_MIN) {
*reverse = false;
} else if(start == LLONG_MIN) {
*reverse = true;
} else {
*reverse = end < start;
}
size_t size = 0;
const UA_NodeIdStoreContextItem_backend_memory *item = getNodeIdStoreContextItem_backend_memory_Circular((UA_MemoryStoreContext *)backend->context, server, nodeId);
if(item == NULL) {
size = 0;
} else {
size = item->storeEnd;
}
return size;
}
static UA_StatusCode
getHistoryData_service_Circular(UA_Server *server,
const UA_NodeId *sessionId,
void *sessionContext,
const UA_HistoryDataBackend *backend,
const UA_DateTime start,
const UA_DateTime end,
const UA_NodeId *nodeId,
size_t maxSize,
UA_UInt32 numValuesPerNode,
UA_Boolean returnBounds,
UA_TimestampsToReturn timestampsToReturn,
UA_NumericRange range,
UA_Boolean releaseContinuationPoints,
const UA_ByteString *continuationPoint,
UA_ByteString *outContinuationPoint,
UA_HistoryData *historyData) {
size_t *resultSize = &historyData->dataValuesSize;
UA_DataValue **result = &historyData->dataValues;
size_t skip = 0;
UA_ByteString backendContinuationPoint;
UA_ByteString_init(&backendContinuationPoint);
if(continuationPoint->length > 0) {
if(continuationPoint->length < sizeof(size_t))
return UA_STATUSCODE_BADCONTINUATIONPOINTINVALID;
skip = *((size_t *)(continuationPoint->data));
backendContinuationPoint.length = continuationPoint->length - sizeof(size_t);
backendContinuationPoint.data = continuationPoint->data + sizeof(size_t);
}
size_t storeEnd = backend->getEnd(server, backend->context, sessionId, sessionContext, nodeId);
size_t startIndex;
size_t endIndex;
UA_Boolean addFirst;
UA_Boolean addLast;
UA_Boolean reverse;
size_t _resultSize = getResultSize_service_Circular(backend,
server,
sessionId,
sessionContext,
nodeId,
start,
end,
numValuesPerNode == 0 ? 0 : numValuesPerNode + (UA_UInt32)skip,
returnBounds,
&startIndex,
&endIndex,
&addFirst,
&addLast,
&reverse);
*resultSize = _resultSize - skip;
if(*resultSize > maxSize) {
*resultSize = maxSize;
}
UA_DataValue *outResult = (UA_DataValue *)UA_Array_new(*resultSize, &UA_TYPES[UA_TYPES_DATAVALUE]);
if(!outResult) {
*resultSize = 0;
return UA_STATUSCODE_BADOUTOFMEMORY;
}
*result = outResult;
size_t counter = 0;
if(addFirst) {
if(skip == 0) {
outResult[counter].hasStatus = true;
outResult[counter].status = UA_STATUSCODE_BADBOUNDNOTFOUND;
outResult[counter].hasSourceTimestamp = true;
if(start == LLONG_MIN) {
outResult[counter].sourceTimestamp = end;
} else {
outResult[counter].sourceTimestamp = start;
}
++counter;
}
}
UA_ByteString backendOutContinuationPoint;
UA_ByteString_init(&backendOutContinuationPoint);
if(endIndex != storeEnd && startIndex != storeEnd) {
size_t retval = 0;
size_t valueSize = *resultSize - counter;
if(valueSize + skip > _resultSize - addFirst - addLast) {
if(skip == 0) {
valueSize = _resultSize - addFirst - addLast;
} else {
valueSize = _resultSize - skip - addLast;
}
}
UA_StatusCode ret = UA_STATUSCODE_GOOD;
if(valueSize > 0)
ret = backend->copyDataValues(server,
backend->context,
sessionId,
sessionContext,
nodeId,
startIndex,
endIndex,
reverse,
valueSize,
range,
releaseContinuationPoints,
&backendContinuationPoint,
&backendOutContinuationPoint,
&retval,
&outResult[counter]);
if(ret != UA_STATUSCODE_GOOD) {
UA_Array_delete(outResult, *resultSize, &UA_TYPES[UA_TYPES_DATAVALUE]);
*result = NULL;
*resultSize = 0;
return ret;
}
counter += retval;
}
if(addLast && counter < *resultSize) {
outResult[counter].hasStatus = true;
outResult[counter].status = UA_STATUSCODE_BADBOUNDNOTFOUND;
outResult[counter].hasSourceTimestamp = true;
if(start == LLONG_MIN && storeEnd != backend->firstIndex(server, backend->context, sessionId, sessionContext, nodeId)) {
outResult[counter].sourceTimestamp = backend->getDataValue(server, backend->context, sessionId, sessionContext, nodeId, endIndex)->sourceTimestamp - UA_DATETIME_SEC;
} else if(end == LLONG_MIN && storeEnd != backend->firstIndex(server, backend->context, sessionId, sessionContext, nodeId)) {
outResult[counter].sourceTimestamp = backend->getDataValue(server, backend->context, sessionId, sessionContext, nodeId, endIndex)->sourceTimestamp + UA_DATETIME_SEC;
} else {
outResult[counter].sourceTimestamp = end;
}
}
// there are more values
if(skip + *resultSize < _resultSize
// there are not more values for this request, but there are more values in
// database
|| (backendOutContinuationPoint.length > 0 && numValuesPerNode != 0)
// we deliver just one value which is a FIRST/LAST value
|| (skip == 0 && addFirst == true && *resultSize == 1)) {
if(UA_ByteString_allocBuffer(outContinuationPoint, backendOutContinuationPoint.length + sizeof(size_t)) != UA_STATUSCODE_GOOD) {
return UA_STATUSCODE_BADOUTOFMEMORY;
}
*((size_t *)(outContinuationPoint->data)) = skip + *resultSize;
if(backendOutContinuationPoint.length > 0)
memcpy(outContinuationPoint->data + sizeof(size_t), backendOutContinuationPoint.data, backendOutContinuationPoint.length);
}
UA_ByteString_clear(&backendOutContinuationPoint);
return UA_STATUSCODE_GOOD;
}
UA_HistoryDataBackend
UA_HistoryDataBackend_Memory_Circular(size_t initialNodeIdStoreSize, size_t initialDataStoreSize) {
UA_HistoryDataBackend result = UA_HistoryDataBackend_Memory(initialNodeIdStoreSize, initialDataStoreSize);
result.serverSetHistoryData = &serverSetHistoryData_backend_memory_Circular;
result.getHistoryData = &getHistoryData_service_Circular;
return result;
}

View File

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2018 (c) basysKom GmbH <opensource@basyskom.com> (Author: Peter Rustler)
* Copyright 2021 (c) luibass92 <luibass92@live.it> (Author: Luigi Bassetta)
*/
#include <open62541/client_subscriptions.h>
@ -228,3 +229,30 @@ UA_HistoryDataGathering_Default(size_t initialNodeIdStoreSize)
gathering.context = context;
return gathering;
}
/* Circular buffer implementation */
static UA_StatusCode
registerNodeId_gathering_circular(UA_Server *server, void *context,
const UA_NodeId *nodeId,
const UA_HistorizingNodeIdSettings setting) {
UA_NodeIdStoreContext *ctx = (UA_NodeIdStoreContext *)context;
if(getNodeIdStoreContextItem_gathering_default(ctx, nodeId)) {
return UA_STATUSCODE_BADNODEIDEXISTS;
}
if(ctx->storeEnd >= ctx->storeSize || !ctx->dataStore) {
return UA_STATUSCODE_BADOUTOFMEMORY;
}
UA_NodeId_copy(nodeId, &ctx->dataStore[ctx->storeEnd].nodeId);
size_t current = ctx->storeEnd;
ctx->dataStore[current].setting = setting;
++ctx->storeEnd;
return UA_STATUSCODE_GOOD;
}
UA_HistoryDataGathering
UA_HistoryDataGathering_Circular(size_t initialNodeIdStoreSize) {
UA_HistoryDataGathering gathering = UA_HistoryDataGathering_Default(initialNodeIdStoreSize);
gathering.registerNodeId = &registerNodeId_gathering_circular;
return gathering;
}

View File

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2018 (c) basysKom GmbH <opensource@basyskom.com> (Author: Peter Rustler)
* Copyright 2021 (c) luibass92 <luibass92@live.it> (Author: Luigi Bassetta)
*/
#ifndef UA_HISTORYDATABACKEND_MEMORY_H_
@ -17,6 +18,15 @@ _UA_BEGIN_DECLS
UA_HistoryDataBackend UA_EXPORT
UA_HistoryDataBackend_Memory(size_t initialNodeIdStoreSize, size_t initialDataStoreSize);
/* This function construct a UA_HistoryDataBackend which implements a circular buffer in memory.
*
* initialNodeIdStoreSize is the maximum number of NodeIds that will be historized. This number cannot be overcomed.
* initialDataStoreSize is the maximum number of UA_DataValueMemoryStoreItem that will be saved in the circular buffer for a particular NodeId.
* Subsequent UA_DataValueMemoryStoreItem will be saved replacing the oldest ones following the logic of circular buffers.
*/
UA_HistoryDataBackend UA_EXPORT
UA_HistoryDataBackend_Memory_Circular(size_t initialNodeIdStoreSize, size_t initialDataStoreSize);
void UA_EXPORT
UA_HistoryDataBackend_Memory_clear(UA_HistoryDataBackend *backend);

View File

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2018 (c) basysKom GmbH <opensource@basyskom.com> (Author: Peter Rustler)
* Copyright 2021 (c) luibass92 <luibass92@live.it> (Author: Luigi Bassetta)
*/
#ifndef UA_HISTORYDATAGATHERING_DEFAULT_H_
@ -15,6 +16,13 @@ _UA_BEGIN_DECLS
UA_HistoryDataGathering UA_EXPORT
UA_HistoryDataGathering_Default(size_t initialNodeIdStoreSize);
/* This function construct a UA_HistoryDataGathering which implements a circular buffer in memory.
*
* initialNodeIdStoreSize is the maximum number of NodeIds for which the data will be gathered. This number cannot be overcomed.
*/
UA_HistoryDataGathering UA_EXPORT
UA_HistoryDataGathering_Circular(size_t initialNodeIdStoreSize);
_UA_END_DECLS
#endif /* UA_HISTORYDATAGATHERING_DEFAULT_H_ */

View File

@ -41,15 +41,17 @@ UA_DURATIONRANGE(UA_Duration min, UA_Duration max) {
return range;
}
static UA_StatusCode
setDefaultConfig(UA_ServerConfig *conf);
UA_Server *
UA_Server_new() {
UA_ServerConfig config;
memset(&config, 0, sizeof(UA_ServerConfig));
/* Set a default logger and NodeStore for the initialization */
config.logger = UA_Log_Stdout_;
if(UA_STATUSCODE_GOOD != UA_Nodestore_HashMap(&config.nodestore)) {
UA_StatusCode res = setDefaultConfig(&config);
if(res != UA_STATUSCODE_GOOD)
return NULL;
}
return UA_Server_newWithConfig(&config);
}
@ -125,14 +127,22 @@ setDefaultConfig(UA_ServerConfig *conf) {
if(!conf)
return UA_STATUSCODE_BADINVALIDARGUMENT;
/* NodeStore */
if(conf->nodestore.context == NULL)
UA_Nodestore_HashMap(&conf->nodestore);
/* --> Start setting the default static config <-- */
/* Allow user to set his own logger */
/* Logging */
if(!conf->logger.log)
conf->logger = UA_Log_Stdout_;
/* EventLoop */
if(conf->eventLoop == NULL) {
conf->eventLoop = UA_EventLoop_new(&conf->logger);
conf->externalEventLoop = false;
}
/* --> Start setting the default static config <-- */
conf->shutdownDelay = 0.0;
/* Server Description */
@ -752,6 +762,12 @@ UA_ClientConfig_setDefault(UA_ClientConfig *config) {
config->logger.clear = UA_Log_Stdout_clear;
}
/* EventLoop */
if(config->eventLoop == NULL) {
config->eventLoop = UA_EventLoop_new(&config->logger);
config->externalEventLoop = false;
}
if (config->sessionLocaleIdsSize > 0 && config->sessionLocaleIds) {
UA_Array_delete(config->sessionLocaleIds, config->sessionLocaleIdsSize, &UA_TYPES[UA_TYPES_LOCALEID]);
}

View File

@ -41,8 +41,9 @@ const char *logLevelNames[6] = {"trace", "debug",
ANSI_COLOR_YELLOW "warn",
ANSI_COLOR_RED "error",
ANSI_COLOR_MAGENTA "fatal"};
const char *logCategoryNames[7] = {"network", "channel", "session", "server",
"client", "userland", "securitypolicy"};
const char *logCategoryNames[UA_LOGCATEGORIES] =
{"network", "channel", "session", "server",
"client", "userland", "securitypolicy", "eventloop"};
#ifdef __clang__
__attribute__((__format__(__printf__, 4 , 0)))

View File

@ -13,8 +13,9 @@
const char *syslogLevelNames[6] = {"trace", "debug", "info",
"warn", "error", "fatal"};
const char *syslogCategoryNames[7] = {"network", "channel", "session", "server",
"client", "userland", "securitypolicy"};
const char *syslogCategoryNames[UA_LOGCATEGORIES] =
{"network", "channel", "session", "server",
"client", "userland", "securitypolicy", "eventloop"};
#ifdef __clang__
__attribute__((__format__(__printf__, 4 , 0)))

View File

@ -254,7 +254,10 @@ UA_NodeMap_deleteNode(void *context, UA_Node *node) {
}
static const UA_Node *
UA_NodeMap_getNode(void *context, const UA_NodeId *nodeid) {
UA_NodeMap_getNode(void *context, const UA_NodeId *nodeid,
UA_UInt32 attributeMask,
UA_ReferenceTypeSet references,
UA_BrowseDirection referenceDirections) {
UA_NodeMap *ns = (UA_NodeMap*)context;
UA_NodeMapSlot *slot = findOccupiedSlot(ns, nodeid);
if(!slot)
@ -263,6 +266,17 @@ UA_NodeMap_getNode(void *context, const UA_NodeId *nodeid) {
return &slot->entry->node;
}
static const UA_Node *
UA_NodeMap_getNodeFromPtr(void *context, UA_NodePointer ptr,
UA_UInt32 attributeMask,
UA_ReferenceTypeSet references,
UA_BrowseDirection referenceDirections) {
if(!UA_NodePointer_isLocal(ptr))
return NULL;
UA_NodeId id = UA_NodePointer_toNodeId(ptr);
return UA_NodeMap_getNode(context, &id, attributeMask, references, referenceDirections);
}
static void
UA_NodeMap_releaseNode(void *context, const UA_Node *node) {
if (!node)
@ -513,6 +527,7 @@ UA_Nodestore_HashMap(UA_Nodestore *ns) {
ns->newNode = UA_NodeMap_newNode;
ns->deleteNode = UA_NodeMap_deleteNode;
ns->getNode = UA_NodeMap_getNode;
ns->getNodeFromPtr = UA_NodeMap_getNodeFromPtr;
ns->releaseNode = UA_NodeMap_releaseNode;
ns->getNodeCopy = UA_NodeMap_getNodeCopy;
ns->insertNode = UA_NodeMap_insertNode;

View File

@ -139,7 +139,10 @@ zipNsDeleteNode(void *nsCtx, UA_Node *node) {
}
static const UA_Node *
zipNsGetNode(void *nsCtx, const UA_NodeId *nodeId) {
zipNsGetNode(void *nsCtx, const UA_NodeId *nodeId,
UA_UInt32 attributeMask,
UA_ReferenceTypeSet references,
UA_BrowseDirection referenceDirections) {
ZipContext *ns = (ZipContext*)nsCtx;
NodeEntry dummy;
dummy.nodeIdHash = UA_NodeId_hash(nodeId);
@ -151,6 +154,18 @@ zipNsGetNode(void *nsCtx, const UA_NodeId *nodeId) {
return (const UA_Node*)&entry->nodeId;
}
static const UA_Node *
zipNsGetNodeFromPtr(void *nsCtx, UA_NodePointer ptr,
UA_UInt32 attributeMask,
UA_ReferenceTypeSet references,
UA_BrowseDirection referenceDirections) {
if(!UA_NodePointer_isLocal(ptr))
return NULL;
UA_NodeId id = UA_NodePointer_toNodeId(ptr);
return zipNsGetNode(nsCtx, &id, attributeMask,
references, referenceDirections);
}
static void
zipNsReleaseNode(void *nsCtx, const UA_Node *node) {
if(!node)
@ -163,9 +178,12 @@ zipNsReleaseNode(void *nsCtx, const UA_Node *node) {
static UA_StatusCode
zipNsGetNodeCopy(void *nsCtx, const UA_NodeId *nodeId,
UA_Node **outNode) {
/* Find the node */
const UA_Node *node = zipNsGetNode(nsCtx, nodeId);
UA_Node **outNode) {
/* Get the node (with all attributes and references, the mask and refs are
currently noy evaluated within the plugin.) */
const UA_Node *node =
zipNsGetNode(nsCtx, nodeId, UA_NODEATTRIBUTESMASK_ALL,
UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
if(!node)
return UA_STATUSCODE_BADNODEIDUNKNOWN;
@ -261,8 +279,10 @@ zipNsInsertNode(void *nsCtx, UA_Node *node, UA_NodeId *addedNodeId) {
static UA_StatusCode
zipNsReplaceNode(void *nsCtx, UA_Node *node) {
/* Find the node */
const UA_Node *oldNode = zipNsGetNode(nsCtx, &node->head.nodeId);
/* Find the node (the mask and refs are not evaluated yet by the plugin)*/
const UA_Node *oldNode =
zipNsGetNode(nsCtx, &node->head.nodeId, UA_NODEATTRIBUTESMASK_ALL,
UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
if(!oldNode) {
deleteEntry(container_of(node, NodeEntry, nodeId));
return UA_STATUSCODE_BADNODEIDUNKNOWN;
@ -372,6 +392,7 @@ UA_Nodestore_ZipTree(UA_Nodestore *ns) {
ns->newNode = zipNsNewNode;
ns->deleteNode = zipNsDeleteNode;
ns->getNode = zipNsGetNode;
ns->getNodeFromPtr = zipNsGetNodeFromPtr;
ns->releaseNode = zipNsReleaseNode;
ns->getNodeCopy = zipNsGetNodeCopy;
ns->insertNode = zipNsInsertNode;

View File

@ -1243,7 +1243,7 @@ UA_PubSubChannelEthernet_receive(UA_PubSubChannel *channel,
* VLAN header size is stripped before it is recieved
* so the packet length is less than 60bytes */
messageLength = messageLength + ((size_t)dataLen - sizeof(struct ether_header));
messageLength = ((size_t)dataLen - sizeof(struct ether_header));
buffer.length = messageLength;
retval = receiveCallback(channel, receiveCallbackContext, &buffer);

View File

@ -631,6 +631,7 @@ UA_PubSubChannelUDPMC_receive(UA_PubSubChannel *channel,
UA_DateTime newTimeoutValue = remainingTimeoutValue - receiveDuration;
timeoutValue.tv_sec = (long int)(newTimeoutValue / UA_DATETIME_SEC);
timeoutValue.tv_usec = (long int)((newTimeoutValue % UA_DATETIME_SEC) * 100);
} while(true); /* TODO:Need to handle for jumbo frames*/
/* 1518 bytes is the maximum size of ethernet packet
* where 18 bytes used for header size, 28 bytes of header

View File

@ -16,6 +16,7 @@
* Copyright 2017 (c) Mark Giraud, Fraunhofer IOSB
* Copyright 2018 (c) Kalycito Infotech Private Limited
* Copyright 2020 (c) Christian von Arnim, ISW University of Stuttgart
* Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes)
*/
#include <open62541/transport_generated.h>
@ -32,7 +33,6 @@ static void
UA_Client_init(UA_Client* client) {
UA_SecureChannel_init(&client->channel, &client->config.localConnectionConfig);
client->connectStatus = UA_STATUSCODE_GOOD;
UA_Timer_init(&client->timer);
notifyClientState(client);
}
@ -70,6 +70,19 @@ UA_ClientConfig_clear(UA_ClientConfig *config) {
UA_free(config->securityPolicies);
config->securityPolicies = 0;
/* Stop and delete the EventLoop */
if(config->eventLoop && !config->externalEventLoop) {
if(UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_FRESH &&
UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_STOPPED) {
UA_EventLoop_stop(config->eventLoop);
while(UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_STOPPED) {
UA_EventLoop_run(config->eventLoop, 100);
}
}
UA_EventLoop_delete(config->eventLoop);
config->eventLoop = NULL;
}
/* Logger */
if(config->logger.clear)
config->logger.clear(config->logger.context);
@ -99,8 +112,6 @@ UA_Client_clear(UA_Client *client) {
UA_Client_Subscriptions_clean(client);
#endif
/* Delete the timed work */
UA_Timer_clear(&client->timer);
}
void
@ -592,27 +603,29 @@ UA_Client_sendAsyncRequest(UA_Client *client, const void *request,
UA_StatusCode UA_EXPORT
UA_Client_addTimedCallback(UA_Client *client, UA_ClientCallback callback,
void *data, UA_DateTime date, UA_UInt64 *callbackId) {
return UA_Timer_addTimedCallback(&client->timer, (UA_ApplicationCallback)callback,
return UA_EventLoop_addTimedCallback(client->config.eventLoop, (UA_Callback)callback,
client, data, date, callbackId);
}
UA_StatusCode
UA_Client_addRepeatedCallback(UA_Client *client, UA_ClientCallback callback,
void *data, UA_Double interval_ms, UA_UInt64 *callbackId) {
return UA_Timer_addRepeatedCallback(&client->timer, (UA_ApplicationCallback)callback,
client, data, interval_ms, NULL,
UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, callbackId);
return UA_EventLoop_addCyclicCallback(
client->config.eventLoop, (UA_Callback)callback, client, data,
interval_ms, NULL, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, callbackId);
}
UA_StatusCode
UA_Client_changeRepeatedCallbackInterval(UA_Client *client, UA_UInt64 callbackId,
UA_Double interval_ms) {
return UA_Timer_changeRepeatedCallback(&client->timer, callbackId, interval_ms, NULL, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME);
return UA_EventLoop_modifyCyclicCallback(client->config.eventLoop, callbackId,
interval_ms, NULL, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME);
}
void
UA_Client_removeCallback(UA_Client *client, UA_UInt64 callbackId) {
UA_Timer_removeCallback(&client->timer, callbackId);
UA_EventLoop_removeCyclicCallback(client->config.eventLoop, callbackId);
}
static void
@ -672,19 +685,23 @@ UA_Client_backgroundConnectivity(UA_Client *client) {
client->pendingConnectivityCheck = true;
}
static void
clientExecuteRepeatedCallback(void *executionApplication, UA_ApplicationCallback cb,
void *callbackApplication, void *data) {
cb(callbackApplication, data);
}
UA_StatusCode
UA_Client_run_iterate(UA_Client *client, UA_UInt32 timeout) {
UA_ClientConfig *cc = UA_Client_getConfig(client);
UA_CHECK_ERROR(UA_EventLoop_getState(cc->eventLoop) != UA_EVENTLOOPSTATE_STOPPED,
return UA_STATUSCODE_BAD, &client->config.logger, UA_LOGCATEGORY_CLIENT,
"Eventloop was explicitly stopped.");
UA_StatusCode rv = UA_STATUSCODE_GOOD;
if(UA_EventLoop_getState(cc->eventLoop) == UA_EVENTLOOPSTATE_FRESH) {
rv = UA_EventLoop_start(cc->eventLoop);
UA_CHECK_STATUS(rv, return rv);
}
/* Process timed (repeated) jobs */
UA_DateTime now = UA_DateTime_nowMonotonic();
UA_DateTime maxDate =
UA_Timer_process(&client->timer, now, (UA_TimerExecutionCallback)
clientExecuteRepeatedCallback, client);
UA_EventLoop_run(client->config.eventLoop, 0);
UA_DateTime maxDate = UA_EventLoop_nextCyclicTime(client->config.eventLoop);
if(maxDate > now + ((UA_DateTime)timeout * UA_DATETIME_MSEC))
maxDate = now + ((UA_DateTime)timeout * UA_DATETIME_MSEC);

View File

@ -20,7 +20,8 @@
#include "open62541_queue.h"
#include "ua_securechannel.h"
#include "ua_timer.h"
#include "common/ua_timer.h"
#include "ua_util_internal.h"
_UA_BEGIN_DECLS
@ -109,7 +110,6 @@ typedef struct CustomCallback {
struct UA_Client {
UA_ClientConfig config;
UA_Timer timer;
/* Overall connection status */
UA_StatusCode connectStatus;

View File

@ -278,6 +278,7 @@ UA_DataSetReader_handleMessageReceiveTimeout(UA_Server *server,
UA_StatusCode
UA_DataSetReader_generateNetworkMessage(UA_PubSubConnection *pubSubConnection,
UA_ReaderGroup *readerGroup,
UA_DataSetReader *dataSetReader,
UA_DataSetMessage *dsm, UA_UInt16 *writerId,
UA_Byte dsmCount, UA_NetworkMessage *nm);

View File

@ -4,6 +4,7 @@
*
* Copyright (c) 2017-2019 Fraunhofer IOSB (Author: Andreas Ebner)
* Copyright (c) 2018 Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright (c) 2021 Fraunhofer IOSB (Author: Jan Hermes)
*/
#include <open62541/server_pubsub.h>
@ -316,12 +317,10 @@ UA_PubSubManager_delete(UA_Server *server, UA_PubSubManager *pubSubManager) {
/* Stop and unfreeze all WriterGroups */
UA_PubSubConnection *tmpConnection;
TAILQ_FOREACH(tmpConnection, &server->pubSubManager.connections, listEntry){
for(size_t i = 0; i < pubSubManager->connectionsSize; i++) {
UA_WriterGroup *writerGroup;
LIST_FOREACH(writerGroup, &tmpConnection->writerGroups, listEntry) {
UA_WriterGroup_setPubSubState(server, UA_PUBSUBSTATE_DISABLED, writerGroup);
UA_Server_unfreezeWriterGroupConfiguration(server, writerGroup->identifier);
}
UA_WriterGroup *writerGroup;
LIST_FOREACH(writerGroup, &tmpConnection->writerGroups, listEntry) {
UA_WriterGroup_setPubSubState(server, UA_PUBSUBSTATE_DISABLED, writerGroup);
UA_Server_unfreezeWriterGroupConfiguration(server, writerGroup->identifier);
}
}
@ -352,20 +351,22 @@ UA_StatusCode
UA_PubSubManager_addRepeatedCallback(UA_Server *server, UA_ServerCallback callback,
void *data, UA_Double interval_ms, UA_DateTime *baseTime,
UA_TimerPolicy timerPolicy, UA_UInt64 *callbackId) {
return UA_Timer_addRepeatedCallback(&server->timer, (UA_ApplicationCallback)callback,
server, data, interval_ms, baseTime, timerPolicy, callbackId);
return UA_EventLoop_addCyclicCallback(server->config.eventLoop, (UA_Callback)callback,
server, data, interval_ms, baseTime,
timerPolicy, callbackId);
}
UA_StatusCode
UA_PubSubManager_changeRepeatedCallback(UA_Server *server, UA_UInt64 callbackId,
UA_Double interval_ms, UA_DateTime *baseTime,
UA_TimerPolicy timerPolicy) {
return UA_Timer_changeRepeatedCallback(&server->timer, callbackId, interval_ms, baseTime, timerPolicy);
return UA_EventLoop_modifyCyclicCallback(server->config.eventLoop, callbackId,
interval_ms, baseTime, timerPolicy);
}
void
UA_PubSubManager_removeRepeatedPubSubCallback(UA_Server *server, UA_UInt64 callbackId) {
UA_Timer_removeCallback(&server->timer, callbackId);
UA_EventLoop_removeCyclicCallback(server->config.eventLoop, callbackId);
}
@ -427,7 +428,7 @@ UA_PubSubComponent_startMonitoring(UA_Server *server, UA_NodeId Id, UA_PubSubCom
/* use a timed callback, because one notification is enough,
we assume that MessageReceiveTimeout configuration is in [ms], we do not handle or check fractions */
UA_UInt64 interval = (UA_UInt64)(reader->config.messageReceiveTimeout * UA_DATETIME_MSEC);
ret = UA_Timer_addTimedCallback(&server->timer, (UA_ApplicationCallback) reader->msgRcvTimeoutTimerCallback,
ret = UA_EventLoop_addTimedCallback(server->config.eventLoop, (UA_Callback) reader->msgRcvTimeoutTimerCallback,
server, reader, UA_DateTime_nowMonotonic() + (UA_DateTime) interval, &(reader->msgRcvTimeoutTimerId));
if (ret == UA_STATUSCODE_GOOD) {
UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER,
@ -475,7 +476,7 @@ UA_PubSubComponent_stopMonitoring(UA_Server *server, UA_NodeId Id, UA_PubSubComp
UA_DataSetReader *reader = (UA_DataSetReader*) data;
switch (eMonitoringType) {
case UA_PUBSUB_MONITORING_MESSAGE_RECEIVE_TIMEOUT: {
UA_Timer_removeCallback(&server->timer, reader->msgRcvTimeoutTimerId);
UA_EventLoop_removeCyclicCallback(server->config.eventLoop, reader->msgRcvTimeoutTimerId);
UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER,
"UA_PubSubComponent_stopMonitoring(): DataSetReader '%.*s' - MessageReceiveTimeout: MessageReceiveTimeout = '%f' "
"Timer Id = '%u'", (UA_Int32) reader->config.name.length, reader->config.name.data,
@ -515,7 +516,7 @@ UA_PubSubComponent_updateMonitoringInterval(UA_Server *server, UA_NodeId Id, UA_
UA_DataSetReader *reader = (UA_DataSetReader*) data;
switch (eMonitoringType) {
case UA_PUBSUB_MONITORING_MESSAGE_RECEIVE_TIMEOUT: {
ret = UA_Timer_changeRepeatedCallback(&server->timer, reader->msgRcvTimeoutTimerId,
ret = UA_EventLoop_modifyCyclicCallback(server->config.eventLoop, reader->msgRcvTimeoutTimerId,
reader->config.messageReceiveTimeout, NULL,
UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME);
if (ret == UA_STATUSCODE_GOOD) {

View File

@ -49,6 +49,10 @@ const UA_Byte DS_MESSAGEHEADER_PICOSECONDS_INCLUDED_MASK = 32;
const UA_Byte NM_SHIFT_LEN = 2;
const UA_Byte DS_MH_SHIFT_LEN = 1;
/* Static memory allocation for the message nonce */
#define MESSAGE_NONCE_LENGTH 8
static UA_Byte MessageNonceGenerated[MESSAGE_NONCE_LENGTH];
static UA_Boolean UA_NetworkMessage_ExtendedFlags1Enabled(const UA_NetworkMessage* src);
static UA_Boolean UA_NetworkMessage_ExtendedFlags2Enabled(const UA_NetworkMessage* src);
static UA_Boolean UA_DataSetMessageHeader_DataSetFlags2Enabled(const UA_DataSetMessageHeader* src);
@ -762,7 +766,8 @@ UA_SecurityHeader_decodeBinary(const UA_ByteString *src, size_t *offset,
// MessageNonce
if(nonceLength > 0) {
//TODO: check for memory leaks
rv = UA_ByteString_allocBuffer(&dst->securityHeader.messageNonce, nonceLength);
dst->securityHeader.messageNonce.length = MESSAGE_NONCE_LENGTH;
dst->securityHeader.messageNonce.data = MessageNonceGenerated;
UA_CHECK_STATUS(rv, return rv);
for (UA_Byte i = 0; i < nonceLength; i++) {
rv = UA_Byte_decodeBinary(src, offset,
@ -1095,8 +1100,6 @@ UA_NetworkMessage_clear(UA_NetworkMessage* p) {
if(p->promotedFieldsEnabled)
UA_Array_delete(p->promotedFields, p->promotedFieldsSize, &UA_TYPES[UA_TYPES_VARIANT]);
UA_ByteString_clear(&p->securityHeader.messageNonce);
if(p->networkMessageType == UA_NETWORKMESSAGE_DATASET) {
if(p->payloadHeaderEnabled) {
if(p->payloadHeader.dataSetPayloadHeader.dataSetWriterIds != NULL) {

View File

@ -221,6 +221,10 @@ typedef struct {
UA_Boolean RTsubscriberEnabled; /* Addtional offsets computation like publisherId, WGId if this bool enabled */
UA_NetworkMessage *nm; /* The precomputed NetworkMessage for subscriber */
size_t rawMessageLength;
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
UA_ByteString encryptBuffer; /* The precomputed message buffer is copied into the encrypt buffer for encryption and signing*/
UA_Byte *payloadPosition; /* Payload Position of the message to encrypt*/
#endif
} UA_NetworkMessageOffsetBuffer;
/**

View File

@ -32,6 +32,12 @@
/* This functionality of this API will be used in future to create mirror Variables - TODO */
/* #define UA_MAX_SIZENAME 64 */ /* Max size of Qualified Name of Subscribed Variable */
/* Static memory allocation for the message nonce */
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
#define MESSAGE_NONCE_LENGTH 8
static UA_Byte MessageNonceGenerated[MESSAGE_NONCE_LENGTH];
#endif
/* Clear DataSetReader */
static void
UA_DataSetReader_clear(UA_Server *server, UA_DataSetReader *dataSetReader);
@ -210,10 +216,9 @@ UA_DataSetReader_generateDataSetMessage(UA_Server *server,
}
UA_StatusCode
UA_DataSetReader_generateNetworkMessage(UA_PubSubConnection *pubSubConnection,
UA_DataSetReader *dataSetReader,
UA_DataSetMessage *dsm, UA_UInt16 *writerId,
UA_Byte dsmCount, UA_NetworkMessage *nm) {
UA_DataSetReader_generateNetworkMessage(UA_PubSubConnection *pubSubConnection, UA_ReaderGroup *readerGroup,
UA_DataSetReader *dataSetReader, UA_DataSetMessage *dsm, UA_UInt16 *writerId, UA_Byte dsmCount,
UA_NetworkMessage *nm) {
UA_ExtensionObject *settings = &dataSetReader->config.messageSettings;
if(settings->content.decoded.type != &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE])
return UA_STATUSCODE_BADNOTSUPPORTED;
@ -242,6 +247,28 @@ UA_DataSetReader_generateNetworkMessage(UA_PubSubConnection *pubSubConnection,
(u64)UA_UADPNETWORKMESSAGECONTENTMASK_DATASETCLASSID) != 0;
nm->promotedFieldsEnabled = ((u64)dsrm->networkMessageContentMask &
(u64)UA_UADPNETWORKMESSAGECONTENTMASK_PROMOTEDFIELDS) != 0;
/* Set the SecurityHeader */
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
if(readerGroup->config.securityMode > UA_MESSAGESECURITYMODE_NONE) {
nm->securityEnabled = true;
nm->securityHeader.networkMessageSigned = true;
if(readerGroup->config.securityMode >= UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
nm->securityHeader.networkMessageEncrypted = true;
nm->securityHeader.securityTokenId = readerGroup->securityTokenId;
/* Generate the MessageNonce */
nm->securityHeader.messageNonce.length = MESSAGE_NONCE_LENGTH;
nm->securityHeader.messageNonce.data = MessageNonceGenerated;
nm->securityHeader.messageNonce.length = 4; /* Generate 4 random bytes */
UA_StatusCode rv = readerGroup->config.securityPolicy->symmetricModule.
generateNonce(readerGroup->config.securityPolicy->policyContext,
&nm->securityHeader.messageNonce);
if(rv != UA_STATUSCODE_GOOD)
return rv;
nm->securityHeader.messageNonce.length = 8;
}
#endif
nm->version = 1;
nm->networkMessageType = UA_NETWORKMESSAGE_DATASET;
@ -1360,6 +1387,26 @@ decodeAndProcessNetworkMessageRT(UA_Server *server, UA_ReaderGroup *readerGroup,
UA_DataSetReader *dataSetReader = LIST_FIRST(&readerGroup->readers);
UA_NetworkMessage *nm = dataSetReader->bufferedMessage.nm;
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
UA_NetworkMessage currentNetworkMessage;
memset(&currentNetworkMessage, 0, sizeof(UA_NetworkMessage));
UA_StatusCode rv;
size_t payLoadPosition = 0;
rv = UA_NetworkMessage_decodeHeaders(
buffer, &payLoadPosition, &currentNetworkMessage);
UA_CHECK_STATUS_ERROR(rv, return rv, &server->config.logger, UA_LOGCATEGORY_SERVER,
"PubSub receive. decoding headers failed");
rv = verifyAndDecryptNetworkMessage(&server->config.logger,
buffer,
&payLoadPosition,
&currentNetworkMessage,
readerGroup);
UA_CHECK_STATUS_WARN(rv, return rv, &server->config.logger, UA_LOGCATEGORY_SERVER,
"Subscribe failed. verify and decrypt network message failed.");
UA_NetworkMessage_clear(&currentNetworkMessage);
#endif
/* Decode only the necessary offset and update the networkMessage */
UA_StatusCode res =
UA_NetworkMessage_updateBufferedNwMessage(&dataSetReader->bufferedMessage,

View File

@ -563,7 +563,7 @@ UA_Server_freezeReaderGroupConfiguration(UA_Server *server,
return UA_STATUSCODE_BADOUTOFMEMORY;
}
res = UA_DataSetReader_generateNetworkMessage(pubSubConnection, dataSetReader, dsm,
res = UA_DataSetReader_generateNetworkMessage(pubSubConnection, rg, dataSetReader, dsm,
dsWriterIds, 1, networkMessage);
if(res != UA_STATUSCODE_GOOD) {
UA_free(networkMessage->payload.dataSetPayload.sizes);

View File

@ -27,6 +27,14 @@
static void
UA_WriterGroup_clear(UA_Server *server, UA_WriterGroup *writerGroup);
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
static UA_StatusCode
encryptAndSign(UA_WriterGroup *wg, const UA_NetworkMessage *nm,
UA_Byte *signStart, UA_Byte *encryptStart,
UA_Byte *msgEnd);
#endif
static UA_StatusCode
generateNetworkMessage(UA_PubSubConnection *connection, UA_WriterGroup *wg,
UA_DataSetMessage *dsm, UA_UInt16 *writerIds, UA_Byte dsmCount,
@ -286,6 +294,13 @@ UA_Server_freezeWriterGroupConfiguration(UA_Server *server,
/* Allocate the buffer. Allocate on the stack if the buffer is small. */
msgSize = UA_NetworkMessage_calcSizeBinary(&networkMessage, NULL);
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
if(wg->config.securityMode > UA_MESSAGESECURITYMODE_NONE) {
UA_PubSubSecurityPolicy *sp = wg->config.securityPolicy;
msgSize += sp->symmetricModule.cryptoModule.
signatureAlgorithm.getLocalSignatureSize(sp->policyContext);
}
#endif
res = UA_ByteString_allocBuffer(&buf, msgSize);
if(res != UA_STATUSCODE_GOOD)
goto cleanup;
@ -294,7 +309,21 @@ UA_Server_freezeWriterGroupConfiguration(UA_Server *server,
/* Encode the NetworkMessage */
bufEnd = &wg->bufferedMessage.buffer.data[wg->bufferedMessage.buffer.length];
bufPos = wg->bufferedMessage.buffer.data;
UA_NetworkMessage_encodeBinary(&networkMessage, &bufPos, bufEnd, NULL);
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
if (wg->config.securityMode > UA_MESSAGESECURITYMODE_NONE){
UA_Byte *payloadPosition;
UA_NetworkMessage_encodeBinary(&networkMessage, &bufPos, bufEnd, &payloadPosition);
wg->bufferedMessage.payloadPosition = payloadPosition;
wg->bufferedMessage.nm = (UA_NetworkMessage *)UA_malloc(sizeof(networkMessage));
wg->bufferedMessage.nm->securityHeader.networkMessageEncrypted = networkMessage.securityHeader.networkMessageEncrypted;
wg->bufferedMessage.nm->securityHeader.networkMessageSigned = networkMessage.securityHeader.networkMessageSigned;
UA_ByteString_copy(&networkMessage.securityHeader.messageNonce, &wg->bufferedMessage.nm->securityHeader.messageNonce);
UA_ByteString_allocBuffer(&wg->bufferedMessage.encryptBuffer, msgSize);
UA_ByteString_clear(&networkMessage.securityHeader.messageNonce);
}
#endif
if (wg->config.securityMode <= UA_MESSAGESECURITYMODE_NONE)
UA_NetworkMessage_encodeBinary(&networkMessage, &bufPos, bufEnd, NULL);
cleanup:
UA_free(networkMessage.payload.dataSetPayload.sizes);
@ -348,8 +377,18 @@ UA_Server_unfreezeWriterGroupConfiguration(UA_Server *server,
}
dataSetWriter->configurationFrozen = UA_FALSE;
}
if(wg->config.rtLevel == UA_PUBSUB_RT_FIXED_SIZE)
if(wg->config.rtLevel == UA_PUBSUB_RT_FIXED_SIZE) {
UA_ByteString_clear(&wg->bufferedMessage.buffer);
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
if (wg->config.securityMode > UA_MESSAGESECURITYMODE_NONE) {
if (wg->bufferedMessage.nm != NULL) {
UA_ByteString_clear(&wg->bufferedMessage.nm->securityHeader.messageNonce);
UA_free(wg->bufferedMessage.nm);
}
UA_ByteString_clear(&wg->bufferedMessage.encryptBuffer);
}
#endif
}
return UA_STATUSCODE_GOOD;
}
@ -699,7 +738,7 @@ encryptAndSign(UA_WriterGroup *wg, const UA_NetworkMessage *nm,
signStart};
size_t sigSize = wg->config.securityPolicy->symmetricModule.cryptoModule.
signatureAlgorithm.getLocalSignatureSize(channelContext);
signatureAlgorithm.getLocalSignatureSize(channelContext);
UA_ByteString signature = {sigSize, msgEnd};
rv = wg->config.securityPolicy->symmetricModule.cryptoModule.
@ -949,19 +988,18 @@ cleanup:
static UA_StatusCode
sendBufferedNetworkMessage(UA_Server *server, UA_PubSubConnection *connection,
UA_NetworkMessageOffsetBuffer *buffer,
UA_ByteString *buffer,
UA_ExtensionObject *transportSettings) {
if(UA_NetworkMessage_updateBufferedMessage(buffer) != UA_STATUSCODE_GOOD)
UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER,
"PubSub sending. Unknown field type.");
return connection->channel->send(connection->channel,
transportSettings, &buffer->buffer);
transportSettings, buffer);
}
/* This callback triggers the collection and publish of NetworkMessages and the
* contained DataSetMessages. */
void
UA_WriterGroup_publishCallback(UA_Server *server, UA_WriterGroup *writerGroup) {
UA_StatusCode res = UA_STATUSCODE_GOOD;
UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER, "Publish Callback");
// TODO: review if its okay to force correct value from caller side instead
@ -997,14 +1035,39 @@ UA_WriterGroup_publishCallback(UA_Server *server, UA_WriterGroup *writerGroup) {
}
if(writerGroup->config.rtLevel == UA_PUBSUB_RT_FIXED_SIZE) {
UA_StatusCode res =
sendBufferedNetworkMessage(server, connection, &writerGroup->bufferedMessage,
&writerGroup->config.transportSettings);
if(UA_NetworkMessage_updateBufferedMessage(&writerGroup->bufferedMessage) != UA_STATUSCODE_GOOD)
UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER,
"PubSub sending. Unknown field type.");
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
if (writerGroup->config.securityMode > UA_MESSAGESECURITYMODE_NONE) {
size_t sigSize = writerGroup->config.securityPolicy->symmetricModule.cryptoModule.
signatureAlgorithm.getLocalSignatureSize(writerGroup->securityPolicyContext);
UA_Byte payloadOffset = (UA_Byte)(writerGroup->bufferedMessage.payloadPosition - writerGroup->bufferedMessage.buffer.data);
memcpy(writerGroup->bufferedMessage.encryptBuffer.data, writerGroup->bufferedMessage.buffer.data, writerGroup->bufferedMessage.buffer.length);
res = encryptAndSign(writerGroup, writerGroup->bufferedMessage.nm,
writerGroup->bufferedMessage.encryptBuffer.data,
writerGroup->bufferedMessage.encryptBuffer.data + payloadOffset,
writerGroup->bufferedMessage.encryptBuffer.data + writerGroup->bufferedMessage.encryptBuffer.length - sigSize);
if(res != UA_STATUSCODE_GOOD)
UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, "PubSub Encryption failed");
/* Send the encrypted buffered network message
* if PubSub encryption is enabled */
res = sendBufferedNetworkMessage(server, connection, &writerGroup->bufferedMessage.encryptBuffer,
&writerGroup->config.transportSettings);
}
#endif
if (writerGroup->config.securityMode < UA_MESSAGESECURITYMODE_NONE)
res = sendBufferedNetworkMessage(server, connection, &writerGroup->bufferedMessage.buffer,
&writerGroup->config.transportSettings);
if(res == UA_STATUSCODE_GOOD) {
writerGroup->sequenceNumber++;
} else {
UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER,
"Publish failed. RT fixed size. sendBufferedNetworkMessage failed");
"Publish failed. sendBufferedNetworkMessage failed. StatusCode %s", UA_StatusCode_name(res));
UA_WriterGroup_setPubSubState(server, UA_PUBSUBSTATE_ERROR, writerGroup);
}
return;
@ -1022,7 +1085,6 @@ UA_WriterGroup_publishCallback(UA_Server *server, UA_WriterGroup *writerGroup) {
* But only if they do not contain promoted fields. NM with only DSM are
* sent out right away. The others are kept in a buffer for "batching". */
size_t dsmCount = 0;
UA_StatusCode res = UA_STATUSCODE_GOOD;
UA_STACKARRAY(UA_UInt16, dsWriterIds, writerGroup->writersCount);
UA_STACKARRAY(UA_DataSetMessage, dsmStore, writerGroup->writersCount);
UA_DataSetWriter *dsw;

View File

@ -15,6 +15,37 @@
#include "ua_types_encoding_binary.h"
#include "aa_tree.h"
/*********************/
/* ReferenceType Set */
/*********************/
#define UA_REFTYPES_ALL_MASK (~(UA_UInt32)0)
#define UA_REFTYPES_ALL_MASK2 UA_REFTYPES_ALL_MASK, UA_REFTYPES_ALL_MASK
#define UA_REFTYPES_ALL_MASK4 UA_REFTYPES_ALL_MASK2, UA_REFTYPES_ALL_MASK2
#if (UA_REFERENCETYPESET_MAX) / 32 > 8
# error Adjust macros to support than 256 reference types
#elif (UA_REFERENCETYPESET_MAX) / 32 == 8
# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK4, UA_REFTYPES_ALL_MASK4
#elif (UA_REFERENCETYPESET_MAX) / 32 == 7
# define UA_REFTYPES_ALL_ARRAY \
UA_REFTYPES_ALL_MASK4, UA_REFTYPES_ALL_MASK2, UA_REFTYPES_ALL_MASK
#elif (UA_REFERENCETYPESET_MAX) / 32 == 6
# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK4, UA_REFTYPES_ALL_MASK2
#elif (UA_REFERENCETYPESET_MAX) / 32 == 5
# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK4, UA_REFTYPES_ALL_MASK
#elif (UA_REFERENCETYPESET_MAX) / 32 == 4
# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK4
#elif (UA_REFERENCETYPESET_MAX) / 32 == 3
# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK2, UA_REFTYPES_ALL_MASK
#elif (UA_REFERENCETYPESET_MAX) / 32 == 2
# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK2
#else
# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK
#endif
const UA_ReferenceTypeSet UA_REFERENCETYPESET_NONE = {0};
const UA_ReferenceTypeSet UA_REFERENCETYPESET_ALL = {{UA_REFTYPES_ALL_ARRAY}};
/*****************/
/* Node Pointers */
/*****************/
@ -358,14 +389,6 @@ UA_NodeReferenceKind_findTarget(const UA_NodeReferenceKind *rk,
return NULL;
}
const UA_Node *
UA_NODESTORE_GETFROMREF(UA_Server *server, UA_NodePointer target) {
if(!UA_NodePointer_isLocal(target))
return NULL;
UA_NodeId id = UA_NodePointer_toNodeId(target);
return UA_NODESTORE_GET(server, &id);
}
/* General node handling methods. There is no UA_Node_new() method here.
* Creating nodes is part of the Nodestore layer */

View File

@ -156,10 +156,6 @@ cleanup:
/* Server Lifecycle */
/********************/
static void
serverExecuteRepeatedCallback(UA_Server *server, UA_ApplicationCallback cb,
void *callbackApplication, void *data);
/* The server needs to be stopped before it can be deleted */
void UA_Server_delete(UA_Server *server) {
UA_LOCK(&server->serviceMutex);
@ -204,16 +200,20 @@ void UA_Server_delete(UA_Server *server) {
UA_AsyncManager_clear(&server->asyncManager, server);
#endif
/* Stop the EventLoop and iterate until stopped or an error occurs */
UA_EventLoop_stop(server->config.eventLoop);
UA_StatusCode res = UA_STATUSCODE_GOOD;
UA_EventLoopState state = UA_EventLoop_getState(server->config.eventLoop);
while(res == UA_STATUSCODE_GOOD && state != UA_EVENTLOOPSTATE_STOPPED) {
res = UA_EventLoop_run(server->config.eventLoop, 100);
state = UA_EventLoop_getState(server->config.eventLoop);
}
/* Clean up the Admin Session */
UA_Session_clear(&server->adminSession, server);
UA_UNLOCK(&server->serviceMutex); /* The timer has its own mutex */
/* Execute all remaining delayed events and clean up the timer */
UA_Timer_process(&server->timer, UA_DateTime_nowMonotonic() + 1,
(UA_TimerExecutionCallback)serverExecuteRepeatedCallback, server);
UA_Timer_clear(&server->timer);
/* Clean up the config */
UA_ServerConfig_clean(&server->config);
@ -272,9 +272,6 @@ UA_Server_init(UA_Server *server) {
UA_LOCK_INIT(&server->serviceMutex);
#endif
/* Initialize the handling of repeated callbacks */
UA_Timer_init(&server->timer);
/* Initialize the adminSession */
UA_Session_init(&server->adminSession);
server->adminSession.sessionId.identifierType = UA_NODEIDTYPE_GUID;
@ -335,15 +332,21 @@ UA_Server *
UA_Server_newWithConfig(UA_ServerConfig *config) {
UA_CHECK_MEM(config, return NULL);
UA_CHECK_LOG(config->eventLoop != NULL, return NULL, ERROR,
&config->logger, UA_LOGCATEGORY_SERVER, "No EventLoop configured");
UA_Server *server = (UA_Server *)UA_calloc(1, sizeof(UA_Server));
UA_CHECK_MEM(server, UA_ServerConfig_clean(config); return NULL);
server->config = *config;
/* The config might have been "moved" into the server struct. Ensure that
* the logger pointer is correct. */
for(size_t i = 0; i < server->config.securityPoliciesSize; i++)
server->config.securityPolicies[i].logger = &server->config.logger;
UA_EventLoop_setLogger(server->config.eventLoop, &server->config.logger);
/* Reset the old config */
memset(config, 0, sizeof(UA_ServerConfig));
return UA_Server_init(server);
@ -371,9 +374,9 @@ UA_Server_addTimedCallback(UA_Server *server, UA_ServerCallback callback,
void *data, UA_DateTime date, UA_UInt64 *callbackId) {
UA_LOCK(&server->serviceMutex);
UA_StatusCode retval =
UA_Timer_addTimedCallback(&server->timer,
(UA_ApplicationCallback)callback,
server, data, date, callbackId);
UA_EventLoop_addTimedCallback(server->config.eventLoop,
(UA_Callback)callback,
server, data, date, callbackId);
UA_UNLOCK(&server->serviceMutex);
return retval;
}
@ -382,10 +385,10 @@ UA_StatusCode
addRepeatedCallback(UA_Server *server, UA_ServerCallback callback,
void *data, UA_Double interval_ms,
UA_UInt64 *callbackId) {
return UA_Timer_addRepeatedCallback(&server->timer,
(UA_ApplicationCallback)callback,
server, data, interval_ms, NULL,
UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, callbackId);
return UA_EventLoop_addCyclicCallback(server->config.eventLoop, (UA_Callback) callback,
server, data, interval_ms, NULL,
UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME,
callbackId);
}
UA_StatusCode
@ -402,8 +405,9 @@ UA_Server_addRepeatedCallback(UA_Server *server, UA_ServerCallback callback,
UA_StatusCode
changeRepeatedCallbackInterval(UA_Server *server, UA_UInt64 callbackId,
UA_Double interval_ms) {
return UA_Timer_changeRepeatedCallback(&server->timer, callbackId,
interval_ms, NULL, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME);
return UA_EventLoop_modifyCyclicCallback(server->config.eventLoop, callbackId,
interval_ms, NULL,
UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME);
}
UA_StatusCode
@ -418,7 +422,7 @@ UA_Server_changeRepeatedCallbackInterval(UA_Server *server, UA_UInt64 callbackId
void
removeCallback(UA_Server *server, UA_UInt64 callbackId) {
UA_Timer_removeCallback(&server->timer, callbackId);
UA_EventLoop_removeCyclicCallback(server->config.eventLoop, callbackId);
}
void
@ -549,14 +553,16 @@ UA_Server_run_startup(UA_Server *server) {
"This should only be used for specific fuzzing builds.");
#endif
UA_StatusCode retVal = UA_EventLoop_start(server->config.eventLoop);
UA_CHECK_STATUS(retVal, return retVal);
/* ensure that the uri for ns1 is set up from the app description */
setupNs1Uri(server);
/* write ServerArray with same ApplicationURI value as NamespaceArray */
UA_StatusCode retVal =
writeNs0VariableArray(server, UA_NS0ID_SERVER_SERVERARRAY,
&server->config.applicationDescription.applicationUri,
1, &UA_TYPES[UA_TYPES_STRING]);
retVal = writeNs0VariableArray(server, UA_NS0ID_SERVER_SERVERARRAY,
&server->config.applicationDescription.applicationUri,
1, &UA_TYPES[UA_TYPES_STRING]);
UA_CHECK_STATUS(retVal, return retVal);
if(server->state > UA_SERVERLIFECYCLE_FRESH)
@ -630,22 +636,12 @@ UA_Server_run_startup(UA_Server *server) {
return result;
}
static void
serverExecuteRepeatedCallback(UA_Server *server, UA_ApplicationCallback cb,
void *callbackApplication, void *data) {
/* Service mutex is not set inside the timer that triggers the callback */
/* The following check cannot be used since another thread can take the
* serviceMutex during a server_iterate_call. */
//UA_LOCK_ASSERT(&server->serviceMutex, 0);
cb(callbackApplication, data);
}
UA_UInt16
UA_Server_run_iterate(UA_Server *server, UA_Boolean waitInternal) {
/* Process repeated work */
UA_DateTime now = UA_DateTime_nowMonotonic();
UA_DateTime nextRepeated = UA_Timer_process(&server->timer, now,
(UA_TimerExecutionCallback)serverExecuteRepeatedCallback, server);
UA_EventLoop_run(server->config.eventLoop, 0);
UA_DateTime nextRepeated = UA_EventLoop_nextCyclicTime(server->config.eventLoop);
UA_DateTime latest = now + (UA_MAXTIMEOUT * UA_DATETIME_MSEC);
if(nextRepeated > latest)
nextRepeated = latest;

View File

@ -29,6 +29,19 @@ UA_ServerConfig_clean(UA_ServerConfig *config) {
/* Custom DataTypes */
/* nothing to do */
/* Stop and delete the EventLoop */
if(config->eventLoop && !config->externalEventLoop) {
if(UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_FRESH &&
UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_STOPPED) {
UA_EventLoop_stop(config->eventLoop);
while(UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_STOPPED) {
UA_EventLoop_run(config->eventLoop, 100);
}
}
UA_EventLoop_delete(config->eventLoop);
config->eventLoop = NULL;
}
/* Networking */
for(size_t i = 0; i < config->networkLayersSize; ++i)
config->networkLayers[i].clear(&config->networkLayers[i]);

View File

@ -23,7 +23,7 @@
#include "ua_connection_internal.h"
#include "ua_session.h"
#include "ua_server_async.h"
#include "ua_timer.h"
#include "common/ua_timer.h" /* arch-folder, TODO: Remove after the EventLoop is integrated */
#include "ua_util_internal.h"
#include "ziptree.h"
@ -66,13 +66,13 @@ typedef enum {
} UA_DiagnosticEvent;
typedef struct channel_entry {
UA_TimerEntry cleanupCallback;
UA_DelayedCallback cleanupCallback;
TAILQ_ENTRY(channel_entry) pointers;
UA_SecureChannel channel;
} channel_entry;
typedef struct session_list_entry {
UA_TimerEntry cleanupCallback;
UA_DelayedCallback cleanupCallback;
LIST_ENTRY(session_list_entry) pointers;
UA_Session session;
} session_list_entry;
@ -111,9 +111,6 @@ struct UA_Server {
size_t namespacesSize;
UA_String *namespaces;
/* Callbacks with a repetition interval */
UA_Timer timer;
/* For bootstrapping, omit some consistency checks, creating a reference to
* the parent and member instantiation */
UA_Boolean bootstrapNS0;
@ -588,12 +585,29 @@ UA_StatusCode writeNs0VariableArray(UA_Server *server, UA_UInt32 id, void *v,
#define UA_NODESTORE_DELETE(server, node) \
server->config.nodestore.deleteNode(server->config.nodestore.context, node)
#define UA_NODESTORE_GET(server, nodeid) \
server->config.nodestore.getNode(server->config.nodestore.context, nodeid)
/* Get the node with all attributes and references */
static UA_INLINE const UA_Node *
UA_NODESTORE_GET(UA_Server *server, const UA_NodeId *nodeId) {
return server->config.nodestore.
getNode(server->config.nodestore.context, nodeId, UA_NODEATTRIBUTESMASK_ALL,
UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
}
/* Returns NULL if the target is an external Reference (per the ExpandedNodeId) */
const UA_Node *
UA_NODESTORE_GETFROMREF(UA_Server *server, UA_NodePointer target);
/* Get the node with all attributes and references */
static UA_INLINE const UA_Node *
UA_NODESTORE_GETFROMREF(UA_Server *server, UA_NodePointer target) {
return server->config.nodestore.
getNodeFromPtr(server->config.nodestore.context, target, UA_NODEATTRIBUTESMASK_ALL,
UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
}
#define UA_NODESTORE_GET_SELECTIVE(server, nodeid, attrMask, refs, refDirs) \
server->config.nodestore.getNode(server->config.nodestore.context, \
nodeid, attrMask, refs, refDirs)
#define UA_NODESTORE_GETFROMREF_SELECTIVE(server, target, attrMask, refs, refDirs) \
server->config.nodestore.getNodeFromPtr(server->config.nodestore.context, \
target, attrMask, refs, refDirs)
#define UA_NODESTORE_RELEASE(server, node) \
server->config.nodestore.releaseNode(server->config.nodestore.context, node)

View File

@ -27,6 +27,40 @@
#include <open62541/plugin/historydatabase.h>
#endif
static UA_UInt32
attributeId2AttributeMask(UA_AttributeId id) {
switch(id) {
case UA_ATTRIBUTEID_NODEID: return UA_NODEATTRIBUTESMASK_NODEID;
case UA_ATTRIBUTEID_NODECLASS: return UA_NODEATTRIBUTESMASK_NODECLASS;
case UA_ATTRIBUTEID_BROWSENAME: return UA_NODEATTRIBUTESMASK_BROWSENAME;
case UA_ATTRIBUTEID_DISPLAYNAME: return UA_NODEATTRIBUTESMASK_DISPLAYNAME;
case UA_ATTRIBUTEID_DESCRIPTION: return UA_NODEATTRIBUTESMASK_DESCRIPTION;
case UA_ATTRIBUTEID_WRITEMASK: return UA_NODEATTRIBUTESMASK_WRITEMASK;
case UA_ATTRIBUTEID_USERWRITEMASK: return UA_NODEATTRIBUTESMASK_USERWRITEMASK;
case UA_ATTRIBUTEID_ISABSTRACT: return UA_NODEATTRIBUTESMASK_ISABSTRACT;
case UA_ATTRIBUTEID_SYMMETRIC: return UA_NODEATTRIBUTESMASK_SYMMETRIC;
case UA_ATTRIBUTEID_INVERSENAME: return UA_NODEATTRIBUTESMASK_INVERSENAME;
case UA_ATTRIBUTEID_CONTAINSNOLOOPS: return UA_NODEATTRIBUTESMASK_CONTAINSNOLOOPS;
case UA_ATTRIBUTEID_EVENTNOTIFIER: return UA_NODEATTRIBUTESMASK_EVENTNOTIFIER;
case UA_ATTRIBUTEID_VALUE: return UA_NODEATTRIBUTESMASK_VALUE;
case UA_ATTRIBUTEID_DATATYPE: return UA_NODEATTRIBUTESMASK_DATATYPE;
case UA_ATTRIBUTEID_VALUERANK: return UA_NODEATTRIBUTESMASK_VALUERANK;
case UA_ATTRIBUTEID_ARRAYDIMENSIONS: return UA_NODEATTRIBUTESMASK_ARRAYDIMENSIONS;
case UA_ATTRIBUTEID_ACCESSLEVEL: return UA_NODEATTRIBUTESMASK_ACCESSLEVEL;
case UA_ATTRIBUTEID_USERACCESSLEVEL: return UA_NODEATTRIBUTESMASK_USERACCESSLEVEL;
case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL: return UA_NODEATTRIBUTESMASK_MINIMUMSAMPLINGINTERVAL;
case UA_ATTRIBUTEID_HISTORIZING: return UA_NODEATTRIBUTESMASK_HISTORIZING;
case UA_ATTRIBUTEID_EXECUTABLE: return UA_NODEATTRIBUTESMASK_EXECUTABLE;
case UA_ATTRIBUTEID_USEREXECUTABLE: return UA_NODEATTRIBUTESMASK_USEREXECUTABLE;
case UA_ATTRIBUTEID_DATATYPEDEFINITION: return UA_NODEATTRIBUTESMASK_DATATYPEDEFINITION;
case UA_ATTRIBUTEID_ROLEPERMISSIONS: return UA_NODEATTRIBUTESMASK_ROLEPERMISSIONS;
case UA_ATTRIBUTEID_USERROLEPERMISSIONS: return UA_NODEATTRIBUTESMASK_ROLEPERMISSIONS;
case UA_ATTRIBUTEID_ACCESSRESTRICTIONS: return UA_NODEATTRIBUTESMASK_ACCESSRESTRICTIONS;
case UA_ATTRIBUTEID_ACCESSLEVELEX: return UA_NODEATTRIBUTESMASK_ACCESSLEVEL;
default: return UA_NODEATTRIBUTESMASK_NONE;
}
}
/******************/
/* Access Control */
/******************/
@ -131,7 +165,11 @@ readValueAttributeFromNode(UA_Server *server, UA_Session *session,
&vn->head.nodeId, vn->head.context, rangeptr,
&vn->value.data.value);
UA_LOCK(&server->serviceMutex);
vn = (const UA_VariableNode*)UA_NODESTORE_GET(server, &vn->head.nodeId);
vn = (const UA_VariableNode*)
UA_NODESTORE_GET_SELECTIVE(server, &vn->head.nodeId,
UA_NODEATTRIBUTESMASK_VALUE,
UA_REFERENCETYPESET_NONE,
UA_BROWSEDIRECTION_INVALID);
if(!vn)
return UA_STATUSCODE_BADNODEIDUNKNOWN;
}
@ -562,8 +600,12 @@ ReadWithNode(const UA_Node *node, UA_Server *server, UA_Session *session,
static void
Operation_Read(UA_Server *server, UA_Session *session, UA_ReadRequest *request,
UA_ReadValueId *rvi, UA_DataValue *result) {
/* Get the node */
const UA_Node *node = UA_NODESTORE_GET(server, &rvi->nodeId);
/* Get the node (with only the selected attribute if the NodeStore supports that) */
const UA_Node *node =
UA_NODESTORE_GET_SELECTIVE(server, &rvi->nodeId,
attributeId2AttributeMask((UA_AttributeId)rvi->attributeId),
UA_REFERENCETYPESET_NONE,
UA_BROWSEDIRECTION_INVALID);
/* Perform the read operation */
if(node) {
@ -620,8 +662,12 @@ UA_Server_readWithSession(UA_Server *server, UA_Session *session,
UA_DataValue dv;
UA_DataValue_init(&dv);
/* Get the node */
const UA_Node *node = UA_NODESTORE_GET(server, &item->nodeId);
/* Get the node (with only the selected attribute if the NodeStore supports it) */
const UA_Node *node =
UA_NODESTORE_GET_SELECTIVE(server, &item->nodeId,
attributeId2AttributeMask((UA_AttributeId)item->attributeId),
UA_REFERENCETYPESET_NONE,
UA_BROWSEDIRECTION_INVALID);
if(!node) {
dv.hasStatus = true;
dv.status = UA_STATUSCODE_BADNODEIDUNKNOWN;

View File

@ -23,14 +23,19 @@ getArgumentsVariableNode(UA_Server *server, const UA_NodeHead *head,
UA_String withBrowseName) {
for(size_t i = 0; i < head->referencesSize; ++i) {
const UA_NodeReferenceKind *rk = &head->references[i];
if(rk->isInverse != false)
if(rk->isInverse)
continue;
if(rk->referenceTypeIndex != UA_REFERENCETYPEINDEX_HASPROPERTY)
continue;
const UA_ReferenceTarget *t = NULL;
while((t = UA_NodeReferenceKind_iterate(rk, t))) {
/* Get only the NodeClass and Value attributes, no references */
const UA_Node *refTarget =
UA_NODESTORE_GETFROMREF(server, t->targetId);
UA_NODESTORE_GETFROMREF_SELECTIVE(server, t->targetId,
UA_NODEATTRIBUTESMASK_NODECLASS |
UA_NODEATTRIBUTESMASK_VALUE,
UA_REFERENCETYPESET_NONE,
UA_BROWSEDIRECTION_INVALID);
if(!refTarget)
continue;
if(refTarget->head.nodeClass == UA_NODECLASS_VARIABLE &&
@ -327,15 +332,29 @@ Operation_CallMethodAsync(UA_Server *server, UA_Session *session, UA_UInt32 requ
UA_UInt32 requestHandle, size_t opIndex,
UA_CallMethodRequest *opRequest, UA_CallMethodResult *opResult,
UA_AsyncResponse **ar) {
/* Get the method node */
const UA_Node *method = UA_NODESTORE_GET(server, &opRequest->methodId);
/* Get the method node. We only need the nodeClass and executable attribute.
* Take all forward hasProperty references to get the input/output argument
* definition variables. */
const UA_Node *method =
UA_NODESTORE_GET_SELECTIVE(server, &opRequest->methodId,
UA_NODEATTRIBUTESMASK_NODECLASS |
UA_NODEATTRIBUTESMASK_EXECUTABLE,
UA_REFTYPESET(UA_REFERENCETYPEINDEX_HASPROPERTY),
UA_BROWSEDIRECTION_FORWARD);
if(!method) {
opResult->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN;
return;
}
/* Get the object node */
const UA_Node *object = UA_NODESTORE_GET(server, &opRequest->objectId);
/* Get the object node. We only need the NodeClass attribute. But take all
* references for now.
*
* TODO: Which references do we need actually? */
const UA_Node *object =
UA_NODESTORE_GET_SELECTIVE(server, &opRequest->objectId,
UA_NODEATTRIBUTESMASK_NODECLASS,
UA_REFERENCETYPESET_ALL,
UA_BROWSEDIRECTION_BOTH);
if(!object) {
opResult->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN;
UA_NODESTORE_RELEASE(server, method);
@ -410,15 +429,29 @@ Service_CallAsync(UA_Server *server, UA_Session *session, UA_UInt32 requestId,
static void
Operation_CallMethod(UA_Server *server, UA_Session *session, void *context,
const UA_CallMethodRequest *request, UA_CallMethodResult *result) {
/* Get the method node */
const UA_Node *method = UA_NODESTORE_GET(server, &request->methodId);
/* Get the method node. We only need the nodeClass and executable attribute.
* Take all forward hasProperty references to get the input/output argument
* definition variables. */
const UA_Node *method =
UA_NODESTORE_GET_SELECTIVE(server, &request->methodId,
UA_NODEATTRIBUTESMASK_NODECLASS |
UA_NODEATTRIBUTESMASK_EXECUTABLE,
UA_REFTYPESET(UA_REFERENCETYPEINDEX_HASPROPERTY),
UA_BROWSEDIRECTION_FORWARD);
if(!method) {
result->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN;
return;
}
/* Get the object node */
const UA_Node *object = UA_NODESTORE_GET(server, &request->objectId);
/* Get the object node. We only need the NodeClass attribute. But take all
* references for now.
*
* TODO: Which references do we need actually? */
const UA_Node *object =
UA_NODESTORE_GET_SELECTIVE(server, &request->objectId,
UA_NODEATTRIBUTESMASK_NODECLASS,
UA_REFERENCETYPESET_ALL,
UA_BROWSEDIRECTION_BOTH);
if(!object) {
result->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN;
UA_NODESTORE_RELEASE(server, method);

View File

@ -68,12 +68,10 @@ removeSecureChannel(UA_Server *server, channel_entry *entry,
/* Add a delayed callback to remove the channel when the currently
* scheduled jobs have completed */
entry->cleanupCallback.callback = (UA_ApplicationCallback)removeSecureChannelCallback;
entry->cleanupCallback.callback = (UA_Callback)removeSecureChannelCallback;
entry->cleanupCallback.application = NULL;
entry->cleanupCallback.data = entry;
entry->cleanupCallback.nextTime = UA_DateTime_nowMonotonic() + 1;
entry->cleanupCallback.interval = 0; /* Remove the structure */
UA_Timer_addTimerEntry(&server->timer, &entry->cleanupCallback, NULL);
UA_EventLoop_addDelayedCallback(server->config.eventLoop, &entry->cleanupCallback);
}
void

View File

@ -88,12 +88,10 @@ UA_Server_removeSession(UA_Server *server, session_list_entry *sentry,
/* Add a delayed callback to remove the session when the currently
* scheduled jobs have completed */
sentry->cleanupCallback.callback = (UA_ApplicationCallback)removeSessionCallback;
sentry->cleanupCallback.callback = (UA_Callback)removeSessionCallback;
sentry->cleanupCallback.application = server;
sentry->cleanupCallback.data = sentry;
sentry->cleanupCallback.nextTime = UA_DateTime_nowMonotonic() + 1;
sentry->cleanupCallback.interval = 0; /* Remove the structure */
UA_Timer_addTimerEntry(&server->timer, &sentry->cleanupCallback, NULL);
UA_EventLoop_addDelayedCallback(server->config.eventLoop, &sentry->cleanupCallback);
}
UA_StatusCode

View File

@ -22,16 +22,37 @@
#define UA_MAX_TREE_RECURSE 50 /* How deep up/down the tree do we recurse at most? */
static UA_UInt32
resultMask2AttributesMask(UA_UInt32 resultMask) {
UA_UInt32 result = 0;
if(resultMask & UA_BROWSERESULTMASK_NODECLASS)
result |= UA_NODEATTRIBUTESMASK_NODECLASS;
if(resultMask & UA_BROWSERESULTMASK_BROWSENAME)
result |= UA_NODEATTRIBUTESMASK_BROWSENAME;
if(resultMask & UA_BROWSERESULTMASK_DISPLAYNAME)
result |= UA_NODEATTRIBUTESMASK_DISPLAYNAME;
return result;
}
UA_StatusCode
referenceTypeIndices(UA_Server *server, const UA_NodeId *refType,
UA_ReferenceTypeSet *indices, UA_Boolean includeSubtypes) {
if(UA_NodeId_isNull(refType)) {
UA_ReferenceTypeSet_any(indices);
*indices = UA_REFERENCETYPESET_ALL;
return UA_STATUSCODE_GOOD;
}
UA_ReferenceTypeSet_init(indices);
const UA_Node *refNode = UA_NODESTORE_GET(server, refType);
/* Get the node with only the NodeClass attribute. If it is a
* ReferenceTypeNode, then the indices are always included, as this is an
* open62541 specific field (not selectable via the attribute id). */
const UA_Node *refNode =
UA_NODESTORE_GET_SELECTIVE(server, refType,
UA_NODEATTRIBUTESMASK_NODECLASS,
UA_REFERENCETYPESET_NONE,
UA_BROWSEDIRECTION_INVALID);
if(!refNode)
return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
@ -82,7 +103,13 @@ isNodeInTreeNoCircular(UA_Server *server,
if(visitedRefs->depth >= UA_MAX_TREE_RECURSE)
return false;
const UA_Node *node = UA_NODESTORE_GETFROMREF(server, leafNode);
/* Get the node without attributes (if the NodeStore supports it) and only
* the relevant references in inverse direction */
const UA_Node *node =
UA_NODESTORE_GETFROMREF_SELECTIVE(server, leafNode,
UA_NODEATTRIBUTESMASK_NONE,
*relevantRefs,
UA_BROWSEDIRECTION_INVERSE);
if(!node)
return false;
@ -299,7 +326,12 @@ browseRecursiveInner(UA_Server *server, RefTree *rt, UA_UInt16 depth, UA_Boolean
if(depth >= UA_MAX_TREE_RECURSE)
return UA_STATUSCODE_GOOD;
const UA_Node *node = UA_NODESTORE_GETFROMREF(server, nodeP);
/* We only look at the NodeClass attribute and a subset of the references.
* Get a node with only these elements if the NodeStore supports that. */
const UA_Node *node =
UA_NODESTORE_GETFROMREF_SELECTIVE(server, nodeP,
UA_NODEATTRIBUTESMASK_NODECLASS,
*refTypes, browseDirection);
if(!node)
return UA_STATUSCODE_BADNODEIDUNKNOWN;
@ -572,8 +604,19 @@ browseReferences(UA_Server *server, const UA_NodeHead *head,
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Get the node with additional reference types if we need to lookup the
* TypeDefinition */
UA_ReferenceTypeSet resultRefs = cp->relevantReferences;
if(bd->resultMask & UA_BROWSERESULTMASK_TYPEDEFINITION) {
resultRefs = UA_ReferenceTypeSet_union(resultRefs,
UA_REFTYPESET(UA_REFERENCETYPEINDEX_HASTYPEDEFINITION));
resultRefs = UA_ReferenceTypeSet_union(resultRefs,
UA_REFTYPESET(UA_REFERENCETYPEINDEX_HASSUBTYPE));
}
/* Loop over the ReferenceTypes */
UA_StatusCode retval = UA_STATUSCODE_GOOD;
for(; i < head->referencesSize; ++i) {
UA_NodeReferenceKind *rk = &head->references[i];
@ -595,9 +638,13 @@ browseReferences(UA_Server *server, const UA_NodeHead *head,
if(!ref)
ref = UA_NodeReferenceKind_iterate(rk, ref);
for(;ref; ref = UA_NodeReferenceKind_iterate(rk, ref)) {
/* Get the node if it is not a remote reference */
/* Get the node (NULL if is a remote reference). Include only the
* ReferenceTypes we are interested in, including those for figuring
* out the TypeDefinition (if that was requested). */
const UA_Node *target =
UA_NODESTORE_GETFROMREF(server, ref->targetId);
UA_NODESTORE_GETFROMREF_SELECTIVE(server, ref->targetId,
resultMask2AttributesMask(bd->resultMask),
resultRefs, bd->browseDirection);
/* Test if the node class matches */
if(target && !matchClassMask(target, bd->nodeClassMask)) {
@ -653,7 +700,11 @@ browseWithContinuation(UA_Server *server, UA_Session *session,
/* Is the reference type valid? */
if(!UA_NodeId_isNull(&descr->referenceTypeId)) {
const UA_Node *reftype = UA_NODESTORE_GET(server, &descr->referenceTypeId);
const UA_Node *reftype =
UA_NODESTORE_GET_SELECTIVE(server, &descr->referenceTypeId,
UA_NODEATTRIBUTESMASK_NODECLASS,
UA_REFERENCETYPESET_NONE,
UA_BROWSEDIRECTION_INVALID);
if(!reftype) {
result->statusCode = UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
return true;
@ -668,7 +719,11 @@ browseWithContinuation(UA_Server *server, UA_Session *session,
}
}
const UA_Node *node = UA_NODESTORE_GET(server, &descr->nodeId);
/* Get node with only the selected references and attributes */
const UA_Node *node =
UA_NODESTORE_GET_SELECTIVE(server, &descr->nodeId,
resultMask2AttributesMask(descr->resultMask),
cp->relevantReferences, descr->browseDirection);
if(!node) {
result->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN;
return true;
@ -990,8 +1045,16 @@ walkBrowsePathElement(UA_Server *server, UA_Session *session,
continue;
}
/* Local Node. Add to the tree of results at the next depth. */
const UA_Node *node = UA_NODESTORE_GET(server, &current->targets[i].nodeId);
/* Local Node. Add to the tree of results at the next depth. Get only
* the NodeClass + BrowseName attribute and the selected ReferenceTypes
* if the nodestore supports that. */
const UA_Node *node =
UA_NODESTORE_GET_SELECTIVE(server, &current->targets[i].nodeId,
UA_NODEATTRIBUTESMASK_NODECLASS |
UA_NODEATTRIBUTESMASK_BROWSENAME,
refTypes,
elem->isInverse ? UA_BROWSEDIRECTION_INVERSE :
UA_BROWSEDIRECTION_FORWARD);
if(!node)
continue;
@ -1076,7 +1139,11 @@ Operation_TranslateBrowsePathToNodeIds(UA_Server *server, UA_Session *session,
}
/* Check if the starting node exists */
const UA_Node *startingNode = UA_NODESTORE_GET(server, &path->startingNode);
const UA_Node *startingNode =
UA_NODESTORE_GET_SELECTIVE(server, &path->startingNode,
UA_NODEATTRIBUTESMASK_NONE,
UA_REFERENCETYPESET_NONE,
UA_BROWSEDIRECTION_INVALID);
if(!startingNode) {
result->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN;
return;
@ -1141,8 +1208,13 @@ Operation_TranslateBrowsePathToNodeIds(UA_Server *server, UA_Session *session,
result->targets = tmpResults;
for(size_t k = 0; k < next->size; k++) {
/* Check the BrowseName. It has been filtered only via its hash so far. */
const UA_Node *node = UA_NODESTORE_GET(server, &next->targets[k].nodeId);
/* Check the BrowseName. It has been filtered only via its hash so far.
* Get only the BrowseName attribute if the nodestore supports that. */
const UA_Node *node =
UA_NODESTORE_GET_SELECTIVE(server, &next->targets[k].nodeId,
UA_NODEATTRIBUTESMASK_BROWSENAME,
UA_REFERENCETYPESET_NONE,
UA_BROWSEDIRECTION_INVALID);
if(!node)
continue;
UA_Boolean match = UA_QualifiedName_equal(browseNameFilter, &node->head.browseName);

View File

@ -8,6 +8,7 @@
*/
#include "ua_session.h"
#include "open62541/types.h"
#include "ua_server_internal.h"
#ifdef UA_ENABLE_SUBSCRIPTIONS
#include "ua_subscription.h"
@ -224,30 +225,31 @@ UA_Server_closeSession(UA_Server *server, const UA_NodeId *sessionId) {
UA_StatusCode
UA_Server_setSessionParameter(UA_Server *server, const UA_NodeId *sessionId,
const char *name, const UA_Variant *parameter) {
const UA_QualifiedName key, const UA_Variant *value) {
UA_LOCK(&server->serviceMutex);
UA_Session *session = UA_Server_getSessionById(server, sessionId);
UA_StatusCode res = UA_STATUSCODE_BADSESSIONIDINVALID;
if(session)
res = UA_KeyValueMap_set(&session->params, &session->paramsSize,
name, parameter);
key, value);
UA_UNLOCK(&server->serviceMutex);
return res;
}
void
UA_Server_deleteSessionParameter(UA_Server *server, const UA_NodeId *sessionId,
const char *name) {
const UA_QualifiedName key) {
UA_LOCK(&server->serviceMutex);
UA_Session *session = UA_Server_getSessionById(server, sessionId);
if(session)
UA_KeyValueMap_delete(&session->params, &session->paramsSize, name);
UA_KeyValueMap_delete(&session->params, &session->paramsSize, key);
UA_UNLOCK(&server->serviceMutex);
}
UA_StatusCode
UA_Server_getSessionParameter(UA_Server *server, const UA_NodeId *sessionId,
const char *name, UA_Variant *outParameter) {
const UA_QualifiedName key,
UA_Variant *outParameter) {
UA_LOCK(&server->serviceMutex);
if(!outParameter) {
UA_UNLOCK(&server->serviceMutex);
@ -261,7 +263,7 @@ UA_Server_getSessionParameter(UA_Server *server, const UA_NodeId *sessionId,
}
const UA_Variant *param =
UA_KeyValueMap_get(session->params, session->paramsSize, name);
UA_KeyValueMap_get(session->params, session->paramsSize, key);
if(!param) {
UA_UNLOCK(&server->serviceMutex);
return UA_STATUSCODE_BADNOTFOUND;
@ -273,9 +275,10 @@ UA_Server_getSessionParameter(UA_Server *server, const UA_NodeId *sessionId,
}
UA_StatusCode
UA_Server_getSessionScalarParameter(UA_Server *server, const UA_NodeId *sessionId,
const char *name, const UA_DataType *type,
UA_Variant *outParameter) {
UA_Server_getSessionParameter_scalar(UA_Server *server, const UA_NodeId *sessionId,
const UA_QualifiedName key,
const UA_DataType *type,
void *outParameter) {
UA_LOCK(&server->serviceMutex);
if(!outParameter) {
UA_UNLOCK(&server->serviceMutex);
@ -289,41 +292,13 @@ UA_Server_getSessionScalarParameter(UA_Server *server, const UA_NodeId *sessionI
}
const UA_Variant *param =
UA_KeyValueMap_get(session->params, session->paramsSize, name);
UA_KeyValueMap_get(session->params, session->paramsSize, key);
if(!param || !UA_Variant_hasScalarType(param, type)) {
UA_UNLOCK(&server->serviceMutex);
return UA_STATUSCODE_BADNOTFOUND;
}
UA_StatusCode res = UA_Variant_copy(param, outParameter);
UA_UNLOCK(&server->serviceMutex);
return res;
}
UA_StatusCode
UA_Server_getSessionArrayParameter(UA_Server *server, const UA_NodeId *sessionId,
const char *name, const UA_DataType *type,
UA_Variant *outParameter) {
UA_LOCK(&server->serviceMutex);
if(!outParameter) {
UA_UNLOCK(&server->serviceMutex);
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_Session *session = UA_Server_getSessionById(server, sessionId);
if(!session) {
UA_UNLOCK(&server->serviceMutex);
return UA_STATUSCODE_BADSESSIONIDINVALID;
}
const UA_Variant *param =
UA_KeyValueMap_get(session->params, session->paramsSize, name);
if(!param || !UA_Variant_hasArrayType(param, type)) {
UA_UNLOCK(&server->serviceMutex);
return UA_STATUSCODE_BADNOTFOUND;
}
UA_StatusCode res = UA_Variant_copy(param, outParameter);
UA_StatusCode res = UA_copy(param->data, outParameter, type);
UA_UNLOCK(&server->serviceMutex);
return res;
}

View File

@ -89,9 +89,7 @@ UA_Subscription_delete(UA_Server *server, UA_Subscription *sub) {
sub->delayedFreePointers.callback = NULL;
sub->delayedFreePointers.application = server;
sub->delayedFreePointers.data = NULL;
sub->delayedFreePointers.nextTime = UA_DateTime_nowMonotonic() + 1;
sub->delayedFreePointers.interval = 0; /* Remove the structure */
UA_Timer_addTimerEntry(&server->timer, &sub->delayedFreePointers, NULL);
UA_EventLoop_addDelayedCallback(server->config.eventLoop, &sub->delayedFreePointers);
}
UA_MonitoredItem *

View File

@ -12,6 +12,7 @@
* Copyright 2019 (c) HMS Industrial Networks AB (Author: Jonas Green)
* Copyright 2020 (c) Christian von Arnim, ISW University of Stuttgart (for VDW and umati)
* Copyright 2021 (c) Fraunhofer IOSB (Author: Andreas Ebner)
* Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes)
*/
#ifndef UA_SUBSCRIPTION_H_
@ -22,7 +23,7 @@
#include <open62541/plugin/nodestore.h>
#include "ua_session.h"
#include "ua_timer.h"
#include "common/ua_timer.h"
#include "ua_util_internal.h"
_UA_BEGIN_DECLS
@ -101,7 +102,7 @@ typedef TAILQ_HEAD(NotificationMessageQueue, UA_NotificationMessageEntry)
/*****************/
struct UA_MonitoredItem {
UA_TimerEntry delayedFreePointers;
UA_DelayedCallback delayedFreePointers;
LIST_ENTRY(UA_MonitoredItem) listEntry; /* Linked list in the Subscription */
UA_MonitoredItem *next; /* Linked list of MonitoredItems directly attached
* to a Node. Initialized to ~0 to indicate that the
@ -239,7 +240,7 @@ typedef enum {
* may keep Subscriptions intact beyond the Session lifetime. They can then be
* re-bound to a new Session with the TransferSubscription Service. */
struct UA_Subscription {
UA_TimerEntry delayedFreePointers;
UA_DelayedCallback delayedFreePointers;
LIST_ENTRY(UA_Subscription) serverListEntry;
/* Ordered according to the priority byte and round-robin scheduling for
* late subscriptions. See ua_session.h. Only set if session != NULL. */
@ -332,7 +333,7 @@ UA_Server_evaluateWhereClauseContentFilter(UA_Server *server, UA_Session *sessio
const UA_ContentFilter *contentFilter,
UA_ContentFilterResult *contentFilterResult);
#endif
/* Setting an integer value within bounds */
#define UA_BOUNDEDVALUE_SETWBOUNDS(BOUNDS, SRC, DST) { \
if(SRC > BOUNDS.max) DST = BOUNDS.max; \

View File

@ -162,6 +162,29 @@ isValidEvent(UA_Server *server, const UA_NodeId *validEventParent,
return isSubtypeOfBaseEvent;
}
/* Resolves a variant of type string or boolean into a corresponding status code */
static UA_StatusCode
resolveBoolean(UA_Variant operand) {
UA_String value;
value = UA_STRING("True");
if(((operand.type == &UA_TYPES[UA_TYPES_STRING]) &&
(UA_String_equal((UA_String *)operand.data, &value))) ||
((operand.type == &UA_TYPES[UA_TYPES_BOOLEAN]) &&
(*(UA_Boolean *)operand.data == UA_TRUE))) {
return UA_STATUSCODE_GOOD;
}
value = UA_STRING("False");
if(((operand.type == &UA_TYPES[UA_TYPES_STRING]) &&
(UA_String_equal((UA_String *)operand.data, &value))) ||
((operand.type == &UA_TYPES[UA_TYPES_BOOLEAN]) &&
(*(UA_Boolean *)operand.data == UA_FALSE))) {
return UA_STATUSCODE_BADNOMATCH;
}
/* If the operand can't be resolved, an error is returned */
return UA_STATUSCODE_BADFILTEROPERANDINVALID;
}
/* Part 4: 7.4.4.5 SimpleAttributeOperand
* The clause can point to any attribute of nodes. Either a child of the event
* node and also the event type. */
@ -233,6 +256,7 @@ resolveOperand(UA_Server *server, UA_Session *session, const UA_NodeId *origin,
UA_StatusCode res;
UA_Variant variant;
UA_Variant_init(&variant);
/*SimpleAttributeOperands*/
if(contentFilter->elements[index].filterOperands[nr].content.decoded.type ==
&UA_TYPES[UA_TYPES_SIMPLEATTRIBUTEOPERAND]) {
@ -246,23 +270,17 @@ resolveOperand(UA_Server *server, UA_Session *session, const UA_NodeId *origin,
} else if(contentFilter->elements[index].filterOperands[nr].content.decoded.type ==
&UA_TYPES[UA_TYPES_LITERALOPERAND]) {
variant = ((UA_LiteralOperand *)contentFilter->elements[index]
.filterOperands[nr]
.content.decoded.data)
->value;
.filterOperands[nr].content.decoded.data)->value;
res = UA_STATUSCODE_GOOD;
} else if(contentFilter->elements[index].filterOperands[nr].content.decoded.type ==
&UA_TYPES[UA_TYPES_ELEMENTOPERAND]) {
res = evaluateWhereClauseContentFilter(
server, session, origin, contentFilter, contentFilterResult, valueResult,
(UA_UInt16)((UA_ElementOperand *)contentFilter->elements[index]
.filterOperands[nr]
.content.decoded.data)
->index);
.filterOperands[nr].content.decoded.data)->index);
variant =
valueResult[(UA_UInt16)((UA_ElementOperand *)contentFilter->elements[index]
.filterOperands[nr]
.content.decoded.data)
->index];
.filterOperands[nr].content.decoded.data)->index];
/*ElementOperands*/
} else {
res = UA_STATUSCODE_BADFILTEROPERANDINVALID;
@ -274,6 +292,161 @@ resolveOperand(UA_Server *server, UA_Session *session, const UA_NodeId *origin,
return variant;
}
static UA_StatusCode
ofTypeOperator(UA_Server *server, UA_Session *session,
const UA_NodeId *eventNode,
const UA_ContentFilter *contentFilter,
UA_ContentFilterResult *contentFilterResult,
UA_Variant* valueResult, UA_UInt16 index,
UA_UInt16 nr, UA_ContentFilterElement *pElement){
UA_Boolean result = false;
valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN];
if(pElement->filterOperandsSize != 1)
return UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH;
if(pElement->filterOperands[0].content.decoded.type !=
&UA_TYPES[UA_TYPES_LITERALOPERAND])
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
UA_LiteralOperand *literalOperand =
(UA_LiteralOperand *) pElement->filterOperands[0].content.decoded.data;
if(!UA_Variant_isScalar(&literalOperand->value))
return UA_STATUSCODE_BADEVENTFILTERINVALID;
if(literalOperand->value.type != &UA_TYPES[UA_TYPES_NODEID] || literalOperand->value.data == NULL)
return UA_STATUSCODE_BADEVENTFILTERINVALID;
UA_NodeId *literalOperandNodeId = (UA_NodeId *) literalOperand->value.data;
UA_Variant typeNodeIdVariant;
UA_Variant_init(&typeNodeIdVariant);
UA_StatusCode readStatusCode =
readObjectProperty(server, *eventNode, UA_QUALIFIEDNAME(0, "EventType"),
&typeNodeIdVariant);
if(readStatusCode != UA_STATUSCODE_GOOD)
return readStatusCode;
if(!UA_Variant_isScalar(&typeNodeIdVariant) ||
typeNodeIdVariant.type != &UA_TYPES[UA_TYPES_NODEID] ||
typeNodeIdVariant.data == NULL) {
UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER,
"EventType has an invalid type.");
UA_Variant_clear(&typeNodeIdVariant);
return UA_STATUSCODE_BADINTERNALERROR;
}
//check if the eventtype-nodeid is equal to the given oftype argument
result = UA_NodeId_equal((UA_NodeId*) typeNodeIdVariant.data, literalOperandNodeId);
//check if the eventtype-nodeid is a subtype of the given oftype argument
if(!result)
result = isNodeInTree_singleRef(server,
(UA_NodeId*) typeNodeIdVariant.data,
literalOperandNodeId,
UA_REFERENCETYPEINDEX_HASSUBTYPE);
UA_Variant_clear(&typeNodeIdVariant);
if(!result)
return UA_STATUSCODE_BADNOMATCH;
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
andOperator(UA_Server *server, UA_Session *session,
const UA_NodeId *eventNode,
const UA_ContentFilter *contentFilter,
UA_ContentFilterResult *contentFilterResult,
UA_Variant* valueResult, UA_UInt16 index,
UA_UInt16 nr, UA_ContentFilterElement *pElement) {
UA_StatusCode firstBoolean_and = resolveBoolean(
resolveOperand(server, session, eventNode, contentFilter,
contentFilterResult, valueResult, index, 0));
if(firstBoolean_and == UA_STATUSCODE_BADNOMATCH) {
valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN];
return UA_STATUSCODE_BADNOMATCH;
}
/* Evaluation of second operand */
UA_StatusCode secondBoolean = resolveBoolean(
resolveOperand(server, session, eventNode, contentFilter,
contentFilterResult, valueResult, index, 1));
/* Filteroperator AND */
if(secondBoolean == UA_STATUSCODE_BADNOMATCH) {
valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN];
return UA_STATUSCODE_BADNOMATCH;
} else if((firstBoolean_and == UA_STATUSCODE_GOOD) &&
(secondBoolean == UA_STATUSCODE_GOOD)) {
valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN];
return UA_STATUSCODE_GOOD;
} else {
return UA_STATUSCODE_BADFILTERELEMENTINVALID;
}
}
static UA_StatusCode
orOperator(UA_Server *server, UA_Session *session,
const UA_NodeId *eventNode,
const UA_ContentFilter *contentFilter,
UA_ContentFilterResult *contentFilterResult,
UA_Variant* valueResult, UA_UInt16 index,
UA_UInt16 nr, UA_ContentFilterElement *pElement) {
UA_StatusCode firstBoolean_or = resolveBoolean(
resolveOperand(server, session, eventNode, contentFilter,
contentFilterResult, valueResult, index, 0));
if(firstBoolean_or == UA_STATUSCODE_GOOD) {
valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN];
return UA_STATUSCODE_GOOD;
}
/* Evaluation of second operand */
UA_StatusCode secondBoolean = resolveBoolean(
resolveOperand(server, session, eventNode, contentFilter,
contentFilterResult, valueResult, index, 1));
if(secondBoolean == UA_STATUSCODE_GOOD) {
valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN];
return UA_STATUSCODE_GOOD;
} else if((firstBoolean_or == UA_STATUSCODE_BADNOMATCH) &&
(secondBoolean == UA_STATUSCODE_BADNOMATCH)) {
valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN];
return UA_STATUSCODE_BADNOMATCH;
} else {
return UA_STATUSCODE_BADFILTERELEMENTINVALID;
}
}
static UA_StatusCode
isNullOperator(UA_Server *server, UA_Session *session,
const UA_NodeId *eventNode,
const UA_ContentFilter *contentFilter,
UA_ContentFilterResult *contentFilterResult,
UA_Variant* valueResult, UA_UInt16 index,
UA_UInt16 nr, UA_ContentFilterElement *pElement) {
/* Checking if operand is NULL. This is done by reducing the operand to a
* variant and then checking if it is empty. */
UA_Variant operand =
resolveOperand(server, session, eventNode, contentFilter,
contentFilterResult, valueResult, index, 0);
valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN];
if(!UA_Variant_isEmpty(&operand)) {
return UA_STATUSCODE_BADNOMATCH;
}
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
notOperator(UA_Server *server, UA_Session *session,
const UA_NodeId *eventNode,
const UA_ContentFilter *contentFilter,
UA_ContentFilterResult *contentFilterResult,
UA_Variant* valueResult, UA_UInt16 index,
UA_UInt16 nr, UA_ContentFilterElement *pElement) {
/* Inverting the boolean value of the operand. */
UA_StatusCode res = resolveBoolean(
resolveOperand(server, session, eventNode, contentFilter,
contentFilterResult, valueResult, index, 0));
valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN];
//invert result
if(res == UA_STATUSCODE_GOOD) {
return UA_STATUSCODE_BADNOMATCH;
}
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
evaluateWhereClauseContentFilter(UA_Server *server, UA_Session *session,
const UA_NodeId *eventNode,
@ -289,97 +462,72 @@ evaluateWhereClauseContentFilter(UA_Server *server, UA_Session *session,
/* The first element needs to be evaluated, this might be linked to other
* elements, which are evaluated in these cases. See 7.4.1 in Part 4. */
UA_ContentFilterElement *pElement = &contentFilter->elements[0];
UA_ContentFilterElement *pElement = &contentFilter->elements[index];
switch(pElement->filterOperator) {
case UA_FILTEROPERATOR_INVIEW:
return UA_STATUSCODE_BADEVENTFILTERINVALID;
case UA_FILTEROPERATOR_RELATEDTO: {
/* Not allowed for event WhereClause according to 7.17.3 in Part 4 */
return UA_STATUSCODE_BADEVENTFILTERINVALID;
}
case UA_FILTEROPERATOR_EQUALS:
case UA_FILTEROPERATOR_ISNULL: {
/* Checking if operand is NULL. This is done by reducing the operand to a
* variant and then checking if it is empty. */
UA_Variant operand =
resolveOperand(server, session, eventNode, contentFilter,
contentFilterResult, valueResult, index, 0);
valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN];
if(UA_Variant_isEmpty(&operand)) {
contentFilterResult->elementResults[index].statusCode =
UA_STATUSCODE_GOOD;
break;
}
contentFilterResult->elementResults[index].statusCode =
UA_STATUSCODE_BADNOMATCH;
break;
}
case UA_FILTEROPERATOR_GREATERTHAN:
case UA_FILTEROPERATOR_LESSTHAN:
case UA_FILTEROPERATOR_GREATERTHANOREQUAL:
case UA_FILTEROPERATOR_LESSTHANOREQUAL:
case UA_FILTEROPERATOR_LIKE:
case UA_FILTEROPERATOR_NOT:
case UA_FILTEROPERATOR_BETWEEN:
case UA_FILTEROPERATOR_INLIST:
case UA_FILTEROPERATOR_AND:
case UA_FILTEROPERATOR_OR:
case UA_FILTEROPERATOR_CAST:
case UA_FILTEROPERATOR_BITWISEAND:
case UA_FILTEROPERATOR_BITWISEOR:
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
case UA_FILTEROPERATOR_OFTYPE: {
UA_Boolean result = UA_FALSE;
if(pElement->filterOperandsSize != 1)
return UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH;
if(pElement->filterOperands[0].content.decoded.type !=
&UA_TYPES[UA_TYPES_LITERALOPERAND])
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
UA_LiteralOperand *pOperand =
(UA_LiteralOperand *) pElement->filterOperands[0].content.decoded.data;
if(!UA_Variant_isScalar(&pOperand->value))
return UA_STATUSCODE_BADEVENTFILTERINVALID;
if(pOperand->value.type != &UA_TYPES[UA_TYPES_NODEID] ||
pOperand->value.data == NULL) {
result = UA_FALSE;
} else {
UA_NodeId *pOperandNodeId = (UA_NodeId *) pOperand->value.data;
UA_QualifiedName eventTypeQualifiedName = UA_QUALIFIEDNAME(0, "EventType");
UA_Variant typeNodeIdVariant;
UA_Variant_init(&typeNodeIdVariant);
UA_StatusCode readStatusCode =
readObjectProperty(server, *eventNode, eventTypeQualifiedName,
&typeNodeIdVariant);
if(readStatusCode != UA_STATUSCODE_GOOD)
return readStatusCode;
if(!UA_Variant_isScalar(&typeNodeIdVariant) ||
typeNodeIdVariant.type != &UA_TYPES[UA_TYPES_NODEID] ||
typeNodeIdVariant.data == NULL) {
UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER,
"EventType has an invalid type.");
UA_Variant_clear(&typeNodeIdVariant);
return UA_STATUSCODE_BADINTERNALERROR;
}
result = isNodeInTree_singleRef(server,
(UA_NodeId*) typeNodeIdVariant.data,
pOperandNodeId,
UA_REFERENCETYPEINDEX_HASSUBTYPE);
UA_Variant_clear(&typeNodeIdVariant);
}
if(result)
return UA_STATUSCODE_GOOD;
else
return UA_STATUSCODE_BADNOMATCH;
}
case UA_FILTEROPERATOR_ISNULL:
contentFilterResult->elementResults[index].statusCode =
isNullOperator(server, session, eventNode, contentFilter,
contentFilterResult, valueResult, index, 0, pElement);
break;
default:
return UA_STATUSCODE_BADFILTEROPERATORINVALID;
break;
case UA_FILTEROPERATOR_GREATERTHAN:
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
case UA_FILTEROPERATOR_LESSTHAN:
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
case UA_FILTEROPERATOR_GREATERTHANOREQUAL:
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
case UA_FILTEROPERATOR_LESSTHANOREQUAL:
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
case UA_FILTEROPERATOR_LIKE:
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
case UA_FILTEROPERATOR_NOT:
contentFilterResult->elementResults[index].statusCode =
notOperator(server, session, eventNode, contentFilter,
contentFilterResult, valueResult, index, 0, pElement);
break;
case UA_FILTEROPERATOR_BETWEEN:
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
case UA_FILTEROPERATOR_INLIST:
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
case UA_FILTEROPERATOR_AND: {
contentFilterResult->elementResults[index].statusCode =
andOperator(server, session, eventNode, contentFilter,
contentFilterResult, valueResult, index, 0, pElement);
break;
case UA_FILTEROPERATOR_OR:
contentFilterResult->elementResults[index].statusCode =
orOperator(server, session, eventNode, contentFilter,
contentFilterResult, valueResult, index, 0, pElement);
break;
case UA_FILTEROPERATOR_CAST:
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
case UA_FILTEROPERATOR_BITWISEAND:
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
case UA_FILTEROPERATOR_BITWISEOR:
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
case UA_FILTEROPERATOR_OFTYPE:
contentFilterResult->elementResults[index].statusCode =
ofTypeOperator(server, session, eventNode, contentFilter,
contentFilterResult, valueResult, index, 0, pElement);
break;
default:
return UA_STATUSCODE_BADFILTEROPERATORINVALID;
}
}
if(valueResult[index].type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
UA_Boolean *result = UA_Boolean_new();
if(contentFilterResult->elementResults[index].statusCode == UA_STATUSCODE_GOOD)
*result = true;
else
*result = false;
valueResult[index].data = result;
}
return contentFilterResult->elementResults[index].statusCode;
}

View File

@ -581,9 +581,7 @@ UA_MonitoredItem_delete(UA_Server *server, UA_MonitoredItem *mon) {
mon->delayedFreePointers.callback = NULL;
mon->delayedFreePointers.application = server;
mon->delayedFreePointers.data = NULL;
mon->delayedFreePointers.nextTime = UA_DateTime_nowMonotonic() + 1;
mon->delayedFreePointers.interval = 0;
UA_Timer_addTimerEntry(&server->timer, &mon->delayedFreePointers, NULL);
UA_EventLoop_addDelayedCallback(server->config.eventLoop, &mon->delayedFreePointers);
}
void

View File

@ -18,6 +18,7 @@
#include <open62541/transport_generated.h>
#include "open62541_queue.h"
#include "ua_util_internal.h"
#include "ua_connection_internal.h"
_UA_BEGIN_DECLS

View File

@ -774,33 +774,45 @@ UA_print(const void *p, const UA_DataType *type, UA_String *output) {
ctx.depth = 0;
TAILQ_INIT(&ctx.outputs);
/* Allocate before the goto */
size_t total = 0;
size_t pos = 0;
UA_PrintOutput *out, *out_tmp;
/* Encode */
UA_StatusCode retval = printJumpTable[type->typeKind](&ctx, p, type);
if(retval != UA_STATUSCODE_GOOD)
goto cleanup;
/* Allocate memory for the output */
if(retval == UA_STATUSCODE_GOOD) {
size_t total = 0;
UA_PrintOutput *out;
TAILQ_FOREACH(out, &ctx.outputs, next)
total += out->length;
/* If printing succeeded the output cannot be empty*/
TAILQ_FOREACH(out, &ctx.outputs, next)
total += out->length;
UA_assert(total > 0);
if(output->length == 0) {
/* Allocate memory for the output */
retval = UA_ByteString_allocBuffer((UA_String*)output, total);
} else {
/* Check if the buffer is large enough */
if(output->length >= total)
output->length = total;
else
retval = UA_STATUSCODE_BADOUTOFMEMORY;
}
if(retval != UA_STATUSCODE_GOOD)
goto cleanup;
/* Write to the output buffer */
TAILQ_FOREACH(out, &ctx.outputs, next) {
memcpy(&output->data[pos], out->data, out->length);
pos += out->length;
}
/* Write the output */
if(retval == UA_STATUSCODE_GOOD) {
size_t pos = 0;
UA_PrintOutput *out;
TAILQ_FOREACH(out, &ctx.outputs, next) {
memcpy(&output->data[pos], out->data, out->length);
pos += out->length;
}
}
cleanup:
/* Free the context */
UA_PrintOutput *o, *o2;
TAILQ_FOREACH_SAFE(o, &ctx.outputs, next, o2) {
TAILQ_REMOVE(&ctx.outputs, o, next);
UA_free(o);
TAILQ_FOREACH_SAFE(out, &ctx.outputs, next, out_tmp) {
TAILQ_REMOVE(&ctx.outputs, out, next);
UA_free(out);
}
return retval;
}

View File

@ -211,7 +211,7 @@ UA_ByteString_toBase64(const UA_ByteString *byteString,
return UA_STATUSCODE_GOOD;
}
UA_StatusCode UA_EXPORT
UA_StatusCode
UA_ByteString_fromBase64(UA_ByteString *bs,
const UA_String *input) {
UA_ByteString_init(bs);
@ -228,11 +228,11 @@ UA_ByteString_fromBase64(UA_ByteString *bs,
/* Key Value Map */
UA_StatusCode
UA_KeyValueMap_setQualified(UA_KeyValuePair **map, size_t *mapSize,
const UA_QualifiedName *key,
const UA_Variant *value) {
UA_KeyValueMap_set(UA_KeyValuePair **map, size_t *mapSize,
const UA_QualifiedName key,
const UA_Variant *value) {
/* Parameter exists already */
const UA_Variant *v = UA_KeyValueMap_getQualified(*map, *mapSize, key);
const UA_Variant *v = UA_KeyValueMap_get(*map, *mapSize, key);
if(v) {
UA_Variant copyV;
UA_StatusCode res = UA_Variant_copy(v, &copyV);
@ -246,69 +246,43 @@ UA_KeyValueMap_setQualified(UA_KeyValuePair **map, size_t *mapSize,
/* Append to the array */
UA_KeyValuePair pair;
pair.key = *key;
pair.key = key;
pair.value = *value;
return UA_Array_appendCopy((void**)map, mapSize, &pair,
&UA_TYPES[UA_TYPES_KEYVALUEPAIR]);
}
UA_StatusCode
UA_KeyValueMap_set(UA_KeyValuePair **map, size_t *mapSize,
const char *key, const UA_Variant *value) {
UA_QualifiedName qnKey;
qnKey.namespaceIndex = 0;
qnKey.name = UA_STRING((char*)(uintptr_t)key);
return UA_KeyValueMap_setQualified(map, mapSize, &qnKey, value);
}
const UA_Variant *
UA_KeyValueMap_getQualified(UA_KeyValuePair *map, size_t mapSize,
const UA_QualifiedName *key) {
UA_KeyValueMap_get(UA_KeyValuePair *map, size_t mapSize,
const UA_QualifiedName key) {
for(size_t i = 0; i < mapSize; i++) {
if(map[i].key.namespaceIndex == key->namespaceIndex &&
UA_String_equal(&map[i].key.name, &key->name))
if(map[i].key.namespaceIndex == key.namespaceIndex &&
UA_String_equal(&map[i].key.name, &key.name))
return &map[i].value;
}
return NULL;
}
const UA_Variant *
UA_KeyValueMap_get(UA_KeyValuePair *map, size_t mapSize,
const char *key) {
UA_QualifiedName qnKey;
qnKey.namespaceIndex = 0;
qnKey.name = UA_STRING((char*)(uintptr_t)key);
return UA_KeyValueMap_getQualified(map, mapSize, &qnKey);
}
/* Returns NULL if the parameter is not defined or not of the right datatype */
const UA_Variant *
const void *
UA_KeyValueMap_getScalar(UA_KeyValuePair *map, size_t mapSize,
const char *key, const UA_DataType *type) {
const UA_QualifiedName key,
const UA_DataType *type) {
const UA_Variant *v = UA_KeyValueMap_get(map, mapSize, key);
if(!v || !UA_Variant_hasScalarType(v, type))
return NULL;
return v;
}
const UA_Variant *
UA_KeyValueMap_getArray(UA_KeyValuePair *map, size_t mapSize,
const char *key, const UA_DataType *type) {
const UA_Variant *v = UA_KeyValueMap_get(map, mapSize, key);
if(!v || !UA_Variant_hasArrayType(v, type))
return NULL;
return v;
return v->data;
}
void
UA_KeyValueMap_deleteQualified(UA_KeyValuePair **map, size_t *mapSize,
const UA_QualifiedName *key) {
UA_KeyValueMap_delete(UA_KeyValuePair **map, size_t *mapSize,
const UA_QualifiedName key) {
UA_KeyValuePair *m = *map;
size_t s = *mapSize;
for(size_t i = 0; i < s; i++) {
if(m[i].key.namespaceIndex != key->namespaceIndex ||
!UA_String_equal(&m[i].key.name, &key->name))
if(m[i].key.namespaceIndex != key.namespaceIndex ||
!UA_String_equal(&m[i].key.name, &key.name))
continue;
/* Clean the pair */
@ -327,15 +301,6 @@ UA_KeyValueMap_deleteQualified(UA_KeyValuePair **map, size_t *mapSize,
* array around. Resize never fails when reducing
* the size to zero. Reduce the size integer in
* any case. */
return;
break;
}
}
void
UA_KeyValueMap_delete(UA_KeyValuePair **map, size_t *mapSize,
const char *key) {
UA_QualifiedName qnKey;
qnKey.namespaceIndex = 0;
qnKey.name = UA_STRING((char*)(uintptr_t)key);
UA_KeyValueMap_deleteQualified(map, mapSize, &qnKey);
}

View File

@ -185,6 +185,9 @@ isTrue(uint8_t expr) {
#define UA_CHECK_STATUS_INFO(STATUSCODE, EVAL, LOGGER, CAT, ...) \
UA_MACRO_EXPAND( \
UA_CHECK_STATUS_LOG(STATUSCODE, EVAL, INFO, LOGGER, CAT, __VA_ARGS__))
#define UA_CHECK_STATUS_DEBUG(STATUSCODE, EVAL, LOGGER, CAT, ...) \
UA_MACRO_EXPAND( \
UA_CHECK_STATUS_LOG(STATUSCODE, EVAL, DEBUG, LOGGER, CAT, __VA_ARGS__))
#define UA_CHECK_MEM_FATAL(PTR, EVAL, LOGGER, CAT, ...) \
UA_MACRO_EXPAND( \

View File

@ -23,6 +23,7 @@ include_directories(${open62541_BUILD_INCLUDE_DIRS})
# ua_server_internal.h
include_directories("${PROJECT_SOURCE_DIR}/src")
include_directories("${PROJECT_SOURCE_DIR}/src/server")
include_directories("${PROJECT_SOURCE_DIR}/arch/common")
# testing_clock.h
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/testing-plugins")
# #include <src_generated/<...>.h>
@ -65,7 +66,10 @@ endif()
# Use different plugins for testing
set(test_plugin_sources ${PROJECT_SOURCE_DIR}/arch/network_tcp.c
set(test_plugin_sources
${PROJECT_SOURCE_DIR}/arch/network_tcp.c
${PROJECT_SOURCE_DIR}/arch/eventloop_posix.c
${PROJECT_SOURCE_DIR}/arch/eventloop_posix_tcp.c
${PROJECT_SOURCE_DIR}/tests/testing-plugins/testing_clock.c
${PROJECT_SOURCE_DIR}/plugins/ua_log_stdout.c
${PROJECT_SOURCE_DIR}/plugins/ua_config_default.c
@ -156,6 +160,9 @@ endif()
add_library(open62541-testplugins OBJECT ${test_plugin_sources} ${PROJECT_SOURCE_DIR}/arch/${UA_ARCHITECTURE}/ua_architecture_functions.c)
add_dependencies(open62541-testplugins open62541)
target_compile_definitions(open62541-testplugins PRIVATE -DUA_DYNAMIC_LINKING_EXPORT)
if(UA_ENABLE_COVERAGE)
add_coverage(open62541-testplugins)
endif()
# Workaround some clang warnings in the uni tests
if((NOT ${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") AND (CMAKE_COMPILER_IS_GNUCC OR "x${CMAKE_C_COMPILER_ID}" STREQUAL "xClang"))
@ -255,6 +262,14 @@ add_executable(check_timer check_timer.c $<TARGET_OBJECTS:open62541-object> $<TA
target_link_libraries(check_timer ${LIBS})
add_test_valgrind(timer ${TESTS_BINARY_DIR}/check_timer)
add_executable(check_eventloop check_eventloop.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
target_link_libraries(check_eventloop ${LIBS})
add_test_valgrind(eventloop ${TESTS_BINARY_DIR}/check_eventloop)
add_executable(check_eventloop_tcp check_eventloop_tcp.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
target_link_libraries(check_eventloop_tcp ${LIBS})
add_test_valgrind(eventloop_tcp ${TESTS_BINARY_DIR}/check_eventloop_tcp)
# Test Server
add_executable(check_accesscontrol server/check_accesscontrol.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
@ -343,6 +358,10 @@ if(UA_ENABLE_HISTORIZING)
add_executable(check_server_historical_data server/check_server_historical_data.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
target_link_libraries(check_server_historical_data ${LIBS})
add_test_valgrind(server_historical_data ${TESTS_BINARY_DIR}/check_server_historical_data)
add_executable(check_server_historical_data_circular server/check_server_historical_data_circular.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
target_link_libraries(check_server_historical_data_circular ${LIBS})
add_test_valgrind(server_historical_data_circular ${TESTS_BINARY_DIR}/check_server_historical_data_circular)
endif()
add_executable(check_session server/check_session.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
@ -447,14 +466,22 @@ if(UA_ENABLE_PUBSUB)
$<TARGET_OBJECTS:open62541-testplugins>)
target_link_libraries(check_pubsub_subscribe_encrypted ${LIBS})
add_test_valgrind(check_pubsub_subscribe_encrypted ${TESTS_BINARY_DIR}/check_pubsub_subscribe_encrypted)
add_executable(check_pubsub_encrypted_rt_levels pubsub/check_pubsub_encrypted_rt_levels.c
$<TARGET_OBJECTS:open62541-object>
$<TARGET_OBJECTS:open62541-testplugins>)
target_link_libraries(check_pubsub_encrypted_rt_levels ${LIBS})
add_test_valgrind(check_pubsub_encrypted_rt_levels ${TESTS_BINARY_DIR}/check_pubsub_encrypted_rt_levels)
endif()
if (UA_ENABLE_PUBSUB_MONITORING)
add_executable(check_pubsub_subscribe_msgrcvtimeout pubsub/check_pubsub_subscribe_msgrcvtimeout.c
$<TARGET_OBJECTS:open62541-object>
$<TARGET_OBJECTS:open62541-testplugins>)
target_link_libraries(check_pubsub_subscribe_msgrcvtimeout ${LIBS})
add_test_valgrind(check_pubsub_subscribe_msgrcvtimeout ${TESTS_BINARY_DIR}/check_pubsub_subscribe_msgrcvtimeout)
if(NOT UA_ENABLE_PUBSUB_ENCRYPTION) #ToDO: Multiple Receive handling for PubsubEncryption
add_executable(check_pubsub_subscribe_msgrcvtimeout pubsub/check_pubsub_subscribe_msgrcvtimeout.c
$<TARGET_OBJECTS:open62541-object>
$<TARGET_OBJECTS:open62541-testplugins>)
target_link_libraries(check_pubsub_subscribe_msgrcvtimeout ${LIBS})
add_test_valgrind(check_pubsub_subscribe_msgrcvtimeout ${TESTS_BINARY_DIR}/check_pubsub_subscribe_msgrcvtimeout)
endif()
endif()
if(UA_ENABLE_PUBSUB_ETH_UADP)

65
tests/check_eventloop.c Normal file
View File

@ -0,0 +1,65 @@
/* 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/plugin/eventloop.h>
#include "testing_clock.h"
#include <time.h>
#include <check.h>
#define N_EVENTS 10000
UA_EventLoop *el;
size_t count = 0;
static void
timerCallback(void *application, void *data) {
count++;
}
/* Create empty events with different callback intervals */
static void
createEvents(UA_UInt32 events) {
for(size_t i = 0; i < events; i++) {
UA_Double interval = (UA_Double)i+1;
UA_StatusCode retval =
UA_EventLoop_addCyclicCallback(el, timerCallback, NULL, NULL, interval, NULL,
UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, NULL);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
}
}
START_TEST(benchmarkTimer) {
el = UA_EventLoop_new(NULL);
createEvents(N_EVENTS);
clock_t begin = clock();
for(size_t i = 0; i < 1000; i++) {
UA_DateTime next = UA_EventLoop_run(el, 1);
UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC));
}
clock_t finish = clock();
double time_spent = (double)(finish - begin) / CLOCKS_PER_SEC;
printf("duration was %f s\n", time_spent);
printf("%lu callbacks\n", (unsigned long)count);
UA_EventLoop_stop(el);
UA_EventLoop_delete(el);
el = NULL;
} END_TEST
int main(void) {
Suite *s = suite_create("Test EventLoop");
TCase *tc = tcase_create("test cases");
tcase_add_test(tc, benchmarkTimer);
suite_add_tcase(s, tc);
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;
}

256
tests/check_eventloop_tcp.c Normal file
View File

@ -0,0 +1,256 @@
/* 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/plugin/eventloop.h>
#include <open62541/plugin/log_stdout.h>
#include "open62541/types.h"
#include "open62541/types_generated.h"
#include "testing_clock.h"
#include <time.h>
#include <check.h>
#define N_EVENTS 10000
UA_EventLoop *el;
static void noopCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
void **connectionContext, UA_StatusCode status,
UA_ByteString msg) {}
START_TEST(listenTCP) {
el = UA_EventLoop_new(UA_Log_Stdout);
UA_UInt16 port = 4840;
UA_Variant portVar;
UA_Variant_setScalar(&portVar, &port, &UA_TYPES[UA_TYPES_UINT16]);
UA_ConnectionManager *cm = UA_ConnectionManager_TCP_new(UA_STRING("tcpCM"));
cm->connectionCallback = noopCallback;
UA_KeyValueMap_set(&cm->eventSource.params,
&cm->eventSource.paramsSize,
UA_QUALIFIEDNAME(0, "listen-port"), &portVar);
UA_EventLoop_registerEventSource(el, &cm->eventSource);
UA_EventLoop_start(el);
for(size_t i = 0; i < 10; i++) {
UA_DateTime next = UA_EventLoop_run(el, 1);
UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC));
}
int max_stop_iteration_count = 1000;
int iteration = 0;
/* Stop the EventLoop */
UA_EventLoop_stop(el);
while(UA_EventLoop_getState(el) != UA_EVENTLOOPSTATE_STOPPED && iteration < max_stop_iteration_count) {
UA_DateTime next = UA_EventLoop_run(el, 1);
UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC));
iteration++;
}
UA_EventLoop_delete(el);
el = NULL;
} END_TEST
static unsigned connCount;
static char *testMsg = "open62541";
static uintptr_t clientId;
static UA_Boolean received;
static void
connectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
void **connectionContext, UA_StatusCode status,
UA_ByteString msg) {
if(*connectionContext != NULL)
clientId = connectionId;
if(msg.length == 0 && status == UA_STATUSCODE_GOOD)
connCount++;
if(status != UA_STATUSCODE_GOOD) {
connCount--;
}
if(msg.length > 0) {
UA_ByteString rcv = UA_BYTESTRING(testMsg);
ck_assert(UA_String_equal(&msg, &rcv));
received = true;
}
}
static void
illegalConnectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
void **connectionContext, UA_StatusCode status,
UA_ByteString msg) {
UA_StatusCode rv = UA_EventLoop_run(el, 1);
ck_assert_uint_eq(rv, UA_STATUSCODE_BADINTERNALERROR);
if(*connectionContext != NULL)
clientId = connectionId;
if(msg.length == 0 && status == UA_STATUSCODE_GOOD)
connCount++;
if(status != UA_STATUSCODE_GOOD)
connCount--;
if(msg.length > 0) {
UA_ByteString rcv = UA_BYTESTRING(testMsg);
ck_assert(UA_String_equal(&msg, &rcv));
received = true;
}
}
START_TEST(runEventloopFailsIfCalledFromCallback) {
el = UA_EventLoop_new(UA_Log_Stdout);
UA_UInt16 port = 4840;
UA_Variant portVar;
UA_Variant_setScalar(&portVar, &port, &UA_TYPES[UA_TYPES_UINT16]);
UA_ConnectionManager *cm = UA_ConnectionManager_TCP_new(UA_STRING("tcpCM"));
cm->connectionCallback = illegalConnectionCallback;
UA_KeyValueMap_set(&cm->eventSource.params,
&cm->eventSource.paramsSize,
UA_QUALIFIEDNAME(0, "listen-port"), &portVar);
UA_EventLoop_registerEventSource(el, &cm->eventSource);
connCount = 0;
UA_EventLoop_start(el);
/* Open a client connection */
clientId = 0;
UA_String targetHost = UA_STRING("localhost");
UA_KeyValuePair params[2];
params[0].key = UA_QUALIFIEDNAME(0, "target-port");
params[0].value = portVar;
params[1].key = UA_QUALIFIEDNAME(0, "target-hostname");
UA_Variant_setScalar(&params[1].value, &targetHost, &UA_TYPES[UA_TYPES_STRING]);
UA_StatusCode retval = cm->openConnection(cm, 2, params, (void*)0x01);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
for(size_t i = 0; i < 10; i++) {
UA_DateTime next = UA_EventLoop_run(el, 1);
UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC));
}
ck_assert(clientId != 0);
ck_assert_uint_eq(connCount, 2);
/* Send a message from the client */
received = false;
UA_ByteString snd;
retval = cm->allocNetworkBuffer(cm, clientId, &snd, strlen(testMsg));
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
memcpy(snd.data, testMsg, strlen(testMsg));
retval = cm->sendWithConnection(cm, clientId, 0, NULL, &snd);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
for(size_t i = 0; i < 10; i++) {
UA_DateTime next = UA_EventLoop_run(el, 1);
UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC));
}
ck_assert(received);
/* Close the connection */
retval = cm->closeConnection(cm, clientId);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
ck_assert_uint_eq(connCount, 2);
for(size_t i = 0; i < 10; i++) {
UA_DateTime next = UA_EventLoop_run(el, 1);
UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC));
}
ck_assert_uint_eq(connCount, 0);
int max_stop_iteration_count = 1000;
int iteration = 0;
/* Stop the EventLoop */
UA_EventLoop_stop(el);
while(UA_EventLoop_getState(el) != UA_EVENTLOOPSTATE_STOPPED && iteration < max_stop_iteration_count) {
UA_DateTime next = UA_EventLoop_run(el, 1);
UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC));
iteration++;
}
UA_EventLoop_delete(el);
el = NULL;
} END_TEST
START_TEST(connectTCP) {
el = UA_EventLoop_new(UA_Log_Stdout);
UA_UInt16 port = 4840;
UA_Variant portVar;
UA_Variant_setScalar(&portVar, &port, &UA_TYPES[UA_TYPES_UINT16]);
UA_ConnectionManager *cm = UA_ConnectionManager_TCP_new(UA_STRING("tcpCM"));
cm->connectionCallback = connectionCallback;
UA_KeyValueMap_set(&cm->eventSource.params,
&cm->eventSource.paramsSize,
UA_QUALIFIEDNAME(0, "listen-port"), &portVar);
UA_EventLoop_registerEventSource(el, &cm->eventSource);
connCount = 0;
UA_EventLoop_start(el);
/* Open a client connection */
clientId = 0;
UA_String targetHost = UA_STRING("localhost");
UA_KeyValuePair params[2];
params[0].key = UA_QUALIFIEDNAME(0, "target-port");
params[0].value = portVar;
params[1].key = UA_QUALIFIEDNAME(0, "target-hostname");
UA_Variant_setScalar(&params[1].value, &targetHost, &UA_TYPES[UA_TYPES_STRING]);
UA_StatusCode retval = cm->openConnection(cm, 2, params, (void*)0x01);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
for(size_t i = 0; i < 10; i++) {
UA_DateTime next = UA_EventLoop_run(el, 1);
UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC));
}
ck_assert(clientId != 0);
ck_assert_uint_eq(connCount, 2);
/* Send a message from the client */
received = false;
UA_ByteString snd;
retval = cm->allocNetworkBuffer(cm, clientId, &snd, strlen(testMsg));
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
memcpy(snd.data, testMsg, strlen(testMsg));
retval = cm->sendWithConnection(cm, clientId, 0, NULL, &snd);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
for(size_t i = 0; i < 10; i++) {
UA_DateTime next = UA_EventLoop_run(el, 1);
UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC));
}
ck_assert(received);
/* Close the connection */
retval = cm->closeConnection(cm, clientId);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
ck_assert_uint_eq(connCount, 2);
for(size_t i = 0; i < 10; i++) {
UA_DateTime next = UA_EventLoop_run(el, 1);
UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC));
}
ck_assert_uint_eq(connCount, 0);
/* Stop the EventLoop */
int max_stop_iteration_count = 1000;
int iteration = 0;
/* Stop the EventLoop */
UA_EventLoop_stop(el);
while(UA_EventLoop_getState(el) != UA_EVENTLOOPSTATE_STOPPED && iteration < max_stop_iteration_count) {
UA_DateTime next = UA_EventLoop_run(el, 1);
UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC));
iteration++;
}
UA_EventLoop_delete(el);
el = NULL;
} END_TEST
int main(void) {
Suite *s = suite_create("Test TCP EventLoop");
TCase *tc = tcase_create("test cases");
tcase_add_test(tc, listenTCP);
tcase_add_test(tc, connectTCP);
tcase_add_test(tc, runEventloopFailsIfCalledFromCallback);
suite_add_tcase(s, tc);
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;
}

View File

@ -15,57 +15,6 @@
#include "check.h"
/* Define types to a dummy value if they are not available (e.g. not built with
* NS0 full) */
#ifndef UA_TYPES_UNION
#define UA_TYPES_UNION UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_HISTORYREADDETAILS
#define UA_TYPES_HISTORYREADDETAILS UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_NOTIFICATIONDATA
#define UA_TYPES_NOTIFICATIONDATA UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_MONITORINGFILTER
#define UA_TYPES_MONITORINGFILTER UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_MONITORINGFILTERRESULT
#define UA_TYPES_MONITORINGFILTERRESULT UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_DATASETREADERMESSAGEDATATYPE
#define UA_TYPES_DATASETREADERMESSAGEDATATYPE UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_WRITERGROUPTRANSPORTDATATYPE
#define UA_TYPES_WRITERGROUPTRANSPORTDATATYPE UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_CONNECTIONTRANSPORTDATATYPE
#define UA_TYPES_CONNECTIONTRANSPORTDATATYPE UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_WRITERGROUPMESSAGEDATATYPE
#define UA_TYPES_WRITERGROUPMESSAGEDATATYPE UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_READERGROUPTRANSPORTDATATYPE
#define UA_TYPES_READERGROUPTRANSPORTDATATYPE UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_PUBLISHEDDATASETSOURCEDATATYPE
#define UA_TYPES_PUBLISHEDDATASETSOURCEDATATYPE UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_DATASETREADERTRANSPORTDATATYPE
#define UA_TYPES_DATASETREADERTRANSPORTDATATYPE UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_DATASETWRITERTRANSPORTDATATYPE
#define UA_TYPES_DATASETWRITERTRANSPORTDATATYPE UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_SUBSCRIBEDDATASETDATATYPE
#define UA_TYPES_SUBSCRIBEDDATASETDATATYPE UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_READERGROUPMESSAGEDATATYPE
#define UA_TYPES_READERGROUPMESSAGEDATATYPE UA_TYPES_COUNT
#endif
#ifndef UA_TYPES_DATASETWRITERMESSAGEDATATYPE
#define UA_TYPES_DATASETWRITERMESSAGEDATATYPE UA_TYPES_COUNT
#endif
START_TEST(newAndEmptyObjectShallBeDeleted) {
// given
void *obj = UA_new(&UA_TYPES[_i]);
@ -106,18 +55,6 @@ START_TEST(arrayCopyShallMakeADeepCopy) {
END_TEST
START_TEST(encodeShallYieldDecode) {
/* floating point types may change the representaton due to several possible NaN values. */
if(_i != UA_TYPES_FLOAT || _i != UA_TYPES_DOUBLE ||
_i != UA_TYPES_CREATESESSIONREQUEST || _i != UA_TYPES_CREATESESSIONRESPONSE ||
_i != UA_TYPES_VARIABLEATTRIBUTES || _i != UA_TYPES_READREQUEST
#ifdef UA_ENABLE_SUBSCRIPTIONS
||
_i != UA_TYPES_MONITORINGPARAMETERS || _i != UA_TYPES_MONITOREDITEMCREATERESULT ||
_i != UA_TYPES_CREATESUBSCRIPTIONREQUEST || _i != UA_TYPES_CREATESUBSCRIPTIONRESPONSE
#endif
)
return;
// given
UA_ByteString msg1, msg2;
void *obj1 = UA_new(&UA_TYPES[_i]);
@ -154,6 +91,16 @@ START_TEST(encodeShallYieldDecode) {
UA_TYPES[_i].typeId.identifier.numeric);
ck_assert(UA_order(obj1, obj2, &UA_TYPES[_i]) == UA_ORDER_EQ);
// pretty-print the value
#ifdef UA_ENABLE_TYPEDESCRIPTION
UA_Byte staticBuf[4096];
UA_String buf;
buf.data = staticBuf;
buf.length = 4096;
retval = UA_print(obj2, &UA_TYPES[_i], &buf);
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
#endif
// finally
UA_delete(obj1, &UA_TYPES[_i]);
UA_delete(obj2, &UA_TYPES[_i]);

View File

@ -45,7 +45,7 @@ if(UA_NAMESPACE_ZERO STREQUAL "FULL")
ua_generate_nodeset_and_datatypes(
NAME "tests-plc"
# PLCopen does not define custom types. Only generate the nodeset
FILE_NS "${open62541_NODESET_DIR}/PLCopen/Opc.Ua.Plc.NodeSet2.xml"
FILE_NS "${open62541_NODESET_DIR}/PLCopen/Opc.Ua.PLCopen.NodeSet2_V1.02.xml"
# PLCopen depends on the di nodeset, which must be generated before
OUTPUT_DIR "${GENERATE_OUTPUT_DIR}"
DEPENDS "tests-di"

View File

@ -0,0 +1,843 @@
/* 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 (c) 2020 -2021 Kalycito Infotech Private Limited (Author: Keerthivasan)
*/
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/plugin/pubsub_udp.h>
#include "open62541/server_pubsub.h"
#include <open62541/plugin/securitypolicy_default.h>
#include "ua_pubsub.h"
#include "ua_pubsub_networkmessage.h"
#include <check.h>
#include <stdio.h>
UA_Server *server = NULL;
UA_NodeId connectionIdentifier, publishedDataSetIdent, writerGroupIdent, dataSetWriterIdent, dataSetFieldIdent, dataSetFieldIdent1, readerGroupIdentifier, readerIdentifier;
UA_UInt32 *subValue;
UA_DataValue *subDataValueRT;
UA_UInt32 *subValue1;
UA_DataValue *subDataValueRT1;
UA_NodeId subNodeId;
UA_NodeId subNodeId1;
UA_NodeId pubNodeId;
UA_NodeId pubNodeId1;
#define UA_AES128CTR_SIGNING_KEY_LENGTH 32
#define UA_AES128CTR_KEY_LENGTH 16
#define UA_AES128CTR_KEYNONCE_LENGTH 4
UA_Byte signingKeyPub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0};
UA_Byte encryptingKeyPub[UA_AES128CTR_KEY_LENGTH] = {0};
UA_Byte keyNoncePub[UA_AES128CTR_KEYNONCE_LENGTH] = {0};
typedef struct {
UA_ByteString *buffer;
} UA_ReceiveContext;
static UA_StatusCode
addMinimalPubSubConfiguration(void){
UA_StatusCode retVal = UA_STATUSCODE_GOOD;
/* Add one PubSubConnection */
UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(connectionConfig));
connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
connectionConfig.enabled = UA_TRUE;
UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")};
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherId.numeric = 2234;
retVal = UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
if(retVal != UA_STATUSCODE_GOOD)
return retVal;
/* Add one PublishedDataSet */
UA_PublishedDataSetConfig publishedDataSetConfig;
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
publishedDataSetConfig.name = UA_STRING("Demo PDS");
/* Add one DataSetField to the PDS */
UA_AddPublishedDataSetResult addResult = UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent);
return addResult.addResult;
}
static void setup(void) {
server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefault(config);
/* Instantiate the PubSub SecurityPolicy */
config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*)
UA_malloc(sizeof(UA_PubSubSecurityPolicy));
config->pubSubConfig.securityPoliciesSize = 1;
UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[0],
&config->logger);
UA_ServerConfig_addPubSubTransportLayer(config, UA_PubSubTransportLayerUDPMP());
UA_Server_run_startup(server);
}
static void teardown(void) {
UA_Server_run_shutdown(server);
UA_Server_delete(server);
}
static UA_StatusCode
recvTestFun(UA_PubSubChannel *channel, void *context, const UA_ByteString *buffer) {
UA_ReceiveContext *ctx = (UA_ReceiveContext*)context;
memcpy(ctx->buffer->data, buffer->data, buffer->length);
ctx->buffer->length = buffer->length;
return UA_STATUSCODE_GOOD;
}
static void receiveSingleMessageRT(UA_PubSubConnection *connection, UA_ReaderGroup *readerGroup) {
UA_ByteString buffer;
UA_DataSetReader *dataSetReader = LIST_FIRST(&readerGroup->readers);
if (UA_ByteString_allocBuffer(&buffer, 512) != UA_STATUSCODE_GOOD) {
ck_abort_msg("Message buffer allocation failed!");
}
if(!connection->channel) {
ck_abort_msg("No connection established");
return;
}
UA_ReceiveContext testCtx = {&buffer};
UA_StatusCode retval = connection->channel->receive(connection->channel, NULL, recvTestFun, &testCtx, 1000000);
if(retval != UA_STATUSCODE_GOOD || buffer.length == 0) {
buffer.length = 512;
UA_ByteString_clear(&buffer);
ck_abort_msg("Expected message not received!");
}
UA_NetworkMessage currentNetworkMessage;
memset(&currentNetworkMessage, 0, sizeof(UA_NetworkMessage));
size_t payLoadPosition = 0;
UA_NetworkMessage_decodeHeaders(&buffer, &payLoadPosition, &currentNetworkMessage);
UA_ServerConfig *config = UA_Server_getConfig(server);
verifyAndDecryptNetworkMessage(&config->logger,
&buffer,
&payLoadPosition,
&currentNetworkMessage,
readerGroup);
UA_NetworkMessage_clear(&currentNetworkMessage);
size_t currentPosition = 0;
/* Decode only the necessary offset and update the networkMessage */
if(UA_NetworkMessage_updateBufferedNwMessage(&dataSetReader->bufferedMessage, &buffer, &currentPosition) != UA_STATUSCODE_GOOD) {
ck_abort_msg("PubSub receive. Unknown field type!");
}
/* Check the decoded message is the expected one */
if((dataSetReader->bufferedMessage.nm->groupHeader.writerGroupId != dataSetReader->config.writerGroupId) ||
(*dataSetReader->bufferedMessage.nm->payloadHeader.dataSetPayloadHeader.dataSetWriterIds != dataSetReader->config.dataSetWriterId)) {
ck_abort_msg("PubSub receive. Unknown message received. Will not be processed.");
}
UA_ReaderGroup *rg =
UA_ReaderGroup_findRGbyId(server, dataSetReader->linkedReaderGroup);
UA_DataSetReader_process(server, rg, dataSetReader,
dataSetReader->bufferedMessage.nm->payload.dataSetPayload.dataSetMessages);
/* Delete the payload value of every dsf's decoded */
UA_DataSetMessage *dsm = dataSetReader->bufferedMessage.nm->payload.dataSetPayload.dataSetMessages;
if(dsm->header.fieldEncoding == UA_FIELDENCODING_VARIANT) {
for(UA_UInt16 i = 0; i < dsm->data.keyFrameData.fieldCount; i++) {
UA_Variant_clear(&dsm->data.keyFrameData.dataSetFields[i].value);
}
}
UA_ByteString_clear(&buffer);
}
/* If the external data source is written over the information model, the
* externalDataWriteCallback will be triggered. The user has to take care and assure
* that the write leads not to synchronization issues and race conditions. */
static UA_StatusCode
externalDataWriteCallback(UA_Server *serverLocal, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeId,
void *nodeContext, const UA_NumericRange *range,
const UA_DataValue *data){
if(UA_NodeId_equal(nodeId, &subNodeId)){
memcpy(subValue, data->value.data, sizeof(UA_UInt32));
}
if(UA_NodeId_equal(nodeId, &subNodeId1)){
memcpy(subValue1, data->value.data, sizeof(UA_UInt32));
}
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
externalDataReadNotificationCallback(UA_Server *serverLocal, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeid,
void *nodeContext, const UA_NumericRange *range){
//allow read without any preparation
return UA_STATUSCODE_GOOD;
}
START_TEST(SetupInvalidPubSubConfig) {
UA_StatusCode retVal = UA_STATUSCODE_GOOD;
ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD);
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = 100;
writerGroupConfig.enabled = UA_FALSE;
writerGroupConfig.writerGroupId = 100;
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
UA_ServerConfig *config = UA_Server_getConfig(server);
writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new();
wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
writerGroupConfig.messageSettings.content.decoded.data = wgm;
writerGroupConfig.messageSettings.content.decoded.type =
&UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE];
writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD);
UA_UadpWriterGroupMessageDataType_delete(wgm);
/* Add the encryption key informaton */
UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub};
UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub};
UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub};
UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn);
UA_DataSetFieldConfig dsfConfig;
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
// Create Variant and configure as DataSetField source
UA_VariableAttributes attributes = UA_VariableAttributes_default;
UA_UInt32 *intValue = UA_UInt32_new();
*intValue = (UA_UInt32) 1000;
UA_Variant variant;
memset(&variant, 0, sizeof(UA_Variant));
UA_Variant_setScalar(&variant, intValue, &UA_TYPES[UA_TYPES_UINT32]);
attributes.value = variant;
UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 1000),
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "variable"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attributes, NULL, NULL);
dsfConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, 1000);
dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
/* Not using static value source */
ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD);
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Test DataSetWriter");
dataSetWriterConfig.dataSetWriterId = 62541;
/* UA_Server_addDataSetWriter fails because fields in PDS is not RT capable */
ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_BADCONFIGURATIONERROR);
/* Reader Group */
UA_ReaderGroupConfig readerGroupConfig;
memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig));
readerGroupConfig.name = UA_STRING ("ReaderGroup Test");
readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig,
&readerGroupIdentifier);
// TODO security token not necessary for readergroup (extracted from security-header)
UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
/* Data Set Reader */
UA_DataSetReaderConfig readerConfig;
memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig));
readerConfig.name = UA_STRING ("DataSetReader Test");
UA_UInt16 publisherIdentifier = 2234;
readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16];
readerConfig.publisherId.data = &publisherIdentifier;
readerConfig.writerGroupId = 100;
readerConfig.dataSetWriterId = 62541;
readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE];
UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new();
dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage;
/* Setting up Meta data configuration in DataSetReader for DateTime DataType */
UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData;
/* FilltestMetadata function in subscriber implementation */
UA_DataSetMetaDataType_init(pMetaData);
pMetaData->name = UA_STRING("DataSet Test");
/* Static definition of number of fields size to 1 to create one
targetVariable */
pMetaData->fieldsSize = 1;
pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize,
&UA_TYPES[UA_TYPES_FIELDMETADATA]);
/* DateTime DataType */
UA_FieldMetaData_init(&pMetaData->fields[0]);
UA_NodeId_copy(&UA_TYPES[UA_TYPES_DATETIME].typeId,
&pMetaData->fields[0].dataType);
pMetaData->fields[0].builtInType = UA_NS0ID_DATETIME;
pMetaData->fields[0].valueRank = -1; /* scalar */
/* Add Subscribed Variables */
UA_NodeId folderId;
UA_NodeId newnodeId;
UA_String folderName = readerConfig.dataSetMetaData.name;
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
UA_QualifiedName folderBrowseName;
if (folderName.length > 0) {
oAttr.displayName.locale = UA_STRING ("en-US");
oAttr.displayName.text = folderName;
folderBrowseName.namespaceIndex = 1;
folderBrowseName.name = folderName;
}
else {
oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables");
folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables");
}
UA_Server_addObjectNode (server, UA_NODEID_NULL,
UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES),
folderBrowseName, UA_NODEID_NUMERIC (0,
UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId);
/* Variable to subscribe data */
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed DateTime");
vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed DateTime");
vAttr.dataType = UA_TYPES[UA_TYPES_DATETIME].typeId;
retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 50002),
folderId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed DateTime"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1;
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *)
UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable));
/* For creating Targetvariable */
UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable);
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = subNodeId;
retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig,
&readerIdentifier);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
UA_NodeId readerIdentifier2;
retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig,
&readerIdentifier2);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage);
UA_FieldTargetDataType_clear(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable);
UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables);
UA_free(readerConfig.dataSetMetaData.fields);
UA_Variant_clear(&variant);
ck_assert(UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_BADNOTIMPLEMENTED); // Multiple DSR not supported
ck_assert(UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD);
retVal = UA_Server_removeDataSetReader(server, readerIdentifier2);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
ck_assert(UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_BADNOTSUPPORTED); // DateTime not supported
ck_assert(UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD);
} END_TEST
START_TEST(PublishAndSubscribeSingleFieldWithFixedOffsets) {
UA_StatusCode retVal = UA_STATUSCODE_GOOD;
ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD);
UA_PubSubConnection *connection = UA_PubSubConnection_findConnectionbyId(server, connectionIdentifier);
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = 100;
writerGroupConfig.enabled = UA_FALSE;
writerGroupConfig.writerGroupId = 100;
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
UA_ServerConfig *config = UA_Server_getConfig(server);
writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new();
wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
writerGroupConfig.messageSettings.content.decoded.data = wgm;
writerGroupConfig.messageSettings.content.decoded.type =
&UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE];
writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD);
UA_UadpWriterGroupMessageDataType_delete(wgm);
/* Add the encryption key informaton */
UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub};
UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub};
UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub};
UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn);
UA_DataSetFieldConfig dsfConfig;
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
// Create Variant and configure as DataSetField source
UA_UInt32 *intValue = UA_UInt32_new();
*intValue = 1000;
UA_DataValue *dataValue = UA_DataValue_new();
UA_Variant_setScalar(&dataValue->value, intValue, &UA_TYPES[UA_TYPES_UINT32]);
dsfConfig.field.variable.fieldNameAlias = UA_STRING("Published Int32");
dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
dsfConfig.field.variable.rtValueSource.staticValueSource = &dataValue;
dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD);
/* Add dataset writer */
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Test DataSetWriter");
dataSetWriterConfig.dataSetWriterId = 62541;
ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD);
/* Reader Group */
UA_ReaderGroupConfig readerGroupConfig;
memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig));
readerGroupConfig.name = UA_STRING ("ReaderGroup Test");
readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig,
&readerGroupIdentifier);
// TODO security token not necessary for readergroup (extracted from security-header)
UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
/* Data Set Reader */
UA_DataSetReaderConfig readerConfig;
memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig));
readerConfig.name = UA_STRING ("DataSetReader Test");
UA_UInt16 publisherIdentifier = 2234;
readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16];
readerConfig.publisherId.data = &publisherIdentifier;
readerConfig.writerGroupId = 100;
readerConfig.dataSetWriterId = 62541;
readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE];
UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new();
dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage;
/* Setting up Meta data configuration in DataSetReader for DateTime DataType */
UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData;
/* FilltestMetadata function in subscriber implementation */
UA_DataSetMetaDataType_init(pMetaData);
pMetaData->name = UA_STRING("DataSet Test");
/* Static definition of number of fields size to 1 to create one
targetVariable */
pMetaData->fieldsSize = 1;
pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize,
&UA_TYPES[UA_TYPES_FIELDMETADATA]);
/* UInt32 DataType */
UA_FieldMetaData_init(&pMetaData->fields[0]);
UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId,
&pMetaData->fields[0].dataType);
pMetaData->fields[0].builtInType = UA_NS0ID_UINT32;
pMetaData->fields[0].valueRank = -1; /* scalar */
/* Add Subscribed Variables */
UA_NodeId folderId;
UA_String folderName = readerConfig.dataSetMetaData.name;
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
UA_QualifiedName folderBrowseName;
if (folderName.length > 0) {
oAttr.displayName.locale = UA_STRING ("en-US");
oAttr.displayName.text = folderName;
folderBrowseName.namespaceIndex = 1;
folderBrowseName.name = folderName;
}
else {
oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables");
folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables");
}
UA_Server_addObjectNode (server, UA_NODEID_NULL,
UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES),
folderBrowseName, UA_NODEID_NUMERIC (0,
UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId);
/* Variable to subscribe data */
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32");
vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32");
vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 50002),
folderId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed UInt32"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &subNodeId);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
subValue = UA_UInt32_new();
subDataValueRT = UA_DataValue_new();
subDataValueRT->hasValue = UA_TRUE;
UA_Variant_setScalar(&subDataValueRT->value, subValue, &UA_TYPES[UA_TYPES_UINT32]);
/* Set the value backend of the above create node to 'external value source' */
UA_ValueBackend valueBackend;
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend.backend.external.value = &subDataValueRT;
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
UA_Server_setVariableNode_valueBackend(server, subNodeId, valueBackend);
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1;
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *)
UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable));
/* For creating Targetvariable */
UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable);
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = subNodeId;
retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig,
&readerIdentifier);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage);
UA_FieldTargetDataType_clear(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable);
UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables);
UA_free(readerConfig.dataSetMetaData.fields);
ck_assert(UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD);
ck_assert(UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent) == UA_STATUSCODE_GOOD);
ck_assert(UA_Server_setWriterGroupOperational(server, writerGroupIdent) == UA_STATUSCODE_GOOD);
ck_assert(UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD);
ck_assert(UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD);
UA_ReaderGroup *readerGroup = UA_ReaderGroup_findRGbyId(server, readerGroupIdentifier);
receiveSingleMessageRT(connection, readerGroup);
/* Read data received by the Subscriber */
UA_Variant *subscribedNodeData = UA_Variant_new();
retVal = UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 50002), subscribedNodeData);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
ck_assert((*(UA_UInt32 *)subscribedNodeData->data) == 1000);
UA_Variant_clear(subscribedNodeData);
UA_free(subscribedNodeData);
ck_assert(UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD);
UA_DataValue_delete(dataValue);
UA_free(subValue);
UA_free(subDataValueRT);
} END_TEST
START_TEST(PublishPDSWithMultipleFieldsAndSubscribeFixedSize) {
UA_StatusCode retVal = UA_STATUSCODE_GOOD;
ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD);
UA_PubSubConnection *connection = UA_PubSubConnection_findConnectionbyId(server, connectionIdentifier);
/* Add Subscribed Variables */
UA_NodeId folderId1;
UA_String folderName1 = UA_STRING("PubNodes");
UA_ObjectAttributes oAttr1 = UA_ObjectAttributes_default;
UA_QualifiedName folderBrowseName1;
if (folderName1.length > 0) {
oAttr1.displayName.locale = UA_STRING ("en-US");
oAttr1.displayName.text = folderName1;
folderBrowseName1.namespaceIndex = 1;
folderBrowseName1.name = folderName1;
}
else {
oAttr1.displayName = UA_LOCALIZEDTEXT ("en-US", "Published Variables");
folderBrowseName1 = UA_QUALIFIEDNAME (1, "Published Variables");
}
UA_Server_addObjectNode (server, UA_NODEID_NULL,
UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES),
folderBrowseName1, UA_NODEID_NUMERIC (0,
UA_NS0ID_BASEOBJECTTYPE), oAttr1, NULL, &folderId1);
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
UA_UInt32 value = 0;
vAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_Variant_setScalar(&vAttr.value, &value, &UA_TYPES[UA_TYPES_UINT32]);
vAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Published variable");
vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 60000),
folderId1,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed DateTime"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &pubNodeId);
retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 60001),
folderId1,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed1 DateTime"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &pubNodeId1);
UA_DataSetFieldConfig dsfConfig;
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
UA_UInt32 *intValue = UA_UInt32_new();
*intValue = 1000;
UA_DataValue *dataValue = UA_DataValue_new();
UA_Variant_setScalar(&dataValue->value, intValue, &UA_TYPES[UA_TYPES_UINT32]);
dataValue->hasValue = true;
dsfConfig.field.variable.fieldNameAlias = UA_STRING("Published UInt32");
dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
/* Set the value backend of the above create node to 'external value source' */
UA_ValueBackend valueBackend;
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend.backend.external.value = &dataValue;
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, 60000), valueBackend);
dsfConfig.field.variable.rtValueSource.rtInformationModelNode = true;
dsfConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, 60000);
ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, NULL).result == UA_STATUSCODE_GOOD);
UA_DataSetFieldConfig dsfConfig1;
memset(&dsfConfig1, 0, sizeof(UA_DataSetFieldConfig));
UA_UInt32 *intValue1 = UA_UInt32_new();
*intValue1 = 2000;
UA_DataValue *dataValue1 = UA_DataValue_new();
UA_Variant_setScalar(&dataValue1->value, intValue1, &UA_TYPES[UA_TYPES_UINT32]);
dataValue->hasValue = true;
dsfConfig1.field.variable.fieldNameAlias = UA_STRING("Published1 UInt32");
dsfConfig1.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
/* Set the value backend of the above create node to 'external value source' */
UA_ValueBackend valueBackend1;
valueBackend1.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend1.backend.external.value = &dataValue1;
valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, 60001), valueBackend1);
dsfConfig1.field.variable.rtValueSource.rtInformationModelNode = true;
dsfConfig1.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, 60001);
ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig1, NULL).result == UA_STATUSCODE_GOOD);
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = 100;
writerGroupConfig.enabled = UA_FALSE;
writerGroupConfig.writerGroupId = 100;
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
UA_ServerConfig *config = UA_Server_getConfig(server);
writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new();
wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
writerGroupConfig.messageSettings.content.decoded.data = wgm;
writerGroupConfig.messageSettings.content.decoded.type =
&UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE];
writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD);
UA_UadpWriterGroupMessageDataType_delete(wgm);
/* Add the encryption key informaton */
UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub};
UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub};
UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub};
UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn);
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Test DataSetWriter");
dataSetWriterConfig.dataSetWriterId = 62541;
ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD);
/* Reader Group */
UA_ReaderGroupConfig readerGroupConfig;
memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig));
readerGroupConfig.name = UA_STRING ("ReaderGroup Test");
readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig,
&readerGroupIdentifier);
// TODO security token not necessary for readergroup (extracted from security-header)
UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
/* Data Set Reader */
UA_DataSetReaderConfig readerConfig;
memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig));
readerConfig.name = UA_STRING ("DataSetReader Test");
UA_UInt16 publisherIdentifier = 2234;
readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16];
readerConfig.publisherId.data = &publisherIdentifier;
readerConfig.writerGroupId = 100;
readerConfig.dataSetWriterId = 62541;
readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE];
UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new();
dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage;
/* Setting up Meta data configuration in DataSetReader for DateTime DataType */
UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData;
/* FilltestMetadata function in subscriber implementation */
UA_DataSetMetaDataType_init(pMetaData);
pMetaData->name = UA_STRING("DataSet Test");
/* Static definition of number of fields size to 1 to create one
targetVariable */
pMetaData->fieldsSize = 2;
pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize,
&UA_TYPES[UA_TYPES_FIELDMETADATA]);
/* UInt32 DataType */
UA_FieldMetaData_init(&pMetaData->fields[0]);
UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId,
&pMetaData->fields[0].dataType);
pMetaData->fields[0].builtInType = UA_NS0ID_UINT32;
pMetaData->fields[0].valueRank = -1; /* scalar */
UA_FieldMetaData_init(&pMetaData->fields[1]);
UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId,
&pMetaData->fields[1].dataType);
pMetaData->fields[1].builtInType = UA_NS0ID_UINT32;
pMetaData->fields[1].valueRank = -1; /* scalar */
/* Add Subscribed Variables */
UA_NodeId folderId;
UA_String folderName = readerConfig.dataSetMetaData.name;
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
UA_QualifiedName folderBrowseName;
if (folderName.length > 0) {
oAttr.displayName.locale = UA_STRING ("en-US");
oAttr.displayName.text = folderName;
folderBrowseName.namespaceIndex = 1;
folderBrowseName.name = folderName;
}
else {
oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables");
folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables");
}
UA_Server_addObjectNode (server, UA_NODEID_NULL,
UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES),
folderBrowseName, UA_NODEID_NUMERIC (0,
UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId);
/* Variable to subscribe data */
vAttr = UA_VariableAttributes_default;
vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32");
vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32");
vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 60003),
folderId1,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed UInt32"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &subNodeId);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
subValue = UA_UInt32_new();
subDataValueRT = UA_DataValue_new();
subDataValueRT->hasValue = UA_TRUE;
UA_Variant_setScalar(&subDataValueRT->value, subValue, &UA_TYPES[UA_TYPES_UINT32]);
/* Set the value backend of the above create node to 'external value source' */
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend.backend.external.value = &subDataValueRT;
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
UA_Server_setVariableNode_valueBackend(server, subNodeId, valueBackend);
vAttr = UA_VariableAttributes_default;
vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed1 UInt32");
vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed1 UInt32");
vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 60004),
folderId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed1 UInt32"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &subNodeId1);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
subValue1 = UA_UInt32_new();
subDataValueRT1 = UA_DataValue_new();
subDataValueRT1->hasValue = UA_TRUE;
UA_Variant_setScalar(&subDataValueRT1->value, subValue1, &UA_TYPES[UA_TYPES_UINT32]);
/* Set the value backend of the above create node to 'external value source' */
valueBackend1.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend1.backend.external.value = &subDataValueRT1;
valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
UA_Server_setVariableNode_valueBackend(server, subNodeId1, valueBackend1);
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 2;
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable*) UA_calloc(2, sizeof(UA_FieldTargetVariable));
UA_FieldTargetDataType_init(&targetVars[0].targetVariable);
targetVars[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[0].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, 60003);
UA_FieldTargetDataType_init(&targetVars[1].targetVariable);
targetVars[1].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[1].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, 60004);
readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET;
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = targetVars;
retVal = UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig,
&readerIdentifier);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage);
UA_FieldTargetDataType_clear(&targetVars[0].targetVariable);
UA_FieldTargetDataType_clear(&targetVars[1].targetVariable);
UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables);
UA_free(readerConfig.dataSetMetaData.fields);
ck_assert(UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD);
ck_assert(UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent) == UA_STATUSCODE_GOOD);
ck_assert(UA_Server_setWriterGroupOperational(server, writerGroupIdent) == UA_STATUSCODE_GOOD);
ck_assert(UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD);
ck_assert(UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD);
UA_ReaderGroup *readerGroup = UA_ReaderGroup_findRGbyId(server, readerGroupIdentifier);
receiveSingleMessageRT(connection, readerGroup);
/* Read data received by the Subscriber */
UA_Variant *subscribedNodeData = UA_Variant_new();
retVal = UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 60003), subscribedNodeData);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
ck_assert((*(UA_UInt32 *)subscribedNodeData->data) == 1000);
UA_Variant_clear(subscribedNodeData);
UA_free(subscribedNodeData);
/* Read data received by the Subscriber */
UA_Variant *subscribedNodeData1 = UA_Variant_new();
retVal = UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 60004), subscribedNodeData1);
ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
ck_assert((*(UA_UInt32 *)subscribedNodeData1->data) == 2000);
UA_Variant_clear(subscribedNodeData1);
UA_free(subscribedNodeData1);
ck_assert(UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD);
UA_Server_deleteNode(server, pubNodeId1, UA_TRUE);
UA_NodeId_clear(&pubNodeId);
UA_Server_deleteNode(server, pubNodeId1, UA_TRUE);
UA_NodeId_clear(&pubNodeId1);
UA_Server_deleteNode(server, subNodeId, UA_TRUE);
UA_NodeId_clear(&subNodeId);
UA_Server_deleteNode(server, subNodeId1, UA_TRUE);
UA_NodeId_clear(&subNodeId1);
UA_free(subValue);
UA_free(subDataValueRT);
UA_free(subValue1);
UA_free(subDataValueRT1);
/* Free external data source */
UA_free(intValue);
UA_free(dataValue);
/* Free external data source */
UA_free(intValue1);
UA_free(dataValue1);
} END_TEST
int main(void) {
TCase *tc_pubsub_encryption_rt = tcase_create("PubSub encryption RT with fixed offsets");
tcase_add_checked_fixture(tc_pubsub_encryption_rt, setup, teardown);
tcase_add_test(tc_pubsub_encryption_rt, SetupInvalidPubSubConfig);
tcase_add_test(tc_pubsub_encryption_rt, PublishAndSubscribeSingleFieldWithFixedOffsets);
tcase_add_test(tc_pubsub_encryption_rt, PublishPDSWithMultipleFieldsAndSubscribeFixedSize);
Suite *s = suite_create("PubSub encryption RT configuration levels");
suite_add_tcase(s, tc_pubsub_encryption_rt);
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;
}

View File

@ -5,6 +5,8 @@
#include <open62541/types.h>
#include <open62541/util.h>
#include <open62541/plugin/nodestore_default.h>
#include "open62541/plugin/nodestore.h"
#include "open62541/types_generated.h"
#include <stdio.h>
#include <stdlib.h>
@ -79,7 +81,8 @@ START_TEST(findNodeInUA_NodeStoreWithSingleEntry) {
UA_Node* n1 = createNode(0,2253);
ns.insertNode(ns.context, n1, NULL);
UA_NodeId in1 = UA_NODEID_NUMERIC(0,2253);
const UA_Node* nr = ns.getNode(ns.context, &in1);
const UA_Node* nr = ns.getNode(ns.context, &in1, ~(UA_UInt32)0,
UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
ck_assert_uint_eq((uintptr_t)n1, (uintptr_t)nr);
ns.releaseNode(ns.context, nr);
}
@ -89,7 +92,8 @@ START_TEST(failToFindNodeInOtherUA_NodeStore) {
UA_Node* n1 = createNode(0,2255);
ns.insertNode(ns.context, n1, NULL);
UA_NodeId in1 = UA_NODEID_NUMERIC(1, 2255);
const UA_Node* nr = ns.getNode(ns.context, &in1);
const UA_Node* nr = ns.getNode(ns.context, &in1, ~(UA_UInt32)0,
UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
ck_assert_uint_eq((uintptr_t)nr, 0);
}
END_TEST
@ -109,7 +113,8 @@ START_TEST(findNodeInUA_NodeStoreWithSeveralEntries) {
ns.insertNode(ns.context, n6, NULL);
UA_NodeId in3 = UA_NODEID_NUMERIC(0, 2257);
const UA_Node* nr = ns.getNode(ns.context, &in3);
const UA_Node* nr = ns.getNode(ns.context, &in3, ~(UA_UInt32)0,
UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
ck_assert_uint_eq((uintptr_t)nr, (uintptr_t)n3);
ns.releaseNode(ns.context, nr);
}
@ -144,7 +149,8 @@ START_TEST(findNodeInExpandedNamespace) {
}
// when
UA_Node *n2 = createNode(0,25);
const UA_Node* nr = ns.getNode(ns.context, &n2->head.nodeId);
const UA_Node* nr = ns.getNode(ns.context, &n2->head.nodeId, ~(UA_UInt32)0,
UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
ck_assert_int_eq(nr->head.nodeId.identifier.numeric, n2->head.nodeId.identifier.numeric);
ns.releaseNode(ns.context, nr);
ns.deleteNode(ns.context, n2);
@ -179,7 +185,8 @@ START_TEST(failToFindNonExistentNodeInUA_NodeStoreWithSeveralEntries) {
ns.insertNode(ns.context, n5, NULL);
UA_NodeId id = UA_NODEID_NUMERIC(0, 12);
const UA_Node* nr = ns.getNode(ns.context, &id);
const UA_Node* nr = ns.getNode(ns.context, &id, ~(UA_UInt32)0,
UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
ck_assert_uint_eq((uintptr_t)nr, 0);
}
END_TEST
@ -203,7 +210,8 @@ static void *profileGetThread(void *arg) {
for(UA_Int32 x = 0; x<test->rounds; x++) {
for(UA_Int32 i=test->min_val; i<max_val; i++) {
id.identifier.numeric = i+1;
const UA_Node *n = ns.getNode(ns.context, &id);
const UA_Node* n = ns.getNode(ns.context, &id, ~(UA_UInt32)0,
UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
ns.releaseNode(ns.context, n);
}
}
@ -238,7 +246,8 @@ START_TEST(profileGetDelete) {
UA_NodeId id = UA_NODEID_NULL;
for(size_t i = 0; i < N; i++) {
id.identifier.numeric = (UA_UInt32)i+1;
const UA_Node *node = ns.getNode(ns.context, &id);
const UA_Node *node = ns.getNode(ns.context, &id, ~(UA_UInt32)0,
UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH);
ns.releaseNode(ns.context, node);
}
end = clock();

View File

@ -0,0 +1,294 @@
/* 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 2021 (c) luibass92 <luibass92@live.it> (Author: Luigi Bassetta)
*/
#include <open62541/client.h>
#include <open62541/client_config_default.h>
#include <open62541/client_highlevel.h>
#include <open62541/plugin/historydata/history_data_backend.h>
#include <open62541/plugin/historydata/history_data_backend_memory.h>
#include <open62541/plugin/historydata/history_data_gathering_default.h>
#include <open62541/plugin/historydata/history_database_default.h>
#include <open62541/plugin/historydatabase.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include "client/ua_client_internal.h"
#include "server/ua_server_internal.h"
#include <check.h>
#include "testing_clock.h"
#include "testing_networklayers.h"
#include "thread_wrapper.h"
#include <stddef.h>
static UA_Server *server;
#ifdef UA_ENABLE_HISTORIZING
static UA_HistoryDataGathering *gathering;
#endif
static UA_Boolean running;
static THREAD_HANDLE server_thread;
static MUTEX_HANDLE serverMutex;
static UA_Client *client;
static UA_NodeId parentNodeId;
static UA_NodeId parentReferenceNodeId;
static UA_NodeId outNodeId;
static void serverMutexLock(void) {
if (!(MUTEX_LOCK(serverMutex))) {
fprintf(stderr, "Mutex cannot be locked.\n");
exit(1);
}
}
static void serverMutexUnlock(void) {
if (!(MUTEX_UNLOCK(serverMutex))) {
fprintf(stderr, "Mutex cannot be unlocked.\n");
exit(1);
}
}
THREAD_CALLBACK(serverloop) {
while(running) {
serverMutexLock();
UA_Server_run_iterate(server, false);
serverMutexUnlock();
}
return 0;
}
static void setup(void) {
if (!(MUTEX_INIT(serverMutex))) {
fprintf(stderr, "Server mutex was not created correctly.\n");
exit(1);
}
running = true;
server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefault(config);
#ifdef UA_ENABLE_HISTORIZING
gathering = (UA_HistoryDataGathering*)UA_calloc(1, sizeof(UA_HistoryDataGathering));
*gathering = UA_HistoryDataGathering_Circular(1);
config->historyDatabase = UA_HistoryDatabase_default(*gathering);
#endif
UA_StatusCode retval = UA_Server_run_startup(server);
if(retval != UA_STATUSCODE_GOOD) {
fprintf(stderr, "Error while calling Server_run_startup. %s\n", UA_StatusCode_name(retval));
UA_Server_delete(server);
exit(1);
}
THREAD_CREATE(server_thread, serverloop);
/* Define the attribute of the uint32 variable node */
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_UInt32 myUint32 = 40;
UA_Variant_setScalar(&attr.value, &myUint32, &UA_TYPES[UA_TYPES_UINT32]);
attr.description = UA_LOCALIZEDTEXT("en-US","the answer");
attr.displayName = UA_LOCALIZEDTEXT("en-US","the answer");
attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE | UA_ACCESSLEVELMASK_HISTORYREAD | UA_ACCESSLEVELMASK_HISTORYWRITE;
attr.historizing = true;
/* Add the variable node to the information model */
UA_NodeId uint32NodeId = UA_NODEID_STRING(1, "the.answer");
UA_QualifiedName uint32Name = UA_QUALIFIEDNAME(1, "the answer");
parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
UA_NodeId_init(&outNodeId);
retval = UA_Server_addVariableNode(server, uint32NodeId, parentNodeId,
parentReferenceNodeId, uint32Name,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, &outNodeId);
if (retval != UA_STATUSCODE_GOOD) {
fprintf(stderr, "Error adding variable node. %s\n", UA_StatusCode_name(retval));
UA_Server_delete(server);
exit(1);
}
client = UA_Client_new();
UA_ClientConfig_setDefault(UA_Client_getConfig(client));
retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
if (retval != UA_STATUSCODE_GOOD) {
fprintf(stderr, "Client can not connect to opc.tcp://localhost:4840. %s\n", UA_StatusCode_name(retval));
UA_Client_delete(client);
UA_Server_delete(server);
exit(1);
}
UA_Client_recv = client->connection.recv;
client->connection.recv = UA_Client_recvTesting;
}
static void teardown(void) {
/* cleanup */
UA_Client_disconnect(client);
UA_Client_delete(client);
running = false;
THREAD_JOIN(server_thread);
UA_NodeId_clear(&parentNodeId);
UA_NodeId_clear(&parentReferenceNodeId);
UA_NodeId_clear(&outNodeId);
UA_Server_run_shutdown(server);
UA_Server_delete(server);
#ifdef UA_ENABLE_HISTORIZING
UA_free(gathering);
#endif
if (!MUTEX_DESTROY(serverMutex)) {
fprintf(stderr, "Server mutex was not destroyed correctly.\n");
exit(1);
}
}
#ifdef UA_ENABLE_HISTORIZING
#include <stdio.h>
#include "ua_session.h"
static UA_StatusCode
setUInt32(UA_Client *thisClient, UA_NodeId node, UA_UInt32 value)
{
UA_Variant variant;
UA_Variant_setScalar(&variant, &value, &UA_TYPES[UA_TYPES_UINT32]);
return UA_Client_writeValueAttribute(thisClient, node, &variant);
}
void
Service_HistoryRead(UA_Server *server, UA_Session *session,
const UA_HistoryReadRequest *request,
UA_HistoryReadResponse *response);
static void
requestHistory(UA_DateTime start,
UA_DateTime end,
UA_HistoryReadResponse * response,
UA_UInt32 numValuesPerNode,
UA_Boolean returnBounds,
UA_ByteString *continuationPoint)
{
UA_ReadRawModifiedDetails *details = UA_ReadRawModifiedDetails_new();
details->startTime = start;
details->endTime = end;
details->isReadModified = false;
details->numValuesPerNode = numValuesPerNode;
details->returnBounds = returnBounds;
UA_HistoryReadValueId *valueId = UA_HistoryReadValueId_new();
UA_NodeId_copy(&outNodeId, &valueId->nodeId);
if (continuationPoint)
UA_ByteString_copy(continuationPoint, &valueId->continuationPoint);
UA_HistoryReadRequest request;
UA_HistoryReadRequest_init(&request);
request.historyReadDetails.encoding = UA_EXTENSIONOBJECT_DECODED;
request.historyReadDetails.content.decoded.type = &UA_TYPES[UA_TYPES_READRAWMODIFIEDDETAILS];
request.historyReadDetails.content.decoded.data = details;
request.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
request.nodesToReadSize = 1;
request.nodesToRead = valueId;
UA_LOCK(&server->serviceMutex);
Service_HistoryRead(server, &server->adminSession, &request, response);
UA_UNLOCK(&server->serviceMutex);
UA_HistoryReadRequest_clear(&request);
}
START_TEST(Server_HistorizingStrategyValueSet)
{
// init to a defined value
UA_StatusCode retval = setUInt32(client, outNodeId, 43);
ck_assert_str_eq(UA_StatusCode_name(retval), UA_StatusCode_name(UA_STATUSCODE_GOOD));
// set a data backend
UA_HistorizingNodeIdSettings setting;
setting.historizingBackend = UA_HistoryDataBackend_Memory_Circular(3, 10);
setting.maxHistoryDataResponseSize = 10;
setting.historizingUpdateStrategy = UA_HISTORIZINGUPDATESTRATEGY_VALUESET;
serverMutexLock();
retval = gathering->registerNodeId(server, gathering->context, &outNodeId, setting);
serverMutexUnlock();
ck_assert_str_eq(UA_StatusCode_name(retval), UA_StatusCode_name(UA_STATUSCODE_GOOD));
// Fill the data overcoming the buffer size and starting to write new values replacing the old ones.
// The circular buffer size is 10, the number of elements historized is 15 (from 0 to 14). So the final buffer will be:
//
// | 10 | 11 | 12 | 13 | 14 | 5 | 6 | 7 | 8 | 9 |
//
UA_fakeSleep(100);
UA_DateTime start = UA_DateTime_now();
UA_fakeSleep(100);
for (UA_UInt32 i = 0; i < 15; ++i) {
retval = setUInt32(client, outNodeId, i);
ck_assert_str_eq(UA_StatusCode_name(retval), UA_StatusCode_name(UA_STATUSCODE_GOOD));
UA_fakeSleep(100);
}
UA_DateTime end = UA_DateTime_now();
// request
UA_HistoryReadResponse response;
UA_HistoryReadResponse_init(&response);
requestHistory(start, end, &response, 0, false, NULL);
// test the response
ck_assert_str_eq(UA_StatusCode_name(response.responseHeader.serviceResult), UA_StatusCode_name(UA_STATUSCODE_GOOD));
ck_assert_uint_eq(response.resultsSize, 1);
for (size_t i = 0; i < response.resultsSize; ++i) {
ck_assert_str_eq(UA_StatusCode_name(response.results[i].statusCode), UA_StatusCode_name(UA_STATUSCODE_GOOD));
ck_assert_uint_eq(response.results[i].historyData.encoding, UA_EXTENSIONOBJECT_DECODED);
ck_assert(response.results[i].historyData.content.decoded.type == &UA_TYPES[UA_TYPES_HISTORYDATA]);
UA_HistoryData * data = (UA_HistoryData *)response.results[i].historyData.content.decoded.data;
ck_assert(data->dataValuesSize > 0);
for (size_t j = 0; j < data->dataValuesSize; ++j) {
ck_assert(data->dataValues[j].sourceTimestamp >= start && data->dataValues[j].sourceTimestamp < end);
ck_assert_uint_eq(data->dataValues[j].hasSourceTimestamp, true);
ck_assert_str_eq(UA_StatusCode_name(data->dataValues[j].status), UA_StatusCode_name(UA_STATUSCODE_GOOD));
ck_assert_uint_eq(data->dataValues[j].hasValue, true);
ck_assert(data->dataValues[j].value.type == &UA_TYPES[UA_TYPES_UINT32]);
UA_UInt32 * value = (UA_UInt32 *)data->dataValues[j].value.data;
if(j >= 5)
ck_assert_uint_eq(*value, j);
else
ck_assert_uint_eq(*value, j + 10);
}
}
UA_HistoryReadResponse_clear(&response);
UA_HistoryDataBackend_Memory_clear(&setting.historizingBackend);
}
END_TEST
#endif /*UA_ENABLE_HISTORIZING*/
static Suite* testSuite_Client(void)
{
Suite *s = suite_create("Server Historical Data");
TCase *tc_server = tcase_create("Server Historical Data Circular");
tcase_add_checked_fixture(tc_server, setup, teardown);
#ifdef UA_ENABLE_HISTORIZING
tcase_add_test(tc_server, Server_HistorizingStrategyValueSet);
#endif /* UA_ENABLE_HISTORIZING */
suite_add_tcase(s, tc_server);
return s;
}
int main(void)
{
Suite *s = testSuite_Client();
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;
}

View File

@ -24,10 +24,7 @@ static UA_Server *server;
static UA_NodeId readNodeIds[READNODES];
static void setup(void) {
UA_ServerConfig config;
memset(&config, 0, sizeof(UA_ServerConfig));
UA_Nodestore_HashMap(&config.nodestore);
server = UA_Server_newWithConfig(&config);
server = UA_Server_new();
}
static void teardown(void) {

View File

@ -150,6 +150,28 @@ function unit_tests {
make test ARGS="-V"
}
function unit_tests_32 {
mkdir -p build; cd build; rm -rf *
cmake -DCMAKE_BUILD_TYPE=Debug \
-DUA_BUILD_EXAMPLES=ON \
-DUA_BUILD_UNIT_TESTS=ON \
-DUA_ENABLE_DISCOVERY=ON \
-DUA_ENABLE_DISCOVERY_MULTICAST=ON \
-DUA_ENABLE_SUBSCRIPTIONS_EVENTS=ON \
-DUA_ENABLE_HISTORIZING=ON \
-DUA_ENABLE_JSON_ENCODING=ON \
-DUA_ENABLE_PUBSUB=ON \
-DUA_ENABLE_PUBSUB_DELTAFRAMES=ON \
-DUA_ENABLE_PUBSUB_INFORMATIONMODEL=ON \
-DUA_ENABLE_PUBSUB_MONITORING=ON \
-DUA_FORCE_32BIT=ON \
..
#-DUA_ENABLE_PUBSUB_ETH_UADP=ON \ # TODO: Enable this
make ${MAKEOPTS}
set_capabilities
make test ARGS="-V"
}
function unit_tests_mt {
mkdir -p build; cd build; rm -rf *
cmake -DCMAKE_BUILD_TYPE=Debug \
@ -210,6 +232,34 @@ function unit_tests_encryption_mbedtls_pubsub {
make test ARGS="-V"
}
##########################################
# Build and Run Unit Tests with Coverage #
##########################################
function unit_tests_with_coverage {
mkdir -p build; cd build; rm -rf *
cmake -DCMAKE_BUILD_TYPE=Debug \
-DUA_BUILD_EXAMPLES=ON \
-DUA_BUILD_UNIT_TESTS=ON \
-DUA_ENABLE_COVERAGE=ON \
-DUA_ENABLE_DISCOVERY=ON \
-DUA_ENABLE_DISCOVERY_MULTICAST=ON \
-DUA_ENABLE_SUBSCRIPTIONS_EVENTS=ON \
-DUA_ENABLE_HISTORIZING=ON \
-DUA_ENABLE_JSON_ENCODING=ON \
-DUA_ENABLE_PUBSUB=ON \
-DUA_ENABLE_PUBSUB_ETH_UADP=ON \
-DUA_ENABLE_PUBSUB_DELTAFRAMES=ON \
-DUA_ENABLE_PUBSUB_INFORMATIONMODEL=ON \
-DUA_ENABLE_PUBSUB_MONITORING=ON \
-DUA_ENABLE_ENCRYPTION=MBEDTLS \
..
make ${MAKEOPTS}
set_capabilities
make test ARGS="-V"
make gcov
}
##########################################
# Build and Run Unit Tests with Valgrind #
##########################################

View File

@ -17,7 +17,7 @@
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
INCLUDE( FindPkgConfig )
find_package(PkgConfig REQUIRED)
# Take care about check.pc settings
PKG_SEARCH_MODULE( CHECK check )

162
tools/cmake/FindGcov.cmake Normal file
View File

@ -0,0 +1,162 @@
# This file is part of CMake-codecov.
#
# Copyright (c)
# 2015-2020 RWTH Aachen University, Federal Republic of Germany
#
# See the LICENSE file in the package base directory for details
#
# Written by Alexander Haase, alexander.haase@rwth-aachen.de
#
# include required Modules
include(FindPackageHandleStandardArgs)
# Search for gcov binary.
set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET})
set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY})
get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
foreach (LANG ${ENABLED_LANGUAGES})
# Gcov evaluation is dependent on the used compiler. Check gcov support for
# each compiler that is used. If gcov binary was already found for this
# compiler, do not try to find it again.
if (NOT GCOV_${CMAKE_${LANG}_COMPILER_ID}_BIN)
get_filename_component(COMPILER_PATH "${CMAKE_${LANG}_COMPILER}" PATH)
if ("${CMAKE_${LANG}_COMPILER_ID}" STREQUAL "GNU")
# Some distributions like OSX (homebrew) ship gcov with the compiler
# version appended as gcov-x. To find this binary we'll build the
# suggested binary name with the compiler version.
string(REGEX MATCH "^[0-9]+" GCC_VERSION
"${CMAKE_${LANG}_COMPILER_VERSION}")
find_program(GCOV_BIN NAMES gcov-${GCC_VERSION} gcov
HINTS ${COMPILER_PATH})
elseif ("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "^(Apple)?Clang$")
# Some distributions like Debian ship llvm-cov with the compiler
# version appended as llvm-cov-x.y. To find this binary we'll build
# the suggested binary name with the compiler version.
string(REGEX MATCH "^[0-9]+.[0-9]+" LLVM_VERSION
"${CMAKE_${LANG}_COMPILER_VERSION}")
# llvm-cov prior version 3.5 seems to be not working with coverage
# evaluation tools, but these versions are compatible with the gcc
# gcov tool.
if(LLVM_VERSION VERSION_GREATER 3.4)
find_program(LLVM_COV_BIN NAMES "llvm-cov-${LLVM_VERSION}"
"llvm-cov" HINTS ${COMPILER_PATH})
mark_as_advanced(LLVM_COV_BIN)
if (LLVM_COV_BIN)
find_program(LLVM_COV_WRAPPER "llvm-cov-wrapper" PATHS
${CMAKE_MODULE_PATH})
if (LLVM_COV_WRAPPER)
set(GCOV_BIN "${LLVM_COV_WRAPPER}" CACHE FILEPATH "")
# set additional parameters
set(GCOV_${CMAKE_${LANG}_COMPILER_ID}_ENV
"LLVM_COV_BIN=${LLVM_COV_BIN}" CACHE STRING
"Environment variables for llvm-cov-wrapper.")
mark_as_advanced(GCOV_${CMAKE_${LANG}_COMPILER_ID}_ENV)
endif ()
endif ()
endif ()
if (NOT GCOV_BIN)
# Fall back to gcov binary if llvm-cov was not found or is
# incompatible. This is the default on OSX, but may crash on
# recent Linux versions.
find_program(GCOV_BIN gcov HINTS ${COMPILER_PATH})
endif ()
endif ()
if (GCOV_BIN)
set(GCOV_${CMAKE_${LANG}_COMPILER_ID}_BIN "${GCOV_BIN}" CACHE STRING
"${LANG} gcov binary.")
if (NOT CMAKE_REQUIRED_QUIET)
message("-- Found gcov evaluation for "
"${CMAKE_${LANG}_COMPILER_ID}: ${GCOV_BIN}")
endif()
unset(GCOV_BIN CACHE)
endif ()
endif ()
endforeach ()
# Add a new global target for all gcov targets. This target could be used to
# generate the gcov files for the whole project instead of calling <TARGET>-gcov
# for each target.
if (NOT TARGET gcov)
add_custom_target(gcov)
endif (NOT TARGET gcov)
# This function will add gcov evaluation for target <TNAME>. Only sources of
# this target will be evaluated and no dependencies will be added. It will call
# Gcov on any source file of <TNAME> once and store the gcov file in the same
# directory.
function (add_gcov_target TNAME)
get_target_property(TBIN_DIR ${TNAME} BINARY_DIR)
set(TDIR ${TBIN_DIR}/CMakeFiles/${TNAME}.dir)
# We don't have to check, if the target has support for coverage, thus this
# will be checked by add_coverage_target in Findcoverage.cmake. Instead we
# have to determine which gcov binary to use.
get_target_property(TSOURCES ${TNAME} SOURCES)
set(SOURCES "")
set(TCOMPILER "")
foreach (FILE ${TSOURCES})
codecov_path_of_source(${FILE} FILE)
if (NOT "${FILE}" STREQUAL "")
codecov_lang_of_source(${FILE} LANG)
if (NOT "${LANG}" STREQUAL "")
list(APPEND SOURCES "${FILE}")
set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID})
endif ()
endif ()
endforeach ()
# If no gcov binary was found, coverage data can't be evaluated.
if (NOT GCOV_${TCOMPILER}_BIN)
message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.")
return()
endif ()
set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}")
set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}")
set(BUFFER "")
set(NULL_DEVICE "/dev/null")
if(WIN32)
set(NULL_DEVICE "NUL")
endif()
foreach(FILE ${SOURCES})
get_filename_component(FILE_PATH "${TDIR}/${FILE}" PATH)
# call gcov
add_custom_command(OUTPUT ${TDIR}/${FILE}.gcov
COMMAND ${GCOV_ENV} ${GCOV_BIN} -p ${TDIR}/${FILE}.gcno > ${NULL_DEVICE}
DEPENDS ${TNAME} ${TDIR}/${FILE}.gcno
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
list(APPEND BUFFER ${TDIR}/${FILE}.gcov)
endforeach()
# add target for gcov evaluation of <TNAME>
add_custom_target(${TNAME}-gcov DEPENDS ${BUFFER})
# add evaluation target to the global gcov target.
add_dependencies(gcov ${TNAME}-gcov)
endfunction (add_gcov_target)

View File

@ -0,0 +1,265 @@
# This file is part of CMake-codecov.
#
# Copyright (c)
# 2015-2020 RWTH Aachen University, Federal Republic of Germany
#
# See the LICENSE file in the package base directory for details
#
# Written by Alexander Haase, alexander.haase@rwth-aachen.de
#
set(COVERAGE_FLAG_CANDIDATES
# gcc and clang
"-O0 -g -fprofile-arcs -ftest-coverage"
# gcc and clang fallback
"-O0 -g --coverage"
)
# Add coverage support for target ${TNAME} and register target for coverage
# evaluation. If coverage is disabled or not supported, this function will
# simply do nothing.
#
# Note: This function is only a wrapper to define this function always, even if
# coverage is not supported by the compiler or disabled. This function must
# be defined here, because the module will be exited, if there is no coverage
# support by the compiler or it is disabled by the user.
function (add_coverage TNAME)
# only add coverage for target, if coverage is support and enabled.
if (ENABLE_COVERAGE)
foreach (TNAME ${ARGV})
add_coverage_target(${TNAME})
endforeach ()
endif ()
endfunction (add_coverage)
# Add global target to gather coverage information after all targets have been
# added. Other evaluation functions could be added here, after checks for the
# specific module have been passed.
#
# Note: This function is only a wrapper to define this function always, even if
# coverage is not supported by the compiler or disabled. This function must
# be defined here, because the module will be exited, if there is no coverage
# support by the compiler or it is disabled by the user.
function (coverage_evaluate)
# add lcov evaluation
if (LCOV_FOUND)
lcov_capture_initial()
lcov_capture()
endif (LCOV_FOUND)
endfunction ()
# Exit this module, if coverage is disabled. add_coverage is defined before this
# return, so this module can be exited now safely without breaking any build-
# scripts.
if (NOT ENABLE_COVERAGE)
return()
endif ()
# Find the required flags foreach language.
set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET})
set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY})
get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
foreach (LANG ${ENABLED_LANGUAGES})
if (NOT ${LANG} MATCHES "^(C|CXX|Fortran)$")
message(STATUS "Skipping coverage for unsupported language: ${LANG}")
continue()
endif ()
# Coverage flags are not dependent on language, but the used compiler. So
# instead of searching flags foreach language, search flags foreach compiler
# used.
set(COMPILER ${CMAKE_${LANG}_COMPILER_ID})
if (NOT COVERAGE_${COMPILER}_FLAGS)
foreach (FLAG ${COVERAGE_FLAG_CANDIDATES})
if(NOT CMAKE_REQUIRED_QUIET)
message(STATUS "Try ${COMPILER} code coverage flag = [${FLAG}]")
endif()
set(CMAKE_REQUIRED_FLAGS "${FLAG}")
unset(COVERAGE_FLAG_DETECTED CACHE)
if (${LANG} STREQUAL "C")
include(CheckCCompilerFlag)
check_c_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED)
elseif (${LANG} STREQUAL "CXX")
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED)
elseif (${LANG} STREQUAL "Fortran")
# CheckFortranCompilerFlag was introduced in CMake 3.x. To be
# compatible with older Cmake versions, we will check if this
# module is present before we use it. Otherwise we will define
# Fortran coverage support as not available.
include(CheckFortranCompilerFlag OPTIONAL
RESULT_VARIABLE INCLUDED)
if (INCLUDED)
check_fortran_compiler_flag("${FLAG}"
COVERAGE_FLAG_DETECTED)
elseif (NOT CMAKE_REQUIRED_QUIET)
message("-- Performing Test COVERAGE_FLAG_DETECTED")
message("-- Performing Test COVERAGE_FLAG_DETECTED - Failed"
" (Check not supported)")
endif ()
endif()
if (COVERAGE_FLAG_DETECTED)
set(COVERAGE_${COMPILER}_FLAGS "${FLAG}"
CACHE STRING "${COMPILER} flags for code coverage.")
mark_as_advanced(COVERAGE_${COMPILER}_FLAGS)
break()
else ()
message(WARNING "Code coverage is not available for ${COMPILER}"
" compiler. Targets using this compiler will be "
"compiled without it.")
endif ()
endforeach ()
endif ()
endforeach ()
set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_SAVE})
# Helper function to get the language of a source file.
function (codecov_lang_of_source FILE RETURN_VAR)
# Usually, only the last extension of the file should be checked, to avoid
# template files (i.e. *.t.cpp) are checked with the full file extension.
# However, this feature requires CMake 3.14 or later.
set(EXT_COMP "LAST_EXT")
if(${CMAKE_VERSION} VERSION_LESS "3.14.0")
set(EXT_COMP "EXT")
endif()
get_filename_component(FILE_EXT "${FILE}" ${EXT_COMP})
string(TOLOWER "${FILE_EXT}" FILE_EXT)
string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT)
get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
foreach (LANG ${ENABLED_LANGUAGES})
list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP)
if (NOT ${TEMP} EQUAL -1)
set(${RETURN_VAR} "${LANG}" PARENT_SCOPE)
return()
endif ()
endforeach()
set(${RETURN_VAR} "" PARENT_SCOPE)
endfunction ()
# Helper function to get the relative path of the source file destination path.
# This path is needed by FindGcov and FindLcov cmake files to locate the
# captured data.
function (codecov_path_of_source FILE RETURN_VAR)
string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _source ${FILE})
# If expression was found, SOURCEFILE is a generator-expression for an
# object library. Currently we found no way to call this function automatic
# for the referenced target, so it must be called in the directoryso of the
# object library definition.
if (NOT "${_source}" STREQUAL "")
set(${RETURN_VAR} "" PARENT_SCOPE)
return()
endif ()
string(REPLACE "${CMAKE_CURRENT_BINARY_DIR}/" "" FILE "${FILE}")
if(IS_ABSOLUTE ${FILE})
file(RELATIVE_PATH FILE ${CMAKE_CURRENT_SOURCE_DIR} ${FILE})
endif()
# get the right path for file
string(REPLACE ".." "__" PATH "${FILE}")
set(${RETURN_VAR} "${PATH}" PARENT_SCOPE)
endfunction()
# Add coverage support for target ${TNAME} and register target for coverage
# evaluation.
function(add_coverage_target TNAME)
# Check if all sources for target use the same compiler. If a target uses
# e.g. C and Fortran mixed and uses different compilers (e.g. clang and
# gfortran) this can trigger huge problems, because different compilers may
# use different implementations for code coverage.
get_target_property(TSOURCES ${TNAME} SOURCES)
set(TARGET_COMPILER "")
set(ADDITIONAL_FILES "")
foreach (FILE ${TSOURCES})
# If expression was found, FILE is a generator-expression for an object
# library. Object libraries will be ignored.
string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE})
if ("${_file}" STREQUAL "")
codecov_lang_of_source(${FILE} LANG)
if (LANG)
list(APPEND TARGET_COMPILER ${CMAKE_${LANG}_COMPILER_ID})
list(APPEND ADDITIONAL_FILES "${FILE}.gcno")
list(APPEND ADDITIONAL_FILES "${FILE}.gcda")
endif ()
endif ()
endforeach ()
list(REMOVE_DUPLICATES TARGET_COMPILER)
list(LENGTH TARGET_COMPILER NUM_COMPILERS)
if (NUM_COMPILERS GREATER 1)
message(WARNING "Can't use code coverage for target ${TNAME}, because "
"it will be compiled by incompatible compilers. Target will be "
"compiled without code coverage.")
return()
elseif (NUM_COMPILERS EQUAL 0)
message(WARNING "Can't use code coverage for target ${TNAME}, because "
"it uses an unknown compiler. Target will be compiled without "
"code coverage.")
return()
elseif (NOT DEFINED "COVERAGE_${TARGET_COMPILER}_FLAGS")
# A warning has been printed before, so just return if flags for this
# compiler aren't available.
return()
endif()
# enable coverage for target
set_property(TARGET ${TNAME} APPEND_STRING
PROPERTY COMPILE_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}")
set_property(TARGET ${TNAME} APPEND_STRING
PROPERTY LINK_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}")
# Add gcov files generated by compiler to clean target.
set(CLEAN_FILES "")
foreach (FILE ${ADDITIONAL_FILES})
codecov_path_of_source(${FILE} FILE)
list(APPEND CLEAN_FILES "CMakeFiles/${TNAME}.dir/${FILE}")
endforeach()
if(${CMAKE_VERSION} VERSION_LESS "3.15.0")
set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES
"${CLEAN_FILES}")
else()
set_directory_properties(PROPERTIES ADDITIONAL_CLEAN_FILES
"${CLEAN_FILES}")
endif()
add_gcov_target(${TNAME})
endfunction(add_coverage_target)
# Include modules for parsing the collected data and output it in a readable
# format (like gcov and lcov).
find_package(Gcov)

View File

@ -246,7 +246,7 @@ class CGenerator(object):
return "typedef enum {\n " + ",\n ".join(
map(lambda kv: makeCIdentifier("UA_" + enum.name.upper() + "_" + kv[0].upper()) +
" = " + kv[1], values)) + \
",\n __UA_{0}_FORCE32BIT = 0x7fffffff\n".format(makeCIdentifier(enum.name.upper())) + "} " + \
"{}\n __UA_{}_FORCE32BIT = 0x7fffffff\n".format("," if len(enum.elements) != 0 else "", makeCIdentifier(enum.name.upper())) + "} " + \
"UA_{0};\nUA_STATIC_ASSERT(sizeof(UA_{0}) == sizeof(UA_Int32), enum_must_be_32bit);".format(
makeCIdentifier(enum.name))

View File

@ -559,6 +559,9 @@ class DataTypeNode(Node):
isOptional = str(av)
elif at == "ArrayDimensions":
arrayDimensions = int(av)
elif at == "AllowSubTypes":
# ignore
continue
else:
logger.warn("Unknown Field Attribute " + str(at))
# This can either be an enumeration OR a structure, not both.