Merge remote-tracking branch 'origin/master' into merge_14_master_25

This commit is contained in:
Julius Pfrommer 2025-01-20 16:09:35 +01:00
commit 0e62e11afb
210 changed files with 12227 additions and 19305 deletions

View File

@ -10,7 +10,7 @@ updates:
schedule: schedule:
interval: "weekly" interval: "weekly"
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: ".github" directory: "/"
target-branch: "master" target-branch: "master"
schedule: schedule:
interval: "weekly" interval: "weekly"

View File

@ -2,9 +2,9 @@ name: "Code Scanning"
on: on:
push: push:
branches: [ master, 1\.1, 1\.2 ] branches: [ master, 1.* ]
pull_request: pull_request:
branches: [ master, 1\.1, 1\.2 ] branches: [ master, 1.* ]
paths-ignore: paths-ignore:
- '**/*.md' - '**/*.md'
- '**/*.txt' - '**/*.txt'

View File

@ -6,8 +6,6 @@ on:
- v1.* - v1.*
pull_request: pull_request:
branches: [ main, master ] branches: [ main, master ]
tags:
- v1.*
jobs: jobs:
run: run:
@ -18,7 +16,7 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install -y -qq python3-sphinx graphviz check libmbedtls-dev mosquitto sudo apt-get install -y -qq python3-sphinx graphviz check libmbedtls-dev mosquitto
- name: Fetch - name: Fetch
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
submodules: true submodules: true
- name: Execute Tests - name: Execute Tests
@ -29,4 +27,4 @@ jobs:
run: | run: |
tree . tree .
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v5

View File

@ -20,7 +20,7 @@ jobs:
- name: Install Dependencies - name: Install Dependencies
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y -qq python3-sphinx graphviz python-six texlive-fonts-recommended texlive-latex-extra texlive-plain-generic texlive-latex-recommended latexmk texlive-fonts-extra sudo apt-get install -y -qq python3-sphinx graphviz texlive-fonts-recommended texlive-latex-extra texlive-plain-generic texlive-latex-recommended latexmk texlive-fonts-extra
pip install sphinx-rtd-theme pip install sphinx-rtd-theme
- name: Build Documentation - name: Build Documentation
run: source tools/ci/ci.sh && build_docs_pdf run: source tools/ci/ci.sh && build_docs_pdf
@ -33,7 +33,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Get the current branch name - name: Get the current branch name
shell: bash shell: bash
run: echo "::set-output name=branch::${GITHUB_REF##*/}" run: echo "branch=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT"
id: myref id: myref
- name: Copy Documentation Files to Website repository - name: Copy Documentation Files to Website repository
run: | run: |

14
.gitignore vendored
View File

@ -58,27 +58,13 @@ coverage_report
/.settings /.settings
.autotools .autotools
test-driver test-driver
include/opcua.h
include/ua_namespace_0.h
src/opcua.c
src/ua_namespace_0.c
tests/check_builtin
tests/check_create
tests/check_decode
tests/check_delete
tests/check_encode
tests/check_memory
tests/check_namespace
tests/coverage
tests/**/CMakeFiles tests/**/CMakeFiles
Testing Testing
Makefile Makefile
/CMakeCache.txt /CMakeCache.txt
/CMakeFiles/ /CMakeFiles/
/cmake_install.cmake /cmake_install.cmake
doc/
doc_src/ doc_src/
examples/
/exampleClient /exampleClient
/exampleClient_legacy /exampleClient_legacy
/src_generated/ /src_generated/

View File

@ -3,6 +3,32 @@ refactorings and bug fixes are not reported here.
# Development # Development
### New Realtime-PubSub model
The new Realtime-PubSub model builds upon two new public APIS: (i) The
possibility to integrate custom state machines to control the state of
PubSub-components and (ii) the generation of offset-tables for the content of
PubSub NetworkMessages. The approach is described in
/examples/pubsub_realtime/README.md with code examples in the same folder.
### JSON encoding changed with the v1.05 specification
The JSON encoding was reworked for the v1.05 version of the OPC UA
specification. The change breaks backwards compatibility. The legacy JSON
encoding is still available throught the UA_ENABLE_JSON_ENCODING_LEGACY build
option. This legacy feature wil get removed at some point in the future.
### PubSub NetworkMessage structure has an explicit DataSetMessageSize
In prior versions of the standard, when the PayloadHeader was missing, the
PubSub NetworkMessage needed to have exactly one DataSetMessage. In the current
standard there can be several DataSetMessages also without a PayloadHeader. To
allow for this the UA_NetworkMessage structure now contains an explicit
DataSetMessageSize field outside of the PayloadHeader.
Note that code could before rely on the default of one DataSetMessage. This code
needs to be revised to set the new DataSetMessageSize field to one.
### PubSub Components are disabled initially ### PubSub Components are disabled initially
PubSubComponents (PubSubConnections, ReaderGroups, ...) are no longer enabled PubSubComponents (PubSubConnections, ReaderGroups, ...) are no longer enabled

View File

@ -48,9 +48,11 @@ set(OPEN62541_VER_PATCH 8)
set(OPEN62541_VER_LABEL "-undefined") # like "-rc1" or "-g4538abcd" or "-g4538abcd-dirty" set(OPEN62541_VER_LABEL "-undefined") # like "-rc1" or "-g4538abcd" or "-g4538abcd-dirty"
set(OPEN62541_VER_COMMIT "unknown-commit") set(OPEN62541_VER_COMMIT "unknown-commit")
# Overwrite the version information based on git if available # Overwrite the version information based on git if available and we are the main cmake project.
include(SetGitBasedVersion) if (CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
set_open62541_version() include(SetGitBasedVersion)
set_open62541_version()
endif()
# Examples for the version string are: # Examples for the version string are:
# v1.2 # v1.2
@ -71,11 +73,11 @@ set(UA_ARCHITECTURE "" CACHE STRING "Architecture to build open62541 for")
SET_PROPERTY(CACHE UA_ARCHITECTURE PROPERTY STRINGS "" ${UA_ARCHITECTURES}) SET_PROPERTY(CACHE UA_ARCHITECTURE PROPERTY STRINGS "" ${UA_ARCHITECTURES})
if("${UA_ARCHITECTURE}" STREQUAL "") if("${UA_ARCHITECTURE}" STREQUAL "")
if(UNIX) if(WIN32)
set(UA_ARCHITECTURE "posix" CACHE STRING "" FORCE)
elseif(WIN32)
set(UA_ARCHITECTURE "win32" CACHE STRING "" FORCE) set(UA_ARCHITECTURE "win32" CACHE STRING "" FORCE)
endif(UNIX) else()
set(UA_ARCHITECTURE "posix" CACHE STRING "" FORCE)
endif()
endif() endif()
message(STATUS "The selected architecture is: ${UA_ARCHITECTURE}") message(STATUS "The selected architecture is: ${UA_ARCHITECTURE}")
@ -115,6 +117,12 @@ option(UA_ENABLE_NODESETLOADER "Enable nodesetLoader public API" OFF)
option(UA_ENABLE_GDS_PUSHMANAGEMENT "Enable GDS pushManagement support" OFF) option(UA_ENABLE_GDS_PUSHMANAGEMENT "Enable GDS pushManagement support" OFF)
option(UA_ENABLE_DATATYPES_ALL "Generate all datatypes for namespace zero (uses more binary space)" ON) option(UA_ENABLE_DATATYPES_ALL "Generate all datatypes for namespace zero (uses more binary space)" ON)
option(UA_ENABLE_JSON_ENCODING_LEGACY "Use the old JSON encoding before the v1.05 spec" OFF)
mark_as_advanced(UA_ENABLE_JSON_ENCODING_LEGACY)
if(UA_ENABLE_JSON_ENCODING_LEGACY AND NOT UA_ENABLE_JSON_ENCODING)
message(FATAL_ERROR "UA_ENABLE_JSON_ENCODING_LEGACY requires UA_ENABLE_JSON_ENCODING also")
endif()
if(UA_INFORMATION_MODEL_AUTOLOAD AND NOT UA_BUILD_FUZZING) if(UA_INFORMATION_MODEL_AUTOLOAD AND NOT UA_BUILD_FUZZING)
set(UA_ENABLE_NODESET_INJECTOR ON) set(UA_ENABLE_NODESET_INJECTOR ON)
endif() endif()
@ -139,8 +147,42 @@ mark_as_advanced(UA_ENABLE_PARSING)
option(UA_ENABLE_INLINABLE_EXPORT "Export 'static inline' methods as regular API" OFF) option(UA_ENABLE_INLINABLE_EXPORT "Export 'static inline' methods as regular API" OFF)
mark_as_advanced(UA_ENABLE_INLINABLE_EXPORT) mark_as_advanced(UA_ENABLE_INLINABLE_EXPORT)
option(UA_ENABLE_DISCOVERY_MULTICAST "Enable Discovery Service with multicast support (LDS-ME)" OFF) # mDNS provider
set(UA_MDNS_PLUGINS "MDNSD" "AVAHI")
set(UA_ENABLE_DISCOVERY_MULTICAST "OFF" CACHE STRING "mDNS discovery support")
mark_as_advanced(UA_ENABLE_DISCOVERY_MULTICAST) mark_as_advanced(UA_ENABLE_DISCOVERY_MULTICAST)
SET_PROPERTY(CACHE UA_ENABLE_DISCOVERY_MULTICAST PROPERTY STRINGS "OFF" ${UA_MDNS_PLUGINS})
option(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD "Enable mDNS discovery support (uses mdnsd)" OFF)
mark_as_advanced(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD)
option(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI "Enable mDNS discovery support (uses Avahi)" OFF)
mark_as_advanced(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
list (FIND UA_MDNS_PLUGINS ${UA_ENABLE_DISCOVERY_MULTICAST} _tmp)
if(UA_ENABLE_DISCOVERY_MULTICAST STREQUAL "OFF" OR ${_tmp} GREATER -1)
set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD OFF)
set(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI OFF)
if(UA_ENABLE_DISCOVERY_MULTICAST STREQUAL "MDNSD")
set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD ON)
elseif(UA_ENABLE_DISCOVERY_MULTICAST STREQUAL "AVAHI")
set(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI ON)
endif()
# Only for backward compatability
elseif(UA_ENABLE_DISCOVERY_MULTICAST OR UA_ENABLE_DISCOVERY_MULTICAST STREQUAL "ON")
message(DEPRECATION "Set UA_ENABLE_DISCOVERY_MULTICAST to the desired mDNS library." )
if(NOT UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD ON)
endif()
else()
message(DEPRECATION "Set UA_ENABLE_DISCOVERY_MULTICAST to the desired mDNS library." )
if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD)
set(UA_ENABLE_DISCOVERY_MULTICAST "MDNSD")
set(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI OFF)
endif()
if(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
set(UA_ENABLE_DISCOVERY_MULTICAST "AVAHI")
set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD OFF)
endif()
endif()
# security provider # security provider
set(UA_ENCRYPTION_PLUGINS "MBEDTLS" "OPENSSL" "LIBRESSL") set(UA_ENCRYPTION_PLUGINS "MBEDTLS" "OPENSSL" "LIBRESSL")
@ -231,7 +273,8 @@ endif()
if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ OR UA_BUILD_FUZZING_CORPUS) if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ OR UA_BUILD_FUZZING_CORPUS)
# Force enable options not passed in the build script, to also fuzzy-test this code # Force enable options not passed in the build script, to also fuzzy-test this code
set(UA_ENABLE_DISCOVERY ON CACHE STRING "" FORCE) set(UA_ENABLE_DISCOVERY ON CACHE STRING "" FORCE)
set(UA_ENABLE_DISCOVERY_MULTICAST ON CACHE STRING "" FORCE) set(UA_ENABLE_DISCOVERY_MULTICAST MDNSD CACHE STRING "" FORCE)
set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD ON CACHE STRING "" FORCE)
set(UA_ENABLE_ENCRYPTION ON CACHE STRING "OFF" FORCE) set(UA_ENABLE_ENCRYPTION ON CACHE STRING "OFF" FORCE)
set(UA_ENABLE_ENCRYPTION_MBEDTLS ON CACHE STRING "" FORCE) set(UA_ENABLE_ENCRYPTION_MBEDTLS ON CACHE STRING "" FORCE)
set(UA_ENABLE_HISTORIZING ON CACHE STRING "" FORCE) set(UA_ENABLE_HISTORIZING ON CACHE STRING "" FORCE)
@ -252,7 +295,7 @@ endif()
if(UA_ENABLE_DISCOVERY_MULTICAST AND NOT UA_ENABLE_DISCOVERY) if(UA_ENABLE_DISCOVERY_MULTICAST AND NOT UA_ENABLE_DISCOVERY)
MESSAGE(WARNING "UA_ENABLE_DISCOVERY_MULTICAST is enabled, but not UA_ENABLE_DISCOVERY. UA_ENABLE_DISCOVERY_MULTICAST will be set to OFF") MESSAGE(WARNING "UA_ENABLE_DISCOVERY_MULTICAST is enabled, but not UA_ENABLE_DISCOVERY. UA_ENABLE_DISCOVERY_MULTICAST will be set to OFF")
SET(UA_ENABLE_DISCOVERY_MULTICAST OFF CACHE BOOL "Enable Discovery Service with multicast support (LDS-ME)" FORCE) SET(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD OFF CACHE BOOL "Enable Discovery Service with multicast support (LDS-ME)" FORCE)
endif() endif()
# Advanced options # Advanced options
@ -498,6 +541,13 @@ if(MINGW)
list(APPEND open62541_LIBRARIES ws2_32 ssp) list(APPEND open62541_LIBRARIES ws2_32 ssp)
endif() endif()
if(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
find_package(PkgConfig REQUIRED)
pkg_search_module(AVAHI REQUIRED avahi-client avahi-common)
include_directories(${AVAHI_INCLUDE_DIRS})
list(APPEND open62541_LIBRARIES "${AVAHI_LIBRARIES}")
endif()
##################### #####################
# Compiler Settings # # Compiler Settings #
##################### #####################
@ -691,7 +741,7 @@ file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/src_generated")
# Generate the config.h # Generate the config.h
configure_file(include/open62541/config.h.in ${PROJECT_BINARY_DIR}/src_generated/open62541/config.h) configure_file(include/open62541/config.h.in ${PROJECT_BINARY_DIR}/src_generated/open62541/config.h)
if(UA_ENABLE_DISCOVERY_MULTICAST) if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD)
include(GenerateExportHeader) include(GenerateExportHeader)
set(MDNSD_LOGLEVEL 300 CACHE STRING "Level at which logs shall be reported" FORCE) set(MDNSD_LOGLEVEL 300 CACHE STRING "Level at which logs shall be reported" FORCE)
@ -742,6 +792,7 @@ set(lib_headers ${PROJECT_SOURCE_DIR}/deps/open62541_queue.h
${PROJECT_SOURCE_DIR}/deps/base64.h ${PROJECT_SOURCE_DIR}/deps/base64.h
${PROJECT_SOURCE_DIR}/deps/dtoa.h ${PROJECT_SOURCE_DIR}/deps/dtoa.h
${PROJECT_SOURCE_DIR}/deps/mp_printf.h ${PROJECT_SOURCE_DIR}/deps/mp_printf.h
${PROJECT_SOURCE_DIR}/deps/utf8.h
${PROJECT_SOURCE_DIR}/deps/itoa.h ${PROJECT_SOURCE_DIR}/deps/itoa.h
${PROJECT_SOURCE_DIR}/deps/ziptree.h ${PROJECT_SOURCE_DIR}/deps/ziptree.h
${PROJECT_SOURCE_DIR}/src/ua_types_encoding_binary.h ${PROJECT_SOURCE_DIR}/src/ua_types_encoding_binary.h
@ -758,6 +809,10 @@ set(lib_headers ${PROJECT_SOURCE_DIR}/deps/open62541_queue.h
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_internal.h ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_internal.h
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_keystorage.h) ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_keystorage.h)
if(UA_ENABLE_ENCRYPTION AND UA_ARCHITECTURE_WIN32)
list(APPEND lib_headers ${PROJECT_SOURCE_DIR}/deps/tr_dirent.h)
endif()
set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
${PROJECT_SOURCE_DIR}/src/ua_types_encoding_binary.c ${PROJECT_SOURCE_DIR}/src/ua_types_encoding_binary.c
${PROJECT_BINARY_DIR}/src_generated/open62541/types_generated.c ${PROJECT_BINARY_DIR}/src_generated/open62541/types_generated.c
@ -812,6 +867,7 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
${PROJECT_SOURCE_DIR}/deps/base64.c ${PROJECT_SOURCE_DIR}/deps/base64.c
${PROJECT_SOURCE_DIR}/deps/dtoa.c ${PROJECT_SOURCE_DIR}/deps/dtoa.c
${PROJECT_SOURCE_DIR}/deps/mp_printf.c ${PROJECT_SOURCE_DIR}/deps/mp_printf.c
${PROJECT_SOURCE_DIR}/deps/utf8.c
${PROJECT_SOURCE_DIR}/deps/itoa.c ${PROJECT_SOURCE_DIR}/deps/itoa.c
${PROJECT_SOURCE_DIR}/deps/ziptree.c) ${PROJECT_SOURCE_DIR}/deps/ziptree.c)
@ -837,6 +893,7 @@ if(UA_ENABLE_JSON_ENCODING)
list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/deps/cj5.c list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/deps/cj5.c
${PROJECT_SOURCE_DIR}/deps/parse_num.c ${PROJECT_SOURCE_DIR}/deps/parse_num.c
${PROJECT_SOURCE_DIR}/src/ua_types_encoding_json.c ${PROJECT_SOURCE_DIR}/src/ua_types_encoding_json.c
${PROJECT_SOURCE_DIR}/src/ua_types_encoding_json_105.c
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage_json.c) ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage_json.c)
endif() endif()
@ -869,20 +926,24 @@ if(UA_DEBUG_DUMP_PKGS)
list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/plugins/ua_debug_dump_pkgs.c) list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/plugins/ua_debug_dump_pkgs.c)
endif() endif()
if(UA_ENABLE_DISCOVERY_MULTICAST) if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD)
# prepend in list, otherwise it complains that winsock2.h has to be included before windows.h # prepend in list, otherwise it complains that winsock2.h has to be included before windows.h
list(APPEND lib_headers list(APPEND lib_headers
${PROJECT_BINARY_DIR}/src_generated/mdnsd_config.h ${PROJECT_BINARY_DIR}/src_generated/mdnsd_config.h
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.h ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.h
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.h ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.h
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.h ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.h
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.h) ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.h)
list(APPEND lib_sources list(APPEND lib_sources
${PROJECT_SOURCE_DIR}/src/server/ua_discovery_mdns.c ${PROJECT_SOURCE_DIR}/src/server/ua_discovery_mdns.c
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.c ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.c
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.c ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.c
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.c ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.c
${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.c) ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.c)
elseif(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
list(APPEND lib_sources
${PROJECT_SOURCE_DIR}/src/server/ua_discovery_mdns_avahi.c
)
endif() endif()
if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ) if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ)
@ -966,7 +1027,7 @@ endif()
# Always include encryption plugins into the amalgamation # Always include encryption plugins into the amalgamation
# Use guards in the files to ensure that UA_ENABLE_ENCRYPTON_MBEDTLS and UA_ENABLE_ENCRYPTION_OPENSSL are honored. # Use guards in the files to ensure that UA_ENABLE_ENCRYPTON_MBEDTLS and UA_ENABLE_ENCRYPTION_OPENSSL are honored.
if((UNIX AND UA_ENABLE_ENCRYPTION) OR UA_ENABLE_AMALGAMATION) if(((UNIX OR UA_ARCHITECTURE_WIN32) AND UA_ENABLE_ENCRYPTION) OR UA_ENABLE_AMALGAMATION)
list(APPEND plugin_sources ${PROJECT_SOURCE_DIR}/plugins/crypto/ua_filestore_common.h list(APPEND plugin_sources ${PROJECT_SOURCE_DIR}/plugins/crypto/ua_filestore_common.h
${PROJECT_SOURCE_DIR}/plugins/crypto/ua_filestore_common.c ${PROJECT_SOURCE_DIR}/plugins/crypto/ua_filestore_common.c
${PROJECT_SOURCE_DIR}/plugins/crypto/ua_certificategroup_filestore.c ${PROJECT_SOURCE_DIR}/plugins/crypto/ua_certificategroup_filestore.c
@ -1037,7 +1098,7 @@ endif()
set(UA_FILE_NODESETS) # List of nodeset-xml files to be considered in the generated information model set(UA_FILE_NODESETS) # List of nodeset-xml files to be considered in the generated information model
set(UA_NODESET_DIR ${PROJECT_SOURCE_DIR}/deps/ua-nodeset CACHE STRING "The path to the node-set directory (e.g. from https://github.com/OPCFoundation/UA-Nodeset)") set(UA_NODESET_DIR ${PROJECT_SOURCE_DIR}/deps/ua-nodeset CACHE STRING "The path to the node-set directory (e.g. from https://github.com/OPCFoundation/UA-Nodeset)")
unset(UA_FILE_NS0_PRIVATE) set(UA_FILE_NS0_PRIVATE "")
if(UA_FILE_NS0) if(UA_FILE_NS0)
set(UA_FILE_NS0_PRIVATE "${UA_FILE_NS0}") set(UA_FILE_NS0_PRIVATE "${UA_FILE_NS0}")
endif() endif()
@ -1301,7 +1362,7 @@ add_library(open62541::open62541 ALIAS open62541)
target_compile_definitions(open62541-object PRIVATE -DUA_DYNAMIC_LINKING_EXPORT) target_compile_definitions(open62541-object PRIVATE -DUA_DYNAMIC_LINKING_EXPORT)
target_compile_definitions(open62541-plugins PRIVATE -DUA_DYNAMIC_LINKING_EXPORT) target_compile_definitions(open62541-plugins PRIVATE -DUA_DYNAMIC_LINKING_EXPORT)
target_compile_definitions(open62541 PRIVATE -DUA_DYNAMIC_LINKING_EXPORT) target_compile_definitions(open62541 PRIVATE -DUA_DYNAMIC_LINKING_EXPORT)
if(UA_ENABLE_DISCOVERY_MULTICAST) if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD)
target_compile_definitions(open62541-object PRIVATE -DMDNSD_DYNAMIC_LINKING_EXPORT) target_compile_definitions(open62541-object PRIVATE -DMDNSD_DYNAMIC_LINKING_EXPORT)
target_compile_definitions(open62541 PRIVATE -DMDNSD_DYNAMIC_LINKING_EXPORT) target_compile_definitions(open62541 PRIVATE -DMDNSD_DYNAMIC_LINKING_EXPORT)
endif() endif()
@ -1323,6 +1384,9 @@ SET_TARGET_PROPERTIES(open62541 PROPERTIES
# DLL requires linking to dependencies # DLL requires linking to dependencies
target_link_libraries(open62541 PUBLIC ${open62541_PUBLIC_LIBRARIES}) target_link_libraries(open62541 PUBLIC ${open62541_PUBLIC_LIBRARIES})
target_link_libraries(open62541 PRIVATE ${open62541_LIBRARIES}) target_link_libraries(open62541 PRIVATE ${open62541_LIBRARIES})
if(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
target_link_libraries(open62541 PUBLIC ${AVAHI_LIBRARIES})
endif()
########################## ##########################
# Build Selected Targets # # Build Selected Targets #

View File

@ -122,6 +122,7 @@ typedef SSIZE_T ssize_t;
/* POSIX Definitions */ /* POSIX Definitions */
/*********************/ /*********************/
#include <sys/socket.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <netinet/tcp.h> #include <netinet/tcp.h>

View File

@ -72,7 +72,7 @@ typedef enum {
} MultiCastType; } MultiCastType;
typedef union { typedef union {
#ifdef _WIN32 #if !defined(ip_mreqn)
struct ip_mreq ipv4; struct ip_mreq ipv4;
#else #else
struct ip_mreqn ipv4; struct ip_mreqn ipv4;
@ -197,9 +197,11 @@ setMulticastInterface(const char *netif, struct addrinfo *info,
if(ifa->ifa_addr->sa_family != info->ai_family) if(ifa->ifa_addr->sa_family != info->ai_family)
continue; continue;
#if defined(_WIN32) || defined(ip_mreqn)
idx = UA_if_nametoindex(ifa->ifa_name); idx = UA_if_nametoindex(ifa->ifa_name);
if(idx == 0) if(idx == 0)
continue; continue;
#endif
/* Found network interface by name */ /* Found network interface by name */
if(strcmp(ifa->ifa_name, netif) == 0) if(strcmp(ifa->ifa_name, netif) == 0)
@ -228,12 +230,15 @@ setMulticastInterface(const char *netif, struct addrinfo *info,
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
/* Write the interface index */ /* Write the interface index */
if(info->ai_family == AF_INET) if(info->ai_family == AF_INET) {
#if defined(ip_mreqn)
req->ipv4.imr_ifindex = idx; req->ipv4.imr_ifindex = idx;
#endif
#if UA_IPV6 #if UA_IPV6
else /* if(info->ai_family == AF_INET6) */ } else { /* if(info->ai_family == AF_INET6) */
req->ipv6.ipv6mr_interface = idx; req->ipv6.ipv6mr_interface = idx;
#endif #endif
}
return UA_STATUSCODE_GOOD; return UA_STATUSCODE_GOOD;
} }
@ -246,7 +251,7 @@ setupMulticastRequest(UA_FD socket, MulticastRequest *req, const UA_KeyValueMap
if(info->ai_family == AF_INET) { if(info->ai_family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in *)info->ai_addr; struct sockaddr_in *sin = (struct sockaddr_in *)info->ai_addr;
req->ipv4.imr_multiaddr = sin->sin_addr; req->ipv4.imr_multiaddr = sin->sin_addr;
#ifdef _WIN32 #if !defined(ip_mreqn)
req->ipv4.imr_interface.s_addr = htonl(INADDR_ANY); /* default ANY */ req->ipv4.imr_interface.s_addr = htonl(INADDR_ANY); /* default ANY */
#else #else
req->ipv4.imr_address.s_addr = htonl(INADDR_ANY); /* default ANY */ req->ipv4.imr_address.s_addr = htonl(INADDR_ANY); /* default ANY */

2
deps/README.md vendored
View File

@ -21,3 +21,5 @@ The following third party libraries may be included -- depending on the activate
| mqtt-c | MIT | a portable MQTT client in C | | mqtt-c | MIT | a portable MQTT client in C |
| dtoa | BSL (Boost) | Printing of float numbers | | dtoa | BSL (Boost) | Printing of float numbers |
| mp_printf | MIT | Our version of github:mpaland/printf | | mp_printf | MIT | Our version of github:mpaland/printf |
| utf8 | MPL 2.0 | Lightweight utf8 de/encoding |
| tr_dirent | MIT | Dirent interface for Microsoft Visual Studio |

14
deps/base64.c vendored
View File

@ -59,7 +59,7 @@ UA_base64_buf(const unsigned char *src, size_t len, unsigned char *out) {
return (size_t)(pos - out); return (size_t)(pos - out);
} }
static unsigned char dtable[256] = { static unsigned char dtable[128] = {
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 62 , 0x80, 62 , 0x80, 63 , 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 62 , 0x80, 62 , 0x80, 63 ,
@ -67,15 +67,7 @@ static unsigned char dtable[256] = {
0x80, 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 0x80, 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 ,
15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , 0x80, 0x80, 0x80, 0x80, 63 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , 0x80, 0x80, 0x80, 0x80, 63 ,
0x80, 26 , 27 , 28 , 29 , 30 , 31 , 32 , 33 , 34 , 35 , 36 , 37 , 38 , 39 , 40 , 0x80, 26 , 27 , 28 , 29 , 30 , 31 , 32 , 33 , 34 , 35 , 36 , 37 , 38 , 39 , 40 ,
41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 , 50 , 51 , 0x80, 0x80, 0x80, 0x80, 0x80, 41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 , 50 , 51 , 0x80, 0x80, 0x80, 0x80, 0x80
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80
}; };
unsigned char * unsigned char *
@ -102,7 +94,7 @@ UA_unbase64(const unsigned char *src, size_t len, size_t *out_len) {
unsigned char block[4]; unsigned char block[4];
unsigned char *pos = out; unsigned char *pos = out;
for(size_t i = 0; i < len; i++) { for(size_t i = 0; i < len; i++) {
unsigned char tmp = dtable[src[i]]; unsigned char tmp = dtable[src[i] & 0x07f];
if(tmp == 0x80) if(tmp == 0x80)
goto error; /* Invalid input */ goto error; /* Invalid input */

170
deps/cj5.c vendored
View File

@ -23,6 +23,7 @@
#include "cj5.h" #include "cj5.h"
#include "parse_num.h" #include "parse_num.h"
#include "utf8.h"
#include <math.h> #include <math.h>
#include <float.h> #include <float.h>
@ -131,49 +132,16 @@ cj5__parse_string(cj5__parser *parser) {
return; return;
} }
// Escape char // Skip escape character
if(c == '\\') { if(c == '\\') {
if(parser->pos + 1 >= len) { if(parser->pos + 1 >= len) {
parser->error = CJ5_ERROR_INCOMPLETE; parser->error = CJ5_ERROR_INCOMPLETE;
return; return;
} }
parser->pos++; parser->pos++;
switch(json5[parser->pos]) { if(json5[parser->pos] == '\n') {
case '\"':
case '/':
case '\\':
case 'b':
case 'f':
case 'r':
case 'n':
case 't':
break;
case 'u': // The next four characters are an utf8 code
parser->pos++;
if(parser->pos + 4 >= len) {
parser->error = CJ5_ERROR_INVALID;
return;
}
for(unsigned int i = 0; i < 4; i++) {
// If it isn't a hex character we have an error
if(!(json5[parser->pos] >= 48 && json5[parser->pos] <= 57) && /* 0-9 */
!(json5[parser->pos] >= 65 && json5[parser->pos] <= 70) && /* A-F */
!(json5[parser->pos] >= 97 && json5[parser->pos] <= 102)) /* a-f */
{
parser->error = CJ5_ERROR_INVALID;
return;
}
parser->pos++;
}
parser->pos--;
break;
case '\n': // Escape break line
parser->line++; parser->line++;
parser->line_start = parser->pos; parser->line_start = parser->pos;
break;
default:
parser->error = CJ5_ERROR_INVALID;
return;
} }
} }
} }
@ -683,87 +651,67 @@ cj5_get_str(const cj5_result *r, unsigned int tok_index,
unsigned int outpos = 0; unsigned int outpos = 0;
for(; pos < end; pos++) { for(; pos < end; pos++) {
uint8_t c = (uint8_t)*pos; uint8_t c = (uint8_t)*pos;
// Unprintable ascii characters must be escaped
if(c < ' ' || c == 127)
return CJ5_ERROR_INVALID;
// Process an escape character // Unescaped Ascii character or utf8 byte
if(c == '\\') { if(c != '\\') {
if(pos + 1 >= end) buf[outpos++] = (char)c;
return CJ5_ERROR_INCOMPLETE;
pos++;
c = (uint8_t)*pos;
switch(c) {
case '\"': buf[outpos++] = '\"'; break;
case '\\': buf[outpos++] = '\\'; break;
case '\n': buf[outpos++] = '\n'; break; // escape newline
case '/': buf[outpos++] = '/'; break;
case 'b': buf[outpos++] = '\b'; break;
case 'f': buf[outpos++] = '\f'; break;
case 'r': buf[outpos++] = '\r'; break;
case 'n': buf[outpos++] = '\n'; break;
case 't': buf[outpos++] = '\t'; break;
case 'u': {
// Parse the unicode code point
if(pos + 4 >= end)
return CJ5_ERROR_INCOMPLETE;
pos++;
uint32_t utf;
cj5_error_code err = parse_codepoint(pos, &utf);
if(err != CJ5_ERROR_NONE)
return err;
pos += 3;
if(0xD800 <= utf && utf <= 0xDBFF) {
// Parse a surrogate pair
if(pos + 6 >= end)
return CJ5_ERROR_INVALID;
if(pos[1] != '\\' && pos[3] != 'u')
return CJ5_ERROR_INVALID;
pos += 3;
uint32_t trail;
err = parse_codepoint(pos, &trail);
if(err != CJ5_ERROR_NONE)
return err;
pos += 3;
utf = (utf << 10) + trail + SURROGATE_OFFSET;
} else if(0xDC00 <= utf && utf <= 0xDFFF) {
// Invalid Unicode '\\u%04X'
return CJ5_ERROR_INVALID;
}
// Write the utf8 bytes of the code point
if(utf <= 0x7F) { // Plain ASCII
buf[outpos++] = (char)utf;
} else if(utf <= 0x07FF) { // 2-byte unicode
buf[outpos++] = (char)(((utf >> 6) & 0x1F) | 0xC0);
buf[outpos++] = (char)(((utf >> 0) & 0x3F) | 0x80);
} else if(utf <= 0xFFFF) { // 3-byte unicode
buf[outpos++] = (char)(((utf >> 12) & 0x0F) | 0xE0);
buf[outpos++] = (char)(((utf >> 6) & 0x3F) | 0x80);
buf[outpos++] = (char)(((utf >> 0) & 0x3F) | 0x80);
} else if(utf <= 0x10FFFF) { // 4-byte unicode
buf[outpos++] = (char)(((utf >> 18) & 0x07) | 0xF0);
buf[outpos++] = (char)(((utf >> 12) & 0x3F) | 0x80);
buf[outpos++] = (char)(((utf >> 6) & 0x3F) | 0x80);
buf[outpos++] = (char)(((utf >> 0) & 0x3F) | 0x80);
} else {
return CJ5_ERROR_INVALID; // Not a utf8 string
}
break;
}
default:
return CJ5_ERROR_INVALID;
}
continue; continue;
} }
// Unprintable ascii characters must be escaped. JSON5 allows nested // End of input before the escaped character
// quotes if the quote character is not the same as the surrounding if(pos + 1 >= end)
// quote character, e.g. 'this is my "quote"'. This logic is in the return CJ5_ERROR_INCOMPLETE;
// token parsing code and not in this "string extraction" method.
if(c < ' ' || c == 127)
return CJ5_ERROR_INVALID;
// Ascii character or utf8 byte // Process escaped character
buf[outpos++] = (char)c; pos++;
c = (uint8_t)*pos;
switch(c) {
case 'b': buf[outpos++] = '\b'; break;
case 'f': buf[outpos++] = '\f'; break;
case 'r': buf[outpos++] = '\r'; break;
case 'n': buf[outpos++] = '\n'; break;
case 't': buf[outpos++] = '\t'; break;
default: buf[outpos++] = c; break;
case 'u': {
// Parse a unicode code point
if(pos + 4 >= end)
return CJ5_ERROR_INCOMPLETE;
pos++;
uint32_t utf;
cj5_error_code err = parse_codepoint(pos, &utf);
if(err != CJ5_ERROR_NONE)
return err;
pos += 3;
// Parse a surrogate pair
if(0xd800 <= utf && utf <= 0xdfff) {
if(pos + 6 >= end)
return CJ5_ERROR_INVALID;
if(pos[1] != '\\' && pos[2] != 'u')
return CJ5_ERROR_INVALID;
pos += 3;
uint32_t utf2;
err = parse_codepoint(pos, &utf2);
if(err != CJ5_ERROR_NONE)
return err;
pos += 3;
// High or low surrogate pair
utf = (utf <= 0xdbff) ?
(utf << 10) + utf2 + SURROGATE_OFFSET :
(utf2 << 10) + utf + SURROGATE_OFFSET;
}
// Write the utf8 bytes of the code point
unsigned len = utf8_from_codepoint((unsigned char*)buf + outpos, utf);
if(len == 0)
return CJ5_ERROR_INVALID; // Not a utf8 string
outpos += len;
break;
}
}
} }
// Terminate with \0 // Terminate with \0

1239
deps/tr_dirent.h vendored Normal file

File diff suppressed because it is too large Load Diff

67
deps/utf8.c vendored Normal file
View File

@ -0,0 +1,67 @@
/* 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 2024 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
*/
#include "utf8.h"
#define UTF_PARSE_BYTE(pos) do { \
byte = str[pos]; \
if(UTF_UNLIKELY(byte < 0x80 || byte > 0xBF)) \
return 0; /* Not a continuation byte */ \
*codepoint = (*codepoint << 6) + (byte & 0x3F); \
} while(0)
unsigned
utf8_to_codepoint(const unsigned char *str, size_t len, unsigned *codepoint) {
/* Ensure there is a character to read */
if(UTF_UNLIKELY(len == 0))
return 0;
*codepoint = str[0];
if(UTF_LIKELY(*codepoint < 0x80))
return 1; /* Normal ASCII */
if(UTF_UNLIKELY(*codepoint <= 0xC1))
return 0; /* Continuation byte not allowed here */
unsigned count;
unsigned char byte;
if(*codepoint <= 0xDF) { /* 2-byte sequence */
if(len < 2)
return 0;
count = 2;
*codepoint &= 0x1F;
UTF_PARSE_BYTE(1);
if(UTF_UNLIKELY(*codepoint < 0x80))
return 0; /* Too small for the encoding length */
} else if(*codepoint <= 0xEF) { /* 3-byte sequence */
if(len < 3)
return 0;
count = 3;
*codepoint &= 0xF;
UTF_PARSE_BYTE(1);
UTF_PARSE_BYTE(2);
if(UTF_UNLIKELY(*codepoint < 0x800))
return 0; /* Too small for the encoding length */
} else if(*codepoint <= 0xF4) { /* 4-byte sequence */
if(len < 4)
return 0;
count = 4;
*codepoint &= 0x7;
UTF_PARSE_BYTE(1);
UTF_PARSE_BYTE(2);
UTF_PARSE_BYTE(3);
if(UTF_UNLIKELY(*codepoint < 0x10000))
return 0; /* Too small for the encoding length */
} else {
return 0; /* Invalid utf8 encoding */
}
if(UTF_UNLIKELY(*codepoint > 0x10FFFF))
return 0; /* Not in the Unicode range */
return count;
}

83
deps/utf8.h vendored Normal file
View File

@ -0,0 +1,83 @@
/* 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 2024 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
*/
#ifndef UTF8_H_
#define UTF8_H_
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _MSC_VER
# define UTF_INLINE __inline
#else
# define UTF_INLINE inline
#endif
#if defined(__GNUC__) || defined(__clang__)
# define UTF_LIKELY(x) __builtin_expect((x), 1)
# define UTF_UNLIKELY(x) __builtin_expect((x), 0)
#else
# define UTF_LIKELY(x) (x)
# define UTF_UNLIKELY(x) (x)
#endif
/* Extract the next utf8 codepoint from the buffer. Returns the length (1-4) of
* the codepoint encoding or 0 upon an error. */
unsigned
utf8_to_codepoint(const unsigned char *str, size_t len, unsigned *codepoint);
/* Encodes the codepoint in utf8. The string needs to have enough space (at most
* four byte) available. Returns the encoding length. */
static UTF_INLINE unsigned
utf8_from_codepoint(unsigned char *str, unsigned codepoint) {
if(UTF_LIKELY(codepoint <= 0x7F)) { /* Plain ASCII */
str[0] = (unsigned char)codepoint;
return 1;
}
if(UTF_LIKELY(codepoint <= 0x07FF)) { /* 2-byte unicode */
str[0] = (unsigned char)(((codepoint >> 6) & 0x1F) | 0xC0);
str[1] = (unsigned char)(((codepoint >> 0) & 0x3F) | 0x80);
return 2;
}
if(UTF_LIKELY(codepoint <= 0xFFFF)) { /* 3-byte unicode */
str[0] = (unsigned char)(((codepoint >> 12) & 0x0F) | 0xE0);
str[1] = (unsigned char)(((codepoint >> 6) & 0x3F) | 0x80);
str[2] = (unsigned char)(((codepoint >> 0) & 0x3F) | 0x80);
return 3;
}
if(UTF_LIKELY(codepoint <= 0x10FFFF)) { /* 4-byte unicode */
str[0] = (unsigned char)(((codepoint >> 18) & 0x07) | 0xF0);
str[1] = (unsigned char)(((codepoint >> 12) & 0x3F) | 0x80);
str[2] = (unsigned char)(((codepoint >> 6) & 0x3F) | 0x80);
str[3] = (unsigned char)(((codepoint >> 0) & 0x3F) | 0x80);
return 4;
}
return 0; /* Not a unicode codepoint */
}
/* Returns the encoding length of the codepoint */
static UTF_INLINE unsigned
utf8_length(unsigned codepoint) {
if(UTF_LIKELY(codepoint <= 0x7F))
return 1; /* Plain ASCII */
if(UTF_LIKELY(codepoint <= 0x07FF))
return 2; /* 2-byte unicode */
if(UTF_LIKELY(codepoint <= 0xFFFF))
return 3; /* 3-byte unicode */
if(UTF_LIKELY(codepoint <= 0x10FFFF))
return 4; /* 4-byte unicode */
return 0; /* Not a unicode codepoint */
}
#ifdef __cplusplus
}
#endif
#endif /* UTF8_H_ */

2
deps/ziptree.c vendored
View File

@ -127,7 +127,7 @@ __ZIP_INSERT(void *h, zip_cmp_cb cmp, unsigned short fieldoffset,
* than "x" */ * than "x" */
zip_elem *prev = NULL; zip_elem *prev = NULL;
zip_elem *cur = head->root; zip_elem *cur = head->root;
enum ZIP_CMP cur_order, prev_order; enum ZIP_CMP cur_order, prev_order = ZIP_CMP_EQ;
do { do {
cur_order = __ZIP_UNIQUE_CMP(cmp, x_key, ZIP_KEY_PTR(cur)); cur_order = __ZIP_UNIQUE_CMP(cmp, x_key, ZIP_KEY_PTR(cur));
if(cur_order == ZIP_CMP_EQ) if(cur_order == ZIP_CMP_EQ)

View File

@ -23,6 +23,7 @@ Building with CMake on Ubuntu or Debian
sudo apt-get install check libsubunit-dev # for unit tests sudo apt-get install check libsubunit-dev # for unit tests
sudo apt-get install python3-sphinx graphviz # for documentation generation sudo apt-get install python3-sphinx graphviz # for documentation generation
sudo apt-get install python3-sphinx-rtd-theme # documentation style sudo apt-get install python3-sphinx-rtd-theme # documentation style
sudo apt-get install libavahi-client-dev libavahi-common-dev # for LDS-ME (multicast discovery)
git clone https://github.com/open62541/open62541.git git clone https://github.com/open62541/open62541.git
cd open62541 cd open62541
@ -294,7 +295,12 @@ Detailed SDK Features
Enable Discovery Service (LDS) Enable Discovery Service (LDS)
**UA_ENABLE_DISCOVERY_MULTICAST** **UA_ENABLE_DISCOVERY_MULTICAST**
Enable Discovery Service with multicast support (LDS-ME) Enable Discovery Service with multicast support (LDS-ME) and specify the
multicast backend. The possible options are:
- ``OFF`` No multicast support. (default)
- ``MDNSD`` Multicast support using libmdnsd
- ``AVAHI`` Multicast support using Avahi
**UA_ENABLE_DISCOVERY_SEMAPHORE** **UA_ENABLE_DISCOVERY_SEMAPHORE**
Enable Discovery Semaphore support Enable Discovery Semaphore support
@ -330,7 +336,10 @@ Detailed SDK Features
Enable diagnostics information exposed by the server. Enabled by default. Enable diagnostics information exposed by the server. Enabled by default.
**UA_ENABLE_JSON_ENCODING** **UA_ENABLE_JSON_ENCODING**
Enable JSON encoding. Enabled by default. Enable JSON encoding. Enabled by default. The JSON encoding changed with the
1.05 version of the OPC UA specification. The legacy encoding can be enabled
via the ``UA_ENABLE_JSON_ENCODING_LEGACY`` option. Note that this legacy
feature wil get removed at some point in the future.
Some options are marked as advanced. The advanced options need to be toggled to Some options are marked as advanced. The advanced options need to be toggled to
be visible in the cmake GUIs. be visible in the cmake GUIs.

View File

@ -1,45 +1,34 @@
cmake_minimum_required(VERSION 3.0...3.12) cmake_minimum_required(VERSION 3.13)
project(open62541-examples C) project(open62541-examples C)
if(${CMAKE_VERSION} VERSION_LESS 3.12) if(${CMAKE_VERSION} VERSION_LESS 3.12)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
endif() endif()
# This examples folder can also be built standalone.
# First install open62541 using `make install` then # The examples folder can be built standalone.
# copy this folder to any other location and call CMake directly: # First install open62541 system-wide using `make install`.
# Then copy this folder to any other location and call CMake directly:
# #
# cp -r open62541/examples $HOME/open62541_examples # cd ./open62541_examples
# cd $HOME/open62541_examples
# mkdir build && cd build # mkdir build && cd build
# cmake -DUA_NAMESPACE_ZERO=FULL .. # cmake ..
# make -j # make -j
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
# Examples are built standalone. Find installed open62541 # Examples are built standalone. Find installed open62541
find_package(open62541 REQUIRED)
if(UA_NAMESPACE_ZERO STREQUAL "FULL") # Define empty function. We don't need it in standalone
find_package(open62541 REQUIRED COMPONENTS FullNamespace)
else()
find_package(open62541 REQUIRED)
endif()
if(NOT UA_TOOLS_DIR)
set(UA_TOOLS_DIR ${open62541_TOOLS_DIR})
endif()
function(assign_source_group) function(assign_source_group)
# define empty function. We don't need it in standalone
endfunction(assign_source_group) endfunction(assign_source_group)
include_directories(${PROJECT_BINARY_DIR}/src_generated)
endif() endif()
#####################
# CMake Definitions #
#####################
# Required for common.h header file used in examples # Required for common.h header file used in examples
include_directories(${CMAKE_CURRENT_LIST_DIR}) include_directories(${CMAKE_CURRENT_LIST_DIR})
#############################
# Compiled binaries folders #
#############################
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/examples) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/examples)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/examples) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/examples)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/examples) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/examples)
@ -47,19 +36,21 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/bin/exampl
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_BINARY_DIR}/bin/examples) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_BINARY_DIR}/bin/examples)
macro(add_example EXAMPLE_NAME EXAMPLE_SOURCE) macro(add_example EXAMPLE_NAME EXAMPLE_SOURCE)
add_executable(${EXAMPLE_NAME} ${STATIC_OBJECTS} ${EXAMPLE_SOURCE} ${ARGN} ${PROJECT_SOURCE_DIR}/common.h) add_executable(${EXAMPLE_NAME} ${STATIC_OBJECTS}
${EXAMPLE_SOURCE} ${ARGN} ${PROJECT_SOURCE_DIR}/common.h)
target_link_libraries(${EXAMPLE_NAME} open62541::open62541) target_link_libraries(${EXAMPLE_NAME} open62541::open62541)
assign_source_group(${EXAMPLE_SOURCE}) assign_source_group(${EXAMPLE_SOURCE})
set_target_properties(${EXAMPLE_NAME} PROPERTIES FOLDER "open62541/examples") set_target_properties(${EXAMPLE_NAME} PROPERTIES FOLDER "open62541/examples")
set_target_properties(${EXAMPLE_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/bin") set_target_properties(${EXAMPLE_NAME} PROPERTIES
VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
if(UA_ENABLE_NODESET_INJECTOR) if(UA_ENABLE_NODESET_INJECTOR)
set(UA_NODESETINJECTOR_EXAMPLE_NAMES ${EXAMPLE_NAME} ${UA_NODESETINJECTOR_EXAMPLE_NAMES}) set(UA_NODESETINJECTOR_EXAMPLE_NAMES ${EXAMPLE_NAME} ${UA_NODESETINJECTOR_EXAMPLE_NAMES})
# If the nodeset injector is activated, the target must be built twice. # If the nodeset injector is activated, the target must be built twice.
# Otherwise, it may result in the nodesets not being inserted. # Otherwise, it may result in the nodesets not being inserted.
add_custom_command(TARGET ${EXAMPLE_NAME} POST_BUILD add_custom_command(TARGET ${EXAMPLE_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target ${EXAMPLE_NAME} COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}
) --target ${EXAMPLE_NAME})
endif() endif()
endmacro() endmacro()
@ -68,25 +59,17 @@ endmacro()
############# #############
add_example(tutorial_datatypes tutorial_datatypes.c) add_example(tutorial_datatypes tutorial_datatypes.c)
add_example(tutorial_server_firststeps tutorial_server_firststeps.c) add_example(tutorial_server_firststeps tutorial_server_firststeps.c)
add_example(tutorial_server_variable tutorial_server_variable.c) add_example(tutorial_server_variable tutorial_server_variable.c)
add_example(tutorial_server_datasource tutorial_server_datasource.c) add_example(tutorial_server_datasource tutorial_server_datasource.c)
add_example(tutorial_server_variabletype tutorial_server_variabletype.c)
add_example(server_settimestamp server_settimestamp.c) add_example(tutorial_server_object tutorial_server_object.c)
add_example(tutorial_server_reverseconnect tutorial_server_reverseconnect.c)
if(UA_ENABLE_SUBSCRIPTIONS) if(UA_ENABLE_SUBSCRIPTIONS)
add_example(tutorial_server_monitoreditems tutorial_server_monitoreditems.c) add_example(tutorial_server_monitoreditems tutorial_server_monitoreditems.c)
endif() endif()
add_example(tutorial_server_variabletype tutorial_server_variabletype.c)
add_example(tutorial_server_object tutorial_server_object.c)
add_example(tutorial_server_reverseconnect tutorial_server_reverseconnect.c)
if(UA_ENABLE_METHODCALLS) if(UA_ENABLE_METHODCALLS)
add_example(tutorial_server_method tutorial_server_method.c) add_example(tutorial_server_method tutorial_server_method.c)
if (UA_MULTITHREADING GREATER_EQUAL 100) if (UA_MULTITHREADING GREATER_EQUAL 100)
@ -94,26 +77,15 @@ if(UA_ENABLE_METHODCALLS)
endif() endif()
endif() endif()
if(UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS)
add_example(tutorial_server_alarms_conditions tutorial_server_alarms_conditions.c)
endif()
add_example(tutorial_client_firststeps tutorial_client_firststeps.c) add_example(tutorial_client_firststeps tutorial_client_firststeps.c)
if(UA_ENABLE_SUBSCRIPTIONS_EVENTS) if(UA_ENABLE_SUBSCRIPTIONS_EVENTS)
add_example(tutorial_client_events tutorial_client_events.c) add_example(tutorial_client_events tutorial_client_events.c)
add_example(tutorial_server_events tutorial_server_events.c) add_example(tutorial_server_events tutorial_server_events.c)
add_example(server_events_random events/server_random_events.c)
add_example(client_event_filter events/client_eventfilter.c)
if(UA_ENABLE_PARSING)
add_example(client_event_filter_queries events/client_filter_queries.c)
file(STRINGS ${PROJECT_SOURCE_DIR}/events/example_queries/case_1.txt CASE_1 NEWLINE_CONSUME)
file(STRINGS ${PROJECT_SOURCE_DIR}/events/example_queries/case_2.txt CASE_2 NEWLINE_CONSUME)
STRING(REGEX REPLACE "\n" " " CASE_1 ${CASE_1})
STRING(REGEX REPLACE "\"" "\\\\\"" CASE_1 "${CASE_1}")
STRING(REGEX REPLACE "\n" " " CASE_2 ${CASE_2})
STRING(REGEX REPLACE "\"" "\\\\\"" CASE_2 "${CASE_2}")
configure_file(${PROJECT_SOURCE_DIR}/events/eventfilter_parser_examples.h.in ${PROJECT_BINARY_DIR}/../src_generated/open62541/eventfilter_parser_examples.h @ONLY)
endif()
if(UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS)
add_example(tutorial_server_alarms_conditions tutorial_server_alarms_conditions.c)
endif()
endif() endif()
################## ##################
@ -121,27 +93,18 @@ endif()
################## ##################
add_example(client client.c) add_example(client client.c)
add_example(client_connect client_connect.c) add_example(client_connect client_connect.c)
add_example(client_async client_async.c)
add_example(client_connect_loop client_connect_loop.c)
if(UA_ENABLE_HISTORIZING) if(UA_ENABLE_HISTORIZING)
add_example(client_historical client_historical.c) add_example(client_historical client_historical.c)
endif() endif()
install(PROGRAMS $<TARGET_FILE:client> if(UA_ENABLE_METHODCALLS AND UA_MULTITHREADING GREATER_EQUAL 100)
DESTINATION bin add_example(client_method_async client_method_async.c)
RENAME ua_client)
add_example(client_async client_async.c)
if(UA_ENABLE_METHODCALLS)
if (UA_MULTITHREADING GREATER_EQUAL 100)
add_example(client_method_async client_method_async.c)
endif()
endif() endif()
add_example(client_connect_loop client_connect_loop.c)
if(UA_ENABLE_SUBSCRIPTIONS) if(UA_ENABLE_SUBSCRIPTIONS)
add_example(client_subscription_loop client_subscription_loop.c) add_example(client_subscription_loop client_subscription_loop.c)
endif() endif()
@ -151,19 +114,25 @@ endif()
#################### ####################
add_example(ci_server ci_server.c) add_example(ci_server ci_server.c)
add_example(server_settimestamp server_settimestamp.c)
add_example(server_mainloop server_mainloop.c) add_example(server_mainloop server_mainloop.c)
add_example(server_instantiation server_instantiation.c) add_example(server_instantiation server_instantiation.c)
add_example(server_repeated_job server_repeated_job.c) add_example(server_repeated_job server_repeated_job.c)
add_example(server_inheritance server_inheritance.c) add_example(server_inheritance server_inheritance.c)
add_example(server_loglevel server_loglevel.c) add_example(server_loglevel server_loglevel.c)
add_example(custom_datatype_client custom_datatype/client_types_custom.c)
add_example(custom_datatype_server custom_datatype/server_types_custom.c)
if(UA_ENABLE_SUBSCRIPTIONS_EVENTS)
add_example(server_events_random events/server_random_events.c)
add_example(client_event_filter events/client_eventfilter.c)
if(UA_ENABLE_PARSING)
add_example(client_event_filter_queries events/client_filter_queries.c)
endif()
endif()
if(UA_ENABLE_JSON_ENCODING) if(UA_ENABLE_JSON_ENCODING)
add_example(server_file_based_config file_based_server/server_file_based_config.c) add_example(server_json_config server_json_config.c)
endif() endif()
if(UA_ENABLE_HISTORIZING) if(UA_ENABLE_HISTORIZING)
@ -171,7 +140,9 @@ if(UA_ENABLE_HISTORIZING)
add_example(tutorial_server_historicaldata_circular tutorial_server_historicaldata_circular.c) add_example(tutorial_server_historicaldata_circular tutorial_server_historicaldata_circular.c)
endif() endif()
if(UA_ENABLE_ENCRYPTION OR UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL") if(UA_ENABLE_ENCRYPTION OR
UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR
UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL")
add_example(server_encryption encryption/server_encryption.c) add_example(server_encryption encryption/server_encryption.c)
add_example(client_encryption encryption/client_encryption.c) add_example(client_encryption encryption/client_encryption.c)
target_include_directories(server_encryption PRIVATE "${PROJECT_SOURCE_DIR}/examples") target_include_directories(server_encryption PRIVATE "${PROJECT_SOURCE_DIR}/examples")
@ -184,19 +155,13 @@ if(UA_ENABLE_ENCRYPTION OR UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR UA_ENABLE_
endif() endif()
endif() endif()
if (NOT (BUILD_SHARED_LIBS AND WIN32))
add_example(custom_datatype_client custom_datatype/client_types_custom.c)
add_example(custom_datatype_server custom_datatype/server_types_custom.c)
else()
MESSAGE(WARNING "Can't build custom datatype examples on WIN32 when BUILD_SHARED_LIBS enabled. Skipping
custom_datatype_client and custom_datatype_server!")
endif()
if(UA_ENABLE_NODEMANAGEMENT) if(UA_ENABLE_NODEMANAGEMENT)
add_example(access_control_server access_control/server_access_control.c) add_example(access_control_server access_control/server_access_control.c)
add_example(access_control_client access_control/client_access_control.c) add_example(access_control_client access_control/client_access_control.c)
if(UA_ENABLE_ENCRYPTION OR UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL") if(UA_ENABLE_ENCRYPTION OR
add_example(access_control_client_encrypt access_control_encrypt/client_access_control_encrypt.c) UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR
UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL")
add_example(access_control_client_encrypt access_control/client_access_control_encrypt.c)
endif() endif()
endif() endif()
@ -208,6 +173,7 @@ if(UA_ENABLE_DISCOVERY_MULTICAST)
endif() endif()
add_subdirectory(nodeset) add_subdirectory(nodeset)
#################### ####################
# Example PubSub # # Example PubSub #
#################### ####################
@ -215,19 +181,23 @@ add_subdirectory(nodeset)
if(UA_ENABLE_PUBSUB) if(UA_ENABLE_PUBSUB)
add_example(tutorial_pubsub_connection pubsub/tutorial_pubsub_connection.c) add_example(tutorial_pubsub_connection pubsub/tutorial_pubsub_connection.c)
add_example(tutorial_pubsub_publish pubsub/tutorial_pubsub_publish.c) add_example(tutorial_pubsub_publish pubsub/tutorial_pubsub_publish.c)
add_example(tutorial_pubsub_subscribe pubsub/tutorial_pubsub_subscribe.c)
add_example(server_pubsub_publish_on_demand pubsub/server_pubsub_publisher_on_demand.c) add_example(server_pubsub_publish_on_demand pubsub/server_pubsub_publisher_on_demand.c)
add_example(server_pubsub_publisher_iop pubsub/server_pubsub_publisher_iop.c) add_example(server_pubsub_publisher_iop pubsub/server_pubsub_publisher_iop.c)
if(NOT WIN32) add_example(pubsub_subscribe_standalone_dataset pubsub/pubsub_subscribe_standalone_dataset.c)
add_example(server_pubsub_publish_rt_level pubsub_realtime/server_pubsub_publisher_rt_level.c)
add_example(server_pubsub_subscribe_rt_level pubsub_realtime/server_pubsub_subscriber_rt_level.c) add_example(server_pubsub_publish_rt_offsets pubsub_realtime/server_pubsub_publish_rt_offsets.c)
add_example(server_pubsub_rt_information_model pubsub_realtime/server_pubsub_rt_field_information_model.c) add_example(server_pubsub_subscribe_rt_offsets pubsub_realtime/server_pubsub_subscribe_rt_offsets.c)
endif()
add_example(tutorial_pubsub_subscribe pubsub/tutorial_pubsub_subscribe.c) if(UA_ARCHITECTURE_POSIX)
if (BUILD_SHARED_LIBS) add_example(server_pubsub_publish_rt_state_machine
message(WARNING "Build option BUILD_SHARED_LIBS not supported for standalone subscriber and realtime examples. Skipping these examples.") pubsub_realtime/server_pubsub_publish_rt_state_machine.c)
else (NOT BUILD_SHARED_LIBS) target_link_libraries(server_pubsub_publish_rt_state_machine "rt")
add_example(pubsub_subscribe_standalone_dataset pubsub/pubsub_subscribe_standalone_dataset.c) add_example(server_pubsub_subscribe_rt_state_machine
pubsub_realtime/server_pubsub_subscribe_rt_state_machine.c)
target_link_libraries(server_pubsub_subscribe_rt_state_machine "rt")
endif() endif()
if(UA_ENABLE_ENCRYPTION_MBEDTLS) if(UA_ENABLE_ENCRYPTION_MBEDTLS)
add_example(pubsub_publish_encrypted pubsub/pubsub_publish_encrypted.c) add_example(pubsub_publish_encrypted pubsub/pubsub_publish_encrypted.c)
add_example(pubsub_subscribe_encrypted pubsub/pubsub_subscribe_encrypted.c) add_example(pubsub_subscribe_encrypted pubsub/pubsub_subscribe_encrypted.c)
@ -236,8 +206,10 @@ if(UA_ENABLE_PUBSUB)
add_example(pubsub_subscribe_encrypted_tpm pubsub/pubsub_subscribe_encrypted_tpm.c) add_example(pubsub_subscribe_encrypted_tpm pubsub/pubsub_subscribe_encrypted_tpm.c)
endif() endif()
if(UA_ENABLE_TPM2_KEYSTORE) if(UA_ENABLE_TPM2_KEYSTORE)
add_example(pubsub_publish_encrypted_tpm_keystore pubsub/pubsub_publish_encrypted_tpm_keystore.c) add_example(pubsub_publish_encrypted_tpm_keystore
add_example(pubsub_subscribe_encrypted_tpm_keystore pubsub/pubsub_subscribe_encrypted_tpm_keystore.c) pubsub/pubsub_publish_encrypted_tpm_keystore.c)
add_example(pubsub_subscribe_encrypted_tpm_keystore
pubsub/pubsub_subscribe_encrypted_tpm_keystore.c)
target_link_libraries(pubsub_publish_encrypted_tpm_keystore tpm2_pkcs11 ssl crypto) target_link_libraries(pubsub_publish_encrypted_tpm_keystore tpm2_pkcs11 ssl crypto)
target_link_libraries(pubsub_subscribe_encrypted_tpm_keystore tpm2_pkcs11 ssl crypto) target_link_libraries(pubsub_subscribe_encrypted_tpm_keystore tpm2_pkcs11 ssl crypto)
endif() endif()
@ -252,10 +224,7 @@ if(UA_ENABLE_PUBSUB)
endif() endif()
if(UA_ENABLE_PUBSUB_FILE_CONFIG) if(UA_ENABLE_PUBSUB_FILE_CONFIG)
# TODO: This example accesses a private API
add_example(server_pubsub_file_configuration pubsub/server_pubsub_file_configuration.c) add_example(server_pubsub_file_configuration pubsub/server_pubsub_file_configuration.c)
target_include_directories(server_pubsub_file_configuration PUBLIC "${PROJECT_SOURCE_DIR}/../src/pubsub")
target_include_directories(server_pubsub_file_configuration PUBLIC "${PROJECT_SOURCE_DIR}/../deps")
endif() endif()
add_example(server_pubsub_subscribe_custom_monitoring add_example(server_pubsub_subscribe_custom_monitoring
@ -265,6 +234,7 @@ endif()
########################### ###########################
# Nodeser Loader Examples # # Nodeser Loader Examples #
########################### ###########################
if(UA_ENABLE_NODESETLOADER) if(UA_ENABLE_NODESETLOADER)
add_subdirectory(nodeset_loader) add_subdirectory(nodeset_loader)
endif() endif()

View File

@ -46,8 +46,6 @@ int main(int argc, char* argv[]) {
UA_Client *client = UA_Client_new(); UA_Client *client = UA_Client_new();
UA_ClientConfig *config = UA_Client_getConfig(client); UA_ClientConfig *config = UA_Client_getConfig(client);
config->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; config->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
UA_String_clear(&config->clientDescription.applicationUri);
config->clientDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.server.application");
UA_ClientConfig_setDefaultEncryption(config, certificate, privateKey, UA_ClientConfig_setDefaultEncryption(config, certificate, privateKey,
trustList, trustListSize, trustList, trustListSize,
revocationList, revocationListSize); revocationList, revocationListSize);

View File

@ -153,13 +153,6 @@ int main(int argc, char *argv[]) {
UA_ClientConfig_setDefault(cc); UA_ClientConfig_setDefault(cc);
#endif #endif
/* The application URI must be the same as the one in the certificate.
* The script for creating a self-created certificate generates a certificate
* with the Uri specified below.*/
UA_ApplicationDescription_clear(&cc->clientDescription);
cc->clientDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.server.application");
cc->clientDescription.applicationType = UA_APPLICATIONTYPE_CLIENT;
/* Connect to the server */ /* Connect to the server */
UA_StatusCode retval = UA_STATUSCODE_GOOD; UA_StatusCode retval = UA_STATUSCODE_GOOD;
if(username) { if(username) {

View File

@ -50,6 +50,55 @@ subscriptionInactivityCallback (UA_Client *client, UA_UInt32 subId, void *subCon
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Inactivity for subscription %u", subId); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Inactivity for subscription %u", subId);
} }
static void
monCallback(UA_Client *client, void *userdata,
UA_UInt32 requestId, void *r) {
UA_CreateMonitoredItemsResponse *monResponse = (UA_CreateMonitoredItemsResponse *)r;
if(0 < monResponse->resultsSize && monResponse->results[0].statusCode == UA_STATUSCODE_GOOD)
{
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Monitoring UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME', id %u",
monResponse->results[0].monitoredItemId);
}
}
static void
createSubscriptionCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *r)
{
UA_CreateSubscriptionResponse *response = (UA_CreateSubscriptionResponse *)r;
if (response->subscriptionId == 0)
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"response->subscriptionId == 0, %u", response->subscriptionId);
else if (response->responseHeader.serviceResult != UA_STATUSCODE_GOOD)
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Create subscription failed, serviceResult %u",
response->responseHeader.serviceResult);
else
{
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Create subscription succeeded, id %u",
response->subscriptionId);
/* Add a MonitoredItem */
UA_NodeId currentTime =
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
UA_CreateMonitoredItemsRequest req;
UA_CreateMonitoredItemsRequest_init(&req);
UA_MonitoredItemCreateRequest monRequest =
UA_MonitoredItemCreateRequest_default(currentTime);
req.itemsToCreate = &monRequest;
req.itemsToCreateSize = 1;
req.subscriptionId = response->subscriptionId;
UA_Client_DataChangeNotificationCallback dataChangeNotificationCallback[1] = { handler_currentTimeChanged };
UA_StatusCode retval =
UA_Client_MonitoredItems_createDataChanges_async(client, req, NULL, dataChangeNotificationCallback, NULL, monCallback, NULL, NULL);
if (retval != UA_STATUSCODE_GOOD)
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"UA_Client_MonitoredItems_createDataChanges_async ", UA_StatusCode_name(retval));
}
}
static void static void
stateCallback(UA_Client *client, UA_SecureChannelState channelState, stateCallback(UA_Client *client, UA_SecureChannelState channelState,
UA_SessionState sessionState, UA_StatusCode recoveryStatus) { UA_SessionState sessionState, UA_StatusCode recoveryStatus) {
@ -76,28 +125,12 @@ stateCallback(UA_Client *client, UA_SecureChannelState channelState,
/* A new session was created. We need to create the subscription. */ /* A new session was created. We need to create the subscription. */
/* Create a subscription */ /* Create a subscription */
UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default(); UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
UA_CreateSubscriptionResponse response = UA_StatusCode retval =
UA_Client_Subscriptions_create(client, request, NULL, NULL, deleteSubscriptionCallback); UA_Client_Subscriptions_create_async(client, request, NULL, NULL, deleteSubscriptionCallback,
if(response.responseHeader.serviceResult == UA_STATUSCODE_GOOD) createSubscriptionCallback, NULL, NULL);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, if (retval != UA_STATUSCODE_GOOD)
"Create subscription succeeded, id %u", UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
response.subscriptionId); "UA_Client_Subscriptions_create_async ", UA_StatusCode_name(retval));
else
return;
/* Add a MonitoredItem */
UA_NodeId currentTimeNode = UA_NS0ID(SERVER_SERVERSTATUS_CURRENTTIME);
UA_MonitoredItemCreateRequest monRequest =
UA_MonitoredItemCreateRequest_default(currentTimeNode);
UA_MonitoredItemCreateResult monResponse =
UA_Client_MonitoredItems_createDataChange(client, response.subscriptionId,
UA_TIMESTAMPSTORETURN_BOTH, monRequest,
NULL, handler_currentTimeChanged, NULL);
if(monResponse.statusCode == UA_STATUSCODE_GOOD)
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Monitoring UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME', id %u",
monResponse.monitoredItemId);
} }
break; break;
case UA_SESSIONSTATE_CLOSED: case UA_SESSIONSTATE_CLOSED:

View File

@ -12,6 +12,8 @@
#define STRING_BUFFER_SIZE 20 #define STRING_BUFFER_SIZE 20
int main(void) { int main(void) {
setupCustomTypes();
/* Make your custom datatype known to the stack */ /* Make your custom datatype known to the stack */
UA_DataType types[4]; UA_DataType types[4];
types[0] = PointType; types[0] = PointType;

View File

@ -1,6 +1,10 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. /* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
/* For dynamic linking (with .dll) on Windows, the memory locations from the dll
* are not available during compilation. This prevents the use of constant initializers.
* So the datatype definitions need to be set up in a method call at runtime. */
typedef struct { typedef struct {
UA_Float x; UA_Float x;
UA_Float y; UA_Float y;
@ -17,48 +21,8 @@ typedef struct {
#define Opt_binary_encoding_id 3 #define Opt_binary_encoding_id 3
#define Uni_binary_encoding_id 4 #define Uni_binary_encoding_id 4
static UA_DataTypeMember Point_members[3];
static UA_DataTypeMember Point_members[3] = { static UA_DataType PointType;
/* x */
{
UA_TYPENAME("x") /* .memberName */
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
0, /* .padding */
false, /* .isArray */
false /* .isOptional */
},
/* y */
{
UA_TYPENAME("y") /* .memberName */
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
Point_padding_y, /* .padding */
false, /* .isArray */
false /* .isOptional */
},
/* z */
{
UA_TYPENAME("z") /* .memberName */
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
Point_padding_z, /* .padding */
false, /* .isArray */
false /* .isOptional */
}
};
static const UA_DataType PointType = {
UA_TYPENAME("Point") /* .tyspeName */
{1, UA_NODEIDTYPE_NUMERIC, {4242}}, /* .typeId */
{1, UA_NODEIDTYPE_NUMERIC, {Point_binary_encoding_id}}, /* .binaryEncodingId, the numeric
identifier used on the wire (the
namespaceindex is from .typeId) */
sizeof(Point), /* .memSize */
UA_DATATYPEKIND_STRUCTURE, /* .typeKind */
true, /* .pointerFree */
false, /* .overlayable (depends on endianness and
the absence of padding) */
3, /* .membersSize */
Point_members
};
/* The datatype description for the Measurement-Series datatype (Array Example)*/ /* The datatype description for the Measurement-Series datatype (Array Example)*/
typedef struct { typedef struct {
@ -67,38 +31,8 @@ typedef struct {
UA_Float *measurement; UA_Float *measurement;
} Measurements; } Measurements;
static UA_DataTypeMember Measurements_members[2] = { static UA_DataTypeMember Measurements_members[2];
{ static UA_DataType MeasurementType;
UA_TYPENAME("Measurement description") /* .memberName */
&UA_TYPES[UA_TYPES_STRING], /* .memberType */
0, /* .padding */
false, /* .isArray */
false /* .isOptional */
},
{
UA_TYPENAME("Measurements") /* .memberName */
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
0, /* .padding */
true, /* .isArray */
false /* .isOptional */
}
};
static const UA_DataType MeasurementType = {
UA_TYPENAME("Measurement") /* .typeName */
{1, UA_NODEIDTYPE_NUMERIC, {4443}}, /* .typeId */
{1, UA_NODEIDTYPE_NUMERIC, {Measurement_binary_encoding_id}}, /* .binaryEncodingId, the numeric
identifier used on the wire (the
namespaceindex is from .typeId) */
sizeof(Measurements), /* .memSize */
UA_DATATYPEKIND_STRUCTURE, /* .typeKind */
false, /* .pointerFree */
false, /* .overlayable (depends on endianness and
the absence of padding) */
2, /* .membersSize */
Measurements_members
};
/* The datatype description for the Opt datatype (Structure with optional fields example)*/ /* The datatype description for the Opt datatype (Structure with optional fields example)*/
typedef struct { typedef struct {
@ -107,50 +41,15 @@ typedef struct {
UA_Float *c; UA_Float *c;
} Opt; } Opt;
static UA_DataTypeMember Opt_members[3] = { static UA_DataTypeMember Opt_members[3];
/* a */ static UA_DataType OptType;
{
UA_TYPENAME("a") /* .memberName */
&UA_TYPES[UA_TYPES_INT16], /* .memberType */
0, /* .padding */
false, /* .isArray */
false /* .isOptional */
},
/* b */
{
UA_TYPENAME("b") /* .memberName */
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
offsetof(Opt,b) - offsetof(Opt,a) - sizeof(UA_Int16), /* .padding */
false, /* .isArray */
true /* .isOptional */
},
/* c */
{
UA_TYPENAME("c") /* .memberName */
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
offsetof(Opt,c) - offsetof(Opt,b) - sizeof(void *), /* .padding */
false, /* .isArray */
true /* .isOptional */
}
};
static const UA_DataType OptType = {
UA_TYPENAME("Opt") /* .typeName */
{1, UA_NODEIDTYPE_NUMERIC, {4644}}, /* .typeId */
{1, UA_NODEIDTYPE_NUMERIC, {Opt_binary_encoding_id}}, /* .binaryEncodingId, the numeric
identifier used on the wire (the
namespaceindex is from .typeId) */
sizeof(Opt), /* .memSize */
UA_DATATYPEKIND_OPTSTRUCT, /* .typeKind */
false, /* .pointerFree */
false, /* .overlayable (depends on endianness and
the absence of padding) */
3, /* .membersSize */
Opt_members
};
/* The datatype description for the Uni datatype (Union example) */ /* The datatype description for the Uni datatype (Union example) */
typedef enum {UA_UNISWITCH_NONE = 0, UA_UNISWITCH_OPTIONA = 1, UA_UNISWITCH_OPTIONB = 2} UA_UniSwitch; typedef enum {
UA_UNISWITCH_NONE = 0,
UA_UNISWITCH_OPTIONA = 1,
UA_UNISWITCH_OPTIONB = 2
} UA_UniSwitch;
typedef struct { typedef struct {
UA_UniSwitch switchField; UA_UniSwitch switchField;
@ -160,34 +59,153 @@ typedef struct {
} fields; } fields;
} Uni; } Uni;
static UA_DataTypeMember Uni_members[2] = { static UA_DataTypeMember Uni_members[2];
{ static UA_DataType UniType;
static void setupCustomTypes(void) {
/* x */
Point_members[0] = (UA_DataTypeMember){
UA_TYPENAME("x") /* .memberName */
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
0, /* .padding */
false, /* .isArray */
false /* .isOptional */
};
/* y */
Point_members[1] = (UA_DataTypeMember){
UA_TYPENAME("y") /* .memberName */
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
Point_padding_y, /* .padding */
false, /* .isArray */
false /* .isOptional */
};
/* z */
Point_members[2] = (UA_DataTypeMember){
UA_TYPENAME("z") /* .memberName */
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
Point_padding_z, /* .padding */
false, /* .isArray */
false /* .isOptional */
};
PointType = (UA_DataType){
UA_TYPENAME("Point") /* .typeName */
{1, UA_NODEIDTYPE_NUMERIC, { 4242 }}, /* .typeId */
{ 1, UA_NODEIDTYPE_NUMERIC, { Point_binary_encoding_id } }, /* .binaryEncodingId, the numeric
identifier used on the wire (the
namespaceindex is from .typeId) */
sizeof(Point), /* .memSize */
UA_DATATYPEKIND_STRUCTURE, /* .typeKind */
true, /* .pointerFree */
false, /* .overlayable (depends on endianness and
the absence of padding) */
3, /* .membersSize */
Point_members
};
Measurements_members[0] = (UA_DataTypeMember) {
UA_TYPENAME("Measurement description") /* .memberName */
&UA_TYPES[UA_TYPES_STRING], /* .memberType */
0, /* .padding */
false, /* .isArray */
false /* .isOptional */
};
Measurements_members[1] = (UA_DataTypeMember) {
UA_TYPENAME("Measurements") /* .memberName */
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
0, /* .padding */
true, /* .isArray */
false /* .isOptional */
};
MeasurementType = (UA_DataType) {
UA_TYPENAME("Measurement") /* .typeName */
{1, UA_NODEIDTYPE_NUMERIC, { 4443 }}, /* .typeId */
{ 1, UA_NODEIDTYPE_NUMERIC, { Measurement_binary_encoding_id } }, /* .binaryEncodingId, the numeric
identifier used on the wire (the
namespaceindex is from .typeId) */
sizeof(Measurements), /* .memSize */
UA_DATATYPEKIND_STRUCTURE, /* .typeKind */
false, /* .pointerFree */
false, /* .overlayable (depends on endianness and
the absence of padding) */
2, /* .membersSize */
Measurements_members
};
/* a */
Opt_members[0] = (UA_DataTypeMember) {
UA_TYPENAME("a") /* .memberName */
&UA_TYPES[UA_TYPES_INT16], /* .memberType */
0, /* .padding */
false, /* .isArray */
false /* .isOptional */
};
/* b */
Opt_members[1] = (UA_DataTypeMember) {
UA_TYPENAME("b") /* .memberName */
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
offsetof(Opt, b) - offsetof(Opt, a) - sizeof(UA_Int16), /* .padding */
false, /* .isArray */
true /* .isOptional */
};
/* c */
Opt_members[2] = (UA_DataTypeMember) {
UA_TYPENAME("c") /* .memberName */
&UA_TYPES[UA_TYPES_FLOAT], /* .memberType */
offsetof(Opt, c) - offsetof(Opt, b) - sizeof(void *), /* .padding */
false, /* .isArray */
true /* .isOptional */
};
OptType = (UA_DataType) {
UA_TYPENAME("Opt") /* .typeName */
{1, UA_NODEIDTYPE_NUMERIC, { 4644 }}, /* .typeId */
{ 1, UA_NODEIDTYPE_NUMERIC, { Opt_binary_encoding_id } }, /* .binaryEncodingId, the numeric
identifier used on the wire (the
namespaceindex is from .typeId) */
sizeof(Opt), /* .memSize */
UA_DATATYPEKIND_OPTSTRUCT, /* .typeKind */
false, /* .pointerFree */
false, /* .overlayable (depends on endianness and
the absence of padding) */
3, /* .membersSize */
Opt_members
};
Uni_members[0] = (UA_DataTypeMember) {
UA_TYPENAME("optionA") /* .memberName */ UA_TYPENAME("optionA") /* .memberName */
&UA_TYPES[UA_TYPES_DOUBLE], /* .memberType */ &UA_TYPES[UA_TYPES_DOUBLE], /* .memberType */
offsetof(Uni, fields.optionA), /* .padding */ (UA_Byte)(offsetof(Uni, fields.optionA) - 0), /* .padding */
false, /* .isArray */ false, /* .isArray */
false /* .isOptional */ false /* .isOptional */
}, };
{
Uni_members[1] = (UA_DataTypeMember) {
UA_TYPENAME("optionB") /* .memberName */ UA_TYPENAME("optionB") /* .memberName */
&UA_TYPES[UA_TYPES_STRING], /* .memberType */ &UA_TYPES[UA_TYPES_STRING], /* .memberType */
offsetof(Uni, fields.optionB), /* .padding */ (UA_Byte)(offsetof(Uni, fields.optionB) - 0), /* .padding */
false, /* .isArray */ false, /* .isArray */
false /* .isOptional */ false /* .isOptional */
} };
};
static const UA_DataType UniType = { UniType = (UA_DataType) {
UA_TYPENAME("Uni") /* .typeName */ UA_TYPENAME("Uni") /* .typeName */
{1, UA_NODEIDTYPE_NUMERIC, {4845}}, /* .typeId */ {1, UA_NODEIDTYPE_NUMERIC, { 4845 }}, /* .typeId */
{1, UA_NODEIDTYPE_NUMERIC, {Uni_binary_encoding_id}}, /* .binaryEncodingId, the numeric { 1, UA_NODEIDTYPE_NUMERIC, { Uni_binary_encoding_id } }, /* .binaryEncodingId, the numeric
identifier used on the wire (the identifier used on the wire (the
namespaceindex is from .typeId) */ namespaceindex is from .typeId) */
sizeof(Uni), /* .memSize */ sizeof(Uni), /* .memSize */
UA_DATATYPEKIND_UNION, /* .typeKind */ UA_DATATYPEKIND_UNION, /* .typeKind */
false, /* .pointerFree */ false, /* .pointerFree */
false, /* .overlayable (depends on endianness and false, /* .overlayable (depends on endianness and
the absence of padding) */ the absence of padding) */
2, /* .membersSize */ 2, /* .membersSize */
Uni_members Uni_members
}; };
}

View File

@ -251,6 +251,8 @@ int main(void) {
UA_ServerConfig *config = UA_Server_getConfig(server); UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefault(config); UA_ServerConfig_setDefault(config);
setupCustomTypes();
/* Make your custom datatype known to the stack */ /* Make your custom datatype known to the stack */
UA_DataType *types = (UA_DataType*)UA_malloc(4 * sizeof(UA_DataType)); UA_DataType *types = (UA_DataType*)UA_malloc(4 * sizeof(UA_DataType));
UA_DataTypeMember *pointMembers = (UA_DataTypeMember*)UA_malloc(sizeof(UA_DataTypeMember) * 3); UA_DataTypeMember *pointMembers = (UA_DataTypeMember*)UA_malloc(sizeof(UA_DataTypeMember) * 3);

View File

@ -240,8 +240,10 @@ int main(int argc, char **argv) {
config->mdnsConfig.mdnsServerName = UA_String_fromChars("Sample-Multicast-Server"); config->mdnsConfig.mdnsServerName = UA_String_fromChars("Sample-Multicast-Server");
//setting custom outbound interface #ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD
//setting custom outbound interface for libmdnsd
config->mdnsInterfaceIP = UA_String_fromChars("0.0.0.0"); config->mdnsInterfaceIP = UA_String_fromChars("0.0.0.0");
#endif
// See http://www.opcfoundation.org/UA/schemas/1.03/ServerCapabilities.csv // See http://www.opcfoundation.org/UA/schemas/1.03/ServerCapabilities.csv
// For a LDS server, you should only indicate the LDS capability. // For a LDS server, you should only indicate the LDS capability.

View File

@ -88,10 +88,10 @@ Create encryption and signing key in both the server and client node filesystems
cd open62541/tools/tpm_keystore/ cd open62541/tools/tpm_keystore/
In server, In server,
python3 ../certs/create_self-signed.py -u urn:open62541.server.application python3 ../certs/create_self-signed.py -u urn:open62541.unconfigured.application -c server
In client, In client,
python3 ../certs/create_self-signed.py -u urn:unconfigured:application -c client python3 ../certs/create_self-signed.py -u urn:open62541.unconfigured.application -c client
Seal the encryption and signing key files using the key available in TPM Seal the encryption and signing key files using the key available in TPM
gcc cert_encrypt_tpm.c -o cert_encrypt_tpm -ltpm2_pkcs11 -lssl -lcrypto gcc cert_encrypt_tpm.c -o cert_encrypt_tpm -ltpm2_pkcs11 -lssl -lcrypto

View File

@ -45,11 +45,9 @@ int main(int argc, char* argv[]) {
UA_Client *client = UA_Client_new(); UA_Client *client = UA_Client_new();
UA_ClientConfig *cc = UA_Client_getConfig(client); UA_ClientConfig *cc = UA_Client_getConfig(client);
cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
UA_String_clear(&cc->clientDescription.applicationUri);
cc->clientDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.server.application");
UA_StatusCode retval = UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey, UA_StatusCode retval = UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey,
trustList, trustListSize, trustList, trustListSize,
revocationList, revocationListSize); revocationList, revocationListSize);
if(retval != UA_STATUSCODE_GOOD) { if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Failed to set encryption." ); "Failed to set encryption." );

View File

@ -12,6 +12,7 @@
#include <open62541/plugin/securitypolicy.h> #include <open62541/plugin/securitypolicy.h>
#include <open62541/server.h> #include <open62541/server.h>
#include <open62541/server_config_default.h> #include <open62541/server_config_default.h>
#include <open62541/plugin/certificategroup_default.h>
#include <signal.h> #include <signal.h>
#include <stdlib.h> #include <stdlib.h>
@ -46,7 +47,7 @@ int main(int argc, char* argv[]) {
UA_UInt32 lenSubject = 3; UA_UInt32 lenSubject = 3;
UA_String subjectAltName[2]= { UA_String subjectAltName[2]= {
UA_STRING_STATIC("DNS:localhost"), UA_STRING_STATIC("DNS:localhost"),
UA_STRING_STATIC("URI:urn:open62541.server.application") UA_STRING_STATIC("URI:urn:open62541.unconfigured.application")
}; };
UA_UInt32 lenSubjectAltName = 2; UA_UInt32 lenSubjectAltName = 2;
UA_KeyValueMap *kvm = UA_KeyValueMap_new(); UA_KeyValueMap *kvm = UA_KeyValueMap_new();
@ -93,6 +94,13 @@ int main(int argc, char* argv[]) {
issuerList, issuerListSize, issuerList, issuerListSize,
revocationList, revocationListSize); revocationList, revocationListSize);
/* Accept all certificates */
config->secureChannelPKI.clear(&config->secureChannelPKI);
UA_CertificateGroup_AcceptAll(&config->secureChannelPKI);
config->sessionPKI.clear(&config->sessionPKI);
UA_CertificateGroup_AcceptAll(&config->sessionPKI);
UA_ByteString_clear(&certificate); UA_ByteString_clear(&certificate);
UA_ByteString_clear(&privateKey); UA_ByteString_clear(&privateKey);
for(size_t i = 0; i < trustListSize; i++) for(size_t i = 0; i < trustListSize; i++)

View File

@ -9,7 +9,6 @@
#include <open62541/client_config_default.h> #include <open62541/client_config_default.h>
#include <open62541/client_subscriptions.h> #include <open62541/client_subscriptions.h>
#include <open62541/plugin/log_stdout.h> #include <open62541/plugin/log_stdout.h>
#include <open62541/eventfilter_parser_examples.h>
#include <signal.h> #include <signal.h>
#include <stdio.h> #include <stdio.h>
@ -18,94 +17,9 @@
* This Tutorial repeats the client_eventfilter.c tutorial, * This Tutorial repeats the client_eventfilter.c tutorial,
* however the filter are created based on the Query Language for Eventfilter * however the filter are created based on the Query Language for Eventfilter
*/ */
static void
check_eventfilter(UA_EventFilter *filter, UA_Boolean print_filter){
UA_EventFilter empty_filter;
UA_EventFilter_init(&empty_filter);
if(memcmp(&empty_filter, filter, sizeof(UA_EventFilter)) == 0){
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Failed to parse the EventFilter");
}
else{
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"EventFilter parsing succeeded");
if(print_filter){
UA_String out = UA_STRING_NULL;
UA_print(filter, &UA_TYPES[UA_TYPES_EVENTFILTER], &out);
printf("%.*s\n", (int)out.length, out.data);
UA_String_clear(&out);
}
}
}
static UA_Boolean running = true; static UA_Boolean running = true;
static UA_StatusCode
read_queries(UA_UInt16 filterSelection, UA_EventFilter *filter){
switch(filterSelection){
case 0 : {
char *inp = "SELECT /Message, /0:Severity /EventType "
"WHERE OR($ref_1, $ref_2) "
"FOR "
"$ref2 := OFTYPE(ns=1;i=5003) AND "
"$ref1 := (OFTYPE i=3035)";
UA_ByteString case_0 = UA_String_fromChars(inp);
UA_EventFilter_parse(filter, case_0, NULL);
check_eventfilter(filter, UA_FALSE);
UA_ByteString_clear(&case_0);
break;
}
case 1 : {
/*query can be found in the file example_queries/case_1 */
UA_ByteString case_1 = UA_String_fromChars(CASE_1);
UA_EventFilter_parse(filter, case_1, NULL);
check_eventfilter(filter, UA_FALSE);
UA_ByteString_clear(&case_1);
break;
}
case 2 : {
/*query can be found in the file example_queries/case_2 */
UA_ByteString case_2 = UA_String_fromChars(CASE_2);
UA_EventFilter_parse(filter, case_2, NULL);
check_eventfilter(filter, UA_FALSE);
UA_ByteString_clear(&case_2);
break;
}
case 3 : {
char *inp = "SELECT /Message, /Severity, /EventType "
"WHERE AND(OFTYPE(ns=1;i=5001), $abc) "
"FOR "
"$abc := AND($xyz, $uvw) AND "
"$xyz := ==(INT64 99, 99) AND "
"$uvw := GREATER(i=5000/Severity, 99)";
UA_ByteString case_3 = UA_String_fromChars(inp);
UA_EventFilter_parse(filter, case_3, NULL);
check_eventfilter(filter, UA_FALSE);
UA_ByteString_clear(&case_3);
break;
}
case 4 : {
char *inp = "SELECT /Message, /0:Severity, /EventType "
"WHERE "
"AND($a, GREATER(i=5000/Severity, $ref)) "
"FOR "
"$ref := 99 AND "
"$a := OFTYPE(ns=1;i=5000)";
UA_ByteString case_4 = UA_String_fromChars(inp);
UA_EventFilter_parse(filter, case_4, NULL);
check_eventfilter(filter, UA_FALSE);
UA_ByteString_clear(&case_4);
break;
}
default:
UA_EventFilter_clear(filter);
return UA_STATUSCODE_BADCONFIGURATIONERROR;
}
return UA_STATUSCODE_GOOD;
}
static void static void
handler_events_filter(UA_Client *client, UA_UInt32 subId, void *subContext, handler_events_filter(UA_Client *client, UA_UInt32 subId, void *subContext,
UA_UInt32 monId, void *monContext, UA_UInt32 monId, void *monContext,
@ -139,14 +53,16 @@ handler_events_filter(UA_Client *client, UA_UInt32 subId, void *subContext,
} }
} }
static UA_StatusCode static UA_StatusCode
create_event_filter_with_monitored_item(UA_UInt16 filterSelection, UA_Client *client, create_event_filter_with_monitored_item(UA_Client *client,
UA_EventFilter *filter, UA_EventFilter *filter,
UA_CreateSubscriptionResponse *response, UA_CreateSubscriptionResponse *response,
UA_MonitoredItemCreateResult *result){ UA_MonitoredItemCreateResult *result){
/* read the eventfilter query string and create the corresponding eventfilter */ /* read the eventfilter query string and create the corresponding eventfilter */
UA_StatusCode retval = read_queries(filterSelection, filter);
char *input = "SELECT /Message, /0:Severity, /EventType "
"WHERE /Severity >= 100";
UA_StatusCode retval = UA_EventFilter_parse(filter, UA_STRING(input), NULL);
if(retval != UA_STATUSCODE_GOOD) { if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Failed to parse the filter query with statuscode %s \n", "Failed to parse the filter query with statuscode %s \n",
@ -187,8 +103,8 @@ create_event_filter_with_monitored_item(UA_UInt16 filterSelection, UA_Client *cl
return UA_STATUSCODE_BAD; return UA_STATUSCODE_BAD;
} }
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Monitoring 'Root->Objects->Server', id %u", "Monitoring 'Root/Objects/Server', id %u",
response->subscriptionId); response->subscriptionId);
monId = result->monitoredItemId; monId = result->monitoredItemId;
return UA_STATUSCODE_GOOD; return UA_STATUSCODE_GOOD;
} }
@ -219,7 +135,7 @@ int main(int argc, char *argv[]) {
UA_EventFilter_init(&filter); UA_EventFilter_init(&filter);
UA_CreateSubscriptionResponse *response = UA_CreateSubscriptionResponse_new(); UA_CreateSubscriptionResponse *response = UA_CreateSubscriptionResponse_new();
UA_MonitoredItemCreateResult *result = UA_MonitoredItemCreateResult_new(); UA_MonitoredItemCreateResult *result = UA_MonitoredItemCreateResult_new();
retval = create_event_filter_with_monitored_item(3, client, &filter, response, result); retval = create_event_filter_with_monitored_item(client, &filter, response, result);
if(retval == UA_STATUSCODE_GOOD){ if(retval == UA_STATUSCODE_GOOD){
while(running) while(running)
UA_Client_run_iterate(client, true); UA_Client_run_iterate(client, true);

View File

@ -1,14 +0,0 @@
/* 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 2023-2024 (c) Fraunhofer IOSB (Author: Florian Düwel)
*/
#ifndef VERSION_H_IN
#define VERSION_H_IN
#define CASE_1 "@CASE_1@"
#define CASE_2 "@CASE_2@"
#endif // VERSION_H_IN

View File

@ -1,11 +0,0 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
*/
SELECT
PATH "/Message", PATH "/0:Severity", PATH "/EventType"
WHERE
OR($"ref_1", $"ref_2")
FOR
$"ref_2":= OFTYPE ns=1;i=5003
$"ref_1":= OFTYPE i=3035

View File

@ -1,24 +0,0 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
*/
SELECT
PATH "/Message",
PATH "/Severity",
PATH "/EventType"
WHERE
OFTYPE ns=1;i=5001
/*alternative queries to create an identical eventfilter
1.
SELECT PATH "/Message", PATH "/Severity", PATH "/EventType"
WHERE
$4
FOR
$4:= OFTYPE ns=1;i=5001
*/

View File

@ -1,86 +0,0 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
*/
SELECT
PATH "/Message", PATH "/Severity", PATH "/EventType"
WHERE
OR(OR(OR(OFTYPE ns=1;i=5002, $4), OR($5, OFTYPE i=3035)), OR($1,$2))
FOR
$1:= OFTYPE $7
$2:= OFTYPE $8
$4:= OFTYPE ns=1;i=5003
$5:= OFTYPE ns=1;i=5004
$7:= NODEID ns=1;i=5000
$8:= ns=1;i=5001
/* alternative queries to create an identical eventfilter
1.
SELECT
PATH "/Message", PATH "/Severity", PATH "/EventType"
WHERE
OR(OR(OR(OFTYPE ns=1;i=5002, OFTYPE ns=1;i=5003), OR(OFTYPE ns=1;i=5004, OFTYPE ns=0;i=3035)), OR(OFTYPE ns=1;i=5000, OFTYPE ns=1;i=5001))
2.
SELECT
PATH "/Message", PATH "/Severity", PATH "/EventType"
WHERE
OR(OR(OR($3, $4), OR($5, $6)), OR($1,$2))
FOR
$1:= OFTYPE ns=1;i=5000
$2:= OFTYPE ns=1;i=5001
$3:= OFTYPE ns=1;i=5002
$4:= OFTYPE ns=1;i=5003
$5:= OFTYPE ns=1;i=5004
$6:= OFTYPE i=3035
3.
SELECT
PATH "/Message", PATH "/Severity", PATH "/EventType"
WHERE
$"test"
FOR
$"test":= OR(OR(OR(OFTYPE ns=1;i=5002, OFTYPE ns=1;i=5003), OR(OFTYPE ns=1;i=5004, OFTYPE ns=0;i=3035)), OR(OFTYPE ns=1;i=5000, OFTYPE ns=1;i=5001))
4.
SELECT
PATH "/Message", PATH "/Severity", PATH "/EventType"
WHERE
OR($"first branch", $"second branch")
FOR
$"first branch":= OR($"third branch", $"fourth branch")
$"second branch":= OR($1, $2)
$"third branch":= OR($3, $4)
$"fourth branch":= OR($5, $6)
$1:= OFTYPE ns=1;i=5000
$2:= OFTYPE ns=1;i=5001
$3:= OFTYPE ns=1;i=5002
$4:= OFTYPE ns=1;i=5003
$5:= OFTYPE $7
$6:= OFTYPE $8
$7:= ns=1;i=5004
$8:= i=3035
5.
SELECT
PATH "/Message", PATH "/Severity", PATH "/EventType"
WHERE
OR(OR(OR(OFTYPE ns=1;i=5002, $4), OR($5, OFTYPE i=3035)), OR($1,$2))
FOR
$1:= OFTYPE $7
$2:= OFTYPE $8
$4:= OFTYPE ns=1;i=5003
$5:= OFTYPE ns=1;i=5004
$7:= NODEID ns=1;i=5000
$8:= TYPEID ns=1;i=5001 PATH "." ATTRIBUTE 1
*/

View File

@ -1,17 +0,0 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
*/
SELECT
PATH "/Message",
PATH "/Severity",
PATH "/EventType"
WHERE
AND((OFTYPE ns=1;i=5001), $1)
FOR
$1:= AND($20, $30)
$20:= 99 == Int64 99
$30:= TYPEID i=5000 PATH "/Severity" > 99

View File

@ -1,17 +0,0 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
*/
SELECT
PATH "/Message",
PATH "/0:Severity",
PATH "/EventType"
WHERE
AND($4, TYPEID i=5000 PATH "/Severity" GREATERTHAN $"ref")
FOR
$"ref":= 99
$4:= OFTYPE ns=1;i=5000

View File

@ -135,8 +135,8 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
* received DataSet fields and target Variables in the Subscriber AddressSpace. * received DataSet fields and target Variables in the Subscriber AddressSpace.
* The values subscribed from the Publisher are updated in the value field of these variables */ * The values subscribed from the Publisher are updated in the value field of these variables */
/* Create the TargetVariables with respect to DataSetMetaData fields */ /* Create the TargetVariables with respect to DataSetMetaData fields */
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*)
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
/* Variable to subscribe data */ /* Variable to subscribe data */
UA_VariableAttributes vAttr = UA_VariableAttributes_default; UA_VariableAttributes vAttr = UA_VariableAttributes_default;
@ -152,18 +152,13 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
UA_QUALIFIEDNAME(1, (char *)readerConfig.dataSetMetaData.fields[i].name.data), UA_QUALIFIEDNAME(1, (char *)readerConfig.dataSetMetaData.fields[i].name.data),
UA_NS0ID(BASEDATAVARIABLETYPE), UA_NS0ID(BASEDATAVARIABLETYPE),
vAttr, NULL, &newNode); vAttr, NULL, &newNode);
targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
/* For creating Targetvariables */ targetVars[i].targetNodeId = newNode;
UA_FieldTargetDataType_init(&targetVars[i].targetVariable);
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[i].targetVariable.targetNodeId = newNode;
} }
UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
readerConfig.dataSetMetaData.fieldsSize, readerConfig.dataSetMetaData.fieldsSize,
targetVars); targetVars);
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
UA_free(targetVars); UA_free(targetVars);
UA_free(readerConfig.dataSetMetaData.fields); UA_free(readerConfig.dataSetMetaData.fields);

View File

@ -142,8 +142,8 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
* received DataSet fields and target Variables in the Subscriber AddressSpace. * received DataSet fields and target Variables in the Subscriber AddressSpace.
* The values subscribed from the Publisher are updated in the value field of these variables */ * The values subscribed from the Publisher are updated in the value field of these variables */
/* Create the TargetVariables with respect to DataSetMetaData fields */ /* Create the TargetVariables with respect to DataSetMetaData fields */
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType *)
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
/* Variable to subscribe data */ /* Variable to subscribe data */
UA_VariableAttributes vAttr = UA_VariableAttributes_default; UA_VariableAttributes vAttr = UA_VariableAttributes_default;
@ -160,17 +160,13 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
UA_NS0ID(BASEDATAVARIABLETYPE), UA_NS0ID(BASEDATAVARIABLETYPE),
vAttr, NULL, &newNode); vAttr, NULL, &newNode);
/* For creating Targetvariables */ targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
UA_FieldTargetDataType_init(&targetVars[i].targetVariable); targetVars[i].targetNodeId = newNode;
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[i].targetVariable.targetNodeId = newNode;
} }
UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
readerConfig.dataSetMetaData.fieldsSize, readerConfig.dataSetMetaData.fieldsSize,
targetVars); targetVars);
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
UA_free(targetVars); UA_free(targetVars);
UA_free(readerConfig.dataSetMetaData.fields); UA_free(readerConfig.dataSetMetaData.fields);

View File

@ -178,8 +178,8 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
* received DataSet fields and target Variables in the Subscriber AddressSpace. * received DataSet fields and target Variables in the Subscriber AddressSpace.
* The values subscribed from the Publisher are updated in the value field of these variables */ * The values subscribed from the Publisher are updated in the value field of these variables */
/* Create the TargetVariables with respect to DataSetMetaData fields */ /* Create the TargetVariables with respect to DataSetMetaData fields */
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*)
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
/* Variable to subscribe data */ /* Variable to subscribe data */
UA_VariableAttributes vAttr = UA_VariableAttributes_default; UA_VariableAttributes vAttr = UA_VariableAttributes_default;
@ -196,16 +196,12 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
UA_NS0ID(BASEDATAVARIABLETYPE), UA_NS0ID(BASEDATAVARIABLETYPE),
vAttr, NULL, &newNode); vAttr, NULL, &newNode);
/* For creating Targetvariables */ targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
UA_FieldTargetDataType_init(&targetVars[i].targetVariable); targetVars[i].targetNodeId = newNode;
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[i].targetVariable.targetNodeId = newNode;
} }
retval = UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, retval = UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
readerConfig.dataSetMetaData.fieldsSize, targetVars); readerConfig.dataSetMetaData.fieldsSize, targetVars);
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
UA_free(targetVars); UA_free(targetVars);
UA_free(readerConfig.dataSetMetaData.fields); UA_free(readerConfig.dataSetMetaData.fields);

View File

@ -6,14 +6,14 @@
#include <open62541/server_pubsub.h> #include <open62541/server_pubsub.h>
#include <open62541/server_config_default.h> #include <open62541/server_config_default.h>
#include "ua_pubsub_internal.h"
#include "common.h" #include "common.h"
/* Function to give user information about correct usage */ /* Function to give user information about correct usage */
static void usage_info(void) { static void usage_info(void) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "USAGE: ./server_pubsub_file_configuration [name of UA_Binary_Config_File]"); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Alternatively, Bin-files can be loaded via configuration method calls."); "USAGE: ./server_pubsub_file_configuration [name of UA_Binary_Config_File]");
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
"Alternatively, Bin-files can be loaded via configuration method calls.");
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {
@ -107,7 +107,8 @@ int main(int argc, char** argv) {
statusCode |= UA_Server_enableAllPubSubComponents(server); statusCode |= UA_Server_enableAllPubSubComponents(server);
statusCode |= UA_Server_runUntilInterrupt(server); statusCode |= UA_Server_runUntilInterrupt(server);
if(statusCode != UA_STATUSCODE_GOOD) { if(statusCode != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Server stopped. Status code: 0x%x\n", statusCode); UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Server stopped. Status code: 0x%x\n", statusCode);
return(-1); return(-1);
} }
@ -120,7 +121,8 @@ int main(int argc, char** argv) {
if(statusCode != UA_STATUSCODE_GOOD) if(statusCode != UA_STATUSCODE_GOOD)
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Saving PubSub configuration to file failed. StatusCode: 0x%x\n", statusCode); "Saving PubSub configuration to file failed. "
"StatusCode: 0x%x\n", statusCode);
UA_ByteString_clear(&buffer); UA_ByteString_clear(&buffer);
} }

View File

@ -123,8 +123,8 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId) {
UA_NS0ID(BASEOBJECTTYPE), oAttr, NULL, &folderId); UA_NS0ID(BASEOBJECTTYPE), oAttr, NULL, &folderId);
/* Create the TargetVariables with respect to DataSetMetaData fields */ /* Create the TargetVariables with respect to DataSetMetaData fields */
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*)
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
/* Variable to subscribe data */ /* Variable to subscribe data */
UA_VariableAttributes vAttr = UA_VariableAttributes_default; UA_VariableAttributes vAttr = UA_VariableAttributes_default;
@ -141,15 +141,14 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId) {
UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newNode); UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newNode);
/* For creating Targetvariables */ /* For creating Targetvariables */
UA_FieldTargetDataType_init(&targetVars[i].targetVariable); targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; targetVars[i].targetNodeId = newNode;
targetVars[i].targetVariable.targetNodeId = newNode;
} }
UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
readerConfig.dataSetMetaData.fieldsSize, targetVars); readerConfig.dataSetMetaData.fieldsSize, targetVars);
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable); UA_FieldTargetDataType_clear(&targetVars[i]);
UA_free(targetVars); UA_free(targetVars);
UA_free(readerConfig.dataSetMetaData.fields); UA_free(readerConfig.dataSetMetaData.fields);

View File

@ -141,9 +141,9 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId) {
* between received DataSet fields and target Variables in the Subscriber * between received DataSet fields and target Variables in the Subscriber
* AddressSpace. The values subscribed from the Publisher are updated in the value * AddressSpace. The values subscribed from the Publisher are updated in the value
* field of these variables */ * field of these variables */
/* Create the TargetVariables with respect to DataSetMetaData fields */
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *)UA_calloc( UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*)
readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
/* Variable to subscribe data */ /* Variable to subscribe data */
UA_VariableAttributes vAttr = UA_VariableAttributes_default; UA_VariableAttributes vAttr = UA_VariableAttributes_default;
@ -160,16 +160,12 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId) {
UA_QUALIFIEDNAME(1, (char *)readerConfig.dataSetMetaData.fields[i].name.data), UA_QUALIFIEDNAME(1, (char *)readerConfig.dataSetMetaData.fields[i].name.data),
UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newNode); UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newNode);
/* For creating Targetvariables */ targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
UA_FieldTargetDataType_init(&targetVars[i].targetVariable); targetVars[i].targetNodeId = newNode;
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[i].targetVariable.targetNodeId = newNode;
} }
UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
readerConfig.dataSetMetaData.fieldsSize, targetVars); readerConfig.dataSetMetaData.fieldsSize, targetVars);
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
UA_free(targetVars); UA_free(targetVars);
UA_free(readerConfig.dataSetMetaData.fields); UA_free(readerConfig.dataSetMetaData.fields);

View File

@ -428,11 +428,6 @@ main(int argc, char **argv) {
if(enableTime) if(enableTime)
config.verifyRequestTimestamp = UA_RULEHANDLING_DEFAULT; config.verifyRequestTimestamp = UA_RULEHANDLING_DEFAULT;
/* Override with a custom access control policy */
UA_String_clear(&config.applicationDescription.applicationUri);
config.applicationDescription.applicationUri =
UA_String_fromChars("urn:open62541.server.application");
config.shutdownDelay = 5000.0; /* 5s */ config.shutdownDelay = 5000.0; /* 5s */
/* Add supported pubsub security policies by this sks instance */ /* Add supported pubsub security policies by this sks instance */

View File

@ -215,8 +215,8 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
* received DataSet fields and target Variables in the Subscriber AddressSpace. * received DataSet fields and target Variables in the Subscriber AddressSpace.
* The values subscribed from the Publisher are updated in the value field of these variables */ * The values subscribed from the Publisher are updated in the value field of these variables */
/* Create the TargetVariables with respect to DataSetMetaData fields */ /* Create the TargetVariables with respect to DataSetMetaData fields */
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType *)
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
/* Variable to subscribe data */ /* Variable to subscribe data */
UA_VariableAttributes vAttr = UA_VariableAttributes_default; UA_VariableAttributes vAttr = UA_VariableAttributes_default;
@ -233,17 +233,13 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
UA_NS0ID(BASEDATAVARIABLETYPE), UA_NS0ID(BASEDATAVARIABLETYPE),
vAttr, NULL, &newNode); vAttr, NULL, &newNode);
/* For creating Targetvariables */ targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
UA_FieldTargetDataType_init(&targetVars[i].targetVariable); targetVars[i].targetNodeId = newNode;
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[i].targetVariable.targetNodeId = newNode;
} }
UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
readerConfig.dataSetMetaData.fieldsSize, readerConfig.dataSetMetaData.fieldsSize,
targetVars); targetVars);
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
UA_free(targetVars); UA_free(targetVars);
UA_free(readerConfig.dataSetMetaData.fields); UA_free(readerConfig.dataSetMetaData.fields);

View File

@ -117,15 +117,17 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(ORGANIZES), UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(ORGANIZES),
folderBrowseName, UA_NS0ID(BASEOBJECTTYPE), oAttr, NULL, &folderId); folderBrowseName, UA_NS0ID(BASEOBJECTTYPE), oAttr, NULL, &folderId);
/** /**
* **TargetVariables** * **TargetVariables**
* *
* The SubscribedDataSet option TargetVariables defines a list of Variable mappings between * The SubscribedDataSet option TargetVariables defines a list of Variable
* received DataSet fields and target Variables in the Subscriber AddressSpace. * mappings between received DataSet fields and target Variables in the
* The values subscribed from the Publisher are updated in the value field of these variables */ * Subscriber AddressSpace. The values subscribed from the Publisher are
* updated in the value field of these variables */
/* Create the TargetVariables with respect to DataSetMetaData fields */ /* Create the TargetVariables with respect to DataSetMetaData fields */
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType *)
UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType));
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
/* Variable to subscribe data */ /* Variable to subscribe data */
UA_VariableAttributes vAttr = UA_VariableAttributes_default; UA_VariableAttributes vAttr = UA_VariableAttributes_default;
@ -143,16 +145,13 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
vAttr, NULL, &newNode); vAttr, NULL, &newNode);
/* For creating Targetvariables */ /* For creating Targetvariables */
UA_FieldTargetDataType_init(&targetVars[i].targetVariable); targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; targetVars[i].targetNodeId = newNode;
targetVars[i].targetVariable.targetNodeId = newNode;
} }
UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId,
readerConfig.dataSetMetaData.fieldsSize, readerConfig.dataSetMetaData.fieldsSize,
targetVars); targetVars);
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
UA_free(targetVars); UA_free(targetVars);
UA_free(readerConfig.dataSetMetaData.fields); UA_free(readerConfig.dataSetMetaData.fields);

View File

@ -0,0 +1,34 @@
# open62541 Realtime-PubSub Support
Two features of open625451 enable realtime Pubsub: The custom state machine for
PubSub-Components and the generation of offset tables for the PubSub
NetworkMessage. Both features are showcased in the respective examples for
Publishers and Subscribers.
## Custom State Machine
The custom state machine mechanism lets user-defined code control the state of
the PubSub-Components (PubSubConnection, ReaderGroup, Reader, ...). Hence the
user-defined code is also responsible to register time-based events, network
connections, and so on.
This allows the integration with non-POSIX networking APIs for publishers and
subscribers. Note that the provided examples are still POSIX based, but handle
all sockets in userland.
## NetworkMessage Offset Table
Typically realtime PubSub is used together with the periodic-fixed header layout
where the position of the content in the NetworkMessage does not change between
publish cycles. Then the NetworkMessage offset tables can be generated to speed
up the updating and parsing of NetworkMessages. For the relevant fields, the
well-known offsets are computed ahead of time. Each offset furthermore has a
known source (the NodeId of the respective PubSub-Component in the information
model).
The NetworkMessage offset table is usually generated when the
ReaderGroup/WriterGroup changes its state to OPERATIONAL. At this time, from the
offset table, each field of the NetworkMessage is resolved to point to a backend
information source with fast access. Then the updating/parsing of the
NetworkMessage in the publish interval is simply a matter of looping over the
offset table and performing mostly memcpy steps.

View File

@ -1,78 +0,0 @@
# open62541 Realtime OPC UA PubSub Publisher
This example is a self-contained PubSub publisher over raw Ethernet. It
showcases the realtime-capabilities of OPC UA PubSub. The core idea is that the
publisher callback can be triggered from a time-triggered system interrupt and
sends out the PubSub message within the interrupt.
The publisher retrieves its configuration and the payload data from the
information model of an OPC UA server. (It could also be run in a standalone
mode without an OPC UA server.) Since the publisher interrupt preempts the
execution of the normal OPC UA server, the information model needs to be
consistent at every time (reentrant). The specific techniques used to make the
OPC UA server reentrant are described in this publication:
```
@inproceedings{pfrommer2018open,
title={Open source OPC UA PubSub over TSN for realtime industrial communication},
author={Pfrommer, Julius and Ebner, Andreas and Ravikumar, Siddharth and Karunakaran, Bhagath},
booktitle={2018 IEEE 23rd International Conference on Emerging Technologies and Factory Automation (ETFA)},
pages={1087--1090},
year={2018},
organization={IEEE}
}
```
Please cite if you use this work.
OPC UA PubSub for open62541 is funded by an industry consortium in the context
of an OSADL project (Open Source Automation Development Lab). Technical
development is conducted by Fraunhofer IOSB and Kalycito Infotech.
https://www.osadl.org/OPC-UA-TSN.opcua-tsn.0.html
## Realtime communication with Time-Sensitive Networking (TSN)
OPC UA PubSub can be used together with TSN for hard-realtime Ethernet-based
communication. Vendor-specific APIs are commonly used for TSN capabilities. This
example only uses the standard Linux API for raw Ethernet. Vendor-specific
examples may be added at a later time.
## Building the RT Publisher
The main open62541 library needs to be built with the following build options
enabled for the realtime PubSub example. Note that some of the other examples
supplied with open62541 will not link against the library with these build
options. For good timings, ensure that the `CMAKE_BUILD_TYPE` is set to
`Release`.
- UA_ENABLE_PUBSUB
- UA_BUILD_EXAMPLES
- UA_ENABLE_MALLOC_SINGLETON
- UA_ENABLE_PUBSUB_BUFMALLOC
- UA_ENABLE_IMMUTABLE_NODES
The publisher contains some hard-coded values that need to be adjusted to
specific systems. Please check the top definitions in
`pubsub_interrupt_publish.c` and `start_rt_publish.sh`.
## Running the RT Publisher
The publisher must be run as root for direct access to the Ethernet interface.
`# ./rt_publisher`
The example contains a script to be used with RT-Preempt Linux kernel. The
following command starts the publisher, locks the process to a specific CPU, and
sets the scheduling policy.
`# start_rt_publish.sh ./rt_publisher`
The measurements are written to a file (publisher_measurement.csv) with the
following fields for every publish callback:
- Counter
- Publication Interval
- Nominal time for the current publish
- Start delay from the nominal time
- Duration of the publish callback

View File

@ -1,188 +0,0 @@
Tested Environment:
Apollo Lake processor-1.60GHz with Intel i210 Ethernet Controller
OS - Debian/Lubuntu
Kernel - 4.19.37-rt19, 5.4.59-rt36
[Note: The pubsub TSN applications have two functionalities - ETF and XDP.
ETF will work in all RT kernels above 4.19. XDP is tested only on 5.4.59-rt36 kernel and it may work on kernels above 5.4.
If user wishes to run these pubsub TSN applications in the kernel below 4.19, there might be minimal changes required in the applications]
PRE-REQUISITES:
We recommend at least two Intel x86-based nodes with 4-cores and Intel i210 Ethernet Controllers connected in peer-peer fashion
RT enabled kernel in nodes (say node1 and node2)
Ensure the nodes are ptp synchronized
PTP SYNCHRONIZATION:
Clone and install linuxptp version 2.0
git clone https://github.com/richardcochran/linuxptp.git
cd linuxptp/
git checkout -f 059269d0cc50f8543b00c3c1f52f33a6c1aa5912
make
make install
cp configs/gPTP.cfg /etc/linuxptp/
Create VLAN with VLAN ID of 8 in peer to peer connected interface, copy paste the following lines in "/etc/network/interfaces" file and setup suitable IP address
auto <i210 interface>.8
iface <i210 interface>.8 inet static
address <suitable IP address>
netmask 255.255.255.0
To make node as ptp master run the following command (say in node1):
sudo daemonize -E BUILD_ID=dontKillMe -o /var/log/ptp4l.log -e /var/log/ptp4l.err.log /usr/bin/taskset -c 1 chrt 90 /usr/local/sbin/ptp4l -i <I210 interface> -2 -mq -f /etc/linuxptp/gPTP.cfg --step_threshold=1 --fault_reset_interval=0 --announceReceiptTimeout=10 --transportSpecific=1
To make node as ptp slave run the following command (say in node2):
sudo daemonize -E BUILD_ID=dontKillMe -o /var/log/ptp4l.log -e /var/log/ptp4l.err.log /usr/bin/taskset -c 1 chrt 90 /usr/local/sbin/ptp4l -i <I210 interface> -2 -mq -s -f /etc/linuxptp/gPTP.cfg --step_threshold=1 --fault_reset_interval=0 --announceReceiptTimeout=10 --transportSpecific=1
To ensure phc2sys synchronization (in both nodes, node1 and node2):
sudo daemonize -E BUILD_ID=dontKillMe -o /var/log/phc2sys.log -e /var/log/phc2sys.err.log /usr/bin/taskset -c 1 chrt 89 /usr/local/sbin/phc2sys -s <I210 interface> -c CLOCK_REALTIME --step_threshold=1 --transportSpecific=1 -w -m
Check if ptp4l and phc2sys are running
ptp4l log in /var/log/ptp4l.log
phc2sys log in /var/log/phc2sys.log
Check if /etc/modules has entry for 8021q
cat /etc/modules | grep '8021q'
For ETF Transmit: (In both nodes)
#Configure ETF
sudo tc qdisc add dev <I210 interface> parent root mqprio num_tc 3 map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 queues 1@0 1@1 2@2 hw 0
MQPRIO_NUM=`sudo tc qdisc show | grep mqprio | cut -d ':' -f1 | cut -d ' ' -f3`
sudo tc qdisc add dev <I210 interface> parent $MQPRIO_NUM:1 etf offload clockid CLOCK_TAI delta 150000
sudo tc qdisc add dev <I210 interface> parent $MQPRIO_NUM:2 etf offload clockid CLOCK_TAI delta 150000
In both nodes:
for j in `seq 0 7`;do sudo ip link set <I210 interface>.8 type vlan egress $j:$j ; done
for j in `seq 0 7`;do sudo ip link set <I210 interface>.8 type vlan ingress $j:$j ; done
TO RUN ETF APPLICATIONS:
To run ETF applications over Ethernet in two nodes connected in peer-to-peer network
mkdir build
cd build
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON ..
make
The generated binaries are generated in build/bin/ folder
./bin/examples/pubsub_TSN_publisher -interface <I210 interface> - Run in node 1
./bin/examples/pubsub_TSN_loopback -interface <I210 interface> - Run in node 2
Eg: ./bin/examples/pubsub_TSN_publisher -interface enp2s0
./bin/examples/pubsub_TSN_loopback -interface enp2s0
NOTE: It is always recommended to run pubsub_TSN_loopback application first to avoid missing the initial data published by pubsub_TSN_publisher
To know more usage
./bin/examples/pubsub_TSN_publisher -help
./bin/examples/pubsub_TSN_loopback -help
NOTE: To know more about running the OPC UA PubSub application and to evaluate performance, refer the Quick Start Guide - https://www.kalycito.com/how-to-run-opc-ua-pubsub-tsn/
============================================================================================================
You can also subscribe using XDP (Express Data Path) for faster processing the data, follow the below steps:
Pre-requisties for XDP:
Minimum kernel version of 5.4 (Tested in RT enabled kernel - 5.4.59-rt36 with Debian OS)
Install llvm and clang
apt-get install llvm
apt-get install clang
Clone libbpf using the following steps:
git clone https://github.com/libbpf/libbpf.git
cd libbpf/
git checkout ab067ed3710550c6d1b127aac6437f96f8f99447
cd src/
OBJDIR=/usr/lib make install
Check if libbpf.so is available in /usr/lib folder, and bpf/ folder is available in /usr/include folder in both nodes (node1 and node2)
NOTE: Make sure the header file if_xdp.h available in /usr/include/linux/ is the right header file as in kernel version 5.4.59, if not update the if_xdp.h to newer version. (Verify your if_xdp.h file with https://elixir.bootlin.com/linux/v5.4.36/source/include/uapi/linux/if_xdp.h)
As per the sample application, XDP listens to Rx_Queue 2; to direct the incoming ethernet packets to the second Rx_Queue, the follow the given steps
In both nodes:
INGRESS POLICY steps:
#Configure equal weightage to queue 0 and 1
ethtool -X <I210 interface> equal 2
#Disable VLAN offload
ethtool -K <I210 interface> rxvlan off
ethtool -K <I210 interface> ntuple on
ethtool --config-ntuple <I210 interface> delete 15
ethtool --config-ntuple <I210 interface> delete 14
#Below command forwards the VLAN tagged packets to RX_Queue 2
ethtool --config-ntuple <I210 interface> flow-type ether proto 0x8100 dst 01:00:5E:7F:00:01 loc 15 action 2
ethtool --config-ntuple <I210 interface> flow-type ether proto 0x8100 dst 01:00:5E:7F:00:02 loc 14 action 2
To run ETF in Publisher and XDP in Subscriber in two nodes connected in peer-to-peer network
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON ..
make
By default XDP will be disabled in the Subscriber, use -enableXdpSubscribe to use XDP in RX.
./bin/examples/pubsub_TSN_publisher -interface <I210 interface> -enableXdpSubscribe - Run in node 1
./bin/examples/pubsub_TSN_loopback -interface <I210 interface> -enableXdpSubscribe - Run in node 2
Optional steps:
By default, XDP listens to RX_Queue 2 with SKB and COPY mode.
To make XDP listen to other RX_Queue, provide -xdpQueue <num> as arguments when executing applications and modify the action value (INGRESS POLICY) to the desired queue number.
To enable ZEROCOPY mode, provide -xdpBindFlagZeroCopy as an additional argument to the applications
To enable XDP Driver (DRV) mode, provide -xdpFlagDrvMode as an additional argument to the applications
NOTE: To enable XDP socket, pass its configuration parameters through connectionProperties (KeyValuePair) in connectionConfig as mentioned below. XDP socket has support
for both Publisher and Subscriber connection but it is recommended to use XDP only in Subscriber connection.
UA_KeyValuePair connectionOptions[4]; // KeyValuePair for XDP configuration
connectionOptions[0].key = UA_QUALIFIEDNAME(0, "enableXdpSocket"); // Key for enabling the XDP socket
UA_Boolean enableXdp = UA_TRUE; // Boolean value for enabling and disabling XDP socket
UA_Variant_setScalar(&connectionOptions[0].value, &enableXdp, &UA_TYPES[UA_TYPES_BOOLEAN]);
connectionOptions[1].key = UA_QUALIFIEDNAME(0, "xdpflag"); // Key for the XDP flags
UA_UInt32 flags = xdpFlag; // Value to determine whether XDP works in SKB mode or DRV mode
UA_Variant_setScalar(&connectionOptions[1].value, &flags, &UA_TYPES[UA_TYPES_UINT32]);
connectionOptions[2].key = UA_QUALIFIEDNAME(0, "hwreceivequeue"); // Key for the hardware queue
UA_UInt32 rxqueue = xdpQueue; // Value of the hardware queue to receive the packets
UA_Variant_setScalar(&connectionOptions[2].value, &rxqueue, &UA_TYPES[UA_TYPES_UINT32]);
connectionOptions[3].key = UA_QUALIFIEDNAME(0, "xdpbindflag"); // Key for the XDP bind flags
UA_UInt32 bindflags = xdpBindFlag; // Value to determine whether XDP works in COPY or ZEROCOPY mode
UA_Variant_setScalar(&connectionOptions[3].value, &bindflags, &UA_TYPES[UA_TYPES_UINT16]);
connectionConfig.connectionProperties = connectionOptions; // Provide all the KeyValuePairs to properties of connectionConfig
connectionConfig.connectionPropertiesSize = 4;
To know more usage
./bin/examples/pubsub_TSN_publisher -help
./bin/examples/pubsub_TSN_loopback -help
===================================================================================================================================================================
NOTE:
If VLAN tag is used:
Ensure the MAC address is given along with VLAN ID and PCP
Eg: "opc.eth://01-00-5E-00-00-01:8.3" where 8 is the VLAN ID and 3 is the PCP
If VLAN tag is not used:
Ensure the MAC address is not given along with VLAN ID and PCP
Eg: "opc.eth://01-00-5E-00-00-01"
If MAC address is changed, follow INGRESS POLICY steps with dst address to be the same as SUBSCRIBING_MAC_ADDRESS for both nodes
To increase the payload size, change the REPEATED_NODECOUNTS macro in pubsub_TSN_publisher.c and pubsub_TSN_loopback.c applications (for 1 REPEATED_NODECOUNTS, 9 bytes of data will increase in payload)
=====================================================================================================================================================================
TO RUN THE UNIT TEST CASES FOR ETHERNET FUNCTIONALITY:
Change the ethernet interface in #define ETHERNET_INTERFACE macro in
check_pubsub_connection_ethernet.c
check_pubsub_publish_ethernet.c
check_pubsub_connection_ethernet_etf.c
check_pubsub_publish_ethernet_etf.c
1. To test ethernet connection creation and ethernet publish unit tests(without realtime)
cd open62541/build
make clean
cmake -DUA_ENABLE_PUBSUB=ON -DUA_BUILD_UNIT_TESTS=ON ..
make
The following binaries are generated in build/bin/tests folder
./bin/tests/check_pubsub_connection_ethernet - To check ethernet connection creation
./bin/tests/check_pubsub_publish_ethernet - To check ethernet send functionality
2. To test ethernet connection creation and ethernet publish unit tests(with realtime)
cd open62541/build
make clean
cmake -DUA_ENABLE_PUBSUB=ON -DUA_BUILD_UNIT_TESTS=ON ..
make
The following binaries are generated in build/bin/tests folder
./bin/tests/check_pubsub_connection_ethernet_etf - To check ethernet connection creation with etf
./bin/tests/check_pubsub_publish_ethernet_etf - To check ethernet send functionality with etf
TO RUN THE UNIT TEST CASES FOR XDP FUNCTIONALITY:
1. To test connection creation using XDP transport layer
cd open62541/build
make clean
cmake -DUA_ENABLE_PUBSUB=ON -DUA_BUILD_UNIT_TESTS=ON ..
make
The following binary is generated in build/bin/tests folder
./bin/tests/check_pubsub_connection_xdp <I210 interface> - To check connection creation with XDP

View File

@ -1,195 +0,0 @@
Tested Environment:
TTTech IP (2.3.0) - Arm architecture
Kernel - 5.4.40 (non-RT)
============================================================================================================
PRE-REQUISITES:
1) We recommend at least two TTTech TSN IP nodes with 2-cores
2) Ensure the nodes are ptp synchronized
deptp_tool --get-current-dataset
3) Add and aditional clock domain /etc/deptp/ptp_config.xml, reboot the board after applying this change (this is a one time step)
<Clock>
<clock_class>187</clock_class>
<clock_accuracy>1us</clock_accuracy>
<clock_priority1>247</clock_priority1>
<clock_priority2>248</clock_priority2>
<domain>100</domain>
<time_source>internal oscillator</time_source>
</Clock>
4) Create VLAN with VLAN ID of 8 in peer to peer connected interface using the below command
ip link add link <interfaceName> name myvlan type vlan id 8 ingress-qos-map 0:7 1:7 2:7 3:7 4:7 5:7 6:7 7:7 egress-qos-map 0:7 1:7 2:7 3:7 4:7 5:7 6:7 7:7
ip addr add <VlanIpAddress> dev myvlan
ip link set dev myvlan up
bridge vlan add vid 8 dev <portName>
bridge vlan add vid 8 dev <portName2> (optional e.g. for monitoring)
bridge vlan add vid 8 dev <InternalPortName>
ip route add 224.0.0.0/24 dev myvlan
NOTE: <portName> refers to the external physical port that has ethernet cable connected to it in each node
5) Create the Qbv configuration i.e setting up the gating configuration:
1)Create a config file on both the nodes using the below command
touch qbv.cfg
2)At node 1 copy the below gating configuration and paste it in the qbv.cfg
sgs 5000 0x80
sgs 490000 0x7F
3)At node 2 copy the below gating configuration and paste it in the qbv.cfg
sgs 5000 0x80
sgs 490000 0x7F
4)To schedule the gating configuration run the below command on both the nodes
tsntool st wrcl <portName> qbv.cfg
tsntool st rdacl <portName> /dev/stdout
tsntool st configure 5.0 1/2000 10000 <portName>
tsntool st show <portName>
6) After these steps the following files shoud exist and have values:
cat /run/ptp_wc_mon_offset
cat /sys/class/net/<portName>/ieee8021ST/OperBaseTime
============================================================================================================
Cross Compiler options to compile:
1) To compile the binaries create the folder using the below command:
mkdir build
2) Traverse to the folder using the command
cd build
3) Install the arm cross compiler - arm-linux-gnueabihf
4) Cross compilation for ARM architecture
CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc
CMAKE_C_COMPILER_AR /usr/bin/arm-linux-gnueabihf-gcc-ar-7
CMAKE_C_COMPILER_RANLIB /usr/bin/arm-linux-gnueabihf-gcc-ranlib-7
CMAKE_C_FLAGS -Wno-error
CMAKE_C_FLAGS_DEBUG -g -Wno-error
CMAKE_C_LINKER /usr/bin/arm-linux-gnueabihf-ld
CMAKE_NM /usr/bin/arm-linux-gnueabihf-nm
CMAKE_OBJCOPY /usr/bin/arm-linux-gnueabihf-objcopy
CMAKE_OBJDUMP /usr/bin/arm-linux-gnueabihf-objdump
CMAKE_RANLIB /usr/bin/arm-linux-gnueabihf-ranlib
CMAKE_STRIP /usr/bin/arm-linux-gnueabihf-strip
Note: The above mentioned options are changes for arm compiler.Ignore them if you
are compiling for x86. The options will be available in tools/cmake/Toolchain-ARM.cmake
for cross compilation
CMake options for Publisher application(pubsub_TSN_publisher_multiple_thread):
cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-ARM.cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON ..
make -j4 pubsub_TSN_publisher_multiple_thread
CMake options for loopback application(pubsub_TSN_loopback_single_thread):
cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-ARM.cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON -DUA_ENABLE_PUBSUB_BUFMALLOC=ON -DUA_ENABLE_MALLOC_SINGLETON=ON ..
make -j4 pubsub_TSN_loopback_single_thread
5) Compilation for x86 architecture
CMake options for Publisher application(pubsub_TSN_publisher_multiple_thread):
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON ..
make -j4 pubsub_TSN_publisher_multiple_thread
CMake options for loopback application(pubsub_TSN_loopback_single_thread):
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON -DUA_ENABLE_PUBSUB_BUFMALLOC=ON -DUA_ENABLE_MALLOC_SINGLETON=ON ..
make -j4 pubsub_TSN_loopback_single_thread
============================================================================================================
To RUN the APPLICATIONS:
The generated binaries are generated in build/bin/ folder
============================================================================================================
TWO WAY COMMUNICATION
============================================================================================================
For Ethernet(Without logs) For long run:
./pubsub_TSN_publisher_multiple_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -subAppPriority 99 -pubAppPriority 98 - Run in node 1
./pubsub_TSN_loopback_single_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -pubSubAppPriority 99 - Run in node 2
For Ethernet:(With logs - Provide the counterdata and its time which helps to identify the roundtrip time): For Short run
./pubsub_TSN_publisher_multiple_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -subAppPriority 99 -pubAppPriority 98 - Run in node 1
./pubsub_TSN_loopback_single_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2
Note: Application will close after sending 100000 packets and you will get the logs in .csv files. To change the number of packets to capture for short run change the parameter #MAX_MEASUREMENTS define value in pubsub_TSN_publisher_multiple_thread and pubsub_TSN_loopback_single_thread
For UDP(Without logs) For long run:
For UDP only one change need to be done before running that is need to provide the interface ip address as an input for -interface option
./pubsub_TSN_publisher_multiple_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -subAppPriority 99 -pubAppPriority 98 - Run in node 1
./pubsub_TSN_loopback_single_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -pubSubAppPriority 99 - Run in node 2
For UDP:(With logs - Provide the counterdata and its time which helps to identify the roundtrip time): For Short run
./pubsub_TSN_publisher_multiple_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -subAppPriority 99 -pubAppPriority 98 - Run in node 1
./pubsub_TSN_loopback_single_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2
NOTE: Application will close after sending 100000 packets and you will get the logs in .csv files. To change the number of packets to capture for short run
change the parameter #MAX_MEASUREMENTS define value in pubsub_TSN_publisher_multiple_thread and pubsub_TSN_loopback_single_thread
============================================================================================================
ONE WAY COMMUNICATION: For one way communication disable(comment) the macro TWO_WAY_COMMUNICATION in the the pubsub_TSN_publisher_multiple_thread and pubsub_TSN_loopback_single_thread
============================================================================================================
Run steps for Ethernet:
./pubsub_TSN_publisher_multiple_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubAppPriority - Run in node 1
./pubsub_TSN_loopback_single_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2
Run steps for UDP:
./pubsub_TSN_publisher_multiple_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubAppPriority - Run in node 1
./pubsub_TSN_loopback_single_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2
NOTE: As we have mentioned previously for long run remove the option -enableCsvLog
============================================================================================================
NOTE: It is always recommended to run pubsub_TSN_loopback_single_thread application first to avoid missing the initial data published by pubsub_TSN_publisher_multiple_thread
To know more usage
./bin/examples/pubsub_TSN_publisher_multiple_thread -help
./bin/examples/pubsub_TSN_loopback_single_thread -help
============================================================================================================
NOTE:
For Ethernet
If VLAN tag is used:
Ensure the MAC address is given along with VLAN ID and PCP
Eg: "opc.eth://01-00-5E-00-00-01:8.3" where 8 is the VLAN ID and 3 is the PCP
If VLAN tag is not used:
Ensure the MAC address is not given along with VLAN ID and PCP
Eg: "opc.eth://01-00-5E-00-00-01"
If MAC address is changed, follow INGRESS POLICY steps with dst address to be the same as SUBSCRIBING_MAC_ADDRESS for both nodes
To increase the payload size, change the REPEATED_NODECOUNTS macro in pubsub_TSN_publisher_multiple_thread.c and pubsub_TSN_loopback_single_thread.c applications (for 1 REPEATED_NODECOUNTS, 9 bytes of data will increase in payload)
============================================================================================================
Output Logs: If application runs with option enableCsvLog
/**
* Trace point setup
*
* +--------------+ +----------------+
* T1 | OPCUA PubSub | T8 T5 | OPCUA loopback | T4
* | | Application | ^ | | Application | ^
* | +--------------+ | | +----------------+ |
* User | | | | | | | |
* Space | | | | | | | |
* | | | | | | | |
* ----------|--------------|------------------------|----------------|-------
* | | Node 1 | | | | Node 2 | |
* Kernel| | | | | | | |
* Space | | | | | | | |
* | | | | | | | |
* v +--------------+ | v +----------------+ |
* T2 | TX tcpdump | T7<----------------T6 | RX tcpdump | T3
* | +--------------+ +----------------+ ^
* | |
* ----------------------------------------------------------------
*/
For publisher application (pubsub_TSN_publisher_multiple_thread): publisher_T1.csv, subscriber_T8.csv
For loopback application: publisher_T5.csv, subscriber_T4.csv
To Compute the round trip time: Subtract T8-T1 (subtract the time in the .csv files of subscriber_T8.csv - publisher_T1.csv)
============================================================================================================
To close the running application press ctrl+c during the application exit the packet loss count of the entire run will be displayed in the console print.
============================================================================================================
For the TTTech TSN IP version 2.3.0 the following are applicable:
<interfaceName> sw0ep
<portName> available external physical ports: sw0p2, sw0p3, sw0p5, sw0p3
<InternalPortName> sw0p1

View File

@ -1,49 +0,0 @@
####################
# Nodeset Examples PubSub#
####################
###################
# Custom XML #
###################
set(FILE_CSV_DIRPREFIX ${PROJECT_SOURCE_DIR}/pubsub_realtime/nodeset)
set(FILE_BSD_DIRPREFIX ${PROJECT_SOURCE_DIR}/pubsub_realtime/nodeset)
set(FILE_NS_DIRPREFIX ${PROJECT_SOURCE_DIR}/pubsub_realtime/nodeset)
get_target_property(OPEN62541_BIN_DIR open62541::open62541 BINARY_DIR)
set(PROJECT_BINARY_DIR ${OPEN62541_BIN_DIR})
# generate namespace from XML file
ua_generate_nodeset_and_datatypes(
NAME "example_publisher"
FILE_NS "${FILE_NS_DIRPREFIX}/pubDataModel.xml"
DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../../../tools/schema/Opc.Ua.NodeSet2.Reduced.xml"
)
ua_generate_nodeset_and_datatypes(
NAME "example_subscriber"
FILE_NS "${FILE_NS_DIRPREFIX}/subDataModel.xml"
DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../../../tools/schema/Opc.Ua.NodeSet2.Reduced.xml"
)
# The .csv file can be created from within UaModeler or manually
ua_generate_nodeid_header(
NAME "example_nodeids_publisher"
ID_PREFIX "EXAMPLE_NS_PUBLISHER"
TARGET_SUFFIX "ids_example_publisher"
FILE_CSV "${FILE_CSV_DIRPREFIX}/pubDataModel.csv"
)
ua_generate_nodeid_header(
NAME "example_nodeids_subscriber"
ID_PREFIX "EXAMPLE_NS_SUBSCRIBER"
TARGET_SUFFIX "ids_example_subscriber"
FILE_CSV "${FILE_CSV_DIRPREFIX}/subDataModel.csv"
)
add_example(pubsub_nodeset_rt_publisher pubsub_nodeset_rt_publisher.c
${UA_NODESET_EXAMPLE_PUBLISHER_SOURCES}
${PROJECT_BINARY_DIR}/src_generated/open62541/example_nodeids_publisher.h)
add_example(pubsub_nodeset_rt_subscriber pubsub_nodeset_rt_subscriber.c
${UA_NODESET_EXAMPLE_SUBSCRIBER_SOURCES}
${PROJECT_BINARY_DIR}/src_generated/open62541/example_nodeids_subscriber.h)
target_link_libraries(pubsub_nodeset_rt_publisher rt pthread)
target_link_libraries(pubsub_nodeset_rt_subscriber rt pthread)

View File

@ -1,4 +0,0 @@
PublisherInfo,2001,ObjectType
Publisher,2004,Object
PublisherCounterValue,2005,Variable
Pressure,2006,Variable
1 PublisherInfo 2001 ObjectType
2 Publisher 2004 Object
3 PublisherCounterValue 2005 Variable
4 Pressure 2006 Variable

View File

@ -1,80 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<UANodeSet xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd" xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<NamespaceUris>
<Uri>http://yourorganisation.org/test/</Uri>
</NamespaceUris>
<Aliases>
<Alias Alias="HasModellingRule">i=37</Alias>
<Alias Alias="UInt64">i=9</Alias>
<Alias Alias="Double">i=11</Alias>
<Alias Alias="Organizes">i=35</Alias>
<Alias Alias="HasTypeDefinition">i=40</Alias>
<Alias Alias="HasSubtype">i=45</Alias>
<Alias Alias="HasComponent">i=47</Alias>
</Aliases>
<UAObjectType BrowseName="1:PublisherInfo" NodeId="ns=1;i=2001">
<DisplayName>PublisherInfo</DisplayName>
<Description>PublisherInfo</Description>
<References>
<Reference IsForward="false" ReferenceType="HasSubtype">i=58</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2002</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2003</Reference>
</References>
</UAObjectType>
<UAVariable BrowseName="1:PublisherCounterVariable" DataType="UInt64" NodeId="ns=1;i=2002" ParentNodeId="ns=1;i=2001">
<DisplayName>PublisherCounterVariable</DisplayName>
<Description>PublisherCounterVariable</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2001</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
<Reference ReferenceType="HasModellingRule">i=78</Reference>
</References>
<Value>
<uax:UInt64>0</uax:UInt64>
</Value>
</UAVariable>
<UAVariable BrowseName="1:Pressure" DataType="Double" NodeId="ns=1;i=2003" ParentNodeId="ns=1;i=2001">
<DisplayName>Pressure</DisplayName>
<Description>Pressure</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2001</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
<Reference ReferenceType="HasModellingRule">i=78</Reference>
</References>
<Value>
<uax:Double>0.0</uax:Double>
</Value>
</UAVariable>
<UAObject BrowseName="1:Publisher" NodeId="ns=1;i=2004" ParentNodeId="i=85">
<DisplayName>Publisher</DisplayName>
<Description>PublisherInfo</Description>
<References>
<Reference IsForward="false" ReferenceType="Organizes">i=85</Reference>
<Reference ReferenceType="HasTypeDefinition">ns=1;i=2001</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2005</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2006</Reference>
</References>
</UAObject>
<UAVariable BrowseName="1:PublisherCounterVariable" DataType="UInt64" NodeId="ns=1;i=2005" ParentNodeId="ns=1;i=2004">
<DisplayName>PublisherCounterVariable</DisplayName>
<Description>PublisherCounterVariable</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2004</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
</References>
<Value>
<uax:UInt64>0</uax:UInt64>
</Value>
</UAVariable>
<UAVariable BrowseName="1:Pressure" DataType="Double" NodeId="ns=1;i=2006" ParentNodeId="ns=1;i=2004">
<DisplayName>Pressure</DisplayName>
<Description>Pressure</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2004</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
</References>
<Value>
<uax:Double>0.0</uax:Double>
</Value>
</UAVariable>
</UANodeSet>

View File

@ -1,736 +0,0 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
/**
* .. _pubsub-nodeset-tutorial:
*
* Publisher Realtime example using custom nodes
* ---------------------------------------------
*
* The purpose of this example file is to use the custom nodes of the XML
* file(pubDataModel.xml) for publisher. This Publisher example uses the two
* custom nodes (PublisherCounterVariable and Pressure) created using the XML
* file(pubDataModel.xml) for publishing the packet. The pubDataModel.csv will
* contain the nodeids of custom nodes(object and variables) and the nodeids of
* the custom nodes are harcoded inside the addDataSetField API. This example
* uses two threads namely the Publisher and UserApplication. The Publisher
* thread is used to publish data at every cycle. The UserApplication thread
* serves the functionality of the Control loop, which increments the
* counterdata to be published by the Publisher and also writes the published
* data in a csv along with transmission timestamp.
*
* Run steps of the Publisher application as mentioned below:
*
* ``./bin/examples/pubsub_nodeset_rt_publisher -i <iface>``
*
* For more information run ``./bin/examples/pubsub_nodeset_rt_publisher -h``. */
#define _GNU_SOURCE
/* For thread operations */
#include <pthread.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/types_generated.h>
#include "ua_pubsub.h"
#include "open62541/namespace_example_publisher_generated.h"
/* to find load of each thread
* ps -L -o pid,pri,%cpu -C pubsub_nodeset_rt_publisher */
/* Configurable Parameters */
/* Cycle time in milliseconds */
#define DEFAULT_CYCLE_TIME 0.25
/* Qbv offset */
#define QBV_OFFSET 25 * 1000
#define DEFAULT_SOCKET_PRIORITY 3
#define PUBLISHER_ID 2234
#define WRITER_GROUP_ID 101
#define DATA_SET_WRITER_ID 62541
#define PUBLISHING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.3"
#define PORT_NUMBER 62541
/* Non-Configurable Parameters */
/* Milli sec and sec conversion to nano sec */
#define MILLI_SECONDS 1000 * 1000
#define SECONDS 1000 * 1000 * 1000
#define SECONDS_SLEEP 5
#define DEFAULT_PUB_SCHED_PRIORITY 78
#define DEFAULT_PUBSUB_CORE 2
#define DEFAULT_USER_APP_CORE 3
#define MAX_MEASUREMENTS 30000000
#define SECONDS_INCREMENT 1
#define CLOCKID CLOCK_TAI
#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"
#define DEFAULT_USERAPPLICATION_SCHED_PRIORITY 75
/* Below mentioned parameters can be provided as input using command line arguments
* If user did not provide the below mentioned parameters as input through command line
* argument then default value will be used */
static UA_Double cycleTimeMsec = DEFAULT_CYCLE_TIME;
static UA_Boolean consolePrint = UA_FALSE;
static UA_Int32 socketPriority = DEFAULT_SOCKET_PRIORITY;
static UA_Int32 pubPriority = DEFAULT_PUB_SCHED_PRIORITY;
static UA_Int32 userAppPriority = DEFAULT_USERAPPLICATION_SCHED_PRIORITY;
static UA_Int32 pubSubCore = DEFAULT_PUBSUB_CORE;
static UA_Int32 userAppCore = DEFAULT_USER_APP_CORE;
static UA_Boolean useSoTxtime = UA_TRUE;
/* User application Pub will wakeup at the 30% of cycle time and handles the */
/* user data write in Information model */
/* First 30% is left for subscriber for future use*/
static UA_Double userAppWakeupPercentage = 0.3;
/* Publisher will sleep for 60% of cycle time and then prepares the */
/* transmission packet within 40% */
/* after some prototyping and analyzing it */
static UA_Double pubWakeupPercentage = 0.6;
static UA_Boolean fileWrite = UA_FALSE;
/* If the Hardcoded publisher MAC addresses need to be changed,
* change PUBLISHING_MAC_ADDRESS
*/
/* Set server running as true */
UA_Boolean running = UA_TRUE;
UA_UInt16 nsIdx = 0;
/* Variables corresponding to PubSub connection creation,
* published data set and writer group */
UA_NodeId connectionIdent;
UA_NodeId publishedDataSetIdent;
UA_NodeId writerGroupIdent;
/* Variables for counter data handling in address space */
UA_UInt64 *pubCounterData;
UA_DataValue *pubDataValueRT;
/* Variables for counter data handling in address space */
UA_Double *pressureData;
UA_DataValue *pressureValueRT;
/* File to store the data and timestamps for different traffic */
FILE *fpPublisher;
char *fileName = "publisher_T1.csv";
/* Array to store published counter data */
UA_UInt64 publishCounterValue[MAX_MEASUREMENTS];
UA_Double pressureValues[MAX_MEASUREMENTS];
size_t measurementsPublisher = 0;
/* Array to store timestamp */
struct timespec publishTimestamp[MAX_MEASUREMENTS];
/* Thread for publisher */
pthread_t pubthreadID;
struct timespec dataModificationTime;
/* Thread for user application*/
pthread_t userApplicationThreadID;
typedef struct {
UA_Server* ServerRun;
} serverConfigStruct;
/* Structure to define thread parameters */
typedef struct {
UA_Server* server;
void* data;
UA_ServerCallback callback;
UA_Duration interval_ms;
UA_UInt64* callbackId;
} threadArg;
/* Publisher thread routine for ETF */
void *publisherETF(void *arg);
/* User application thread routine */
void *userApplicationPub(void *arg);
/* To create multi-threads */
static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *),
char *applicationName, void *serverConfig);
/* Stop signal */
static void stopHandler(int sign) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = UA_FALSE;
}
/**
* **Nanosecond field handling**
*
* Nanosecond field in timespec is checked for overflowing and one second
* is added to seconds field and nanosecond field is set to zero
*/
static void nanoSecondFieldConversion(struct timespec *timeSpecValue) {
/* Check if ns field is greater than '1 ns less than 1sec' */
while (timeSpecValue->tv_nsec > (SECONDS -1)) {
/* Move to next second and remove it from ns field */
timeSpecValue->tv_sec += SECONDS_INCREMENT;
timeSpecValue->tv_nsec -= SECONDS;
}
}
/**
* **Custom callback handling**
*
* Custom callback thread handling overwrites the default timer based
* callback function with the custom (user-specified) callback interval. */
/* Add a callback for cyclic repetition */
static UA_StatusCode
addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
UA_ServerCallback callback,
void *data, UA_Double interval_ms,
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy,
UA_UInt64 *callbackId) {
/* Initialize arguments required for the thread to run */
threadArg *threadArguments = (threadArg *) UA_malloc(sizeof(threadArg));
/* Pass the value required for the threads */
threadArguments->server = server;
threadArguments->data = data;
threadArguments->callback = callback;
threadArguments->interval_ms = interval_ms;
threadArguments->callbackId = callbackId;
/* Create the publisher thread with the required priority and core affinity */
char threadNamePub[10] = "Publisher";
pubthreadID = threadCreation(pubPriority, pubSubCore, publisherETF, threadNamePub, threadArguments);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
UA_UInt64 callbackId, UA_Double interval_ms,
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) {
/* Callback interval need not be modified as it is thread based implementation.
* The thread uses nanosleep for calculating cycle time and modification in
* nanosleep value changes cycle time */
return UA_STATUSCODE_GOOD;
}
/* Remove the callback added for cyclic repetition */
static void
removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId){
if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0))
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Pthread Join Failed thread: %lu\n", (long unsigned)callbackId);
}
/**
* **External data source handling**
*
* 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 *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeId,
void *nodeContext, const UA_NumericRange *range,
const UA_DataValue *data){
//node values are updated by using variables in the memory
//UA_Server_write is not used for updating node values.
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
externalDataReadNotificationCallback(UA_Server *server, 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;
}
/**
* **PubSub connection handling**
*
* Create a new ConnectionConfig. The addPubSubConnection function takes the
* config and creates a new connection. The Connection identifier is
* copied to the NodeId parameter.
*/
static void
addPubSubConnection(UA_Server *server, UA_NetworkAddressUrlDataType *networkAddressUrlPub){
/* Details about the connection configuration and handling are located
* in the pubsub connection tutorial */
UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(connectionConfig));
connectionConfig.name = UA_STRING("Publisher Connection");
connectionConfig.enabled = UA_TRUE;
UA_NetworkAddressUrlDataType networkAddressUrl = *networkAddressUrlPub;
connectionConfig.transportProfileUri = UA_STRING(ETH_TRANSPORT_PROFILE);
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT16;
connectionConfig.publisherId.uint16 = PUBLISHER_ID;
/* Connection options are given as Key/Value Pairs - Sockprio and Txtime */
UA_KeyValuePair connectionOptions[2];
connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority");
UA_UInt32 sockPriority = (UA_UInt32)socketPriority;
UA_Variant_setScalar(&connectionOptions[0].value, &sockPriority, &UA_TYPES[UA_TYPES_UINT32]);
connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime");
UA_Boolean enableTxTime = UA_TRUE;
UA_Variant_setScalar(&connectionOptions[1].value, &enableTxTime, &UA_TYPES[UA_TYPES_BOOLEAN]);
connectionConfig.connectionProperties.map = connectionOptions;
connectionConfig.connectionProperties.mapSize = 2;
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
}
/**
* **PublishedDataSet handling**
*
* Details about the connection configuration and handling are located
* in the pubsub connection tutorial
*/
static void
addPublishedDataSet(UA_Server *server) {
UA_PublishedDataSetConfig publishedDataSetConfig;
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
publishedDataSetConfig.name = UA_STRING("Demo PDS");
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent);
}
/**
* **DataSetField handling**
*
* The DataSetField (DSF) is part of the PDS and describes exactly one
* published field.
*/
/* This example only uses two addDataSetField which uses the custom nodes of the XML file
* (pubDataModel.xml) */
static void
_addDataSetField(UA_Server *server) {
UA_NodeId dataSetFieldIdent;
UA_DataSetFieldConfig dsfConfig;
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
pubCounterData = UA_UInt64_new();
*pubCounterData = 0;
pubDataValueRT = UA_DataValue_new();
UA_Variant_setScalar(&pubDataValueRT->value, pubCounterData, &UA_TYPES[UA_TYPES_UINT64]);
pubDataValueRT->hasValue = UA_TRUE;
/* 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 = &pubDataValueRT;
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
/* If user need to change the nodeid of the custom nodes in the application then it must be
* changed inside the xml and .csv file inside examples\pubsub_realtime\nodeset\*/
/* The nodeid of the Custom node PublisherCounterVariable is 2005 which is used below */
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2005), valueBackend);
/* setup RT DataSetField config */
dsfConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE;
dsfConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(nsIdx, 2005);
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent);
UA_NodeId dataSetFieldIdent1;
UA_DataSetFieldConfig dsfConfig1;
memset(&dsfConfig1, 0, sizeof(UA_DataSetFieldConfig));
pressureData = UA_Double_new();
*pressureData = 17.07;
pressureValueRT = UA_DataValue_new();
UA_Variant_setScalar(&pressureValueRT->value, pressureData, &UA_TYPES[UA_TYPES_DOUBLE]);
pressureValueRT->hasValue = UA_TRUE;
/* 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 = &pressureValueRT;
valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
/* The nodeid of the Custom node Pressure is 2006 which is used below */
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2006), valueBackend1);
/* setup RT DataSetField config */
dsfConfig1.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE;
dsfConfig1.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(nsIdx, 2006);
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig1, &dataSetFieldIdent1);
}
/**
* **WriterGroup handling**
*
* The WriterGroup (WG) is part of the connection and contains the primary
* configuration parameters for the message creation.
*/
static void
addWriterGroup(UA_Server *server) {
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = cycleTimeMsec;
writerGroupConfig.enabled = UA_FALSE;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
writerGroupConfig.writerGroupId = WRITER_GROUP_ID;
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
writerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback;
writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback;
writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback;
writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE];
/* 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.
* UadpWriterGroupMessageDataType */
UA_UadpWriterGroupMessageDataType *writerGroupMessage = UA_UadpWriterGroupMessageDataType_new();
/* Change message settings of writerGroup to send PublisherId,
* WriterGroupId in GroupHeader and DataSetWriterId in PayloadHeader
* of NetworkMessage */
writerGroupMessage->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 = writerGroupMessage;
UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent);
UA_Server_enableWriterGroup(server, writerGroupIdent);
UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage);
}
/**
* **DataSetWriter handling**
*
* A DataSetWriter (DSW) is the glue between the WG and the PDS. The DSW is
* linked to exactly one PDS and contains additional information for the
* message generation.
*/
static void
addDataSetWriter(UA_Server *server) {
UA_NodeId dataSetWriterIdent;
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID;
dataSetWriterConfig.keyFrameCount = 10;
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
&dataSetWriterConfig, &dataSetWriterIdent);
}
/**
* **Published data handling**
*
* The published data is updated in the array using this function
*/
static void
updateMeasurementsPublisher(struct timespec start_time,
UA_UInt64 counterValue, UA_Double pressureValue) {
publishTimestamp[measurementsPublisher] = start_time;
publishCounterValue[measurementsPublisher] = counterValue;
pressureValues[measurementsPublisher] = pressureValue;
measurementsPublisher++;
}
/**
* **Publisher thread routine**
*
* The Publisher thread sleeps for 60% of the cycletime (250us) and prepares the tranmission packet within 40% of
* cycletime. The data published by this thread in one cycle is subscribed by the subscriber thread of pubsub_nodeset_rt_subscriber in the
* next cycle (two cycle timing model).
*
* The publisherETF function is the routine used by the publisher thread.
*/
void *publisherETF(void *arg) {
struct timespec nextnanosleeptime;
UA_ServerCallback pubCallback;
UA_Server* server;
UA_WriterGroup* currentWriterGroup;
UA_UInt64 interval_ns;
UA_UInt64 transmission_time;
/* Initialise value for nextnanosleeptime timespec */
nextnanosleeptime.tv_nsec = 0;
threadArg *threadArgumentsPublisher = (threadArg *)arg;
server = threadArgumentsPublisher->server;
pubCallback = threadArgumentsPublisher->callback;
currentWriterGroup = (UA_WriterGroup *)threadArgumentsPublisher->data;
interval_ns = (UA_UInt64)(threadArgumentsPublisher->interval_ms * MILLI_SECONDS);
/* Get current time and compute the next nanosleeptime */
clock_gettime(CLOCKID, &nextnanosleeptime);
/* Variable to nano Sleep until 1ms before a 1 second boundary */
nextnanosleeptime.tv_sec += SECONDS_SLEEP;
nextnanosleeptime.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * pubWakeupPercentage * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptime);
/* Define Ethernet ETF transport settings */
UA_EthernetWriterGroupTransportDataType ethernettransportSettings;
memset(&ethernettransportSettings, 0, sizeof(UA_EthernetWriterGroupTransportDataType));
ethernettransportSettings.transmission_time = 0;
/* Encapsulate ETF config in transportSettings */
UA_ExtensionObject transportSettings;
memset(&transportSettings, 0, sizeof(UA_ExtensionObject));
/* TODO: transportSettings encoding and type to be defined */
transportSettings.content.decoded.data = &ethernettransportSettings;
currentWriterGroup->config.transportSettings = transportSettings;
UA_UInt64 roundOffCycleTime = (UA_UInt64)((cycleTimeMsec * MILLI_SECONDS) - (cycleTimeMsec * pubWakeupPercentage * MILLI_SECONDS));
while (running) {
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptime, NULL);
transmission_time = ((UA_UInt64)nextnanosleeptime.tv_sec * SECONDS + (UA_UInt64)nextnanosleeptime.tv_nsec) + roundOffCycleTime + QBV_OFFSET;
ethernettransportSettings.transmission_time = transmission_time;
pubCallback(server, currentWriterGroup);
nextnanosleeptime.tv_nsec += (__syscall_slong_t)interval_ns;
nanoSecondFieldConversion(&nextnanosleeptime);
}
UA_free(threadArgumentsPublisher);
return (void*)NULL;
}
/**
* **UserApplication thread routine**
*
* The userapplication thread will wakeup at 30% of cycle time and handles the userdata in the Information Model.
* This thread is used to increment the counterdata that will be published by the Publisher thread and also writes the published data in a csv.
*/
void *userApplicationPub(void *arg) {
struct timespec nextnanosleeptimeUserApplication;
/* Get current time and compute the next nanosleeptime */
clock_gettime(CLOCKID, &nextnanosleeptimeUserApplication);
/* Variable to nano Sleep until 1ms before a 1 second boundary */
nextnanosleeptimeUserApplication.tv_sec += SECONDS_SLEEP;
nextnanosleeptimeUserApplication.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * userAppWakeupPercentage * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptimeUserApplication);
*pubCounterData = 0;
while (running) {
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL);
*pubCounterData = *pubCounterData + 1;
*pressureData = *pressureData + 1;
clock_gettime(CLOCKID, &dataModificationTime);
if ((fileWrite == UA_TRUE) || (consolePrint == UA_TRUE))
updateMeasurementsPublisher(dataModificationTime, *pubCounterData, *pressureData);
nextnanosleeptimeUserApplication.tv_nsec += (__syscall_slong_t)(cycleTimeMsec * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptimeUserApplication);
}
return (void*)NULL;
}
/**
* **Thread creation**
*
* The threadcreation functionality creates thread with given threadpriority, coreaffinity. The function returns the threadID of the newly
* created thread.
*/
static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *), char *applicationName, void *serverConfig){
/* Core affinity set */
cpu_set_t cpuset;
pthread_t threadID;
struct sched_param schedParam;
UA_Int32 returnValue = 0;
UA_Int32 errorSetAffinity = 0;
/* Return the ID for thread */
threadID = pthread_self();
schedParam.sched_priority = threadPriority;
returnValue = pthread_setschedparam(threadID, SCHED_FIFO, &schedParam);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"pthread_setschedparam: failed\n");
exit(1);
}
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,\
"\npthread_setschedparam:%s Thread priority is %d \n", \
applicationName, schedParam.sched_priority);
CPU_ZERO(&cpuset);
CPU_SET((size_t)coreAffinity, &cpuset);
errorSetAffinity = pthread_setaffinity_np(threadID, sizeof(cpu_set_t), &cpuset);
if (errorSetAffinity) {
fprintf(stderr, "pthread_setaffinity_np: %s\n", strerror(errorSetAffinity));
exit(1);
}
returnValue = pthread_create(&threadID, NULL, thread, serverConfig);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName);
}
if (CPU_ISSET((size_t)coreAffinity, &cpuset)) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %d\n", applicationName, coreAffinity);
}
return threadID;
}
/**
* **Usage function**
*
* The usage function gives the list of options that can be configured in the application.
*
* ./bin/examples/pubsub_nodeset_rt_publisher -h gives the list of options for running the application.
*/
static void usage(char *appname)
{
fprintf(stderr,
"\n"
"usage: %s [options]\n"
"\n"
" -i [name] use network interface 'name'\n"
" -C [num] cycle time in milli seconds (default %lf)\n"
" -p Do you need to print the data in console output\n"
" -s [num] set SO_PRIORITY to 'num' (default %d)\n"
" -P [num] Publisher priority value (default %d)\n"
" -U [num] User application priority value (default %d)\n"
" -c [num] run on CPU for publisher'num'(default %d)\n"
" -u [num] run on CPU for userApplication'num'(default %d)\n"
" -t do not use SO_TXTIME\n"
" -m [mac_addr] ToDO:dst MAC address\n"
" -h prints this message and exits\n"
"\n",
appname, DEFAULT_CYCLE_TIME, DEFAULT_SOCKET_PRIORITY, DEFAULT_PUB_SCHED_PRIORITY, \
DEFAULT_USERAPPLICATION_SCHED_PRIORITY, DEFAULT_PUBSUB_CORE, DEFAULT_USER_APP_CORE);
}
/**
* **Main Server code**
*
* The main function contains publisher threads running
*/
int main(int argc, char **argv) {
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
UA_Int32 returnValue = 0;
char *interface = NULL;
char *progname;
UA_Int32 argInputs = -1;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_Server *server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
pthread_t userThreadID;
UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL);
/* Files namespace_example_publisher_generated.h and namespace_example_publisher_generated.c are created from
* pubDataModel.xml in the /src_generated directory by CMake */
/* Loading the user created variables into the information model from the generated .c and .h files */
if(namespace_example_publisher_generated(server) != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not add the example nodeset. "
"Check previous output for any error.");
}
else
{
nsIdx = UA_Server_addNamespace(server, "http://yourorganisation.org/test/");
}
UA_NetworkAddressUrlDataType networkAddressUrlPub;
/* Process the command line arguments */
/* For more information run ./bin/examples/pubsub_nodeset_rt_publisher -h */
progname = strrchr(argv[0], '/');
progname = progname ? 1 + progname : argv[0];
while (EOF != (argInputs = getopt(argc, argv, "i:C:f:ps:P:U:c:u:tm:h:"))) {
switch (argInputs) {
case 'i':
interface = optarg;
break;
case 'C':
cycleTimeMsec = atof(optarg);
break;
case 'f':
fileName = optarg;
fileWrite = UA_TRUE;
fpPublisher = fopen(fileName, "w");
break;
case 'p':
consolePrint = UA_TRUE;
break;
case 's':
socketPriority = atoi(optarg);
break;
case 'P':
pubPriority = atoi(optarg);
break;
case 'U':
userAppPriority = atoi(optarg);
break;
case 'c':
pubSubCore = atoi(optarg);
break;
case 'u':
userAppCore = atoi(optarg);
break;
case 't':
useSoTxtime = UA_FALSE;
break;
case 'm':
/*ToDo:Need to handle for mac address*/
break;
case 'h':
usage(progname);
return -1;
case '?':
usage(progname);
return -1;
}
}
if (cycleTimeMsec < 0.125) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "%f Bad cycle time", cycleTimeMsec);
usage(progname);
return -1;
}
if (!interface) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Need a network interface to run");
usage(progname);
UA_Server_delete(server);
return 0;
}
networkAddressUrlPub.networkInterface = UA_STRING(interface);
networkAddressUrlPub.url = UA_STRING(PUBLISHING_MAC_ADDRESS);
addPubSubConnection(server, &networkAddressUrlPub);
addPublishedDataSet(server);
_addDataSetField(server);
addWriterGroup(server);
addDataSetWriter(server);
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
serverConfigStruct *serverConfig;
serverConfig = (serverConfigStruct*)UA_malloc(sizeof(serverConfigStruct));
serverConfig->ServerRun = server;
char threadNameUserApplication[22] = "UserApplicationPub";
userThreadID = threadCreation(userAppPriority, userAppCore, userApplicationPub, threadNameUserApplication, serverConfig);
retval |= UA_Server_run(server, &running);
returnValue = pthread_join(pubthreadID, NULL);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for publisher thread:%d\n", returnValue);
}
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);
}
if (fileWrite == UA_TRUE) {
/* Write the published data in a file */
size_t pubLoopVariable = 0;
for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher;
pubLoopVariable++) {
fprintf(fpPublisher, "%lu,%ld.%09ld,%lf\n",
(long unsigned)publishCounterValue[pubLoopVariable],
publishTimestamp[pubLoopVariable].tv_sec,
publishTimestamp[pubLoopVariable].tv_nsec,
pressureValues[pubLoopVariable]);
}
fclose(fpPublisher);
}
if (consolePrint == UA_TRUE) {
size_t pubLoopVariable = 0;
for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher;
pubLoopVariable++) {
printf("%lu,%ld.%09ld,%lf\n",
(long unsigned)publishCounterValue[pubLoopVariable],
publishTimestamp[pubLoopVariable].tv_sec,
publishTimestamp[pubLoopVariable].tv_nsec,
pressureValues[pubLoopVariable]);
}
}
UA_Server_delete(server);
UA_free(serverConfig);
UA_free(pubCounterData);
/* Free external data source */
UA_free(pubDataValueRT);
UA_free(pressureData);
/* Free external data source */
UA_free(pressureValueRT);
return (int)retval;
}

View File

@ -1,656 +0,0 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
/**
* .. _pubsub-nodeset-subscriber-tutorial:
*
* Subscriber Realtime example using custom nodes
* ----------------------------------------------
*
* The purpose of this example file is to use the custom nodes of the XML
* file(subDataModel.xml) for subscriber. This Subscriber example uses the two
* custom nodes (SubscriberCounterVariable and Pressure) created using the XML
* file(subDataModel.xml) for subscribing the packet. The subDataModel.csv will
* contain the nodeids of custom nodes(object and variables) and the nodeids of
* the custom nodes are harcoded inside the addSubscribedVariables API
*
* This example uses two threads namely the Subscriber and UserApplication. The
* Subscriber thread is used to subscribe to data at every cycle. The
* UserApplication thread serves the functionality of the Control loop, which
* reads the Information Model of the Subscriber and the new counterdata will be
* written in the csv along with received timestamp.
*
* Run steps of the Subscriber application as mentioned below:
*
* ``./bin/examples/pubsub_nodeset_rt_subscriber -i <iface>``
*
* For more information run ``./bin/examples/pubsub_nodeset_rt_subscriber -h``. */
#define _GNU_SOURCE
/* For thread operations */
#include <pthread.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/types_generated.h>
#include "ua_pubsub.h"
#include "open62541/namespace_example_subscriber_generated.h"
UA_NodeId readerGroupIdentifier;
UA_NodeId readerIdentifier;
UA_DataSetReaderConfig readerConfig;
/* to find load of each thread
* ps -L -o pid,pri,%cpu -C pubsub_nodeset_rt_subscriber*/
/* Configurable Parameters */
/* Cycle time in milliseconds */
#define DEFAULT_CYCLE_TIME 0.25
/* Qbv offset */
#define QBV_OFFSET 25 * 1000
#define DEFAULT_SOCKET_PRIORITY 3
#define PUBLISHER_ID_SUB 2234
#define WRITER_GROUP_ID_SUB 101
#define DATA_SET_WRITER_ID_SUB 62541
#define SUBSCRIBING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.3"
#define PORT_NUMBER 62541
/* Non-Configurable Parameters */
/* Milli sec and sec conversion to nano sec */
#define MILLI_SECONDS 1000 * 1000
#define SECONDS 1000 * 1000 * 1000
#define SECONDS_SLEEP 5
#define DEFAULT_SUB_SCHED_PRIORITY 81
#define MAX_MEASUREMENTS 30000000
#define DEFAULT_PUBSUB_CORE 2
#define DEFAULT_USER_APP_CORE 3
#define SECONDS_INCREMENT 1
#define CLOCKID CLOCK_TAI
#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"
#define DEFAULT_USERAPPLICATION_SCHED_PRIORITY 75
/* Below mentioned parameters can be provided as input using command line arguments
* If user did not provide the below mentioned parameters as input through command line
* argument then default value will be used */
static UA_Double cycleTimeMsec = DEFAULT_CYCLE_TIME;
static UA_Boolean consolePrint = UA_FALSE;
static UA_Int32 subPriority = DEFAULT_SUB_SCHED_PRIORITY;
static UA_Int32 userAppPriority = DEFAULT_USERAPPLICATION_SCHED_PRIORITY;
static UA_Int32 pubSubCore = DEFAULT_PUBSUB_CORE;
static UA_Int32 userAppCore = DEFAULT_USER_APP_CORE;
/* User application Pub will wakeup at the 30% of cycle time and handles the */
/* user data write in Information model */
/* After 60% is left for publisher */
static UA_Double userAppWakeupPercentage = 0.3;
/* Subscriber will wake up at the start of cycle time and then receives the packet */
static UA_Double subWakeupPercentage = 0;
static UA_Boolean fileWrite = UA_FALSE;
/* Set server running as true */
UA_Boolean running = UA_TRUE;
UA_UInt16 nsIdx = 0;
UA_UInt64 *subCounterData;
UA_DataValue *subDataValueRT;
UA_Double *pressureData;
UA_DataValue *pressureValueRT;
/* File to store the data and timestamps for different traffic */
FILE *fpSubscriber;
char *fileName = "subscriber_T4.csv";
/* Array to store subscribed counter data */
UA_UInt64 subscribeCounterValue[MAX_MEASUREMENTS];
UA_Double pressureValues[MAX_MEASUREMENTS];
size_t measurementsSubscriber = 0;
/* Array to store timestamp */
struct timespec subscribeTimestamp[MAX_MEASUREMENTS];
/* Thread for subscriber */
pthread_t subthreadID;
/* Variable for PubSub connection creation */
UA_NodeId connectionIdentSubscriber;
struct timespec dataReceiveTime;
/* Thread for user application*/
pthread_t userApplicationThreadID;
typedef struct {
UA_Server* ServerRun;
} serverConfigStruct;
/* Structure to define thread parameters */
typedef struct {
UA_Server* server;
void* data;
UA_ServerCallback callback;
UA_Duration interval_ms;
UA_UInt64* callbackId;
} threadArg;
/* Subscriber thread routine */
void *subscriber(void *arg);
/* User application thread routine */
void *userApplicationSub(void *arg);
/* To create multi-threads */
static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *),
char *applicationName, void *serverConfig);
/* Stop signal */
static void stopHandler(int sign) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = UA_FALSE;
}
/**
* **Nanosecond field handling**
*
* Nanosecond field in timespec is checked for overflowing and one second
* is added to seconds field and nanosecond field is set to zero
*/
static void nanoSecondFieldConversion(struct timespec *timeSpecValue) {
/* Check if ns field is greater than '1 ns less than 1sec' */
while (timeSpecValue->tv_nsec > (SECONDS -1)) {
/* Move to next second and remove it from ns field */
timeSpecValue->tv_sec += SECONDS_INCREMENT;
timeSpecValue->tv_nsec -= SECONDS;
}
}
/**
* **External data source handling**
*
* 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 *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeId,
void *nodeContext, const UA_NumericRange *range,
const UA_DataValue *data){
//node values are updated by using variables in the memory
//UA_Server_write is not used for updating node values.
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
externalDataReadNotificationCallback(UA_Server *server, 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;
}
/**
* **Subscriber Connection Creation**
*
* Create Subscriber connection for the Subscriber thread
*/
static void
addPubSubConnectionSubscriber(UA_Server *server, UA_NetworkAddressUrlDataType *networkAddressUrlSubscriber){
UA_StatusCode retval = UA_STATUSCODE_GOOD;
/* Details about the connection configuration and handling are located
* in the pubsub connection tutorial */
UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(connectionConfig));
connectionConfig.name = UA_STRING("Subscriber Connection");
connectionConfig.enabled = UA_TRUE;
UA_NetworkAddressUrlDataType networkAddressUrlsubscribe = *networkAddressUrlSubscriber;
connectionConfig.transportProfileUri = UA_STRING(ETH_TRANSPORT_PROFILE);
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrlsubscribe, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT32;
connectionConfig.publisherId.uint32 = UA_UInt32_random();
retval |= UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentSubscriber);
if (retval == UA_STATUSCODE_GOOD)
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,"The PubSub Connection was created successfully!");
}
/**
* **ReaderGroup**
*
* ReaderGroup is used to group a list of DataSetReaders. All ReaderGroups are
* created within a PubSubConnection and automatically deleted if the connection
* is removed. */
/* Add ReaderGroup to the created connection */
static void
addReaderGroup(UA_Server *server) {
if (server == NULL) {
return;
}
UA_ReaderGroupConfig readerGroupConfig;
memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig));
readerGroupConfig.name = UA_STRING("ReaderGroup1");
readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
UA_Server_addReaderGroup(server, connectionIdentSubscriber, &readerGroupConfig,
&readerGroupIdentifier);
}
/**
* **SubscribedDataSet**
*
* Set SubscribedDataSet type to TargetVariables data type
* Add SubscriberCounter variable to the DataSetReader */
static void addSubscribedVariables (UA_Server *server) {
if (server == NULL) {
return;
}
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable*)
UA_calloc(2, sizeof(UA_FieldTargetVariable));
subCounterData = UA_UInt64_new();
*subCounterData = 0;
subDataValueRT = UA_DataValue_new();
UA_Variant_setScalar(&subDataValueRT->value, subCounterData, &UA_TYPES[UA_TYPES_UINT64]);
subDataValueRT->hasValue = UA_TRUE;
/* 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;
/* If user need to change the nodeid of the custom nodes in the application then it must be
* changed inside the xml and .csv file inside examples\pubsub_realtime\nodeset\*/
/* The nodeid of the Custom node SubscriberCounterVariable is 2005 which is used below */
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2005), valueBackend);
UA_FieldTargetDataType_init(&targetVars[0].targetVariable);
targetVars[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[0].targetVariable.targetNodeId = UA_NODEID_NUMERIC(nsIdx, 2005);
pressureData = UA_Double_new();
*pressureData = 0;
pressureValueRT = UA_DataValue_new();
UA_Variant_setScalar(&pressureValueRT->value, pressureData, &UA_TYPES[UA_TYPES_DOUBLE]);
pressureValueRT->hasValue = UA_TRUE;
/* 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 = &pressureValueRT;
valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
/* The nodeid of the Custom node Pressure is 2006 which is used below */
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2006), valueBackend1);
UA_FieldTargetDataType_init(&targetVars[1].targetVariable);
targetVars[1].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[1].targetVariable.targetNodeId = UA_NODEID_NUMERIC(nsIdx, 2006);
/* Set the subscribed data to TargetVariable type */
readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET;
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = targetVars;
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 2;
}
/**
* **DataSetReader**
*
* DataSetReader can receive NetworkMessages with the DataSetMessage
* of interest sent by the Publisher. DataSetReader provides
* the configuration necessary to receive and process DataSetMessages
* on the Subscriber side. DataSetReader must be linked with a
* SubscribedDataSet and be contained within a ReaderGroup. */
/* Add DataSetReader to the ReaderGroup */
static void
addDataSetReader(UA_Server *server) {
if (server == NULL) {
return;
}
memset (&readerConfig, 0, sizeof(UA_DataSetReaderConfig));
readerConfig.name = UA_STRING("DataSet Reader 1");
UA_UInt16 publisherIdentifier = PUBLISHER_ID_SUB;
readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16];
readerConfig.publisherId.data = &publisherIdentifier;
readerConfig.writerGroupId = WRITER_GROUP_ID_SUB;
readerConfig.dataSetWriterId = DATA_SET_WRITER_ID_SUB;
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 */
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]);
/* Unsigned Integer DataType */
UA_FieldMetaData_init (&pMetaData->fields[0]);
UA_NodeId_copy (&UA_TYPES[UA_TYPES_UINT64].typeId,
&pMetaData->fields[0].dataType);
pMetaData->fields[0].builtInType = UA_NS0ID_UINT64;
pMetaData->fields[0].valueRank = -1; /* scalar */
/* Double DataType */
UA_FieldMetaData_init (&pMetaData->fields[1]);
UA_NodeId_copy (&UA_TYPES[UA_TYPES_DOUBLE].typeId,
&pMetaData->fields[1].dataType);
pMetaData->fields[1].builtInType = UA_NS0ID_DOUBLE;
pMetaData->fields[1].valueRank = -1; /* scalar */
/* Setup Target Variables in DSR config */
addSubscribedVariables(server);
/* Setting up Meta data configuration in DataSetReader */
UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig,
&readerIdentifier);
UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables);
UA_free(readerConfig.dataSetMetaData.fields);
UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage);
}
/**
* **Subscribed data handling**
*
* The subscribed data is updated in the array using this function Subscribed data handling**
*/
static void
updateMeasurementsSubscriber(struct timespec receive_time, UA_UInt64 counterValue, UA_Double pressureValue) {
subscribeTimestamp[measurementsSubscriber] = receive_time;
subscribeCounterValue[measurementsSubscriber] = counterValue;
pressureValues[measurementsSubscriber] = pressureValue;
measurementsSubscriber++;
}
/**
* **Subscriber thread routine**
*
* Subscriber thread will wakeup during the start of cycle at 250us interval and check if the packets are received.
* The subscriber function is the routine used by the subscriber thread.
*/
void *subscriber(void *arg) {
UA_Server* server;
UA_ReaderGroup* currentReaderGroup;
UA_ServerCallback subCallback;
struct timespec nextnanosleeptimeSub;
threadArg *threadArgumentsSubscriber = (threadArg *)arg;
server = threadArgumentsSubscriber->server;
subCallback = threadArgumentsSubscriber->callback;
currentReaderGroup = (UA_ReaderGroup *)threadArgumentsSubscriber->data;
/* Get current time and compute the next nanosleeptime */
clock_gettime(CLOCKID, &nextnanosleeptimeSub);
/* Variable to nano Sleep until 1ms before a 1 second boundary */
nextnanosleeptimeSub.tv_sec += SECONDS_SLEEP;
nextnanosleeptimeSub.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * subWakeupPercentage * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptimeSub);
while (running) {
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeSub, NULL);
/* Read subscribed data from the SubscriberCounter variable */
subCallback(server, currentReaderGroup);
nextnanosleeptimeSub.tv_nsec += (__syscall_slong_t)(cycleTimeMsec * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptimeSub);
}
UA_free(threadArgumentsSubscriber);
return (void*)NULL;
}
/**
* **UserApplication thread routine**
*
* The userapplication thread will wakeup at 30% of cycle time and handles the userdata in the Information Model.
* This thread is used to write the counterdata that is subscribed by the Subscriber thread in a csv.
*/
void *userApplicationSub(void *arg) {
struct timespec nextnanosleeptimeUserApplication;
/* Get current time and compute the next nanosleeptime */
clock_gettime(CLOCKID, &nextnanosleeptimeUserApplication);
/* Variable to nano Sleep until 1ms before a 1 second boundary */
nextnanosleeptimeUserApplication.tv_sec += SECONDS_SLEEP;
nextnanosleeptimeUserApplication.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * userAppWakeupPercentage * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptimeUserApplication);
while (running) {
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL);
clock_gettime(CLOCKID, &dataReceiveTime);
if ((fileWrite == UA_TRUE) || (consolePrint == UA_TRUE)) {
if (*subCounterData > 0)
updateMeasurementsSubscriber(dataReceiveTime, *subCounterData, *pressureData);
}
nextnanosleeptimeUserApplication.tv_nsec += (__syscall_slong_t)(cycleTimeMsec * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptimeUserApplication);
}
return (void*)NULL;
}
/**
* **Thread creation**
*
* The threadcreation functionality creates thread with given threadpriority, coreaffinity. The function returns the threadID of the newly
* created thread.
*/
static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *), \
char *applicationName, void *serverConfig){
/* Core affinity set */
cpu_set_t cpuset;
pthread_t threadID;
struct sched_param schedParam;
UA_Int32 returnValue = 0;
UA_Int32 errorSetAffinity = 0;
/* Return the ID for thread */
threadID = pthread_self();
schedParam.sched_priority = threadPriority;
returnValue = pthread_setschedparam(threadID, SCHED_FIFO, &schedParam);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"pthread_setschedparam: failed\n");
exit(1);
}
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,\
"\npthread_setschedparam:%s Thread priority is %d \n", \
applicationName, schedParam.sched_priority);
CPU_ZERO(&cpuset);
CPU_SET((size_t)coreAffinity, &cpuset);
errorSetAffinity = pthread_setaffinity_np(threadID, sizeof(cpu_set_t), &cpuset);
if (errorSetAffinity) {
fprintf(stderr, "pthread_setaffinity_np: %s\n", strerror(errorSetAffinity));
exit(1);
}
returnValue = pthread_create(&threadID, NULL, thread, serverConfig);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName);
}
if (CPU_ISSET((size_t)coreAffinity, &cpuset)) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %d\n", applicationName, coreAffinity);
}
return threadID;
}
/**
* **Usage function**
*
* The usage function gives the list of options that can be configured in the application.
*
* ./bin/examples/pubsub_nodeset_rt_subscriber -h gives the list of options for running the application.
*/
static void usage(char *appname)
{
fprintf(stderr,
"\n"
"usage: %s [options]\n"
"\n"
" -i [name] use network interface 'name'\n"
" -C [num] cycle time in milli seconds (default %lf)\n"
" -p Do you need to print the data in console output\n"
" -P [num] Publisher priority value (default %d)\n"
" -U [num] User application priority value (default %d)\n"
" -c [num] run on CPU for publisher'num'(default %d)\n"
" -u [num] run on CPU for userApplication'num'(default %d)\n"
" -m [mac_addr] ToDO:dst MAC address\n"
" -h prints this message and exits\n"
"\n",
appname, DEFAULT_CYCLE_TIME, DEFAULT_SUB_SCHED_PRIORITY, \
DEFAULT_USERAPPLICATION_SCHED_PRIORITY, DEFAULT_PUBSUB_CORE, DEFAULT_USER_APP_CORE);
}
/**
* **Main Server code**
*
* The main function contains subscriber threads running
*/
int main(int argc, char **argv) {
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
UA_Int32 returnValue = 0;
char *interface = NULL;
char *progname;
UA_Int32 argInputs = -1;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_Server *server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
pthread_t userThreadID;
UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL);
/* Files namespace_example_subscriber_generated.h and namespace_example_subscriber_generated.c are created from
* subDataModel.xml in the /src_generated directory by CMake */
/* Loading the user created variables into the information model from the generated .c and .h files */
if(namespace_example_subscriber_generated(server) != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not add the example nodeset. "
"Check previous output for any error.");
}
else
{
nsIdx = UA_Server_addNamespace(server, "http://yourorganisation.org/test/");
}
UA_NetworkAddressUrlDataType networkAddressUrlSub;
/* For more information run ./bin/examples/pubsub_nodeset_rt_subscriber -h */
/* Process the command line arguments */
progname = strrchr(argv[0], '/');
progname = progname ? 1 + progname : argv[0];
while (EOF != (argInputs = getopt(argc, argv, "i:C:f:ps:P:U:c:u:tm:h:"))) {
switch (argInputs) {
case 'i':
interface = optarg;
break;
case 'C':
cycleTimeMsec = atof(optarg);
break;
case 'f':
fileName = optarg;
fileWrite = UA_TRUE;
fpSubscriber = fopen(fileName, "w");
break;
case 'p':
consolePrint = UA_TRUE;
break;
case 'P':
subPriority = atoi(optarg);
break;
case 'U':
userAppPriority = atoi(optarg);
break;
case 'c':
pubSubCore = atoi(optarg);
break;
case 'u':
userAppCore = atoi(optarg);
break;
case 'm':
/*ToDo:Need to handle for mac address*/
break;
case 'h':
usage(progname);
return -1;
case '?':
usage(progname);
return -1;
}
}
if (cycleTimeMsec < 0.125) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "%f Bad cycle time", cycleTimeMsec);
usage(progname);
return -1;
}
if (!interface) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Need a network interface to run");
usage(progname);
UA_Server_delete(server);
return 0;
}
networkAddressUrlSub.networkInterface = UA_STRING(interface);
networkAddressUrlSub.url = UA_STRING(SUBSCRIBING_MAC_ADDRESS);
addPubSubConnectionSubscriber(server, &networkAddressUrlSub);
addReaderGroup(server);
addDataSetReader(server);
UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier);
UA_Server_enableReaderGroup(server, readerGroupIdentifier);
serverConfigStruct *serverConfig;
serverConfig = (serverConfigStruct*)UA_malloc(sizeof(serverConfigStruct));
serverConfig->ServerRun = server;
char threadNameUserApplication[22] = "UserApplicationSub";
userThreadID = threadCreation(userAppPriority, userAppCore, userApplicationSub, threadNameUserApplication, serverConfig);
retval |= UA_Server_run(server, &running);
UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier);
returnValue = pthread_join(subthreadID, NULL);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for subscriber thread:%d\n", returnValue);
}
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);
}
if (fileWrite == UA_TRUE) {
/* Write the subscribed data in the file */
size_t subLoopVariable = 0;
for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber;
subLoopVariable++) {
fprintf(fpSubscriber, "%lu,%ld.%09ld,%lf\n",
(long unsigned)subscribeCounterValue[subLoopVariable],
subscribeTimestamp[subLoopVariable].tv_sec,
subscribeTimestamp[subLoopVariable].tv_nsec,
pressureValues[subLoopVariable]);
}
fclose(fpSubscriber);
}
if (consolePrint == UA_TRUE) {
size_t subLoopVariable = 0;
for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber;
subLoopVariable++) {
fprintf(fpSubscriber, "%lu,%ld.%09ld,%lf\n",
(long unsigned)subscribeCounterValue[subLoopVariable],
subscribeTimestamp[subLoopVariable].tv_sec,
subscribeTimestamp[subLoopVariable].tv_nsec,
pressureValues[subLoopVariable]);
}
}
UA_Server_delete(server);
UA_free(serverConfig);
UA_free(subCounterData);
/* Free external data source */
UA_free(subDataValueRT);
UA_free(pressureData);
/* Free external data source */
UA_free(pressureValueRT);
return (int)retval;
}

View File

@ -1,4 +0,0 @@
SubscriberInfo,2001,ObjectType
Subscriber,2004,Object
PublisherCounterValue,2005,Variable
Pressure,2006,Variable
1 SubscriberInfo 2001 ObjectType
2 Subscriber 2004 Object
3 PublisherCounterValue 2005 Variable
4 Pressure 2006 Variable

View File

@ -1,80 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<UANodeSet xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd" xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<NamespaceUris>
<Uri>http://yourorganisation.org/test/</Uri>
</NamespaceUris>
<Aliases>
<Alias Alias="HasModellingRule">i=37</Alias>
<Alias Alias="UInt64">i=9</Alias>
<Alias Alias="Double">i=11</Alias>
<Alias Alias="Organizes">i=35</Alias>
<Alias Alias="HasTypeDefinition">i=40</Alias>
<Alias Alias="HasSubtype">i=45</Alias>
<Alias Alias="HasComponent">i=47</Alias>
</Aliases>
<UAObjectType BrowseName="1:SubscriberInfo" NodeId="ns=1;i=2001">
<DisplayName>SubscriberInfo</DisplayName>
<Description>SubscriberInfo</Description>
<References>
<Reference IsForward="false" ReferenceType="HasSubtype">i=58</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2002</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2003</Reference>
</References>
</UAObjectType>
<UAVariable BrowseName="1:SubscriberCounterVariable" DataType="UInt64" NodeId="ns=1;i=2002" ParentNodeId="ns=1;i=2001">
<DisplayName>SubscriberCounterVariable</DisplayName>
<Description>SubscriberCounterVariable</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2001</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
<Reference ReferenceType="HasModellingRule">i=78</Reference>
</References>
<Value>
<uax:UInt64>0</uax:UInt64>
</Value>
</UAVariable>
<UAVariable BrowseName="1:Pressure" DataType="Double" NodeId="ns=1;i=2003" ParentNodeId="ns=1;i=2001">
<DisplayName>Pressure</DisplayName>
<Description>Pressure</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2001</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
<Reference ReferenceType="HasModellingRule">i=78</Reference>
</References>
<Value>
<uax:Double>0.0</uax:Double>
</Value>
</UAVariable>
<UAObject BrowseName="1:Subscriber" NodeId="ns=1;i=2004" ParentNodeId="i=85">
<DisplayName>Subscriber</DisplayName>
<Description>SubscriberInfo</Description>
<References>
<Reference IsForward="false" ReferenceType="Organizes">i=85</Reference>
<Reference ReferenceType="HasTypeDefinition">ns=1;i=2001</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2005</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2006</Reference>
</References>
</UAObject>
<UAVariable BrowseName="1:SubscriberCounterVariable" DataType="UInt64" NodeId="ns=1;i=2005" ParentNodeId="ns=1;i=2004">
<DisplayName>SubscriberCounterVariable</DisplayName>
<Description>SubscriberCounterVariable</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2004</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
</References>
<Value>
<uax:UInt64>0</uax:UInt64>
</Value>
</UAVariable>
<UAVariable BrowseName="1:Pressure" DataType="Double" NodeId="ns=1;i=2006" ParentNodeId="ns=1;i=2004">
<DisplayName>Pressure</DisplayName>
<Description>Pressure</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2004</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
</References>
<Value>
<uax:Double>0.0</uax:Double>
</Value>
</UAVariable>
</UANodeSet>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,380 +0,0 @@
/* 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 2018-2019 (c) Kalycito Infotech
* Copyright 2019 (c) Fraunhofer IOSB (Author: Andreas Ebner)
* Copyright 2019 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright (c) 2020 Wind River Systems, Inc.
*/
#if __STDC_VERSION__ >= 199901L
#define _XOPEN_SOURCE 600
#else
#define _XOPEN_SOURCE 500
#endif /* __STDC_VERSION__ */
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/server_pubsub.h>
#define ETH_PUBLISH_ADDRESS "opc.eth://0a-00-27-00-00-08"
#define ETH_INTERFACE "enp0s8"
#define MAX_MEASUREMENTS 10000
#define MILLI_AS_NANO_SECONDS (1000 * 1000)
#define SECONDS_AS_NANO_SECONDS (1000 * 1000 * 1000)
#define CLOCKID CLOCK_MONOTONIC_RAW
#define SIG SIGUSR1
#define PUB_INTERVAL 0.25 /* Publish interval in milliseconds */
#define DATA_SET_WRITER_ID 62541
#define MEASUREMENT_OUTPUT "publisher_measurement.csv"
/* The RT level of the publisher */
/* possible options: PUBSUB_CONFIG_FASTPATH_NONE, PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS, PUBSUB_CONFIG_FASTPATH_STATIC_VALUES */
#define PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS
UA_NodeId counterNodePublisher = {1, UA_NODEIDTYPE_NUMERIC, {1234}};
UA_Int64 pubIntervalNs;
UA_ServerCallback pubCallback = NULL; /* Sentinel if a timer is active */
UA_Server *pubServer;
UA_Boolean running = true;
void *pubData;
timer_t pubEventTimer;
struct sigevent pubEvent;
struct sigaction signalAction;
#if defined(PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS) || (PUBSUB_CONFIG_FASTPATH_STATIC_VALUES)
UA_DataValue *staticValueSource = NULL;
#endif
/* Arrays to store measurement data */
UA_Int32 currentPublishCycleTime[MAX_MEASUREMENTS+1];
struct timespec calculatedCycleStartTime[MAX_MEASUREMENTS+1];
struct timespec cycleStartDelay[MAX_MEASUREMENTS+1];
struct timespec cycleDuration[MAX_MEASUREMENTS+1];
size_t publisherMeasurementsCounter = 0;
/* The value to published */
static UA_UInt64 publishValue = 62541;
static UA_StatusCode
readPublishValue(UA_Server *server,
const UA_NodeId *sessionId, void *sessionContext,
const UA_NodeId *nodeId, void *nodeContext,
UA_Boolean sourceTimeStamp, const UA_NumericRange *range,
UA_DataValue *dataValue) {
UA_Variant_setScalarCopy(&dataValue->value, &publishValue,
&UA_TYPES[UA_TYPES_UINT64]);
dataValue->hasValue = true;
return UA_STATUSCODE_GOOD;
}
static void
timespec_diff(struct timespec *start, struct timespec *stop,
struct timespec *result) {
if((stop->tv_nsec - start->tv_nsec) < 0) {
result->tv_sec = stop->tv_sec - start->tv_sec - 1;
result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000;
} else {
result->tv_sec = stop->tv_sec - start->tv_sec;
result->tv_nsec = stop->tv_nsec - start->tv_nsec;
}
}
/* Used to adjust the nanosecond > 1s field value */
static void
nanoSecondFieldConversion(struct timespec *timeSpecValue) {
while(timeSpecValue->tv_nsec > (SECONDS_AS_NANO_SECONDS - 1)) {
timeSpecValue->tv_sec += 1;
timeSpecValue->tv_nsec -= SECONDS_AS_NANO_SECONDS;
}
}
/* Signal handler */
static void
publishInterrupt(int sig, siginfo_t* si, void* uc) {
if(si->si_value.sival_ptr != &pubEventTimer) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "stray signal");
return;
}
/* Execute the publish callback in the interrupt */
struct timespec begin, end;
clock_gettime(CLOCKID, &begin);
pubCallback(pubServer, pubData);
clock_gettime(CLOCKID, &end);
if(publisherMeasurementsCounter >= MAX_MEASUREMENTS)
return;
/* Save current configured publish interval */
currentPublishCycleTime[publisherMeasurementsCounter] = (UA_Int32)pubIntervalNs;
/* Save the difference to the calculated time */
timespec_diff(&calculatedCycleStartTime[publisherMeasurementsCounter],
&begin, &cycleStartDelay[publisherMeasurementsCounter]);
/* Save the duration of the publish callback */
timespec_diff(&begin, &end, &cycleDuration[publisherMeasurementsCounter]);
publisherMeasurementsCounter++;
/* Save the calculated starting time for the next cycle */
calculatedCycleStartTime[publisherMeasurementsCounter].tv_nsec =
calculatedCycleStartTime[publisherMeasurementsCounter - 1].tv_nsec + pubIntervalNs;
calculatedCycleStartTime[publisherMeasurementsCounter].tv_sec =
calculatedCycleStartTime[publisherMeasurementsCounter - 1].tv_sec;
nanoSecondFieldConversion(&calculatedCycleStartTime[publisherMeasurementsCounter]);
/* Write the pubsub measurement data */
if(publisherMeasurementsCounter == MAX_MEASUREMENTS) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Logging the measurements to %s", MEASUREMENT_OUTPUT);
FILE *fpPublisher = fopen(MEASUREMENT_OUTPUT, "w");
for(UA_UInt32 i = 0; i < publisherMeasurementsCounter; i++) {
fprintf(fpPublisher, "%u, %u, %ld.%09ld, %ld.%09ld, %ld.%09ld\n",
i,
currentPublishCycleTime[i],
calculatedCycleStartTime[i].tv_sec,
calculatedCycleStartTime[i].tv_nsec,
cycleStartDelay[i].tv_sec,
cycleStartDelay[i].tv_nsec,
cycleDuration[i].tv_sec,
cycleDuration[i].tv_nsec);
}
fclose(fpPublisher);
}
}
/* The following three methods are originally defined in
* /src/pubsub/ua_pubsub_manager.c. We provide a custom implementation here to
* use system interrupts instead if time-triggered callbacks in the OPC UA
* server control flow. */
static UA_StatusCode
addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
UA_ServerCallback callback,
void *data, UA_Double interval_ms,
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy,
UA_UInt64 *callbackId) {
if(pubCallback) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"At most one publisher can be registered for interrupt callbacks");
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Adding a publisher with a cycle time of %lf milliseconds", interval_ms);
/* Set global values for the publish callback */
int resultTimerCreate = 0;
pubIntervalNs = (UA_Int64) (interval_ms * MILLI_AS_NANO_SECONDS);
/* Handle the signal */
memset(&signalAction, 0, sizeof(signalAction));
signalAction.sa_flags = SA_SIGINFO;
signalAction.sa_sigaction = publishInterrupt;
sigemptyset(&signalAction.sa_mask);
sigaction(SIG, &signalAction, NULL);
/* Create the timer */
memset(&pubEventTimer, 0, sizeof(pubEventTimer));
pubEvent.sigev_notify = SIGEV_SIGNAL;
pubEvent.sigev_signo = SIG;
pubEvent.sigev_value.sival_ptr = &pubEventTimer;
resultTimerCreate = timer_create(CLOCKID, &pubEvent, &pubEventTimer);
if(resultTimerCreate != 0) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Failed to create a system event with code %s",
strerror(errno));
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Arm the timer */
struct itimerspec timerspec;
timerspec.it_interval.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
timerspec.it_interval.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
timerspec.it_value.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
timerspec.it_value.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
resultTimerCreate = timer_settime(pubEventTimer, 0, &timerspec, NULL);
if(resultTimerCreate != 0) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Failed to arm the system timer with code %i", resultTimerCreate);
timer_delete(pubEventTimer);
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Start taking measurements */
publisherMeasurementsCounter = 0;
clock_gettime(CLOCKID, &calculatedCycleStartTime[0]);
calculatedCycleStartTime[0].tv_nsec += pubIntervalNs;
nanoSecondFieldConversion(&calculatedCycleStartTime[0]);
/* Set the callback -- used as a sentinel to detect an operational publisher */
pubServer = server;
pubCallback = callback;
pubData = data;
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
UA_UInt64 callbackId, UA_Double interval_ms,
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Switching the publisher cycle to %lf milliseconds", interval_ms);
struct itimerspec timerspec;
int resultTimerCreate = 0;
pubIntervalNs = (UA_Int64) (interval_ms * MILLI_AS_NANO_SECONDS);
timerspec.it_interval.tv_sec = (long int) (pubIntervalNs / SECONDS_AS_NANO_SECONDS);
timerspec.it_interval.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
timerspec.it_value.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
timerspec.it_value.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
resultTimerCreate = timer_settime(pubEventTimer, 0, &timerspec, NULL);
if(resultTimerCreate != 0) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Failed to arm the system timer");
timer_delete(pubEventTimer);
return UA_STATUSCODE_BADINTERNALERROR;
}
clock_gettime(CLOCKID, &calculatedCycleStartTime[publisherMeasurementsCounter]);
calculatedCycleStartTime[publisherMeasurementsCounter].tv_nsec += pubIntervalNs;
nanoSecondFieldConversion(&calculatedCycleStartTime[publisherMeasurementsCounter]);
return UA_STATUSCODE_GOOD;
}
static void
removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) {
if(!pubCallback)
return;
timer_delete(pubEventTimer);
pubCallback = NULL; /* So that a new callback can be registered */
}
static void
addPubSubConfiguration(UA_Server* server) {
UA_NodeId connectionIdent;
UA_NodeId publishedDataSetIdent;
UA_NodeId writerGroupIdent;
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-eth-uadp");
connectionConfig.enabled = true;
UA_NetworkAddressUrlDataType networkAddressUrl =
{UA_STRING(ETH_INTERFACE), UA_STRING(ETH_PUBLISH_ADDRESS)};
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT32;
connectionConfig.publisherId.uint32 = UA_UInt32_random();
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
UA_PublishedDataSetConfig publishedDataSetConfig;
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
publishedDataSetConfig.name = UA_STRING("Demo PDS");
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig,
&publishedDataSetIdent);
UA_NodeId dataSetFieldIdentCounter;
UA_DataSetFieldConfig counterValue;
memset(&counterValue, 0, sizeof(UA_DataSetFieldConfig));
counterValue.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
counterValue.field.variable.fieldNameAlias = UA_STRING ("Counter Variable 1");
counterValue.field.variable.promotedField = UA_FALSE;
counterValue.field.variable.publishParameters.publishedVariable = counterNodePublisher;
counterValue.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
#if defined(PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS) || (PUBSUB_CONFIG_FASTPATH_STATIC_VALUES)
staticValueSource = UA_DataValue_new();
UA_Variant_setScalar(&staticValueSource->value, &publishValue, &UA_TYPES[UA_TYPES_UINT64]);
counterValue.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
counterValue.field.variable.rtValueSource.staticValueSource = &staticValueSource;
#endif
UA_Server_addDataSetField(server, publishedDataSetIdent, &counterValue,
&dataSetFieldIdentCounter);
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = PUB_INTERVAL;
writerGroupConfig.enabled = UA_FALSE;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
writerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback;
writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback;
writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback;
UA_Server_addWriterGroup(server, connectionIdent,
&writerGroupConfig, &writerGroupIdent);
UA_NodeId dataSetWriterIdent;
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID;
dataSetWriterConfig.keyFrameCount = 10;
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
&dataSetWriterConfig, &dataSetWriterIdent);
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
UA_Server_enableWriterGroup(server, writerGroupIdent);
}
static void
addServerNodes(UA_Server* server) {
UA_UInt64 value = 0;
UA_VariableAttributes publisherAttr = UA_VariableAttributes_default;
UA_Variant_setScalar(&publisherAttr.value, &value, &UA_TYPES[UA_TYPES_UINT64]);
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher Counter");
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_DataSource dataSource;
dataSource.read = readPublishValue;
dataSource.write = NULL;
UA_Server_addDataSourceVariableNode(server, counterNodePublisher,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "Publisher Counter"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
publisherAttr, dataSource, NULL, NULL);
}
/* Stop signal */
static void stopHandler(int sign) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = UA_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);
addServerNodes(server);
addPubSubConfiguration(server);
/* Run the server */
UA_StatusCode retval = UA_Server_run(server, &running);
#if defined(PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS) || (PUBSUB_CONFIG_FASTPATH_STATIC_VALUES)
if(staticValueSource != NULL) {
UA_DataValue_init(staticValueSource);
UA_DataValue_delete(staticValueSource);
}
#endif
UA_Server_delete(server);
return (int)retval;
}

View File

@ -1,51 +0,0 @@
#!/bin/bash
#program path or install dir
IF=enp0s8
CPU_NR=3
PRIO=90
OLDDIR=$(pwd)
reset() {
#standard on most systems. ondemand -> Dynamic CPU-Freq.
echo ondemand >/sys/devices/system/cpu/cpu$CPU_NR/cpufreq/scaling_governor
cd /sys/devices/system/cpu/cpu$CPU_NR/cpuidle
for i in *
do
#~disable sleep state
echo 0 >$i/disable
done
phy=$IF #get interface name
#get pid's from interface irq
for i in `ps ax | grep -v grep | grep $phy | sed "s/^ //" | cut -d" " -f1`
do
#retrive or set a process's CPU affinity
taskset -pc 0-$CPU_NR $i >/dev/null
#manipulate the real-time attributes of a process -p priority -f scheduling policy to SCHED_FIFO
chrt -pf 50 $i
done
#distribute hardware interrupts across processsors on a muliprocessor system
systemctl start irqbalance
}
trap reset ERR
systemctl stop irqbalance
phy=$IF
for i in `ps ax | grep -v grep | grep $phy | sed "s/^ //" | cut -d" " -f1`
do
taskset -pc $CPU_NR $i >/dev/null
chrt -pf $PRIO $i
done
cd /sys/devices/system/cpu/cpu$CPU_NR/cpuidle
for i in `ls -1r`
do
echo 1 >$i/disable
done
echo performance >/sys/devices/system/cpu/cpu$CPU_NR/cpufreq/scaling_governor
cd $OLDDIR
taskset -c $CPU_NR chrt -f $PRIO $1

View File

@ -1,103 +0,0 @@
# Open62541 pubsub VxWorks TSN publisher
This TSN publisher is a complement to [pubsub_interrupt_publish.c](../pubsub_interrupt_publish.c)(For more information, please refer to its [README](../README.md)). It is a VxWorks specific implementation with VxWorks-native TSN features like TSN Clock, TSN Stream, etc, which can be applied to hard realtime scenarios.
In this publisher, TSN cycle is triggered by TSN Clock -- A high-precision hardware interrupt generated by a 1588 device. The ISR will wake up a task to publish OPC-UA packets, one packet per cycle.
Considering in a typical VxWorks user scenario -- an embedded device with limited compute resources and probably without local storage, the performance will not be measured directly by this publisher. Instead, the publisher will collect all the necessary data, pack the data into an OPC-UA packet and publish it.
The measurement data includes:
1. The trigger time(1588 time) of each cycle.
2. Each wake-up time(1588 time) of the publisher task.
3. Each end time(1588 time) of the publisher task.
## User interfaces
This publisher provides 2 APIs to users:
1. `open62541PubTSNStart`
```C
/**
* Create a publisher with/without TSN.
* eName: Ethernet interface name like "gei", "gem", etc
* eNameSize: The length of eName including "\0"
* unit: Unit NO of the ethernet interface
* stkIdx: Network stack index
* withTsn: true: Enable TSN; false: Disable TSN
*
* @return OK on success, otherwise ERROR
*/
STATUS open62541PubTSNStart(char *eName, size_t eNameSize, int unit, uint32_t stkIdx, bool withTsn);
```
In order to get the best performance, this API supports to associate a dedicated CPU core with TSN related interrupt and tasks. The stkIdx argument is used for this purpose. Its value is the CPU core number: 0 means Core 0, 1 means Core 1, etc.
2. `open62541PubTSNStop`. This API is used to stop the publisher.
```C
void open62541PubTSNStop();
```
## Building the VxWorks TSN Publisher
This publisher should be built under VxWorks' building environment. For specific building instructions, please refer to VxWorks' user manual.
## Running the VxWorks TSN Publisher
1. Before starting the publisher, you should make sure that PTP is well synchronized. For the usage of PTP, please refer to VxWorks' user manual.
2. Do TSN configuration by tsnConfig as below:
```sh
-> tsnConfig("gei", 4, 1, 0, "/romfs/62541.json")
```
`62541.json` is a TSN configuration file. Below is one example configuration using 250 usecs cycle time. Adjust cycle_time and tx_time according to your system performance.
```json
{
"schedule": {
"cycle_time": 250000,
"start_sec": 0,
"start_nsec": 0
},
"stream_objects": [
{
"stream": {
"name": "flow1",
"dst_mac": "01:00:5E:00:00:01",
"vid": 3000,
"pcp": 7,
"tclass": 7,
"tx_time": {
"offset": 200000
}
}
}
]
}
```
3. Then run `open62541PubTSNStart`. Below is an example.
```sh
-> open62541PubTSNStart("gei", 4, 1, 3)
value = 0 = 0x0
-> [1970-01-01 00:47:33.100 (UTC+0000)] warn/server Username/Password configured, but no encrypting SecurityPolicy. This can leak credentials on the ne.
[1970-01-01 00:47:33.100 (UTC+0000)] warn/userland AcceptAll Certificate Verification. Any remote certificate will be accepted.
[1970-01-01 00:47:33.100 (UTC+0000)] info/userland PubSub channel requested
[1970-01-01 00:47:33.100 (UTC+0000)] info/server Open PubSub ethernet connection.
[1970-01-01 00:47:33.100 (UTC+0000)] info/userland Adding a publisher with a cycle time of 0.250000 milliseconds
[1970-01-01 00:47:33.200 (UTC+0000)] info/network TCP network layer listening on opc.tcp://vxWorks:4840/
```
4. Run `open62541PubTSNStop` to stop the publisher.
```sh
-> open62541PubTSNStop
[1970-01-01 00:48:16.500 (UTC+0000)] info/server Stop TSN publisher
[1970-01-01 00:48:16.516 (UTC+0000)] info/network Shutting down the TCP network layer
[1970-01-01 00:48:16.516 (UTC+0000)] info/server PubSub cleanup was called.
value = 0 = 0x0
```

View File

@ -1,630 +0,0 @@
/* 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, 2022, 2024 Wind River Systems, Inc.
*/
#include <vxWorks.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <endCommon.h>
#include <tsnClkLib.h>
#include <endian.h>
#include <semLib.h>
#include <taskLib.h>
#include <tsnConfigLib.h>
#include <open62541/server.h>
#include <open62541/server_pubsub.h>
#include <open62541/server_config_default.h>
#include <open62541/plugin/log_stdout.h>
#define ETH_PUBLISH_ADDRESS "opc.eth://01-00-5E-00-00-01"
#define MILLI_AS_NANO_SECONDS (1000 * 1000)
#define SECONDS_AS_NANO_SECONDS (1000 * 1000 * 1000)
#define DATA_SET_WRITER_ID 62541
#define TSN_TIMER_NO 0
#define NS_PER_SEC 1000000000 /* nanoseconds per one second */
#define NS_PER_MS 1000000 /* nanoseconds per 1 millisecond */
#define TSN_TASK_PRIO 30
#define TSN_TASK_STACKSZ 8192
#define KEY_STREAM_NAME "streamName"
#define KEY_STACK_IDX "stackIdx"
static UA_NodeId seqNumNodeId;
static UA_NodeId cycleTriggerTimeNodeId;
static UA_NodeId taskBeginTimeNodeId;
static UA_NodeId taskEndTimeNodeId;
static UA_ServerCallback pubCallback = NULL; /* Sentinel if a timer is active */
static UA_Server *pubServer;
static UA_Boolean running = true;
static void *pubData;
static TASK_ID tsnTask = TASK_ID_NULL;
static TASK_ID serverTask = TASK_ID_NULL;
static TSN_STREAM_CFG *streamCfg = NULL;
/* The value to published */
static UA_UInt32 sequenceNumber = 0;
static UA_UInt64 cycleTriggerTime = 0;
static UA_UInt64 lastCycleTriggerTime = 0;
static UA_UInt64 lastTaskBeginTime = 0;
static UA_UInt64 lastTaskEndTime = 0;
static UA_DataValue *staticValueSeqNum = NULL;
static UA_DataValue *staticValueCycTrig = NULL;
static UA_DataValue *staticValueCycBegin = NULL;
static UA_DataValue *staticValueCycEnd = NULL;
static clockid_t tsnClockId = 0; /* TSN clock ID */
static char *ethName = NULL;
static int ethUnit = 0;
static char ethInterface[END_NAME_MAX];
static SEM_ID msgSendSem = SEM_ID_NULL;
static UA_String streamName = {0, NULL};
static uint32_t stackIndex = 0;
static UA_Double pubInterval = 0;
static bool withTSN = false;
static UA_UInt64 ieee1588TimeGet() {
struct timespec ts;
(void)tsnClockTimeGet(tsnClockId, &ts);
return ((UA_UInt64)ts.tv_sec * NS_PER_SEC + (UA_UInt64)ts.tv_nsec);
}
/* Signal handler */
static void
publishInterrupt(_Vx_usr_arg_t arg) {
cycleTriggerTime = ieee1588TimeGet();
if(running) {
(void)semGive(msgSendSem);
}
}
/**
* **initTsnTimer**
*
* This function initializes a TSN timer. It connects a user defined routine
* to the interrupt handler and sets timer expiration rate.
*
* period is the period of the timer in nanoseconds.
* RETURNS: Clock Id or 0 if anything fails*/
static clockid_t
initTsnTimer(uint32_t period) {
clockid_t cid;
uint32_t tickRate = 0;
cid = tsnClockIdGet(ethName, ethUnit, TSN_TIMER_NO);
if(cid != 0) {
tickRate = NS_PER_SEC / period;
if(tsnTimerAllocate(cid) == ERROR) {
return 0;
}
if((tsnClockConnect(cid, (FUNCPTR)publishInterrupt, NULL) == ERROR) ||
(tsnClockRateSet(cid, tickRate) == ERROR)) {
(void)tsnTimerRelease(cid);
return 0;
}
}
/* Reroute TSN timer interrupt to a specific CPU core */
if(tsnClockIntReroute(ethName, ethUnit, stackIndex) != OK) {
cid = 0;
}
return cid;
}
/* The following three methods are originally defined in
* /src/pubsub/ua_pubsub_manager.c. We provide a custom implementation here to
* use system interrupts instead of time-triggered callbacks in the OPC UA
* server control flow. */
static UA_StatusCode
addApplicationCallback(UA_Server *server, UA_NodeId identifier,
UA_ServerCallback callback,
void *data,
UA_Double interval_ms,
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy,
UA_UInt64 *callbackId) {
if(pubCallback) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"At most one publisher can be registered for interrupt callbacks");
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Adding a publisher with a cycle time of %lf milliseconds", interval_ms);
/* Convert a double float value milliseconds into an integer value in nanoseconds */
uint32_t interval = (uint32_t)(interval_ms * NS_PER_MS);
tsnClockId = initTsnTimer(interval);
if(tsnClockId == 0) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot allocate a TSN timer");
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Set the callback -- used as a sentinel to detect an operational publisher */
pubServer = server;
pubCallback = callback;
pubData = data;
if(tsnClockEnable (tsnClockId, NULL) == ERROR) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot enable a TSN timer");
(void)tsnTimerRelease(tsnClockId);
tsnClockId = 0;
return UA_STATUSCODE_BADINTERNALERROR;
}
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
changeApplicationCallbackInterval(UA_Server *server, UA_NodeId identifier,
UA_UInt64 callbackId,
UA_Double interval_ms,
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Switching the publisher cycle to %lf milliseconds", interval_ms);
/* We are not going to change the timer interval for this case */
return UA_STATUSCODE_GOOD;
}
static void
removeApplicationPubSubCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) {
if(!pubCallback) {
return;
}
/* Before release timer resource, wait for TSN task stopping first. */
(void) semGive(msgSendSem);
if(tsnTask != TASK_ID_NULL) {
(void) taskWait(tsnTask, WAIT_FOREVER);
tsnTask = TASK_ID_NULL;
}
/* It is safe to disable and release the timer first, then clear callback */
if(tsnClockId != 0) {
(void)tsnClockDisable(tsnClockId);
(void)tsnTimerRelease(tsnClockId);
tsnClockId = 0;
}
pubCallback = NULL;
pubServer = NULL;
pubData = NULL;
}
static void
addPubSubConfiguration(UA_Server* server) {
UA_NodeId connectionIdent;
UA_NodeId publishedDataSetIdent;
UA_NodeId writerGroupIdent;
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-eth-uadp");
connectionConfig.enabled = true;
UA_NetworkAddressUrlDataType networkAddressUrl =
{UA_STRING(ethInterface), UA_STRING(ETH_PUBLISH_ADDRESS)};
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherId.numeric = UA_UInt32_random();
UA_KeyValuePair connectionOptions[2];
connectionOptions[0].key = UA_QUALIFIEDNAME(0, KEY_STREAM_NAME);
UA_Variant_setScalar(&connectionOptions[0].value, &streamName, &UA_TYPES[UA_TYPES_STRING]);
connectionOptions[1].key = UA_QUALIFIEDNAME(0, KEY_STACK_IDX);
UA_Variant_setScalar(&connectionOptions[1].value, &stackIndex, &UA_TYPES[UA_TYPES_UINT32]);
connectionConfig.connectionPropertiesSize = 2;
connectionConfig.connectionProperties = connectionOptions;
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
UA_PublishedDataSetConfig publishedDataSetConfig;
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
publishedDataSetConfig.name = UA_STRING("Demo PDS");
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig,
&publishedDataSetIdent);
UA_DataSetFieldConfig dataSetFieldCfg;
UA_NodeId f4;
memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig));
dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Sequence Number");
dataSetFieldCfg.field.variable.promotedField = UA_FALSE;
dataSetFieldCfg.field.variable.publishParameters.publishedVariable = seqNumNodeId;
dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
staticValueSeqNum = UA_DataValue_new();
UA_Variant_setScalar(&staticValueSeqNum->value, &sequenceNumber, &UA_TYPES[UA_TYPES_UINT32]);
staticValueSeqNum->value.storageType = UA_VARIANT_DATA_NODELETE;
dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueSeqNum;
UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f4);
UA_NodeId f3;
memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig));
dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Cycle Trigger Time");
dataSetFieldCfg.field.variable.promotedField = UA_FALSE;
dataSetFieldCfg.field.variable.publishParameters.publishedVariable = cycleTriggerTimeNodeId;
dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
staticValueCycTrig = UA_DataValue_new();
UA_Variant_setScalar(&staticValueCycTrig->value, &lastCycleTriggerTime, &UA_TYPES[UA_TYPES_UINT64]);
staticValueCycTrig->value.storageType = UA_VARIANT_DATA_NODELETE;
dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueCycTrig;
UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f3);
UA_NodeId f2;
memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig));
dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Task Begin Time");
dataSetFieldCfg.field.variable.promotedField = UA_FALSE;
dataSetFieldCfg.field.variable.publishParameters.publishedVariable = taskBeginTimeNodeId;
dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
staticValueCycBegin = UA_DataValue_new();
UA_Variant_setScalar(&staticValueCycBegin->value, &lastTaskBeginTime, &UA_TYPES[UA_TYPES_UINT64]);
staticValueCycBegin->value.storageType = UA_VARIANT_DATA_NODELETE;
dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueCycBegin;
UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f2);
UA_NodeId f1;
memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig));
dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Task End Time");
dataSetFieldCfg.field.variable.promotedField = UA_FALSE;
dataSetFieldCfg.field.variable.publishParameters.publishedVariable = taskEndTimeNodeId;
dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
staticValueCycEnd = UA_DataValue_new();
UA_Variant_setScalar(&staticValueCycEnd->value, &lastTaskEndTime, &UA_TYPES[UA_TYPES_UINT64]);
staticValueCycEnd->value.storageType = UA_VARIANT_DATA_NODELETE;
dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueCycEnd;
UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f1);
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = pubInterval;
writerGroupConfig.enabled = UA_FALSE;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
writerGroupConfig.pubsubManagerCallback.addCustomCallback = addApplicationCallback;
writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changeApplicationCallbackInterval;
writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removeApplicationPubSubCallback;
UA_Server_addWriterGroup(server, connectionIdent,
&writerGroupConfig, &writerGroupIdent);
UA_NodeId dataSetWriterIdent;
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID;
dataSetWriterConfig.keyFrameCount = 10;
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
&dataSetWriterConfig, &dataSetWriterIdent);
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
UA_Server_enableWriterGroup(server, writerGroupIdent);
}
static void
addServerNodes(UA_Server* server) {
UA_UInt64 initVal64 = 0;
UA_UInt32 initVal32 = 0;
UA_NodeId folderId;
UA_NodeId_init(&folderId);
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
oAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher TSN");
UA_Server_addObjectNode(server, UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "Publisher TSN"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
oAttr, NULL, &folderId);
UA_NodeId_init(&seqNumNodeId);
seqNumNodeId = UA_NODEID_STRING(1, "sequence.number");
UA_VariableAttributes publisherAttr = UA_VariableAttributes_default;
publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
UA_Variant_setScalar(&publisherAttr.value, &initVal32, &UA_TYPES[UA_TYPES_UINT32]);
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Sequence Number");
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_Server_addVariableNode(server, seqNumNodeId,
folderId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "Sequence Number"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
publisherAttr, NULL, NULL);
UA_ValueBackend valueBackend;
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend.backend.external.value = &staticValueSeqNum;
UA_Server_setVariableNode_valueBackend(server, seqNumNodeId, valueBackend);
UA_NodeId_init(&cycleTriggerTimeNodeId);
cycleTriggerTimeNodeId = UA_NODEID_STRING(1, "cycle.trigger.time");
publisherAttr = UA_VariableAttributes_default;
publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId;
UA_Variant_setScalar(&publisherAttr.value, &initVal64, &UA_TYPES[UA_TYPES_UINT64]);
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cycle Trigger Time");
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_Server_addVariableNode(server, cycleTriggerTimeNodeId,
folderId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "Cycle Trigger Time"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
publisherAttr, NULL, NULL);
valueBackend.backend.external.value = &staticValueCycTrig;
UA_Server_setVariableNode_valueBackend(server, cycleTriggerTimeNodeId, valueBackend);
UA_NodeId_init(&taskBeginTimeNodeId);
taskBeginTimeNodeId = UA_NODEID_STRING(1, "task.begin.time");
publisherAttr = UA_VariableAttributes_default;
publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId;
UA_Variant_setScalar(&publisherAttr.value, &initVal64, &UA_TYPES[UA_TYPES_UINT64]);
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Task Begin Time");
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_Server_addVariableNode(server, taskBeginTimeNodeId,
folderId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "Task Begin Time"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
publisherAttr, NULL, NULL);
valueBackend.backend.external.value = &staticValueCycBegin;
UA_Server_setVariableNode_valueBackend(server, taskBeginTimeNodeId, valueBackend);
UA_NodeId_init(&taskEndTimeNodeId);
taskEndTimeNodeId = UA_NODEID_STRING(1, "task.end.time");
publisherAttr = UA_VariableAttributes_default;
publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId;
UA_Variant_setScalar(&publisherAttr.value, &initVal64, &UA_TYPES[UA_TYPES_UINT64]);
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Task End Time");
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_Server_addVariableNode(server, taskEndTimeNodeId,
folderId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "Task End Time"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
publisherAttr, NULL, NULL);
valueBackend.backend.external.value = &staticValueCycEnd;
UA_Server_setVariableNode_valueBackend(server, taskEndTimeNodeId, valueBackend);
}
static void open62541EthTSNTask(void) {
uint64_t t = 0;
while(running) {
(void) semTake(msgSendSem, WAIT_FOREVER);
if(!running) {
break;
}
t = ieee1588TimeGet();
/*
* Because we cannot get the task end time of one packet before it is
* sent, we let one packet take its previous packet's cycleTriggerTime,
* taskBeginTime and taskEndTime.
*/
if(sequenceNumber == 0) {
lastCycleTriggerTime = 0;
lastTaskBeginTime = 0;
lastTaskEndTime = 0;
}
pubCallback(pubServer, pubData);
sequenceNumber++;
lastCycleTriggerTime = cycleTriggerTime;
lastTaskBeginTime = t;
lastTaskEndTime = ieee1588TimeGet();
}
}
static void open62541ServerTask(void) {
UA_Server *server = UA_Server_new();
if(server == NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot allocate a server object");
goto serverCleanup;
}
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefault(config);
addServerNodes(server);
addPubSubConfiguration(server);
/* Run the server */
(void) UA_Server_run(server, &running);
serverCleanup:
if(server != NULL) {
UA_Server_delete(server);
server = NULL;
}
}
static bool initTSNStream(char *eName, size_t eNameSize, int unit) {
streamCfg = tsnConfigFind (eName, eNameSize, unit);
if(streamCfg == NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot find TSN configuration for %s%d", eName, unit);
return false;
}
if(streamCfg->streamCount == 0) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot find stream defined for %s%d", eName, unit);
return false;
}
else {
if(withTSN) {
streamName = UA_STRING(streamCfg->streamObjs[0].stream.name);
} else {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "No TSN stream associated.");
streamName = UA_STRING_NULL;
}
}
pubInterval = (UA_Double)((streamCfg->schedule.cycleTime * 1.0) / NS_PER_MS);
return true;
}
static bool initTSNTask() {
tsnTask = taskSpawn ((char *)"tTsnPub", TSN_TASK_PRIO, 0, TSN_TASK_STACKSZ, (FUNCPTR)open62541EthTSNTask,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
if(tsnTask == TASK_ID_ERROR) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot spawn a TSN task");
return false;
}
/* Reroute TSN task to a specific CPU core */
#ifdef _WRS_CONFIG_SMP
unsigned int ncpus = vxCpuConfiguredGet ();
cpuset_t cpus;
if(stackIndex < ncpus) {
CPUSET_ZERO (cpus);
CPUSET_SET (cpus, stackIndex);
if(taskCpuAffinitySet (tsnTask, cpus) != OK) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot move TSN task to core %d", stackIndex);
return false;
}
}
#endif
return true;
}
static bool initServerTask() {
serverTask = taskSpawn((char *)"tPubServer", TSN_TASK_PRIO + 5, 0, TSN_TASK_STACKSZ*2, (FUNCPTR)open62541ServerTask,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
if(serverTask == TASK_ID_ERROR) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot spawn a server task");
return false;
}
/* Reroute TSN task to a specific CPU core */
#ifdef _WRS_CONFIG_SMP
unsigned int ncpus = vxCpuConfiguredGet();
cpuset_t cpus;
if(stackIndex < ncpus) {
CPUSET_ZERO(cpus);
CPUSET_SET(cpus, stackIndex);
if(taskCpuAffinitySet(serverTask, cpus) != OK) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot move TSN task to core %d", stackIndex);
return false;
}
}
#endif
return true;
}
void open62541PubTSNStop() {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Stop TSN publisher");
running = UA_FALSE;
if(serverTask != TASK_ID_NULL) {
(void) taskWait(serverTask, WAIT_FOREVER);
serverTask = TASK_ID_NULL;
}
(void) semGive(msgSendSem);
if(tsnTask != TASK_ID_NULL) {
(void) taskWait(tsnTask, WAIT_FOREVER);
tsnTask = TASK_ID_NULL;
}
if(msgSendSem != NULL) {
(void)semDelete(msgSendSem);
msgSendSem = SEM_ID_NULL;
}
ethName = NULL;
ethUnit = 0;
streamName = UA_STRING_NULL;
stackIndex = 0;
withTSN = false;
pubInterval = 0;
streamCfg = NULL;
if(staticValueSeqNum != NULL) {
UA_DataValue_delete(staticValueSeqNum);
staticValueSeqNum = NULL;
}
if(staticValueCycTrig != NULL) {
UA_DataValue_delete(staticValueCycTrig);
staticValueCycTrig = NULL;
}
if(staticValueCycBegin != NULL) {
UA_DataValue_delete(staticValueCycBegin);
staticValueCycBegin = NULL;
}
if(staticValueCycEnd != NULL) {
UA_DataValue_delete(staticValueCycEnd);
staticValueCycEnd = NULL;
}
sequenceNumber = 0;
cycleTriggerTime = 0;
lastCycleTriggerTime = 0;
lastTaskBeginTime = 0;
lastTaskEndTime = 0;
}
/**
* Create a publisher with/without TSN.
* eName: Ethernet interface name like "gei", "gem", etc
* eNameSize: The length of eName including "\0"
* unit: Unit NO of the ethernet interface
* stkIdx: Network stack index
* withTsn: true: Enable TSN; false: Disable TSN
*
* @return OK on success, otherwise ERROR
*/
STATUS open62541PubTSNStart(char *eName, size_t eNameSize, int unit, uint32_t stkIdx, bool withTsn) {
if((eName == NULL) || (eNameSize == 0)) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Ethernet interface name is invalid");
return ERROR;
}
ethName = eName;
ethUnit = unit;
stackIndex = stkIdx;
withTSN = withTsn;
snprintf(ethInterface, sizeof(ethInterface), "%s%d", eName, unit);
if(!initTSNStream(eName, eNameSize, unit)) {
goto startCleanup;
}
/* Create a binary semaphore which is used by the TSN timer to wake up the sender task */
msgSendSem = semBCreate(SEM_Q_FIFO, SEM_EMPTY);
if(msgSendSem == SEM_ID_NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot create a semaphore");
goto startCleanup;
}
running = true;
if(!initTSNTask()) {
goto startCleanup;
}
if(!initServerTask()) {
goto startCleanup;
}
return OK;
startCleanup:
open62541PubTSNStop();
return ERROR;
}

0
examples/pubsub_realtime/opc-ua-tsn-wrs.pdf Executable file → Normal file
View File

View File

@ -0,0 +1,151 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
#include <open62541/server.h>
#include <open62541/server_pubsub.h>
#include <stdio.h>
#define PUBSUB_CONFIG_PUBLISH_CYCLE_MS 100
#define PUBSUB_CONFIG_FIELD_COUNT 10
static UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier;
/* Values in static locations. We cycle the dvPointers double-pointer to the
* next with atomic operations. */
UA_UInt32 valueStore[PUBSUB_CONFIG_FIELD_COUNT];
UA_DataValue dvStore[PUBSUB_CONFIG_FIELD_COUNT];
UA_DataValue *dvPointers[PUBSUB_CONFIG_FIELD_COUNT];
UA_NodeId publishVariables[PUBSUB_CONFIG_FIELD_COUNT];
int main(void) {
/* Prepare the values */
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
valueStore[i] = (UA_UInt32) i + 1;
UA_Variant_setScalar(&dvStore[i].value, &valueStore[i], &UA_TYPES[UA_TYPES_UINT32]);
dvStore[i].hasValue = true;
dvPointers[i] = &dvStore[i];
}
/* Initialize the server */
UA_Server *server = UA_Server_new();
/* Add a 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");
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.idType = UA_PUBLISHERIDTYPE_UINT16;
connectionConfig.publisherId.id.uint16 = 2234;
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
/* Add a PublishedDataSet */
UA_PublishedDataSetConfig publishedDataSetConfig;
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
publishedDataSetConfig.name = UA_STRING("Demo PDS");
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent);
/* Add DataSetFields with static value source to PDS */
UA_DataSetFieldConfig dsfConfig;
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
/* Create the variable */
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32");
vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, (UA_UInt32)i + 50000),
UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(HASCOMPONENT),
UA_QUALIFIEDNAME(1, "Subscribed UInt32"),
UA_NS0ID(BASEDATAVARIABLETYPE),
vAttr, NULL, &publishVariables[i]);
/* Set the value backend of the above create node to 'external value source' */
UA_ValueBackend valueBackend;
memset(&valueBackend, 0, sizeof(UA_ValueBackend));
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend.backend.external.value = &dvPointers[i];
UA_Server_setVariableNode_valueBackend(server, publishVariables[i], valueBackend);
/* Add the DataSetField */
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
dsfConfig.field.variable.publishParameters.publishedVariable = publishVariables[i];
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent);
}
/* Add a WriterGroup */
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS;
writerGroupConfig.writerGroupId = 100;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
/* Change message settings of writerGroup to send PublisherId, WriterGroupId
* in GroupHeader and DataSetWriterId in PayloadHeader of NetworkMessage */
UA_UadpWriterGroupMessageDataType writerGroupMessage;
UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage);
writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask)
(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
UA_UADPNETWORKMESSAGECONTENTMASK_GROUPVERSION |
UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER |
UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage,
&UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]);
UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent);
/* Add a DataSetWriter to the WriterGroup */
UA_NodeId dataSetWriterIdent;
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
dataSetWriterConfig.dataSetWriterId = 62541;
dataSetWriterConfig.keyFrameCount = 10;
dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA;
UA_UadpDataSetWriterMessageDataType uadpDataSetWriterMessageDataType;
UA_UadpDataSetWriterMessageDataType_init(&uadpDataSetWriterMessageDataType);
uadpDataSetWriterMessageDataType.dataSetMessageContentMask =
UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER;
UA_ExtensionObject_setValue(&dataSetWriterConfig.messageSettings,
&uadpDataSetWriterMessageDataType,
&UA_TYPES[UA_TYPES_UADPDATASETWRITERMESSAGEDATATYPE]);
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
&dataSetWriterConfig, &dataSetWriterIdent);
/* Print the Offset Table */
UA_PubSubOffsetTable ot;
UA_Server_computeWriterGroupOffsetTable(server, writerGroupIdent, &ot);
for(size_t i = 0; i < ot.offsetsSize; i++) {
UA_String out = UA_STRING_NULL;
if(ot.offsets[i].offsetType >= UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE) {
/* For writers the component is the NodeId is the DataSetField.
* Instead print the source node that contains the data */
UA_DataSetFieldConfig dsfc;
UA_Server_getDataSetFieldConfig(server, ot.offsets[i].component, &dsfc);
UA_NodeId_print(&dsfc.field.variable.publishParameters.publishedVariable, &out);
UA_DataSetFieldConfig_clear(&dsfc);
} else {
UA_NodeId_print(&ot.offsets[i].component, &out);
}
printf("%u:\tOffset %u\tOffsetType %u\tComponent %.*s\n",
(unsigned)i, (unsigned)ot.offsets[i].offset,
(unsigned)ot.offsets[i].offsetType,
(int)out.length, out.data);
UA_String_clear(&out);
}
/* Cleanup */
UA_PubSubOffsetTable_clear(&ot);
UA_Server_delete(server);
return EXIT_SUCCESS;
}

View File

@ -8,11 +8,15 @@
#include <open62541/types.h> #include <open62541/types.h>
#include <signal.h> #include <signal.h>
#include <stdio.h>
#include <time.h>
#define PUBSUB_CONFIG_PUBLISH_CYCLE_MS 100 #define PUBSUB_CONFIG_PUBLISH_CYCLE_MS 100
#define PUBSUB_CONFIG_FIELD_COUNT 10 #define PUBSUB_CONFIG_FIELD_COUNT 10
UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier; static UA_Server *server;
static timer_t writerGroupTimer;
static UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier;
/** /**
* For realtime publishing the following is configured: * For realtime publishing the following is configured:
@ -47,36 +51,65 @@ valueUpdateCallback(UA_Server *server, void *data) {
} }
} }
/* Dedicated EventLoop for PubSub */ /* WriterGroup timer managed by a custom state machine. This uses
volatile UA_Boolean pubSubELRunning = true; * UA_Server_triggerWriterGroupPublish. The server can block its internal mutex,
UA_EventLoop *pubSubEL; * so this can have some jitter. For hard realtime the publish callback has to
* send out the packet without going through the server. */
static void * static void
runPubSubEL(void *_) { writerGroupPublishTrigger(union sigval signal) {
sigset_t set; printf("XXX Publish Callback\n");
sigemptyset(&set); UA_Server_triggerWriterGroupPublish(server, writerGroupIdent);
sigaddset(&set, SIGINT); }
pthread_sigmask(SIG_BLOCK, &set, NULL);
while(pubSubELRunning) static UA_StatusCode
pubSubEL->run(pubSubEL, 100); writerGroupStateMachine(UA_Server *server, const UA_NodeId componentId,
return NULL; void *componentContext, UA_PubSubState *state,
UA_PubSubState targetState) {
UA_WriterGroupConfig config;
struct itimerspec interval;
memset(&interval, 0, sizeof(interval));
if(targetState == *state)
return UA_STATUSCODE_GOOD;
switch(targetState) {
/* Disabled or Error */
case UA_PUBSUBSTATE_ERROR:
case UA_PUBSUBSTATE_DISABLED:
case UA_PUBSUBSTATE_PAUSED:
printf("XXX Disabling the WriterGroup\n");
timer_settime(writerGroupTimer, 0, &interval, NULL);
*state = targetState;
break;
/* Operational */
case UA_PUBSUBSTATE_PREOPERATIONAL:
case UA_PUBSUBSTATE_OPERATIONAL:
if(*state == UA_PUBSUBSTATE_OPERATIONAL)
break;
printf("XXX Enabling the WriterGroup\n");
UA_Server_getWriterGroupConfig(server, writerGroupIdent, &config);
interval.it_interval.tv_sec = config.publishingInterval / 1000;
interval.it_interval.tv_nsec =
((long long)(config.publishingInterval * 1000 * 1000)) % (1000 * 1000 * 1000);
interval.it_value = interval.it_interval;
UA_WriterGroupConfig_clear(&config);
int res = timer_settime(writerGroupTimer, 0, &interval, NULL);
if(res != 0)
return UA_STATUSCODE_BADINTERNALERROR;
*state = UA_PUBSUBSTATE_OPERATIONAL;
break;
/* Unknown state */
default:
return UA_STATUSCODE_BADINTERNALERROR;
}
return UA_STATUSCODE_GOOD;
} }
int main(void) { int main(void) {
UA_Server *server = UA_Server_new();
/* Instantiate the custom EventLoop.
* Will be attached to the PubSubConnection and gets used for everything "above".
* This should be bound to a dedicated core for RT. */
pubSubEL = UA_EventLoop_new_POSIX(UA_Log_Stdout);
UA_ConnectionManager *udpCM =
UA_ConnectionManager_new_POSIX_UDP(UA_STRING("udp connection manager"));
pubSubEL->registerEventSource(pubSubEL, (UA_EventSource *)udpCM);
pubSubEL->start(pubSubEL);
pthread_t pubSubELThread;
pthread_create(&pubSubELThread, NULL, runPubSubEL, NULL);
/* Prepare the values */ /* Prepare the values */
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
valueStore[i] = (UA_UInt32) i + 1; valueStore[i] = (UA_UInt32) i + 1;
@ -85,14 +118,26 @@ int main(void) {
dvPointers[i] = &dvStore[i]; dvPointers[i] = &dvStore[i];
} }
/* Initialize the timer */
struct sigevent sigev;
memset(&sigev, 0, sizeof(sigev));
sigev.sigev_notify = SIGEV_THREAD;
sigev.sigev_notify_function = writerGroupPublishTrigger;
timer_create(CLOCK_REALTIME, &sigev, &writerGroupTimer);
/* Initialize the server */
server = UA_Server_new();
/* Add a PubSubConnection */ /* Add a PubSubConnection */
UA_PubSubConnectionConfig connectionConfig; UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(connectionConfig)); memset(&connectionConfig, 0, sizeof(connectionConfig));
connectionConfig.name = UA_STRING("UDP-UADP Connection 1"); connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); connectionConfig.transportProfileUri =
connectionConfig.eventLoop = pubSubEL; UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; UA_NetworkAddressUrlDataType networkAddressUrl =
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); {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.idType = UA_PUBLISHERIDTYPE_UINT16; connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16;
connectionConfig.publisherId.id.uint16 = 2234; connectionConfig.publisherId.id.uint16 = 2234;
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
@ -109,8 +154,6 @@ int main(void) {
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
/* TODO: Point to a variable in the information model */ /* TODO: Point to a variable in the information model */
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = true;
dsfConfig.field.variable.rtValueSource.staticValueSource = &dvPointers[i];
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent); UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent);
} }
@ -121,15 +164,17 @@ int main(void) {
writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS; writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS;
writerGroupConfig.writerGroupId = 100; writerGroupConfig.writerGroupId = 100;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; writerGroupConfig.customStateMachine = writerGroupStateMachine;
/* Change message settings of writerGroup to send PublisherId, WriterGroupId /* Change message settings of writerGroup to send PublisherId, WriterGroupId
* in GroupHeader and DataSetWriterId in PayloadHeader of NetworkMessage */ * in GroupHeader and DataSetWriterId in PayloadHeader of NetworkMessage */
UA_UadpWriterGroupMessageDataType writerGroupMessage; UA_UadpWriterGroupMessageDataType writerGroupMessage;
UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage); UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage);
writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask) writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask)
(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | (UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER |
UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage, UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage,
&UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]); &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]);
@ -163,17 +208,10 @@ int main(void) {
UA_Server_addRepeatedCallback(server, valueUpdateCallback, NULL, UA_Server_addRepeatedCallback(server, valueUpdateCallback, NULL,
PUBSUB_CONFIG_PUBLISH_CYCLE_MS, &callbackId); PUBSUB_CONFIG_PUBLISH_CYCLE_MS, &callbackId);
UA_StatusCode retval = UA_Server_runUntilInterrupt(server); UA_Server_runUntilInterrupt(server);
pubSubELRunning = false;
pthread_join(pubSubELThread, NULL);
pubSubEL->stop(pubSubEL);
while(pubSubEL->state != UA_EVENTLOOPSTATE_STOPPED)
pubSubEL->run(pubSubEL, 0);
pubSubEL->free(pubSubEL);
/* Cleanup */
UA_Server_delete(server); UA_Server_delete(server);
timer_delete(writerGroupTimer);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; return EXIT_SUCCESS;
} }

View File

@ -1,194 +0,0 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
#include <open62541/server.h>
#include <open62541/server_pubsub.h>
#include <open62541/plugin/log.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/server_config_default.h>
#include <stdlib.h>
#include <signal.h>
UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier;
UA_UInt32 *integerRTValue, *integerRTValue2;
UA_NodeId rtNodeId1, rtNodeId2;
/* Info: It is still possible to create a RT-PubSub configuration without an
* information model node. Just set the DSF flags to 'rtInformationModelNode' ->
* true and 'rtInformationModelNode' -> false and provide the PTR to your self
* managed value source. */
static UA_NodeId
addVariable(UA_Server *server, char *name) {
/* Define the attribute of the myInteger variable node */
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_UInt32 myInteger = 42;
UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_UINT32]);
attr.description = UA_LOCALIZEDTEXT("en-US", name);
attr.displayName = UA_LOCALIZEDTEXT("en-US", name);
attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
/* Add the variable node to the information model */
UA_NodeId outNodeId;
UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, name);
UA_NodeId parentNodeId = UA_NS0ID(OBJECTSFOLDER);
UA_NodeId parentReferenceNodeId = UA_NS0ID(ORGANIZES);
UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId, parentReferenceNodeId,
myIntegerName, UA_NS0ID(BASEDATAVARIABLETYPE),
attr, NULL, &outNodeId);
return outNodeId;
}
/* 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 *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeId,
void *nodeContext, const UA_NumericRange *range,
const UA_DataValue *data) {
/* It's possible to create a new DataValue here and use an atomic ptr switch
* to update the value without the need for locks e.g. UA_atomic_cmpxchg(); */
if(UA_NodeId_equal(nodeId, &rtNodeId1)){
memcpy(integerRTValue, data->value.data, sizeof(UA_UInt32));
} else if(UA_NodeId_equal(nodeId, &rtNodeId2)){
memcpy(integerRTValue2, data->value.data, sizeof(UA_UInt32));
}
return UA_STATUSCODE_GOOD;
}
static void
cyclicValueUpdateCallback_UpdateToMemory(UA_Server *server, void *data) {
*integerRTValue = (*integerRTValue)+1;
*integerRTValue2 = (*integerRTValue2)+1;
}
static void
cyclicValueUpdateCallback_UpdateToStack(UA_Server *server, void *data) {
UA_Variant valueToWrite;
UA_UInt32 newValue = (*integerRTValue)+10;
UA_Variant_setScalar(&valueToWrite, &newValue, &UA_TYPES[UA_TYPES_UINT32]);
UA_Server_writeValue(server, rtNodeId1, valueToWrite);
UA_Variant valueToWrite2;
UA_UInt32 newValue2 = (*integerRTValue2)+10;
UA_Variant_setScalar(&valueToWrite2, &newValue2, &UA_TYPES[UA_TYPES_UINT32]);
UA_Server_writeValue(server, rtNodeId2, valueToWrite2);
}
int main(void){
UA_Server *server = UA_Server_new();
/* 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");
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.idType = UA_PUBLISHERIDTYPE_UINT16;
connectionConfig.publisherId.id.uint16 = 2234;
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
/* 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_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent);
/* Add RT configuration */
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = 1000;
writerGroupConfig.writerGroupId = 100;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
UA_UadpWriterGroupMessageDataType writerGroupMessage;
UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage);
writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask)
(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage,
&UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]);
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent);
/* Add one DataSetWriter */
UA_NodeId dataSetWriterIdent;
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
dataSetWriterConfig.dataSetWriterId = 62541;
dataSetWriterConfig.keyFrameCount = 10;
/* Encode fields as RAW-Encoded */
dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA;
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
&dataSetWriterConfig, &dataSetWriterIdent);
/* Add new nodes */
rtNodeId1 = addVariable(server, "RT value source 1");
rtNodeId2 = addVariable(server, "RT value source 2");
/* Set the value backend to 'external value source' */
integerRTValue = UA_UInt32_new();
UA_DataValue *dataValueRT = UA_DataValue_new();
dataValueRT->hasValue = UA_TRUE;
UA_Variant_setScalar(&dataValueRT->value, integerRTValue, &UA_TYPES[UA_TYPES_UINT32]);
UA_ValueBackend valueBackend;
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend.backend.external.value = &dataValueRT;
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
UA_Server_setVariableNode_valueBackend(server, rtNodeId1, valueBackend);
/* Setup RT DataSetField config */
UA_NodeId dsfNodeId;
UA_DataSetFieldConfig dsfConfig;
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
dsfConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE;
dsfConfig.field.variable.publishParameters.publishedVariable = rtNodeId1;
dsfConfig.field.variable.fieldNameAlias = UA_STRING("Field 1");
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dsfNodeId);
/* Set the value backend of the above create node to 'external value source' */
integerRTValue2 = UA_UInt32_new();
*integerRTValue2 = 1000;
UA_DataValue *dataValue2RT = UA_DataValue_new();
dataValue2RT->hasValue = true;
UA_Variant_setScalar(&dataValue2RT->value, integerRTValue2, &UA_TYPES[UA_TYPES_UINT32]);
UA_ValueBackend valueBackend2;
valueBackend2.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend2.backend.external.value = &dataValue2RT;
valueBackend2.backend.external.callback.userWrite = externalDataWriteCallback;
UA_Server_setVariableNode_valueBackend(server, rtNodeId2, valueBackend2);
/* Setup second DataSetField config */
UA_DataSetFieldConfig dsfConfig2;
memset(&dsfConfig2, 0, sizeof(UA_DataSetFieldConfig));
dsfConfig2.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE;
dsfConfig2.field.variable.publishParameters.publishedVariable = rtNodeId2;
dsfConfig2.field.variable.fieldNameAlias = UA_STRING("Field 2");
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig2, NULL);
UA_Server_enableAllPubSubComponents(server);
UA_Server_addRepeatedCallback(server, cyclicValueUpdateCallback_UpdateToMemory,
NULL, 1000, NULL);
UA_Server_addRepeatedCallback(server, cyclicValueUpdateCallback_UpdateToStack,
NULL, 5000, NULL);
UA_StatusCode retval = UA_Server_runUntilInterrupt(server);
UA_Server_delete(server);
UA_DataValue_delete(dataValueRT);
UA_DataValue_delete(dataValue2RT);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@ -1,19 +1,19 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. /* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h> #include <open62541/server.h>
#include <open62541/server_pubsub.h> #include <open62541/server_pubsub.h>
#include <open62541/server_config_default.h> #include <open62541/server_config_default.h>
#include <open62541/types.h>
#include <open62541/types_generated.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define PUBSUB_CONFIG_FIELD_COUNT 10 #define PUBSUB_CONFIG_FIELD_COUNT 10
static UA_NetworkAddressUrlDataType networkAddressUrl =
{{0, NULL}, UA_STRING_STATIC("opc.udp://224.0.0.22:4840/")};
static UA_String transportProfile =
UA_STRING_STATIC("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
/** /**
* The main target of this example is to reduce the time spread and effort * The main target of this example is to reduce the time spread and effort
* during the subscribe cycle. This RT level example is based on buffered * during the subscribe cycle. This RT level example is based on buffered
@ -27,83 +27,9 @@
* the buffered NetworkMessage will only be updated. * the buffered NetworkMessage will only be updated.
*/ */
UA_NodeId connectionIdentifier; UA_Server *server;
UA_NodeId readerGroupIdentifier;
UA_NodeId readerIdentifier;
UA_DataSetReaderConfig readerConfig; UA_DataSetReaderConfig readerConfig;
UA_NodeId connectionIdentifier, readerGroupIdentifier, readerIdentifier;
/* Simulate a custom data sink (e.g. shared memory) */
UA_UInt32 repeatedFieldValues[PUBSUB_CONFIG_FIELD_COUNT];
UA_DataValue *repeatedDataValueRT[PUBSUB_CONFIG_FIELD_COUNT];
/* 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 *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeId,
void *nodeContext, const UA_NumericRange *range,
const UA_DataValue *data){
//node values are updated by using variables in the memory
//UA_Server_write is not used for updating node values.
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
externalDataReadNotificationCallback(UA_Server *server, 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;
}
static void
subscribeAfterWriteCallback(UA_Server *server, const UA_NodeId *dataSetReaderId,
const UA_NodeId *readerGroupId,
const UA_NodeId *targetVariableId,
void *targetVariableContext,
UA_DataValue **externalDataValue) {
(void) server;
(void) dataSetReaderId;
(void) readerGroupId;
(void) targetVariableContext;
assert(targetVariableId != 0);
assert(externalDataValue != 0);
UA_String strId;
UA_String_init(&strId);
UA_NodeId_print(targetVariableId, &strId);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "subscribeAfterWriteCallback(): "
"WriteUpdate() for node Id = '%.*s'. New Value = %u", (UA_Int32) strId.length, strId.data,
*((UA_UInt32*) (**externalDataValue).value.data));
UA_String_clear(&strId);
}
/* Callback gets triggered before subscriber has received data received data
* hasn't been copied/handled yet */
static void
subscribeBeforeWriteCallback(UA_Server *server, const UA_NodeId *dataSetReaderId,
const UA_NodeId *readerGroupId, const UA_NodeId *targetVariableId,
void *targetVariableContext, UA_DataValue **externalDataValue) {
(void) server;
(void) dataSetReaderId;
(void) readerGroupId;
(void) targetVariableContext;
assert(targetVariableId != 0);
assert(externalDataValue != 0);
UA_String strId;
UA_String_init(&strId);
UA_NodeId_print(targetVariableId, &strId);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
"subscribeBeforeWriteCallback(): "
"WriteUpdate() for node Id = '%.*s'",
(UA_Int32) strId.length, strId.data);
UA_String_clear(&strId);
}
/* Define MetaData for TargetVariables */ /* Define MetaData for TargetVariables */
static void static void
@ -133,14 +59,13 @@ fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData) {
/* Add new connection to the server */ /* Add new connection to the server */
static void static void
addPubSubConnection(UA_Server *server, UA_String *transportProfile, addPubSubConnection(UA_Server *server) {
UA_NetworkAddressUrlDataType *networkAddressUrl) {
/* Configuration creation for the connection */ /* Configuration creation for the connection */
UA_PubSubConnectionConfig connectionConfig; UA_PubSubConnectionConfig connectionConfig;
memset (&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); memset (&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig));
connectionConfig.name = UA_STRING("UDPMC Connection 1"); connectionConfig.name = UA_STRING("UDPMC Connection 1");
connectionConfig.transportProfileUri = *transportProfile; connectionConfig.transportProfileUri = transportProfile;
UA_Variant_setScalar(&connectionConfig.address, networkAddressUrl, UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT32; connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT32;
connectionConfig.publisherId.id.uint32 = UA_UInt32_random(); connectionConfig.publisherId.id.uint32 = UA_UInt32_random();
@ -153,7 +78,6 @@ addReaderGroup(UA_Server *server) {
UA_ReaderGroupConfig readerGroupConfig; UA_ReaderGroupConfig readerGroupConfig;
memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig));
readerGroupConfig.name = UA_STRING("ReaderGroup1"); readerGroupConfig.name = UA_STRING("ReaderGroup1");
readerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC;
UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig,
&readerGroupIdentifier); &readerGroupIdentifier);
} }
@ -185,12 +109,11 @@ addSubscribedVariables (UA_Server *server) {
/* Set the subscribed data to TargetVariable type */ /* Set the subscribed data to TargetVariable type */
readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET;
/* Create the TargetVariables with respect to DataSetMetaData fields */ /* Create the TargetVariables with respect to DataSetMetaData fields */
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = readerConfig.subscribedDataSet.target.targetVariablesSize =
readerConfig.dataSetMetaData.fieldsSize; readerConfig.dataSetMetaData.fieldsSize;
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = readerConfig.subscribedDataSet.target.targetVariables = (UA_FieldTargetDataType*)
(UA_FieldTargetVariable *)UA_calloc( UA_calloc(readerConfig.subscribedDataSet.target.targetVariablesSize,
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetDataType));
sizeof(UA_FieldTargetVariable));
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
/* Variable to subscribe data */ /* Variable to subscribe data */
UA_VariableAttributes vAttr = UA_VariableAttributes_default; UA_VariableAttributes vAttr = UA_VariableAttributes_default;
@ -209,33 +132,10 @@ addSubscribedVariables (UA_Server *server) {
UA_QUALIFIEDNAME(1, "Subscribed UInt32"), UA_QUALIFIEDNAME(1, "Subscribed UInt32"),
UA_NS0ID(BASEDATAVARIABLETYPE), UA_NS0ID(BASEDATAVARIABLETYPE),
vAttr, NULL, &newnodeId); vAttr, NULL, &newnodeId);
repeatedFieldValues[i] = 0;
repeatedDataValueRT[i] = UA_DataValue_new();
UA_Variant_setScalar(&repeatedDataValueRT[i]->value, &repeatedFieldValues[i],
&UA_TYPES[UA_TYPES_UINT32]);
repeatedDataValueRT[i]->value.storageType = UA_VARIANT_DATA_NODELETE;
repeatedDataValueRT[i]->hasValue = true;
/* Set the value backend of the above create node to 'external value source' */ UA_FieldTargetDataType *tv = &readerConfig.subscribedDataSet.target.targetVariables[i];
UA_ValueBackend valueBackend; tv->attributeId = UA_ATTRIBUTEID_VALUE;
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; tv->targetNodeId = newnodeId;
valueBackend.backend.external.value = &repeatedDataValueRT[i];
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
UA_Server_setVariableNode_valueBackend(server, newnodeId, valueBackend);
UA_FieldTargetVariable *tv =
&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i];
UA_FieldTargetDataType *ftdt = &tv->targetVariable;
/* For creating Targetvariables */
UA_FieldTargetDataType_init(ftdt);
ftdt->attributeId = UA_ATTRIBUTEID_VALUE;
ftdt->targetNodeId = newnodeId;
/* set both before and after write callback to show the usage */
tv->beforeWrite = subscribeBeforeWriteCallback;
tv->externalDataValue = &repeatedDataValueRT[i];
tv->afterWrite = subscribeAfterWriteCallback;
} }
} }
@ -273,43 +173,12 @@ addDataSetReader(UA_Server *server) {
addSubscribedVariables(server); addSubscribedVariables(server);
UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, &readerIdentifier); UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, &readerIdentifier);
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { UA_DataSetReaderConfig_clear(&readerConfig);
UA_FieldTargetVariable *tv =
&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i];
UA_FieldTargetDataType *ftdt = &tv->targetVariable;
UA_FieldTargetDataType_clear(ftdt);
}
UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables);
UA_free(readerConfig.dataSetMetaData.fields); UA_free(readerConfig.dataSetMetaData.fields);
UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage);
} }
static int
run(UA_String *transportProfile, UA_NetworkAddressUrlDataType *networkAddressUrl) {
/* Return value initialized to Status Good */
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_Server *server = UA_Server_new();
addPubSubConnection(server, transportProfile, networkAddressUrl);
addReaderGroup(server);
addDataSetReader(server);
UA_Server_enableAllPubSubComponents(server);
retval = UA_Server_runUntilInterrupt(server);
UA_Server_delete(server);
for(UA_Int32 i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
UA_DataValue_delete(repeatedDataValueRT[i]);
}
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
UA_String transportProfile = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")};
if(argc > 1) { if(argc > 1) {
if(strcmp(argv[1], "-h") == 0) { if(strcmp(argv[1], "-h") == 0) {
printf("usage: %s <uri> [device]\n", argv[0]); printf("usage: %s <uri> [device]\n", argv[0]);
@ -330,9 +199,32 @@ int main(int argc, char **argv) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
} }
if (argc > 2) { if(argc > 2)
networkAddressUrl.networkInterface = UA_STRING(argv[2]); networkAddressUrl.networkInterface = UA_STRING(argv[2]);
/* Return value initialized to Status Good */
UA_StatusCode retval = UA_STATUSCODE_GOOD;
server = UA_Server_new();
addPubSubConnection(server);
addReaderGroup(server);
addDataSetReader(server);
/* Print the Offset Table */
UA_PubSubOffsetTable ot;
UA_Server_computeReaderGroupOffsetTable(server, readerGroupIdentifier, &ot);
for(size_t i = 0; i < ot.offsetsSize; i++) {
UA_String out = UA_STRING_NULL;
UA_NodeId_print(&ot.offsets[i].component, &out);
printf("%u:\tOffset %u\tOffsetType %u\tComponent %.*s\n",
(unsigned)i, (unsigned)ot.offsets[i].offset,
(unsigned)ot.offsets[i].offsetType,
(int)out.length, out.data);
UA_String_clear(&out);
} }
return run(&transportProfile, &networkAddressUrl); UA_Server_delete(server);
UA_PubSubOffsetTable_clear(&ot);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
} }

View File

@ -0,0 +1,399 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h>
#include <open62541/server_pubsub.h>
#include <open62541/server_config_default.h>
#include <open62541/types.h>
#include <open62541/types_generated.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <poll.h>
#include <unistd.h>
#include <fcntl.h>
#define PUBSUB_CONFIG_FIELD_COUNT 10
static UA_NetworkAddressUrlDataType networkAddressUrl =
{{0, NULL}, UA_STRING_STATIC("opc.udp://224.0.0.22:4840/")};
static UA_String transportProfile =
UA_STRING_STATIC("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
/**
* The main target of this example is to reduce the time spread and effort
* during the subscribe cycle. This RT level example is based on buffered
* DataSetMessages and NetworkMessages. Since changes in the
* PubSub-configuration will invalidate the buffered frames, the PubSub
* configuration must be frozen after the configuration phase.
*
* After enabling the subscriber (and when the first message is received), the
* NetworkMessages and DataSetMessages will be calculated and buffered. During
* the subscribe cycle, decoding will happen only to the necessary offsets and
* the buffered NetworkMessage will only be updated.
*/
UA_NodeId connectionIdentifier;
UA_NodeId readerGroupIdentifier;
UA_NodeId readerIdentifier;
UA_Server *server;
pthread_t listenThread;
int listenSocket;
UA_DataSetReaderConfig readerConfig;
static void *
listenUDP(void *_) {
(void)_;
/* Block SIGINT for correct shutdown via the main thread */
sigset_t blockset;
sigemptyset(&blockset);
sigaddset(&blockset, SIGINT);
sigprocmask(SIG_BLOCK, &blockset, NULL);
/* Extract the hostname */
UA_UInt16 port = 0;
UA_String hostname = UA_STRING_NULL;
UA_parseEndpointUrl(&networkAddressUrl.url, &hostname, &port, NULL);
/* Get all the interface and IPv4/6 combinations for the configured hostname */
struct addrinfo hints, *info;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; /* Allow IPv4 and IPv6 */
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_PASSIVE;
/* getaddrinfo */
char portstr[6];
char hostnamebuf[256];
snprintf(portstr, 6, "%d", port);
memcpy(hostnamebuf, hostname.data, hostname.length);
hostnamebuf[hostname.length] = 0;
int result = getaddrinfo(hostnamebuf, portstr, &hints, &info);
if(result != 0) {
printf("XXX getaddrinfo failed\n");
return NULL;
}
/* Open the socket */
listenSocket = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
if(listenSocket <= 0) {
printf("XXX Cannot create the socket\n");
return NULL;
}
/* Set socket options */
int opts = fcntl(listenSocket, F_GETFL);
result |= fcntl(listenSocket, F_SETFL, opts | O_NONBLOCK);
int optval = 1;
result |= setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR,
(const char*)&optval, sizeof(optval));
if(result < 0) {
printf("XXX Cannot set the socket options\n");
return NULL;
}
/* Bind the socket */
result = bind(listenSocket, info->ai_addr, (socklen_t)info->ai_addrlen);
if(result < 0) {
printf("XXX Cannot bind the socket\n");
return NULL;
}
/* Join the multicast group */
if(info->ai_family == AF_INET) {
struct ip_mreqn ipv4;
struct sockaddr_in *sin = (struct sockaddr_in *)info->ai_addr;
ipv4.imr_multiaddr = sin->sin_addr;
ipv4.imr_address.s_addr = htonl(INADDR_ANY); /* default ANY */
ipv4.imr_ifindex = 0;
result = setsockopt(listenSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &ipv4, sizeof(ipv4));
} else if(info->ai_family == AF_INET6) {
struct ipv6_mreq ipv6;
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)info->ai_addr;
ipv6.ipv6mr_multiaddr = sin6->sin6_addr;
ipv6.ipv6mr_interface = 0; /* default ANY interface */
result = setsockopt(listenSocket, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6, sizeof(ipv6));
}
if(result < 0) {
printf("XXX Cannot join the multicast group\n");
return NULL;
}
freeaddrinfo(info);
/* The connection is open, change the state to OPERATIONAL.
* The state machine checks whether listenSocket != 0. */
printf("XXX Listening on UDP multicast (%s, port %u)\n",
hostnamebuf, (unsigned)port);
UA_Server_enablePubSubConnection(server, connectionIdentifier);
/* Poll and process in a loop.
* The socket is closed in the state machine and */
struct pollfd pfd;
pfd.fd = listenSocket;
pfd.events = POLLIN;
while(true) {
result = poll(&pfd, 1, -1); /* infinite timeout */
if(pfd.revents & POLLERR || pfd.revents & POLLHUP || pfd.revents & POLLNVAL)
break;
if(pfd.revents & POLLIN) {
static char buf[1024];
ssize_t size = read(listenSocket, buf, sizeof(buf));
if(size > 0) {
printf("XXX Received a packet\n");
UA_ByteString packet = {(size_t)size, (UA_Byte*)buf};
UA_Server_processPubSubConnectionReceive(server, connectionIdentifier, packet);
}
}
}
printf("XXX The UDP multicast connection is closed\n");
/* Clean up and notify the state machine */
close(listenSocket);
listenSocket = 0;
UA_Server_disablePubSubConnection(server, connectionIdentifier);
return NULL;
}
static UA_StatusCode
connectionStateMachine(UA_Server *server, const UA_NodeId componentId,
void *componentContext, UA_PubSubState *state,
UA_PubSubState targetState) {
if(targetState == *state)
return UA_STATUSCODE_GOOD;
switch(targetState) {
/* Disabled or Error */
case UA_PUBSUBSTATE_ERROR:
case UA_PUBSUBSTATE_DISABLED:
case UA_PUBSUBSTATE_PAUSED:
printf("XXX Closing the UDP multicast connection\n");
if(listenSocket != 0)
shutdown(listenSocket, SHUT_RDWR);
*state = targetState;
break;
/* Operational */
case UA_PUBSUBSTATE_PREOPERATIONAL:
case UA_PUBSUBSTATE_OPERATIONAL:
if(listenSocket != 0) {
*state = UA_PUBSUBSTATE_OPERATIONAL;
break;
}
printf("XXX Opening the UDP multicast connection\n");
*state = UA_PUBSUBSTATE_PREOPERATIONAL;
int res = pthread_create(&listenThread, NULL, listenUDP, NULL);
if(res != 0)
return UA_STATUSCODE_BADINTERNALERROR;
break;
/* Unknown state */
default:
return UA_STATUSCODE_BADINTERNALERROR;
}
return UA_STATUSCODE_GOOD;
}
/* Define MetaData for TargetVariables */
static void
fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData) {
if(pMetaData == NULL)
return;
UA_DataSetMetaDataType_init (pMetaData);
pMetaData->name = UA_STRING ("DataSet 1");
/* Static definition of number of fields size to PUBSUB_CONFIG_FIELD_COUNT
* to create targetVariables */
pMetaData->fieldsSize = PUBSUB_CONFIG_FIELD_COUNT;
pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize,
&UA_TYPES[UA_TYPES_FIELDMETADATA]);
for(size_t i = 0; i < pMetaData->fieldsSize; i++) {
/* UInt32 DataType */
UA_FieldMetaData_init (&pMetaData->fields[i]);
UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId,
&pMetaData->fields[i].dataType);
pMetaData->fields[i].builtInType = UA_NS0ID_UINT32;
pMetaData->fields[i].name = UA_STRING ("UInt32 varibale");
pMetaData->fields[i].valueRank = -1; /* scalar */
}
}
/* Add new connection to the server */
static void
addPubSubConnection(UA_Server *server) {
/* Configuration creation for the connection */
UA_PubSubConnectionConfig connectionConfig;
memset (&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig));
connectionConfig.name = UA_STRING("UDPMC Connection 1");
connectionConfig.transportProfileUri = transportProfile;
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT32;
connectionConfig.publisherId.id.uint32 = UA_UInt32_random();
connectionConfig.customStateMachine = connectionStateMachine;
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
}
/* Add ReaderGroup to the created connection */
static void
addReaderGroup(UA_Server *server) {
UA_ReaderGroupConfig readerGroupConfig;
memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig));
readerGroupConfig.name = UA_STRING("ReaderGroup1");
UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig,
&readerGroupIdentifier);
}
/* Set SubscribedDataSet type to TargetVariables data type
* Add subscribedvariables to the DataSetReader */
static void
addSubscribedVariables (UA_Server *server) {
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_NS0ID(OBJECTSFOLDER),
UA_NS0ID(ORGANIZES), folderBrowseName,
UA_NS0ID(BASEOBJECTTYPE), oAttr,
NULL, &folderId);
/* Set the subscribed data to TargetVariable type */
readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET;
/* Create the TargetVariables with respect to DataSetMetaData fields */
readerConfig.subscribedDataSet.target.targetVariablesSize =
readerConfig.dataSetMetaData.fieldsSize;
readerConfig.subscribedDataSet.target.targetVariables = (UA_FieldTargetDataType*)
UA_calloc(readerConfig.subscribedDataSet.target.targetVariablesSize,
sizeof(UA_FieldTargetDataType));
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
/* 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;
// Initialize the values at first to create the buffered NetworkMessage
// with correct size and offsets
UA_Variant value;
UA_Variant_init(&value);
UA_UInt32 intValue = 0;
UA_Variant_setScalar(&value, &intValue, &UA_TYPES[UA_TYPES_UINT32]);
vAttr.value = value;
UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, (UA_UInt32)i + 50000),
folderId, UA_NS0ID(HASCOMPONENT),
UA_QUALIFIEDNAME(1, "Subscribed UInt32"),
UA_NS0ID(BASEDATAVARIABLETYPE),
vAttr, NULL, &newnodeId);
UA_FieldTargetDataType *tv =
&readerConfig.subscribedDataSet.target.targetVariables[i];
tv->attributeId = UA_ATTRIBUTEID_VALUE;
tv->targetNodeId = newnodeId;
}
}
/* Add DataSetReader to the ReaderGroup */
static void
addDataSetReader(UA_Server *server) {
memset(&readerConfig, 0, sizeof(UA_DataSetReaderConfig));
readerConfig.name = UA_STRING("DataSet Reader 1");
/* Parameters to filter which DataSetMessage has to be processed
* by the DataSetReader */
/* The following parameters are used to show that the data published by
* tutorial_pubsub_publish.c is being subscribed and is being updated in
* the information model */
UA_UInt16 publisherIdentifier = 2234;
readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16;
readerConfig.publisherId.id.uint16 = publisherIdentifier;
readerConfig.writerGroupId = 100;
readerConfig.dataSetWriterId = 62541;
readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
readerConfig.expectedEncoding = UA_PUBSUB_RT_RAW;
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_GROUPHEADER |
UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER | UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
dataSetReaderMessage->dataSetMessageContentMask = UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER;
readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage;
readerConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA;
/* Setting up Meta data configuration in DataSetReader */
fillTestDataSetMetaData(&readerConfig.dataSetMetaData);
addSubscribedVariables(server);
UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, &readerIdentifier);
UA_DataSetReaderConfig_clear(&readerConfig);
UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage);
}
int main(int argc, char **argv) {
if(argc > 1) {
if(strcmp(argv[1], "-h") == 0) {
printf("usage: %s <uri> [device]\n", argv[0]);
return EXIT_SUCCESS;
} else if(strncmp(argv[1], "opc.udp://", 10) == 0) {
networkAddressUrl.url = UA_STRING(argv[1]);
} else if(strncmp(argv[1], "opc.eth://", 10) == 0) {
transportProfile =
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
if(argc < 3) {
printf("Error: UADP/ETH needs an interface name\n");
return EXIT_FAILURE;
}
networkAddressUrl.networkInterface = UA_STRING(argv[2]);
networkAddressUrl.url = UA_STRING(argv[1]);
} else {
printf ("Error: unknown URI\n");
return EXIT_FAILURE;
}
}
if(argc > 2)
networkAddressUrl.networkInterface = UA_STRING(argv[2]);
/* Return value initialized to Status Good */
UA_StatusCode retval = UA_STATUSCODE_GOOD;
server = UA_Server_new();
addPubSubConnection(server);
addReaderGroup(server);
addDataSetReader(server);
UA_Server_enableAllPubSubComponents(server);
retval = UA_Server_runUntilInterrupt(server);
UA_Server_delete(server);
pthread_join(listenThread, NULL);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@ -145,6 +145,32 @@ struct UA_ClientConfig {
* data types are provided in ``/examples/custom_datatype/``. */ * data types are provided in ``/examples/custom_datatype/``. */
const UA_DataTypeArray *customDataTypes; const UA_DataTypeArray *customDataTypes;
/**
* Namespace Mapping
* ~~~~~~~~~~~~~~~~~
* The namespaces index is "just" a mapping to the Uris in the namespace
* array of the server. In order to have stable NodeIds across servers, the
* client keeps a list of predefined namespaces. Use
* ``UA_Client_addNamespaceUri``, ``UA_Client_getNamespaceUri`` and
* ``UA_Client_getNamespaceIndex`` to interact with the local namespace
* mapping.
*
* The namespace indices are assigned internally in the client as follows:
*
* - Ns0 and Ns1 are pre-defined by the standard. Ns0 is always
* ```http://opcfoundation.org/UA/``` and used for standard-defined
* NodeIds. Ns1 corresponds to the application uri of the individual
* server.
* - The next namespaces are added in-order from the list below at startup
* (starting at index 2).
* - The local API ``UA_Client_addNamespaceUri`` can be used to add more
* namespaces.
* - When the client connects, the namespace array of the server is read.
* All previously unknown namespaces are added from this to the internal
* array of the client. */
UA_String *namespaces;
size_t namespacesSize;
/** /**
* Advanced Client Configuration * Advanced Client Configuration
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -953,6 +979,20 @@ UA_Client_removeCallback(UA_Client *client, UA_UInt64 callbackId);
UA_EXPORT const UA_DataType * UA_EXPORT const UA_DataType *
UA_Client_findDataType(UA_Client *client, const UA_NodeId *typeId); UA_Client_findDataType(UA_Client *client, const UA_NodeId *typeId);
/* The string is allocated and needs to be cleared */
UA_EXPORT UA_StatusCode UA_THREADSAFE
UA_Client_getNamespaceUri(UA_Client *client, UA_UInt16 index,
UA_String *nsUri);
UA_EXPORT UA_StatusCode UA_THREADSAFE
UA_Client_getNamespaceIndex(UA_Client *client, const UA_String nsUri,
UA_UInt16 *outIndex);
/* Returns the old index of the namespace already exists */
UA_EXPORT UA_StatusCode UA_THREADSAFE
UA_Client_addNamespace(UA_Client *client, const UA_String nsUri,
UA_UInt16 *outIndex);
/** /**
* .. toctree:: * .. toctree::
* *

View File

@ -53,6 +53,7 @@
#cmakedefine UA_ENABLE_PARSING #cmakedefine UA_ENABLE_PARSING
#cmakedefine UA_ENABLE_SUBSCRIPTIONS_EVENTS #cmakedefine UA_ENABLE_SUBSCRIPTIONS_EVENTS
#cmakedefine UA_ENABLE_JSON_ENCODING #cmakedefine UA_ENABLE_JSON_ENCODING
#cmakedefine UA_ENABLE_JSON_ENCODING_LEGACY
#cmakedefine UA_ENABLE_XML_ENCODING #cmakedefine UA_ENABLE_XML_ENCODING
#cmakedefine UA_ENABLE_MQTT #cmakedefine UA_ENABLE_MQTT
#cmakedefine UA_ENABLE_NODESET_INJECTOR #cmakedefine UA_ENABLE_NODESET_INJECTOR
@ -77,7 +78,11 @@
#cmakedefine UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS #cmakedefine UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS
#cmakedefine UA_ENABLE_DETERMINISTIC_RNG #cmakedefine UA_ENABLE_DETERMINISTIC_RNG
#cmakedefine UA_ENABLE_DISCOVERY #cmakedefine UA_ENABLE_DISCOVERY
#cmakedefine UA_ENABLE_DISCOVERY_MULTICAST #cmakedefine UA_ENABLE_DISCOVERY_MULTICAST_MDNSD
#cmakedefine UA_ENABLE_DISCOVERY_MULTICAST_AVAHI
#if defined(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD) || defined(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
#define UA_ENABLE_DISCOVERY_MULTICAST
#endif
#cmakedefine UA_ENABLE_QUERY #cmakedefine UA_ENABLE_QUERY
#cmakedefine UA_ENABLE_MALLOC_SINGLETON #cmakedefine UA_ENABLE_MALLOC_SINGLETON
#cmakedefine UA_ENABLE_DISCOVERY_SEMAPHORE #cmakedefine UA_ENABLE_DISCOVERY_SEMAPHORE

View File

@ -63,7 +63,8 @@ struct UA_CertificateGroup {
UA_EXPORT UA_StatusCode UA_EXPORT UA_StatusCode
UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling,
const UA_ByteString *certificate, const UA_ByteString *certificate,
const UA_String *applicationURI); const UA_String *applicationURI,
UA_Logger *logger);
/* Get the expire date from certificate */ /* Get the expire date from certificate */
UA_EXPORT UA_StatusCode UA_EXPORT UA_StatusCode

View File

@ -43,15 +43,16 @@ typedef enum {
typedef enum { typedef enum {
UA_LOGCATEGORY_NETWORK = 0, UA_LOGCATEGORY_NETWORK = 0,
UA_LOGCATEGORY_SECURECHANNEL, UA_LOGCATEGORY_SECURECHANNEL = 1,
UA_LOGCATEGORY_SESSION, UA_LOGCATEGORY_SESSION = 2,
UA_LOGCATEGORY_SERVER, UA_LOGCATEGORY_SERVER = 3,
UA_LOGCATEGORY_CLIENT, UA_LOGCATEGORY_CLIENT = 4,
UA_LOGCATEGORY_USERLAND, UA_LOGCATEGORY_USERLAND = 5,
UA_LOGCATEGORY_SECURITYPOLICY, UA_LOGCATEGORY_SECURITYPOLICY = 6,
UA_LOGCATEGORY_EVENTLOOP, UA_LOGCATEGORY_SECURITY = 6, /* == SECURITYPOLICY */
UA_LOGCATEGORY_PUBSUB, UA_LOGCATEGORY_EVENTLOOP = 7,
UA_LOGCATEGORY_DISCOVERY UA_LOGCATEGORY_PUBSUB = 8,
UA_LOGCATEGORY_DISCOVERY = 9
} UA_LogCategory; } UA_LogCategory;
typedef struct UA_Logger { typedef struct UA_Logger {

View File

@ -29,11 +29,6 @@ _UA_BEGIN_DECLS
* DataSet Message * DataSet Message
* ^^^^^^^^^^^^^^^ */ * ^^^^^^^^^^^^^^^ */
typedef struct {
UA_Byte count;
UA_UInt16* dataSetWriterIds;
} UA_DataSetPayloadHeader;
typedef enum { typedef enum {
UA_FIELDENCODING_VARIANT = 0, UA_FIELDENCODING_VARIANT = 0,
UA_FIELDENCODING_RAWDATA = 1, UA_FIELDENCODING_RAWDATA = 1,
@ -42,28 +37,42 @@ typedef enum {
} UA_FieldEncoding; } UA_FieldEncoding;
typedef enum { typedef enum {
UA_DATASETMESSAGE_DATAKEYFRAME = 0, UA_DATASETMESSAGETYPE_DATAKEYFRAME = 0,
UA_DATASETMESSAGE_DATADELTAFRAME = 1, UA_DATASETMESSAGE_DATAKEYFRAME = 0,
UA_DATASETMESSAGE_EVENT = 2, UA_DATASETMESSAGETYPE_DATADELTAFRAME = 1,
UA_DATASETMESSAGE_KEEPALIVE = 3 UA_DATASETMESSAGE_DATADELTAFRAME = 1,
UA_DATASETMESSAGETYPE_EVENT = 2,
UA_DATASETMESSAGE_EVENT = 2,
UA_DATASETMESSAGETYPE_KEEPALIVE = 3,
UA_DATASETMESSAGE_KEEPALIVE = 3
} UA_DataSetMessageType; } UA_DataSetMessageType;
typedef struct { typedef struct {
/* Settings and message fields enabled with the DataSetFlags1 */
UA_Boolean dataSetMessageValid; UA_Boolean dataSetMessageValid;
UA_FieldEncoding fieldEncoding; UA_FieldEncoding fieldEncoding;
UA_Boolean dataSetMessageSequenceNrEnabled; UA_Boolean dataSetMessageSequenceNrEnabled;
UA_Boolean timestampEnabled;
UA_Boolean statusEnabled;
UA_Boolean configVersionMajorVersionEnabled;
UA_Boolean configVersionMinorVersionEnabled;
UA_DataSetMessageType dataSetMessageType;
UA_Boolean picoSecondsIncluded;
UA_UInt16 dataSetMessageSequenceNr; UA_UInt16 dataSetMessageSequenceNr;
UA_UtcTime timestamp;
UA_UInt16 picoSeconds; UA_Boolean statusEnabled;
UA_UInt16 status; UA_UInt16 status;
UA_Boolean configVersionMajorVersionEnabled;
UA_UInt32 configVersionMajorVersion; UA_UInt32 configVersionMajorVersion;
UA_Boolean configVersionMinorVersionEnabled;
UA_UInt32 configVersionMinorVersion; UA_UInt32 configVersionMinorVersion;
/* Settings and message fields enabled with the DataSetFlags2 */
UA_DataSetMessageType dataSetMessageType;
UA_Boolean timestampEnabled;
UA_UtcTime timestamp;
UA_Boolean picoSecondsIncluded;
UA_UInt16 picoSeconds;
} UA_DataSetMessageHeader; } UA_DataSetMessageHeader;
typedef struct { typedef struct {
@ -87,6 +96,8 @@ typedef struct {
} UA_DataSetMessage_DataDeltaFrameData; } UA_DataSetMessage_DataDeltaFrameData;
typedef struct { typedef struct {
UA_UInt16 dataSetWriterId; /* Goes into the payload header */
UA_DataSetMessageHeader header; UA_DataSetMessageHeader header;
union { union {
UA_DataSetMessage_DataKeyFrameData keyFrameData; UA_DataSetMessage_DataKeyFrameData keyFrameData;
@ -105,19 +116,17 @@ typedef enum {
UA_NETWORKMESSAGE_DISCOVERY_RESPONSE = 2 UA_NETWORKMESSAGE_DISCOVERY_RESPONSE = 2
} UA_NetworkMessageType; } UA_NetworkMessageType;
typedef struct {
UA_UInt16* sizes;
UA_DataSetMessage* dataSetMessages;
} UA_DataSetPayload;
typedef struct { typedef struct {
UA_Boolean writerGroupIdEnabled; UA_Boolean writerGroupIdEnabled;
UA_Boolean groupVersionEnabled;
UA_Boolean networkMessageNumberEnabled;
UA_Boolean sequenceNumberEnabled;
UA_UInt16 writerGroupId; UA_UInt16 writerGroupId;
UA_Boolean groupVersionEnabled;
UA_UInt32 groupVersion; UA_UInt32 groupVersion;
UA_Boolean networkMessageNumberEnabled;
UA_UInt16 networkMessageNumber; UA_UInt16 networkMessageNumber;
UA_Boolean sequenceNumberEnabled;
UA_UInt16 sequenceNumber; UA_UInt16 sequenceNumber;
} UA_NetworkMessageGroupHeader; } UA_NetworkMessageGroupHeader;
@ -125,47 +134,64 @@ typedef struct {
typedef struct { typedef struct {
UA_Boolean networkMessageSigned; UA_Boolean networkMessageSigned;
UA_Boolean networkMessageEncrypted; UA_Boolean networkMessageEncrypted;
UA_Boolean securityFooterEnabled; UA_Boolean securityFooterEnabled;
UA_Boolean forceKeyReset;
UA_UInt32 securityTokenId;
UA_Byte messageNonce[UA_NETWORKMESSAGE_MAX_NONCE_LENGTH];
UA_UInt16 messageNonceSize;
UA_UInt16 securityFooterSize; UA_UInt16 securityFooterSize;
UA_Boolean forceKeyReset;
UA_UInt32 securityTokenId;
UA_UInt16 messageNonceSize;
UA_Byte messageNonce[UA_NETWORKMESSAGE_MAX_NONCE_LENGTH];
} UA_NetworkMessageSecurityHeader; } UA_NetworkMessageSecurityHeader;
typedef struct { typedef struct {
UA_Byte version; UA_Byte version;
UA_Boolean messageIdEnabled;
UA_String messageId; /* For Json NetworkMessage */
UA_Boolean publisherIdEnabled;
UA_Boolean groupHeaderEnabled;
UA_Boolean payloadHeaderEnabled;
UA_Boolean dataSetClassIdEnabled;
UA_Boolean securityEnabled;
UA_Boolean timestampEnabled;
UA_Boolean picosecondsEnabled;
UA_Boolean chunkMessage;
UA_Boolean promotedFieldsEnabled;
UA_NetworkMessageType networkMessageType;
UA_PublisherId publisherId;
UA_Guid dataSetClassId;
/* Fields defined via the UADPFlags */
UA_Boolean publisherIdEnabled;
UA_PublisherId publisherId;
UA_Boolean groupHeaderEnabled;
UA_NetworkMessageGroupHeader groupHeader; UA_NetworkMessageGroupHeader groupHeader;
union { UA_Boolean payloadHeaderEnabled;
UA_DataSetPayloadHeader dataSetPayloadHeader;
} payloadHeader;
UA_DateTime timestamp; /* Fields defined via the Extended1Flags */
UA_UInt16 picoseconds; UA_Boolean dataSetClassIdEnabled;
UA_UInt16 promotedFieldsSize; UA_Guid dataSetClassId;
UA_Variant* promotedFields; /* BaseDataType */
UA_Boolean securityEnabled;
UA_NetworkMessageSecurityHeader securityHeader; UA_NetworkMessageSecurityHeader securityHeader;
UA_Boolean timestampEnabled;
UA_DateTime timestamp;
UA_Boolean picosecondsEnabled;
UA_UInt16 picoseconds;
/* Fields defined via the Extended2Flags */
UA_Boolean chunkMessage;
UA_Boolean promotedFieldsEnabled;
UA_UInt16 promotedFieldsSize;
UA_Variant *promotedFields; /* BaseDataType */
UA_NetworkMessageType networkMessageType;
/* For Json NetworkMessage */
UA_Boolean messageIdEnabled;
UA_String messageId;
union { union {
UA_DataSetPayload dataSetPayload; struct {
UA_DataSetMessage *dataSetMessages;
size_t dataSetMessagesSize; /* Goes into the payload header */
} dataSetPayload;
/* Extended with other payload types in the future */
} payload; } payload;
UA_ByteString securityFooter; UA_ByteString securityFooter;

View File

@ -288,10 +288,12 @@ struct UA_ServerConfig {
# ifdef UA_ENABLE_DISCOVERY_MULTICAST # ifdef UA_ENABLE_DISCOVERY_MULTICAST
UA_Boolean mdnsEnabled; UA_Boolean mdnsEnabled;
UA_MdnsDiscoveryConfiguration mdnsConfig; UA_MdnsDiscoveryConfiguration mdnsConfig;
# ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD
UA_String mdnsInterfaceIP; UA_String mdnsInterfaceIP;
# if !defined(UA_HAS_GETIFADDR) # if !defined(UA_HAS_GETIFADDR)
size_t mdnsIpAddressListSize; size_t mdnsIpAddressListSize;
UA_UInt32 *mdnsIpAddressList; UA_UInt32 *mdnsIpAddressList;
# endif
# endif # endif
# endif # endif
#endif #endif

View File

@ -181,6 +181,30 @@ typedef struct {
UA_PubSubSecurityPolicy *securityPolicies; UA_PubSubSecurityPolicy *securityPolicies;
} UA_PubSubConfiguration; } UA_PubSubConfiguration;
/**
* PubSub Components
* -----------------
* All PubSubComponents (Connection, Reader, ReaderGroup, ...) have a two
* configuration items in common: A void context-pointer and a callback to
* override the default state machine with a custom implementation.
*
* When a custom state machine is set, then internally no sockets are opened and
* no periodic callbacks are registered. All "active behavior" has to be
* managed/configured entirely in the custom state machine. */
/* The custom state machine callback is optional (can be NULL). It gets called
* with a request to change the state targetState. The state pointer contains
* the old (and afterwards the new) state. The notification stateChangeCallback
* is called afterwards. When a bad statuscode is returned, the component must
* be set to an ERROR state. */
#define UA_PUBSUB_COMPONENT_CONTEXT \
void *context; \
UA_StatusCode (*customStateMachine)(UA_Server *server, \
const UA_NodeId componentId, \
void *componentContext, \
UA_PubSubState *state, \
UA_PubSubState targetState); \
/* Enable all PubSubComponents. Returns the ORed statuscodes for enabling each /* Enable all PubSubComponents. Returns the ORed statuscodes for enabling each
* component individually. */ * component individually. */
UA_EXPORT UA_StatusCode UA_EXPORT UA_StatusCode
@ -199,6 +223,7 @@ UA_Server_disableAllPubSubComponents(UA_Server *server);
* runtime. */ * runtime. */
typedef struct { typedef struct {
/* Configuration parameters from PubSubConnectionDataType */
UA_String name; UA_String name;
UA_PublisherId publisherId; UA_PublisherId publisherId;
UA_String transportProfileUri; UA_String transportProfileUri;
@ -206,10 +231,7 @@ typedef struct {
UA_KeyValueMap connectionProperties; UA_KeyValueMap connectionProperties;
UA_Variant connectionTransportSettings; UA_Variant connectionTransportSettings;
UA_EventLoop *eventLoop; /* Use an external EventLoop (use the EventLoop of UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */
* the server if this is NULL). Propagates to the
* ReaderGroup/WriterGroup attached to the
* Connection. */
} UA_PubSubConnectionConfig; } UA_PubSubConnectionConfig;
/* Add a new PubSub connection to the given server and open it. /* Add a new PubSub connection to the given server and open it.
@ -232,6 +254,14 @@ UA_EXPORT UA_StatusCode UA_THREADSAFE
UA_Server_disablePubSubConnection(UA_Server *server, UA_Server_disablePubSubConnection(UA_Server *server,
const UA_NodeId connectionId); const UA_NodeId connectionId);
/* Manually "inject" a packet as if it had been received by the
* PubSubConnection. This is intended to be used in combination with a custom
* state machine where sockets (connections) are handled by user code. */
UA_EXPORT UA_StatusCode UA_THREADSAFE
UA_Server_processPubSubConnectionReceive(UA_Server *server,
const UA_NodeId connectionId,
const UA_ByteString packet);
/* Returns a deep copy of the config */ /* Returns a deep copy of the config */
UA_StatusCode UA_EXPORT UA_THREADSAFE UA_StatusCode UA_EXPORT UA_THREADSAFE
UA_Server_getPubSubConnectionConfig(UA_Server *server, UA_Server_getPubSubConnectionConfig(UA_Server *server,
@ -291,6 +321,9 @@ typedef struct {
UA_PublishedEventConfig event; UA_PublishedEventConfig event;
UA_PublishedEventTemplateConfig eventTemplate; UA_PublishedEventTemplateConfig eventTemplate;
} config; } config;
void *context; /* Context Configuration (PublishedDataSet has no state
* machine) */
} UA_PublishedDataSetConfig; } UA_PublishedDataSetConfig;
void UA_EXPORT void UA_EXPORT
@ -338,20 +371,10 @@ typedef struct {
UA_Boolean promotedField; UA_Boolean promotedField;
UA_PublishedVariableDataType publishParameters; UA_PublishedVariableDataType publishParameters;
/* non std. field */
struct {
UA_Boolean rtFieldSourceEnabled;
/* If the rtInformationModelNode is set, the nodeid in publishParameter
* must point to a node with external data source backend defined */
UA_Boolean rtInformationModelNode;
/* TODO: Decide if suppress C++ warnings and use 'UA_DataValue * * const
* staticValueSource;' */
UA_DataValue ** staticValueSource;
} rtValueSource;
UA_UInt32 maxStringLength; UA_UInt32 maxStringLength;
UA_LocalizedText description; UA_LocalizedText description;
/* If dataSetFieldId is not set, the GUID will be generated on adding the /* If dataSetFieldId is not set, the GUID will be generated on adding the
* field*/ * field */
UA_Guid dataSetFieldId; UA_Guid dataSetFieldId;
} UA_DataSetVariableConfig; } UA_DataSetVariableConfig;
@ -398,70 +421,13 @@ UA_Server_removeDataSetField(UA_Server *server, const UA_NodeId dsfId);
* container for :ref:`dsw` and network message settings. The WriterGroup can be * container for :ref:`dsw` and network message settings. The WriterGroup can be
* imagined as producer of the network messages. The creation of network * imagined as producer of the network messages. The creation of network
* messages is controlled by parameters like the publish interval, which is e.g. * messages is controlled by parameters like the publish interval, which is e.g.
* contained in the WriterGroup. * contained in the WriterGroup. */
*
* The message publishing can be configured for realtime requirements. The RT-levels
* go along with different requirements. The below listed levels can be configured:
*
* UA_PUBSUB_RT_NONE
* No realtime-specific configuration.
*
* UA_PUBSUB_RT_DIRECT_VALUE_ACCESS
* All PublishedDataSets need to point to a variable with a
* ``UA_VALUEBACKENDTYPE_EXTERNAL`` value backend. The value backend gets
* cached when the configuration is frozen. No lookup of the variable from
* the information is performed afterwards. This enables also big data
* structures to be updated atomically with a compare-and-switch operation on
* the ``UA_DataValue`` double-pointer in the backend.
*
* UA_PUBSUB_RT_FIXED_SIZE
* Validate that the message constains only fields with a known size.
* Then the message fields have fixed offsets that are known ahead of time.
*
* UA_PUBSUB_RT_DETERMINISTIC
* Both direct-access and fixed-size is being used. The server pre-allocates
* buffers when the configuration is frozen and uses only memcpy operations
* to update the PubSub network messages for sending.
*
* WARNING! For hard real time requirements the underlying system must be
* RT-capable. Also note that each PubSubConnection can have a dedicated
* EventLoop. That way normal client/server operations can run independently
* from PubSub. The double-pointer in the ``UA_VALUEBACKENDTYPE_EXTERNAL`` value
* backend allows avoid race-condition with non-blocking atomic operations. */
typedef enum {
UA_PUBSUB_RT_NONE = 0,
UA_PUBSUB_RT_DIRECT_VALUE_ACCESS = 1,
UA_PUBSUB_RT_FIXED_SIZE = 2,
UA_PUBSUB_RT_DETERMINISTIC = 3,
} UA_PubSubRTLevel;
typedef enum { typedef enum {
UA_PUBSUB_ENCODING_UADP = 0, UA_PUBSUB_ENCODING_UADP = 0,
UA_PUBSUB_ENCODING_JSON UA_PUBSUB_ENCODING_JSON
} UA_PubSubEncodingType; } UA_PubSubEncodingType;
/**
* The user can define his own callback implementation for publishing and
* subscribing. The user must take care of the callback to call for every
* publishing or subscibing interval. The configured base time and timer policy
* are provided as an argument so that the user can implement his callback
* (thread) considering base time and timer policies */
typedef struct {
UA_StatusCode (*addCustomCallback)(UA_Server *server, UA_NodeId identifier,
UA_ServerCallback callback, void *data,
UA_Double interval_ms, UA_DateTime *baseTime,
UA_TimerPolicy timerPolicy,
UA_UInt64 *callbackId);
UA_StatusCode (*changeCustomCallback)(UA_Server *server, UA_NodeId identifier,
UA_UInt64 callbackId, UA_Double interval_ms,
UA_DateTime *baseTime,
UA_TimerPolicy timerPolicy);
void (*removeCustomCallback)(UA_Server *server, UA_NodeId identifier,
UA_UInt64 callbackId);
} UA_PubSub_CallbackLifecycle;
typedef struct { typedef struct {
UA_String name; UA_String name;
UA_UInt16 writerGroupId; UA_UInt16 writerGroupId;
@ -472,20 +438,20 @@ typedef struct {
UA_ExtensionObject messageSettings; UA_ExtensionObject messageSettings;
UA_KeyValueMap groupProperties; UA_KeyValueMap groupProperties;
UA_PubSubEncodingType encodingMimeType; UA_PubSubEncodingType encodingMimeType;
/* PubSub Manager Callback */
UA_PubSub_CallbackLifecycle pubsubManagerCallback;
/* non std. config parameter. maximum count of embedded DataSetMessage in /* non std. config parameter. maximum count of embedded DataSetMessage in
* one NetworkMessage */ * one NetworkMessage */
UA_UInt16 maxEncapsulatedDataSetMessageCount; UA_UInt16 maxEncapsulatedDataSetMessageCount;
/* non std. field */
UA_PubSubRTLevel rtLevel;
/* Message are encrypted if a SecurityPolicy is configured and the /* Security Configuration
* Message are encrypted if a SecurityPolicy is configured and the
* securityMode set accordingly. The symmetric key is a runtime information * securityMode set accordingly. The symmetric key is a runtime information
* and has to be set via UA_Server_setWriterGroupEncryptionKey. */ * and has to be set via UA_Server_setWriterGroupEncryptionKey. */
UA_MessageSecurityMode securityMode; /* via the UA_WriterGroupDataType */ UA_MessageSecurityMode securityMode; /* via the UA_WriterGroupDataType */
UA_PubSubSecurityPolicy *securityPolicy; UA_PubSubSecurityPolicy *securityPolicy;
UA_String securityGroupId; UA_String securityGroupId;
UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */
} UA_WriterGroupConfig; } UA_WriterGroupConfig;
void UA_EXPORT void UA_EXPORT
@ -563,6 +529,8 @@ typedef struct {
UA_ExtensionObject transportSettings; UA_ExtensionObject transportSettings;
UA_String dataSetName; UA_String dataSetName;
UA_KeyValueMap dataSetWriterProperties; UA_KeyValueMap dataSetWriterProperties;
UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */
} UA_DataSetWriterConfig; } UA_DataSetWriterConfig;
void UA_EXPORT void UA_EXPORT
@ -625,45 +593,17 @@ typedef enum {
UA_PUBSUB_SDS_MIRROR UA_PUBSUB_SDS_MIRROR
} UA_SubscribedDataSetType; } UA_SubscribedDataSetType;
typedef struct {
/* Standard-defined FieldTargetDataType */
UA_FieldTargetDataType targetVariable;
/* If realtime-handling is required, set this pointer non-NULL and it will be used
* to memcpy the value instead of using the Write service.
* If the beforeWrite method pointer is set, it will be called before a memcpy update
* to the value. But param externalDataValue already contains the new value.
* If the afterWrite method pointer is set, it will be called after a memcpy update
* to the value. */
UA_DataValue **externalDataValue;
void *targetVariableContext; /* user-defined pointer */
void (*beforeWrite)(UA_Server *server,
const UA_NodeId *readerIdentifier,
const UA_NodeId *readerGroupIdentifier,
const UA_NodeId *targetVariableIdentifier,
void *targetVariableContext,
UA_DataValue **externalDataValue);
void (*afterWrite)(UA_Server *server,
const UA_NodeId *readerIdentifier,
const UA_NodeId *readerGroupIdentifier,
const UA_NodeId *targetVariableIdentifier,
void *targetVariableContext,
UA_DataValue **externalDataValue);
} UA_FieldTargetVariable;
typedef struct {
size_t targetVariablesSize;
UA_FieldTargetVariable *targetVariables;
} UA_TargetVariables;
typedef struct { typedef struct {
UA_String name; UA_String name;
UA_SubscribedDataSetType subscribedDataSetType; UA_SubscribedDataSetType subscribedDataSetType;
union { union {
/* datasetmirror is currently not implemented */ /* DataSetMirror is currently not implemented */
UA_TargetVariablesDataType target; UA_TargetVariablesDataType target;
} subscribedDataSet; } subscribedDataSet;
UA_DataSetMetaDataType dataSetMetaData; UA_DataSetMetaDataType dataSetMetaData;
void *context; /* Context Configuration (SubscribedDataSet has no state
* machine) */
} UA_SubscribedDataSetConfig; } UA_SubscribedDataSetConfig;
UA_EXPORT void UA_EXPORT void
@ -711,14 +651,15 @@ typedef struct {
UA_ExtensionObject messageSettings; UA_ExtensionObject messageSettings;
UA_ExtensionObject transportSettings; UA_ExtensionObject transportSettings;
UA_SubscribedDataSetType subscribedDataSetType; UA_SubscribedDataSetType subscribedDataSetType;
/* TODO UA_SubscribedDataSetMirrorDataType subscribedDataSetMirror */
union { union {
UA_TargetVariables subscribedDataSetTarget; /* TODO: UA_SubscribedDataSetMirrorDataType subscribedDataSetMirror */
// UA_SubscribedDataSetMirrorDataType subscribedDataSetMirror; UA_TargetVariablesDataType target;
} subscribedDataSet; } subscribedDataSet;
/* non std. fields */ /* non std. fields */
UA_String linkedStandaloneSubscribedDataSetName; UA_String linkedStandaloneSubscribedDataSetName;
UA_PubSubRtEncoding expectedEncoding; UA_PubSubRtEncoding expectedEncoding;
UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */
} UA_DataSetReaderConfig; } UA_DataSetReaderConfig;
UA_EXPORT UA_StatusCode UA_EXPORT UA_StatusCode
@ -753,10 +694,8 @@ UA_EXPORT UA_StatusCode UA_THREADSAFE
UA_Server_disableDataSetReader(UA_Server *server, const UA_NodeId dsrId); UA_Server_disableDataSetReader(UA_Server *server, const UA_NodeId dsrId);
UA_EXPORT UA_StatusCode UA_THREADSAFE UA_EXPORT UA_StatusCode UA_THREADSAFE
UA_Server_setDataSetReaderTargetVariables(UA_Server *server, UA_Server_setDataSetReaderTargetVariables(UA_Server *server, const UA_NodeId dsrId,
const UA_NodeId dsrId, size_t targetVariablesSize, const UA_FieldTargetDataType *targetVariables);
size_t tvsSize,
const UA_FieldTargetVariable *tvs);
/* Legacy API */ /* Legacy API */
#define UA_Server_DataSetReader_getConfig(server, dsrId, config) \ #define UA_Server_DataSetReader_getConfig(server, dsrId, config) \
@ -776,19 +715,12 @@ UA_Server_setDataSetReaderTargetVariables(UA_Server *server,
* DataSetReader. * DataSetReader.
* *
* The RT-levels go along with different requirements. The below listed levels * The RT-levels go along with different requirements. The below listed levels
* can be configured for a ReaderGroup. * can be configured for a ReaderGroup. */
*
* - UA_PUBSUB_RT_NONE: RT applied to this level
* - UA_PUBSUB_RT_FIXED_SIZE: Extends PubSub RT functionality and implements
* fast path message decoding in the Subscriber. Uses a buffered network
* message and only decodes the necessary offsets stored in an offset
* buffer. */
typedef struct { typedef struct {
UA_String name; UA_String name;
/* non std. field */ /* non std. field */
UA_PubSubRTLevel rtLevel;
UA_KeyValueMap groupProperties; UA_KeyValueMap groupProperties;
UA_PubSubEncodingType encodingMimeType; UA_PubSubEncodingType encodingMimeType;
UA_ExtensionObject transportSettings; UA_ExtensionObject transportSettings;
@ -799,6 +731,8 @@ typedef struct {
UA_MessageSecurityMode securityMode; UA_MessageSecurityMode securityMode;
UA_PubSubSecurityPolicy *securityPolicy; UA_PubSubSecurityPolicy *securityPolicy;
UA_String securityGroupId; UA_String securityGroupId;
UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */
} UA_ReaderGroupConfig; } UA_ReaderGroupConfig;
void UA_EXPORT void UA_EXPORT
@ -971,6 +905,67 @@ UA_Server_setWriterGroupActivateKey(UA_Server *server,
#endif /* UA_ENABLE_PUBSUB_SKS */ #endif /* UA_ENABLE_PUBSUB_SKS */
/**
* Offset Table
* ------------
* When the content of a PubSub Networkmessage has a fixed length, then only a
* few "content bytes" at known locations within the NetworkMessage change
* between publish cycles. The so-called offset table exposes this to enable
* fast-path implementations for realtime applications. */
typedef enum {
UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_GROUPVERSION, /* UInt32 */
UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER, /* UInt16 */
UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_TIMESTAMP, /* DateTime */
UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_PICOSECONDS, /* UInt16 */
UA_PUBSUBOFFSETTYPE_DATASETMESSAGE, /* no content, marks the DSM beginning */
UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER, /* UInt16 */
UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_STATUS, /* UInt16 */
UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_TIMESTAMP, /* DateTime */
UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_PICOSECONDS, /* UInt16 */
UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE,
UA_PUBSUBOFFSETTYPE_DATASETFIELD_VARIANT,
UA_PUBSUBOFFSETTYPE_DATASETFIELD_RAW
} UA_PubSubOffsetType;
typedef struct {
UA_PubSubOffsetType offsetType; /* Content type at the offset */
size_t offset; /* Offset in the NetworkMessage */
/* The PubSub component that originates / receives the offset content.
* - For NetworkMessage-offsets this is the ReaderGroup / WriterGroup.
* - For DataSetMessage-offsets this is DataSetReader / DataSetWriter.
* - For DataSetFields this is the NodeId associated with the field:
* - For Writers the NodeId of the DataSetField (in a PublishedDataSet).
* - For Readers the TargetNodeId of the FieldTargetDataType (this can
* come from a SubscribedDataSet or a StandaloneSubscribedDataSets).
* Access more metadata from the FieldTargetVariable by counting the
* index of the current DataSetField-offset within the DataSetMessage
* and use that index for the lookup in the DataSetReader configuration. */
UA_NodeId component;
} UA_PubSubOffset;
typedef struct {
UA_PubSubOffset *offsets; /* Array of offset entries */
size_t offsetsSize; /* Number of entries */
UA_ByteString networkMessage; /* Current NetworkMessage in binary encoding */
} UA_PubSubOffsetTable;
UA_EXPORT void
UA_PubSubOffsetTable_clear(UA_PubSubOffsetTable *ot);
/* Compute the offset table for a WriterGroup */
UA_EXPORT UA_StatusCode UA_THREADSAFE
UA_Server_computeWriterGroupOffsetTable(UA_Server *server,
const UA_NodeId writerGroupId,
UA_PubSubOffsetTable *ot);
/* Compute the offset table for a ReaderGroup */
UA_EXPORT UA_StatusCode UA_THREADSAFE
UA_Server_computeReaderGroupOffsetTable(UA_Server *server,
const UA_NodeId readerGroupId,
UA_PubSubOffsetTable *ot);
#endif /* UA_ENABLE_PUBSUB */ #endif /* UA_ENABLE_PUBSUB */
_UA_END_DECLS _UA_END_DECLS

View File

@ -21,6 +21,9 @@
#include <open62541/common.h> #include <open62541/common.h>
#include <open62541/statuscodes.h> #include <open62541/statuscodes.h>
struct UA_NamespaceMapping;
typedef struct UA_NamespaceMapping UA_NamespaceMapping;
_UA_BEGIN_DECLS _UA_BEGIN_DECLS
/** /**
@ -200,7 +203,7 @@ UA_String_fromChars(const char *src) UA_FUNC_ATTR_WARN_UNUSED_RESULT;
UA_Boolean UA_EXPORT UA_Boolean UA_EXPORT
UA_String_isEmpty(const UA_String *s); UA_String_isEmpty(const UA_String *s);
UA_StatusCode UA_StatusCode UA_EXPORT
UA_String_append(UA_String *s, const UA_String s2); UA_String_append(UA_String *s, const UA_String s2);
UA_EXPORT extern const UA_String UA_STRING_NULL; UA_EXPORT extern const UA_String UA_STRING_NULL;
@ -417,8 +420,7 @@ UA_EXPORT extern const UA_NodeId UA_NODEID_NULL;
UA_Boolean UA_EXPORT UA_NodeId_isNull(const UA_NodeId *p); UA_Boolean UA_EXPORT UA_NodeId_isNull(const UA_NodeId *p);
/* Print the NodeId in the human-readable format defined in Part 6, /* Print the NodeId in the human-readable format defined in Part 6.
* 5.3.1.10.
* *
* Examples: * Examples:
* UA_NODEID("i=13") * UA_NODEID("i=13")
@ -432,13 +434,39 @@ UA_Boolean UA_EXPORT UA_NodeId_isNull(const UA_NodeId *p);
UA_StatusCode UA_EXPORT UA_StatusCode UA_EXPORT
UA_NodeId_print(const UA_NodeId *id, UA_String *output); UA_NodeId_print(const UA_NodeId *id, UA_String *output);
/* Extended NodeId printing. If nsMapping argument is non-NULL, then the
* NamespaceIndex is translated to the NamespaceUri. If that is not successful,
* the numerical NamespaceIndex is used instead.
*
* Examples:
* nsu=http://widgets.com/schemas/hello;s=Hello World
*/
UA_StatusCode UA_EXPORT
UA_NodeId_printEx(const UA_NodeId *id, UA_String *output,
const UA_NamespaceMapping *nsMapping);
#ifdef UA_ENABLE_PARSING
/* Parse the human-readable NodeId format. Attention! String and /* Parse the human-readable NodeId format. Attention! String and
* ByteString NodeIds have their identifier malloc'ed and need to be * ByteString NodeIds have their identifier malloc'ed and need to be
* cleaned up. */ * cleaned up. */
#ifdef UA_ENABLE_PARSING
UA_StatusCode UA_EXPORT UA_StatusCode UA_EXPORT
UA_NodeId_parse(UA_NodeId *id, const UA_String str); UA_NodeId_parse(UA_NodeId *id, const UA_String str);
/* Extended parsing that uses the provided namespace mapping to find the
* NamespaceIndex for a provided NamespaceUri.
*
* If the NodeId uses an unknown NamespaceUri, then a String-NodeId is returned
* that uses NamespaceIndex 0 and the full original encoding for the string
* part.
*
* Example:
* nsu=my_uri;i=5 => s="nsu=my_uri;i=5" (The quotation marks are for
* illustration purposes and not actually included)
*/
UA_StatusCode UA_EXPORT
UA_NodeId_parseEx(UA_NodeId *id, const UA_String str,
const UA_NamespaceMapping *nsMapping);
UA_INLINABLE(UA_NodeId UA_INLINABLE(UA_NodeId
UA_NODEID(const char *chars), { UA_NODEID(const char *chars), {
UA_NodeId id; UA_NodeId id;
@ -550,13 +578,29 @@ UA_EXPORT extern const UA_ExpandedNodeId UA_EXPANDEDNODEID_NULL;
UA_StatusCode UA_EXPORT UA_StatusCode UA_EXPORT
UA_ExpandedNodeId_print(const UA_ExpandedNodeId *id, UA_String *output); UA_ExpandedNodeId_print(const UA_ExpandedNodeId *id, UA_String *output);
/* Extended printing of ExpandedNodeId. It tries to map NamespaceIndex and
* ServerIndex to a Uri using the provided mapping.
*
* Examples:
* svu=http://smith.com/west/factory;nsu=tag:acme.com,2023;i=1234
*/
UA_StatusCode UA_EXPORT
UA_ExpandedNodeId_printEx(const UA_ExpandedNodeId *id, UA_String *output,
const UA_NamespaceMapping *nsMapping,
size_t serverUrisSize, const UA_String *serverUris);
#ifdef UA_ENABLE_PARSING
/* Parse the human-readable NodeId format. Attention! String and /* Parse the human-readable NodeId format. Attention! String and
* ByteString NodeIds have their identifier malloc'ed and need to be * ByteString NodeIds have their identifier malloc'ed and need to be
* cleaned up. */ * cleaned up. */
#ifdef UA_ENABLE_PARSING
UA_StatusCode UA_EXPORT UA_StatusCode UA_EXPORT
UA_ExpandedNodeId_parse(UA_ExpandedNodeId *id, const UA_String str); UA_ExpandedNodeId_parse(UA_ExpandedNodeId *id, const UA_String str);
UA_StatusCode UA_EXPORT
UA_ExpandedNodeId_parseEx(UA_ExpandedNodeId *id, const UA_String str,
const UA_NamespaceMapping *nsMapping,
size_t serverUrisSize, const UA_String *serverUris);
UA_INLINABLE(UA_ExpandedNodeId UA_INLINABLE(UA_ExpandedNodeId
UA_EXPANDEDNODEID(const char *chars), { UA_EXPANDEDNODEID(const char *chars), {
UA_ExpandedNodeId id; UA_ExpandedNodeId id;
@ -665,6 +709,42 @@ UA_INLINABLE(UA_QualifiedName
return qn; return qn;
}) })
/* Print the human-readable QualifiedName format. QualifiedNames can be printed
* with either the integer NamespaceIndex or using the NamespaceUri.
* The Namespace 0 is always omitted.
*
* The extended printing tries to translate the NamespaceIndex to the
* NamespaceUri from the mapping table. When the mapping fails, the integer
* NamespaceIndex from is used.
*
* Examples:
* Namespace Zero: HelloWorld
* NamespaceIndex Form: 3:HelloWorld
* NamespaceUri Form: nsu=http://widgets.com/schemas/hello;HelloWorld
*
* The method can either use a pre-allocated string buffer or allocates memory
* internally if called with an empty output string. */
UA_StatusCode UA_EXPORT
UA_QualifiedName_print(const UA_QualifiedName *qn, UA_String *output);
UA_StatusCode UA_EXPORT
UA_QualifiedName_printEx(const UA_QualifiedName *qn, UA_String *output,
const UA_NamespaceMapping *nsMapping);
#ifdef UA_ENABLE_PARSING
/* Parse the human-readable QualifiedName format.
*
* The extended parsing tries to translate the NamespaceIndex to a NamespaceUri
* from the mapping table. When the mapping fails, the name component gets the
* entire string. */
UA_StatusCode UA_EXPORT
UA_QualifiedName_parse(UA_QualifiedName *qn, const UA_String str);
UA_StatusCode UA_EXPORT
UA_QualifiedName_parseEx(UA_QualifiedName *qn, const UA_String str,
const UA_NamespaceMapping *nsMapping);
#endif
/** /**
* LocalizedText * LocalizedText
* ^^^^^^^^^^^^^ * ^^^^^^^^^^^^^
@ -1245,6 +1325,66 @@ UA_INLINABLE(UA_Boolean
return (UA_order(p1, p2, type) == UA_ORDER_EQ); return (UA_order(p1, p2, type) == UA_ORDER_EQ);
}) })
/**
* Namespace Mapping
* -----------------
*
* Every :ref:`nodeid` references a namespace index. Actually the namespace is
* identified by its URI. The namespace-array of the server maps the URI to the
* namespace index in the array. Namespace zero always has the URI
* ```http://opcfoundation.org/UA/```. Namespace one has the application URI of
* the server. All namespaces beyond get a custom assignment.
*
* In order to have predictable NodeIds, a client might predefined its own
* namespace array that is different from the server's. When a NodeId is decoded
* from a network message (binary or JSON), a mapping-table can be used to
* automatically translate between the remote and local namespace index. The
* mapping is typically done by the client who can generate the mapping table
* after reading the namespace-array of the server. The reverse mapping is done
* in the encoding if the mapping table is set in the options.
*
* The mapping table also contains the full URI names. It is also used to
* translate the ``NamespaceUri`` field of an ExpandedNodeId into the namespace
* index of the NodeId embedded in the ExpandedNodeId. */
struct UA_NamespaceMapping {
/* Namespaces with their local index */
UA_String *namespaceUris;
size_t namespaceUrisSize;
/* Map from local to remote indices */
UA_UInt16 *local2remote;
size_t local2remoteSize;
/* Map from remote to local indices */
UA_UInt16 *remote2local;
size_t remote2localSize;
};
/* If the index is unknown, returns (UINT16_MAX - index) */
UA_UInt16
UA_NamespaceMapping_local2Remote(const UA_NamespaceMapping *nm,
UA_UInt16 localIndex);
UA_UInt16
UA_NamespaceMapping_remote2Local(const UA_NamespaceMapping *nm,
UA_UInt16 remoteIndex);
/* Returns an error if the namespace uri was not found.
* The pointer to the index argument needs to be non-NULL. */
UA_StatusCode
UA_NamespaceMapping_uri2Index(const UA_NamespaceMapping *nm,
UA_String uri, UA_UInt16 *index);
/* Upon success, the uri string gets set. The string is not copied and must not
* outlive the namespace mapping structure. */
UA_StatusCode
UA_NamespaceMapping_index2Uri(const UA_NamespaceMapping *nm,
UA_UInt16 index, UA_String *uri);
void
UA_NamespaceMapping_delete(UA_NamespaceMapping *nm);
/** /**
* Binary Encoding/Decoding * Binary Encoding/Decoding
* ------------------------ * ------------------------
@ -1252,10 +1392,17 @@ UA_INLINABLE(UA_Boolean
* Encoding and decoding routines for the binary format. For the binary decoding * Encoding and decoding routines for the binary format. For the binary decoding
* additional data types can be forwarded. */ * additional data types can be forwarded. */
typedef struct {
/* Mapping of namespace indices in NodeIds and of NamespaceUris in
* ExpandedNodeIds. */
UA_NamespaceMapping *namespaceMapping;
} UA_EncodeBinaryOptions;
/* Returns the number of bytes the value p takes in binary encoding. Returns /* Returns the number of bytes the value p takes in binary encoding. Returns
* zero if an error occurs. */ * zero if an error occurs. */
UA_EXPORT size_t UA_EXPORT size_t
UA_calcSizeBinary(const void *p, const UA_DataType *type); UA_calcSizeBinary(const void *p, const UA_DataType *type,
UA_EncodeBinaryOptions *options);
/* Encodes a data-structure in the binary format. If outBuf has a length of /* Encodes a data-structure in the binary format. If outBuf has a length of
* zero, a buffer of the required size is allocated. Otherwise, encoding into * zero, a buffer of the required size is allocated. Otherwise, encoding into
@ -1263,7 +1410,7 @@ UA_calcSizeBinary(const void *p, const UA_DataType *type);
* small). */ * small). */
UA_EXPORT UA_StatusCode UA_EXPORT UA_StatusCode
UA_encodeBinary(const void *p, const UA_DataType *type, UA_encodeBinary(const void *p, const UA_DataType *type,
UA_ByteString *outBuf); UA_ByteString *outBuf, UA_EncodeBinaryOptions *options);
/* The structure with the decoding options may be extended in the future. /* The structure with the decoding options may be extended in the future.
* Zero-out the entire structure initially to ensure code-compatibility when * Zero-out the entire structure initially to ensure code-compatibility when
@ -1272,6 +1419,10 @@ typedef struct {
/* Begin of a linked list with custom datatype definitions */ /* Begin of a linked list with custom datatype definitions */
const UA_DataTypeArray *customTypes; const UA_DataTypeArray *customTypes;
/* Mapping of namespace indices in NodeIds and of NamespaceUris in
* ExpandedNodeIds. */
UA_NamespaceMapping *namespaceMapping;
/* Override calloc for arena-based memory allocation. Note that allocated /* Override calloc for arena-based memory allocation. Note that allocated
* memory is not freed if decoding fails afterwards. */ * memory is not freed if decoding fails afterwards. */
void *callocContext; void *callocContext;
@ -1310,8 +1461,10 @@ UA_decodeBinary(const UA_ByteString *inBuf,
#ifdef UA_ENABLE_JSON_ENCODING #ifdef UA_ENABLE_JSON_ENCODING
typedef struct { typedef struct {
const UA_String *namespaces; /* Mapping of namespace indices in NodeIds and of NamespaceUris in
size_t namespacesSize; * ExpandedNodeIds. */
UA_NamespaceMapping *namespaceMapping;
const UA_String *serverUris; const UA_String *serverUris;
size_t serverUrisSize; size_t serverUrisSize;
UA_Boolean useReversible; UA_Boolean useReversible;
@ -1347,12 +1500,16 @@ UA_encodeJson(const void *src, const UA_DataType *type, UA_ByteString *outBuf,
* Zero-out the entire structure initially to ensure code-compatibility when * Zero-out the entire structure initially to ensure code-compatibility when
* more fields are added in a later release. */ * more fields are added in a later release. */
typedef struct { typedef struct {
const UA_String *namespaces; /* Mapping of namespace indices in NodeIds and of NamespaceUris in
size_t namespacesSize; * ExpandedNodeIds. */
UA_NamespaceMapping *namespaceMapping;
const UA_String *serverUris; const UA_String *serverUris;
size_t serverUrisSize; size_t serverUrisSize;
const UA_DataTypeArray *customTypes; /* Begin of a linked list with custom const UA_DataTypeArray *customTypes; /* Begin of a linked list with custom
* datatype definitions */ * datatype definitions */
size_t *decodedLength; /* If non-NULL, the length of the decoded input is size_t *decodedLength; /* If non-NULL, the length of the decoded input is
* stored to the pointer. When this is set, decoding * stored to the pointer. When this is set, decoding
* succeeds also if there is more content after the * succeeds also if there is more content after the

View File

@ -439,6 +439,10 @@ UA_ByteString_memZero(UA_ByteString *bs);
UA_EXPORT UA_StatusCode UA_EXPORT UA_StatusCode
UA_TrustListDataType_add(const UA_TrustListDataType *src, UA_TrustListDataType *dst); UA_TrustListDataType_add(const UA_TrustListDataType *src, UA_TrustListDataType *dst);
/* Replaces the contents of the destination trusted list with the certificates from the source trusted list. */
UA_EXPORT UA_StatusCode
UA_TrustListDataType_set(const UA_TrustListDataType *src, UA_TrustListDataType *dst);
/* Removes all of the certificates from the dst trust list that are specified /* Removes all of the certificates from the dst trust list that are specified
* in the src trust list. */ * in the src trust list. */
UA_EXPORT UA_StatusCode UA_EXPORT UA_StatusCode

View File

@ -9,7 +9,6 @@
#include <open62541/util.h> #include <open62541/util.h>
#include <open62541/plugin/certificategroup_default.h> #include <open62541/plugin/certificategroup_default.h>
#include <open62541/plugin/log_stdout.h>
#ifdef UA_ENABLE_ENCRYPTION_MBEDTLS #ifdef UA_ENABLE_ENCRYPTION_MBEDTLS
@ -95,8 +94,8 @@ MemoryCertStore_setTrustList(UA_CertificateGroup *certGroup, const UA_TrustListD
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
} }
context->reloadRequired = true; context->reloadRequired = true;
UA_TrustListDataType_clear(&context->trustList); /* Remove the section of the trust list that needs to be reset, while keeping the remaining parts intact */
return UA_TrustListDataType_add(trustList, &context->trustList); return UA_TrustListDataType_set(trustList, &context->trustList);
} }
static UA_StatusCode static UA_StatusCode
@ -153,7 +152,7 @@ mbedtlsFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate
mbedtls_x509_crt_init(&cert); mbedtls_x509_crt_init(&cert);
UA_StatusCode retval = UA_mbedTLS_LoadCertificate(certificate, &cert); UA_StatusCode retval = UA_mbedTLS_LoadCertificate(certificate, &cert);
if(retval != UA_STATUSCODE_GOOD) { if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER, UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY,
"An error occurred while parsing the certificate."); "An error occurred while parsing the certificate.");
return retval; return retval;
} }
@ -161,7 +160,7 @@ mbedtlsFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate
/* Check if the certificate is a CA certificate. /* Check if the certificate is a CA certificate.
* Only a CA certificate can have a CRL. */ * Only a CA certificate can have a CRL. */
if(!mbedtlsCheckCA(&cert)) { if(!mbedtlsCheckCA(&cert)) {
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER, UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY,
"The certificate is not a CA certificate and therefore does not have a CRL."); "The certificate is not a CA certificate and therefore does not have a CRL.");
mbedtls_x509_crt_free(&cert); mbedtls_x509_crt_free(&cert);
return UA_STATUSCODE_GOOD; return UA_STATUSCODE_GOOD;
@ -173,7 +172,7 @@ mbedtlsFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate
mbedtls_x509_crl_init(&crl); mbedtls_x509_crl_init(&crl);
retval = UA_mbedTLS_LoadCrl(&crlList[i], &crl); retval = UA_mbedTLS_LoadCrl(&crlList[i], &crl);
if(retval != UA_STATUSCODE_GOOD) { if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER, UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY,
"An error occurred while parsing the crl."); "An error occurred while parsing the crl.");
mbedtls_x509_crt_free(&cert); mbedtls_x509_crt_free(&cert);
return retval; return retval;
@ -697,6 +696,8 @@ cleanup:
return retval; return retval;
} }
#if !defined(mbedtls_x509_subject_alternative_name)
/* Find binary substring. Taken and adjusted from /* Find binary substring. Taken and adjusted from
* http://tungchingkai.blogspot.com/2011/07/binary-strstr.html */ * http://tungchingkai.blogspot.com/2011/07/binary-strstr.html */
@ -736,10 +737,13 @@ UA_Bstrstr(const unsigned char *s1, size_t l1, const unsigned char *s2, size_t l
return NULL; return NULL;
} }
#endif
UA_StatusCode UA_StatusCode
UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling,
const UA_ByteString *certificate, const UA_ByteString *certificate,
const UA_String *applicationURI) { const UA_String *applicationURI,
UA_Logger *logger) {
/* Parse the certificate */ /* Parse the certificate */
mbedtls_x509_crt remoteCertificate; mbedtls_x509_crt remoteCertificate;
mbedtls_x509_crt_init(&remoteCertificate); mbedtls_x509_crt_init(&remoteCertificate);
@ -748,21 +752,56 @@ UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling,
if(retval != UA_STATUSCODE_GOOD) if(retval != UA_STATUSCODE_GOOD)
return retval; return retval;
#if defined(mbedtls_x509_subject_alternative_name)
/* Get the Subject Alternative Name and compate */
mbedtls_x509_subject_alternative_name san;
mbedtls_x509_sequence *cur = &remoteCertificate.subject_alt_names;
retval = UA_STATUSCODE_BADCERTIFICATEURIINVALID;
for(; cur; cur = cur->next) {
int res = mbedtls_x509_parse_subject_alt_name(&cur->buf, &san);
if(res != 0)
continue;
if(san.type != MBEDTLS_X509_SAN_UNIFORM_RESOURCE_IDENTIFIER) {
mbedtls_x509_free_subject_alt_name(&san);
continue;
}
UA_String uri = {san.san.unstructured_name.len, san.san.unstructured_name.p};
UA_Boolean found = UA_String_equal(&uri, applicationURI);
if(found) {
retval = UA_STATUSCODE_GOOD;
} else if(ruleHandling != UA_RULEHANDLING_ACCEPT) {
UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY,
"The certificate's Subject Alternative Name URI (%S) "
"does not match the ApplicationURI (%S)",
uri, *applicationURI);
}
mbedtls_x509_free_subject_alt_name(&san);
break;
}
if(!cur && ruleHandling != UA_RULEHANDLING_ACCEPT) {
UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY,
"The certificate has no Subject Alternative Name URI defined");
}
#else
/* Poor man's ApplicationUri verification. mbedTLS does not parse all fields /* Poor man's ApplicationUri verification. mbedTLS does not parse all fields
* of the Alternative Subject Name. Instead test whether the URI-string is * of the Alternative Subject Name. Instead test whether the URI-string is
* present in the v3_ext field in general. * present in the v3_ext field in general. */
*
* TODO: Improve parsing of the Alternative Subject Name */
if(UA_Bstrstr(remoteCertificate.v3_ext.p, remoteCertificate.v3_ext.len, if(UA_Bstrstr(remoteCertificate.v3_ext.p, remoteCertificate.v3_ext.len,
applicationURI->data, applicationURI->length) == NULL) applicationURI->data, applicationURI->length) == NULL)
retval = UA_STATUSCODE_BADCERTIFICATEURIINVALID; retval = UA_STATUSCODE_BADCERTIFICATEURIINVALID;
if(retval != UA_STATUSCODE_GOOD && ruleHandling == UA_RULEHANDLING_DEFAULT) { if(retval != UA_STATUSCODE_GOOD && ruleHandling != UA_RULEHANDLING_ACCEPT) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY,
"The certificate's application URI could not be verified. StatusCode %s", "The certificate's application URI could not be verified. StatusCode %s",
UA_StatusCode_name(retval)); UA_StatusCode_name(retval));
retval = UA_STATUSCODE_GOOD;
} }
#endif
if(ruleHandling != UA_RULEHANDLING_ABORT)
retval = UA_STATUSCODE_GOOD;
mbedtls_x509_crt_free(&remoteCertificate); mbedtls_x509_crt_free(&remoteCertificate);
return retval; return retval;
} }
@ -798,13 +837,23 @@ UA_CertificateUtils_getSubjectName(UA_ByteString *certificate,
mbedtls_x509_crt publicKey; mbedtls_x509_crt publicKey;
mbedtls_x509_crt_init(&publicKey); mbedtls_x509_crt_init(&publicKey);
UA_StatusCode retval = UA_mbedTLS_LoadCertificate(certificate, &publicKey); mbedtls_x509_crl crl;
if(retval != UA_STATUSCODE_GOOD) mbedtls_x509_crl_init(&crl);
return retval;
char buf[1024]; char buf[1024];
int res = mbedtls_x509_dn_gets(buf, 1024, &publicKey.subject); int res = 0;
mbedtls_x509_crt_free(&publicKey); UA_StatusCode retval = UA_mbedTLS_LoadCertificate(certificate, &publicKey);
if(retval == UA_STATUSCODE_GOOD) {
res = mbedtls_x509_dn_gets(buf, 1024, &publicKey.subject);
mbedtls_x509_crt_free(&publicKey);
} else {
retval = UA_mbedTLS_LoadCrl(certificate, &crl);
if(retval != UA_STATUSCODE_GOOD)
return retval;
res = mbedtls_x509_dn_gets(buf, 1024, &crl.issuer);
mbedtls_x509_crl_free(&crl);
}
if(res < 0) if(res < 0)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
UA_String tmp = {(size_t)res, (UA_Byte*)buf}; UA_String tmp = {(size_t)res, (UA_Byte*)buf};

View File

@ -9,7 +9,6 @@
#include <open62541/util.h> #include <open62541/util.h>
#include <open62541/plugin/certificategroup_default.h> #include <open62541/plugin/certificategroup_default.h>
#include <open62541/plugin/log_stdout.h>
#if defined(UA_ENABLE_ENCRYPTION_OPENSSL) || defined(UA_ENABLE_ENCRYPTION_LIBRESSL) #if defined(UA_ENABLE_ENCRYPTION_OPENSSL) || defined(UA_ENABLE_ENCRYPTION_LIBRESSL)
#include <openssl/x509.h> #include <openssl/x509.h>
@ -93,8 +92,8 @@ MemoryCertStore_setTrustList(UA_CertificateGroup *certGroup, const UA_TrustListD
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
} }
context->reloadRequired = true; context->reloadRequired = true;
UA_TrustListDataType_clear(&context->trustList); /* Remove the section of the trust list that needs to be reset, while keeping the remaining parts intact */
return UA_TrustListDataType_add(trustList, &context->trustList); return UA_TrustListDataType_set(trustList, &context->trustList);
} }
static UA_StatusCode static UA_StatusCode
@ -159,8 +158,8 @@ openSSLFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate
/* Check if the certificate is a CA certificate. /* Check if the certificate is a CA certificate.
* Only a CA certificate can have a CRL. */ * Only a CA certificate can have a CRL. */
if(!openSSLCheckCA(cert)) { if(!openSSLCheckCA(cert)) {
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER, UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY,
"The certificate is not a CA certificate and therefore does not have a CRL."); "The certificate is not a CA certificate and therefore does not have a CRL.");
X509_free(cert); X509_free(cert);
return UA_STATUSCODE_GOOD; return UA_STATUSCODE_GOOD;
} }
@ -761,7 +760,8 @@ cleanup:
UA_StatusCode UA_StatusCode
UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling,
const UA_ByteString *certificate, const UA_ByteString *certificate,
const UA_String *applicationURI) { const UA_String *applicationURI,
UA_Logger *logger) {
const unsigned char * pData; const unsigned char * pData;
X509 * certificateX509; X509 * certificateX509;
UA_String subjectURI = UA_STRING_NULL; UA_String subjectURI = UA_STRING_NULL;
@ -810,13 +810,16 @@ UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling,
ret = UA_STATUSCODE_BADCERTIFICATEURIINVALID; ret = UA_STATUSCODE_BADCERTIFICATEURIINVALID;
} }
if(ret != UA_STATUSCODE_GOOD && ruleHandling == UA_RULEHANDLING_DEFAULT) { if(ret != UA_STATUSCODE_GOOD && ruleHandling != UA_RULEHANDLING_ACCEPT) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY,
"The certificate's application URI could not be verified. StatusCode %s", "The certificate's Subject Alternative Name URI (%S) "
UA_StatusCode_name(ret)); "does not match the ApplicationURI (%S)",
ret = UA_STATUSCODE_GOOD; subjectURI, *applicationURI);
} }
if(ruleHandling != UA_RULEHANDLING_ABORT)
ret = UA_STATUSCODE_GOOD;
X509_free (certificateX509); X509_free (certificateX509);
sk_GENERAL_NAME_pop_free(pNames, GENERAL_NAME_free); sk_GENERAL_NAME_pop_free(pNames, GENERAL_NAME_free);
UA_String_clear (&subjectURI); UA_String_clear (&subjectURI);
@ -855,14 +858,24 @@ UA_CertificateUtils_getExpirationDate(UA_ByteString *certificate,
UA_StatusCode UA_StatusCode
UA_CertificateUtils_getSubjectName(UA_ByteString *certificate, UA_CertificateUtils_getSubjectName(UA_ByteString *certificate,
UA_String *subjectName) { UA_String *subjectName) {
X509_NAME *sn = NULL;
X509 *x509 = UA_OpenSSL_LoadCertificate(certificate); X509 *x509 = UA_OpenSSL_LoadCertificate(certificate);
if(!x509) X509_CRL *x509_crl = NULL;
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
if(x509) {
sn = X509_get_subject_name(x509);
} else {
x509_crl = UA_OpenSSL_LoadCrl(certificate);
if(!x509_crl)
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
sn = X509_CRL_get_issuer(x509_crl);
}
X509_NAME *sn = X509_get_subject_name(x509);
char buf[1024]; char buf[1024];
*subjectName = UA_STRING_ALLOC(X509_NAME_oneline(sn, buf, 1024)); *subjectName = UA_STRING_ALLOC(X509_NAME_oneline(sn, buf, 1024));
X509_free(x509);
if (x509) X509_free(x509);
if (x509_crl) X509_CRL_free(x509_crl);
return UA_STATUSCODE_GOOD; return UA_STATUSCODE_GOOD;
} }
@ -872,15 +885,25 @@ UA_CertificateUtils_getThumbprint(UA_ByteString *certificate,
if(certificate == NULL || thumbprint->length != (SHA1_DIGEST_LENGTH * 2)) if(certificate == NULL || thumbprint->length != (SHA1_DIGEST_LENGTH * 2))
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
X509 *cert = UA_OpenSSL_LoadCertificate(certificate);
if(!cert)
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
unsigned char digest[SHA1_DIGEST_LENGTH]; unsigned char digest[SHA1_DIGEST_LENGTH];
unsigned int digestLen; unsigned int digestLen;
if(X509_digest(cert, EVP_sha1(), digest, &digestLen) != 1) {
X509 *cert = UA_OpenSSL_LoadCertificate(certificate);
if(cert) {
if(X509_digest(cert, EVP_sha1(), digest, &digestLen) != 1) {
X509_free(cert);
return UA_STATUSCODE_BADINTERNALERROR;
}
X509_free(cert); X509_free(cert);
return UA_STATUSCODE_BADINTERNALERROR; } else {
X509_CRL *crl = UA_OpenSSL_LoadCrl(certificate);
if(!crl)
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
if(X509_CRL_digest(crl, EVP_sha1(), digest, &digestLen) != 1) {
X509_CRL_free(crl);
return UA_STATUSCODE_BADINTERNALERROR;
}
X509_CRL_free(crl);
} }
UA_String thumb = UA_STRING_NULL; UA_String thumb = UA_STRING_NULL;
@ -894,8 +917,6 @@ UA_CertificateUtils_getThumbprint(UA_ByteString *certificate,
} }
memcpy(thumbprint->data, thumb.data, thumbprint->length); memcpy(thumbprint->data, thumb.data, thumbprint->length);
X509_free(cert);
free(thumb.data); free(thumb.data);
return UA_STATUSCODE_GOOD; return UA_STATUSCODE_GOOD;

View File

@ -9,34 +9,26 @@
#include <open62541/util.h> #include <open62541/util.h>
#include <open62541/plugin/certificategroup_default.h> #include <open62541/plugin/certificategroup_default.h>
#include <open62541/plugin/log_stdout.h>
#include "ua_filestore_common.h" #include "ua_filestore_common.h"
#include "mp_printf.h" #include "mp_printf.h"
#ifdef UA_ENABLE_ENCRYPTION #ifdef UA_ENABLE_ENCRYPTION
#ifdef __linux__ /* Linux only so far */ #if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
#include <stdio.h>
#include <linux/limits.h>
#include <libgen.h>
#include <sys/stat.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <bits/stdio_lim.h>
#include <errno.h>
#ifdef __linux__
#define EVENT_SIZE (sizeof(struct inotify_event)) #define EVENT_SIZE (sizeof(struct inotify_event))
#define BUF_LEN (1024 * ( EVENT_SIZE + 16 )) #define BUF_LEN (1024 * ( EVENT_SIZE + 16 ))
#endif /* __linux__ */
typedef struct { typedef struct {
/* Memory cert store as a base */ /* Memory cert store as a base */
UA_CertificateGroup *store; UA_CertificateGroup *store;
#ifdef __linux__
int inotifyFd; int inotifyFd;
#endif /* __linux__ */
UA_String trustedCertFolder; UA_String trustedCertFolder;
UA_String trustedCrlFolder; UA_String trustedCrlFolder;
@ -49,13 +41,13 @@ typedef struct {
} FileCertStore; } FileCertStore;
static int static int
mkpath(char *dir, mode_t mode) { mkpath(char *dir, UA_MODE mode) {
struct stat sb; struct UA_STAT sb;
if(dir == NULL) if(dir == NULL)
return 1; return 1;
if(!stat(dir, &sb)) if(!UA_stat(dir, &sb))
return 0; /* Directory already exist */ return 0; /* Directory already exist */
size_t len = strlen(dir) + 1; size_t len = strlen(dir) + 1;
@ -66,10 +58,10 @@ mkpath(char *dir, mode_t mode) {
/* Before the actual target directory is created, the recursive call ensures /* Before the actual target directory is created, the recursive call ensures
* that all parent directories are created or already exist. */ * that all parent directories are created or already exist. */
mkpath(dirname(tmp_dir), mode); mkpath(UA_dirname(tmp_dir), mode);
UA_free(tmp_dir); UA_free(tmp_dir);
return mkdir(dir, mode); return UA_mkdir(dir, mode);
} }
static UA_StatusCode static UA_StatusCode
@ -81,22 +73,23 @@ removeAllFilesFromDir(const char *const path, bool removeSubDirs) {
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
/* remove all regular files from directory */ /* remove all regular files from directory */
DIR *dir = opendir(path); UA_DIR *dir = UA_opendir(path);
if(!dir) if(!dir)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
struct dirent *dirent; struct UA_DIRENT *dirent;
while((dirent = readdir(dir)) != NULL) { while((dirent = UA_readdir(dir)) != NULL) {
if(dirent->d_type == DT_REG) { if(dirent->d_type == UA_DT_REG) {
char file_name[FILENAME_MAX]; char file_name[UA_FILENAME_MAX];
mp_snprintf(file_name, FILENAME_MAX, "%s/%s", path, (char*)dirent->d_name); mp_snprintf(file_name, UA_FILENAME_MAX, "%s/%s", path,
remove(file_name); (char *)dirent->d_name);
UA_remove(file_name);
} }
if(dirent->d_type == DT_DIR && removeSubDirs == true) { if(dirent->d_type == UA_DT_DIR && removeSubDirs == true) {
char *directory = (char*)dirent->d_name; char *directory = (char*)dirent->d_name;
char dir_name[FILENAME_MAX]; char dir_name[UA_FILENAME_MAX];
mp_snprintf(dir_name, FILENAME_MAX, "%s/%s", path, (char*)dirent->d_name); mp_snprintf(dir_name, UA_FILENAME_MAX, "%s/%s", path, (char *)dirent->d_name);
if(strlen(directory) == 1 && directory[0] == '.') if(strlen(directory) == 1 && directory[0] == '.')
continue; continue;
@ -106,7 +99,7 @@ removeAllFilesFromDir(const char *const path, bool removeSubDirs) {
retval = removeAllFilesFromDir(dir_name, removeSubDirs); retval = removeAllFilesFromDir(dir_name, removeSubDirs);
} }
} }
closedir(dir); UA_closedir(dir);
return retval; return retval;
} }
@ -122,7 +115,7 @@ getCertFileName(const char *path, const UA_ByteString *certificate,
UA_String thumbprint = UA_STRING_NULL; UA_String thumbprint = UA_STRING_NULL;
thumbprint.length = 40; thumbprint.length = 40;
thumbprint.data = (UA_Byte*)UA_malloc(sizeof(UA_Byte)*thumbprint.length); thumbprint.data = (UA_Byte*)UA_calloc(thumbprint.length, sizeof(UA_Byte));
UA_String subjectName = UA_STRING_NULL; UA_String subjectName = UA_STRING_NULL;
@ -153,7 +146,8 @@ getCertFileName(const char *path, const UA_ByteString *certificate,
subName = subjectNameBuffer; subName = subjectNameBuffer;
} }
if(snprintf(fileNameBuf, fileNameLen, "%s/%s[%s]", path, subName, thumbprintBuffer) < 0) if(mp_snprintf(fileNameBuf, fileNameLen, "%s/%s[%s]", path, subName,
thumbprintBuffer) < 0)
retval = UA_STATUSCODE_BADINTERNALERROR; retval = UA_STATUSCODE_BADINTERNALERROR;
UA_String_clear(&thumbprint); UA_String_clear(&thumbprint);
@ -168,54 +162,54 @@ static UA_StatusCode
readCertificates(UA_ByteString **list, size_t *listSize, const UA_String path) { readCertificates(UA_ByteString **list, size_t *listSize, const UA_String path) {
UA_StatusCode retval = UA_STATUSCODE_GOOD; UA_StatusCode retval = UA_STATUSCODE_GOOD;
char listPath[PATH_MAX] = {0}; char listPath[UA_PATH_MAX] = {0};
mp_snprintf(listPath, PATH_MAX, "%.*s", mp_snprintf(listPath, UA_PATH_MAX, "%.*s",
(int)path.length, (char*)path.data); (int)path.length, (char*)path.data);
/* Determine number of certificates */ /* Determine number of certificates */
size_t numCerts = 0; size_t numCerts = 0;
DIR *dir = opendir(listPath); UA_DIR *dir = UA_opendir(listPath);
if(!dir) if(!dir)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
struct dirent *dirent; struct UA_DIRENT *dirent;
while((dirent = readdir(dir)) != NULL) { while((dirent = UA_readdir(dir)) != NULL) {
if(dirent->d_type != DT_REG) if(dirent->d_type != UA_DT_REG)
continue; continue;
numCerts++; numCerts++;
} }
retval = UA_Array_resize((void **)list, listSize, numCerts, &UA_TYPES[UA_TYPES_BYTESTRING]); retval = UA_Array_resize((void **)list, listSize, numCerts, &UA_TYPES[UA_TYPES_BYTESTRING]);
if(retval != UA_STATUSCODE_GOOD) { if(retval != UA_STATUSCODE_GOOD) {
closedir(dir); UA_closedir(dir);
return retval; return retval;
} }
/* Read files from directory */ /* Read files from directory */
size_t numActCerts = 0; size_t numActCerts = 0;
rewinddir(dir); UA_rewinddir(dir);
while((dirent = readdir(dir)) != NULL) { while((dirent = UA_readdir(dir)) != NULL) {
if(dirent->d_type != DT_REG) if(dirent->d_type != UA_DT_REG)
continue; continue;
if(numActCerts < numCerts) { if(numActCerts < numCerts) {
/* Create filename to load */ /* Create filename to load */
char filename[PATH_MAX]; char filename[UA_PATH_MAX] = {0};
if(mp_snprintf(filename, PATH_MAX, "%s/%s", listPath, dirent->d_name) < 0) { if(mp_snprintf(filename, UA_PATH_MAX, "%s/%s", listPath, dirent->d_name) < 0) {
closedir(dir); UA_closedir(dir);
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
} }
/* Load data from file */ /* Load data from file */
retval = readFileToByteString(filename, &((*list)[numActCerts])); retval = readFileToByteString(filename, &((*list)[numActCerts]));
if(retval != UA_STATUSCODE_GOOD) { if(retval != UA_STATUSCODE_GOOD) {
closedir(dir); UA_closedir(dir);
return retval; return retval;
} }
} }
numActCerts++; numActCerts++;
} }
closedir(dir); UA_closedir(dir);
return retval; return retval;
} }
@ -265,12 +259,17 @@ reloadTrustStore(UA_CertificateGroup *certGroup) {
if(certGroup == NULL) if(certGroup == NULL)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
#ifdef __linux__
FileCertStore *context = (FileCertStore *)certGroup->context; FileCertStore *context = (FileCertStore *)certGroup->context;
char buffer[BUF_LEN]; char buffer[BUF_LEN];
const int length = read(context->inotifyFd, buffer, BUF_LEN ); const int length = read(context->inotifyFd, buffer, BUF_LEN );
if(length == -1 && errno != EAGAIN) if(length == -1 && errno != EAGAIN)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
#else
/* TODO: Implement a way to check for changes in the pki folder */
const int length = 0;
#endif /* __linux__ */
/* No events, which means no changes to the pki folder */ /* No events, which means no changes to the pki folder */
/* If the nonblocking read() found no events to read, then /* If the nonblocking read() found no events to read, then
@ -294,8 +293,8 @@ writeCertificates(UA_CertificateGroup *certGroup, const UA_ByteString *list,
UA_StatusCode retval = UA_STATUSCODE_GOOD; UA_StatusCode retval = UA_STATUSCODE_GOOD;
for(size_t i = 0; i < listSize; i++) { for(size_t i = 0; i < listSize; i++) {
/* Create filename to load */ /* Create filename to load */
char filename[PATH_MAX]; char filename[UA_PATH_MAX] = {0};
retval = getCertFileName(listPath, &list[i], filename, PATH_MAX); retval = getCertFileName(listPath, &list[i], filename, UA_PATH_MAX);
if(retval != UA_STATUSCODE_GOOD) if(retval != UA_STATUSCODE_GOOD)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
@ -317,8 +316,8 @@ writeTrustList(UA_CertificateGroup *certGroup, const UA_ByteString *list,
if(listSize > 0 && list == NULL) if(listSize > 0 && list == NULL)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
char listPath[PATH_MAX] = {0}; char listPath[UA_PATH_MAX] = {0};
mp_snprintf(listPath, PATH_MAX, "%.*s", (int)path.length, (char*)path.data); mp_snprintf(listPath, UA_PATH_MAX, "%.*s", (int)path.length, (char *)path.data);
/* remove existing files in directory */ /* remove existing files in directory */
UA_StatusCode retval = removeAllFilesFromDir(listPath, false); UA_StatusCode retval = removeAllFilesFromDir(listPath, false);
if(retval != UA_STATUSCODE_GOOD) if(retval != UA_STATUSCODE_GOOD)
@ -374,13 +373,13 @@ writeTrustStore(UA_CertificateGroup *certGroup, const UA_UInt32 trustListMask) {
static UA_StatusCode static UA_StatusCode
FileCertStore_setupStorePath(char *directory, char *rootDirectory, FileCertStore_setupStorePath(char *directory, char *rootDirectory,
size_t rootDirectorySize, UA_String *out) { size_t rootDirectorySize, UA_String *out) {
char path[PATH_MAX] = {0}; char path[UA_PATH_MAX] = {0};
size_t pathSize = 0; size_t pathSize = 0;
strncpy(path, rootDirectory, PATH_MAX); strncpy(path, rootDirectory, UA_PATH_MAX);
pathSize = strnlen(path, PATH_MAX); pathSize = strnlen(path, UA_PATH_MAX);
strncpy(&path[pathSize], directory, PATH_MAX - pathSize); strncpy(&path[pathSize], directory, UA_PATH_MAX - pathSize);
*out = UA_STRING_ALLOC(path); *out = UA_STRING_ALLOC(path);
@ -397,14 +396,14 @@ FileCertStore_createPkiDirectory(UA_CertificateGroup *certGroup, const UA_String
if(!context) if(!context)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
char rootDirectory[PATH_MAX] = {0}; char rootDirectory[UA_PATH_MAX] = {0};
size_t rootDirectorySize = 0; size_t rootDirectorySize = 0;
if(directory.length <= 0 || directory.length >= PATH_MAX) if(directory.length <= 0 || directory.length >= UA_PATH_MAX)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
memcpy(rootDirectory, directory.data, directory.length); memcpy(rootDirectory, directory.data, directory.length);
rootDirectorySize = strnlen(rootDirectory, PATH_MAX); rootDirectorySize = strnlen(rootDirectory, UA_PATH_MAX);
/* Add Certificate Group Id */ /* Add Certificate Group Id */
UA_NodeId applCertGroup = UA_NodeId applCertGroup =
@ -415,19 +414,19 @@ FileCertStore_createPkiDirectory(UA_CertificateGroup *certGroup, const UA_String
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTUSERTOKENGROUP); UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTUSERTOKENGROUP);
if(UA_NodeId_equal(&certGroup->certificateGroupId, &applCertGroup)) { if(UA_NodeId_equal(&certGroup->certificateGroupId, &applCertGroup)) {
strncpy(&rootDirectory[rootDirectorySize], "/ApplCerts", PATH_MAX - rootDirectorySize); strncpy(&rootDirectory[rootDirectorySize], "/ApplCerts", UA_PATH_MAX - rootDirectorySize);
} else if(UA_NodeId_equal(&certGroup->certificateGroupId, &httpCertGroup)) { } else if(UA_NodeId_equal(&certGroup->certificateGroupId, &httpCertGroup)) {
strncpy(&rootDirectory[rootDirectorySize], "/HttpCerts", PATH_MAX - rootDirectorySize); strncpy(&rootDirectory[rootDirectorySize], "/HttpCerts", UA_PATH_MAX - rootDirectorySize);
} else if(UA_NodeId_equal(&certGroup->certificateGroupId, &userTokenCertGroup)) { } else if(UA_NodeId_equal(&certGroup->certificateGroupId, &userTokenCertGroup)) {
strncpy(&rootDirectory[rootDirectorySize], "/UserTokenCerts", PATH_MAX - rootDirectorySize); strncpy(&rootDirectory[rootDirectorySize], "/UserTokenCerts", UA_PATH_MAX - rootDirectorySize);
} else { } else {
UA_String nodeIdStr; UA_String nodeIdStr;
UA_String_init(&nodeIdStr); UA_String_init(&nodeIdStr);
UA_NodeId_print(&certGroup->certificateGroupId, &nodeIdStr); UA_NodeId_print(&certGroup->certificateGroupId, &nodeIdStr);
strncpy(&rootDirectory[rootDirectorySize], (char*)nodeIdStr.data, PATH_MAX - rootDirectorySize); strncpy(&rootDirectory[rootDirectorySize], (char *)nodeIdStr.data, UA_PATH_MAX - rootDirectorySize);
UA_String_clear(&nodeIdStr); UA_String_clear(&nodeIdStr);
} }
rootDirectorySize = strnlen(rootDirectory, PATH_MAX); rootDirectorySize = strnlen(rootDirectory, UA_PATH_MAX);
context->rootFolder = UA_STRING_ALLOC(rootDirectory); context->rootFolder = UA_STRING_ALLOC(rootDirectory);
@ -450,6 +449,8 @@ FileCertStore_createPkiDirectory(UA_CertificateGroup *certGroup, const UA_String
return retval; return retval;
} }
#ifdef __linux__
static UA_StatusCode static UA_StatusCode
FileCertStore_createInotifyEvent(UA_CertificateGroup *certGroup) { FileCertStore_createInotifyEvent(UA_CertificateGroup *certGroup) {
if(certGroup == NULL) if(certGroup == NULL)
@ -461,10 +462,19 @@ FileCertStore_createInotifyEvent(UA_CertificateGroup *certGroup) {
if(context->inotifyFd == -1) if(context->inotifyFd == -1)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
char rootFolder[PATH_MAX] = {0}; char folder[UA_PATH_MAX] = {0};
mp_snprintf(rootFolder, PATH_MAX, "%.*s", mp_snprintf(folder, UA_PATH_MAX, "%.*s",
(int)context->rootFolder.length, (char*)context->rootFolder.data); (int)context->rootFolder.length, (char*)context->rootFolder.data);
int wd = inotify_add_watch(context->inotifyFd, rootFolder, IN_ALL_EVENTS); int wd = inotify_add_watch(context->inotifyFd, folder, IN_ALL_EVENTS);
if(wd == -1) {
close(context->inotifyFd);
context->inotifyFd = -1;
return UA_STATUSCODE_BADINTERNALERROR;
}
mp_snprintf(folder, UA_PATH_MAX, "%.*s",
(int)context->trustedCertFolder.length, (char*)context->trustedCertFolder.data);
wd = inotify_add_watch(context->inotifyFd, folder, IN_ALL_EVENTS);
if(wd == -1) { if(wd == -1) {
close(context->inotifyFd); close(context->inotifyFd);
context->inotifyFd = -1; context->inotifyFd = -1;
@ -474,6 +484,8 @@ FileCertStore_createInotifyEvent(UA_CertificateGroup *certGroup) {
return UA_STATUSCODE_GOOD; return UA_STATUSCODE_GOOD;
} }
#endif /* __linux__ */
static UA_StatusCode static UA_StatusCode
FileCertStore_getTrustList(UA_CertificateGroup *certGroup, UA_TrustListDataType *trustList) { FileCertStore_getTrustList(UA_CertificateGroup *certGroup, UA_TrustListDataType *trustList) {
/* Check parameter */ /* Check parameter */
@ -625,8 +637,10 @@ FileCertStore_clear(UA_CertificateGroup *certGroup) {
UA_String_clear(&context->ownKeyFolder); UA_String_clear(&context->ownKeyFolder);
UA_String_clear(&context->rootFolder); UA_String_clear(&context->rootFolder);
#ifdef __linux__
if(context->inotifyFd > 0) if(context->inotifyFd > 0)
close(context->inotifyFd); close(context->inotifyFd);
#endif /* __linux__ */
UA_free(context); UA_free(context);
certGroup->context = NULL; certGroup->context = NULL;
@ -679,10 +693,12 @@ UA_CertificateGroup_Filestore(UA_CertificateGroup *certGroup,
goto cleanup; goto cleanup;
} }
#ifdef __linux__
retval = FileCertStore_createInotifyEvent(certGroup); retval = FileCertStore_createInotifyEvent(certGroup);
if(retval != UA_STATUSCODE_GOOD) { if(retval != UA_STATUSCODE_GOOD) {
goto cleanup; goto cleanup;
} }
#endif /* __linux__ */
retval = reloadAndWriteTrustStore(certGroup); retval = reloadAndWriteTrustStore(certGroup);
if(retval != UA_STATUSCODE_GOOD) { if(retval != UA_STATUSCODE_GOOD) {
@ -696,6 +712,6 @@ UA_CertificateGroup_Filestore(UA_CertificateGroup *certGroup,
return retval; return retval;
} }
#endif #endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
#endif #endif /* UA_ENABLE_ENCRYPTION */

View File

@ -40,7 +40,8 @@ void UA_CertificateGroup_AcceptAll(UA_CertificateGroup *certGroup) {
UA_StatusCode UA_StatusCode
UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling,
const UA_ByteString *certificate, const UA_ByteString *certificate,
const UA_String *applicationURI){ const UA_String *applicationURI,
UA_Logger *logger) {
return UA_STATUSCODE_GOOD; return UA_STATUSCODE_GOOD;
} }

View File

@ -6,11 +6,21 @@
*/ */
#include "ua_filestore_common.h" #include "ua_filestore_common.h"
#include <stdio.h>
#ifdef UA_ENABLE_ENCRYPTION #ifdef UA_ENABLE_ENCRYPTION
#ifdef __linux__ /* Linux only so far */ #if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
#ifdef UA_ARCHITECTURE_WIN32
/* TODO: Replace with a proper dirname implementation. This is a just minimal
* implementation working with correct input data. */
char *
_UA_dirname_minimal(char *path) {
char *lastSlash = strrchr(path, '/');
*lastSlash = 0;
return path;
}
#endif /* UA_ARCHITECTURE_WIN32 */
UA_StatusCode UA_StatusCode
readFileToByteString(const char *const path, UA_ByteString *data) { readFileToByteString(const char *const path, UA_ByteString *data) {
@ -18,23 +28,23 @@ readFileToByteString(const char *const path, UA_ByteString *data) {
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
/* Open the file */ /* Open the file */
FILE *fp = fopen(path, "rb"); UA_FILE *fp = UA_fopen(path, "rb");
if(!fp) if(!fp)
return UA_STATUSCODE_BADNOTFOUND; return UA_STATUSCODE_BADNOTFOUND;
/* Get the file length, allocate the data and read */ /* Get the file length, allocate the data and read */
fseek(fp, 0, SEEK_END); UA_fseek(fp, 0, UA_SEEK_END);
UA_StatusCode retval = UA_ByteString_allocBuffer(data, (size_t)ftell(fp)); UA_StatusCode retval = UA_ByteString_allocBuffer(data, (size_t)UA_ftell(fp));
if(retval == UA_STATUSCODE_GOOD) { if(retval == UA_STATUSCODE_GOOD) {
fseek(fp, 0, SEEK_SET); UA_fseek(fp, 0, UA_SEEK_SET);
size_t read = fread(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp); size_t read = UA_fread(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp);
if(read != data->length) { if(read != data->length) {
UA_ByteString_clear(data); UA_ByteString_clear(data);
} }
} else { } else {
data->length = 0; data->length = 0;
} }
fclose(fp); UA_fclose(fp);
return UA_STATUSCODE_GOOD; return UA_STATUSCODE_GOOD;
} }
@ -44,21 +54,21 @@ writeByteStringToFile(const char *const path, const UA_ByteString *data) {
UA_StatusCode retval = UA_STATUSCODE_GOOD; UA_StatusCode retval = UA_STATUSCODE_GOOD;
/* Open the file */ /* Open the file */
FILE *fp = fopen(path, "wb"); UA_FILE *fp = UA_fopen(path, "wb");
if(!fp) if(!fp)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
/* Write byte string to file */ /* Write byte string to file */
size_t len = fwrite(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp); size_t len = UA_fwrite(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp);
if(len != data->length) { if(len != data->length) {
fclose(fp); UA_fclose(fp);
retval = UA_STATUSCODE_BADINTERNALERROR; retval = UA_STATUSCODE_BADINTERNALERROR;
} }
fclose(fp); UA_fclose(fp);
return retval; return retval;
} }
#endif #endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
#endif #endif /* UA_ENABLE_ENCRYPTION */

View File

@ -5,11 +5,102 @@
* Copyright 2024 (c) Fraunhofer IOSB (Author: Noel Graf) * Copyright 2024 (c) Fraunhofer IOSB (Author: Noel Graf)
*/ */
#ifndef UA_FILESTORE_COMMON_H_
#define UA_FILESTORE_COMMON_H_
#include <open62541/util.h> #include <open62541/util.h>
#ifdef UA_ENABLE_ENCRYPTION #ifdef UA_ENABLE_ENCRYPTION
#ifdef __linux__ /* Linux only so far */ #if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
#if defined(UA_ARCHITECTURE_WIN32)
#include <direct.h>
#include <minwindef.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/stat.h>
#include "tr_dirent.h"
_UA_BEGIN_DECLS
char *
_UA_dirname_minimal(char *path);
_UA_END_DECLS
#define UA_STAT stat
#define UA_DIR DIR
#define UA_DIRENT dirent
#define UA_FILE FILE
#define UA_MODE uint16_t
#define UA_stat stat
#define UA_opendir opendir
#define UA_readdir readdir
#define UA_rewinddir rewinddir
#define UA_closedir closedir
#define UA_mkdir(path, mode) _mkdir(path)
#define UA_fopen fopen
#define UA_fread fread
#define UA_fwrite fwrite
#define UA_fseek fseek
#define UA_ftell ftell
#define UA_fclose fclose
#define UA_remove remove
#define UA_dirname _UA_dirname_minimal
#define UA_SEEK_END SEEK_END
#define UA_SEEK_SET SEEK_SET
#define UA_DT_REG DT_REG
#define UA_DT_DIR DT_DIR
#define UA_PATH_MAX MAX_PATH
#define UA_FILENAME_MAX FILENAME_MAX
#elif defined(__linux__)
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <linux/limits.h>
#include <stdio.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <unistd.h>
#ifndef __ANDROID__
#include <bits/stdio_lim.h>
#endif /* !__ANDROID__ */
#define UA_STAT stat
#define UA_DIR DIR
#define UA_DIRENT dirent
#define UA_FILE FILE
#define UA_MODE mode_t
#define UA_stat stat
#define UA_opendir opendir
#define UA_readdir readdir
#define UA_rewinddir rewinddir
#define UA_closedir closedir
#define UA_mkdir mkdir
#define UA_fopen fopen
#define UA_fread fread
#define UA_fwrite fwrite
#define UA_fseek fseek
#define UA_ftell ftell
#define UA_fclose fclose
#define UA_remove remove
#define UA_dirname dirname
#define UA_SEEK_END SEEK_END
#define UA_SEEK_SET SEEK_SET
#define UA_DT_REG DT_REG
#define UA_DT_DIR DT_DIR
#define UA_PATH_MAX PATH_MAX
#define UA_FILENAME_MAX FILENAME_MAX
#endif
UA_StatusCode UA_StatusCode
readFileToByteString(const char *const path, readFileToByteString(const char *const path,
@ -19,6 +110,8 @@ UA_StatusCode
writeByteStringToFile(const char *const path, writeByteStringToFile(const char *const path,
const UA_ByteString *data); const UA_ByteString *data);
#endif /* __linux__ */ #endif /* __linux__ || UA_ARCHITECTURE_WIN32 */
#endif /* UA_ENABLE_ENCRYPTION */ #endif /* UA_ENABLE_ENCRYPTION */
#endif /* UA_FILESTORE_COMMON_H_ */

View File

@ -14,14 +14,7 @@
#ifdef UA_ENABLE_ENCRYPTION #ifdef UA_ENABLE_ENCRYPTION
#ifdef __linux__ /* Linux only so far */ #if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
#include <stdio.h>
#include <dirent.h>
#ifndef __ANDROID__
#include <bits/stdio_lim.h>
#endif // !__ANDROID__
typedef struct { typedef struct {
/* In-Memory security policy as a base */ /* In-Memory security policy as a base */
@ -35,18 +28,18 @@ checkCertificateInFilestore(char *path, const UA_ByteString newCertificate) {
UA_StatusCode retval = UA_STATUSCODE_GOOD; UA_StatusCode retval = UA_STATUSCODE_GOOD;
bool isStored = false; bool isStored = false;
UA_ByteString fileData = UA_BYTESTRING_NULL; UA_ByteString fileData = UA_BYTESTRING_NULL;
DIR *dir = opendir(path); UA_DIR *dir = UA_opendir(path);
if(!dir) if(!dir)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
struct dirent *dirent; struct UA_DIRENT *dirent;
while((dirent = readdir(dir)) != NULL) { while((dirent = UA_readdir(dir)) != NULL) {
if(dirent->d_type != DT_REG) if(dirent->d_type != UA_DT_REG)
continue; continue;
/* Get filename to load */ /* Get filename to load */
char filename[FILENAME_MAX]; char filename[UA_FILENAME_MAX];
if(mp_snprintf(filename, FILENAME_MAX, "%s/%s", path, dirent->d_name) < 0) if(mp_snprintf(filename, UA_FILENAME_MAX, "%s/%s", path, dirent->d_name) < 0)
return false; return false;
/* Load data from file */ /* Load data from file */
@ -66,7 +59,7 @@ checkCertificateInFilestore(char *path, const UA_ByteString newCertificate) {
cleanup: cleanup:
if(fileData.length > 0) if(fileData.length > 0)
UA_ByteString_clear(&fileData); UA_ByteString_clear(&fileData);
closedir(dir); UA_closedir(dir);
return isStored; return isStored;
} }
@ -81,7 +74,7 @@ createCertName(const UA_ByteString *certificate, char *fileNameBuf, size_t fileN
UA_String thumbprint = UA_STRING_NULL; UA_String thumbprint = UA_STRING_NULL;
thumbprint.length = 40; thumbprint.length = 40;
thumbprint.data = (UA_Byte*)UA_malloc(sizeof(UA_Byte)*thumbprint.length); thumbprint.data = (UA_Byte*)UA_calloc(thumbprint.length, sizeof(UA_Byte));
UA_String subjectName = UA_STRING_NULL; UA_String subjectName = UA_STRING_NULL;
@ -129,13 +122,13 @@ writeCertificateAndPrivateKeyToFilestore(const UA_String storePath,
UA_StatusCode retval = UA_STATUSCODE_GOOD; UA_StatusCode retval = UA_STATUSCODE_GOOD;
/* Create the paths to the certificates and private key folders */ /* Create the paths to the certificates and private key folders */
char ownCertPathDir[PATH_MAX]; char ownCertPathDir[UA_PATH_MAX];
if(mp_snprintf(ownCertPathDir, PATH_MAX, "%.*s%s", (int)storePath.length, if(mp_snprintf(ownCertPathDir, UA_PATH_MAX, "%.*s%s", (int)storePath.length,
(char *)storePath.data, "/ApplCerts/own/certs") < 0) (char *)storePath.data, "/ApplCerts/own/certs") < 0)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
char ownKeyPathDir[PATH_MAX]; char ownKeyPathDir[UA_PATH_MAX];
if(mp_snprintf(ownKeyPathDir, PATH_MAX, "%.*s%s", (int)storePath.length, if(mp_snprintf(ownKeyPathDir, UA_PATH_MAX, "%.*s%s", (int)storePath.length,
(char *)storePath.data, "/ApplCerts/own/private") < 0) (char *)storePath.data, "/ApplCerts/own/private") < 0)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
@ -144,24 +137,28 @@ writeCertificateAndPrivateKeyToFilestore(const UA_String storePath,
return UA_STATUSCODE_GOOD; return UA_STATUSCODE_GOOD;
/* Create filename for new certificate */ /* Create filename for new certificate */
char newFilename[PATH_MAX]; char newFilename[UA_PATH_MAX];
retval = createCertName(&newCertificate, newFilename, PATH_MAX); retval = createCertName(&newCertificate, newFilename, UA_PATH_MAX);
if(retval != UA_STATUSCODE_GOOD) if(retval != UA_STATUSCODE_GOOD)
return retval; return retval;
char newCertFilename[PATH_MAX]; char newCertFilename[UA_PATH_MAX];
if(mp_snprintf(newCertFilename, PATH_MAX, "%s/%s%s", ownCertPathDir, newFilename, ".der") < 0) if(mp_snprintf(newCertFilename, UA_PATH_MAX, "%s/%s%s", ownCertPathDir, newFilename, ".der") < 0)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
char newKeyFilename[PATH_MAX]; char newKeyFilename[UA_PATH_MAX];
if(mp_snprintf(newKeyFilename, PATH_MAX, "%s/%s%s", ownKeyPathDir, newFilename, ".key") < 0) if(mp_snprintf(newKeyFilename, UA_PATH_MAX, "%s/%s%s", ownKeyPathDir, newFilename, ".key") < 0)
return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_BADINTERNALERROR;
retval = writeByteStringToFile(newCertFilename, &newCertificate); retval = writeByteStringToFile(newCertFilename, &newCertificate);
if(retval != UA_STATUSCODE_GOOD) if(retval != UA_STATUSCODE_GOOD)
return retval; return retval;
return writeByteStringToFile(newKeyFilename, &newPrivateKey); /* Write new private key only if it is set */
if(newPrivateKey.length > 0)
return writeByteStringToFile(newKeyFilename, &newPrivateKey);
else
return UA_STATUSCODE_GOOD;
} }
/********************/ /********************/
@ -347,6 +344,6 @@ UA_SecurityPolicy_Filestore(UA_SecurityPolicy *policy,
return retval; return retval;
} }
#endif #endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
#endif #endif /* UA_ENABLE_ENCRYPTION */

View File

@ -39,7 +39,7 @@ UA_CertificateGroup_Memorystore(UA_CertificateGroup *certGroup,
const UA_Logger *logger, const UA_Logger *logger,
const UA_KeyValueMap *params); const UA_KeyValueMap *params);
#ifdef __linux__ /* Linux only so far */ #if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
/* /*
* Initialises and configures a certificate group with a filestore backend. * Initialises and configures a certificate group with a filestore backend.
* *
@ -92,9 +92,9 @@ UA_CertificateGroup_Filestore(UA_CertificateGroup *certGroup,
const UA_String storePath, const UA_String storePath,
const UA_Logger *logger, const UA_Logger *logger,
const UA_KeyValueMap *params); const UA_KeyValueMap *params);
#endif #endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
#endif #endif /* UA_ENABLE_ENCRYPTION */
_UA_END_DECLS _UA_END_DECLS

View File

@ -58,14 +58,14 @@ UA_SecurityPolicy_EccNistP256(UA_SecurityPolicy *policy,
const UA_ByteString localPrivateKey, const UA_ByteString localPrivateKey,
const UA_Logger *logger); const UA_Logger *logger);
#ifdef __linux__ /* Linux only so far */ #if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
UA_EXPORT UA_StatusCode UA_EXPORT UA_StatusCode
UA_SecurityPolicy_Filestore(UA_SecurityPolicy *policy, UA_SecurityPolicy_Filestore(UA_SecurityPolicy *policy,
UA_SecurityPolicy *innerPolicy, UA_SecurityPolicy *innerPolicy,
const UA_String storePath); const UA_String storePath);
#endif #endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
#endif #endif /* UA_ENABLE_ENCRYPTION */
UA_EXPORT UA_StatusCode UA_EXPORT UA_StatusCode
UA_PubSubSecurityPolicy_Aes128Ctr(UA_PubSubSecurityPolicy *policy, UA_PubSubSecurityPolicy_Aes128Ctr(UA_PubSubSecurityPolicy *policy,

View File

@ -86,7 +86,7 @@ UA_ServerConfig_setDefaultWithSecureSecurityPolicies(UA_ServerConfig *conf,
const UA_ByteString *revocationList, const UA_ByteString *revocationList,
size_t revocationListSize); size_t revocationListSize);
#ifdef __linux__ /* Linux only so far */ #if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
UA_EXPORT UA_StatusCode UA_EXPORT UA_StatusCode
UA_ServerConfig_setDefaultWithFilestore(UA_ServerConfig *conf, UA_ServerConfig_setDefaultWithFilestore(UA_ServerConfig *conf,
@ -95,9 +95,9 @@ UA_ServerConfig_setDefaultWithFilestore(UA_ServerConfig *conf,
const UA_ByteString *privateKey, const UA_ByteString *privateKey,
const UA_String storePath); const UA_String storePath);
#endif #endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
#endif #endif /* UA_ENABLE_ENCRYPTION */
/* Creates a server config on the default port 4840 with no server /* Creates a server config on the default port 4840 with no server
* certificate. */ * certificate. */
@ -257,7 +257,7 @@ UA_ServerConfig_addAllSecureSecurityPolicies(UA_ServerConfig *config,
const UA_ByteString *certificate, const UA_ByteString *certificate,
const UA_ByteString *privateKey); const UA_ByteString *privateKey);
#ifdef __linux__ /* Linux only so far */ #if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
/* Adds a filestore security policy based on a given security policy to the server. /* Adds a filestore security policy based on a given security policy to the server.
* *
@ -289,9 +289,9 @@ UA_ServerConfig_addSecurityPolicies_Filestore(UA_ServerConfig *config,
const UA_ByteString *certificate, const UA_ByteString *certificate,
const UA_ByteString *privateKey, const UA_ByteString *privateKey,
const UA_String storePath); const UA_String storePath);
#endif #endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
#endif #endif /* UA_ENABLE_ENCRYPTION */
/* Adds an endpoint for the given security policy and mode. The security /* Adds an endpoint for the given security policy and mode. The security
* policy has to be added already. See UA_ServerConfig_addXxx functions. * policy has to be added already. See UA_ServerConfig_addXxx functions.

View File

@ -207,8 +207,7 @@ const UA_ConnectionConfig UA_ConnectionConfig_default = {
#define PRODUCT_NAME "open62541 OPC UA Server" #define PRODUCT_NAME "open62541 OPC UA Server"
#define PRODUCT_URI "http://open62541.org" #define PRODUCT_URI "http://open62541.org"
#define APPLICATION_NAME "open62541-based OPC UA Application" #define APPLICATION_NAME "open62541-based OPC UA Application"
#define APPLICATION_URI "urn:unconfigured:application" #define APPLICATION_URI "urn:open62541.unconfigured.application"
#define APPLICATION_URI_SERVER "urn:open62541.server.application"
#define SECURITY_POLICY_SIZE 7 #define SECURITY_POLICY_SIZE 7
@ -368,7 +367,7 @@ setDefaultConfig(UA_ServerConfig *conf, UA_UInt16 portNumber) {
conf->buildInfo.buildDate = UA_DateTime_now(); conf->buildInfo.buildDate = UA_DateTime_now();
UA_ApplicationDescription_clear(&conf->applicationDescription); UA_ApplicationDescription_clear(&conf->applicationDescription);
conf->applicationDescription.applicationUri = UA_STRING_ALLOC(APPLICATION_URI_SERVER); conf->applicationDescription.applicationUri = UA_STRING_ALLOC(APPLICATION_URI);
conf->applicationDescription.productUri = UA_STRING_ALLOC(PRODUCT_URI); conf->applicationDescription.productUri = UA_STRING_ALLOC(PRODUCT_URI);
conf->applicationDescription.applicationName = conf->applicationDescription.applicationName =
UA_LOCALIZEDTEXT_ALLOC("en", APPLICATION_NAME); UA_LOCALIZEDTEXT_ALLOC("en", APPLICATION_NAME);
@ -380,10 +379,12 @@ setDefaultConfig(UA_ServerConfig *conf, UA_UInt16 portNumber) {
#ifdef UA_ENABLE_DISCOVERY_MULTICAST #ifdef UA_ENABLE_DISCOVERY_MULTICAST
UA_MdnsDiscoveryConfiguration_clear(&conf->mdnsConfig); UA_MdnsDiscoveryConfiguration_clear(&conf->mdnsConfig);
# ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD
conf->mdnsInterfaceIP = UA_STRING_NULL; conf->mdnsInterfaceIP = UA_STRING_NULL;
# if !defined(UA_HAS_GETIFADDR) # if !defined(UA_HAS_GETIFADDR)
conf->mdnsIpAddressList = NULL; conf->mdnsIpAddressList = NULL;
conf->mdnsIpAddressListSize = 0; conf->mdnsIpAddressListSize = 0;
# endif
# endif # endif
#endif #endif
@ -1220,7 +1221,7 @@ UA_ServerConfig_setDefaultWithSecureSecurityPolicies(UA_ServerConfig *conf,
return UA_STATUSCODE_GOOD; return UA_STATUSCODE_GOOD;
} }
#ifdef __linux__ /* Linux only so far */ #if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
UA_StatusCode UA_StatusCode
UA_ServerConfig_addSecurityPolicy_Filestore(UA_ServerConfig *config, UA_ServerConfig_addSecurityPolicy_Filestore(UA_ServerConfig *config,
@ -1579,9 +1580,9 @@ UA_ServerConfig_setDefaultWithFilestore(UA_ServerConfig *conf,
return retval; return retval;
} }
#endif #endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */
#endif #endif /* UA_ENABLE_ENCRYPTION */
/***************************/ /***************************/
/* Default Client Settings */ /* Default Client Settings */

View File

@ -449,6 +449,7 @@ PARSE_JSON(MdnsConfigurationField) {
parseJsonJumpTable[UA_SERVERCONFIGFIELD_STRING](ctx, &config->mdnsConfig.mdnsServerName, NULL); parseJsonJumpTable[UA_SERVERCONFIGFIELD_STRING](ctx, &config->mdnsConfig.mdnsServerName, NULL);
else if(strcmp(field_str, "serverCapabilities") == 0) else if(strcmp(field_str, "serverCapabilities") == 0)
parseJsonJumpTable[UA_SERVERCONFIGFIELD_STRINGARRAY](ctx, &config->mdnsConfig.serverCapabilities, &config->mdnsConfig.serverCapabilitiesSize); parseJsonJumpTable[UA_SERVERCONFIGFIELD_STRINGARRAY](ctx, &config->mdnsConfig.serverCapabilities, &config->mdnsConfig.serverCapabilitiesSize);
#ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD
else if(strcmp(field_str, "mdnsInterfaceIP") == 0) else if(strcmp(field_str, "mdnsInterfaceIP") == 0)
parseJsonJumpTable[UA_SERVERCONFIGFIELD_STRING](ctx, &config->mdnsInterfaceIP, NULL); parseJsonJumpTable[UA_SERVERCONFIGFIELD_STRING](ctx, &config->mdnsInterfaceIP, NULL);
/* mdnsIpAddressList and mdnsIpAddressListSize are only available if UA_HAS_GETIFADDR is not defined: */ /* mdnsIpAddressList and mdnsIpAddressListSize are only available if UA_HAS_GETIFADDR is not defined: */
@ -456,6 +457,7 @@ PARSE_JSON(MdnsConfigurationField) {
else if(strcmp(field_str, "mdnsIpAddressList") == 0) else if(strcmp(field_str, "mdnsIpAddressList") == 0)
parseJsonJumpTable[UA_SERVERCONFIGFIELD_UINT32ARRAY](ctx, &config->mdnsIpAddressList, &config->mdnsIpAddressListSize); parseJsonJumpTable[UA_SERVERCONFIGFIELD_UINT32ARRAY](ctx, &config->mdnsIpAddressList, &config->mdnsIpAddressListSize);
# endif # endif
#endif
else { else {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown field name."); UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown field name.");
} }
@ -771,7 +773,7 @@ PARSE_JSON(SecurityPkiField) {
if(retval != UA_STATUSCODE_GOOD) if(retval != UA_STATUSCODE_GOOD)
return retval; return retval;
#ifndef __linux__ #if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32)
/* Currently not supported! */ /* Currently not supported! */
(void)config; (void)config;
return UA_STATUSCODE_GOOD; return UA_STATUSCODE_GOOD;
@ -957,10 +959,6 @@ parseJSONConfig(UA_ServerConfig *config, UA_ByteString json_config) {
retval = parseJsonJumpTable[UA_SERVERCONFIGFIELD_BOOLEAN](&ctx, &config->mdnsEnabled, NULL); retval = parseJsonJumpTable[UA_SERVERCONFIGFIELD_BOOLEAN](&ctx, &config->mdnsEnabled, NULL);
else if(strcmp(field, "mdns") == 0) else if(strcmp(field, "mdns") == 0)
retval = parseJsonJumpTable[UA_SERVERCONFIGFIELD_MDNSCONFIGURATION](&ctx, config, NULL); retval = parseJsonJumpTable[UA_SERVERCONFIGFIELD_MDNSCONFIGURATION](&ctx, config, NULL);
#if !defined(UA_HAS_GETIFADDR)
else if(strcmp(field, "mdnsIpAddressList") == 0)
retval = parseJsonJumpTable[UA_SERVERCONFIGFIELD_UINT32ARRAY](&ctx, &config->mdnsIpAddressList, &config->mdnsIpAddressListSize);
#endif
#endif #endif
#endif #endif

View File

@ -42,7 +42,7 @@ const char *logLevelNames[6] = {"trace", "debug",
static const char * static const char *
logCategoryNames[UA_LOGCATEGORIES] = logCategoryNames[UA_LOGCATEGORIES] =
{"network", "channel", "session", "server", "client", {"network", "channel", "session", "server", "client",
"userland", "securitypolicy", "eventloop", "pubsub", "discovery"}; "userland", "security", "eventloop", "pubsub", "discovery"};
/* Protect crosstalk during logging via global lock. /* Protect crosstalk during logging via global lock.
* Use a spinlock on non-POSIX as we cannot statically initialize a global lock. */ * Use a spinlock on non-POSIX as we cannot statically initialize a global lock. */

View File

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

View File

@ -121,7 +121,27 @@ UA_Client_newWithConfig(const UA_ClientConfig *config) {
UA_LOCK_INIT(&client->clientMutex); UA_LOCK_INIT(&client->clientMutex);
#endif #endif
/* Initialize the namespace mapping */
size_t initialNs = 2 + config->namespacesSize;
client->namespaces = (UA_String*)UA_calloc(initialNs, sizeof(UA_String));
if(!client->namespaces)
goto error;
client->namespacesSize = initialNs;
client->namespaces[0] = UA_STRING_ALLOC("http://opcfoundation.org/UA/");
client->namespaces[1] = UA_STRING_NULL; /* Gets set when we connect to the server */
UA_StatusCode res = UA_STATUSCODE_GOOD;
for(size_t i = 0; i < config->namespacesSize; i++) {
res |= UA_String_copy(&client->namespaces[i+2], &config->namespaces[i]);
}
if(res != UA_STATUSCODE_GOOD)
goto error;
return client; return client;
error:
memset(&client->config, 0, sizeof(UA_ClientConfig));
UA_Client_delete(client);
return NULL;
} }
void void
@ -226,8 +246,15 @@ UA_Client_clear(UA_Client *client) {
UA_Client_removeCallback(client, client->houseKeepingCallbackId); UA_Client_removeCallback(client, client->houseKeepingCallbackId);
client->houseKeepingCallbackId = 0; client->houseKeepingCallbackId = 0;
/* Clean up the SecureChannel */
UA_SecureChannel_clear(&client->channel); UA_SecureChannel_clear(&client->channel);
/* Free the namespace mapping */
UA_Array_delete(client->namespaces, client->namespacesSize,
&UA_TYPES[UA_TYPES_STRING]);
client->namespaces = NULL;
client->namespacesSize = 0;
#if UA_MULTITHREADING >= 100 #if UA_MULTITHREADING >= 100
UA_LOCK_DESTROY(&client->clientMutex); UA_LOCK_DESTROY(&client->clientMutex);
#endif #endif
@ -1168,3 +1195,48 @@ UA_Client_getConnectionAttribute_scalar(UA_Client *client,
UA_UNLOCK(&client->clientMutex); UA_UNLOCK(&client->clientMutex);
return UA_STATUSCODE_GOOD; return UA_STATUSCODE_GOOD;
} }
/* Namespace Mapping */
UA_StatusCode
UA_Client_getNamespaceUri(UA_Client *client, UA_UInt16 index,
UA_String *nsUri) {
UA_LOCK(&client->clientMutex);
UA_StatusCode res = UA_STATUSCODE_GOOD;
if(index > client->namespacesSize)
res = UA_String_copy(&client->namespaces[index], nsUri);
else
res = UA_STATUSCODE_BADNOTFOUND;
UA_UNLOCK(&client->clientMutex);
return res;
}
UA_StatusCode
UA_Client_getNamespaceIndex(UA_Client *client, const UA_String nsUri,
UA_UInt16 *outIndex) {
UA_LOCK(&client->clientMutex);
for(size_t i = 0; i < client->namespacesSize; i++) {
if(UA_String_equal(&nsUri, &client->namespaces[i])) {
*outIndex = (UA_UInt16)i;
UA_UNLOCK(&client->clientMutex);
return UA_STATUSCODE_GOOD;
}
}
UA_UNLOCK(&client->clientMutex);
return UA_STATUSCODE_BADNOTFOUND;
}
UA_StatusCode
UA_Client_addNamespace(UA_Client *client, const UA_String nsUri,
UA_UInt16 *outIndex) {
UA_StatusCode res = UA_Client_getNamespaceIndex(client, nsUri, outIndex);
if(res == UA_STATUSCODE_GOOD)
return res;
UA_LOCK(&client->clientMutex);
res = UA_Array_appendCopy((void**)&client->namespaces, &client->namespacesSize,
&nsUri, &UA_TYPES[UA_TYPES_STRING]);
if(res == UA_STATUSCODE_GOOD)
*outIndex = (UA_UInt16)(client->namespacesSize - 1);
UA_UNLOCK(&client->clientMutex);
return res;
}

View File

@ -25,6 +25,7 @@
* UA_Client and reconnect. * UA_Client and reconnect.
* - Call GetEndpoints and select an Endpoint * - Call GetEndpoints and select an Endpoint
* - Open a SecureChannel and Session for that Endpoint * - Open a SecureChannel and Session for that Endpoint
* - Read the namespaces array of the server (and create the namespaces mapping)
*/ */
#define UA_MINMESSAGESIZE 8192 #define UA_MINMESSAGESIZE 8192
@ -429,7 +430,7 @@ sendHELMessage(UA_Client *client) {
const UA_Byte *bufEnd = &message.data[message.length]; const UA_Byte *bufEnd = &message.data[message.length];
client->connectStatus = client->connectStatus =
UA_encodeBinaryInternal(&hello, &UA_TRANSPORT[UA_TRANSPORT_TCPHELLOMESSAGE], UA_encodeBinaryInternal(&hello, &UA_TRANSPORT[UA_TRANSPORT_TCPHELLOMESSAGE],
&bufPos, &bufEnd, NULL, NULL); &bufPos, &bufEnd, NULL, NULL, NULL);
/* Encode the message header at offset 0 */ /* Encode the message header at offset 0 */
UA_TcpMessageHeader messageHeader; UA_TcpMessageHeader messageHeader;
@ -438,7 +439,7 @@ sendHELMessage(UA_Client *client) {
bufPos = message.data; bufPos = message.data;
retval = UA_encodeBinaryInternal(&messageHeader, retval = UA_encodeBinaryInternal(&messageHeader,
&UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], &UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER],
&bufPos, &bufEnd, NULL, NULL); &bufPos, &bufEnd, NULL, NULL, NULL);
if(retval != UA_STATUSCODE_GOOD) { if(retval != UA_STATUSCODE_GOOD) {
cm->freeNetworkBuffer(cm, client->channel.connectionId, &message); cm->freeNetworkBuffer(cm, client->channel.connectionId, &message);
return retval; return retval;
@ -649,6 +650,121 @@ UA_Client_renewSecureChannel(UA_Client *client) {
return res; return res;
} }
static void
responseReadNamespacesArray(UA_Client *client, void *userdata, UA_UInt32 requestId,
void *response) {
client->namespacesHandshake = false;
client->haveNamespaces = true;
UA_ReadResponse *resp = (UA_ReadResponse *)response;
/* Add received namespaces to the local array. */
if(!resp->results || !resp->results[0].value.data) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"No result in the read namespace array response");
return;
}
UA_String *ns = (UA_String *)resp->results[0].value.data;
size_t nsSize = resp->results[0].value.arrayLength;
UA_String_copy(&ns[1], &client->namespaces[1]);
for(size_t i = 2; i < nsSize; ++i) {
UA_UInt16 nsIndex = 0;
UA_Client_addNamespace(client, ns[i], &nsIndex);
}
/* Set up the mapping. */
UA_NamespaceMapping *nsMapping = (UA_NamespaceMapping*)UA_calloc(1, sizeof(UA_NamespaceMapping));
if(!nsMapping) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Namespace mapping creation failed. Out of Memory.");
return;
}
UA_StatusCode retval = UA_Array_copy(client->namespaces, client->namespacesSize, (void**)&nsMapping->namespaceUris, &UA_TYPES[UA_TYPES_STRING]);
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Failed to copy the namespaces with StatusCode %s.",
UA_StatusCode_name(retval));
return;
}
nsMapping->namespaceUrisSize = client->namespacesSize;
nsMapping->remote2local = (UA_UInt16*)UA_calloc( nsSize, sizeof(UA_UInt16));
if(!nsMapping->remote2local) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Namespace mapping creation failed. Out of Memory.");
return;
}
nsMapping->remote2localSize = nsSize;
nsMapping->remote2local[0] = 0;
nsMapping->remote2local[1] = 1;
for(size_t i = 2; i < nsSize; ++i) {
UA_UInt16 nsIndex = 0;
UA_Client_getNamespaceIndex(client, ns[i], &nsIndex);
nsMapping->remote2local[i] = nsIndex;
}
nsMapping->local2remote = (UA_UInt16*)UA_calloc( nsSize, sizeof(UA_UInt16));
if(!nsMapping->local2remote) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Namespace mapping creation failed. Out of Memory.");
return;
}
nsMapping->local2remoteSize = nsSize;
nsMapping->local2remote[0] = 0;
nsMapping->local2remote[1] = 1;
for(size_t i = 2; i < nsMapping->remote2localSize; ++i) {
UA_UInt16 localIndex = nsMapping->remote2local[i];
nsMapping->local2remote[localIndex] = (UA_UInt16)i;
}
client->channel.namespaceMapping = nsMapping;
}
/* We read the namespaces right after the session has opened. The user might
* already requests other services in parallel. That leaves a short time where
* requests can be made before the namespace mapping is configured. */
static void
readNamespacesArrayAsync(UA_Client *client) {
UA_LOCK_ASSERT(&client->clientMutex);
/* Check the connection status */
if(client->sessionState != UA_SESSIONSTATE_CREATED &&
client->sessionState != UA_SESSIONSTATE_ACTIVATED) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Cannot read the namespaces array, session neither created nor "
"activated. Actual state: '%u'", client->sessionState);
return;
}
/* Set up the read request */
UA_ReadRequest rr;
UA_ReadRequest_init(&rr);
UA_ReadValueId nodesToRead;
UA_ReadValueId_init(&nodesToRead);
nodesToRead.nodeId = UA_NS0ID(SERVER_NAMESPACEARRAY);
nodesToRead.attributeId = UA_ATTRIBUTEID_VALUE;
rr.nodesToRead = &nodesToRead;
rr.nodesToReadSize = 1;
/* Send the async read request */
UA_StatusCode res =
__Client_AsyncService(client, &rr, &UA_TYPES[UA_TYPES_READREQUEST],
(UA_ClientAsyncServiceCallback)responseReadNamespacesArray,
&UA_TYPES[UA_TYPES_READRESPONSE],
NULL, NULL);
if(res == UA_STATUSCODE_GOOD)
client->namespacesHandshake = true;
else
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Could not read the namespace array with error code %s",
UA_StatusCode_name(res));
}
static void static void
responseActivateSession(UA_Client *client, void *userdata, responseActivateSession(UA_Client *client, void *userdata,
UA_UInt32 requestId, void *response) { UA_UInt32 requestId, void *response) {
@ -700,6 +816,10 @@ responseActivateSession(UA_Client *client, void *userdata,
client->sessionState = UA_SESSIONSTATE_ACTIVATED; client->sessionState = UA_SESSIONSTATE_ACTIVATED;
notifyClientState(client); notifyClientState(client);
/* Read the namespaces array if we don't already have it */
if(!client->haveNamespaces)
readNamespacesArrayAsync(client);
/* Immediately check if publish requests are outstanding - for example when /* Immediately check if publish requests are outstanding - for example when
* an existing Session has been reattached / activated. */ * an existing Session has been reattached / activated. */
#ifdef UA_ENABLE_SUBSCRIPTIONS #ifdef UA_ENABLE_SUBSCRIPTIONS
@ -838,7 +958,8 @@ matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigne
!UA_String_equal(&client->config.applicationUri, !UA_String_equal(&client->config.applicationUri,
&endpoint->server.applicationUri)) { &endpoint->server.applicationUri)) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Rejecting endpoint %u: application uri not match", i); "Rejecting Endpoint %u: The server's ApplicationUri %S does not match "
"the client configuration", i, endpoint->server.applicationUri);
return false; return false;
} }
@ -847,14 +968,15 @@ matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigne
if(endpoint->transportProfileUri.length != 0 && if(endpoint->transportProfileUri.length != 0 &&
!UA_String_equal(&endpoint->transportProfileUri, &binaryTransport)) { !UA_String_equal(&endpoint->transportProfileUri, &binaryTransport)) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Rejecting endpoint %u: transport profile does not match", i); "Rejecting Endpoint %u: TransportProfileUri %S not supported",
i, endpoint->transportProfileUri);
return false; return false;
} }
/* Valid SecurityMode? */ /* Valid SecurityMode? */
if(endpoint->securityMode < 1 || endpoint->securityMode > 3) { if(endpoint->securityMode < 1 || endpoint->securityMode > 3) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Rejecting endpoint %u: invalid security mode", i); "Rejecting Endpoint %u: Invalid SecurityMode", i);
return false; return false;
} }
@ -862,7 +984,7 @@ matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigne
if(client->config.securityMode > 0 && if(client->config.securityMode > 0 &&
client->config.securityMode != endpoint->securityMode) { client->config.securityMode != endpoint->securityMode) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Rejecting endpoint %u: security mode does not match", i); "Rejecting Endpoint %u: SecurityMode does not match the configuration", i);
return false; return false;
} }
@ -870,14 +992,16 @@ matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigne
if(client->config.securityPolicyUri.length > 0 && if(client->config.securityPolicyUri.length > 0 &&
!UA_String_equal(&client->config.securityPolicyUri, &endpoint->securityPolicyUri)) { !UA_String_equal(&client->config.securityPolicyUri, &endpoint->securityPolicyUri)) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Rejecting endpoint %u: security policy does not match the configuration", i); "Rejecting Endpoint %u: SecurityPolicy %S does not match the configuration",
i, endpoint->securityPolicyUri);
return false; return false;
} }
/* SecurityPolicy available? */ /* SecurityPolicy available? */
if(!getSecurityPolicy(client, endpoint->securityPolicyUri)) { if(!getSecurityPolicy(client, endpoint->securityPolicyUri)) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Rejecting endpoint %u: security policy not available", i); "Rejecting Endpoint %u: SecurityPolicy %S not supported",
i, endpoint->securityPolicyUri);
return false; return false;
} }
@ -1004,7 +1128,7 @@ responseGetEndpoints(UA_Client *client, void *userdata,
* UserTokenPolicy that matches the configuration. */ * UserTokenPolicy that matches the configuration. */
if(!client->config.noSession && !findUserTokenPolicy(client, endpoint)) { if(!client->config.noSession && !findUserTokenPolicy(client, endpoint)) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Rejecting endpoint %lu: No matching UserTokenPolicy", "Rejecting Endpoint %lu: No matching UserTokenPolicy",
(long unsigned)i); (long unsigned)i);
continue; continue;
} }
@ -1491,13 +1615,15 @@ verifyClientApplicationURI(const UA_Client *client) {
} }
UA_StatusCode retval = UA_StatusCode retval =
UA_CertificateUtils_verifyApplicationURI(client->allowAllCertificateUris, &sp->localCertificate, UA_CertificateUtils_verifyApplicationURI(client->allowAllCertificateUris,
&client->config.clientDescription.applicationUri); &sp->localCertificate,
&client->config.clientDescription.applicationUri,
client->config.logging);
if(retval != UA_STATUSCODE_GOOD) { if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
"The configured ApplicationURI does not match the URI " "The configured ApplicationURI does not match the URI "
"specified in the certificate for the SecurityPolicy %S", "specified in the certificate for the SecurityPolicy %S",
sp->policyUri.length); sp->policyUri);
} }
} }
#endif #endif

View File

@ -125,6 +125,8 @@ struct UA_Client {
UA_Boolean findServersHandshake; /* Ongoing FindServers */ UA_Boolean findServersHandshake; /* Ongoing FindServers */
UA_Boolean endpointsHandshake; /* Ongoing GetEndpoints */ UA_Boolean endpointsHandshake; /* Ongoing GetEndpoints */
UA_Boolean namespacesHandshake; /* Ongoing Namespaces read */
UA_Boolean haveNamespaces; /* Do we have the namespaces? */
/* The discoveryUrl can be different from the EndpointUrl in the client /* The discoveryUrl can be different from the EndpointUrl in the client
* configuration. The EndpointUrl is used to connect initially, then the * configuration. The EndpointUrl is used to connect initially, then the
@ -167,6 +169,11 @@ struct UA_Client {
UA_UInt32 monitoredItemHandles; UA_UInt32 monitoredItemHandles;
UA_UInt16 currentlyOutStandingPublishRequests; UA_UInt16 currentlyOutStandingPublishRequests;
/* Internal namespaces. The table maps the namespace Uri to its index. This
* is used for the automatic namespace mapping in de/encoding. */
UA_String *namespaces;
size_t namespacesSize;
/* Internal locking for thread-safety. Methods starting with UA_Client_ that /* Internal locking for thread-safety. Methods starting with UA_Client_ that
* are marked with UA_THREADSAFE take the lock. The lock is released before * are marked with UA_THREADSAFE take the lock. The lock is released before
* dropping into the EventLoop and before calling user-defined callbacks. * dropping into the EventLoop and before calling user-defined callbacks.

View File

@ -517,33 +517,19 @@ addSubscribedDataSet(UA_PubSubManager *psm, const UA_NodeId dsReaderIdent,
if(subscribedDataSet->content.decoded.type == if(subscribedDataSet->content.decoded.type ==
&UA_TYPES[UA_TYPES_TARGETVARIABLESDATATYPE]) { &UA_TYPES[UA_TYPES_TARGETVARIABLESDATATYPE]) {
UA_TargetVariablesDataType *tmpTargetVars = (UA_TargetVariablesDataType*) UA_TargetVariablesDataType *targetVars = (UA_TargetVariablesDataType*)
subscribedDataSet->content.decoded.data; subscribedDataSet->content.decoded.data;
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *)
UA_calloc(tmpTargetVars->targetVariablesSize, sizeof(UA_FieldTargetVariable));
for(size_t index = 0; index < tmpTargetVars->targetVariablesSize; index++) {
UA_FieldTargetDataType_copy(&tmpTargetVars->targetVariables[index],
&targetVars[index].targetVariable);
}
UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR; UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR;
UA_DataSetReader *dsr = UA_DataSetReader_find(psm, dsReaderIdent); UA_DataSetReader *dsr = UA_DataSetReader_find(psm, dsReaderIdent);
if(dsr) if(dsr)
res = DataSetReader_createTargetVariables(psm, dsr, res = DataSetReader_createTargetVariables(psm, dsr,
tmpTargetVars->targetVariablesSize, targetVars->targetVariablesSize,
targetVars); targetVars->targetVariables);
if(res != UA_STATUSCODE_GOOD) { if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(psm->logging, UA_LOGCATEGORY_PUBSUB, UA_LOG_ERROR(psm->logging, UA_LOGCATEGORY_PUBSUB,
"[UA_PubSubManager_addSubscribedDataSet] " "[UA_PubSubManager_addSubscribedDataSet] "
"create TargetVariables failed"); "create TargetVariables failed");
} }
for(size_t index = 0; index < tmpTargetVars->targetVariablesSize; index++) {
UA_FieldTargetDataType_clear(&targetVars[index].targetVariable);
}
UA_free(targetVars);
return res; return res;
} }
@ -989,22 +975,21 @@ generateDataSetReaderDataType(const UA_DataSetReader *src,
UA_TargetVariablesDataType *tmpTarget = UA_TargetVariablesDataType_new(); UA_TargetVariablesDataType *tmpTarget = UA_TargetVariablesDataType_new();
if(!tmpTarget) if(!tmpTarget)
return UA_STATUSCODE_BADOUTOFMEMORY; return UA_STATUSCODE_BADOUTOFMEMORY;
UA_ExtensionObject_setValue(&dst->subscribedDataSet, tmpTarget,
&UA_TYPES[UA_TYPES_TARGETVARIABLESDATATYPE]);
const UA_TargetVariables *targets = const UA_TargetVariablesDataType *targets = &src->config.subscribedDataSet.target;
&src->config.subscribedDataSet.subscribedDataSetTarget;
tmpTarget->targetVariables = (UA_FieldTargetDataType *) tmpTarget->targetVariables = (UA_FieldTargetDataType *)
UA_calloc(targets->targetVariablesSize, sizeof(UA_FieldTargetDataType)); UA_calloc(targets->targetVariablesSize, sizeof(UA_FieldTargetDataType));
if(!tmpTarget->targetVariables) if(!tmpTarget->targetVariables)
return UA_STATUSCODE_BADOUTOFMEMORY; return UA_STATUSCODE_BADOUTOFMEMORY;
tmpTarget->targetVariablesSize = targets->targetVariablesSize; tmpTarget->targetVariablesSize = targets->targetVariablesSize;
for(size_t i = 0; i < tmpTarget->targetVariablesSize; i++) { for(size_t i = 0; i < tmpTarget->targetVariablesSize; i++) {
res |= UA_FieldTargetDataType_copy(&targets->targetVariables[i].targetVariable, res |= UA_FieldTargetDataType_copy(&targets->targetVariables[i],
&tmpTarget->targetVariables[i]); &tmpTarget->targetVariables[i]);
} }
UA_ExtensionObject_setValue(&dst->subscribedDataSet, tmpTarget,
&UA_TYPES[UA_TYPES_TARGETVARIABLESDATATYPE]);
return res; return res;
} }

View File

@ -23,7 +23,7 @@ UA_PubSubConnection_connect(UA_PubSubManager *psm, UA_PubSubConnection *c,
static void static void
UA_PubSubConnection_process(UA_PubSubManager *psm, UA_PubSubConnection *c, UA_PubSubConnection_process(UA_PubSubManager *psm, UA_PubSubConnection *c,
UA_ByteString msg); const UA_ByteString msg);
static void static void
UA_PubSubConnection_disconnect(UA_PubSubConnection *c); UA_PubSubConnection_disconnect(UA_PubSubConnection *c);
@ -250,7 +250,7 @@ UA_PubSubConnection_delete(UA_PubSubManager *psm, UA_PubSubConnection *c) {
/* The WriterGroups / ReaderGroups are not deleted. Try again in the next /* The WriterGroups / ReaderGroups are not deleted. Try again in the next
* iteration of the event loop.*/ * iteration of the event loop.*/
if(!LIST_EMPTY(&c->writerGroups) || !LIST_EMPTY(&c->readerGroups)) { if(!LIST_EMPTY(&c->writerGroups) || !LIST_EMPTY(&c->readerGroups)) {
UA_EventLoop *el = UA_PubSubConnection_getEL(psm, c); UA_EventLoop *el = psm->sc.server->config.eventLoop;
c->dc.callback = delayedPubSubConnection_delete; c->dc.callback = delayedPubSubConnection_delete;
c->dc.application = psm; c->dc.application = psm;
c->dc.context = c; c->dc.context = c;
@ -276,33 +276,21 @@ UA_PubSubConnection_delete(UA_PubSubManager *psm, UA_PubSubConnection *c) {
static void static void
UA_PubSubConnection_process(UA_PubSubManager *psm, UA_PubSubConnection *c, UA_PubSubConnection_process(UA_PubSubManager *psm, UA_PubSubConnection *c,
UA_ByteString msg) { const UA_ByteString msg) {
UA_LOG_TRACE_PUBSUB(psm->logging, c, "Processing a received buffer"); UA_LOG_TRACE_PUBSUB(psm->logging, c, "Processing a received buffer");
/* Process RT ReaderGroups */ /* Process RT ReaderGroups */
UA_ReaderGroup *rg;
UA_Boolean processed = false; UA_Boolean processed = false;
UA_ReaderGroup *nonRtRg = NULL; UA_ReaderGroup *rg = LIST_FIRST(&c->readerGroups);
LIST_FOREACH(rg, &c->readerGroups, listEntry) { /* Any interested ReaderGroups? */
if(rg->head.state != UA_PUBSUBSTATE_OPERATIONAL && if(!rg)
rg->head.state != UA_PUBSUBSTATE_PREOPERATIONAL)
continue;
if(!(rg->config.rtLevel & UA_PUBSUB_RT_FIXED_SIZE)) {
nonRtRg = rg;
continue;
}
processed |= UA_ReaderGroup_decodeAndProcessRT(psm, rg, msg);
}
/* Any non-RT ReaderGroups? */
if(!nonRtRg)
goto finish; goto finish;
/* Decode the received message for the non-RT ReaderGroups */ /* Decode the received message for the non-RT ReaderGroups */
UA_StatusCode res; UA_StatusCode res;
UA_NetworkMessage nm; UA_NetworkMessage nm;
memset(&nm, 0, sizeof(UA_NetworkMessage)); memset(&nm, 0, sizeof(UA_NetworkMessage));
if(nonRtRg->config.encodingMimeType == UA_PUBSUB_ENCODING_UADP) { if(rg->config.encodingMimeType == UA_PUBSUB_ENCODING_UADP) {
res = UA_PubSubConnection_decodeNetworkMessage(psm, c, msg, &nm); res = UA_PubSubConnection_decodeNetworkMessage(psm, c, msg, &nm);
} else { /* if(writerGroup->config.encodingMimeType == UA_PUBSUB_ENCODING_JSON) */ } else { /* if(writerGroup->config.encodingMimeType == UA_PUBSUB_ENCODING_JSON) */
#ifdef UA_ENABLE_JSON_ENCODING #ifdef UA_ENABLE_JSON_ENCODING
@ -326,8 +314,6 @@ UA_PubSubConnection_process(UA_PubSubManager *psm, UA_PubSubConnection *c,
if(rg->head.state != UA_PUBSUBSTATE_OPERATIONAL && if(rg->head.state != UA_PUBSUBSTATE_OPERATIONAL &&
rg->head.state != UA_PUBSUBSTATE_PREOPERATIONAL) rg->head.state != UA_PUBSUBSTATE_PREOPERATIONAL)
continue; continue;
if(rg->config.rtLevel & UA_PUBSUB_RT_FIXED_SIZE)
continue;
processed |= UA_ReaderGroup_process(psm, rg, &nm); processed |= UA_ReaderGroup_process(psm, rg, &nm);
} }
UA_NetworkMessage_clear(&nm); UA_NetworkMessage_clear(&nm);
@ -357,9 +343,20 @@ UA_PubSubConnection_setPubSubState(UA_PubSubManager *psm, UA_PubSubConnection *c
UA_Boolean isTransient = c->head.transientState; UA_Boolean isTransient = c->head.transientState;
c->head.transientState = true; c->head.transientState = true;
UA_Server *server = psm->sc.server;
UA_StatusCode ret = UA_STATUSCODE_GOOD; UA_StatusCode ret = UA_STATUSCODE_GOOD;
UA_PubSubState oldState = c->head.state; UA_PubSubState oldState = c->head.state;
/* Custom state machine */
if(c->config.customStateMachine) {
UA_UNLOCK(&server->serviceMutex);
ret = c->config.customStateMachine(server, c->head.identifier, c->config.context,
&c->head.state, targetState);
UA_LOCK(&server->serviceMutex);
goto finalize_state_machine;
}
/* Internal state machine */
switch(targetState) { switch(targetState) {
/* Disabled or Error */ /* Disabled or Error */
case UA_PUBSUBSTATE_ERROR: case UA_PUBSUBSTATE_ERROR:
@ -405,6 +402,8 @@ UA_PubSubConnection_setPubSubState(UA_PubSubManager *psm, UA_PubSubConnection *c
UA_PubSubConnection_disconnect(c); UA_PubSubConnection_disconnect(c);
} }
finalize_state_machine:
/* Only the top-level state update (if recursive calls are happening) /* Only the top-level state update (if recursive calls are happening)
* notifies the application and updates Reader and WriterGroups */ * notifies the application and updates Reader and WriterGroups */
c->head.transientState = isTransient; c->head.transientState = isTransient;
@ -413,15 +412,14 @@ UA_PubSubConnection_setPubSubState(UA_PubSubManager *psm, UA_PubSubConnection *c
/* Inform application about state change */ /* Inform application about state change */
if(c->head.state != oldState) { if(c->head.state != oldState) {
UA_ServerConfig *config = &psm->sc.server->config;
UA_LOG_INFO_PUBSUB(psm->logging, c, "%s -> %s", UA_LOG_INFO_PUBSUB(psm->logging, c, "%s -> %s",
UA_PubSubState_name(oldState), UA_PubSubState_name(oldState),
UA_PubSubState_name(c->head.state)); UA_PubSubState_name(c->head.state));
if(config->pubSubConfig.stateChangeCallback) { if(server->config.pubSubConfig.stateChangeCallback) {
UA_UNLOCK(&psm->sc.server->serviceMutex); UA_UNLOCK(&server->serviceMutex);
config->pubSubConfig. server->config.pubSubConfig.
stateChangeCallback(psm->sc.server, c->head.identifier, targetState, ret); stateChangeCallback(server, c->head.identifier, targetState, ret);
UA_LOCK(&psm->sc.server->serviceMutex); UA_LOCK(&server->serviceMutex);
} }
} }
@ -457,13 +455,6 @@ disablePubSubConnection(UA_PubSubManager *psm, const UA_NodeId connectionId) {
: UA_STATUSCODE_BADNOTFOUND; : UA_STATUSCODE_BADNOTFOUND;
} }
UA_EventLoop *
UA_PubSubConnection_getEL(UA_PubSubManager *psm, UA_PubSubConnection *c) {
if(c->config.eventLoop)
return c->config.eventLoop;
return psm->sc.server->config.eventLoop;
}
/***********************/ /***********************/
/* Connection Handling */ /* Connection Handling */
/***********************/ /***********************/
@ -822,7 +813,7 @@ UA_PubSubConnection_connect(UA_PubSubManager *psm, UA_PubSubConnection *c,
UA_Server *server = psm->sc.server; UA_Server *server = psm->sc.server;
UA_LOCK_ASSERT(&server->serviceMutex); UA_LOCK_ASSERT(&server->serviceMutex);
UA_EventLoop *el = UA_PubSubConnection_getEL(psm, c); UA_EventLoop *el = psm->sc.server->config.eventLoop;
if(!el) { if(!el) {
UA_LOG_ERROR_PUBSUB(psm->logging, c, "No EventLoop configured"); UA_LOG_ERROR_PUBSUB(psm->logging, c, "No EventLoop configured");
return UA_STATUSCODE_BADINTERNALERROR;; return UA_STATUSCODE_BADINTERNALERROR;;
@ -935,4 +926,29 @@ UA_Server_disablePubSubConnection(UA_Server *server, const UA_NodeId cId) {
return res; return res;
} }
UA_StatusCode
UA_Server_processPubSubConnectionReceive(UA_Server *server,
const UA_NodeId connectionId,
const UA_ByteString packet) {
if(!server)
return UA_STATUSCODE_BADINTERNALERROR;
UA_LOCK(&server->serviceMutex);
UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR;
UA_PubSubManager *psm = getPSM(server);
if(psm) {
UA_PubSubConnection *c = UA_PubSubConnection_find(psm, connectionId);
if(c) {
res = UA_STATUSCODE_GOOD;
UA_PubSubConnection_process(psm, c, packet);
} else {
res = UA_STATUSCODE_BADCONNECTIONCLOSED;
UA_LOG_WARNING_PUBSUB(psm->logging, c,
"Cannot process a packet if the "
"PubSubConnection is not operational");
}
}
UA_UNLOCK(&server->serviceMutex);
return res;
}
#endif /* UA_ENABLE_PUBSUB */ #endif /* UA_ENABLE_PUBSUB */

View File

@ -121,12 +121,9 @@ generateFieldMetaData(UA_PubSubManager *psm, UA_PublishedDataSet *pds,
const UA_DataSetVariableConfig *var = &field->config.field.variable; const UA_DataSetVariableConfig *var = &field->config.field.variable;
/* Set the field identifier */ /* Set the field identifier */
if(!UA_Guid_equal(&var->dataSetFieldId, &UA_GUID_NULL)) if(!UA_Guid_equal(&var->dataSetFieldId, &UA_GUID_NULL)) {
{
fieldMetaData->dataSetFieldId = var->dataSetFieldId; fieldMetaData->dataSetFieldId = var->dataSetFieldId;
} } else {
else
{
fieldMetaData->dataSetFieldId = UA_PubSubManager_generateUniqueGuid(psm); fieldMetaData->dataSetFieldId = UA_PubSubManager_generateUniqueGuid(psm);
} }
@ -137,32 +134,6 @@ generateFieldMetaData(UA_PubSubManager *psm, UA_PublishedDataSet *pds,
UA_StatusCode res = UA_String_copy(&var->fieldNameAlias, &fieldMetaData->name); UA_StatusCode res = UA_String_copy(&var->fieldNameAlias, &fieldMetaData->name);
UA_CHECK_STATUS(res, return res); UA_CHECK_STATUS(res, return res);
/* Static value source. ToDo after freeze PR, the value source must be
* checked (other behavior for static value source) */
if(var->rtValueSource.rtFieldSourceEnabled &&
!var->rtValueSource.rtInformationModelNode) {
const UA_DataValue *svs = *var->rtValueSource.staticValueSource;
if(svs->value.arrayDimensionsSize > 0) {
fieldMetaData->arrayDimensions = (UA_UInt32 *)
UA_calloc(svs->value.arrayDimensionsSize, sizeof(UA_UInt32));
if(fieldMetaData->arrayDimensions == NULL)
return UA_STATUSCODE_BADOUTOFMEMORY;
memcpy(fieldMetaData->arrayDimensions, svs->value.arrayDimensions,
sizeof(UA_UInt32) * svs->value.arrayDimensionsSize);
}
fieldMetaData->arrayDimensionsSize = svs->value.arrayDimensionsSize;
if(svs->value.type)
res = UA_NodeId_copy(&svs->value.type->typeId, &fieldMetaData->dataType);
UA_CHECK_STATUS(res, return res);
//TODO collect value rank for the static field source
fieldMetaData->properties = NULL;
fieldMetaData->propertiesSize = 0;
fieldMetaData->fieldFlags = UA_DATASETFIELDFLAGS_NONE;
return UA_STATUSCODE_GOOD;
}
/* Set the Array Dimensions */ /* Set the Array Dimensions */
const UA_PublishedVariableDataType *pp = &var->publishParameters; const UA_PublishedVariableDataType *pp = &var->publishParameters;
UA_Variant value; UA_Variant value;
@ -180,8 +151,6 @@ generateFieldMetaData(UA_PubSubManager *psm, UA_PublishedDataSet *pds,
UA_calloc(value.arrayDimensionsSize, sizeof(UA_UInt32)); UA_calloc(value.arrayDimensionsSize, sizeof(UA_UInt32));
if(!fieldMetaData->arrayDimensions) if(!fieldMetaData->arrayDimensions)
return UA_STATUSCODE_BADOUTOFMEMORY; return UA_STATUSCODE_BADOUTOFMEMORY;
memcpy(fieldMetaData->arrayDimensions, value.arrayDimensions,
sizeof(UA_UInt32)*value.arrayDimensionsSize);
} }
fieldMetaData->arrayDimensionsSize = value.arrayDimensionsSize; fieldMetaData->arrayDimensionsSize = value.arrayDimensionsSize;
@ -496,25 +465,13 @@ UA_PubSubDataSetField_sampleValue(UA_PubSubManager *psm, UA_DataSetField *field,
UA_DataValue *value) { UA_DataValue *value) {
UA_PublishedVariableDataType *params = &field->config.field.variable.publishParameters; UA_PublishedVariableDataType *params = &field->config.field.variable.publishParameters;
/* Read the value */ UA_ReadValueId rvid;
if(field->config.field.variable.rtValueSource.rtInformationModelNode) { UA_ReadValueId_init(&rvid);
const UA_VariableNode *rtNode = (const UA_VariableNode *) rvid.nodeId = params->publishedVariable;
UA_NODESTORE_GET(psm->sc.server, &params->publishedVariable); rvid.attributeId = params->attributeId;
*value = **rtNode->valueBackend.backend.external.value; rvid.indexRange = params->indexRange;
value->value.storageType = UA_VARIANT_DATA_NODELETE; *value = readWithSession(psm->sc.server, &psm->sc.server->adminSession,
UA_NODESTORE_RELEASE(psm->sc.server, (const UA_Node *) rtNode); &rvid, UA_TIMESTAMPSTORETURN_BOTH);
} else if(field->config.field.variable.rtValueSource.rtFieldSourceEnabled == false){
UA_ReadValueId rvid;
UA_ReadValueId_init(&rvid);
rvid.nodeId = params->publishedVariable;
rvid.attributeId = params->attributeId;
rvid.indexRange = params->indexRange;
*value = readWithSession(psm->sc.server, &psm->sc.server->adminSession,
&rvid, UA_TIMESTAMPSTORETURN_BOTH);
} else {
*value = **field->config.field.variable.rtValueSource.staticValueSource;
value->value.storageType = UA_VARIANT_DATA_NODELETE;
}
} }
UA_AddPublishedDataSetResult UA_AddPublishedDataSetResult
@ -731,9 +688,10 @@ UA_SubscribedDataSetConfig_copy(const UA_SubscribedDataSetConfig *src,
memcpy(dst, src, sizeof(UA_SubscribedDataSetConfig)); memcpy(dst, src, sizeof(UA_SubscribedDataSetConfig));
res = UA_DataSetMetaDataType_copy(&src->dataSetMetaData, &dst->dataSetMetaData); res = UA_DataSetMetaDataType_copy(&src->dataSetMetaData, &dst->dataSetMetaData);
res |= UA_String_copy(&src->name, &dst->name); res |= UA_String_copy(&src->name, &dst->name);
res |= UA_TargetVariablesDataType_copy(&src->subscribedDataSet.target, if(src->subscribedDataSetType == UA_PUBSUB_SDS_TARGET) {
&dst->subscribedDataSet.target); res |= UA_TargetVariablesDataType_copy(&src->subscribedDataSet.target,
&dst->subscribedDataSet.target);
}
if(res != UA_STATUSCODE_GOOD) if(res != UA_STATUSCODE_GOOD)
UA_SubscribedDataSetConfig_clear(dst); UA_SubscribedDataSetConfig_clear(dst);
return res; return res;

View File

@ -184,6 +184,9 @@ typedef struct UA_PublishedDataSet {
UA_DataSetMetaDataType dataSetMetaData; UA_DataSetMetaDataType dataSetMetaData;
UA_UInt16 fieldSize; UA_UInt16 fieldSize;
UA_UInt16 promotedFieldsCount; UA_UInt16 promotedFieldsCount;
/* The counter is required because the PDS has not state.
* Check if it is actively used when changes are introduced. */
UA_UInt16 configurationFreezeCounter; UA_UInt16 configurationFreezeCounter;
} UA_PublishedDataSet; } UA_PublishedDataSet;
@ -284,11 +287,6 @@ UA_PubSubConnectionConfig_clear(UA_PubSubConnectionConfig *connectionConfig);
void void
UA_PubSubConnection_delete(UA_PubSubManager *psm, UA_PubSubConnection *c); UA_PubSubConnection_delete(UA_PubSubManager *psm, UA_PubSubConnection *c);
/* Returns either the eventloop configured in the connection or, in its absence,
* for the server */
UA_EventLoop *
UA_PubSubConnection_getEL(UA_PubSubManager *psm, UA_PubSubConnection *c);
UA_StatusCode UA_StatusCode
UA_PubSubConnection_setPubSubState(UA_PubSubManager *psm, UA_PubSubConnection *c, UA_PubSubConnection_setPubSubState(UA_PubSubManager *psm, UA_PubSubConnection *c,
UA_PubSubState targetState); UA_PubSubState targetState);
@ -341,12 +339,8 @@ UA_DataSetWriter_setPubSubState(UA_PubSubManager *psm, UA_DataSetWriter *dsw,
UA_StatusCode UA_StatusCode
UA_DataSetWriter_generateDataSetMessage(UA_PubSubManager *psm, UA_DataSetWriter_generateDataSetMessage(UA_PubSubManager *psm,
UA_DataSetMessage *dsm, UA_DataSetWriter *dsw,
UA_DataSetWriter *dsw); UA_DataSetMessage *dsm);
UA_StatusCode
UA_DataSetWriter_prepareDataSet(UA_PubSubManager *psm, UA_DataSetWriter *dsw,
UA_DataSetMessage *dsm);
UA_StatusCode UA_StatusCode
UA_DataSetWriter_create(UA_PubSubManager *psm, UA_DataSetWriter_create(UA_PubSubManager *psm,
@ -371,9 +365,7 @@ struct UA_WriterGroup {
UA_UInt32 writersCount; UA_UInt32 writersCount;
UA_UInt64 publishCallbackId; /* registered if != 0 */ UA_UInt64 publishCallbackId; /* registered if != 0 */
UA_NetworkMessageOffsetBuffer bufferedMessage;
UA_UInt16 sequenceNumber; /* Increased after every sent message */ UA_UInt16 sequenceNumber; /* Increased after every sent message */
UA_Boolean configurationFrozen;
UA_DateTime lastPublishTimeStamp; UA_DateTime lastPublishTimeStamp;
/* The ConnectionManager pointer is stored in the Connection. The channels /* The ConnectionManager pointer is stored in the Connection. The channels
@ -471,8 +463,6 @@ struct UA_DataSetReader {
UA_DataSetReaderConfig config; UA_DataSetReaderConfig config;
UA_ReaderGroup *linkedReaderGroup; UA_ReaderGroup *linkedReaderGroup;
UA_NetworkMessageOffsetBuffer bufferedMessage;
/* MessageReceiveTimeout handling */ /* MessageReceiveTimeout handling */
UA_UInt64 msgRcvTimeoutTimerId; UA_UInt64 msgRcvTimeoutTimerId;
}; };
@ -495,38 +485,17 @@ UA_DataSetReader_create(UA_PubSubManager *psm, UA_NodeId readerGroupIdentifier,
const UA_DataSetReaderConfig *dataSetReaderConfig, const UA_DataSetReaderConfig *dataSetReaderConfig,
UA_NodeId *readerIdentifier); UA_NodeId *readerIdentifier);
UA_StatusCode
UA_DataSetReader_prepareOffsetBuffer(Ctx *ctx, UA_DataSetReader *reader,
UA_ByteString *buf);
void
UA_DataSetReader_decodeAndProcessRT(UA_PubSubManager *psm, UA_DataSetReader *dsr,
UA_ByteString buf);
UA_StatusCode UA_StatusCode
UA_DataSetReader_remove(UA_PubSubManager *psm, UA_DataSetReader *dsr); UA_DataSetReader_remove(UA_PubSubManager *psm, UA_DataSetReader *dsr);
/* Copy the configuration of Target Variables */
UA_StatusCode UA_TargetVariables_copy(const UA_TargetVariables *src,
UA_TargetVariables *dst);
/* Clear the Target Variables configuration */
void UA_TargetVariables_clear(UA_TargetVariables *subscribedDataSetTarget);
/* Copy the configuration of Field Target Variables */
UA_StatusCode UA_FieldTargetVariable_copy(const UA_FieldTargetVariable *src,
UA_FieldTargetVariable *dst);
UA_StatusCode UA_StatusCode
DataSetReader_createTargetVariables(UA_PubSubManager *psm, UA_DataSetReader *dsr, DataSetReader_createTargetVariables(UA_PubSubManager *psm, UA_DataSetReader *dsr,
size_t targetVariablesSize, size_t targetsSize, const UA_FieldTargetDataType *targets);
const UA_FieldTargetVariable *targetVariables);
/* Returns an error reason if the target state is `Error` */ /* Returns an error reason if the target state is `Error` */
void void
UA_DataSetReader_setPubSubState(UA_PubSubManager *psm, UA_DataSetReader *dsr, UA_DataSetReader_setPubSubState(UA_PubSubManager *psm, UA_DataSetReader *dsr,
UA_PubSubState targetState, UA_PubSubState targetState, UA_StatusCode errorReason);
UA_StatusCode errorReason);
/**********************************************/ /**********************************************/
/* ReaderGroup */ /* ReaderGroup */
@ -541,7 +510,6 @@ struct UA_ReaderGroup {
LIST_HEAD(, UA_DataSetReader) readers; LIST_HEAD(, UA_DataSetReader) readers;
UA_UInt32 readersCount; UA_UInt32 readersCount;
UA_Boolean configurationFrozen;
UA_Boolean hasReceived; /* Received a message since the last _connect */ UA_Boolean hasReceived; /* Received a message since the last _connect */
/* The ConnectionManager pointer is stored in the Connection. The channels /* The ConnectionManager pointer is stored in the Connection. The channels
@ -595,10 +563,6 @@ UA_StatusCode
UA_ReaderGroup_setPubSubState(UA_PubSubManager *psm, UA_ReaderGroup *rg, UA_ReaderGroup_setPubSubState(UA_PubSubManager *psm, UA_ReaderGroup *rg,
UA_PubSubState targetState); UA_PubSubState targetState);
UA_Boolean
UA_ReaderGroup_decodeAndProcessRT(UA_PubSubManager *psm, UA_ReaderGroup *rg,
UA_ByteString buf);
UA_Boolean UA_Boolean
UA_ReaderGroup_process(UA_PubSubManager *psm, UA_ReaderGroup *rg, UA_ReaderGroup_process(UA_PubSubManager *psm, UA_ReaderGroup *rg,
UA_NetworkMessage *nm); UA_NetworkMessage *nm);

Some files were not shown because too many files have changed in this diff Show More