mirror of
https://github.com/open62541/open62541.git
synced 2025-06-03 04:00:21 +00:00
Merge branch 'master' into 1.3
This commit is contained in:
commit
2f2b9fea7d
6
.github/workflows/build_linux.yml
vendored
6
.github/workflows/build_linux.yml
vendored
@ -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
32
.github/workflows/coverage.yml
vendored
Normal 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
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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);
|
@ -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,
|
@ -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
596
arch/eventloop_posix.c
Normal 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
99
arch/eventloop_posix.h
Normal 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
788
arch/eventloop_posix_tcp.c
Normal 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;
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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
2
deps/ua-nodeset
vendored
@ -1 +1 @@
|
||||
Subproject commit 393b633468a5d1d062dd253e1488d1d8ba335b6f
|
||||
Subproject commit f71b3f411d5cb16097c3ae0c744f67ad45535ffb
|
@ -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")
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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]);
|
||||
|
@ -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]);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
133
examples/tutorial_server_historicaldata_circular.c
Normal file
133
examples/tutorial_server_historicaldata_circular.c
Normal 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;
|
||||
}
|
@ -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;
|
||||
|
302
include/open62541/plugin/eventloop.h
Normal file
302
include/open62541/plugin/eventloop.h
Normal 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_ */
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 = ®isterNodeId_gathering_circular;
|
||||
return gathering;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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_ */
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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)))
|
||||
|
@ -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)))
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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(¤tNetworkMessage, 0, sizeof(UA_NetworkMessage));
|
||||
UA_StatusCode rv;
|
||||
size_t payLoadPosition = 0;
|
||||
rv = UA_NetworkMessage_decodeHeaders(
|
||||
buffer, &payLoadPosition, ¤tNetworkMessage);
|
||||
|
||||
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,
|
||||
¤tNetworkMessage,
|
||||
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(¤tNetworkMessage);
|
||||
#endif
|
||||
|
||||
/* Decode only the necessary offset and update the networkMessage */
|
||||
UA_StatusCode res =
|
||||
UA_NetworkMessage_updateBufferedNwMessage(&dataSetReader->bufferedMessage,
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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]);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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, ¤t->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, ¤t->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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 *
|
||||
|
@ -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; \
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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, ©V);
|
||||
@ -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);
|
||||
}
|
||||
|
@ -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( \
|
||||
|
@ -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
65
tests/check_eventloop.c
Normal 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
256
tests/check_eventloop_tcp.c
Normal 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(¶ms[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(¶ms[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;
|
||||
}
|
@ -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]);
|
||||
|
@ -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"
|
||||
|
843
tests/pubsub/check_pubsub_encrypted_rt_levels.c
Normal file
843
tests/pubsub/check_pubsub_encrypted_rt_levels.c
Normal 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(¤tNetworkMessage, 0, sizeof(UA_NetworkMessage));
|
||||
size_t payLoadPosition = 0;
|
||||
UA_NetworkMessage_decodeHeaders(&buffer, &payLoadPosition, ¤tNetworkMessage);
|
||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||
verifyAndDecryptNetworkMessage(&config->logger,
|
||||
&buffer,
|
||||
&payLoadPosition,
|
||||
¤tNetworkMessage,
|
||||
readerGroup);
|
||||
UA_NetworkMessage_clear(¤tNetworkMessage);
|
||||
size_t currentPosition = 0;
|
||||
/* Decode only the necessary offset and update the networkMessage */
|
||||
if(UA_NetworkMessage_updateBufferedNwMessage(&dataSetReader->bufferedMessage, &buffer, ¤tPosition) != 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;
|
||||
}
|
@ -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();
|
||||
|
294
tests/server/check_server_historical_data_circular.c
Normal file
294
tests/server/check_server_historical_data_circular.c
Normal 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;
|
||||
}
|
@ -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) {
|
||||
|
50
tools/ci.sh
50
tools/ci.sh
@ -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 #
|
||||
##########################################
|
||||
|
@ -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
162
tools/cmake/FindGcov.cmake
Normal 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)
|
265
tools/cmake/Findcodecov.cmake
Normal file
265
tools/cmake/Findcodecov.cmake
Normal 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)
|
@ -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))
|
||||
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user