From d401f3198a2b3675d602bfc2739c170efb78bee1 Mon Sep 17 00:00:00 2001 From: andreasebner Date: Fri, 3 Dec 2021 09:49:42 +0100 Subject: [PATCH 0001/1963] fix(server) add cmake find file for python3 (#4815) --- tools/cmake/CMakeFindFrameworks.cmake | 32 + .../cmake/FindPackageHandleStandardArgs.cmake | 386 ++++++++ tools/cmake/FindPackageMessage.cmake | 47 + tools/cmake/FindPython/Support.cmake | 925 ++++++++++++++++++ tools/cmake/FindPython3.cmake | 146 +++ 5 files changed, 1536 insertions(+) create mode 100644 tools/cmake/CMakeFindFrameworks.cmake create mode 100644 tools/cmake/FindPackageHandleStandardArgs.cmake create mode 100644 tools/cmake/FindPackageMessage.cmake create mode 100644 tools/cmake/FindPython/Support.cmake create mode 100644 tools/cmake/FindPython3.cmake diff --git a/tools/cmake/CMakeFindFrameworks.cmake b/tools/cmake/CMakeFindFrameworks.cmake new file mode 100644 index 00000000000..6c4c5272963 --- /dev/null +++ b/tools/cmake/CMakeFindFrameworks.cmake @@ -0,0 +1,32 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# CMakeFindFrameworks +# ------------------- +# +# helper module to find OSX frameworks +# +# This module reads hints about search locations from variables:: +# +# CMAKE_FIND_FRAMEWORK_EXTRA_LOCATIONS - Extra directories + +if(NOT CMAKE_FIND_FRAMEWORKS_INCLUDED) + set(CMAKE_FIND_FRAMEWORKS_INCLUDED 1) + macro(CMAKE_FIND_FRAMEWORKS fwk) + set(${fwk}_FRAMEWORKS) + if(APPLE) + foreach(dir + ~/Library/Frameworks/${fwk}.framework + /usr/local/Frameworks/${fwk}.framework + /Library/Frameworks/${fwk}.framework + /System/Library/Frameworks/${fwk}.framework + /Network/Library/Frameworks/${fwk}.framework + ${CMAKE_FIND_FRAMEWORK_EXTRA_LOCATIONS}) + if(EXISTS ${dir}) + set(${fwk}_FRAMEWORKS ${${fwk}_FRAMEWORKS} ${dir}) + endif() + endforeach() + endif() + endmacro() +endif() diff --git a/tools/cmake/FindPackageHandleStandardArgs.cmake b/tools/cmake/FindPackageHandleStandardArgs.cmake new file mode 100644 index 00000000000..b3f25083a60 --- /dev/null +++ b/tools/cmake/FindPackageHandleStandardArgs.cmake @@ -0,0 +1,386 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindPackageHandleStandardArgs +----------------------------- + +This module provides a function intended to be used in :ref:`Find Modules` +implementing :command:`find_package()` calls. It handles the +``REQUIRED``, ``QUIET`` and version-related arguments of ``find_package``. +It also sets the ``_FOUND`` variable. The package is +considered found if all variables listed contain valid results, e.g. +valid filepaths. + +.. command:: find_package_handle_standard_args + + There are two signatures:: + + find_package_handle_standard_args( + (DEFAULT_MSG|) + ... + ) + + find_package_handle_standard_args( + [FOUND_VAR ] + [REQUIRED_VARS ...] + [VERSION_VAR ] + [HANDLE_COMPONENTS] + [CONFIG_MODE] + [FAIL_MESSAGE ] + ) + + The ``_FOUND`` variable will be set to ``TRUE`` if all + the variables ``...`` are valid and any optional + constraints are satisfied, and ``FALSE`` otherwise. A success or + failure message may be displayed based on the results and on + whether the ``REQUIRED`` and/or ``QUIET`` option was given to + the :command:`find_package` call. + + The options are: + + ``(DEFAULT_MSG|)`` + In the simple signature this specifies the failure message. + Use ``DEFAULT_MSG`` to ask for a default message to be computed + (recommended). Not valid in the full signature. + + ``FOUND_VAR `` + Obsolete. Specifies either ``_FOUND`` or + ``_FOUND`` as the result variable. This exists only + for compatibility with older versions of CMake and is now ignored. + Result variables of both names are always set for compatibility. + + ``REQUIRED_VARS ...`` + Specify the variables which are required for this package. + These may be named in the generated failure message asking the + user to set the missing variable values. Therefore these should + typically be cache entries such as ``FOO_LIBRARY`` and not output + variables like ``FOO_LIBRARIES``. + + ``VERSION_VAR `` + Specify the name of a variable that holds the version of the package + that has been found. This version will be checked against the + (potentially) specified required version given to the + :command:`find_package` call, including its ``EXACT`` option. + The default messages include information about the required + version and the version which has been actually found, both + if the version is ok or not. + + ``HANDLE_COMPONENTS`` + Enable handling of package components. In this case, the command + will report which components have been found and which are missing, + and the ``_FOUND`` variable will be set to ``FALSE`` + if any of the required components (i.e. not the ones listed after + the ``OPTIONAL_COMPONENTS`` option of :command:`find_package`) are + missing. + + ``CONFIG_MODE`` + Specify that the calling find module is a wrapper around a + call to ``find_package( NO_MODULE)``. This implies + a ``VERSION_VAR`` value of ``_VERSION``. The command + will automatically check whether the package configuration file + was found. + + ``FAIL_MESSAGE `` + Specify a custom failure message instead of using the default + generated message. Not recommended. + +Example for the simple signature: + +.. code-block:: cmake + + find_package_handle_standard_args(LibXml2 DEFAULT_MSG + LIBXML2_LIBRARY LIBXML2_INCLUDE_DIR) + +The ``LibXml2`` package is considered to be found if both +``LIBXML2_LIBRARY`` and ``LIBXML2_INCLUDE_DIR`` are valid. +Then also ``LibXml2_FOUND`` is set to ``TRUE``. If it is not found +and ``REQUIRED`` was used, it fails with a +:command:`message(FATAL_ERROR)`, independent whether ``QUIET`` was +used or not. If it is found, success will be reported, including +the content of the first ````. On repeated CMake runs, +the same message will not be printed again. + +Example for the full signature: + +.. code-block:: cmake + + find_package_handle_standard_args(LibArchive + REQUIRED_VARS LibArchive_LIBRARY LibArchive_INCLUDE_DIR + VERSION_VAR LibArchive_VERSION) + +In this case, the ``LibArchive`` package is considered to be found if +both ``LibArchive_LIBRARY`` and ``LibArchive_INCLUDE_DIR`` are valid. +Also the version of ``LibArchive`` will be checked by using the version +contained in ``LibArchive_VERSION``. Since no ``FAIL_MESSAGE`` is given, +the default messages will be printed. + +Another example for the full signature: + +.. code-block:: cmake + + find_package(Automoc4 QUIET NO_MODULE HINTS /opt/automoc4) + find_package_handle_standard_args(Automoc4 CONFIG_MODE) + +In this case, a ``FindAutmoc4.cmake`` module wraps a call to +``find_package(Automoc4 NO_MODULE)`` and adds an additional search +directory for ``automoc4``. Then the call to +``find_package_handle_standard_args`` produces a proper success/failure +message. +#]=======================================================================] + +include(${CMAKE_CURRENT_LIST_DIR}/FindPackageMessage.cmake) + +# internal helper macro +macro(_FPHSA_FAILURE_MESSAGE _msg) + if (${_NAME}_FIND_REQUIRED) + message(FATAL_ERROR "${_msg}") + else () + if (NOT ${_NAME}_FIND_QUIETLY) + message(STATUS "${_msg}") + endif () + endif () +endmacro() + + +# internal helper macro to generate the failure message when used in CONFIG_MODE: +macro(_FPHSA_HANDLE_FAILURE_CONFIG_MODE) + # _CONFIG is set, but FOUND is false, this means that some other of the REQUIRED_VARS was not found: + if(${_NAME}_CONFIG) + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: missing:${MISSING_VARS} (found ${${_NAME}_CONFIG} ${VERSION_MSG})") + else() + # If _CONSIDERED_CONFIGS is set, the config-file has been found, but no suitable version. + # List them all in the error message: + if(${_NAME}_CONSIDERED_CONFIGS) + set(configsText "") + list(LENGTH ${_NAME}_CONSIDERED_CONFIGS configsCount) + math(EXPR configsCount "${configsCount} - 1") + foreach(currentConfigIndex RANGE ${configsCount}) + list(GET ${_NAME}_CONSIDERED_CONFIGS ${currentConfigIndex} filename) + list(GET ${_NAME}_CONSIDERED_VERSIONS ${currentConfigIndex} version) + string(APPEND configsText " ${filename} (version ${version})\n") + endforeach() + if (${_NAME}_NOT_FOUND_MESSAGE) + string(APPEND configsText " Reason given by package: ${${_NAME}_NOT_FOUND_MESSAGE}\n") + endif() + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE} ${VERSION_MSG}, checked the following files:\n${configsText}") + + else() + # Simple case: No Config-file was found at all: + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: found neither ${_NAME}Config.cmake nor ${_NAME_LOWER}-config.cmake ${VERSION_MSG}") + endif() + endif() +endmacro() + + +function(FIND_PACKAGE_HANDLE_STANDARD_ARGS _NAME _FIRST_ARG) + + # Set up the arguments for `cmake_parse_arguments`. + set(options CONFIG_MODE HANDLE_COMPONENTS) + set(oneValueArgs FAIL_MESSAGE VERSION_VAR FOUND_VAR) + set(multiValueArgs REQUIRED_VARS) + + # Check whether we are in 'simple' or 'extended' mode: + set(_KEYWORDS_FOR_EXTENDED_MODE ${options} ${oneValueArgs} ${multiValueArgs} ) + list(FIND _KEYWORDS_FOR_EXTENDED_MODE "${_FIRST_ARG}" INDEX) + + if(${INDEX} EQUAL -1) + set(FPHSA_FAIL_MESSAGE ${_FIRST_ARG}) + set(FPHSA_REQUIRED_VARS ${ARGN}) + set(FPHSA_VERSION_VAR) + else() + cmake_parse_arguments(FPHSA "${options}" "${oneValueArgs}" "${multiValueArgs}" ${_FIRST_ARG} ${ARGN}) + + if(FPHSA_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown keywords given to FIND_PACKAGE_HANDLE_STANDARD_ARGS(): \"${FPHSA_UNPARSED_ARGUMENTS}\"") + endif() + + if(NOT FPHSA_FAIL_MESSAGE) + set(FPHSA_FAIL_MESSAGE "DEFAULT_MSG") + endif() + + # In config-mode, we rely on the variable _CONFIG, which is set by find_package() + # when it successfully found the config-file, including version checking: + if(FPHSA_CONFIG_MODE) + list(INSERT FPHSA_REQUIRED_VARS 0 ${_NAME}_CONFIG) + list(REMOVE_DUPLICATES FPHSA_REQUIRED_VARS) + set(FPHSA_VERSION_VAR ${_NAME}_VERSION) + endif() + + if(NOT FPHSA_REQUIRED_VARS) + message(FATAL_ERROR "No REQUIRED_VARS specified for FIND_PACKAGE_HANDLE_STANDARD_ARGS()") + endif() + endif() + + # now that we collected all arguments, process them + + if("x${FPHSA_FAIL_MESSAGE}" STREQUAL "xDEFAULT_MSG") + set(FPHSA_FAIL_MESSAGE "Could NOT find ${_NAME}") + endif() + + list(GET FPHSA_REQUIRED_VARS 0 _FIRST_REQUIRED_VAR) + + string(TOUPPER ${_NAME} _NAME_UPPER) + string(TOLOWER ${_NAME} _NAME_LOWER) + + if(FPHSA_FOUND_VAR) + if(FPHSA_FOUND_VAR MATCHES "^${_NAME}_FOUND$" OR FPHSA_FOUND_VAR MATCHES "^${_NAME_UPPER}_FOUND$") + set(_FOUND_VAR ${FPHSA_FOUND_VAR}) + else() + message(FATAL_ERROR "The argument for FOUND_VAR is \"${FPHSA_FOUND_VAR}\", but only \"${_NAME}_FOUND\" and \"${_NAME_UPPER}_FOUND\" are valid names.") + endif() + else() + set(_FOUND_VAR ${_NAME_UPPER}_FOUND) + endif() + + # collect all variables which were not found, so they can be printed, so the + # user knows better what went wrong (#6375) + set(MISSING_VARS "") + set(DETAILS "") + # check if all passed variables are valid + set(FPHSA_FOUND_${_NAME} TRUE) + foreach(_CURRENT_VAR ${FPHSA_REQUIRED_VARS}) + if(NOT ${_CURRENT_VAR}) + set(FPHSA_FOUND_${_NAME} FALSE) + string(APPEND MISSING_VARS " ${_CURRENT_VAR}") + else() + string(APPEND DETAILS "[${${_CURRENT_VAR}}]") + endif() + endforeach() + if(FPHSA_FOUND_${_NAME}) + set(${_NAME}_FOUND TRUE) + set(${_NAME_UPPER}_FOUND TRUE) + else() + set(${_NAME}_FOUND FALSE) + set(${_NAME_UPPER}_FOUND FALSE) + endif() + + # component handling + unset(FOUND_COMPONENTS_MSG) + unset(MISSING_COMPONENTS_MSG) + + if(FPHSA_HANDLE_COMPONENTS) + foreach(comp ${${_NAME}_FIND_COMPONENTS}) + if(${_NAME}_${comp}_FOUND) + + if(NOT DEFINED FOUND_COMPONENTS_MSG) + set(FOUND_COMPONENTS_MSG "found components: ") + endif() + string(APPEND FOUND_COMPONENTS_MSG " ${comp}") + + else() + + if(NOT DEFINED MISSING_COMPONENTS_MSG) + set(MISSING_COMPONENTS_MSG "missing components: ") + endif() + string(APPEND MISSING_COMPONENTS_MSG " ${comp}") + + if(${_NAME}_FIND_REQUIRED_${comp}) + set(${_NAME}_FOUND FALSE) + string(APPEND MISSING_VARS " ${comp}") + endif() + + endif() + endforeach() + set(COMPONENT_MSG "${FOUND_COMPONENTS_MSG} ${MISSING_COMPONENTS_MSG}") + string(APPEND DETAILS "[c${COMPONENT_MSG}]") + endif() + + # version handling: + set(VERSION_MSG "") + set(VERSION_OK TRUE) + + # check with DEFINED here as the requested or found version may be "0" + if (DEFINED ${_NAME}_FIND_VERSION) + if(DEFINED ${FPHSA_VERSION_VAR}) + set(_FOUND_VERSION ${${FPHSA_VERSION_VAR}}) + + if(${_NAME}_FIND_VERSION_EXACT) # exact version required + # count the dots in the version string + string(REGEX REPLACE "[^.]" "" _VERSION_DOTS "${_FOUND_VERSION}") + # add one dot because there is one dot more than there are components + string(LENGTH "${_VERSION_DOTS}." _VERSION_DOTS) + if (_VERSION_DOTS GREATER ${_NAME}_FIND_VERSION_COUNT) + # Because of the C++ implementation of find_package() ${_NAME}_FIND_VERSION_COUNT + # is at most 4 here. Therefore a simple lookup table is used. + if (${_NAME}_FIND_VERSION_COUNT EQUAL 1) + set(_VERSION_REGEX "[^.]*") + elseif (${_NAME}_FIND_VERSION_COUNT EQUAL 2) + set(_VERSION_REGEX "[^.]*\\.[^.]*") + elseif (${_NAME}_FIND_VERSION_COUNT EQUAL 3) + set(_VERSION_REGEX "[^.]*\\.[^.]*\\.[^.]*") + else () + set(_VERSION_REGEX "[^.]*\\.[^.]*\\.[^.]*\\.[^.]*") + endif () + string(REGEX REPLACE "^(${_VERSION_REGEX})\\..*" "\\1" _VERSION_HEAD "${_FOUND_VERSION}") + unset(_VERSION_REGEX) + if (NOT ${_NAME}_FIND_VERSION VERSION_EQUAL _VERSION_HEAD) + set(VERSION_MSG "Found unsuitable version \"${_FOUND_VERSION}\", but required is exact version \"${${_NAME}_FIND_VERSION}\"") + set(VERSION_OK FALSE) + else () + set(VERSION_MSG "(found suitable exact version \"${_FOUND_VERSION}\")") + endif () + unset(_VERSION_HEAD) + else () + if (NOT ${_NAME}_FIND_VERSION VERSION_EQUAL _FOUND_VERSION) + set(VERSION_MSG "Found unsuitable version \"${_FOUND_VERSION}\", but required is exact version \"${${_NAME}_FIND_VERSION}\"") + set(VERSION_OK FALSE) + else () + set(VERSION_MSG "(found suitable exact version \"${_FOUND_VERSION}\")") + endif () + endif () + unset(_VERSION_DOTS) + + else() # minimum version specified: + if (${_NAME}_FIND_VERSION VERSION_GREATER _FOUND_VERSION) + set(VERSION_MSG "Found unsuitable version \"${_FOUND_VERSION}\", but required is at least \"${${_NAME}_FIND_VERSION}\"") + set(VERSION_OK FALSE) + else () + set(VERSION_MSG "(found suitable version \"${_FOUND_VERSION}\", minimum required is \"${${_NAME}_FIND_VERSION}\")") + endif () + endif() + + else() + + # if the package was not found, but a version was given, add that to the output: + if(${_NAME}_FIND_VERSION_EXACT) + set(VERSION_MSG "(Required is exact version \"${${_NAME}_FIND_VERSION}\")") + else() + set(VERSION_MSG "(Required is at least version \"${${_NAME}_FIND_VERSION}\")") + endif() + + endif() + else () + # Check with DEFINED as the found version may be 0. + if(DEFINED ${FPHSA_VERSION_VAR}) + set(VERSION_MSG "(found version \"${${FPHSA_VERSION_VAR}}\")") + endif() + endif () + + if(VERSION_OK) + string(APPEND DETAILS "[v${${FPHSA_VERSION_VAR}}(${${_NAME}_FIND_VERSION})]") + else() + set(${_NAME}_FOUND FALSE) + endif() + + + # print the result: + if (${_NAME}_FOUND) + FIND_PACKAGE_MESSAGE(${_NAME} "Found ${_NAME}: ${${_FIRST_REQUIRED_VAR}} ${VERSION_MSG} ${COMPONENT_MSG}" "${DETAILS}") + else () + + if(FPHSA_CONFIG_MODE) + _FPHSA_HANDLE_FAILURE_CONFIG_MODE() + else() + if(NOT VERSION_OK) + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: ${VERSION_MSG} (found ${${_FIRST_REQUIRED_VAR}})") + else() + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE} (missing:${MISSING_VARS}) ${VERSION_MSG}") + endif() + endif() + + endif () + + set(${_NAME}_FOUND ${${_NAME}_FOUND} PARENT_SCOPE) + set(${_NAME_UPPER}_FOUND ${${_NAME}_FOUND} PARENT_SCOPE) +endfunction() \ No newline at end of file diff --git a/tools/cmake/FindPackageMessage.cmake b/tools/cmake/FindPackageMessage.cmake new file mode 100644 index 00000000000..947dca9e7e9 --- /dev/null +++ b/tools/cmake/FindPackageMessage.cmake @@ -0,0 +1,47 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# FindPackageMessage +# ------------------ +# +# +# +# FIND_PACKAGE_MESSAGE( "message for user" "find result details") +# +# This macro is intended to be used in FindXXX.cmake modules files. It +# will print a message once for each unique find result. This is useful +# for telling the user where a package was found. The first argument +# specifies the name (XXX) of the package. The second argument +# specifies the message to display. The third argument lists details +# about the find result so that if they change the message will be +# displayed again. The macro also obeys the QUIET argument to the +# find_package command. +# +# Example: +# +# :: +# +# if(X11_FOUND) +# FIND_PACKAGE_MESSAGE(X11 "Found X11: ${X11_X11_LIB}" +# "[${X11_X11_LIB}][${X11_INCLUDE_DIR}]") +# else() +# ... +# endif() + +function(FIND_PACKAGE_MESSAGE pkg msg details) + # Avoid printing a message repeatedly for the same find result. + if(NOT ${pkg}_FIND_QUIETLY) + string(REPLACE "\n" "" details "${details}") + set(DETAILS_VAR FIND_PACKAGE_MESSAGE_DETAILS_${pkg}) + if(NOT "${details}" STREQUAL "${${DETAILS_VAR}}") + # The message has not yet been printed. + message(STATUS "${msg}") + + # Save the find details in the cache to avoid printing the same + # message again. + set("${DETAILS_VAR}" "${details}" + CACHE INTERNAL "Details about finding ${pkg}") + endif() + endif() +endfunction() \ No newline at end of file diff --git a/tools/cmake/FindPython/Support.cmake b/tools/cmake/FindPython/Support.cmake new file mode 100644 index 00000000000..a73edac70c6 --- /dev/null +++ b/tools/cmake/FindPython/Support.cmake @@ -0,0 +1,925 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +# +# This file is a "template" file used by various FindPython modules. +# + +cmake_policy (VERSION 3.7) + +# +# Initial configuration +# +if (NOT DEFINED _PYTHON_PREFIX) + message (FATAL_ERROR "FindPython: INTERNAL ERROR") +endif() +if (NOT DEFINED _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + message (FATAL_ERROR "FindPython: INTERNAL ERROR") +endif() +if (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR EQUAL 3) + set(_${_PYTHON_PREFIX}_VERSIONS 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0) +elseif (_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR EQUAL 2) + set(_${_PYTHON_PREFIX}_VERSIONS 2.7 2.6 2.5 2.4 2.3 2.2 2.1 2.0) +else() + message (FATAL_ERROR "FindPython: INTERNAL ERROR") +endif() + + +# +# helper commands +# +macro (_PYTHON_DISPLAY_FAILURE _PYTHON_MSG) + if (${_PYTHON_PREFIX}_FIND_REQUIRED) + message (FATAL_ERROR "${_PYTHON_MSG}") + else() + if (NOT ${_PYTHON_PREFIX}_FIND_QUIETLY) + message(STATUS "${_PYTHON_MSG}") + endif () + endif() + + set (${_PYTHON_PREFIX}_FOUND FALSE) + string (TOUPPER "${_PYTHON_PREFIX}" _${_PYTHON_PREFIX}_UPPER_PREFIX) + set (${_PYTHON_UPPER_PREFIX}_FOUND FALSE) + return() +endmacro() + + +function (_PYTHON_GET_FRAMEWORKS _PYTHON_PGF_FRAMEWORK_PATHS _PYTHON_VERSION) + set (_PYTHON_FRAMEWORK_PATHS) + foreach (_PYTHON_FRAMEWORK IN LISTS Python_FRAMEWORKS) + list (APPEND _PYTHON_FRAMEWORK_PATHS + "${_PYTHON_FRAMEWORK}/Versions/${_PYTHON_VERSION}") + endforeach() + set (${_PYTHON_PGF_FRAMEWORK_PATHS} ${_PYTHON_FRAMEWORK_PATHS} PARENT_SCOPE) +endfunction() + + +function (_PYTHON_VALIDATE_INTERPRETER) + if (NOT ${_PYTHON_PREFIX}_EXECUTABLE) + return() + endif() + + if (${_PYTHON_PREFIX}_EXECUTABLE MATCHES "python${CMAKE_EXECUTABLE_SUFFIX}$") + # executable found do not have version in name + # ensure major version is OK + execute_process (COMMAND "${${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys; sys.stdout.write(str(sys.version_info[0]))" + RESULT_VARIABLE result + OUTPUT_VARIABLE version + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (result OR NOT version EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + # interpreter not usable or has wrong major version + set (${_PYTHON_PREFIX}_EXECUTABLE ${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND CACHE INTERNAL "" FORCE) + return() + endif() + endif() + + if (CMAKE_SIZEOF_VOID_P AND "Development" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + AND NOT CMAKE_CROSSCOMPILING) + # In this case, interpreter must have same architecture as environment + execute_process (COMMAND "${${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys, struct; sys.stdout.write(str(struct.calcsize(\"P\")))" + RESULT_VARIABLE result + OUTPUT_VARIABLE size + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (result OR NOT size EQUAL CMAKE_SIZEOF_VOID_P) + # interpreter not usable or has wrong architecture + set (${_PYTHON_PREFIX}_EXECUTABLE ${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND CACHE INTERNAL "" FORCE) + return() + endif() + endif() +endfunction() + + +function (_PYTHON_FIND_RUNTIME_LIBRARY _PYTHON_LIB) + string (REPLACE "_RUNTIME" "" _PYTHON_LIB "${_PYTHON_LIB}") + # look at runtime part on systems supporting it + if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR + (CMAKE_SYSTEM_NAME MATCHES "MSYS|CYGWIN" + AND ${_PYTHON_LIB} MATCHES "${CMAKE_IMPORT_LIBRARY_SUFFIX}$")) + set (CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_SHARED_LIBRARY_SUFFIX}) + # MSYS has a special syntax for runtime libraries + if (CMAKE_SYSTEM_NAME MATCHES "MSYS") + list (APPEND CMAKE_FIND_LIBRARY_PREFIXES "msys-") + endif() + find_library (${ARGV}) + endif() +endfunction() + + +function (_PYTHON_SET_LIBRARY_DIRS _PYTHON_SLD_RESULT) + unset (_PYTHON_DIRS) + set (_PYTHON_LIBS ${ARGV}) + list (REMOVE_AT _PYTHON_LIBS 0) + foreach (_PYTHON_LIB IN LISTS _PYTHON_LIBS) + if (${_PYTHON_LIB}) + get_filename_component (_PYTHON_DIR "${${_PYTHON_LIB}}" DIRECTORY) + list (APPEND _PYTHON_DIRS "${_PYTHON_DIR}") + endif() + endforeach() + if (_PYTHON_DIRS) + list (REMOVE_DUPLICATES _PYTHON_DIRS) + endif() + set (${_PYTHON_SLD_RESULT} ${_PYTHON_DIRS} PARENT_SCOPE) +endfunction() + + +# If major version is specified, it must be the same as internal major version +if (DEFINED ${_PYTHON_PREFIX}_FIND_VERSION_MAJOR + AND NOT ${_PYTHON_PREFIX}_FIND_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + _python_display_failure ("Could NOT find ${_PYTHON_PREFIX}: Wrong major version specified is \"${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}\", but expected major version is \"${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}\"") +endif() + + +# handle components +if (NOT ${_PYTHON_PREFIX}_FIND_COMPONENTS) + set (${_PYTHON_PREFIX}_FIND_COMPONENTS Interpreter) + set (${_PYTHON_PREFIX}_FIND_REQUIRED_Interpreter TRUE) +endif() +foreach (_${_PYTHON_PREFIX}_COMPONENT IN LISTS ${_PYTHON_PREFIX}_FIND_COMPONENTS) + set (${_PYTHON_PREFIX}_${_${_PYTHON_PREFIX}_COMPONENT}_FOUND FALSE) +endforeach() +unset (_${_PYTHON_PREFIX}_FIND_VERSIONS) + +# Set versions to search +## default: search any version +set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSIONS}) + +if (${_PYTHON_PREFIX}_FIND_VERSION_COUNT GREATER 1) + if (${_PYTHON_PREFIX}_FIND_VERSION_EXACT) + set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_FIND_VERSION_MAJOR}.${${_PYTHON_PREFIX}_FIND_VERSION_MINOR}) + else() + unset (_${_PYTHON_PREFIX}_FIND_VERSIONS) + # add all compatible versions + foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_VERSIONS) + if (_${_PYTHON_PREFIX}_VERSION VERSION_GREATER_EQUAL ${_PYTHON_PREFIX}_FIND_VERSION) + list (APPEND _${_PYTHON_PREFIX}_FIND_VERSIONS ${_${_PYTHON_PREFIX}_VERSION}) + endif() + endforeach() + endif() +endif() + +# Anaconda distribution: define which architectures can be used +if (CMAKE_SIZEOF_VOID_P) + # In this case, search only for 64bit or 32bit + math (EXPR _${_PYTHON_PREFIX}_ARCH "${CMAKE_SIZEOF_VOID_P} * 8") + set (_${_PYTHON_PREFIX}_ARCH2 ${_${_PYTHON_PREFIX}_ARCH}) +else() + # architecture unknown, search for both 64bit and 32bit + set (_${_PYTHON_PREFIX}_ARCH 64) + set (_${_PYTHON_PREFIX}_ARCH2 32) +endif() + +# IronPython support +if (CMAKE_SIZEOF_VOID_P) + # In this case, search only for 64bit or 32bit + math (EXPR _${_PYTHON_PREFIX}_ARCH "${CMAKE_SIZEOF_VOID_P} * 8") + set (_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES ipy${_${_PYTHON_PREFIX}_ARCH} ipy) +else() + # architecture unknown, search for natural interpreter + set (_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES ipy) +endif() + +# Apple frameworks handling +include (${CMAKE_CURRENT_LIST_DIR}/../CMakeFindFrameworks.cmake) +cmake_find_frameworks (Python) + +# Save CMAKE_FIND_FRAMEWORK +if (DEFINED CMAKE_FIND_FRAMEWORK) + set (_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK}) +else() + unset (_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK) +endif() +# To avoid picking up the system elements pre-maturely. +set (CMAKE_FIND_FRAMEWORK LAST) + + +unset (_${_PYTHON_PREFIX}_REQUIRED_VARS) +unset (_${_PYTHON_PREFIX}_CACHED_VARS) + + +# first step, search for the interpreter +if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) + if (${_PYTHON_PREFIX}_FIND_REQUIRED_Interpreter) + list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_EXECUTABLE) + list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS ${_PYTHON_PREFIX}_EXECUTABLE) + endif() + + set (_${_PYTHON_PREFIX}_HINTS "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) + + # look-up for various versions and locations + foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) + string (REPLACE "." "" _${_PYTHON_PREFIX}_VERSION_NO_DOTS ${_${_PYTHON_PREFIX}_VERSION}) + + _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION}) + + # try using HINTS + find_program (${_PYTHON_PREFIX}_EXECUTABLE + NAMES python${_${_PYTHON_PREFIX}_VERSION} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + PATH_SUFFIXES bin + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + # try using registry + if (WIN32) + find_program (${_PYTHON_PREFIX}_EXECUTABLE + NAMES python${_${_PYTHON_PREFIX}_VERSION} python + ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH2}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + PATH_SUFFIXES bin + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + # try in standard paths + find_program (${_PYTHON_PREFIX}_EXECUTABLE + NAMES python${_${_PYTHON_PREFIX}_VERSION}) + + _python_validate_interpreter () + if (${_PYTHON_PREFIX}_EXECUTABLE) + break() + endif() + endforeach() + + # try more generic names + if (NOT ${_PYTHON_PREFIX}_EXECUTABLE) + find_program (${_PYTHON_PREFIX}_EXECUTABLE + NAMES python${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR} python + ${_${_PYTHON_PREFIX}_IRON_PYTHON_NAMES} + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATH_SUFFIXES bin) + + _python_validate_interpreter () + endif() + + # retrieve exact version of executable found + if (${_PYTHON_PREFIX}_EXECUTABLE) + execute_process (COMMAND "${${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:3]]))" + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE ${_PYTHON_PREFIX}_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (NOT _${_PYTHON_PREFIX}_RESULT) + string (REGEX MATCHALL "[0-9]+" _${_PYTHON_PREFIX}_VERSIONS "${${_PYTHON_PREFIX}_VERSION}") + list (GET _${_PYTHON_PREFIX}_VERSIONS 0 ${_PYTHON_PREFIX}_VERSION_MAJOR) + list (GET _${_PYTHON_PREFIX}_VERSIONS 1 ${_PYTHON_PREFIX}_VERSION_MINOR) + list (GET _${_PYTHON_PREFIX}_VERSIONS 2 ${_PYTHON_PREFIX}_VERSION_PATCH) + else() + # Interpreter is not usable + set (${_PYTHON_PREFIX}_EXECUTABLE ${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND CACHE INTERNAL "" FORCE) + unset (${_PYTHON_PREFIX}_VERSION) + endif() + endif() + + if (${_PYTHON_PREFIX}_EXECUTABLE + AND ${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + set (${_PYTHON_PREFIX}_Interpreter_FOUND TRUE) + # Use interpreter version for future searches to ensure consistency + set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}) + endif() + + if (${_PYTHON_PREFIX}_Interpreter_FOUND) + # retrieve interpreter identity + execute_process (COMMAND "${${_PYTHON_PREFIX}_EXECUTABLE}" -V + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE ${_PYTHON_PREFIX}_INTERPRETER_ID + ERROR_VARIABLE ${_PYTHON_PREFIX}_INTERPRETER_ID) + if (NOT _${_PYTHON_PREFIX}_RESULT) + if (${_PYTHON_PREFIX}_INTERPRETER_ID MATCHES "Anaconda") + set (${_PYTHON_PREFIX}_INTERPRETER_ID "Anaconda") + elseif (${_PYTHON_PREFIX}_INTERPRETER_ID MATCHES "Enthought") + set (${_PYTHON_PREFIX}_INTERPRETER_ID "Canopy") + else() + string (REGEX REPLACE "^([^ ]+).*" "\\1" ${_PYTHON_PREFIX}_INTERPRETER_ID "${${_PYTHON_PREFIX}_INTERPRETER_ID}") + if (${_PYTHON_PREFIX}_INTERPRETER_ID STREQUAL "Python") + # try to get a more precise ID + execute_process (COMMAND "${${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys; print(sys.copyright)" + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE ${_PYTHON_PREFIX}_COPYRIGHT + ERROR_QUIET) + if (${_PYTHON_PREFIX}_COPYRIGHT MATCHES "ActiveState") + set (${_PYTHON_PREFIX}_INTERPRETER_ID "ActivePython") + endif() + endif() + endif() + else() + set (${_PYTHON_PREFIX}_INTERPRETER_ID Python) + endif() + else() + unset (${_PYTHON_PREFIX}_INTERPRETER_ID) + endif() + + # retrieve various package installation directories + execute_process (COMMAND "${${_PYTHON_PREFIX}_EXECUTABLE}" -c "import sys; from distutils import sysconfig;sys.stdout.write(';'.join([sysconfig.get_python_lib(plat_specific=False,standard_lib=True),sysconfig.get_python_lib(plat_specific=True,standard_lib=True),sysconfig.get_python_lib(plat_specific=False,standard_lib=False),sysconfig.get_python_lib(plat_specific=True,standard_lib=False)]))" + + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_LIBPATHS + ERROR_QUIET) + if (NOT _${_PYTHON_PREFIX}_RESULT) + list (GET _${_PYTHON_PREFIX}_LIBPATHS 0 ${_PYTHON_PREFIX}_STDLIB) + list (GET _${_PYTHON_PREFIX}_LIBPATHS 1 ${_PYTHON_PREFIX}_STDARCH) + list (GET _${_PYTHON_PREFIX}_LIBPATHS 2 ${_PYTHON_PREFIX}_SITELIB) + list (GET _${_PYTHON_PREFIX}_LIBPATHS 3 ${_PYTHON_PREFIX}_SITEARCH) + else() + unset (${_PYTHON_PREFIX}_STDLIB) + unset (${_PYTHON_PREFIX}_STDARCH) + unset (${_PYTHON_PREFIX}_SITELIB) + unset (${_PYTHON_PREFIX}_SITEARCH) + endif() + + mark_as_advanced (${_PYTHON_PREFIX}_EXECUTABLE) +endif() + + +# second step, search for compiler (IronPython) +if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS) + if (${_PYTHON_PREFIX}_FIND_REQUIRED_Compiler) + list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_COMPILER) + list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS ${_PYTHON_PREFIX}_COMPILER) + endif() + + # IronPython specific artifacts + # If IronPython interpreter is found, use its path + unset (_${_PYTHON_PREFIX}_IRON_ROOT) + if (${_PYTHON_PREFIX}_Interpreter_FOUND AND ${_PYTHON_PREFIX}_INTERPRETER_ID STREQUAL "IronPython") + get_filename_component (_${_PYTHON_PREFIX}_IRON_ROOT "${${_PYTHON_PREFIX}_EXECUTABLE}" DIRECTORY) + endif() + + # try using root dir and registry + foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) + find_program (${_PYTHON_PREFIX}_COMPILER + NAMES ipyc + HINTS ${_${_PYTHON_PREFIX}_IRON_ROOT} ${_${_PYTHON_PREFIX}_HINTS} + PATHS [HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + if (${_PYTHON_PREFIX}_COMPILER) + break() + endif() + endforeach() + # try in standard paths + find_program (${_PYTHON_PREFIX}_COMPILER + NAMES ipyc) + + if (${_PYTHON_PREFIX}_COMPILER) + # retrieve python environment version from compiler + set (_${_PYTHON_PREFIX}_VERSION_DIR "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/PythonCompilerVersion.dir") + file (WRITE "${_${_PYTHON_PREFIX}_VERSION_DIR}/version.py" "import sys; sys.stdout.write('.'.join([str(x) for x in sys.version_info[:3]]))\n") + execute_process (COMMAND "${${_PYTHON_PREFIX}_COMPILER}" /target:exe /embed "${_${_PYTHON_PREFIX}_VERSION_DIR}/version.py" + WORKING_DIRECTORY "${_${_PYTHON_PREFIX}_VERSION_DIR}" + OUTPUT_QUIET + ERROR_QUIET) + execute_process (COMMAND "${_${_PYTHON_PREFIX}_VERSION_DIR}/version" + WORKING_DIRECTORY "${_${_PYTHON_PREFIX}_VERSION_DIR}" + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_VERSION + ERROR_QUIET) + if (NOT _${_PYTHON_PREFIX}_RESULT) + string (REGEX MATCHALL "[0-9]+" _${_PYTHON_PREFIX}_VERSIONS "${_${_PYTHON_PREFIX}_VERSION}") + list (GET _${_PYTHON_PREFIX}_VERSIONS 0 _${_PYTHON_PREFIX}_VERSION_MAJOR) + list (GET _${_PYTHON_PREFIX}_VERSIONS 1 _${_PYTHON_PREFIX}_VERSION_MINOR) + list (GET _${_PYTHON_PREFIX}_VERSIONS 2 _${_PYTHON_PREFIX}_VERSION_PATCH) + + if (NOT ${_PYTHON_PREFIX}_Interpreter_FOUND) + # set public version information + set (${_PYTHON_PREFIX}_VERSION ${_${_PYTHON_PREFIX}_VERSION}) + set (${_PYTHON_PREFIX}_VERSION_MAJOR ${_${_PYTHON_PREFIX}_VERSION_MAJOR}) + set (${_PYTHON_PREFIX}_VERSION_MINOR ${_${_PYTHON_PREFIX}_VERSION_MINOR}) + set (${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_VERSION_PATCH}) + endif() + else() + # compiler not usable + set (${_PYTHON_PREFIX}_COMPILER ${_PYTHON_PREFIX}_COMPILER-NOTFOUND CACHE INTERNAL "" FORCE) + endif() + file (REMOVE_RECURSE "${_${_PYTHON_PREFIX}_VERSION_DIR}") + endif() + + if (${_PYTHON_PREFIX}_COMPILER) + if (${_PYTHON_PREFIX}_Interpreter_FOUND) + # Compiler must be compatible with interpreter + if (${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR} VERSION_EQUAL ${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}) + set (${_PYTHON_PREFIX}_Compiler_FOUND TRUE) + endif() + elseif (${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + set (${_PYTHON_PREFIX}_Compiler_FOUND TRUE) + # Use compiler version for future searches to ensure consistency + set (_${_PYTHON_PREFIX}_FIND_VERSIONS ${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}) + endif() + endif() + + if (${_PYTHON_PREFIX}_Compiler_FOUND) + set (${_PYTHON_PREFIX}_COMPILER_ID IronPython) + else() + unset (${_PYTHON_PREFIX}_COMPILER_ID) + endif() + + mark_as_advanced (${_PYTHON_PREFIX}_COMPILER) +endif() + + +# third step, search for the development artifacts +## Development environment is not compatible with IronPython interpreter +if ("Development" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + AND NOT ${_PYTHON_PREFIX}_INTERPRETER_ID STREQUAL "IronPython") + if (${_PYTHON_PREFIX}_FIND_REQUIRED_Development) + list (APPEND _${_PYTHON_PREFIX}_REQUIRED_VARS ${_PYTHON_PREFIX}_LIBRARY + ${_PYTHON_PREFIX}_INCLUDE_DIR) + list (APPEND _${_PYTHON_PREFIX}_CACHED_VARS ${_PYTHON_PREFIX}_LIBRARY + ${_PYTHON_PREFIX}_LIBRARY_RELEASE + ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE + ${_PYTHON_PREFIX}_LIBRARY_DEBUG + ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG + ${_PYTHON_PREFIX}_INCLUDE_DIR) + endif() + + # Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES + unset (_${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES) + if (DEFINED ${_PYTHON_PREFIX}_USE_STATIC_LIBS AND NOT WIN32) + set(_${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) + if(${_PYTHON_PREFIX}_USE_STATIC_LIBS) + set (CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) + else() + list (REMOVE_ITEM CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) + endif() + else() + endif() + + # if python interpreter is found, use its location and version to ensure consistency + # between interpreter and development environment + unset (_${_PYTHON_PREFIX}_PREFIX) + if (${_PYTHON_PREFIX}_Interpreter_FOUND) + execute_process (COMMAND "${${_PYTHON_PREFIX}_EXECUTABLE}" -c + "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.PREFIX)" + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_PREFIX + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_${_PYTHON_PREFIX}_RESULT) + unset (_${_PYTHON_PREFIX}_PREFIX) + endif() + endif() + set (_${_PYTHON_PREFIX}_HINTS "${_${_PYTHON_PREFIX}_PREFIX}" "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) + + foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) + string (REPLACE "." "" _${_PYTHON_PREFIX}_VERSION_NO_DOTS ${_${_PYTHON_PREFIX}_VERSION}) + + # try to use pythonX.Y-config tool + set (_${_PYTHON_PREFIX}_CONFIG_NAMES) + if (DEFINED CMAKE_LIBRARY_ARCHITECTURE) + set (_${_PYTHON_PREFIX}_CONFIG_NAMES "${CMAKE_LIBRARY_ARCHITECTURE}-python${_${_PYTHON_PREFIX}_VERSION}-config") + endif() + list (APPEND _${_PYTHON_PREFIX}_CONFIG_NAMES "python${_${_PYTHON_PREFIX}_VERSION}-config") + find_program (_${_PYTHON_PREFIX}_CONFIG + NAMES ${_${_PYTHON_PREFIX}_CONFIG_NAMES} + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATH_SUFFIXES bin) + unset (_${_PYTHON_PREFIX}_CONFIG_NAMES) + + if (NOT _${_PYTHON_PREFIX}_CONFIG) + continue() + endif() + + # retrieve root install directory + execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --prefix + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_PREFIX + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_${_PYTHON_PREFIX}_RESULT) + # python-config is not usable + unset (_${_PYTHON_PREFIX}_CONFIG CACHE) + continue() + endif() + set (_${_PYTHON_PREFIX}_HINTS "${_${_PYTHON_PREFIX}_PREFIX}" "${${_PYTHON_PREFIX}_ROOT_DIR}" ENV ${_PYTHON_PREFIX}_ROOT_DIR) + + # retrieve library + execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --ldflags + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_FLAGS + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (NOT _${_PYTHON_PREFIX}_RESULT) + # retrieve library directory + string (REGEX MATCHALL "-L[^ ]+" _${_PYTHON_PREFIX}_LIB_DIRS "${_${_PYTHON_PREFIX}_FLAGS}") + string (REPLACE "-L" "" _${_PYTHON_PREFIX}_LIB_DIRS "${_${_PYTHON_PREFIX}_LIB_DIRS}") + list (REMOVE_DUPLICATES _${_PYTHON_PREFIX}_LIB_DIRS) + # retrieve library name + string (REGEX MATCHALL "-lpython[^ ]+" _${_PYTHON_PREFIX}_LIB_NAMES "${_${_PYTHON_PREFIX}_FLAGS}") + string (REPLACE "-l" "" _${_PYTHON_PREFIX}_LIB_NAMES "${_${_PYTHON_PREFIX}_LIB_NAMES}") + list (REMOVE_DUPLICATES _${_PYTHON_PREFIX}_LIB_NAMES) + + find_library (${_PYTHON_PREFIX}_LIBRARY_RELEASE + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} ${_${_PYTHON_PREFIX}_LIB_DIRS} + PATH_SUFFIXES lib + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + # retrieve runtime library + if (${_PYTHON_PREFIX}_LIBRARY_RELEASE) + get_filename_component (_${_PYTHON_PREFIX}_PATH "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}" DIRECTORY) + _python_find_runtime_library (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE + NAMES ${_${_PYTHON_PREFIX}_LIB_NAMES} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_PATH} ${_${_PYTHON_PREFIX}_HINTS} + PATH_SUFFIXES bin + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + endif() + + # retrieve include directory + execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --includes + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_FLAGS + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (NOT _${_PYTHON_PREFIX}_RESULT) + # retrieve include directory + string (REGEX MATCHALL "-I[^ ]+" _${_PYTHON_PREFIX}_INCLUDE_DIRS "${_${_PYTHON_PREFIX}_FLAGS}") + string (REPLACE "-I" "" _${_PYTHON_PREFIX}_INCLUDE_DIRS "${_${_PYTHON_PREFIX}_INCLUDE_DIRS}") + list (REMOVE_DUPLICATES _${_PYTHON_PREFIX}_INCLUDE_DIRS) + + find_path (${_PYTHON_PREFIX}_INCLUDE_DIR + NAMES Python.h + HINTS ${_${_PYTHON_PREFIX}_INCLUDE_DIRS} + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + if (${_PYTHON_PREFIX}_LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_INCLUDE_DIR) + break() + endif() + endforeach() + + # Rely on HINTS and standard paths if config tool failed to locate artifacts + if (NOT (${_PYTHON_PREFIX}_LIBRARY_RELEASE OR ${_PYTHON_PREFIX}_LIBRARY_DEBUG) OR NOT ${_PYTHON_PREFIX}_INCLUDE_DIR) + foreach (_${_PYTHON_PREFIX}_VERSION IN LISTS _${_PYTHON_PREFIX}_FIND_VERSIONS) + string (REPLACE "." "" _${_PYTHON_PREFIX}_VERSION_NO_DOTS ${_${_PYTHON_PREFIX}_VERSION}) + + _python_get_frameworks (_${_PYTHON_PREFIX}_FRAMEWORK_PATHS ${_${_PYTHON_PREFIX}_VERSION}) + + # search first in known locations + find_library (${_PYTHON_PREFIX}_LIBRARY_RELEASE + NAMES python${_${_PYTHON_PREFIX}_VERSION_NO_DOTS} + python${_${_PYTHON_PREFIX}_VERSION}mu + python${_${_PYTHON_PREFIX}_VERSION}m + python${_${_PYTHON_PREFIX}_VERSION}u + python${_${_PYTHON_PREFIX}_VERSION} + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + PATH_SUFFIXES lib/${CMAKE_LIBRARY_ARCHITECTURE} lib libs + lib/python${_${_PYTHON_PREFIX}_VERSION}/config-${_${_PYTHON_PREFIX}_VERSION}mu + lib/python${_${_PYTHON_PREFIX}_VERSION}/config-${_${_PYTHON_PREFIX}_VERSION}m + lib/python${_${_PYTHON_PREFIX}_VERSION}/config-${_${_PYTHON_PREFIX}_VERSION}u + lib/python${_${_PYTHON_PREFIX}_VERSION}/config-${_${_PYTHON_PREFIX}_VERSION} + lib/python${_${_PYTHON_PREFIX}_VERSION}/config + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + # search in all default paths + find_library (${_PYTHON_PREFIX}_LIBRARY_RELEASE + NAMES python${_${_PYTHON_PREFIX}_VERSION_NO_DOTS} + python${_${_PYTHON_PREFIX}_VERSION}mu + python${_${_PYTHON_PREFIX}_VERSION}m + python${_${_PYTHON_PREFIX}_VERSION}u + python${_${_PYTHON_PREFIX}_VERSION} + NAMES_PER_DIR + PATH_SUFFIXES lib/${CMAKE_LIBRARY_ARCHITECTURE} lib libs + lib/python${_${_PYTHON_PREFIX}_VERSION}/config-${_${_PYTHON_PREFIX}_VERSION}mu + lib/python${_${_PYTHON_PREFIX}_VERSION}/config-${_${_PYTHON_PREFIX}_VERSION}m + lib/python${_${_PYTHON_PREFIX}_VERSION}/config-${_${_PYTHON_PREFIX}_VERSION}u + lib/python${_${_PYTHON_PREFIX}_VERSION}/config-${_${_PYTHON_PREFIX}_VERSION} + lib/python${_${_PYTHON_PREFIX}_VERSION}/config) + # retrieve runtime library + if (${_PYTHON_PREFIX}_LIBRARY_RELEASE) + get_filename_component (_${_PYTHON_PREFIX}_PATH "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}" DIRECTORY) + _python_find_runtime_library (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE + NAMES python${_${_PYTHON_PREFIX}_VERSION_NO_DOTS} + python${_${_PYTHON_PREFIX}_VERSION}mu + python${_${_PYTHON_PREFIX}_VERSION}m + python${_${_PYTHON_PREFIX}_VERSION}u + python${_${_PYTHON_PREFIX}_VERSION} + NAMES_PER_DIR + HINTS "${_${_PYTHON_PREFIX}_PATH}" ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + PATH_SUFFIXES bin) + endif() + + if (WIN32) + # search for debug library + if (${_PYTHON_PREFIX}_LIBRARY_RELEASE) + # use library location as a hint + get_filename_component (_${_PYTHON_PREFIX}_PATH "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}" DIRECTORY) + find_library (${_PYTHON_PREFIX}_LIBRARY_DEBUG + NAMES python${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}_d + NAMES_PER_DIR + HINTS "${_${_PYTHON_PREFIX}_PATH}" ${_${_PYTHON_PREFIX}_HINTS} + NO_DEFAULT_PATH) + else() + # search first in known locations + find_library (${_PYTHON_PREFIX}_LIBRARY_DEBUG + NAMES python${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}_d + NAMES_PER_DIR + HINTS ${_${_PYTHON_PREFIX}_HINTS} + PATHS [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + PATH_SUFFIXES lib libs + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + # search in all default paths + find_library (${_PYTHON_PREFIX}_LIBRARY_DEBUG + NAMES python${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}_d + NAMES_PER_DIR + PATH_SUFFIXES lib libs) + endif() + if (${_PYTHON_PREFIX}_LIBRARY_DEBUG) + get_filename_component (_${_PYTHON_PREFIX}_PATH "${${_PYTHON_PREFIX}_LIBRARY_DEBUG}" DIRECTORY) + _python_find_runtime_library (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG + NAMES python${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}_d + NAMES_PER_DIR + HINTS "${_${_PYTHON_PREFIX}_PATH}" ${_${_PYTHON_PREFIX}_HINTS} + PATHS [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + PATH_SUFFIXES bin) + endif() + endif() + + # Don't search for include dir until library location is known + if (${_PYTHON_PREFIX}_LIBRARY_RELEASE OR ${_PYTHON_PREFIX}_LIBRARY_DEBUG) + unset (_${_PYTHON_PREFIX}_INCLUDE_HINTS) + foreach (_${_PYTHON_PREFIX}_LIB IN ITEMS ${_PYTHON_PREFIX}_LIBRARY_RELEASE ${_PYTHON_PREFIX}_LIBRARY_DEBUG) + if (${_${_PYTHON_PREFIX}_LIB}) + # Use the library's install prefix as a hint + if (${_${_PYTHON_PREFIX}_LIB} MATCHES "^(.+/Frameworks/Python.framework/Versions/[0-9.]+)") + list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${CMAKE_MATCH_1}") + elseif (${_${_PYTHON_PREFIX}_LIB} MATCHES "^(.+)/lib(64|32)?/python[0-9.]+/config") + list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${CMAKE_MATCH_1}") + elseif (DEFINED CMAKE_LIBRARY_ARCHITECTURE AND ${_${_PYTHON_PREFIX}_LIB} MATCHES "^(.+)/lib/${CMAKE_LIBRARY_ARCHITECTURE}") + list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${CMAKE_MATCH_1}") + else() + # assume library is in a directory under root + get_filename_component (_${_PYTHON_PREFIX}_PREFIX "${${_${_PYTHON_PREFIX}_LIB}}" DIRECTORY) + get_filename_component (_${_PYTHON_PREFIX}_PREFIX "${_${_PYTHON_PREFIX}_PREFIX}" DIRECTORY) + list (APPEND _${_PYTHON_PREFIX}_INCLUDE_HINTS "${_${_PYTHON_PREFIX}_PREFIX}") + endif() + endif() + endforeach() + list (REMOVE_DUPLICATES _${_PYTHON_PREFIX}_INCLUDE_HINTS) + + find_path (${_PYTHON_PREFIX}_INCLUDE_DIR + NAMES Python.h + HINTS ${_${_PYTHON_PREFIX}_INCLUDE_HINTS} ${_${_PYTHON_PREFIX}_HINTS} + PATHS ${_${_PYTHON_PREFIX}_FRAMEWORK_PATHS} + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + [HKEY_CURRENT_USER\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_${_PYTHON_PREFIX}_VERSION}\\InstallPath] + [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\ContinuumAnalytics\\Anaconda${_${_PYTHON_PREFIX}_VERSION_NO_DOTS}-${_${_PYTHON_PREFIX}_ARCH}\\InstallPath] + PATH_SUFFIXES include/python${_${_PYTHON_PREFIX}_VERSION}mu + include/python${_${_PYTHON_PREFIX}_VERSION}m + include/python${_${_PYTHON_PREFIX}_VERSION}u + include/python${_${_PYTHON_PREFIX}_VERSION} + include + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH) + endif() + + if ((${_PYTHON_PREFIX}_LIBRARY_RELEASE OR ${_PYTHON_PREFIX}_LIBRARY_DEBUG) AND ${_PYTHON_PREFIX}_INCLUDE_DIR) + break() + endif() + endforeach() + + # search header file in standard locations + find_path (${_PYTHON_PREFIX}_INCLUDE_DIR + NAMES Python.h) + endif() + + if (${_PYTHON_PREFIX}_INCLUDE_DIR) + # retrieve version from header file + file (STRINGS "${${_PYTHON_PREFIX}_INCLUDE_DIR}/patchlevel.h" _${_PYTHON_PREFIX}_VERSION + REGEX "^#define[ \t]+PY_VERSION[ \t]+\"[^\"]+\"") + string (REGEX REPLACE "^#define[ \t]+PY_VERSION[ \t]+\"([^\"]+)\".*" "\\1" + _${_PYTHON_PREFIX}_VERSION "${_${_PYTHON_PREFIX}_VERSION}") + string (REGEX MATCHALL "[0-9]+" _${_PYTHON_PREFIX}_VERSIONS "${_${_PYTHON_PREFIX}_VERSION}") + list (GET _${_PYTHON_PREFIX}_VERSIONS 0 _${_PYTHON_PREFIX}_VERSION_MAJOR) + list (GET _${_PYTHON_PREFIX}_VERSIONS 1 _${_PYTHON_PREFIX}_VERSION_MINOR) + list (GET _${_PYTHON_PREFIX}_VERSIONS 2 _${_PYTHON_PREFIX}_VERSION_PATCH) + + if (NOT ${_PYTHON_PREFIX}_Interpreter_FOUND AND NOT ${_PYTHON_PREFIX}_Compiler_FOUND) + # set public version information + set (${_PYTHON_PREFIX}_VERSION ${_${_PYTHON_PREFIX}_VERSION}) + set (${_PYTHON_PREFIX}_VERSION_MAJOR ${_${_PYTHON_PREFIX}_VERSION_MAJOR}) + set (${_PYTHON_PREFIX}_VERSION_MINOR ${_${_PYTHON_PREFIX}_VERSION_MINOR}) + set (${_PYTHON_PREFIX}_VERSION_PATCH ${_${_PYTHON_PREFIX}_VERSION_PATCH}) + endif() + endif() + + # define public variables + include (${CMAKE_CURRENT_LIST_DIR}/../SelectLibraryConfigurations.cmake) + select_library_configurations (${_PYTHON_PREFIX}) + if (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE) + set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE}") + elseif (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG) + set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG}") + else() + set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY "$${_PYTHON_PREFIX}_RUNTIME_LIBRARY-NOTFOUND") + endif() + + _python_set_library_dirs (${_PYTHON_PREFIX}_LIBRARY_DIRS + ${_PYTHON_PREFIX}_LIBRARY_RELEASE ${_PYTHON_PREFIX}_LIBRARY_DEBUG) + if (UNIX) + if (${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$" + OR ${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$") + set (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DIRS ${${_PYTHON_PREFIX}_LIBRARY_DIRS}) + endif() + else() + _python_set_library_dirs (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DIRS + ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG) + endif() + + set (${_PYTHON_PREFIX}_INCLUDE_DIRS "${${_PYTHON_PREFIX}_INCLUDE_DIR}") + + mark_as_advanced (${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE + ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG + ${_PYTHON_PREFIX}_INCLUDE_DIR) + + if ((${_PYTHON_PREFIX}_LIBRARY_RELEASE OR ${_PYTHON_PREFIX}_LIBRARY_DEBUG) + AND ${_PYTHON_PREFIX}_INCLUDE_DIR) + if (${_PYTHON_PREFIX}_Interpreter_FOUND OR ${_PYTHON_PREFIX}_Compiler_FOUND) + # development environment must be compatible with interpreter/compiler + if (${_${_PYTHON_PREFIX}_VERSION_MAJOR}.${_${_PYTHON_PREFIX}_VERSION_MINOR} VERSION_EQUAL ${${_PYTHON_PREFIX}_VERSION_MAJOR}.${${_PYTHON_PREFIX}_VERSION_MINOR}) + set (${_PYTHON_PREFIX}_Development_FOUND TRUE) + endif() + elseif (${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + set (${_PYTHON_PREFIX}_Development_FOUND TRUE) + endif() + endif() + + # Restore the original find library ordering + if (DEFINED _${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES) + set (CMAKE_FIND_LIBRARY_SUFFIXES ${_${_PYTHON_PREFIX}_CMAKE_FIND_LIBRARY_SUFFIXES}) + endif() +endif() + +# final validation +if (${_PYTHON_PREFIX}_VERSION_MAJOR AND + NOT ${_PYTHON_PREFIX}_VERSION_MAJOR VERSION_EQUAL _${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR) + _python_display_failure ("Could NOT find ${_PYTHON_PREFIX}: Found unsuitable major version \"${${_PYTHON_PREFIX}_VERSION_MAJOR}\", but required major version is exact version \"${_${_PYTHON_PREFIX}_REQUIRED_VERSION_MAJOR}\"") +endif() + +include (${CMAKE_CURRENT_LIST_DIR}/../FindPackageHandleStandardArgs.cmake) +find_package_handle_standard_args (${_PYTHON_PREFIX} + REQUIRED_VARS ${_${_PYTHON_PREFIX}_REQUIRED_VARS} + VERSION_VAR ${_PYTHON_PREFIX}_VERSION + HANDLE_COMPONENTS) + +# Create imported targets and helper functions +if ("Interpreter" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + AND ${_PYTHON_PREFIX}_Interpreter_FOUND + AND NOT TARGET ${_PYTHON_PREFIX}::Interpreter) + add_executable (${_PYTHON_PREFIX}::Interpreter IMPORTED) + set_property (TARGET ${_PYTHON_PREFIX}::Interpreter + PROPERTY IMPORTED_LOCATION "${${_PYTHON_PREFIX}_EXECUTABLE}") +endif() + +if ("Compiler" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + AND ${_PYTHON_PREFIX}_Compiler_FOUND + AND NOT TARGET ${_PYTHON_PREFIX}::Compiler) + add_executable (${_PYTHON_PREFIX}::Compiler IMPORTED) + set_property (TARGET ${_PYTHON_PREFIX}::Compiler + PROPERTY IMPORTED_LOCATION "${${_PYTHON_PREFIX}_COMPILER}") +endif() + +if ("Development" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS + AND ${_PYTHON_PREFIX}_Development_FOUND AND NOT TARGET ${_PYTHON_PREFIX}::Python) + + if (${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$" + OR ${_PYTHON_PREFIX}_LIBRARY_DEBUG MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$" + OR ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE OR ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG) + set (_${_PYTHON_PREFIX}_LIBRARY_TYPE SHARED) + else() + set (_${_PYTHON_PREFIX}_LIBRARY_TYPE STATIC) + endif() + + add_library (${_PYTHON_PREFIX}::Python ${_${_PYTHON_PREFIX}_LIBRARY_TYPE} IMPORTED) + + set_property (TARGET ${_PYTHON_PREFIX}::Python + PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${_PYTHON_PREFIX}_INCLUDE_DIR}") + + if ((${_PYTHON_PREFIX}_LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE) + OR (${_PYTHON_PREFIX}_LIBRARY_DEBUG AND ${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG)) + # System manage shared libraries in two parts: import and runtime + if (${_PYTHON_PREFIX}_LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_LIBRARY_DEBUG) + set_property (TARGET ${_PYTHON_PREFIX}::Python PROPERTY IMPORTED_CONFIGURATIONS RELEASE DEBUG) + set_target_properties (${_PYTHON_PREFIX}::Python + PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C" + IMPORTED_IMPLIB_RELEASE "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}" + IMPORTED_LOCATION_RELEASE "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE}") + set_target_properties (${_PYTHON_PREFIX}::Python + PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" + IMPORTED_IMPLIB_DEBUG "${${_PYTHON_PREFIX}_LIBRARY_DEBUG}" + IMPORTED_LOCATION_DEBUG "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG}") + else() + set_target_properties (${_PYTHON_PREFIX}::Python + PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_IMPLIB "${${_PYTHON_PREFIX}_LIBRARY}" + IMPORTED_LOCATION "${${_PYTHON_PREFIX}_RUNTIME_LIBRARY}") + endif() + else() + if (${_PYTHON_PREFIX}_LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_LIBRARY_DEBUG) + set_property (TARGET ${_PYTHON_PREFIX}::Python PROPERTY IMPORTED_CONFIGURATIONS RELEASE DEBUG) + set_target_properties (${_PYTHON_PREFIX}::Python + PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C" + IMPORTED_LOCATION_RELEASE "${${_PYTHON_PREFIX}_LIBRARY_RELEASE}") + set_target_properties (${_PYTHON_PREFIX}::Python + PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" + IMPORTED_LOCATION_DEBUG "${${_PYTHON_PREFIX}_LIBRARY_DEBUG}") + else() + set_target_properties (${_PYTHON_PREFIX}::Python + PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${${_PYTHON_PREFIX}_LIBRARY}") + endif() + endif() + + if (_${_PYTHON_PREFIX}_CONFIG AND _${_PYTHON_PREFIX}_LIBRARY_TYPE STREQUAL "STATIC") + # extend link information with dependent libraries + execute_process (COMMAND "${_${_PYTHON_PREFIX}_CONFIG}" --ldflags + RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT + OUTPUT_VARIABLE _${_PYTHON_PREFIX}_FLAGS + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (NOT _${_PYTHON_PREFIX}_RESULT) + string (REGEX MATCHALL "-[Ll][^ ]+" _${_PYTHON_PREFIX}_LINK_LIBRARIES "${_${_PYTHON_PREFIX}_FLAGS}") + # remove elements relative to python library itself + list (FILTER _${_PYTHON_PREFIX}_LINK_LIBRARIES EXCLUDE REGEX "-lpython") + foreach (_${_PYTHON_PREFIX}_DIR IN LISTS ${_PYTHON_PREFIX}_LIBRARY_DIRS) + list (FILTER _${_PYTHON_PREFIX}_LINK_LIBRARIES EXCLUDE REGEX "-L${${_PYTHON_PREFIX}_DIR}") + endforeach() + set_property (TARGET ${_PYTHON_PREFIX}::Python + PROPERTY INTERFACE_LINK_LIBRARIES ${_${_PYTHON_PREFIX}_LINK_LIBRARIES}) + endif() + endif() + + # + # PYTHON_ADD_LIBRARY ( [STATIC|SHARED|MODULE] src1 src2 ... srcN) + # It is used to build modules for python. + # + function (__${_PYTHON_PREFIX}_ADD_LIBRARY prefix name) + cmake_parse_arguments (PARSE_ARGV 2 PYTHON_ADD_LIBRARY + "STATIC;SHARED;MODULE" "" "") + + unset (type) + if (NOT (PYTHON_ADD_LIBRARY_STATIC + OR PYTHON_ADD_LIBRARY_SHARED + OR PYTHON_ADD_LIBRARY_MODULE)) + set (type MODULE) + endif() + add_library (${name} ${type} ${ARGN}) + target_link_libraries (${name} PRIVATE ${prefix}::Python) + + # customize library name to follow module name rules + get_property (type TARGET ${name} PROPERTY TYPE) + if (type STREQUAL "MODULE_LIBRARY") + set_property (TARGET ${name} PROPERTY PREFIX "") + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set_property (TARGET ${name} PROPERTY SUFFIX ".pyd") + endif() + endif() + endfunction() +endif() + +# final clean-up + +# Restore CMAKE_FIND_FRAMEWORK +if (DEFINED _${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK) + set (CMAKE_FIND_FRAMEWORK ${_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK}) + unset (_${_PYTHON_PREFIX}_CMAKE_FIND_FRAMEWORK) +else() + unset (CMAKE_FIND_FRAMEWORK) +endif() + +unset (_${_PYTHON_PREFIX}_CONFIG CACHE) \ No newline at end of file diff --git a/tools/cmake/FindPython3.cmake b/tools/cmake/FindPython3.cmake new file mode 100644 index 00000000000..325aff57905 --- /dev/null +++ b/tools/cmake/FindPython3.cmake @@ -0,0 +1,146 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindPython3 +----------- + +Find Python 3 interpreter, compiler and development environment (include +directories and libraries). + +Three components are supported: + +* ``Interpreter``: search for Python 3 interpreter +* ``Compiler``: search for Python 3 compiler. Only offered by IronPython. +* ``Development``: search for development artifacts (include directories and + libraries) + +If no ``COMPONENTS`` is specified, ``Interpreter`` is assumed. + +To ensure consistent versions between components ``Interpreter``, ``Compiler`` +and ``Development``, specify all components at the same time:: + + find_package (Python3 COMPONENTS Interpreter Development) + +This module looks only for version 3 of Python. This module can be used +concurrently with :module:`FindPython2` module to use both Python versions. + +The :module:`FindPython` module can be used if Python version does not matter +for you. + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module defines the following :ref:`Imported Targets `: + +``Python3::Interpreter`` + Python 3 interpreter. Target defined if component ``Interpreter`` is found. +``Python3::Compiler`` + Python 3 compiler. Target defined if component ``Compiler`` is found. +``Python3::Python`` + Python 3 library. Target defined if component ``Development`` is found. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables in your project +(see :ref:`Standard Variable Names `): + +``Python3_FOUND`` + System has the Python 3 requested components. +``Python3_Interpreter_FOUND`` + System has the Python 3 interpreter. +``Python3_EXECUTABLE`` + Path to the Python 3 interpreter. +``Python3_INTERPRETER_ID`` + A short string unique to the interpreter. Possible values include: + * Python + * ActivePython + * Anaconda + * Canopy + * IronPython +``Python3_STDLIB`` + Standard platform independent installation directory. + + Information returned by + ``distutils.sysconfig.get_python_lib(plat_specific=False,standard_lib=True)``. +``Python3_STDARCH`` + Standard platform dependent installation directory. + + Information returned by + ``distutils.sysconfig.get_python_lib(plat_specific=True,standard_lib=True)``. +``Python3_SITELIB`` + Third-party platform independent installation directory. + + Information returned by + ``distutils.sysconfig.get_python_lib(plat_specific=False,standard_lib=False)``. +``Python3_SITEARCH`` + Third-party platform dependent installation directory. + + Information returned by + ``distutils.sysconfig.get_python_lib(plat_specific=True,standard_lib=False)``. +``Python3_Compiler_FOUND`` + System has the Python 3 compiler. +``Python3_COMPILER`` + Path to the Python 3 compiler. Only offered by IronPython. +``Python3_COMPILER_ID`` + A short string unique to the compiler. Possible values include: + * IronPython +``Python3_Development_FOUND`` + System has the Python 3 development artifacts. +``Python3_INCLUDE_DIRS`` + The Python 3 include directories. +``Python3_LIBRARIES`` + The Python 3 libraries. +``Python3_LIBRARY_DIRS`` + The Python 3 library directories. +``Python3_RUNTIME_LIBRARY_DIRS`` + The Python 3 runtime library directories. +``Python3_VERSION`` + Python 3 version. +``Python3_VERSION_MAJOR`` + Python 3 major version. +``Python3_VERSION_MINOR`` + Python 3 minor version. +``Python3_VERSION_PATCH`` + Python 3 patch version. + +Hints +^^^^^ + +``Python3_ROOT_DIR`` + Define the root directory of a Python 3 installation. + +``Python3_USE_STATIC_LIBS`` + * If not defined, search for shared libraries and static libraries in that + order. + * If set to TRUE, search **only** for static libraries. + * If set to FALSE, search **only** for shared libraries. + +Commands +^^^^^^^^ + +This module defines the command ``Python3_add_library`` which have the same +semantic as :command:`add_library` but take care of Python module naming rules +(only applied if library is of type ``MODULE``) and add dependency to target +``Python3::Python``:: + + Python3_add_library (my_module MODULE src1.cpp) + +If library type is not specified, ``MODULE`` is assumed. +#]=======================================================================] + + +set (_PYTHON_PREFIX Python3) + +set (_Python3_REQUIRED_VERSION_MAJOR 3) + +include (${CMAKE_CURRENT_LIST_DIR}/FindPython/Support.cmake) + +if (COMMAND __Python3_add_library) + macro (Python3_add_library) + __Python3_add_library (Python3 ${ARGV}) + endmacro() +endif() + +unset (_PYTHON_PREFIX) \ No newline at end of file From caeba3a903aecfd9d7c75a5f4e9ab5f46b45bb1f Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Fri, 3 Dec 2021 23:27:56 +0100 Subject: [PATCH 0002/1963] fix(examples): Set a maximum number of Subscriptions for the CTT tests --- examples/server_ctt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/server_ctt.c b/examples/server_ctt.c index 911aa66dea5..7d745934b16 100644 --- a/examples/server_ctt.c +++ b/examples/server_ctt.c @@ -1226,6 +1226,9 @@ int main(int argc, char **argv) { config.maxNodesPerNodeManagement = MAX_OPERATION_LIMIT; config.maxMonitoredItemsPerCall = MAX_OPERATION_LIMIT; + /* Set Subscription limits */ + config.maxSubscriptions = 100; + /* If RequestTimestamp is '0', log the warning and proceed */ config.verifyRequestTimestamp = UA_RULEHANDLING_WARN; if(enableTime) From fc89dd1753e7c0eb95adbe8bc48f58b87cf208e9 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Fri, 3 Dec 2021 23:31:17 +0100 Subject: [PATCH 0003/1963] fix(core): Use UA_String for PolicyURIs (instead of UA_ByteString) --- include/open62541/plugin/securitypolicy.h | 8 ++++---- src/ua_securechannel.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/open62541/plugin/securitypolicy.h b/include/open62541/plugin/securitypolicy.h index fc6b3a93253..1220b5d3038 100644 --- a/include/open62541/plugin/securitypolicy.h +++ b/include/open62541/plugin/securitypolicy.h @@ -16,7 +16,7 @@ _UA_BEGIN_DECLS -extern UA_EXPORT const UA_ByteString UA_SECURITY_POLICY_NONE_URI; +extern UA_EXPORT const UA_String UA_SECURITY_POLICY_NONE_URI; struct UA_SecurityPolicy; typedef struct UA_SecurityPolicy UA_SecurityPolicy; @@ -296,7 +296,7 @@ struct UA_SecurityPolicy { void *policyContext; /* The policy uri that identifies the implemented algorithms */ - UA_ByteString policyUri; + UA_String policyUri; /* The local certificate is specific for each SecurityPolicy since it * depends on the used key length. */ @@ -333,8 +333,8 @@ struct UA_PubSubSecurityPolicy; typedef struct UA_PubSubSecurityPolicy UA_PubSubSecurityPolicy; struct UA_PubSubSecurityPolicy { - UA_ByteString policyUri; /* The policy uri that identifies the implemented - * algorithms */ + UA_String policyUri; /* The policy uri that identifies the implemented + * algorithms */ UA_SecurityPolicySymmetricModule symmetricModule; /* Create the context for the WriterGroup. The keys and nonce can be NULL diff --git a/src/ua_securechannel.c b/src/ua_securechannel.c index dce41d50023..1e3aa184554 100644 --- a/src/ua_securechannel.c +++ b/src/ua_securechannel.c @@ -22,7 +22,7 @@ #define UA_BITMASK_MESSAGETYPE 0x00ffffffu #define UA_BITMASK_CHUNKTYPE 0xff000000u -const UA_ByteString UA_SECURITY_POLICY_NONE_URI = +const UA_String UA_SECURITY_POLICY_NONE_URI = {47, (UA_Byte *)"http://opcfoundation.org/UA/SecurityPolicy#None"}; #ifdef UA_ENABLE_UNIT_TEST_FAILURE_HOOKS From 9ee1ffe96dfc0c7acfd08b5a27cbc85a77e28afd Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Fri, 3 Dec 2021 23:32:58 +0100 Subject: [PATCH 0004/1963] fix(plugin): Define the SecurityPolicyUri for the x509 UserTokenPolicies --- plugins/ua_accesscontrol_default.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/plugins/ua_accesscontrol_default.c b/plugins/ua_accesscontrol_default.c index 1fa619796b7..846eb770f46 100644 --- a/plugins/ua_accesscontrol_default.c +++ b/plugins/ua_accesscontrol_default.c @@ -363,35 +363,38 @@ UA_AccessControl_default(UA_ServerConfig *config, if(allowAnonymous) { ac->userTokenPolicies[policies].tokenType = UA_USERTOKENTYPE_ANONYMOUS; ac->userTokenPolicies[policies].policyId = UA_STRING_ALLOC(ANONYMOUS_POLICY); - if (!ac->userTokenPolicies[policies].policyId.data) - return UA_STATUSCODE_BADOUTOFMEMORY; policies++; } if(verifyX509) { ac->userTokenPolicies[policies].tokenType = UA_USERTOKENTYPE_CERTIFICATE; ac->userTokenPolicies[policies].policyId = UA_STRING_ALLOC(CERTIFICATE_POLICY); - if (!ac->userTokenPolicies[policies].policyId.data) - return UA_STATUSCODE_BADOUTOFMEMORY; +#if UA_LOGLEVEL <= 400 + if(UA_ByteString_equal(userTokenPolicyUri, &UA_SECURITY_POLICY_NONE_URI)) { + UA_LOG_WARNING(&config->logger, UA_LOGCATEGORY_SERVER, + "x509 Certificate Authentication configured, " + "but no encrypting SecurityPolicy. " + "This can leak credentials on the network."); + } +#endif + UA_ByteString_copy(userTokenPolicyUri, + &ac->userTokenPolicies[policies].securityPolicyUri); policies++; } if(usernamePasswordLoginSize > 0) { ac->userTokenPolicies[policies].tokenType = UA_USERTOKENTYPE_USERNAME; ac->userTokenPolicies[policies].policyId = UA_STRING_ALLOC(USERNAME_POLICY); - if(!ac->userTokenPolicies[policies].policyId.data) - return UA_STATUSCODE_BADOUTOFMEMORY; - #if UA_LOGLEVEL <= 400 - const UA_String noneUri = UA_STRING("http://opcfoundation.org/UA/SecurityPolicy#None"); - if(UA_ByteString_equal(userTokenPolicyUri, &noneUri)) { + if(UA_ByteString_equal(userTokenPolicyUri, &UA_SECURITY_POLICY_NONE_URI)) { UA_LOG_WARNING(&config->logger, UA_LOGCATEGORY_SERVER, - "Username/Password configured, but no encrypting SecurityPolicy. " + "Username/Password Authentication configured, " + "but no encrypting SecurityPolicy. " "This can leak credentials on the network."); } #endif - return UA_ByteString_copy(userTokenPolicyUri, - &ac->userTokenPolicies[policies].securityPolicyUri); + UA_ByteString_copy(userTokenPolicyUri, + &ac->userTokenPolicies[policies].securityPolicyUri); } return UA_STATUSCODE_GOOD; } From 5ee638c8f9db337ba5c2b501b77c0cf20ff16465 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 4 Dec 2021 00:50:10 +0100 Subject: [PATCH 0005/1963] refactor(el): Forward (optional) KeyValue parameters to the ConnectionCallback --- arch/eventloop_posix_tcp.c | 13 +++++++------ include/open62541/plugin/eventloop.h | 22 +++++++++++++++++++--- tests/check_eventloop_tcp.c | 11 +++++++---- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index 66dd0ab6263..cdf6aa08c3f 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -123,7 +123,7 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, /* The socket has opened. Signal it to the application. */ cm->connectionCallback(cm, (uintptr_t)fd, fdcontext, - UA_STATUSCODE_GOOD, UA_BYTESTRING_NULL); + UA_STATUSCODE_GOOD, 0, NULL, UA_BYTESTRING_NULL); /* Now we are interested in read-events. */ UA_EventLoop_modifyFD(cm->eventSource.eventLoop, fd, UA_POSIX_EVENT_READ, @@ -164,7 +164,7 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, /* Callback to the application layer */ response.length = (size_t)ret; /* Set the length of the received buffer */ cm->connectionCallback(cm, (uintptr_t)fd, fdcontext, - UA_STATUSCODE_GOOD, response); + UA_STATUSCODE_GOOD, 0, NULL, response); } else if(UA_ERRNO != UA_INTERRUPTED && UA_ERRNO != UA_EAGAIN) { /* Orderly shutdown of the connection. Signal to the application and * then close the connection. We end up in this path after shutdown was @@ -177,7 +177,7 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, /* Close the connection if not a temporary error on a nonblocking socket */ cm->connectionCallback(cm, (uintptr_t)fd, fdcontext, UA_STATUSCODE_BADCONNECTIONCLOSED, - UA_BYTESTRING_NULL); + 0, NULL, UA_BYTESTRING_NULL); TCP_close(cm, fd); } @@ -256,7 +256,7 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, /* The socket has opened. Signal it to the application. The callback can * switch out the context. So put it into a temp variable. */ cm->connectionCallback(cm, (uintptr_t)newsockfd, &ctx, UA_STATUSCODE_GOOD, - UA_BYTESTRING_NULL); + 0, NULL, UA_BYTESTRING_NULL); /* Register in the EventLoop. Signal to the user if registering failed. */ res = UA_EventLoop_registerFD(cm->eventSource.eventLoop, newsockfd, @@ -265,7 +265,8 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, &cm->eventSource, ctx); if(res != UA_STATUSCODE_GOOD) { cm->connectionCallback(cm, (uintptr_t)newsockfd, &ctx, - UA_STATUSCODE_BADINTERNALERROR, UA_BYTESTRING_NULL); + UA_STATUSCODE_BADINTERNALERROR, + 0, NULL, UA_BYTESTRING_NULL); UA_close(newsockfd); } @@ -390,7 +391,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { UA_StatusCode res = UA_EventLoop_registerFD(cm->eventSource.eventLoop, listenSocket, UA_POSIX_EVENT_READ, - (UA_FDCallback) TCP_listenSocketCallback, + (UA_FDCallback)TCP_listenSocketCallback, &cm->eventSource, NULL); if(res != UA_STATUSCODE_GOOD) { UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), diff --git a/include/open62541/plugin/eventloop.h b/include/open62541/plugin/eventloop.h index 3f15a4d816c..e2e20bb38a8 100644 --- a/include/open62541/plugin/eventloop.h +++ b/include/open62541/plugin/eventloop.h @@ -192,12 +192,28 @@ typedef struct UA_ConnectionManager UA_ConnectionManager; /** * The ConnectionCallback is the only interface from the connection back to the - * application. The connectionId is announced to the application when it is - * first used for the callback. The context is a double-pointer so the context - * can be overwritten by the application */ + * application. + * + * - The connectionId is initially unknown to the target application and + * "announced" to the application when first used first in this callback. + * + * - The context is attached to the connection. Initially a default context is set. + * The context can be replaced within the callback (via the double-pointer). + * + * - The status indicates whether the connection is closing down. If status != + * GOOD, then the application should clean up the context, as this is the last + * time the callback will be called for this connection. + * + * - The parameters are a key-value list with additional information. The + * possible keys and their meaning are documented for the individual + * ConnectionManager implementations. + * + * - The msg ByteString is the message (or packet) received on the + * connection. Can be empty. */ typedef void (*UA_ConnectionCallback)(UA_ConnectionManager *cm, uintptr_t connectionId, void **connectionContext, UA_StatusCode status, + size_t paramsSize, UA_KeyValuePair *params, UA_ByteString msg); struct UA_ConnectionManager { diff --git a/tests/check_eventloop_tcp.c b/tests/check_eventloop_tcp.c index 81027701103..acbf815cdd3 100644 --- a/tests/check_eventloop_tcp.c +++ b/tests/check_eventloop_tcp.c @@ -16,8 +16,9 @@ UA_EventLoop *el; static void noopCallback(UA_ConnectionManager *cm, uintptr_t connectionId, - void **connectionContext, UA_StatusCode status, - UA_ByteString msg) {} + void **connectionContext, UA_StatusCode status, + size_t paramsSize, UA_KeyValuePair *params, + UA_ByteString msg) {} START_TEST(listenTCP) { el = UA_EventLoop_new(UA_Log_Stdout); @@ -58,6 +59,7 @@ static UA_Boolean received; static void connectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId, void **connectionContext, UA_StatusCode status, + size_t paramsSize, UA_KeyValuePair *params, UA_ByteString msg) { if(*connectionContext != NULL) clientId = connectionId; @@ -76,8 +78,9 @@ connectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId, static void illegalConnectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId, - void **connectionContext, UA_StatusCode status, - UA_ByteString msg) { + void **connectionContext, UA_StatusCode status, + size_t paramsSize, UA_KeyValuePair *params, + UA_ByteString msg) { UA_StatusCode rv = UA_EventLoop_run(el, 1); ck_assert_uint_eq(rv, UA_STATUSCODE_BADINTERNALERROR); if(*connectionContext != NULL) From a70790354f54b77906e823fd28b097ee376a74b5 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 4 Dec 2021 00:55:34 +0100 Subject: [PATCH 0006/1963] feat(el): Add UA_EventLoop_findEventSource --- arch/eventloop_posix.c | 13 +++++++++++++ include/open62541/plugin/eventloop.h | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/arch/eventloop_posix.c b/arch/eventloop_posix.c index d62620984ba..b8baa216f47 100644 --- a/arch/eventloop_posix.c +++ b/arch/eventloop_posix.c @@ -470,6 +470,19 @@ UA_EventLoop_deregisterEventSource(UA_EventLoop *el, UA_EventSource *es) { return UA_STATUSCODE_GOOD; } +UA_EventSource * +UA_EventLoop_findEventSource(UA_EventLoop *el, const UA_String name) { + UA_LOCK(&el->elMutex); + UA_EventSource *s = el->eventSources; + while(s) { + if(UA_String_equal(&name, &s->name)) + break; + s = s->next; + } + UA_UNLOCK(&el->elMutex); + return s; +} + /********************************/ /* Registering File Descriptors */ /********************************/ diff --git a/include/open62541/plugin/eventloop.h b/include/open62541/plugin/eventloop.h index e2e20bb38a8..66ffc313abb 100644 --- a/include/open62541/plugin/eventloop.h +++ b/include/open62541/plugin/eventloop.h @@ -178,6 +178,11 @@ UA_EXPORT UA_StatusCode UA_EventLoop_deregisterEventSource(UA_EventLoop *el, UA_EventSource *es); +/* Look up the EventSource by name. Returns the first EventSource of that name + * (duplicates should be avoided). */ +UA_EXPORT UA_EventSource * +UA_EventLoop_findEventSource(UA_EventLoop *el, const UA_String name); + /** * Connection Manager * ------------------ From 0987dbe335add507af92d404245a40aa25eb9c09 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 4 Dec 2021 01:04:56 +0100 Subject: [PATCH 0007/1963] feat(el): Add an EventSourceType tag So we can correctly cast after looking up by name. --- arch/eventloop_posix_tcp.c | 1 + include/open62541/plugin/eventloop.h | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index cdf6aa08c3f..53779ed0731 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -775,6 +775,7 @@ UA_ConnectionManager_TCP_new(const UA_String eventSourceName) { if(!cm) return NULL; + cm->cm.eventSource.eventSourceType = UA_EVENTSOURCETYPE_CONNECTIONMANAGER; UA_String_copy(&eventSourceName, &cm->cm.eventSource.name); cm->cm.eventSource.start = (UA_StatusCode (*)(UA_EventSource *)) TCP_eventSourceStart; cm->cm.eventSource.stop = (void (*)(UA_EventSource *))TCP_eventSourceStop; diff --git a/include/open62541/plugin/eventloop.h b/include/open62541/plugin/eventloop.h index 66ffc313abb..61b285d2662 100644 --- a/include/open62541/plugin/eventloop.h +++ b/include/open62541/plugin/eventloop.h @@ -146,21 +146,30 @@ typedef enum { * EventLoop cycles to finish */ } UA_EventSourceState; +/* Type-tag for proper casting of the difference EventSource (e.g. when they are + * looked up via UA_EventLoop_findEventSource). */ +typedef enum { + UA_EVENTSOURCETYPE_ANY = 0, + UA_EVENTSOURCETYPE_CONNECTIONMANAGER +} UA_EventSourceType; + struct UA_EventSource { struct UA_EventSource *next; /* Singly-linked list for use by the * application that registered the ES */ + UA_EventSourceType eventSourceType; + /* Configuration * ~~~~~~~~~~~~~ */ - UA_String name; /* Unique name of the ES for logging */ + UA_String name; /* Unique name of the ES */ + UA_EventLoop *eventLoop; /* EventLoop where the ES is registered */ void *application; /* Application to which the ES belongs */ - size_t paramsSize; - UA_KeyValuePair *params; /* Configuration parameters */ + size_t paramsSize; /* Configuration parameters */ + UA_KeyValuePair *params; /* Lifecycle * ~~~~~~~~~ */ UA_EventSourceState state; - UA_EventLoop *eventLoop; /* EventLoop where the ES is registered */ UA_StatusCode (*start)(UA_EventSource *es); void (*stop)(UA_EventSource *es); /* Asynchronous. Iterate theven EventLoop * until the EventSource is stopped. */ From a02317969a026cfb962a39b34534af09ea422d2a Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 4 Dec 2021 01:07:06 +0100 Subject: [PATCH 0008/1963] refactor(el): Simplify logging in the TCP EventSource --- arch/eventloop_posix_tcp.c | 56 +++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index 53779ed0731..668dfed0515 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -77,7 +77,7 @@ static UA_StatusCode TCP_close(UA_ConnectionManager *cm, UA_FD fd) { UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Closing connection", (unsigned)fd); + "TCP %u\t| Closing connection", (unsigned)fd); TCPConnectionManager *tcm = (TCPConnectionManager*)cm; @@ -94,7 +94,7 @@ TCP_close(UA_ConnectionManager *cm, UA_FD fd) { tcm->fdCount--; UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), - UA_LOGCATEGORY_NETWORK, "TCP #%u\t| Socket closed", (unsigned)fd); + UA_LOGCATEGORY_NETWORK, "TCP %u\t| Socket closed", (unsigned)fd); /* Stopped? */ if(tcm->fdCount == 0 && cm->eventSource.state == UA_EVENTSOURCESTATE_STOPPING) { @@ -112,14 +112,14 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, void **fdcontext, short event) { UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Activity on the socket", (unsigned)fd); + "TCP %u\t| Activity on the socket", (unsigned)fd); /* Write-Event, a new connection has opened. */ UA_StatusCode res = UA_STATUSCODE_GOOD; if(event & UA_POSIX_EVENT_WRITE) { UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Opening a new connection", (unsigned)fd); + "TCP %u\t| Opening a new connection", (unsigned)fd); /* The socket has opened. Signal it to the application. */ cm->connectionCallback(cm, (uintptr_t)fd, fdcontext, @@ -133,7 +133,7 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Allocate receive buffer", (unsigned)fd); + "TCP %u\t| Allocate receive buffer", (unsigned)fd); /* Allocate the receive-buffer */ UA_ByteString response; @@ -147,18 +147,18 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, ssize_t ret = UA_recv(fd, (char*)response.data, response.length, MSG_DONTWAIT); UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| recv(...) returned %zd", (unsigned)fd, ret); + "TCP %u\t| recv(...) returned %zd", (unsigned)fd, ret); #else int ret = UA_recv(fd, (char*)response.data, response.length, MSG_DONTWAIT); UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| recv(...) returned %d", (unsigned)fd, ret); + "TCP %u\t| recv(...) returned %d", (unsigned)fd, ret); #endif if(ret > 0) { UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Received message of size %u", + "TCP %u\t| Received message of size %u", (unsigned)fd, (unsigned)ret); /* Callback to the application layer */ @@ -172,7 +172,7 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, * iteration and the socket is known to be unused. */ UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| recv signaled closed connection", (unsigned)fd); + "TCP %u\t| recv signaled closed connection", (unsigned)fd); /* Close the connection if not a temporary error on a nonblocking socket */ cm->connectionCallback(cm, (uintptr_t)fd, fdcontext, @@ -190,7 +190,7 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, void **fdcontext, short event) { UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Callback on server socket", (unsigned)fd); + "TCP %u\t| Callback on server socket", (unsigned)fd); /* Try to accept a new connection */ struct sockaddr_storage remote; @@ -206,7 +206,7 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, UA_LOG_SOCKET_ERRNO_WRAP( UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Error %s, closing the server socket", + "TCP %u\t| Error %s, closing the server socket", (unsigned)fd, errno_str)); } TCP_close(cm, fd); @@ -226,13 +226,13 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, UA_LOG_SOCKET_ERRNO_WRAP( UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| getnameinfo(...) could not resolve the " + "TCP %u\t| getnameinfo(...) could not resolve the " "hostname (%s)", (unsigned)fd, errno_str)); } } UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Connection opened from \"%s\" via the server socket #%u", + "TCP %u\t| Connection opened from \"%s\" via the server socket %u", (unsigned)newsockfd, hoststr, (unsigned)fd); #endif @@ -245,7 +245,7 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, UA_LOG_SOCKET_ERRNO_WRAP( UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Error seeting the TCP options (%s), closing", + "TCP %u\t| Error seeting the TCP options (%s), closing", (unsigned)newsockfd, errno_str)); /* Close the new socket */ UA_close(newsockfd); @@ -305,7 +305,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { UA_LOG_SOCKET_ERRNO_WRAP( UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Error opening the listen socket for " + "TCP %u\t| Error opening the listen socket for " "\"%s\" on port %s(%s)", (unsigned)listenSocket, hoststr, portstr, errno_str)); return; @@ -313,7 +313,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| New server socket for \"%s\" on port %s", + "TCP %u\t| New server socket for \"%s\" on port %s", (unsigned)listenSocket, hoststr, portstr); /* Some Linux distributions have net.ipv6.bindv6only not activated. So @@ -326,7 +326,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { (const char*)&optval, sizeof(optval)) == -1) { UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Could not set an IPv6 socket to IPv6 only, closing", + "TCP %u\t| Could not set an IPv6 socket to IPv6 only, closing", (unsigned)listenSocket); UA_close(listenSocket); return; @@ -338,7 +338,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { (const char *)&optval, sizeof(optval)) == -1) { UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Could not make the socket reusable, closing", + "TCP %u\t| Could not make the socket reusable, closing", (unsigned)listenSocket); UA_close(listenSocket); return; @@ -348,7 +348,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { if(TCP_setNonBlocking(listenSocket) != UA_STATUSCODE_GOOD) { UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Could not set the socket non-blocking, closing", + "TCP %u\t| Could not set the socket non-blocking, closing", (unsigned)listenSocket); UA_close(listenSocket); return; @@ -358,7 +358,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { if(TCP_setNoSigPipe(listenSocket) != UA_STATUSCODE_GOOD) { UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Could not disable SIGPIPE, closing", + "TCP %u\t| Could not disable SIGPIPE, closing", (unsigned)listenSocket); UA_close(listenSocket); return; @@ -370,7 +370,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { UA_LOG_SOCKET_ERRNO_WRAP( UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Error binding the socket to the address (%s), closing", + "TCP %u\t| Error binding the socket to the address (%s), closing", (unsigned)listenSocket, errno_str)); UA_close(listenSocket); return; @@ -381,7 +381,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { UA_LOG_SOCKET_ERRNO_WRAP( UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Error listening on the socket (%s), closing", + "TCP %u\t| Error listening on the socket (%s), closing", (unsigned)listenSocket, errno_str)); UA_close(listenSocket); return; @@ -396,7 +396,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { if(res != UA_STATUSCODE_GOOD) { UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Error registering the socket in the " + "TCP %u\t| Error registering the socket in the " "EventLoop, closing", (unsigned)listenSocket); UA_close(listenSocket); return; @@ -450,7 +450,7 @@ static UA_StatusCode TCP_shutdownConnection(UA_ConnectionManager *cm, uintptr_t connectionId) { UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Shutdown called", (unsigned)connectionId); + "TCP %u\t| Shutdown called", (unsigned)connectionId); /* Shutdown, will be picked up by the next iteration of the event loop */ #ifndef _WIN32 @@ -463,7 +463,7 @@ TCP_shutdownConnection(UA_ConnectionManager *cm, uintptr_t connectionId) { UA_LOG_SOCKET_ERRNO_WRAP( UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Error shutting down the socket (%s), closing", + "TCP %u\t| Error shutting down the socket (%s), closing", (unsigned)connectionId, errno_str)); retval = TCP_close(cm, (UA_FD)connectionId); } @@ -484,7 +484,7 @@ TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, do { UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Attempting to send", (unsigned)connectionId); + "TCP %u\t| Attempting to send", (unsigned)connectionId); size_t bytes_to_send = buf->length - nWritten; n = UA_send((UA_FD)connectionId, (const char*)buf->data + nWritten, @@ -493,7 +493,7 @@ TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, UA_LOG_SOCKET_ERRNO_GAI_WRAP( UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| Send failed with error %s", + "TCP %u\t| Send failed with error %s", (unsigned)connectionId, errno_str)); TCP_shutdownConnection(cm, connectionId); UA_ByteString_clear(buf); @@ -627,7 +627,7 @@ TCP_openConnection(UA_ConnectionManager *cm, UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, - "TCP #%u\t| New connection to \"%s\" on port %s", + "TCP %u\t| New connection to \"%s\" on port %s", (unsigned)newSock, hostname, portStr); return UA_STATUSCODE_GOOD; From b260b40c2e0e2f367bafa5430bd368b9e99f59e5 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 4 Dec 2021 10:02:55 +0100 Subject: [PATCH 0009/1963] refactor(core): UA_KeyValueMap_get takes a const pointer --- include/open62541/util.h | 4 ++-- src/ua_util.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/open62541/util.h b/include/open62541/util.h index 0409d08a5fa..ee4489b548e 100644 --- a/include/open62541/util.h +++ b/include/open62541/util.h @@ -53,13 +53,13 @@ UA_KeyValueMap_set(UA_KeyValuePair **map, size_t *mapSize, /* Returns a pointer to the value or NULL if the key is not found.*/ UA_EXPORT const UA_Variant * -UA_KeyValueMap_get(UA_KeyValuePair *map, size_t mapSize, +UA_KeyValueMap_get(const UA_KeyValuePair *map, size_t mapSize, const UA_QualifiedName key); /* Returns NULL if the value for the key is not defined or not of the right * datatype and scalar/array */ UA_EXPORT const void * -UA_KeyValueMap_getScalar(UA_KeyValuePair *map, size_t mapSize, +UA_KeyValueMap_getScalar(const UA_KeyValuePair *map, size_t mapSize, const UA_QualifiedName key, const UA_DataType *type); diff --git a/src/ua_util.c b/src/ua_util.c index 0380bd835bf..8b36f130c8b 100644 --- a/src/ua_util.c +++ b/src/ua_util.c @@ -253,7 +253,7 @@ UA_KeyValueMap_set(UA_KeyValuePair **map, size_t *mapSize, } const UA_Variant * -UA_KeyValueMap_get(UA_KeyValuePair *map, size_t mapSize, +UA_KeyValueMap_get(const UA_KeyValuePair *map, size_t mapSize, const UA_QualifiedName key) { for(size_t i = 0; i < mapSize; i++) { if(map[i].key.namespaceIndex == key.namespaceIndex && @@ -266,7 +266,7 @@ UA_KeyValueMap_get(UA_KeyValuePair *map, size_t mapSize, /* Returns NULL if the parameter is not defined or not of the right datatype */ const void * -UA_KeyValueMap_getScalar(UA_KeyValuePair *map, size_t mapSize, +UA_KeyValueMap_getScalar(const UA_KeyValuePair *map, size_t mapSize, const UA_QualifiedName key, const UA_DataType *type) { const UA_Variant *v = UA_KeyValueMap_get(map, mapSize, key); From 24df7a604127087b8d4b8810b05f209fa4505a89 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 4 Dec 2021 10:04:01 +0100 Subject: [PATCH 0010/1963] refactor(el): Make KeyValue-maps in the callbacks const --- arch/eventloop_posix_tcp.c | 5 ++--- include/open62541/plugin/eventloop.h | 10 +++++++--- tests/check_eventloop_tcp.c | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index 668dfed0515..e0f4ed9e0a0 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -472,7 +472,7 @@ TCP_shutdownConnection(UA_ConnectionManager *cm, uintptr_t connectionId) { static UA_StatusCode TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, - size_t paramsSize, UA_KeyValuePair *params, + size_t paramsSize, const UA_KeyValuePair *params, UA_ByteString *buf) { /* Prevent OS signals when sending to a closed socket */ int flags = MSG_NOSIGNAL; @@ -510,9 +510,8 @@ TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, static UA_StatusCode TCP_openConnection(UA_ConnectionManager *cm, - size_t paramsSize, UA_KeyValuePair *params, + size_t paramsSize, const UA_KeyValuePair *params, void *context) { - /* Get the connection parameters */ char hostname[256]; char portStr[16]; diff --git a/include/open62541/plugin/eventloop.h b/include/open62541/plugin/eventloop.h index 61b285d2662..597a7b63254 100644 --- a/include/open62541/plugin/eventloop.h +++ b/include/open62541/plugin/eventloop.h @@ -227,7 +227,7 @@ typedef struct UA_ConnectionManager UA_ConnectionManager; typedef void (*UA_ConnectionCallback)(UA_ConnectionManager *cm, uintptr_t connectionId, void **connectionContext, UA_StatusCode status, - size_t paramsSize, UA_KeyValuePair *params, + size_t paramsSize, const UA_KeyValuePair *params, UA_ByteString msg); struct UA_ConnectionManager { @@ -258,12 +258,16 @@ struct UA_ConnectionManager { * (for TCP). Other protocols (e.g. MQTT, AMQP, etc.) may required * additional arguments to open a connection. * + * The provided context is set as the initial context attached to this + * connection. It is already set before the first call to + * cm->connectionCallback. + * * The connection is opened asynchronously. The ConnectionCallback is * triggered when the connection is fully opened (UA_STATUSCODE_GOOD) or has * failed (with an error code). */ UA_StatusCode (*openConnection)(UA_ConnectionManager *cm, - size_t paramsSize, UA_KeyValuePair *params, + size_t paramsSize, const UA_KeyValuePair *params, void *context); /* Connection Activities @@ -290,7 +294,7 @@ struct UA_ConnectionManager { * example a tx-time for sending in time-synchronized TSN settings. */ UA_StatusCode (*sendWithConnection)(UA_ConnectionManager *cm, uintptr_t connectionId, - size_t paramsSize, UA_KeyValuePair *params, + size_t paramsSize, const UA_KeyValuePair *params, UA_ByteString *buf); /* When a connection is closed, cm->connectionCallback is called with diff --git a/tests/check_eventloop_tcp.c b/tests/check_eventloop_tcp.c index acbf815cdd3..7a430005185 100644 --- a/tests/check_eventloop_tcp.c +++ b/tests/check_eventloop_tcp.c @@ -17,7 +17,7 @@ UA_EventLoop *el; static void noopCallback(UA_ConnectionManager *cm, uintptr_t connectionId, void **connectionContext, UA_StatusCode status, - size_t paramsSize, UA_KeyValuePair *params, + size_t paramsSize, const UA_KeyValuePair *params, UA_ByteString msg) {} START_TEST(listenTCP) { @@ -59,7 +59,7 @@ static UA_Boolean received; static void connectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId, void **connectionContext, UA_StatusCode status, - size_t paramsSize, UA_KeyValuePair *params, + size_t paramsSize, const UA_KeyValuePair *params, UA_ByteString msg) { if(*connectionContext != NULL) clientId = connectionId; @@ -79,7 +79,7 @@ connectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId, static void illegalConnectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId, void **connectionContext, UA_StatusCode status, - size_t paramsSize, UA_KeyValuePair *params, + size_t paramsSize, const UA_KeyValuePair *params, UA_ByteString msg) { UA_StatusCode rv = UA_EventLoop_run(el, 1); ck_assert_uint_eq(rv, UA_STATUSCODE_BADINTERNALERROR); From 28f85f05ea9e26b44e57eb7e6e18d179df0dbedd Mon Sep 17 00:00:00 2001 From: andreasebner Date: Tue, 7 Dec 2021 12:09:50 +0100 Subject: [PATCH 0011/1963] fix(ci) build debian package branch only for standard releases (eg. no rc) (#4822) --- .github/workflows/debian_packing.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/debian_packing.yml b/.github/workflows/debian_packing.yml index 345a7519e25..4b2d06df116 100644 --- a/.github/workflows/debian_packing.yml +++ b/.github/workflows/debian_packing.yml @@ -4,7 +4,8 @@ on: push: branches: [ main, master ] tags: - - v1.* + - 'v1.*' + - 'v1.*.*' jobs: Debian-Package-Branch-Preperation: From 506c785bcb1869301c5a64fc4dc2ffbeae9ec38a Mon Sep 17 00:00:00 2001 From: Mark Giraud Date: Tue, 7 Dec 2021 16:01:28 +0100 Subject: [PATCH 0012/1963] feat(build): Move modules to cmake3.12 (or less) specific directory such that they are not included for newer cmake versions. (#4824) Newer cmake versions already have these modules packaged and we should use those in favor of possibly outdated ones. --- CMakeLists.txt | 3 +++ tools/{cmake => cmake3.12}/CMakeFindFrameworks.cmake | 0 tools/{cmake => cmake3.12}/FindPackageHandleStandardArgs.cmake | 0 tools/{cmake => cmake3.12}/FindPackageMessage.cmake | 0 tools/{cmake => cmake3.12}/FindPython/Support.cmake | 0 tools/{cmake => cmake3.12}/FindPython3.cmake | 0 6 files changed, 3 insertions(+) rename tools/{cmake => cmake3.12}/CMakeFindFrameworks.cmake (100%) rename tools/{cmake => cmake3.12}/FindPackageHandleStandardArgs.cmake (100%) rename tools/{cmake => cmake3.12}/FindPackageMessage.cmake (100%) rename tools/{cmake => cmake3.12}/FindPython/Support.cmake (100%) rename tools/{cmake => cmake3.12}/FindPython3.cmake (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index e5c31e902d8..1f7c6fdfda8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,9 @@ endif() string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER_CASE) set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/tools/cmake") +if(${CMAKE_VERSION} VERSION_LESS 3.12) + set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${PROJECT_SOURCE_DIR}/tools/cmake3.12") +endif() find_package(Python3 REQUIRED) set(PYTHON_EXECUTABLE ${Python3_EXECUTABLE}) find_package(Git) diff --git a/tools/cmake/CMakeFindFrameworks.cmake b/tools/cmake3.12/CMakeFindFrameworks.cmake similarity index 100% rename from tools/cmake/CMakeFindFrameworks.cmake rename to tools/cmake3.12/CMakeFindFrameworks.cmake diff --git a/tools/cmake/FindPackageHandleStandardArgs.cmake b/tools/cmake3.12/FindPackageHandleStandardArgs.cmake similarity index 100% rename from tools/cmake/FindPackageHandleStandardArgs.cmake rename to tools/cmake3.12/FindPackageHandleStandardArgs.cmake diff --git a/tools/cmake/FindPackageMessage.cmake b/tools/cmake3.12/FindPackageMessage.cmake similarity index 100% rename from tools/cmake/FindPackageMessage.cmake rename to tools/cmake3.12/FindPackageMessage.cmake diff --git a/tools/cmake/FindPython/Support.cmake b/tools/cmake3.12/FindPython/Support.cmake similarity index 100% rename from tools/cmake/FindPython/Support.cmake rename to tools/cmake3.12/FindPython/Support.cmake diff --git a/tools/cmake/FindPython3.cmake b/tools/cmake3.12/FindPython3.cmake similarity index 100% rename from tools/cmake/FindPython3.cmake rename to tools/cmake3.12/FindPython3.cmake From 40626280ca167781bb88453be222b894fa643e1f Mon Sep 17 00:00:00 2001 From: Mark Giraud Date: Tue, 7 Dec 2021 23:54:33 +0100 Subject: [PATCH 0013/1963] fix(deps): Avoid adding offset to nullptr. Might have security impact? (#4825) --- deps/ziptree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/ziptree.c b/deps/ziptree.c index 0bbf8f67d16..7c8ad6153c9 100644 --- a/deps/ziptree.c +++ b/deps/ziptree.c @@ -45,13 +45,13 @@ struct zip_entry { void * __ZIP_INSERT(zip_cmp_cb cmp, unsigned short fieldoffset, unsigned short keyoffset, void *root, void *elm) { - struct zip_entry *root_entry = ZIP_ENTRY_PTR(root); struct zip_entry *elm_entry = ZIP_ENTRY_PTR(elm); if(!root) { elm_entry->left = NULL; elm_entry->right = NULL; return elm; } + struct zip_entry *root_entry = ZIP_ENTRY_PTR(root); enum ZIP_CMP order = cmp(ZIP_KEY_PTR(elm), ZIP_KEY_PTR(root)); if(order == ZIP_CMP_LESS) { if(__ZIP_INSERT(cmp, fieldoffset, keyoffset, root_entry->left, elm) == elm) { From 7daacb34f6a9864c344e9bbcc3a0343c3cc9d582 Mon Sep 17 00:00:00 2001 From: andreasebner Date: Wed, 8 Dec 2021 16:15:01 +0100 Subject: [PATCH 0014/1963] fix(server) change namespaceSize variable type (#4823) --- src/server/ua_server.c | 2 +- src/server/ua_server_internal.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server/ua_server.c b/src/server/ua_server.c index 7ce8048bbba..c2560363731 100644 --- a/src/server/ua_server.c +++ b/src/server/ua_server.c @@ -66,7 +66,7 @@ UA_UInt16 addNamespace(UA_Server *server, const UA_String name) { /* Check if the namespace already exists in the server's namespace array */ for(UA_UInt16 i = 0; i < server->namespacesSize; ++i) { if(UA_String_equal(&name, &server->namespaces[i])) - return i; + return (UA_UInt16) i; } /* Make the array bigger */ diff --git a/src/server/ua_server_internal.h b/src/server/ua_server_internal.h index 9f5c087c471..0391546f6d3 100644 --- a/src/server/ua_server_internal.h +++ b/src/server/ua_server_internal.h @@ -12,6 +12,7 @@ * Copyright 2017 (c) Julian Grothoff * Copyright 2019 (c) Kalycito Infotech Private Limited * Copyright 2019 (c) HMS Industrial Networks AB (Author: Jonas Green) + * Copyright 2021 (c) Fraunhofer IOSB (Author: Andreas Ebner) */ #ifndef UA_SERVER_INTERNAL_H_ From 4c0c82cef2d26b9b59111544d76acfe152da83aa Mon Sep 17 00:00:00 2001 From: Mark Giraud Date: Sat, 11 Dec 2021 14:29:00 +0100 Subject: [PATCH 0015/1963] feat(build): Add .idea project files (#4830) This commit adds the .idea directory that contains project and formatting definitions for CLion users. No user specific data is contained in the directory. Opening the project in CLion will automatically use the project files. --- .gitignore | 1 - .idea/.gitignore | 8 +++++ .idea/codeStyles/Project.xml | 45 ++++++++++++++++++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 +++ .idea/csv-plugin.xml | 51 ++++++++++++++++++++++++++++ .idea/misc.xml | 12 +++++++ .idea/modules.xml | 8 +++++ .idea/open62541.iml | 2 ++ .idea/vcs.xml | 8 +++++ 9 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/csv-plugin.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/open62541.iml create mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index 1ec893db20b..e6d2e291239 100644 --- a/.gitignore +++ b/.gitignore @@ -78,7 +78,6 @@ Makefile /exampleClient_legacy /src_generated/ /build -/.idea /cmake-build* /tools/certs/certs/* Pipfile diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000000..13566b81b01 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000000..8e53702d295 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000000..79ee123c2b2 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/csv-plugin.xml b/.idea/csv-plugin.xml new file mode 100644 index 00000000000..e85272a2a04 --- /dev/null +++ b/.idea/csv-plugin.xml @@ -0,0 +1,51 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000000..97470699605 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000000..2e2b5d56c99 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/open62541.iml b/.idea/open62541.iml new file mode 100644 index 00000000000..f08604bb65b --- /dev/null +++ b/.idea/open62541.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000000..9f4ebebc6e4 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From 68520bb0d5dcaaf671cea79c369c6893e09249c0 Mon Sep 17 00:00:00 2001 From: Mark Giraud Date: Thu, 9 Dec 2021 12:12:16 +0100 Subject: [PATCH 0016/1963] fix: Fuzzing didn't compile because of missing sources. --- tests/fuzz/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt index cc011927154..2b990250065 100644 --- a/tests/fuzz/CMakeLists.txt +++ b/tests/fuzz/CMakeLists.txt @@ -62,6 +62,8 @@ link_libraries("-fsanitize=fuzzer") # Use different plugins for testing set(fuzzing_plugin_sources ${PROJECT_SOURCE_DIR}/arch/network_tcp.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_tcp.c ${PROJECT_SOURCE_DIR}/tests/testing-plugins/testing_clock.c ${PROJECT_SOURCE_DIR}/tests/testing-plugins/testing_networklayers.c ${PROJECT_SOURCE_DIR}/plugins/ua_log_stdout.c From b541eca487db470a3f31aa596e4b3350a4c33359 Mon Sep 17 00:00:00 2001 From: Mark Giraud Date: Thu, 9 Dec 2021 12:04:33 +0100 Subject: [PATCH 0017/1963] refactor: Reduce log spam in eventloop --- arch/eventloop_posix.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/eventloop_posix.c b/arch/eventloop_posix.c index b8baa216f47..f7cb3d4839a 100644 --- a/arch/eventloop_posix.c +++ b/arch/eventloop_posix.c @@ -268,7 +268,7 @@ processFDs(UA_EventLoop *el, UA_DateTime usedTimeout) { /* Nothing to do? */ if(highestfd == UA_INVALID_FD) { - UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_TRACE(el->logger, UA_LOGCATEGORY_EVENTLOOP, "No valid FDs for processing"); return UA_STATUSCODE_GOOD; } @@ -347,7 +347,7 @@ UA_StatusCode UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) { UA_LOCK(&el->elMutex); - UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, "iterate the EventLoop"); + UA_LOG_TRACE(el->logger, UA_LOGCATEGORY_EVENTLOOP, "iterate the EventLoop"); if(el->executing) { UA_LOG_ERROR(el->logger, From f1b3f0f0f4145e2907fc57495e4e28fc532fab59 Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Fri, 26 Nov 2021 20:52:27 +0000 Subject: [PATCH 0018/1963] feat(ci): Add CIFuzz integration --- .github/workflows/cifuzz.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/cifuzz.yml diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 00000000000..190798db298 --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,24 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'open62541' + dry-run: false + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'open62541' + fuzz-seconds: 600 + dry-run: false + - name: Upload Crash + uses: actions/upload-artifact@v1 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts From 93823da590c63fb20884db678663ac3cbe58e377 Mon Sep 17 00:00:00 2001 From: andreasebner Date: Thu, 16 Dec 2021 11:21:30 +0100 Subject: [PATCH 0019/1963] fix(build) adjust variable type to solve reported code scanning finding(#4854) --- src/server/ua_server.c | 2 +- tools/prepare_packaging.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/ua_server.c b/src/server/ua_server.c index c2560363731..40c8ef21052 100644 --- a/src/server/ua_server.c +++ b/src/server/ua_server.c @@ -64,7 +64,7 @@ UA_UInt16 addNamespace(UA_Server *server, const UA_String name) { setupNs1Uri(server); /* Check if the namespace already exists in the server's namespace array */ - for(UA_UInt16 i = 0; i < server->namespacesSize; ++i) { + for(size_t i = 0; i < server->namespacesSize; ++i) { if(UA_String_equal(&name, &server->namespaces[i])) return (UA_UInt16) i; } diff --git a/tools/prepare_packaging.py b/tools/prepare_packaging.py index 6bcc033f19b..32da9549612 100644 --- a/tools/prepare_packaging.py +++ b/tools/prepare_packaging.py @@ -23,7 +23,7 @@ # v1.2.3-5-g4538abcd-dirty # git_describe_version = "v1.2.3" -m = re.match(r"^v([0-9]+)(\.[0-9]+)?(\.[0-9]+)?(-(.*)+)?$", git_describe_version) +m = re.match(r"^v([0-9]{1,4})(\.[0-9]{1,4}){0,2}(-(.*){1,100})?$", git_describe_version) version_major = m.group(1) if m.group(1) is not None else "0" version_minor = m.group(2).replace(".", "") if m.group(2) is not None else "0" version_patch = m.group(3).replace(".", "") if m.group(3) is not None else "0" From a01858fe70eccb133f9d42de99734a88504d8eae Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 19 Dec 2021 14:00:13 +0100 Subject: [PATCH 0020/1963] Revert "Revert "Merge branch 'master' into 1.3"" This reverts commit 5de6a2e7c360a5eb2f461bf9fe88f9efe5cf80d9. --- .github/workflows/build_linux.yml | 6 + .github/workflows/coverage.yml | 32 + CMakeLists.txt | 23 +- CONTRIBUTING.md | 1 + {src => arch/common}/ua_timer.c | 7 +- {src => arch/common}/ua_timer.h | 6 +- arch/eCos/ua_architecture.h | 3 +- arch/eventloop_posix.c | 596 +++++++++++++ arch/eventloop_posix.h | 99 ++ arch/eventloop_posix_tcp.c | 788 ++++++++++++++++ arch/posix/ua_architecture.h | 2 +- arch/vxworks/ua_architecture.h | 3 +- arch/wec7/ua_architecture.h | 3 +- deps/ua-nodeset | 2 +- examples/CMakeLists.txt | 1 + examples/events/client_eventfilter.c | 94 +- examples/events/server_random_events.c | 10 +- examples/nodeset/CMakeLists.txt | 2 +- .../nodeset/pubsub_nodeset_rt_publisher.c | 12 +- .../nodeset/pubsub_nodeset_rt_subscriber.c | 12 +- .../pubsub_realtime/pubsub_TSN_loopback.c | 114 ++- .../pubsub_realtime/pubsub_TSN_publisher.c | 134 ++- .../tutorial_server_historicaldata_circular.c | 133 +++ include/open62541/client.h | 4 + include/open62541/plugin/eventloop.h | 302 +++++++ include/open62541/plugin/log.h | 5 +- include/open62541/plugin/nodestore.h | 45 +- include/open62541/server.h | 30 +- include/open62541/types.h | 5 +- include/open62541/util.h | 34 +- .../ua_history_data_backend_memory.c | 261 ++++++ .../ua_history_data_gathering_default.c | 28 + .../historydata/history_data_backend_memory.h | 10 + .../history_data_gathering_default.h | 8 + plugins/ua_config_default.c | 28 +- plugins/ua_log_stdout.c | 5 +- plugins/ua_log_syslog.c | 5 +- plugins/ua_nodestore_hashmap.c | 17 +- plugins/ua_nodestore_ziptree.c | 33 +- plugins/ua_pubsub_ethernet.c | 2 +- plugins/ua_pubsub_udp.c | 1 + src/client/ua_client.c | 53 +- src/client/ua_client_internal.h | 4 +- src/pubsub/ua_pubsub.h | 1 + src/pubsub/ua_pubsub_manager.c | 27 +- src/pubsub/ua_pubsub_networkmessage.c | 9 +- src/pubsub/ua_pubsub_networkmessage.h | 4 + src/pubsub/ua_pubsub_reader.c | 55 +- src/pubsub/ua_pubsub_readergroup.c | 2 +- src/pubsub/ua_pubsub_writergroup.c | 88 +- src/server/ua_nodes.c | 39 +- src/server/ua_server.c | 72 +- src/server/ua_server_config.c | 13 + src/server/ua_server_internal.h | 36 +- src/server/ua_services_attribute.c | 56 +- src/server/ua_services_method.c | 53 +- src/server/ua_services_securechannel.c | 6 +- src/server/ua_services_session.c | 6 +- src/server/ua_services_view.c | 98 +- src/server/ua_session.c | 53 +- src/server/ua_subscription.c | 4 +- src/server/ua_subscription.h | 9 +- src/server/ua_subscription_events.c | 308 +++++-- src/server/ua_subscription_monitoreditem.c | 4 +- src/ua_securechannel.h | 1 + src/ua_types_print.c | 48 +- src/ua_util.c | 73 +- src/ua_util_internal.h | 3 + tests/CMakeLists.txt | 39 +- tests/check_eventloop.c | 65 ++ tests/check_eventloop_tcp.c | 256 ++++++ tests/check_types_memory.c | 73 +- tests/nodeset-compiler/CMakeLists.txt | 2 +- .../pubsub/check_pubsub_encrypted_rt_levels.c | 843 ++++++++++++++++++ tests/server/check_nodestore.c | 23 +- .../check_server_historical_data_circular.c | 294 ++++++ tests/server/check_server_readspeed.c | 5 +- tools/ci.sh | 50 ++ tools/cmake/FindCheck.cmake | 2 +- tools/cmake/FindGcov.cmake | 162 ++++ tools/cmake/Findcodecov.cmake | 265 ++++++ .../backend_open62541_typedefinitions.py | 2 +- tools/nodeset_compiler/nodes.py | 3 + 83 files changed, 5532 insertions(+), 583 deletions(-) create mode 100644 .github/workflows/coverage.yml rename {src => arch/common}/ua_timer.c (98%) rename {src => arch/common}/ua_timer.h (97%) create mode 100644 arch/eventloop_posix.c create mode 100644 arch/eventloop_posix.h create mode 100644 arch/eventloop_posix_tcp.c create mode 100644 examples/tutorial_server_historicaldata_circular.c create mode 100644 include/open62541/plugin/eventloop.h create mode 100644 tests/check_eventloop.c create mode 100644 tests/check_eventloop_tcp.c create mode 100644 tests/pubsub/check_pubsub_encrypted_rt_levels.c create mode 100644 tests/server/check_server_historical_data_circular.c create mode 100644 tools/cmake/FindGcov.cmake create mode 100644 tools/cmake/Findcodecov.cmake diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index 4dbbc2c1074..7c54cfa8438 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -11,6 +11,12 @@ jobs: - build_name: "Debug Build & Unit Tests (gcc)" cmd_deps: "" cmd_action: unit_tests + - build_name: "Debug Build & Unit Tests (gcc, 32bit)" + cmd_deps: | + sudo dpkg --add-architecture i386 + sudo apt-get update + sudo apt-get install -y -qq gcc-multilib libsubunit-dev:i386 check:i386 + cmd_action: unit_tests_32 - build_name: "Debug Build & Unit Tests with multithreading (gcc)" cmd_deps: "" cmd_action: unit_tests_mt diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000000..5b5dce53134 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,32 @@ +name: "Coverage Test" +on: + push: + branches: [ main, master ] + tags: + - v1.* + pull_request: + branches: [ main, master ] + tags: + - v1.* + +jobs: + run: + runs-on: ubuntu-latest + steps: + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install -y -qq python3-sphinx graphviz check libmbedtls-dev + - name: Fetch + uses: actions/checkout@v2 + with: + submodules: true + - name: Execute Tests + run: source tools/ci.sh && unit_tests_with_coverage + env: + ETHERNET_INTERFACE: eth0 + - name: Debug print + run: | + tree . + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v2 diff --git a/CMakeLists.txt b/CMakeLists.txt index bcca1eee6f6..bc53b7112fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,11 +117,14 @@ GET_PROPERTY(ua_architecture_sources GLOBAL PROPERTY UA_ARCHITECTURE_SOURCES) set(ua_architecture_sources ${ua_architecture_sources} ${PROJECT_SOURCE_DIR}/arch/network_tcp.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_tcp.c ) set(ua_architecture_headers ${ua_architecture_headers} ${PROJECT_SOURCE_DIR}/include/open62541/network_tcp.h ${PROJECT_SOURCE_DIR}/include/open62541/architecture_functions.h + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix.h ) if(UA_ENABLE_WEBSOCKET_SERVER) @@ -289,8 +292,9 @@ if((UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS) AND (NOT (UA_ENABLE_SUBSCRIPTIONS endif() if(UA_ENABLE_COVERAGE) - set(CMAKE_BUILD_TYPE DEBUG) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") + # We are using the scripts provided at for coverage testing: https://github.com/RWTH-HPC/CMake-codecov + set(ENABLE_COVERAGE ON) + find_package(codecov REQUIRED) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage -lgcov") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fprofile-arcs -ftest-coverage") endif() @@ -673,7 +677,10 @@ if(NOT UA_FORCE_CPP AND (CMAKE_COMPILER_IS_GNUCC OR "x${CMAKE_C_COMPILER_ID}" ST # Force 32bit build if(UA_FORCE_32BIT) - check_add_cc_flag("-m32") + if(MSVC) + message(FATAL_ERROR "Select the 32bit (cross-) compiler instead of forcing compiler options") + endif() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32") # GCC and Clang, possibly more endif() if(NOT MINGW AND NOT UA_BUILD_OSS_FUZZ) @@ -856,6 +863,7 @@ set(exported_headers ${PROJECT_BINARY_DIR}/src_generated/open62541/config.h ${PROJECT_SOURCE_DIR}/include/open62541/plugin/pubsub.h ${PROJECT_SOURCE_DIR}/deps/ziptree.h ${PROJECT_SOURCE_DIR}/deps/aa_tree.h + ${PROJECT_SOURCE_DIR}/include/open62541/plugin/eventloop.h ${PROJECT_SOURCE_DIR}/include/open62541/plugin/nodestore.h ${historizing_exported_headers} ${PROJECT_SOURCE_DIR}/include/open62541/server_pubsub.h @@ -875,7 +883,7 @@ set(internal_headers ${PROJECT_SOURCE_DIR}/deps/open62541_queue.h ${PROJECT_BINARY_DIR}/src_generated/open62541/transport_generated_handling.h ${PROJECT_SOURCE_DIR}/src/ua_connection_internal.h ${PROJECT_SOURCE_DIR}/src/ua_securechannel.h - ${PROJECT_SOURCE_DIR}/src/ua_timer.h + ${PROJECT_SOURCE_DIR}/arch/common/ua_timer.h ${PROJECT_SOURCE_DIR}/src/server/ua_session.h ${PROJECT_SOURCE_DIR}/src/server/ua_subscription.h ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage.h @@ -896,7 +904,7 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c ${PROJECT_BINARY_DIR}/src_generated/open62541/transport_generated.c ${PROJECT_BINARY_DIR}/src_generated/open62541/statuscodes.c ${PROJECT_SOURCE_DIR}/src/ua_util.c - ${PROJECT_SOURCE_DIR}/src/ua_timer.c + ${PROJECT_SOURCE_DIR}/arch/common/ua_timer.c ${PROJECT_SOURCE_DIR}/src/ua_connection.c ${PROJECT_SOURCE_DIR}/src/ua_securechannel.c ${PROJECT_SOURCE_DIR}/src/ua_securechannel_crypto.c @@ -1360,8 +1368,11 @@ else() open62541-generator-statuscode open62541-generator-namespace ) - + if(UA_ENABLE_COVERAGE) + add_coverage(open62541-object) + endif() target_include_directories(open62541-object PRIVATE ${PROJECT_SOURCE_DIR}/src) + target_include_directories(open62541-object PRIVATE ${PROJECT_SOURCE_DIR}/arch) # TODO: Remove once the EventLoop is integrated add_library(open62541-plugins OBJECT ${default_plugin_sources} ${ua_architecture_sources} ${exported_headers}) add_dependencies(open62541-plugins open62541-generator-types open62541-generator-transport open62541-generator-namespace) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2f1f73a1da9..92966dd71d9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -103,6 +103,7 @@ The scope is optional, but recommended to be used. It should be the name of the The following is the list of supported scopes: - **arch**: Changes to specific architecture code in `root/arch` +- **el**: Changes to the eventloop and associated event sources (also networking) - **client**: Changes only affecting client code - **core**: Core functionality used by the client and server - **ex**: Example code changes diff --git a/src/ua_timer.c b/arch/common/ua_timer.c similarity index 98% rename from src/ua_timer.c rename to arch/common/ua_timer.c index 4d9507907a2..51c0bf31a58 100644 --- a/src/ua_timer.c +++ b/arch/common/ua_timer.c @@ -6,7 +6,6 @@ * Copyright 2017 (c) Stefan Profanter, fortiss GmbH */ -#include "ua_util_internal.h" #include "ua_timer.h" /* There may be several entries with the same nextTime in the tree. We give them @@ -273,6 +272,12 @@ UA_Timer_process(UA_Timer *t, UA_DateTime nowMonotonic, return next; } +UA_DateTime +UA_Timer_nextRepeatedTime(UA_Timer *t) { + UA_TimerEntry *first = (UA_TimerEntry*)aa_min(&t->root); + return (first) ? first->nextTime : UA_INT64_MAX; +} + void UA_Timer_clear(UA_Timer *t) { UA_LOCK(&t->timerMutex); diff --git a/src/ua_timer.h b/arch/common/ua_timer.h similarity index 97% rename from src/ua_timer.h rename to arch/common/ua_timer.h index 99810e7b43d..00e406b1254 100644 --- a/src/ua_timer.h +++ b/arch/common/ua_timer.h @@ -9,7 +9,8 @@ #ifndef UA_TIMER_H_ #define UA_TIMER_H_ -#include "ua_util_internal.h" +#include +#include #include "aa_tree.h" _UA_BEGIN_DECLS @@ -55,6 +56,9 @@ typedef struct { void UA_Timer_init(UA_Timer *t); +UA_DateTime +UA_Timer_nextRepeatedTime(UA_Timer *t); + UA_StatusCode UA_Timer_addTimedCallback(UA_Timer *t, UA_ApplicationCallback callback, void *application, void *data, UA_DateTime date, diff --git a/arch/eCos/ua_architecture.h b/arch/eCos/ua_architecture.h index 4980b93ab55..c6ef58dbd60 100644 --- a/arch/eCos/ua_architecture.h +++ b/arch/eCos/ua_architecture.h @@ -39,7 +39,8 @@ #define UA_WOULDBLOCK EWOULDBLOCK #define UA_ERR_CONNECTION_PROGRESS EINPROGRESS -#define UA_getnameinfo getnameinfo +#define UA_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) \ + getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) #define UA_send send #define UA_recv recv #define UA_sendto sendto diff --git a/arch/eventloop_posix.c b/arch/eventloop_posix.c new file mode 100644 index 00000000000..d62620984ba --- /dev/null +++ b/arch/eventloop_posix.c @@ -0,0 +1,596 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + * Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes) + */ + +#include "eventloop_posix.h" +#include "common/ua_timer.h" + +typedef struct { + UA_FD fd; + short eventMask; + UA_EventSource *es; + UA_FDCallback callback; + void *fdcontext; +} UA_RegisteredFD; + +struct UA_EventLoop { + UA_EventLoopState state; + const UA_Logger *logger; + + /* Timer */ + UA_Timer timer; + + /* Linked List of Delayed Callbacks */ + UA_DelayedCallback *delayedCallbacks; + + /* Pointers to registered EventSources */ + UA_EventSource *eventSources; + + /* Registered file descriptors */ + size_t fdsSize; + UA_RegisteredFD *fds; + + /* Flag determining whether the eventloop is currently within the "run" method */ + UA_Boolean executing; + +#if UA_MULTITHREADING >= 100 + UA_Lock elMutex; +#endif +}; + +/*********/ +/* Timer */ +/*********/ + +static UA_StatusCode +processFDs(UA_EventLoop *el, UA_DateTime usedTimeout); + +static void +timerExecutionTrampoline(void *executionApplication, UA_ApplicationCallback cb, + void *callbackApplication, void *data) { + cb(callbackApplication, data); +} + +UA_StatusCode +UA_EventLoop_addTimedCallback(UA_EventLoop *el, UA_Callback callback, + void *application, void *data, UA_DateTime date, + UA_UInt64 *callbackId) { + return UA_Timer_addTimedCallback(&el->timer, callback, application, + data, date, callbackId); +} + +UA_StatusCode +UA_EventLoop_addCyclicCallback(UA_EventLoop *el, UA_Callback cb, + void *application, void *data, UA_Double interval_ms, + UA_DateTime *baseTime, UA_TimerPolicy timerPolicy, + UA_UInt64 *callbackId) { + return UA_Timer_addRepeatedCallback(&el->timer, cb, application, data, + interval_ms, baseTime, timerPolicy, callbackId); +} + +UA_StatusCode +UA_EventLoop_modifyCyclicCallback(UA_EventLoop *el, UA_UInt64 callbackId, + UA_Double interval_ms, UA_DateTime *baseTime, + UA_TimerPolicy timerPolicy) { + return UA_Timer_changeRepeatedCallback(&el->timer, callbackId, interval_ms, + baseTime, timerPolicy); +} + +void +UA_EventLoop_removeCyclicCallback(UA_EventLoop *el, UA_UInt64 callbackId) { + UA_Timer_removeCallback(&el->timer, callbackId); +} + +void +UA_EventLoop_addDelayedCallback(UA_EventLoop *el, UA_DelayedCallback *dc) { + UA_LOCK(&el->elMutex); + dc->next = el->delayedCallbacks; + el->delayedCallbacks = dc; + UA_UNLOCK(&el->elMutex); +} + +/* Process and then free registered delayed callbacks */ +static void +processDelayed(UA_EventLoop *el) { + UA_LOCK_ASSERT(&el->elMutex, 1); + while(el->delayedCallbacks) { + UA_DelayedCallback *dc = el->delayedCallbacks; + el->delayedCallbacks = dc->next; + /* Delayed Callbacks might have no cb pointer if all we want to do is + * free the memory */ + if(dc->callback) { + UA_UNLOCK(&el->elMutex); + dc->callback(dc->application, dc->data); + UA_LOCK(&el->elMutex); + } + UA_free(dc); + } +} + +/***********************/ +/* EventLoop Lifecycle */ +/***********************/ + +UA_EventLoop * +UA_EventLoop_new(const UA_Logger *logger) { + UA_EventLoop *el = (UA_EventLoop*)UA_malloc(sizeof(UA_EventLoop)); + if(!el) + return NULL; + memset(el, 0, sizeof(UA_EventLoop)); + UA_LOCK_INIT(&el->elMutex); + el->logger = logger; + UA_Timer_init(&el->timer); + return el; +} + +UA_StatusCode +UA_EventLoop_delete(UA_EventLoop *el) { + UA_LOCK(&el->elMutex); + + /* Check if the EventLoop can be deleted */ + if(el->state != UA_EVENTLOOPSTATE_STOPPED && + el->state != UA_EVENTLOOPSTATE_FRESH) { + UA_LOG_WARNING(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "Cannot delete a running EventLoop"); + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Deregister and delete all the EventSources */ + while(el->eventSources) { + UA_EventSource *es = el->eventSources; + UA_UNLOCK(&el->elMutex); + UA_EventLoop_deregisterEventSource(el, es); + UA_LOCK(&el->elMutex); + es->free(es); + } + + /* Remove the repeated timed callbacks */ + UA_Timer_clear(&el->timer); + + /* Process remaining delayed callbacks */ + processDelayed(el); + + /* free the file descriptors */ + UA_free(el->fds); + + /* Clean up */ + UA_UNLOCK(&el->elMutex); + UA_LOCK_DESTROY(&el->elMutex); + UA_free(el); + return UA_STATUSCODE_GOOD; +} + +UA_EventLoopState +UA_EventLoop_getState(UA_EventLoop *el) { + return el->state; +} + +UA_DateTime +UA_EventLoop_nextCyclicTime(UA_EventLoop *el) { + return UA_Timer_nextRepeatedTime(&el->timer); +} + +UA_StatusCode +UA_EventLoop_start(UA_EventLoop *el) { + UA_LOCK(&el->elMutex); + if(el->state != UA_EVENTLOOPSTATE_FRESH && + el->state != UA_EVENTLOOPSTATE_STOPPED) { + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_BADINTERNALERROR; + } + + UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, "Starting the EventLoop"); + + UA_EventSource *es = el->eventSources; + UA_StatusCode res = UA_STATUSCODE_GOOD; + while(es) { + UA_UNLOCK(&el->elMutex); + res |= es->start(es); + UA_LOCK(&el->elMutex); + es = es->next; + } + + el->state = UA_EVENTLOOPSTATE_STARTED; + UA_UNLOCK(&el->elMutex); + return res; +} + +static void +checkClosed(UA_EventLoop *el) { + UA_EventSource *es = el->eventSources; + while(es) { + if(es->state != UA_EVENTSOURCESTATE_STOPPED) + return; + es = es->next; + } + + UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, "The EventLoop has stopped"); + el->state = UA_EVENTLOOPSTATE_STOPPED; +} + +void +UA_EventLoop_stop(UA_EventLoop *el) { + UA_LOCK(&el->elMutex); + + UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, "Stopping the EventLoop"); + + /* Shutdown all event sources. This will close open connections. */ + UA_EventSource *es = el->eventSources; + while(es) { + if(es->state == UA_EVENTSOURCESTATE_STARTING || + es->state == UA_EVENTSOURCESTATE_STARTED) + es->stop(es); + es = es->next; + } + + UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "All EventSources are stopped"); + + el->state = UA_EVENTLOOPSTATE_STOPPING; + checkClosed(el); + UA_UNLOCK(&el->elMutex); +} + +/* After every select, reset the file-descriptors to listen on */ +static UA_FD +setFDSets(UA_EventLoop *el, fd_set *readset, fd_set *writeset, fd_set *errset) { + FD_ZERO(readset); + FD_ZERO(writeset); + FD_ZERO(errset); + UA_FD highestfd = UA_INVALID_FD; + for(size_t i = 0; i < el->fdsSize; i++) { + + UA_FD currentFD = el->fds[i].fd; + /* Add to the fd_sets */ + if(el->fds[i].eventMask & UA_POSIX_EVENT_READ) + UA_fd_set(currentFD, readset); + if(el->fds[i].eventMask & UA_POSIX_EVENT_WRITE) + UA_fd_set(currentFD, writeset); + if(el->fds[i].eventMask & UA_POSIX_EVENT_ERR) + UA_fd_set(currentFD, errset); + + /* Highest fd? */ + if(currentFD > highestfd || highestfd == UA_INVALID_FD) + highestfd = currentFD; + } + return highestfd; +} + +static UA_StatusCode +processFDs(UA_EventLoop *el, UA_DateTime usedTimeout) { + fd_set readset, writeset, errset; + UA_FD highestfd = setFDSets(el, &readset, &writeset, &errset); + + /* Nothing to do? */ + if(highestfd == UA_INVALID_FD) { + UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "No valid FDs for processing"); + return UA_STATUSCODE_GOOD; + } + + struct timeval tmptv = { +#ifndef _WIN32 + (time_t)(usedTimeout / UA_DATETIME_SEC), + (suseconds_t)((usedTimeout % UA_DATETIME_SEC) / UA_DATETIME_USEC) +#else + (long)(usedTimeout / UA_DATETIME_SEC), + (long)((usedTimeout % UA_DATETIME_SEC) / UA_DATETIME_USEC) +#endif + }; + + int selectStatus = UA_select(highestfd+1, &readset, &writeset, &errset, &tmptv); + if(selectStatus < 0) { + /* We will retry, only log the error */ + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(UA_EventLoop_getLogger(el), + UA_LOGCATEGORY_EVENTLOOP, + "Error during select: %s", errno_str)); + el->executing = false; + return UA_STATUSCODE_GOODCALLAGAIN; + } + + /* Loop over all registered FD to see if an event arrived. Yes, this is why + * select is slow for many open sockets. */ + for(size_t i = 0; i < el->fdsSize; i++) { + UA_RegisteredFD *rfd = &el->fds[i]; + UA_FD fd = rfd->fd; + UA_assert(fd > 0); + + UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "Processing fd: %u", (unsigned)fd); + + /* Error Event */ + if((rfd->eventMask & UA_POSIX_EVENT_ERR) && UA_fd_isset(fd, &errset)) { + UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "Processing error event for fd: %u", (unsigned)fd); + UA_UNLOCK(&el->elMutex); + rfd->callback(rfd->es, fd, &rfd->fdcontext, UA_POSIX_EVENT_ERR); + UA_LOCK(&el->elMutex); + if(i == el->fdsSize || fd != el->fds[i].fd) + i--; /* The fd has removed itself */ + continue; + } + + /* Read Event */ + if((rfd->eventMask & UA_POSIX_EVENT_READ) && UA_fd_isset(fd, &readset)) { + UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "Processing read event for fd: %u", (unsigned)fd); + UA_UNLOCK(&el->elMutex); + rfd->callback(rfd->es, fd, &rfd->fdcontext, UA_POSIX_EVENT_READ); + UA_LOCK(&el->elMutex); + if(i == el->fdsSize || fd != el->fds[i].fd) + i--; /* The fd has removed itself */ + continue; + } + + /* Write Event */ + if((rfd->eventMask & UA_POSIX_EVENT_WRITE) && UA_fd_isset(fd, &writeset)) { + UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "Processing write event for fd: %u", (unsigned)fd); + UA_UNLOCK(&el->elMutex); + rfd->callback(rfd->es, fd, &rfd->fdcontext, UA_POSIX_EVENT_WRITE); + UA_LOCK(&el->elMutex); + if(i == el->fdsSize || fd != el->fds[i].fd) + i--; /* The fd has removed itself */ + continue; + } + } + return UA_STATUSCODE_GOOD; +} + +UA_StatusCode +UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) { + UA_LOCK(&el->elMutex); + + UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, "iterate the EventLoop"); + + if(el->executing) { + UA_LOG_ERROR(el->logger, + UA_LOGCATEGORY_EVENTLOOP, + "Cannot run EventLoop from the run method itself"); + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_BADINTERNALERROR; + } + /* TODO: use check macros instead + UA_CHECK_ERROR(!el->executing, return UA_STATUSCODE_BADINTERNALERROR, el->logger, + UA_LOGCATEGORY_EVENTLOOP, + "Cannot run eventloop from the run method itself"); + */ + + el->executing = true; + + if(el->state == UA_EVENTLOOPSTATE_FRESH || + el->state == UA_EVENTLOOPSTATE_STOPPED) { + UA_LOG_WARNING(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "Cannot iterate a stopped EventLoop"); + el->executing = false; + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Process cyclic callbacks */ + UA_DateTime dateBeforeCallback = UA_DateTime_nowMonotonic(); + + UA_UNLOCK(&el->elMutex); + UA_DateTime dateOfNextCallback = + UA_Timer_process(&el->timer, dateBeforeCallback, timerExecutionTrampoline, NULL); + UA_LOCK(&el->elMutex); + + UA_DateTime dateAfterCallback = UA_DateTime_nowMonotonic(); + + UA_DateTime processTimerDuration = dateAfterCallback - dateBeforeCallback; + + UA_DateTime callbackTimeout = dateOfNextCallback - dateAfterCallback; + UA_DateTime maxTimeout = UA_MAX(timeout * UA_DATETIME_MSEC - processTimerDuration, 0); + + UA_DateTime usedTimeout = UA_MIN(callbackTimeout, maxTimeout); + + /* Listen on the active file-descriptors (sockets) from the ConnectionManagers */ + UA_StatusCode rv = processFDs(el, usedTimeout); + if(rv == UA_STATUSCODE_GOODCALLAGAIN) { + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_GOOD; + } + if(rv != UA_STATUSCODE_GOOD) { + UA_UNLOCK(&el->elMutex); + return rv; + } + + /* Process and then free registered delayed callbacks */ + processDelayed(el); + + /* Check if the last EventSource was successfully stopped */ + if(el->state == UA_EVENTLOOPSTATE_STOPPING) + checkClosed(el); + + el->executing = false; + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_GOOD; +} + + +/*****************************/ +/* Registering Event Sources */ +/*****************************/ + +UA_StatusCode +UA_EventLoop_registerEventSource(UA_EventLoop *el, UA_EventSource *es) { + /* Already registered? */ + if(es->state != UA_EVENTSOURCESTATE_FRESH) { + UA_LOG_ERROR(UA_EventLoop_getLogger(el), UA_LOGCATEGORY_NETWORK, + "Cannot register the EventSource \"%.*s\": already registered", + (int)es->name.length, (char*)es->name.data); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Add to linked list */ + UA_LOCK(&el->elMutex); + es->next = el->eventSources; + el->eventSources = es; + UA_UNLOCK(&el->elMutex); + + es->eventLoop = el; + es->state = UA_EVENTSOURCESTATE_STOPPED; + + /* Start if the entire EventLoop is started */ + if(el->state == UA_EVENTLOOPSTATE_STARTED) + return es->start(es); + return UA_STATUSCODE_GOOD; +} + +UA_StatusCode +UA_EventLoop_deregisterEventSource(UA_EventLoop *el, UA_EventSource *es) { + if(es->state != UA_EVENTSOURCESTATE_STOPPED) { + UA_LOG_WARNING(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "Cannot deregister the EventSource %.*s. Has to be stopped first", + (int)es->name.length, es->name.data); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Remove from the linked list */ + UA_LOCK(&el->elMutex); + UA_EventSource **s = &el->eventSources; + while(*s) { + if(*s == es) { + *s = es->next; + break; + } + s = &(*s)->next; + } + UA_UNLOCK(&el->elMutex); + + /* Set the state to non-registered */ + es->state = UA_EVENTSOURCESTATE_FRESH; + + return UA_STATUSCODE_GOOD; +} + +/********************************/ +/* Registering File Descriptors */ +/********************************/ + +UA_StatusCode +UA_EventLoop_registerFD(UA_EventLoop *el, UA_FD fd, short eventMask, + UA_FDCallback cb, UA_EventSource *es, void *fdcontext) { + UA_LOCK(&el->elMutex); + + UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "Registering fd: %u", (unsigned)fd); + + /* Realloc */ + UA_RegisteredFD *fds_tmp = (UA_RegisteredFD*) + UA_realloc(el->fds, sizeof(UA_RegisteredFD) * (el->fdsSize + 1)); + if(!fds_tmp) { + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + el->fds = fds_tmp; + + /* Add to the last entry */ + el->fds[el->fdsSize].callback = cb; + el->fds[el->fdsSize].eventMask = eventMask; + el->fds[el->fdsSize].es = es; + el->fds[el->fdsSize].fdcontext = fdcontext; + el->fds[el->fdsSize].fd = fd; + el->fdsSize++; + + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_GOOD; +} + +UA_StatusCode +UA_EventLoop_modifyFD(UA_EventLoop *el, UA_FD fd, short eventMask, + UA_FDCallback cb, void *fdcontext) { + UA_LOCK(&el->elMutex); + + /* Find the entry */ + size_t i = 0; + for(; i < el->fdsSize; i++) { + if(el->fds[i].fd == fd) + break; + } + + /* Not found? */ + if(i == el->fdsSize) { + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_BADNOTFOUND; + } + + /* Modify */ + el->fds[i].callback = cb; + el->fds[i].eventMask = eventMask; + el->fds[i].fdcontext = fdcontext; + + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_GOOD; +} + +UA_StatusCode +UA_EventLoop_deregisterFD(UA_EventLoop *el, UA_FD fd) { + UA_LOCK(&el->elMutex); + + UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "Unregistering fd: %u", (unsigned)fd); + + /* Find the entry */ + size_t i = 0; + for(; i < el->fdsSize; i++) { + if(el->fds[i].fd == fd) + break; + } + + /* Not found? */ + if(i == el->fdsSize) { + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_BADNOTFOUND; + } + + if(el->fdsSize > 1) { + /* Move the last entry in the ith slot and realloc. */ + el->fdsSize--; + el->fds[i] = el->fds[el->fdsSize]; + UA_RegisteredFD *fds_tmp = (UA_RegisteredFD*) + UA_realloc(el->fds, sizeof(UA_RegisteredFD) * el->fdsSize); + /* if realloc fails the fds are still in a correct state with + * possibly lost memory, so failing silently here is ok */ + if(fds_tmp) + el->fds = fds_tmp; + } else { + /* Remove the last entry */ + UA_free(el->fds); + el->fds = NULL; + el->fdsSize = 0; + } + + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_GOOD; +} + +void +UA_EventLoop_iterateFD(UA_EventLoop *el, UA_EventSource *es, UA_FDCallback cb) { + for(size_t i = 0; i < el->fdsSize; i++) { + if(el->fds[i].es != es) + continue; + UA_FD fd = el->fds[i].fd; + cb(es, fd, el->fds[i].fdcontext, 0); + if(i == el->fdsSize || fd != el->fds[i].fd) + i--; /* The fd has removed itself */ + } +} + +/* Helper Functions */ + +const UA_Logger * +UA_EventLoop_getLogger(UA_EventLoop *el) { + return el->logger; +} + +void +UA_EventLoop_setLogger(UA_EventLoop *el, const UA_Logger *logger) { + el->logger = logger; +} diff --git a/arch/eventloop_posix.h b/arch/eventloop_posix.h new file mode 100644 index 00000000000..fcc1dd8ce31 --- /dev/null +++ b/arch/eventloop_posix.h @@ -0,0 +1,99 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + * Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes) + */ + +#ifndef UA_EVENTLOOP_POSIX_H_ +#define UA_EVENTLOOP_POSIX_H_ + +#include +#include + +/* A macro-forest to work around small differences between POSIX and + * nearly-POSIX architectures */ + +#if defined(_WIN32) /* Windows */ +# include +# if !defined(__MINGW32__) || defined(__clang__) +# define UA_FD SOCKET /* On MSVC, a socket is a pointer and not an int */ +# define UA_INVALID_FD INVALID_SOCKET +//# define UA_close(s) closesocket(s) /* closesocket() takes a SOCKET (and sock() an int) */ +# endif +# define UA_ERRNO WSAGetLastError() +# define UA_INTERRUPTED WSAEINTR +# define UA_AGAIN WSAEWOULDBLOCK +# define UA_EAGAIN EAGAIN +# define UA_WOULDBLOCK WSAEWOULDBLOCK +# define UA_ERR_CONNECTION_PROGRESS WSAEWOULDBLOCK +#else /* Unix */ +# include +#endif + +/* Catch-all for the architectures that are "actually POSIX" */ +#ifndef UA_FD +# define UA_FD int +# define UA_INVALID_FD -1 +//# define UA_close(s) close(s) +#endif +#ifndef UA_ERRNO +# define UA_ERRNO errno +# define UA_INTERRUPTED EINTR +# define UA_AGAIN EAGAIN +# define UA_EAGAIN EAGAIN +# define UA_WOULDBLOCK EWOULDBLOCK +# define UA_ERR_CONNECTION_PROGRESS EINPROGRESS +#endif + +/* Workaround a bug in early glibc. Additionally, some non-glibc implementations + * use a macro for FD_SET that triggers a cast-warning (e.g. early BSD libc or + * musl libc). */ +#if (!defined(__GNU_LIBRARY__) && defined(FD_SET)) || \ + (defined(__GNU_LIBRARY__) && (__GNU_LIBRARY__ <= 6) && \ + (__GLIBC__ <= 2) && (__GLIBC_MINOR__ < 16)) +# define UA_FD_SET(fd, fds) FD_SET((unsigned int)fd, fds) +# define UA_FD_ISSET(fd, fds) FD_ISSET((unsigned int)fd, fds) +#else +# define UA_FD_SET(fd, fds) FD_SET((UA_FD)fd, fds) +# define UA_FD_ISSET(fd, fds) FD_ISSET((UA_FD)fd, fds) +#endif + +_UA_BEGIN_DECLS + +/* POSIX events are based on sockets / file descriptors. The EventSources can + * register their fd in the EventLoop so that they are considered by the + * EventLoop dropping into "select" to wait for events. */ + +/* POSIX-select can listen for three types of events. It has to be selected + * for each registered fd which events they are interested in. */ +#define UA_POSIX_EVENT_READ 1 +#define UA_POSIX_EVENT_WRITE 2 +#define UA_POSIX_EVENT_ERR 4 + +typedef void +(*UA_FDCallback)(UA_EventSource *es, UA_FD fd, void *fdcontext, short event); + +UA_StatusCode +UA_EventLoop_registerFD(UA_EventLoop *el, UA_FD fd, short eventMask, + UA_FDCallback cb, UA_EventSource *es, void *fdcontext); + +/* Change the fd settings (event mask, callback) in-place. Fails only if the fd + * no longer exists. */ +UA_StatusCode +UA_EventLoop_modifyFD(UA_EventLoop *el, UA_FD fd, short eventMask, + UA_FDCallback cb, void *fdcontext); + +/* During processing of an fd-event, the fd may deregister itself. But in the + * fd-callback they must not deregister another fd. */ +UA_StatusCode +UA_EventLoop_deregisterFD(UA_EventLoop *el, UA_FD fd); + +/* Call the callback for all fd that are registered from that event source */ +void +UA_EventLoop_iterateFD(UA_EventLoop *el, UA_EventSource *es, UA_FDCallback cb); + +_UA_END_DECLS + +#endif /* UA_EVENTLOOP_POSIX_H_ */ diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c new file mode 100644 index 00000000000..66dd0ab6263 --- /dev/null +++ b/arch/eventloop_posix_tcp.c @@ -0,0 +1,788 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + * Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes) + */ + +#include "eventloop_posix.h" + +#define UA_MAXBACKLOG 100 + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +#ifndef MSG_DONTWAIT +#define MSG_DONTWAIT 0 +#endif + +typedef struct { + UA_ConnectionManager cm; + size_t fdCount; /* Number of fd registered in the EventLoop */ + size_t recvBufferSize; +} TCPConnectionManager; + +static UA_StatusCode +TCP_allocNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId, + UA_ByteString *buf, size_t bufSize) { + return UA_ByteString_allocBuffer(buf, bufSize); +} + +static void +TCP_freeNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId, + UA_ByteString *buf) { + UA_ByteString_clear(buf); +} + +/* Set the socket non-blocking */ +static UA_StatusCode +TCP_setNonBlocking(UA_FD sockfd) { +#ifndef _WIN32 + int opts = fcntl(sockfd, F_GETFL); + if(opts < 0 || fcntl(sockfd, F_SETFL, opts | O_NONBLOCK) < 0) + return UA_STATUSCODE_BADINTERNALERROR; +#else + u_long iMode = 1; + if(ioctlsocket(sockfd, FIONBIO, &iMode) != NO_ERROR) + return UA_STATUSCODE_BADINTERNALERROR; +#endif + return UA_STATUSCODE_GOOD; +} + +/* Don't have the socket create interrupt signals */ +static UA_StatusCode +TCP_setNoSigPipe(UA_FD sockfd) { +#ifdef SO_NOSIGPIPE + int val = 1; + int res = UA_setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof(val)); + if(res < 0) + return UA_STATUSCODE_BADINTERNALERROR; +#endif + return UA_STATUSCODE_GOOD; +} + +/* Do not merge packets on the socket (disable Nagle's algorithm) */ +static UA_StatusCode +TCP_setNoNagle(UA_FD sockfd) { + int val = 1; + int res = UA_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)); + if(res < 0) + return UA_STATUSCODE_BADINTERNALERROR; + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +TCP_close(UA_ConnectionManager *cm, UA_FD fd) { + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Closing connection", (unsigned)fd); + + TCPConnectionManager *tcm = (TCPConnectionManager*)cm; + + /* Close the fd and deregister */ + int ret = UA_close(fd); + if(ret != 0) + return UA_STATUSCODE_BADINTERNALERROR; + UA_StatusCode sc = UA_EventLoop_deregisterFD(tcm->cm.eventSource.eventLoop, fd); + if(sc != UA_STATUSCODE_GOOD) + return sc; + + /* Reduce the count */ + UA_assert(tcm->fdCount > 0); + tcm->fdCount--; + + UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, "TCP #%u\t| Socket closed", (unsigned)fd); + + /* Stopped? */ + if(tcm->fdCount == 0 && cm->eventSource.state == UA_EVENTSOURCESTATE_STOPPING) { + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP\t| All sockets closed, the EventLoop has stopped"); + cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPED; + } + return UA_STATUSCODE_GOOD; +} + +/* Gets called when a connection socket opens, receives data or closes */ +static void +TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, + void **fdcontext, short event) { + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Activity on the socket", (unsigned)fd); + + /* Write-Event, a new connection has opened. */ + UA_StatusCode res = UA_STATUSCODE_GOOD; + if(event & UA_POSIX_EVENT_WRITE) { + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Opening a new connection", (unsigned)fd); + + /* The socket has opened. Signal it to the application. */ + cm->connectionCallback(cm, (uintptr_t)fd, fdcontext, + UA_STATUSCODE_GOOD, UA_BYTESTRING_NULL); + + /* Now we are interested in read-events. */ + UA_EventLoop_modifyFD(cm->eventSource.eventLoop, fd, UA_POSIX_EVENT_READ, + (UA_FDCallback)TCP_connectionSocketCallback, *fdcontext); + return; + } + + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Allocate receive buffer", (unsigned)fd); + + /* Allocate the receive-buffer */ + UA_ByteString response; + TCPConnectionManager *tcm = (TCPConnectionManager*)cm; + res = UA_ByteString_allocBuffer(&response, tcm->recvBufferSize); + if(res != UA_STATUSCODE_GOOD) + return; /* Retry in the next iteration */ + + /* Receive */ +#ifndef _WIN32 + ssize_t ret = UA_recv(fd, (char*)response.data, response.length, MSG_DONTWAIT); + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| recv(...) returned %zd", (unsigned)fd, ret); +#else + int ret = UA_recv(fd, (char*)response.data, response.length, MSG_DONTWAIT); + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| recv(...) returned %d", (unsigned)fd, ret); +#endif + + if(ret > 0) { + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Received message of size %u", + (unsigned)fd, (unsigned)ret); + + /* Callback to the application layer */ + response.length = (size_t)ret; /* Set the length of the received buffer */ + cm->connectionCallback(cm, (uintptr_t)fd, fdcontext, + UA_STATUSCODE_GOOD, response); + } else if(UA_ERRNO != UA_INTERRUPTED && UA_ERRNO != UA_EAGAIN) { + /* Orderly shutdown of the connection. Signal to the application and + * then close the connection. We end up in this path after shutdown was + * called on the socket. Here, we then are in the next EventLoop + * iteration and the socket is known to be unused. */ + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| recv signaled closed connection", (unsigned)fd); + + /* Close the connection if not a temporary error on a nonblocking socket */ + cm->connectionCallback(cm, (uintptr_t)fd, fdcontext, + UA_STATUSCODE_BADCONNECTIONCLOSED, + UA_BYTESTRING_NULL); + TCP_close(cm, fd); + } + + UA_ByteString_clear(&response); +} + +/* Gets called when a new connection opens or if the listenSocket is closed */ +static void +TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, + void **fdcontext, short event) { + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Callback on server socket", (unsigned)fd); + + /* Try to accept a new connection */ + struct sockaddr_storage remote; + socklen_t remote_size = sizeof(remote); + UA_FD newsockfd = UA_accept(fd, (struct sockaddr*)&remote, &remote_size); + if(newsockfd == UA_INVALID_FD) { + /* Temporary error -- retry */ + if(UA_ERRNO == UA_INTERRUPTED) + return; + + /* Close the listen socket */ + if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPING) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Error %s, closing the server socket", + (unsigned)fd, errno_str)); + } + TCP_close(cm, fd); + return; + } + + /* Log the name of the remote host */ +#if UA_LOGLEVEL <= 300 + char hoststr[256]; + int get_res = UA_getnameinfo((struct sockaddr *)&remote, sizeof(remote), + hoststr, sizeof(hoststr), NULL, 0, 0); + if(get_res != 0) { + get_res = UA_getnameinfo((struct sockaddr *)&remote, sizeof(remote), + hoststr, sizeof(hoststr), NULL, 0, NI_NUMERICHOST); + if(get_res != 0) { + hoststr[0] = 0; + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| getnameinfo(...) could not resolve the " + "hostname (%s)", (unsigned)fd, errno_str)); + } + } + UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Connection opened from \"%s\" via the server socket #%u", + (unsigned)newsockfd, hoststr, (unsigned)fd); +#endif + + /* Configure the new socket */ + UA_StatusCode res = UA_STATUSCODE_GOOD; + res |= TCP_setNonBlocking(newsockfd); /* Set the socket non-blocking */ + res |= TCP_setNoSigPipe(newsockfd); /* Supress interrupts from the socket */ + res |= TCP_setNoNagle(newsockfd); /* Disable Nagle's algorithm */ + if(res != UA_STATUSCODE_GOOD) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Error seeting the TCP options (%s), closing", + (unsigned)newsockfd, errno_str)); + /* Close the new socket */ + UA_close(newsockfd); + return; + } + + void *ctx = cm->initialConnectionContext; + /* The socket has opened. Signal it to the application. The callback can + * switch out the context. So put it into a temp variable. */ + cm->connectionCallback(cm, (uintptr_t)newsockfd, &ctx, UA_STATUSCODE_GOOD, + UA_BYTESTRING_NULL); + + /* Register in the EventLoop. Signal to the user if registering failed. */ + res = UA_EventLoop_registerFD(cm->eventSource.eventLoop, newsockfd, + UA_POSIX_EVENT_READ, + (UA_FDCallback)TCP_connectionSocketCallback, + &cm->eventSource, ctx); + if(res != UA_STATUSCODE_GOOD) { + cm->connectionCallback(cm, (uintptr_t)newsockfd, &ctx, + UA_STATUSCODE_BADINTERNALERROR, UA_BYTESTRING_NULL); + UA_close(newsockfd); + } + + /* Increase the count of registered fd */ + TCPConnectionManager *tcm = (TCPConnectionManager*)cm; + tcm->fdCount++; +} + +static void +TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { + /* Get logging information */ + char hoststr[256]; + char portstr[16]; + int get_res = UA_getnameinfo(ai->ai_addr, ai->ai_addrlen, + hoststr, sizeof(hoststr), + portstr, sizeof(portstr), NI_NUMERICSERV); + if(get_res != 0) { + get_res = UA_getnameinfo(ai->ai_addr, ai->ai_addrlen, + hoststr, sizeof(hoststr), + portstr, sizeof(portstr), + NI_NUMERICHOST | NI_NUMERICSERV); + if(get_res != 0) { + hoststr[0] = 0; + portstr[0] = 0; + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP\t| getnameinfo(...) could not resolve the hostname (%s)", + errno_str)); + } + } + + /* Create the server socket */ + UA_FD listenSocket = UA_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if(listenSocket == UA_INVALID_SOCKET) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Error opening the listen socket for " + "\"%s\" on port %s(%s)", + (unsigned)listenSocket, hoststr, portstr, errno_str)); + return; + } + + UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| New server socket for \"%s\" on port %s", + (unsigned)listenSocket, hoststr, portstr); + + /* Some Linux distributions have net.ipv6.bindv6only not activated. So + * sockets can double-bind to IPv4 and IPv6. This leads to problems. Use + * AF_INET6 sockets only for IPv6. */ + int optval = 1; +#if UA_IPV6 + if(ai->ai_family == AF_INET6 && + UA_setsockopt(listenSocket, IPPROTO_IPV6, IPV6_V6ONLY, + (const char*)&optval, sizeof(optval)) == -1) { + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Could not set an IPv6 socket to IPv6 only, closing", + (unsigned)listenSocket); + UA_close(listenSocket); + return; + } +#endif + + /* Allow rebinding to the IP/port combination. Eg. to restart the server. */ + if(UA_setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, + (const char *)&optval, sizeof(optval)) == -1) { + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Could not make the socket reusable, closing", + (unsigned)listenSocket); + UA_close(listenSocket); + return; + } + + /* Set the socket non-blocking */ + if(TCP_setNonBlocking(listenSocket) != UA_STATUSCODE_GOOD) { + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Could not set the socket non-blocking, closing", + (unsigned)listenSocket); + UA_close(listenSocket); + return; + } + + /* Supress interrupts from the socket */ + if(TCP_setNoSigPipe(listenSocket) != UA_STATUSCODE_GOOD) { + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Could not disable SIGPIPE, closing", + (unsigned)listenSocket); + UA_close(listenSocket); + return; + } + + /* Bind socket to address */ + int ret = UA_bind(listenSocket, ai->ai_addr, (socklen_t)ai->ai_addrlen); + if(ret < 0) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Error binding the socket to the address (%s), closing", + (unsigned)listenSocket, errno_str)); + UA_close(listenSocket); + return; + } + + /* Start listening */ + if(UA_listen(listenSocket, UA_MAXBACKLOG) < 0) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Error listening on the socket (%s), closing", + (unsigned)listenSocket, errno_str)); + UA_close(listenSocket); + return; + } + + /* Register the socket */ + UA_StatusCode res = + UA_EventLoop_registerFD(cm->eventSource.eventLoop, listenSocket, + UA_POSIX_EVENT_READ, + (UA_FDCallback) TCP_listenSocketCallback, + &cm->eventSource, NULL); + if(res != UA_STATUSCODE_GOOD) { + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Error registering the socket in the " + "EventLoop, closing", (unsigned)listenSocket); + UA_close(listenSocket); + return; + } + + /* Increase the registered fd count */ + TCPConnectionManager *tcm = (TCPConnectionManager*)cm; + tcm->fdCount++; +} + +static UA_StatusCode +TCP_registerListenSocketDomainName(UA_ConnectionManager *cm, const char *hostname, + const char *port) { + /* Get all the interface and IPv4/6 combinations for the configured hostname */ + struct addrinfo hints, *res; + memset(&hints, 0, sizeof hints); +#if UA_IPV6 + hints.ai_family = AF_UNSPEC; /* Allow IPv4 and IPv6 */ +#else + hints.ai_family = AF_INET; /* IPv4 only */ +#endif + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = IPPROTO_TCP; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; /* Only return IPv4/IPv6 if at least one + * such address is configured */ +#endif + + int retcode = UA_getaddrinfo(hostname, port, &hints, &res); + if(retcode != 0) { + UA_LOG_SOCKET_ERRNO_GAI_WRAP( + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP\t| getaddrinfo lookup for \"%s\" on port %s failed (%s)", + hostname, port, errno_str)); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Add listen sockets */ + struct addrinfo *ai = res; + while(ai) { + TCP_registerListenSocket(cm, ai); + ai = ai->ai_next; + } + UA_freeaddrinfo(res); + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +TCP_shutdownConnection(UA_ConnectionManager *cm, uintptr_t connectionId) { + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Shutdown called", (unsigned)connectionId); + + /* Shutdown, will be picked up by the next iteration of the event loop */ +#ifndef _WIN32 + int res = UA_shutdown((UA_FD)connectionId, SHUT_RDWR); +#else + int res = UA_shutdown((UA_FD)connectionId, SD_BOTH); +#endif + UA_StatusCode retval = UA_STATUSCODE_GOOD; + if(res != 0) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Error shutting down the socket (%s), closing", + (unsigned)connectionId, errno_str)); + retval = TCP_close(cm, (UA_FD)connectionId); + } + return retval; +} + +static UA_StatusCode +TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, + size_t paramsSize, UA_KeyValuePair *params, + UA_ByteString *buf) { + /* Prevent OS signals when sending to a closed socket */ + int flags = MSG_NOSIGNAL; + + /* Send the full buffer. This may require several calls to send */ + size_t nWritten = 0; + do { + ssize_t n = 0; + do { + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Attempting to send", (unsigned)connectionId); + size_t bytes_to_send = buf->length - nWritten; + n = UA_send((UA_FD)connectionId, + (const char*)buf->data + nWritten, + bytes_to_send, flags); + if(n < 0 && UA_ERRNO != UA_INTERRUPTED && UA_ERRNO != UA_AGAIN) { + UA_LOG_SOCKET_ERRNO_GAI_WRAP( + UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| Send failed with error %s", + (unsigned)connectionId, errno_str)); + TCP_shutdownConnection(cm, connectionId); + UA_ByteString_clear(buf); + return UA_STATUSCODE_BADCONNECTIONCLOSED; + } + } while(n < 0); + nWritten += (size_t)n; + } while(nWritten < buf->length); + + /* Free the buffer */ + UA_ByteString_clear(buf); + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +TCP_openConnection(UA_ConnectionManager *cm, + size_t paramsSize, UA_KeyValuePair *params, + void *context) { + + /* Get the connection parameters */ + char hostname[256]; + char portStr[16]; + + /* Prepare the port parameter as a string */ + const UA_UInt16 *port = (const UA_UInt16*) + UA_KeyValueMap_getScalar(params, paramsSize, + UA_QUALIFIEDNAME(0, "target-port"), + &UA_TYPES[UA_TYPES_UINT16]); + if(!port) { + UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_EVENTLOOP, + "TCP\t| Open TCP Connection: No target port defined, aborting"); + return UA_STATUSCODE_BADINTERNALERROR; + } + UA_snprintf(portStr, 6, "%d", *port); + + /* Prepare the hostname string */ + const UA_String *host = (const UA_String*) + UA_KeyValueMap_getScalar(params, paramsSize, + UA_QUALIFIEDNAME(0, "target-hostname"), + &UA_TYPES[UA_TYPES_STRING]); + if(!host) { + UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_EVENTLOOP, + "TCP\t| Open TCP Connection: No target hostname defined, aborting"); + return UA_STATUSCODE_BADINTERNALERROR; + } + if(host->length >= 256) { + UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_EVENTLOOP, + "TCP\t| Open TCP Connection: No target hostname too long, aborting"); + return UA_STATUSCODE_BADINTERNALERROR; + } + strncpy(hostname, (const char*)host->data, host->length); + hostname[host->length] = 0; + + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, "TCP\t| Open a connection to \"%s\" on port %s", + hostname, portStr); + + /* Create the socket description from the connectString + * TODO: Make this non-blocking */ + struct addrinfo hints, *info; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + int error = getaddrinfo(hostname, portStr, &hints, &info); + if(error != 0) { + UA_LOG_SOCKET_ERRNO_GAI_WRAP( + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP\t| Lookup of %s failed with error %d - %s", + hostname, error, errno_str)); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Create a socket */ + UA_FD newSock = socket(info->ai_family, info->ai_socktype, info->ai_protocol); + if(newSock == UA_INVALID_SOCKET) { + freeaddrinfo(info); + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP\t| Could not create socket to connect to %s (%s)", + hostname, errno_str)); + return UA_STATUSCODE_BADDISCONNECT; + } + + /* Set the socket options */ + UA_StatusCode res = UA_STATUSCODE_GOOD; + res |= TCP_setNonBlocking(newSock); + res |= TCP_setNoSigPipe(newSock); + res |= TCP_setNoNagle(newSock); + if(res != UA_STATUSCODE_GOOD) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP\t| Could not set socket options: %s", errno_str)); + freeaddrinfo(info); + UA_close(newSock); + return res; + } + + /* Non-blocking connect */ + error = UA_connect(newSock, info->ai_addr, info->ai_addrlen); + freeaddrinfo(info); + if(error != 0 && UA_ERRNO != UA_ERR_CONNECTION_PROGRESS) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP\t| Connecting the socket to %s failed (%s)", + hostname, errno_str)); + return UA_STATUSCODE_BADDISCONNECT; + } + + /* Register the fd to trigger when output is possible (the connection is open) */ + res = UA_EventLoop_registerFD(cm->eventSource.eventLoop, newSock, UA_POSIX_EVENT_WRITE, + (UA_FDCallback)TCP_connectionSocketCallback, + &cm->eventSource, context); + if(res != UA_STATUSCODE_GOOD) { + UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP\t| Registering the socket to connect to %s failed", hostname); + UA_close(newSock); + return res; + } + + /* Increase the count */ + TCPConnectionManager *tcm = (TCPConnectionManager*)cm; + tcm->fdCount++; + + UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP #%u\t| New connection to \"%s\" on port %s", + (unsigned)newSock, hostname, portStr); + + return UA_STATUSCODE_GOOD; +} + +/* Asynchronously register the listenSocket */ +static UA_StatusCode +TCP_eventSourceStart(UA_ConnectionManager *cm) { + /* Check the state */ + if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPED) { + UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_EVENTLOOP, "To start the TCP ConnectionManager, " + "it has to be registered in an EventLoop and not started"); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Initialize networking */ +#ifdef _WIN32 + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); +#endif + + /* Listening on a socket? */ + const UA_UInt16 *port = (const UA_UInt16*) + UA_KeyValueMap_getScalar(cm->eventSource.params, + cm->eventSource.paramsSize, + UA_QUALIFIEDNAME(0, "listen-port"), + &UA_TYPES[UA_TYPES_UINT16]); + if(!port) { + UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_EVENTLOOP, + "TCP\t| No port configured, don't accept connections"); + return UA_STATUSCODE_GOOD; + } + + /* Prepare the port parameter as a string */ + char portno[6]; + UA_snprintf(portno, 6, "%d", *port); + + /* Get the hostnames configuration */ + const UA_Variant *hostNames = + UA_KeyValueMap_get(cm->eventSource.params, + cm->eventSource.paramsSize, + UA_QUALIFIEDNAME(0, "listen-hostnames")); + if(!hostNames) { + /* No hostnames configured */ + UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, "TCP\t| Listening on all interfaces"); + TCP_registerListenSocketDomainName(cm, NULL, portno); + } else if(hostNames->type != &UA_TYPES[UA_TYPES_STRING]) { + /* Wrong datatype */ + UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_EVENTLOOP, + "TCP\t| The hostnames have to be strings"); + return UA_STATUSCODE_BADINTERNALERROR; + } else { + size_t interfaces = hostNames->arrayLength; + if(UA_Variant_isScalar(hostNames)) + interfaces = 1; + if(interfaces == 0) { + UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_EVENTLOOP, "TCP\t| Listening on all interfaces"); + TCP_registerListenSocketDomainName(cm, NULL, portno); + } else { + /* Iterate over the configured hostnames */ + UA_String *hostStrings = (UA_String*)hostNames->data; + for(size_t i = 0; i < hostNames->arrayLength; i++) { + char hostname[512]; + if(hostStrings[i].length >= sizeof(hostname)) + continue; + memcpy(hostname, hostStrings[i].data, hostStrings->length); + hostname[hostStrings->length] = '\0'; + TCP_registerListenSocketDomainName(cm, hostname, portno); + } + } + } + + /* The receive buffersize was configured? */ + const UA_UInt16 *bufSize = (const UA_UInt16*) + UA_KeyValueMap_getScalar(cm->eventSource.params, + cm->eventSource.paramsSize, + UA_QUALIFIEDNAME(0, "recv-bufsize"), + &UA_TYPES[UA_TYPES_UINT16]); + if(bufSize) + ((TCPConnectionManager*)cm)->recvBufferSize = *bufSize; + + /* Set the EventSource to the started state */ + cm->eventSource.state = UA_EVENTSOURCESTATE_STARTED; + return UA_STATUSCODE_GOOD; +} + +static void +TCP_shutdownCallback(UA_EventSource *es, UA_FD fd, void *fdcontext, short event) { + TCP_shutdownConnection((UA_ConnectionManager*)es, (uintptr_t)fd); +} + +static void +TCP_eventSourceStop(UA_ConnectionManager *cm) { + UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, "TCP\t| Shutting down the ConnectionManager"); + + /* Shut down all registered fd. The cm is set to "stopped" when the last fd + * is closed and deregistered in the callback from the EventLoop. */ + UA_EventLoop_iterateFD(cm->eventSource.eventLoop, &cm->eventSource, + TCP_shutdownCallback); + cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPING; + + TCPConnectionManager *tcm = (TCPConnectionManager*)cm; + + /* Closed? */ + if(tcm->fdCount == 0 && cm->eventSource.state == UA_EVENTSOURCESTATE_STOPPING) + cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPED; + + UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, "TCP\t| EventSource successfully stopped"); +} + +static UA_StatusCode +TCP_eventSourceDelete(UA_ConnectionManager *cm) { + if(cm->eventSource.state >= UA_EVENTSOURCESTATE_STARTING) { + UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_EVENTLOOP, + "TCP\t| The EventSource must be stopped before it can be deleted"); + return UA_STATUSCODE_BADINTERNALERROR; + } + + UA_deinitialize_architecture_network(); + + /* Delete the parameters */ + UA_Array_delete(cm->eventSource.params, + cm->eventSource.paramsSize, + &UA_TYPES[UA_TYPES_KEYVALUEPAIR]); + cm->eventSource.params = NULL; + cm->eventSource.paramsSize = 0; + + UA_String_clear(&cm->eventSource.name); + UA_free(cm); + return UA_STATUSCODE_GOOD; +} + +UA_ConnectionManager * +UA_ConnectionManager_TCP_new(const UA_String eventSourceName) { + TCPConnectionManager *cm = (TCPConnectionManager*) + UA_calloc(1, sizeof(TCPConnectionManager)); + if(!cm) + return NULL; + + UA_String_copy(&eventSourceName, &cm->cm.eventSource.name); + cm->cm.eventSource.start = (UA_StatusCode (*)(UA_EventSource *)) TCP_eventSourceStart; + cm->cm.eventSource.stop = (void (*)(UA_EventSource *))TCP_eventSourceStop; + cm->cm.eventSource.free = (UA_StatusCode (*)(UA_EventSource *))TCP_eventSourceDelete; + cm->cm.openConnection = TCP_openConnection; + cm->cm.allocNetworkBuffer = TCP_allocNetworkBuffer; + cm->cm.freeNetworkBuffer = TCP_freeNetworkBuffer; + cm->cm.sendWithConnection = TCP_sendWithConnection; + cm->cm.closeConnection = TCP_shutdownConnection; + cm->recvBufferSize = 1 << 14; /* TODO: Read from the config */ + return &cm->cm; +} diff --git a/arch/posix/ua_architecture.h b/arch/posix/ua_architecture.h index a8522a8cd22..3cf2dc2b868 100644 --- a/arch/posix/ua_architecture.h +++ b/arch/posix/ua_architecture.h @@ -76,9 +76,9 @@ void UA_sleep_ms(unsigned long ms); #define UA_ENABLE_LOG_COLORS -#define UA_poll poll #define UA_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) \ getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) +#define UA_poll poll #define UA_send send #define UA_recv recv #define UA_sendto sendto diff --git a/arch/vxworks/ua_architecture.h b/arch/vxworks/ua_architecture.h index b8899b28213..a7f67338f88 100644 --- a/arch/vxworks/ua_architecture.h +++ b/arch/vxworks/ua_architecture.h @@ -64,7 +64,8 @@ #define UA_ENABLE_LOG_COLORS -#define UA_getnameinfo getnameinfo +#define UA_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) \ + getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) #define UA_send send #define UA_recv recv #define UA_sendto sendto diff --git a/arch/wec7/ua_architecture.h b/arch/wec7/ua_architecture.h index 57370190cd8..d27df762a5a 100644 --- a/arch/wec7/ua_architecture.h +++ b/arch/wec7/ua_architecture.h @@ -83,7 +83,8 @@ void UA_sleep_ms(unsigned long ms); #define UA_ERRNO WSAGetLastError() #endif -#define UA_getnameinfo getnameinfo +#define UA_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) \ + getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) #define UA_send(sockfd, buf, len, flags) send(sockfd, buf, (int)(len), flags) #define UA_recv recv #define UA_sendto(sockfd, buf, len, flags, dest_addr, addrlen) sendto(sockfd, (const char*)(buf), (int)(len), flags, dest_addr, (int) (addrlen)) diff --git a/deps/ua-nodeset b/deps/ua-nodeset index 393b633468a..f71b3f411d5 160000 --- a/deps/ua-nodeset +++ b/deps/ua-nodeset @@ -1 +1 @@ -Subproject commit 393b633468a5d1d062dd253e1488d1d8ba335b6f +Subproject commit f71b3f411d5cb16097c3ae0c744f67ad45535ffb diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 4559d9a940d..e2ca238dcfc 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -165,6 +165,7 @@ add_example(server_loglevel server_loglevel.c) if(UA_ENABLE_HISTORIZING) add_example(tutorial_server_historicaldata tutorial_server_historicaldata.c) + add_example(tutorial_server_historicaldata_circular tutorial_server_historicaldata_circular.c) endif() if(UA_ENABLE_ENCRYPTION OR UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL") diff --git a/examples/events/client_eventfilter.c b/examples/events/client_eventfilter.c index 44663eb1e38..97892c0a1f0 100644 --- a/examples/events/client_eventfilter.c +++ b/examples/events/client_eventfilter.c @@ -108,14 +108,14 @@ setupOrFilter(UA_ContentFilterElement *element){ } static void -setupOfTypeFilter(UA_ContentFilterElement *element, UA_UInt32 typeId){ +setupOfTypeFilter(UA_ContentFilterElement *element, UA_UInt16 nsIndex, UA_UInt32 typeId){ element->filterOperands[0].content.decoded.type = &UA_TYPES[UA_TYPES_LITERALOPERAND]; element->filterOperands[0].encoding = UA_EXTENSIONOBJECT_DECODED; UA_LiteralOperand *literalOperand = UA_LiteralOperand_new(); UA_LiteralOperand_init(literalOperand); UA_NodeId *nodeId = UA_NodeId_new(); UA_NodeId_init(nodeId); - nodeId->namespaceIndex = 0; + nodeId->namespaceIndex = nsIndex; nodeId->identifierType = UA_NODEIDTYPE_NUMERIC; nodeId->identifier.numeric = typeId; UA_Variant_setScalar(&literalOperand->value, nodeId, &UA_TYPES[UA_TYPES_NODEID]); @@ -133,6 +133,14 @@ setupOfTypeFilter(UA_ContentFilterElement *element, UA_UInt32 typeId){ * * filterSelection 0: * ( (OfType AUDITEVENTTYPE ) (or) (OfType EVENTQUEUEOVERFLOWEVENTTYPE) ) + * filterSelection 2: + * ((EventTypeId == NodeID("StartScanEvent") || + * (EventTypeId == NodeID("DcpScanFinishedEvent") || + * (EventTypeId == NodeID("ScanFinishedEvent") || + * (EventTypeId == NodeID("CancelScanEvent") || + * (EventTypeId == NodeID("CancelScanFinishedEvent") || + * (EventTypeId == NodeID("ShutdownEvent")) + * */ static UA_StatusCode setupWhereClauses(UA_ContentFilter *contentFilter, UA_UInt16 whereClauseSize, UA_UInt16 filterSelection){ @@ -147,7 +155,7 @@ setupWhereClauses(UA_ContentFilter *contentFilter, UA_UInt16 whereClauseSize, UA } UA_StatusCode result = UA_STATUSCODE_GOOD; switch(filterSelection) { - case 0: + case 0: { contentFilter->elements[0].filterOperator = UA_FILTEROPERATOR_OR; contentFilter->elements[1].filterOperator = UA_FILTEROPERATOR_OFTYPE; contentFilter->elements[2].filterOperator = UA_FILTEROPERATOR_OFTYPE; @@ -156,17 +164,77 @@ setupWhereClauses(UA_ContentFilter *contentFilter, UA_UInt16 whereClauseSize, UA contentFilter->elements[2].filterOperandsSize = 1; /* Setup Operand Arrays */ result = setupOperandArrays(contentFilter); - if(result != UA_STATUSCODE_GOOD){ + if(result != UA_STATUSCODE_GOOD) { UA_ContentFilter_clear(contentFilter); return UA_STATUSCODE_BADCONFIGURATIONERROR; } /* first Element (OR) */ setupOrFilter(&contentFilter->elements[0]); /* second Element (OfType) */ - setupOfTypeFilter(&contentFilter->elements[1], 60443); + setupOfTypeFilter(&contentFilter->elements[1], 1, 5003); /* third Element (OfType) */ - setupOfTypeFilter(&contentFilter->elements[2], UA_NS0ID_EVENTQUEUEOVERFLOWEVENTTYPE); + setupOfTypeFilter(&contentFilter->elements[2], 0, + UA_NS0ID_EVENTQUEUEOVERFLOWEVENTTYPE); + break; + } + case 1: { + contentFilter->elements[0].filterOperator = UA_FILTEROPERATOR_OFTYPE; + UA_UInt32 placeholder_ScanFinishedEvent = 10000; + setupOfTypeFilter(&contentFilter->elements[0], 1, + placeholder_ScanFinishedEvent); + break; + } + case 2: { + contentFilter->elements[0].filterOperator = UA_FILTEROPERATOR_OR; + contentFilter->elements[1].filterOperator = UA_FILTEROPERATOR_OR; + contentFilter->elements[2].filterOperator = UA_FILTEROPERATOR_OR; + contentFilter->elements[3].filterOperator = UA_FILTEROPERATOR_OR; + contentFilter->elements[4].filterOperator = UA_FILTEROPERATOR_OR; + contentFilter->elements[5].filterOperator = UA_FILTEROPERATOR_OFTYPE; + contentFilter->elements[6].filterOperator = UA_FILTEROPERATOR_OFTYPE; + contentFilter->elements[7].filterOperator = UA_FILTEROPERATOR_OFTYPE; + contentFilter->elements[8].filterOperator = UA_FILTEROPERATOR_OFTYPE; + contentFilter->elements[9].filterOperator = UA_FILTEROPERATOR_OFTYPE; + contentFilter->elements[10].filterOperator = UA_FILTEROPERATOR_OFTYPE; + + // init or clauses + setupOrFilter(&contentFilter->elements[0]); + setupOrFilter(&contentFilter->elements[1]); + setupOrFilter(&contentFilter->elements[2]); + setupOrFilter(&contentFilter->elements[3]); + setupOrFilter(&contentFilter->elements[4]); + // init oftype + UA_UInt32 placeholder_StartScanEvent = 10000; + UA_UInt32 placeholder_DcpScanFinishedEvent = 10001; + UA_UInt32 CancelScanEvent = 10003; + UA_UInt32 placeholder_CancelScanFinishedEvent = 10004; + UA_UInt32 placeholder_ShutdownEvent = 10005; + UA_UInt32 placeholder_ShutdownEvent_1 = 10006; + setupOfTypeFilter(&contentFilter->elements[5], 1, placeholder_StartScanEvent); + setupOfTypeFilter(&contentFilter->elements[6], 1, + placeholder_DcpScanFinishedEvent); + setupOfTypeFilter(&contentFilter->elements[7], 1, + placeholder_ShutdownEvent_1); + setupOfTypeFilter(&contentFilter->elements[8], 1, CancelScanEvent); + setupOfTypeFilter(&contentFilter->elements[9], 1, + placeholder_CancelScanFinishedEvent); + setupOfTypeFilter(&contentFilter->elements[10], 1, placeholder_ShutdownEvent); break; + } + case 3:{ + UA_UInt32 placeholder_ShutdownEvent_2 = 10000; + contentFilter->elements[0].filterOperator = UA_FILTEROPERATOR_OFTYPE; + setupOfTypeFilter(&contentFilter->elements[0], 1, placeholder_ShutdownEvent_2); + break; + } + case 4: { + contentFilter->elements[0].filterOperator = UA_FILTEROPERATOR_AND; + contentFilter->elements[1].filterOperator = UA_FILTEROPERATOR_OFTYPE; + contentFilter->elements[2].filterOperator = UA_FILTEROPERATOR_AND; + contentFilter->elements[2].filterOperator = UA_FILTEROPERATOR_EQUALS; + contentFilter->elements[2].filterOperator = UA_FILTEROPERATOR_EQUALS; + break; + } default: UA_ContentFilter_clear(contentFilter); return UA_STATUSCODE_BADCONFIGURATIONERROR; @@ -175,11 +243,10 @@ setupWhereClauses(UA_ContentFilter *contentFilter, UA_UInt16 whereClauseSize, UA } static void -handler_events(UA_Client *client, UA_UInt32 subId, void *subContext, +handler_events_filter(UA_Client *client, UA_UInt32 subId, void *subContext, UA_UInt32 monId, void *monContext, size_t nEventFields, UA_Variant *eventFields) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Notification"); - + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Received Event Notification (Filter passed)"); for(size_t i = 0; i < nEventFields; ++i) { if(UA_Variant_hasScalarType(&eventFields[i], &UA_TYPES[UA_TYPES_UINT16])) { UA_UInt16 severity = *(UA_UInt16 *)eventFields[i].data; @@ -188,8 +255,7 @@ handler_events(UA_Client *client, UA_UInt32 subId, void *subContext, UA_LocalizedText *lt = (UA_LocalizedText *)eventFields[i].data; UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Message: '%.*s'", (int)lt->text.length, lt->text.data); - } - else { + } else { #ifdef UA_ENABLE_TYPEDESCRIPTION UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Don't know how to handle type: '%s'", eventFields[i].type->typeName); @@ -262,11 +328,11 @@ int main(int argc, char *argv[]) { UA_MonitoredItemCreateResult result = UA_Client_MonitoredItems_createEvent(client, subId, UA_TIMESTAMPSTORETURN_BOTH, item, - &monId, handler_events, NULL); + &monId, handler_events_filter, NULL); if(result.statusCode != UA_STATUSCODE_GOOD) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Could not add the MonitoredItem with %s", UA_StatusCode_name(retval)); + "Could not add the MonitoredItem with %s", UA_StatusCode_name(result.statusCode)); goto cleanup; } else { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, @@ -276,7 +342,7 @@ int main(int argc, char *argv[]) { monId = result.monitoredItemId; while(running) - retval = UA_Client_run_iterate(client, 100); + UA_Client_run_iterate(client, 100); /* Delete the subscription */ cleanup: diff --git a/examples/events/server_random_events.c b/examples/events/server_random_events.c index 889a677707d..6e1b90fee63 100644 --- a/examples/events/server_random_events.c +++ b/examples/events/server_random_events.c @@ -48,27 +48,27 @@ addSampleEventTypes(UA_Server *server) { UA_Array_new(SAMPLE_EVENT_TYPES_COUNT, &UA_TYPES[UA_TYPES_NODEID]); UA_StatusCode retval = addEventType(server, "SampleBaseEventType", UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE), - UA_NODEID_NULL, + UA_NODEID_NUMERIC(1, 5000), &eventTypes[0]); if (retval != UA_STATUSCODE_GOOD) return retval; retval = addEventType(server, "SampleDeviceFailureEventType", UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE), - UA_NODEID_NULL, + UA_NODEID_NUMERIC(1, 5001), &eventTypes[1]); if (retval != UA_STATUSCODE_GOOD) return retval; retval = addEventType(server, "SampleEventQueueOverflowEventType", UA_NODEID_NUMERIC(0, UA_NS0ID_EVENTQUEUEOVERFLOWEVENTTYPE), - UA_NODEID_NULL, + UA_NODEID_NUMERIC(1, 5002), &eventTypes[2]); if (retval != UA_STATUSCODE_GOOD) return retval; retval = addEventType(server, "SampleProgressEventType", UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE), - UA_NODEID_NULL, + UA_NODEID_NUMERIC(1, 5003), &eventTypes[3]); if (retval != UA_STATUSCODE_GOOD) return retval; retval = addEventType(server, "SampleAuditSecurityEventType", UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE), - UA_NODEID_NUMERIC(0,60443), + UA_NODEID_NUMERIC(1, 5004), &eventTypes[4]); if (retval != UA_STATUSCODE_GOOD) return retval; return UA_STATUSCODE_GOOD; diff --git a/examples/nodeset/CMakeLists.txt b/examples/nodeset/CMakeLists.txt index 5c699db6cdd..d404770005a 100644 --- a/examples/nodeset/CMakeLists.txt +++ b/examples/nodeset/CMakeLists.txt @@ -91,7 +91,7 @@ if(UA_NAMESPACE_ZERO STREQUAL "FULL") ua_generate_nodeset_and_datatypes( NAME "plc" # PLCopen does not define custom types. Only generate the nodeset - FILE_NS "${FILE_NS_DIRPREFIX}/PLCopen/Opc.Ua.Plc.NodeSet2.xml" + FILE_NS "${FILE_NS_DIRPREFIX}/PLCopen/Opc.Ua.PLCopen.NodeSet2_V1.02.xml" # PLCopen depends on the di nodeset, which must be generated before DEPENDS "di" INTERNAL diff --git a/examples/pubsub_realtime/nodeset/pubsub_nodeset_rt_publisher.c b/examples/pubsub_realtime/nodeset/pubsub_nodeset_rt_publisher.c index 2c3a88a7d01..dd80286987b 100644 --- a/examples/pubsub_realtime/nodeset/pubsub_nodeset_rt_publisher.c +++ b/examples/pubsub_realtime/nodeset/pubsub_nodeset_rt_publisher.c @@ -206,9 +206,9 @@ changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, /* Remove the callback added for cyclic repetition */ static void removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId){ - if(callbackId && (pthread_join(callbackId, NULL) != 0)) + if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0)) UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Pthread Join Failed thread: %ld\n", callbackId); + "Pthread Join Failed thread: %lu\n", (long unsigned)callbackId); } /** @@ -705,8 +705,8 @@ int main(int argc, char **argv) { size_t pubLoopVariable = 0; for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher; pubLoopVariable++) { - fprintf(fpPublisher, "%ld,%ld.%09ld,%lf\n", - publishCounterValue[pubLoopVariable], + fprintf(fpPublisher, "%lu,%ld.%09ld,%lf\n", + (long unsigned)publishCounterValue[pubLoopVariable], publishTimestamp[pubLoopVariable].tv_sec, publishTimestamp[pubLoopVariable].tv_nsec, pressureValues[pubLoopVariable]); @@ -717,8 +717,8 @@ int main(int argc, char **argv) { size_t pubLoopVariable = 0; for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher; pubLoopVariable++) { - printf("%ld,%ld.%09ld,%lf\n", - publishCounterValue[pubLoopVariable], + printf("%lu,%ld.%09ld,%lf\n", + (long unsigned)publishCounterValue[pubLoopVariable], publishTimestamp[pubLoopVariable].tv_sec, publishTimestamp[pubLoopVariable].tv_nsec, pressureValues[pubLoopVariable]); diff --git a/examples/pubsub_realtime/nodeset/pubsub_nodeset_rt_subscriber.c b/examples/pubsub_realtime/nodeset/pubsub_nodeset_rt_subscriber.c index bae0e5dd5a5..54c2d7a8a83 100644 --- a/examples/pubsub_realtime/nodeset/pubsub_nodeset_rt_subscriber.c +++ b/examples/pubsub_realtime/nodeset/pubsub_nodeset_rt_subscriber.c @@ -194,9 +194,9 @@ changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt /* Remove the callback added for cyclic repetition */ static void removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) { - if(callbackId && (pthread_join(callbackId, NULL) != 0)) + if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0)) UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Pthread Join Failed thread: %ld\n", callbackId); + "Pthread Join Failed thread: %lu\n", (long unsigned)callbackId); } /** @@ -665,8 +665,8 @@ int main(int argc, char **argv) { size_t subLoopVariable = 0; for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber; subLoopVariable++) { - fprintf(fpSubscriber, "%ld,%ld.%09ld,%lf\n", - subscribeCounterValue[subLoopVariable], + fprintf(fpSubscriber, "%lu,%ld.%09ld,%lf\n", + (long unsigned)subscribeCounterValue[subLoopVariable], subscribeTimestamp[subLoopVariable].tv_sec, subscribeTimestamp[subLoopVariable].tv_nsec, pressureValues[subLoopVariable]); @@ -677,8 +677,8 @@ int main(int argc, char **argv) { size_t subLoopVariable = 0; for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber; subLoopVariable++) { - fprintf(fpSubscriber, "%ld,%ld.%09ld,%lf\n", - subscribeCounterValue[subLoopVariable], + fprintf(fpSubscriber, "%lu,%ld.%09ld,%lf\n", + (long unsigned)subscribeCounterValue[subLoopVariable], subscribeTimestamp[subLoopVariable].tv_sec, subscribeTimestamp[subLoopVariable].tv_nsec, pressureValues[subLoopVariable]); diff --git a/examples/pubsub_realtime/pubsub_TSN_loopback.c b/examples/pubsub_realtime/pubsub_TSN_loopback.c index 872241f5172..da169417185 100644 --- a/examples/pubsub_realtime/pubsub_TSN_loopback.c +++ b/examples/pubsub_realtime/pubsub_TSN_loopback.c @@ -79,6 +79,7 @@ #include #include +#include #include #include @@ -103,12 +104,10 @@ UA_DataSetReaderConfig readerConfig; /* Qbv offset */ #define DEFAULT_QBV_OFFSET 125 #define DEFAULT_SOCKET_PRIORITY 3 -#if defined(PUBLISHER) #define PUBLISHER_ID 2235 #define WRITER_GROUP_ID 100 #define DATA_SET_WRITER_ID 62541 #define DEFAULT_PUBLISHING_MAC_ADDRESS "opc.eth://01-00-5E-00-00-01:8.3" -#endif #if defined(SUBSCRIBER) #define PUBLISHER_ID_SUB 2234 #define WRITER_GROUP_ID_SUB 101 @@ -125,9 +124,11 @@ UA_DataSetReaderConfig readerConfig; #define MILLI_SECONDS 1000 * 1000 #define SECONDS 1000 * 1000 * 1000 #define SECONDS_SLEEP 5 +#if defined(PUBLISHER) /* Publisher will sleep for 60% of cycle time and then prepares the */ /* transmission packet within 40% */ static UA_Double pubWakeupPercentage = 0.6; +#endif /* Subscriber will wakeup only during start of cycle and check whether */ /* the packets are received */ static UA_Double subWakeupPercentage = 0; @@ -150,6 +151,24 @@ static UA_Double userAppWakeupPercentage = 0.3; #define CLOCKID CLOCK_TAI #define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp" +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION +#define UA_AES128CTR_SIGNING_KEY_LENGTH 32 +#define UA_AES128CTR_KEY_LENGTH 16 +#define UA_AES128CTR_KEYNONCE_LENGTH 4 + +#if defined(PUBLISHER) +UA_Byte signingKeyPub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0}; +UA_Byte encryptingKeyPub[UA_AES128CTR_KEY_LENGTH] = {0}; +UA_Byte keyNoncePub[UA_AES128CTR_KEYNONCE_LENGTH] = {0}; +#endif + +#if defined(SUBSCRIBER) +UA_Byte signingKeySub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0}; +UA_Byte encryptingKeySub[UA_AES128CTR_KEY_LENGTH] = {0}; +UA_Byte keyNonceSub[UA_AES128CTR_KEYNONCE_LENGTH] = {0}; +#endif +#endif + /* If the Hardcoded publisher/subscriber MAC addresses need to be changed, * change PUBLISHING_MAC_ADDRESS and SUBSCRIBING_MAC_ADDRESS */ @@ -334,7 +353,7 @@ addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, char threadNamePub[10] = "Publisher"; *callbackId = threadCreation((UA_Int16)pubPriority, (size_t)pubCore, publisherETF, threadNamePub, threadArguments); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Publisher thread callback Id: %ld\n", *callbackId); + "Publisher thread callback Id: %lu\n", (long unsigned)*callbackId); #endif } else { @@ -343,7 +362,7 @@ addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, char threadNameSub[11] = "Subscriber"; *callbackId = threadCreation((UA_Int16)subPriority,(size_t)subCore, subscriber, threadNameSub, threadArguments); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Subscriber thread callback Id: %ld\n", *callbackId); + "Subscriber thread callback Id: %lu\n", (long unsigned)*callbackId); #endif } @@ -363,9 +382,9 @@ changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, /* Remove the callback added for cyclic repetition */ static void removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) { - if(callbackId && (pthread_join(callbackId, NULL) != 0)) + if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0)) UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Pthread Join Failed thread: %ld\n", callbackId); + "Pthread Join Failed thread: %lu\n", (long unsigned)callbackId); } @@ -454,12 +473,29 @@ addReaderGroup(UA_Server *server) { readerGroupConfig.timeout = 0; //Blocking socket } +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + /* Encryption settings */ + UA_ServerConfig *config = UA_Server_getConfig(server); + readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; + readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; +#endif + readerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback; readerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback; readerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback; UA_Server_addReaderGroup(server, connectionIdentSubscriber, &readerGroupConfig, &readerGroupIdentifier); + +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + /* Add the encryption key informaton */ + UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeySub}; + UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeySub}; + UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNonceSub}; + // TODO security token not necessary for readergroup (extracted from security-header) + UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn); +#endif + } /* Set SubscribedDataSet type to TargetVariables data type @@ -811,6 +847,12 @@ addWriterGroup(UA_Server *server) { writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; + +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + UA_ServerConfig *config = UA_Server_getConfig(server); + writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; + writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[1]; +#endif /* The configuration flags for the messages are encapsulated inside the * message- and transport settings extension objects. These extension * objects are defined by the standard. e.g. @@ -827,6 +869,14 @@ addWriterGroup(UA_Server *server) { UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent); UA_Server_setWriterGroupOperational(server, writerGroupIdent); UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage); + +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + /* Add the encryption key informaton */ + UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub}; + UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub}; + UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub}; + UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn); +#endif } /* DataSetWriter handling */ @@ -859,7 +909,8 @@ updateMeasurementsPublisher(struct timespec start_time, } if(consolePrint) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Pub:%ld,%ld.%09ld\n", counterValue, start_time.tv_sec, start_time.tv_nsec); + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Pub:%lu,%ld.%09ld\n", + (long unsigned)counterValue, start_time.tv_sec, start_time.tv_nsec); if (signalTerm != UA_TRUE){ publishTimestamp[measurementsPublisher] = start_time; @@ -884,7 +935,8 @@ updateMeasurementsSubscriber(struct timespec receive_time, UA_UInt64 counterValu } if(consolePrint) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Sub:%ld,%ld.%09ld\n", counterValue, receive_time.tv_sec, receive_time.tv_nsec); + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Sub:%lu,%ld.%09ld\n", + (long unsigned)counterValue, receive_time.tv_sec, receive_time.tv_nsec); if (signalTerm != UA_TRUE){ subscribeTimestamp[measurementsSubscriber] = receive_time; @@ -894,6 +946,7 @@ updateMeasurementsSubscriber(struct timespec receive_time, UA_UInt64 counterValu } #endif +#if defined(PUBLISHER) /** * **Publisher thread routine** * @@ -972,6 +1025,7 @@ void *publisherETF(void *arg) { runningServer = UA_FALSE; return (void*)NULL; } +#endif #if defined(SUBSCRIBER) /** @@ -1028,6 +1082,9 @@ void *subscriber(void *arg) { if (*runningSub == UA_FALSE) signalTerm = UA_TRUE; +#if defined(SUBSCRIBER) && !defined(PUBLISHER) + runningServer = UA_FALSE; +#endif UA_free(threadArgumentsSubscriber); return (void*)NULL; } @@ -1058,7 +1115,11 @@ void *userApplicationPubSub(void *arg) { nextnanosleeptimeUserApplication.tv_nsec = threadBaseTime.tv_nsec + (__syscall_slong_t)(cycleTimeInMsec * MILLI_SECONDS * userAppWakeupPercentage); nanoSecondFieldConversion(&nextnanosleeptimeUserApplication); +#if defined(PUBLISHER) && defined(SUBSCRIBER) while (*runningSub || *runningPub) { +#else + while (*runningSub) { +#endif /* The User application threads wakes up at the configured userApp wake up percentage (30%) of each cycle */ clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL); #if defined(SUBSCRIBER) @@ -1144,7 +1205,7 @@ static pthread_t threadCreation(UA_Int16 threadPriority, size_t coreAffinity, vo UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName); if (CPU_ISSET(coreAffinity, &cpuset)) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %ld\n", applicationName, coreAffinity); + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %lu\n", applicationName, (long unsigned)coreAffinity); return threadID; } @@ -1259,7 +1320,6 @@ static void removeServerNodes(UA_Server *server) { } UA_Server_deleteNode(server, runningPubStatusNodeID, UA_TRUE); UA_NodeId_clear(&runningPubStatusNodeID); - UA_Server_deleteNode(server, subNodeID, UA_TRUE); UA_NodeId_clear(&subNodeID); for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) @@ -1463,6 +1523,24 @@ int main(int argc, char **argv) { UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL); +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION +#if defined(PUBLISHER) && defined(SUBSCRIBER) + /* Instantiate the PubSub SecurityPolicy */ + config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*) + UA_calloc(2, sizeof(UA_PubSubSecurityPolicy)); + config->pubSubConfig.securityPoliciesSize = 2; +#else + config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*) + UA_malloc(sizeof(UA_PubSubSecurityPolicy)); + config->pubSubConfig.securityPoliciesSize = 1; +#endif +#endif + +#if defined(UA_ENABLE_PUBSUB_ENCRYPTION) && defined(PUBLISHER) + UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[1], + &config->logger); +#endif + #if defined(PUBLISHER) UA_NetworkAddressUrlDataType networkAddressUrlPub; #endif @@ -1512,6 +1590,10 @@ if (enableCsvLog) UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent); #endif +#if defined(UA_ENABLE_PUBSUB_ENCRYPTION) && defined(SUBSCRIBER) + UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[0], + &config->logger); +#endif #if defined (PUBLISHER) && defined(SUBSCRIBER) UA_ServerConfig_addPubSubTransportLayer(config, UA_PubSubTransportLayerEthernet()); #endif @@ -1537,7 +1619,10 @@ if (enableCsvLog) #endif retval |= UA_Server_run(server, &runningServer); +#if defined(SUBSCRIBER) UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier); +#endif + #if defined(PUBLISHER) || defined(SUBSCRIBER) returnValue = pthread_join(userThreadID, NULL); if (returnValue != 0) @@ -1550,8 +1635,8 @@ if (enableCsvLog) size_t pubLoopVariable = 0; for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher; pubLoopVariable++) { - fprintf(fpPublisher, "%ld,%ld.%09ld\n", - publishCounterValue[pubLoopVariable], + fprintf(fpPublisher, "%lu,%ld.%09ld\n", + (long unsigned)publishCounterValue[pubLoopVariable], publishTimestamp[pubLoopVariable].tv_sec, publishTimestamp[pubLoopVariable].tv_nsec); } @@ -1561,8 +1646,8 @@ if (enableCsvLog) size_t subLoopVariable = 0; for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber; subLoopVariable++) { - fprintf(fpSubscriber, "%ld,%ld.%09ld\n", - subscribeCounterValue[subLoopVariable], + fprintf(fpSubscriber, "%lu,%ld.%09ld\n", + (long unsigned)subscribeCounterValue[subLoopVariable], subscribeTimestamp[subLoopVariable].tv_sec, subscribeTimestamp[subLoopVariable].tv_nsec); } @@ -1574,6 +1659,7 @@ if (enableCsvLog) UA_Server_delete(server); UA_free(serverConfig); #endif + #if defined(PUBLISHER) UA_free(runningPub); UA_free(pubCounterData); diff --git a/examples/pubsub_realtime/pubsub_TSN_publisher.c b/examples/pubsub_realtime/pubsub_TSN_publisher.c index 2b2615ff1f1..7910579f1de 100644 --- a/examples/pubsub_realtime/pubsub_TSN_publisher.c +++ b/examples/pubsub_realtime/pubsub_TSN_publisher.c @@ -80,6 +80,8 @@ #include #include +#include + #include "ua_pubsub.h" #include @@ -110,12 +112,10 @@ UA_DataSetReaderConfig readerConfig; #define DATA_SET_WRITER_ID 62541 #define DEFAULT_PUBLISHING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.3" #endif -#if defined(SUBSCRIBER) #define PUBLISHER_ID_SUB 2235 #define WRITER_GROUP_ID_SUB 100 #define DATA_SET_WRITER_ID_SUB 62541 #define DEFAULT_SUBSCRIBING_MAC_ADDRESS "opc.eth://01-00-5E-00-00-01:8.3" -#endif #define REPEATED_NODECOUNTS 2 // Default to publish 64 bytes #define PORT_NUMBER 62541 #define DEFAULT_XDP_QUEUE 2 @@ -129,9 +129,11 @@ UA_DataSetReaderConfig readerConfig; /* Publisher will sleep for 60% of cycle time and then prepares the */ /* transmission packet within 40% */ static UA_Double pubWakeupPercentage = 0.6; +#if defined(SUBSCRIBER) /* Subscriber will wakeup only during start of cycle and check whether */ /* the packets are received */ static UA_Double subWakeupPercentage = 0; +#endif /* User application Pub/Sub will wakeup at the 30% of cycle time and handles the */ /* user data such as read and write in Information model */ static UA_Double userAppWakeupPercentage = 0.3; @@ -153,6 +155,24 @@ static UA_Double userAppWakeupPercentage = 0.3; #define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp" #define LATENCY_CSV_FILE_NAME "latencyT1toT8.csv" +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION +#define UA_AES128CTR_SIGNING_KEY_LENGTH 32 +#define UA_AES128CTR_KEY_LENGTH 16 +#define UA_AES128CTR_KEYNONCE_LENGTH 4 + +#if defined(PUBLISHER) +UA_Byte signingKeyPub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0}; +UA_Byte encryptingKeyPub[UA_AES128CTR_KEY_LENGTH] = {0}; +UA_Byte keyNoncePub[UA_AES128CTR_KEYNONCE_LENGTH] = {0}; +#endif + +#if defined(SUBSCRIBER) +UA_Byte signingKeySub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0}; +UA_Byte encryptingKeySub[UA_AES128CTR_KEY_LENGTH] = {0}; +UA_Byte keyNonceSub[UA_AES128CTR_KEYNONCE_LENGTH] = {0}; +#endif +#endif + /* If the Hardcoded publisher/subscriber MAC addresses need to be changed, * change PUBLISHING_MAC_ADDRESS and SUBSCRIBING_MAC_ADDRESS */ @@ -336,7 +356,7 @@ addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, char threadNamePub[10] = "Publisher"; *callbackId = threadCreation((UA_Int16)pubPriority, (size_t)pubCore, publisherETF, threadNamePub, threadArguments); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Publisher thread callback Id: %ld\n", *callbackId); + "Publisher thread callback Id: %lu\n", (unsigned long)*callbackId); #endif } else { @@ -345,7 +365,7 @@ addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, char threadNameSub[11] = "Subscriber"; *callbackId = threadCreation((UA_Int16)subPriority, (size_t)subCore, subscriber, threadNameSub, threadArguments); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Subscriber thread callback Id: %ld\n", *callbackId); + "Subscriber thread callback Id: %lu\n", (unsigned long)*callbackId); #endif } @@ -365,9 +385,9 @@ changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, /* Remove the callback added for cyclic repetition */ static void removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) { - if(callbackId && (pthread_join(callbackId, NULL) != 0)) + if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0)) UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Pthread Join Failed thread: %ld\n", callbackId); + "Pthread Join Failed thread: %lu\n", (unsigned long)callbackId); } /** @@ -456,14 +476,31 @@ addReaderGroup(UA_Server *server) { readerGroupConfig.timeout = 0; //Blocking socket } +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + /* Encryption settings */ + UA_ServerConfig *config = UA_Server_getConfig(server); + readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; + readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[1]; +#endif + readerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback; readerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback; readerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback; UA_Server_addReaderGroup(server, connectionIdentSubscriber, &readerGroupConfig, &readerGroupIdentifier); + +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + /* Add the encryption key informaton */ + UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeySub}; + UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeySub}; + UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNonceSub}; + // TODO security token not necessary for readergroup (extracted from security-header) + UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn); +#endif } + /* Set SubscribedDataSet type to TargetVariables data type * Add SubscriberCounter variable to the DataSetReader */ static void addSubscribedVariables (UA_Server *server) { @@ -817,6 +854,13 @@ addWriterGroup(UA_Server *server) { writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; + +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + UA_ServerConfig *config = UA_Server_getConfig(server); + writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; + writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; +#endif + /* The configuration flags for the messages are encapsulated inside the * message- and transport settings extension objects. These extension * objects are defined by the standard. e.g. @@ -833,6 +877,14 @@ addWriterGroup(UA_Server *server) { UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent); UA_Server_setWriterGroupOperational(server, writerGroupIdent); UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage); + +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + /* Add the encryption key informaton */ + UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub}; + UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub}; + UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub}; + UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn); +#endif } /* DataSetWriter handling */ @@ -864,7 +916,8 @@ updateMeasurementsPublisher(struct timespec start_time, } if(consolePrint) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Pub:%ld,%ld.%09ld\n", counterValue, start_time.tv_sec, start_time.tv_nsec); + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Pub:%lu,%ld.%09ld\n", + (long unsigned)counterValue, start_time.tv_sec, start_time.tv_nsec); if (signalTerm != UA_TRUE){ @@ -890,7 +943,8 @@ updateMeasurementsSubscriber(struct timespec receive_time, } if(consolePrint) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Sub:%ld,%ld.%09ld\n", counterValue, receive_time.tv_sec, receive_time.tv_nsec); + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Sub:%lu,%ld.%09ld\n", + (long unsigned)counterValue, receive_time.tv_sec, receive_time.tv_nsec); if (signalTerm != UA_TRUE) { @@ -969,6 +1023,9 @@ void *publisherETF(void *arg) { nanoSecondFieldConversion(&nextnanosleeptime); } +#if defined(PUBLISHER) && !defined(SUBSCRIBER) + runningServer = UA_FALSE; +#endif UA_free(threadArgumentsPublisher); return (void*)NULL; } @@ -1066,7 +1123,11 @@ void *userApplicationPubSub(void *arg) { *repeatedCounterData[iterator] = repeatedCounterValue; } +#if defined(PUBLISHER) && defined(SUBSCRIBER) while (*runningPub || *runningSub) { +#else + while (*runningPub) { +#endif /* The User application threads wakes up at the configured userApp wake up percentage (30%) of each cycle */ clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL); #if defined(PUBLISHER) @@ -1150,7 +1211,7 @@ static pthread_t threadCreation(UA_Int16 threadPriority, size_t coreAffinity, vo UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName); if (CPU_ISSET(coreAffinity, &cpuset)) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %ld\n", applicationName, coreAffinity); + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %lu\n", applicationName, (unsigned long)coreAffinity); return threadID; } @@ -1263,7 +1324,6 @@ static void removeServerNodes(UA_Server *server) { } UA_Server_deleteNode(server, runningPubStatusNodeID, UA_TRUE); UA_NodeId_clear(&runningPubStatusNodeID); - UA_Server_deleteNode(server, subNodeID, UA_TRUE); UA_NodeId_clear(&subNodeID); for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) @@ -1274,7 +1334,7 @@ static void removeServerNodes(UA_Server *server) { UA_Server_deleteNode(server, runningSubStatusNodeID, UA_TRUE); UA_NodeId_clear(&runningSubStatusNodeID); } - +#if defined (PUBLISHER) && defined(SUBSCRIBER) /** * **Time Difference Calculation** * @@ -1345,8 +1405,8 @@ static void computeLatencyAndGenerateCsv(char *latencyFileName) { if(((latencyCharIndex - prevLatencyCharIndex) + latencyCharIndex + 3) < MAX_MEASUREMENTS_FILEWRITE) { latencyCharIndex += (UA_UInt64)sprintf(&latency_measurements[latencyCharIndex], - "%0.3f, %ld, %ld\n", - finalTime, missed_counter, repeated_counter); + "%0.3f, %lu, %lu\n", + finalTime, (unsigned long)missed_counter, (unsigned long)repeated_counter); } else { UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, @@ -1358,10 +1418,10 @@ static void computeLatencyAndGenerateCsv(char *latencyFileName) { } /* Write into the latency file */ - fwrite(&latency_measurements[0], prevLatencyCharIndex, 1, fp_latency); + fwrite(&latency_measurements[0], (size_t)prevLatencyCharIndex, 1, fp_latency); fclose(fp_latency); } - +#endif /** * **Usage function** * @@ -1557,6 +1617,23 @@ int main(int argc, char **argv) { } UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL); +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION +#if defined(PUBLISHER) && defined(SUBSCRIBER) + /* Instantiate the PubSub SecurityPolicy */ + config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*) + UA_calloc(2, sizeof(UA_PubSubSecurityPolicy)); + config->pubSubConfig.securityPoliciesSize = 2; +#else + config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*) + UA_malloc(sizeof(UA_PubSubSecurityPolicy)); + config->pubSubConfig.securityPoliciesSize = 1; +#endif +#endif + +#if defined(UA_ENABLE_PUBSUB_ENCRYPTION) && defined(PUBLISHER) + UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[0], + &config->logger); +#endif #if defined(PUBLISHER) UA_NetworkAddressUrlDataType networkAddressUrlPub; @@ -1606,6 +1683,11 @@ if (enableCsvLog) { UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent); #endif +#if defined(UA_ENABLE_PUBSUB_ENCRYPTION) && defined(SUBSCRIBER) + UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[1], + &config->logger); +#endif + #if defined (PUBLISHER) && defined(SUBSCRIBER) UA_ServerConfig_addPubSubTransportLayer(config, UA_PubSubTransportLayerEthernet()); #endif @@ -1631,23 +1713,24 @@ if (enableCsvLog) { #endif retval |= UA_Server_run(server, &runningServer); +#if defined(SUBSCRIBER) UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier); +#endif #if defined(PUBLISHER) || defined(SUBSCRIBER) returnValue = pthread_join(userThreadID, NULL); if (returnValue != 0) UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for User thread:%d\n", returnValue); #endif - if (enableCsvLog) { #if defined(PUBLISHER) /* Write the published data in the publisher_T1.csv file */ size_t pubLoopVariable = 0; for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher; pubLoopVariable++) { - fprintf(fpPublisher, "%ld,%ld.%09ld\n", - publishCounterValue[pubLoopVariable], - publishTimestamp[pubLoopVariable].tv_sec, - publishTimestamp[pubLoopVariable].tv_nsec); + fprintf(fpPublisher, "%lu,%lu.%09lu\n", + (long unsigned)publishCounterValue[pubLoopVariable], + (long unsigned)publishTimestamp[pubLoopVariable].tv_sec, + (long unsigned)publishTimestamp[pubLoopVariable].tv_nsec); } #endif #if defined(SUBSCRIBER) @@ -1655,17 +1738,19 @@ if (enableCsvLog) { size_t subLoopVariable = 0; for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber; subLoopVariable++) { - fprintf(fpSubscriber, "%ld,%ld.%09ld\n", - subscribeCounterValue[subLoopVariable], - subscribeTimestamp[subLoopVariable].tv_sec, - subscribeTimestamp[subLoopVariable].tv_nsec); + fprintf(fpSubscriber, "%lu,%lu.%09lu\n", + (long unsigned)subscribeCounterValue[subLoopVariable], + (long unsigned)subscribeTimestamp[subLoopVariable].tv_sec, + (long unsigned)subscribeTimestamp[subLoopVariable].tv_nsec); } #endif } if(enableLatencyCsvLog) { +#if defined (PUBLISHER) && defined(SUBSCRIBER) char *latencyCsvName = LATENCY_CSV_FILE_NAME; computeLatencyAndGenerateCsv(latencyCsvName); +#endif } #if defined(PUBLISHER) || defined(SUBSCRIBER) @@ -1688,7 +1773,6 @@ if (enableCsvLog) { if (enableCsvLog) fclose(fpPublisher); #endif - #if defined(SUBSCRIBER) UA_free(runningSub); UA_free(subCounterData); diff --git a/examples/tutorial_server_historicaldata_circular.c b/examples/tutorial_server_historicaldata_circular.c new file mode 100644 index 00000000000..bc61f8327f0 --- /dev/null +++ b/examples/tutorial_server_historicaldata_circular.c @@ -0,0 +1,133 @@ +/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. + * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. + * + * Copyright 2019 (c) basysKom GmbH (Author: Peter Rustler) + * Copyright 2021 (c) luibass92 (Author: Luigi Bassetta) + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static volatile UA_Boolean running = true; +static void stopHandler(int sign) { + (void)sign; + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); + running = false; +} + +int main(void) { + signal(SIGINT, stopHandler); + signal(SIGTERM, stopHandler); + + UA_Server *server = UA_Server_new(); + UA_ServerConfig *config = UA_Server_getConfig(server); + UA_ServerConfig_setDefault(config); + + /* We need a gathering for the plugin to constuct. + * The UA_HistoryDataGathering is responsible to collect data and store it to the database. + * We will use this gathering for one node, only. initialNodeIdStoreSize = 1 + * The store will NOT automatically grow if you register more than one node will return a UA_STATUS_BADOUTOFMEMORY. */ + UA_HistoryDataGathering gathering = UA_HistoryDataGathering_Circular(1); + + /* We set the responsible plugin in the configuration. UA_HistoryDatabase is + * the main plugin which handles the historical data service. */ + config->historyDatabase = UA_HistoryDatabase_default(gathering); + + /* Define the attribute of the uint32 variable node */ + UA_VariableAttributes attr = UA_VariableAttributes_default; + UA_UInt32 myUint32 = 40; + UA_Variant_setScalar(&attr.value, &myUint32, &UA_TYPES[UA_TYPES_UINT32]); + attr.description = UA_LOCALIZEDTEXT("en-US","myUintValue"); + attr.displayName = UA_LOCALIZEDTEXT("en-US","myUintValue"); + attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; + /* We set the access level to also support history read + * This is what will be reported to clients */ + attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE | UA_ACCESSLEVELMASK_HISTORYREAD; + /* We also set this node to historizing, so the server internals also know from it. */ + attr.historizing = true; + + /* Add the variable node to the information model */ + UA_NodeId uint32NodeId = UA_NODEID_STRING(1, "myUintValue"); + UA_QualifiedName uint32Name = UA_QUALIFIEDNAME(1, "myUintValue"); + UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); + UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); + UA_NodeId outNodeId; + UA_NodeId_init(&outNodeId); + UA_StatusCode retval = UA_Server_addVariableNode(server, + uint32NodeId, + parentNodeId, + parentReferenceNodeId, + uint32Name, + UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), + attr, + NULL, + &outNodeId); + + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "UA_Server_addVariableNode %s", UA_StatusCode_name(retval)); + + /* Now we define the settings for our node */ + UA_HistorizingNodeIdSettings setting; + + /* There is a memory based database plugin. We will use that. We just + * reserve space for 3 nodes with 10 values each. This will NOT automatically grow + * but will store data as a circular buffer of size 10. The 11th value will be + * stored replacing the oldest one and the process will continue like that. */ + setting.historizingBackend = UA_HistoryDataBackend_Memory_Circular(3, 10); + + /* We want the server to serve a maximum of 100 values per request. This + * value depend on the plattform you are running the server. A big server + * can serve more values, smaller ones less. */ + setting.maxHistoryDataResponseSize = 100; + + /* If we have a sensor which do not report updates + * and need to be polled we change the setting like that. + * The polling interval in ms. + * + setting.pollingInterval = 100; + * + * Set the update strategie to polling. + * + setting.historizingUpdateStrategy = UA_HISTORIZINGUPDATESTRATEGY_POLL; + */ + + /* If you want to insert the values to the database yourself, we can set the user strategy here. + * This is useful if you for example want a value stored, if a defined delta is reached. + * Then you should use a local monitored item with a fuzziness and store the value in the callback. + * + setting.historizingUpdateStrategy = UA_HISTORIZINGUPDATESTRATEGY_USER; + */ + + /* We want the values stored in the database, when the nodes value is + * set. */ + setting.historizingUpdateStrategy = UA_HISTORIZINGUPDATESTRATEGY_VALUESET; + + /* At the end we register the node for gathering data in the database. */ + retval = gathering.registerNodeId(server, gathering.context, &outNodeId, setting); + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "registerNodeId %s", UA_StatusCode_name(retval)); + + /* If you use UA_HISTORIZINGUPDATESTRATEGY_POLL, then start the polling. + * + retval = gathering.startPoll(server, gathering.context, &outNodeId); + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "startPoll %s", UA_StatusCode_name(retval)); + */ + retval = UA_Server_run(server, &running); + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "UA_Server_run %s", UA_StatusCode_name(retval)); + /* + * If you use UA_HISTORIZINGUPDATESTRATEGY_POLL, then stop the polling. + * + retval = gathering.stopPoll(server, gathering.context, &outNodeId); + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "stopPoll %s", UA_StatusCode_name(retval)); + */ + + UA_Server_delete(server); + return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/include/open62541/client.h b/include/open62541/client.h index 08bf3fd5f46..47da9b2c5e2 100644 --- a/include/open62541/client.h +++ b/include/open62541/client.h @@ -27,6 +27,7 @@ #include #include #include +#include _UA_BEGIN_DECLS @@ -119,6 +120,9 @@ typedef struct { * up together with the * configuration. So it is possible * to allocate them on ROM. */ + /* EventLoop */ + UA_EventLoop *eventLoop; + UA_Boolean externalEventLoop; /* The EventLoop is not deleted with the config */ /* Available SecurityPolicies */ size_t securityPoliciesSize; diff --git a/include/open62541/plugin/eventloop.h b/include/open62541/plugin/eventloop.h new file mode 100644 index 00000000000..3f15a4d816c --- /dev/null +++ b/include/open62541/plugin/eventloop.h @@ -0,0 +1,302 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + * Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes) + */ + +#ifndef UA_EVENTLOOP_H_ +#define UA_EVENTLOOP_H_ + +#include +#include +#include +#include + +_UA_BEGIN_DECLS + +struct UA_EventLoop; +typedef struct UA_EventLoop UA_EventLoop; + +struct UA_EventSource; +typedef struct UA_EventSource UA_EventSource; + +/** + * Event Loop Subsystem + * ==================== + * + * An OPC UA-enabled application can have several clients and servers. And + * server can serve different transport-level protocols for OPC UA. The + * EventLoop is a central module that provides a unified control-flow for all of + * these. Hence, several applications can share an EventLoop. + * + * The EventLoop and the ConnectionManager implementation is + * architecture-specific. The goal is to have a single call to "select" (epoll, + * kqueue, ...) in the EventLoop that covers all ConnectionManagers. Hence the + * EventLoop plugin implementation must know implementation details of the + * ConnectionManager implementations. So the EventLoop can extract socket + * information, etc. from the ConnectionManagers. + * + * Event Loop + * ---------- + * The EventLoop implementation is part of the selected architecture. For + * example, "Win32/POSIX" stands for a Windows environment with an EventLoop + * that uses the POSIX API. Several EventLoops can be instantiated in parallel. + * But the globally defined functions are the same everywhere. */ + +typedef void (*UA_Callback)(void *application, void *context); + +/* To be executed in the next EventLoop cycle */ +typedef struct UA_DelayedCallback { + struct UA_DelayedCallback *next; /* Singly-linked list */ + UA_Callback callback; + void *application; + void *data; +} UA_DelayedCallback; + +typedef enum { + UA_EVENTLOOPSTATE_FRESH = 0, + UA_EVENTLOOPSTATE_STARTED, + UA_EVENTLOOPSTATE_STOPPING, /* stopping in progress, needs EventLoop + * cycles to finish */ + UA_EVENTLOOPSTATE_STOPPED +} UA_EventLoopState; + +/** + * EventLoop Lifecycle + * ~~~~~~~~~~~~~~~~~~~ */ + +UA_EXPORT UA_EventLoop * +UA_EventLoop_new(const UA_Logger *logger); + +/* Clean up the EventLoop and free allocated memory. Can fail if the EventLoop + * is not stopped. */ +UA_EXPORT UA_StatusCode +UA_EventLoop_delete(UA_EventLoop *el); + +UA_EXPORT UA_EventLoopState +UA_EventLoop_getState(UA_EventLoop *el); + +UA_EXPORT UA_StatusCode +UA_EventLoop_start(UA_EventLoop *el); + +/* Stop all EventSources. This is asynchronous and might need a few + * iterations of the main-loop to succeed. */ +UA_EXPORT void +UA_EventLoop_stop(UA_EventLoop *el); + +/* Process events for at most "timeout" ms or until an unrecoverable error + * occurs. If timeout==0, then only already received events are processed. */ +UA_EXPORT UA_StatusCode +UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout); + +/* Time of the next cyclic callback. Returns the max DateTime if no cyclic + * callback is registered. */ +UA_EXPORT UA_DateTime +UA_EventLoop_nextCyclicTime(UA_EventLoop *el); + +/** + * Cyclic and Delayed Callbacks + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Cyclic callbacks are executed regularly with an interval. A delayed callback + * is executed in the next cycle of the EventLoop. The memory for the delayed + * callback is freed after the execution. */ + +/* The execution interval is in ms. Returns the callbackId if the pointer is + * non-NULL. */ +UA_EXPORT UA_StatusCode +UA_EventLoop_addCyclicCallback(UA_EventLoop *el, UA_Callback cb, + void *application, void *data, UA_Double interval_ms, + UA_DateTime *baseTime, UA_TimerPolicy timerPolicy, + UA_UInt64 *callbackId); + +UA_EXPORT UA_StatusCode +UA_EventLoop_addTimedCallback(UA_EventLoop *el, UA_Callback callback, + void *application, void *data, UA_DateTime date, + UA_UInt64 *callbackId); +UA_EXPORT UA_StatusCode +UA_EventLoop_modifyCyclicCallback(UA_EventLoop *el, UA_UInt64 callbackId, + UA_Double interval_ms, UA_DateTime *baseTime, + UA_TimerPolicy timerPolicy); + +UA_EXPORT void +UA_EventLoop_removeCyclicCallback(UA_EventLoop *el, UA_UInt64 callbackId); + +UA_EXPORT void +UA_EventLoop_addDelayedCallback(UA_EventLoop *el, UA_DelayedCallback *dc); + +/* Helper Functions */ +UA_EXPORT const UA_Logger * +UA_EventLoop_getLogger(UA_EventLoop *el); + +UA_EXPORT void +UA_EventLoop_setLogger(UA_EventLoop *el, const UA_Logger *logger); + +/** + * Event Source + * ------------ */ + +typedef enum { + UA_EVENTSOURCESTATE_FRESH = 0, + UA_EVENTSOURCESTATE_STOPPED, /* Registered but stopped */ + UA_EVENTSOURCESTATE_STARTING, + UA_EVENTSOURCESTATE_STARTED, + UA_EVENTSOURCESTATE_STOPPING /* Stopping in progress, needs + * EventLoop cycles to finish */ +} UA_EventSourceState; + +struct UA_EventSource { + struct UA_EventSource *next; /* Singly-linked list for use by the + * application that registered the ES */ + + /* Configuration + * ~~~~~~~~~~~~~ */ + UA_String name; /* Unique name of the ES for logging */ + void *application; /* Application to which the ES belongs */ + size_t paramsSize; + UA_KeyValuePair *params; /* Configuration parameters */ + + /* Lifecycle + * ~~~~~~~~~ */ + UA_EventSourceState state; + UA_EventLoop *eventLoop; /* EventLoop where the ES is registered */ + UA_StatusCode (*start)(UA_EventSource *es); + void (*stop)(UA_EventSource *es); /* Asynchronous. Iterate theven EventLoop + * until the EventSource is stopped. */ + UA_StatusCode (*free)(UA_EventSource *es); +}; + +/* Register the ES. Immediately starts the ES if the EventLoop is already + * started. Otherwise the ES is started together with the EventLoop. */ +UA_EXPORT UA_StatusCode +UA_EventLoop_registerEventSource(UA_EventLoop *el, + UA_EventSource *es); + +/* If still registered, call _stop (but not _clear) on the CM and deregister. */ +UA_EXPORT UA_StatusCode +UA_EventLoop_deregisterEventSource(UA_EventLoop *el, + UA_EventSource *es); + +/** + * Connection Manager + * ------------------ + * Every Connection is created by a ConnectionManager. Every ConnectionManager + * belongs to just one application. A ConnectionManager can act purely as a + * passive "Factory" for Connections. But it can also be stateful. For example, + * it can keep a session to an MQTT broker open which is used by individual + * connections that are each bound to an MQTT topic. */ + +struct UA_ConnectionManager; +typedef struct UA_ConnectionManager UA_ConnectionManager; + +/** + * The ConnectionCallback is the only interface from the connection back to the + * application. The connectionId is announced to the application when it is + * first used for the callback. The context is a double-pointer so the context + * can be overwritten by the application */ +typedef void +(*UA_ConnectionCallback)(UA_ConnectionManager *cm, uintptr_t connectionId, + void **connectionContext, UA_StatusCode status, + UA_ByteString msg); + +struct UA_ConnectionManager { + /* Every ConnectionManager is treated like an EventSource from the + * perspective of the EventLoop. */ + UA_EventSource eventSource; + + /* Passively listen for new connections + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Some ConnectionManagers passively listen to open new Connections. The + * configuration parameters stored in the EventSource are used during + * "start" of the EventSource to set this up. The "connectionCallback" + * callback is used to indicate that a new connection has been are created + * (status==Good, msg=empty). + * + * The callback depends on the application and has to be manually + * configured. */ + UA_ConnectionCallback connectionCallback; + void *initialConnectionContext; + + /* Actively Open a Connection + * ~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Some ConnectionManagers can actively open a new Connection. Connecting is + * asynchronous. cm->connectionCallback is called when the connection is + * open (status=GOOD) or aborted (status!=GOOD) when connecting failed. + * + * The parameters describe the connection. For example hostname and port + * (for TCP). Other protocols (e.g. MQTT, AMQP, etc.) may required + * additional arguments to open a connection. + * + * The connection is opened asynchronously. The ConnectionCallback is + * triggered when the connection is fully opened (UA_STATUSCODE_GOOD) or has + * failed (with an error code). */ + UA_StatusCode + (*openConnection)(UA_ConnectionManager *cm, + size_t paramsSize, UA_KeyValuePair *params, + void *context); + + /* Connection Activities + * ~~~~~~~~~~~~~~~~~~~~~ + * The following are activities to be performed on an open connection. + * + * Each ConnectionManager allocates and frees his own memory for the network + * buffers. This enables, for example, zero-copy neworking mechanisms. The + * connectionId is part of the API to enable cases where memory is + * statically allocated for every connection */ + UA_StatusCode + (*allocNetworkBuffer)(UA_ConnectionManager *cm, uintptr_t connectionId, + UA_ByteString *buf, size_t bufSize); + void + (*freeNetworkBuffer)(UA_ConnectionManager *cm, uintptr_t connectionId, + UA_ByteString *buf); + + /* Send a message. Sending is asynchronous. That is, the function returns + * before the message is ACKed from remote. The memory for the buffer is + * expected to be allocated with allocNetworkBuffer and is released + * internally (also if sending fails). + * + * Some ConnectionManagers can accept additional parameters for sending. For + * example a tx-time for sending in time-synchronized TSN settings. */ + UA_StatusCode + (*sendWithConnection)(UA_ConnectionManager *cm, uintptr_t connectionId, + size_t paramsSize, UA_KeyValuePair *params, + UA_ByteString *buf); + + /* When a connection is closed, cm->connectionCallback is called with + * (status=BadConnectionClosed, msg=empty). Then the connection is cleared + * up inside the ConnectionManager. This is the case both for connections + * that are actively closed and those that are closed remotely. The return + * code is non-good only if the connection is already closed. */ + UA_StatusCode + (*closeConnection)(UA_ConnectionManager *cm, uintptr_t connectionId); +}; + +/** + * TCP Connection Manager + * ~~~~~~~~~~~~~~~~~~~~~~ + * Listens on the network and manages TCP connections. The configuration + * parameters have to set before calling _start to take effect. + * + * Configuration Parameters: + * - 0:listen-port [uint16]: Port to listen for new connections (default: do not + * listen on any port). + * - 0:listen-hostnames [string | string array]: Hostnames of the devices to + * listen on (default: listen on + * all devices). + * - 0:recv-bufsize [uint16]: Size of the buffer that is allocated for receiving + * messages (default 16kB). + * + * Open Connection Parameters: + * - 0:target-hostname [string]: Hostname (or IPv4/IPv6 address) of the target + * (required). + * - 0:target-port [uint16]: Port of the target host (required). + * + * Send Parameters: + * No additional parameters for sending over an established TCP socket defined. */ +UA_EXPORT UA_ConnectionManager * +UA_ConnectionManager_TCP_new(const UA_String eventSourceName); + +_UA_END_DECLS + +#endif /* UA_EVENTLOOP_H_ */ diff --git a/include/open62541/plugin/log.h b/include/open62541/plugin/log.h index e8a9bbc06e9..aa09ecc3bf8 100644 --- a/include/open62541/plugin/log.h +++ b/include/open62541/plugin/log.h @@ -37,6 +37,8 @@ typedef enum { UA_LOGLEVEL_FATAL } UA_LogLevel; +#define UA_LOGCATEGORIES 8 + typedef enum { UA_LOGCATEGORY_NETWORK = 0, UA_LOGCATEGORY_SECURECHANNEL, @@ -44,7 +46,8 @@ typedef enum { UA_LOGCATEGORY_SERVER, UA_LOGCATEGORY_CLIENT, UA_LOGCATEGORY_USERLAND, - UA_LOGCATEGORY_SECURITYPOLICY + UA_LOGCATEGORY_SECURITYPOLICY, + UA_LOGCATEGORY_EVENTLOOP } UA_LogCategory; typedef struct { diff --git a/include/open62541/plugin/nodestore.h b/include/open62541/plugin/nodestore.h index cd0524ec0d3..bcdd1b58d5a 100644 --- a/include/open62541/plugin/nodestore.h +++ b/include/open62541/plugin/nodestore.h @@ -209,18 +209,18 @@ typedef struct { /* The maximum number of ReferrenceTypes. Must be a multiple of 32. */ #define UA_REFERENCETYPESET_MAX 128 -typedef struct { UA_UInt32 bits[UA_REFERENCETYPESET_MAX / 32]; } UA_ReferenceTypeSet; +typedef struct { + UA_UInt32 bits[UA_REFERENCETYPESET_MAX / 32]; +} UA_ReferenceTypeSet; + +UA_EXPORT extern const UA_ReferenceTypeSet UA_REFERENCETYPESET_NONE; +UA_EXPORT extern const UA_ReferenceTypeSet UA_REFERENCETYPESET_ALL; static UA_INLINE void UA_ReferenceTypeSet_init(UA_ReferenceTypeSet *set) { memset(set, 0, sizeof(UA_ReferenceTypeSet)); } -static UA_INLINE void -UA_ReferenceTypeSet_any(UA_ReferenceTypeSet *set) { - memset(set, -1, sizeof(UA_ReferenceTypeSet)); -} - static UA_INLINE UA_ReferenceTypeSet UA_REFTYPESET(UA_Byte index) { UA_Byte i = index / 32, j = index % 32; @@ -959,10 +959,35 @@ typedef struct { void (*deleteNode)(void *nsCtx, UA_Node *node); - /* ``Get`` returns a pointer to an immutable node. ``Release`` indicates - * that the pointer is no longer accessed afterwards. */ - const UA_Node * (*getNode)(void *nsCtx, const UA_NodeId *nodeId); - + /* ``Get`` returns a pointer to an immutable node. Call ``releaseNode`` to + * indicate when the pointer is no longer accessed. + * + * It can be indicated if only a subset of the attributes and referencs need + * to be accessed. That is relevant when the nodestore accesses a slow + * storage backend for the attributes. The attribute mask is a bitfield with + * ORed entries from UA_NodeAttributesMask. + * + * The returned node always contains the context-pointer and other fields + * specific to open626541 (not official attributes). + * + * The NodeStore does not complain if attributes and references that don't + * exist (for that node) are requested. Attributes and references in + * addition to those specified can be returned. For example, if the full + * node already is kept in memory by the Nodestore. */ + const UA_Node * (*getNode)(void *nsCtx, const UA_NodeId *nodeId, + UA_UInt32 attributeMask, + UA_ReferenceTypeSet references, + UA_BrowseDirection referenceDirections); + + /* Similar to the normal ``getNode``. But it can take advantage of the + * NodePointer structure, e.g. if it contains a direct pointer. */ + const UA_Node * (*getNodeFromPtr)(void *nsCtx, UA_NodePointer ptr, + UA_UInt32 attributeMask, + UA_ReferenceTypeSet references, + UA_BrowseDirection referenceDirections); + + /* Release a node that has been retrieved with ``getNode`` or + * ``getNodeFromPtr``. */ void (*releaseNode)(void *nsCtx, const UA_Node *node); /* Returns an editable copy of a node (needs to be deleted with the diff --git a/include/open62541/server.h b/include/open62541/server.h index 1513b5ba066..d20008ed3ac 100644 --- a/include/open62541/server.h +++ b/include/open62541/server.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -119,6 +120,10 @@ struct UA_ServerConfig { * with custom data types are provided in * ``/examples/custom_datatype/``. */ + /* EventLoop */ + UA_EventLoop *eventLoop; + UA_Boolean externalEventLoop; /* The EventLoop is not deleted with the config */ + /* Networking */ size_t networkLayersSize; UA_ServerNetworkLayer *networkLayers; @@ -424,28 +429,29 @@ UA_Server_closeSession(UA_Server *server, const UA_NodeId *sessionId); UA_EXPORT UA_StatusCode UA_THREADSAFE UA_Server_setSessionParameter(UA_Server *server, const UA_NodeId *sessionId, - const char *name, const UA_Variant *parameter); + const UA_QualifiedName key, + const UA_Variant *value); UA_EXPORT void UA_THREADSAFE UA_Server_deleteSessionParameter(UA_Server *server, const UA_NodeId *sessionId, - const char *name); + const UA_QualifiedName key); /* Returns NULL if the session or the parameter are not defined. Returns a deep * copy otherwise */ UA_EXPORT UA_StatusCode UA_THREADSAFE UA_Server_getSessionParameter(UA_Server *server, const UA_NodeId *sessionId, - const char *name, UA_Variant *outParameter); - -/* Returns NULL if the parameter is not defined or not of the right datatype */ -UA_EXPORT UA_StatusCode UA_THREADSAFE -UA_Server_getSessionScalarParameter(UA_Server *server, const UA_NodeId *sessionId, - const char *name, const UA_DataType *type, - UA_Variant *outParameter); + const UA_QualifiedName key, + UA_Variant *outValue); +/* Returns NULL if the parameter is not defined or not a scalar or not of the + * right datatype. Otherwise a deep copy of the scalar value is filled at the + * target location of the void pointer. */ UA_EXPORT UA_StatusCode UA_THREADSAFE -UA_Server_getSessionArrayParameter(UA_Server *server, const UA_NodeId *sessionId, - const char *name, const UA_DataType *type, - UA_Variant *outParameter); +UA_Server_getSessionParameter_scalar(UA_Server *server, + const UA_NodeId *sessionId, + const UA_QualifiedName key, + const UA_DataType *type, + void *outValue); /** * Reading and Writing Node Attributes diff --git a/include/open62541/types.h b/include/open62541/types.h index d8cdf685a2f..2e96882b9b0 100644 --- a/include/open62541/types.h +++ b/include/open62541/types.h @@ -1106,7 +1106,10 @@ void UA_EXPORT UA_delete(void *p, const UA_DataType *type); * * @param p The memory location of the variable * @param type The datatype description of the variable - * @param output A string that is memory-allocated for the pretty-printed output + * @param output A string that is used for the pretty-printed output. If the + * memory for string is already allocated, we try to use the existing + * string (the length is adjusted). If the string is empty, memory + * is allocated for it. * @return Indicates whether the operation succeeded*/ #ifdef UA_ENABLE_TYPEDESCRIPTION UA_StatusCode UA_EXPORT diff --git a/include/open62541/util.h b/include/open62541/util.h index 79e49f20c36..0409d08a5fa 100644 --- a/include/open62541/util.h +++ b/include/open62541/util.h @@ -47,44 +47,26 @@ typedef enum { * invalidates pointers into the previous array. If the key exists already, the * value is overwritten. */ UA_EXPORT UA_StatusCode -UA_KeyValueMap_setQualified(UA_KeyValuePair **map, size_t *mapSize, - const UA_QualifiedName *key, - const UA_Variant *value); - -/* Simplified version that assumes the key is in namespace 0 */ -UA_EXPORT UA_StatusCode UA_KeyValueMap_set(UA_KeyValuePair **map, size_t *mapSize, - const char *key, const UA_Variant *value); + const UA_QualifiedName key, + const UA_Variant *value); -/* Returns a pointer into underlying array or NULL if the key is not found.*/ -UA_EXPORT const UA_Variant * -UA_KeyValueMap_getQualified(UA_KeyValuePair *map, size_t mapSize, - const UA_QualifiedName *key); - -/* Simplified version that assumes the key is in namespace 0 */ +/* Returns a pointer to the value or NULL if the key is not found.*/ UA_EXPORT const UA_Variant * UA_KeyValueMap_get(UA_KeyValuePair *map, size_t mapSize, - const char *key); + const UA_QualifiedName key); /* Returns NULL if the value for the key is not defined or not of the right * datatype and scalar/array */ -UA_EXPORT const UA_Variant * +UA_EXPORT const void * UA_KeyValueMap_getScalar(UA_KeyValuePair *map, size_t mapSize, - const char *key, const UA_DataType *type); - -UA_EXPORT const UA_Variant * -UA_KeyValueMap_getArray(UA_KeyValuePair *map, size_t mapSize, - const char *key, const UA_DataType *type); + const UA_QualifiedName key, + const UA_DataType *type); /* Remove a single entry. To delete the entire map, use UA_Array_delete. */ UA_EXPORT void -UA_KeyValueMap_deleteQualified(UA_KeyValuePair **map, size_t *mapSize, - const UA_QualifiedName *key); - -/* Simplified version that assumes the key is in namespace 0 */ -UA_EXPORT void UA_KeyValueMap_delete(UA_KeyValuePair **map, size_t *mapSize, - const char *key); + const UA_QualifiedName key); /** * Endpoint URL Parser diff --git a/plugins/historydata/ua_history_data_backend_memory.c b/plugins/historydata/ua_history_data_backend_memory.c index 74421638699..3e99794ec33 100644 --- a/plugins/historydata/ua_history_data_backend_memory.c +++ b/plugins/historydata/ua_history_data_backend_memory.c @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2018 (c) basysKom GmbH (Author: Peter Rustler) + * Copyright 2021 (c) luibass92 (Author: Luigi Bassetta) */ #include @@ -26,6 +27,8 @@ typedef struct { UA_DataValueMemoryStoreItem **dataStore; size_t storeEnd; size_t storeSize; + /* New field useful for circular buffer management */ + size_t lastInserted; } UA_NodeIdStoreContextItem_backend_memory; static void @@ -603,3 +606,261 @@ UA_HistoryDataBackend_Memory_clear(UA_HistoryDataBackend *backend) UA_MemoryStoreContext_delete(ctx); memset(backend, 0, sizeof(UA_HistoryDataBackend)); } + +/* Circular buffer implementation */ + +static UA_NodeIdStoreContextItem_backend_memory * +getNewNodeIdContext_backend_memory_Circular(UA_MemoryStoreContext *context, + UA_Server *server, + const UA_NodeId *nodeId) { + UA_MemoryStoreContext *ctx = (UA_MemoryStoreContext *)context; + if(ctx->storeEnd >= ctx->storeSize) { + return NULL; + } + UA_NodeIdStoreContextItem_backend_memory *item = &ctx->dataStore[ctx->storeEnd]; + UA_NodeId_copy(nodeId, &item->nodeId); + UA_DataValueMemoryStoreItem **store = (UA_DataValueMemoryStoreItem **)UA_calloc(ctx->initialStoreSize, sizeof(UA_DataValueMemoryStoreItem *)); + if(!store) { + UA_NodeIdStoreContextItem_clear(item); + return NULL; + } + item->dataStore = store; + item->storeSize = ctx->initialStoreSize; + item->storeEnd = 0; + ++ctx->storeEnd; + return item; +} + +static UA_NodeIdStoreContextItem_backend_memory * +getNodeIdStoreContextItem_backend_memory_Circular(UA_MemoryStoreContext *context, + UA_Server *server, + const UA_NodeId *nodeId) { + for(size_t i = 0; i < context->storeEnd; ++i) { + if(UA_NodeId_equal(nodeId, &context->dataStore[i].nodeId)) { + return &context->dataStore[i]; + } + } + return getNewNodeIdContext_backend_memory_Circular(context, server, nodeId); +} + +static UA_StatusCode +serverSetHistoryData_backend_memory_Circular(UA_Server *server, + void *context, + const UA_NodeId *sessionId, + void *sessionContext, + const UA_NodeId *nodeId, + UA_Boolean historizing, + const UA_DataValue *value) { + UA_NodeIdStoreContextItem_backend_memory *item = getNodeIdStoreContextItem_backend_memory_Circular((UA_MemoryStoreContext *)context, server, nodeId); + if(item == NULL) { + return UA_STATUSCODE_BADOUTOFMEMORY; + } + if(item->lastInserted >= item->storeSize) { + /* If the buffer size is overcomed, push new elements from the start of the buffer */ + item->lastInserted = 0; + } + UA_DateTime timestamp = 0; + if(value->hasSourceTimestamp) { + timestamp = value->sourceTimestamp; + } else if(value->hasServerTimestamp) { + timestamp = value->serverTimestamp; + } else { + timestamp = UA_DateTime_now(); + } + UA_DataValueMemoryStoreItem *newItem = (UA_DataValueMemoryStoreItem *)UA_calloc(1, sizeof(UA_DataValueMemoryStoreItem)); + newItem->timestamp = timestamp; + UA_DataValue_copy(value, &newItem->value); + + /* This implementation does NOT sort values by timestamp */ + + if(item->dataStore[item->lastInserted] != NULL) { + UA_DataValueMemoryStoreItem_clear(item->dataStore[item->lastInserted]); + UA_free(item->dataStore[item->lastInserted]); + } + item->dataStore[item->lastInserted] = newItem; + ++item->lastInserted; + + if(item->storeEnd < item->storeSize) { + ++item->storeEnd; + } + + return UA_STATUSCODE_GOOD; +} + +static size_t +getResultSize_service_Circular(const UA_HistoryDataBackend *backend, UA_Server *server, + const UA_NodeId *sessionId, void *sessionContext, + const UA_NodeId *nodeId, UA_DateTime start, + UA_DateTime end, UA_UInt32 numValuesPerNode, + UA_Boolean returnBounds, size_t *startIndex, + size_t *endIndex, UA_Boolean *addFirst, + UA_Boolean *addLast, UA_Boolean *reverse) { + *startIndex = 0; + *endIndex = backend->lastIndex(server, backend->context, sessionId, sessionContext, nodeId); + *addFirst = false; + *addLast = false; + if(end == LLONG_MIN) { + *reverse = false; + } else if(start == LLONG_MIN) { + *reverse = true; + } else { + *reverse = end < start; + } + + size_t size = 0; + const UA_NodeIdStoreContextItem_backend_memory *item = getNodeIdStoreContextItem_backend_memory_Circular((UA_MemoryStoreContext *)backend->context, server, nodeId); + if(item == NULL) { + size = 0; + } else { + size = item->storeEnd; + } + return size; +} + +static UA_StatusCode +getHistoryData_service_Circular(UA_Server *server, + const UA_NodeId *sessionId, + void *sessionContext, + const UA_HistoryDataBackend *backend, + const UA_DateTime start, + const UA_DateTime end, + const UA_NodeId *nodeId, + size_t maxSize, + UA_UInt32 numValuesPerNode, + UA_Boolean returnBounds, + UA_TimestampsToReturn timestampsToReturn, + UA_NumericRange range, + UA_Boolean releaseContinuationPoints, + const UA_ByteString *continuationPoint, + UA_ByteString *outContinuationPoint, + UA_HistoryData *historyData) { + size_t *resultSize = &historyData->dataValuesSize; + UA_DataValue **result = &historyData->dataValues; + size_t skip = 0; + UA_ByteString backendContinuationPoint; + UA_ByteString_init(&backendContinuationPoint); + if(continuationPoint->length > 0) { + if(continuationPoint->length < sizeof(size_t)) + return UA_STATUSCODE_BADCONTINUATIONPOINTINVALID; + skip = *((size_t *)(continuationPoint->data)); + backendContinuationPoint.length = continuationPoint->length - sizeof(size_t); + backendContinuationPoint.data = continuationPoint->data + sizeof(size_t); + } + size_t storeEnd = backend->getEnd(server, backend->context, sessionId, sessionContext, nodeId); + size_t startIndex; + size_t endIndex; + UA_Boolean addFirst; + UA_Boolean addLast; + UA_Boolean reverse; + size_t _resultSize = getResultSize_service_Circular(backend, + server, + sessionId, + sessionContext, + nodeId, + start, + end, + numValuesPerNode == 0 ? 0 : numValuesPerNode + (UA_UInt32)skip, + returnBounds, + &startIndex, + &endIndex, + &addFirst, + &addLast, + &reverse); + *resultSize = _resultSize - skip; + if(*resultSize > maxSize) { + *resultSize = maxSize; + } + UA_DataValue *outResult = (UA_DataValue *)UA_Array_new(*resultSize, &UA_TYPES[UA_TYPES_DATAVALUE]); + if(!outResult) { + *resultSize = 0; + return UA_STATUSCODE_BADOUTOFMEMORY; + } + *result = outResult; + size_t counter = 0; + if(addFirst) { + if(skip == 0) { + outResult[counter].hasStatus = true; + outResult[counter].status = UA_STATUSCODE_BADBOUNDNOTFOUND; + outResult[counter].hasSourceTimestamp = true; + if(start == LLONG_MIN) { + outResult[counter].sourceTimestamp = end; + } else { + outResult[counter].sourceTimestamp = start; + } + ++counter; + } + } + UA_ByteString backendOutContinuationPoint; + UA_ByteString_init(&backendOutContinuationPoint); + if(endIndex != storeEnd && startIndex != storeEnd) { + size_t retval = 0; + size_t valueSize = *resultSize - counter; + if(valueSize + skip > _resultSize - addFirst - addLast) { + if(skip == 0) { + valueSize = _resultSize - addFirst - addLast; + } else { + valueSize = _resultSize - skip - addLast; + } + } + UA_StatusCode ret = UA_STATUSCODE_GOOD; + if(valueSize > 0) + ret = backend->copyDataValues(server, + backend->context, + sessionId, + sessionContext, + nodeId, + startIndex, + endIndex, + reverse, + valueSize, + range, + releaseContinuationPoints, + &backendContinuationPoint, + &backendOutContinuationPoint, + &retval, + &outResult[counter]); + if(ret != UA_STATUSCODE_GOOD) { + UA_Array_delete(outResult, *resultSize, &UA_TYPES[UA_TYPES_DATAVALUE]); + *result = NULL; + *resultSize = 0; + return ret; + } + counter += retval; + } + if(addLast && counter < *resultSize) { + outResult[counter].hasStatus = true; + outResult[counter].status = UA_STATUSCODE_BADBOUNDNOTFOUND; + outResult[counter].hasSourceTimestamp = true; + if(start == LLONG_MIN && storeEnd != backend->firstIndex(server, backend->context, sessionId, sessionContext, nodeId)) { + outResult[counter].sourceTimestamp = backend->getDataValue(server, backend->context, sessionId, sessionContext, nodeId, endIndex)->sourceTimestamp - UA_DATETIME_SEC; + } else if(end == LLONG_MIN && storeEnd != backend->firstIndex(server, backend->context, sessionId, sessionContext, nodeId)) { + outResult[counter].sourceTimestamp = backend->getDataValue(server, backend->context, sessionId, sessionContext, nodeId, endIndex)->sourceTimestamp + UA_DATETIME_SEC; + } else { + outResult[counter].sourceTimestamp = end; + } + } + // there are more values + if(skip + *resultSize < _resultSize + // there are not more values for this request, but there are more values in + // database + || (backendOutContinuationPoint.length > 0 && numValuesPerNode != 0) + // we deliver just one value which is a FIRST/LAST value + || (skip == 0 && addFirst == true && *resultSize == 1)) { + if(UA_ByteString_allocBuffer(outContinuationPoint, backendOutContinuationPoint.length + sizeof(size_t)) != UA_STATUSCODE_GOOD) { + return UA_STATUSCODE_BADOUTOFMEMORY; + } + *((size_t *)(outContinuationPoint->data)) = skip + *resultSize; + if(backendOutContinuationPoint.length > 0) + memcpy(outContinuationPoint->data + sizeof(size_t), backendOutContinuationPoint.data, backendOutContinuationPoint.length); + } + UA_ByteString_clear(&backendOutContinuationPoint); + return UA_STATUSCODE_GOOD; +} + +UA_HistoryDataBackend +UA_HistoryDataBackend_Memory_Circular(size_t initialNodeIdStoreSize, size_t initialDataStoreSize) { + UA_HistoryDataBackend result = UA_HistoryDataBackend_Memory(initialNodeIdStoreSize, initialDataStoreSize); + result.serverSetHistoryData = &serverSetHistoryData_backend_memory_Circular; + result.getHistoryData = &getHistoryData_service_Circular; + return result; +} diff --git a/plugins/historydata/ua_history_data_gathering_default.c b/plugins/historydata/ua_history_data_gathering_default.c index ada881fd1ce..db55473c4a0 100644 --- a/plugins/historydata/ua_history_data_gathering_default.c +++ b/plugins/historydata/ua_history_data_gathering_default.c @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2018 (c) basysKom GmbH (Author: Peter Rustler) + * Copyright 2021 (c) luibass92 (Author: Luigi Bassetta) */ #include @@ -228,3 +229,30 @@ UA_HistoryDataGathering_Default(size_t initialNodeIdStoreSize) gathering.context = context; return gathering; } + +/* Circular buffer implementation */ + +static UA_StatusCode +registerNodeId_gathering_circular(UA_Server *server, void *context, + const UA_NodeId *nodeId, + const UA_HistorizingNodeIdSettings setting) { + UA_NodeIdStoreContext *ctx = (UA_NodeIdStoreContext *)context; + if(getNodeIdStoreContextItem_gathering_default(ctx, nodeId)) { + return UA_STATUSCODE_BADNODEIDEXISTS; + } + if(ctx->storeEnd >= ctx->storeSize || !ctx->dataStore) { + return UA_STATUSCODE_BADOUTOFMEMORY; + } + UA_NodeId_copy(nodeId, &ctx->dataStore[ctx->storeEnd].nodeId); + size_t current = ctx->storeEnd; + ctx->dataStore[current].setting = setting; + ++ctx->storeEnd; + return UA_STATUSCODE_GOOD; +} + +UA_HistoryDataGathering +UA_HistoryDataGathering_Circular(size_t initialNodeIdStoreSize) { + UA_HistoryDataGathering gathering = UA_HistoryDataGathering_Default(initialNodeIdStoreSize); + gathering.registerNodeId = ®isterNodeId_gathering_circular; + return gathering; +} diff --git a/plugins/include/open62541/plugin/historydata/history_data_backend_memory.h b/plugins/include/open62541/plugin/historydata/history_data_backend_memory.h index 7c0ec59eb57..17f1b87c9b7 100644 --- a/plugins/include/open62541/plugin/historydata/history_data_backend_memory.h +++ b/plugins/include/open62541/plugin/historydata/history_data_backend_memory.h @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2018 (c) basysKom GmbH (Author: Peter Rustler) + * Copyright 2021 (c) luibass92 (Author: Luigi Bassetta) */ #ifndef UA_HISTORYDATABACKEND_MEMORY_H_ @@ -17,6 +18,15 @@ _UA_BEGIN_DECLS UA_HistoryDataBackend UA_EXPORT UA_HistoryDataBackend_Memory(size_t initialNodeIdStoreSize, size_t initialDataStoreSize); +/* This function construct a UA_HistoryDataBackend which implements a circular buffer in memory. + * + * initialNodeIdStoreSize is the maximum number of NodeIds that will be historized. This number cannot be overcomed. + * initialDataStoreSize is the maximum number of UA_DataValueMemoryStoreItem that will be saved in the circular buffer for a particular NodeId. + * Subsequent UA_DataValueMemoryStoreItem will be saved replacing the oldest ones following the logic of circular buffers. + */ +UA_HistoryDataBackend UA_EXPORT +UA_HistoryDataBackend_Memory_Circular(size_t initialNodeIdStoreSize, size_t initialDataStoreSize); + void UA_EXPORT UA_HistoryDataBackend_Memory_clear(UA_HistoryDataBackend *backend); diff --git a/plugins/include/open62541/plugin/historydata/history_data_gathering_default.h b/plugins/include/open62541/plugin/historydata/history_data_gathering_default.h index cfd039e4bf9..7985bc763f2 100644 --- a/plugins/include/open62541/plugin/historydata/history_data_gathering_default.h +++ b/plugins/include/open62541/plugin/historydata/history_data_gathering_default.h @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2018 (c) basysKom GmbH (Author: Peter Rustler) + * Copyright 2021 (c) luibass92 (Author: Luigi Bassetta) */ #ifndef UA_HISTORYDATAGATHERING_DEFAULT_H_ @@ -15,6 +16,13 @@ _UA_BEGIN_DECLS UA_HistoryDataGathering UA_EXPORT UA_HistoryDataGathering_Default(size_t initialNodeIdStoreSize); +/* This function construct a UA_HistoryDataGathering which implements a circular buffer in memory. + * + * initialNodeIdStoreSize is the maximum number of NodeIds for which the data will be gathered. This number cannot be overcomed. + */ +UA_HistoryDataGathering UA_EXPORT +UA_HistoryDataGathering_Circular(size_t initialNodeIdStoreSize); + _UA_END_DECLS #endif /* UA_HISTORYDATAGATHERING_DEFAULT_H_ */ diff --git a/plugins/ua_config_default.c b/plugins/ua_config_default.c index 6e4ed55a30a..ab0c66d9340 100644 --- a/plugins/ua_config_default.c +++ b/plugins/ua_config_default.c @@ -41,15 +41,17 @@ UA_DURATIONRANGE(UA_Duration min, UA_Duration max) { return range; } +static UA_StatusCode +setDefaultConfig(UA_ServerConfig *conf); + UA_Server * UA_Server_new() { UA_ServerConfig config; memset(&config, 0, sizeof(UA_ServerConfig)); - /* Set a default logger and NodeStore for the initialization */ - config.logger = UA_Log_Stdout_; - if(UA_STATUSCODE_GOOD != UA_Nodestore_HashMap(&config.nodestore)) { + + UA_StatusCode res = setDefaultConfig(&config); + if(res != UA_STATUSCODE_GOOD) return NULL; - } return UA_Server_newWithConfig(&config); } @@ -125,14 +127,22 @@ setDefaultConfig(UA_ServerConfig *conf) { if(!conf) return UA_STATUSCODE_BADINVALIDARGUMENT; + /* NodeStore */ if(conf->nodestore.context == NULL) UA_Nodestore_HashMap(&conf->nodestore); - /* --> Start setting the default static config <-- */ - /* Allow user to set his own logger */ + /* Logging */ if(!conf->logger.log) conf->logger = UA_Log_Stdout_; + /* EventLoop */ + if(conf->eventLoop == NULL) { + conf->eventLoop = UA_EventLoop_new(&conf->logger); + conf->externalEventLoop = false; + } + + /* --> Start setting the default static config <-- */ + conf->shutdownDelay = 0.0; /* Server Description */ @@ -752,6 +762,12 @@ UA_ClientConfig_setDefault(UA_ClientConfig *config) { config->logger.clear = UA_Log_Stdout_clear; } + /* EventLoop */ + if(config->eventLoop == NULL) { + config->eventLoop = UA_EventLoop_new(&config->logger); + config->externalEventLoop = false; + } + if (config->sessionLocaleIdsSize > 0 && config->sessionLocaleIds) { UA_Array_delete(config->sessionLocaleIds, config->sessionLocaleIdsSize, &UA_TYPES[UA_TYPES_LOCALEID]); } diff --git a/plugins/ua_log_stdout.c b/plugins/ua_log_stdout.c index ce0630bf5be..109fa1e7a69 100644 --- a/plugins/ua_log_stdout.c +++ b/plugins/ua_log_stdout.c @@ -41,8 +41,9 @@ const char *logLevelNames[6] = {"trace", "debug", ANSI_COLOR_YELLOW "warn", ANSI_COLOR_RED "error", ANSI_COLOR_MAGENTA "fatal"}; -const char *logCategoryNames[7] = {"network", "channel", "session", "server", - "client", "userland", "securitypolicy"}; +const char *logCategoryNames[UA_LOGCATEGORIES] = + {"network", "channel", "session", "server", + "client", "userland", "securitypolicy", "eventloop"}; #ifdef __clang__ __attribute__((__format__(__printf__, 4 , 0))) diff --git a/plugins/ua_log_syslog.c b/plugins/ua_log_syslog.c index 31264aeb26f..dcbae585840 100644 --- a/plugins/ua_log_syslog.c +++ b/plugins/ua_log_syslog.c @@ -13,8 +13,9 @@ const char *syslogLevelNames[6] = {"trace", "debug", "info", "warn", "error", "fatal"}; -const char *syslogCategoryNames[7] = {"network", "channel", "session", "server", - "client", "userland", "securitypolicy"}; +const char *syslogCategoryNames[UA_LOGCATEGORIES] = + {"network", "channel", "session", "server", + "client", "userland", "securitypolicy", "eventloop"}; #ifdef __clang__ __attribute__((__format__(__printf__, 4 , 0))) diff --git a/plugins/ua_nodestore_hashmap.c b/plugins/ua_nodestore_hashmap.c index 2d3c30846ee..002eda2dd6b 100644 --- a/plugins/ua_nodestore_hashmap.c +++ b/plugins/ua_nodestore_hashmap.c @@ -254,7 +254,10 @@ UA_NodeMap_deleteNode(void *context, UA_Node *node) { } static const UA_Node * -UA_NodeMap_getNode(void *context, const UA_NodeId *nodeid) { +UA_NodeMap_getNode(void *context, const UA_NodeId *nodeid, + UA_UInt32 attributeMask, + UA_ReferenceTypeSet references, + UA_BrowseDirection referenceDirections) { UA_NodeMap *ns = (UA_NodeMap*)context; UA_NodeMapSlot *slot = findOccupiedSlot(ns, nodeid); if(!slot) @@ -263,6 +266,17 @@ UA_NodeMap_getNode(void *context, const UA_NodeId *nodeid) { return &slot->entry->node; } +static const UA_Node * +UA_NodeMap_getNodeFromPtr(void *context, UA_NodePointer ptr, + UA_UInt32 attributeMask, + UA_ReferenceTypeSet references, + UA_BrowseDirection referenceDirections) { + if(!UA_NodePointer_isLocal(ptr)) + return NULL; + UA_NodeId id = UA_NodePointer_toNodeId(ptr); + return UA_NodeMap_getNode(context, &id, attributeMask, references, referenceDirections); +} + static void UA_NodeMap_releaseNode(void *context, const UA_Node *node) { if (!node) @@ -513,6 +527,7 @@ UA_Nodestore_HashMap(UA_Nodestore *ns) { ns->newNode = UA_NodeMap_newNode; ns->deleteNode = UA_NodeMap_deleteNode; ns->getNode = UA_NodeMap_getNode; + ns->getNodeFromPtr = UA_NodeMap_getNodeFromPtr; ns->releaseNode = UA_NodeMap_releaseNode; ns->getNodeCopy = UA_NodeMap_getNodeCopy; ns->insertNode = UA_NodeMap_insertNode; diff --git a/plugins/ua_nodestore_ziptree.c b/plugins/ua_nodestore_ziptree.c index c4a1d5895f7..88fb811574e 100644 --- a/plugins/ua_nodestore_ziptree.c +++ b/plugins/ua_nodestore_ziptree.c @@ -139,7 +139,10 @@ zipNsDeleteNode(void *nsCtx, UA_Node *node) { } static const UA_Node * -zipNsGetNode(void *nsCtx, const UA_NodeId *nodeId) { +zipNsGetNode(void *nsCtx, const UA_NodeId *nodeId, + UA_UInt32 attributeMask, + UA_ReferenceTypeSet references, + UA_BrowseDirection referenceDirections) { ZipContext *ns = (ZipContext*)nsCtx; NodeEntry dummy; dummy.nodeIdHash = UA_NodeId_hash(nodeId); @@ -151,6 +154,18 @@ zipNsGetNode(void *nsCtx, const UA_NodeId *nodeId) { return (const UA_Node*)&entry->nodeId; } +static const UA_Node * +zipNsGetNodeFromPtr(void *nsCtx, UA_NodePointer ptr, + UA_UInt32 attributeMask, + UA_ReferenceTypeSet references, + UA_BrowseDirection referenceDirections) { + if(!UA_NodePointer_isLocal(ptr)) + return NULL; + UA_NodeId id = UA_NodePointer_toNodeId(ptr); + return zipNsGetNode(nsCtx, &id, attributeMask, + references, referenceDirections); +} + static void zipNsReleaseNode(void *nsCtx, const UA_Node *node) { if(!node) @@ -163,9 +178,12 @@ zipNsReleaseNode(void *nsCtx, const UA_Node *node) { static UA_StatusCode zipNsGetNodeCopy(void *nsCtx, const UA_NodeId *nodeId, - UA_Node **outNode) { - /* Find the node */ - const UA_Node *node = zipNsGetNode(nsCtx, nodeId); + UA_Node **outNode) { + /* Get the node (with all attributes and references, the mask and refs are + currently noy evaluated within the plugin.) */ + const UA_Node *node = + zipNsGetNode(nsCtx, nodeId, UA_NODEATTRIBUTESMASK_ALL, + UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH); if(!node) return UA_STATUSCODE_BADNODEIDUNKNOWN; @@ -261,8 +279,10 @@ zipNsInsertNode(void *nsCtx, UA_Node *node, UA_NodeId *addedNodeId) { static UA_StatusCode zipNsReplaceNode(void *nsCtx, UA_Node *node) { - /* Find the node */ - const UA_Node *oldNode = zipNsGetNode(nsCtx, &node->head.nodeId); + /* Find the node (the mask and refs are not evaluated yet by the plugin)*/ + const UA_Node *oldNode = + zipNsGetNode(nsCtx, &node->head.nodeId, UA_NODEATTRIBUTESMASK_ALL, + UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH); if(!oldNode) { deleteEntry(container_of(node, NodeEntry, nodeId)); return UA_STATUSCODE_BADNODEIDUNKNOWN; @@ -372,6 +392,7 @@ UA_Nodestore_ZipTree(UA_Nodestore *ns) { ns->newNode = zipNsNewNode; ns->deleteNode = zipNsDeleteNode; ns->getNode = zipNsGetNode; + ns->getNodeFromPtr = zipNsGetNodeFromPtr; ns->releaseNode = zipNsReleaseNode; ns->getNodeCopy = zipNsGetNodeCopy; ns->insertNode = zipNsInsertNode; diff --git a/plugins/ua_pubsub_ethernet.c b/plugins/ua_pubsub_ethernet.c index 0f1889550d3..8032ca6a64c 100644 --- a/plugins/ua_pubsub_ethernet.c +++ b/plugins/ua_pubsub_ethernet.c @@ -1243,7 +1243,7 @@ UA_PubSubChannelEthernet_receive(UA_PubSubChannel *channel, * VLAN header size is stripped before it is recieved * so the packet length is less than 60bytes */ - messageLength = messageLength + ((size_t)dataLen - sizeof(struct ether_header)); + messageLength = ((size_t)dataLen - sizeof(struct ether_header)); buffer.length = messageLength; retval = receiveCallback(channel, receiveCallbackContext, &buffer); diff --git a/plugins/ua_pubsub_udp.c b/plugins/ua_pubsub_udp.c index 80161f84777..b42968cd6c2 100644 --- a/plugins/ua_pubsub_udp.c +++ b/plugins/ua_pubsub_udp.c @@ -631,6 +631,7 @@ UA_PubSubChannelUDPMC_receive(UA_PubSubChannel *channel, UA_DateTime newTimeoutValue = remainingTimeoutValue - receiveDuration; timeoutValue.tv_sec = (long int)(newTimeoutValue / UA_DATETIME_SEC); timeoutValue.tv_usec = (long int)((newTimeoutValue % UA_DATETIME_SEC) * 100); + } while(true); /* TODO:Need to handle for jumbo frames*/ /* 1518 bytes is the maximum size of ethernet packet * where 18 bytes used for header size, 28 bytes of header diff --git a/src/client/ua_client.c b/src/client/ua_client.c index d9b36535027..ffe2fc2e61d 100644 --- a/src/client/ua_client.c +++ b/src/client/ua_client.c @@ -16,6 +16,7 @@ * Copyright 2017 (c) Mark Giraud, Fraunhofer IOSB * Copyright 2018 (c) Kalycito Infotech Private Limited * Copyright 2020 (c) Christian von Arnim, ISW University of Stuttgart + * Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes) */ #include @@ -32,7 +33,6 @@ static void UA_Client_init(UA_Client* client) { UA_SecureChannel_init(&client->channel, &client->config.localConnectionConfig); client->connectStatus = UA_STATUSCODE_GOOD; - UA_Timer_init(&client->timer); notifyClientState(client); } @@ -70,6 +70,19 @@ UA_ClientConfig_clear(UA_ClientConfig *config) { UA_free(config->securityPolicies); config->securityPolicies = 0; + /* Stop and delete the EventLoop */ + if(config->eventLoop && !config->externalEventLoop) { + if(UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_FRESH && + UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_STOPPED) { + UA_EventLoop_stop(config->eventLoop); + while(UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_STOPPED) { + UA_EventLoop_run(config->eventLoop, 100); + } + } + UA_EventLoop_delete(config->eventLoop); + config->eventLoop = NULL; + } + /* Logger */ if(config->logger.clear) config->logger.clear(config->logger.context); @@ -99,8 +112,6 @@ UA_Client_clear(UA_Client *client) { UA_Client_Subscriptions_clean(client); #endif - /* Delete the timed work */ - UA_Timer_clear(&client->timer); } void @@ -592,27 +603,29 @@ UA_Client_sendAsyncRequest(UA_Client *client, const void *request, UA_StatusCode UA_EXPORT UA_Client_addTimedCallback(UA_Client *client, UA_ClientCallback callback, void *data, UA_DateTime date, UA_UInt64 *callbackId) { - return UA_Timer_addTimedCallback(&client->timer, (UA_ApplicationCallback)callback, + return UA_EventLoop_addTimedCallback(client->config.eventLoop, (UA_Callback)callback, client, data, date, callbackId); } UA_StatusCode UA_Client_addRepeatedCallback(UA_Client *client, UA_ClientCallback callback, void *data, UA_Double interval_ms, UA_UInt64 *callbackId) { - return UA_Timer_addRepeatedCallback(&client->timer, (UA_ApplicationCallback)callback, - client, data, interval_ms, NULL, - UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, callbackId); + + return UA_EventLoop_addCyclicCallback( + client->config.eventLoop, (UA_Callback)callback, client, data, + interval_ms, NULL, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, callbackId); } UA_StatusCode UA_Client_changeRepeatedCallbackInterval(UA_Client *client, UA_UInt64 callbackId, UA_Double interval_ms) { - return UA_Timer_changeRepeatedCallback(&client->timer, callbackId, interval_ms, NULL, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME); + return UA_EventLoop_modifyCyclicCallback(client->config.eventLoop, callbackId, + interval_ms, NULL, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME); } void UA_Client_removeCallback(UA_Client *client, UA_UInt64 callbackId) { - UA_Timer_removeCallback(&client->timer, callbackId); + UA_EventLoop_removeCyclicCallback(client->config.eventLoop, callbackId); } static void @@ -672,19 +685,23 @@ UA_Client_backgroundConnectivity(UA_Client *client) { client->pendingConnectivityCheck = true; } -static void -clientExecuteRepeatedCallback(void *executionApplication, UA_ApplicationCallback cb, - void *callbackApplication, void *data) { - cb(callbackApplication, data); -} - UA_StatusCode UA_Client_run_iterate(UA_Client *client, UA_UInt32 timeout) { + UA_ClientConfig *cc = UA_Client_getConfig(client); + UA_CHECK_ERROR(UA_EventLoop_getState(cc->eventLoop) != UA_EVENTLOOPSTATE_STOPPED, + return UA_STATUSCODE_BAD, &client->config.logger, UA_LOGCATEGORY_CLIENT, + "Eventloop was explicitly stopped."); + + UA_StatusCode rv = UA_STATUSCODE_GOOD; + if(UA_EventLoop_getState(cc->eventLoop) == UA_EVENTLOOPSTATE_FRESH) { + rv = UA_EventLoop_start(cc->eventLoop); + UA_CHECK_STATUS(rv, return rv); + } + /* Process timed (repeated) jobs */ UA_DateTime now = UA_DateTime_nowMonotonic(); - UA_DateTime maxDate = - UA_Timer_process(&client->timer, now, (UA_TimerExecutionCallback) - clientExecuteRepeatedCallback, client); + UA_EventLoop_run(client->config.eventLoop, 0); + UA_DateTime maxDate = UA_EventLoop_nextCyclicTime(client->config.eventLoop); if(maxDate > now + ((UA_DateTime)timeout * UA_DATETIME_MSEC)) maxDate = now + ((UA_DateTime)timeout * UA_DATETIME_MSEC); diff --git a/src/client/ua_client_internal.h b/src/client/ua_client_internal.h index be9a751a469..5757f5134b9 100644 --- a/src/client/ua_client_internal.h +++ b/src/client/ua_client_internal.h @@ -20,7 +20,8 @@ #include "open62541_queue.h" #include "ua_securechannel.h" -#include "ua_timer.h" +#include "common/ua_timer.h" +#include "ua_util_internal.h" _UA_BEGIN_DECLS @@ -109,7 +110,6 @@ typedef struct CustomCallback { struct UA_Client { UA_ClientConfig config; - UA_Timer timer; /* Overall connection status */ UA_StatusCode connectStatus; diff --git a/src/pubsub/ua_pubsub.h b/src/pubsub/ua_pubsub.h index 19dd924895a..834541e7958 100644 --- a/src/pubsub/ua_pubsub.h +++ b/src/pubsub/ua_pubsub.h @@ -278,6 +278,7 @@ UA_DataSetReader_handleMessageReceiveTimeout(UA_Server *server, UA_StatusCode UA_DataSetReader_generateNetworkMessage(UA_PubSubConnection *pubSubConnection, + UA_ReaderGroup *readerGroup, UA_DataSetReader *dataSetReader, UA_DataSetMessage *dsm, UA_UInt16 *writerId, UA_Byte dsmCount, UA_NetworkMessage *nm); diff --git a/src/pubsub/ua_pubsub_manager.c b/src/pubsub/ua_pubsub_manager.c index 968f1afefbe..c12a01d1316 100644 --- a/src/pubsub/ua_pubsub_manager.c +++ b/src/pubsub/ua_pubsub_manager.c @@ -4,6 +4,7 @@ * * Copyright (c) 2017-2019 Fraunhofer IOSB (Author: Andreas Ebner) * Copyright (c) 2018 Fraunhofer IOSB (Author: Julius Pfrommer) + * Copyright (c) 2021 Fraunhofer IOSB (Author: Jan Hermes) */ #include @@ -316,12 +317,10 @@ UA_PubSubManager_delete(UA_Server *server, UA_PubSubManager *pubSubManager) { /* Stop and unfreeze all WriterGroups */ UA_PubSubConnection *tmpConnection; TAILQ_FOREACH(tmpConnection, &server->pubSubManager.connections, listEntry){ - for(size_t i = 0; i < pubSubManager->connectionsSize; i++) { - UA_WriterGroup *writerGroup; - LIST_FOREACH(writerGroup, &tmpConnection->writerGroups, listEntry) { - UA_WriterGroup_setPubSubState(server, UA_PUBSUBSTATE_DISABLED, writerGroup); - UA_Server_unfreezeWriterGroupConfiguration(server, writerGroup->identifier); - } + UA_WriterGroup *writerGroup; + LIST_FOREACH(writerGroup, &tmpConnection->writerGroups, listEntry) { + UA_WriterGroup_setPubSubState(server, UA_PUBSUBSTATE_DISABLED, writerGroup); + UA_Server_unfreezeWriterGroupConfiguration(server, writerGroup->identifier); } } @@ -352,20 +351,22 @@ UA_StatusCode UA_PubSubManager_addRepeatedCallback(UA_Server *server, UA_ServerCallback callback, void *data, UA_Double interval_ms, UA_DateTime *baseTime, UA_TimerPolicy timerPolicy, UA_UInt64 *callbackId) { - return UA_Timer_addRepeatedCallback(&server->timer, (UA_ApplicationCallback)callback, - server, data, interval_ms, baseTime, timerPolicy, callbackId); + return UA_EventLoop_addCyclicCallback(server->config.eventLoop, (UA_Callback)callback, + server, data, interval_ms, baseTime, + timerPolicy, callbackId); } UA_StatusCode UA_PubSubManager_changeRepeatedCallback(UA_Server *server, UA_UInt64 callbackId, UA_Double interval_ms, UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) { - return UA_Timer_changeRepeatedCallback(&server->timer, callbackId, interval_ms, baseTime, timerPolicy); + return UA_EventLoop_modifyCyclicCallback(server->config.eventLoop, callbackId, + interval_ms, baseTime, timerPolicy); } void UA_PubSubManager_removeRepeatedPubSubCallback(UA_Server *server, UA_UInt64 callbackId) { - UA_Timer_removeCallback(&server->timer, callbackId); + UA_EventLoop_removeCyclicCallback(server->config.eventLoop, callbackId); } @@ -427,7 +428,7 @@ UA_PubSubComponent_startMonitoring(UA_Server *server, UA_NodeId Id, UA_PubSubCom /* use a timed callback, because one notification is enough, we assume that MessageReceiveTimeout configuration is in [ms], we do not handle or check fractions */ UA_UInt64 interval = (UA_UInt64)(reader->config.messageReceiveTimeout * UA_DATETIME_MSEC); - ret = UA_Timer_addTimedCallback(&server->timer, (UA_ApplicationCallback) reader->msgRcvTimeoutTimerCallback, + ret = UA_EventLoop_addTimedCallback(server->config.eventLoop, (UA_Callback) reader->msgRcvTimeoutTimerCallback, server, reader, UA_DateTime_nowMonotonic() + (UA_DateTime) interval, &(reader->msgRcvTimeoutTimerId)); if (ret == UA_STATUSCODE_GOOD) { UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER, @@ -475,7 +476,7 @@ UA_PubSubComponent_stopMonitoring(UA_Server *server, UA_NodeId Id, UA_PubSubComp UA_DataSetReader *reader = (UA_DataSetReader*) data; switch (eMonitoringType) { case UA_PUBSUB_MONITORING_MESSAGE_RECEIVE_TIMEOUT: { - UA_Timer_removeCallback(&server->timer, reader->msgRcvTimeoutTimerId); + UA_EventLoop_removeCyclicCallback(server->config.eventLoop, reader->msgRcvTimeoutTimerId); UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER, "UA_PubSubComponent_stopMonitoring(): DataSetReader '%.*s' - MessageReceiveTimeout: MessageReceiveTimeout = '%f' " "Timer Id = '%u'", (UA_Int32) reader->config.name.length, reader->config.name.data, @@ -515,7 +516,7 @@ UA_PubSubComponent_updateMonitoringInterval(UA_Server *server, UA_NodeId Id, UA_ UA_DataSetReader *reader = (UA_DataSetReader*) data; switch (eMonitoringType) { case UA_PUBSUB_MONITORING_MESSAGE_RECEIVE_TIMEOUT: { - ret = UA_Timer_changeRepeatedCallback(&server->timer, reader->msgRcvTimeoutTimerId, + ret = UA_EventLoop_modifyCyclicCallback(server->config.eventLoop, reader->msgRcvTimeoutTimerId, reader->config.messageReceiveTimeout, NULL, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME); if (ret == UA_STATUSCODE_GOOD) { diff --git a/src/pubsub/ua_pubsub_networkmessage.c b/src/pubsub/ua_pubsub_networkmessage.c index 010f8d1f3b7..54d18cde5b1 100644 --- a/src/pubsub/ua_pubsub_networkmessage.c +++ b/src/pubsub/ua_pubsub_networkmessage.c @@ -49,6 +49,10 @@ const UA_Byte DS_MESSAGEHEADER_PICOSECONDS_INCLUDED_MASK = 32; const UA_Byte NM_SHIFT_LEN = 2; const UA_Byte DS_MH_SHIFT_LEN = 1; +/* Static memory allocation for the message nonce */ +#define MESSAGE_NONCE_LENGTH 8 +static UA_Byte MessageNonceGenerated[MESSAGE_NONCE_LENGTH]; + static UA_Boolean UA_NetworkMessage_ExtendedFlags1Enabled(const UA_NetworkMessage* src); static UA_Boolean UA_NetworkMessage_ExtendedFlags2Enabled(const UA_NetworkMessage* src); static UA_Boolean UA_DataSetMessageHeader_DataSetFlags2Enabled(const UA_DataSetMessageHeader* src); @@ -762,7 +766,8 @@ UA_SecurityHeader_decodeBinary(const UA_ByteString *src, size_t *offset, // MessageNonce if(nonceLength > 0) { //TODO: check for memory leaks - rv = UA_ByteString_allocBuffer(&dst->securityHeader.messageNonce, nonceLength); + dst->securityHeader.messageNonce.length = MESSAGE_NONCE_LENGTH; + dst->securityHeader.messageNonce.data = MessageNonceGenerated; UA_CHECK_STATUS(rv, return rv); for (UA_Byte i = 0; i < nonceLength; i++) { rv = UA_Byte_decodeBinary(src, offset, @@ -1095,8 +1100,6 @@ UA_NetworkMessage_clear(UA_NetworkMessage* p) { if(p->promotedFieldsEnabled) UA_Array_delete(p->promotedFields, p->promotedFieldsSize, &UA_TYPES[UA_TYPES_VARIANT]); - UA_ByteString_clear(&p->securityHeader.messageNonce); - if(p->networkMessageType == UA_NETWORKMESSAGE_DATASET) { if(p->payloadHeaderEnabled) { if(p->payloadHeader.dataSetPayloadHeader.dataSetWriterIds != NULL) { diff --git a/src/pubsub/ua_pubsub_networkmessage.h b/src/pubsub/ua_pubsub_networkmessage.h index 19d8295887a..64369d1d6ff 100644 --- a/src/pubsub/ua_pubsub_networkmessage.h +++ b/src/pubsub/ua_pubsub_networkmessage.h @@ -221,6 +221,10 @@ typedef struct { UA_Boolean RTsubscriberEnabled; /* Addtional offsets computation like publisherId, WGId if this bool enabled */ UA_NetworkMessage *nm; /* The precomputed NetworkMessage for subscriber */ size_t rawMessageLength; +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + UA_ByteString encryptBuffer; /* The precomputed message buffer is copied into the encrypt buffer for encryption and signing*/ + UA_Byte *payloadPosition; /* Payload Position of the message to encrypt*/ +#endif } UA_NetworkMessageOffsetBuffer; /** diff --git a/src/pubsub/ua_pubsub_reader.c b/src/pubsub/ua_pubsub_reader.c index f9e7bbc91c6..52c4bf0c775 100644 --- a/src/pubsub/ua_pubsub_reader.c +++ b/src/pubsub/ua_pubsub_reader.c @@ -32,6 +32,12 @@ /* This functionality of this API will be used in future to create mirror Variables - TODO */ /* #define UA_MAX_SIZENAME 64 */ /* Max size of Qualified Name of Subscribed Variable */ +/* Static memory allocation for the message nonce */ +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION +#define MESSAGE_NONCE_LENGTH 8 +static UA_Byte MessageNonceGenerated[MESSAGE_NONCE_LENGTH]; +#endif + /* Clear DataSetReader */ static void UA_DataSetReader_clear(UA_Server *server, UA_DataSetReader *dataSetReader); @@ -210,10 +216,9 @@ UA_DataSetReader_generateDataSetMessage(UA_Server *server, } UA_StatusCode -UA_DataSetReader_generateNetworkMessage(UA_PubSubConnection *pubSubConnection, - UA_DataSetReader *dataSetReader, - UA_DataSetMessage *dsm, UA_UInt16 *writerId, - UA_Byte dsmCount, UA_NetworkMessage *nm) { +UA_DataSetReader_generateNetworkMessage(UA_PubSubConnection *pubSubConnection, UA_ReaderGroup *readerGroup, + UA_DataSetReader *dataSetReader, UA_DataSetMessage *dsm, UA_UInt16 *writerId, UA_Byte dsmCount, + UA_NetworkMessage *nm) { UA_ExtensionObject *settings = &dataSetReader->config.messageSettings; if(settings->content.decoded.type != &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]) return UA_STATUSCODE_BADNOTSUPPORTED; @@ -242,6 +247,28 @@ UA_DataSetReader_generateNetworkMessage(UA_PubSubConnection *pubSubConnection, (u64)UA_UADPNETWORKMESSAGECONTENTMASK_DATASETCLASSID) != 0; nm->promotedFieldsEnabled = ((u64)dsrm->networkMessageContentMask & (u64)UA_UADPNETWORKMESSAGECONTENTMASK_PROMOTEDFIELDS) != 0; + /* Set the SecurityHeader */ +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + if(readerGroup->config.securityMode > UA_MESSAGESECURITYMODE_NONE) { + nm->securityEnabled = true; + nm->securityHeader.networkMessageSigned = true; + if(readerGroup->config.securityMode >= UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) + nm->securityHeader.networkMessageEncrypted = true; + nm->securityHeader.securityTokenId = readerGroup->securityTokenId; + + /* Generate the MessageNonce */ + nm->securityHeader.messageNonce.length = MESSAGE_NONCE_LENGTH; + nm->securityHeader.messageNonce.data = MessageNonceGenerated; + + nm->securityHeader.messageNonce.length = 4; /* Generate 4 random bytes */ + UA_StatusCode rv = readerGroup->config.securityPolicy->symmetricModule. + generateNonce(readerGroup->config.securityPolicy->policyContext, + &nm->securityHeader.messageNonce); + if(rv != UA_STATUSCODE_GOOD) + return rv; + nm->securityHeader.messageNonce.length = 8; + } +#endif nm->version = 1; nm->networkMessageType = UA_NETWORKMESSAGE_DATASET; @@ -1360,6 +1387,26 @@ decodeAndProcessNetworkMessageRT(UA_Server *server, UA_ReaderGroup *readerGroup, UA_DataSetReader *dataSetReader = LIST_FIRST(&readerGroup->readers); UA_NetworkMessage *nm = dataSetReader->bufferedMessage.nm; +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + UA_NetworkMessage currentNetworkMessage; + memset(¤tNetworkMessage, 0, sizeof(UA_NetworkMessage)); + UA_StatusCode rv; + size_t payLoadPosition = 0; + rv = UA_NetworkMessage_decodeHeaders( + buffer, &payLoadPosition, ¤tNetworkMessage); + + UA_CHECK_STATUS_ERROR(rv, return rv, &server->config.logger, UA_LOGCATEGORY_SERVER, + "PubSub receive. decoding headers failed"); + rv = verifyAndDecryptNetworkMessage(&server->config.logger, + buffer, + &payLoadPosition, + ¤tNetworkMessage, + readerGroup); + UA_CHECK_STATUS_WARN(rv, return rv, &server->config.logger, UA_LOGCATEGORY_SERVER, + "Subscribe failed. verify and decrypt network message failed."); + UA_NetworkMessage_clear(¤tNetworkMessage); +#endif + /* Decode only the necessary offset and update the networkMessage */ UA_StatusCode res = UA_NetworkMessage_updateBufferedNwMessage(&dataSetReader->bufferedMessage, diff --git a/src/pubsub/ua_pubsub_readergroup.c b/src/pubsub/ua_pubsub_readergroup.c index dffe0ef062d..e82549762e9 100644 --- a/src/pubsub/ua_pubsub_readergroup.c +++ b/src/pubsub/ua_pubsub_readergroup.c @@ -563,7 +563,7 @@ UA_Server_freezeReaderGroupConfiguration(UA_Server *server, return UA_STATUSCODE_BADOUTOFMEMORY; } - res = UA_DataSetReader_generateNetworkMessage(pubSubConnection, dataSetReader, dsm, + res = UA_DataSetReader_generateNetworkMessage(pubSubConnection, rg, dataSetReader, dsm, dsWriterIds, 1, networkMessage); if(res != UA_STATUSCODE_GOOD) { UA_free(networkMessage->payload.dataSetPayload.sizes); diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index 46878d151e1..cde8709181a 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -27,6 +27,14 @@ static void UA_WriterGroup_clear(UA_Server *server, UA_WriterGroup *writerGroup); +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION +static UA_StatusCode +encryptAndSign(UA_WriterGroup *wg, const UA_NetworkMessage *nm, + UA_Byte *signStart, UA_Byte *encryptStart, + UA_Byte *msgEnd); + +#endif + static UA_StatusCode generateNetworkMessage(UA_PubSubConnection *connection, UA_WriterGroup *wg, UA_DataSetMessage *dsm, UA_UInt16 *writerIds, UA_Byte dsmCount, @@ -286,6 +294,13 @@ UA_Server_freezeWriterGroupConfiguration(UA_Server *server, /* Allocate the buffer. Allocate on the stack if the buffer is small. */ msgSize = UA_NetworkMessage_calcSizeBinary(&networkMessage, NULL); +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + if(wg->config.securityMode > UA_MESSAGESECURITYMODE_NONE) { + UA_PubSubSecurityPolicy *sp = wg->config.securityPolicy; + msgSize += sp->symmetricModule.cryptoModule. + signatureAlgorithm.getLocalSignatureSize(sp->policyContext); + } +#endif res = UA_ByteString_allocBuffer(&buf, msgSize); if(res != UA_STATUSCODE_GOOD) goto cleanup; @@ -294,7 +309,21 @@ UA_Server_freezeWriterGroupConfiguration(UA_Server *server, /* Encode the NetworkMessage */ bufEnd = &wg->bufferedMessage.buffer.data[wg->bufferedMessage.buffer.length]; bufPos = wg->bufferedMessage.buffer.data; - UA_NetworkMessage_encodeBinary(&networkMessage, &bufPos, bufEnd, NULL); +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + if (wg->config.securityMode > UA_MESSAGESECURITYMODE_NONE){ + UA_Byte *payloadPosition; + UA_NetworkMessage_encodeBinary(&networkMessage, &bufPos, bufEnd, &payloadPosition); + wg->bufferedMessage.payloadPosition = payloadPosition; + wg->bufferedMessage.nm = (UA_NetworkMessage *)UA_malloc(sizeof(networkMessage)); + wg->bufferedMessage.nm->securityHeader.networkMessageEncrypted = networkMessage.securityHeader.networkMessageEncrypted; + wg->bufferedMessage.nm->securityHeader.networkMessageSigned = networkMessage.securityHeader.networkMessageSigned; + UA_ByteString_copy(&networkMessage.securityHeader.messageNonce, &wg->bufferedMessage.nm->securityHeader.messageNonce); + UA_ByteString_allocBuffer(&wg->bufferedMessage.encryptBuffer, msgSize); + UA_ByteString_clear(&networkMessage.securityHeader.messageNonce); + } +#endif + if (wg->config.securityMode <= UA_MESSAGESECURITYMODE_NONE) + UA_NetworkMessage_encodeBinary(&networkMessage, &bufPos, bufEnd, NULL); cleanup: UA_free(networkMessage.payload.dataSetPayload.sizes); @@ -348,8 +377,18 @@ UA_Server_unfreezeWriterGroupConfiguration(UA_Server *server, } dataSetWriter->configurationFrozen = UA_FALSE; } - if(wg->config.rtLevel == UA_PUBSUB_RT_FIXED_SIZE) + if(wg->config.rtLevel == UA_PUBSUB_RT_FIXED_SIZE) { UA_ByteString_clear(&wg->bufferedMessage.buffer); +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + if (wg->config.securityMode > UA_MESSAGESECURITYMODE_NONE) { + if (wg->bufferedMessage.nm != NULL) { + UA_ByteString_clear(&wg->bufferedMessage.nm->securityHeader.messageNonce); + UA_free(wg->bufferedMessage.nm); + } + UA_ByteString_clear(&wg->bufferedMessage.encryptBuffer); + } +#endif + } return UA_STATUSCODE_GOOD; } @@ -699,7 +738,7 @@ encryptAndSign(UA_WriterGroup *wg, const UA_NetworkMessage *nm, signStart}; size_t sigSize = wg->config.securityPolicy->symmetricModule.cryptoModule. - signatureAlgorithm.getLocalSignatureSize(channelContext); + signatureAlgorithm.getLocalSignatureSize(channelContext); UA_ByteString signature = {sigSize, msgEnd}; rv = wg->config.securityPolicy->symmetricModule.cryptoModule. @@ -949,19 +988,18 @@ sendNetworkMessage(UA_PubSubConnection *connection, UA_WriterGroup *wg, static UA_StatusCode sendBufferedNetworkMessage(UA_Server *server, UA_PubSubConnection *connection, - UA_NetworkMessageOffsetBuffer *buffer, + UA_ByteString *buffer, UA_ExtensionObject *transportSettings) { - if(UA_NetworkMessage_updateBufferedMessage(buffer) != UA_STATUSCODE_GOOD) - UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER, - "PubSub sending. Unknown field type."); + return connection->channel->send(connection->channel, - transportSettings, &buffer->buffer); + transportSettings, buffer); } /* This callback triggers the collection and publish of NetworkMessages and the * contained DataSetMessages. */ void UA_WriterGroup_publishCallback(UA_Server *server, UA_WriterGroup *writerGroup) { + UA_StatusCode res = UA_STATUSCODE_GOOD; UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER, "Publish Callback"); // TODO: review if its okay to force correct value from caller side instead @@ -997,14 +1035,39 @@ UA_WriterGroup_publishCallback(UA_Server *server, UA_WriterGroup *writerGroup) { } if(writerGroup->config.rtLevel == UA_PUBSUB_RT_FIXED_SIZE) { - UA_StatusCode res = - sendBufferedNetworkMessage(server, connection, &writerGroup->bufferedMessage, - &writerGroup->config.transportSettings); + if(UA_NetworkMessage_updateBufferedMessage(&writerGroup->bufferedMessage) != UA_STATUSCODE_GOOD) + UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER, + "PubSub sending. Unknown field type."); + +#ifdef UA_ENABLE_PUBSUB_ENCRYPTION + if (writerGroup->config.securityMode > UA_MESSAGESECURITYMODE_NONE) { + size_t sigSize = writerGroup->config.securityPolicy->symmetricModule.cryptoModule. + signatureAlgorithm.getLocalSignatureSize(writerGroup->securityPolicyContext); + + UA_Byte payloadOffset = (UA_Byte)(writerGroup->bufferedMessage.payloadPosition - writerGroup->bufferedMessage.buffer.data); + memcpy(writerGroup->bufferedMessage.encryptBuffer.data, writerGroup->bufferedMessage.buffer.data, writerGroup->bufferedMessage.buffer.length); + res = encryptAndSign(writerGroup, writerGroup->bufferedMessage.nm, + writerGroup->bufferedMessage.encryptBuffer.data, + writerGroup->bufferedMessage.encryptBuffer.data + payloadOffset, + writerGroup->bufferedMessage.encryptBuffer.data + writerGroup->bufferedMessage.encryptBuffer.length - sigSize); + + if(res != UA_STATUSCODE_GOOD) + UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, "PubSub Encryption failed"); + /* Send the encrypted buffered network message + * if PubSub encryption is enabled */ + res = sendBufferedNetworkMessage(server, connection, &writerGroup->bufferedMessage.encryptBuffer, + &writerGroup->config.transportSettings); + } +#endif + if (writerGroup->config.securityMode < UA_MESSAGESECURITYMODE_NONE) + res = sendBufferedNetworkMessage(server, connection, &writerGroup->bufferedMessage.buffer, + &writerGroup->config.transportSettings); + if(res == UA_STATUSCODE_GOOD) { writerGroup->sequenceNumber++; } else { UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, - "Publish failed. RT fixed size. sendBufferedNetworkMessage failed"); + "Publish failed. sendBufferedNetworkMessage failed. StatusCode %s", UA_StatusCode_name(res)); UA_WriterGroup_setPubSubState(server, UA_PUBSUBSTATE_ERROR, writerGroup); } return; @@ -1022,7 +1085,6 @@ UA_WriterGroup_publishCallback(UA_Server *server, UA_WriterGroup *writerGroup) { * But only if they do not contain promoted fields. NM with only DSM are * sent out right away. The others are kept in a buffer for "batching". */ size_t dsmCount = 0; - UA_StatusCode res = UA_STATUSCODE_GOOD; UA_STACKARRAY(UA_UInt16, dsWriterIds, writerGroup->writersCount); UA_STACKARRAY(UA_DataSetMessage, dsmStore, writerGroup->writersCount); UA_DataSetWriter *dsw; diff --git a/src/server/ua_nodes.c b/src/server/ua_nodes.c index c4816b97896..29f97148a16 100644 --- a/src/server/ua_nodes.c +++ b/src/server/ua_nodes.c @@ -15,6 +15,37 @@ #include "ua_types_encoding_binary.h" #include "aa_tree.h" +/*********************/ +/* ReferenceType Set */ +/*********************/ + +#define UA_REFTYPES_ALL_MASK (~(UA_UInt32)0) +#define UA_REFTYPES_ALL_MASK2 UA_REFTYPES_ALL_MASK, UA_REFTYPES_ALL_MASK +#define UA_REFTYPES_ALL_MASK4 UA_REFTYPES_ALL_MASK2, UA_REFTYPES_ALL_MASK2 +#if (UA_REFERENCETYPESET_MAX) / 32 > 8 +# error Adjust macros to support than 256 reference types +#elif (UA_REFERENCETYPESET_MAX) / 32 == 8 +# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK4, UA_REFTYPES_ALL_MASK4 +#elif (UA_REFERENCETYPESET_MAX) / 32 == 7 +# define UA_REFTYPES_ALL_ARRAY \ + UA_REFTYPES_ALL_MASK4, UA_REFTYPES_ALL_MASK2, UA_REFTYPES_ALL_MASK +#elif (UA_REFERENCETYPESET_MAX) / 32 == 6 +# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK4, UA_REFTYPES_ALL_MASK2 +#elif (UA_REFERENCETYPESET_MAX) / 32 == 5 +# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK4, UA_REFTYPES_ALL_MASK +#elif (UA_REFERENCETYPESET_MAX) / 32 == 4 +# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK4 +#elif (UA_REFERENCETYPESET_MAX) / 32 == 3 +# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK2, UA_REFTYPES_ALL_MASK +#elif (UA_REFERENCETYPESET_MAX) / 32 == 2 +# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK2 +#else +# define UA_REFTYPES_ALL_ARRAY UA_REFTYPES_ALL_MASK +#endif + +const UA_ReferenceTypeSet UA_REFERENCETYPESET_NONE = {0}; +const UA_ReferenceTypeSet UA_REFERENCETYPESET_ALL = {{UA_REFTYPES_ALL_ARRAY}}; + /*****************/ /* Node Pointers */ /*****************/ @@ -358,14 +389,6 @@ UA_NodeReferenceKind_findTarget(const UA_NodeReferenceKind *rk, return NULL; } -const UA_Node * -UA_NODESTORE_GETFROMREF(UA_Server *server, UA_NodePointer target) { - if(!UA_NodePointer_isLocal(target)) - return NULL; - UA_NodeId id = UA_NodePointer_toNodeId(target); - return UA_NODESTORE_GET(server, &id); -} - /* General node handling methods. There is no UA_Node_new() method here. * Creating nodes is part of the Nodestore layer */ diff --git a/src/server/ua_server.c b/src/server/ua_server.c index d68df17700f..7ce8048bbba 100644 --- a/src/server/ua_server.c +++ b/src/server/ua_server.c @@ -156,10 +156,6 @@ UA_Server_forEachChildNodeCall(UA_Server *server, UA_NodeId parentNodeId, /* Server Lifecycle */ /********************/ -static void -serverExecuteRepeatedCallback(UA_Server *server, UA_ApplicationCallback cb, - void *callbackApplication, void *data); - /* The server needs to be stopped before it can be deleted */ void UA_Server_delete(UA_Server *server) { UA_LOCK(&server->serviceMutex); @@ -204,16 +200,20 @@ void UA_Server_delete(UA_Server *server) { UA_AsyncManager_clear(&server->asyncManager, server); #endif + /* Stop the EventLoop and iterate until stopped or an error occurs */ + UA_EventLoop_stop(server->config.eventLoop); + UA_StatusCode res = UA_STATUSCODE_GOOD; + UA_EventLoopState state = UA_EventLoop_getState(server->config.eventLoop); + while(res == UA_STATUSCODE_GOOD && state != UA_EVENTLOOPSTATE_STOPPED) { + res = UA_EventLoop_run(server->config.eventLoop, 100); + state = UA_EventLoop_getState(server->config.eventLoop); + } + /* Clean up the Admin Session */ UA_Session_clear(&server->adminSession, server); UA_UNLOCK(&server->serviceMutex); /* The timer has its own mutex */ - /* Execute all remaining delayed events and clean up the timer */ - UA_Timer_process(&server->timer, UA_DateTime_nowMonotonic() + 1, - (UA_TimerExecutionCallback)serverExecuteRepeatedCallback, server); - UA_Timer_clear(&server->timer); - /* Clean up the config */ UA_ServerConfig_clean(&server->config); @@ -272,9 +272,6 @@ UA_Server_init(UA_Server *server) { UA_LOCK_INIT(&server->serviceMutex); #endif - /* Initialize the handling of repeated callbacks */ - UA_Timer_init(&server->timer); - /* Initialize the adminSession */ UA_Session_init(&server->adminSession); server->adminSession.sessionId.identifierType = UA_NODEIDTYPE_GUID; @@ -335,15 +332,21 @@ UA_Server * UA_Server_newWithConfig(UA_ServerConfig *config) { UA_CHECK_MEM(config, return NULL); + UA_CHECK_LOG(config->eventLoop != NULL, return NULL, ERROR, + &config->logger, UA_LOGCATEGORY_SERVER, "No EventLoop configured"); + UA_Server *server = (UA_Server *)UA_calloc(1, sizeof(UA_Server)); UA_CHECK_MEM(server, UA_ServerConfig_clean(config); return NULL); server->config = *config; + /* The config might have been "moved" into the server struct. Ensure that * the logger pointer is correct. */ for(size_t i = 0; i < server->config.securityPoliciesSize; i++) server->config.securityPolicies[i].logger = &server->config.logger; + UA_EventLoop_setLogger(server->config.eventLoop, &server->config.logger); + /* Reset the old config */ memset(config, 0, sizeof(UA_ServerConfig)); return UA_Server_init(server); @@ -371,9 +374,9 @@ UA_Server_addTimedCallback(UA_Server *server, UA_ServerCallback callback, void *data, UA_DateTime date, UA_UInt64 *callbackId) { UA_LOCK(&server->serviceMutex); UA_StatusCode retval = - UA_Timer_addTimedCallback(&server->timer, - (UA_ApplicationCallback)callback, - server, data, date, callbackId); + UA_EventLoop_addTimedCallback(server->config.eventLoop, + (UA_Callback)callback, + server, data, date, callbackId); UA_UNLOCK(&server->serviceMutex); return retval; } @@ -382,10 +385,10 @@ UA_StatusCode addRepeatedCallback(UA_Server *server, UA_ServerCallback callback, void *data, UA_Double interval_ms, UA_UInt64 *callbackId) { - return UA_Timer_addRepeatedCallback(&server->timer, - (UA_ApplicationCallback)callback, - server, data, interval_ms, NULL, - UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, callbackId); + return UA_EventLoop_addCyclicCallback(server->config.eventLoop, (UA_Callback) callback, + server, data, interval_ms, NULL, + UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, + callbackId); } UA_StatusCode @@ -402,8 +405,9 @@ UA_Server_addRepeatedCallback(UA_Server *server, UA_ServerCallback callback, UA_StatusCode changeRepeatedCallbackInterval(UA_Server *server, UA_UInt64 callbackId, UA_Double interval_ms) { - return UA_Timer_changeRepeatedCallback(&server->timer, callbackId, - interval_ms, NULL, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME); + return UA_EventLoop_modifyCyclicCallback(server->config.eventLoop, callbackId, + interval_ms, NULL, + UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME); } UA_StatusCode @@ -418,7 +422,7 @@ UA_Server_changeRepeatedCallbackInterval(UA_Server *server, UA_UInt64 callbackId void removeCallback(UA_Server *server, UA_UInt64 callbackId) { - UA_Timer_removeCallback(&server->timer, callbackId); + UA_EventLoop_removeCyclicCallback(server->config.eventLoop, callbackId); } void @@ -549,14 +553,16 @@ UA_Server_run_startup(UA_Server *server) { "This should only be used for specific fuzzing builds."); #endif + UA_StatusCode retVal = UA_EventLoop_start(server->config.eventLoop); + UA_CHECK_STATUS(retVal, return retVal); + /* ensure that the uri for ns1 is set up from the app description */ setupNs1Uri(server); /* write ServerArray with same ApplicationURI value as NamespaceArray */ - UA_StatusCode retVal = - writeNs0VariableArray(server, UA_NS0ID_SERVER_SERVERARRAY, - &server->config.applicationDescription.applicationUri, - 1, &UA_TYPES[UA_TYPES_STRING]); + retVal = writeNs0VariableArray(server, UA_NS0ID_SERVER_SERVERARRAY, + &server->config.applicationDescription.applicationUri, + 1, &UA_TYPES[UA_TYPES_STRING]); UA_CHECK_STATUS(retVal, return retVal); if(server->state > UA_SERVERLIFECYCLE_FRESH) @@ -630,22 +636,12 @@ UA_Server_run_startup(UA_Server *server) { return result; } -static void -serverExecuteRepeatedCallback(UA_Server *server, UA_ApplicationCallback cb, - void *callbackApplication, void *data) { - /* Service mutex is not set inside the timer that triggers the callback */ - /* The following check cannot be used since another thread can take the - * serviceMutex during a server_iterate_call. */ - //UA_LOCK_ASSERT(&server->serviceMutex, 0); - cb(callbackApplication, data); -} - UA_UInt16 UA_Server_run_iterate(UA_Server *server, UA_Boolean waitInternal) { /* Process repeated work */ UA_DateTime now = UA_DateTime_nowMonotonic(); - UA_DateTime nextRepeated = UA_Timer_process(&server->timer, now, - (UA_TimerExecutionCallback)serverExecuteRepeatedCallback, server); + UA_EventLoop_run(server->config.eventLoop, 0); + UA_DateTime nextRepeated = UA_EventLoop_nextCyclicTime(server->config.eventLoop); UA_DateTime latest = now + (UA_MAXTIMEOUT * UA_DATETIME_MSEC); if(nextRepeated > latest) nextRepeated = latest; diff --git a/src/server/ua_server_config.c b/src/server/ua_server_config.c index 692465d70d1..950099ee09c 100644 --- a/src/server/ua_server_config.c +++ b/src/server/ua_server_config.c @@ -29,6 +29,19 @@ UA_ServerConfig_clean(UA_ServerConfig *config) { /* Custom DataTypes */ /* nothing to do */ + /* Stop and delete the EventLoop */ + if(config->eventLoop && !config->externalEventLoop) { + if(UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_FRESH && + UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_STOPPED) { + UA_EventLoop_stop(config->eventLoop); + while(UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_STOPPED) { + UA_EventLoop_run(config->eventLoop, 100); + } + } + UA_EventLoop_delete(config->eventLoop); + config->eventLoop = NULL; + } + /* Networking */ for(size_t i = 0; i < config->networkLayersSize; ++i) config->networkLayers[i].clear(&config->networkLayers[i]); diff --git a/src/server/ua_server_internal.h b/src/server/ua_server_internal.h index 7f1ec4908d1..9f5c087c471 100644 --- a/src/server/ua_server_internal.h +++ b/src/server/ua_server_internal.h @@ -23,7 +23,7 @@ #include "ua_connection_internal.h" #include "ua_session.h" #include "ua_server_async.h" -#include "ua_timer.h" +#include "common/ua_timer.h" /* arch-folder, TODO: Remove after the EventLoop is integrated */ #include "ua_util_internal.h" #include "ziptree.h" @@ -66,13 +66,13 @@ typedef enum { } UA_DiagnosticEvent; typedef struct channel_entry { - UA_TimerEntry cleanupCallback; + UA_DelayedCallback cleanupCallback; TAILQ_ENTRY(channel_entry) pointers; UA_SecureChannel channel; } channel_entry; typedef struct session_list_entry { - UA_TimerEntry cleanupCallback; + UA_DelayedCallback cleanupCallback; LIST_ENTRY(session_list_entry) pointers; UA_Session session; } session_list_entry; @@ -111,9 +111,6 @@ struct UA_Server { size_t namespacesSize; UA_String *namespaces; - /* Callbacks with a repetition interval */ - UA_Timer timer; - /* For bootstrapping, omit some consistency checks, creating a reference to * the parent and member instantiation */ UA_Boolean bootstrapNS0; @@ -588,12 +585,29 @@ UA_StatusCode writeNs0VariableArray(UA_Server *server, UA_UInt32 id, void *v, #define UA_NODESTORE_DELETE(server, node) \ server->config.nodestore.deleteNode(server->config.nodestore.context, node) -#define UA_NODESTORE_GET(server, nodeid) \ - server->config.nodestore.getNode(server->config.nodestore.context, nodeid) +/* Get the node with all attributes and references */ +static UA_INLINE const UA_Node * +UA_NODESTORE_GET(UA_Server *server, const UA_NodeId *nodeId) { + return server->config.nodestore. + getNode(server->config.nodestore.context, nodeId, UA_NODEATTRIBUTESMASK_ALL, + UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH); +} -/* Returns NULL if the target is an external Reference (per the ExpandedNodeId) */ -const UA_Node * -UA_NODESTORE_GETFROMREF(UA_Server *server, UA_NodePointer target); +/* Get the node with all attributes and references */ +static UA_INLINE const UA_Node * +UA_NODESTORE_GETFROMREF(UA_Server *server, UA_NodePointer target) { + return server->config.nodestore. + getNodeFromPtr(server->config.nodestore.context, target, UA_NODEATTRIBUTESMASK_ALL, + UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH); +} + +#define UA_NODESTORE_GET_SELECTIVE(server, nodeid, attrMask, refs, refDirs) \ + server->config.nodestore.getNode(server->config.nodestore.context, \ + nodeid, attrMask, refs, refDirs) + +#define UA_NODESTORE_GETFROMREF_SELECTIVE(server, target, attrMask, refs, refDirs) \ + server->config.nodestore.getNodeFromPtr(server->config.nodestore.context, \ + target, attrMask, refs, refDirs) #define UA_NODESTORE_RELEASE(server, node) \ server->config.nodestore.releaseNode(server->config.nodestore.context, node) diff --git a/src/server/ua_services_attribute.c b/src/server/ua_services_attribute.c index 8e892ddcfa3..697f68753f2 100644 --- a/src/server/ua_services_attribute.c +++ b/src/server/ua_services_attribute.c @@ -27,6 +27,40 @@ #include #endif +static UA_UInt32 +attributeId2AttributeMask(UA_AttributeId id) { + switch(id) { + case UA_ATTRIBUTEID_NODEID: return UA_NODEATTRIBUTESMASK_NODEID; + case UA_ATTRIBUTEID_NODECLASS: return UA_NODEATTRIBUTESMASK_NODECLASS; + case UA_ATTRIBUTEID_BROWSENAME: return UA_NODEATTRIBUTESMASK_BROWSENAME; + case UA_ATTRIBUTEID_DISPLAYNAME: return UA_NODEATTRIBUTESMASK_DISPLAYNAME; + case UA_ATTRIBUTEID_DESCRIPTION: return UA_NODEATTRIBUTESMASK_DESCRIPTION; + case UA_ATTRIBUTEID_WRITEMASK: return UA_NODEATTRIBUTESMASK_WRITEMASK; + case UA_ATTRIBUTEID_USERWRITEMASK: return UA_NODEATTRIBUTESMASK_USERWRITEMASK; + case UA_ATTRIBUTEID_ISABSTRACT: return UA_NODEATTRIBUTESMASK_ISABSTRACT; + case UA_ATTRIBUTEID_SYMMETRIC: return UA_NODEATTRIBUTESMASK_SYMMETRIC; + case UA_ATTRIBUTEID_INVERSENAME: return UA_NODEATTRIBUTESMASK_INVERSENAME; + case UA_ATTRIBUTEID_CONTAINSNOLOOPS: return UA_NODEATTRIBUTESMASK_CONTAINSNOLOOPS; + case UA_ATTRIBUTEID_EVENTNOTIFIER: return UA_NODEATTRIBUTESMASK_EVENTNOTIFIER; + case UA_ATTRIBUTEID_VALUE: return UA_NODEATTRIBUTESMASK_VALUE; + case UA_ATTRIBUTEID_DATATYPE: return UA_NODEATTRIBUTESMASK_DATATYPE; + case UA_ATTRIBUTEID_VALUERANK: return UA_NODEATTRIBUTESMASK_VALUERANK; + case UA_ATTRIBUTEID_ARRAYDIMENSIONS: return UA_NODEATTRIBUTESMASK_ARRAYDIMENSIONS; + case UA_ATTRIBUTEID_ACCESSLEVEL: return UA_NODEATTRIBUTESMASK_ACCESSLEVEL; + case UA_ATTRIBUTEID_USERACCESSLEVEL: return UA_NODEATTRIBUTESMASK_USERACCESSLEVEL; + case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL: return UA_NODEATTRIBUTESMASK_MINIMUMSAMPLINGINTERVAL; + case UA_ATTRIBUTEID_HISTORIZING: return UA_NODEATTRIBUTESMASK_HISTORIZING; + case UA_ATTRIBUTEID_EXECUTABLE: return UA_NODEATTRIBUTESMASK_EXECUTABLE; + case UA_ATTRIBUTEID_USEREXECUTABLE: return UA_NODEATTRIBUTESMASK_USEREXECUTABLE; + case UA_ATTRIBUTEID_DATATYPEDEFINITION: return UA_NODEATTRIBUTESMASK_DATATYPEDEFINITION; + case UA_ATTRIBUTEID_ROLEPERMISSIONS: return UA_NODEATTRIBUTESMASK_ROLEPERMISSIONS; + case UA_ATTRIBUTEID_USERROLEPERMISSIONS: return UA_NODEATTRIBUTESMASK_ROLEPERMISSIONS; + case UA_ATTRIBUTEID_ACCESSRESTRICTIONS: return UA_NODEATTRIBUTESMASK_ACCESSRESTRICTIONS; + case UA_ATTRIBUTEID_ACCESSLEVELEX: return UA_NODEATTRIBUTESMASK_ACCESSLEVEL; + default: return UA_NODEATTRIBUTESMASK_NONE; + } +} + /******************/ /* Access Control */ /******************/ @@ -131,7 +165,11 @@ readValueAttributeFromNode(UA_Server *server, UA_Session *session, &vn->head.nodeId, vn->head.context, rangeptr, &vn->value.data.value); UA_LOCK(&server->serviceMutex); - vn = (const UA_VariableNode*)UA_NODESTORE_GET(server, &vn->head.nodeId); + vn = (const UA_VariableNode*) + UA_NODESTORE_GET_SELECTIVE(server, &vn->head.nodeId, + UA_NODEATTRIBUTESMASK_VALUE, + UA_REFERENCETYPESET_NONE, + UA_BROWSEDIRECTION_INVALID); if(!vn) return UA_STATUSCODE_BADNODEIDUNKNOWN; } @@ -562,8 +600,12 @@ ReadWithNode(const UA_Node *node, UA_Server *server, UA_Session *session, static void Operation_Read(UA_Server *server, UA_Session *session, UA_ReadRequest *request, UA_ReadValueId *rvi, UA_DataValue *result) { - /* Get the node */ - const UA_Node *node = UA_NODESTORE_GET(server, &rvi->nodeId); + /* Get the node (with only the selected attribute if the NodeStore supports that) */ + const UA_Node *node = + UA_NODESTORE_GET_SELECTIVE(server, &rvi->nodeId, + attributeId2AttributeMask((UA_AttributeId)rvi->attributeId), + UA_REFERENCETYPESET_NONE, + UA_BROWSEDIRECTION_INVALID); /* Perform the read operation */ if(node) { @@ -620,8 +662,12 @@ UA_Server_readWithSession(UA_Server *server, UA_Session *session, UA_DataValue dv; UA_DataValue_init(&dv); - /* Get the node */ - const UA_Node *node = UA_NODESTORE_GET(server, &item->nodeId); + /* Get the node (with only the selected attribute if the NodeStore supports it) */ + const UA_Node *node = + UA_NODESTORE_GET_SELECTIVE(server, &item->nodeId, + attributeId2AttributeMask((UA_AttributeId)item->attributeId), + UA_REFERENCETYPESET_NONE, + UA_BROWSEDIRECTION_INVALID); if(!node) { dv.hasStatus = true; dv.status = UA_STATUSCODE_BADNODEIDUNKNOWN; diff --git a/src/server/ua_services_method.c b/src/server/ua_services_method.c index 83e506cf9d8..25f876f7733 100644 --- a/src/server/ua_services_method.c +++ b/src/server/ua_services_method.c @@ -23,14 +23,19 @@ getArgumentsVariableNode(UA_Server *server, const UA_NodeHead *head, UA_String withBrowseName) { for(size_t i = 0; i < head->referencesSize; ++i) { const UA_NodeReferenceKind *rk = &head->references[i]; - if(rk->isInverse != false) + if(rk->isInverse) continue; if(rk->referenceTypeIndex != UA_REFERENCETYPEINDEX_HASPROPERTY) continue; const UA_ReferenceTarget *t = NULL; while((t = UA_NodeReferenceKind_iterate(rk, t))) { + /* Get only the NodeClass and Value attributes, no references */ const UA_Node *refTarget = - UA_NODESTORE_GETFROMREF(server, t->targetId); + UA_NODESTORE_GETFROMREF_SELECTIVE(server, t->targetId, + UA_NODEATTRIBUTESMASK_NODECLASS | + UA_NODEATTRIBUTESMASK_VALUE, + UA_REFERENCETYPESET_NONE, + UA_BROWSEDIRECTION_INVALID); if(!refTarget) continue; if(refTarget->head.nodeClass == UA_NODECLASS_VARIABLE && @@ -327,15 +332,29 @@ Operation_CallMethodAsync(UA_Server *server, UA_Session *session, UA_UInt32 requ UA_UInt32 requestHandle, size_t opIndex, UA_CallMethodRequest *opRequest, UA_CallMethodResult *opResult, UA_AsyncResponse **ar) { - /* Get the method node */ - const UA_Node *method = UA_NODESTORE_GET(server, &opRequest->methodId); + /* Get the method node. We only need the nodeClass and executable attribute. + * Take all forward hasProperty references to get the input/output argument + * definition variables. */ + const UA_Node *method = + UA_NODESTORE_GET_SELECTIVE(server, &opRequest->methodId, + UA_NODEATTRIBUTESMASK_NODECLASS | + UA_NODEATTRIBUTESMASK_EXECUTABLE, + UA_REFTYPESET(UA_REFERENCETYPEINDEX_HASPROPERTY), + UA_BROWSEDIRECTION_FORWARD); if(!method) { opResult->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN; return; } - /* Get the object node */ - const UA_Node *object = UA_NODESTORE_GET(server, &opRequest->objectId); + /* Get the object node. We only need the NodeClass attribute. But take all + * references for now. + * + * TODO: Which references do we need actually? */ + const UA_Node *object = + UA_NODESTORE_GET_SELECTIVE(server, &opRequest->objectId, + UA_NODEATTRIBUTESMASK_NODECLASS, + UA_REFERENCETYPESET_ALL, + UA_BROWSEDIRECTION_BOTH); if(!object) { opResult->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN; UA_NODESTORE_RELEASE(server, method); @@ -410,15 +429,29 @@ Service_CallAsync(UA_Server *server, UA_Session *session, UA_UInt32 requestId, static void Operation_CallMethod(UA_Server *server, UA_Session *session, void *context, const UA_CallMethodRequest *request, UA_CallMethodResult *result) { - /* Get the method node */ - const UA_Node *method = UA_NODESTORE_GET(server, &request->methodId); + /* Get the method node. We only need the nodeClass and executable attribute. + * Take all forward hasProperty references to get the input/output argument + * definition variables. */ + const UA_Node *method = + UA_NODESTORE_GET_SELECTIVE(server, &request->methodId, + UA_NODEATTRIBUTESMASK_NODECLASS | + UA_NODEATTRIBUTESMASK_EXECUTABLE, + UA_REFTYPESET(UA_REFERENCETYPEINDEX_HASPROPERTY), + UA_BROWSEDIRECTION_FORWARD); if(!method) { result->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN; return; } - /* Get the object node */ - const UA_Node *object = UA_NODESTORE_GET(server, &request->objectId); + /* Get the object node. We only need the NodeClass attribute. But take all + * references for now. + * + * TODO: Which references do we need actually? */ + const UA_Node *object = + UA_NODESTORE_GET_SELECTIVE(server, &request->objectId, + UA_NODEATTRIBUTESMASK_NODECLASS, + UA_REFERENCETYPESET_ALL, + UA_BROWSEDIRECTION_BOTH); if(!object) { result->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN; UA_NODESTORE_RELEASE(server, method); diff --git a/src/server/ua_services_securechannel.c b/src/server/ua_services_securechannel.c index a4a4890ab98..e20cd137c64 100644 --- a/src/server/ua_services_securechannel.c +++ b/src/server/ua_services_securechannel.c @@ -68,12 +68,10 @@ removeSecureChannel(UA_Server *server, channel_entry *entry, /* Add a delayed callback to remove the channel when the currently * scheduled jobs have completed */ - entry->cleanupCallback.callback = (UA_ApplicationCallback)removeSecureChannelCallback; + entry->cleanupCallback.callback = (UA_Callback)removeSecureChannelCallback; entry->cleanupCallback.application = NULL; entry->cleanupCallback.data = entry; - entry->cleanupCallback.nextTime = UA_DateTime_nowMonotonic() + 1; - entry->cleanupCallback.interval = 0; /* Remove the structure */ - UA_Timer_addTimerEntry(&server->timer, &entry->cleanupCallback, NULL); + UA_EventLoop_addDelayedCallback(server->config.eventLoop, &entry->cleanupCallback); } void diff --git a/src/server/ua_services_session.c b/src/server/ua_services_session.c index c7042296421..687de97779e 100644 --- a/src/server/ua_services_session.c +++ b/src/server/ua_services_session.c @@ -88,12 +88,10 @@ UA_Server_removeSession(UA_Server *server, session_list_entry *sentry, /* Add a delayed callback to remove the session when the currently * scheduled jobs have completed */ - sentry->cleanupCallback.callback = (UA_ApplicationCallback)removeSessionCallback; + sentry->cleanupCallback.callback = (UA_Callback)removeSessionCallback; sentry->cleanupCallback.application = server; sentry->cleanupCallback.data = sentry; - sentry->cleanupCallback.nextTime = UA_DateTime_nowMonotonic() + 1; - sentry->cleanupCallback.interval = 0; /* Remove the structure */ - UA_Timer_addTimerEntry(&server->timer, &sentry->cleanupCallback, NULL); + UA_EventLoop_addDelayedCallback(server->config.eventLoop, &sentry->cleanupCallback); } UA_StatusCode diff --git a/src/server/ua_services_view.c b/src/server/ua_services_view.c index 9947ab91718..7233b401ac7 100644 --- a/src/server/ua_services_view.c +++ b/src/server/ua_services_view.c @@ -22,16 +22,37 @@ #define UA_MAX_TREE_RECURSE 50 /* How deep up/down the tree do we recurse at most? */ +static UA_UInt32 +resultMask2AttributesMask(UA_UInt32 resultMask) { + UA_UInt32 result = 0; + if(resultMask & UA_BROWSERESULTMASK_NODECLASS) + result |= UA_NODEATTRIBUTESMASK_NODECLASS; + if(resultMask & UA_BROWSERESULTMASK_BROWSENAME) + result |= UA_NODEATTRIBUTESMASK_BROWSENAME; + if(resultMask & UA_BROWSERESULTMASK_DISPLAYNAME) + result |= UA_NODEATTRIBUTESMASK_DISPLAYNAME; + return result; +} + UA_StatusCode referenceTypeIndices(UA_Server *server, const UA_NodeId *refType, UA_ReferenceTypeSet *indices, UA_Boolean includeSubtypes) { if(UA_NodeId_isNull(refType)) { - UA_ReferenceTypeSet_any(indices); + *indices = UA_REFERENCETYPESET_ALL; return UA_STATUSCODE_GOOD; } UA_ReferenceTypeSet_init(indices); - const UA_Node *refNode = UA_NODESTORE_GET(server, refType); + + /* Get the node with only the NodeClass attribute. If it is a + * ReferenceTypeNode, then the indices are always included, as this is an + * open62541 specific field (not selectable via the attribute id). */ + const UA_Node *refNode = + UA_NODESTORE_GET_SELECTIVE(server, refType, + UA_NODEATTRIBUTESMASK_NODECLASS, + UA_REFERENCETYPESET_NONE, + UA_BROWSEDIRECTION_INVALID); + if(!refNode) return UA_STATUSCODE_BADREFERENCETYPEIDINVALID; @@ -82,7 +103,13 @@ isNodeInTreeNoCircular(UA_Server *server, if(visitedRefs->depth >= UA_MAX_TREE_RECURSE) return false; - const UA_Node *node = UA_NODESTORE_GETFROMREF(server, leafNode); + /* Get the node without attributes (if the NodeStore supports it) and only + * the relevant references in inverse direction */ + const UA_Node *node = + UA_NODESTORE_GETFROMREF_SELECTIVE(server, leafNode, + UA_NODEATTRIBUTESMASK_NONE, + *relevantRefs, + UA_BROWSEDIRECTION_INVERSE); if(!node) return false; @@ -299,7 +326,12 @@ browseRecursiveInner(UA_Server *server, RefTree *rt, UA_UInt16 depth, UA_Boolean if(depth >= UA_MAX_TREE_RECURSE) return UA_STATUSCODE_GOOD; - const UA_Node *node = UA_NODESTORE_GETFROMREF(server, nodeP); + /* We only look at the NodeClass attribute and a subset of the references. + * Get a node with only these elements if the NodeStore supports that. */ + const UA_Node *node = + UA_NODESTORE_GETFROMREF_SELECTIVE(server, nodeP, + UA_NODEATTRIBUTESMASK_NODECLASS, + *refTypes, browseDirection); if(!node) return UA_STATUSCODE_BADNODEIDUNKNOWN; @@ -572,8 +604,19 @@ browseReferences(UA_Server *server, const UA_NodeHead *head, return UA_STATUSCODE_BADINTERNALERROR; } + /* Get the node with additional reference types if we need to lookup the + * TypeDefinition */ + UA_ReferenceTypeSet resultRefs = cp->relevantReferences; + if(bd->resultMask & UA_BROWSERESULTMASK_TYPEDEFINITION) { + resultRefs = UA_ReferenceTypeSet_union(resultRefs, + UA_REFTYPESET(UA_REFERENCETYPEINDEX_HASTYPEDEFINITION)); + resultRefs = UA_ReferenceTypeSet_union(resultRefs, + UA_REFTYPESET(UA_REFERENCETYPEINDEX_HASSUBTYPE)); + } + /* Loop over the ReferenceTypes */ UA_StatusCode retval = UA_STATUSCODE_GOOD; + for(; i < head->referencesSize; ++i) { UA_NodeReferenceKind *rk = &head->references[i]; @@ -595,9 +638,13 @@ browseReferences(UA_Server *server, const UA_NodeHead *head, if(!ref) ref = UA_NodeReferenceKind_iterate(rk, ref); for(;ref; ref = UA_NodeReferenceKind_iterate(rk, ref)) { - /* Get the node if it is not a remote reference */ + /* Get the node (NULL if is a remote reference). Include only the + * ReferenceTypes we are interested in, including those for figuring + * out the TypeDefinition (if that was requested). */ const UA_Node *target = - UA_NODESTORE_GETFROMREF(server, ref->targetId); + UA_NODESTORE_GETFROMREF_SELECTIVE(server, ref->targetId, + resultMask2AttributesMask(bd->resultMask), + resultRefs, bd->browseDirection); /* Test if the node class matches */ if(target && !matchClassMask(target, bd->nodeClassMask)) { @@ -653,7 +700,11 @@ browseWithContinuation(UA_Server *server, UA_Session *session, /* Is the reference type valid? */ if(!UA_NodeId_isNull(&descr->referenceTypeId)) { - const UA_Node *reftype = UA_NODESTORE_GET(server, &descr->referenceTypeId); + const UA_Node *reftype = + UA_NODESTORE_GET_SELECTIVE(server, &descr->referenceTypeId, + UA_NODEATTRIBUTESMASK_NODECLASS, + UA_REFERENCETYPESET_NONE, + UA_BROWSEDIRECTION_INVALID); if(!reftype) { result->statusCode = UA_STATUSCODE_BADREFERENCETYPEIDINVALID; return true; @@ -668,7 +719,11 @@ browseWithContinuation(UA_Server *server, UA_Session *session, } } - const UA_Node *node = UA_NODESTORE_GET(server, &descr->nodeId); + /* Get node with only the selected references and attributes */ + const UA_Node *node = + UA_NODESTORE_GET_SELECTIVE(server, &descr->nodeId, + resultMask2AttributesMask(descr->resultMask), + cp->relevantReferences, descr->browseDirection); if(!node) { result->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN; return true; @@ -990,8 +1045,16 @@ walkBrowsePathElement(UA_Server *server, UA_Session *session, continue; } - /* Local Node. Add to the tree of results at the next depth. */ - const UA_Node *node = UA_NODESTORE_GET(server, ¤t->targets[i].nodeId); + /* Local Node. Add to the tree of results at the next depth. Get only + * the NodeClass + BrowseName attribute and the selected ReferenceTypes + * if the nodestore supports that. */ + const UA_Node *node = + UA_NODESTORE_GET_SELECTIVE(server, ¤t->targets[i].nodeId, + UA_NODEATTRIBUTESMASK_NODECLASS | + UA_NODEATTRIBUTESMASK_BROWSENAME, + refTypes, + elem->isInverse ? UA_BROWSEDIRECTION_INVERSE : + UA_BROWSEDIRECTION_FORWARD); if(!node) continue; @@ -1076,7 +1139,11 @@ Operation_TranslateBrowsePathToNodeIds(UA_Server *server, UA_Session *session, } /* Check if the starting node exists */ - const UA_Node *startingNode = UA_NODESTORE_GET(server, &path->startingNode); + const UA_Node *startingNode = + UA_NODESTORE_GET_SELECTIVE(server, &path->startingNode, + UA_NODEATTRIBUTESMASK_NONE, + UA_REFERENCETYPESET_NONE, + UA_BROWSEDIRECTION_INVALID); if(!startingNode) { result->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN; return; @@ -1141,8 +1208,13 @@ Operation_TranslateBrowsePathToNodeIds(UA_Server *server, UA_Session *session, result->targets = tmpResults; for(size_t k = 0; k < next->size; k++) { - /* Check the BrowseName. It has been filtered only via its hash so far. */ - const UA_Node *node = UA_NODESTORE_GET(server, &next->targets[k].nodeId); + /* Check the BrowseName. It has been filtered only via its hash so far. + * Get only the BrowseName attribute if the nodestore supports that. */ + const UA_Node *node = + UA_NODESTORE_GET_SELECTIVE(server, &next->targets[k].nodeId, + UA_NODEATTRIBUTESMASK_BROWSENAME, + UA_REFERENCETYPESET_NONE, + UA_BROWSEDIRECTION_INVALID); if(!node) continue; UA_Boolean match = UA_QualifiedName_equal(browseNameFilter, &node->head.browseName); diff --git a/src/server/ua_session.c b/src/server/ua_session.c index cee06721ffd..2e11ada49ac 100644 --- a/src/server/ua_session.c +++ b/src/server/ua_session.c @@ -8,6 +8,7 @@ */ #include "ua_session.h" +#include "open62541/types.h" #include "ua_server_internal.h" #ifdef UA_ENABLE_SUBSCRIPTIONS #include "ua_subscription.h" @@ -224,30 +225,31 @@ UA_Server_closeSession(UA_Server *server, const UA_NodeId *sessionId) { UA_StatusCode UA_Server_setSessionParameter(UA_Server *server, const UA_NodeId *sessionId, - const char *name, const UA_Variant *parameter) { + const UA_QualifiedName key, const UA_Variant *value) { UA_LOCK(&server->serviceMutex); UA_Session *session = UA_Server_getSessionById(server, sessionId); UA_StatusCode res = UA_STATUSCODE_BADSESSIONIDINVALID; if(session) res = UA_KeyValueMap_set(&session->params, &session->paramsSize, - name, parameter); + key, value); UA_UNLOCK(&server->serviceMutex); return res; } void UA_Server_deleteSessionParameter(UA_Server *server, const UA_NodeId *sessionId, - const char *name) { + const UA_QualifiedName key) { UA_LOCK(&server->serviceMutex); UA_Session *session = UA_Server_getSessionById(server, sessionId); if(session) - UA_KeyValueMap_delete(&session->params, &session->paramsSize, name); + UA_KeyValueMap_delete(&session->params, &session->paramsSize, key); UA_UNLOCK(&server->serviceMutex); } UA_StatusCode UA_Server_getSessionParameter(UA_Server *server, const UA_NodeId *sessionId, - const char *name, UA_Variant *outParameter) { + const UA_QualifiedName key, + UA_Variant *outParameter) { UA_LOCK(&server->serviceMutex); if(!outParameter) { UA_UNLOCK(&server->serviceMutex); @@ -261,7 +263,7 @@ UA_Server_getSessionParameter(UA_Server *server, const UA_NodeId *sessionId, } const UA_Variant *param = - UA_KeyValueMap_get(session->params, session->paramsSize, name); + UA_KeyValueMap_get(session->params, session->paramsSize, key); if(!param) { UA_UNLOCK(&server->serviceMutex); return UA_STATUSCODE_BADNOTFOUND; @@ -273,9 +275,10 @@ UA_Server_getSessionParameter(UA_Server *server, const UA_NodeId *sessionId, } UA_StatusCode -UA_Server_getSessionScalarParameter(UA_Server *server, const UA_NodeId *sessionId, - const char *name, const UA_DataType *type, - UA_Variant *outParameter) { +UA_Server_getSessionParameter_scalar(UA_Server *server, const UA_NodeId *sessionId, + const UA_QualifiedName key, + const UA_DataType *type, + void *outParameter) { UA_LOCK(&server->serviceMutex); if(!outParameter) { UA_UNLOCK(&server->serviceMutex); @@ -289,41 +292,13 @@ UA_Server_getSessionScalarParameter(UA_Server *server, const UA_NodeId *sessionI } const UA_Variant *param = - UA_KeyValueMap_get(session->params, session->paramsSize, name); + UA_KeyValueMap_get(session->params, session->paramsSize, key); if(!param || !UA_Variant_hasScalarType(param, type)) { UA_UNLOCK(&server->serviceMutex); return UA_STATUSCODE_BADNOTFOUND; } - UA_StatusCode res = UA_Variant_copy(param, outParameter); - UA_UNLOCK(&server->serviceMutex); - return res; -} - -UA_StatusCode -UA_Server_getSessionArrayParameter(UA_Server *server, const UA_NodeId *sessionId, - const char *name, const UA_DataType *type, - UA_Variant *outParameter) { - UA_LOCK(&server->serviceMutex); - if(!outParameter) { - UA_UNLOCK(&server->serviceMutex); - return UA_STATUSCODE_BADINTERNALERROR; - } - - UA_Session *session = UA_Server_getSessionById(server, sessionId); - if(!session) { - UA_UNLOCK(&server->serviceMutex); - return UA_STATUSCODE_BADSESSIONIDINVALID; - } - - const UA_Variant *param = - UA_KeyValueMap_get(session->params, session->paramsSize, name); - if(!param || !UA_Variant_hasArrayType(param, type)) { - UA_UNLOCK(&server->serviceMutex); - return UA_STATUSCODE_BADNOTFOUND; - } - - UA_StatusCode res = UA_Variant_copy(param, outParameter); + UA_StatusCode res = UA_copy(param->data, outParameter, type); UA_UNLOCK(&server->serviceMutex); return res; } diff --git a/src/server/ua_subscription.c b/src/server/ua_subscription.c index 8e72dc84f05..1cddf691bed 100644 --- a/src/server/ua_subscription.c +++ b/src/server/ua_subscription.c @@ -89,9 +89,7 @@ UA_Subscription_delete(UA_Server *server, UA_Subscription *sub) { sub->delayedFreePointers.callback = NULL; sub->delayedFreePointers.application = server; sub->delayedFreePointers.data = NULL; - sub->delayedFreePointers.nextTime = UA_DateTime_nowMonotonic() + 1; - sub->delayedFreePointers.interval = 0; /* Remove the structure */ - UA_Timer_addTimerEntry(&server->timer, &sub->delayedFreePointers, NULL); + UA_EventLoop_addDelayedCallback(server->config.eventLoop, &sub->delayedFreePointers); } UA_MonitoredItem * diff --git a/src/server/ua_subscription.h b/src/server/ua_subscription.h index ddc0b23c647..07387cf50ab 100644 --- a/src/server/ua_subscription.h +++ b/src/server/ua_subscription.h @@ -12,6 +12,7 @@ * Copyright 2019 (c) HMS Industrial Networks AB (Author: Jonas Green) * Copyright 2020 (c) Christian von Arnim, ISW University of Stuttgart (for VDW and umati) * Copyright 2021 (c) Fraunhofer IOSB (Author: Andreas Ebner) + * Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes) */ #ifndef UA_SUBSCRIPTION_H_ @@ -22,7 +23,7 @@ #include #include "ua_session.h" -#include "ua_timer.h" +#include "common/ua_timer.h" #include "ua_util_internal.h" _UA_BEGIN_DECLS @@ -101,7 +102,7 @@ typedef TAILQ_HEAD(NotificationMessageQueue, UA_NotificationMessageEntry) /*****************/ struct UA_MonitoredItem { - UA_TimerEntry delayedFreePointers; + UA_DelayedCallback delayedFreePointers; LIST_ENTRY(UA_MonitoredItem) listEntry; /* Linked list in the Subscription */ UA_MonitoredItem *next; /* Linked list of MonitoredItems directly attached * to a Node. Initialized to ~0 to indicate that the @@ -239,7 +240,7 @@ typedef enum { * may keep Subscriptions intact beyond the Session lifetime. They can then be * re-bound to a new Session with the TransferSubscription Service. */ struct UA_Subscription { - UA_TimerEntry delayedFreePointers; + UA_DelayedCallback delayedFreePointers; LIST_ENTRY(UA_Subscription) serverListEntry; /* Ordered according to the priority byte and round-robin scheduling for * late subscriptions. See ua_session.h. Only set if session != NULL. */ @@ -326,7 +327,7 @@ UA_Server_evaluateWhereClauseContentFilter(UA_Server *server, UA_Session *sessio const UA_ContentFilter *contentFilter, UA_ContentFilterResult *contentFilterResult); #endif - + /* Setting an integer value within bounds */ #define UA_BOUNDEDVALUE_SETWBOUNDS(BOUNDS, SRC, DST) { \ if(SRC > BOUNDS.max) DST = BOUNDS.max; \ diff --git a/src/server/ua_subscription_events.c b/src/server/ua_subscription_events.c index 591d5657b6a..1fd74b2dcd3 100644 --- a/src/server/ua_subscription_events.c +++ b/src/server/ua_subscription_events.c @@ -162,6 +162,29 @@ isValidEvent(UA_Server *server, const UA_NodeId *validEventParent, return isSubtypeOfBaseEvent; } +/* Resolves a variant of type string or boolean into a corresponding status code */ +static UA_StatusCode +resolveBoolean(UA_Variant operand) { + UA_String value; + value = UA_STRING("True"); + if(((operand.type == &UA_TYPES[UA_TYPES_STRING]) && + (UA_String_equal((UA_String *)operand.data, &value))) || + ((operand.type == &UA_TYPES[UA_TYPES_BOOLEAN]) && + (*(UA_Boolean *)operand.data == UA_TRUE))) { + return UA_STATUSCODE_GOOD; + } + value = UA_STRING("False"); + if(((operand.type == &UA_TYPES[UA_TYPES_STRING]) && + (UA_String_equal((UA_String *)operand.data, &value))) || + ((operand.type == &UA_TYPES[UA_TYPES_BOOLEAN]) && + (*(UA_Boolean *)operand.data == UA_FALSE))) { + return UA_STATUSCODE_BADNOMATCH; + } + + /* If the operand can't be resolved, an error is returned */ + return UA_STATUSCODE_BADFILTEROPERANDINVALID; +} + /* Part 4: 7.4.4.5 SimpleAttributeOperand * The clause can point to any attribute of nodes. Either a child of the event * node and also the event type. */ @@ -233,6 +256,7 @@ resolveOperand(UA_Server *server, UA_Session *session, const UA_NodeId *origin, UA_StatusCode res; UA_Variant variant; + UA_Variant_init(&variant); /*SimpleAttributeOperands*/ if(contentFilter->elements[index].filterOperands[nr].content.decoded.type == &UA_TYPES[UA_TYPES_SIMPLEATTRIBUTEOPERAND]) { @@ -246,23 +270,17 @@ resolveOperand(UA_Server *server, UA_Session *session, const UA_NodeId *origin, } else if(contentFilter->elements[index].filterOperands[nr].content.decoded.type == &UA_TYPES[UA_TYPES_LITERALOPERAND]) { variant = ((UA_LiteralOperand *)contentFilter->elements[index] - .filterOperands[nr] - .content.decoded.data) - ->value; + .filterOperands[nr].content.decoded.data)->value; res = UA_STATUSCODE_GOOD; } else if(contentFilter->elements[index].filterOperands[nr].content.decoded.type == &UA_TYPES[UA_TYPES_ELEMENTOPERAND]) { res = evaluateWhereClauseContentFilter( server, session, origin, contentFilter, contentFilterResult, valueResult, (UA_UInt16)((UA_ElementOperand *)contentFilter->elements[index] - .filterOperands[nr] - .content.decoded.data) - ->index); + .filterOperands[nr].content.decoded.data)->index); variant = valueResult[(UA_UInt16)((UA_ElementOperand *)contentFilter->elements[index] - .filterOperands[nr] - .content.decoded.data) - ->index]; + .filterOperands[nr].content.decoded.data)->index]; /*ElementOperands*/ } else { res = UA_STATUSCODE_BADFILTEROPERANDINVALID; @@ -274,6 +292,161 @@ resolveOperand(UA_Server *server, UA_Session *session, const UA_NodeId *origin, return variant; } +static UA_StatusCode +ofTypeOperator(UA_Server *server, UA_Session *session, + const UA_NodeId *eventNode, + const UA_ContentFilter *contentFilter, + UA_ContentFilterResult *contentFilterResult, + UA_Variant* valueResult, UA_UInt16 index, + UA_UInt16 nr, UA_ContentFilterElement *pElement){ + UA_Boolean result = false; + valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + if(pElement->filterOperandsSize != 1) + return UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; + if(pElement->filterOperands[0].content.decoded.type != + &UA_TYPES[UA_TYPES_LITERALOPERAND]) + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + + UA_LiteralOperand *literalOperand = + (UA_LiteralOperand *) pElement->filterOperands[0].content.decoded.data; + if(!UA_Variant_isScalar(&literalOperand->value)) + return UA_STATUSCODE_BADEVENTFILTERINVALID; + + if(literalOperand->value.type != &UA_TYPES[UA_TYPES_NODEID] || literalOperand->value.data == NULL) + return UA_STATUSCODE_BADEVENTFILTERINVALID; + + UA_NodeId *literalOperandNodeId = (UA_NodeId *) literalOperand->value.data; + UA_Variant typeNodeIdVariant; + UA_Variant_init(&typeNodeIdVariant); + UA_StatusCode readStatusCode = + readObjectProperty(server, *eventNode, UA_QUALIFIEDNAME(0, "EventType"), + &typeNodeIdVariant); + if(readStatusCode != UA_STATUSCODE_GOOD) + return readStatusCode; + + if(!UA_Variant_isScalar(&typeNodeIdVariant) || + typeNodeIdVariant.type != &UA_TYPES[UA_TYPES_NODEID] || + typeNodeIdVariant.data == NULL) { + UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, + "EventType has an invalid type."); + UA_Variant_clear(&typeNodeIdVariant); + return UA_STATUSCODE_BADINTERNALERROR; + } + //check if the eventtype-nodeid is equal to the given oftype argument + result = UA_NodeId_equal((UA_NodeId*) typeNodeIdVariant.data, literalOperandNodeId); + //check if the eventtype-nodeid is a subtype of the given oftype argument + if(!result) + result = isNodeInTree_singleRef(server, + (UA_NodeId*) typeNodeIdVariant.data, + literalOperandNodeId, + UA_REFERENCETYPEINDEX_HASSUBTYPE); + UA_Variant_clear(&typeNodeIdVariant); + if(!result) + return UA_STATUSCODE_BADNOMATCH; + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +andOperator(UA_Server *server, UA_Session *session, + const UA_NodeId *eventNode, + const UA_ContentFilter *contentFilter, + UA_ContentFilterResult *contentFilterResult, + UA_Variant* valueResult, UA_UInt16 index, + UA_UInt16 nr, UA_ContentFilterElement *pElement) { + UA_StatusCode firstBoolean_and = resolveBoolean( + resolveOperand(server, session, eventNode, contentFilter, + contentFilterResult, valueResult, index, 0)); + if(firstBoolean_and == UA_STATUSCODE_BADNOMATCH) { + valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + return UA_STATUSCODE_BADNOMATCH; + } + /* Evaluation of second operand */ + UA_StatusCode secondBoolean = resolveBoolean( + resolveOperand(server, session, eventNode, contentFilter, + contentFilterResult, valueResult, index, 1)); + + /* Filteroperator AND */ + if(secondBoolean == UA_STATUSCODE_BADNOMATCH) { + valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + return UA_STATUSCODE_BADNOMATCH; + } else if((firstBoolean_and == UA_STATUSCODE_GOOD) && + (secondBoolean == UA_STATUSCODE_GOOD)) { + valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + return UA_STATUSCODE_GOOD; + } else { + return UA_STATUSCODE_BADFILTERELEMENTINVALID; + } +} + +static UA_StatusCode +orOperator(UA_Server *server, UA_Session *session, + const UA_NodeId *eventNode, + const UA_ContentFilter *contentFilter, + UA_ContentFilterResult *contentFilterResult, + UA_Variant* valueResult, UA_UInt16 index, + UA_UInt16 nr, UA_ContentFilterElement *pElement) { + UA_StatusCode firstBoolean_or = resolveBoolean( + resolveOperand(server, session, eventNode, contentFilter, + contentFilterResult, valueResult, index, 0)); + if(firstBoolean_or == UA_STATUSCODE_GOOD) { + valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + return UA_STATUSCODE_GOOD; + } + /* Evaluation of second operand */ + UA_StatusCode secondBoolean = resolveBoolean( + resolveOperand(server, session, eventNode, contentFilter, + contentFilterResult, valueResult, index, 1)); + + if(secondBoolean == UA_STATUSCODE_GOOD) { + valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + return UA_STATUSCODE_GOOD; + } else if((firstBoolean_or == UA_STATUSCODE_BADNOMATCH) && + (secondBoolean == UA_STATUSCODE_BADNOMATCH)) { + valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + return UA_STATUSCODE_BADNOMATCH; + } else { + return UA_STATUSCODE_BADFILTERELEMENTINVALID; + } +} + +static UA_StatusCode +isNullOperator(UA_Server *server, UA_Session *session, + const UA_NodeId *eventNode, + const UA_ContentFilter *contentFilter, + UA_ContentFilterResult *contentFilterResult, + UA_Variant* valueResult, UA_UInt16 index, + UA_UInt16 nr, UA_ContentFilterElement *pElement) { + /* Checking if operand is NULL. This is done by reducing the operand to a + * variant and then checking if it is empty. */ + UA_Variant operand = + resolveOperand(server, session, eventNode, contentFilter, + contentFilterResult, valueResult, index, 0); + valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + if(!UA_Variant_isEmpty(&operand)) { + return UA_STATUSCODE_BADNOMATCH; + } + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +notOperator(UA_Server *server, UA_Session *session, + const UA_NodeId *eventNode, + const UA_ContentFilter *contentFilter, + UA_ContentFilterResult *contentFilterResult, + UA_Variant* valueResult, UA_UInt16 index, + UA_UInt16 nr, UA_ContentFilterElement *pElement) { + /* Inverting the boolean value of the operand. */ + UA_StatusCode res = resolveBoolean( + resolveOperand(server, session, eventNode, contentFilter, + contentFilterResult, valueResult, index, 0)); + valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + //invert result + if(res == UA_STATUSCODE_GOOD) { + return UA_STATUSCODE_BADNOMATCH; + } + return UA_STATUSCODE_GOOD; +} + static UA_StatusCode evaluateWhereClauseContentFilter(UA_Server *server, UA_Session *session, const UA_NodeId *eventNode, @@ -289,97 +462,72 @@ evaluateWhereClauseContentFilter(UA_Server *server, UA_Session *session, /* The first element needs to be evaluated, this might be linked to other * elements, which are evaluated in these cases. See 7.4.1 in Part 4. */ - UA_ContentFilterElement *pElement = &contentFilter->elements[0]; + UA_ContentFilterElement *pElement = &contentFilter->elements[index]; switch(pElement->filterOperator) { case UA_FILTEROPERATOR_INVIEW: + return UA_STATUSCODE_BADEVENTFILTERINVALID; case UA_FILTEROPERATOR_RELATEDTO: { /* Not allowed for event WhereClause according to 7.17.3 in Part 4 */ return UA_STATUSCODE_BADEVENTFILTERINVALID; } case UA_FILTEROPERATOR_EQUALS: - case UA_FILTEROPERATOR_ISNULL: { - /* Checking if operand is NULL. This is done by reducing the operand to a - * variant and then checking if it is empty. */ - UA_Variant operand = - resolveOperand(server, session, eventNode, contentFilter, - contentFilterResult, valueResult, index, 0); - valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - if(UA_Variant_isEmpty(&operand)) { - contentFilterResult->elementResults[index].statusCode = - UA_STATUSCODE_GOOD; - break; - } + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + case UA_FILTEROPERATOR_ISNULL: contentFilterResult->elementResults[index].statusCode = - UA_STATUSCODE_BADNOMATCH; + isNullOperator(server, session, eventNode, contentFilter, + contentFilterResult, valueResult, index, 0, pElement); break; - } case UA_FILTEROPERATOR_GREATERTHAN: + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; case UA_FILTEROPERATOR_LESSTHAN: + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; case UA_FILTEROPERATOR_GREATERTHANOREQUAL: + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; case UA_FILTEROPERATOR_LESSTHANOREQUAL: + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; case UA_FILTEROPERATOR_LIKE: + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; case UA_FILTEROPERATOR_NOT: + contentFilterResult->elementResults[index].statusCode = + notOperator(server, session, eventNode, contentFilter, + contentFilterResult, valueResult, index, 0, pElement); + break; case UA_FILTEROPERATOR_BETWEEN: + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; case UA_FILTEROPERATOR_INLIST: - case UA_FILTEROPERATOR_AND: - case UA_FILTEROPERATOR_OR: - case UA_FILTEROPERATOR_CAST: - case UA_FILTEROPERATOR_BITWISEAND: - case UA_FILTEROPERATOR_BITWISEOR: return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - - case UA_FILTEROPERATOR_OFTYPE: { - UA_Boolean result = UA_FALSE; - if(pElement->filterOperandsSize != 1) - return UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; - if(pElement->filterOperands[0].content.decoded.type != - &UA_TYPES[UA_TYPES_LITERALOPERAND]) + case UA_FILTEROPERATOR_AND: { + contentFilterResult->elementResults[index].statusCode = + andOperator(server, session, eventNode, contentFilter, + contentFilterResult, valueResult, index, 0, pElement); + break; + case UA_FILTEROPERATOR_OR: + contentFilterResult->elementResults[index].statusCode = + orOperator(server, session, eventNode, contentFilter, + contentFilterResult, valueResult, index, 0, pElement); + break; + case UA_FILTEROPERATOR_CAST: return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - - UA_LiteralOperand *pOperand = - (UA_LiteralOperand *) pElement->filterOperands[0].content.decoded.data; - if(!UA_Variant_isScalar(&pOperand->value)) - return UA_STATUSCODE_BADEVENTFILTERINVALID; - - if(pOperand->value.type != &UA_TYPES[UA_TYPES_NODEID] || - pOperand->value.data == NULL) { - result = UA_FALSE; - } else { - UA_NodeId *pOperandNodeId = (UA_NodeId *) pOperand->value.data; - UA_QualifiedName eventTypeQualifiedName = UA_QUALIFIEDNAME(0, "EventType"); - UA_Variant typeNodeIdVariant; - UA_Variant_init(&typeNodeIdVariant); - UA_StatusCode readStatusCode = - readObjectProperty(server, *eventNode, eventTypeQualifiedName, - &typeNodeIdVariant); - if(readStatusCode != UA_STATUSCODE_GOOD) - return readStatusCode; - - if(!UA_Variant_isScalar(&typeNodeIdVariant) || - typeNodeIdVariant.type != &UA_TYPES[UA_TYPES_NODEID] || - typeNodeIdVariant.data == NULL) { - UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, - "EventType has an invalid type."); - UA_Variant_clear(&typeNodeIdVariant); - return UA_STATUSCODE_BADINTERNALERROR; - } - - result = isNodeInTree_singleRef(server, - (UA_NodeId*) typeNodeIdVariant.data, - pOperandNodeId, - UA_REFERENCETYPEINDEX_HASSUBTYPE); - UA_Variant_clear(&typeNodeIdVariant); - } - - if(result) - return UA_STATUSCODE_GOOD; - else - return UA_STATUSCODE_BADNOMATCH; + case UA_FILTEROPERATOR_BITWISEAND: + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + case UA_FILTEROPERATOR_BITWISEOR: + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + case UA_FILTEROPERATOR_OFTYPE: + contentFilterResult->elementResults[index].statusCode = + ofTypeOperator(server, session, eventNode, contentFilter, + contentFilterResult, valueResult, index, 0, pElement); + break; + default: + return UA_STATUSCODE_BADFILTEROPERATORINVALID; } - break; - default: - return UA_STATUSCODE_BADFILTEROPERATORINVALID; - break; + } + if(valueResult[index].type == &UA_TYPES[UA_TYPES_BOOLEAN]) { + UA_Boolean *result = UA_Boolean_new(); + if(contentFilterResult->elementResults[index].statusCode == UA_STATUSCODE_GOOD) + *result = true; + else + *result = false; + valueResult[index].data = result; } return contentFilterResult->elementResults[index].statusCode; } diff --git a/src/server/ua_subscription_monitoreditem.c b/src/server/ua_subscription_monitoreditem.c index daf864d4b2c..752370139bc 100644 --- a/src/server/ua_subscription_monitoreditem.c +++ b/src/server/ua_subscription_monitoreditem.c @@ -599,9 +599,7 @@ UA_MonitoredItem_delete(UA_Server *server, UA_MonitoredItem *mon) { mon->delayedFreePointers.callback = NULL; mon->delayedFreePointers.application = server; mon->delayedFreePointers.data = NULL; - mon->delayedFreePointers.nextTime = UA_DateTime_nowMonotonic() + 1; - mon->delayedFreePointers.interval = 0; - UA_Timer_addTimerEntry(&server->timer, &mon->delayedFreePointers, NULL); + UA_EventLoop_addDelayedCallback(server->config.eventLoop, &mon->delayedFreePointers); } void diff --git a/src/ua_securechannel.h b/src/ua_securechannel.h index ed883401dc3..718be82c334 100644 --- a/src/ua_securechannel.h +++ b/src/ua_securechannel.h @@ -18,6 +18,7 @@ #include #include "open62541_queue.h" +#include "ua_util_internal.h" #include "ua_connection_internal.h" _UA_BEGIN_DECLS diff --git a/src/ua_types_print.c b/src/ua_types_print.c index 48566da2214..ec4cbb779c1 100644 --- a/src/ua_types_print.c +++ b/src/ua_types_print.c @@ -774,33 +774,45 @@ UA_print(const void *p, const UA_DataType *type, UA_String *output) { ctx.depth = 0; TAILQ_INIT(&ctx.outputs); + /* Allocate before the goto */ + size_t total = 0; + size_t pos = 0; + UA_PrintOutput *out, *out_tmp; + /* Encode */ UA_StatusCode retval = printJumpTable[type->typeKind](&ctx, p, type); + if(retval != UA_STATUSCODE_GOOD) + goto cleanup; + + /* If printing succeeded the output cannot be empty*/ + TAILQ_FOREACH(out, &ctx.outputs, next) + total += out->length; + UA_assert(total > 0); - /* Allocate memory for the output */ - if(retval == UA_STATUSCODE_GOOD) { - size_t total = 0; - UA_PrintOutput *out; - TAILQ_FOREACH(out, &ctx.outputs, next) - total += out->length; + if(output->length == 0) { + /* Allocate memory for the output */ retval = UA_ByteString_allocBuffer((UA_String*)output, total); + } else { + /* Check if the buffer is large enough */ + if(output->length >= total) + output->length = total; + else + retval = UA_STATUSCODE_BADOUTOFMEMORY; } + if(retval != UA_STATUSCODE_GOOD) + goto cleanup; - /* Write the output */ - if(retval == UA_STATUSCODE_GOOD) { - size_t pos = 0; - UA_PrintOutput *out; - TAILQ_FOREACH(out, &ctx.outputs, next) { - memcpy(&output->data[pos], out->data, out->length); - pos += out->length; - } + /* Write to the output buffer */ + TAILQ_FOREACH(out, &ctx.outputs, next) { + memcpy(&output->data[pos], out->data, out->length); + pos += out->length; } + cleanup: /* Free the context */ - UA_PrintOutput *o, *o2; - TAILQ_FOREACH_SAFE(o, &ctx.outputs, next, o2) { - TAILQ_REMOVE(&ctx.outputs, o, next); - UA_free(o); + TAILQ_FOREACH_SAFE(out, &ctx.outputs, next, out_tmp) { + TAILQ_REMOVE(&ctx.outputs, out, next); + UA_free(out); } return retval; } diff --git a/src/ua_util.c b/src/ua_util.c index 6ed211912a6..0380bd835bf 100644 --- a/src/ua_util.c +++ b/src/ua_util.c @@ -211,7 +211,7 @@ UA_ByteString_toBase64(const UA_ByteString *byteString, return UA_STATUSCODE_GOOD; } -UA_StatusCode UA_EXPORT +UA_StatusCode UA_ByteString_fromBase64(UA_ByteString *bs, const UA_String *input) { UA_ByteString_init(bs); @@ -228,11 +228,11 @@ UA_ByteString_fromBase64(UA_ByteString *bs, /* Key Value Map */ UA_StatusCode -UA_KeyValueMap_setQualified(UA_KeyValuePair **map, size_t *mapSize, - const UA_QualifiedName *key, - const UA_Variant *value) { +UA_KeyValueMap_set(UA_KeyValuePair **map, size_t *mapSize, + const UA_QualifiedName key, + const UA_Variant *value) { /* Parameter exists already */ - const UA_Variant *v = UA_KeyValueMap_getQualified(*map, *mapSize, key); + const UA_Variant *v = UA_KeyValueMap_get(*map, *mapSize, key); if(v) { UA_Variant copyV; UA_StatusCode res = UA_Variant_copy(v, ©V); @@ -246,69 +246,43 @@ UA_KeyValueMap_setQualified(UA_KeyValuePair **map, size_t *mapSize, /* Append to the array */ UA_KeyValuePair pair; - pair.key = *key; + pair.key = key; pair.value = *value; return UA_Array_appendCopy((void**)map, mapSize, &pair, &UA_TYPES[UA_TYPES_KEYVALUEPAIR]); } -UA_StatusCode -UA_KeyValueMap_set(UA_KeyValuePair **map, size_t *mapSize, - const char *key, const UA_Variant *value) { - UA_QualifiedName qnKey; - qnKey.namespaceIndex = 0; - qnKey.name = UA_STRING((char*)(uintptr_t)key); - return UA_KeyValueMap_setQualified(map, mapSize, &qnKey, value); -} - const UA_Variant * -UA_KeyValueMap_getQualified(UA_KeyValuePair *map, size_t mapSize, - const UA_QualifiedName *key) { +UA_KeyValueMap_get(UA_KeyValuePair *map, size_t mapSize, + const UA_QualifiedName key) { for(size_t i = 0; i < mapSize; i++) { - if(map[i].key.namespaceIndex == key->namespaceIndex && - UA_String_equal(&map[i].key.name, &key->name)) + if(map[i].key.namespaceIndex == key.namespaceIndex && + UA_String_equal(&map[i].key.name, &key.name)) return &map[i].value; } return NULL; } -const UA_Variant * -UA_KeyValueMap_get(UA_KeyValuePair *map, size_t mapSize, - const char *key) { - UA_QualifiedName qnKey; - qnKey.namespaceIndex = 0; - qnKey.name = UA_STRING((char*)(uintptr_t)key); - return UA_KeyValueMap_getQualified(map, mapSize, &qnKey); -} - /* Returns NULL if the parameter is not defined or not of the right datatype */ -const UA_Variant * +const void * UA_KeyValueMap_getScalar(UA_KeyValuePair *map, size_t mapSize, - const char *key, const UA_DataType *type) { + const UA_QualifiedName key, + const UA_DataType *type) { const UA_Variant *v = UA_KeyValueMap_get(map, mapSize, key); if(!v || !UA_Variant_hasScalarType(v, type)) return NULL; - return v; -} - -const UA_Variant * -UA_KeyValueMap_getArray(UA_KeyValuePair *map, size_t mapSize, - const char *key, const UA_DataType *type) { - const UA_Variant *v = UA_KeyValueMap_get(map, mapSize, key); - if(!v || !UA_Variant_hasArrayType(v, type)) - return NULL; - return v; + return v->data; } void -UA_KeyValueMap_deleteQualified(UA_KeyValuePair **map, size_t *mapSize, - const UA_QualifiedName *key) { +UA_KeyValueMap_delete(UA_KeyValuePair **map, size_t *mapSize, + const UA_QualifiedName key) { UA_KeyValuePair *m = *map; size_t s = *mapSize; for(size_t i = 0; i < s; i++) { - if(m[i].key.namespaceIndex != key->namespaceIndex || - !UA_String_equal(&m[i].key.name, &key->name)) + if(m[i].key.namespaceIndex != key.namespaceIndex || + !UA_String_equal(&m[i].key.name, &key.name)) continue; /* Clean the pair */ @@ -327,15 +301,6 @@ UA_KeyValueMap_deleteQualified(UA_KeyValuePair **map, size_t *mapSize, * array around. Resize never fails when reducing * the size to zero. Reduce the size integer in * any case. */ - return; + break; } } - -void -UA_KeyValueMap_delete(UA_KeyValuePair **map, size_t *mapSize, - const char *key) { - UA_QualifiedName qnKey; - qnKey.namespaceIndex = 0; - qnKey.name = UA_STRING((char*)(uintptr_t)key); - UA_KeyValueMap_deleteQualified(map, mapSize, &qnKey); -} diff --git a/src/ua_util_internal.h b/src/ua_util_internal.h index 9ee206c51d9..949bff6e510 100644 --- a/src/ua_util_internal.h +++ b/src/ua_util_internal.h @@ -185,6 +185,9 @@ isTrue(uint8_t expr) { #define UA_CHECK_STATUS_INFO(STATUSCODE, EVAL, LOGGER, CAT, ...) \ UA_MACRO_EXPAND( \ UA_CHECK_STATUS_LOG(STATUSCODE, EVAL, INFO, LOGGER, CAT, __VA_ARGS__)) +#define UA_CHECK_STATUS_DEBUG(STATUSCODE, EVAL, LOGGER, CAT, ...) \ + UA_MACRO_EXPAND( \ + UA_CHECK_STATUS_LOG(STATUSCODE, EVAL, DEBUG, LOGGER, CAT, __VA_ARGS__)) #define UA_CHECK_MEM_FATAL(PTR, EVAL, LOGGER, CAT, ...) \ UA_MACRO_EXPAND( \ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ed88551f4d8..1bd779e17b3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,6 +23,7 @@ include_directories(${open62541_BUILD_INCLUDE_DIRS}) # ua_server_internal.h include_directories("${PROJECT_SOURCE_DIR}/src") include_directories("${PROJECT_SOURCE_DIR}/src/server") +include_directories("${PROJECT_SOURCE_DIR}/arch/common") # testing_clock.h include_directories("${CMAKE_CURRENT_SOURCE_DIR}/testing-plugins") # #include .h> @@ -65,7 +66,10 @@ endif() # Use different plugins for testing -set(test_plugin_sources ${PROJECT_SOURCE_DIR}/arch/network_tcp.c +set(test_plugin_sources + ${PROJECT_SOURCE_DIR}/arch/network_tcp.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_tcp.c ${PROJECT_SOURCE_DIR}/tests/testing-plugins/testing_clock.c ${PROJECT_SOURCE_DIR}/plugins/ua_log_stdout.c ${PROJECT_SOURCE_DIR}/plugins/ua_config_default.c @@ -156,6 +160,9 @@ endif() add_library(open62541-testplugins OBJECT ${test_plugin_sources} ${PROJECT_SOURCE_DIR}/arch/${UA_ARCHITECTURE}/ua_architecture_functions.c) add_dependencies(open62541-testplugins open62541) target_compile_definitions(open62541-testplugins PRIVATE -DUA_DYNAMIC_LINKING_EXPORT) +if(UA_ENABLE_COVERAGE) + add_coverage(open62541-testplugins) +endif() # Workaround some clang warnings in the uni tests if((NOT ${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") AND (CMAKE_COMPILER_IS_GNUCC OR "x${CMAKE_C_COMPILER_ID}" STREQUAL "xClang")) @@ -255,6 +262,14 @@ add_executable(check_timer check_timer.c $ $ $) +target_link_libraries(check_eventloop ${LIBS}) +add_test_valgrind(eventloop ${TESTS_BINARY_DIR}/check_eventloop) + +add_executable(check_eventloop_tcp check_eventloop_tcp.c $ $) +target_link_libraries(check_eventloop_tcp ${LIBS}) +add_test_valgrind(eventloop_tcp ${TESTS_BINARY_DIR}/check_eventloop_tcp) + # Test Server add_executable(check_accesscontrol server/check_accesscontrol.c $ $) @@ -343,6 +358,10 @@ if(UA_ENABLE_HISTORIZING) add_executable(check_server_historical_data server/check_server_historical_data.c $ $) target_link_libraries(check_server_historical_data ${LIBS}) add_test_valgrind(server_historical_data ${TESTS_BINARY_DIR}/check_server_historical_data) + + add_executable(check_server_historical_data_circular server/check_server_historical_data_circular.c $ $) + target_link_libraries(check_server_historical_data_circular ${LIBS}) + add_test_valgrind(server_historical_data_circular ${TESTS_BINARY_DIR}/check_server_historical_data_circular) endif() add_executable(check_session server/check_session.c $ $) @@ -447,14 +466,22 @@ if(UA_ENABLE_PUBSUB) $) target_link_libraries(check_pubsub_subscribe_encrypted ${LIBS}) add_test_valgrind(check_pubsub_subscribe_encrypted ${TESTS_BINARY_DIR}/check_pubsub_subscribe_encrypted) + + add_executable(check_pubsub_encrypted_rt_levels pubsub/check_pubsub_encrypted_rt_levels.c + $ + $) + target_link_libraries(check_pubsub_encrypted_rt_levels ${LIBS}) + add_test_valgrind(check_pubsub_encrypted_rt_levels ${TESTS_BINARY_DIR}/check_pubsub_encrypted_rt_levels) endif() if (UA_ENABLE_PUBSUB_MONITORING) - add_executable(check_pubsub_subscribe_msgrcvtimeout pubsub/check_pubsub_subscribe_msgrcvtimeout.c - $ - $) - target_link_libraries(check_pubsub_subscribe_msgrcvtimeout ${LIBS}) - add_test_valgrind(check_pubsub_subscribe_msgrcvtimeout ${TESTS_BINARY_DIR}/check_pubsub_subscribe_msgrcvtimeout) + if(NOT UA_ENABLE_PUBSUB_ENCRYPTION) #ToDO: Multiple Receive handling for PubsubEncryption + add_executable(check_pubsub_subscribe_msgrcvtimeout pubsub/check_pubsub_subscribe_msgrcvtimeout.c + $ + $) + target_link_libraries(check_pubsub_subscribe_msgrcvtimeout ${LIBS}) + add_test_valgrind(check_pubsub_subscribe_msgrcvtimeout ${TESTS_BINARY_DIR}/check_pubsub_subscribe_msgrcvtimeout) + endif() endif() if(UA_ENABLE_PUBSUB_ETH_UADP) diff --git a/tests/check_eventloop.c b/tests/check_eventloop.c new file mode 100644 index 00000000000..9679eba4ebe --- /dev/null +++ b/tests/check_eventloop.c @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "testing_clock.h" +#include +#include + +#define N_EVENTS 10000 + +UA_EventLoop *el; +size_t count = 0; + +static void +timerCallback(void *application, void *data) { + count++; +} + +/* Create empty events with different callback intervals */ +static void +createEvents(UA_UInt32 events) { + for(size_t i = 0; i < events; i++) { + UA_Double interval = (UA_Double)i+1; + UA_StatusCode retval = + UA_EventLoop_addCyclicCallback(el, timerCallback, NULL, NULL, interval, NULL, + UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, NULL); + ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); + } +} + +START_TEST(benchmarkTimer) { + el = UA_EventLoop_new(NULL); + createEvents(N_EVENTS); + + clock_t begin = clock(); + for(size_t i = 0; i < 1000; i++) { + UA_DateTime next = UA_EventLoop_run(el, 1); + UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); + } + + clock_t finish = clock(); + double time_spent = (double)(finish - begin) / CLOCKS_PER_SEC; + printf("duration was %f s\n", time_spent); + printf("%lu callbacks\n", (unsigned long)count); + + UA_EventLoop_stop(el); + UA_EventLoop_delete(el); + el = NULL; +} END_TEST + +int main(void) { + Suite *s = suite_create("Test EventLoop"); + TCase *tc = tcase_create("test cases"); + tcase_add_test(tc, benchmarkTimer); + suite_add_tcase(s, tc); + + SRunner *sr = srunner_create(s); + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all (sr, CK_NORMAL); + int number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/check_eventloop_tcp.c b/tests/check_eventloop_tcp.c new file mode 100644 index 00000000000..81027701103 --- /dev/null +++ b/tests/check_eventloop_tcp.c @@ -0,0 +1,256 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include "open62541/types.h" +#include "open62541/types_generated.h" + +#include "testing_clock.h" +#include +#include + +#define N_EVENTS 10000 + +UA_EventLoop *el; + +static void noopCallback(UA_ConnectionManager *cm, uintptr_t connectionId, + void **connectionContext, UA_StatusCode status, + UA_ByteString msg) {} + +START_TEST(listenTCP) { + el = UA_EventLoop_new(UA_Log_Stdout); + + UA_UInt16 port = 4840; + UA_Variant portVar; + UA_Variant_setScalar(&portVar, &port, &UA_TYPES[UA_TYPES_UINT16]); + UA_ConnectionManager *cm = UA_ConnectionManager_TCP_new(UA_STRING("tcpCM")); + cm->connectionCallback = noopCallback; + UA_KeyValueMap_set(&cm->eventSource.params, + &cm->eventSource.paramsSize, + UA_QUALIFIEDNAME(0, "listen-port"), &portVar); + UA_EventLoop_registerEventSource(el, &cm->eventSource); + + UA_EventLoop_start(el); + + for(size_t i = 0; i < 10; i++) { + UA_DateTime next = UA_EventLoop_run(el, 1); + UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); + } + int max_stop_iteration_count = 1000; + int iteration = 0; + /* Stop the EventLoop */ + UA_EventLoop_stop(el); + while(UA_EventLoop_getState(el) != UA_EVENTLOOPSTATE_STOPPED && iteration < max_stop_iteration_count) { + UA_DateTime next = UA_EventLoop_run(el, 1); + UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); + iteration++; + } + UA_EventLoop_delete(el); + el = NULL; +} END_TEST + +static unsigned connCount; +static char *testMsg = "open62541"; +static uintptr_t clientId; +static UA_Boolean received; +static void +connectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId, + void **connectionContext, UA_StatusCode status, + UA_ByteString msg) { + if(*connectionContext != NULL) + clientId = connectionId; + if(msg.length == 0 && status == UA_STATUSCODE_GOOD) + connCount++; + if(status != UA_STATUSCODE_GOOD) { + connCount--; + } + + if(msg.length > 0) { + UA_ByteString rcv = UA_BYTESTRING(testMsg); + ck_assert(UA_String_equal(&msg, &rcv)); + received = true; + } +} + +static void +illegalConnectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId, + void **connectionContext, UA_StatusCode status, + UA_ByteString msg) { + UA_StatusCode rv = UA_EventLoop_run(el, 1); + ck_assert_uint_eq(rv, UA_STATUSCODE_BADINTERNALERROR); + if(*connectionContext != NULL) + clientId = connectionId; + if(msg.length == 0 && status == UA_STATUSCODE_GOOD) + connCount++; + if(status != UA_STATUSCODE_GOOD) + connCount--; + if(msg.length > 0) { + UA_ByteString rcv = UA_BYTESTRING(testMsg); + ck_assert(UA_String_equal(&msg, &rcv)); + received = true; + } +} + +START_TEST(runEventloopFailsIfCalledFromCallback) { + el = UA_EventLoop_new(UA_Log_Stdout); + + UA_UInt16 port = 4840; + UA_Variant portVar; + UA_Variant_setScalar(&portVar, &port, &UA_TYPES[UA_TYPES_UINT16]); + UA_ConnectionManager *cm = UA_ConnectionManager_TCP_new(UA_STRING("tcpCM")); + cm->connectionCallback = illegalConnectionCallback; + UA_KeyValueMap_set(&cm->eventSource.params, + &cm->eventSource.paramsSize, + UA_QUALIFIEDNAME(0, "listen-port"), &portVar); + UA_EventLoop_registerEventSource(el, &cm->eventSource); + + connCount = 0; + UA_EventLoop_start(el); + + /* Open a client connection */ + clientId = 0; + + UA_String targetHost = UA_STRING("localhost"); + UA_KeyValuePair params[2]; + params[0].key = UA_QUALIFIEDNAME(0, "target-port"); + params[0].value = portVar; + params[1].key = UA_QUALIFIEDNAME(0, "target-hostname"); + UA_Variant_setScalar(¶ms[1].value, &targetHost, &UA_TYPES[UA_TYPES_STRING]); + + UA_StatusCode retval = cm->openConnection(cm, 2, params, (void*)0x01); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + for(size_t i = 0; i < 10; i++) { + UA_DateTime next = UA_EventLoop_run(el, 1); + UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); + } + ck_assert(clientId != 0); + ck_assert_uint_eq(connCount, 2); + + /* Send a message from the client */ + received = false; + UA_ByteString snd; + retval = cm->allocNetworkBuffer(cm, clientId, &snd, strlen(testMsg)); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + memcpy(snd.data, testMsg, strlen(testMsg)); + retval = cm->sendWithConnection(cm, clientId, 0, NULL, &snd); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + for(size_t i = 0; i < 10; i++) { + UA_DateTime next = UA_EventLoop_run(el, 1); + UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); + } + ck_assert(received); + + /* Close the connection */ + retval = cm->closeConnection(cm, clientId); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + ck_assert_uint_eq(connCount, 2); + for(size_t i = 0; i < 10; i++) { + UA_DateTime next = UA_EventLoop_run(el, 1); + UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); + } + ck_assert_uint_eq(connCount, 0); + + int max_stop_iteration_count = 1000; + int iteration = 0; + /* Stop the EventLoop */ + UA_EventLoop_stop(el); + while(UA_EventLoop_getState(el) != UA_EVENTLOOPSTATE_STOPPED && iteration < max_stop_iteration_count) { + UA_DateTime next = UA_EventLoop_run(el, 1); + UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); + iteration++; + } + UA_EventLoop_delete(el); + el = NULL; +} END_TEST + +START_TEST(connectTCP) { + el = UA_EventLoop_new(UA_Log_Stdout); + + UA_UInt16 port = 4840; + UA_Variant portVar; + UA_Variant_setScalar(&portVar, &port, &UA_TYPES[UA_TYPES_UINT16]); + UA_ConnectionManager *cm = UA_ConnectionManager_TCP_new(UA_STRING("tcpCM")); + cm->connectionCallback = connectionCallback; + UA_KeyValueMap_set(&cm->eventSource.params, + &cm->eventSource.paramsSize, + UA_QUALIFIEDNAME(0, "listen-port"), &portVar); + UA_EventLoop_registerEventSource(el, &cm->eventSource); + + connCount = 0; + UA_EventLoop_start(el); + + /* Open a client connection */ + clientId = 0; + + UA_String targetHost = UA_STRING("localhost"); + UA_KeyValuePair params[2]; + params[0].key = UA_QUALIFIEDNAME(0, "target-port"); + params[0].value = portVar; + params[1].key = UA_QUALIFIEDNAME(0, "target-hostname"); + UA_Variant_setScalar(¶ms[1].value, &targetHost, &UA_TYPES[UA_TYPES_STRING]); + + UA_StatusCode retval = cm->openConnection(cm, 2, params, (void*)0x01); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + for(size_t i = 0; i < 10; i++) { + UA_DateTime next = UA_EventLoop_run(el, 1); + UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); + } + ck_assert(clientId != 0); + ck_assert_uint_eq(connCount, 2); + + /* Send a message from the client */ + received = false; + UA_ByteString snd; + retval = cm->allocNetworkBuffer(cm, clientId, &snd, strlen(testMsg)); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + memcpy(snd.data, testMsg, strlen(testMsg)); + retval = cm->sendWithConnection(cm, clientId, 0, NULL, &snd); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + for(size_t i = 0; i < 10; i++) { + UA_DateTime next = UA_EventLoop_run(el, 1); + UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); + } + ck_assert(received); + + /* Close the connection */ + retval = cm->closeConnection(cm, clientId); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + ck_assert_uint_eq(connCount, 2); + for(size_t i = 0; i < 10; i++) { + UA_DateTime next = UA_EventLoop_run(el, 1); + UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); + } + ck_assert_uint_eq(connCount, 0); + + /* Stop the EventLoop */ + int max_stop_iteration_count = 1000; + int iteration = 0; + /* Stop the EventLoop */ + UA_EventLoop_stop(el); + while(UA_EventLoop_getState(el) != UA_EVENTLOOPSTATE_STOPPED && iteration < max_stop_iteration_count) { + UA_DateTime next = UA_EventLoop_run(el, 1); + UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); + iteration++; + } + UA_EventLoop_delete(el); + el = NULL; +} END_TEST + +int main(void) { + Suite *s = suite_create("Test TCP EventLoop"); + TCase *tc = tcase_create("test cases"); + tcase_add_test(tc, listenTCP); + tcase_add_test(tc, connectTCP); + tcase_add_test(tc, runEventloopFailsIfCalledFromCallback); + suite_add_tcase(s, tc); + + SRunner *sr = srunner_create(s); + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all (sr, CK_NORMAL); + int number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/check_types_memory.c b/tests/check_types_memory.c index 4748b6ebf28..34b594810b9 100644 --- a/tests/check_types_memory.c +++ b/tests/check_types_memory.c @@ -15,57 +15,6 @@ #include "check.h" -/* Define types to a dummy value if they are not available (e.g. not built with - * NS0 full) */ -#ifndef UA_TYPES_UNION -#define UA_TYPES_UNION UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_HISTORYREADDETAILS -#define UA_TYPES_HISTORYREADDETAILS UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_NOTIFICATIONDATA -#define UA_TYPES_NOTIFICATIONDATA UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_MONITORINGFILTER -#define UA_TYPES_MONITORINGFILTER UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_MONITORINGFILTERRESULT -#define UA_TYPES_MONITORINGFILTERRESULT UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_DATASETREADERMESSAGEDATATYPE -#define UA_TYPES_DATASETREADERMESSAGEDATATYPE UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_WRITERGROUPTRANSPORTDATATYPE -#define UA_TYPES_WRITERGROUPTRANSPORTDATATYPE UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_CONNECTIONTRANSPORTDATATYPE -#define UA_TYPES_CONNECTIONTRANSPORTDATATYPE UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_WRITERGROUPMESSAGEDATATYPE -#define UA_TYPES_WRITERGROUPMESSAGEDATATYPE UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_READERGROUPTRANSPORTDATATYPE -#define UA_TYPES_READERGROUPTRANSPORTDATATYPE UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_PUBLISHEDDATASETSOURCEDATATYPE -#define UA_TYPES_PUBLISHEDDATASETSOURCEDATATYPE UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_DATASETREADERTRANSPORTDATATYPE -#define UA_TYPES_DATASETREADERTRANSPORTDATATYPE UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_DATASETWRITERTRANSPORTDATATYPE -#define UA_TYPES_DATASETWRITERTRANSPORTDATATYPE UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_SUBSCRIBEDDATASETDATATYPE -#define UA_TYPES_SUBSCRIBEDDATASETDATATYPE UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_READERGROUPMESSAGEDATATYPE -#define UA_TYPES_READERGROUPMESSAGEDATATYPE UA_TYPES_COUNT -#endif -#ifndef UA_TYPES_DATASETWRITERMESSAGEDATATYPE -#define UA_TYPES_DATASETWRITERMESSAGEDATATYPE UA_TYPES_COUNT -#endif - START_TEST(newAndEmptyObjectShallBeDeleted) { // given void *obj = UA_new(&UA_TYPES[_i]); @@ -106,18 +55,6 @@ START_TEST(arrayCopyShallMakeADeepCopy) { END_TEST START_TEST(encodeShallYieldDecode) { - /* floating point types may change the representaton due to several possible NaN values. */ - if(_i != UA_TYPES_FLOAT || _i != UA_TYPES_DOUBLE || - _i != UA_TYPES_CREATESESSIONREQUEST || _i != UA_TYPES_CREATESESSIONRESPONSE || - _i != UA_TYPES_VARIABLEATTRIBUTES || _i != UA_TYPES_READREQUEST -#ifdef UA_ENABLE_SUBSCRIPTIONS - || - _i != UA_TYPES_MONITORINGPARAMETERS || _i != UA_TYPES_MONITOREDITEMCREATERESULT || - _i != UA_TYPES_CREATESUBSCRIPTIONREQUEST || _i != UA_TYPES_CREATESUBSCRIPTIONRESPONSE -#endif - ) - return; - // given UA_ByteString msg1, msg2; void *obj1 = UA_new(&UA_TYPES[_i]); @@ -154,6 +91,16 @@ START_TEST(encodeShallYieldDecode) { UA_TYPES[_i].typeId.identifier.numeric); ck_assert(UA_order(obj1, obj2, &UA_TYPES[_i]) == UA_ORDER_EQ); + // pretty-print the value +#ifdef UA_ENABLE_TYPEDESCRIPTION + UA_Byte staticBuf[4096]; + UA_String buf; + buf.data = staticBuf; + buf.length = 4096; + retval = UA_print(obj2, &UA_TYPES[_i], &buf); + ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); +#endif + // finally UA_delete(obj1, &UA_TYPES[_i]); UA_delete(obj2, &UA_TYPES[_i]); diff --git a/tests/nodeset-compiler/CMakeLists.txt b/tests/nodeset-compiler/CMakeLists.txt index 5f0c8e2ca3d..5720a2c2f19 100644 --- a/tests/nodeset-compiler/CMakeLists.txt +++ b/tests/nodeset-compiler/CMakeLists.txt @@ -45,7 +45,7 @@ if(UA_NAMESPACE_ZERO STREQUAL "FULL") ua_generate_nodeset_and_datatypes( NAME "tests-plc" # PLCopen does not define custom types. Only generate the nodeset - FILE_NS "${open62541_NODESET_DIR}/PLCopen/Opc.Ua.Plc.NodeSet2.xml" + FILE_NS "${open62541_NODESET_DIR}/PLCopen/Opc.Ua.PLCopen.NodeSet2_V1.02.xml" # PLCopen depends on the di nodeset, which must be generated before OUTPUT_DIR "${GENERATE_OUTPUT_DIR}" DEPENDS "tests-di" diff --git a/tests/pubsub/check_pubsub_encrypted_rt_levels.c b/tests/pubsub/check_pubsub_encrypted_rt_levels.c new file mode 100644 index 00000000000..e3fb968e578 --- /dev/null +++ b/tests/pubsub/check_pubsub_encrypted_rt_levels.c @@ -0,0 +1,843 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2020 -2021 Kalycito Infotech Private Limited (Author: Keerthivasan) + */ + +#include +#include +#include +#include "open62541/server_pubsub.h" +#include + +#include "ua_pubsub.h" +#include "ua_pubsub_networkmessage.h" + +#include +#include + +UA_Server *server = NULL; +UA_NodeId connectionIdentifier, publishedDataSetIdent, writerGroupIdent, dataSetWriterIdent, dataSetFieldIdent, dataSetFieldIdent1, readerGroupIdentifier, readerIdentifier; + +UA_UInt32 *subValue; +UA_DataValue *subDataValueRT; +UA_UInt32 *subValue1; +UA_DataValue *subDataValueRT1; +UA_NodeId subNodeId; +UA_NodeId subNodeId1; +UA_NodeId pubNodeId; +UA_NodeId pubNodeId1; +#define UA_AES128CTR_SIGNING_KEY_LENGTH 32 +#define UA_AES128CTR_KEY_LENGTH 16 +#define UA_AES128CTR_KEYNONCE_LENGTH 4 + +UA_Byte signingKeyPub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0}; +UA_Byte encryptingKeyPub[UA_AES128CTR_KEY_LENGTH] = {0}; +UA_Byte keyNoncePub[UA_AES128CTR_KEYNONCE_LENGTH] = {0}; + +typedef struct { + UA_ByteString *buffer; +} UA_ReceiveContext; + +static UA_StatusCode +addMinimalPubSubConfiguration(void){ + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + /* Add one PubSubConnection */ + UA_PubSubConnectionConfig connectionConfig; + memset(&connectionConfig, 0, sizeof(connectionConfig)); + connectionConfig.name = UA_STRING("UDP-UADP Connection 1"); + connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); + connectionConfig.enabled = UA_TRUE; + UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; + UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); + connectionConfig.publisherId.numeric = 2234; + retVal = UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); + if(retVal != UA_STATUSCODE_GOOD) + return retVal; + + /* Add one PublishedDataSet */ + UA_PublishedDataSetConfig publishedDataSetConfig; + memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); + publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; + publishedDataSetConfig.name = UA_STRING("Demo PDS"); + /* Add one DataSetField to the PDS */ + UA_AddPublishedDataSetResult addResult = UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent); + return addResult.addResult; +} + +static void setup(void) { + server = UA_Server_new(); + UA_ServerConfig *config = UA_Server_getConfig(server); + UA_ServerConfig_setDefault(config); + /* Instantiate the PubSub SecurityPolicy */ + config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*) + UA_malloc(sizeof(UA_PubSubSecurityPolicy)); + config->pubSubConfig.securityPoliciesSize = 1; + UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[0], + &config->logger); + UA_ServerConfig_addPubSubTransportLayer(config, UA_PubSubTransportLayerUDPMP()); + UA_Server_run_startup(server); +} + +static void teardown(void) { + UA_Server_run_shutdown(server); + UA_Server_delete(server); +} + +static UA_StatusCode +recvTestFun(UA_PubSubChannel *channel, void *context, const UA_ByteString *buffer) { + UA_ReceiveContext *ctx = (UA_ReceiveContext*)context; + memcpy(ctx->buffer->data, buffer->data, buffer->length); + ctx->buffer->length = buffer->length; + return UA_STATUSCODE_GOOD; +} + +static void receiveSingleMessageRT(UA_PubSubConnection *connection, UA_ReaderGroup *readerGroup) { + UA_ByteString buffer; + UA_DataSetReader *dataSetReader = LIST_FIRST(&readerGroup->readers); + if (UA_ByteString_allocBuffer(&buffer, 512) != UA_STATUSCODE_GOOD) { + ck_abort_msg("Message buffer allocation failed!"); + } + + if(!connection->channel) { + ck_abort_msg("No connection established"); + return; + } + + UA_ReceiveContext testCtx = {&buffer}; + UA_StatusCode retval = connection->channel->receive(connection->channel, NULL, recvTestFun, &testCtx, 1000000); + if(retval != UA_STATUSCODE_GOOD || buffer.length == 0) { + buffer.length = 512; + UA_ByteString_clear(&buffer); + ck_abort_msg("Expected message not received!"); + } + + UA_NetworkMessage currentNetworkMessage; + memset(¤tNetworkMessage, 0, sizeof(UA_NetworkMessage)); + size_t payLoadPosition = 0; + UA_NetworkMessage_decodeHeaders(&buffer, &payLoadPosition, ¤tNetworkMessage); + UA_ServerConfig *config = UA_Server_getConfig(server); + verifyAndDecryptNetworkMessage(&config->logger, + &buffer, + &payLoadPosition, + ¤tNetworkMessage, + readerGroup); + UA_NetworkMessage_clear(¤tNetworkMessage); + size_t currentPosition = 0; + /* Decode only the necessary offset and update the networkMessage */ + if(UA_NetworkMessage_updateBufferedNwMessage(&dataSetReader->bufferedMessage, &buffer, ¤tPosition) != UA_STATUSCODE_GOOD) { + ck_abort_msg("PubSub receive. Unknown field type!"); + } + + /* Check the decoded message is the expected one */ + if((dataSetReader->bufferedMessage.nm->groupHeader.writerGroupId != dataSetReader->config.writerGroupId) || + (*dataSetReader->bufferedMessage.nm->payloadHeader.dataSetPayloadHeader.dataSetWriterIds != dataSetReader->config.dataSetWriterId)) { + ck_abort_msg("PubSub receive. Unknown message received. Will not be processed."); + } + + UA_ReaderGroup *rg = + UA_ReaderGroup_findRGbyId(server, dataSetReader->linkedReaderGroup); + + UA_DataSetReader_process(server, rg, dataSetReader, + dataSetReader->bufferedMessage.nm->payload.dataSetPayload.dataSetMessages); + + /* Delete the payload value of every dsf's decoded */ + UA_DataSetMessage *dsm = dataSetReader->bufferedMessage.nm->payload.dataSetPayload.dataSetMessages; + if(dsm->header.fieldEncoding == UA_FIELDENCODING_VARIANT) { + for(UA_UInt16 i = 0; i < dsm->data.keyFrameData.fieldCount; i++) { + UA_Variant_clear(&dsm->data.keyFrameData.dataSetFields[i].value); + } + } + + UA_ByteString_clear(&buffer); +} + +/* If the external data source is written over the information model, the + * externalDataWriteCallback will be triggered. The user has to take care and assure + * that the write leads not to synchronization issues and race conditions. */ +static UA_StatusCode +externalDataWriteCallback(UA_Server *serverLocal, const UA_NodeId *sessionId, + void *sessionContext, const UA_NodeId *nodeId, + void *nodeContext, const UA_NumericRange *range, + const UA_DataValue *data){ + if(UA_NodeId_equal(nodeId, &subNodeId)){ + memcpy(subValue, data->value.data, sizeof(UA_UInt32)); + } + + if(UA_NodeId_equal(nodeId, &subNodeId1)){ + memcpy(subValue1, data->value.data, sizeof(UA_UInt32)); + } + + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +externalDataReadNotificationCallback(UA_Server *serverLocal, const UA_NodeId *sessionId, + void *sessionContext, const UA_NodeId *nodeid, + void *nodeContext, const UA_NumericRange *range){ + //allow read without any preparation + return UA_STATUSCODE_GOOD; +} + +START_TEST(SetupInvalidPubSubConfig) { + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); + UA_WriterGroupConfig writerGroupConfig; + memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); + writerGroupConfig.name = UA_STRING("Demo WriterGroup"); + writerGroupConfig.publishingInterval = 100; + writerGroupConfig.enabled = UA_FALSE; + writerGroupConfig.writerGroupId = 100; + writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; + writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; + UA_ServerConfig *config = UA_Server_getConfig(server); + writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; + writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; + UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); + wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); + writerGroupConfig.messageSettings.content.decoded.data = wgm; + writerGroupConfig.messageSettings.content.decoded.type = + &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; + writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; + ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); + UA_UadpWriterGroupMessageDataType_delete(wgm); + /* Add the encryption key informaton */ + UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub}; + UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub}; + UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub}; + UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn); + UA_DataSetFieldConfig dsfConfig; + memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); + // Create Variant and configure as DataSetField source + UA_VariableAttributes attributes = UA_VariableAttributes_default; + UA_UInt32 *intValue = UA_UInt32_new(); + *intValue = (UA_UInt32) 1000; + UA_Variant variant; + memset(&variant, 0, sizeof(UA_Variant)); + UA_Variant_setScalar(&variant, intValue, &UA_TYPES[UA_TYPES_UINT32]); + attributes.value = variant; + UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 1000), + UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), + UA_QUALIFIEDNAME(1, "variable"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), + attributes, NULL, NULL); + dsfConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, 1000); + dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; + /* Not using static value source */ + ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD); + UA_DataSetWriterConfig dataSetWriterConfig; + memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); + dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); + dataSetWriterConfig.dataSetWriterId = 62541; + /* UA_Server_addDataSetWriter fails because fields in PDS is not RT capable */ + ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_BADCONFIGURATIONERROR); + /* Reader Group */ + UA_ReaderGroupConfig readerGroupConfig; + memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig)); + readerGroupConfig.name = UA_STRING ("ReaderGroup Test"); + readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; + readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; + readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; + retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, + &readerGroupIdentifier); + // TODO security token not necessary for readergroup (extracted from security-header) + UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + /* Data Set Reader */ + UA_DataSetReaderConfig readerConfig; + memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig)); + readerConfig.name = UA_STRING ("DataSetReader Test"); + UA_UInt16 publisherIdentifier = 2234; + readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16]; + readerConfig.publisherId.data = &publisherIdentifier; + readerConfig.writerGroupId = 100; + readerConfig.dataSetWriterId = 62541; + readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; + readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; + UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); + dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); + readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; + /* Setting up Meta data configuration in DataSetReader for DateTime DataType */ + UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; + /* FilltestMetadata function in subscriber implementation */ + UA_DataSetMetaDataType_init(pMetaData); + pMetaData->name = UA_STRING("DataSet Test"); + /* Static definition of number of fields size to 1 to create one + targetVariable */ + pMetaData->fieldsSize = 1; + pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, + &UA_TYPES[UA_TYPES_FIELDMETADATA]); + /* DateTime DataType */ + UA_FieldMetaData_init(&pMetaData->fields[0]); + UA_NodeId_copy(&UA_TYPES[UA_TYPES_DATETIME].typeId, + &pMetaData->fields[0].dataType); + pMetaData->fields[0].builtInType = UA_NS0ID_DATETIME; + pMetaData->fields[0].valueRank = -1; /* scalar */ + + /* Add Subscribed Variables */ + UA_NodeId folderId; + UA_NodeId newnodeId; + UA_String folderName = readerConfig.dataSetMetaData.name; + UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; + UA_QualifiedName folderBrowseName; + if (folderName.length > 0) { + oAttr.displayName.locale = UA_STRING ("en-US"); + oAttr.displayName.text = folderName; + folderBrowseName.namespaceIndex = 1; + folderBrowseName.name = folderName; + } + else { + oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); + folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); + } + + UA_Server_addObjectNode (server, UA_NODEID_NULL, + UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), + UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), + folderBrowseName, UA_NODEID_NUMERIC (0, + UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); + /* Variable to subscribe data */ + UA_VariableAttributes vAttr = UA_VariableAttributes_default; + vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed DateTime"); + vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed DateTime"); + vAttr.dataType = UA_TYPES[UA_TYPES_DATETIME].typeId; + retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 50002), + folderId, + UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed DateTime"), + UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1; + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *) + UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable)); + + /* For creating Targetvariable */ + UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = subNodeId; + + retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig, + &readerIdentifier); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + UA_NodeId readerIdentifier2; + retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig, + &readerIdentifier2); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); + + UA_FieldTargetDataType_clear(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); + UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); + UA_free(readerConfig.dataSetMetaData.fields); + UA_Variant_clear(&variant); + + ck_assert(UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_BADNOTIMPLEMENTED); // Multiple DSR not supported + + ck_assert(UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD); + retVal = UA_Server_removeDataSetReader(server, readerIdentifier2); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + + ck_assert(UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_BADNOTSUPPORTED); // DateTime not supported + ck_assert(UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD); +} END_TEST + +START_TEST(PublishAndSubscribeSingleFieldWithFixedOffsets) { + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); + UA_PubSubConnection *connection = UA_PubSubConnection_findConnectionbyId(server, connectionIdentifier); + UA_WriterGroupConfig writerGroupConfig; + memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); + writerGroupConfig.name = UA_STRING("Demo WriterGroup"); + writerGroupConfig.publishingInterval = 100; + writerGroupConfig.enabled = UA_FALSE; + writerGroupConfig.writerGroupId = 100; + writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; + writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; + UA_ServerConfig *config = UA_Server_getConfig(server); + writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; + writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; + UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); + wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); + writerGroupConfig.messageSettings.content.decoded.data = wgm; + writerGroupConfig.messageSettings.content.decoded.type = + &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; + writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; + ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); + UA_UadpWriterGroupMessageDataType_delete(wgm); + /* Add the encryption key informaton */ + UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub}; + UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub}; + UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub}; + UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn); + UA_DataSetFieldConfig dsfConfig; + memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); + // Create Variant and configure as DataSetField source + UA_UInt32 *intValue = UA_UInt32_new(); + *intValue = 1000; + UA_DataValue *dataValue = UA_DataValue_new(); + UA_Variant_setScalar(&dataValue->value, intValue, &UA_TYPES[UA_TYPES_UINT32]); + dsfConfig.field.variable.fieldNameAlias = UA_STRING("Published Int32"); + dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; + dsfConfig.field.variable.rtValueSource.staticValueSource = &dataValue; + dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; + ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD); + + /* Add dataset writer */ + UA_DataSetWriterConfig dataSetWriterConfig; + memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); + dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); + dataSetWriterConfig.dataSetWriterId = 62541; + ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); + /* Reader Group */ + UA_ReaderGroupConfig readerGroupConfig; + memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig)); + readerGroupConfig.name = UA_STRING ("ReaderGroup Test"); + readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; + readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; + readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; + retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, + &readerGroupIdentifier); + // TODO security token not necessary for readergroup (extracted from security-header) + UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + /* Data Set Reader */ + UA_DataSetReaderConfig readerConfig; + memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig)); + readerConfig.name = UA_STRING ("DataSetReader Test"); + UA_UInt16 publisherIdentifier = 2234; + readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16]; + readerConfig.publisherId.data = &publisherIdentifier; + readerConfig.writerGroupId = 100; + readerConfig.dataSetWriterId = 62541; + readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; + readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; + UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); + dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); + readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; + /* Setting up Meta data configuration in DataSetReader for DateTime DataType */ + UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; + /* FilltestMetadata function in subscriber implementation */ + UA_DataSetMetaDataType_init(pMetaData); + pMetaData->name = UA_STRING("DataSet Test"); + /* Static definition of number of fields size to 1 to create one + targetVariable */ + pMetaData->fieldsSize = 1; + pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, + &UA_TYPES[UA_TYPES_FIELDMETADATA]); + /* UInt32 DataType */ + UA_FieldMetaData_init(&pMetaData->fields[0]); + UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId, + &pMetaData->fields[0].dataType); + pMetaData->fields[0].builtInType = UA_NS0ID_UINT32; + pMetaData->fields[0].valueRank = -1; /* scalar */ + + /* Add Subscribed Variables */ + UA_NodeId folderId; + UA_String folderName = readerConfig.dataSetMetaData.name; + UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; + UA_QualifiedName folderBrowseName; + if (folderName.length > 0) { + oAttr.displayName.locale = UA_STRING ("en-US"); + oAttr.displayName.text = folderName; + folderBrowseName.namespaceIndex = 1; + folderBrowseName.name = folderName; + } + else { + oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); + folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); + } + + UA_Server_addObjectNode (server, UA_NODEID_NULL, + UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), + UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), + folderBrowseName, UA_NODEID_NUMERIC (0, + UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); + /* Variable to subscribe data */ + UA_VariableAttributes vAttr = UA_VariableAttributes_default; + vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); + vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); + vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; + retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 50002), + folderId, + UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed UInt32"), + UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &subNodeId); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + + subValue = UA_UInt32_new(); + subDataValueRT = UA_DataValue_new(); + subDataValueRT->hasValue = UA_TRUE; + UA_Variant_setScalar(&subDataValueRT->value, subValue, &UA_TYPES[UA_TYPES_UINT32]); + /* Set the value backend of the above create node to 'external value source' */ + UA_ValueBackend valueBackend; + valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; + valueBackend.backend.external.value = &subDataValueRT; + valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; + valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; + UA_Server_setVariableNode_valueBackend(server, subNodeId, valueBackend); + + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1; + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *) + UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable)); + + /* For creating Targetvariable */ + UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = subNodeId; + + retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig, + &readerIdentifier); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + + UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); + UA_FieldTargetDataType_clear(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); + UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); + UA_free(readerConfig.dataSetMetaData.fields); + + ck_assert(UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD); + ck_assert(UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent) == UA_STATUSCODE_GOOD); + ck_assert(UA_Server_setWriterGroupOperational(server, writerGroupIdent) == UA_STATUSCODE_GOOD); + + ck_assert(UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD); + ck_assert(UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD); + + UA_ReaderGroup *readerGroup = UA_ReaderGroup_findRGbyId(server, readerGroupIdentifier); + receiveSingleMessageRT(connection, readerGroup); + /* Read data received by the Subscriber */ + UA_Variant *subscribedNodeData = UA_Variant_new(); + retVal = UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 50002), subscribedNodeData); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + + ck_assert((*(UA_UInt32 *)subscribedNodeData->data) == 1000); + UA_Variant_clear(subscribedNodeData); + UA_free(subscribedNodeData); + ck_assert(UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD); + UA_DataValue_delete(dataValue); + UA_free(subValue); + UA_free(subDataValueRT); +} END_TEST + + +START_TEST(PublishPDSWithMultipleFieldsAndSubscribeFixedSize) { + UA_StatusCode retVal = UA_STATUSCODE_GOOD; + ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); + UA_PubSubConnection *connection = UA_PubSubConnection_findConnectionbyId(server, connectionIdentifier); + /* Add Subscribed Variables */ + UA_NodeId folderId1; + UA_String folderName1 = UA_STRING("PubNodes"); + UA_ObjectAttributes oAttr1 = UA_ObjectAttributes_default; + UA_QualifiedName folderBrowseName1; + if (folderName1.length > 0) { + oAttr1.displayName.locale = UA_STRING ("en-US"); + oAttr1.displayName.text = folderName1; + folderBrowseName1.namespaceIndex = 1; + folderBrowseName1.name = folderName1; + } + else { + oAttr1.displayName = UA_LOCALIZEDTEXT ("en-US", "Published Variables"); + folderBrowseName1 = UA_QUALIFIEDNAME (1, "Published Variables"); + } + + UA_Server_addObjectNode (server, UA_NODEID_NULL, + UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), + UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), + folderBrowseName1, UA_NODEID_NUMERIC (0, + UA_NS0ID_BASEOBJECTTYPE), oAttr1, NULL, &folderId1); + UA_VariableAttributes vAttr = UA_VariableAttributes_default; + UA_UInt32 value = 0; + vAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; + UA_Variant_setScalar(&vAttr.value, &value, &UA_TYPES[UA_TYPES_UINT32]); + vAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Published variable"); + vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; + retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 60000), + folderId1, + UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed DateTime"), + UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &pubNodeId); + retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 60001), + folderId1, + UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed1 DateTime"), + UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &pubNodeId1); + UA_DataSetFieldConfig dsfConfig; + memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); + UA_UInt32 *intValue = UA_UInt32_new(); + *intValue = 1000; + UA_DataValue *dataValue = UA_DataValue_new(); + UA_Variant_setScalar(&dataValue->value, intValue, &UA_TYPES[UA_TYPES_UINT32]); + dataValue->hasValue = true; + dsfConfig.field.variable.fieldNameAlias = UA_STRING("Published UInt32"); + dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; + + /* Set the value backend of the above create node to 'external value source' */ + UA_ValueBackend valueBackend; + valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; + valueBackend.backend.external.value = &dataValue; + valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; + valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; + UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, 60000), valueBackend); + dsfConfig.field.variable.rtValueSource.rtInformationModelNode = true; + dsfConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, 60000); + ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, NULL).result == UA_STATUSCODE_GOOD); + + UA_DataSetFieldConfig dsfConfig1; + memset(&dsfConfig1, 0, sizeof(UA_DataSetFieldConfig)); + UA_UInt32 *intValue1 = UA_UInt32_new(); + *intValue1 = 2000; + UA_DataValue *dataValue1 = UA_DataValue_new(); + UA_Variant_setScalar(&dataValue1->value, intValue1, &UA_TYPES[UA_TYPES_UINT32]); + dataValue->hasValue = true; + dsfConfig1.field.variable.fieldNameAlias = UA_STRING("Published1 UInt32"); + dsfConfig1.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; + /* Set the value backend of the above create node to 'external value source' */ + UA_ValueBackend valueBackend1; + valueBackend1.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; + valueBackend1.backend.external.value = &dataValue1; + valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback; + valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback; + UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, 60001), valueBackend1); + dsfConfig1.field.variable.rtValueSource.rtInformationModelNode = true; + dsfConfig1.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, 60001); + ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig1, NULL).result == UA_STATUSCODE_GOOD); + + UA_WriterGroupConfig writerGroupConfig; + memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); + writerGroupConfig.name = UA_STRING("Demo WriterGroup"); + writerGroupConfig.publishingInterval = 100; + writerGroupConfig.enabled = UA_FALSE; + writerGroupConfig.writerGroupId = 100; + writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; + writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; + UA_ServerConfig *config = UA_Server_getConfig(server); + writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; + writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; + UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); + wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); + writerGroupConfig.messageSettings.content.decoded.data = wgm; + writerGroupConfig.messageSettings.content.decoded.type = + &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; + writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; + ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); + UA_UadpWriterGroupMessageDataType_delete(wgm); + /* Add the encryption key informaton */ + UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub}; + UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub}; + UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub}; + UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn); + UA_DataSetWriterConfig dataSetWriterConfig; + memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); + dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); + dataSetWriterConfig.dataSetWriterId = 62541; + ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); + + + /* Reader Group */ + UA_ReaderGroupConfig readerGroupConfig; + memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig)); + readerGroupConfig.name = UA_STRING ("ReaderGroup Test"); + readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; + readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; + readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; + retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, + &readerGroupIdentifier); + // TODO security token not necessary for readergroup (extracted from security-header) + UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + /* Data Set Reader */ + UA_DataSetReaderConfig readerConfig; + memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig)); + readerConfig.name = UA_STRING ("DataSetReader Test"); + UA_UInt16 publisherIdentifier = 2234; + readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16]; + readerConfig.publisherId.data = &publisherIdentifier; + readerConfig.writerGroupId = 100; + readerConfig.dataSetWriterId = 62541; + readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; + readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; + UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); + dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | + (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); + readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; + /* Setting up Meta data configuration in DataSetReader for DateTime DataType */ + UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; + /* FilltestMetadata function in subscriber implementation */ + UA_DataSetMetaDataType_init(pMetaData); + pMetaData->name = UA_STRING("DataSet Test"); + /* Static definition of number of fields size to 1 to create one + targetVariable */ + pMetaData->fieldsSize = 2; + pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, + &UA_TYPES[UA_TYPES_FIELDMETADATA]); + /* UInt32 DataType */ + UA_FieldMetaData_init(&pMetaData->fields[0]); + UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId, + &pMetaData->fields[0].dataType); + pMetaData->fields[0].builtInType = UA_NS0ID_UINT32; + pMetaData->fields[0].valueRank = -1; /* scalar */ + + UA_FieldMetaData_init(&pMetaData->fields[1]); + UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId, + &pMetaData->fields[1].dataType); + pMetaData->fields[1].builtInType = UA_NS0ID_UINT32; + pMetaData->fields[1].valueRank = -1; /* scalar */ + + /* Add Subscribed Variables */ + UA_NodeId folderId; + UA_String folderName = readerConfig.dataSetMetaData.name; + UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; + UA_QualifiedName folderBrowseName; + if (folderName.length > 0) { + oAttr.displayName.locale = UA_STRING ("en-US"); + oAttr.displayName.text = folderName; + folderBrowseName.namespaceIndex = 1; + folderBrowseName.name = folderName; + } + else { + oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); + folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); + } + + UA_Server_addObjectNode (server, UA_NODEID_NULL, + UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), + UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), + folderBrowseName, UA_NODEID_NUMERIC (0, + UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); + /* Variable to subscribe data */ + vAttr = UA_VariableAttributes_default; + vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); + vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); + vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; + retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 60003), + folderId1, + UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed UInt32"), + UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &subNodeId); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + + subValue = UA_UInt32_new(); + subDataValueRT = UA_DataValue_new(); + subDataValueRT->hasValue = UA_TRUE; + UA_Variant_setScalar(&subDataValueRT->value, subValue, &UA_TYPES[UA_TYPES_UINT32]); + /* Set the value backend of the above create node to 'external value source' */ + valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; + valueBackend.backend.external.value = &subDataValueRT; + valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; + valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; + UA_Server_setVariableNode_valueBackend(server, subNodeId, valueBackend); + + vAttr = UA_VariableAttributes_default; + vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed1 UInt32"); + vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed1 UInt32"); + vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; + retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 60004), + folderId, + UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed1 UInt32"), + UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &subNodeId1); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + + subValue1 = UA_UInt32_new(); + subDataValueRT1 = UA_DataValue_new(); + subDataValueRT1->hasValue = UA_TRUE; + UA_Variant_setScalar(&subDataValueRT1->value, subValue1, &UA_TYPES[UA_TYPES_UINT32]); + /* Set the value backend of the above create node to 'external value source' */ + valueBackend1.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; + valueBackend1.backend.external.value = &subDataValueRT1; + valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback; + valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback; + UA_Server_setVariableNode_valueBackend(server, subNodeId1, valueBackend1); + + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 2; + UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable*) UA_calloc(2, sizeof(UA_FieldTargetVariable)); + UA_FieldTargetDataType_init(&targetVars[0].targetVariable); + targetVars[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; + targetVars[0].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, 60003); + UA_FieldTargetDataType_init(&targetVars[1].targetVariable); + targetVars[1].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; + targetVars[1].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, 60004); + readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = targetVars; + + retVal = UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, + &readerIdentifier); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + + UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); + UA_FieldTargetDataType_clear(&targetVars[0].targetVariable); + UA_FieldTargetDataType_clear(&targetVars[1].targetVariable); + UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); + UA_free(readerConfig.dataSetMetaData.fields); + + ck_assert(UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD); + ck_assert(UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent) == UA_STATUSCODE_GOOD); + ck_assert(UA_Server_setWriterGroupOperational(server, writerGroupIdent) == UA_STATUSCODE_GOOD); + + ck_assert(UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD); + ck_assert(UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD); + + UA_ReaderGroup *readerGroup = UA_ReaderGroup_findRGbyId(server, readerGroupIdentifier); + receiveSingleMessageRT(connection, readerGroup); + /* Read data received by the Subscriber */ + UA_Variant *subscribedNodeData = UA_Variant_new(); + retVal = UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 60003), subscribedNodeData); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + + ck_assert((*(UA_UInt32 *)subscribedNodeData->data) == 1000); + UA_Variant_clear(subscribedNodeData); + UA_free(subscribedNodeData); + /* Read data received by the Subscriber */ + UA_Variant *subscribedNodeData1 = UA_Variant_new(); + retVal = UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 60004), subscribedNodeData1); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + ck_assert((*(UA_UInt32 *)subscribedNodeData1->data) == 2000); + UA_Variant_clear(subscribedNodeData1); + UA_free(subscribedNodeData1); + ck_assert(UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier) == UA_STATUSCODE_GOOD); + UA_Server_deleteNode(server, pubNodeId1, UA_TRUE); + UA_NodeId_clear(&pubNodeId); + UA_Server_deleteNode(server, pubNodeId1, UA_TRUE); + UA_NodeId_clear(&pubNodeId1); + UA_Server_deleteNode(server, subNodeId, UA_TRUE); + UA_NodeId_clear(&subNodeId); + UA_Server_deleteNode(server, subNodeId1, UA_TRUE); + UA_NodeId_clear(&subNodeId1); + UA_free(subValue); + UA_free(subDataValueRT); + UA_free(subValue1); + UA_free(subDataValueRT1); + /* Free external data source */ + UA_free(intValue); + UA_free(dataValue); + /* Free external data source */ + UA_free(intValue1); + UA_free(dataValue1); +} END_TEST + +int main(void) { + TCase *tc_pubsub_encryption_rt = tcase_create("PubSub encryption RT with fixed offsets"); + tcase_add_checked_fixture(tc_pubsub_encryption_rt, setup, teardown); + tcase_add_test(tc_pubsub_encryption_rt, SetupInvalidPubSubConfig); + tcase_add_test(tc_pubsub_encryption_rt, PublishAndSubscribeSingleFieldWithFixedOffsets); + tcase_add_test(tc_pubsub_encryption_rt, PublishPDSWithMultipleFieldsAndSubscribeFixedSize); + + Suite *s = suite_create("PubSub encryption RT configuration levels"); + suite_add_tcase(s, tc_pubsub_encryption_rt); + + SRunner *sr = srunner_create(s); + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all(sr,CK_NORMAL); + int number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/server/check_nodestore.c b/tests/server/check_nodestore.c index b8762748e6b..04de9158915 100644 --- a/tests/server/check_nodestore.c +++ b/tests/server/check_nodestore.c @@ -5,6 +5,8 @@ #include #include #include +#include "open62541/plugin/nodestore.h" +#include "open62541/types_generated.h" #include #include @@ -79,7 +81,8 @@ START_TEST(findNodeInUA_NodeStoreWithSingleEntry) { UA_Node* n1 = createNode(0,2253); ns.insertNode(ns.context, n1, NULL); UA_NodeId in1 = UA_NODEID_NUMERIC(0,2253); - const UA_Node* nr = ns.getNode(ns.context, &in1); + const UA_Node* nr = ns.getNode(ns.context, &in1, ~(UA_UInt32)0, + UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH); ck_assert_uint_eq((uintptr_t)n1, (uintptr_t)nr); ns.releaseNode(ns.context, nr); } @@ -89,7 +92,8 @@ START_TEST(failToFindNodeInOtherUA_NodeStore) { UA_Node* n1 = createNode(0,2255); ns.insertNode(ns.context, n1, NULL); UA_NodeId in1 = UA_NODEID_NUMERIC(1, 2255); - const UA_Node* nr = ns.getNode(ns.context, &in1); + const UA_Node* nr = ns.getNode(ns.context, &in1, ~(UA_UInt32)0, + UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH); ck_assert_uint_eq((uintptr_t)nr, 0); } END_TEST @@ -109,7 +113,8 @@ START_TEST(findNodeInUA_NodeStoreWithSeveralEntries) { ns.insertNode(ns.context, n6, NULL); UA_NodeId in3 = UA_NODEID_NUMERIC(0, 2257); - const UA_Node* nr = ns.getNode(ns.context, &in3); + const UA_Node* nr = ns.getNode(ns.context, &in3, ~(UA_UInt32)0, + UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH); ck_assert_uint_eq((uintptr_t)nr, (uintptr_t)n3); ns.releaseNode(ns.context, nr); } @@ -144,7 +149,8 @@ START_TEST(findNodeInExpandedNamespace) { } // when UA_Node *n2 = createNode(0,25); - const UA_Node* nr = ns.getNode(ns.context, &n2->head.nodeId); + const UA_Node* nr = ns.getNode(ns.context, &n2->head.nodeId, ~(UA_UInt32)0, + UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH); ck_assert_int_eq(nr->head.nodeId.identifier.numeric, n2->head.nodeId.identifier.numeric); ns.releaseNode(ns.context, nr); ns.deleteNode(ns.context, n2); @@ -179,7 +185,8 @@ START_TEST(failToFindNonExistentNodeInUA_NodeStoreWithSeveralEntries) { ns.insertNode(ns.context, n5, NULL); UA_NodeId id = UA_NODEID_NUMERIC(0, 12); - const UA_Node* nr = ns.getNode(ns.context, &id); + const UA_Node* nr = ns.getNode(ns.context, &id, ~(UA_UInt32)0, + UA_REFERENCETYPESET_ALL, UA_BROWSEDIRECTION_BOTH); ck_assert_uint_eq((uintptr_t)nr, 0); } END_TEST @@ -203,7 +210,8 @@ static void *profileGetThread(void *arg) { for(UA_Int32 x = 0; xrounds; x++) { for(UA_Int32 i=test->min_val; i (Author: Luigi Bassetta) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "client/ua_client_internal.h" +#include "server/ua_server_internal.h" + +#include + +#include "testing_clock.h" +#include "testing_networklayers.h" +#include "thread_wrapper.h" +#include + +static UA_Server *server; +#ifdef UA_ENABLE_HISTORIZING +static UA_HistoryDataGathering *gathering; +#endif +static UA_Boolean running; +static THREAD_HANDLE server_thread; +static MUTEX_HANDLE serverMutex; + +static UA_Client *client; +static UA_NodeId parentNodeId; +static UA_NodeId parentReferenceNodeId; +static UA_NodeId outNodeId; + +static void serverMutexLock(void) { + if (!(MUTEX_LOCK(serverMutex))) { + fprintf(stderr, "Mutex cannot be locked.\n"); + exit(1); + } +} + +static void serverMutexUnlock(void) { + if (!(MUTEX_UNLOCK(serverMutex))) { + fprintf(stderr, "Mutex cannot be unlocked.\n"); + exit(1); + } +} + +THREAD_CALLBACK(serverloop) { + while(running) { + serverMutexLock(); + UA_Server_run_iterate(server, false); + serverMutexUnlock(); + } + return 0; +} + +static void setup(void) { + if (!(MUTEX_INIT(serverMutex))) { + fprintf(stderr, "Server mutex was not created correctly.\n"); + exit(1); + } + running = true; + + server = UA_Server_new(); + UA_ServerConfig *config = UA_Server_getConfig(server); + UA_ServerConfig_setDefault(config); + +#ifdef UA_ENABLE_HISTORIZING + gathering = (UA_HistoryDataGathering*)UA_calloc(1, sizeof(UA_HistoryDataGathering)); + *gathering = UA_HistoryDataGathering_Circular(1); + config->historyDatabase = UA_HistoryDatabase_default(*gathering); +#endif + + UA_StatusCode retval = UA_Server_run_startup(server); + if(retval != UA_STATUSCODE_GOOD) { + fprintf(stderr, "Error while calling Server_run_startup. %s\n", UA_StatusCode_name(retval)); + UA_Server_delete(server); + exit(1); + } + + THREAD_CREATE(server_thread, serverloop); + /* Define the attribute of the uint32 variable node */ + UA_VariableAttributes attr = UA_VariableAttributes_default; + UA_UInt32 myUint32 = 40; + UA_Variant_setScalar(&attr.value, &myUint32, &UA_TYPES[UA_TYPES_UINT32]); + attr.description = UA_LOCALIZEDTEXT("en-US","the answer"); + attr.displayName = UA_LOCALIZEDTEXT("en-US","the answer"); + attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; + attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE | UA_ACCESSLEVELMASK_HISTORYREAD | UA_ACCESSLEVELMASK_HISTORYWRITE; + attr.historizing = true; + + /* Add the variable node to the information model */ + UA_NodeId uint32NodeId = UA_NODEID_STRING(1, "the.answer"); + UA_QualifiedName uint32Name = UA_QUALIFIEDNAME(1, "the answer"); + parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); + parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); + UA_NodeId_init(&outNodeId); + retval = UA_Server_addVariableNode(server, uint32NodeId, parentNodeId, + parentReferenceNodeId, uint32Name, + UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), + attr, NULL, &outNodeId); + if (retval != UA_STATUSCODE_GOOD) { + fprintf(stderr, "Error adding variable node. %s\n", UA_StatusCode_name(retval)); + UA_Server_delete(server); + exit(1); + } + + client = UA_Client_new(); + UA_ClientConfig_setDefault(UA_Client_getConfig(client)); + retval = UA_Client_connect(client, "opc.tcp://localhost:4840"); + if (retval != UA_STATUSCODE_GOOD) { + fprintf(stderr, "Client can not connect to opc.tcp://localhost:4840. %s\n", UA_StatusCode_name(retval)); + UA_Client_delete(client); + UA_Server_delete(server); + exit(1); + } + + UA_Client_recv = client->connection.recv; + client->connection.recv = UA_Client_recvTesting; +} + +static void teardown(void) { + /* cleanup */ + UA_Client_disconnect(client); + UA_Client_delete(client); + running = false; + THREAD_JOIN(server_thread); + UA_NodeId_clear(&parentNodeId); + UA_NodeId_clear(&parentReferenceNodeId); + UA_NodeId_clear(&outNodeId); + UA_Server_run_shutdown(server); + UA_Server_delete(server); +#ifdef UA_ENABLE_HISTORIZING + UA_free(gathering); +#endif + if (!MUTEX_DESTROY(serverMutex)) { + fprintf(stderr, "Server mutex was not destroyed correctly.\n"); + exit(1); + } +} + +#ifdef UA_ENABLE_HISTORIZING + +#include +#include "ua_session.h" + +static UA_StatusCode +setUInt32(UA_Client *thisClient, UA_NodeId node, UA_UInt32 value) +{ + UA_Variant variant; + UA_Variant_setScalar(&variant, &value, &UA_TYPES[UA_TYPES_UINT32]); + return UA_Client_writeValueAttribute(thisClient, node, &variant); +} + + +void +Service_HistoryRead(UA_Server *server, UA_Session *session, + const UA_HistoryReadRequest *request, + UA_HistoryReadResponse *response); + +static void +requestHistory(UA_DateTime start, + UA_DateTime end, + UA_HistoryReadResponse * response, + UA_UInt32 numValuesPerNode, + UA_Boolean returnBounds, + UA_ByteString *continuationPoint) +{ + UA_ReadRawModifiedDetails *details = UA_ReadRawModifiedDetails_new(); + details->startTime = start; + details->endTime = end; + details->isReadModified = false; + details->numValuesPerNode = numValuesPerNode; + details->returnBounds = returnBounds; + + UA_HistoryReadValueId *valueId = UA_HistoryReadValueId_new(); + UA_NodeId_copy(&outNodeId, &valueId->nodeId); + if (continuationPoint) + UA_ByteString_copy(continuationPoint, &valueId->continuationPoint); + + UA_HistoryReadRequest request; + UA_HistoryReadRequest_init(&request); + request.historyReadDetails.encoding = UA_EXTENSIONOBJECT_DECODED; + request.historyReadDetails.content.decoded.type = &UA_TYPES[UA_TYPES_READRAWMODIFIEDDETAILS]; + request.historyReadDetails.content.decoded.data = details; + + request.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; + + request.nodesToReadSize = 1; + request.nodesToRead = valueId; + + UA_LOCK(&server->serviceMutex); + Service_HistoryRead(server, &server->adminSession, &request, response); + UA_UNLOCK(&server->serviceMutex); + UA_HistoryReadRequest_clear(&request); +} + + +START_TEST(Server_HistorizingStrategyValueSet) +{ + // init to a defined value + UA_StatusCode retval = setUInt32(client, outNodeId, 43); + ck_assert_str_eq(UA_StatusCode_name(retval), UA_StatusCode_name(UA_STATUSCODE_GOOD)); + + // set a data backend + UA_HistorizingNodeIdSettings setting; + setting.historizingBackend = UA_HistoryDataBackend_Memory_Circular(3, 10); + setting.maxHistoryDataResponseSize = 10; + setting.historizingUpdateStrategy = UA_HISTORIZINGUPDATESTRATEGY_VALUESET; + serverMutexLock(); + retval = gathering->registerNodeId(server, gathering->context, &outNodeId, setting); + serverMutexUnlock(); + ck_assert_str_eq(UA_StatusCode_name(retval), UA_StatusCode_name(UA_STATUSCODE_GOOD)); + + // Fill the data overcoming the buffer size and starting to write new values replacing the old ones. + // The circular buffer size is 10, the number of elements historized is 15 (from 0 to 14). So the final buffer will be: + // + // | 10 | 11 | 12 | 13 | 14 | 5 | 6 | 7 | 8 | 9 | + // + UA_fakeSleep(100); + UA_DateTime start = UA_DateTime_now(); + UA_fakeSleep(100); + for (UA_UInt32 i = 0; i < 15; ++i) { + retval = setUInt32(client, outNodeId, i); + ck_assert_str_eq(UA_StatusCode_name(retval), UA_StatusCode_name(UA_STATUSCODE_GOOD)); + UA_fakeSleep(100); + } + UA_DateTime end = UA_DateTime_now(); + + // request + UA_HistoryReadResponse response; + UA_HistoryReadResponse_init(&response); + requestHistory(start, end, &response, 0, false, NULL); + + // test the response + ck_assert_str_eq(UA_StatusCode_name(response.responseHeader.serviceResult), UA_StatusCode_name(UA_STATUSCODE_GOOD)); + ck_assert_uint_eq(response.resultsSize, 1); + for (size_t i = 0; i < response.resultsSize; ++i) { + ck_assert_str_eq(UA_StatusCode_name(response.results[i].statusCode), UA_StatusCode_name(UA_STATUSCODE_GOOD)); + ck_assert_uint_eq(response.results[i].historyData.encoding, UA_EXTENSIONOBJECT_DECODED); + ck_assert(response.results[i].historyData.content.decoded.type == &UA_TYPES[UA_TYPES_HISTORYDATA]); + UA_HistoryData * data = (UA_HistoryData *)response.results[i].historyData.content.decoded.data; + ck_assert(data->dataValuesSize > 0); + for (size_t j = 0; j < data->dataValuesSize; ++j) { + ck_assert(data->dataValues[j].sourceTimestamp >= start && data->dataValues[j].sourceTimestamp < end); + ck_assert_uint_eq(data->dataValues[j].hasSourceTimestamp, true); + ck_assert_str_eq(UA_StatusCode_name(data->dataValues[j].status), UA_StatusCode_name(UA_STATUSCODE_GOOD)); + ck_assert_uint_eq(data->dataValues[j].hasValue, true); + ck_assert(data->dataValues[j].value.type == &UA_TYPES[UA_TYPES_UINT32]); + UA_UInt32 * value = (UA_UInt32 *)data->dataValues[j].value.data; + if(j >= 5) + ck_assert_uint_eq(*value, j); + else + ck_assert_uint_eq(*value, j + 10); + } + } + UA_HistoryReadResponse_clear(&response); + UA_HistoryDataBackend_Memory_clear(&setting.historizingBackend); +} +END_TEST + +#endif /*UA_ENABLE_HISTORIZING*/ + +static Suite* testSuite_Client(void) +{ + Suite *s = suite_create("Server Historical Data"); + TCase *tc_server = tcase_create("Server Historical Data Circular"); + tcase_add_checked_fixture(tc_server, setup, teardown); +#ifdef UA_ENABLE_HISTORIZING + tcase_add_test(tc_server, Server_HistorizingStrategyValueSet); +#endif /* UA_ENABLE_HISTORIZING */ + suite_add_tcase(s, tc_server); + + return s; +} + +int main(void) +{ + Suite *s = testSuite_Client(); + SRunner *sr = srunner_create(s); + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all(sr,CK_NORMAL); + int number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/server/check_server_readspeed.c b/tests/server/check_server_readspeed.c index 5afd4e611e7..b75b8aff977 100644 --- a/tests/server/check_server_readspeed.c +++ b/tests/server/check_server_readspeed.c @@ -24,10 +24,7 @@ static UA_Server *server; static UA_NodeId readNodeIds[READNODES]; static void setup(void) { - UA_ServerConfig config; - memset(&config, 0, sizeof(UA_ServerConfig)); - UA_Nodestore_HashMap(&config.nodestore); - server = UA_Server_newWithConfig(&config); + server = UA_Server_new(); } static void teardown(void) { diff --git a/tools/ci.sh b/tools/ci.sh index cb27c189652..58267b73aaa 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -150,6 +150,28 @@ function unit_tests { make test ARGS="-V" } +function unit_tests_32 { + mkdir -p build; cd build; rm -rf * + cmake -DCMAKE_BUILD_TYPE=Debug \ + -DUA_BUILD_EXAMPLES=ON \ + -DUA_BUILD_UNIT_TESTS=ON \ + -DUA_ENABLE_DISCOVERY=ON \ + -DUA_ENABLE_DISCOVERY_MULTICAST=ON \ + -DUA_ENABLE_SUBSCRIPTIONS_EVENTS=ON \ + -DUA_ENABLE_HISTORIZING=ON \ + -DUA_ENABLE_JSON_ENCODING=ON \ + -DUA_ENABLE_PUBSUB=ON \ + -DUA_ENABLE_PUBSUB_DELTAFRAMES=ON \ + -DUA_ENABLE_PUBSUB_INFORMATIONMODEL=ON \ + -DUA_ENABLE_PUBSUB_MONITORING=ON \ + -DUA_FORCE_32BIT=ON \ + .. + #-DUA_ENABLE_PUBSUB_ETH_UADP=ON \ # TODO: Enable this + make ${MAKEOPTS} + set_capabilities + make test ARGS="-V" +} + function unit_tests_mt { mkdir -p build; cd build; rm -rf * cmake -DCMAKE_BUILD_TYPE=Debug \ @@ -210,6 +232,34 @@ function unit_tests_encryption_mbedtls_pubsub { make test ARGS="-V" } +########################################## +# Build and Run Unit Tests with Coverage # +########################################## + +function unit_tests_with_coverage { + mkdir -p build; cd build; rm -rf * + cmake -DCMAKE_BUILD_TYPE=Debug \ + -DUA_BUILD_EXAMPLES=ON \ + -DUA_BUILD_UNIT_TESTS=ON \ + -DUA_ENABLE_COVERAGE=ON \ + -DUA_ENABLE_DISCOVERY=ON \ + -DUA_ENABLE_DISCOVERY_MULTICAST=ON \ + -DUA_ENABLE_SUBSCRIPTIONS_EVENTS=ON \ + -DUA_ENABLE_HISTORIZING=ON \ + -DUA_ENABLE_JSON_ENCODING=ON \ + -DUA_ENABLE_PUBSUB=ON \ + -DUA_ENABLE_PUBSUB_ETH_UADP=ON \ + -DUA_ENABLE_PUBSUB_DELTAFRAMES=ON \ + -DUA_ENABLE_PUBSUB_INFORMATIONMODEL=ON \ + -DUA_ENABLE_PUBSUB_MONITORING=ON \ + -DUA_ENABLE_ENCRYPTION=MBEDTLS \ + .. + make ${MAKEOPTS} + set_capabilities + make test ARGS="-V" + make gcov +} + ########################################## # Build and Run Unit Tests with Valgrind # ########################################## diff --git a/tools/cmake/FindCheck.cmake b/tools/cmake/FindCheck.cmake index ed410c16b83..5220315bcde 100644 --- a/tools/cmake/FindCheck.cmake +++ b/tools/cmake/FindCheck.cmake @@ -17,7 +17,7 @@ # For details see the accompanying COPYING-CMAKE-SCRIPTS file. -INCLUDE( FindPkgConfig ) +find_package(PkgConfig REQUIRED) # Take care about check.pc settings PKG_SEARCH_MODULE( CHECK check ) diff --git a/tools/cmake/FindGcov.cmake b/tools/cmake/FindGcov.cmake new file mode 100644 index 00000000000..db03e534d00 --- /dev/null +++ b/tools/cmake/FindGcov.cmake @@ -0,0 +1,162 @@ +# This file is part of CMake-codecov. +# +# Copyright (c) +# 2015-2020 RWTH Aachen University, Federal Republic of Germany +# +# See the LICENSE file in the package base directory for details +# +# Written by Alexander Haase, alexander.haase@rwth-aachen.de +# + + +# include required Modules +include(FindPackageHandleStandardArgs) + + +# Search for gcov binary. +set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET}) +set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY}) + +get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach (LANG ${ENABLED_LANGUAGES}) + # Gcov evaluation is dependent on the used compiler. Check gcov support for + # each compiler that is used. If gcov binary was already found for this + # compiler, do not try to find it again. + if (NOT GCOV_${CMAKE_${LANG}_COMPILER_ID}_BIN) + get_filename_component(COMPILER_PATH "${CMAKE_${LANG}_COMPILER}" PATH) + + if ("${CMAKE_${LANG}_COMPILER_ID}" STREQUAL "GNU") + # Some distributions like OSX (homebrew) ship gcov with the compiler + # version appended as gcov-x. To find this binary we'll build the + # suggested binary name with the compiler version. + string(REGEX MATCH "^[0-9]+" GCC_VERSION + "${CMAKE_${LANG}_COMPILER_VERSION}") + + find_program(GCOV_BIN NAMES gcov-${GCC_VERSION} gcov + HINTS ${COMPILER_PATH}) + + elseif ("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "^(Apple)?Clang$") + # Some distributions like Debian ship llvm-cov with the compiler + # version appended as llvm-cov-x.y. To find this binary we'll build + # the suggested binary name with the compiler version. + string(REGEX MATCH "^[0-9]+.[0-9]+" LLVM_VERSION + "${CMAKE_${LANG}_COMPILER_VERSION}") + + # llvm-cov prior version 3.5 seems to be not working with coverage + # evaluation tools, but these versions are compatible with the gcc + # gcov tool. + if(LLVM_VERSION VERSION_GREATER 3.4) + find_program(LLVM_COV_BIN NAMES "llvm-cov-${LLVM_VERSION}" + "llvm-cov" HINTS ${COMPILER_PATH}) + mark_as_advanced(LLVM_COV_BIN) + + if (LLVM_COV_BIN) + find_program(LLVM_COV_WRAPPER "llvm-cov-wrapper" PATHS + ${CMAKE_MODULE_PATH}) + if (LLVM_COV_WRAPPER) + set(GCOV_BIN "${LLVM_COV_WRAPPER}" CACHE FILEPATH "") + + # set additional parameters + set(GCOV_${CMAKE_${LANG}_COMPILER_ID}_ENV + "LLVM_COV_BIN=${LLVM_COV_BIN}" CACHE STRING + "Environment variables for llvm-cov-wrapper.") + mark_as_advanced(GCOV_${CMAKE_${LANG}_COMPILER_ID}_ENV) + endif () + endif () + endif () + + if (NOT GCOV_BIN) + # Fall back to gcov binary if llvm-cov was not found or is + # incompatible. This is the default on OSX, but may crash on + # recent Linux versions. + find_program(GCOV_BIN gcov HINTS ${COMPILER_PATH}) + endif () + endif () + + + if (GCOV_BIN) + set(GCOV_${CMAKE_${LANG}_COMPILER_ID}_BIN "${GCOV_BIN}" CACHE STRING + "${LANG} gcov binary.") + + if (NOT CMAKE_REQUIRED_QUIET) + message("-- Found gcov evaluation for " + "${CMAKE_${LANG}_COMPILER_ID}: ${GCOV_BIN}") + endif() + + unset(GCOV_BIN CACHE) + endif () + endif () +endforeach () + + + + +# Add a new global target for all gcov targets. This target could be used to +# generate the gcov files for the whole project instead of calling -gcov +# for each target. +if (NOT TARGET gcov) + add_custom_target(gcov) +endif (NOT TARGET gcov) + + + +# This function will add gcov evaluation for target . Only sources of +# this target will be evaluated and no dependencies will be added. It will call +# Gcov on any source file of once and store the gcov file in the same +# directory. +function (add_gcov_target TNAME) + get_target_property(TBIN_DIR ${TNAME} BINARY_DIR) + set(TDIR ${TBIN_DIR}/CMakeFiles/${TNAME}.dir) + + # We don't have to check, if the target has support for coverage, thus this + # will be checked by add_coverage_target in Findcoverage.cmake. Instead we + # have to determine which gcov binary to use. + get_target_property(TSOURCES ${TNAME} SOURCES) + set(SOURCES "") + set(TCOMPILER "") + foreach (FILE ${TSOURCES}) + codecov_path_of_source(${FILE} FILE) + if (NOT "${FILE}" STREQUAL "") + codecov_lang_of_source(${FILE} LANG) + if (NOT "${LANG}" STREQUAL "") + list(APPEND SOURCES "${FILE}") + set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID}) + endif () + endif () + endforeach () + + # If no gcov binary was found, coverage data can't be evaluated. + if (NOT GCOV_${TCOMPILER}_BIN) + message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.") + return() + endif () + + set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}") + set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") + + + set(BUFFER "") + set(NULL_DEVICE "/dev/null") + if(WIN32) + set(NULL_DEVICE "NUL") + endif() + foreach(FILE ${SOURCES}) + get_filename_component(FILE_PATH "${TDIR}/${FILE}" PATH) + + # call gcov + add_custom_command(OUTPUT ${TDIR}/${FILE}.gcov + COMMAND ${GCOV_ENV} ${GCOV_BIN} -p ${TDIR}/${FILE}.gcno > ${NULL_DEVICE} + DEPENDS ${TNAME} ${TDIR}/${FILE}.gcno + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + + list(APPEND BUFFER ${TDIR}/${FILE}.gcov) + endforeach() + + + # add target for gcov evaluation of + add_custom_target(${TNAME}-gcov DEPENDS ${BUFFER}) + + # add evaluation target to the global gcov target. + add_dependencies(gcov ${TNAME}-gcov) +endfunction (add_gcov_target) diff --git a/tools/cmake/Findcodecov.cmake b/tools/cmake/Findcodecov.cmake new file mode 100644 index 00000000000..9fe2485623f --- /dev/null +++ b/tools/cmake/Findcodecov.cmake @@ -0,0 +1,265 @@ +# This file is part of CMake-codecov. +# +# Copyright (c) +# 2015-2020 RWTH Aachen University, Federal Republic of Germany +# +# See the LICENSE file in the package base directory for details +# +# Written by Alexander Haase, alexander.haase@rwth-aachen.de +# + +set(COVERAGE_FLAG_CANDIDATES + # gcc and clang + "-O0 -g -fprofile-arcs -ftest-coverage" + + # gcc and clang fallback + "-O0 -g --coverage" +) + + +# Add coverage support for target ${TNAME} and register target for coverage +# evaluation. If coverage is disabled or not supported, this function will +# simply do nothing. +# +# Note: This function is only a wrapper to define this function always, even if +# coverage is not supported by the compiler or disabled. This function must +# be defined here, because the module will be exited, if there is no coverage +# support by the compiler or it is disabled by the user. +function (add_coverage TNAME) + # only add coverage for target, if coverage is support and enabled. + if (ENABLE_COVERAGE) + foreach (TNAME ${ARGV}) + add_coverage_target(${TNAME}) + endforeach () + endif () +endfunction (add_coverage) + + +# Add global target to gather coverage information after all targets have been +# added. Other evaluation functions could be added here, after checks for the +# specific module have been passed. +# +# Note: This function is only a wrapper to define this function always, even if +# coverage is not supported by the compiler or disabled. This function must +# be defined here, because the module will be exited, if there is no coverage +# support by the compiler or it is disabled by the user. +function (coverage_evaluate) + # add lcov evaluation + if (LCOV_FOUND) + lcov_capture_initial() + lcov_capture() + endif (LCOV_FOUND) +endfunction () + + +# Exit this module, if coverage is disabled. add_coverage is defined before this +# return, so this module can be exited now safely without breaking any build- +# scripts. +if (NOT ENABLE_COVERAGE) + return() +endif () + + + + +# Find the required flags foreach language. +set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET}) +set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY}) + +get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach (LANG ${ENABLED_LANGUAGES}) + if (NOT ${LANG} MATCHES "^(C|CXX|Fortran)$") + message(STATUS "Skipping coverage for unsupported language: ${LANG}") + continue() + endif () + + # Coverage flags are not dependent on language, but the used compiler. So + # instead of searching flags foreach language, search flags foreach compiler + # used. + set(COMPILER ${CMAKE_${LANG}_COMPILER_ID}) + if (NOT COVERAGE_${COMPILER}_FLAGS) + foreach (FLAG ${COVERAGE_FLAG_CANDIDATES}) + if(NOT CMAKE_REQUIRED_QUIET) + message(STATUS "Try ${COMPILER} code coverage flag = [${FLAG}]") + endif() + + set(CMAKE_REQUIRED_FLAGS "${FLAG}") + unset(COVERAGE_FLAG_DETECTED CACHE) + + if (${LANG} STREQUAL "C") + include(CheckCCompilerFlag) + check_c_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED) + + elseif (${LANG} STREQUAL "CXX") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED) + + elseif (${LANG} STREQUAL "Fortran") + # CheckFortranCompilerFlag was introduced in CMake 3.x. To be + # compatible with older Cmake versions, we will check if this + # module is present before we use it. Otherwise we will define + # Fortran coverage support as not available. + include(CheckFortranCompilerFlag OPTIONAL + RESULT_VARIABLE INCLUDED) + if (INCLUDED) + check_fortran_compiler_flag("${FLAG}" + COVERAGE_FLAG_DETECTED) + elseif (NOT CMAKE_REQUIRED_QUIET) + message("-- Performing Test COVERAGE_FLAG_DETECTED") + message("-- Performing Test COVERAGE_FLAG_DETECTED - Failed" + " (Check not supported)") + endif () + endif() + + if (COVERAGE_FLAG_DETECTED) + set(COVERAGE_${COMPILER}_FLAGS "${FLAG}" + CACHE STRING "${COMPILER} flags for code coverage.") + mark_as_advanced(COVERAGE_${COMPILER}_FLAGS) + break() + else () + message(WARNING "Code coverage is not available for ${COMPILER}" + " compiler. Targets using this compiler will be " + "compiled without it.") + endif () + endforeach () + endif () +endforeach () + +set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_SAVE}) + + + + +# Helper function to get the language of a source file. +function (codecov_lang_of_source FILE RETURN_VAR) + # Usually, only the last extension of the file should be checked, to avoid + # template files (i.e. *.t.cpp) are checked with the full file extension. + # However, this feature requires CMake 3.14 or later. + set(EXT_COMP "LAST_EXT") + if(${CMAKE_VERSION} VERSION_LESS "3.14.0") + set(EXT_COMP "EXT") + endif() + + get_filename_component(FILE_EXT "${FILE}" ${EXT_COMP}) + string(TOLOWER "${FILE_EXT}" FILE_EXT) + string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT) + + get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) + foreach (LANG ${ENABLED_LANGUAGES}) + list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP) + if (NOT ${TEMP} EQUAL -1) + set(${RETURN_VAR} "${LANG}" PARENT_SCOPE) + return() + endif () + endforeach() + + set(${RETURN_VAR} "" PARENT_SCOPE) +endfunction () + + +# Helper function to get the relative path of the source file destination path. +# This path is needed by FindGcov and FindLcov cmake files to locate the +# captured data. +function (codecov_path_of_source FILE RETURN_VAR) + string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _source ${FILE}) + + # If expression was found, SOURCEFILE is a generator-expression for an + # object library. Currently we found no way to call this function automatic + # for the referenced target, so it must be called in the directoryso of the + # object library definition. + if (NOT "${_source}" STREQUAL "") + set(${RETURN_VAR} "" PARENT_SCOPE) + return() + endif () + + + string(REPLACE "${CMAKE_CURRENT_BINARY_DIR}/" "" FILE "${FILE}") + if(IS_ABSOLUTE ${FILE}) + file(RELATIVE_PATH FILE ${CMAKE_CURRENT_SOURCE_DIR} ${FILE}) + endif() + + # get the right path for file + string(REPLACE ".." "__" PATH "${FILE}") + + set(${RETURN_VAR} "${PATH}" PARENT_SCOPE) +endfunction() + + + + +# Add coverage support for target ${TNAME} and register target for coverage +# evaluation. +function(add_coverage_target TNAME) + # Check if all sources for target use the same compiler. If a target uses + # e.g. C and Fortran mixed and uses different compilers (e.g. clang and + # gfortran) this can trigger huge problems, because different compilers may + # use different implementations for code coverage. + get_target_property(TSOURCES ${TNAME} SOURCES) + set(TARGET_COMPILER "") + set(ADDITIONAL_FILES "") + foreach (FILE ${TSOURCES}) + # If expression was found, FILE is a generator-expression for an object + # library. Object libraries will be ignored. + string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE}) + if ("${_file}" STREQUAL "") + codecov_lang_of_source(${FILE} LANG) + if (LANG) + list(APPEND TARGET_COMPILER ${CMAKE_${LANG}_COMPILER_ID}) + + list(APPEND ADDITIONAL_FILES "${FILE}.gcno") + list(APPEND ADDITIONAL_FILES "${FILE}.gcda") + endif () + endif () + endforeach () + + list(REMOVE_DUPLICATES TARGET_COMPILER) + list(LENGTH TARGET_COMPILER NUM_COMPILERS) + + if (NUM_COMPILERS GREATER 1) + message(WARNING "Can't use code coverage for target ${TNAME}, because " + "it will be compiled by incompatible compilers. Target will be " + "compiled without code coverage.") + return() + + elseif (NUM_COMPILERS EQUAL 0) + message(WARNING "Can't use code coverage for target ${TNAME}, because " + "it uses an unknown compiler. Target will be compiled without " + "code coverage.") + return() + + elseif (NOT DEFINED "COVERAGE_${TARGET_COMPILER}_FLAGS") + # A warning has been printed before, so just return if flags for this + # compiler aren't available. + return() + endif() + + + # enable coverage for target + set_property(TARGET ${TNAME} APPEND_STRING + PROPERTY COMPILE_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}") + set_property(TARGET ${TNAME} APPEND_STRING + PROPERTY LINK_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}") + + + # Add gcov files generated by compiler to clean target. + set(CLEAN_FILES "") + foreach (FILE ${ADDITIONAL_FILES}) + codecov_path_of_source(${FILE} FILE) + list(APPEND CLEAN_FILES "CMakeFiles/${TNAME}.dir/${FILE}") + endforeach() + + if(${CMAKE_VERSION} VERSION_LESS "3.15.0") + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES + "${CLEAN_FILES}") + else() + set_directory_properties(PROPERTIES ADDITIONAL_CLEAN_FILES + "${CLEAN_FILES}") + endif() + + + add_gcov_target(${TNAME}) +endfunction(add_coverage_target) + +# Include modules for parsing the collected data and output it in a readable +# format (like gcov and lcov). +find_package(Gcov) diff --git a/tools/nodeset_compiler/backend_open62541_typedefinitions.py b/tools/nodeset_compiler/backend_open62541_typedefinitions.py index 989d64a9087..f35408f6cad 100644 --- a/tools/nodeset_compiler/backend_open62541_typedefinitions.py +++ b/tools/nodeset_compiler/backend_open62541_typedefinitions.py @@ -246,7 +246,7 @@ def print_enum_typedef(enum): return "typedef enum {\n " + ",\n ".join( map(lambda kv: makeCIdentifier("UA_" + enum.name.upper() + "_" + kv[0].upper()) + " = " + kv[1], values)) + \ - ",\n __UA_{0}_FORCE32BIT = 0x7fffffff\n".format(makeCIdentifier(enum.name.upper())) + "} " + \ + "{}\n __UA_{}_FORCE32BIT = 0x7fffffff\n".format("," if len(enum.elements) != 0 else "", makeCIdentifier(enum.name.upper())) + "} " + \ "UA_{0};\nUA_STATIC_ASSERT(sizeof(UA_{0}) == sizeof(UA_Int32), enum_must_be_32bit);".format( makeCIdentifier(enum.name)) diff --git a/tools/nodeset_compiler/nodes.py b/tools/nodeset_compiler/nodes.py index 99a95d236d6..ae88ccdcd04 100644 --- a/tools/nodeset_compiler/nodes.py +++ b/tools/nodeset_compiler/nodes.py @@ -559,6 +559,9 @@ def buildEncoding(self, nodeset, indent=0, force=False, namespaceMapping=None): isOptional = str(av) elif at == "ArrayDimensions": arrayDimensions = int(av) + elif at == "AllowSubTypes": + # ignore + continue else: logger.warn("Unknown Field Attribute " + str(at)) # This can either be an enumeration OR a structure, not both. From 73c5bff7ccdd0423f8073bed1edd6f7363042459 Mon Sep 17 00:00:00 2001 From: Stefan Profanter Date: Mon, 20 Dec 2021 12:09:38 +0100 Subject: [PATCH 0021/1963] fix(build): Use `#!/usr/bin/env python3` instead of `#!/usr/bin/env python` (#4862) The newer Ubuntu since 20.04 do not have the `python` binary. There one explicitly needs to choose between `python2` or `python3`. See also: https://askubuntu.com/q/1296790/141958 --- tools/amalgamate.py | 2 +- tools/c2rst.py | 2 +- tools/gdb-prettyprint.py | 2 +- tools/generate_datatypes.py | 2 +- tools/generate_nodeid_header.py | 2 +- tools/generate_statuscode_descriptions.py | 2 +- tools/nodeset_compiler/nodeset.py | 2 +- tools/nodeset_compiler/nodeset_testing.py | 2 +- tools/prepare_packaging.py | 2 +- tools/update_copyright_header.py | 2 +- tools/valgrind_check_error.py | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tools/amalgamate.py b/tools/amalgamate.py index 8db9d2f7c1d..6e042a6ac91 100755 --- a/tools/amalgamate.py +++ b/tools/amalgamate.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # coding: UTF-8 # This Source Code Form is subject to the terms of the Mozilla Public diff --git a/tools/c2rst.py b/tools/c2rst.py index b9971e54c1c..036b6b921f6 100755 --- a/tools/c2rst.py +++ b/tools/c2rst.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # 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 diff --git a/tools/gdb-prettyprint.py b/tools/gdb-prettyprint.py index ca80df449ae..486ee9a7e97 100644 --- a/tools/gdb-prettyprint.py +++ b/tools/gdb-prettyprint.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Load into gdb with 'source /tools/gdb-prettyprint.py' # Make sure to have 'set print pretty on' to get nice structure printouts diff --git a/tools/generate_datatypes.py b/tools/generate_datatypes.py index 3ad6a8a3040..227dd41c858 100755 --- a/tools/generate_datatypes.py +++ b/tools/generate_datatypes.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # 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 diff --git a/tools/generate_nodeid_header.py b/tools/generate_nodeid_header.py index b7c8a9f733a..14b7062cd49 100644 --- a/tools/generate_nodeid_header.py +++ b/tools/generate_nodeid_header.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # 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 diff --git a/tools/generate_statuscode_descriptions.py b/tools/generate_statuscode_descriptions.py index 9679af234ac..d6ced7df8c0 100755 --- a/tools/generate_statuscode_descriptions.py +++ b/tools/generate_statuscode_descriptions.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # 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 diff --git a/tools/nodeset_compiler/nodeset.py b/tools/nodeset_compiler/nodeset.py index 1889ff6d759..b5e016cc9fc 100644 --- a/tools/nodeset_compiler/nodeset.py +++ b/tools/nodeset_compiler/nodeset.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- ### This Source Code Form is subject to the terms of the Mozilla Public diff --git a/tools/nodeset_compiler/nodeset_testing.py b/tools/nodeset_compiler/nodeset_testing.py index a1121086788..5997f82e8bd 100644 --- a/tools/nodeset_compiler/nodeset_testing.py +++ b/tools/nodeset_compiler/nodeset_testing.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 ### 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 diff --git a/tools/prepare_packaging.py b/tools/prepare_packaging.py index bb775b1f407..84d7b2d0df0 100644 --- a/tools/prepare_packaging.py +++ b/tools/prepare_packaging.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # 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 diff --git a/tools/update_copyright_header.py b/tools/update_copyright_header.py index a8d950df84a..1ce6fed68d4 100755 --- a/tools/update_copyright_header.py +++ b/tools/update_copyright_header.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # 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 diff --git a/tools/valgrind_check_error.py b/tools/valgrind_check_error.py index e192cecce3f..a263f6aac26 100755 --- a/tools/valgrind_check_error.py +++ b/tools/valgrind_check_error.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # coding: UTF-8 # This Source Code Form is subject to the terms of the Mozilla Public From 5e90cfd19a99aa31fa25f888a20330e522c1bc46 Mon Sep 17 00:00:00 2001 From: andreasebner Date: Tue, 21 Dec 2021 11:12:42 +0100 Subject: [PATCH 0022/1963] feat(server) added dedicated event filter test, event-filter isNull and compare operator added, simplify filter by adding UA_FilterOperatorContext * feat(ci): feat(server) added dedicated event filter test * feat(server) event-filter isNull and compare operator added, event-filter unit test case added, lookup method for data type precedence added * feat(server) event-filter inList, between, bitwiseAnd, bitwaseOr added. Several casting rules added. * simplify filter by adding UA_FilterOperatorContext Co-authored-by: Julius Pfrommer --- include/open62541/types.h | 5 + src/server/ua_services_monitoreditem.c | 1 + src/server/ua_subscription_events.c | 794 +++++++++++++---- src/ua_types.c | 45 + tests/CMakeLists.txt | 4 + .../server/check_subscription_event_filter.c | 799 ++++++++++++++++++ 6 files changed, 1463 insertions(+), 185 deletions(-) create mode 100644 tests/server/check_subscription_event_filter.c diff --git a/include/open62541/types.h b/include/open62541/types.h index 2e96882b9b0..bd506983cc1 100644 --- a/include/open62541/types.h +++ b/include/open62541/types.h @@ -1046,6 +1046,11 @@ typedef struct UA_DataTypeArray { UA_Boolean UA_DataType_isNumeric(const UA_DataType *type); +/* Return the Data Type Precedence-Rank defined in Part 4. + * If there is no Precedence-Rank assigned with the type -1 is returned.*/ +UA_Int16 +UA_DataType_getPrecedence(const UA_DataType *type); + /** * Builtin data types can be accessed as UA_TYPES[UA_TYPES_XXX], where XXX is * the name of the data type. If only the NodeId of a type is known, use the diff --git a/src/server/ua_services_monitoreditem.c b/src/server/ua_services_monitoreditem.c index 924b024b558..89a2b150326 100644 --- a/src/server/ua_services_monitoreditem.c +++ b/src/server/ua_services_monitoreditem.c @@ -287,6 +287,7 @@ checkEventFilterParam(UA_Server *server, UA_Session *session, return whereResult; } } + UA_ContentFilterResult_clear(&contentFilterResult); } //check the select clause for logical consistency UA_StatusCode selectClauseValidationResult[128]; diff --git a/src/server/ua_subscription_events.c b/src/server/ua_subscription_events.c index 1fd74b2dcd3..c97c52d0c74 100644 --- a/src/server/ua_subscription_events.c +++ b/src/server/ua_subscription_events.c @@ -12,13 +12,18 @@ #ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS -//forward declaration +typedef struct { + UA_Server *server; + UA_Session *session; + const UA_NodeId *eventNode; + const UA_ContentFilter *contentFilter; + UA_ContentFilterResult *contentFilterResult; + UA_Variant *valueResult; + UA_UInt16 index; +} UA_FilterOperatorContext; + static UA_StatusCode -evaluateWhereClauseContentFilter(UA_Server *server, UA_Session *session, - const UA_NodeId *eventNode, - const UA_ContentFilter *contentFilter, - UA_ContentFilterResult *contentFilterResult, - UA_Variant* valueResult, UA_UInt16 index); +evaluateWhereClauseContentFilter(UA_FilterOperatorContext *ctx); /* We use a 16-Byte ByteString as an identifier */ UA_StatusCode @@ -60,7 +65,7 @@ UA_Server_createEvent(UA_Server *server, const UA_NodeId eventType, /* Create an ObjectNode which represents the event */ UA_QualifiedName name; - // set a dummy name. This is not used. + /* set a dummy name. This is not used. */ name = UA_QUALIFIEDNAME(0,"E"); UA_NodeId newNodeId = UA_NODEID_NULL; UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; @@ -249,58 +254,47 @@ resolveSimpleAttributeOperand(UA_Server *server, UA_Session *session, const UA_N /* Resolve operands to variants according to the operand type. * Part 4: 7.17.3 Table 142 specifies the allowed types. */ static UA_Variant -resolveOperand(UA_Server *server, UA_Session *session, const UA_NodeId *origin, - const UA_ContentFilter *contentFilter, - UA_ContentFilterResult *contentFilterResult, UA_Variant *valueResult, - UA_UInt16 index, UA_UInt16 nr) { - +resolveOperand(UA_FilterOperatorContext *ctx, UA_UInt16 nr) { UA_StatusCode res; UA_Variant variant; UA_Variant_init(&variant); /*SimpleAttributeOperands*/ - if(contentFilter->elements[index].filterOperands[nr].content.decoded.type == + if(ctx->contentFilter->elements[ctx->index].filterOperands[nr].content.decoded.type == &UA_TYPES[UA_TYPES_SIMPLEATTRIBUTEOPERAND]) { - res = resolveSimpleAttributeOperand( - server, session, origin, - (UA_SimpleAttributeOperand *)contentFilter->elements[index] - .filterOperands[nr] - .content.decoded.data, - &variant); + res = resolveSimpleAttributeOperand(ctx->server, ctx->session, ctx->eventNode, + (UA_SimpleAttributeOperand *)ctx->contentFilter->elements[ctx->index] + .filterOperands[nr].content.decoded.data, + &variant); /*LiteralAttribute*/ - } else if(contentFilter->elements[index].filterOperands[nr].content.decoded.type == + } else if(ctx->contentFilter->elements[ctx->index].filterOperands[nr].content.decoded.type == &UA_TYPES[UA_TYPES_LITERALOPERAND]) { - variant = ((UA_LiteralOperand *)contentFilter->elements[index] - .filterOperands[nr].content.decoded.data)->value; + variant = ((UA_LiteralOperand *)ctx->contentFilter->elements[ctx->index] + .filterOperands[nr].content.decoded.data)->value; res = UA_STATUSCODE_GOOD; - } else if(contentFilter->elements[index].filterOperands[nr].content.decoded.type == + } else if(ctx->contentFilter->elements[ctx->index].filterOperands[nr].content.decoded.type == &UA_TYPES[UA_TYPES_ELEMENTOPERAND]) { - res = evaluateWhereClauseContentFilter( - server, session, origin, contentFilter, contentFilterResult, valueResult, - (UA_UInt16)((UA_ElementOperand *)contentFilter->elements[index] - .filterOperands[nr].content.decoded.data)->index); - variant = - valueResult[(UA_UInt16)((UA_ElementOperand *)contentFilter->elements[index] - .filterOperands[nr].content.decoded.data)->index]; + UA_UInt16 oldIndex = ctx->index; + ctx->index = (UA_UInt16)((UA_ElementOperand *)ctx->contentFilter->elements[ctx->index] + .filterOperands[nr].content.decoded.data)->index; + res = evaluateWhereClauseContentFilter(ctx); + variant = ctx->valueResult[ctx->index]; + ctx->index = oldIndex; /* restore the old index */ /*ElementOperands*/ } else { res = UA_STATUSCODE_BADFILTEROPERANDINVALID; } if(res != UA_STATUSCODE_GOOD && res != UA_STATUSCODE_BADNOMATCH) { variant.type = NULL; - contentFilterResult->elementResults[index].operandStatusCodes[nr] = res; + ctx->contentFilterResult->elementResults[ctx->index].operandStatusCodes[nr] = res; } return variant; } static UA_StatusCode -ofTypeOperator(UA_Server *server, UA_Session *session, - const UA_NodeId *eventNode, - const UA_ContentFilter *contentFilter, - UA_ContentFilterResult *contentFilterResult, - UA_Variant* valueResult, UA_UInt16 index, - UA_UInt16 nr, UA_ContentFilterElement *pElement){ +ofTypeOperator(UA_FilterOperatorContext *ctx) { + UA_ContentFilterElement *pElement = &ctx->contentFilter->elements[ctx->index]; UA_Boolean result = false; - valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; if(pElement->filterOperandsSize != 1) return UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; if(pElement->filterOperands[0].content.decoded.type != @@ -319,24 +313,24 @@ ofTypeOperator(UA_Server *server, UA_Session *session, UA_Variant typeNodeIdVariant; UA_Variant_init(&typeNodeIdVariant); UA_StatusCode readStatusCode = - readObjectProperty(server, *eventNode, UA_QUALIFIEDNAME(0, "EventType"), - &typeNodeIdVariant); + readObjectProperty(ctx->server, *ctx->eventNode, + UA_QUALIFIEDNAME(0, "EventType"), &typeNodeIdVariant); if(readStatusCode != UA_STATUSCODE_GOOD) return readStatusCode; if(!UA_Variant_isScalar(&typeNodeIdVariant) || typeNodeIdVariant.type != &UA_TYPES[UA_TYPES_NODEID] || typeNodeIdVariant.data == NULL) { - UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, + UA_LOG_ERROR(&ctx->server->config.logger, UA_LOGCATEGORY_SERVER, "EventType has an invalid type."); UA_Variant_clear(&typeNodeIdVariant); return UA_STATUSCODE_BADINTERNALERROR; } - //check if the eventtype-nodeid is equal to the given oftype argument + /* check if the eventtype-nodeid is equal to the given oftype argument */ result = UA_NodeId_equal((UA_NodeId*) typeNodeIdVariant.data, literalOperandNodeId); - //check if the eventtype-nodeid is a subtype of the given oftype argument + /* check if the eventtype-nodeid is a subtype of the given oftype argument */ if(!result) - result = isNodeInTree_singleRef(server, + result = isNodeInTree_singleRef(ctx->server, (UA_NodeId*) typeNodeIdVariant.data, literalOperandNodeId, UA_REFERENCETYPEINDEX_HASSUBTYPE); @@ -347,189 +341,616 @@ ofTypeOperator(UA_Server *server, UA_Session *session, } static UA_StatusCode -andOperator(UA_Server *server, UA_Session *session, - const UA_NodeId *eventNode, - const UA_ContentFilter *contentFilter, - UA_ContentFilterResult *contentFilterResult, - UA_Variant* valueResult, UA_UInt16 index, - UA_UInt16 nr, UA_ContentFilterElement *pElement) { - UA_StatusCode firstBoolean_and = resolveBoolean( - resolveOperand(server, session, eventNode, contentFilter, - contentFilterResult, valueResult, index, 0)); +andOperator(UA_FilterOperatorContext *ctx) { + UA_StatusCode firstBoolean_and = resolveBoolean(resolveOperand(ctx, 0)); if(firstBoolean_and == UA_STATUSCODE_BADNOMATCH) { - valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; return UA_STATUSCODE_BADNOMATCH; } /* Evaluation of second operand */ - UA_StatusCode secondBoolean = resolveBoolean( - resolveOperand(server, session, eventNode, contentFilter, - contentFilterResult, valueResult, index, 1)); - + UA_StatusCode secondBoolean = resolveBoolean(resolveOperand(ctx, 1)); /* Filteroperator AND */ if(secondBoolean == UA_STATUSCODE_BADNOMATCH) { - valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; return UA_STATUSCODE_BADNOMATCH; - } else if((firstBoolean_and == UA_STATUSCODE_GOOD) && - (secondBoolean == UA_STATUSCODE_GOOD)) { - valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + } + if((firstBoolean_and == UA_STATUSCODE_GOOD) && + (secondBoolean == UA_STATUSCODE_GOOD)) { + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; return UA_STATUSCODE_GOOD; - } else { - return UA_STATUSCODE_BADFILTERELEMENTINVALID; } + return UA_STATUSCODE_BADFILTERELEMENTINVALID; } static UA_StatusCode -orOperator(UA_Server *server, UA_Session *session, - const UA_NodeId *eventNode, - const UA_ContentFilter *contentFilter, - UA_ContentFilterResult *contentFilterResult, - UA_Variant* valueResult, UA_UInt16 index, - UA_UInt16 nr, UA_ContentFilterElement *pElement) { - UA_StatusCode firstBoolean_or = resolveBoolean( - resolveOperand(server, session, eventNode, contentFilter, - contentFilterResult, valueResult, index, 0)); +orOperator(UA_FilterOperatorContext *ctx) { + UA_StatusCode firstBoolean_or = resolveBoolean(resolveOperand(ctx, 0)); if(firstBoolean_or == UA_STATUSCODE_GOOD) { - valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; return UA_STATUSCODE_GOOD; } /* Evaluation of second operand */ - UA_StatusCode secondBoolean = resolveBoolean( - resolveOperand(server, session, eventNode, contentFilter, - contentFilterResult, valueResult, index, 1)); - + UA_StatusCode secondBoolean = resolveBoolean(resolveOperand(ctx, 1)); if(secondBoolean == UA_STATUSCODE_GOOD) { - valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; return UA_STATUSCODE_GOOD; - } else if((firstBoolean_or == UA_STATUSCODE_BADNOMATCH) && - (secondBoolean == UA_STATUSCODE_BADNOMATCH)) { - valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + } + if((firstBoolean_or == UA_STATUSCODE_BADNOMATCH) && + (secondBoolean == UA_STATUSCODE_BADNOMATCH)) { + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; return UA_STATUSCODE_BADNOMATCH; + } + return UA_STATUSCODE_BADFILTERELEMENTINVALID; +} + +static UA_Boolean +isNumericUnsigned(UA_UInt32 dataTypeKind){ + if(dataTypeKind == UA_DATATYPEKIND_UINT64 || + dataTypeKind == UA_DATATYPEKIND_UINT32 || + dataTypeKind == UA_DATATYPEKIND_UINT16 || + dataTypeKind == UA_DATATYPEKIND_BYTE) + return true; + return false; +} + +static UA_Boolean +isNumericSigned(UA_UInt32 dataTypeKind){ + if(dataTypeKind == UA_DATATYPEKIND_INT64 || + dataTypeKind == UA_DATATYPEKIND_INT32 || + dataTypeKind == UA_DATATYPEKIND_INT16 || + dataTypeKind == UA_DATATYPEKIND_SBYTE) + return true; + return false; +} + +static UA_Boolean +isFloatingPoint(UA_UInt32 dataTypeKind){ + if(dataTypeKind == UA_DATATYPEKIND_FLOAT || + dataTypeKind == UA_DATATYPEKIND_DOUBLE) + return true; + return false; +} + +static UA_Boolean +isStringType(UA_UInt32 dataTypeKind){ + if(dataTypeKind == UA_DATATYPEKIND_STRING || + dataTypeKind == UA_DATATYPEKIND_BYTESTRING) + return true; + return false; +} + + + +static UA_StatusCode +implicitNumericVariantTransformation(UA_Variant *variant, void *data){ + if(variant->type == &UA_TYPES[UA_TYPES_UINT64]){ + *(UA_UInt64 *)data = *(UA_UInt64 *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_UINT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_UINT32]){ + *(UA_UInt64 *)data = *(UA_UInt32 *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_UINT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_UINT16]){ + *(UA_UInt64 *)data = *(UA_UInt16 *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_UINT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_BYTE]){ + *(UA_UInt64 *)data = *(UA_Byte *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_UINT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_INT64]){ + *(UA_Int64 *)data = *(UA_Int64 *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_INT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_INT32]){ + *(UA_Int64 *)data = *(UA_Int32 *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_INT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_INT16]){ + *(UA_Int64 *)data = *(UA_Int16 *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_INT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_SBYTE]){ + *(UA_Int64 *)data = *(UA_SByte *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_INT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_DOUBLE]){ + *(UA_Double *)data = *(UA_Double *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_DOUBLE]); + } else if(variant->type == &UA_TYPES[UA_TYPES_SBYTE]){ + *(UA_Double *)data = *(UA_Float *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_DOUBLE]); } else { - return UA_STATUSCODE_BADFILTERELEMENTINVALID; + return UA_STATUSCODE_BADTYPEMISMATCH; } + return UA_STATUSCODE_GOOD; } +/* 0 -> Same Type, 1 -> Implicit Cast, 2 -> Only explicit Cast, -1 -> cast invalid */ +static UA_SByte convertLookup[21][21] = { + { 0, 1,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 1,-1, 2,-1,-1, 1, 1, 1,-1}, + { 2, 0,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 1,-1, 2,-1,-1, 1, 1, 1,-1}, + {-1,-1, 0,-1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, + {-1,-1,-1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1}, + { 2,2,-1,-1, 0,-1, 2,-1, 2, 2, 2,-1, 2,-1, 2,-1,-1, 2, 2, 2,-1}, + {-1,-1,-1,-1,-1, 0,-1,-1,-1,-1,-1, 2,-1,-1, 1,-1,-1,-1,-1,-1,-1}, + { 2, 2,-1,-1, 1,-1, 0,-1, 2, 2, 2,-1, 2,-1, 2,-1,-1, 2, 2, 2,-1}, + {-1,-1, 2,-1,-1,-1,-1, 0,-1,-1,-1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 0, 1, 1,-1, 2,-1, 2,-1,-1, 2, 1, 1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 2, 0, 1,-1, 2, 2, 2,-1,-1, 2, 2, 1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 2, 2, 0,-1, 2, 2, 2,-1,-1, 2, 2, 2,-1}, + {-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1, 0,-1,-1, 1,-1,-1,-1,-1,-1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 0,-1, 2,-1,-1, 1, 1, 1,-1}, + {-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1,-1,-1, 0,-1,-1,-1, 2, 1, 1,-1}, + { 1, 1,-1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1,-1, 0, 2, 2, 1, 1, 1,-1}, + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 0,-1,-1,-1,-1,-1}, + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 0,-1,-1,-1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 2, 1, 2,-1,-1, 0, 1, 1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 2, 1, 1,-1, 2, 2, 2,-1,-1, 2, 0, 1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 2, 2, 1,-1, 2, 2, 2,-1,-1, 2, 2, 0,-1}, + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0} +}; + +/* This array maps the index of the + * standard DataType-Kind order to the + * order of the type convertion array */ +static UA_Byte dataTypeKindIndex[30] = { + 0, 12, 1, 8, 17, + 9, 18, 10, 19, 6, + 4, 14, 3, 7, 2, + 20, 11, 5, 13, 16, + 15, 255,255,255,255, + 255,255,255,255,255 +}; + +/* + * The OPC UA Standard defines in Part 4 several data type casting-rules. (see 1.04 part 4 Table 122) + * Return: + * 0 -> same type + * 1 -> types can be casted implicit + * 2 -> types can only be explicitly casted + * -1 -> types can't be casted + */ +static UA_SByte +checkTypeCastingOption(const UA_DataType *cast_target, const UA_DataType *cast_source) { + UA_Byte firstOperatorTypeKindIndex = UA_BYTE_MAX; + UA_Byte secondOperatorTypeKindIndex = UA_BYTE_MAX; + firstOperatorTypeKindIndex = dataTypeKindIndex[cast_target->typeKind]; + secondOperatorTypeKindIndex = dataTypeKindIndex[cast_source->typeKind]; + + if(firstOperatorTypeKindIndex == UA_BYTE_MAX || secondOperatorTypeKindIndex == UA_BYTE_MAX) + return -1; + + return convertLookup[firstOperatorTypeKindIndex][secondOperatorTypeKindIndex]; +} + +/* Compare operation for equal, gt, le, gte, lee + * UA_STATUSCODE_GOOD if the comparison was true + * UA_STATUSCODE_BADNOMATCH if the comparison was false + * UA_STATUSCODE_BADFILTEROPERATORINVALID for invalid operators + * UA_STATUSCODE_BADTYPEMISMATCH if one of the operands was not numeric + * ToDo Array-Casting + */ static UA_StatusCode -isNullOperator(UA_Server *server, UA_Session *session, - const UA_NodeId *eventNode, - const UA_ContentFilter *contentFilter, - UA_ContentFilterResult *contentFilterResult, - UA_Variant* valueResult, UA_UInt16 index, - UA_UInt16 nr, UA_ContentFilterElement *pElement) { +compareOperation(UA_Variant *firstOperand, UA_Variant *secondOperand, UA_FilterOperator op) { + /* get precedence of the operand types */ + UA_Int16 firstOperand_precedence = UA_DataType_getPrecedence(firstOperand->type); + UA_Int16 secondOperand_precedence = UA_DataType_getPrecedence(secondOperand->type); + /* if the types are not equal and one of the precedence-ranks is -1, then there is + no implicit conversion possible and therefore no compare */ + if(!UA_NodeId_equal(&firstOperand->type->typeId, &secondOperand->type->typeId) && + (firstOperand_precedence == -1 || secondOperand_precedence == -1)){ + return UA_STATUSCODE_BADTYPEMISMATCH; + } + /* check if the precedence order of the operators is swapped */ + UA_Variant *firstCompareOperand = firstOperand; + UA_Variant *secondCompareOperand = secondOperand; + UA_Boolean swapped = false; + if (firstOperand_precedence < secondOperand_precedence){ + firstCompareOperand = secondOperand; + secondCompareOperand = firstOperand; + swapped = true; + } + UA_SByte castRule = + checkTypeCastingOption(firstCompareOperand->type, secondCompareOperand->type); + + if(!(castRule == 0 || castRule == 1)){ + return UA_STATUSCODE_BADTYPEMISMATCH; + } + + /* The operand Data-Types influence the behavior and steps for the comparison. + * We need to check the operand types and store a rule which is used to select + * the right behavior afterwards. */ + enum compareHandlingRuleEnum { + UA_TYPES_EQUAL_ORDERED, + UA_TYPES_EQUAL_UNORDERED, + UA_TYPES_DIFFERENT_NUMERIC_UNSIGNED, + UA_TYPES_DIFFERENT_NUMERIC_SIGNED, + UA_TYPES_DIFFERENT_NUMERIC_FLOATING_POINT, + UA_TYPES_DIFFERENT_TEXT, + UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN, + UA_TYPES_DIFFERENT_COMPARE_EXPLIC + } compareHandlingRuleEnum; + + if(castRule == 0 && + (UA_DataType_isNumeric(firstOperand->type) || + firstCompareOperand->type->typeKind == (UA_UInt32) UA_DATATYPEKIND_DATETIME || + firstCompareOperand->type->typeKind == (UA_UInt32) UA_DATATYPEKIND_STRING || + firstCompareOperand->type->typeKind == (UA_UInt32) UA_DATATYPEKIND_BYTESTRING)){ + /* Data-Types with a natural order (allow le, gt, lee, gte) */ + compareHandlingRuleEnum = UA_TYPES_EQUAL_ORDERED; + } else if(castRule == 0){ + /* Data-Types without a natural order (le, gt, lee, gte are not allowed) */ + compareHandlingRuleEnum = UA_TYPES_EQUAL_UNORDERED; + } else if(castRule == 1 && + isNumericSigned(firstOperand->type->typeKind) && + isNumericSigned(secondOperand->type->typeKind)){ + compareHandlingRuleEnum = UA_TYPES_DIFFERENT_NUMERIC_SIGNED; + } else if(castRule == 1 && + isNumericUnsigned(firstOperand->type->typeKind) && + isNumericUnsigned(secondOperand->type->typeKind)){ + compareHandlingRuleEnum = UA_TYPES_DIFFERENT_NUMERIC_UNSIGNED; + } else if(castRule == 1 && + isFloatingPoint(firstOperand->type->typeKind) && + isFloatingPoint(secondOperand->type->typeKind)){ + compareHandlingRuleEnum = UA_TYPES_DIFFERENT_NUMERIC_FLOATING_POINT; + } else if(castRule == 1 && + isStringType(firstOperand->type->typeKind)&& + isStringType(secondOperand->type->typeKind)){ + compareHandlingRuleEnum = UA_TYPES_DIFFERENT_TEXT; + } else if(castRule == -1 || castRule == 2){ + compareHandlingRuleEnum = UA_TYPES_DIFFERENT_COMPARE_EXPLIC; + } else { + compareHandlingRuleEnum = UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN; + } + + if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN) + return UA_STATUSCODE_BADFILTEROPERATORINVALID; + + if(swapped){ + firstCompareOperand = secondCompareOperand; + secondCompareOperand = firstCompareOperand; + } + + if(op == UA_FILTEROPERATOR_EQUALS){ + UA_Byte variantContent[16]; + memset(&variantContent, 0, sizeof(UA_Byte) * 16); + if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_SIGNED || + compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_UNSIGNED || + compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_FLOATING_POINT) { + implicitNumericVariantTransformation(firstCompareOperand, variantContent); + implicitNumericVariantTransformation(secondCompareOperand, &variantContent[8]); + } else if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_TEXT) { + firstCompareOperand->type = &UA_TYPES[UA_TYPES_STRING]; + secondCompareOperand->type = &UA_TYPES[UA_TYPES_STRING]; + } else if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN || + compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_EXPLIC ){ + return UA_STATUSCODE_BADFILTEROPERATORINVALID; + } + if(UA_order(firstCompareOperand, secondCompareOperand, &UA_TYPES[UA_TYPES_VARIANT]) == UA_ORDER_EQ) { + return UA_STATUSCODE_GOOD; + } + } else { + UA_Byte variantContent[16]; + if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_SIGNED || + compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_UNSIGNED || + compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_FLOATING_POINT) { + memset(&variantContent, 0, sizeof(UA_Byte) * 16); + implicitNumericVariantTransformation(firstCompareOperand, variantContent); + implicitNumericVariantTransformation(secondCompareOperand, &variantContent[8]); + } else if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_TEXT) { + firstCompareOperand->type = &UA_TYPES[UA_TYPES_STRING]; + secondCompareOperand->type = &UA_TYPES[UA_TYPES_STRING]; + } else if(compareHandlingRuleEnum == UA_TYPES_EQUAL_UNORDERED) { + return UA_STATUSCODE_BADFILTEROPERATORINVALID; + } else if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN || + compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_EXPLIC) { + return UA_STATUSCODE_BADFILTEROPERATORINVALID; + } + UA_Order gte_result = UA_order(firstCompareOperand, secondCompareOperand, &UA_TYPES[UA_TYPES_VARIANT]); + if(op == UA_FILTEROPERATOR_LESSTHAN) { + if(gte_result == UA_ORDER_LESS) { + return UA_STATUSCODE_GOOD; + } + } else if(op == UA_FILTEROPERATOR_GREATERTHAN) { + if(gte_result == UA_ORDER_MORE) { + return UA_STATUSCODE_GOOD; + } + } else if(op == UA_FILTEROPERATOR_LESSTHANOREQUAL) { + if(gte_result == UA_ORDER_LESS || gte_result == UA_ORDER_EQ) { + return UA_STATUSCODE_GOOD; + } + } else if(op == UA_FILTEROPERATOR_GREATERTHANOREQUAL) { + if(gte_result == UA_ORDER_MORE || gte_result == UA_ORDER_EQ) { + return UA_STATUSCODE_GOOD; + } + } + } + return UA_STATUSCODE_BADNOMATCH; +} + +static UA_StatusCode +compareOperator(UA_FilterOperatorContext *ctx) { + UA_Variant firstOperand = resolveOperand(ctx, 0); + if(UA_Variant_isEmpty(&firstOperand)) + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + UA_Variant secondOperand = resolveOperand(ctx, 1); + if(UA_Variant_isEmpty(&secondOperand)) { + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + } + /* ToDo remove the following restriction: Add support for arrays */ + if(!UA_Variant_isScalar(&firstOperand) || !UA_Variant_isScalar(&secondOperand)){ + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + } + return compareOperation(&firstOperand, &secondOperand, + ctx->contentFilter->elements[ctx->index].filterOperator); +} + +static UA_StatusCode +bitwiseOperator(UA_FilterOperatorContext *ctx) { + /* The bitwise operators all have 2 operands which are evaluated equally. */ + UA_Variant firstOperand = resolveOperand(ctx, 0); + if(UA_Variant_isEmpty(&firstOperand)) { + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + } + UA_Variant secondOperand = resolveOperand(ctx, 1); + if(UA_Variant_isEmpty(&secondOperand)) { + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + } + + UA_Boolean bitwiseAnd = + ctx->contentFilter->elements[ctx->index].filterOperator == UA_FILTEROPERATOR_BITWISEAND; + + /* check if the operators are integers */ + if(!UA_DataType_isNumeric(firstOperand.type) || + !UA_DataType_isNumeric(secondOperand.type) || + !UA_Variant_isScalar(&firstOperand) || + !UA_Variant_isScalar(&secondOperand) || + (firstOperand.type == &UA_TYPES[UA_TYPES_DOUBLE]) || + (secondOperand.type == &UA_TYPES[UA_TYPES_DOUBLE]) || + (secondOperand.type == &UA_TYPES[UA_TYPES_FLOAT]) || + (firstOperand.type == &UA_TYPES[UA_TYPES_FLOAT])) { + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + } + + /* check which is the return type (higher precedence == bigger integer)*/ + UA_Int16 precedence = UA_DataType_getPrecedence(firstOperand.type); + if(precedence > UA_DataType_getPrecedence(secondOperand.type)) { + precedence = UA_DataType_getPrecedence(secondOperand.type); + } + + switch(precedence){ + case 3: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_INT64]; + UA_Int64 result_int64; + if(bitwiseAnd) { + result_int64 = *((UA_Int64 *)firstOperand.data) & *((UA_Int64 *)secondOperand.data); + } else { + result_int64 = *((UA_Int64 *)firstOperand.data) | *((UA_Int64 *)secondOperand.data); + } + UA_Int64_copy(&result_int64, (UA_Int64 *) ctx->valueResult[ctx->index].data); + break; + case 4: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_UINT64]; + UA_UInt64 result_uint64s; + if(bitwiseAnd) { + result_uint64s = *((UA_UInt64 *)firstOperand.data) & *((UA_UInt64 *)secondOperand.data); + } else { + result_uint64s = *((UA_UInt64 *)firstOperand.data) | *((UA_UInt64 *)secondOperand.data); + } + UA_UInt64_copy(&result_uint64s, (UA_UInt64 *) ctx->valueResult[ctx->index].data); + break; + case 5: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_INT32]; + UA_Int32 result_int32; + if(bitwiseAnd) { + result_int32 = *((UA_Int32 *)firstOperand.data) & *((UA_Int32 *)secondOperand.data); + } else { + result_int32 = *((UA_Int32 *)firstOperand.data) | *((UA_Int32 *)secondOperand.data); + } + UA_Int32_copy(&result_int32, (UA_Int32 *) ctx->valueResult[ctx->index].data); + break; + case 6: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_UINT32]; + UA_UInt32 result_uint32; + if(bitwiseAnd) { + result_uint32 = *((UA_UInt32 *)firstOperand.data) & *((UA_UInt32 *)secondOperand.data); + } else { + result_uint32 = *((UA_UInt32 *)firstOperand.data) | *((UA_UInt32 *)secondOperand.data); + } + UA_UInt32_copy(&result_uint32, (UA_UInt32 *) ctx->valueResult[ctx->index].data); + break; + case 8: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_INT16]; + UA_Int16 result_int16; + if(bitwiseAnd) { + result_int16 = *((UA_Int16 *)firstOperand.data) & *((UA_Int16 *)secondOperand.data); + } else { + result_int16 = *((UA_Int16 *)firstOperand.data) | *((UA_Int16 *)secondOperand.data); + } + UA_Int16_copy(&result_int16, (UA_Int16 *) ctx->valueResult[ctx->index].data); + break; + case 9: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_UINT16]; + UA_UInt16 result_uint16; + if(bitwiseAnd) { + result_uint16 = *((UA_UInt16 *)firstOperand.data) & *((UA_UInt16 *)secondOperand.data); + } else { + result_uint16 = *((UA_UInt16 *)firstOperand.data) | *((UA_UInt16 *)secondOperand.data); + } + UA_UInt16_copy(&result_uint16, (UA_UInt16 *) ctx->valueResult[ctx->index].data); + break; + case 10: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_SBYTE]; + UA_SByte result_sbyte; + if(bitwiseAnd) { + result_sbyte = *((UA_SByte *)firstOperand.data) & *((UA_SByte *)secondOperand.data); + } else { + result_sbyte = *((UA_SByte *)firstOperand.data) | *((UA_SByte *)secondOperand.data); + } + UA_SByte_copy(&result_sbyte, (UA_SByte *) ctx->valueResult[ctx->index].data); + break; + case 11: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BYTE]; + UA_Byte result_byte; + if(bitwiseAnd) { + result_byte = *((UA_Byte *)firstOperand.data) & *((UA_Byte *)secondOperand.data); + } else { + result_byte = *((UA_Byte *)firstOperand.data) | *((UA_Byte *)secondOperand.data); + } + UA_Byte_copy(&result_byte, (UA_Byte *) ctx->valueResult[ctx->index].data); + break; + default: + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + } + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +betweenOperator(UA_FilterOperatorContext *ctx) { + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + + UA_Variant firstOperand = resolveOperand(ctx, 0); + UA_Variant secondOperand = resolveOperand(ctx, 1); + UA_Variant thirdOperand = resolveOperand(ctx, 2); + + if((UA_Variant_isEmpty(&firstOperand) || + UA_Variant_isEmpty(&secondOperand) || + UA_Variant_isEmpty(&thirdOperand)) || + (!UA_DataType_isNumeric(firstOperand.type) || + !UA_DataType_isNumeric(secondOperand.type) || + !UA_DataType_isNumeric(thirdOperand.type)) || + (!UA_Variant_isScalar(&firstOperand) || + !UA_Variant_isScalar(&secondOperand) || + !UA_Variant_isScalar(&thirdOperand))) { + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + } + + /* Between can be evaluated through greaterThanOrEqual and lessThanOrEqual */ + if(compareOperation(&firstOperand, &secondOperand, UA_FILTEROPERATOR_GREATERTHANOREQUAL) == UA_STATUSCODE_GOOD && + compareOperation(&firstOperand, &thirdOperand, UA_FILTEROPERATOR_LESSTHANOREQUAL) == UA_STATUSCODE_GOOD){ + return UA_STATUSCODE_GOOD; + } + return UA_STATUSCODE_BADNOMATCH; +} + +static UA_StatusCode +inListOperator(UA_FilterOperatorContext *ctx) { + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + UA_Variant firstOperand = resolveOperand(ctx, 0); + + if(UA_Variant_isEmpty(&firstOperand) || + !UA_Variant_isScalar(&firstOperand)) { + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + } + + /* Evaluating the list of operands */ + for(size_t i = 1; i < ctx->contentFilter->elements[ctx->index].filterOperandsSize; i++) { + /* Resolving the current operand */ + UA_Variant currentOperator = resolveOperand(ctx, (UA_UInt16)i); + + /* Check if the operand conforms to the operator*/ + if(UA_Variant_isEmpty(¤tOperator) || + !UA_Variant_isScalar(¤tOperator)) { + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + } + if(compareOperation(&firstOperand, ¤tOperator, UA_FILTEROPERATOR_EQUALS)) { + return UA_STATUSCODE_GOOD; + } + } + return UA_STATUSCODE_BADNOMATCH; +} + +static UA_StatusCode +isNullOperator(UA_FilterOperatorContext *ctx) { /* Checking if operand is NULL. This is done by reducing the operand to a * variant and then checking if it is empty. */ - UA_Variant operand = - resolveOperand(server, session, eventNode, contentFilter, - contentFilterResult, valueResult, index, 0); - valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - if(!UA_Variant_isEmpty(&operand)) { + UA_Variant operand = resolveOperand(ctx, 0); + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + if(!UA_Variant_isEmpty(&operand)) return UA_STATUSCODE_BADNOMATCH; - } return UA_STATUSCODE_GOOD; } static UA_StatusCode -notOperator(UA_Server *server, UA_Session *session, - const UA_NodeId *eventNode, - const UA_ContentFilter *contentFilter, - UA_ContentFilterResult *contentFilterResult, - UA_Variant* valueResult, UA_UInt16 index, - UA_UInt16 nr, UA_ContentFilterElement *pElement) { +notOperator(UA_FilterOperatorContext *ctx) { /* Inverting the boolean value of the operand. */ - UA_StatusCode res = resolveBoolean( - resolveOperand(server, session, eventNode, contentFilter, - contentFilterResult, valueResult, index, 0)); - valueResult[index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - //invert result - if(res == UA_STATUSCODE_GOOD) { + UA_StatusCode res = resolveBoolean(resolveOperand(ctx, 0)); + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + /* invert result */ + if(res == UA_STATUSCODE_GOOD) return UA_STATUSCODE_BADNOMATCH; - } return UA_STATUSCODE_GOOD; } static UA_StatusCode -evaluateWhereClauseContentFilter(UA_Server *server, UA_Session *session, - const UA_NodeId *eventNode, - const UA_ContentFilter *contentFilter, - UA_ContentFilterResult *contentFilterResult, - UA_Variant* valueResult, UA_UInt16 index) { - UA_LOCK_ASSERT(&server->serviceMutex, 1); +evaluateWhereClauseContentFilter(UA_FilterOperatorContext *ctx) { + UA_LOCK_ASSERT(&ctx->server->serviceMutex, 1); - if(contentFilter->elements == NULL || contentFilter->elementsSize == 0) { + if(ctx->contentFilter->elements == NULL || ctx->contentFilter->elementsSize == 0) { /* Nothing to do.*/ return UA_STATUSCODE_GOOD; } /* The first element needs to be evaluated, this might be linked to other * elements, which are evaluated in these cases. See 7.4.1 in Part 4. */ - UA_ContentFilterElement *pElement = &contentFilter->elements[index]; + UA_ContentFilterElement *pElement = &ctx->contentFilter->elements[ctx->index]; + UA_StatusCode *result = &ctx->contentFilterResult->elementResults[ctx->index].statusCode; switch(pElement->filterOperator) { case UA_FILTEROPERATOR_INVIEW: - return UA_STATUSCODE_BADEVENTFILTERINVALID; - case UA_FILTEROPERATOR_RELATEDTO: { + /* Fallthrough */ + case UA_FILTEROPERATOR_RELATEDTO: /* Not allowed for event WhereClause according to 7.17.3 in Part 4 */ return UA_STATUSCODE_BADEVENTFILTERINVALID; - } case UA_FILTEROPERATOR_EQUALS: - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - case UA_FILTEROPERATOR_ISNULL: - contentFilterResult->elementResults[index].statusCode = - isNullOperator(server, session, eventNode, contentFilter, - contentFilterResult, valueResult, index, 0, pElement); - break; + /* Fallthrough */ case UA_FILTEROPERATOR_GREATERTHAN: - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + /* Fallthrough */ case UA_FILTEROPERATOR_LESSTHAN: - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + /* Fallthrough */ case UA_FILTEROPERATOR_GREATERTHANOREQUAL: - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + /* Fallthrough */ case UA_FILTEROPERATOR_LESSTHANOREQUAL: - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + *result = compareOperator(ctx); + break; case UA_FILTEROPERATOR_LIKE: return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; case UA_FILTEROPERATOR_NOT: - contentFilterResult->elementResults[index].statusCode = - notOperator(server, session, eventNode, contentFilter, - contentFilterResult, valueResult, index, 0, pElement); + *result = notOperator(ctx); break; case UA_FILTEROPERATOR_BETWEEN: - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + *result = betweenOperator(ctx); + break; case UA_FILTEROPERATOR_INLIST: + /* ToDo currently only numeric types are allowed */ + *result = inListOperator(ctx); + break; + case UA_FILTEROPERATOR_ISNULL: + *result = isNullOperator(ctx); + break; + case UA_FILTEROPERATOR_AND: + *result = andOperator(ctx); + break; + case UA_FILTEROPERATOR_OR: + *result = orOperator(ctx); + break; + case UA_FILTEROPERATOR_CAST: return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - case UA_FILTEROPERATOR_AND: { - contentFilterResult->elementResults[index].statusCode = - andOperator(server, session, eventNode, contentFilter, - contentFilterResult, valueResult, index, 0, pElement); + case UA_FILTEROPERATOR_BITWISEAND: + *result = bitwiseOperator(ctx); break; - case UA_FILTEROPERATOR_OR: - contentFilterResult->elementResults[index].statusCode = - orOperator(server, session, eventNode, contentFilter, - contentFilterResult, valueResult, index, 0, pElement); - break; - case UA_FILTEROPERATOR_CAST: - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - case UA_FILTEROPERATOR_BITWISEAND: - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - case UA_FILTEROPERATOR_BITWISEOR: - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - case UA_FILTEROPERATOR_OFTYPE: - contentFilterResult->elementResults[index].statusCode = - ofTypeOperator(server, session, eventNode, contentFilter, - contentFilterResult, valueResult, index, 0, pElement); - break; - default: - return UA_STATUSCODE_BADFILTEROPERATORINVALID; - } + case UA_FILTEROPERATOR_BITWISEOR: + *result = bitwiseOperator(ctx); + break; + case UA_FILTEROPERATOR_OFTYPE: + *result = ofTypeOperator(ctx); + break; + default: + return UA_STATUSCODE_BADFILTEROPERATORINVALID; } - if(valueResult[index].type == &UA_TYPES[UA_TYPES_BOOLEAN]) { - UA_Boolean *result = UA_Boolean_new(); - if(contentFilterResult->elementResults[index].statusCode == UA_STATUSCODE_GOOD) - *result = true; + + if(ctx->valueResult[ctx->index].type == &UA_TYPES[UA_TYPES_BOOLEAN]) { + UA_Boolean *res = UA_Boolean_new(); + if(ctx->contentFilterResult->elementResults[ctx->index].statusCode == UA_STATUSCODE_GOOD) + *res = true; else - *result = false; - valueResult[index].data = result; + *res = false; + ctx->valueResult[ctx->index].data = res; } - return contentFilterResult->elementResults[index].statusCode; + return ctx->contentFilterResult->elementResults[ctx->index].statusCode; } UA_StatusCode @@ -539,18 +960,27 @@ UA_Server_evaluateWhereClauseContentFilter(UA_Server *server, UA_Session *sessio UA_ContentFilterResult *contentFilterResult) { if(contentFilter->elementsSize == 0) return UA_STATUSCODE_GOOD; - //TODO add maximum lenth size to the server config + /* TODO add maximum lenth size to the server config */ if (contentFilter->elementsSize > 256) return UA_STATUSCODE_BADINVALIDARGUMENT; UA_STACKARRAY(UA_Variant, valueResult, contentFilter->elementsSize); for(size_t i = 0; i < contentFilter->elementsSize; ++i) { UA_Variant_init(&valueResult[i]); } - UA_StatusCode res = evaluateWhereClauseContentFilter( - server, session, eventNode, contentFilter, contentFilterResult, valueResult, 0); - for(size_t i = 0; i < contentFilter->elementsSize; i++) { - if(!UA_Variant_isEmpty(&valueResult[i])) - UA_Variant_clear(&valueResult[i]); + + UA_FilterOperatorContext ctx; + ctx.server = server; + ctx.session = session; + ctx.eventNode = eventNode; + ctx.contentFilter = contentFilter; + ctx.contentFilterResult = contentFilterResult; + ctx.valueResult = valueResult; + ctx.index = 0; + + UA_StatusCode res = evaluateWhereClauseContentFilter(&ctx); + for(size_t i = 0; i < ctx.contentFilter->elementsSize; i++) { + if(!UA_Variant_isEmpty(&ctx.valueResult[i])) + UA_Variant_clear(&ctx.valueResult[i]); } return res; } @@ -571,7 +1001,7 @@ UA_Server_filterEvent(UA_Server *server, UA_Session *session, return UA_STATUSCODE_BADOUTOFMEMORY; efl->eventFieldsSize = filter->selectClausesSize; - //empty event filter result + /* empty event filter result */ UA_EventFilterResult_init(result); result->selectClauseResultsSize = filter->selectClausesSize; result->selectClauseResults = (UA_StatusCode *) @@ -581,7 +1011,7 @@ UA_Server_filterEvent(UA_Server *server, UA_Session *session, UA_EventFilterResult_clear(result); return UA_STATUSCODE_BADOUTOFMEMORY; } - //prepare content filter result structure + /* prepare content filter result structure */ if(filter->whereClause.elementsSize != 0) { result->whereClauseResult.elementResultsSize = filter->whereClause.elementsSize; result->whereClauseResult.elementResults = (UA_ContentFilterElementResult *) @@ -975,7 +1405,7 @@ UA_Event_staticSelectClauseValidation(UA_Server *server, return; for(size_t i = 0; i < eventFilter->selectClausesSize; ++i) { result[i] = UA_STATUSCODE_GOOD; - ///typedefenitionid or browsepath of any clause is not NULL ? + /* /typedefenitionid or browsepath of any clause is not NULL ? */ if(UA_NodeId_isNull(&eventFilter->selectClauses[i].typeDefinitionId)) { result[i] = UA_STATUSCODE_BADTYPEDEFINITIONINVALID; continue; @@ -993,7 +1423,7 @@ UA_Event_staticSelectClauseValidation(UA_Server *server, result[i] = UA_STATUSCODE_BADBROWSENAMEINVALID; continue; } - //eventType is a subtype of BaseEventType ? + /* eventType is a subtype of BaseEventType ? */ UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE); if(!isNodeInTree_singleRef( server, &eventFilter->selectClauses[i].typeDefinitionId, @@ -1001,13 +1431,13 @@ UA_Event_staticSelectClauseValidation(UA_Server *server, result[i] = UA_STATUSCODE_BADTYPEDEFINITIONINVALID; continue; } - //attributeId is valid ? + /* attributeId is valid ? */ if(!((0 < eventFilter->selectClauses[i].attributeId) && (eventFilter->selectClauses[i].attributeId < 28))) { result[i] = UA_STATUSCODE_BADATTRIBUTEIDINVALID; continue; } - //browsePath contains null ? + /* browsePath contains null ? */ for(size_t j = 0; j < eventFilter->selectClauses[i].browsePathSize; ++j) { if(UA_QualifiedName_isNull( &eventFilter->selectClauses[i].browsePath[j])) { @@ -1017,10 +1447,10 @@ UA_Event_staticSelectClauseValidation(UA_Server *server, } if(result[i] != UA_STATUSCODE_GOOD) continue; - //indexRange is defined ? + /*indexRange is defined ? */ if(!UA_String_equal(&eventFilter->selectClauses[i].indexRange, &UA_STRING_NULL)) { - //indexRange is parsable ? + /* indexRange is parsable ? */ UA_NumericRange numericRange = UA_NUMERICRANGE(""); if(UA_NumericRange_parse(&numericRange, eventFilter->selectClauses[i].indexRange) != @@ -1029,7 +1459,7 @@ UA_Event_staticSelectClauseValidation(UA_Server *server, continue; } UA_free(numericRange.dimensions); - //attributeId is value ? + /* attributeId is value ? */ if(eventFilter->selectClauses[i].attributeId != UA_ATTRIBUTEID_VALUE) { result[i] = UA_STATUSCODE_BADTYPEMISMATCH; continue; @@ -1130,7 +1560,7 @@ UA_Event_staticWhereClauseValidation(UA_Server *server, break; } case UA_FILTEROPERATOR_INLIST: { - if(ef.filterOperandsSize >= 2) { + if(ef.filterOperandsSize <= 2) { er->statusCode = UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; break; @@ -1164,12 +1594,6 @@ UA_Event_staticWhereClauseValidation(UA_Server *server, (UA_LiteralOperand *)ef.filterOperands[0] .content.decoded.data; - if(((UA_NodeId *)literalOperand->value.data)->identifierType != - UA_NODEIDTYPE_NUMERIC) { - er->statusCode = - UA_STATUSCODE_BADATTRIBUTEIDINVALID; - break; - } /* Make sure the &pOperand->nodeId is a subtype of BaseEventType */ UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE); if(!isNodeInTree_singleRef( diff --git a/src/ua_types.c b/src/ua_types.c index 04a19b3da91..83afebc9f68 100644 --- a/src/ua_types.c +++ b/src/ua_types.c @@ -1847,6 +1847,51 @@ UA_DataType_isNumeric(const UA_DataType *type) { } } +UA_Int16 +UA_DataType_getPrecedence(const UA_DataType *type){ + //Defined in Part 4 Table 123 "Data Precedence Rules" + switch(type->typeKind) { + case UA_DATATYPEKIND_DOUBLE: + return 1; + case UA_DATATYPEKIND_FLOAT: + return 2; + case UA_DATATYPEKIND_INT64: + return 3; + case UA_DATATYPEKIND_UINT64: + return 4; + case UA_DATATYPEKIND_INT32: + return 5; + case UA_DATATYPEKIND_UINT32: + return 6; + case UA_DATATYPEKIND_STATUSCODE: + return 7; + case UA_DATATYPEKIND_INT16: + return 8; + case UA_DATATYPEKIND_UINT16: + return 9; + case UA_DATATYPEKIND_SBYTE: + return 10; + case UA_DATATYPEKIND_BYTE: + return 11; + case UA_DATATYPEKIND_BOOLEAN: + return 12; + case UA_DATATYPEKIND_GUID: + return 13; + case UA_DATATYPEKIND_STRING: + return 14; + case UA_DATATYPEKIND_EXPANDEDNODEID: + return 15; + case UA_DATATYPEKIND_NODEID: + return 16; + case UA_DATATYPEKIND_LOCALIZEDTEXT: + return 17; + case UA_DATATYPEKIND_QUALIFIEDNAME: + return 18; + default: + return -1; + } +} + /**********************/ /* Parse NumericRange */ /**********************/ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1bd779e17b3..dbac6e9585f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -348,6 +348,10 @@ if(UA_ENABLE_SUBSCRIPTIONS) add_executable(check_subscription_events server/check_subscription_events.c $ $) target_link_libraries(check_subscription_events ${LIBS}) add_test_valgrind(subscription_events ${TESTS_BINARY_DIR}/check_subscription_events) + + add_executable(check_subscription_event_filter server/check_subscription_event_filter.c $ $) + target_link_libraries(check_subscription_event_filter ${LIBS}) + add_test_valgrind(check_subscription_event_filter ${TESTS_BINARY_DIR}/check_subscription_event_filter) endif() add_executable(check_nodestore server/check_nodestore.c $ $) diff --git a/tests/server/check_subscription_event_filter.c b/tests/server/check_subscription_event_filter.c new file mode 100644 index 00000000000..1d153adb1c9 --- /dev/null +++ b/tests/server/check_subscription_event_filter.c @@ -0,0 +1,799 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +* +* Copyright 2021 (c) Fraunhofer IOSB (Author: Andreas Ebner) +*/ + +#include +#include +#include "server/ua_server_internal.h" +#include "server/ua_services.h" + +#include +#include + +#include + +#include "testing_clock.h" +#include "thread_wrapper.h" + +static UA_Server *server; +static UA_Boolean running; +static size_t serverIterations; +static THREAD_HANDLE server_thread; +static MUTEX_HANDLE serverMutex; +UA_NodeId EventType_A_Layer_1, EventType_B_Layer_1, EventType_C_Layer_2, EventType_D_Layer_3; + +UA_Client *client; +static UA_UInt32 subscriptionId; +static UA_UInt32 monitoredItemId; + +UA_Double publishingInterval = 500.0; +static UA_SimpleAttributeOperand *selectClauses; +static UA_Boolean notificationReceived; +static UA_UInt32 defaultSlectClauseSize = 4; +static UA_NodeId eventType; + +static void +addEventType(char* name, UA_NodeId parentNodeId, UA_NodeId requestedId, UA_NodeId* newEventType) { + UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default; + attr.displayName = UA_LOCALIZEDTEXT("en-US", name); + attr.description = UA_LOCALIZEDTEXT("en-US", name); + UA_StatusCode retval = UA_Server_addObjectTypeNode(server, requestedId, + parentNodeId, + UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), + UA_QUALIFIEDNAME(0, name), + attr, NULL, newEventType); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); +} + +/* Event Structure below + EventType + - EventType_A_Layer_1 + - EventType_B_Layer_1 + - EventType_C_Layer_2 + - EventType_D_Layer_3 +*/ +static void addEventTypes(void){ + addEventType("EventType_A_Layer_1", + UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE), + UA_NODEID_NUMERIC(1, 5000), + &EventType_A_Layer_1); + addEventType("EventType_B_Layer_1", + UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE), + UA_NODEID_NUMERIC(1, 5001), + &EventType_B_Layer_1); + addEventType("EventType_C_Layer_2", + EventType_B_Layer_1, + UA_NODEID_NUMERIC(1, 5002), + &EventType_C_Layer_2); + addEventType("EventType_D_Layer_3", + EventType_C_Layer_2, + UA_NODEID_NUMERIC(1, 5003), + &EventType_D_Layer_3); +} + +static void +setupSelectClauses(void) { + /* Check for severity (set manually), message (set manually), eventType + * (automatic) and sourceNode (automatic) */ + selectClauses = (UA_SimpleAttributeOperand *) + UA_Array_new(defaultSlectClauseSize, &UA_TYPES[UA_TYPES_SIMPLEATTRIBUTEOPERAND]); + ck_assert_ptr_ne(selectClauses, NULL); + for(size_t i = 0; i < defaultSlectClauseSize; ++i) { + UA_SimpleAttributeOperand_init(&selectClauses[i]); + selectClauses[i].typeDefinitionId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE); + selectClauses[i].browsePathSize = 1; + selectClauses[i].attributeId = UA_ATTRIBUTEID_VALUE; + selectClauses[i].browsePath = (UA_QualifiedName *) + UA_Array_new(selectClauses[i].browsePathSize, &UA_TYPES[UA_TYPES_QUALIFIEDNAME]); + ck_assert_ptr_ne(selectClauses[i].browsePath, NULL); + } + selectClauses[0].browsePath[0] = UA_QUALIFIEDNAME_ALLOC(0, "Severity"); + selectClauses[1].browsePath[0] = UA_QUALIFIEDNAME_ALLOC(0, "Message"); + selectClauses[2].browsePath[0] = UA_QUALIFIEDNAME_ALLOC(0, "EventType"); + selectClauses[3].browsePath[0] = UA_QUALIFIEDNAME_ALLOC(0, "SourceNode"); +} + +static void +handler_events_simple(UA_Client *lclient, UA_UInt32 subId, void *subContext, + UA_UInt32 monId, void *monContext, + size_t nEventFields, UA_Variant *eventFields) { + UA_Boolean foundSeverity = UA_FALSE; + UA_Boolean foundMessage = UA_FALSE; + UA_Boolean foundType = UA_FALSE; + UA_Boolean foundSource = UA_FALSE; + ck_assert_uint_eq(*(UA_UInt32 *) monContext, monitoredItemId); + ck_assert_uint_eq(nEventFields, defaultSlectClauseSize); + /* check all event fields */ + for(size_t i = 0; i < nEventFields; i++) { + /* find out which attribute of the event is being looked at */ + if(UA_Variant_hasScalarType(&eventFields[i], &UA_TYPES[UA_TYPES_UINT16])) { + /* Severity */ + ck_assert_uint_eq(*((UA_UInt16 *) (eventFields[i].data)), 1000); + foundSeverity = UA_TRUE; + } else if(UA_Variant_hasScalarType(&eventFields[i], &UA_TYPES[UA_TYPES_LOCALIZEDTEXT])) { + /* Message */ + UA_LocalizedText comp = UA_LOCALIZEDTEXT("en-US", "Generated Event"); + ck_assert(UA_String_equal(&((UA_LocalizedText *) eventFields[i].data)->locale, &comp.locale)); + ck_assert(UA_String_equal(&((UA_LocalizedText *) eventFields[i].data)->text, &comp.text)); + foundMessage = UA_TRUE; + } else if(UA_Variant_hasScalarType(&eventFields[i], &UA_TYPES[UA_TYPES_NODEID])) { + /* either SourceNode or EventType */ + UA_NodeId serverId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER); + if(UA_NodeId_equal((UA_NodeId *) eventFields[i].data, &eventType)) { + /* EventType */ + foundType = UA_TRUE; + } else if(UA_NodeId_equal((UA_NodeId *) eventFields[i].data, &serverId)) { + /* SourceNode */ + foundSource = UA_TRUE; + } else { + ck_assert_msg(UA_FALSE, "NodeId doesn't match"); + } + } else { + ck_assert_msg(UA_FALSE, "Field doesn't match"); + } + } + ck_assert_uint_eq(foundMessage, UA_TRUE); + ck_assert_uint_eq(foundSeverity, UA_TRUE); + ck_assert_uint_eq(foundType, UA_TRUE); + ck_assert_uint_eq(foundSource, UA_TRUE); + notificationReceived = true; +} + +static void serverMutexLock(void) { + if (!(MUTEX_LOCK(serverMutex))) { + fprintf(stderr, "Mutex cannot be locked.\n"); + exit(1); + } +} + +static void serverMutexUnlock(void) { + if (!(MUTEX_UNLOCK(serverMutex))) { + fprintf(stderr, "Mutex cannot be unlocked.\n"); + exit(1); + } +} + +THREAD_CALLBACK(serverloop) { + while (running) { + serverMutexLock(); + UA_Server_run_iterate(server, false); + serverIterations++; + serverMutexUnlock(); + } + return 0; +} + +static void +sleepUntilAnswer(UA_Double sleepMs) { + UA_fakeSleep((UA_UInt32)sleepMs); + serverMutexLock(); + size_t oldIterations = serverIterations; + size_t newIterations; + serverMutexUnlock(); + while(true) { + serverMutexLock(); + newIterations = serverIterations; + serverMutexUnlock(); + if(oldIterations != newIterations) + return; + UA_realSleep(1); + } +} + +static void setup(void){ + /* Setup Server */ + if (!MUTEX_INIT(serverMutex)) { + fprintf(stderr, "Server mutex was not created correctly."); + exit(1); + } + running = true; + server = UA_Server_new(); + UA_ServerConfig *config = UA_Server_getConfig(server); + UA_ServerConfig_setDefault(config); + + config->maxPublishReqPerSession = 5; + UA_Server_run_startup(server); + addEventTypes(); + THREAD_CREATE(server_thread, serverloop); + + /* Setup Client */ + client = UA_Client_new(); + UA_ClientConfig_setDefault(UA_Client_getConfig(client)); + UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840"); + if(retval != UA_STATUSCODE_GOOD) { + fprintf(stderr, "Client can not connect to opc.tcp://localhost:4840. %s", + UA_StatusCode_name(retval)); + exit(1); + } + /* Create subscription */ + UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default(); + UA_CreateSubscriptionResponse response = + UA_Client_Subscriptions_create(client, request, NULL, NULL, NULL); + subscriptionId = response.subscriptionId; + sleepUntilAnswer(publishingInterval + 100); +} + +static void +removeSubscription(void) { + UA_DeleteSubscriptionsRequest deleteSubscriptionsRequest; + UA_DeleteSubscriptionsRequest_init(&deleteSubscriptionsRequest); + UA_UInt32 removeId = subscriptionId; + deleteSubscriptionsRequest.subscriptionIdsSize = 1; + deleteSubscriptionsRequest.subscriptionIds = &removeId; + + UA_DeleteSubscriptionsResponse deleteSubscriptionsResponse; + UA_DeleteSubscriptionsResponse_init(&deleteSubscriptionsResponse); + UA_LOCK(&server->serviceMutex); + Service_DeleteSubscriptions(server, &server->adminSession, &deleteSubscriptionsRequest, + &deleteSubscriptionsResponse); + UA_UNLOCK(&server->serviceMutex); + UA_DeleteSubscriptionsResponse_clear(&deleteSubscriptionsResponse); +} + +static void teardown(void) { + /* Delete Server */ + running = false; + THREAD_JOIN(server_thread); + removeSubscription(); + UA_Server_run_shutdown(server); + UA_Server_delete(server); + + /* Delete Client */ + UA_Client_disconnect(client); + UA_Client_delete(client); + if (!MUTEX_DESTROY(serverMutex)) { + fprintf(stderr, "Server mutex was not destroyed correctly."); + exit(1); + } +} + +static UA_MonitoredItemCreateResult +addMonitoredItem(UA_Client_EventNotificationCallback handler, UA_EventFilter *filter, bool discardOldest) { + UA_MonitoredItemCreateRequest item; + UA_MonitoredItemCreateRequest_init(&item); + item.itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, 2253); /* Root->Objects->Server */ + item.itemToMonitor.attributeId = UA_ATTRIBUTEID_EVENTNOTIFIER; + item.monitoringMode = UA_MONITORINGMODE_REPORTING; + + if (filter) { + item.requestedParameters.filter.encoding = UA_EXTENSIONOBJECT_DECODED; + item.requestedParameters.filter.content.decoded.data = filter; + item.requestedParameters.filter.content.decoded.type = &UA_TYPES[UA_TYPES_EVENTFILTER]; + } + + item.requestedParameters.queueSize = 1; + item.requestedParameters.discardOldest = discardOldest; + + return UA_Client_MonitoredItems_createEvent(client, subscriptionId, + UA_TIMESTAMPSTORETURN_BOTH, item, + &monitoredItemId, handler, NULL); +} + +static UA_StatusCode +eventSetup(UA_NodeId *eventNodeId) { + UA_StatusCode retval; + serverMutexLock(); + retval = UA_Server_createEvent(server, eventType, eventNodeId); + serverMutexUnlock(); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + /* add a severity to the event */ + UA_Variant value; + UA_RelativePathElement rpe; + UA_RelativePathElement_init(&rpe); + rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY); + rpe.isInverse = false; + rpe.includeSubtypes = false; + UA_BrowsePath bp; + UA_BrowsePath_init(&bp); + bp.startingNode = *eventNodeId; + bp.relativePath.elementsSize = 1; + bp.relativePath.elements = &rpe; + rpe.targetName = UA_QUALIFIEDNAME(0, "Severity"); + serverMutexLock(); + UA_BrowsePathResult bpr = UA_Server_translateBrowsePathToNodeIds(server, &bp); + serverMutexUnlock(); + ck_assert_uint_eq(bpr.statusCode, UA_STATUSCODE_GOOD); + /* number with no special meaning */ + UA_UInt16 eventSeverity = 1000; + UA_Variant_setScalar(&value, &eventSeverity, &UA_TYPES[UA_TYPES_UINT16]); + serverMutexLock(); + retval = UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value); + serverMutexUnlock(); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + UA_BrowsePathResult_clear(&bpr); + + /* add a message to the event */ + rpe.targetName = UA_QUALIFIEDNAME(0, "Message"); + serverMutexLock(); + bpr = UA_Server_translateBrowsePathToNodeIds(server, &bp); + serverMutexUnlock(); + ck_assert_uint_eq(bpr.statusCode, UA_STATUSCODE_GOOD); + UA_LocalizedText message = UA_LOCALIZEDTEXT("en-US", "Generated Event"); + UA_Variant_setScalar(&value, &message, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); + serverMutexLock(); + retval = UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value); + serverMutexUnlock(); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + UA_BrowsePathResult_clear(&bpr); + + return retval; +} + +static UA_StatusCode +triggerEventLocked(const UA_NodeId eventNodeId, const UA_NodeId origin, + UA_ByteString *outEventId, const UA_Boolean deleteEventNode) { + serverMutexLock(); + UA_StatusCode retval = UA_Server_triggerEvent(server, eventNodeId, origin, + outEventId, deleteEventNode); + serverMutexUnlock(); + return retval; +} + +static void deleteMonitoredItems(void){ + /* delete the monitoredItem */ + UA_DeleteMonitoredItemsRequest deleteRequest; + UA_DeleteMonitoredItemsRequest_init(&deleteRequest); + deleteRequest.subscriptionId = subscriptionId; + deleteRequest.monitoredItemIds = &monitoredItemId; + deleteRequest.monitoredItemIdsSize = 1; + + UA_DeleteMonitoredItemsResponse deleteResponse = + UA_Client_MonitoredItems_delete(client, deleteRequest); + + sleepUntilAnswer(publishingInterval + 100); + ck_assert_uint_eq(deleteResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); + ck_assert_uint_eq(deleteResponse.resultsSize, 1); + ck_assert_uint_eq(*(deleteResponse.results), UA_STATUSCODE_GOOD); + + UA_DeleteMonitoredItemsResponse_clear(&deleteResponse); +} + +static void +checkForEvent(UA_MonitoredItemCreateResult *createResult, UA_Boolean expect){ + /* let the client fetch the event and check if the correct values were received */ + notificationReceived = false; + sleepUntilAnswer(publishingInterval + 100); + UA_StatusCode retval = UA_Client_run_iterate(client, 0); + sleepUntilAnswer(publishingInterval + 100); + retval |= UA_Client_run_iterate(client, 0); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + ck_assert_uint_eq(notificationReceived, expect); + ck_assert_uint_eq(createResult->revisedQueueSize, 1); +} + +/* helper functions for the generation of the content-filter */ +static void +setupContentFilter(UA_ContentFilter *contentFilter, size_t elements){ + UA_ContentFilter_init(contentFilter); + contentFilter->elementsSize = elements; + contentFilter->elements = (UA_ContentFilterElement *) + UA_Array_new(contentFilter->elementsSize, &UA_TYPES[UA_TYPES_CONTENTFILTERELEMENT]); + ck_assert_ptr_ne(contentFilter->elements, NULL); + for(size_t i = 0; i < contentFilter->elementsSize; ++i) { + UA_ContentFilterElement_init(&contentFilter->elements[i]); + } +} + +static void +setupOperandArrays(UA_ContentFilterElement *contentFilterElement){ + contentFilterElement->filterOperands = (UA_ExtensionObject*) + UA_Array_new( + contentFilterElement->filterOperandsSize, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]); + ck_assert_ptr_ne(contentFilterElement->filterOperands, NULL); + for(size_t n =0; n< contentFilterElement->filterOperandsSize; ++n) { + UA_ExtensionObject_init(&contentFilterElement->filterOperands[n]); + } +} + +static void +setupNotFilter(UA_ContentFilterElement *element){ + element->filterOperator = UA_FILTEROPERATOR_NOT; + element->filterOperandsSize = 1; + setupOperandArrays(element); +} + +static void +setupOfTypeFilter(UA_ContentFilterElement *element){ + element->filterOperator = UA_FILTEROPERATOR_OFTYPE; + element->filterOperandsSize = 1; + setupOperandArrays(element); +} + +static void +setupEqualsFilter(UA_ContentFilterElement *element, UA_FilterOperator compareOperator){ + switch(compareOperator) { + case UA_FILTEROPERATOR_EQUALS: + element->filterOperator = UA_FILTEROPERATOR_EQUALS; + break; + case UA_FILTEROPERATOR_LESSTHAN: + element->filterOperator = UA_FILTEROPERATOR_LESSTHAN; + break; + case UA_FILTEROPERATOR_GREATERTHAN: + element->filterOperator = UA_FILTEROPERATOR_GREATERTHAN; + break; + default: + element->filterOperator = UA_FILTEROPERATOR_EQUALS; + break; + } + element->filterOperandsSize = 2; + setupOperandArrays(element); +} + +static void +setupBetweenFilter(UA_ContentFilterElement *element){ + element->filterOperator = UA_FILTEROPERATOR_BETWEEN; + element->filterOperandsSize = 3; + setupOperandArrays(element); +} + +static void +setupInListFilter(UA_ContentFilterElement *element, UA_UInt16 elements){ + element->filterOperator = UA_FILTEROPERATOR_INLIST; + element->filterOperandsSize = elements; + setupOperandArrays(element); +} + +/*static void +setupElementOperand(UA_ContentFilterElement *element, size_t count, UA_UInt32 *indexes){ + for(size_t i = 0; i < count; ++i) { + element->filterOperands[i].content.decoded.type = &UA_TYPES[UA_TYPES_ELEMENTOPERAND]; + element->filterOperands[i].encoding = UA_EXTENSIONOBJECT_DECODED; + UA_ElementOperand *firstElementOperand = UA_ElementOperand_new(); + UA_ElementOperand_init(firstElementOperand); + firstElementOperand->index = indexes[i]; + element->filterOperands[i].content.decoded.data = firstElementOperand; + } +}*/ + +static void +setupLiteralOperand(UA_ContentFilterElement *element, size_t count, UA_Variant *literals){ + for(size_t i = 0; i < count; ++i) { + element->filterOperands[i].content.decoded.type = &UA_TYPES[UA_TYPES_LITERALOPERAND]; + element->filterOperands[i].encoding = UA_EXTENSIONOBJECT_DECODED; + UA_LiteralOperand *literalOperand = UA_LiteralOperand_new(); + UA_LiteralOperand_init(literalOperand); + literalOperand->value = literals[i]; + element->filterOperands[i].content.decoded.data = literalOperand; + } +} + +/* Test Case "not-Operator" Description: + Phase 1: + Action -> Fire default "EventType_A_Layer_1" Event + Event-Source: Server-Object + Filters: Select(Severity, Message, EventType, SourceNode) Where (!true) + Expect: No Notification +Phase 2: + Action -> Fire default "EventType_A_Layer_1" Event + Event-Source: Server-Object + Filters: Select(Severity, Message, EventType, SourceNode) Where (!false) + Expect: Get Notification */ +START_TEST(notOperatorValidation) { + /* setup event filter */ + UA_EventFilter filter; + UA_EventFilter_init(&filter); + setupSelectClauses(); + filter.selectClauses = selectClauses; + filter.selectClausesSize = defaultSlectClauseSize; + setupContentFilter(&filter.whereClause, 1); + setupNotFilter(&filter.whereClause.elements[0]); + UA_Boolean condition = true; + UA_Variant literalContent; + UA_Variant_init(&literalContent); + UA_Variant_setScalar(&literalContent, &condition, &UA_TYPES[UA_TYPES_BOOLEAN]); + setupLiteralOperand(&filter.whereClause.elements[0], 1, &literalContent); + /* setup event */ + eventType = EventType_A_Layer_1; + UA_NodeId eventNodeId; + UA_StatusCode retval = eventSetup(&eventNodeId); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + /* add a monitored item (with filter) */ + UA_MonitoredItemCreateResult createResult = addMonitoredItem(handler_events_simple, &filter, true); + ck_assert_uint_eq(createResult.statusCode, UA_STATUSCODE_GOOD); + monitoredItemId = createResult.monitoredItemId; + /* trigger the event */ + retval = triggerEventLocked(eventNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), NULL, UA_TRUE); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + + checkForEvent(&createResult, false); + deleteMonitoredItems(); + UA_free(filter.whereClause.elements[0].filterOperands->content.decoded.data); + + condition = false; + UA_Variant_setScalarCopy(&literalContent, &condition, &UA_TYPES[UA_TYPES_BOOLEAN]); + setupLiteralOperand(&filter.whereClause.elements[0], 1, &literalContent); + createResult = addMonitoredItem(handler_events_simple, &filter, true); + ck_assert_uint_eq(createResult.statusCode, UA_STATUSCODE_GOOD); + monitoredItemId = createResult.monitoredItemId; + /* trigger the event */ + eventSetup(&eventNodeId); + retval = triggerEventLocked(eventNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), NULL, UA_TRUE); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + checkForEvent(&createResult, true); + deleteMonitoredItems(); + UA_EventFilter_clear(&filter); +} END_TEST + +/* Test Case "ofType-Operator" Description: + Phase 1: + Action -> Fire default "EventType_A_Layer_1" Event + Event-Source: Server-Object + Filters: Select(Severity, Message, EventType, SourceNode) Where (ofType EventType_B_Layer_1) + Expect: No Notification +Phase 2: + Action -> Fire default "EventType_B_Layer_1" Event + Event-Source: Server-Object + Filters: Select(Severity, Message, EventType, SourceNode) Where (ofType EventType_B_Layer_1) + Expect: Get Notification +*/ +START_TEST(ofTypeOperatorValidation) { + /* setup event filter */ + UA_EventFilter filter; + UA_EventFilter_init(&filter); + setupSelectClauses(); + filter.selectClauses = selectClauses; + filter.selectClausesSize = defaultSlectClauseSize; + setupContentFilter(&filter.whereClause, 1); + setupOfTypeFilter(&filter.whereClause.elements[0]); + UA_Variant literalContent; + UA_NodeId *nodeId = UA_NodeId_new(); + UA_NodeId_init(nodeId); + *nodeId = EventType_B_Layer_1; + UA_Variant_setScalar(&literalContent, nodeId, &UA_TYPES[UA_TYPES_NODEID]); + setupLiteralOperand(&filter.whereClause.elements[0], 1, &literalContent); + /* setup event */ + eventType = EventType_A_Layer_1; + UA_NodeId eventNodeId; + UA_StatusCode retval = eventSetup(&eventNodeId); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + /* add a monitored item (with filter) */ + UA_MonitoredItemCreateResult createResult = addMonitoredItem(handler_events_simple, &filter, true); + ck_assert_uint_eq(createResult.statusCode, UA_STATUSCODE_GOOD); + monitoredItemId = createResult.monitoredItemId; + /* trigger the event */ + retval = triggerEventLocked(eventNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), NULL, UA_TRUE); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + checkForEvent(&createResult, false); + /* trigger the event */ + eventType = EventType_B_Layer_1; + eventSetup(&eventNodeId); + retval = triggerEventLocked(eventNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), NULL, UA_TRUE); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + checkForEvent(&createResult, true); + deleteMonitoredItems(); + UA_EventFilter_clear(&filter); +} END_TEST + +START_TEST(orTypeOperatorValidation) { + +} END_TEST + +START_TEST(andTypeOperatorValidation) { + +} END_TEST + +START_TEST(equalOperatorValidation) { + /* setup event filter */ + UA_EventFilter filter; + UA_EventFilter_init(&filter); + setupSelectClauses(); + filter.selectClauses = selectClauses; + filter.selectClausesSize = defaultSlectClauseSize; + setupContentFilter(&filter.whereClause, 1); + setupEqualsFilter(&filter.whereClause.elements[0], UA_FILTEROPERATOR_EQUALS); + /* setup operands */ + UA_UInt32 left = 62541; + UA_UInt32 right = 62541; + UA_Variant literalContent[2]; + memset(literalContent, 0, sizeof(UA_Variant) * 2); + UA_Variant_setScalar(&literalContent[0], &left, &UA_TYPES[UA_TYPES_UINT32]); + UA_Variant_setScalar(&literalContent[1], &right, &UA_TYPES[UA_TYPES_UINT32]); + setupLiteralOperand(&filter.whereClause.elements[0], 2, literalContent); + /* setup event */ + eventType = EventType_A_Layer_1; + UA_NodeId eventNodeId; + UA_StatusCode retval = eventSetup(&eventNodeId); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + /* add a monitored item (with filter) */ + UA_MonitoredItemCreateResult createResult = addMonitoredItem(handler_events_simple, &filter, true); + ck_assert_uint_eq(createResult.statusCode, UA_STATUSCODE_GOOD); + monitoredItemId = createResult.monitoredItemId; + /* trigger the event */ + retval = triggerEventLocked(eventNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), NULL, UA_TRUE); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + checkForEvent(&createResult, true); + deleteMonitoredItems(); + UA_free(filter.whereClause.elements->filterOperands[0].content.decoded.data); + UA_free(filter.whereClause.elements->filterOperands[1].content.decoded.data); + + left = 62542; + UA_Variant_setScalar(&literalContent[0], &left, &UA_TYPES[UA_TYPES_UINT32]); + setupLiteralOperand(&filter.whereClause.elements[0], 2, literalContent); + createResult = addMonitoredItem(handler_events_simple, &filter, true); + ck_assert_uint_eq(createResult.statusCode, UA_STATUSCODE_GOOD); + monitoredItemId = createResult.monitoredItemId; + /* trigger the event */ + eventSetup(&eventNodeId); + retval = triggerEventLocked(eventNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), NULL, UA_TRUE); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + checkForEvent(&createResult, false); + deleteMonitoredItems(); + UA_free(filter.whereClause.elements->filterOperands[0].content.decoded.data); + UA_free(filter.whereClause.elements->filterOperands[1].content.decoded.data); + + /* test types wich need implicit cast */ + UA_UInt64 left_big = 62541; + UA_Variant_setScalar(&literalContent[0], &left_big, &UA_TYPES[UA_TYPES_UINT64]); + setupLiteralOperand(&filter.whereClause.elements[0], 2, literalContent); + createResult = addMonitoredItem(handler_events_simple, &filter, true); + ck_assert_uint_eq(createResult.statusCode, UA_STATUSCODE_GOOD); + monitoredItemId = createResult.monitoredItemId; + /* trigger the event */ + eventSetup(&eventNodeId); + retval = triggerEventLocked(eventNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), NULL, UA_TRUE); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + checkForEvent(&createResult, true); + deleteMonitoredItems(); + UA_free(filter.whereClause.elements->filterOperands[0].content.decoded.data); + UA_free(filter.whereClause.elements->filterOperands[1].content.decoded.data); + + /* check equal with nodeid */ + UA_NodeId left_nodeid = UA_NODEID_NUMERIC(0, 14123); + UA_NodeId right_nodeid = UA_NODEID_NUMERIC(0, 14123); + memset(literalContent, 0, sizeof(UA_Variant) * 2); + UA_Variant_setScalarCopy(&literalContent[0], &left_nodeid, &UA_TYPES[UA_TYPES_NODEID]); + UA_Variant_setScalarCopy(&literalContent[1], &right_nodeid, &UA_TYPES[UA_TYPES_NODEID]); + setupLiteralOperand(&filter.whereClause.elements[0], 2, literalContent); + /* setup event */ + eventType = EventType_A_Layer_1; + eventSetup(&eventNodeId); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + /* add a monitored item (with filter) */ + createResult = addMonitoredItem(handler_events_simple, &filter, true); + ck_assert_uint_eq(createResult.statusCode, UA_STATUSCODE_GOOD); + monitoredItemId = createResult.monitoredItemId; + /* trigger the event */ + retval = triggerEventLocked(eventNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), NULL, UA_TRUE); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + checkForEvent(&createResult, true); + deleteMonitoredItems(); + UA_EventFilter_clear(&filter); + } END_TEST + +START_TEST(orderedCompareOperatorValidation) { + /* setup event filter */ + UA_EventFilter filter; + UA_EventFilter_init(&filter); + setupSelectClauses(); + filter.selectClauses = selectClauses; + filter.selectClausesSize = defaultSlectClauseSize; + setupContentFilter(&filter.whereClause, 1); + setupEqualsFilter(&filter.whereClause.elements[0], UA_FILTEROPERATOR_LESSTHAN); + /* setup operands */ + UA_UInt32 left = 100; + UA_UInt32 right = 1000; + UA_Variant literalContent[2]; + memset(literalContent, 0, sizeof(UA_Variant) * 2); + UA_Variant_setScalarCopy(&literalContent[0], &left, &UA_TYPES[UA_TYPES_UINT32]); + UA_Variant_setScalarCopy(&literalContent[1], &right, &UA_TYPES[UA_TYPES_UINT32]); + setupLiteralOperand(&filter.whereClause.elements[0], 2, literalContent); + /* setup event */ + eventType = EventType_A_Layer_1; + UA_NodeId eventNodeId; + UA_StatusCode retval = eventSetup(&eventNodeId); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + /* add a monitored item (with filter) */ + UA_MonitoredItemCreateResult createResult = addMonitoredItem(handler_events_simple, &filter, true); + ck_assert_uint_eq(createResult.statusCode, UA_STATUSCODE_GOOD); + monitoredItemId = createResult.monitoredItemId; + /* trigger the event */ + retval = triggerEventLocked(eventNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), NULL, UA_TRUE); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + checkForEvent(&createResult, true); + deleteMonitoredItems(); + UA_EventFilter_clear(&filter); +} END_TEST + +START_TEST(betweenOperatorValidation) { + /* setup event filter */ + UA_EventFilter filter; + UA_EventFilter_init(&filter); + setupSelectClauses(); + filter.selectClauses = selectClauses; + filter.selectClausesSize = defaultSlectClauseSize; + setupContentFilter(&filter.whereClause, 1); + setupBetweenFilter(&filter.whereClause.elements[0]); + /* setup operands */ + UA_UInt32 range_element = 40; + UA_UInt32 range_start = 10; + UA_UInt32 range_stop = 100; + UA_Variant literalContent[3]; + memset(literalContent, 0, sizeof(UA_Variant) * 3); + UA_Variant_setScalarCopy(&literalContent[0], &range_element, &UA_TYPES[UA_TYPES_UINT32]); + UA_Variant_setScalarCopy(&literalContent[1], &range_start, &UA_TYPES[UA_TYPES_UINT32]); + UA_Variant_setScalarCopy(&literalContent[2], &range_stop, &UA_TYPES[UA_TYPES_UINT32]); + setupLiteralOperand(&filter.whereClause.elements[0], 3, literalContent); + /* setup event */ + eventType = EventType_A_Layer_1; + UA_NodeId eventNodeId; + UA_StatusCode retval = eventSetup(&eventNodeId); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + /* add a monitored item (with filter) */ + UA_MonitoredItemCreateResult createResult = addMonitoredItem(handler_events_simple, &filter, true); + ck_assert_uint_eq(createResult.statusCode, UA_STATUSCODE_GOOD); + monitoredItemId = createResult.monitoredItemId; + /* trigger the event */ + retval = triggerEventLocked(eventNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), NULL, UA_TRUE); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + checkForEvent(&createResult, true); + deleteMonitoredItems(); + UA_EventFilter_clear(&filter); +} END_TEST + +START_TEST(inListOperatorValidation) { + /* setup event filter */ + UA_EventFilter filter; + UA_EventFilter_init(&filter); + setupSelectClauses(); + filter.selectClauses = selectClauses; + filter.selectClausesSize = defaultSlectClauseSize; + setupContentFilter(&filter.whereClause, 1); + setupInListFilter(&filter.whereClause.elements[0], 4); + /* setup operands */ + UA_UInt32 target_element = 40; + UA_UInt32 element_1 = 10; + UA_UInt32 element_2 = 100; + UA_UInt32 element_3 = 40; + UA_Variant literalContent[4]; + memset(literalContent, 0, sizeof(UA_Variant) * 4); + UA_Variant_setScalarCopy(&literalContent[0], &target_element, &UA_TYPES[UA_TYPES_INT32]); + UA_Variant_setScalarCopy(&literalContent[1], &element_1, &UA_TYPES[UA_TYPES_INT32]); + UA_Variant_setScalarCopy(&literalContent[2], &element_2, &UA_TYPES[UA_TYPES_INT32]); + UA_Variant_setScalarCopy(&literalContent[3], &element_3, &UA_TYPES[UA_TYPES_INT32]); + setupLiteralOperand(&filter.whereClause.elements[0], 4, literalContent); + /* setup event */ + eventType = EventType_A_Layer_1; + UA_NodeId eventNodeId; + UA_StatusCode retval = eventSetup(&eventNodeId); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + /* add a monitored item (with filter) */ + UA_MonitoredItemCreateResult createResult = addMonitoredItem(handler_events_simple, &filter, true); + ck_assert_uint_eq(createResult.statusCode, UA_STATUSCODE_GOOD); + monitoredItemId = createResult.monitoredItemId; + /* trigger the event */ + retval = triggerEventLocked(eventNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), NULL, UA_TRUE); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + checkForEvent(&createResult, true); + deleteMonitoredItems(); + UA_EventFilter_clear(&filter); +} END_TEST + +static Suite *testSuite_Client(void) { + Suite *s = suite_create("Server Subscription Event Filters"); + TCase *tc_server = tcase_create("Basic Event Filters"); + tcase_add_unchecked_fixture(tc_server, setup, teardown); + tcase_add_test(tc_server, notOperatorValidation); + tcase_add_test(tc_server, ofTypeOperatorValidation); + tcase_add_test(tc_server, orTypeOperatorValidation); + tcase_add_test(tc_server, andTypeOperatorValidation); + tcase_add_test(tc_server, equalOperatorValidation); + tcase_add_test(tc_server, orderedCompareOperatorValidation); + tcase_add_test(tc_server, betweenOperatorValidation); + tcase_add_test(tc_server, inListOperatorValidation); + + suite_add_tcase(s, tc_server); + return s; +} + +int main(void) { + Suite *s = testSuite_Client(); + SRunner *sr = srunner_create(s); + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all(sr, CK_NORMAL); + int number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} From 482c98760bd861610442211379978f6136c2eb7f Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 22 Dec 2021 00:17:53 +0100 Subject: [PATCH 0023/1963] refactor(server): Split event filter into separate file --- CMakeLists.txt | 4 +- src/server/ua_server_internal.h | 9 + src/server/ua_subscription_events.c | 1202 +------------------ src/server/ua_subscription_events_filter.c | 1203 ++++++++++++++++++++ 4 files changed, 1220 insertions(+), 1198 deletions(-) create mode 100644 src/server/ua_subscription_events_filter.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ba62a51d45..489a0dea104 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1118,7 +1118,9 @@ if(UA_ENABLE_SUBSCRIPTIONS) if(UA_NAMESPACE_ZERO STREQUAL "MINIMAL") message(FATAL_ERROR "Events require at least the reduced Namespace Zero") endif() - list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/src/server/ua_subscription_events.c) + list(APPEND lib_sources + ${PROJECT_SOURCE_DIR}/src/server/ua_subscription_events.c + ${PROJECT_SOURCE_DIR}/src/server/ua_subscription_events_filter.c) if(UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS) list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/src/server/ua_subscription_alarms_conditions.c) endif() diff --git a/src/server/ua_server_internal.h b/src/server/ua_server_internal.h index 0391546f6d3..3db6e6af0a9 100644 --- a/src/server/ua_server_internal.h +++ b/src/server/ua_server_internal.h @@ -387,11 +387,20 @@ void monitoredItem_sampleCallback(UA_Server *server, UA_MonitoredItem *monitored UA_Subscription * UA_Server_getSubscriptionById(UA_Server *server, UA_UInt32 subscriptionId); +#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS UA_StatusCode triggerEvent(UA_Server *server, const UA_NodeId eventNodeId, const UA_NodeId origin, UA_ByteString *outEventId, const UA_Boolean deleteEventNode); +/* Filters the given event with the given filter and writes the results into a + * notification */ +UA_StatusCode +filterEvent(UA_Server *server, UA_Session *session, + const UA_NodeId *eventNode, UA_EventFilter *filter, + UA_EventFieldList *efl, UA_EventFilterResult *result); + +#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */ #endif /* UA_ENABLE_SUBSCRIPTIONS */ UA_BrowsePathResult diff --git a/src/server/ua_subscription_events.c b/src/server/ua_subscription_events.c index c97c52d0c74..046841e88cf 100644 --- a/src/server/ua_subscription_events.c +++ b/src/server/ua_subscription_events.c @@ -12,19 +12,6 @@ #ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS -typedef struct { - UA_Server *server; - UA_Session *session; - const UA_NodeId *eventNode; - const UA_ContentFilter *contentFilter; - UA_ContentFilterResult *contentFilterResult; - UA_Variant *valueResult; - UA_UInt16 index; -} UA_FilterOperatorContext; - -static UA_StatusCode -evaluateWhereClauseContentFilter(UA_FilterOperatorContext *ctx); - /* We use a 16-Byte ByteString as an identifier */ UA_StatusCode UA_Event_generateEventId(UA_ByteString *generatedId) { @@ -117,957 +104,6 @@ UA_Server_createEvent(UA_Server *server, const UA_NodeId eventType, return UA_STATUSCODE_GOOD; } -static UA_Boolean -isValidEvent(UA_Server *server, const UA_NodeId *validEventParent, - const UA_NodeId *eventId) { - /* find the eventType variableNode */ - UA_QualifiedName findName = UA_QUALIFIEDNAME(0, "EventType"); - UA_BrowsePathResult bpr = browseSimplifiedBrowsePath(server, *eventId, 1, &findName); - if(bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) { - UA_BrowsePathResult_clear(&bpr); - return false; - } - - /* Get the EventType Property Node */ - UA_Variant tOutVariant; - UA_Variant_init(&tOutVariant); - - /* Read the Value of EventType Property Node (the Value should be a NodeId) */ - UA_StatusCode retval = readWithReadValue(server, &bpr.targets[0].targetId.nodeId, - UA_ATTRIBUTEID_VALUE, &tOutVariant); - if(retval != UA_STATUSCODE_GOOD || - !UA_Variant_hasScalarType(&tOutVariant, &UA_TYPES[UA_TYPES_NODEID])) { - UA_BrowsePathResult_clear(&bpr); - return false; - } - - const UA_NodeId *tEventType = (UA_NodeId*)tOutVariant.data; - - /* check whether the EventType is a Subtype of CondtionType - * (Part 9 first implementation) */ - UA_NodeId conditionTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE); - if(UA_NodeId_equal(validEventParent, &conditionTypeId) && - isNodeInTree_singleRef(server, tEventType, &conditionTypeId, - UA_REFERENCETYPEINDEX_HASSUBTYPE)) { - UA_BrowsePathResult_clear(&bpr); - UA_Variant_clear(&tOutVariant); - return true; - } - - /*EventType is not a Subtype of CondtionType - *(ConditionId Clause won't be present in Events, which are not Conditions)*/ - /* check whether Valid Event other than Conditions */ - UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE); - UA_Boolean isSubtypeOfBaseEvent = - isNodeInTree_singleRef(server, tEventType, &baseEventTypeId, - UA_REFERENCETYPEINDEX_HASSUBTYPE); - - UA_BrowsePathResult_clear(&bpr); - UA_Variant_clear(&tOutVariant); - return isSubtypeOfBaseEvent; -} - -/* Resolves a variant of type string or boolean into a corresponding status code */ -static UA_StatusCode -resolveBoolean(UA_Variant operand) { - UA_String value; - value = UA_STRING("True"); - if(((operand.type == &UA_TYPES[UA_TYPES_STRING]) && - (UA_String_equal((UA_String *)operand.data, &value))) || - ((operand.type == &UA_TYPES[UA_TYPES_BOOLEAN]) && - (*(UA_Boolean *)operand.data == UA_TRUE))) { - return UA_STATUSCODE_GOOD; - } - value = UA_STRING("False"); - if(((operand.type == &UA_TYPES[UA_TYPES_STRING]) && - (UA_String_equal((UA_String *)operand.data, &value))) || - ((operand.type == &UA_TYPES[UA_TYPES_BOOLEAN]) && - (*(UA_Boolean *)operand.data == UA_FALSE))) { - return UA_STATUSCODE_BADNOMATCH; - } - - /* If the operand can't be resolved, an error is returned */ - return UA_STATUSCODE_BADFILTEROPERANDINVALID; -} - -/* Part 4: 7.4.4.5 SimpleAttributeOperand - * The clause can point to any attribute of nodes. Either a child of the event - * node and also the event type. */ -static UA_StatusCode -resolveSimpleAttributeOperand(UA_Server *server, UA_Session *session, const UA_NodeId *origin, - const UA_SimpleAttributeOperand *sao, UA_Variant *value) { - /* Prepare the ReadValueId */ - UA_ReadValueId rvi; - UA_ReadValueId_init(&rvi); - rvi.indexRange = sao->indexRange; - rvi.attributeId = sao->attributeId; - - UA_DataValue v; - - if(sao->browsePathSize == 0) { - /* If this list (browsePath) is empty, the Node is the instance of the - * TypeDefinition. (Part 4, 7.4.4.5) */ - rvi.nodeId = *origin; - - /* A Condition is an indirection. Look up the target node. */ - /* TODO: check for Branches! One Condition could have multiple Branches */ - UA_NodeId conditionTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE); - if(UA_NodeId_equal(&sao->typeDefinitionId, &conditionTypeId)) { -#ifdef UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS - UA_StatusCode res = UA_getConditionId(server, origin, &rvi.nodeId); - if(res != UA_STATUSCODE_GOOD) - return res; -#else - return UA_STATUSCODE_BADNOTSUPPORTED; -#endif - } - - v = UA_Server_readWithSession(server, session, &rvi, UA_TIMESTAMPSTORETURN_NEITHER); - - } else { - /* Resolve the browse path, starting from the event-source (and not the - * typeDefinitionId). */ - UA_BrowsePathResult bpr = - browseSimplifiedBrowsePath(server, *origin, sao->browsePathSize, sao->browsePath); - if(bpr.targetsSize == 0 && bpr.statusCode == UA_STATUSCODE_GOOD) - bpr.statusCode = UA_STATUSCODE_BADNOTFOUND; - if(bpr.statusCode != UA_STATUSCODE_GOOD) { - UA_StatusCode res = bpr.statusCode; - UA_BrowsePathResult_clear(&bpr); - return res; - } - - /* Use the first match */ - rvi.nodeId = bpr.targets[0].targetId.nodeId; - v = UA_Server_readWithSession(server, session, &rvi, UA_TIMESTAMPSTORETURN_NEITHER); - UA_BrowsePathResult_clear(&bpr); - } - - /* Move the result to the output */ - if(v.status == UA_STATUSCODE_GOOD && v.hasValue) - *value = v.value; - else - UA_Variant_clear(&v.value); - return v.status; -} - -/* Resolve operands to variants according to the operand type. - * Part 4: 7.17.3 Table 142 specifies the allowed types. */ -static UA_Variant -resolveOperand(UA_FilterOperatorContext *ctx, UA_UInt16 nr) { - UA_StatusCode res; - UA_Variant variant; - UA_Variant_init(&variant); - /*SimpleAttributeOperands*/ - if(ctx->contentFilter->elements[ctx->index].filterOperands[nr].content.decoded.type == - &UA_TYPES[UA_TYPES_SIMPLEATTRIBUTEOPERAND]) { - res = resolveSimpleAttributeOperand(ctx->server, ctx->session, ctx->eventNode, - (UA_SimpleAttributeOperand *)ctx->contentFilter->elements[ctx->index] - .filterOperands[nr].content.decoded.data, - &variant); - /*LiteralAttribute*/ - } else if(ctx->contentFilter->elements[ctx->index].filterOperands[nr].content.decoded.type == - &UA_TYPES[UA_TYPES_LITERALOPERAND]) { - variant = ((UA_LiteralOperand *)ctx->contentFilter->elements[ctx->index] - .filterOperands[nr].content.decoded.data)->value; - res = UA_STATUSCODE_GOOD; - } else if(ctx->contentFilter->elements[ctx->index].filterOperands[nr].content.decoded.type == - &UA_TYPES[UA_TYPES_ELEMENTOPERAND]) { - UA_UInt16 oldIndex = ctx->index; - ctx->index = (UA_UInt16)((UA_ElementOperand *)ctx->contentFilter->elements[ctx->index] - .filterOperands[nr].content.decoded.data)->index; - res = evaluateWhereClauseContentFilter(ctx); - variant = ctx->valueResult[ctx->index]; - ctx->index = oldIndex; /* restore the old index */ - /*ElementOperands*/ - } else { - res = UA_STATUSCODE_BADFILTEROPERANDINVALID; - } - if(res != UA_STATUSCODE_GOOD && res != UA_STATUSCODE_BADNOMATCH) { - variant.type = NULL; - ctx->contentFilterResult->elementResults[ctx->index].operandStatusCodes[nr] = res; - } - return variant; -} - -static UA_StatusCode -ofTypeOperator(UA_FilterOperatorContext *ctx) { - UA_ContentFilterElement *pElement = &ctx->contentFilter->elements[ctx->index]; - UA_Boolean result = false; - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - if(pElement->filterOperandsSize != 1) - return UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; - if(pElement->filterOperands[0].content.decoded.type != - &UA_TYPES[UA_TYPES_LITERALOPERAND]) - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - - UA_LiteralOperand *literalOperand = - (UA_LiteralOperand *) pElement->filterOperands[0].content.decoded.data; - if(!UA_Variant_isScalar(&literalOperand->value)) - return UA_STATUSCODE_BADEVENTFILTERINVALID; - - if(literalOperand->value.type != &UA_TYPES[UA_TYPES_NODEID] || literalOperand->value.data == NULL) - return UA_STATUSCODE_BADEVENTFILTERINVALID; - - UA_NodeId *literalOperandNodeId = (UA_NodeId *) literalOperand->value.data; - UA_Variant typeNodeIdVariant; - UA_Variant_init(&typeNodeIdVariant); - UA_StatusCode readStatusCode = - readObjectProperty(ctx->server, *ctx->eventNode, - UA_QUALIFIEDNAME(0, "EventType"), &typeNodeIdVariant); - if(readStatusCode != UA_STATUSCODE_GOOD) - return readStatusCode; - - if(!UA_Variant_isScalar(&typeNodeIdVariant) || - typeNodeIdVariant.type != &UA_TYPES[UA_TYPES_NODEID] || - typeNodeIdVariant.data == NULL) { - UA_LOG_ERROR(&ctx->server->config.logger, UA_LOGCATEGORY_SERVER, - "EventType has an invalid type."); - UA_Variant_clear(&typeNodeIdVariant); - return UA_STATUSCODE_BADINTERNALERROR; - } - /* check if the eventtype-nodeid is equal to the given oftype argument */ - result = UA_NodeId_equal((UA_NodeId*) typeNodeIdVariant.data, literalOperandNodeId); - /* check if the eventtype-nodeid is a subtype of the given oftype argument */ - if(!result) - result = isNodeInTree_singleRef(ctx->server, - (UA_NodeId*) typeNodeIdVariant.data, - literalOperandNodeId, - UA_REFERENCETYPEINDEX_HASSUBTYPE); - UA_Variant_clear(&typeNodeIdVariant); - if(!result) - return UA_STATUSCODE_BADNOMATCH; - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -andOperator(UA_FilterOperatorContext *ctx) { - UA_StatusCode firstBoolean_and = resolveBoolean(resolveOperand(ctx, 0)); - if(firstBoolean_and == UA_STATUSCODE_BADNOMATCH) { - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - return UA_STATUSCODE_BADNOMATCH; - } - /* Evaluation of second operand */ - UA_StatusCode secondBoolean = resolveBoolean(resolveOperand(ctx, 1)); - /* Filteroperator AND */ - if(secondBoolean == UA_STATUSCODE_BADNOMATCH) { - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - return UA_STATUSCODE_BADNOMATCH; - } - if((firstBoolean_and == UA_STATUSCODE_GOOD) && - (secondBoolean == UA_STATUSCODE_GOOD)) { - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - return UA_STATUSCODE_GOOD; - } - return UA_STATUSCODE_BADFILTERELEMENTINVALID; -} - -static UA_StatusCode -orOperator(UA_FilterOperatorContext *ctx) { - UA_StatusCode firstBoolean_or = resolveBoolean(resolveOperand(ctx, 0)); - if(firstBoolean_or == UA_STATUSCODE_GOOD) { - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - return UA_STATUSCODE_GOOD; - } - /* Evaluation of second operand */ - UA_StatusCode secondBoolean = resolveBoolean(resolveOperand(ctx, 1)); - if(secondBoolean == UA_STATUSCODE_GOOD) { - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - return UA_STATUSCODE_GOOD; - } - if((firstBoolean_or == UA_STATUSCODE_BADNOMATCH) && - (secondBoolean == UA_STATUSCODE_BADNOMATCH)) { - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - return UA_STATUSCODE_BADNOMATCH; - } - return UA_STATUSCODE_BADFILTERELEMENTINVALID; -} - -static UA_Boolean -isNumericUnsigned(UA_UInt32 dataTypeKind){ - if(dataTypeKind == UA_DATATYPEKIND_UINT64 || - dataTypeKind == UA_DATATYPEKIND_UINT32 || - dataTypeKind == UA_DATATYPEKIND_UINT16 || - dataTypeKind == UA_DATATYPEKIND_BYTE) - return true; - return false; -} - -static UA_Boolean -isNumericSigned(UA_UInt32 dataTypeKind){ - if(dataTypeKind == UA_DATATYPEKIND_INT64 || - dataTypeKind == UA_DATATYPEKIND_INT32 || - dataTypeKind == UA_DATATYPEKIND_INT16 || - dataTypeKind == UA_DATATYPEKIND_SBYTE) - return true; - return false; -} - -static UA_Boolean -isFloatingPoint(UA_UInt32 dataTypeKind){ - if(dataTypeKind == UA_DATATYPEKIND_FLOAT || - dataTypeKind == UA_DATATYPEKIND_DOUBLE) - return true; - return false; -} - -static UA_Boolean -isStringType(UA_UInt32 dataTypeKind){ - if(dataTypeKind == UA_DATATYPEKIND_STRING || - dataTypeKind == UA_DATATYPEKIND_BYTESTRING) - return true; - return false; -} - - - -static UA_StatusCode -implicitNumericVariantTransformation(UA_Variant *variant, void *data){ - if(variant->type == &UA_TYPES[UA_TYPES_UINT64]){ - *(UA_UInt64 *)data = *(UA_UInt64 *)variant->data; - UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_UINT64]); - } else if(variant->type == &UA_TYPES[UA_TYPES_UINT32]){ - *(UA_UInt64 *)data = *(UA_UInt32 *)variant->data; - UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_UINT64]); - } else if(variant->type == &UA_TYPES[UA_TYPES_UINT16]){ - *(UA_UInt64 *)data = *(UA_UInt16 *)variant->data; - UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_UINT64]); - } else if(variant->type == &UA_TYPES[UA_TYPES_BYTE]){ - *(UA_UInt64 *)data = *(UA_Byte *)variant->data; - UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_UINT64]); - } else if(variant->type == &UA_TYPES[UA_TYPES_INT64]){ - *(UA_Int64 *)data = *(UA_Int64 *)variant->data; - UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_INT64]); - } else if(variant->type == &UA_TYPES[UA_TYPES_INT32]){ - *(UA_Int64 *)data = *(UA_Int32 *)variant->data; - UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_INT64]); - } else if(variant->type == &UA_TYPES[UA_TYPES_INT16]){ - *(UA_Int64 *)data = *(UA_Int16 *)variant->data; - UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_INT64]); - } else if(variant->type == &UA_TYPES[UA_TYPES_SBYTE]){ - *(UA_Int64 *)data = *(UA_SByte *)variant->data; - UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_INT64]); - } else if(variant->type == &UA_TYPES[UA_TYPES_DOUBLE]){ - *(UA_Double *)data = *(UA_Double *)variant->data; - UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_DOUBLE]); - } else if(variant->type == &UA_TYPES[UA_TYPES_SBYTE]){ - *(UA_Double *)data = *(UA_Float *)variant->data; - UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_DOUBLE]); - } else { - return UA_STATUSCODE_BADTYPEMISMATCH; - } - return UA_STATUSCODE_GOOD; -} - -/* 0 -> Same Type, 1 -> Implicit Cast, 2 -> Only explicit Cast, -1 -> cast invalid */ -static UA_SByte convertLookup[21][21] = { - { 0, 1,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 1,-1, 2,-1,-1, 1, 1, 1,-1}, - { 2, 0,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 1,-1, 2,-1,-1, 1, 1, 1,-1}, - {-1,-1, 0,-1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, - {-1,-1,-1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1}, - { 2,2,-1,-1, 0,-1, 2,-1, 2, 2, 2,-1, 2,-1, 2,-1,-1, 2, 2, 2,-1}, - {-1,-1,-1,-1,-1, 0,-1,-1,-1,-1,-1, 2,-1,-1, 1,-1,-1,-1,-1,-1,-1}, - { 2, 2,-1,-1, 1,-1, 0,-1, 2, 2, 2,-1, 2,-1, 2,-1,-1, 2, 2, 2,-1}, - {-1,-1, 2,-1,-1,-1,-1, 0,-1,-1,-1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1}, - { 2, 2,-1,-1, 1,-1, 1,-1, 0, 1, 1,-1, 2,-1, 2,-1,-1, 2, 1, 1,-1}, - { 2, 2,-1,-1, 1,-1, 1,-1, 2, 0, 1,-1, 2, 2, 2,-1,-1, 2, 2, 1,-1}, - { 2, 2,-1,-1, 1,-1, 1,-1, 2, 2, 0,-1, 2, 2, 2,-1,-1, 2, 2, 2,-1}, - {-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1, 0,-1,-1, 1,-1,-1,-1,-1,-1,-1}, - { 2, 2,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 0,-1, 2,-1,-1, 1, 1, 1,-1}, - {-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1,-1,-1, 0,-1,-1,-1, 2, 1, 1,-1}, - { 1, 1,-1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1,-1, 0, 2, 2, 1, 1, 1,-1}, - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 0,-1,-1,-1,-1,-1}, - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 0,-1,-1,-1,-1}, - { 2, 2,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 2, 1, 2,-1,-1, 0, 1, 1,-1}, - { 2, 2,-1,-1, 1,-1, 1,-1, 2, 1, 1,-1, 2, 2, 2,-1,-1, 2, 0, 1,-1}, - { 2, 2,-1,-1, 1,-1, 1,-1, 2, 2, 1,-1, 2, 2, 2,-1,-1, 2, 2, 0,-1}, - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0} -}; - -/* This array maps the index of the - * standard DataType-Kind order to the - * order of the type convertion array */ -static UA_Byte dataTypeKindIndex[30] = { - 0, 12, 1, 8, 17, - 9, 18, 10, 19, 6, - 4, 14, 3, 7, 2, - 20, 11, 5, 13, 16, - 15, 255,255,255,255, - 255,255,255,255,255 -}; - -/* - * The OPC UA Standard defines in Part 4 several data type casting-rules. (see 1.04 part 4 Table 122) - * Return: - * 0 -> same type - * 1 -> types can be casted implicit - * 2 -> types can only be explicitly casted - * -1 -> types can't be casted - */ -static UA_SByte -checkTypeCastingOption(const UA_DataType *cast_target, const UA_DataType *cast_source) { - UA_Byte firstOperatorTypeKindIndex = UA_BYTE_MAX; - UA_Byte secondOperatorTypeKindIndex = UA_BYTE_MAX; - firstOperatorTypeKindIndex = dataTypeKindIndex[cast_target->typeKind]; - secondOperatorTypeKindIndex = dataTypeKindIndex[cast_source->typeKind]; - - if(firstOperatorTypeKindIndex == UA_BYTE_MAX || secondOperatorTypeKindIndex == UA_BYTE_MAX) - return -1; - - return convertLookup[firstOperatorTypeKindIndex][secondOperatorTypeKindIndex]; -} - -/* Compare operation for equal, gt, le, gte, lee - * UA_STATUSCODE_GOOD if the comparison was true - * UA_STATUSCODE_BADNOMATCH if the comparison was false - * UA_STATUSCODE_BADFILTEROPERATORINVALID for invalid operators - * UA_STATUSCODE_BADTYPEMISMATCH if one of the operands was not numeric - * ToDo Array-Casting - */ -static UA_StatusCode -compareOperation(UA_Variant *firstOperand, UA_Variant *secondOperand, UA_FilterOperator op) { - /* get precedence of the operand types */ - UA_Int16 firstOperand_precedence = UA_DataType_getPrecedence(firstOperand->type); - UA_Int16 secondOperand_precedence = UA_DataType_getPrecedence(secondOperand->type); - /* if the types are not equal and one of the precedence-ranks is -1, then there is - no implicit conversion possible and therefore no compare */ - if(!UA_NodeId_equal(&firstOperand->type->typeId, &secondOperand->type->typeId) && - (firstOperand_precedence == -1 || secondOperand_precedence == -1)){ - return UA_STATUSCODE_BADTYPEMISMATCH; - } - /* check if the precedence order of the operators is swapped */ - UA_Variant *firstCompareOperand = firstOperand; - UA_Variant *secondCompareOperand = secondOperand; - UA_Boolean swapped = false; - if (firstOperand_precedence < secondOperand_precedence){ - firstCompareOperand = secondOperand; - secondCompareOperand = firstOperand; - swapped = true; - } - UA_SByte castRule = - checkTypeCastingOption(firstCompareOperand->type, secondCompareOperand->type); - - if(!(castRule == 0 || castRule == 1)){ - return UA_STATUSCODE_BADTYPEMISMATCH; - } - - /* The operand Data-Types influence the behavior and steps for the comparison. - * We need to check the operand types and store a rule which is used to select - * the right behavior afterwards. */ - enum compareHandlingRuleEnum { - UA_TYPES_EQUAL_ORDERED, - UA_TYPES_EQUAL_UNORDERED, - UA_TYPES_DIFFERENT_NUMERIC_UNSIGNED, - UA_TYPES_DIFFERENT_NUMERIC_SIGNED, - UA_TYPES_DIFFERENT_NUMERIC_FLOATING_POINT, - UA_TYPES_DIFFERENT_TEXT, - UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN, - UA_TYPES_DIFFERENT_COMPARE_EXPLIC - } compareHandlingRuleEnum; - - if(castRule == 0 && - (UA_DataType_isNumeric(firstOperand->type) || - firstCompareOperand->type->typeKind == (UA_UInt32) UA_DATATYPEKIND_DATETIME || - firstCompareOperand->type->typeKind == (UA_UInt32) UA_DATATYPEKIND_STRING || - firstCompareOperand->type->typeKind == (UA_UInt32) UA_DATATYPEKIND_BYTESTRING)){ - /* Data-Types with a natural order (allow le, gt, lee, gte) */ - compareHandlingRuleEnum = UA_TYPES_EQUAL_ORDERED; - } else if(castRule == 0){ - /* Data-Types without a natural order (le, gt, lee, gte are not allowed) */ - compareHandlingRuleEnum = UA_TYPES_EQUAL_UNORDERED; - } else if(castRule == 1 && - isNumericSigned(firstOperand->type->typeKind) && - isNumericSigned(secondOperand->type->typeKind)){ - compareHandlingRuleEnum = UA_TYPES_DIFFERENT_NUMERIC_SIGNED; - } else if(castRule == 1 && - isNumericUnsigned(firstOperand->type->typeKind) && - isNumericUnsigned(secondOperand->type->typeKind)){ - compareHandlingRuleEnum = UA_TYPES_DIFFERENT_NUMERIC_UNSIGNED; - } else if(castRule == 1 && - isFloatingPoint(firstOperand->type->typeKind) && - isFloatingPoint(secondOperand->type->typeKind)){ - compareHandlingRuleEnum = UA_TYPES_DIFFERENT_NUMERIC_FLOATING_POINT; - } else if(castRule == 1 && - isStringType(firstOperand->type->typeKind)&& - isStringType(secondOperand->type->typeKind)){ - compareHandlingRuleEnum = UA_TYPES_DIFFERENT_TEXT; - } else if(castRule == -1 || castRule == 2){ - compareHandlingRuleEnum = UA_TYPES_DIFFERENT_COMPARE_EXPLIC; - } else { - compareHandlingRuleEnum = UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN; - } - - if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN) - return UA_STATUSCODE_BADFILTEROPERATORINVALID; - - if(swapped){ - firstCompareOperand = secondCompareOperand; - secondCompareOperand = firstCompareOperand; - } - - if(op == UA_FILTEROPERATOR_EQUALS){ - UA_Byte variantContent[16]; - memset(&variantContent, 0, sizeof(UA_Byte) * 16); - if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_SIGNED || - compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_UNSIGNED || - compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_FLOATING_POINT) { - implicitNumericVariantTransformation(firstCompareOperand, variantContent); - implicitNumericVariantTransformation(secondCompareOperand, &variantContent[8]); - } else if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_TEXT) { - firstCompareOperand->type = &UA_TYPES[UA_TYPES_STRING]; - secondCompareOperand->type = &UA_TYPES[UA_TYPES_STRING]; - } else if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN || - compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_EXPLIC ){ - return UA_STATUSCODE_BADFILTEROPERATORINVALID; - } - if(UA_order(firstCompareOperand, secondCompareOperand, &UA_TYPES[UA_TYPES_VARIANT]) == UA_ORDER_EQ) { - return UA_STATUSCODE_GOOD; - } - } else { - UA_Byte variantContent[16]; - if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_SIGNED || - compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_UNSIGNED || - compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_FLOATING_POINT) { - memset(&variantContent, 0, sizeof(UA_Byte) * 16); - implicitNumericVariantTransformation(firstCompareOperand, variantContent); - implicitNumericVariantTransformation(secondCompareOperand, &variantContent[8]); - } else if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_TEXT) { - firstCompareOperand->type = &UA_TYPES[UA_TYPES_STRING]; - secondCompareOperand->type = &UA_TYPES[UA_TYPES_STRING]; - } else if(compareHandlingRuleEnum == UA_TYPES_EQUAL_UNORDERED) { - return UA_STATUSCODE_BADFILTEROPERATORINVALID; - } else if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN || - compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_EXPLIC) { - return UA_STATUSCODE_BADFILTEROPERATORINVALID; - } - UA_Order gte_result = UA_order(firstCompareOperand, secondCompareOperand, &UA_TYPES[UA_TYPES_VARIANT]); - if(op == UA_FILTEROPERATOR_LESSTHAN) { - if(gte_result == UA_ORDER_LESS) { - return UA_STATUSCODE_GOOD; - } - } else if(op == UA_FILTEROPERATOR_GREATERTHAN) { - if(gte_result == UA_ORDER_MORE) { - return UA_STATUSCODE_GOOD; - } - } else if(op == UA_FILTEROPERATOR_LESSTHANOREQUAL) { - if(gte_result == UA_ORDER_LESS || gte_result == UA_ORDER_EQ) { - return UA_STATUSCODE_GOOD; - } - } else if(op == UA_FILTEROPERATOR_GREATERTHANOREQUAL) { - if(gte_result == UA_ORDER_MORE || gte_result == UA_ORDER_EQ) { - return UA_STATUSCODE_GOOD; - } - } - } - return UA_STATUSCODE_BADNOMATCH; -} - -static UA_StatusCode -compareOperator(UA_FilterOperatorContext *ctx) { - UA_Variant firstOperand = resolveOperand(ctx, 0); - if(UA_Variant_isEmpty(&firstOperand)) - return UA_STATUSCODE_BADFILTEROPERANDINVALID; - UA_Variant secondOperand = resolveOperand(ctx, 1); - if(UA_Variant_isEmpty(&secondOperand)) { - return UA_STATUSCODE_BADFILTEROPERANDINVALID; - } - /* ToDo remove the following restriction: Add support for arrays */ - if(!UA_Variant_isScalar(&firstOperand) || !UA_Variant_isScalar(&secondOperand)){ - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - } - return compareOperation(&firstOperand, &secondOperand, - ctx->contentFilter->elements[ctx->index].filterOperator); -} - -static UA_StatusCode -bitwiseOperator(UA_FilterOperatorContext *ctx) { - /* The bitwise operators all have 2 operands which are evaluated equally. */ - UA_Variant firstOperand = resolveOperand(ctx, 0); - if(UA_Variant_isEmpty(&firstOperand)) { - return UA_STATUSCODE_BADFILTEROPERANDINVALID; - } - UA_Variant secondOperand = resolveOperand(ctx, 1); - if(UA_Variant_isEmpty(&secondOperand)) { - return UA_STATUSCODE_BADFILTEROPERANDINVALID; - } - - UA_Boolean bitwiseAnd = - ctx->contentFilter->elements[ctx->index].filterOperator == UA_FILTEROPERATOR_BITWISEAND; - - /* check if the operators are integers */ - if(!UA_DataType_isNumeric(firstOperand.type) || - !UA_DataType_isNumeric(secondOperand.type) || - !UA_Variant_isScalar(&firstOperand) || - !UA_Variant_isScalar(&secondOperand) || - (firstOperand.type == &UA_TYPES[UA_TYPES_DOUBLE]) || - (secondOperand.type == &UA_TYPES[UA_TYPES_DOUBLE]) || - (secondOperand.type == &UA_TYPES[UA_TYPES_FLOAT]) || - (firstOperand.type == &UA_TYPES[UA_TYPES_FLOAT])) { - return UA_STATUSCODE_BADFILTEROPERANDINVALID; - } - - /* check which is the return type (higher precedence == bigger integer)*/ - UA_Int16 precedence = UA_DataType_getPrecedence(firstOperand.type); - if(precedence > UA_DataType_getPrecedence(secondOperand.type)) { - precedence = UA_DataType_getPrecedence(secondOperand.type); - } - - switch(precedence){ - case 3: - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_INT64]; - UA_Int64 result_int64; - if(bitwiseAnd) { - result_int64 = *((UA_Int64 *)firstOperand.data) & *((UA_Int64 *)secondOperand.data); - } else { - result_int64 = *((UA_Int64 *)firstOperand.data) | *((UA_Int64 *)secondOperand.data); - } - UA_Int64_copy(&result_int64, (UA_Int64 *) ctx->valueResult[ctx->index].data); - break; - case 4: - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_UINT64]; - UA_UInt64 result_uint64s; - if(bitwiseAnd) { - result_uint64s = *((UA_UInt64 *)firstOperand.data) & *((UA_UInt64 *)secondOperand.data); - } else { - result_uint64s = *((UA_UInt64 *)firstOperand.data) | *((UA_UInt64 *)secondOperand.data); - } - UA_UInt64_copy(&result_uint64s, (UA_UInt64 *) ctx->valueResult[ctx->index].data); - break; - case 5: - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_INT32]; - UA_Int32 result_int32; - if(bitwiseAnd) { - result_int32 = *((UA_Int32 *)firstOperand.data) & *((UA_Int32 *)secondOperand.data); - } else { - result_int32 = *((UA_Int32 *)firstOperand.data) | *((UA_Int32 *)secondOperand.data); - } - UA_Int32_copy(&result_int32, (UA_Int32 *) ctx->valueResult[ctx->index].data); - break; - case 6: - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_UINT32]; - UA_UInt32 result_uint32; - if(bitwiseAnd) { - result_uint32 = *((UA_UInt32 *)firstOperand.data) & *((UA_UInt32 *)secondOperand.data); - } else { - result_uint32 = *((UA_UInt32 *)firstOperand.data) | *((UA_UInt32 *)secondOperand.data); - } - UA_UInt32_copy(&result_uint32, (UA_UInt32 *) ctx->valueResult[ctx->index].data); - break; - case 8: - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_INT16]; - UA_Int16 result_int16; - if(bitwiseAnd) { - result_int16 = *((UA_Int16 *)firstOperand.data) & *((UA_Int16 *)secondOperand.data); - } else { - result_int16 = *((UA_Int16 *)firstOperand.data) | *((UA_Int16 *)secondOperand.data); - } - UA_Int16_copy(&result_int16, (UA_Int16 *) ctx->valueResult[ctx->index].data); - break; - case 9: - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_UINT16]; - UA_UInt16 result_uint16; - if(bitwiseAnd) { - result_uint16 = *((UA_UInt16 *)firstOperand.data) & *((UA_UInt16 *)secondOperand.data); - } else { - result_uint16 = *((UA_UInt16 *)firstOperand.data) | *((UA_UInt16 *)secondOperand.data); - } - UA_UInt16_copy(&result_uint16, (UA_UInt16 *) ctx->valueResult[ctx->index].data); - break; - case 10: - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_SBYTE]; - UA_SByte result_sbyte; - if(bitwiseAnd) { - result_sbyte = *((UA_SByte *)firstOperand.data) & *((UA_SByte *)secondOperand.data); - } else { - result_sbyte = *((UA_SByte *)firstOperand.data) | *((UA_SByte *)secondOperand.data); - } - UA_SByte_copy(&result_sbyte, (UA_SByte *) ctx->valueResult[ctx->index].data); - break; - case 11: - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BYTE]; - UA_Byte result_byte; - if(bitwiseAnd) { - result_byte = *((UA_Byte *)firstOperand.data) & *((UA_Byte *)secondOperand.data); - } else { - result_byte = *((UA_Byte *)firstOperand.data) | *((UA_Byte *)secondOperand.data); - } - UA_Byte_copy(&result_byte, (UA_Byte *) ctx->valueResult[ctx->index].data); - break; - default: - return UA_STATUSCODE_BADFILTEROPERANDINVALID; - } - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -betweenOperator(UA_FilterOperatorContext *ctx) { - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - - UA_Variant firstOperand = resolveOperand(ctx, 0); - UA_Variant secondOperand = resolveOperand(ctx, 1); - UA_Variant thirdOperand = resolveOperand(ctx, 2); - - if((UA_Variant_isEmpty(&firstOperand) || - UA_Variant_isEmpty(&secondOperand) || - UA_Variant_isEmpty(&thirdOperand)) || - (!UA_DataType_isNumeric(firstOperand.type) || - !UA_DataType_isNumeric(secondOperand.type) || - !UA_DataType_isNumeric(thirdOperand.type)) || - (!UA_Variant_isScalar(&firstOperand) || - !UA_Variant_isScalar(&secondOperand) || - !UA_Variant_isScalar(&thirdOperand))) { - return UA_STATUSCODE_BADFILTEROPERANDINVALID; - } - - /* Between can be evaluated through greaterThanOrEqual and lessThanOrEqual */ - if(compareOperation(&firstOperand, &secondOperand, UA_FILTEROPERATOR_GREATERTHANOREQUAL) == UA_STATUSCODE_GOOD && - compareOperation(&firstOperand, &thirdOperand, UA_FILTEROPERATOR_LESSTHANOREQUAL) == UA_STATUSCODE_GOOD){ - return UA_STATUSCODE_GOOD; - } - return UA_STATUSCODE_BADNOMATCH; -} - -static UA_StatusCode -inListOperator(UA_FilterOperatorContext *ctx) { - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - UA_Variant firstOperand = resolveOperand(ctx, 0); - - if(UA_Variant_isEmpty(&firstOperand) || - !UA_Variant_isScalar(&firstOperand)) { - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - } - - /* Evaluating the list of operands */ - for(size_t i = 1; i < ctx->contentFilter->elements[ctx->index].filterOperandsSize; i++) { - /* Resolving the current operand */ - UA_Variant currentOperator = resolveOperand(ctx, (UA_UInt16)i); - - /* Check if the operand conforms to the operator*/ - if(UA_Variant_isEmpty(¤tOperator) || - !UA_Variant_isScalar(¤tOperator)) { - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - } - if(compareOperation(&firstOperand, ¤tOperator, UA_FILTEROPERATOR_EQUALS)) { - return UA_STATUSCODE_GOOD; - } - } - return UA_STATUSCODE_BADNOMATCH; -} - -static UA_StatusCode -isNullOperator(UA_FilterOperatorContext *ctx) { - /* Checking if operand is NULL. This is done by reducing the operand to a - * variant and then checking if it is empty. */ - UA_Variant operand = resolveOperand(ctx, 0); - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - if(!UA_Variant_isEmpty(&operand)) - return UA_STATUSCODE_BADNOMATCH; - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -notOperator(UA_FilterOperatorContext *ctx) { - /* Inverting the boolean value of the operand. */ - UA_StatusCode res = resolveBoolean(resolveOperand(ctx, 0)); - ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; - /* invert result */ - if(res == UA_STATUSCODE_GOOD) - return UA_STATUSCODE_BADNOMATCH; - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -evaluateWhereClauseContentFilter(UA_FilterOperatorContext *ctx) { - UA_LOCK_ASSERT(&ctx->server->serviceMutex, 1); - - if(ctx->contentFilter->elements == NULL || ctx->contentFilter->elementsSize == 0) { - /* Nothing to do.*/ - return UA_STATUSCODE_GOOD; - } - - /* The first element needs to be evaluated, this might be linked to other - * elements, which are evaluated in these cases. See 7.4.1 in Part 4. */ - UA_ContentFilterElement *pElement = &ctx->contentFilter->elements[ctx->index]; - UA_StatusCode *result = &ctx->contentFilterResult->elementResults[ctx->index].statusCode; - switch(pElement->filterOperator) { - case UA_FILTEROPERATOR_INVIEW: - /* Fallthrough */ - case UA_FILTEROPERATOR_RELATEDTO: - /* Not allowed for event WhereClause according to 7.17.3 in Part 4 */ - return UA_STATUSCODE_BADEVENTFILTERINVALID; - case UA_FILTEROPERATOR_EQUALS: - /* Fallthrough */ - case UA_FILTEROPERATOR_GREATERTHAN: - /* Fallthrough */ - case UA_FILTEROPERATOR_LESSTHAN: - /* Fallthrough */ - case UA_FILTEROPERATOR_GREATERTHANOREQUAL: - /* Fallthrough */ - case UA_FILTEROPERATOR_LESSTHANOREQUAL: - *result = compareOperator(ctx); - break; - case UA_FILTEROPERATOR_LIKE: - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - case UA_FILTEROPERATOR_NOT: - *result = notOperator(ctx); - break; - case UA_FILTEROPERATOR_BETWEEN: - *result = betweenOperator(ctx); - break; - case UA_FILTEROPERATOR_INLIST: - /* ToDo currently only numeric types are allowed */ - *result = inListOperator(ctx); - break; - case UA_FILTEROPERATOR_ISNULL: - *result = isNullOperator(ctx); - break; - case UA_FILTEROPERATOR_AND: - *result = andOperator(ctx); - break; - case UA_FILTEROPERATOR_OR: - *result = orOperator(ctx); - break; - case UA_FILTEROPERATOR_CAST: - return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - case UA_FILTEROPERATOR_BITWISEAND: - *result = bitwiseOperator(ctx); - break; - case UA_FILTEROPERATOR_BITWISEOR: - *result = bitwiseOperator(ctx); - break; - case UA_FILTEROPERATOR_OFTYPE: - *result = ofTypeOperator(ctx); - break; - default: - return UA_STATUSCODE_BADFILTEROPERATORINVALID; - } - - if(ctx->valueResult[ctx->index].type == &UA_TYPES[UA_TYPES_BOOLEAN]) { - UA_Boolean *res = UA_Boolean_new(); - if(ctx->contentFilterResult->elementResults[ctx->index].statusCode == UA_STATUSCODE_GOOD) - *res = true; - else - *res = false; - ctx->valueResult[ctx->index].data = res; - } - return ctx->contentFilterResult->elementResults[ctx->index].statusCode; -} - -UA_StatusCode -UA_Server_evaluateWhereClauseContentFilter(UA_Server *server, UA_Session *session, - const UA_NodeId *eventNode, - const UA_ContentFilter *contentFilter, - UA_ContentFilterResult *contentFilterResult) { - if(contentFilter->elementsSize == 0) - return UA_STATUSCODE_GOOD; - /* TODO add maximum lenth size to the server config */ - if (contentFilter->elementsSize > 256) - return UA_STATUSCODE_BADINVALIDARGUMENT; - UA_STACKARRAY(UA_Variant, valueResult, contentFilter->elementsSize); - for(size_t i = 0; i < contentFilter->elementsSize; ++i) { - UA_Variant_init(&valueResult[i]); - } - - UA_FilterOperatorContext ctx; - ctx.server = server; - ctx.session = session; - ctx.eventNode = eventNode; - ctx.contentFilter = contentFilter; - ctx.contentFilterResult = contentFilterResult; - ctx.valueResult = valueResult; - ctx.index = 0; - - UA_StatusCode res = evaluateWhereClauseContentFilter(&ctx); - for(size_t i = 0; i < ctx.contentFilter->elementsSize; i++) { - if(!UA_Variant_isEmpty(&ctx.valueResult[i])) - UA_Variant_clear(&ctx.valueResult[i]); - } - return res; -} - -/* Filters the given event with the given filter and writes the results into a - * notification */ -static UA_StatusCode -UA_Server_filterEvent(UA_Server *server, UA_Session *session, - const UA_NodeId *eventNode, UA_EventFilter *filter, - UA_EventFieldList *efl, UA_EventFilterResult *result) { - if(filter->selectClausesSize == 0) - return UA_STATUSCODE_BADEVENTFILTERINVALID; - - UA_EventFieldList_init(efl); - efl->eventFields = (UA_Variant *) - UA_Array_new(filter->selectClausesSize, &UA_TYPES[UA_TYPES_VARIANT]); - if(!efl->eventFields) - return UA_STATUSCODE_BADOUTOFMEMORY; - efl->eventFieldsSize = filter->selectClausesSize; - - /* empty event filter result */ - UA_EventFilterResult_init(result); - result->selectClauseResultsSize = filter->selectClausesSize; - result->selectClauseResults = (UA_StatusCode *) - UA_Array_new(filter->selectClausesSize, &UA_TYPES[UA_TYPES_STATUSCODE]); - if(!result->selectClauseResults) { - UA_EventFieldList_clear(efl); - UA_EventFilterResult_clear(result); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - /* prepare content filter result structure */ - if(filter->whereClause.elementsSize != 0) { - result->whereClauseResult.elementResultsSize = filter->whereClause.elementsSize; - result->whereClauseResult.elementResults = (UA_ContentFilterElementResult *) - UA_Array_new(filter->whereClause.elementsSize, - &UA_TYPES[UA_TYPES_CONTENTFILTERELEMENTRESULT]); - if(!result->whereClauseResult.elementResults) { - UA_EventFieldList_clear(efl); - UA_EventFilterResult_clear(result); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - for(size_t i = 0; i < result->whereClauseResult.elementResultsSize; ++i) { - result->whereClauseResult.elementResults[i].operandStatusCodesSize = - filter->whereClause.elements->filterOperandsSize; - result->whereClauseResult.elementResults[i].operandStatusCodes = - (UA_StatusCode *)UA_Array_new( - filter->whereClause.elements->filterOperandsSize, - &UA_TYPES[UA_TYPES_STATUSCODE]); - if(!result->whereClauseResult.elementResults[i].operandStatusCodes) { - UA_EventFieldList_clear(efl); - UA_EventFilterResult_clear(result); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - } - } - - /* Apply the content (where) filter */ - UA_StatusCode res = - UA_Server_evaluateWhereClauseContentFilter(server, session, eventNode, - &filter->whereClause, &result->whereClauseResult); - if(res != UA_STATUSCODE_GOOD){ - UA_EventFieldList_clear(efl); - UA_EventFilterResult_clear(result); - return res; - } - - /* Apply the select filter */ - /* Check if the browsePath is BaseEventType, in which case nothing more - * needs to be checked */ - UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE); - for(size_t i = 0; i < filter->selectClausesSize; i++) { - if(!UA_NodeId_equal(&filter->selectClauses[i].typeDefinitionId, &baseEventTypeId) && - !isValidEvent(server, &filter->selectClauses[i].typeDefinitionId, eventNode)) { - UA_Variant_init(&efl->eventFields[i]); - /* EventFilterResult currently isn't being used - notification->result.selectClauseResults[i] = UA_STATUSCODE_BADTYPEDEFINITIONINVALID; */ - continue; - } - - /* TODO: Put the result into the selectClausResults */ - resolveSimpleAttributeOperand(server, session, eventNode, - &filter->selectClauses[i], &efl->eventFields[i]); - } - - return UA_STATUSCODE_GOOD; -} - static UA_StatusCode eventSetStandardFields(UA_Server *server, const UA_NodeId *event, const UA_NodeId *origin, UA_ByteString *outEventId) { @@ -1154,9 +190,9 @@ UA_Event_addEventToMonitoredItem(UA_Server *server, const UA_NodeId *event, UA_Subscription *sub = mon->subscription; UA_Session *session = sub->session; - UA_StatusCode retval = UA_Server_filterEvent(server, session, event, - eventFilter, ¬ification->data.event, - ¬ification->result); + UA_StatusCode retval = filterEvent(server, session, event, + eventFilter, ¬ification->data.event, + ¬ification->result); if(retval != UA_STATUSCODE_GOOD) { UA_Notification_delete(notification); if(retval == UA_STATUSCODE_BADNOMATCH) @@ -1208,8 +244,8 @@ setHistoricalEvent(UA_Server *server, const UA_NodeId *origin, UA_EventFilter *filter = (UA_EventFilter*) historicalEventFilterValue.data; UA_EventFieldList efl; UA_EventFilterResult result; - retval = UA_Server_filterEvent(server, &server->adminSession, - eventNodeId, filter, &efl, &result); + retval = filterEvent(server, &server->adminSession, + eventNodeId, filter, &efl, &result); if(retval == UA_STATUSCODE_GOOD) server->config.historyDatabase.setEvent(server, server->config.historyDatabase.context, origin, emitNodeId, filter, &efl); @@ -1387,234 +423,6 @@ triggerEvent(UA_Server *server, const UA_NodeId eventNodeId, return retval; } -/* - * Initial select clause validation. The following checks are currently performed: - * - Check if typedefenitionid or browsepath of any clause is NULL - * - Check if the eventType is a subtype of BaseEventType - * - Check if attributeId is valid - * - Check if browsePath contains null - * - Check if indexRange is defined and if it is parsable - * - Check if attributeId is value - */ -void -UA_Event_staticSelectClauseValidation(UA_Server *server, - const UA_EventFilter *eventFilter, - UA_StatusCode *result) { - /* The selectClause only has to be checked, if the size is not zero */ - if(eventFilter->selectClausesSize == 0) - return; - for(size_t i = 0; i < eventFilter->selectClausesSize; ++i) { - result[i] = UA_STATUSCODE_GOOD; - /* /typedefenitionid or browsepath of any clause is not NULL ? */ - if(UA_NodeId_isNull(&eventFilter->selectClauses[i].typeDefinitionId)) { - result[i] = UA_STATUSCODE_BADTYPEDEFINITIONINVALID; - continue; - } - /*ToDo: Check the following workaround. In UaExpert Event View the selection - * of the Server Object set up 7 select filter entries by default. The last - * element ist from node 2782 (A&C ConditionType). Since the reduced - * information model dos not contain this type, the result has a brows path of - * "null" which results in an error. */ - UA_NodeId ac_conditionType = UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE); - if(UA_NodeId_equal(&eventFilter->selectClauses[i].typeDefinitionId, &ac_conditionType)) { - continue; - } - if(&eventFilter->selectClauses[i].browsePath[0] == NULL) { - result[i] = UA_STATUSCODE_BADBROWSENAMEINVALID; - continue; - } - /* eventType is a subtype of BaseEventType ? */ - UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE); - if(!isNodeInTree_singleRef( - server, &eventFilter->selectClauses[i].typeDefinitionId, - &baseEventTypeId, UA_REFERENCETYPEINDEX_HASSUBTYPE)) { - result[i] = UA_STATUSCODE_BADTYPEDEFINITIONINVALID; - continue; - } - /* attributeId is valid ? */ - if(!((0 < eventFilter->selectClauses[i].attributeId) && - (eventFilter->selectClauses[i].attributeId < 28))) { - result[i] = UA_STATUSCODE_BADATTRIBUTEIDINVALID; - continue; - } - /* browsePath contains null ? */ - for(size_t j = 0; j < eventFilter->selectClauses[i].browsePathSize; ++j) { - if(UA_QualifiedName_isNull( - &eventFilter->selectClauses[i].browsePath[j])) { - result[i] = UA_STATUSCODE_BADBROWSENAMEINVALID; - break; - } - } - if(result[i] != UA_STATUSCODE_GOOD) - continue; - /*indexRange is defined ? */ - if(!UA_String_equal(&eventFilter->selectClauses[i].indexRange, - &UA_STRING_NULL)) { - /* indexRange is parsable ? */ - UA_NumericRange numericRange = UA_NUMERICRANGE(""); - if(UA_NumericRange_parse(&numericRange, - eventFilter->selectClauses[i].indexRange) != - UA_STATUSCODE_GOOD) { - result[i] = UA_STATUSCODE_BADINDEXRANGEINVALID; - continue; - } - UA_free(numericRange.dimensions); - /* attributeId is value ? */ - if(eventFilter->selectClauses[i].attributeId != UA_ATTRIBUTEID_VALUE) { - result[i] = UA_STATUSCODE_BADTYPEMISMATCH; - continue; - } - } - } -} - -/* - * Initial content filter (where clause) check. Current checks: - * - Number of operands for each (supported) operator - */ -UA_StatusCode -UA_Event_staticWhereClauseValidation(UA_Server *server, - const UA_ContentFilter *filter, - UA_ContentFilterResult *result) { - UA_ContentFilterResult_init(result); - result->elementResultsSize = filter->elementsSize; - if(result->elementResultsSize == 0) - return UA_STATUSCODE_GOOD; - result->elementResults = - (UA_ContentFilterElementResult *)UA_Array_new( - result->elementResultsSize, - &UA_TYPES[UA_TYPES_CONTENTFILTERELEMENTRESULT]); - if(!result->elementResults) - return UA_STATUSCODE_BADOUTOFMEMORY; - for(size_t i = 0; i < result->elementResultsSize; ++i) { - UA_ContentFilterElementResult *er = &result->elementResults[i]; - UA_ContentFilterElement ef = filter->elements[i]; - UA_ContentFilterElementResult_init(er); - er->operandStatusCodes = - (UA_StatusCode *)UA_Array_new( - ef.filterOperandsSize, - &UA_TYPES[UA_TYPES_STATUSCODE]); - er->operandStatusCodesSize = ef.filterOperandsSize; - - switch(ef.filterOperator) { - case UA_FILTEROPERATOR_INVIEW: - case UA_FILTEROPERATOR_RELATEDTO: { - /* Not allowed for event WhereClause according to 7.17.3 in Part 4 */ - er->statusCode = - UA_STATUSCODE_BADEVENTFILTERINVALID; - break; - } - case UA_FILTEROPERATOR_EQUALS: - case UA_FILTEROPERATOR_GREATERTHAN: - case UA_FILTEROPERATOR_LESSTHAN: - case UA_FILTEROPERATOR_GREATERTHANOREQUAL: - case UA_FILTEROPERATOR_LESSTHANOREQUAL: - case UA_FILTEROPERATOR_LIKE: - case UA_FILTEROPERATOR_CAST: - case UA_FILTEROPERATOR_BITWISEAND: - case UA_FILTEROPERATOR_BITWISEOR: { - if(ef.filterOperandsSize != 2) { - er->statusCode = - UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; - break; - } - er->statusCode = UA_STATUSCODE_GOOD; - break; - } - case UA_FILTEROPERATOR_AND: - case UA_FILTEROPERATOR_OR: { - if(ef.filterOperandsSize != 2) { - er->statusCode = - UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; - break; - } - for(size_t j = 0; j < 2; ++j) { - if(ef.filterOperands[j].content.decoded.type != - &UA_TYPES[UA_TYPES_ELEMENTOPERAND]) { - er->operandStatusCodes[j] = - UA_STATUSCODE_BADFILTEROPERANDINVALID; - er->statusCode = - UA_STATUSCODE_BADFILTEROPERANDINVALID; - break; - } - if(((UA_ElementOperand *)ef.filterOperands[j] - .content.decoded.data)->index > filter->elementsSize - 1) { - er->operandStatusCodes[j] = - UA_STATUSCODE_BADINDEXRANGEINVALID; - er->statusCode = - UA_STATUSCODE_BADINDEXRANGEINVALID; - break; - } - } - er->statusCode = UA_STATUSCODE_GOOD; - break; - } - case UA_FILTEROPERATOR_ISNULL: - case UA_FILTEROPERATOR_NOT: { - if(ef.filterOperandsSize != 1) { - er->statusCode = - UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; - break; - } - er->statusCode = UA_STATUSCODE_GOOD; - break; - } - case UA_FILTEROPERATOR_INLIST: { - if(ef.filterOperandsSize <= 2) { - er->statusCode = - UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; - break; - } - er->statusCode = UA_STATUSCODE_GOOD; - break; - } - case UA_FILTEROPERATOR_BETWEEN: { - if(ef.filterOperandsSize != 3) { - er->statusCode = - UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; - break; - } - er->statusCode = UA_STATUSCODE_GOOD; - break; - } - case UA_FILTEROPERATOR_OFTYPE: { - if(ef.filterOperandsSize != 1) { - er->statusCode = - UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; - break; - } - er->operandStatusCodesSize = ef.filterOperandsSize; - if(ef.filterOperands[0].content.decoded.type != - &UA_TYPES[UA_TYPES_LITERALOPERAND]) { - er->statusCode = - UA_STATUSCODE_BADFILTEROPERANDINVALID; - break; - } - UA_LiteralOperand *literalOperand = - (UA_LiteralOperand *)ef.filterOperands[0] - .content.decoded.data; - - /* Make sure the &pOperand->nodeId is a subtype of BaseEventType */ - UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE); - if(!isNodeInTree_singleRef( - server, (UA_NodeId *)literalOperand->value.data, &baseEventTypeId, - UA_REFERENCETYPEINDEX_HASSUBTYPE)) { - er->statusCode = - UA_STATUSCODE_BADNODEIDINVALID; - break; - } - er->statusCode = UA_STATUSCODE_GOOD; - break; - } - default: - er->statusCode = - UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; - break; - } - } - return UA_STATUSCODE_GOOD; -} - UA_StatusCode UA_Server_triggerEvent(UA_Server *server, const UA_NodeId eventNodeId, const UA_NodeId origin, UA_ByteString *outEventId, diff --git a/src/server/ua_subscription_events_filter.c b/src/server/ua_subscription_events_filter.c new file mode 100644 index 00000000000..452718c7995 --- /dev/null +++ b/src/server/ua_subscription_events_filter.c @@ -0,0 +1,1203 @@ +/* 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 2018 (c) Ari Breitkreuz, fortiss GmbH + * Copyright 2020 (c) Christian von Arnim, ISW University of Stuttgart (for VDW and umati) + * Copyright 2021 (c) Fraunhofer IOSB (Author: Andreas Ebner) + */ + +#include "ua_server_internal.h" +#include "ua_subscription.h" + +#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS + +typedef struct { + UA_Server *server; + UA_Session *session; + const UA_NodeId *eventNode; + const UA_ContentFilter *contentFilter; + UA_ContentFilterResult *contentFilterResult; + UA_Variant *valueResult; + UA_UInt16 index; +} UA_FilterOperatorContext; + +static UA_StatusCode +evaluateWhereClauseContentFilter(UA_FilterOperatorContext *ctx); + +/* Resolves a variant of type string or boolean into a corresponding status code */ +static UA_StatusCode +resolveBoolean(UA_Variant operand) { + UA_String value; + value = UA_STRING("True"); + if(((operand.type == &UA_TYPES[UA_TYPES_STRING]) && + (UA_String_equal((UA_String *)operand.data, &value))) || + ((operand.type == &UA_TYPES[UA_TYPES_BOOLEAN]) && + (*(UA_Boolean *)operand.data == UA_TRUE))) { + return UA_STATUSCODE_GOOD; + } + value = UA_STRING("False"); + if(((operand.type == &UA_TYPES[UA_TYPES_STRING]) && + (UA_String_equal((UA_String *)operand.data, &value))) || + ((operand.type == &UA_TYPES[UA_TYPES_BOOLEAN]) && + (*(UA_Boolean *)operand.data == UA_FALSE))) { + return UA_STATUSCODE_BADNOMATCH; + } + + /* If the operand can't be resolved, an error is returned */ + return UA_STATUSCODE_BADFILTEROPERANDINVALID; +} + +/* Part 4: 7.4.4.5 SimpleAttributeOperand + * The clause can point to any attribute of nodes. Either a child of the event + * node and also the event type. */ +static UA_StatusCode +resolveSimpleAttributeOperand(UA_Server *server, UA_Session *session, + const UA_NodeId *origin, + const UA_SimpleAttributeOperand *sao, + UA_Variant *value) { + /* Prepare the ReadValueId */ + UA_ReadValueId rvi; + UA_ReadValueId_init(&rvi); + rvi.indexRange = sao->indexRange; + rvi.attributeId = sao->attributeId; + + UA_DataValue v; + + if(sao->browsePathSize == 0) { + /* If this list (browsePath) is empty, the Node is the instance of the + * TypeDefinition. (Part 4, 7.4.4.5) */ + rvi.nodeId = *origin; + + /* A Condition is an indirection. Look up the target node. */ + /* TODO: check for Branches! One Condition could have multiple Branches */ + UA_NodeId conditionTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE); + if(UA_NodeId_equal(&sao->typeDefinitionId, &conditionTypeId)) { +#ifdef UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS + UA_StatusCode res = UA_getConditionId(server, origin, &rvi.nodeId); + if(res != UA_STATUSCODE_GOOD) + return res; +#else + return UA_STATUSCODE_BADNOTSUPPORTED; +#endif + } + + v = UA_Server_readWithSession(server, session, &rvi, + UA_TIMESTAMPSTORETURN_NEITHER); + } else { + /* Resolve the browse path, starting from the event-source (and not the + * typeDefinitionId). */ + UA_BrowsePathResult bpr = + browseSimplifiedBrowsePath(server, *origin, + sao->browsePathSize, sao->browsePath); + if(bpr.targetsSize == 0 && bpr.statusCode == UA_STATUSCODE_GOOD) + bpr.statusCode = UA_STATUSCODE_BADNOTFOUND; + if(bpr.statusCode != UA_STATUSCODE_GOOD) { + UA_StatusCode res = bpr.statusCode; + UA_BrowsePathResult_clear(&bpr); + return res; + } + + /* Use the first match */ + rvi.nodeId = bpr.targets[0].targetId.nodeId; + v = UA_Server_readWithSession(server, session, &rvi, + UA_TIMESTAMPSTORETURN_NEITHER); + UA_BrowsePathResult_clear(&bpr); + } + + /* Move the result to the output */ + if(v.status == UA_STATUSCODE_GOOD && v.hasValue) + *value = v.value; + else + UA_Variant_clear(&v.value); + return v.status; +} + +/* Resolve operands to variants according to the operand type. + * Part 4: 7.17.3 Table 142 specifies the allowed types. */ +static UA_Variant +resolveOperand(UA_FilterOperatorContext *ctx, UA_UInt16 nr) { + UA_StatusCode res; + UA_Variant variant; + UA_Variant_init(&variant); + UA_ExtensionObject *op = &ctx->contentFilter->elements[ctx->index].filterOperands[nr]; + if(op->content.decoded.type == &UA_TYPES[UA_TYPES_SIMPLEATTRIBUTEOPERAND]) { + /* SimpleAttributeOperand */ + res = resolveSimpleAttributeOperand(ctx->server, ctx->session, ctx->eventNode, + (UA_SimpleAttributeOperand *)op->content.decoded.data, + &variant); + } else if(op->content.decoded.type == &UA_TYPES[UA_TYPES_LITERALOPERAND]) { + /* LiteralOperand */ + variant = ((UA_LiteralOperand *)op->content.decoded.data)->value; + res = UA_STATUSCODE_GOOD; + } else if(op->content.decoded.type == &UA_TYPES[UA_TYPES_ELEMENTOPERAND]) { + /* ElementOperand */ + UA_UInt16 oldIndex = ctx->index; + ctx->index = (UA_UInt16)((UA_ElementOperand *)op->content.decoded.data)->index; + res = evaluateWhereClauseContentFilter(ctx); + variant = ctx->valueResult[ctx->index]; + ctx->index = oldIndex; /* restore the old index */ + } else { + res = UA_STATUSCODE_BADFILTEROPERANDINVALID; + } + + if(res != UA_STATUSCODE_GOOD && res != UA_STATUSCODE_BADNOMATCH) { + variant.type = NULL; + ctx->contentFilterResult->elementResults[ctx->index].operandStatusCodes[nr] = res; + } + + return variant; +} + +static UA_StatusCode +ofTypeOperator(UA_FilterOperatorContext *ctx) { + UA_ContentFilterElement *pElement = &ctx->contentFilter->elements[ctx->index]; + UA_Boolean result = false; + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + if(pElement->filterOperandsSize != 1) + return UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; + if(pElement->filterOperands[0].content.decoded.type != + &UA_TYPES[UA_TYPES_LITERALOPERAND]) + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + + UA_LiteralOperand *literalOperand = + (UA_LiteralOperand *) pElement->filterOperands[0].content.decoded.data; + if(!UA_Variant_isScalar(&literalOperand->value)) + return UA_STATUSCODE_BADEVENTFILTERINVALID; + + if(literalOperand->value.type != &UA_TYPES[UA_TYPES_NODEID] || literalOperand->value.data == NULL) + return UA_STATUSCODE_BADEVENTFILTERINVALID; + + UA_NodeId *literalOperandNodeId = (UA_NodeId *) literalOperand->value.data; + UA_Variant typeNodeIdVariant; + UA_Variant_init(&typeNodeIdVariant); + UA_StatusCode readStatusCode = + readObjectProperty(ctx->server, *ctx->eventNode, + UA_QUALIFIEDNAME(0, "EventType"), &typeNodeIdVariant); + if(readStatusCode != UA_STATUSCODE_GOOD) + return readStatusCode; + + if(!UA_Variant_isScalar(&typeNodeIdVariant) || + typeNodeIdVariant.type != &UA_TYPES[UA_TYPES_NODEID] || + typeNodeIdVariant.data == NULL) { + UA_LOG_ERROR(&ctx->server->config.logger, UA_LOGCATEGORY_SERVER, + "EventType has an invalid type."); + UA_Variant_clear(&typeNodeIdVariant); + return UA_STATUSCODE_BADINTERNALERROR; + } + /* check if the eventtype-nodeid is equal to the given oftype argument */ + result = UA_NodeId_equal((UA_NodeId*) typeNodeIdVariant.data, literalOperandNodeId); + /* check if the eventtype-nodeid is a subtype of the given oftype argument */ + if(!result) + result = isNodeInTree_singleRef(ctx->server, + (UA_NodeId*) typeNodeIdVariant.data, + literalOperandNodeId, + UA_REFERENCETYPEINDEX_HASSUBTYPE); + UA_Variant_clear(&typeNodeIdVariant); + if(!result) + return UA_STATUSCODE_BADNOMATCH; + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +andOperator(UA_FilterOperatorContext *ctx) { + UA_StatusCode firstBoolean_and = resolveBoolean(resolveOperand(ctx, 0)); + if(firstBoolean_and == UA_STATUSCODE_BADNOMATCH) { + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + return UA_STATUSCODE_BADNOMATCH; + } + /* Evaluation of second operand */ + UA_StatusCode secondBoolean = resolveBoolean(resolveOperand(ctx, 1)); + /* Filteroperator AND */ + if(secondBoolean == UA_STATUSCODE_BADNOMATCH) { + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + return UA_STATUSCODE_BADNOMATCH; + } + if((firstBoolean_and == UA_STATUSCODE_GOOD) && + (secondBoolean == UA_STATUSCODE_GOOD)) { + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + return UA_STATUSCODE_GOOD; + } + return UA_STATUSCODE_BADFILTERELEMENTINVALID; +} + +static UA_StatusCode +orOperator(UA_FilterOperatorContext *ctx) { + UA_StatusCode firstBoolean_or = resolveBoolean(resolveOperand(ctx, 0)); + if(firstBoolean_or == UA_STATUSCODE_GOOD) { + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + return UA_STATUSCODE_GOOD; + } + /* Evaluation of second operand */ + UA_StatusCode secondBoolean = resolveBoolean(resolveOperand(ctx, 1)); + if(secondBoolean == UA_STATUSCODE_GOOD) { + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + return UA_STATUSCODE_GOOD; + } + if((firstBoolean_or == UA_STATUSCODE_BADNOMATCH) && + (secondBoolean == UA_STATUSCODE_BADNOMATCH)) { + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + return UA_STATUSCODE_BADNOMATCH; + } + return UA_STATUSCODE_BADFILTERELEMENTINVALID; +} + +static UA_Boolean +isNumericUnsigned(UA_UInt32 dataTypeKind){ + if(dataTypeKind == UA_DATATYPEKIND_UINT64 || + dataTypeKind == UA_DATATYPEKIND_UINT32 || + dataTypeKind == UA_DATATYPEKIND_UINT16 || + dataTypeKind == UA_DATATYPEKIND_BYTE) + return true; + return false; +} + +static UA_Boolean +isNumericSigned(UA_UInt32 dataTypeKind){ + if(dataTypeKind == UA_DATATYPEKIND_INT64 || + dataTypeKind == UA_DATATYPEKIND_INT32 || + dataTypeKind == UA_DATATYPEKIND_INT16 || + dataTypeKind == UA_DATATYPEKIND_SBYTE) + return true; + return false; +} + +static UA_Boolean +isFloatingPoint(UA_UInt32 dataTypeKind){ + if(dataTypeKind == UA_DATATYPEKIND_FLOAT || + dataTypeKind == UA_DATATYPEKIND_DOUBLE) + return true; + return false; +} + +static UA_Boolean +isStringType(UA_UInt32 dataTypeKind){ + if(dataTypeKind == UA_DATATYPEKIND_STRING || + dataTypeKind == UA_DATATYPEKIND_BYTESTRING) + return true; + return false; +} + +static UA_StatusCode +implicitNumericVariantTransformation(UA_Variant *variant, void *data){ + if(variant->type == &UA_TYPES[UA_TYPES_UINT64]){ + *(UA_UInt64 *)data = *(UA_UInt64 *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_UINT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_UINT32]){ + *(UA_UInt64 *)data = *(UA_UInt32 *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_UINT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_UINT16]){ + *(UA_UInt64 *)data = *(UA_UInt16 *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_UINT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_BYTE]){ + *(UA_UInt64 *)data = *(UA_Byte *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_UINT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_INT64]){ + *(UA_Int64 *)data = *(UA_Int64 *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_INT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_INT32]){ + *(UA_Int64 *)data = *(UA_Int32 *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_INT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_INT16]){ + *(UA_Int64 *)data = *(UA_Int16 *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_INT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_SBYTE]){ + *(UA_Int64 *)data = *(UA_SByte *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_INT64]); + } else if(variant->type == &UA_TYPES[UA_TYPES_DOUBLE]){ + *(UA_Double *)data = *(UA_Double *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_DOUBLE]); + } else if(variant->type == &UA_TYPES[UA_TYPES_SBYTE]){ + *(UA_Double *)data = *(UA_Float *)variant->data; + UA_Variant_setScalar(variant, data, &UA_TYPES[UA_TYPES_DOUBLE]); + } else { + return UA_STATUSCODE_BADTYPEMISMATCH; + } + return UA_STATUSCODE_GOOD; +} + +/* 0 -> Same Type, 1 -> Implicit Cast, 2 -> Only explicit Cast, -1 -> cast invalid */ +static UA_SByte convertLookup[21][21] = { + { 0, 1,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 1,-1, 2,-1,-1, 1, 1, 1,-1}, + { 2, 0,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 1,-1, 2,-1,-1, 1, 1, 1,-1}, + {-1,-1, 0,-1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, + {-1,-1,-1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1}, + { 2, 2,-1,-1, 0,-1, 2,-1, 2, 2, 2,-1, 2,-1, 2,-1,-1, 2, 2, 2,-1}, + {-1,-1,-1,-1,-1, 0,-1,-1,-1,-1,-1, 2,-1,-1, 1,-1,-1,-1,-1,-1,-1}, + { 2, 2,-1,-1, 1,-1, 0,-1, 2, 2, 2,-1, 2,-1, 2,-1,-1, 2, 2, 2,-1}, + {-1,-1, 2,-1,-1,-1,-1, 0,-1,-1,-1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 0, 1, 1,-1, 2,-1, 2,-1,-1, 2, 1, 1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 2, 0, 1,-1, 2, 2, 2,-1,-1, 2, 2, 1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 2, 2, 0,-1, 2, 2, 2,-1,-1, 2, 2, 2,-1}, + {-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1, 0,-1,-1, 1,-1,-1,-1,-1,-1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 0,-1, 2,-1,-1, 1, 1, 1,-1}, + {-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1,-1,-1, 0,-1,-1,-1, 2, 1, 1,-1}, + { 1, 1,-1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1,-1, 0, 2, 2, 1, 1, 1,-1}, + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 0,-1,-1,-1,-1,-1}, + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 0,-1,-1,-1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 2, 1, 2,-1,-1, 0, 1, 1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 2, 1, 1,-1, 2, 2, 2,-1,-1, 2, 0, 1,-1}, + { 2, 2,-1,-1, 1,-1, 1,-1, 2, 2, 1,-1, 2, 2, 2,-1,-1, 2, 2, 0,-1}, + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0} +}; + +/* This array maps the index of the + * standard DataType-Kind order to the + * order of the type convertion array */ +static UA_Byte dataTypeKindIndex[30] = { + 0, 12, 1, 8, 17, + 9, 18, 10, 19, 6, + 4, 14, 3, 7, 2, + 20, 11, 5, 13, 16, + 15, 255,255,255,255, + 255,255,255,255,255 +}; + +/* The OPC UA Standard defines in Part 4 several data type casting-rules. (see + * 1.04 part 4 Table 122) + * Return: + * 0 -> same type + * 1 -> types can be casted implicit + * 2 -> types can only be explicitly casted + * -1 -> types can't be casted */ +static UA_SByte +checkTypeCastingOption(const UA_DataType *cast_target, const UA_DataType *cast_source) { + UA_Byte firstOperatorTypeKindIndex = dataTypeKindIndex[cast_target->typeKind]; + UA_Byte secondOperatorTypeKindIndex = dataTypeKindIndex[cast_source->typeKind]; + if(firstOperatorTypeKindIndex == UA_BYTE_MAX || + secondOperatorTypeKindIndex == UA_BYTE_MAX) + return -1; + + return convertLookup[firstOperatorTypeKindIndex][secondOperatorTypeKindIndex]; +} + +/* Compare operation for equal, gt, le, gte, lee + * UA_STATUSCODE_GOOD if the comparison was true + * UA_STATUSCODE_BADNOMATCH if the comparison was false + * UA_STATUSCODE_BADFILTEROPERATORINVALID for invalid operators + * UA_STATUSCODE_BADTYPEMISMATCH if one of the operands was not numeric + * ToDo Array-Casting + */ +static UA_StatusCode +compareOperation(UA_Variant *firstOperand, UA_Variant *secondOperand, UA_FilterOperator op) { + /* get precedence of the operand types */ + UA_Int16 firstOperand_precedence = UA_DataType_getPrecedence(firstOperand->type); + UA_Int16 secondOperand_precedence = UA_DataType_getPrecedence(secondOperand->type); + /* if the types are not equal and one of the precedence-ranks is -1, then there is + no implicit conversion possible and therefore no compare */ + if(!UA_NodeId_equal(&firstOperand->type->typeId, &secondOperand->type->typeId) && + (firstOperand_precedence == -1 || secondOperand_precedence == -1)){ + return UA_STATUSCODE_BADTYPEMISMATCH; + } + /* check if the precedence order of the operators is swapped */ + UA_Variant *firstCompareOperand = firstOperand; + UA_Variant *secondCompareOperand = secondOperand; + UA_Boolean swapped = false; + if (firstOperand_precedence < secondOperand_precedence){ + firstCompareOperand = secondOperand; + secondCompareOperand = firstOperand; + swapped = true; + } + UA_SByte castRule = + checkTypeCastingOption(firstCompareOperand->type, secondCompareOperand->type); + + if(!(castRule == 0 || castRule == 1)){ + return UA_STATUSCODE_BADTYPEMISMATCH; + } + + /* The operand Data-Types influence the behavior and steps for the comparison. + * We need to check the operand types and store a rule which is used to select + * the right behavior afterwards. */ + enum compareHandlingRuleEnum { + UA_TYPES_EQUAL_ORDERED, + UA_TYPES_EQUAL_UNORDERED, + UA_TYPES_DIFFERENT_NUMERIC_UNSIGNED, + UA_TYPES_DIFFERENT_NUMERIC_SIGNED, + UA_TYPES_DIFFERENT_NUMERIC_FLOATING_POINT, + UA_TYPES_DIFFERENT_TEXT, + UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN, + UA_TYPES_DIFFERENT_COMPARE_EXPLIC + } compareHandlingRuleEnum; + + if(castRule == 0 && + (UA_DataType_isNumeric(firstOperand->type) || + firstCompareOperand->type->typeKind == (UA_UInt32) UA_DATATYPEKIND_DATETIME || + firstCompareOperand->type->typeKind == (UA_UInt32) UA_DATATYPEKIND_STRING || + firstCompareOperand->type->typeKind == (UA_UInt32) UA_DATATYPEKIND_BYTESTRING)){ + /* Data-Types with a natural order (allow le, gt, lee, gte) */ + compareHandlingRuleEnum = UA_TYPES_EQUAL_ORDERED; + } else if(castRule == 0){ + /* Data-Types without a natural order (le, gt, lee, gte are not allowed) */ + compareHandlingRuleEnum = UA_TYPES_EQUAL_UNORDERED; + } else if(castRule == 1 && + isNumericSigned(firstOperand->type->typeKind) && + isNumericSigned(secondOperand->type->typeKind)){ + compareHandlingRuleEnum = UA_TYPES_DIFFERENT_NUMERIC_SIGNED; + } else if(castRule == 1 && + isNumericUnsigned(firstOperand->type->typeKind) && + isNumericUnsigned(secondOperand->type->typeKind)){ + compareHandlingRuleEnum = UA_TYPES_DIFFERENT_NUMERIC_UNSIGNED; + } else if(castRule == 1 && + isFloatingPoint(firstOperand->type->typeKind) && + isFloatingPoint(secondOperand->type->typeKind)){ + compareHandlingRuleEnum = UA_TYPES_DIFFERENT_NUMERIC_FLOATING_POINT; + } else if(castRule == 1 && + isStringType(firstOperand->type->typeKind)&& + isStringType(secondOperand->type->typeKind)){ + compareHandlingRuleEnum = UA_TYPES_DIFFERENT_TEXT; + } else if(castRule == -1 || castRule == 2){ + compareHandlingRuleEnum = UA_TYPES_DIFFERENT_COMPARE_EXPLIC; + } else { + compareHandlingRuleEnum = UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN; + } + + if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN) + return UA_STATUSCODE_BADFILTEROPERATORINVALID; + + if(swapped){ + firstCompareOperand = secondCompareOperand; + secondCompareOperand = firstCompareOperand; + } + + if(op == UA_FILTEROPERATOR_EQUALS){ + UA_Byte variantContent[16]; + memset(&variantContent, 0, sizeof(UA_Byte) * 16); + if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_SIGNED || + compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_UNSIGNED || + compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_FLOATING_POINT) { + implicitNumericVariantTransformation(firstCompareOperand, variantContent); + implicitNumericVariantTransformation(secondCompareOperand, &variantContent[8]); + } else if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_TEXT) { + firstCompareOperand->type = &UA_TYPES[UA_TYPES_STRING]; + secondCompareOperand->type = &UA_TYPES[UA_TYPES_STRING]; + } else if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN || + compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_EXPLIC ){ + return UA_STATUSCODE_BADFILTEROPERATORINVALID; + } + if(UA_order(firstCompareOperand, secondCompareOperand, &UA_TYPES[UA_TYPES_VARIANT]) == UA_ORDER_EQ) { + return UA_STATUSCODE_GOOD; + } + } else { + UA_Byte variantContent[16]; + if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_SIGNED || + compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_UNSIGNED || + compareHandlingRuleEnum == UA_TYPES_DIFFERENT_NUMERIC_FLOATING_POINT) { + memset(&variantContent, 0, sizeof(UA_Byte) * 16); + implicitNumericVariantTransformation(firstCompareOperand, variantContent); + implicitNumericVariantTransformation(secondCompareOperand, &variantContent[8]); + } else if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_TEXT) { + firstCompareOperand->type = &UA_TYPES[UA_TYPES_STRING]; + secondCompareOperand->type = &UA_TYPES[UA_TYPES_STRING]; + } else if(compareHandlingRuleEnum == UA_TYPES_EQUAL_UNORDERED) { + return UA_STATUSCODE_BADFILTEROPERATORINVALID; + } else if(compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_FORBIDDEN || + compareHandlingRuleEnum == UA_TYPES_DIFFERENT_COMPARE_EXPLIC) { + return UA_STATUSCODE_BADFILTEROPERATORINVALID; + } + UA_Order gte_result = UA_order(firstCompareOperand, secondCompareOperand, + &UA_TYPES[UA_TYPES_VARIANT]); + if(op == UA_FILTEROPERATOR_LESSTHAN) { + if(gte_result == UA_ORDER_LESS) { + return UA_STATUSCODE_GOOD; + } + } else if(op == UA_FILTEROPERATOR_GREATERTHAN) { + if(gte_result == UA_ORDER_MORE) { + return UA_STATUSCODE_GOOD; + } + } else if(op == UA_FILTEROPERATOR_LESSTHANOREQUAL) { + if(gte_result == UA_ORDER_LESS || gte_result == UA_ORDER_EQ) { + return UA_STATUSCODE_GOOD; + } + } else if(op == UA_FILTEROPERATOR_GREATERTHANOREQUAL) { + if(gte_result == UA_ORDER_MORE || gte_result == UA_ORDER_EQ) { + return UA_STATUSCODE_GOOD; + } + } + } + return UA_STATUSCODE_BADNOMATCH; +} + +static UA_StatusCode +compareOperator(UA_FilterOperatorContext *ctx) { + UA_Variant firstOperand = resolveOperand(ctx, 0); + if(UA_Variant_isEmpty(&firstOperand)) + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + UA_Variant secondOperand = resolveOperand(ctx, 1); + if(UA_Variant_isEmpty(&secondOperand)) { + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + } + /* ToDo remove the following restriction: Add support for arrays */ + if(!UA_Variant_isScalar(&firstOperand) || !UA_Variant_isScalar(&secondOperand)){ + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + } + return compareOperation(&firstOperand, &secondOperand, + ctx->contentFilter->elements[ctx->index].filterOperator); +} + +static UA_StatusCode +bitwiseOperator(UA_FilterOperatorContext *ctx) { + /* The bitwise operators all have 2 operands which are evaluated equally. */ + UA_Variant firstOperand = resolveOperand(ctx, 0); + if(UA_Variant_isEmpty(&firstOperand)) { + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + } + UA_Variant secondOperand = resolveOperand(ctx, 1); + if(UA_Variant_isEmpty(&secondOperand)) { + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + } + + UA_Boolean bitwiseAnd = + ctx->contentFilter->elements[ctx->index].filterOperator == UA_FILTEROPERATOR_BITWISEAND; + + /* check if the operators are integers */ + if(!UA_DataType_isNumeric(firstOperand.type) || + !UA_DataType_isNumeric(secondOperand.type) || + !UA_Variant_isScalar(&firstOperand) || + !UA_Variant_isScalar(&secondOperand) || + (firstOperand.type == &UA_TYPES[UA_TYPES_DOUBLE]) || + (secondOperand.type == &UA_TYPES[UA_TYPES_DOUBLE]) || + (secondOperand.type == &UA_TYPES[UA_TYPES_FLOAT]) || + (firstOperand.type == &UA_TYPES[UA_TYPES_FLOAT])) { + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + } + + /* check which is the return type (higher precedence == bigger integer)*/ + UA_Int16 precedence = UA_DataType_getPrecedence(firstOperand.type); + if(precedence > UA_DataType_getPrecedence(secondOperand.type)) { + precedence = UA_DataType_getPrecedence(secondOperand.type); + } + + switch(precedence){ + case 3: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_INT64]; + UA_Int64 result_int64; + if(bitwiseAnd) { + result_int64 = *((UA_Int64 *)firstOperand.data) & *((UA_Int64 *)secondOperand.data); + } else { + result_int64 = *((UA_Int64 *)firstOperand.data) | *((UA_Int64 *)secondOperand.data); + } + UA_Int64_copy(&result_int64, (UA_Int64 *) ctx->valueResult[ctx->index].data); + break; + case 4: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_UINT64]; + UA_UInt64 result_uint64s; + if(bitwiseAnd) { + result_uint64s = *((UA_UInt64 *)firstOperand.data) & *((UA_UInt64 *)secondOperand.data); + } else { + result_uint64s = *((UA_UInt64 *)firstOperand.data) | *((UA_UInt64 *)secondOperand.data); + } + UA_UInt64_copy(&result_uint64s, (UA_UInt64 *) ctx->valueResult[ctx->index].data); + break; + case 5: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_INT32]; + UA_Int32 result_int32; + if(bitwiseAnd) { + result_int32 = *((UA_Int32 *)firstOperand.data) & *((UA_Int32 *)secondOperand.data); + } else { + result_int32 = *((UA_Int32 *)firstOperand.data) | *((UA_Int32 *)secondOperand.data); + } + UA_Int32_copy(&result_int32, (UA_Int32 *) ctx->valueResult[ctx->index].data); + break; + case 6: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_UINT32]; + UA_UInt32 result_uint32; + if(bitwiseAnd) { + result_uint32 = *((UA_UInt32 *)firstOperand.data) & *((UA_UInt32 *)secondOperand.data); + } else { + result_uint32 = *((UA_UInt32 *)firstOperand.data) | *((UA_UInt32 *)secondOperand.data); + } + UA_UInt32_copy(&result_uint32, (UA_UInt32 *) ctx->valueResult[ctx->index].data); + break; + case 8: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_INT16]; + UA_Int16 result_int16; + if(bitwiseAnd) { + result_int16 = *((UA_Int16 *)firstOperand.data) & *((UA_Int16 *)secondOperand.data); + } else { + result_int16 = *((UA_Int16 *)firstOperand.data) | *((UA_Int16 *)secondOperand.data); + } + UA_Int16_copy(&result_int16, (UA_Int16 *) ctx->valueResult[ctx->index].data); + break; + case 9: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_UINT16]; + UA_UInt16 result_uint16; + if(bitwiseAnd) { + result_uint16 = *((UA_UInt16 *)firstOperand.data) & *((UA_UInt16 *)secondOperand.data); + } else { + result_uint16 = *((UA_UInt16 *)firstOperand.data) | *((UA_UInt16 *)secondOperand.data); + } + UA_UInt16_copy(&result_uint16, (UA_UInt16 *) ctx->valueResult[ctx->index].data); + break; + case 10: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_SBYTE]; + UA_SByte result_sbyte; + if(bitwiseAnd) { + result_sbyte = *((UA_SByte *)firstOperand.data) & *((UA_SByte *)secondOperand.data); + } else { + result_sbyte = *((UA_SByte *)firstOperand.data) | *((UA_SByte *)secondOperand.data); + } + UA_SByte_copy(&result_sbyte, (UA_SByte *) ctx->valueResult[ctx->index].data); + break; + case 11: + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BYTE]; + UA_Byte result_byte; + if(bitwiseAnd) { + result_byte = *((UA_Byte *)firstOperand.data) & *((UA_Byte *)secondOperand.data); + } else { + result_byte = *((UA_Byte *)firstOperand.data) | *((UA_Byte *)secondOperand.data); + } + UA_Byte_copy(&result_byte, (UA_Byte *) ctx->valueResult[ctx->index].data); + break; + default: + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + } + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +betweenOperator(UA_FilterOperatorContext *ctx) { + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + + UA_Variant firstOperand = resolveOperand(ctx, 0); + UA_Variant secondOperand = resolveOperand(ctx, 1); + UA_Variant thirdOperand = resolveOperand(ctx, 2); + + if((UA_Variant_isEmpty(&firstOperand) || + UA_Variant_isEmpty(&secondOperand) || + UA_Variant_isEmpty(&thirdOperand)) || + (!UA_DataType_isNumeric(firstOperand.type) || + !UA_DataType_isNumeric(secondOperand.type) || + !UA_DataType_isNumeric(thirdOperand.type)) || + (!UA_Variant_isScalar(&firstOperand) || + !UA_Variant_isScalar(&secondOperand) || + !UA_Variant_isScalar(&thirdOperand))) { + return UA_STATUSCODE_BADFILTEROPERANDINVALID; + } + + /* Between can be evaluated through greaterThanOrEqual and lessThanOrEqual */ + if(compareOperation(&firstOperand, &secondOperand, UA_FILTEROPERATOR_GREATERTHANOREQUAL) == UA_STATUSCODE_GOOD && + compareOperation(&firstOperand, &thirdOperand, UA_FILTEROPERATOR_LESSTHANOREQUAL) == UA_STATUSCODE_GOOD){ + return UA_STATUSCODE_GOOD; + } + return UA_STATUSCODE_BADNOMATCH; +} + +static UA_StatusCode +inListOperator(UA_FilterOperatorContext *ctx) { + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + UA_Variant firstOperand = resolveOperand(ctx, 0); + + if(UA_Variant_isEmpty(&firstOperand) || + !UA_Variant_isScalar(&firstOperand)) { + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + } + + /* Evaluating the list of operands */ + for(size_t i = 1; i < ctx->contentFilter->elements[ctx->index].filterOperandsSize; i++) { + /* Resolving the current operand */ + UA_Variant currentOperator = resolveOperand(ctx, (UA_UInt16)i); + + /* Check if the operand conforms to the operator*/ + if(UA_Variant_isEmpty(¤tOperator) || + !UA_Variant_isScalar(¤tOperator)) { + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + } + if(compareOperation(&firstOperand, ¤tOperator, UA_FILTEROPERATOR_EQUALS)) { + return UA_STATUSCODE_GOOD; + } + } + return UA_STATUSCODE_BADNOMATCH; +} + +static UA_StatusCode +isNullOperator(UA_FilterOperatorContext *ctx) { + /* Checking if operand is NULL. This is done by reducing the operand to a + * variant and then checking if it is empty. */ + UA_Variant operand = resolveOperand(ctx, 0); + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + if(!UA_Variant_isEmpty(&operand)) + return UA_STATUSCODE_BADNOMATCH; + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +notOperator(UA_FilterOperatorContext *ctx) { + /* Inverting the boolean value of the operand. */ + UA_StatusCode res = resolveBoolean(resolveOperand(ctx, 0)); + ctx->valueResult[ctx->index].type = &UA_TYPES[UA_TYPES_BOOLEAN]; + /* invert result */ + if(res == UA_STATUSCODE_GOOD) + return UA_STATUSCODE_BADNOMATCH; + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +evaluateWhereClauseContentFilter(UA_FilterOperatorContext *ctx) { + UA_LOCK_ASSERT(&ctx->server->serviceMutex, 1); + + if(ctx->contentFilter->elements == NULL || ctx->contentFilter->elementsSize == 0) { + /* Nothing to do.*/ + return UA_STATUSCODE_GOOD; + } + + /* The first element needs to be evaluated, this might be linked to other + * elements, which are evaluated in these cases. See 7.4.1 in Part 4. */ + UA_ContentFilterElement *pElement = &ctx->contentFilter->elements[ctx->index]; + UA_StatusCode *result = &ctx->contentFilterResult->elementResults[ctx->index].statusCode; + switch(pElement->filterOperator) { + case UA_FILTEROPERATOR_INVIEW: + /* Fallthrough */ + case UA_FILTEROPERATOR_RELATEDTO: + /* Not allowed for event WhereClause according to 7.17.3 in Part 4 */ + return UA_STATUSCODE_BADEVENTFILTERINVALID; + case UA_FILTEROPERATOR_EQUALS: + /* Fallthrough */ + case UA_FILTEROPERATOR_GREATERTHAN: + /* Fallthrough */ + case UA_FILTEROPERATOR_LESSTHAN: + /* Fallthrough */ + case UA_FILTEROPERATOR_GREATERTHANOREQUAL: + /* Fallthrough */ + case UA_FILTEROPERATOR_LESSTHANOREQUAL: + *result = compareOperator(ctx); + break; + case UA_FILTEROPERATOR_LIKE: + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + case UA_FILTEROPERATOR_NOT: + *result = notOperator(ctx); + break; + case UA_FILTEROPERATOR_BETWEEN: + *result = betweenOperator(ctx); + break; + case UA_FILTEROPERATOR_INLIST: + /* ToDo currently only numeric types are allowed */ + *result = inListOperator(ctx); + break; + case UA_FILTEROPERATOR_ISNULL: + *result = isNullOperator(ctx); + break; + case UA_FILTEROPERATOR_AND: + *result = andOperator(ctx); + break; + case UA_FILTEROPERATOR_OR: + *result = orOperator(ctx); + break; + case UA_FILTEROPERATOR_CAST: + return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + case UA_FILTEROPERATOR_BITWISEAND: + *result = bitwiseOperator(ctx); + break; + case UA_FILTEROPERATOR_BITWISEOR: + *result = bitwiseOperator(ctx); + break; + case UA_FILTEROPERATOR_OFTYPE: + *result = ofTypeOperator(ctx); + break; + default: + return UA_STATUSCODE_BADFILTEROPERATORINVALID; + } + + if(ctx->valueResult[ctx->index].type == &UA_TYPES[UA_TYPES_BOOLEAN]) { + UA_Boolean *res = UA_Boolean_new(); + if(ctx->contentFilterResult->elementResults[ctx->index].statusCode == UA_STATUSCODE_GOOD) + *res = true; + else + *res = false; + ctx->valueResult[ctx->index].data = res; + } + return ctx->contentFilterResult->elementResults[ctx->index].statusCode; +} + +/* Exposes the filters For unit tests */ +UA_StatusCode +UA_Server_evaluateWhereClauseContentFilter(UA_Server *server, UA_Session *session, + const UA_NodeId *eventNode, + const UA_ContentFilter *contentFilter, + UA_ContentFilterResult *contentFilterResult) { + if(contentFilter->elementsSize == 0) + return UA_STATUSCODE_GOOD; + /* TODO add maximum lenth size to the server config */ + if(contentFilter->elementsSize > 256) + return UA_STATUSCODE_BADINVALIDARGUMENT; + UA_Variant valueResult[256]; + for(size_t i = 0; i < contentFilter->elementsSize; ++i) { + UA_Variant_init(&valueResult[i]); + } + + UA_FilterOperatorContext ctx; + ctx.server = server; + ctx.session = session; + ctx.eventNode = eventNode; + ctx.contentFilter = contentFilter; + ctx.contentFilterResult = contentFilterResult; + ctx.valueResult = valueResult; + ctx.index = 0; + + UA_StatusCode res = evaluateWhereClauseContentFilter(&ctx); + for(size_t i = 0; i < ctx.contentFilter->elementsSize; i++) { + if(!UA_Variant_isEmpty(&ctx.valueResult[i])) + UA_Variant_clear(&ctx.valueResult[i]); + } + return res; +} + +static UA_Boolean +isValidEvent(UA_Server *server, const UA_NodeId *validEventParent, + const UA_NodeId *eventId) { + /* find the eventType variableNode */ + UA_QualifiedName findName = UA_QUALIFIEDNAME(0, "EventType"); + UA_BrowsePathResult bpr = browseSimplifiedBrowsePath(server, *eventId, 1, &findName); + if(bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) { + UA_BrowsePathResult_clear(&bpr); + return false; + } + + /* Get the EventType Property Node */ + UA_Variant tOutVariant; + UA_Variant_init(&tOutVariant); + + /* Read the Value of EventType Property Node (the Value should be a NodeId) */ + UA_StatusCode retval = readWithReadValue(server, &bpr.targets[0].targetId.nodeId, + UA_ATTRIBUTEID_VALUE, &tOutVariant); + if(retval != UA_STATUSCODE_GOOD || + !UA_Variant_hasScalarType(&tOutVariant, &UA_TYPES[UA_TYPES_NODEID])) { + UA_BrowsePathResult_clear(&bpr); + return false; + } + + const UA_NodeId *tEventType = (UA_NodeId*)tOutVariant.data; + + /* check whether the EventType is a Subtype of CondtionType + * (Part 9 first implementation) */ + UA_NodeId conditionTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE); + if(UA_NodeId_equal(validEventParent, &conditionTypeId) && + isNodeInTree_singleRef(server, tEventType, &conditionTypeId, + UA_REFERENCETYPEINDEX_HASSUBTYPE)) { + UA_BrowsePathResult_clear(&bpr); + UA_Variant_clear(&tOutVariant); + return true; + } + + /*EventType is not a Subtype of CondtionType + *(ConditionId Clause won't be present in Events, which are not Conditions)*/ + /* check whether Valid Event other than Conditions */ + UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE); + UA_Boolean isSubtypeOfBaseEvent = + isNodeInTree_singleRef(server, tEventType, &baseEventTypeId, + UA_REFERENCETYPEINDEX_HASSUBTYPE); + + UA_BrowsePathResult_clear(&bpr); + UA_Variant_clear(&tOutVariant); + return isSubtypeOfBaseEvent; +} + +UA_StatusCode +filterEvent(UA_Server *server, UA_Session *session, + const UA_NodeId *eventNode, UA_EventFilter *filter, + UA_EventFieldList *efl, UA_EventFilterResult *result) { + if(filter->selectClausesSize == 0) + return UA_STATUSCODE_BADEVENTFILTERINVALID; + + UA_EventFieldList_init(efl); + efl->eventFields = (UA_Variant *) + UA_Array_new(filter->selectClausesSize, &UA_TYPES[UA_TYPES_VARIANT]); + if(!efl->eventFields) + return UA_STATUSCODE_BADOUTOFMEMORY; + efl->eventFieldsSize = filter->selectClausesSize; + + /* empty event filter result */ + UA_EventFilterResult_init(result); + result->selectClauseResultsSize = filter->selectClausesSize; + result->selectClauseResults = (UA_StatusCode *) + UA_Array_new(filter->selectClausesSize, &UA_TYPES[UA_TYPES_STATUSCODE]); + if(!result->selectClauseResults) { + UA_EventFieldList_clear(efl); + UA_EventFilterResult_clear(result); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + /* prepare content filter result structure */ + if(filter->whereClause.elementsSize != 0) { + result->whereClauseResult.elementResultsSize = filter->whereClause.elementsSize; + result->whereClauseResult.elementResults = (UA_ContentFilterElementResult *) + UA_Array_new(filter->whereClause.elementsSize, + &UA_TYPES[UA_TYPES_CONTENTFILTERELEMENTRESULT]); + if(!result->whereClauseResult.elementResults) { + UA_EventFieldList_clear(efl); + UA_EventFilterResult_clear(result); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + for(size_t i = 0; i < result->whereClauseResult.elementResultsSize; ++i) { + result->whereClauseResult.elementResults[i].operandStatusCodesSize = + filter->whereClause.elements->filterOperandsSize; + result->whereClauseResult.elementResults[i].operandStatusCodes = + (UA_StatusCode *)UA_Array_new( + filter->whereClause.elements->filterOperandsSize, + &UA_TYPES[UA_TYPES_STATUSCODE]); + if(!result->whereClauseResult.elementResults[i].operandStatusCodes) { + UA_EventFieldList_clear(efl); + UA_EventFilterResult_clear(result); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + } + } + + /* Apply the content (where) filter */ + UA_StatusCode res = + UA_Server_evaluateWhereClauseContentFilter(server, session, eventNode, + &filter->whereClause, &result->whereClauseResult); + if(res != UA_STATUSCODE_GOOD){ + UA_EventFieldList_clear(efl); + UA_EventFilterResult_clear(result); + return res; + } + + /* Apply the select filter */ + /* Check if the browsePath is BaseEventType, in which case nothing more + * needs to be checked */ + UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE); + for(size_t i = 0; i < filter->selectClausesSize; i++) { + if(!UA_NodeId_equal(&filter->selectClauses[i].typeDefinitionId, &baseEventTypeId) && + !isValidEvent(server, &filter->selectClauses[i].typeDefinitionId, eventNode)) { + UA_Variant_init(&efl->eventFields[i]); + /* EventFilterResult currently isn't being used + notification->result.selectClauseResults[i] = UA_STATUSCODE_BADTYPEDEFINITIONINVALID; */ + continue; + } + + /* TODO: Put the result into the selectClausResults */ + resolveSimpleAttributeOperand(server, session, eventNode, + &filter->selectClauses[i], &efl->eventFields[i]); + } + + return UA_STATUSCODE_GOOD; +} + +/*****************************************/ +/* Validation of Filters during Creation */ +/*****************************************/ + +/* Initial select clause validation. The following checks are currently performed: + * - Check if typedefenitionid or browsepath of any clause is NULL + * - Check if the eventType is a subtype of BaseEventType + * - Check if attributeId is valid + * - Check if browsePath contains null + * - Check if indexRange is defined and if it is parsable + * - Check if attributeId is value */ +void +UA_Event_staticSelectClauseValidation(UA_Server *server, + const UA_EventFilter *eventFilter, + UA_StatusCode *result) { + /* The selectClause only has to be checked, if the size is not zero */ + if(eventFilter->selectClausesSize == 0) + return; + for(size_t i = 0; i < eventFilter->selectClausesSize; ++i) { + result[i] = UA_STATUSCODE_GOOD; + /* /typedefenitionid or browsepath of any clause is not NULL ? */ + if(UA_NodeId_isNull(&eventFilter->selectClauses[i].typeDefinitionId)) { + result[i] = UA_STATUSCODE_BADTYPEDEFINITIONINVALID; + continue; + } + /*ToDo: Check the following workaround. In UaExpert Event View the selection + * of the Server Object set up 7 select filter entries by default. The last + * element ist from node 2782 (A&C ConditionType). Since the reduced + * information model dos not contain this type, the result has a brows path of + * "null" which results in an error. */ + UA_NodeId ac_conditionType = UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE); + if(UA_NodeId_equal(&eventFilter->selectClauses[i].typeDefinitionId, &ac_conditionType)) { + continue; + } + if(&eventFilter->selectClauses[i].browsePath[0] == NULL) { + result[i] = UA_STATUSCODE_BADBROWSENAMEINVALID; + continue; + } + /* eventType is a subtype of BaseEventType ? */ + UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE); + if(!isNodeInTree_singleRef( + server, &eventFilter->selectClauses[i].typeDefinitionId, + &baseEventTypeId, UA_REFERENCETYPEINDEX_HASSUBTYPE)) { + result[i] = UA_STATUSCODE_BADTYPEDEFINITIONINVALID; + continue; + } + /* attributeId is valid ? */ + if(!((0 < eventFilter->selectClauses[i].attributeId) && + (eventFilter->selectClauses[i].attributeId < 28))) { + result[i] = UA_STATUSCODE_BADATTRIBUTEIDINVALID; + continue; + } + /* browsePath contains null ? */ + for(size_t j = 0; j < eventFilter->selectClauses[i].browsePathSize; ++j) { + if(UA_QualifiedName_isNull( + &eventFilter->selectClauses[i].browsePath[j])) { + result[i] = UA_STATUSCODE_BADBROWSENAMEINVALID; + break; + } + } + if(result[i] != UA_STATUSCODE_GOOD) + continue; + /*indexRange is defined ? */ + if(!UA_String_equal(&eventFilter->selectClauses[i].indexRange, + &UA_STRING_NULL)) { + /* indexRange is parsable ? */ + UA_NumericRange numericRange = UA_NUMERICRANGE(""); + if(UA_NumericRange_parse(&numericRange, + eventFilter->selectClauses[i].indexRange) != + UA_STATUSCODE_GOOD) { + result[i] = UA_STATUSCODE_BADINDEXRANGEINVALID; + continue; + } + UA_free(numericRange.dimensions); + /* attributeId is value ? */ + if(eventFilter->selectClauses[i].attributeId != UA_ATTRIBUTEID_VALUE) { + result[i] = UA_STATUSCODE_BADTYPEMISMATCH; + continue; + } + } + } +} + +/* Initial content filter (where clause) check. Current checks: + * - Number of operands for each (supported) operator */ +UA_StatusCode +UA_Event_staticWhereClauseValidation(UA_Server *server, + const UA_ContentFilter *filter, + UA_ContentFilterResult *result) { + UA_ContentFilterResult_init(result); + result->elementResultsSize = filter->elementsSize; + if(result->elementResultsSize == 0) + return UA_STATUSCODE_GOOD; + result->elementResults = + (UA_ContentFilterElementResult *)UA_Array_new( + result->elementResultsSize, + &UA_TYPES[UA_TYPES_CONTENTFILTERELEMENTRESULT]); + if(!result->elementResults) + return UA_STATUSCODE_BADOUTOFMEMORY; + for(size_t i = 0; i < result->elementResultsSize; ++i) { + UA_ContentFilterElementResult *er = &result->elementResults[i]; + UA_ContentFilterElement ef = filter->elements[i]; + UA_ContentFilterElementResult_init(er); + er->operandStatusCodes = + (UA_StatusCode *)UA_Array_new( + ef.filterOperandsSize, + &UA_TYPES[UA_TYPES_STATUSCODE]); + er->operandStatusCodesSize = ef.filterOperandsSize; + + switch(ef.filterOperator) { + case UA_FILTEROPERATOR_INVIEW: + case UA_FILTEROPERATOR_RELATEDTO: { + /* Not allowed for event WhereClause according to 7.17.3 in Part 4 */ + er->statusCode = + UA_STATUSCODE_BADEVENTFILTERINVALID; + break; + } + case UA_FILTEROPERATOR_EQUALS: + case UA_FILTEROPERATOR_GREATERTHAN: + case UA_FILTEROPERATOR_LESSTHAN: + case UA_FILTEROPERATOR_GREATERTHANOREQUAL: + case UA_FILTEROPERATOR_LESSTHANOREQUAL: + case UA_FILTEROPERATOR_LIKE: + case UA_FILTEROPERATOR_CAST: + case UA_FILTEROPERATOR_BITWISEAND: + case UA_FILTEROPERATOR_BITWISEOR: { + if(ef.filterOperandsSize != 2) { + er->statusCode = + UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; + break; + } + er->statusCode = UA_STATUSCODE_GOOD; + break; + } + case UA_FILTEROPERATOR_AND: + case UA_FILTEROPERATOR_OR: { + if(ef.filterOperandsSize != 2) { + er->statusCode = + UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; + break; + } + for(size_t j = 0; j < 2; ++j) { + if(ef.filterOperands[j].content.decoded.type != + &UA_TYPES[UA_TYPES_ELEMENTOPERAND]) { + er->operandStatusCodes[j] = + UA_STATUSCODE_BADFILTEROPERANDINVALID; + er->statusCode = + UA_STATUSCODE_BADFILTEROPERANDINVALID; + break; + } + if(((UA_ElementOperand *)ef.filterOperands[j] + .content.decoded.data)->index > filter->elementsSize - 1) { + er->operandStatusCodes[j] = + UA_STATUSCODE_BADINDEXRANGEINVALID; + er->statusCode = + UA_STATUSCODE_BADINDEXRANGEINVALID; + break; + } + } + er->statusCode = UA_STATUSCODE_GOOD; + break; + } + case UA_FILTEROPERATOR_ISNULL: + case UA_FILTEROPERATOR_NOT: { + if(ef.filterOperandsSize != 1) { + er->statusCode = + UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; + break; + } + er->statusCode = UA_STATUSCODE_GOOD; + break; + } + case UA_FILTEROPERATOR_INLIST: { + if(ef.filterOperandsSize <= 2) { + er->statusCode = + UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; + break; + } + er->statusCode = UA_STATUSCODE_GOOD; + break; + } + case UA_FILTEROPERATOR_BETWEEN: { + if(ef.filterOperandsSize != 3) { + er->statusCode = + UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; + break; + } + er->statusCode = UA_STATUSCODE_GOOD; + break; + } + case UA_FILTEROPERATOR_OFTYPE: { + if(ef.filterOperandsSize != 1) { + er->statusCode = + UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH; + break; + } + er->operandStatusCodesSize = ef.filterOperandsSize; + if(ef.filterOperands[0].content.decoded.type != + &UA_TYPES[UA_TYPES_LITERALOPERAND]) { + er->statusCode = + UA_STATUSCODE_BADFILTEROPERANDINVALID; + break; + } + UA_LiteralOperand *literalOperand = + (UA_LiteralOperand *)ef.filterOperands[0] + .content.decoded.data; + + /* Make sure the &pOperand->nodeId is a subtype of BaseEventType */ + UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE); + if(!isNodeInTree_singleRef( + server, (UA_NodeId *)literalOperand->value.data, &baseEventTypeId, + UA_REFERENCETYPEINDEX_HASSUBTYPE)) { + er->statusCode = + UA_STATUSCODE_BADNODEIDINVALID; + break; + } + er->statusCode = UA_STATUSCODE_GOOD; + break; + } + default: + er->statusCode = + UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED; + break; + } + } + return UA_STATUSCODE_GOOD; +} + +#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */ From ac3abb1846dec89f04b70e50f5dbc895df62dac7 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 22 Dec 2021 00:30:57 +0100 Subject: [PATCH 0024/1963] fix(server): Correct the status code for unsupported filters --- src/server/ua_services_monitoreditem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/ua_services_monitoreditem.c b/src/server/ua_services_monitoreditem.c index 89a2b150326..5a927ff37a1 100644 --- a/src/server/ua_services_monitoreditem.c +++ b/src/server/ua_services_monitoreditem.c @@ -170,7 +170,7 @@ checkAdjustMonitoredItemParams(UA_Server *server, UA_Session *session, * DataChangeFilter */ if(params->filter.encoding != UA_EXTENSIONOBJECT_ENCODED_NOBODY && params->filter.content.decoded.type != &UA_TYPES[UA_TYPES_DATACHANGEFILTER]) - return UA_STATUSCODE_BADMONITOREDITEMFILTERUNSUPPORTED; + return UA_STATUSCODE_BADFILTERNOTALLOWED; /* Check the deadband and adjust if necessary. */ if(params->filter.content.decoded.type == &UA_TYPES[UA_TYPES_DATACHANGEFILTER]) { From 17b247a61dd3351add51cc38082460634b162b40 Mon Sep 17 00:00:00 2001 From: jackybek Date: Sat, 25 Dec 2021 18:03:22 +0800 Subject: [PATCH 0025/1963] fix(pubsub): Missing comma (#4873) --- deps/mqtt-c/mqtt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/mqtt-c/mqtt.c b/deps/mqtt-c/mqtt.c index a44c6767656..920583897f0 100644 --- a/deps/mqtt-c/mqtt.c +++ b/deps/mqtt-c/mqtt.c @@ -244,7 +244,7 @@ enum MQTTErrors mqtt_connect(struct mqtt_client *client, client->mq.curr, client->mq.curr_sz, client_id, will_topic, will_message, will_message_size,user_name, password, - caFilePath, caPath, clientCertPath, clientKeyPath + caFilePath, caPath, clientCertPath, clientKeyPath, connect_flags, keep_alive ), 1 From 4db2f75ad96534d3100357ab49f6a07fb8cb838e Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 22 Dec 2021 20:00:08 +0100 Subject: [PATCH 0026/1963] refactor(el): Assert that all file descriptors are removed during shutdown --- arch/eventloop_posix.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/eventloop_posix.c b/arch/eventloop_posix.c index f7cb3d4839a..fccd53a8f21 100644 --- a/arch/eventloop_posix.c +++ b/arch/eventloop_posix.c @@ -155,8 +155,9 @@ UA_EventLoop_delete(UA_EventLoop *el) { /* Process remaining delayed callbacks */ processDelayed(el); - /* free the file descriptors */ - UA_free(el->fds); + /* All file descriptors were removed together with the coresponding + * EventSource */ + UA_assert(el->fdsSize == 0); /* Clean up */ UA_UNLOCK(&el->elMutex); From dd3dbc907d7a68c3180963f6a87a4e1dde0aeeff Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Thu, 23 Dec 2021 10:36:47 +0100 Subject: [PATCH 0027/1963] refactor(el): Switch from select to poll for the TCP ConnectionManager --- arch/eventloop_posix.c | 184 +++++++++++++--------------------- arch/eventloop_posix.h | 10 +- arch/eventloop_posix_tcp.c | 20 ++-- include/open62541/config.h.in | 7 ++ 4 files changed, 87 insertions(+), 134 deletions(-) diff --git a/arch/eventloop_posix.c b/arch/eventloop_posix.c index fccd53a8f21..bd55b3d775e 100644 --- a/arch/eventloop_posix.c +++ b/arch/eventloop_posix.c @@ -10,8 +10,9 @@ #include "common/ua_timer.h" typedef struct { - UA_FD fd; - short eventMask; + /* The members fd and events are stored in the separate pollfds array: + * UA_FD fd; + * short events; */ UA_EventSource *es; UA_FDCallback callback; void *fdcontext; @@ -33,6 +34,7 @@ struct UA_EventLoop { /* Registered file descriptors */ size_t fdsSize; UA_RegisteredFD *fds; + struct pollfd *pollfds; /* has the same size as "fds" */ /* Flag determining whether the eventloop is currently within the "run" method */ UA_Boolean executing; @@ -46,9 +48,6 @@ struct UA_EventLoop { /* Timer */ /*********/ -static UA_StatusCode -processFDs(UA_EventLoop *el, UA_DateTime usedTimeout); - static void timerExecutionTrampoline(void *executionApplication, UA_ApplicationCallback cb, void *callbackApplication, void *data) { @@ -237,109 +236,52 @@ UA_EventLoop_stop(UA_EventLoop *el) { UA_UNLOCK(&el->elMutex); } -/* After every select, reset the file-descriptors to listen on */ -static UA_FD -setFDSets(UA_EventLoop *el, fd_set *readset, fd_set *writeset, fd_set *errset) { - FD_ZERO(readset); - FD_ZERO(writeset); - FD_ZERO(errset); - UA_FD highestfd = UA_INVALID_FD; - for(size_t i = 0; i < el->fdsSize; i++) { - - UA_FD currentFD = el->fds[i].fd; - /* Add to the fd_sets */ - if(el->fds[i].eventMask & UA_POSIX_EVENT_READ) - UA_fd_set(currentFD, readset); - if(el->fds[i].eventMask & UA_POSIX_EVENT_WRITE) - UA_fd_set(currentFD, writeset); - if(el->fds[i].eventMask & UA_POSIX_EVENT_ERR) - UA_fd_set(currentFD, errset); - - /* Highest fd? */ - if(currentFD > highestfd || highestfd == UA_INVALID_FD) - highestfd = currentFD; - } - return highestfd; -} - static UA_StatusCode -processFDs(UA_EventLoop *el, UA_DateTime usedTimeout) { - fd_set readset, writeset, errset; - UA_FD highestfd = setFDSets(el, &readset, &writeset, &errset); - - /* Nothing to do? */ - if(highestfd == UA_INVALID_FD) { - UA_LOG_TRACE(el->logger, UA_LOGCATEGORY_EVENTLOOP, - "No valid FDs for processing"); - return UA_STATUSCODE_GOOD; - } - - struct timeval tmptv = { -#ifndef _WIN32 - (time_t)(usedTimeout / UA_DATETIME_SEC), - (suseconds_t)((usedTimeout % UA_DATETIME_SEC) / UA_DATETIME_USEC) -#else +pollFDs(UA_EventLoop *el, UA_DateTime usedTimeout) { + /* Poll the registered sockets */ +#ifdef _GNU_SOURCE + struct timespec precisionTimeout = { (long)(usedTimeout / UA_DATETIME_SEC), - (long)((usedTimeout % UA_DATETIME_SEC) / UA_DATETIME_USEC) -#endif + (long)((usedTimeout % UA_DATETIME_SEC) * 100) }; + int pollStatus = ppoll(el->pollfds, el->fdsSize, &precisionTimeout, NULL); +#else + int pollStatus = UA_poll(el->pollfds, el->fdsSize, usedTimeout / UA_DATETIME_MSEC); +#endif - int selectStatus = UA_select(highestfd+1, &readset, &writeset, &errset, &tmptv); - if(selectStatus < 0) { + if(pollStatus < 0) { /* We will retry, only log the error */ UA_LOG_SOCKET_ERRNO_WRAP( UA_LOG_WARNING(UA_EventLoop_getLogger(el), UA_LOGCATEGORY_EVENTLOOP, - "Error during select: %s", errno_str)); - el->executing = false; + "Error during poll: %s", errno_str)); return UA_STATUSCODE_GOODCALLAGAIN; } /* Loop over all registered FD to see if an event arrived. Yes, this is why - * select is slow for many open sockets. */ + * poll is slow for many open sockets. */ + int processed = 0; for(size_t i = 0; i < el->fdsSize; i++) { - UA_RegisteredFD *rfd = &el->fds[i]; - UA_FD fd = rfd->fd; - UA_assert(fd > 0); - - UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, - "Processing fd: %u", (unsigned)fd); + /* All done */ + if(processed >= pollStatus) + break; - /* Error Event */ - if((rfd->eventMask & UA_POSIX_EVENT_ERR) && UA_fd_isset(fd, &errset)) { - UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, - "Processing error event for fd: %u", (unsigned)fd); - UA_UNLOCK(&el->elMutex); - rfd->callback(rfd->es, fd, &rfd->fdcontext, UA_POSIX_EVENT_ERR); - UA_LOCK(&el->elMutex); - if(i == el->fdsSize || fd != el->fds[i].fd) - i--; /* The fd has removed itself */ + /* Nothing to do for this fd */ + if(el->pollfds[i].revents == 0) continue; - } - /* Read Event */ - if((rfd->eventMask & UA_POSIX_EVENT_READ) && UA_fd_isset(fd, &readset)) { - UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, - "Processing read event for fd: %u", (unsigned)fd); - UA_UNLOCK(&el->elMutex); - rfd->callback(rfd->es, fd, &rfd->fdcontext, UA_POSIX_EVENT_READ); - UA_LOCK(&el->elMutex); - if(i == el->fdsSize || fd != el->fds[i].fd) - i--; /* The fd has removed itself */ - continue; - } + /* Process the fd */ + UA_RegisteredFD *rfd = &el->fds[i]; + UA_FD fd = el->pollfds[i].fd; + short revent = el->pollfds[i].revents; + UA_UNLOCK(&el->elMutex); + rfd->callback(rfd->es, fd, &rfd->fdcontext, revent); + UA_LOCK(&el->elMutex); + processed++; - /* Write Event */ - if((rfd->eventMask & UA_POSIX_EVENT_WRITE) && UA_fd_isset(fd, &writeset)) { - UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, - "Processing write event for fd: %u", (unsigned)fd); - UA_UNLOCK(&el->elMutex); - rfd->callback(rfd->es, fd, &rfd->fdcontext, UA_POSIX_EVENT_WRITE); - UA_LOCK(&el->elMutex); - if(i == el->fdsSize || fd != el->fds[i].fd) - i--; /* The fd has removed itself */ - continue; - } + /* The fd has removed itself from within the callback? */ + if(i >= el->fdsSize || fd != el->pollfds[i].fd) + i--; } return UA_STATUSCODE_GOOD; } @@ -392,15 +334,7 @@ UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) { UA_DateTime usedTimeout = UA_MIN(callbackTimeout, maxTimeout); /* Listen on the active file-descriptors (sockets) from the ConnectionManagers */ - UA_StatusCode rv = processFDs(el, usedTimeout); - if(rv == UA_STATUSCODE_GOODCALLAGAIN) { - UA_UNLOCK(&el->elMutex); - return UA_STATUSCODE_GOOD; - } - if(rv != UA_STATUSCODE_GOOD) { - UA_UNLOCK(&el->elMutex); - return rv; - } + UA_StatusCode rv = pollFDs(el, usedTimeout); /* Process and then free registered delayed callbacks */ processDelayed(el); @@ -411,7 +345,7 @@ UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) { el->executing = false; UA_UNLOCK(&el->elMutex); - return UA_STATUSCODE_GOOD; + return rv; } @@ -505,12 +439,20 @@ UA_EventLoop_registerFD(UA_EventLoop *el, UA_FD fd, short eventMask, } el->fds = fds_tmp; + struct pollfd *pollfds_tmp = (struct pollfd*) + UA_realloc(el->pollfds, sizeof(struct pollfd) *(el->fdsSize + 1)); + if(!pollfds_tmp) { + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + el->pollfds = pollfds_tmp; + /* Add to the last entry */ el->fds[el->fdsSize].callback = cb; - el->fds[el->fdsSize].eventMask = eventMask; el->fds[el->fdsSize].es = es; el->fds[el->fdsSize].fdcontext = fdcontext; - el->fds[el->fdsSize].fd = fd; + el->pollfds[el->fdsSize].fd = fd; + el->pollfds[el->fdsSize].events = eventMask; el->fdsSize++; UA_UNLOCK(&el->elMutex); @@ -525,7 +467,7 @@ UA_EventLoop_modifyFD(UA_EventLoop *el, UA_FD fd, short eventMask, /* Find the entry */ size_t i = 0; for(; i < el->fdsSize; i++) { - if(el->fds[i].fd == fd) + if(el->pollfds[i].fd == fd) break; } @@ -537,7 +479,7 @@ UA_EventLoop_modifyFD(UA_EventLoop *el, UA_FD fd, short eventMask, /* Modify */ el->fds[i].callback = cb; - el->fds[i].eventMask = eventMask; + el->pollfds[i].events = eventMask; el->fds[i].fdcontext = fdcontext; UA_UNLOCK(&el->elMutex); @@ -554,7 +496,7 @@ UA_EventLoop_deregisterFD(UA_EventLoop *el, UA_FD fd) { /* Find the entry */ size_t i = 0; for(; i < el->fdsSize; i++) { - if(el->fds[i].fd == fd) + if(el->pollfds[i].fd == fd) break; } @@ -564,21 +506,28 @@ UA_EventLoop_deregisterFD(UA_EventLoop *el, UA_FD fd) { return UA_STATUSCODE_BADNOTFOUND; } - if(el->fdsSize > 1) { - /* Move the last entry in the ith slot and realloc. */ - el->fdsSize--; + el->fdsSize--; + if(el->fdsSize > 0) { + /* Move the last entry to the ith slot and realloc. */ el->fds[i] = el->fds[el->fdsSize]; + el->pollfds[i] = el->pollfds[el->fdsSize]; + + /* If realloc fails the fds are still in a correct state with + * possibly lost memory, so failing silently here is ok */ UA_RegisteredFD *fds_tmp = (UA_RegisteredFD*) UA_realloc(el->fds, sizeof(UA_RegisteredFD) * el->fdsSize); - /* if realloc fails the fds are still in a correct state with - * possibly lost memory, so failing silently here is ok */ if(fds_tmp) el->fds = fds_tmp; + struct pollfd *pollfds_tmp = (struct pollfd*) + UA_realloc(el->pollfds, sizeof(struct pollfd) * el->fdsSize); + if(pollfds_tmp) + el->pollfds = pollfds_tmp; } else { - /* Remove the last entry */ + /* Free the lists */ UA_free(el->fds); el->fds = NULL; - el->fdsSize = 0; + UA_free(el->pollfds); + el->pollfds = NULL; } UA_UNLOCK(&el->elMutex); @@ -590,10 +539,13 @@ UA_EventLoop_iterateFD(UA_EventLoop *el, UA_EventSource *es, UA_FDCallback cb) { for(size_t i = 0; i < el->fdsSize; i++) { if(el->fds[i].es != es) continue; - UA_FD fd = el->fds[i].fd; + + UA_FD fd = el->pollfds[i].fd; cb(es, fd, el->fds[i].fdcontext, 0); - if(i == el->fdsSize || fd != el->fds[i].fd) - i--; /* The fd has removed itself */ + + /* The fd has removed itself from within the callback? */ + if(i >= el->fdsSize || fd != el->pollfds[i].fd) + i--; } } diff --git a/arch/eventloop_posix.h b/arch/eventloop_posix.h index fcc1dd8ce31..dae212f22d2 100644 --- a/arch/eventloop_posix.h +++ b/arch/eventloop_posix.h @@ -29,7 +29,7 @@ # define UA_WOULDBLOCK WSAEWOULDBLOCK # define UA_ERR_CONNECTION_PROGRESS WSAEWOULDBLOCK #else /* Unix */ -# include +# include #endif /* Catch-all for the architectures that are "actually POSIX" */ @@ -64,13 +64,7 @@ _UA_BEGIN_DECLS /* POSIX events are based on sockets / file descriptors. The EventSources can * register their fd in the EventLoop so that they are considered by the - * EventLoop dropping into "select" to wait for events. */ - -/* POSIX-select can listen for three types of events. It has to be selected - * for each registered fd which events they are interested in. */ -#define UA_POSIX_EVENT_READ 1 -#define UA_POSIX_EVENT_WRITE 2 -#define UA_POSIX_EVENT_ERR 4 + * EventLoop dropping into "poll" to wait for events. */ typedef void (*UA_FDCallback)(UA_EventSource *es, UA_FD fd, void *fdcontext, short event); diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index e0f4ed9e0a0..aa2653e93ed 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -36,7 +36,8 @@ TCP_freeNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId, UA_ByteString_clear(buf); } -/* Set the socket non-blocking */ +/* Set the socket non-blocking. If the listen-socket is nonblocking, incoming + * connections inherit this state. */ static UA_StatusCode TCP_setNonBlocking(UA_FD sockfd) { #ifndef _WIN32 @@ -116,7 +117,7 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, /* Write-Event, a new connection has opened. */ UA_StatusCode res = UA_STATUSCODE_GOOD; - if(event & UA_POSIX_EVENT_WRITE) { + if(event == UA_POLLOUT) { UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, "TCP %u\t| Opening a new connection", (unsigned)fd); @@ -126,8 +127,9 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, UA_STATUSCODE_GOOD, 0, NULL, UA_BYTESTRING_NULL); /* Now we are interested in read-events. */ - UA_EventLoop_modifyFD(cm->eventSource.eventLoop, fd, UA_POSIX_EVENT_READ, - (UA_FDCallback)TCP_connectionSocketCallback, *fdcontext); + UA_EventLoop_modifyFD(cm->eventSource.eventLoop, fd, UA_POLLIN, + (UA_FDCallback)TCP_connectionSocketCallback, + *fdcontext); return; } @@ -238,7 +240,7 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, /* Configure the new socket */ UA_StatusCode res = UA_STATUSCODE_GOOD; - res |= TCP_setNonBlocking(newsockfd); /* Set the socket non-blocking */ + /* res |= TCP_setNonBlocking(newsockfd); Inherited from the listen-socket */ res |= TCP_setNoSigPipe(newsockfd); /* Supress interrupts from the socket */ res |= TCP_setNoNagle(newsockfd); /* Disable Nagle's algorithm */ if(res != UA_STATUSCODE_GOOD) { @@ -259,8 +261,7 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, 0, NULL, UA_BYTESTRING_NULL); /* Register in the EventLoop. Signal to the user if registering failed. */ - res = UA_EventLoop_registerFD(cm->eventSource.eventLoop, newsockfd, - UA_POSIX_EVENT_READ, + res = UA_EventLoop_registerFD(cm->eventSource.eventLoop, newsockfd, UA_POLLIN, (UA_FDCallback)TCP_connectionSocketCallback, &cm->eventSource, ctx); if(res != UA_STATUSCODE_GOOD) { @@ -389,8 +390,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { /* Register the socket */ UA_StatusCode res = - UA_EventLoop_registerFD(cm->eventSource.eventLoop, listenSocket, - UA_POSIX_EVENT_READ, + UA_EventLoop_registerFD(cm->eventSource.eventLoop, listenSocket, UA_POLLIN, (UA_FDCallback)TCP_listenSocketCallback, &cm->eventSource, NULL); if(res != UA_STATUSCODE_GOOD) { @@ -609,7 +609,7 @@ TCP_openConnection(UA_ConnectionManager *cm, } /* Register the fd to trigger when output is possible (the connection is open) */ - res = UA_EventLoop_registerFD(cm->eventSource.eventLoop, newSock, UA_POSIX_EVENT_WRITE, + res = UA_EventLoop_registerFD(cm->eventSource.eventLoop, newSock, UA_POLLOUT, (UA_FDCallback)TCP_connectionSocketCallback, &cm->eventSource, context); if(res != UA_STATUSCODE_GOOD) { diff --git a/include/open62541/config.h.in b/include/open62541/config.h.in index f400173649e..82a77768e94 100644 --- a/include/open62541/config.h.in +++ b/include/open62541/config.h.in @@ -126,11 +126,18 @@ # ifndef _DEFAULT_SOURCE # define _DEFAULT_SOURCE # endif + /* On older systems we need to define _BSD_SOURCE. * _DEFAULT_SOURCE is an alias for that. */ # ifndef _BSD_SOURCE # define _BSD_SOURCE # endif + +/* Define _GNU_SOURCE to get functions like ppoll. Comment this out to + * only use standard POSIX definitions. */ +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# endif #endif // specific architectures can undef this From 0bca663c2ba79f28f219a97e506cb5159b7f45b6 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 25 Dec 2021 18:43:42 +0100 Subject: [PATCH 0028/1963] refactor(arch): Replace UA_ERR_CONNECTION_PROGRESS with UA_WOULDBLOCK everywhere --- arch/Readme.md | 1 - arch/common/ua_lwip.h | 1 - arch/eCos/ua_architecture.h | 1 - arch/eventloop_posix.h | 15 --------------- arch/eventloop_posix_tcp.c | 2 +- arch/network_tcp.c | 2 +- arch/posix/ua_architecture.h | 1 - arch/vxworks/ua_architecture.h | 1 - arch/wec7/ua_architecture.h | 1 - arch/win32/ua_architecture.h | 2 -- 10 files changed, 2 insertions(+), 25 deletions(-) diff --git a/arch/Readme.md b/arch/Readme.md index db1ed466d86..00665d65213 100644 --- a/arch/Readme.md +++ b/arch/Readme.md @@ -95,7 +95,6 @@ To port to a new architecture you should follow these steps: //#define UA_AGAIN //#define UA_EAGAIN //#define UA_WOULDBLOCK - //#define UA_ERR_CONNECTION_PROGRESS //#define UA_INTERRUPTED /* diff --git a/arch/common/ua_lwip.h b/arch/common/ua_lwip.h index 06f9ec30707..aa731447cf5 100644 --- a/arch/common/ua_lwip.h +++ b/arch/common/ua_lwip.h @@ -35,7 +35,6 @@ #define UA_AGAIN EAGAIN #define UA_EAGAIN EAGAIN #define UA_WOULDBLOCK EWOULDBLOCK -#define UA_ERR_CONNECTION_PROGRESS EINPROGRESS #define UA_send lwip_send #define UA_recv lwip_recv diff --git a/arch/eCos/ua_architecture.h b/arch/eCos/ua_architecture.h index c6ef58dbd60..6c7dc754b15 100644 --- a/arch/eCos/ua_architecture.h +++ b/arch/eCos/ua_architecture.h @@ -37,7 +37,6 @@ #define UA_AGAIN EAGAIN #define UA_EAGAIN EAGAIN #define UA_WOULDBLOCK EWOULDBLOCK -#define UA_ERR_CONNECTION_PROGRESS EINPROGRESS #define UA_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) \ getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) diff --git a/arch/eventloop_posix.h b/arch/eventloop_posix.h index dae212f22d2..beff62a2810 100644 --- a/arch/eventloop_posix.h +++ b/arch/eventloop_posix.h @@ -27,7 +27,6 @@ # define UA_AGAIN WSAEWOULDBLOCK # define UA_EAGAIN EAGAIN # define UA_WOULDBLOCK WSAEWOULDBLOCK -# define UA_ERR_CONNECTION_PROGRESS WSAEWOULDBLOCK #else /* Unix */ # include #endif @@ -44,20 +43,6 @@ # define UA_AGAIN EAGAIN # define UA_EAGAIN EAGAIN # define UA_WOULDBLOCK EWOULDBLOCK -# define UA_ERR_CONNECTION_PROGRESS EINPROGRESS -#endif - -/* Workaround a bug in early glibc. Additionally, some non-glibc implementations - * use a macro for FD_SET that triggers a cast-warning (e.g. early BSD libc or - * musl libc). */ -#if (!defined(__GNU_LIBRARY__) && defined(FD_SET)) || \ - (defined(__GNU_LIBRARY__) && (__GNU_LIBRARY__ <= 6) && \ - (__GLIBC__ <= 2) && (__GLIBC_MINOR__ < 16)) -# define UA_FD_SET(fd, fds) FD_SET((unsigned int)fd, fds) -# define UA_FD_ISSET(fd, fds) FD_ISSET((unsigned int)fd, fds) -#else -# define UA_FD_SET(fd, fds) FD_SET((UA_FD)fd, fds) -# define UA_FD_ISSET(fd, fds) FD_ISSET((UA_FD)fd, fds) #endif _UA_BEGIN_DECLS diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index aa2653e93ed..2df6cef604b 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -599,7 +599,7 @@ TCP_openConnection(UA_ConnectionManager *cm, /* Non-blocking connect */ error = UA_connect(newSock, info->ai_addr, info->ai_addrlen); freeaddrinfo(info); - if(error != 0 && UA_ERRNO != UA_ERR_CONNECTION_PROGRESS) { + if(error != 0 && UA_ERRNO != UA_WOULDBLOCK) { UA_LOG_SOCKET_ERRNO_WRAP( UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, diff --git a/arch/network_tcp.c b/arch/network_tcp.c index e5998e3638d..ea62719a1b8 100644 --- a/arch/network_tcp.c +++ b/arch/network_tcp.c @@ -764,7 +764,7 @@ UA_ClientConnectionTCP_poll(UA_Connection *connection, UA_UInt32 timeout, } /* The connection failed */ - if((UA_ERRNO != UA_ERR_CONNECTION_PROGRESS)) { + if(UA_ERRNO != UA_WOULDBLOCK && UA_ERRNO != UA_INPROGRESS) { UA_LOG_WARNING(logger, UA_LOGCATEGORY_NETWORK, "Connection to %.*s failed with error: %s", (int)tcpConnection->endpointUrl.length, diff --git a/arch/posix/ua_architecture.h b/arch/posix/ua_architecture.h index 3cf2dc2b868..966671715af 100644 --- a/arch/posix/ua_architecture.h +++ b/arch/posix/ua_architecture.h @@ -69,7 +69,6 @@ void UA_sleep_ms(unsigned long ms); #define UA_AGAIN EAGAIN #define UA_EAGAIN EAGAIN #define UA_WOULDBLOCK EWOULDBLOCK -#define UA_ERR_CONNECTION_PROGRESS EINPROGRESS #define UA_POLLIN POLLIN #define UA_POLLOUT POLLOUT diff --git a/arch/vxworks/ua_architecture.h b/arch/vxworks/ua_architecture.h index a7f67338f88..9a7e60a87bc 100644 --- a/arch/vxworks/ua_architecture.h +++ b/arch/vxworks/ua_architecture.h @@ -60,7 +60,6 @@ #define UA_AGAIN EAGAIN #define UA_EAGAIN EAGAIN #define UA_WOULDBLOCK EWOULDBLOCK -#define UA_ERR_CONNECTION_PROGRESS EINPROGRESS #define UA_ENABLE_LOG_COLORS diff --git a/arch/wec7/ua_architecture.h b/arch/wec7/ua_architecture.h index d27df762a5a..3473da5679f 100644 --- a/arch/wec7/ua_architecture.h +++ b/arch/wec7/ua_architecture.h @@ -74,7 +74,6 @@ void UA_sleep_ms(unsigned long ms); #define UA_AGAIN WSAEWOULDBLOCK #define UA_EAGAIN EAGAIN #define UA_WOULDBLOCK WSAEWOULDBLOCK -#define UA_ERR_CONNECTION_PROGRESS WSAEWOULDBLOCK #define UA_fd_set(fd, fds) FD_SET((UA_SOCKET)fd, fds) #define UA_fd_isset(fd, fds) FD_ISSET((UA_SOCKET)fd, fds) diff --git a/arch/win32/ua_architecture.h b/arch/win32/ua_architecture.h index 4372d0cf391..62f49cf2f50 100644 --- a/arch/win32/ua_architecture.h +++ b/arch/win32/ua_architecture.h @@ -82,8 +82,6 @@ void UA_sleep_ms(unsigned long ms); #define UA_AGAIN WSAEWOULDBLOCK #define UA_EAGAIN EAGAIN #define UA_WOULDBLOCK WSAEWOULDBLOCK -#define UA_ERR_CONNECTION_PROGRESS WSAEWOULDBLOCK - #define UA_POLLIN POLLRDNORM #define UA_POLLOUT POLLWRNORM From 09a19278baa4af821ac3d056a9e693f6cf4a14ee Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 25 Dec 2021 18:44:19 +0100 Subject: [PATCH 0029/1963] refactor(arch): Clean up socket definitions for MinGW --- arch/eventloop_posix.h | 2 -- arch/win32/ua_architecture.h | 15 ++------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/arch/eventloop_posix.h b/arch/eventloop_posix.h index beff62a2810..59c0fafc4a8 100644 --- a/arch/eventloop_posix.h +++ b/arch/eventloop_posix.h @@ -20,7 +20,6 @@ # if !defined(__MINGW32__) || defined(__clang__) # define UA_FD SOCKET /* On MSVC, a socket is a pointer and not an int */ # define UA_INVALID_FD INVALID_SOCKET -//# define UA_close(s) closesocket(s) /* closesocket() takes a SOCKET (and sock() an int) */ # endif # define UA_ERRNO WSAGetLastError() # define UA_INTERRUPTED WSAEINTR @@ -35,7 +34,6 @@ #ifndef UA_FD # define UA_FD int # define UA_INVALID_FD -1 -//# define UA_close(s) close(s) #endif #ifndef UA_ERRNO # define UA_ERRNO errno diff --git a/arch/win32/ua_architecture.h b/arch/win32/ua_architecture.h index 62f49cf2f50..ba9db26bac4 100644 --- a/arch/win32/ua_architecture.h +++ b/arch/win32/ua_architecture.h @@ -11,10 +11,6 @@ #ifndef PLUGINS_ARCH_WIN32_UA_ARCHITECTURE_H_ #define PLUGINS_ARCH_WIN32_UA_ARCHITECTURE_H_ -#ifndef _BSD_SOURCE -# define _BSD_SOURCE -#endif - /* Disable some security warnings on MSVC */ #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) # define _CRT_SECURE_NO_WARNINGS @@ -42,7 +38,6 @@ #include #include #include -#include #if defined (_MSC_VER) || defined(__clang__) # ifndef UNDER_CE @@ -69,14 +64,8 @@ void UA_sleep_ms(unsigned long ms); // #define UA_ENABLE_LOG_COLORS #define UA_IPV6 1 - -#if defined(__MINGW32__) && !defined(__clang__) //mingw defines SOCKET as long long unsigned int, giving errors in logging and when comparing with UA_Int32 -# define UA_SOCKET int -# define UA_INVALID_SOCKET -1 -#else -# define UA_SOCKET SOCKET -# define UA_INVALID_SOCKET INVALID_SOCKET -#endif +#define UA_SOCKET SOCKET +#define UA_INVALID_SOCKET INVALID_SOCKET #define UA_ERRNO WSAGetLastError() #define UA_INTERRUPTED WSAEINTR #define UA_AGAIN WSAEWOULDBLOCK From b20fcfe291c8a9353961c78a0e734c5ce0835622 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 25 Dec 2021 20:58:13 +0100 Subject: [PATCH 0030/1963] refactor(arch): Remove the duplicity of UA_AGAIN, UA_EAGAIN --- arch/Readme.md | 3 +-- arch/common/ua_lwip.h | 3 +-- arch/eCos/ua_architecture.h | 3 +-- arch/eventloop_posix_tcp.c | 9 +++++++-- arch/network_tcp.c | 2 +- arch/posix/ua_architecture.h | 3 +-- arch/vxworks/ua_architecture.h | 3 +-- arch/wec7/ua_architecture.h | 3 +-- arch/win32/ua_architecture.h | 3 +-- 9 files changed, 15 insertions(+), 17 deletions(-) diff --git a/arch/Readme.md b/arch/Readme.md index 00665d65213..a055890fc99 100644 --- a/arch/Readme.md +++ b/arch/Readme.md @@ -90,10 +90,9 @@ To port to a new architecture you should follow these steps: //#define UA_IPV6 1 //or 0 //#define UA_SOCKET //#define UA_INVALID_SOCKET - //#define UA_ERRNO + //#define UA_ERRNO //#define UA_INTERRUPTED //#define UA_AGAIN - //#define UA_EAGAIN //#define UA_WOULDBLOCK //#define UA_INTERRUPTED diff --git a/arch/common/ua_lwip.h b/arch/common/ua_lwip.h index aa731447cf5..82c3de0ba54 100644 --- a/arch/common/ua_lwip.h +++ b/arch/common/ua_lwip.h @@ -32,8 +32,7 @@ #define UA_INVALID_SOCKET -1 #define UA_ERRNO errno #define UA_INTERRUPTED EINTR -#define UA_AGAIN EAGAIN -#define UA_EAGAIN EAGAIN +#define UA_AGAIN EAGAIN /* the same as wouldblock on nearly every system */ #define UA_WOULDBLOCK EWOULDBLOCK #define UA_send lwip_send diff --git a/arch/eCos/ua_architecture.h b/arch/eCos/ua_architecture.h index 6c7dc754b15..2b1b1db92d4 100644 --- a/arch/eCos/ua_architecture.h +++ b/arch/eCos/ua_architecture.h @@ -34,8 +34,7 @@ #define UA_INVALID_SOCKET -1 #define UA_ERRNO errno #define UA_INTERRUPTED EINTR -#define UA_AGAIN EAGAIN -#define UA_EAGAIN EAGAIN +#define UA_AGAIN EAGAIN /* the same as wouldblock on nearly every system */ #define UA_WOULDBLOCK EWOULDBLOCK #define UA_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) \ diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index 2df6cef604b..5e001b32b4b 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -167,7 +167,9 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, response.length = (size_t)ret; /* Set the length of the received buffer */ cm->connectionCallback(cm, (uintptr_t)fd, fdcontext, UA_STATUSCODE_GOOD, 0, NULL, response); - } else if(UA_ERRNO != UA_INTERRUPTED && UA_ERRNO != UA_EAGAIN) { + } else if(UA_ERRNO != UA_INTERRUPTED && + UA_ERRNO != UA_WOULDBLOCK && + UA_ERRNO != UA_AGAIN) { /* Orderly shutdown of the connection. Signal to the application and * then close the connection. We end up in this path after shutdown was * called on the socket. Here, we then are in the next EventLoop @@ -489,7 +491,10 @@ TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, n = UA_send((UA_FD)connectionId, (const char*)buf->data + nWritten, bytes_to_send, flags); - if(n < 0 && UA_ERRNO != UA_INTERRUPTED && UA_ERRNO != UA_AGAIN) { + if(n < 0 && + UA_ERRNO != UA_INTERRUPTED && + UA_ERRNO != UA_WOULDBLOCK && + UA_ERRNO != UA_AGAIN) { UA_LOG_SOCKET_ERRNO_GAI_WRAP( UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, diff --git a/arch/network_tcp.c b/arch/network_tcp.c index ea62719a1b8..ea9de4cf776 100644 --- a/arch/network_tcp.c +++ b/arch/network_tcp.c @@ -153,7 +153,7 @@ connection_recv(UA_Connection *connection, UA_ByteString *response, if(internallyAllocated) UA_ByteString_clear(response); if(UA_ERRNO == UA_INTERRUPTED || (timeout > 0) ? - false : (UA_ERRNO == UA_EAGAIN || UA_ERRNO == UA_WOULDBLOCK)) + false : (UA_ERRNO == UA_AGAIN || UA_ERRNO == UA_WOULDBLOCK)) return UA_STATUSCODE_GOOD; /* statuscode_good but no data -> retry */ connection->close(connection); return UA_STATUSCODE_BADCONNECTIONCLOSED; diff --git a/arch/posix/ua_architecture.h b/arch/posix/ua_architecture.h index 966671715af..0fa808c699f 100644 --- a/arch/posix/ua_architecture.h +++ b/arch/posix/ua_architecture.h @@ -66,8 +66,7 @@ void UA_sleep_ms(unsigned long ms); #define UA_INVALID_SOCKET -1 #define UA_ERRNO errno #define UA_INTERRUPTED EINTR -#define UA_AGAIN EAGAIN -#define UA_EAGAIN EAGAIN +#define UA_AGAIN EAGAIN /* the same as wouldblock on nearly every system */ #define UA_WOULDBLOCK EWOULDBLOCK #define UA_POLLIN POLLIN diff --git a/arch/vxworks/ua_architecture.h b/arch/vxworks/ua_architecture.h index 9a7e60a87bc..b4409c272a3 100644 --- a/arch/vxworks/ua_architecture.h +++ b/arch/vxworks/ua_architecture.h @@ -57,8 +57,7 @@ #define UA_INVALID_SOCKET -1 #define UA_ERRNO errno #define UA_INTERRUPTED EINTR -#define UA_AGAIN EAGAIN -#define UA_EAGAIN EAGAIN +#define UA_AGAIN EAGAIN /* the same as wouldblock on nearly every system */ #define UA_WOULDBLOCK EWOULDBLOCK #define UA_ENABLE_LOG_COLORS diff --git a/arch/wec7/ua_architecture.h b/arch/wec7/ua_architecture.h index 3473da5679f..3e5252d2e7a 100644 --- a/arch/wec7/ua_architecture.h +++ b/arch/wec7/ua_architecture.h @@ -71,8 +71,7 @@ void UA_sleep_ms(unsigned long ms); #endif #define UA_ERRNO WSAGetLastError() #define UA_INTERRUPTED WSAEINTR -#define UA_AGAIN WSAEWOULDBLOCK -#define UA_EAGAIN EAGAIN +#define UA_AGAIN EAGAIN /* the same as wouldblock on nearly every system */ #define UA_WOULDBLOCK WSAEWOULDBLOCK #define UA_fd_set(fd, fds) FD_SET((UA_SOCKET)fd, fds) diff --git a/arch/win32/ua_architecture.h b/arch/win32/ua_architecture.h index ba9db26bac4..f4a4a5da0ca 100644 --- a/arch/win32/ua_architecture.h +++ b/arch/win32/ua_architecture.h @@ -68,8 +68,7 @@ void UA_sleep_ms(unsigned long ms); #define UA_INVALID_SOCKET INVALID_SOCKET #define UA_ERRNO WSAGetLastError() #define UA_INTERRUPTED WSAEINTR -#define UA_AGAIN WSAEWOULDBLOCK -#define UA_EAGAIN EAGAIN +#define UA_AGAIN EAGAIN /* the same as wouldblock on nearly every system */ #define UA_WOULDBLOCK WSAEWOULDBLOCK #define UA_POLLIN POLLRDNORM #define UA_POLLOUT POLLWRNORM From 6b537bb2b543c4822cb4529323d37807b37ec2af Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 25 Dec 2021 20:59:28 +0100 Subject: [PATCH 0031/1963] refactor(arch): Simplify the networking macros for the EventLoop --- arch/eventloop_posix.h | 32 +++----------------------------- arch/eventloop_posix_tcp.c | 4 ++-- 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/arch/eventloop_posix.h b/arch/eventloop_posix.h index 59c0fafc4a8..328ff2a5128 100644 --- a/arch/eventloop_posix.h +++ b/arch/eventloop_posix.h @@ -12,36 +12,10 @@ #include #include -/* A macro-forest to work around small differences between POSIX and - * nearly-POSIX architectures */ +/* TODO: Move the macro-forest from /arch//ua_architecture.h */ -#if defined(_WIN32) /* Windows */ -# include -# if !defined(__MINGW32__) || defined(__clang__) -# define UA_FD SOCKET /* On MSVC, a socket is a pointer and not an int */ -# define UA_INVALID_FD INVALID_SOCKET -# endif -# define UA_ERRNO WSAGetLastError() -# define UA_INTERRUPTED WSAEINTR -# define UA_AGAIN WSAEWOULDBLOCK -# define UA_EAGAIN EAGAIN -# define UA_WOULDBLOCK WSAEWOULDBLOCK -#else /* Unix */ -# include -#endif - -/* Catch-all for the architectures that are "actually POSIX" */ -#ifndef UA_FD -# define UA_FD int -# define UA_INVALID_FD -1 -#endif -#ifndef UA_ERRNO -# define UA_ERRNO errno -# define UA_INTERRUPTED EINTR -# define UA_AGAIN EAGAIN -# define UA_EAGAIN EAGAIN -# define UA_WOULDBLOCK EWOULDBLOCK -#endif +#define UA_FD UA_SOCKET +#define UA_INVALID_FD UA_INVALID_SOCKET _UA_BEGIN_DECLS diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index 5e001b32b4b..750fc7bdfe6 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -304,7 +304,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { /* Create the server socket */ UA_FD listenSocket = UA_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if(listenSocket == UA_INVALID_SOCKET) { + if(listenSocket == UA_INVALID_FD) { UA_LOG_SOCKET_ERRNO_WRAP( UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, @@ -576,7 +576,7 @@ TCP_openConnection(UA_ConnectionManager *cm, /* Create a socket */ UA_FD newSock = socket(info->ai_family, info->ai_socktype, info->ai_protocol); - if(newSock == UA_INVALID_SOCKET) { + if(newSock == UA_INVALID_FD) { freeaddrinfo(info); UA_LOG_SOCKET_ERRNO_WRAP( UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), From 14974576ab32148d8859797669a69898106ff556 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 25 Dec 2021 21:36:12 +0100 Subject: [PATCH 0032/1963] feat(el): Poll if 'send' blocks with EWOULDBLOCK Adapter for the EventLoop from commit 524bf009783343df5504fcf85ff2d784e959a42d by Emmanuel Pacaud --- arch/eCos/ua_architecture.h | 1 + arch/eventloop_posix_tcp.c | 41 +++++++++++++++++++++++----------- arch/network_tcp.c | 2 +- arch/posix/ua_architecture.h | 1 + arch/vxworks/ua_architecture.h | 1 + arch/wec7/ua_architecture.h | 10 +++------ arch/win32/ua_architecture.h | 1 + 7 files changed, 36 insertions(+), 21 deletions(-) diff --git a/arch/eCos/ua_architecture.h b/arch/eCos/ua_architecture.h index 2b1b1db92d4..8c1fbd35121 100644 --- a/arch/eCos/ua_architecture.h +++ b/arch/eCos/ua_architecture.h @@ -35,6 +35,7 @@ #define UA_ERRNO errno #define UA_INTERRUPTED EINTR #define UA_AGAIN EAGAIN /* the same as wouldblock on nearly every system */ +#define UA_INPROGRESS EINPROGRESS #define UA_WOULDBLOCK EWOULDBLOCK #define UA_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) \ diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index 750fc7bdfe6..18835e6a162 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -479,6 +479,10 @@ TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, /* Prevent OS signals when sending to a closed socket */ int flags = MSG_NOSIGNAL; + struct pollfd tmp_poll_fd; + tmp_poll_fd.fd = (UA_FD)connectionId; + tmp_poll_fd.events = UA_POLLOUT; + /* Send the full buffer. This may require several calls to send */ size_t nWritten = 0; do { @@ -491,18 +495,27 @@ TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, n = UA_send((UA_FD)connectionId, (const char*)buf->data + nWritten, bytes_to_send, flags); - if(n < 0 && - UA_ERRNO != UA_INTERRUPTED && - UA_ERRNO != UA_WOULDBLOCK && - UA_ERRNO != UA_AGAIN) { - UA_LOG_SOCKET_ERRNO_GAI_WRAP( - UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), - UA_LOGCATEGORY_NETWORK, - "TCP %u\t| Send failed with error %s", - (unsigned)connectionId, errno_str)); - TCP_shutdownConnection(cm, connectionId); - UA_ByteString_clear(buf); - return UA_STATUSCODE_BADCONNECTIONCLOSED; + if(n < 0) { + /* An error we cannot recover from? */ + if(UA_ERRNO != UA_INTERRUPTED && + UA_ERRNO != UA_WOULDBLOCK && + UA_ERRNO != UA_AGAIN) { + UA_LOG_SOCKET_ERRNO_GAI_WRAP( + UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Send failed with error %s", + (unsigned)connectionId, errno_str)); + TCP_shutdownConnection(cm, connectionId); + UA_ByteString_clear(buf); + return UA_STATUSCODE_BADCONNECTIONCLOSED; + } + + /* Poll for the socket resources to become available and retry + * (blocking) */ + int poll_ret; + do { + poll_ret = UA_poll(&tmp_poll_fd, 1, 100); + } while(poll_ret == 0 || (poll_ret < 0 && UA_ERRNO == UA_INTERRUPTED)); } } while(n < 0); nWritten += (size_t)n; @@ -604,7 +617,9 @@ TCP_openConnection(UA_ConnectionManager *cm, /* Non-blocking connect */ error = UA_connect(newSock, info->ai_addr, info->ai_addrlen); freeaddrinfo(info); - if(error != 0 && UA_ERRNO != UA_WOULDBLOCK) { + if(error != 0 && + UA_ERRNO != UA_INPROGRESS && + UA_ERRNO != UA_WOULDBLOCK) { UA_LOG_SOCKET_ERRNO_WRAP( UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), UA_LOGCATEGORY_NETWORK, diff --git a/arch/network_tcp.c b/arch/network_tcp.c index ea9de4cf776..2a77e514a2c 100644 --- a/arch/network_tcp.c +++ b/arch/network_tcp.c @@ -795,7 +795,7 @@ UA_ClientConnectionTCP_poll(UA_Connection *connection, UA_UInt32 timeout, tcpConnection->server->ai_addrlen); if((error == -1 && UA_ERRNO == EISCONN) || (error == 0)) resultsize = 1; - if(error == -1 && UA_ERRNO != EALREADY && UA_ERRNO != EINPROGRESS) + if(error == -1 && UA_ERRNO != UA_ALREADY && UA_ERRNO != UA_INPROGRESS) break; } while(resultsize == 0); #else diff --git a/arch/posix/ua_architecture.h b/arch/posix/ua_architecture.h index 0fa808c699f..4d2cb1f8f1a 100644 --- a/arch/posix/ua_architecture.h +++ b/arch/posix/ua_architecture.h @@ -67,6 +67,7 @@ void UA_sleep_ms(unsigned long ms); #define UA_ERRNO errno #define UA_INTERRUPTED EINTR #define UA_AGAIN EAGAIN /* the same as wouldblock on nearly every system */ +#define UA_INPROGRESS EINPROGRESS #define UA_WOULDBLOCK EWOULDBLOCK #define UA_POLLIN POLLIN diff --git a/arch/vxworks/ua_architecture.h b/arch/vxworks/ua_architecture.h index b4409c272a3..de4e202b33a 100644 --- a/arch/vxworks/ua_architecture.h +++ b/arch/vxworks/ua_architecture.h @@ -58,6 +58,7 @@ #define UA_ERRNO errno #define UA_INTERRUPTED EINTR #define UA_AGAIN EAGAIN /* the same as wouldblock on nearly every system */ +#define UA_INPROGRESS EINPROGRESS #define UA_WOULDBLOCK EWOULDBLOCK #define UA_ENABLE_LOG_COLORS diff --git a/arch/wec7/ua_architecture.h b/arch/wec7/ua_architecture.h index 3e5252d2e7a..59f60b6b8b6 100644 --- a/arch/wec7/ua_architecture.h +++ b/arch/wec7/ua_architecture.h @@ -62,16 +62,12 @@ void UA_sleep_ms(unsigned long ms); // Windows does not support ansi colors // #define UA_ENABLE_LOG_COLORS -#if defined(__MINGW32__) //mingw defines SOCKET as long long unsigned int, giving errors in logging and when comparing with UA_Int32 -# define UA_SOCKET int -# define UA_INVALID_SOCKET -1 -#else -# define UA_SOCKET SOCKET -# define UA_INVALID_SOCKET INVALID_SOCKET -#endif +#define UA_SOCKET SOCKET +#define UA_INVALID_SOCKET INVALID_SOCKET #define UA_ERRNO WSAGetLastError() #define UA_INTERRUPTED WSAEINTR #define UA_AGAIN EAGAIN /* the same as wouldblock on nearly every system */ +#define UA_INPROGRESS EINPROGRESS #define UA_WOULDBLOCK WSAEWOULDBLOCK #define UA_fd_set(fd, fds) FD_SET((UA_SOCKET)fd, fds) diff --git a/arch/win32/ua_architecture.h b/arch/win32/ua_architecture.h index f4a4a5da0ca..d39896f6135 100644 --- a/arch/win32/ua_architecture.h +++ b/arch/win32/ua_architecture.h @@ -69,6 +69,7 @@ void UA_sleep_ms(unsigned long ms); #define UA_ERRNO WSAGetLastError() #define UA_INTERRUPTED WSAEINTR #define UA_AGAIN EAGAIN /* the same as wouldblock on nearly every system */ +#define UA_INPROGRESS WSAEINPROGRESS #define UA_WOULDBLOCK WSAEWOULDBLOCK #define UA_POLLIN POLLRDNORM #define UA_POLLOUT POLLWRNORM From 9d21e357319cca00af8ee4f7d8591e1e58eb1f34 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 26 Dec 2021 00:34:28 +0100 Subject: [PATCH 0033/1963] refactor(el): Simplify the timeout computation --- arch/eventloop_posix.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/arch/eventloop_posix.c b/arch/eventloop_posix.c index bd55b3d775e..cb67b5a1554 100644 --- a/arch/eventloop_posix.c +++ b/arch/eventloop_posix.c @@ -237,16 +237,19 @@ UA_EventLoop_stop(UA_EventLoop *el) { } static UA_StatusCode -pollFDs(UA_EventLoop *el, UA_DateTime usedTimeout) { +pollFDs(UA_EventLoop *el, UA_DateTime listenTimeout) { + UA_assert(listenTimeout >= 0); /* Poll the registered sockets */ #ifdef _GNU_SOURCE struct timespec precisionTimeout = { - (long)(usedTimeout / UA_DATETIME_SEC), - (long)((usedTimeout % UA_DATETIME_SEC) * 100) + (long)(listenTimeout / UA_DATETIME_SEC), + (long)((listenTimeout % UA_DATETIME_SEC) * 100) }; - int pollStatus = ppoll(el->pollfds, el->fdsSize, &precisionTimeout, NULL); + int pollStatus = ppoll(el->pollfds, el->fdsSize, + &precisionTimeout, NULL); #else - int pollStatus = UA_poll(el->pollfds, el->fdsSize, usedTimeout / UA_DATETIME_MSEC); + int pollStatus = UA_poll(el->pollfds, el->fdsSize, + (int)(listenTimeout / UA_DATETIME_MSEC)); #endif if(pollStatus < 0) { @@ -290,7 +293,7 @@ UA_StatusCode UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) { UA_LOCK(&el->elMutex); - UA_LOG_TRACE(el->logger, UA_LOGCATEGORY_EVENTLOOP, "iterate the EventLoop"); + UA_LOG_TRACE(el->logger, UA_LOGCATEGORY_EVENTLOOP, "Iterate the EventLoop"); if(el->executing) { UA_LOG_ERROR(el->logger, @@ -324,17 +327,16 @@ UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) { UA_Timer_process(&el->timer, dateBeforeCallback, timerExecutionTrampoline, NULL); UA_LOCK(&el->elMutex); - UA_DateTime dateAfterCallback = UA_DateTime_nowMonotonic(); - - UA_DateTime processTimerDuration = dateAfterCallback - dateBeforeCallback; - - UA_DateTime callbackTimeout = dateOfNextCallback - dateAfterCallback; - UA_DateTime maxTimeout = UA_MAX(timeout * UA_DATETIME_MSEC - processTimerDuration, 0); - - UA_DateTime usedTimeout = UA_MIN(callbackTimeout, maxTimeout); + /* Compute the remaining time */ + UA_DateTime maxDate = dateBeforeCallback + (timeout * UA_DATETIME_MSEC); + if(dateOfNextCallback > maxDate) + dateOfNextCallback = maxDate; + UA_DateTime listenTimeout = dateOfNextCallback - UA_DateTime_nowMonotonic(); + if(listenTimeout < 0) + listenTimeout = 0; /* Listen on the active file-descriptors (sockets) from the ConnectionManagers */ - UA_StatusCode rv = pollFDs(el, usedTimeout); + UA_StatusCode rv = pollFDs(el, listenTimeout); /* Process and then free registered delayed callbacks */ processDelayed(el); From 843c6ce8eb828ee1f551ab6d466c7f154796e005 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 26 Dec 2021 23:26:59 +0100 Subject: [PATCH 0034/1963] refactor(el): Use a context pointer for the POSIX fd iterator --- arch/eventloop_posix.c | 7 +++++-- arch/eventloop_posix.h | 14 +++++++++++--- arch/eventloop_posix_tcp.c | 8 +++++--- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/arch/eventloop_posix.c b/arch/eventloop_posix.c index cb67b5a1554..02fcc6d2c40 100644 --- a/arch/eventloop_posix.c +++ b/arch/eventloop_posix.c @@ -537,13 +537,16 @@ UA_EventLoop_deregisterFD(UA_EventLoop *el, UA_FD fd) { } void -UA_EventLoop_iterateFD(UA_EventLoop *el, UA_EventSource *es, UA_FDCallback cb) { +UA_EventLoop_iterateFD(UA_EventLoop *el, UA_EventSource *es, + UA_EventLoopPOSIXIterateCB cb, void *iterateContext) { for(size_t i = 0; i < el->fdsSize; i++) { if(el->fds[i].es != es) continue; UA_FD fd = el->pollfds[i].fd; - cb(es, fd, el->fds[i].fdcontext, 0); + int done = cb(es, fd, el->fds[i].fdcontext, iterateContext); + if(done) + break; /* The fd has removed itself from within the callback? */ if(i >= el->fdsSize || fd != el->pollfds[i].fd) diff --git a/arch/eventloop_posix.h b/arch/eventloop_posix.h index 328ff2a5128..10371672613 100644 --- a/arch/eventloop_posix.h +++ b/arch/eventloop_posix.h @@ -24,7 +24,7 @@ _UA_BEGIN_DECLS * EventLoop dropping into "poll" to wait for events. */ typedef void -(*UA_FDCallback)(UA_EventSource *es, UA_FD fd, void *fdcontext, short event); +(*UA_FDCallback)(UA_EventSource *es, UA_FD fd, void **fdcontext, short event); UA_StatusCode UA_EventLoop_registerFD(UA_EventLoop *el, UA_FD fd, short eventMask, @@ -41,9 +41,17 @@ UA_EventLoop_modifyFD(UA_EventLoop *el, UA_FD fd, short eventMask, UA_StatusCode UA_EventLoop_deregisterFD(UA_EventLoop *el, UA_FD fd); -/* Call the callback for all fd that are registered from that event source */ +/* abort the iteration if the returned boolean is true */ +typedef UA_Boolean +(*UA_EventLoopPOSIXIterateCB)(UA_EventSource *es, UA_FD fd, + void *fdContext, void *iterateContext); + +/* Call the callback for all fd that are registered from that event source. The + * callback is called with the 'event' argument set to zero to disambiguate from + * a callback after 'poll'. */ void -UA_EventLoop_iterateFD(UA_EventLoop *el, UA_EventSource *es, UA_FDCallback cb); +UA_EventLoop_iterateFD(UA_EventLoop *el, UA_EventSource *es, + UA_EventLoopPOSIXIterateCB cb, void *iterateContext); _UA_END_DECLS diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index 18835e6a162..7121ae7e0e4 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -738,9 +738,11 @@ TCP_eventSourceStart(UA_ConnectionManager *cm) { return UA_STATUSCODE_GOOD; } -static void -TCP_shutdownCallback(UA_EventSource *es, UA_FD fd, void *fdcontext, short event) { +static UA_Boolean +TCP_shutdownCallback(UA_EventSource *es, UA_FD fd, + void *fdContext, void *iterateContext) { TCP_shutdownConnection((UA_ConnectionManager*)es, (uintptr_t)fd); + return false; } static void @@ -751,7 +753,7 @@ TCP_eventSourceStop(UA_ConnectionManager *cm) { /* Shut down all registered fd. The cm is set to "stopped" when the last fd * is closed and deregistered in the callback from the EventLoop. */ UA_EventLoop_iterateFD(cm->eventSource.eventLoop, &cm->eventSource, - TCP_shutdownCallback); + TCP_shutdownCallback, NULL); cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPING; TCPConnectionManager *tcm = (TCPConnectionManager*)cm; From 1bd54abce8e66a52eade4058be0405c1dcefb1e3 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 27 Dec 2021 00:21:40 +0100 Subject: [PATCH 0035/1963] style(el): Improve readability of eventloop_posix.c --- arch/eventloop_posix.c | 107 ++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 44 deletions(-) diff --git a/arch/eventloop_posix.c b/arch/eventloop_posix.c index 02fcc6d2c40..cbd0af982bd 100644 --- a/arch/eventloop_posix.c +++ b/arch/eventloop_posix.c @@ -10,7 +10,8 @@ #include "common/ua_timer.h" typedef struct { - /* The members fd and events are stored in the separate pollfds array: + /* The members fd and events are stored in the separate + * pollfds array: * UA_FD fd; * short events; */ UA_EventSource *es; @@ -36,7 +37,8 @@ struct UA_EventLoop { UA_RegisteredFD *fds; struct pollfd *pollfds; /* has the same size as "fds" */ - /* Flag determining whether the eventloop is currently within the "run" method */ + /* Flag determining whether the eventloop is currently within the + * "run" method */ UA_Boolean executing; #if UA_MULTITHREADING >= 100 @@ -49,14 +51,17 @@ struct UA_EventLoop { /*********/ static void -timerExecutionTrampoline(void *executionApplication, UA_ApplicationCallback cb, - void *callbackApplication, void *data) { +timerExecutionTrampoline(void *executionApplication, + UA_ApplicationCallback cb, + void *callbackApplication, + void *data) { cb(callbackApplication, data); } UA_StatusCode UA_EventLoop_addTimedCallback(UA_EventLoop *el, UA_Callback callback, - void *application, void *data, UA_DateTime date, + void *application, void *data, + UA_DateTime date, UA_UInt64 *callbackId) { return UA_Timer_addTimedCallback(&el->timer, callback, application, data, date, callbackId); @@ -64,28 +69,36 @@ UA_EventLoop_addTimedCallback(UA_EventLoop *el, UA_Callback callback, UA_StatusCode UA_EventLoop_addCyclicCallback(UA_EventLoop *el, UA_Callback cb, - void *application, void *data, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy, + void *application, void *data, + UA_Double interval_ms, + UA_DateTime *baseTime, + UA_TimerPolicy timerPolicy, UA_UInt64 *callbackId) { - return UA_Timer_addRepeatedCallback(&el->timer, cb, application, data, - interval_ms, baseTime, timerPolicy, callbackId); + return UA_Timer_addRepeatedCallback(&el->timer, cb, application, + data, interval_ms, baseTime, + timerPolicy, callbackId); } UA_StatusCode -UA_EventLoop_modifyCyclicCallback(UA_EventLoop *el, UA_UInt64 callbackId, - UA_Double interval_ms, UA_DateTime *baseTime, +UA_EventLoop_modifyCyclicCallback(UA_EventLoop *el, + UA_UInt64 callbackId, + UA_Double interval_ms, + UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) { - return UA_Timer_changeRepeatedCallback(&el->timer, callbackId, interval_ms, - baseTime, timerPolicy); + return UA_Timer_changeRepeatedCallback(&el->timer, callbackId, + interval_ms, baseTime, + timerPolicy); } void -UA_EventLoop_removeCyclicCallback(UA_EventLoop *el, UA_UInt64 callbackId) { +UA_EventLoop_removeCyclicCallback(UA_EventLoop *el, + UA_UInt64 callbackId) { UA_Timer_removeCallback(&el->timer, callbackId); } void -UA_EventLoop_addDelayedCallback(UA_EventLoop *el, UA_DelayedCallback *dc) { +UA_EventLoop_addDelayedCallback(UA_EventLoop *el, + UA_DelayedCallback *dc) { UA_LOCK(&el->elMutex); dc->next = el->delayedCallbacks; el->delayedCallbacks = dc; @@ -99,8 +112,8 @@ processDelayed(UA_EventLoop *el) { while(el->delayedCallbacks) { UA_DelayedCallback *dc = el->delayedCallbacks; el->delayedCallbacks = dc->next; - /* Delayed Callbacks might have no cb pointer if all we want to do is - * free the memory */ + /* Delayed Callbacks might have no cb pointer if all + * we want to do is free the memory */ if(dc->callback) { UA_UNLOCK(&el->elMutex); dc->callback(dc->application, dc->data); @@ -116,7 +129,8 @@ processDelayed(UA_EventLoop *el) { UA_EventLoop * UA_EventLoop_new(const UA_Logger *logger) { - UA_EventLoop *el = (UA_EventLoop*)UA_malloc(sizeof(UA_EventLoop)); + UA_EventLoop *el = (UA_EventLoop*) + UA_malloc(sizeof(UA_EventLoop)); if(!el) return NULL; memset(el, 0, sizeof(UA_EventLoop)); @@ -154,8 +168,8 @@ UA_EventLoop_delete(UA_EventLoop *el) { /* Process remaining delayed callbacks */ processDelayed(el); - /* All file descriptors were removed together with the coresponding - * EventSource */ + /* All file descriptors were removed together with the + * coresponding EventSource */ UA_assert(el->fdsSize == 0); /* Clean up */ @@ -184,7 +198,8 @@ UA_EventLoop_start(UA_EventLoop *el) { return UA_STATUSCODE_BADINTERNALERROR; } - UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, "Starting the EventLoop"); + UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "Starting the EventLoop"); UA_EventSource *es = el->eventSources; UA_StatusCode res = UA_STATUSCODE_GOOD; @@ -209,7 +224,8 @@ checkClosed(UA_EventLoop *el) { es = es->next; } - UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, "The EventLoop has stopped"); + UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "The EventLoop has stopped"); el->state = UA_EVENTLOOPSTATE_STOPPED; } @@ -217,9 +233,10 @@ void UA_EventLoop_stop(UA_EventLoop *el) { UA_LOCK(&el->elMutex); - UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, "Stopping the EventLoop"); + UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "Stopping the EventLoop"); - /* Shutdown all event sources. This will close open connections. */ + /* Shutdown all event sources. This closes open connections. */ UA_EventSource *es = el->eventSources; while(es) { if(es->state == UA_EVENTSOURCESTATE_STARTING || @@ -261,8 +278,8 @@ pollFDs(UA_EventLoop *el, UA_DateTime listenTimeout) { return UA_STATUSCODE_GOODCALLAGAIN; } - /* Loop over all registered FD to see if an event arrived. Yes, this is why - * poll is slow for many open sockets. */ + /* Loop over all registered FD to see if an event arrived. Yes, + * this is why poll is slow for many open sockets. */ int processed = 0; for(size_t i = 0; i < el->fdsSize; i++) { /* All done */ @@ -293,7 +310,8 @@ UA_StatusCode UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) { UA_LOCK(&el->elMutex); - UA_LOG_TRACE(el->logger, UA_LOGCATEGORY_EVENTLOOP, "Iterate the EventLoop"); + UA_LOG_TRACE(el->logger, UA_LOGCATEGORY_EVENTLOOP, + "Iterate the EventLoop"); if(el->executing) { UA_LOG_ERROR(el->logger, @@ -302,11 +320,6 @@ UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) { UA_UNLOCK(&el->elMutex); return UA_STATUSCODE_BADINTERNALERROR; } - /* TODO: use check macros instead - UA_CHECK_ERROR(!el->executing, return UA_STATUSCODE_BADINTERNALERROR, el->logger, - UA_LOGCATEGORY_EVENTLOOP, - "Cannot run eventloop from the run method itself"); - */ el->executing = true; @@ -320,22 +333,24 @@ UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) { } /* Process cyclic callbacks */ - UA_DateTime dateBeforeCallback = UA_DateTime_nowMonotonic(); + UA_DateTime dateBefore = UA_DateTime_nowMonotonic(); UA_UNLOCK(&el->elMutex); - UA_DateTime dateOfNextCallback = - UA_Timer_process(&el->timer, dateBeforeCallback, timerExecutionTrampoline, NULL); + UA_DateTime dateNext = + UA_Timer_process(&el->timer, dateBefore, + timerExecutionTrampoline, NULL); UA_LOCK(&el->elMutex); /* Compute the remaining time */ - UA_DateTime maxDate = dateBeforeCallback + (timeout * UA_DATETIME_MSEC); - if(dateOfNextCallback > maxDate) - dateOfNextCallback = maxDate; - UA_DateTime listenTimeout = dateOfNextCallback - UA_DateTime_nowMonotonic(); + UA_DateTime maxDate = dateBefore + (timeout * UA_DATETIME_MSEC); + if(dateNext > maxDate) + dateNext = maxDate; + UA_DateTime listenTimeout = dateNext - UA_DateTime_nowMonotonic(); if(listenTimeout < 0) listenTimeout = 0; - /* Listen on the active file-descriptors (sockets) from the ConnectionManagers */ + /* Listen on the active file-descriptors (sockets) from the + * ConnectionManagers */ UA_StatusCode rv = pollFDs(el, listenTimeout); /* Process and then free registered delayed callbacks */ @@ -360,7 +375,8 @@ UA_EventLoop_registerEventSource(UA_EventLoop *el, UA_EventSource *es) { /* Already registered? */ if(es->state != UA_EVENTSOURCESTATE_FRESH) { UA_LOG_ERROR(UA_EventLoop_getLogger(el), UA_LOGCATEGORY_NETWORK, - "Cannot register the EventSource \"%.*s\": already registered", + "Cannot register the EventSource \"%.*s\": " + "already registered", (int)es->name.length, (char*)es->name.data); return UA_STATUSCODE_BADINTERNALERROR; } @@ -384,7 +400,8 @@ UA_StatusCode UA_EventLoop_deregisterEventSource(UA_EventLoop *el, UA_EventSource *es) { if(es->state != UA_EVENTSOURCESTATE_STOPPED) { UA_LOG_WARNING(el->logger, UA_LOGCATEGORY_EVENTLOOP, - "Cannot deregister the EventSource %.*s. Has to be stopped first", + "Cannot deregister the EventSource %.*s: " + "Has to be stopped first", (int)es->name.length, es->name.data); return UA_STATUSCODE_BADINTERNALERROR; } @@ -426,7 +443,8 @@ UA_EventLoop_findEventSource(UA_EventLoop *el, const UA_String name) { UA_StatusCode UA_EventLoop_registerFD(UA_EventLoop *el, UA_FD fd, short eventMask, - UA_FDCallback cb, UA_EventSource *es, void *fdcontext) { + UA_FDCallback cb, UA_EventSource *es, + void *fdcontext) { UA_LOCK(&el->elMutex); UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, @@ -538,7 +556,8 @@ UA_EventLoop_deregisterFD(UA_EventLoop *el, UA_FD fd) { void UA_EventLoop_iterateFD(UA_EventLoop *el, UA_EventSource *es, - UA_EventLoopPOSIXIterateCB cb, void *iterateContext) { + UA_EventLoopPOSIXIterateCB cb, + void *iterateContext) { for(size_t i = 0; i < el->fdsSize; i++) { if(el->fds[i].es != es) continue; From 5082bf32e75306e9937c34bfef63fb474020b776 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 27 Dec 2021 20:35:41 +0100 Subject: [PATCH 0036/1963] refactor(el): The EventLoop contains methods pointers This allows several EventLoop implementations to be run concurrently. --- CMakeLists.txt | 2 +- arch/eventloop_posix.c | 272 +++++++++---------- arch/eventloop_posix.h | 67 ++++- arch/eventloop_posix_tcp.c | 130 ++++----- include/open62541/plugin/eventloop.h | 292 +++++++++++++-------- plugins/ua_config_default.c | 4 +- src/client/ua_client.c | 49 ++-- src/pubsub/ua_pubsub_manager.c | 28 +- src/server/ua_server.c | 47 ++-- src/server/ua_server_config.c | 15 +- src/server/ua_services_securechannel.c | 3 +- src/server/ua_services_session.c | 3 +- src/server/ua_subscription.c | 3 +- src/server/ua_subscription_monitoreditem.c | 3 +- tests/check_eventloop.c | 12 +- tests/check_eventloop_tcp.c | 66 ++--- 16 files changed, 550 insertions(+), 446 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 489a0dea104..e98cc2316b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,7 @@ GET_PROPERTY(ua_architecture_sources GLOBAL PROPERTY UA_ARCHITECTURE_SOURCES) set(ua_architecture_sources ${ua_architecture_sources} ${PROJECT_SOURCE_DIR}/arch/network_tcp.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix.h ${PROJECT_SOURCE_DIR}/arch/eventloop_posix.c ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_tcp.c ) @@ -127,7 +128,6 @@ set(ua_architecture_sources ${ua_architecture_sources} set(ua_architecture_headers ${ua_architecture_headers} ${PROJECT_SOURCE_DIR}/include/open62541/network_tcp.h ${PROJECT_SOURCE_DIR}/include/open62541/architecture_functions.h - ${PROJECT_SOURCE_DIR}/arch/eventloop_posix.h ) if(UA_ENABLE_WEBSOCKET_SERVER) diff --git a/arch/eventloop_posix.c b/arch/eventloop_posix.c index cbd0af982bd..d9cbe12fa92 100644 --- a/arch/eventloop_posix.c +++ b/arch/eventloop_posix.c @@ -7,44 +7,9 @@ */ #include "eventloop_posix.h" -#include "common/ua_timer.h" - -typedef struct { - /* The members fd and events are stored in the separate - * pollfds array: - * UA_FD fd; - * short events; */ - UA_EventSource *es; - UA_FDCallback callback; - void *fdcontext; -} UA_RegisteredFD; - -struct UA_EventLoop { - UA_EventLoopState state; - const UA_Logger *logger; - - /* Timer */ - UA_Timer timer; - - /* Linked List of Delayed Callbacks */ - UA_DelayedCallback *delayedCallbacks; - - /* Pointers to registered EventSources */ - UA_EventSource *eventSources; - - /* Registered file descriptors */ - size_t fdsSize; - UA_RegisteredFD *fds; - struct pollfd *pollfds; /* has the same size as "fds" */ - /* Flag determining whether the eventloop is currently within the - * "run" method */ - UA_Boolean executing; - -#if UA_MULTITHREADING >= 100 - UA_Lock elMutex; -#endif -}; +static UA_StatusCode +POSIX_EL_deregisterEventSource(POSIX_EL *el, UA_EventSource *es); /*********/ /* Timer */ @@ -58,47 +23,58 @@ timerExecutionTrampoline(void *executionApplication, cb(callbackApplication, data); } -UA_StatusCode -UA_EventLoop_addTimedCallback(UA_EventLoop *el, UA_Callback callback, - void *application, void *data, - UA_DateTime date, - UA_UInt64 *callbackId) { +static UA_DateTime +POSIX_EL_nextCyclicTime(UA_EventLoop *public_el) { + POSIX_EL *el = (POSIX_EL*)public_el; + return UA_Timer_nextRepeatedTime(&el->timer); +} + +static UA_StatusCode +POSIX_EL_addTimedCallback(UA_EventLoop *public_el, UA_Callback callback, + void *application, void *data, + UA_DateTime date, + UA_UInt64 *callbackId) { + POSIX_EL *el = (POSIX_EL*)public_el; return UA_Timer_addTimedCallback(&el->timer, callback, application, data, date, callbackId); } -UA_StatusCode -UA_EventLoop_addCyclicCallback(UA_EventLoop *el, UA_Callback cb, - void *application, void *data, - UA_Double interval_ms, - UA_DateTime *baseTime, - UA_TimerPolicy timerPolicy, - UA_UInt64 *callbackId) { +static UA_StatusCode +POSIX_EL_addCyclicCallback(UA_EventLoop *public_el, UA_Callback cb, + void *application, void *data, + UA_Double interval_ms, + UA_DateTime *baseTime, + UA_TimerPolicy timerPolicy, + UA_UInt64 *callbackId) { + POSIX_EL *el = (POSIX_EL*)public_el; return UA_Timer_addRepeatedCallback(&el->timer, cb, application, data, interval_ms, baseTime, timerPolicy, callbackId); } -UA_StatusCode -UA_EventLoop_modifyCyclicCallback(UA_EventLoop *el, - UA_UInt64 callbackId, - UA_Double interval_ms, - UA_DateTime *baseTime, - UA_TimerPolicy timerPolicy) { +static UA_StatusCode +POSIX_EL_modifyCyclicCallback(UA_EventLoop *public_el, + UA_UInt64 callbackId, + UA_Double interval_ms, + UA_DateTime *baseTime, + UA_TimerPolicy timerPolicy) { + POSIX_EL *el = (POSIX_EL*)public_el; return UA_Timer_changeRepeatedCallback(&el->timer, callbackId, interval_ms, baseTime, timerPolicy); } -void -UA_EventLoop_removeCyclicCallback(UA_EventLoop *el, - UA_UInt64 callbackId) { +static void +POSIX_EL_removeCyclicCallback(UA_EventLoop *public_el, + UA_UInt64 callbackId) { + POSIX_EL *el = (POSIX_EL*)public_el; UA_Timer_removeCallback(&el->timer, callbackId); } -void -UA_EventLoop_addDelayedCallback(UA_EventLoop *el, - UA_DelayedCallback *dc) { +static void +POSIX_EL_addDelayedCallback(UA_EventLoop *public_el, + UA_DelayedCallback *dc) { + POSIX_EL *el = (POSIX_EL*)public_el; UA_LOCK(&el->elMutex); dc->next = el->delayedCallbacks; el->delayedCallbacks = dc; @@ -107,7 +83,7 @@ UA_EventLoop_addDelayedCallback(UA_EventLoop *el, /* Process and then free registered delayed callbacks */ static void -processDelayed(UA_EventLoop *el) { +processDelayed(POSIX_EL *el) { UA_LOCK_ASSERT(&el->elMutex, 1); while(el->delayedCallbacks) { UA_DelayedCallback *dc = el->delayedCallbacks; @@ -127,27 +103,14 @@ processDelayed(UA_EventLoop *el) { /* EventLoop Lifecycle */ /***********************/ -UA_EventLoop * -UA_EventLoop_new(const UA_Logger *logger) { - UA_EventLoop *el = (UA_EventLoop*) - UA_malloc(sizeof(UA_EventLoop)); - if(!el) - return NULL; - memset(el, 0, sizeof(UA_EventLoop)); - UA_LOCK_INIT(&el->elMutex); - el->logger = logger; - UA_Timer_init(&el->timer); - return el; -} - -UA_StatusCode -UA_EventLoop_delete(UA_EventLoop *el) { +static UA_StatusCode +POSIX_EL_free(POSIX_EL *el) { UA_LOCK(&el->elMutex); /* Check if the EventLoop can be deleted */ - if(el->state != UA_EVENTLOOPSTATE_STOPPED && - el->state != UA_EVENTLOOPSTATE_FRESH) { - UA_LOG_WARNING(el->logger, UA_LOGCATEGORY_EVENTLOOP, + if(el->eventLoop.state != UA_EVENTLOOPSTATE_STOPPED && + el->eventLoop.state != UA_EVENTLOOPSTATE_FRESH) { + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "Cannot delete a running EventLoop"); UA_UNLOCK(&el->elMutex); return UA_STATUSCODE_BADINTERNALERROR; @@ -157,7 +120,7 @@ UA_EventLoop_delete(UA_EventLoop *el) { while(el->eventSources) { UA_EventSource *es = el->eventSources; UA_UNLOCK(&el->elMutex); - UA_EventLoop_deregisterEventSource(el, es); + POSIX_EL_deregisterEventSource(el, es); UA_LOCK(&el->elMutex); es->free(es); } @@ -179,26 +142,16 @@ UA_EventLoop_delete(UA_EventLoop *el) { return UA_STATUSCODE_GOOD; } -UA_EventLoopState -UA_EventLoop_getState(UA_EventLoop *el) { - return el->state; -} - -UA_DateTime -UA_EventLoop_nextCyclicTime(UA_EventLoop *el) { - return UA_Timer_nextRepeatedTime(&el->timer); -} - -UA_StatusCode -UA_EventLoop_start(UA_EventLoop *el) { +static UA_StatusCode +POSIX_EL_start(POSIX_EL *el) { UA_LOCK(&el->elMutex); - if(el->state != UA_EVENTLOOPSTATE_FRESH && - el->state != UA_EVENTLOOPSTATE_STOPPED) { + if(el->eventLoop.state != UA_EVENTLOOPSTATE_FRESH && + el->eventLoop.state != UA_EVENTLOOPSTATE_STOPPED) { UA_UNLOCK(&el->elMutex); return UA_STATUSCODE_BADINTERNALERROR; } - UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "Starting the EventLoop"); UA_EventSource *es = el->eventSources; @@ -210,13 +163,15 @@ UA_EventLoop_start(UA_EventLoop *el) { es = es->next; } - el->state = UA_EVENTLOOPSTATE_STARTED; + /* Dirty-write the state that is const "outside" */ + *(UA_EventLoopState*)(uintptr_t)&el->eventLoop.state = + UA_EVENTLOOPSTATE_STARTED; UA_UNLOCK(&el->elMutex); return res; } static void -checkClosed(UA_EventLoop *el) { +checkClosed(POSIX_EL *el) { UA_EventSource *es = el->eventSources; while(es) { if(es->state != UA_EVENTSOURCESTATE_STOPPED) @@ -224,16 +179,18 @@ checkClosed(UA_EventLoop *el) { es = es->next; } - UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "The EventLoop has stopped"); - el->state = UA_EVENTLOOPSTATE_STOPPED; + /* Dirty-write the state that is const "outside" */ + *(UA_EventLoopState*)(uintptr_t)&el->eventLoop.state = + UA_EVENTLOOPSTATE_STOPPED; } -void -UA_EventLoop_stop(UA_EventLoop *el) { +static void +POSIX_EL_stop(POSIX_EL *el) { UA_LOCK(&el->elMutex); - UA_LOG_INFO(el->logger, UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "Stopping the EventLoop"); /* Shutdown all event sources. This closes open connections. */ @@ -245,16 +202,17 @@ UA_EventLoop_stop(UA_EventLoop *el) { es = es->next; } - UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "All EventSources are stopped"); - el->state = UA_EVENTLOOPSTATE_STOPPING; + *(UA_EventLoopState*)(uintptr_t)&el->eventLoop.state = + UA_EVENTLOOPSTATE_STOPPING; checkClosed(el); UA_UNLOCK(&el->elMutex); } static UA_StatusCode -pollFDs(UA_EventLoop *el, UA_DateTime listenTimeout) { +pollFDs(POSIX_EL *el, UA_DateTime listenTimeout) { UA_assert(listenTimeout >= 0); /* Poll the registered sockets */ #ifdef _GNU_SOURCE @@ -272,9 +230,9 @@ pollFDs(UA_EventLoop *el, UA_DateTime listenTimeout) { if(pollStatus < 0) { /* We will retry, only log the error */ UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(UA_EventLoop_getLogger(el), - UA_LOGCATEGORY_EVENTLOOP, - "Error during poll: %s", errno_str)); + UA_LOG_WARNING(el->eventLoop.logger, + UA_LOGCATEGORY_EVENTLOOP, + "Error during poll: %s", errno_str)); return UA_STATUSCODE_GOODCALLAGAIN; } @@ -306,15 +264,15 @@ pollFDs(UA_EventLoop *el, UA_DateTime listenTimeout) { return UA_STATUSCODE_GOOD; } -UA_StatusCode -UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) { +static UA_StatusCode +POSIX_EL_run(POSIX_EL *el, UA_UInt32 timeout) { UA_LOCK(&el->elMutex); - UA_LOG_TRACE(el->logger, UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_TRACE(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "Iterate the EventLoop"); if(el->executing) { - UA_LOG_ERROR(el->logger, + UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "Cannot run EventLoop from the run method itself"); UA_UNLOCK(&el->elMutex); @@ -323,9 +281,9 @@ UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) { el->executing = true; - if(el->state == UA_EVENTLOOPSTATE_FRESH || - el->state == UA_EVENTLOOPSTATE_STOPPED) { - UA_LOG_WARNING(el->logger, UA_LOGCATEGORY_EVENTLOOP, + if(el->eventLoop.state == UA_EVENTLOOPSTATE_FRESH || + el->eventLoop.state == UA_EVENTLOOPSTATE_STOPPED) { + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "Cannot iterate a stopped EventLoop"); el->executing = false; UA_UNLOCK(&el->elMutex); @@ -357,7 +315,7 @@ UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) { processDelayed(el); /* Check if the last EventSource was successfully stopped */ - if(el->state == UA_EVENTLOOPSTATE_STOPPING) + if(el->eventLoop.state == UA_EVENTLOOPSTATE_STOPPING) checkClosed(el); el->executing = false; @@ -370,11 +328,11 @@ UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout) { /* Registering Event Sources */ /*****************************/ -UA_StatusCode -UA_EventLoop_registerEventSource(UA_EventLoop *el, UA_EventSource *es) { +static UA_StatusCode +POSIX_EL_registerEventSource(POSIX_EL *el, UA_EventSource *es) { /* Already registered? */ if(es->state != UA_EVENTSOURCESTATE_FRESH) { - UA_LOG_ERROR(UA_EventLoop_getLogger(el), UA_LOGCATEGORY_NETWORK, + UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "Cannot register the EventSource \"%.*s\": " "already registered", (int)es->name.length, (char*)es->name.data); @@ -387,19 +345,19 @@ UA_EventLoop_registerEventSource(UA_EventLoop *el, UA_EventSource *es) { el->eventSources = es; UA_UNLOCK(&el->elMutex); - es->eventLoop = el; + es->eventLoop = &el->eventLoop; es->state = UA_EVENTSOURCESTATE_STOPPED; /* Start if the entire EventLoop is started */ - if(el->state == UA_EVENTLOOPSTATE_STARTED) + if(el->eventLoop.state == UA_EVENTLOOPSTATE_STARTED) return es->start(es); return UA_STATUSCODE_GOOD; } -UA_StatusCode -UA_EventLoop_deregisterEventSource(UA_EventLoop *el, UA_EventSource *es) { +static UA_StatusCode +POSIX_EL_deregisterEventSource(POSIX_EL *el, UA_EventSource *es) { if(es->state != UA_EVENTSOURCESTATE_STOPPED) { - UA_LOG_WARNING(el->logger, UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "Cannot deregister the EventSource %.*s: " "Has to be stopped first", (int)es->name.length, es->name.data); @@ -424,8 +382,8 @@ UA_EventLoop_deregisterEventSource(UA_EventLoop *el, UA_EventSource *es) { return UA_STATUSCODE_GOOD; } -UA_EventSource * -UA_EventLoop_findEventSource(UA_EventLoop *el, const UA_String name) { +static UA_EventSource * +POSIX_EL_findEventSource(POSIX_EL *el, const UA_String name) { UA_LOCK(&el->elMutex); UA_EventSource *s = el->eventSources; while(s) { @@ -442,12 +400,12 @@ UA_EventLoop_findEventSource(UA_EventLoop *el, const UA_String name) { /********************************/ UA_StatusCode -UA_EventLoop_registerFD(UA_EventLoop *el, UA_FD fd, short eventMask, - UA_FDCallback cb, UA_EventSource *es, - void *fdcontext) { +POSIX_EL_registerFD(POSIX_EL *el, UA_FD fd, short eventMask, + UA_FDCallback cb, UA_EventSource *es, + void *fdcontext) { UA_LOCK(&el->elMutex); - UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "Registering fd: %u", (unsigned)fd); /* Realloc */ @@ -480,8 +438,8 @@ UA_EventLoop_registerFD(UA_EventLoop *el, UA_FD fd, short eventMask, } UA_StatusCode -UA_EventLoop_modifyFD(UA_EventLoop *el, UA_FD fd, short eventMask, - UA_FDCallback cb, void *fdcontext) { +POSIX_EL_modifyFD(POSIX_EL *el, UA_FD fd, short eventMask, + UA_FDCallback cb, void *fdcontext) { UA_LOCK(&el->elMutex); /* Find the entry */ @@ -507,10 +465,10 @@ UA_EventLoop_modifyFD(UA_EventLoop *el, UA_FD fd, short eventMask, } UA_StatusCode -UA_EventLoop_deregisterFD(UA_EventLoop *el, UA_FD fd) { +POSIX_EL_deregisterFD(POSIX_EL *el, UA_FD fd) { UA_LOCK(&el->elMutex); - UA_LOG_DEBUG(el->logger, UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "Unregistering fd: %u", (unsigned)fd); /* Find the entry */ @@ -555,9 +513,9 @@ UA_EventLoop_deregisterFD(UA_EventLoop *el, UA_FD fd) { } void -UA_EventLoop_iterateFD(UA_EventLoop *el, UA_EventSource *es, - UA_EventLoopPOSIXIterateCB cb, - void *iterateContext) { +POSIX_EL_iterateFD(POSIX_EL *el, UA_EventSource *es, + POSIX_EL_IterateCallback cb, + void *iterateContext) { for(size_t i = 0; i < el->fdsSize; i++) { if(el->fds[i].es != es) continue; @@ -573,14 +531,36 @@ UA_EventLoop_iterateFD(UA_EventLoop *el, UA_EventSource *es, } } -/* Helper Functions */ +UA_EventLoop * +UA_EventLoop_new_POSIX(const UA_Logger *logger) { + POSIX_EL *el = (POSIX_EL*)UA_malloc(sizeof(POSIX_EL)); + if(!el) + return NULL; + memset(el, 0, sizeof(POSIX_EL)); + UA_LOCK_INIT(&el->elMutex); + UA_Timer_init(&el->timer); -const UA_Logger * -UA_EventLoop_getLogger(UA_EventLoop *el) { - return el->logger; -} + /* Set the public EventLoop content */ + el->eventLoop.logger = logger; -void -UA_EventLoop_setLogger(UA_EventLoop *el, const UA_Logger *logger) { - el->logger = logger; + el->eventLoop.start = (UA_StatusCode (*)(UA_EventLoop*))POSIX_EL_start; + el->eventLoop.stop = (void (*)(UA_EventLoop*))POSIX_EL_stop; + el->eventLoop.run = (UA_StatusCode (*)(UA_EventLoop*, UA_UInt32))POSIX_EL_run; + el->eventLoop.free = (UA_StatusCode (*)(UA_EventLoop*))POSIX_EL_free; + + el->eventLoop.nextCyclicTime = POSIX_EL_nextCyclicTime; + el->eventLoop.addCyclicCallback = POSIX_EL_addCyclicCallback; + el->eventLoop.modifyCyclicCallback = POSIX_EL_modifyCyclicCallback; + el->eventLoop.removeCyclicCallback = POSIX_EL_removeCyclicCallback; + el->eventLoop.addTimedCallback = POSIX_EL_addTimedCallback; + el->eventLoop.addDelayedCallback = POSIX_EL_addDelayedCallback; + + el->eventLoop.registerEventSource = + (UA_StatusCode (*)(UA_EventLoop*, UA_EventSource*))POSIX_EL_registerEventSource; + el->eventLoop.deregisterEventSource = + (UA_StatusCode (*)(UA_EventLoop*, UA_EventSource*))POSIX_EL_deregisterEventSource; + el->eventLoop.findEventSource = + (UA_EventSource* (*)(UA_EventLoop*, const UA_String))POSIX_EL_findEventSource; + + return &el->eventLoop; } diff --git a/arch/eventloop_posix.h b/arch/eventloop_posix.h index 10371672613..17e6de10f57 100644 --- a/arch/eventloop_posix.h +++ b/arch/eventloop_posix.h @@ -12,10 +12,9 @@ #include #include -/* TODO: Move the macro-forest from /arch//ua_architecture.h */ +#if defined(UA_ARCHITECTURE_POSIX) || defined(UA_ARCHITECTURE_WIN32) -#define UA_FD UA_SOCKET -#define UA_INVALID_FD UA_INVALID_SOCKET +#include "common/ua_timer.h" _UA_BEGIN_DECLS @@ -23,36 +22,80 @@ _UA_BEGIN_DECLS * register their fd in the EventLoop so that they are considered by the * EventLoop dropping into "poll" to wait for events. */ +/* TODO: Move the macro-forest from /arch//ua_architecture.h */ + +#define UA_FD UA_SOCKET +#define UA_INVALID_FD UA_INVALID_SOCKET + typedef void (*UA_FDCallback)(UA_EventSource *es, UA_FD fd, void **fdcontext, short event); +typedef struct { + /* The members fd and events are stored in the separate + * pollfds array: + * - UA_FD fd; + * - short events; */ + UA_EventSource *es; + UA_FDCallback callback; + void *fdcontext; +} UA_RegisteredFD; + +typedef struct { + UA_EventLoop eventLoop; + + /* Timer */ + UA_Timer timer; + + /* Linked List of Delayed Callbacks */ + UA_DelayedCallback *delayedCallbacks; + + /* Pointers to registered EventSources */ + UA_EventSource *eventSources; + + /* Registered file descriptors */ + size_t fdsSize; + UA_RegisteredFD *fds; + struct pollfd *pollfds; /* has the same size as "fds" */ + + /* Flag determining whether the eventloop is currently within the + * "run" method */ + UA_Boolean executing; + +#if UA_MULTITHREADING >= 100 + UA_Lock elMutex; +#endif +} POSIX_EL; + UA_StatusCode -UA_EventLoop_registerFD(UA_EventLoop *el, UA_FD fd, short eventMask, - UA_FDCallback cb, UA_EventSource *es, void *fdcontext); +POSIX_EL_registerFD(POSIX_EL *el, UA_FD fd, short eventMask, + UA_FDCallback cb, UA_EventSource *es, void *fdcontext); /* Change the fd settings (event mask, callback) in-place. Fails only if the fd * no longer exists. */ UA_StatusCode -UA_EventLoop_modifyFD(UA_EventLoop *el, UA_FD fd, short eventMask, - UA_FDCallback cb, void *fdcontext); +POSIX_EL_modifyFD(POSIX_EL *el, UA_FD fd, short eventMask, + UA_FDCallback cb, void *fdcontext); /* During processing of an fd-event, the fd may deregister itself. But in the * fd-callback they must not deregister another fd. */ UA_StatusCode -UA_EventLoop_deregisterFD(UA_EventLoop *el, UA_FD fd); +POSIX_EL_deregisterFD(POSIX_EL *el, UA_FD fd); /* abort the iteration if the returned boolean is true */ typedef UA_Boolean -(*UA_EventLoopPOSIXIterateCB)(UA_EventSource *es, UA_FD fd, - void *fdContext, void *iterateContext); +(*POSIX_EL_IterateCallback)(UA_EventSource *es, UA_FD fd, + void *fdContext, void *iterateContext); /* Call the callback for all fd that are registered from that event source. The * callback is called with the 'event' argument set to zero to disambiguate from * a callback after 'poll'. */ void -UA_EventLoop_iterateFD(UA_EventLoop *el, UA_EventSource *es, - UA_EventLoopPOSIXIterateCB cb, void *iterateContext); +POSIX_EL_iterateFD(POSIX_EL *el, UA_EventSource *es, + POSIX_EL_IterateCallback callback, + void *iterateContext); _UA_END_DECLS +#endif /* defined(UA_ARCHITECTURE_POSIX) || defined(UA_ARCHITECTURE_WIN32) */ + #endif /* UA_EVENTLOOP_POSIX_H_ */ diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index 7121ae7e0e4..503b433f9fd 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -76,7 +76,7 @@ TCP_setNoNagle(UA_FD sockfd) { static UA_StatusCode TCP_close(UA_ConnectionManager *cm, UA_FD fd) { - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Closing connection", (unsigned)fd); @@ -86,7 +86,8 @@ TCP_close(UA_ConnectionManager *cm, UA_FD fd) { int ret = UA_close(fd); if(ret != 0) return UA_STATUSCODE_BADINTERNALERROR; - UA_StatusCode sc = UA_EventLoop_deregisterFD(tcm->cm.eventSource.eventLoop, fd); + UA_StatusCode sc = + POSIX_EL_deregisterFD((POSIX_EL*)tcm->cm.eventSource.eventLoop, fd); if(sc != UA_STATUSCODE_GOOD) return sc; @@ -94,12 +95,12 @@ TCP_close(UA_ConnectionManager *cm, UA_FD fd) { UA_assert(tcm->fdCount > 0); tcm->fdCount--; - UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_INFO(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Socket closed", (unsigned)fd); /* Stopped? */ if(tcm->fdCount == 0 && cm->eventSource.state == UA_EVENTSOURCESTATE_STOPPING) { - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| All sockets closed, the EventLoop has stopped"); cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPED; @@ -111,14 +112,14 @@ TCP_close(UA_ConnectionManager *cm, UA_FD fd) { static void TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, void **fdcontext, short event) { - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Activity on the socket", (unsigned)fd); /* Write-Event, a new connection has opened. */ UA_StatusCode res = UA_STATUSCODE_GOOD; if(event == UA_POLLOUT) { - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Opening a new connection", (unsigned)fd); @@ -127,13 +128,12 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, UA_STATUSCODE_GOOD, 0, NULL, UA_BYTESTRING_NULL); /* Now we are interested in read-events. */ - UA_EventLoop_modifyFD(cm->eventSource.eventLoop, fd, UA_POLLIN, - (UA_FDCallback)TCP_connectionSocketCallback, - *fdcontext); + POSIX_EL_modifyFD((POSIX_EL*)cm->eventSource.eventLoop, fd, UA_POLLIN, + (UA_FDCallback)TCP_connectionSocketCallback, *fdcontext); return; } - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Allocate receive buffer", (unsigned)fd); @@ -147,18 +147,18 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, /* Receive */ #ifndef _WIN32 ssize_t ret = UA_recv(fd, (char*)response.data, response.length, MSG_DONTWAIT); - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| recv(...) returned %zd", (unsigned)fd, ret); #else int ret = UA_recv(fd, (char*)response.data, response.length, MSG_DONTWAIT); - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| recv(...) returned %d", (unsigned)fd, ret); #endif if(ret > 0) { - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Received message of size %u", (unsigned)fd, (unsigned)ret); @@ -174,7 +174,7 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, * then close the connection. We end up in this path after shutdown was * called on the socket. Here, we then are in the next EventLoop * iteration and the socket is known to be unused. */ - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| recv signaled closed connection", (unsigned)fd); @@ -192,7 +192,7 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, static void TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, void **fdcontext, short event) { - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Callback on server socket", (unsigned)fd); @@ -208,7 +208,7 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, /* Close the listen socket */ if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPING) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Error %s, closing the server socket", (unsigned)fd, errno_str)); @@ -228,13 +228,13 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, if(get_res != 0) { hoststr[0] = 0; UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| getnameinfo(...) could not resolve the " "hostname (%s)", (unsigned)fd, errno_str)); } } - UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_INFO(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Connection opened from \"%s\" via the server socket %u", (unsigned)newsockfd, hoststr, (unsigned)fd); @@ -247,7 +247,7 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, res |= TCP_setNoNagle(newsockfd); /* Disable Nagle's algorithm */ if(res != UA_STATUSCODE_GOOD) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Error seeting the TCP options (%s), closing", (unsigned)newsockfd, errno_str)); @@ -263,9 +263,9 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, 0, NULL, UA_BYTESTRING_NULL); /* Register in the EventLoop. Signal to the user if registering failed. */ - res = UA_EventLoop_registerFD(cm->eventSource.eventLoop, newsockfd, UA_POLLIN, - (UA_FDCallback)TCP_connectionSocketCallback, - &cm->eventSource, ctx); + res = POSIX_EL_registerFD((POSIX_EL*)cm->eventSource.eventLoop, newsockfd, + UA_POLLIN, (UA_FDCallback)TCP_connectionSocketCallback, + &cm->eventSource, ctx); if(res != UA_STATUSCODE_GOOD) { cm->connectionCallback(cm, (uintptr_t)newsockfd, &ctx, UA_STATUSCODE_BADINTERNALERROR, @@ -295,7 +295,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { hoststr[0] = 0; portstr[0] = 0; UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| getnameinfo(...) could not resolve the hostname (%s)", errno_str)); @@ -306,7 +306,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { UA_FD listenSocket = UA_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if(listenSocket == UA_INVALID_FD) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Error opening the listen socket for " "\"%s\" on port %s(%s)", @@ -314,7 +314,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { return; } - UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_INFO(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| New server socket for \"%s\" on port %s", (unsigned)listenSocket, hoststr, portstr); @@ -327,7 +327,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { if(ai->ai_family == AF_INET6 && UA_setsockopt(listenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&optval, sizeof(optval)) == -1) { - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Could not set an IPv6 socket to IPv6 only, closing", (unsigned)listenSocket); @@ -339,7 +339,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { /* Allow rebinding to the IP/port combination. Eg. to restart the server. */ if(UA_setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof(optval)) == -1) { - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Could not make the socket reusable, closing", (unsigned)listenSocket); @@ -349,7 +349,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { /* Set the socket non-blocking */ if(TCP_setNonBlocking(listenSocket) != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Could not set the socket non-blocking, closing", (unsigned)listenSocket); @@ -359,7 +359,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { /* Supress interrupts from the socket */ if(TCP_setNoSigPipe(listenSocket) != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Could not disable SIGPIPE, closing", (unsigned)listenSocket); @@ -371,7 +371,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { int ret = UA_bind(listenSocket, ai->ai_addr, (socklen_t)ai->ai_addrlen); if(ret < 0) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Error binding the socket to the address (%s), closing", (unsigned)listenSocket, errno_str)); @@ -382,7 +382,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { /* Start listening */ if(UA_listen(listenSocket, UA_MAXBACKLOG) < 0) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Error listening on the socket (%s), closing", (unsigned)listenSocket, errno_str)); @@ -392,11 +392,11 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { /* Register the socket */ UA_StatusCode res = - UA_EventLoop_registerFD(cm->eventSource.eventLoop, listenSocket, UA_POLLIN, - (UA_FDCallback)TCP_listenSocketCallback, - &cm->eventSource, NULL); + POSIX_EL_registerFD((POSIX_EL*)cm->eventSource.eventLoop, listenSocket, + UA_POLLIN, (UA_FDCallback)TCP_listenSocketCallback, + &cm->eventSource, NULL); if(res != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Error registering the socket in the " "EventLoop, closing", (unsigned)listenSocket); @@ -431,7 +431,7 @@ TCP_registerListenSocketDomainName(UA_ConnectionManager *cm, const char *hostnam int retcode = UA_getaddrinfo(hostname, port, &hints, &res); if(retcode != 0) { UA_LOG_SOCKET_ERRNO_GAI_WRAP( - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| getaddrinfo lookup for \"%s\" on port %s failed (%s)", hostname, port, errno_str)); @@ -450,7 +450,7 @@ TCP_registerListenSocketDomainName(UA_ConnectionManager *cm, const char *hostnam static UA_StatusCode TCP_shutdownConnection(UA_ConnectionManager *cm, uintptr_t connectionId) { - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Shutdown called", (unsigned)connectionId); @@ -463,7 +463,7 @@ TCP_shutdownConnection(UA_ConnectionManager *cm, uintptr_t connectionId) { UA_StatusCode retval = UA_STATUSCODE_GOOD; if(res != 0) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Error shutting down the socket (%s), closing", (unsigned)connectionId, errno_str)); @@ -488,7 +488,7 @@ TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, do { ssize_t n = 0; do { - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Attempting to send", (unsigned)connectionId); size_t bytes_to_send = buf->length - nWritten; @@ -501,7 +501,7 @@ TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, UA_ERRNO != UA_WOULDBLOCK && UA_ERRNO != UA_AGAIN) { UA_LOG_SOCKET_ERRNO_GAI_WRAP( - UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Send failed with error %s", (unsigned)connectionId, errno_str)); @@ -540,7 +540,7 @@ TCP_openConnection(UA_ConnectionManager *cm, UA_QUALIFIEDNAME(0, "target-port"), &UA_TYPES[UA_TYPES_UINT16]); if(!port) { - UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, "TCP\t| Open TCP Connection: No target port defined, aborting"); return UA_STATUSCODE_BADINTERNALERROR; @@ -553,13 +553,13 @@ TCP_openConnection(UA_ConnectionManager *cm, UA_QUALIFIEDNAME(0, "target-hostname"), &UA_TYPES[UA_TYPES_STRING]); if(!host) { - UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, "TCP\t| Open TCP Connection: No target hostname defined, aborting"); return UA_STATUSCODE_BADINTERNALERROR; } if(host->length >= 256) { - UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, "TCP\t| Open TCP Connection: No target hostname too long, aborting"); return UA_STATUSCODE_BADINTERNALERROR; @@ -567,7 +567,7 @@ TCP_openConnection(UA_ConnectionManager *cm, strncpy(hostname, (const char*)host->data, host->length); hostname[host->length] = 0; - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Open a connection to \"%s\" on port %s", hostname, portStr); @@ -580,7 +580,7 @@ TCP_openConnection(UA_ConnectionManager *cm, int error = getaddrinfo(hostname, portStr, &hints, &info); if(error != 0) { UA_LOG_SOCKET_ERRNO_GAI_WRAP( - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Lookup of %s failed with error %d - %s", hostname, error, errno_str)); @@ -592,7 +592,7 @@ TCP_openConnection(UA_ConnectionManager *cm, if(newSock == UA_INVALID_FD) { freeaddrinfo(info); UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Could not create socket to connect to %s (%s)", hostname, errno_str)); @@ -606,7 +606,7 @@ TCP_openConnection(UA_ConnectionManager *cm, res |= TCP_setNoNagle(newSock); if(res != UA_STATUSCODE_GOOD) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Could not set socket options: %s", errno_str)); freeaddrinfo(info); @@ -621,7 +621,7 @@ TCP_openConnection(UA_ConnectionManager *cm, UA_ERRNO != UA_INPROGRESS && UA_ERRNO != UA_WOULDBLOCK) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Connecting the socket to %s failed (%s)", hostname, errno_str)); @@ -629,11 +629,11 @@ TCP_openConnection(UA_ConnectionManager *cm, } /* Register the fd to trigger when output is possible (the connection is open) */ - res = UA_EventLoop_registerFD(cm->eventSource.eventLoop, newSock, UA_POLLOUT, - (UA_FDCallback)TCP_connectionSocketCallback, - &cm->eventSource, context); + res = POSIX_EL_registerFD((POSIX_EL*)cm->eventSource.eventLoop, newSock, + UA_POLLOUT, (UA_FDCallback)TCP_connectionSocketCallback, + &cm->eventSource, context); if(res != UA_STATUSCODE_GOOD) { - UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Registering the socket to connect to %s failed", hostname); UA_close(newSock); @@ -644,7 +644,7 @@ TCP_openConnection(UA_ConnectionManager *cm, TCPConnectionManager *tcm = (TCPConnectionManager*)cm; tcm->fdCount++; - UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_INFO(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| New connection to \"%s\" on port %s", (unsigned)newSock, hostname, portStr); @@ -657,7 +657,7 @@ static UA_StatusCode TCP_eventSourceStart(UA_ConnectionManager *cm) { /* Check the state */ if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPED) { - UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, "To start the TCP ConnectionManager, " "it has to be registered in an EventLoop and not started"); return UA_STATUSCODE_BADINTERNALERROR; @@ -676,7 +676,7 @@ TCP_eventSourceStart(UA_ConnectionManager *cm) { UA_QUALIFIEDNAME(0, "listen-port"), &UA_TYPES[UA_TYPES_UINT16]); if(!port) { - UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, "TCP\t| No port configured, don't accept connections"); return UA_STATUSCODE_GOOD; @@ -693,12 +693,12 @@ TCP_eventSourceStart(UA_ConnectionManager *cm) { UA_QUALIFIEDNAME(0, "listen-hostnames")); if(!hostNames) { /* No hostnames configured */ - UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_INFO(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Listening on all interfaces"); TCP_registerListenSocketDomainName(cm, NULL, portno); } else if(hostNames->type != &UA_TYPES[UA_TYPES_STRING]) { /* Wrong datatype */ - UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, "TCP\t| The hostnames have to be strings"); return UA_STATUSCODE_BADINTERNALERROR; @@ -707,7 +707,7 @@ TCP_eventSourceStart(UA_ConnectionManager *cm) { if(UA_Variant_isScalar(hostNames)) interfaces = 1; if(interfaces == 0) { - UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, "TCP\t| Listening on all interfaces"); TCP_registerListenSocketDomainName(cm, NULL, portno); } else { @@ -747,13 +747,13 @@ TCP_shutdownCallback(UA_EventSource *es, UA_FD fd, static void TCP_eventSourceStop(UA_ConnectionManager *cm) { - UA_LOG_INFO(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_INFO(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Shutting down the ConnectionManager"); /* Shut down all registered fd. The cm is set to "stopped" when the last fd * is closed and deregistered in the callback from the EventLoop. */ - UA_EventLoop_iterateFD(cm->eventSource.eventLoop, &cm->eventSource, - TCP_shutdownCallback, NULL); + POSIX_EL_iterateFD((POSIX_EL*)cm->eventSource.eventLoop, + &cm->eventSource, TCP_shutdownCallback, NULL); cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPING; TCPConnectionManager *tcm = (TCPConnectionManager*)cm; @@ -762,14 +762,14 @@ TCP_eventSourceStop(UA_ConnectionManager *cm) { if(tcm->fdCount == 0 && cm->eventSource.state == UA_EVENTSOURCESTATE_STOPPING) cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPED; - UA_LOG_DEBUG(UA_EventLoop_getLogger(cm->eventSource.eventLoop), - UA_LOGCATEGORY_NETWORK, "TCP\t| EventSource successfully stopped"); + UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, + UA_LOGCATEGORY_NETWORK, "TCP\t| EventSource successfully stopped"); } static UA_StatusCode TCP_eventSourceDelete(UA_ConnectionManager *cm) { if(cm->eventSource.state >= UA_EVENTSOURCESTATE_STARTING) { - UA_LOG_ERROR(UA_EventLoop_getLogger(cm->eventSource.eventLoop), + UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, "TCP\t| The EventSource must be stopped before it can be deleted"); return UA_STATUSCODE_BADINTERNALERROR; @@ -790,7 +790,7 @@ TCP_eventSourceDelete(UA_ConnectionManager *cm) { } UA_ConnectionManager * -UA_ConnectionManager_TCP_new(const UA_String eventSourceName) { +UA_ConnectionManager_new_POSIX_TCP(const UA_String eventSourceName) { TCPConnectionManager *cm = (TCPConnectionManager*) UA_calloc(1, sizeof(TCPConnectionManager)); if(!cm) diff --git a/include/open62541/plugin/eventloop.h b/include/open62541/plugin/eventloop.h index 597a7b63254..4f5a5642bb2 100644 --- a/include/open62541/plugin/eventloop.h +++ b/include/open62541/plugin/eventloop.h @@ -22,6 +22,12 @@ typedef struct UA_EventLoop UA_EventLoop; struct UA_EventSource; typedef struct UA_EventSource UA_EventSource; +struct UA_ConnectionManager; +typedef struct UA_ConnectionManager UA_ConnectionManager; + +struct UA_InterruptManager; +typedef struct UA_InterruptManager UA_InterruptManager; + /** * Event Loop Subsystem * ==================== @@ -63,80 +69,90 @@ typedef enum { UA_EVENTLOOPSTATE_STOPPED } UA_EventLoopState; -/** - * EventLoop Lifecycle - * ~~~~~~~~~~~~~~~~~~~ */ +struct UA_EventLoop { + /* Configuration + * ~~~~~~~~~~~~~~~ + * The configuration should be set before the EventLoop is started */ + const UA_Logger *logger; + size_t paramsSize; + UA_KeyValuePair *params; /* See the implementation-specific documentation */ + + /* EventLoop Lifecycle + * ~~~~~~~~~~~~~~~~~~~~ */ + const volatile UA_EventLoopState state; /* Only read the state from outside */ + + /* Start the EventLoop and start all already registered EventSources */ + UA_StatusCode (*start)(UA_EventLoop *el); + + /* Stop all EventSources. This is asynchronous and might need a few + * iterations of the main-loop to succeed. */ + void (*stop)(UA_EventLoop *el); + + /* Process events for at most "timeout" ms or until an unrecoverable error + * occurs. If timeout==0, then only already received events are + * processed. */ + UA_StatusCode (*run)(UA_EventLoop *el, UA_UInt32 timeout); + + /* Clean up the EventLoop and free allocated memory. Can fail if the + * EventLoop is not stopped. */ + UA_StatusCode (*free)(UA_EventLoop *el); + + /* Cyclic and Delayed Callbacks + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Cyclic callbacks are executed regularly with an interval. A delayed + * callback is executed in the next cycle of the EventLoop. The memory for + * the delayed callback is freed after the execution. */ + + /* Time of the next cyclic callback. Returns the max DateTime if no cyclic + * callback is registered. */ + UA_DateTime (*nextCyclicTime)(UA_EventLoop *el); + + /* The execution interval is in ms. Returns the callbackId if the pointer is + * non-NULL. */ + UA_StatusCode + (*addCyclicCallback)(UA_EventLoop *el, UA_Callback cb, void *application, + void *data, UA_Double interval_ms, UA_DateTime *baseTime, + UA_TimerPolicy timerPolicy, UA_UInt64 *callbackId); -UA_EXPORT UA_EventLoop * -UA_EventLoop_new(const UA_Logger *logger); + UA_StatusCode + (*modifyCyclicCallback)(UA_EventLoop *el, UA_UInt64 callbackId, + UA_Double interval_ms, UA_DateTime *baseTime, + UA_TimerPolicy timerPolicy); -/* Clean up the EventLoop and free allocated memory. Can fail if the EventLoop - * is not stopped. */ -UA_EXPORT UA_StatusCode -UA_EventLoop_delete(UA_EventLoop *el); + void (*removeCyclicCallback)(UA_EventLoop *el, UA_UInt64 callbackId); -UA_EXPORT UA_EventLoopState -UA_EventLoop_getState(UA_EventLoop *el); + /* Like a cyclic callback, but executed only once */ + UA_StatusCode + (*addTimedCallback)(UA_EventLoop *el, UA_Callback cb, void *application, + void *data, UA_DateTime date, UA_UInt64 *callbackId); -UA_EXPORT UA_StatusCode -UA_EventLoop_start(UA_EventLoop *el); + void (*addDelayedCallback)(UA_EventLoop *el, UA_DelayedCallback *dc); -/* Stop all EventSources. This is asynchronous and might need a few - * iterations of the main-loop to succeed. */ -UA_EXPORT void -UA_EventLoop_stop(UA_EventLoop *el); + /* Manage EventSources + * ~~~~~~~~~~~~~~~~~~~ */ -/* Process events for at most "timeout" ms or until an unrecoverable error - * occurs. If timeout==0, then only already received events are processed. */ -UA_EXPORT UA_StatusCode -UA_EventLoop_run(UA_EventLoop *el, UA_UInt32 timeout); + /* Register the ES. Immediately starts the ES if the EventLoop is already + * started. Otherwise the ES is started together with the EventLoop. */ + UA_StatusCode + (*registerEventSource)(UA_EventLoop *el, UA_EventSource *es); -/* Time of the next cyclic callback. Returns the max DateTime if no cyclic - * callback is registered. */ -UA_EXPORT UA_DateTime -UA_EventLoop_nextCyclicTime(UA_EventLoop *el); + /* Stops the EventSource before deregistrering it */ + UA_StatusCode + (*deregisterEventSource)(UA_EventLoop *el, UA_EventSource *es); -/** - * Cyclic and Delayed Callbacks - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * Cyclic callbacks are executed regularly with an interval. A delayed callback - * is executed in the next cycle of the EventLoop. The memory for the delayed - * callback is freed after the execution. */ - -/* The execution interval is in ms. Returns the callbackId if the pointer is - * non-NULL. */ -UA_EXPORT UA_StatusCode -UA_EventLoop_addCyclicCallback(UA_EventLoop *el, UA_Callback cb, - void *application, void *data, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy, - UA_UInt64 *callbackId); - -UA_EXPORT UA_StatusCode -UA_EventLoop_addTimedCallback(UA_EventLoop *el, UA_Callback callback, - void *application, void *data, UA_DateTime date, - UA_UInt64 *callbackId); -UA_EXPORT UA_StatusCode -UA_EventLoop_modifyCyclicCallback(UA_EventLoop *el, UA_UInt64 callbackId, - UA_Double interval_ms, UA_DateTime *baseTime, - UA_TimerPolicy timerPolicy); - -UA_EXPORT void -UA_EventLoop_removeCyclicCallback(UA_EventLoop *el, UA_UInt64 callbackId); - -UA_EXPORT void -UA_EventLoop_addDelayedCallback(UA_EventLoop *el, UA_DelayedCallback *dc); - -/* Helper Functions */ -UA_EXPORT const UA_Logger * -UA_EventLoop_getLogger(UA_EventLoop *el); - -UA_EXPORT void -UA_EventLoop_setLogger(UA_EventLoop *el, const UA_Logger *logger); + /* Look up the EventSource by name. Returns the first EventSource of that + * name (duplicates should be avoided). */ + UA_EventSource * + (*findEventSource)(UA_EventLoop *el, const UA_String name); +}; /** * Event Source - * ------------ */ - + * ------------ + * Event Sources are attached to an EventLoop. Typically the event source and + * the EventLoop are developed together and share a private API in the + * background. */ + typedef enum { UA_EVENTSOURCESTATE_FRESH = 0, UA_EVENTSOURCESTATE_STOPPED, /* Registered but stopped */ @@ -176,22 +192,6 @@ struct UA_EventSource { UA_StatusCode (*free)(UA_EventSource *es); }; -/* Register the ES. Immediately starts the ES if the EventLoop is already - * started. Otherwise the ES is started together with the EventLoop. */ -UA_EXPORT UA_StatusCode -UA_EventLoop_registerEventSource(UA_EventLoop *el, - UA_EventSource *es); - -/* If still registered, call _stop (but not _clear) on the CM and deregister. */ -UA_EXPORT UA_StatusCode -UA_EventLoop_deregisterEventSource(UA_EventLoop *el, - UA_EventSource *es); - -/* Look up the EventSource by name. Returns the first EventSource of that name - * (duplicates should be avoided). */ -UA_EXPORT UA_EventSource * -UA_EventLoop_findEventSource(UA_EventLoop *el, const UA_String name); - /** * Connection Manager * ------------------ @@ -201,51 +201,47 @@ UA_EventLoop_findEventSource(UA_EventLoop *el, const UA_String name); * it can keep a session to an MQTT broker open which is used by individual * connections that are each bound to an MQTT topic. */ -struct UA_ConnectionManager; -typedef struct UA_ConnectionManager UA_ConnectionManager; - -/** - * The ConnectionCallback is the only interface from the connection back to the - * application. - * - * - The connectionId is initially unknown to the target application and - * "announced" to the application when first used first in this callback. - * - * - The context is attached to the connection. Initially a default context is set. - * The context can be replaced within the callback (via the double-pointer). - * - * - The status indicates whether the connection is closing down. If status != - * GOOD, then the application should clean up the context, as this is the last - * time the callback will be called for this connection. - * - * - The parameters are a key-value list with additional information. The - * possible keys and their meaning are documented for the individual - * ConnectionManager implementations. - * - * - The msg ByteString is the message (or packet) received on the - * connection. Can be empty. */ -typedef void -(*UA_ConnectionCallback)(UA_ConnectionManager *cm, uintptr_t connectionId, - void **connectionContext, UA_StatusCode status, - size_t paramsSize, const UA_KeyValuePair *params, - UA_ByteString msg); - struct UA_ConnectionManager { /* Every ConnectionManager is treated like an EventSource from the * perspective of the EventLoop. */ UA_EventSource eventSource; + /* The ConnectionCallback is the only interface from the connection back to + * the application. + * + * - The connectionId is initially unknown to the target application and + * "announced" to the application when first used first in this callback. + * + * - The context is attached to the connection. Initially a default context + * is set. The context can be replaced within the callback (via the + * double-pointer). + * + * - The status indicates whether the connection is closing down. If status + * != GOOD, then the application should clean up the context, as this is + * the last time the callback will be called for this connection. + * + * - The parameters are a key-value list with additional information. The + * possible keys and their meaning are documented for the individual + * ConnectionManager implementations. + * + * - The msg ByteString is the message (or packet) received on the + * connection. Can be empty. */ + void + (*connectionCallback)(UA_ConnectionManager *cm, uintptr_t connectionId, + void **connectionContext, UA_StatusCode status, + size_t paramsSize, const UA_KeyValuePair *params, + UA_ByteString msg); + /* Passively listen for new connections * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Some ConnectionManagers passively listen to open new Connections. The * configuration parameters stored in the EventSource are used during - * "start" of the EventSource to set this up. The "connectionCallback" + * 'start' of the EventSource to set this up. The 'connectionCallback' * callback is used to indicate that a new connection has been are created * (status==Good, msg=empty). * - * The callback depends on the application and has to be manually - * configured. */ - UA_ConnectionCallback connectionCallback; + * The context an internally created new connection is initialized with. + * Before calling the 'connectionCallback' for it the first time. */ void *initialConnectionContext; /* Actively Open a Connection @@ -306,11 +302,75 @@ struct UA_ConnectionManager { (*closeConnection)(UA_ConnectionManager *cm, uintptr_t connectionId); }; +/** + * Interrupt Manager + * ----------------- + * The Interrupt Manager allows to register to listen for system interrupts. + * Triggering the interrupt calls the callback associated with it. + * + * The implementations of the interrupt manager for the different platforms + * shall be designed such that: + * + * Registered interrupts are only intercepted from within the running EventLoop + * + * Processing an interrupt in the EventLoop is handled similarly to handling a + * network event: all methods and also memory allocation are available from + * within the interrupt callback. */ + +/* Interrupts can have additional key-value 'instanceInfos' for each individual + * triggering. See the architecture-specific documentation. */ +typedef void +(*UA_InterruptCallback)(UA_InterruptManager *im, + uintptr_t interruptHandle, void *interruptContext, + size_t instanceInfosSize, + const UA_KeyValuePair *instanceInfos); + +struct UA_InterruptManager { + /* Every InterruptManager is treated like an EventSource from the + * perspective of the EventLoop. */ + UA_EventSource eventSource; + + /* Register an interrupt. The handle and context information is passed + * through to the callback. + * + * The interruptHandle is a numerical identifier of the interrupt. In some + * cases, such as POSIX signals, this is enough information to register + * callback. For other interrupt systems (architectures) additional + * parameters may be required and can be passed in via the parameters + * key-value list. See the implementation-specific documentation. + * + * The interruptContext is opaque user-defined information and passed + * through to the callback without modification. */ + UA_StatusCode + (*registerInterrupt)(UA_InterruptManager *im, uintptr_t interruptHandle, + size_t paramsSize, const UA_KeyValuePair *params, + UA_InterruptCallback callback, void *interruptContext); + + /* Remove a registered interrupt. Returns no error code if the interrupt is + * already deregistered. */ + void + (*deregisterInterrupt)(UA_InterruptManager *im, uintptr_t interruptHandle); +}; + +/** + * POSIX-Specific Implementation + * ----------------------------- + * The POSIX compatibility of WIN32 is 'close enough'. So a joint implementation + * is provided. */ + +#if defined(UA_ARCHITECTURE_POSIX) || defined(UA_ARCHITECTURE_WIN32) + +UA_EXPORT UA_EventLoop * +UA_EventLoop_new_POSIX(const UA_Logger *logger); + /** * TCP Connection Manager * ~~~~~~~~~~~~~~~~~~~~~~ - * Listens on the network and manages TCP connections. The configuration - * parameters have to set before calling _start to take effect. + * Listens on the network and manages TCP connections. This should be available + * for all architectures. + * + * The configuration parameters have to set before calling _start to take + * effect. * * Configuration Parameters: * - 0:listen-port [uint16]: Port to listen for new connections (default: do not @@ -329,7 +389,9 @@ struct UA_ConnectionManager { * Send Parameters: * No additional parameters for sending over an established TCP socket defined. */ UA_EXPORT UA_ConnectionManager * -UA_ConnectionManager_TCP_new(const UA_String eventSourceName); +UA_ConnectionManager_new_POSIX_TCP(const UA_String eventSourceName); + +#endif /* defined(UA_ARCHITECTURE_POSIX) || defined(UA_ARCHITECTURE_WIN32) */ _UA_END_DECLS diff --git a/plugins/ua_config_default.c b/plugins/ua_config_default.c index ab0c66d9340..15e9cc65608 100644 --- a/plugins/ua_config_default.c +++ b/plugins/ua_config_default.c @@ -137,7 +137,7 @@ setDefaultConfig(UA_ServerConfig *conf) { /* EventLoop */ if(conf->eventLoop == NULL) { - conf->eventLoop = UA_EventLoop_new(&conf->logger); + conf->eventLoop = UA_EventLoop_new_POSIX(&conf->logger); conf->externalEventLoop = false; } @@ -764,7 +764,7 @@ UA_ClientConfig_setDefault(UA_ClientConfig *config) { /* EventLoop */ if(config->eventLoop == NULL) { - config->eventLoop = UA_EventLoop_new(&config->logger); + config->eventLoop = UA_EventLoop_new_POSIX(&config->logger); config->externalEventLoop = false; } diff --git a/src/client/ua_client.c b/src/client/ua_client.c index ffe2fc2e61d..5db10fd9e02 100644 --- a/src/client/ua_client.c +++ b/src/client/ua_client.c @@ -71,15 +71,16 @@ UA_ClientConfig_clear(UA_ClientConfig *config) { config->securityPolicies = 0; /* Stop and delete the EventLoop */ - if(config->eventLoop && !config->externalEventLoop) { - if(UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_FRESH && - UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_STOPPED) { - UA_EventLoop_stop(config->eventLoop); - while(UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_STOPPED) { - UA_EventLoop_run(config->eventLoop, 100); + UA_EventLoop *el = config->eventLoop; + if(el && !config->externalEventLoop) { + if(el->state != UA_EVENTLOOPSTATE_FRESH && + el->state != UA_EVENTLOOPSTATE_STOPPED) { + el->stop(el); + while(el->state != UA_EVENTLOOPSTATE_STOPPED) { + el->run(el, 100); } } - UA_EventLoop_delete(config->eventLoop); + el->free(el); config->eventLoop = NULL; } @@ -603,29 +604,33 @@ UA_Client_sendAsyncRequest(UA_Client *client, const void *request, UA_StatusCode UA_EXPORT UA_Client_addTimedCallback(UA_Client *client, UA_ClientCallback callback, void *data, UA_DateTime date, UA_UInt64 *callbackId) { - return UA_EventLoop_addTimedCallback(client->config.eventLoop, (UA_Callback)callback, - client, data, date, callbackId); + return client->config.eventLoop-> + addTimedCallback(client->config.eventLoop, (UA_Callback)callback, + client, data, date, callbackId); } UA_StatusCode UA_Client_addRepeatedCallback(UA_Client *client, UA_ClientCallback callback, void *data, UA_Double interval_ms, UA_UInt64 *callbackId) { - return UA_EventLoop_addCyclicCallback( - client->config.eventLoop, (UA_Callback)callback, client, data, - interval_ms, NULL, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, callbackId); + return client->config.eventLoop-> + addCyclicCallback(client->config.eventLoop, (UA_Callback)callback, + client, data, interval_ms, NULL, + UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, callbackId); } UA_StatusCode UA_Client_changeRepeatedCallbackInterval(UA_Client *client, UA_UInt64 callbackId, UA_Double interval_ms) { - return UA_EventLoop_modifyCyclicCallback(client->config.eventLoop, callbackId, - interval_ms, NULL, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME); + return client->config.eventLoop-> + modifyCyclicCallback(client->config.eventLoop, callbackId, interval_ms, + NULL, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME); } void UA_Client_removeCallback(UA_Client *client, UA_UInt64 callbackId) { - UA_EventLoop_removeCyclicCallback(client->config.eventLoop, callbackId); + client->config.eventLoop-> + removeCyclicCallback(client->config.eventLoop, callbackId); } static void @@ -688,20 +693,22 @@ UA_Client_backgroundConnectivity(UA_Client *client) { UA_StatusCode UA_Client_run_iterate(UA_Client *client, UA_UInt32 timeout) { UA_ClientConfig *cc = UA_Client_getConfig(client); - UA_CHECK_ERROR(UA_EventLoop_getState(cc->eventLoop) != UA_EVENTLOOPSTATE_STOPPED, - return UA_STATUSCODE_BAD, &client->config.logger, UA_LOGCATEGORY_CLIENT, + UA_EventLoop *el = cc->eventLoop; + UA_CHECK_ERROR(el->state != UA_EVENTLOOPSTATE_STOPPED, + return UA_STATUSCODE_BAD, + &client->config.logger, UA_LOGCATEGORY_CLIENT, "Eventloop was explicitly stopped."); UA_StatusCode rv = UA_STATUSCODE_GOOD; - if(UA_EventLoop_getState(cc->eventLoop) == UA_EVENTLOOPSTATE_FRESH) { - rv = UA_EventLoop_start(cc->eventLoop); + if(el->state == UA_EVENTLOOPSTATE_FRESH) { + rv = el->start(el); UA_CHECK_STATUS(rv, return rv); } /* Process timed (repeated) jobs */ UA_DateTime now = UA_DateTime_nowMonotonic(); - UA_EventLoop_run(client->config.eventLoop, 0); - UA_DateTime maxDate = UA_EventLoop_nextCyclicTime(client->config.eventLoop); + el->run(el, 0); + UA_DateTime maxDate = el->nextCyclicTime(el); if(maxDate > now + ((UA_DateTime)timeout * UA_DATETIME_MSEC)) maxDate = now + ((UA_DateTime)timeout * UA_DATETIME_MSEC); diff --git a/src/pubsub/ua_pubsub_manager.c b/src/pubsub/ua_pubsub_manager.c index c12a01d1316..98f10e77f27 100644 --- a/src/pubsub/ua_pubsub_manager.c +++ b/src/pubsub/ua_pubsub_manager.c @@ -351,22 +351,23 @@ UA_StatusCode UA_PubSubManager_addRepeatedCallback(UA_Server *server, UA_ServerCallback callback, void *data, UA_Double interval_ms, UA_DateTime *baseTime, UA_TimerPolicy timerPolicy, UA_UInt64 *callbackId) { - return UA_EventLoop_addCyclicCallback(server->config.eventLoop, (UA_Callback)callback, - server, data, interval_ms, baseTime, - timerPolicy, callbackId); + return server->config.eventLoop-> + addCyclicCallback(server->config.eventLoop, (UA_Callback)callback, server, data, + interval_ms, baseTime, timerPolicy, callbackId); } UA_StatusCode UA_PubSubManager_changeRepeatedCallback(UA_Server *server, UA_UInt64 callbackId, UA_Double interval_ms, UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) { - return UA_EventLoop_modifyCyclicCallback(server->config.eventLoop, callbackId, - interval_ms, baseTime, timerPolicy); + return server->config.eventLoop-> + modifyCyclicCallback(server->config.eventLoop, callbackId, interval_ms, + baseTime, timerPolicy); } void UA_PubSubManager_removeRepeatedPubSubCallback(UA_Server *server, UA_UInt64 callbackId) { - UA_EventLoop_removeCyclicCallback(server->config.eventLoop, callbackId); + server->config.eventLoop->removeCyclicCallback(server->config.eventLoop, callbackId); } @@ -428,8 +429,10 @@ UA_PubSubComponent_startMonitoring(UA_Server *server, UA_NodeId Id, UA_PubSubCom /* use a timed callback, because one notification is enough, we assume that MessageReceiveTimeout configuration is in [ms], we do not handle or check fractions */ UA_UInt64 interval = (UA_UInt64)(reader->config.messageReceiveTimeout * UA_DATETIME_MSEC); - ret = UA_EventLoop_addTimedCallback(server->config.eventLoop, (UA_Callback) reader->msgRcvTimeoutTimerCallback, - server, reader, UA_DateTime_nowMonotonic() + (UA_DateTime) interval, &(reader->msgRcvTimeoutTimerId)); + ret = server->config.eventLoop-> + addTimedCallback(server->config.eventLoop, (UA_Callback) reader->msgRcvTimeoutTimerCallback, + server, reader, UA_DateTime_nowMonotonic() + (UA_DateTime) interval, + &(reader->msgRcvTimeoutTimerId)); if (ret == UA_STATUSCODE_GOOD) { UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER, "UA_PubSubComponent_startMonitoring(): DataSetReader '%.*s'- MessageReceiveTimeout: MessageReceiveTimeout = '%f' " @@ -476,7 +479,7 @@ UA_PubSubComponent_stopMonitoring(UA_Server *server, UA_NodeId Id, UA_PubSubComp UA_DataSetReader *reader = (UA_DataSetReader*) data; switch (eMonitoringType) { case UA_PUBSUB_MONITORING_MESSAGE_RECEIVE_TIMEOUT: { - UA_EventLoop_removeCyclicCallback(server->config.eventLoop, reader->msgRcvTimeoutTimerId); + server->config.eventLoop->removeCyclicCallback(server->config.eventLoop, reader->msgRcvTimeoutTimerId); UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER, "UA_PubSubComponent_stopMonitoring(): DataSetReader '%.*s' - MessageReceiveTimeout: MessageReceiveTimeout = '%f' " "Timer Id = '%u'", (UA_Int32) reader->config.name.length, reader->config.name.data, @@ -516,9 +519,10 @@ UA_PubSubComponent_updateMonitoringInterval(UA_Server *server, UA_NodeId Id, UA_ UA_DataSetReader *reader = (UA_DataSetReader*) data; switch (eMonitoringType) { case UA_PUBSUB_MONITORING_MESSAGE_RECEIVE_TIMEOUT: { - ret = UA_EventLoop_modifyCyclicCallback(server->config.eventLoop, reader->msgRcvTimeoutTimerId, - reader->config.messageReceiveTimeout, NULL, - UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME); + ret = server->config.eventLoop-> + modifyCyclicCallback(server->config.eventLoop, reader->msgRcvTimeoutTimerId, + reader->config.messageReceiveTimeout, NULL, + UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME); if (ret == UA_STATUSCODE_GOOD) { UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER, "UA_PubSubComponent_updateMonitoringInterval(): DataSetReader '%.*s' - MessageReceiveTimeout: new MessageReceiveTimeout = '%f' " diff --git a/src/server/ua_server.c b/src/server/ua_server.c index 40c8ef21052..a5fa4edfa50 100644 --- a/src/server/ua_server.c +++ b/src/server/ua_server.c @@ -201,12 +201,13 @@ void UA_Server_delete(UA_Server *server) { #endif /* Stop the EventLoop and iterate until stopped or an error occurs */ - UA_EventLoop_stop(server->config.eventLoop); + if(server->config.eventLoop->state == UA_EVENTLOOPSTATE_STARTED) + server->config.eventLoop->stop(server->config.eventLoop); UA_StatusCode res = UA_STATUSCODE_GOOD; - UA_EventLoopState state = UA_EventLoop_getState(server->config.eventLoop); - while(res == UA_STATUSCODE_GOOD && state != UA_EVENTLOOPSTATE_STOPPED) { - res = UA_EventLoop_run(server->config.eventLoop, 100); - state = UA_EventLoop_getState(server->config.eventLoop); + while(res == UA_STATUSCODE_GOOD && + (server->config.eventLoop->state != UA_EVENTLOOPSTATE_FRESH && + server->config.eventLoop->state != UA_EVENTLOOPSTATE_STOPPED)) { + res = server->config.eventLoop->run(server->config.eventLoop, 100); } /* Clean up the Admin Session */ @@ -345,7 +346,7 @@ UA_Server_newWithConfig(UA_ServerConfig *config) { for(size_t i = 0; i < server->config.securityPoliciesSize; i++) server->config.securityPolicies[i].logger = &server->config.logger; - UA_EventLoop_setLogger(server->config.eventLoop, &server->config.logger); + server->config.eventLoop->logger = &server->config.logger; /* Reset the old config */ memset(config, 0, sizeof(UA_ServerConfig)); @@ -373,22 +374,20 @@ UA_StatusCode UA_Server_addTimedCallback(UA_Server *server, UA_ServerCallback callback, void *data, UA_DateTime date, UA_UInt64 *callbackId) { UA_LOCK(&server->serviceMutex); - UA_StatusCode retval = - UA_EventLoop_addTimedCallback(server->config.eventLoop, - (UA_Callback)callback, - server, data, date, callbackId); + UA_StatusCode retval = server->config.eventLoop-> + addTimedCallback(server->config.eventLoop, (UA_Callback)callback, + server, data, date, callbackId); UA_UNLOCK(&server->serviceMutex); return retval; } UA_StatusCode addRepeatedCallback(UA_Server *server, UA_ServerCallback callback, - void *data, UA_Double interval_ms, - UA_UInt64 *callbackId) { - return UA_EventLoop_addCyclicCallback(server->config.eventLoop, (UA_Callback) callback, - server, data, interval_ms, NULL, - UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, - callbackId); + void *data, UA_Double interval_ms, UA_UInt64 *callbackId) { + return server->config.eventLoop-> + addCyclicCallback(server->config.eventLoop, (UA_Callback) callback, + server, data, interval_ms, NULL, + UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, callbackId); } UA_StatusCode @@ -405,9 +404,9 @@ UA_Server_addRepeatedCallback(UA_Server *server, UA_ServerCallback callback, UA_StatusCode changeRepeatedCallbackInterval(UA_Server *server, UA_UInt64 callbackId, UA_Double interval_ms) { - return UA_EventLoop_modifyCyclicCallback(server->config.eventLoop, callbackId, - interval_ms, NULL, - UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME); + return server->config.eventLoop-> + modifyCyclicCallback(server->config.eventLoop, callbackId, interval_ms, + NULL, UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME); } UA_StatusCode @@ -422,7 +421,8 @@ UA_Server_changeRepeatedCallbackInterval(UA_Server *server, UA_UInt64 callbackId void removeCallback(UA_Server *server, UA_UInt64 callbackId) { - UA_EventLoop_removeCyclicCallback(server->config.eventLoop, callbackId); + server->config.eventLoop->removeCyclicCallback(server->config.eventLoop, + callbackId); } void @@ -553,7 +553,7 @@ UA_Server_run_startup(UA_Server *server) { "This should only be used for specific fuzzing builds."); #endif - UA_StatusCode retVal = UA_EventLoop_start(server->config.eventLoop); + UA_StatusCode retVal = server->config.eventLoop->start(server->config.eventLoop); UA_CHECK_STATUS(retVal, return retVal); /* ensure that the uri for ns1 is set up from the app description */ @@ -640,8 +640,9 @@ UA_UInt16 UA_Server_run_iterate(UA_Server *server, UA_Boolean waitInternal) { /* Process repeated work */ UA_DateTime now = UA_DateTime_nowMonotonic(); - UA_EventLoop_run(server->config.eventLoop, 0); - UA_DateTime nextRepeated = UA_EventLoop_nextCyclicTime(server->config.eventLoop); + server->config.eventLoop->run(server->config.eventLoop, 0); + UA_DateTime nextRepeated = + server->config.eventLoop->nextCyclicTime(server->config.eventLoop); UA_DateTime latest = now + (UA_MAXTIMEOUT * UA_DATETIME_MSEC); if(nextRepeated > latest) nextRepeated = latest; diff --git a/src/server/ua_server_config.c b/src/server/ua_server_config.c index 950099ee09c..1fff38b76d0 100644 --- a/src/server/ua_server_config.c +++ b/src/server/ua_server_config.c @@ -30,15 +30,16 @@ UA_ServerConfig_clean(UA_ServerConfig *config) { /* nothing to do */ /* Stop and delete the EventLoop */ - if(config->eventLoop && !config->externalEventLoop) { - if(UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_FRESH && - UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_STOPPED) { - UA_EventLoop_stop(config->eventLoop); - while(UA_EventLoop_getState(config->eventLoop) != UA_EVENTLOOPSTATE_STOPPED) { - UA_EventLoop_run(config->eventLoop, 100); + UA_EventLoop *el = config->eventLoop; + if(el && !config->externalEventLoop) { + if(el->state != UA_EVENTLOOPSTATE_FRESH && + el->state != UA_EVENTLOOPSTATE_STOPPED) { + el->stop(el); + while(el->state != UA_EVENTLOOPSTATE_STOPPED) { + el->run(el, 100); } } - UA_EventLoop_delete(config->eventLoop); + el->free(el); config->eventLoop = NULL; } diff --git a/src/server/ua_services_securechannel.c b/src/server/ua_services_securechannel.c index e20cd137c64..c4f6dc96a61 100644 --- a/src/server/ua_services_securechannel.c +++ b/src/server/ua_services_securechannel.c @@ -71,7 +71,8 @@ removeSecureChannel(UA_Server *server, channel_entry *entry, entry->cleanupCallback.callback = (UA_Callback)removeSecureChannelCallback; entry->cleanupCallback.application = NULL; entry->cleanupCallback.data = entry; - UA_EventLoop_addDelayedCallback(server->config.eventLoop, &entry->cleanupCallback); + server->config.eventLoop-> + addDelayedCallback(server->config.eventLoop, &entry->cleanupCallback); } void diff --git a/src/server/ua_services_session.c b/src/server/ua_services_session.c index 687de97779e..b23541d3ddf 100644 --- a/src/server/ua_services_session.c +++ b/src/server/ua_services_session.c @@ -91,7 +91,8 @@ UA_Server_removeSession(UA_Server *server, session_list_entry *sentry, sentry->cleanupCallback.callback = (UA_Callback)removeSessionCallback; sentry->cleanupCallback.application = server; sentry->cleanupCallback.data = sentry; - UA_EventLoop_addDelayedCallback(server->config.eventLoop, &sentry->cleanupCallback); + server->config.eventLoop-> + addDelayedCallback(server->config.eventLoop, &sentry->cleanupCallback); } UA_StatusCode diff --git a/src/server/ua_subscription.c b/src/server/ua_subscription.c index 1cddf691bed..a76dd305ca7 100644 --- a/src/server/ua_subscription.c +++ b/src/server/ua_subscription.c @@ -89,7 +89,8 @@ UA_Subscription_delete(UA_Server *server, UA_Subscription *sub) { sub->delayedFreePointers.callback = NULL; sub->delayedFreePointers.application = server; sub->delayedFreePointers.data = NULL; - UA_EventLoop_addDelayedCallback(server->config.eventLoop, &sub->delayedFreePointers); + server->config.eventLoop-> + addDelayedCallback(server->config.eventLoop, &sub->delayedFreePointers); } UA_MonitoredItem * diff --git a/src/server/ua_subscription_monitoreditem.c b/src/server/ua_subscription_monitoreditem.c index 752370139bc..4a11fffe135 100644 --- a/src/server/ua_subscription_monitoreditem.c +++ b/src/server/ua_subscription_monitoreditem.c @@ -599,7 +599,8 @@ UA_MonitoredItem_delete(UA_Server *server, UA_MonitoredItem *mon) { mon->delayedFreePointers.callback = NULL; mon->delayedFreePointers.application = server; mon->delayedFreePointers.data = NULL; - UA_EventLoop_addDelayedCallback(server->config.eventLoop, &mon->delayedFreePointers); + server->config.eventLoop-> + addDelayedCallback(server->config.eventLoop, &mon->delayedFreePointers); } void diff --git a/tests/check_eventloop.c b/tests/check_eventloop.c index 9679eba4ebe..3a66b1724b5 100644 --- a/tests/check_eventloop.c +++ b/tests/check_eventloop.c @@ -23,19 +23,19 @@ createEvents(UA_UInt32 events) { for(size_t i = 0; i < events; i++) { UA_Double interval = (UA_Double)i+1; UA_StatusCode retval = - UA_EventLoop_addCyclicCallback(el, timerCallback, NULL, NULL, interval, NULL, - UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, NULL); + el->addCyclicCallback(el, timerCallback, NULL, NULL, interval, NULL, + UA_TIMER_HANDLE_CYCLEMISS_WITH_CURRENTTIME, NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); } } START_TEST(benchmarkTimer) { - el = UA_EventLoop_new(NULL); + el = UA_EventLoop_new_POSIX(NULL); createEvents(N_EVENTS); clock_t begin = clock(); for(size_t i = 0; i < 1000; i++) { - UA_DateTime next = UA_EventLoop_run(el, 1); + UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); } @@ -44,8 +44,8 @@ START_TEST(benchmarkTimer) { printf("duration was %f s\n", time_spent); printf("%lu callbacks\n", (unsigned long)count); - UA_EventLoop_stop(el); - UA_EventLoop_delete(el); + el->stop(el); + el->free(el); el = NULL; } END_TEST diff --git a/tests/check_eventloop_tcp.c b/tests/check_eventloop_tcp.c index 7a430005185..2a8febbd1df 100644 --- a/tests/check_eventloop_tcp.c +++ b/tests/check_eventloop_tcp.c @@ -21,34 +21,34 @@ static void noopCallback(UA_ConnectionManager *cm, uintptr_t connectionId, UA_ByteString msg) {} START_TEST(listenTCP) { - el = UA_EventLoop_new(UA_Log_Stdout); + el = UA_EventLoop_new_POSIX(UA_Log_Stdout); UA_UInt16 port = 4840; UA_Variant portVar; UA_Variant_setScalar(&portVar, &port, &UA_TYPES[UA_TYPES_UINT16]); - UA_ConnectionManager *cm = UA_ConnectionManager_TCP_new(UA_STRING("tcpCM")); + UA_ConnectionManager *cm = UA_ConnectionManager_new_POSIX_TCP(UA_STRING("tcpCM")); cm->connectionCallback = noopCallback; UA_KeyValueMap_set(&cm->eventSource.params, &cm->eventSource.paramsSize, UA_QUALIFIEDNAME(0, "listen-port"), &portVar); - UA_EventLoop_registerEventSource(el, &cm->eventSource); + el->registerEventSource(el, &cm->eventSource); - UA_EventLoop_start(el); + el->start(el); for(size_t i = 0; i < 10; i++) { - UA_DateTime next = UA_EventLoop_run(el, 1); + UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); } int max_stop_iteration_count = 1000; int iteration = 0; /* Stop the EventLoop */ - UA_EventLoop_stop(el); - while(UA_EventLoop_getState(el) != UA_EVENTLOOPSTATE_STOPPED && iteration < max_stop_iteration_count) { - UA_DateTime next = UA_EventLoop_run(el, 1); + el->stop(el); + while(el->state != UA_EVENTLOOPSTATE_STOPPED && iteration < max_stop_iteration_count) { + UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); iteration++; } - UA_EventLoop_delete(el); + el->free(el); el = NULL; } END_TEST @@ -81,7 +81,7 @@ illegalConnectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId, void **connectionContext, UA_StatusCode status, size_t paramsSize, const UA_KeyValuePair *params, UA_ByteString msg) { - UA_StatusCode rv = UA_EventLoop_run(el, 1); + UA_StatusCode rv = el->run(el, 1); ck_assert_uint_eq(rv, UA_STATUSCODE_BADINTERNALERROR); if(*connectionContext != NULL) clientId = connectionId; @@ -97,20 +97,20 @@ illegalConnectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId, } START_TEST(runEventloopFailsIfCalledFromCallback) { - el = UA_EventLoop_new(UA_Log_Stdout); + el = UA_EventLoop_new_POSIX(UA_Log_Stdout); UA_UInt16 port = 4840; UA_Variant portVar; UA_Variant_setScalar(&portVar, &port, &UA_TYPES[UA_TYPES_UINT16]); - UA_ConnectionManager *cm = UA_ConnectionManager_TCP_new(UA_STRING("tcpCM")); + UA_ConnectionManager *cm = UA_ConnectionManager_new_POSIX_TCP(UA_STRING("tcpCM")); cm->connectionCallback = illegalConnectionCallback; UA_KeyValueMap_set(&cm->eventSource.params, &cm->eventSource.paramsSize, UA_QUALIFIEDNAME(0, "listen-port"), &portVar); - UA_EventLoop_registerEventSource(el, &cm->eventSource); + el->registerEventSource(el, &cm->eventSource); connCount = 0; - UA_EventLoop_start(el); + el->start(el); /* Open a client connection */ clientId = 0; @@ -125,7 +125,7 @@ START_TEST(runEventloopFailsIfCalledFromCallback) { UA_StatusCode retval = cm->openConnection(cm, 2, params, (void*)0x01); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); for(size_t i = 0; i < 10; i++) { - UA_DateTime next = UA_EventLoop_run(el, 1); + UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); } ck_assert(clientId != 0); @@ -140,7 +140,7 @@ START_TEST(runEventloopFailsIfCalledFromCallback) { retval = cm->sendWithConnection(cm, clientId, 0, NULL, &snd); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); for(size_t i = 0; i < 10; i++) { - UA_DateTime next = UA_EventLoop_run(el, 1); + UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); } ck_assert(received); @@ -150,7 +150,7 @@ START_TEST(runEventloopFailsIfCalledFromCallback) { ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); ck_assert_uint_eq(connCount, 2); for(size_t i = 0; i < 10; i++) { - UA_DateTime next = UA_EventLoop_run(el, 1); + UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); } ck_assert_uint_eq(connCount, 0); @@ -158,31 +158,32 @@ START_TEST(runEventloopFailsIfCalledFromCallback) { int max_stop_iteration_count = 1000; int iteration = 0; /* Stop the EventLoop */ - UA_EventLoop_stop(el); - while(UA_EventLoop_getState(el) != UA_EVENTLOOPSTATE_STOPPED && iteration < max_stop_iteration_count) { - UA_DateTime next = UA_EventLoop_run(el, 1); + el->stop(el); + while(el->state != UA_EVENTLOOPSTATE_STOPPED && + iteration < max_stop_iteration_count) { + UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); iteration++; } - UA_EventLoop_delete(el); + el->free(el); el = NULL; } END_TEST START_TEST(connectTCP) { - el = UA_EventLoop_new(UA_Log_Stdout); + el = UA_EventLoop_new_POSIX(UA_Log_Stdout); UA_UInt16 port = 4840; UA_Variant portVar; UA_Variant_setScalar(&portVar, &port, &UA_TYPES[UA_TYPES_UINT16]); - UA_ConnectionManager *cm = UA_ConnectionManager_TCP_new(UA_STRING("tcpCM")); + UA_ConnectionManager *cm = UA_ConnectionManager_new_POSIX_TCP(UA_STRING("tcpCM")); cm->connectionCallback = connectionCallback; UA_KeyValueMap_set(&cm->eventSource.params, &cm->eventSource.paramsSize, UA_QUALIFIEDNAME(0, "listen-port"), &portVar); - UA_EventLoop_registerEventSource(el, &cm->eventSource); + el->registerEventSource(el, &cm->eventSource); connCount = 0; - UA_EventLoop_start(el); + el->start(el); /* Open a client connection */ clientId = 0; @@ -197,7 +198,7 @@ START_TEST(connectTCP) { UA_StatusCode retval = cm->openConnection(cm, 2, params, (void*)0x01); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); for(size_t i = 0; i < 10; i++) { - UA_DateTime next = UA_EventLoop_run(el, 1); + UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); } ck_assert(clientId != 0); @@ -212,7 +213,7 @@ START_TEST(connectTCP) { retval = cm->sendWithConnection(cm, clientId, 0, NULL, &snd); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); for(size_t i = 0; i < 10; i++) { - UA_DateTime next = UA_EventLoop_run(el, 1); + UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); } ck_assert(received); @@ -222,7 +223,7 @@ START_TEST(connectTCP) { ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); ck_assert_uint_eq(connCount, 2); for(size_t i = 0; i < 10; i++) { - UA_DateTime next = UA_EventLoop_run(el, 1); + UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); } ck_assert_uint_eq(connCount, 0); @@ -231,13 +232,14 @@ START_TEST(connectTCP) { int max_stop_iteration_count = 1000; int iteration = 0; /* Stop the EventLoop */ - UA_EventLoop_stop(el); - while(UA_EventLoop_getState(el) != UA_EVENTLOOPSTATE_STOPPED && iteration < max_stop_iteration_count) { - UA_DateTime next = UA_EventLoop_run(el, 1); + el->stop(el); + while(el->state != UA_EVENTLOOPSTATE_STOPPED && + iteration < max_stop_iteration_count) { + UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); iteration++; } - UA_EventLoop_delete(el); + el->free(el); el = NULL; } END_TEST From 37679473d0fc4ff0c0fddc538f762c44dc781fc2 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Thu, 30 Dec 2021 22:10:55 +0100 Subject: [PATCH 0037/1963] refactor(el): Switch to epoll for the EventLoop on Linux --- CMakeLists.txt | 2 + arch/eventloop_posix.c | 396 +++++++++++----------------------- arch/eventloop_posix.h | 83 +++---- arch/eventloop_posix_epoll.c | 121 +++++++++++ arch/eventloop_posix_select.c | 172 +++++++++++++++ arch/eventloop_posix_tcp.c | 390 ++++++++++++++++++--------------- tests/CMakeLists.txt | 2 + tests/check_eventloop_tcp.c | 9 +- 8 files changed, 687 insertions(+), 488 deletions(-) create mode 100644 arch/eventloop_posix_epoll.c create mode 100644 arch/eventloop_posix_select.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e98cc2316b5..2fb55eb3951 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,6 +122,8 @@ set(ua_architecture_sources ${ua_architecture_sources} ${PROJECT_SOURCE_DIR}/arch/network_tcp.c ${PROJECT_SOURCE_DIR}/arch/eventloop_posix.h ${PROJECT_SOURCE_DIR}/arch/eventloop_posix.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_select.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_epoll.c ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_tcp.c ) diff --git a/arch/eventloop_posix.c b/arch/eventloop_posix.c index d9cbe12fa92..d61eef1d62a 100644 --- a/arch/eventloop_posix.c +++ b/arch/eventloop_posix.c @@ -7,9 +7,7 @@ */ #include "eventloop_posix.h" - -static UA_StatusCode -POSIX_EL_deregisterEventSource(POSIX_EL *el, UA_EventSource *es); +#include "open62541/plugin/eventloop.h" /*********/ /* Timer */ @@ -24,57 +22,59 @@ timerExecutionTrampoline(void *executionApplication, } static UA_DateTime -POSIX_EL_nextCyclicTime(UA_EventLoop *public_el) { - POSIX_EL *el = (POSIX_EL*)public_el; +UA_EventLoopPOSIX_nextCyclicTime(UA_EventLoop *public_el) { + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)public_el; return UA_Timer_nextRepeatedTime(&el->timer); } static UA_StatusCode -POSIX_EL_addTimedCallback(UA_EventLoop *public_el, UA_Callback callback, - void *application, void *data, - UA_DateTime date, - UA_UInt64 *callbackId) { - POSIX_EL *el = (POSIX_EL*)public_el; +UA_EventLoopPOSIX_addTimedCallback(UA_EventLoop *public_el, + UA_Callback callback, + void *application, void *data, + UA_DateTime date, + UA_UInt64 *callbackId) { + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)public_el; return UA_Timer_addTimedCallback(&el->timer, callback, application, data, date, callbackId); } static UA_StatusCode -POSIX_EL_addCyclicCallback(UA_EventLoop *public_el, UA_Callback cb, - void *application, void *data, - UA_Double interval_ms, - UA_DateTime *baseTime, - UA_TimerPolicy timerPolicy, - UA_UInt64 *callbackId) { - POSIX_EL *el = (POSIX_EL*)public_el; +UA_EventLoopPOSIX_addCyclicCallback(UA_EventLoop *public_el, + UA_Callback cb, + void *application, void *data, + UA_Double interval_ms, + UA_DateTime *baseTime, + UA_TimerPolicy timerPolicy, + UA_UInt64 *callbackId) { + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)public_el; return UA_Timer_addRepeatedCallback(&el->timer, cb, application, data, interval_ms, baseTime, timerPolicy, callbackId); } static UA_StatusCode -POSIX_EL_modifyCyclicCallback(UA_EventLoop *public_el, - UA_UInt64 callbackId, - UA_Double interval_ms, - UA_DateTime *baseTime, - UA_TimerPolicy timerPolicy) { - POSIX_EL *el = (POSIX_EL*)public_el; +UA_EventLoopPOSIX_modifyCyclicCallback(UA_EventLoop *public_el, + UA_UInt64 callbackId, + UA_Double interval_ms, + UA_DateTime *baseTime, + UA_TimerPolicy timerPolicy) { + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)public_el; return UA_Timer_changeRepeatedCallback(&el->timer, callbackId, interval_ms, baseTime, timerPolicy); } static void -POSIX_EL_removeCyclicCallback(UA_EventLoop *public_el, - UA_UInt64 callbackId) { - POSIX_EL *el = (POSIX_EL*)public_el; +UA_EventLoopPOSIX_removeCyclicCallback(UA_EventLoop *public_el, + UA_UInt64 callbackId) { + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)public_el; UA_Timer_removeCallback(&el->timer, callbackId); } static void -POSIX_EL_addDelayedCallback(UA_EventLoop *public_el, - UA_DelayedCallback *dc) { - POSIX_EL *el = (POSIX_EL*)public_el; +UA_EventLoopPOSIX_addDelayedCallback(UA_EventLoop *public_el, + UA_DelayedCallback *dc) { + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)public_el; UA_LOCK(&el->elMutex); dc->next = el->delayedCallbacks; el->delayedCallbacks = dc; @@ -83,7 +83,7 @@ POSIX_EL_addDelayedCallback(UA_EventLoop *public_el, /* Process and then free registered delayed callbacks */ static void -processDelayed(POSIX_EL *el) { +processDelayed(UA_EventLoopPOSIX *el) { UA_LOCK_ASSERT(&el->elMutex, 1); while(el->delayedCallbacks) { UA_DelayedCallback *dc = el->delayedCallbacks; @@ -104,46 +104,7 @@ processDelayed(POSIX_EL *el) { /***********************/ static UA_StatusCode -POSIX_EL_free(POSIX_EL *el) { - UA_LOCK(&el->elMutex); - - /* Check if the EventLoop can be deleted */ - if(el->eventLoop.state != UA_EVENTLOOPSTATE_STOPPED && - el->eventLoop.state != UA_EVENTLOOPSTATE_FRESH) { - UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, - "Cannot delete a running EventLoop"); - UA_UNLOCK(&el->elMutex); - return UA_STATUSCODE_BADINTERNALERROR; - } - - /* Deregister and delete all the EventSources */ - while(el->eventSources) { - UA_EventSource *es = el->eventSources; - UA_UNLOCK(&el->elMutex); - POSIX_EL_deregisterEventSource(el, es); - UA_LOCK(&el->elMutex); - es->free(es); - } - - /* Remove the repeated timed callbacks */ - UA_Timer_clear(&el->timer); - - /* Process remaining delayed callbacks */ - processDelayed(el); - - /* All file descriptors were removed together with the - * coresponding EventSource */ - UA_assert(el->fdsSize == 0); - - /* Clean up */ - UA_UNLOCK(&el->elMutex); - UA_LOCK_DESTROY(&el->elMutex); - UA_free(el); - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -POSIX_EL_start(POSIX_EL *el) { +UA_EventLoopPOSIX_start(UA_EventLoopPOSIX *el) { UA_LOCK(&el->elMutex); if(el->eventLoop.state != UA_EVENTLOOPSTATE_FRESH && el->eventLoop.state != UA_EVENTLOOPSTATE_STOPPED) { @@ -154,8 +115,19 @@ POSIX_EL_start(POSIX_EL *el) { UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "Starting the EventLoop"); - UA_EventSource *es = el->eventSources; +#ifdef UA_HAVE_EPOLL + el->epollfd = epoll_create1(0); + if(el->epollfd == -1) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP\t| Could not create the epoll socket (%s)", + errno_str)); + return UA_STATUSCODE_BADINTERNALERROR; + } +#endif + UA_StatusCode res = UA_STATUSCODE_GOOD; + UA_EventSource *es = el->eventSources; while(es) { UA_UNLOCK(&el->elMutex); res |= es->start(es); @@ -163,15 +135,16 @@ POSIX_EL_start(POSIX_EL *el) { es = es->next; } - /* Dirty-write the state that is const "outside" */ + /* Dirty-write the state that is const "from the outside" */ *(UA_EventLoopState*)(uintptr_t)&el->eventLoop.state = UA_EVENTLOOPSTATE_STARTED; + UA_UNLOCK(&el->elMutex); return res; } static void -checkClosed(POSIX_EL *el) { +checkClosed(UA_EventLoopPOSIX *el) { UA_EventSource *es = el->eventSources; while(es) { if(es->state != UA_EVENTSOURCESTATE_STOPPED) @@ -179,17 +152,29 @@ checkClosed(POSIX_EL *el) { es = es->next; } - UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, - "The EventLoop has stopped"); - /* Dirty-write the state that is const "outside" */ + /* Dirty-write the state that is const "from the outside" */ *(UA_EventLoopState*)(uintptr_t)&el->eventLoop.state = UA_EVENTLOOPSTATE_STOPPED; + + /* Close the epoll/IOCP socket once all EventSources have shut down */ +#ifdef UA_HAVE_EPOLL + close(el->epollfd); +#endif + + UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "The EventLoop has stopped"); } static void -POSIX_EL_stop(POSIX_EL *el) { +UA_EventLoopPOSIX_stop(UA_EventLoopPOSIX *el) { UA_LOCK(&el->elMutex); + if(el->eventLoop.state != UA_EVENTLOOPSTATE_STARTED) { + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "The EventLoop is not running, cannot be stopped"); + return; + } + UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "Stopping the EventLoop"); @@ -212,60 +197,7 @@ POSIX_EL_stop(POSIX_EL *el) { } static UA_StatusCode -pollFDs(POSIX_EL *el, UA_DateTime listenTimeout) { - UA_assert(listenTimeout >= 0); - /* Poll the registered sockets */ -#ifdef _GNU_SOURCE - struct timespec precisionTimeout = { - (long)(listenTimeout / UA_DATETIME_SEC), - (long)((listenTimeout % UA_DATETIME_SEC) * 100) - }; - int pollStatus = ppoll(el->pollfds, el->fdsSize, - &precisionTimeout, NULL); -#else - int pollStatus = UA_poll(el->pollfds, el->fdsSize, - (int)(listenTimeout / UA_DATETIME_MSEC)); -#endif - - if(pollStatus < 0) { - /* We will retry, only log the error */ - UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(el->eventLoop.logger, - UA_LOGCATEGORY_EVENTLOOP, - "Error during poll: %s", errno_str)); - return UA_STATUSCODE_GOODCALLAGAIN; - } - - /* Loop over all registered FD to see if an event arrived. Yes, - * this is why poll is slow for many open sockets. */ - int processed = 0; - for(size_t i = 0; i < el->fdsSize; i++) { - /* All done */ - if(processed >= pollStatus) - break; - - /* Nothing to do for this fd */ - if(el->pollfds[i].revents == 0) - continue; - - /* Process the fd */ - UA_RegisteredFD *rfd = &el->fds[i]; - UA_FD fd = el->pollfds[i].fd; - short revent = el->pollfds[i].revents; - UA_UNLOCK(&el->elMutex); - rfd->callback(rfd->es, fd, &rfd->fdcontext, revent); - UA_LOCK(&el->elMutex); - processed++; - - /* The fd has removed itself from within the callback? */ - if(i >= el->fdsSize || fd != el->pollfds[i].fd) - i--; - } - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -POSIX_EL_run(POSIX_EL *el, UA_UInt32 timeout) { +UA_EventLoopPOSIX_run(UA_EventLoopPOSIX *el, UA_UInt32 timeout) { UA_LOCK(&el->elMutex); UA_LOG_TRACE(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, @@ -309,7 +241,7 @@ POSIX_EL_run(POSIX_EL *el, UA_UInt32 timeout) { /* Listen on the active file-descriptors (sockets) from the * ConnectionManagers */ - UA_StatusCode rv = pollFDs(el, listenTimeout); + UA_StatusCode rv = UA_EventLoopPOSIX_pollFDs(el, listenTimeout); /* Process and then free registered delayed callbacks */ processDelayed(el); @@ -323,13 +255,13 @@ POSIX_EL_run(POSIX_EL *el, UA_UInt32 timeout) { return rv; } - /*****************************/ /* Registering Event Sources */ /*****************************/ static UA_StatusCode -POSIX_EL_registerEventSource(POSIX_EL *el, UA_EventSource *es) { +UA_EventLoopPOSIX_registerEventSource(UA_EventLoopPOSIX *el, + UA_EventSource *es) { /* Already registered? */ if(es->state != UA_EVENTSOURCESTATE_FRESH) { UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, @@ -355,7 +287,8 @@ POSIX_EL_registerEventSource(POSIX_EL *el, UA_EventSource *es) { } static UA_StatusCode -POSIX_EL_deregisterEventSource(POSIX_EL *el, UA_EventSource *es) { +UA_EventLoopPOSIX_deregisterEventSource(UA_EventLoopPOSIX *el, + UA_EventSource *es) { if(es->state != UA_EVENTSOURCESTATE_STOPPED) { UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "Cannot deregister the EventSource %.*s: " @@ -383,7 +316,8 @@ POSIX_EL_deregisterEventSource(POSIX_EL *el, UA_EventSource *es) { } static UA_EventSource * -POSIX_EL_findEventSource(POSIX_EL *el, const UA_String name) { +UA_EventLoopPOSIX_findEventSource(UA_EventLoopPOSIX *el, + const UA_String name) { UA_LOCK(&el->elMutex); UA_EventSource *s = el->eventSources; while(s) { @@ -395,172 +329,90 @@ POSIX_EL_findEventSource(POSIX_EL *el, const UA_String name) { return s; } -/********************************/ -/* Registering File Descriptors */ -/********************************/ +/*************************/ +/* Initialize and Delete */ +/*************************/ -UA_StatusCode -POSIX_EL_registerFD(POSIX_EL *el, UA_FD fd, short eventMask, - UA_FDCallback cb, UA_EventSource *es, - void *fdcontext) { +static UA_StatusCode +UA_EventLoopPOSIX_free(UA_EventLoopPOSIX *el) { UA_LOCK(&el->elMutex); - UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, - "Registering fd: %u", (unsigned)fd); - - /* Realloc */ - UA_RegisteredFD *fds_tmp = (UA_RegisteredFD*) - UA_realloc(el->fds, sizeof(UA_RegisteredFD) * (el->fdsSize + 1)); - if(!fds_tmp) { - UA_UNLOCK(&el->elMutex); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - el->fds = fds_tmp; - - struct pollfd *pollfds_tmp = (struct pollfd*) - UA_realloc(el->pollfds, sizeof(struct pollfd) *(el->fdsSize + 1)); - if(!pollfds_tmp) { + /* Check if the EventLoop can be deleted */ + if(el->eventLoop.state != UA_EVENTLOOPSTATE_STOPPED && + el->eventLoop.state != UA_EVENTLOOPSTATE_FRESH) { + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "Cannot delete a running EventLoop"); UA_UNLOCK(&el->elMutex); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - el->pollfds = pollfds_tmp; - - /* Add to the last entry */ - el->fds[el->fdsSize].callback = cb; - el->fds[el->fdsSize].es = es; - el->fds[el->fdsSize].fdcontext = fdcontext; - el->pollfds[el->fdsSize].fd = fd; - el->pollfds[el->fdsSize].events = eventMask; - el->fdsSize++; - - UA_UNLOCK(&el->elMutex); - return UA_STATUSCODE_GOOD; -} - -UA_StatusCode -POSIX_EL_modifyFD(POSIX_EL *el, UA_FD fd, short eventMask, - UA_FDCallback cb, void *fdcontext) { - UA_LOCK(&el->elMutex); - - /* Find the entry */ - size_t i = 0; - for(; i < el->fdsSize; i++) { - if(el->pollfds[i].fd == fd) - break; + return UA_STATUSCODE_BADINTERNALERROR; } - /* Not found? */ - if(i == el->fdsSize) { + /* Deregister and delete all the EventSources */ + while(el->eventSources) { + UA_EventSource *es = el->eventSources; UA_UNLOCK(&el->elMutex); - return UA_STATUSCODE_BADNOTFOUND; + UA_EventLoopPOSIX_deregisterEventSource(el, es); + UA_LOCK(&el->elMutex); + es->free(es); } - /* Modify */ - el->fds[i].callback = cb; - el->pollfds[i].events = eventMask; - el->fds[i].fdcontext = fdcontext; - - UA_UNLOCK(&el->elMutex); - return UA_STATUSCODE_GOOD; -} - -UA_StatusCode -POSIX_EL_deregisterFD(POSIX_EL *el, UA_FD fd) { - UA_LOCK(&el->elMutex); - - UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, - "Unregistering fd: %u", (unsigned)fd); - - /* Find the entry */ - size_t i = 0; - for(; i < el->fdsSize; i++) { - if(el->pollfds[i].fd == fd) - break; - } + /* Remove the repeated timed callbacks */ + UA_Timer_clear(&el->timer); - /* Not found? */ - if(i == el->fdsSize) { - UA_UNLOCK(&el->elMutex); - return UA_STATUSCODE_BADNOTFOUND; - } + /* Process remaining delayed callbacks */ + processDelayed(el); - el->fdsSize--; - if(el->fdsSize > 0) { - /* Move the last entry to the ith slot and realloc. */ - el->fds[i] = el->fds[el->fdsSize]; - el->pollfds[i] = el->pollfds[el->fdsSize]; - - /* If realloc fails the fds are still in a correct state with - * possibly lost memory, so failing silently here is ok */ - UA_RegisteredFD *fds_tmp = (UA_RegisteredFD*) - UA_realloc(el->fds, sizeof(UA_RegisteredFD) * el->fdsSize); - if(fds_tmp) - el->fds = fds_tmp; - struct pollfd *pollfds_tmp = (struct pollfd*) - UA_realloc(el->pollfds, sizeof(struct pollfd) * el->fdsSize); - if(pollfds_tmp) - el->pollfds = pollfds_tmp; - } else { - /* Free the lists */ - UA_free(el->fds); - el->fds = NULL; - UA_free(el->pollfds); - el->pollfds = NULL; - } +#ifdef _WIN32 + /* Stop the Windows networking subsystem */ + WSACleanup(); +#endif + /* Clean up */ UA_UNLOCK(&el->elMutex); + UA_LOCK_DESTROY(&el->elMutex); + UA_free(el); return UA_STATUSCODE_GOOD; } -void -POSIX_EL_iterateFD(POSIX_EL *el, UA_EventSource *es, - POSIX_EL_IterateCallback cb, - void *iterateContext) { - for(size_t i = 0; i < el->fdsSize; i++) { - if(el->fds[i].es != es) - continue; - - UA_FD fd = el->pollfds[i].fd; - int done = cb(es, fd, el->fds[i].fdcontext, iterateContext); - if(done) - break; - - /* The fd has removed itself from within the callback? */ - if(i >= el->fdsSize || fd != el->pollfds[i].fd) - i--; - } -} - UA_EventLoop * UA_EventLoop_new_POSIX(const UA_Logger *logger) { - POSIX_EL *el = (POSIX_EL*)UA_malloc(sizeof(POSIX_EL)); + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*) + UA_calloc(1, sizeof(UA_EventLoopPOSIX)); if(!el) return NULL; - memset(el, 0, sizeof(POSIX_EL)); + UA_LOCK_INIT(&el->elMutex); UA_Timer_init(&el->timer); +#ifdef _WIN32 + /* Start the WSA networking subsystem on Windows */ + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); +#endif + /* Set the public EventLoop content */ el->eventLoop.logger = logger; - el->eventLoop.start = (UA_StatusCode (*)(UA_EventLoop*))POSIX_EL_start; - el->eventLoop.stop = (void (*)(UA_EventLoop*))POSIX_EL_stop; - el->eventLoop.run = (UA_StatusCode (*)(UA_EventLoop*, UA_UInt32))POSIX_EL_run; - el->eventLoop.free = (UA_StatusCode (*)(UA_EventLoop*))POSIX_EL_free; - - el->eventLoop.nextCyclicTime = POSIX_EL_nextCyclicTime; - el->eventLoop.addCyclicCallback = POSIX_EL_addCyclicCallback; - el->eventLoop.modifyCyclicCallback = POSIX_EL_modifyCyclicCallback; - el->eventLoop.removeCyclicCallback = POSIX_EL_removeCyclicCallback; - el->eventLoop.addTimedCallback = POSIX_EL_addTimedCallback; - el->eventLoop.addDelayedCallback = POSIX_EL_addDelayedCallback; + el->eventLoop.start = (UA_StatusCode (*)(UA_EventLoop*))UA_EventLoopPOSIX_start; + el->eventLoop.stop = (void (*)(UA_EventLoop*))UA_EventLoopPOSIX_stop; + el->eventLoop.run = (UA_StatusCode (*)(UA_EventLoop*, UA_UInt32))UA_EventLoopPOSIX_run; + el->eventLoop.free = (UA_StatusCode (*)(UA_EventLoop*))UA_EventLoopPOSIX_free; + + el->eventLoop.nextCyclicTime = UA_EventLoopPOSIX_nextCyclicTime; + el->eventLoop.addCyclicCallback = UA_EventLoopPOSIX_addCyclicCallback; + el->eventLoop.modifyCyclicCallback = UA_EventLoopPOSIX_modifyCyclicCallback; + el->eventLoop.removeCyclicCallback = UA_EventLoopPOSIX_removeCyclicCallback; + el->eventLoop.addTimedCallback = UA_EventLoopPOSIX_addTimedCallback; + el->eventLoop.addDelayedCallback = UA_EventLoopPOSIX_addDelayedCallback; el->eventLoop.registerEventSource = - (UA_StatusCode (*)(UA_EventLoop*, UA_EventSource*))POSIX_EL_registerEventSource; + (UA_StatusCode (*)(UA_EventLoop*, UA_EventSource*)) + UA_EventLoopPOSIX_registerEventSource; el->eventLoop.deregisterEventSource = - (UA_StatusCode (*)(UA_EventLoop*, UA_EventSource*))POSIX_EL_deregisterEventSource; + (UA_StatusCode (*)(UA_EventLoop*, UA_EventSource*)) + UA_EventLoopPOSIX_deregisterEventSource; el->eventLoop.findEventSource = - (UA_EventSource* (*)(UA_EventLoop*, const UA_String))POSIX_EL_findEventSource; + (UA_EventSource* (*)(UA_EventLoop*, const UA_String)) + UA_EventLoopPOSIX_findEventSource; return &el->eventLoop; } diff --git a/arch/eventloop_posix.h b/arch/eventloop_posix.h index 17e6de10f57..5ac83cddf7a 100644 --- a/arch/eventloop_posix.h +++ b/arch/eventloop_posix.h @@ -15,6 +15,13 @@ #if defined(UA_ARCHITECTURE_POSIX) || defined(UA_ARCHITECTURE_WIN32) #include "common/ua_timer.h" +#include "open62541_queue.h" + +/* epoll_pwait returns bogus data with the tc compiler */ +#if defined(__linux__) && !defined(__TINYC__) +# define UA_HAVE_EPOLL +# include +#endif _UA_BEGIN_DECLS @@ -27,22 +34,30 @@ _UA_BEGIN_DECLS #define UA_FD UA_SOCKET #define UA_INVALID_FD UA_INVALID_SOCKET -typedef void -(*UA_FDCallback)(UA_EventSource *es, UA_FD fd, void **fdcontext, short event); +struct UA_RegisteredFD; +typedef struct UA_RegisteredFD UA_RegisteredFD; -typedef struct { - /* The members fd and events are stored in the separate - * pollfds array: - * - UA_FD fd; - * - short events; */ - UA_EventSource *es; +/* Bitmask to be used for the UA_FDCallback event argument */ +#define UA_FDEVENT_IN 1 +#define UA_FDEVENT_OUT 2 +#define UA_FDEVENT_ERR 4 + +typedef void (*UA_FDCallback)(UA_EventSource *es, UA_RegisteredFD *rfd, short event); + +struct UA_RegisteredFD { + LIST_ENTRY(UA_RegisteredFD) es_pointers; /* Register FD in the EventSource */ + + UA_FD fd; + short listenEvents; /* UA_FDEVENT_IN | UA_FDEVENT_OUT*/ + + UA_EventSource *es; /* Backpointer to the EventSource */ UA_FDCallback callback; - void *fdcontext; -} UA_RegisteredFD; + void *context; +}; typedef struct { UA_EventLoop eventLoop; - + /* Timer */ UA_Timer timer; @@ -52,47 +67,39 @@ typedef struct { /* Pointers to registered EventSources */ UA_EventSource *eventSources; - /* Registered file descriptors */ - size_t fdsSize; - UA_RegisteredFD *fds; - struct pollfd *pollfds; /* has the same size as "fds" */ - /* Flag determining whether the eventloop is currently within the * "run" method */ UA_Boolean executing; +#if defined(UA_HAVE_EPOLL) + UA_FD epollfd; +#else + /* Explicit list of file descriptors */ + size_t fdsSize; + UA_RegisteredFD **fds; +#endif + #if UA_MULTITHREADING >= 100 UA_Lock elMutex; #endif -} POSIX_EL; +} UA_EventLoopPOSIX; -UA_StatusCode -POSIX_EL_registerFD(POSIX_EL *el, UA_FD fd, short eventMask, - UA_FDCallback cb, UA_EventSource *es, void *fdcontext); +/* The following functions differ between epoll and normal select */ -/* Change the fd settings (event mask, callback) in-place. Fails only if the fd - * no longer exists. */ +/* Register to start receiving events */ UA_StatusCode -POSIX_EL_modifyFD(POSIX_EL *el, UA_FD fd, short eventMask, - UA_FDCallback cb, void *fdcontext); +UA_EventLoopPOSIX_registerFD(UA_EventLoopPOSIX *el, UA_RegisteredFD *rfd); -/* During processing of an fd-event, the fd may deregister itself. But in the - * fd-callback they must not deregister another fd. */ +/* Modify the events that the fd listens on */ UA_StatusCode -POSIX_EL_deregisterFD(POSIX_EL *el, UA_FD fd); - -/* abort the iteration if the returned boolean is true */ -typedef UA_Boolean -(*POSIX_EL_IterateCallback)(UA_EventSource *es, UA_FD fd, - void *fdContext, void *iterateContext); +UA_EventLoopPOSIX_modifyFD(UA_EventLoopPOSIX *el, UA_RegisteredFD *rfd); -/* Call the callback for all fd that are registered from that event source. The - * callback is called with the 'event' argument set to zero to disambiguate from - * a callback after 'poll'. */ +/* Deregister but do not close the fd. No further events are received. */ void -POSIX_EL_iterateFD(POSIX_EL *el, UA_EventSource *es, - POSIX_EL_IterateCallback callback, - void *iterateContext); +UA_EventLoopPOSIX_deregisterFD(UA_EventLoopPOSIX *el, UA_RegisteredFD *rfd); + +UA_StatusCode +UA_EventLoopPOSIX_pollFDs(UA_EventLoopPOSIX *el, UA_DateTime listenTimeout); _UA_END_DECLS diff --git a/arch/eventloop_posix_epoll.c b/arch/eventloop_posix_epoll.c new file mode 100644 index 00000000000..a393499d6a9 --- /dev/null +++ b/arch/eventloop_posix_epoll.c @@ -0,0 +1,121 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + */ + +#include "eventloop_posix.h" +#ifdef __linux__ +#include +#endif + +#if defined(UA_HAVE_EPOLL) + +UA_StatusCode +UA_EventLoopPOSIX_registerFD(UA_EventLoopPOSIX *el, UA_RegisteredFD *rfd) { + struct epoll_event event; + memset(&event, 0, sizeof(struct epoll_event)); + event.data.ptr = rfd; + event.events = 0; + if(rfd->listenEvents & UA_FDEVENT_IN) + event.events |= EPOLLIN; + if(rfd->listenEvents & UA_FDEVENT_OUT) + event.events |= EPOLLOUT; + + int err = epoll_ctl(el->epollfd, EPOLL_CTL_ADD, rfd->fd, &event); + if(err != 0) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Could not register for epoll (%s)", + rfd->fd, errno_str)); + return UA_STATUSCODE_BADINTERNALERROR; + } + return UA_STATUSCODE_GOOD; +} + +UA_StatusCode +UA_EventLoopPOSIX_modifyFD(UA_EventLoopPOSIX *el, UA_RegisteredFD *rfd) { + struct epoll_event event; + event.data.ptr = rfd; + event.events = 0; + if(rfd->listenEvents & UA_FDEVENT_IN) + event.events |= EPOLLIN; + if(rfd->listenEvents & UA_FDEVENT_OUT) + event.events |= EPOLLOUT; + + int err = epoll_ctl(el->epollfd, EPOLL_CTL_MOD, rfd->fd, &event); + if(err != 0) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Could not modify for epoll (%s)", + rfd->fd, errno_str)); + return UA_STATUSCODE_BADINTERNALERROR; + } + return UA_STATUSCODE_GOOD; +} + +void +UA_EventLoopPOSIX_deregisterFD(UA_EventLoopPOSIX *el, UA_RegisteredFD *rfd) { + int res = epoll_ctl(el->epollfd, EPOLL_CTL_DEL, rfd->fd, NULL); + if(res != 0) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Could not deregister from epoll (%s)", + rfd->fd, errno_str)); + } +} + +UA_StatusCode +UA_EventLoopPOSIX_pollFDs(UA_EventLoopPOSIX *el, UA_DateTime listenTimeout) { + UA_assert(listenTimeout >= 0); + + /* Poll the registered sockets */ + struct epoll_event epoll_events[64]; +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,11,0) + int events = epoll_pwait(el->epollfd, epoll_events, 64, + (int)(listenTimeout / UA_DATETIME_MSEC), NULL); +#else + struct timespec precisionTimeout = { + (long)(listenTimeout / UA_DATETIME_SEC), + (long)((listenTimeout % UA_DATETIME_SEC) * 100) + }; + int events = epoll_pwait2(el->epollfd, epoll_events, 64, + precisionTimeout, NULL); +#endif + + /* Handle error conditions */ + if(events == -1) { + if(errno == EINTR) { + /* We will retry, only log the error */ + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "Timeout during poll"); + return UA_STATUSCODE_GOOD; + } + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP\t| Error %s, closing the server socket", + errno_str)); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Process all received events */ + for(int i = 0; i < events; i++) { + UA_RegisteredFD *rfd = (UA_RegisteredFD*)epoll_events[i].data.ptr; + short revent = 0; + if(epoll_events[i].events == EPOLLIN) { + revent = UA_FDEVENT_IN; + } else if(epoll_events[i].events == EPOLLOUT) { + revent = UA_FDEVENT_OUT; + } else { + revent = UA_FDEVENT_ERR; + } + + UA_UNLOCK(&el->elMutex); + rfd->callback(rfd->es, rfd, revent); + UA_LOCK(&el->elMutex); + } + return UA_STATUSCODE_GOOD; +} + +#endif /* defined(UA_HAVE_EPOLL) */ diff --git a/arch/eventloop_posix_select.c b/arch/eventloop_posix_select.c new file mode 100644 index 00000000000..fc08906fd53 --- /dev/null +++ b/arch/eventloop_posix_select.c @@ -0,0 +1,172 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + */ + +#include "eventloop_posix.h" + +#if !defined(UA_HAVE_EPOLL) + +UA_StatusCode +UA_EventLoopPOSIX_registerFD(UA_EventLoopPOSIX *el, UA_RegisteredFD *rfd) { + UA_LOCK(&el->elMutex); + + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "Registering fd: %u", (unsigned)rfd->fd); + + /* Realloc */ + UA_RegisteredFD **fds_tmp = (UA_RegisteredFD**) + UA_realloc(el->fds, sizeof(UA_RegisteredFD*) * (el->fdsSize + 1)); + if(!fds_tmp) { + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + el->fds = fds_tmp; + + /* Add to the last entry */ + el->fds[el->fdsSize] = rfd; + el->fdsSize++; + + UA_UNLOCK(&el->elMutex); + return UA_STATUSCODE_GOOD; +} + +UA_StatusCode +UA_EventLoopPOSIX_modifyFD(UA_EventLoopPOSIX *el, UA_RegisteredFD *rfd) { + /* Do nothing, it is enough if the data was changed in the rfd */ + return UA_STATUSCODE_GOOD; +} + +void +UA_EventLoopPOSIX_deregisterFD(UA_EventLoopPOSIX *el, UA_RegisteredFD *rfd) { + UA_LOCK(&el->elMutex); + + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "Unregistering fd: %u", (unsigned)rfd->fd); + + /* Find the entry */ + size_t i = 0; + for(; i < el->fdsSize; i++) { + if(el->fds[i] == rfd) + break; + } + + /* Not found? */ + if(i == el->fdsSize) { + UA_UNLOCK(&el->elMutex); + return; + } + + if(el->fdsSize > 1) { + /* Move the last entry in the ith slot and realloc. */ + el->fdsSize--; + el->fds[i] = el->fds[el->fdsSize]; + UA_RegisteredFD **fds_tmp = (UA_RegisteredFD**) + UA_realloc(el->fds, sizeof(UA_RegisteredFD*) * el->fdsSize); + /* if realloc fails the fds are still in a correct state with + * possibly lost memory, so failing silently here is ok */ + if(fds_tmp) + el->fds = fds_tmp; + } else { + /* Remove the last entry */ + UA_free(el->fds); + el->fds = NULL; + el->fdsSize = 0; + } + + UA_UNLOCK(&el->elMutex); +} + +static UA_FD +setFDSets(UA_EventLoopPOSIX *el, fd_set *readset, fd_set *writeset, fd_set *errset) { + FD_ZERO(readset); + FD_ZERO(writeset); + FD_ZERO(errset); + UA_FD highestfd = UA_INVALID_FD; + for(size_t i = 0; i < el->fdsSize; i++) { + + UA_FD currentFD = el->fds[i]->fd; + /* Add to the fd_sets */ + if(el->fds[i]->listenEvents & UA_FDEVENT_IN) + UA_fd_set(currentFD, readset); + if(el->fds[i]->listenEvents & UA_FDEVENT_OUT) + UA_fd_set(currentFD, writeset); + + /* Always return errors */ + UA_fd_set(currentFD, errset); + + /* Highest fd? */ + if(currentFD > highestfd || highestfd == UA_INVALID_FD) + highestfd = currentFD; + } + return highestfd; +} + +UA_StatusCode +UA_EventLoopPOSIX_pollFDs(UA_EventLoopPOSIX *el, UA_DateTime listenTimeout) { + UA_assert(listenTimeout >= 0); + + fd_set readset, writeset, errset; + UA_FD highestfd = setFDSets(el, &readset, &writeset, &errset); + + /* Nothing to do? */ + if(highestfd == UA_INVALID_FD) { + UA_LOG_TRACE(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "No valid FDs for processing"); + return UA_STATUSCODE_GOOD; + } + + struct timeval tmptv = { +#ifndef _WIN32 + (time_t)(listenTimeout / UA_DATETIME_SEC), + (suseconds_t)((listenTimeout % UA_DATETIME_SEC) / UA_DATETIME_USEC) +#else + (long)(listenTimeout / UA_DATETIME_SEC), + (long)((listenTimeout % UA_DATETIME_SEC) / UA_DATETIME_USEC) +#endif + }; + + int selectStatus = UA_select(highestfd+1, &readset, &writeset, &errset, &tmptv); + if(selectStatus < 0) { + /* We will retry, only log the error */ + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "Error during select: %s", errno_str)); + return UA_STATUSCODE_GOOD; + } + + /* Loop over all registered FD to see if an event arrived. Yes, this is why + * select is slow for many open sockets. */ + for(size_t i = 0; i < el->fdsSize; i++) { + UA_RegisteredFD *rfd = el->fds[i]; + UA_FD fd = rfd->fd; + + /* Error Event */ + short event = 0; + if(UA_fd_isset(fd, &readset)) { + event = UA_FDEVENT_IN; + } else if(UA_fd_isset(fd, &writeset)) { + event = UA_FDEVENT_OUT; + } else if(UA_fd_isset(fd, &errset)) { + event = UA_FDEVENT_ERR; + } else { + continue; + } + + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "Processing event %u on fd %u", (unsigned)event, (unsigned)fd); + + UA_UNLOCK(&el->elMutex); + rfd->callback(rfd->es, rfd, event); + UA_LOCK(&el->elMutex); + + /* The fd has removed itself */ + if(i == el->fdsSize || rfd != el->fds[i]) + i--; + } + return UA_STATUSCODE_GOOD; +} + +#endif /* !defined(UA_HAVE_EPOLL) */ diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index 503b433f9fd..b8176ba2782 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -20,10 +20,33 @@ typedef struct { UA_ConnectionManager cm; - size_t fdCount; /* Number of fd registered in the EventLoop */ + + size_t fdsSize; + LIST_HEAD(, UA_RegisteredFD) fds; + size_t recvBufferSize; } TCPConnectionManager; +static UA_StatusCode +TCPConnectionManager_register(TCPConnectionManager *tcm, UA_RegisteredFD *rfd) { + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)tcm->cm.eventSource.eventLoop; + UA_StatusCode res = UA_EventLoopPOSIX_registerFD(el, rfd); + if(res != UA_STATUSCODE_GOOD) + return res; + LIST_INSERT_HEAD(&tcm->fds, rfd, es_pointers); + tcm->fdsSize++; + return UA_STATUSCODE_GOOD; +} + +static void +TCPConnectionManager_deregister(TCPConnectionManager *tcm, UA_RegisteredFD *rfd) { + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)tcm->cm.eventSource.eventLoop; + UA_EventLoopPOSIX_deregisterFD(el, rfd); + LIST_REMOVE(rfd, es_pointers); + UA_assert(tcm->fdsSize > 0); + tcm->fdsSize--; +} + static UA_StatusCode TCP_allocNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId, UA_ByteString *buf, size_t bufSize) { @@ -75,97 +98,88 @@ TCP_setNoNagle(UA_FD sockfd) { } static UA_StatusCode -TCP_close(UA_ConnectionManager *cm, UA_FD fd) { - UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, - "TCP %u\t| Closing connection", (unsigned)fd); - - TCPConnectionManager *tcm = (TCPConnectionManager*)cm; - - /* Close the fd and deregister */ - int ret = UA_close(fd); - if(ret != 0) - return UA_STATUSCODE_BADINTERNALERROR; - UA_StatusCode sc = - POSIX_EL_deregisterFD((POSIX_EL*)tcm->cm.eventSource.eventLoop, fd); - if(sc != UA_STATUSCODE_GOOD) - return sc; - - /* Reduce the count */ - UA_assert(tcm->fdCount > 0); - tcm->fdCount--; +TCP_close(TCPConnectionManager *tcm, UA_RegisteredFD *rfd) { + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)tcm->cm.eventSource.eventLoop; + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Closing connection", (unsigned)rfd->fd); + + /* Deregister from the EventLoop */ + TCPConnectionManager_deregister(tcm, rfd); + + /* Close the socket */ + int ret = UA_close(rfd->fd); + if(ret == 0) { + UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Socket closed", (unsigned)rfd->fd); + } else { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Could not close the socket (%s)", + (unsigned)rfd->fd, errno_str)); + } - UA_LOG_INFO(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, "TCP %u\t| Socket closed", (unsigned)fd); + /* Free the rfd */ + UA_free(rfd); /* Stopped? */ - if(tcm->fdCount == 0 && cm->eventSource.state == UA_EVENTSOURCESTATE_STOPPING) { - UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, + if(tcm->fdsSize == 0 && tcm->cm.eventSource.state == UA_EVENTSOURCESTATE_STOPPING) { + UA_LOG_DEBUG(tcm->cm.eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| All sockets closed, the EventLoop has stopped"); - cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPED; + tcm->cm.eventSource.state = UA_EVENTSOURCESTATE_STOPPED; } return UA_STATUSCODE_GOOD; } /* Gets called when a connection socket opens, receives data or closes */ static void -TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, - void **fdcontext, short event) { - UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, - "TCP %u\t| Activity on the socket", (unsigned)fd); +TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_RegisteredFD *rfd, + short event) { + TCPConnectionManager *tcm = (TCPConnectionManager*)cm; + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Activity on the socket", (unsigned)rfd->fd); /* Write-Event, a new connection has opened. */ UA_StatusCode res = UA_STATUSCODE_GOOD; - if(event == UA_POLLOUT) { - UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, - "TCP %u\t| Opening a new connection", (unsigned)fd); + if(event == UA_FDEVENT_OUT) { + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Opening a new connection", (unsigned)rfd->fd); - /* The socket has opened. Signal it to the application. */ - cm->connectionCallback(cm, (uintptr_t)fd, fdcontext, + /* A new socket has opened. Signal it to the application. */ + cm->connectionCallback(cm, (uintptr_t)rfd->fd, &rfd->context, UA_STATUSCODE_GOOD, 0, NULL, UA_BYTESTRING_NULL); /* Now we are interested in read-events. */ - POSIX_EL_modifyFD((POSIX_EL*)cm->eventSource.eventLoop, fd, UA_POLLIN, - (UA_FDCallback)TCP_connectionSocketCallback, *fdcontext); + rfd->listenEvents = UA_FDEVENT_IN; + UA_EventLoopPOSIX_modifyFD(el, rfd); return; } - UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, - "TCP %u\t| Allocate receive buffer", (unsigned)fd); + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Allocate receive buffer", (unsigned)rfd->fd); /* Allocate the receive-buffer */ UA_ByteString response; - TCPConnectionManager *tcm = (TCPConnectionManager*)cm; res = UA_ByteString_allocBuffer(&response, tcm->recvBufferSize); if(res != UA_STATUSCODE_GOOD) return; /* Retry in the next iteration */ /* Receive */ #ifndef _WIN32 - ssize_t ret = UA_recv(fd, (char*)response.data, response.length, MSG_DONTWAIT); - UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, - "TCP %u\t| recv(...) returned %zd", (unsigned)fd, ret); + ssize_t ret = UA_recv(rfd->fd, (char*)response.data, response.length, MSG_DONTWAIT); #else - int ret = UA_recv(fd, (char*)response.data, response.length, MSG_DONTWAIT); - UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, - "TCP %u\t| recv(...) returned %d", (unsigned)fd, ret); + int ret = UA_recv(rfd->fd, (char*)response.data, response.length, MSG_DONTWAIT); #endif if(ret > 0) { - UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Received message of size %u", - (unsigned)fd, (unsigned)ret); + (unsigned)rfd->fd, (unsigned)ret); /* Callback to the application layer */ response.length = (size_t)ret; /* Set the length of the received buffer */ - cm->connectionCallback(cm, (uintptr_t)fd, fdcontext, + cm->connectionCallback(cm, (uintptr_t)rfd->fd, &rfd->context, UA_STATUSCODE_GOOD, 0, NULL, response); } else if(UA_ERRNO != UA_INTERRUPTED && UA_ERRNO != UA_WOULDBLOCK && @@ -174,15 +188,14 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, * then close the connection. We end up in this path after shutdown was * called on the socket. Here, we then are in the next EventLoop * iteration and the socket is known to be unused. */ - UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, - "TCP %u\t| recv signaled closed connection", (unsigned)fd); + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| recv signaled closed connection", (unsigned)rfd->fd); /* Close the connection if not a temporary error on a nonblocking socket */ - cm->connectionCallback(cm, (uintptr_t)fd, fdcontext, + cm->connectionCallback(cm, (uintptr_t)rfd->fd, &rfd->context, UA_STATUSCODE_BADCONNECTIONCLOSED, 0, NULL, UA_BYTESTRING_NULL); - TCP_close(cm, fd); + TCP_close(tcm, rfd); } UA_ByteString_clear(&response); @@ -190,16 +203,17 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_FD fd, /* Gets called when a new connection opens or if the listenSocket is closed */ static void -TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, - void **fdcontext, short event) { - UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, - "TCP %u\t| Callback on server socket", (unsigned)fd); +TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_RegisteredFD *rfd, + short event) { + TCPConnectionManager *tcm = (TCPConnectionManager*)cm; + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Callback on server socket", (unsigned)rfd->fd); /* Try to accept a new connection */ struct sockaddr_storage remote; socklen_t remote_size = sizeof(remote); - UA_FD newsockfd = UA_accept(fd, (struct sockaddr*)&remote, &remote_size); + UA_FD newsockfd = UA_accept(rfd->fd, (struct sockaddr*)&remote, &remote_size); if(newsockfd == UA_INVALID_FD) { /* Temporary error -- retry */ if(UA_ERRNO == UA_INTERRUPTED) @@ -208,12 +222,11 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, /* Close the listen socket */ if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPING) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Error %s, closing the server socket", - (unsigned)fd, errno_str)); + (unsigned)rfd->fd, errno_str)); } - TCP_close(cm, fd); + TCP_close(tcm, rfd); return; } @@ -231,13 +244,13 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| getnameinfo(...) could not resolve the " - "hostname (%s)", (unsigned)fd, errno_str)); + "hostname (%s)", (unsigned)rfd->fd, errno_str)); } } UA_LOG_INFO(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Connection opened from \"%s\" via the server socket %u", - (unsigned)newsockfd, hoststr, (unsigned)fd); + (unsigned)newsockfd, hoststr, (unsigned)rfd->fd); #endif /* Configure the new socket */ @@ -256,30 +269,45 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_FD fd, return; } - void *ctx = cm->initialConnectionContext; - /* The socket has opened. Signal it to the application. The callback can - * switch out the context. So put it into a temp variable. */ - cm->connectionCallback(cm, (uintptr_t)newsockfd, &ctx, UA_STATUSCODE_GOOD, - 0, NULL, UA_BYTESTRING_NULL); + /* Allocate the UA_RegisteredFD */ + UA_RegisteredFD *newrfd = (UA_RegisteredFD*) + (UA_RegisteredFD*)UA_malloc(sizeof(UA_RegisteredFD)); + if(!newrfd) { + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Error allocating memory for the socket, closing", + (unsigned)newsockfd); + UA_close(newsockfd); + return; + } + + newrfd->fd = newsockfd; + newrfd->es = &cm->eventSource; + newrfd->callback = (UA_FDCallback)TCP_connectionSocketCallback; + newrfd->context = cm->initialConnectionContext; + newrfd->listenEvents = UA_FDEVENT_IN; /* Register in the EventLoop. Signal to the user if registering failed. */ - res = POSIX_EL_registerFD((POSIX_EL*)cm->eventSource.eventLoop, newsockfd, - UA_POLLIN, (UA_FDCallback)TCP_connectionSocketCallback, - &cm->eventSource, ctx); + res = TCPConnectionManager_register(tcm, newrfd); if(res != UA_STATUSCODE_GOOD) { - cm->connectionCallback(cm, (uintptr_t)newsockfd, &ctx, - UA_STATUSCODE_BADINTERNALERROR, - 0, NULL, UA_BYTESTRING_NULL); + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Error registering the socket, closing", + (unsigned)newsockfd); + UA_free(newrfd); UA_close(newsockfd); + return; } - /* Increase the count of registered fd */ - TCPConnectionManager *tcm = (TCPConnectionManager*)cm; - tcm->fdCount++; + /* The socket has opened. Signal it to the application. The callback can + * switch out the context. So put it into a temp variable. */ + cm->connectionCallback(cm, (uintptr_t)newsockfd, &newrfd->context, + UA_STATUSCODE_GOOD, 0, NULL, UA_BYTESTRING_NULL); } static void TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { + TCPConnectionManager *tcm = (TCPConnectionManager*)cm; + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; + /* Get logging information */ char hoststr[256]; char portstr[16]; @@ -295,8 +323,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { hoststr[0] = 0; portstr[0] = 0; UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP\t| getnameinfo(...) could not resolve the hostname (%s)", errno_str)); } @@ -306,16 +333,14 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { UA_FD listenSocket = UA_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if(listenSocket == UA_INVALID_FD) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Error opening the listen socket for " "\"%s\" on port %s(%s)", (unsigned)listenSocket, hoststr, portstr, errno_str)); return; } - UA_LOG_INFO(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| New server socket for \"%s\" on port %s", (unsigned)listenSocket, hoststr, portstr); @@ -327,8 +352,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { if(ai->ai_family == AF_INET6 && UA_setsockopt(listenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&optval, sizeof(optval)) == -1) { - UA_LOG_WARNING(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Could not set an IPv6 socket to IPv6 only, closing", (unsigned)listenSocket); UA_close(listenSocket); @@ -339,8 +363,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { /* Allow rebinding to the IP/port combination. Eg. to restart the server. */ if(UA_setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof(optval)) == -1) { - UA_LOG_WARNING(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Could not make the socket reusable, closing", (unsigned)listenSocket); UA_close(listenSocket); @@ -349,8 +372,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { /* Set the socket non-blocking */ if(TCP_setNonBlocking(listenSocket) != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Could not set the socket non-blocking, closing", (unsigned)listenSocket); UA_close(listenSocket); @@ -359,8 +381,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { /* Supress interrupts from the socket */ if(TCP_setNoSigPipe(listenSocket) != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Could not disable SIGPIPE, closing", (unsigned)listenSocket); UA_close(listenSocket); @@ -371,8 +392,7 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { int ret = UA_bind(listenSocket, ai->ai_addr, (socklen_t)ai->ai_addrlen); if(ret < 0) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Error binding the socket to the address (%s), closing", (unsigned)listenSocket, errno_str)); UA_close(listenSocket); @@ -382,31 +402,40 @@ TCP_registerListenSocket(UA_ConnectionManager *cm, struct addrinfo *ai) { /* Start listening */ if(UA_listen(listenSocket, UA_MAXBACKLOG) < 0) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Error listening on the socket (%s), closing", (unsigned)listenSocket, errno_str)); UA_close(listenSocket); return; } - /* Register the socket */ - UA_StatusCode res = - POSIX_EL_registerFD((POSIX_EL*)cm->eventSource.eventLoop, listenSocket, - UA_POLLIN, (UA_FDCallback)TCP_listenSocketCallback, - &cm->eventSource, NULL); - if(res != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, - "TCP %u\t| Error registering the socket in the " - "EventLoop, closing", (unsigned)listenSocket); + /* Allocate the UA_RegisteredFD */ + UA_RegisteredFD *newrfd = (UA_RegisteredFD*) + (UA_RegisteredFD*)UA_malloc(sizeof(UA_RegisteredFD)); + if(!newrfd) { + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Error allocating memory for the socket, closing", + (unsigned)listenSocket); UA_close(listenSocket); return; } - /* Increase the registered fd count */ - TCPConnectionManager *tcm = (TCPConnectionManager*)cm; - tcm->fdCount++; + newrfd->fd = listenSocket; + newrfd->es = &cm->eventSource; + newrfd->callback = (UA_FDCallback)TCP_listenSocketCallback; + newrfd->context = cm->initialConnectionContext; + newrfd->listenEvents = UA_FDEVENT_IN; + + /* Register in the EventLoop */ + UA_StatusCode res = TCPConnectionManager_register(tcm, newrfd); + if(res != UA_STATUSCODE_GOOD) { + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Error registering the socket, closing", + (unsigned)listenSocket); + UA_free(newrfd); + UA_close(listenSocket); + return; + } } static UA_StatusCode @@ -460,16 +489,14 @@ TCP_shutdownConnection(UA_ConnectionManager *cm, uintptr_t connectionId) { #else int res = UA_shutdown((UA_FD)connectionId, SD_BOTH); #endif - UA_StatusCode retval = UA_STATUSCODE_GOOD; if(res != 0) { UA_LOG_SOCKET_ERRNO_WRAP( UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, - "TCP %u\t| Error shutting down the socket (%s), closing", + "TCP %u\t| Error shutting down the socket (%s)", (unsigned)connectionId, errno_str)); - retval = TCP_close(cm, (UA_FD)connectionId); } - return retval; + return UA_STATUSCODE_GOOD; } static UA_StatusCode @@ -515,7 +542,17 @@ TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, int poll_ret; do { poll_ret = UA_poll(&tmp_poll_fd, 1, 100); - } while(poll_ret == 0 || (poll_ret < 0 && UA_ERRNO == UA_INTERRUPTED)); + if(poll_ret < 0 && UA_ERRNO != UA_INTERRUPTED) { + UA_LOG_SOCKET_ERRNO_GAI_WRAP( + UA_LOG_ERROR(cm->eventSource.eventLoop->logger, + UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Send failed with error %s", + (unsigned)connectionId, errno_str)); + TCP_shutdownConnection(cm, connectionId); + UA_ByteString_clear(buf); + return UA_STATUSCODE_BADCONNECTIONCLOSED; + } + } while(poll_ret <= 0); } } while(n < 0); nWritten += (size_t)n; @@ -530,6 +567,8 @@ static UA_StatusCode TCP_openConnection(UA_ConnectionManager *cm, size_t paramsSize, const UA_KeyValuePair *params, void *context) { + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; + /* Get the connection parameters */ char hostname[256]; char portStr[16]; @@ -540,8 +579,7 @@ TCP_openConnection(UA_ConnectionManager *cm, UA_QUALIFIEDNAME(0, "target-port"), &UA_TYPES[UA_TYPES_UINT16]); if(!port) { - UA_LOG_ERROR(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Open TCP Connection: No target port defined, aborting"); return UA_STATUSCODE_BADINTERNALERROR; } @@ -553,23 +591,20 @@ TCP_openConnection(UA_ConnectionManager *cm, UA_QUALIFIEDNAME(0, "target-hostname"), &UA_TYPES[UA_TYPES_STRING]); if(!host) { - UA_LOG_ERROR(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Open TCP Connection: No target hostname defined, aborting"); return UA_STATUSCODE_BADINTERNALERROR; } if(host->length >= 256) { - UA_LOG_ERROR(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "TCP\t| Open TCP Connection: No target hostname too long, aborting"); return UA_STATUSCODE_BADINTERNALERROR; } strncpy(hostname, (const char*)host->data, host->length); hostname[host->length] = 0; - UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, "TCP\t| Open a connection to \"%s\" on port %s", - hostname, portStr); + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP\t| Open a connection to \"%s\" on port %s", hostname, portStr); /* Create the socket description from the connectString * TODO: Make this non-blocking */ @@ -580,8 +615,7 @@ TCP_openConnection(UA_ConnectionManager *cm, int error = getaddrinfo(hostname, portStr, &hints, &info); if(error != 0) { UA_LOG_SOCKET_ERRNO_GAI_WRAP( - UA_LOG_WARNING(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Lookup of %s failed with error %d - %s", hostname, error, errno_str)); return UA_STATUSCODE_BADINTERNALERROR; @@ -592,8 +626,7 @@ TCP_openConnection(UA_ConnectionManager *cm, if(newSock == UA_INVALID_FD) { freeaddrinfo(info); UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Could not create socket to connect to %s (%s)", hostname, errno_str)); return UA_STATUSCODE_BADDISCONNECT; @@ -606,8 +639,7 @@ TCP_openConnection(UA_ConnectionManager *cm, res |= TCP_setNoNagle(newSock); if(res != UA_STATUSCODE_GOOD) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_WARNING(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Could not set socket options: %s", errno_str)); freeaddrinfo(info); UA_close(newSock); @@ -621,31 +653,42 @@ TCP_openConnection(UA_ConnectionManager *cm, UA_ERRNO != UA_INPROGRESS && UA_ERRNO != UA_WOULDBLOCK) { UA_LOG_SOCKET_ERRNO_WRAP( - UA_LOG_ERROR(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, - "TCP\t| Connecting the socket to %s failed (%s)", - hostname, errno_str)); + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP\t| Connecting the socket to %s failed (%s)", + hostname, errno_str)); return UA_STATUSCODE_BADDISCONNECT; } + /* Allocate the UA_RegisteredFD */ + UA_RegisteredFD *newrfd = (UA_RegisteredFD*) + (UA_RegisteredFD*)UA_malloc(sizeof(UA_RegisteredFD)); + if(!newrfd) { + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Error allocating memory for the socket, closing", + (unsigned)newSock); + UA_close(newSock); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + + newrfd->fd = newSock; + newrfd->es = &cm->eventSource; + newrfd->callback = (UA_FDCallback)TCP_connectionSocketCallback; + newrfd->context = context; + newrfd->listenEvents = UA_FDEVENT_OUT; /* Switched to _IN once the + * connection is open */ + /* Register the fd to trigger when output is possible (the connection is open) */ - res = POSIX_EL_registerFD((POSIX_EL*)cm->eventSource.eventLoop, newSock, - UA_POLLOUT, (UA_FDCallback)TCP_connectionSocketCallback, - &cm->eventSource, context); + TCPConnectionManager *tcm = (TCPConnectionManager*)cm; + res = TCPConnectionManager_register(tcm, newrfd); if(res != UA_STATUSCODE_GOOD) { - UA_LOG_ERROR(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, - "TCP\t| Registering the socket to connect to %s failed", hostname); + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP\t| Registering the socket to connect to %s failed", hostname); UA_close(newSock); + UA_free(newrfd); return res; } - /* Increase the count */ - TCPConnectionManager *tcm = (TCPConnectionManager*)cm; - tcm->fdCount++; - - UA_LOG_INFO(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, + UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| New connection to \"%s\" on port %s", (unsigned)newSock, hostname, portStr); @@ -655,10 +698,12 @@ TCP_openConnection(UA_ConnectionManager *cm, /* Asynchronously register the listenSocket */ static UA_StatusCode TCP_eventSourceStart(UA_ConnectionManager *cm) { + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; + /* Check the state */ if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPED) { - UA_LOG_ERROR(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_EVENTLOOP, "To start the TCP ConnectionManager, " + UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "To start the TCP ConnectionManager, " "it has to be registered in an EventLoop and not started"); return UA_STATUSCODE_BADINTERNALERROR; } @@ -676,8 +721,7 @@ TCP_eventSourceStart(UA_ConnectionManager *cm) { UA_QUALIFIEDNAME(0, "listen-port"), &UA_TYPES[UA_TYPES_UINT16]); if(!port) { - UA_LOG_ERROR(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP\t| No port configured, don't accept connections"); return UA_STATUSCODE_GOOD; } @@ -693,13 +737,12 @@ TCP_eventSourceStart(UA_ConnectionManager *cm) { UA_QUALIFIEDNAME(0, "listen-hostnames")); if(!hostNames) { /* No hostnames configured */ - UA_LOG_INFO(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, "TCP\t| Listening on all interfaces"); + UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP\t| Listening on all interfaces"); TCP_registerListenSocketDomainName(cm, NULL, portno); } else if(hostNames->type != &UA_TYPES[UA_TYPES_STRING]) { /* Wrong datatype */ - UA_LOG_ERROR(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_EVENTLOOP, + UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "TCP\t| The hostnames have to be strings"); return UA_STATUSCODE_BADINTERNALERROR; } else { @@ -707,8 +750,8 @@ TCP_eventSourceStart(UA_ConnectionManager *cm) { if(UA_Variant_isScalar(hostNames)) interfaces = 1; if(interfaces == 0) { - UA_LOG_ERROR(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_EVENTLOOP, "TCP\t| Listening on all interfaces"); + UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "TCP\t| Listening on all interfaces"); TCP_registerListenSocketDomainName(cm, NULL, portno); } else { /* Iterate over the configured hostnames */ @@ -738,28 +781,27 @@ TCP_eventSourceStart(UA_ConnectionManager *cm) { return UA_STATUSCODE_GOOD; } -static UA_Boolean -TCP_shutdownCallback(UA_EventSource *es, UA_FD fd, - void *fdContext, void *iterateContext) { - TCP_shutdownConnection((UA_ConnectionManager*)es, (uintptr_t)fd); - return false; -} - static void TCP_eventSourceStop(UA_ConnectionManager *cm) { + TCPConnectionManager *tcm = (TCPConnectionManager*)cm; UA_LOG_INFO(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Shutting down the ConnectionManager"); /* Shut down all registered fd. The cm is set to "stopped" when the last fd * is closed and deregistered in the callback from the EventLoop. */ - POSIX_EL_iterateFD((POSIX_EL*)cm->eventSource.eventLoop, - &cm->eventSource, TCP_shutdownCallback, NULL); + UA_RegisteredFD *rfd, *rfd_tmp; + LIST_FOREACH_SAFE(rfd, &tcm->fds, es_pointers, rfd_tmp) { + if(rfd->callback == (UA_FDCallback)TCP_listenSocketCallback) { + TCP_close(tcm, rfd); /* Listen sockets are immediately closed. + * shutdown is unsupported for them on win32 */ + } else { + TCP_shutdownConnection(cm, (uintptr_t)rfd->fd); + } + } cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPING; - TCPConnectionManager *tcm = (TCPConnectionManager*)cm; - /* Closed? */ - if(tcm->fdCount == 0 && cm->eventSource.state == UA_EVENTSOURCESTATE_STOPPING) + if(tcm->fdsSize == 0 && cm->eventSource.state == UA_EVENTSOURCESTATE_STOPPING) cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPED; UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dbac6e9585f..64035cafd1d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -69,6 +69,8 @@ endif() set(test_plugin_sources ${PROJECT_SOURCE_DIR}/arch/network_tcp.c ${PROJECT_SOURCE_DIR}/arch/eventloop_posix.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_select.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_epoll.c ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_tcp.c ${PROJECT_SOURCE_DIR}/tests/testing-plugins/testing_clock.c ${PROJECT_SOURCE_DIR}/plugins/ua_log_stdout.c diff --git a/tests/check_eventloop_tcp.c b/tests/check_eventloop_tcp.c index 2a8febbd1df..c7024e4e390 100644 --- a/tests/check_eventloop_tcp.c +++ b/tests/check_eventloop_tcp.c @@ -197,7 +197,7 @@ START_TEST(connectTCP) { UA_StatusCode retval = cm->openConnection(cm, 2, params, (void*)0x01); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); - for(size_t i = 0; i < 10; i++) { + for(size_t i = 0; i < 2; i++) { UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); } @@ -212,7 +212,7 @@ START_TEST(connectTCP) { memcpy(snd.data, testMsg, strlen(testMsg)); retval = cm->sendWithConnection(cm, clientId, 0, NULL, &snd); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); - for(size_t i = 0; i < 10; i++) { + for(size_t i = 0; i < 2; i++) { UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); } @@ -222,14 +222,14 @@ START_TEST(connectTCP) { retval = cm->closeConnection(cm, clientId); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); ck_assert_uint_eq(connCount, 2); - for(size_t i = 0; i < 10; i++) { + for(size_t i = 0; i < 2; i++) { UA_DateTime next = el->run(el, 1); UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); } ck_assert_uint_eq(connCount, 0); /* Stop the EventLoop */ - int max_stop_iteration_count = 1000; + int max_stop_iteration_count = 10; int iteration = 0; /* Stop the EventLoop */ el->stop(el); @@ -239,6 +239,7 @@ START_TEST(connectTCP) { UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); iteration++; } + ck_assert(el->state == UA_EVENTLOOPSTATE_STOPPED); el->free(el); el = NULL; } END_TEST From 0c1cc5aeb27e60d5d2ce0db48755ee796ab6394c Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 26 Dec 2021 23:45:22 +0100 Subject: [PATCH 0038/1963] feat(el): Add InterruptManager API and a POSIX signal implementation --- CMakeLists.txt | 1 + arch/eventloop_posix_interrupt.c | 480 +++++++++++++++++++++++++++ include/open62541/plugin/eventloop.h | 14 +- tests/CMakeLists.txt | 5 + tests/check_eventloop_interrupt.c | 97 ++++++ tests/fuzz/CMakeLists.txt | 3 + 6 files changed, 598 insertions(+), 2 deletions(-) create mode 100644 arch/eventloop_posix_interrupt.c create mode 100644 tests/check_eventloop_interrupt.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fb55eb3951..cc63c3eae3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,6 +125,7 @@ set(ua_architecture_sources ${ua_architecture_sources} ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_select.c ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_epoll.c ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_tcp.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_interrupt.c ) set(ua_architecture_headers ${ua_architecture_headers} diff --git a/arch/eventloop_posix_interrupt.c b/arch/eventloop_posix_interrupt.c new file mode 100644 index 00000000000..1f19e3b2aef --- /dev/null +++ b/arch/eventloop_posix_interrupt.c @@ -0,0 +1,480 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + */ + +#include "eventloop_posix.h" +#include + +/* Different implementation approaches: + * - Linux: Use signalfd + * - Other: Use the self-pipe trick (http://cr.yp.to/docs/selfpipe.html) */ + +struct UA_RegisteredSignal; +typedef struct UA_RegisteredSignal UA_RegisteredSignal; + +struct UA_RegisteredSignal { + UA_RegisteredFD rfd; + + LIST_ENTRY(UA_RegisteredSignal) signalsEntry; /* List in the InterruptManager */ + TAILQ_ENTRY(UA_RegisteredSignal) triggeredEntry; + + UA_Boolean active; /* Signals are only active when the EventLoop is started */ + UA_Boolean triggered; + + int signal; /* POSIX identifier of the interrupt signal */ + UA_InterruptCallback signalCallback; +}; + +typedef struct { + UA_InterruptManager im; + LIST_HEAD(, UA_RegisteredSignal) signals; +#ifndef UA_HAVE_EPOLL + UA_RegisteredFD readFD; + UA_FD writeFD; + TAILQ_HEAD(, UA_RegisteredSignal) triggered; +#endif +} POSIXInterruptManager; + +#ifndef UA_HAVE_EPOLL +/* On non-linux systems we can have at most one interrupt manager */ +static POSIXInterruptManager *singletonIM = NULL; +#endif + +/* The following methods have to be implemented for epoll/self-pipe each. */ +static void activateSignal(UA_RegisteredSignal *rs); +static void deactivateSignal(UA_RegisteredSignal *rs); + +#ifdef UA_HAVE_EPOLL +#include + +static void +handlePOSIXInterruptEvent(UA_EventSource *es, UA_RegisteredFD *rfd, short event) { + UA_RegisteredSignal *rs = (UA_RegisteredSignal*)rfd; + struct signalfd_siginfo fdsi; + ssize_t s = read(rfd->fd, &fdsi, sizeof(fdsi)); + if(s < (ssize_t)sizeof(fdsi)) { + /* A problem occured */ + deactivateSignal(rs); + return; + } + + /* Signal received */ + UA_LOG_DEBUG(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, + "Interrupt %u\t| Received a signal %u", + (unsigned)rfd->fd, fdsi.ssi_signo); + + rs->signalCallback((UA_InterruptManager *)es, + (uintptr_t)rfd->fd, rfd->context, 0, NULL); +} + +static void +activateSignal(UA_RegisteredSignal *rs) { + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)rs->rfd.es->eventLoop; + if(rs->active) + return; + + /* Block the normal signal handling */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, rs->signal); + int res2 = sigprocmask(SIG_BLOCK, &mask, NULL); + if(res2 == -1) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "Interrupt\t| Could not block the default " + "signal handling with an error: %s", + errno_str)); + return; + } + + /* Create the fd */ + UA_FD newfd = signalfd(-1, &mask, 0); + if(newfd < 0) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "Interrupt\t|Could not create a signal file " + "description with error: %s", + errno_str)); + sigprocmask(SIG_UNBLOCK, &mask, NULL); /* restore signal */ + return; + } + + rs->rfd.fd = newfd; + rs->rfd.callback = handlePOSIXInterruptEvent; + rs->rfd.listenEvents = UA_FDEVENT_IN; + + /* Register the fd in the EventLoop */ + UA_StatusCode res = UA_EventLoopPOSIX_registerFD(el, &rs->rfd); + if(res != UA_STATUSCODE_GOOD) { + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "Interrupt\t|Could not register the a signal file " + "description in the EventLoop"); + UA_close(newfd); + sigprocmask(SIG_UNBLOCK, &mask, NULL); /* restore signal */ + return; + } + + rs->active = true; +} + +static void +deactivateSignal(UA_RegisteredSignal *rs) { + /* Only dectivate if active */ + if(!rs->active) + return; + rs->active = false; + + /* Stop receiving the signal on the FD */ + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)rs->rfd.es->eventLoop; + UA_EventLoopPOSIX_deregisterFD(el, &rs->rfd); + + /* Unblock the signal */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, (int)rs->signal); + sigprocmask(SIG_UNBLOCK, &mask, NULL); + + /* Clean up locally */ + UA_close(rs->rfd.fd); +} + +#else /* !UA_HAVE_EPOLL */ + +static void +triggerPOSIXInterruptEvent(int sig) { + UA_assert(singletonIM != NULL); + + /* Don't call the interrupt callback right now. + * Instead, add to the triggered list and call from the EventLoop. */ + UA_RegisteredSignal *rs; + LIST_FOREACH(rs, &singletonIM->signals, signalsEntry) { + if(rs->signal == sig) { + if(rs->triggered) + break; /* A signal can be only once in the triggered list -> is there + already */ + + TAILQ_INSERT_TAIL(&singletonIM->triggered, rs, triggeredEntry); + rs->triggered = true; + break; + } + } + +#ifdef _WIN32 + /* On WIN32 we have to re-arm the signal or it will go back to SIG_DFL */ + signal(sig, triggerPOSIXInterruptEvent); +#endif + + /* Trigger the FD in the EventLoop for the self-pipe trick */ +#ifdef _WIN32 + int err = send(singletonIM->writeFD, ".", 1, 0); +#else + ssize_t err = write(singletonIM->writeFD, ".", 1); +#endif + if(err <= 0) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(singletonIM->im.eventSource.eventLoop->logger, + UA_LOGCATEGORY_EVENTLOOP, + "Error signaling the interrupt on FD %u to the EventLoop (%s)", + (unsigned)singletonIM->writeFD, errno_str)); + } +} + +static void +activateSignal(UA_RegisteredSignal *rs) { + UA_assert(singletonIM != NULL); + + /* Already active? */ + if(rs->active) + return; + + /* Register the signal on the OS level */ + void (*prev)(int); + prev = signal(rs->signal, triggerPOSIXInterruptEvent); + if(prev == SIG_ERR) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(singletonIM->im.eventSource.eventLoop->logger, + UA_LOGCATEGORY_EVENTLOOP, + "Error registering the signal: %s", errno_str)); + return; + } + + rs->active = true; +} + +static void +deactivateSignal(UA_RegisteredSignal *rs) { + /* Only dectivate if active */ + if(!rs->active) + return; + rs->active = false; + + /* Stop receiving the signal */ + signal(rs->signal, SIG_DFL); + + /* Clean up locally */ + if(rs->triggered) { + TAILQ_REMOVE(&singletonIM->triggered, rs, triggeredEntry); + rs->triggered = false; + } +} + +/* Execute all triggered interrupts via the self-pipe trick from within the EventLoop */ +static void +executeTriggeredPOSIXInterrupts(UA_EventSource *es, UA_RegisteredFD *rfd, short event) { + /* Re-arm the socket for the next signal by reading from it */ + char buf[128]; +#ifdef _WIN32 + recv(rfd->fd, buf, 128, 0); /* ignore the result */ +#else + ssize_t i; + do { + i = read(rfd->fd, buf, 128); + } while(i > 0); +#endif + + UA_RegisteredSignal *rs, *rs_tmp; + TAILQ_FOREACH_SAFE(rs, &singletonIM->triggered, triggeredEntry, rs_tmp) { + TAILQ_REMOVE(&singletonIM->triggered, rs, triggeredEntry); + rs->triggered = false; + rs->signalCallback(&singletonIM->im, (uintptr_t)rs->signal, + rs->rfd.context, 0, NULL); + } +} + +#endif /* !UA_HAVE_EPOLL */ + +static UA_StatusCode +registerPOSIXInterrupt(UA_InterruptManager *im, uintptr_t interruptHandle, + size_t paramsSize, const UA_KeyValuePair *params, + UA_InterruptCallback callback, void *interruptContext) { + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)im->eventSource.eventLoop; + if(paramsSize > 0) { + UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "Interrupt\t| Supplied parameters invalid for the " + "POSIX InterruptManager"); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Was the signal already registered? */ + POSIXInterruptManager *pim = (POSIXInterruptManager *)im; + UA_RegisteredSignal *rs; + LIST_FOREACH(rs, &pim->signals, signalsEntry) { + if(rs->signal == (int)interruptHandle) { + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, + "Interrupt\t| Signal %u already registered", + (unsigned)interruptHandle); + return UA_STATUSCODE_BADINTERNALERROR; + } + } + + /* Create and populate the new context object */ + rs = (UA_RegisteredSignal *)UA_calloc(1, sizeof(UA_RegisteredSignal)); + if(!rs) + return UA_STATUSCODE_BADOUTOFMEMORY; + rs->signal = (int)interruptHandle; + rs->signalCallback = callback; + rs->rfd.es = &im->eventSource; + rs->rfd.context = interruptContext; + + /* Add to the InterruptManager */ + LIST_INSERT_HEAD(&pim->signals, rs, signalsEntry); + + /* Activate if we are already running */ + if(pim->im.eventSource.state == UA_EVENTSOURCESTATE_STARTED) + activateSignal(rs); + + return UA_STATUSCODE_GOOD; +} + +static void +deregisterPOSIXInterrupt(UA_InterruptManager *im, uintptr_t interruptHandle) { + POSIXInterruptManager *pim = (POSIXInterruptManager *)im; + UA_RegisteredSignal *rs; + LIST_FOREACH(rs, &pim->signals, signalsEntry) { + if(rs->rfd.fd == (UA_FD)interruptHandle) { + deactivateSignal(rs); + LIST_REMOVE(rs, signalsEntry); + UA_free(rs); + return; + } + } +} + +#ifdef _WIN32 +/* Windows has no pipes. Use a local TCP connection for the self-pipe trick. + * https://stackoverflow.com/a/3333565 */ +static int +pair(SOCKET fds[2]) { + struct sockaddr_in inaddr; + struct sockaddr addr; + SOCKET lst = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + memset(&inaddr, 0, sizeof(inaddr)); + memset(&addr, 0, sizeof(addr)); + inaddr.sin_family = AF_INET; + inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + inaddr.sin_port = 0; + int yes = 1; + setsockopt(lst, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)); + bind(lst, (struct sockaddr *)&inaddr, sizeof(inaddr)); + listen(lst, 1); + int len = sizeof(inaddr); + getsockname(lst, &addr, &len); + fds[0] = socket(AF_INET, SOCK_STREAM, 0); + int err = connect(fds[0], &addr, len); + fds[1] = accept(lst, 0, 0); + closesocket(lst); + return err; +} +#endif + +static UA_StatusCode +startPOSIXInterruptManager(UA_EventSource *es) { + /* Check the state */ + if(es->state != UA_EVENTSOURCESTATE_STOPPED) { + UA_LOG_ERROR(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, + "Interrupt\t| To start the InterruptManager, " + "it has to be registered in an EventLoop and not started"); + return UA_STATUSCODE_BADINTERNALERROR; + } + +#ifndef UA_HAVE_EPOLL + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)es->eventLoop; + /* Set the global pointer */ + if(singletonIM != NULL) { + UA_LOG_ERROR(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, + "Interrupt\t| There can be at most one active " + "InterruptManager at a time"); + return UA_STATUSCODE_BADINTERNALERROR; + } +#endif + + POSIXInterruptManager *pim = (POSIXInterruptManager *)es; + UA_LOG_DEBUG(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, + "Interrupt\t| Starting the InterruptManager"); + +#ifndef UA_HAVE_EPOLL + /* Create pipe for self-signaling */ + UA_FD pipefd[2]; +#ifdef _WIN32 + int err = pair(pipefd); +#else + int err = pipe2(pipefd, O_NONBLOCK); +#endif + if(err != 0) { + UA_LOG_SOCKET_ERRNO_WRAP( + UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "Interrupt\t| Could not open the pipe for " + "self-signaling (%s)", errno_str)); + return UA_STATUSCODE_BADINTERNALERROR; + } + + UA_LOG_DEBUG(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, + "Interrupt\t| Socket pair for the self-pipe: %u,%u", + (unsigned)pipefd[0], (unsigned)pipefd[1]); + + pim->writeFD = pipefd[1]; + pim->readFD.fd = pipefd[0]; + pim->readFD.context = pim; + pim->readFD.listenEvents = UA_FDEVENT_IN; + pim->readFD.callback = executeTriggeredPOSIXInterrupts; + UA_StatusCode res = UA_EventLoopPOSIX_registerFD(el, &pim->readFD); + if(res != UA_STATUSCODE_GOOD) { + UA_LOG_ERROR(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, + "Interrupt\t| Could not register the InterruptManager socket"); + UA_close(pipefd[0]); + UA_close(pipefd[1]); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Register the singleton pointer */ + singletonIM = pim; +#endif + + /* Activate the registered signal handlers */ + UA_RegisteredSignal *rs; + LIST_FOREACH(rs, &pim->signals, signalsEntry) { + activateSignal(rs); + } + + /* Set the EventSource to the started state */ + es->state = UA_EVENTSOURCESTATE_STARTED; + return UA_STATUSCODE_GOOD; +} + +static void +stopPOSIXInterruptManager(UA_EventSource *es) { + if(es->state != UA_EVENTSOURCESTATE_STARTED) + return; + + UA_LOG_DEBUG(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, + "Interrupt\t| Stopping the InterruptManager"); + + /* Close all registered signals */ + POSIXInterruptManager *pim = (POSIXInterruptManager *)es; + UA_RegisteredSignal *rs; + LIST_FOREACH(rs, &pim->signals, signalsEntry) { + deactivateSignal(rs); + } + +#ifndef UA_HAVE_EPOLL + /* Close the FD for the self-pipe trick */ + UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)es->eventLoop; + UA_EventLoopPOSIX_deregisterFD(el, &pim->readFD); + UA_close(pim->readFD.fd); + UA_close(pim->writeFD); + + /* Reset the global pointer */ + singletonIM = NULL; +#endif + + /* Immediately set to stopped */ + es->state = UA_EVENTSOURCESTATE_STOPPED; +} + +static UA_StatusCode +freePOSIXInterruptmanager(UA_EventSource *es) { + if(es->state >= UA_EVENTSOURCESTATE_STARTING) { + UA_LOG_ERROR(es->eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, + "Interrupt\t| The EventSource must be stopped " + "before it can be deleted"); + return UA_STATUSCODE_BADINTERNALERROR; + } + + POSIXInterruptManager *pim = (POSIXInterruptManager *)es; + UA_RegisteredSignal *rs, *rs_tmp; + LIST_FOREACH_SAFE(rs, &pim->signals, signalsEntry, rs_tmp) { + deactivateSignal(rs); + LIST_REMOVE(rs, signalsEntry); + UA_free(rs); + } + + UA_String_clear(&es->name); + UA_free(es); + return UA_STATUSCODE_GOOD; +} + +UA_InterruptManager * +UA_InterruptManager_new_POSIX(const UA_String eventSourceName) { + POSIXInterruptManager *pim = + (POSIXInterruptManager *)UA_calloc(1, sizeof(POSIXInterruptManager)); + if(!pim) + return NULL; + + LIST_INIT(&pim->signals); +#ifndef UA_HAVE_EPOLL + TAILQ_INIT(&pim->triggered); +#endif + + UA_InterruptManager *im = &pim->im; + im->eventSource.eventSourceType = UA_EVENTSOURCETYPE_INTERRUPTMANAGER; + UA_String_copy(&eventSourceName, &im->eventSource.name); + im->eventSource.start = startPOSIXInterruptManager; + im->eventSource.stop = stopPOSIXInterruptManager; + im->eventSource.free = freePOSIXInterruptmanager; + im->registerInterrupt = registerPOSIXInterrupt; + im->deregisterInterrupt = deregisterPOSIXInterrupt; + return im; +} diff --git a/include/open62541/plugin/eventloop.h b/include/open62541/plugin/eventloop.h index 4f5a5642bb2..82f91cd3c7c 100644 --- a/include/open62541/plugin/eventloop.h +++ b/include/open62541/plugin/eventloop.h @@ -38,7 +38,7 @@ typedef struct UA_InterruptManager UA_InterruptManager; * these. Hence, several applications can share an EventLoop. * * The EventLoop and the ConnectionManager implementation is - * architecture-specific. The goal is to have a single call to "select" (epoll, + * architecture-specific. The goal is to have a single call to "poll" (epoll, * kqueue, ...) in the EventLoop that covers all ConnectionManagers. Hence the * EventLoop plugin implementation must know implementation details of the * ConnectionManager implementations. So the EventLoop can extract socket @@ -166,7 +166,8 @@ typedef enum { * looked up via UA_EventLoop_findEventSource). */ typedef enum { UA_EVENTSOURCETYPE_ANY = 0, - UA_EVENTSOURCETYPE_CONNECTIONMANAGER + UA_EVENTSOURCETYPE_CONNECTIONMANAGER, + UA_EVENTSOURCETYPE_INTERRUPTMANAGER } UA_EventSourceType; struct UA_EventSource { @@ -391,6 +392,15 @@ UA_EventLoop_new_POSIX(const UA_Logger *logger); UA_EXPORT UA_ConnectionManager * UA_ConnectionManager_new_POSIX_TCP(const UA_String eventSourceName); +/** + * Signal Interrupt Manager + * ~~~~~~~~~~~~~~~~~~~~~~~~ + * Create an instance of the interrupt manager that handles POSX signals. This + * interrupt manager takes the numerical interrupt identifiers from + * for the interruptHandle. */ +UA_EXPORT UA_InterruptManager * +UA_InterruptManager_new_POSIX(const UA_String eventSourceName); + #endif /* defined(UA_ARCHITECTURE_POSIX) || defined(UA_ARCHITECTURE_WIN32) */ _UA_END_DECLS diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 64035cafd1d..6a3811a53bf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -72,6 +72,7 @@ set(test_plugin_sources ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_select.c ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_epoll.c ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_tcp.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_interrupt.c ${PROJECT_SOURCE_DIR}/tests/testing-plugins/testing_clock.c ${PROJECT_SOURCE_DIR}/plugins/ua_log_stdout.c ${PROJECT_SOURCE_DIR}/plugins/ua_config_default.c @@ -272,6 +273,10 @@ add_executable(check_eventloop_tcp check_eventloop_tcp.c $ $) +target_link_libraries(check_eventloop_interrupt ${LIBS}) +add_test_valgrind(eventloop_interrupt ${TESTS_BINARY_DIR}/check_eventloop_interrupt) + # Test Server add_executable(check_accesscontrol server/check_accesscontrol.c $ $) diff --git a/tests/check_eventloop_interrupt.c b/tests/check_eventloop_interrupt.c new file mode 100644 index 00000000000..945cf1fbf51 --- /dev/null +++ b/tests/check_eventloop_interrupt.c @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include "open62541/types.h" +#include "open62541/types_generated.h" + +#include "testing_clock.h" + +#include +#include + +#ifndef _WIN32 +#define TESTSIG SIGUSR1 +#else +#define TESTSIG SIGINT +#endif + +unsigned counter = 0; + +static void +interruptCallback(UA_InterruptManager *im, + uintptr_t interruptHandle, void *interruptContext, + size_t instanceInfosSize, const UA_KeyValuePair *instanceInfos) { + counter++; +} + +START_TEST(catchInterrupt) { + UA_EventLoop *el = UA_EventLoop_new_POSIX(UA_Log_Stdout); + UA_InterruptManager *im = UA_InterruptManager_new_POSIX(UA_STRING("im1")); + el->registerEventSource(el, &im->eventSource); + + im->registerInterrupt(im, TESTSIG, 0, NULL, interruptCallback, NULL); + el->start(el); + + /* Send signal to self*/ + raise(TESTSIG); + el->run(el, 0); + ck_assert_uint_eq(counter, 1); + + /* Send signal to self*/ + raise(TESTSIG); + el->run(el, 0); + ck_assert_uint_eq(counter, 2); + + /* Stop the EventLoop */ + el->stop(el); + while(el->state != UA_EVENTLOOPSTATE_STOPPED) { + UA_DateTime next = el->run(el, 1); + UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); + } + el->free(el); + el = NULL; +} END_TEST + +START_TEST(registerDuplicate) { + UA_EventLoop *el = UA_EventLoop_new_POSIX(UA_Log_Stdout); + UA_InterruptManager *im = UA_InterruptManager_new_POSIX(UA_STRING("im1")); + el->registerEventSource(el, &im->eventSource); + + el->start(el); + + UA_StatusCode res = + im->registerInterrupt(im, TESTSIG, 0, NULL, interruptCallback, NULL); + ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); + + /* Registering the same signal twice must fail */ + res = im->registerInterrupt(im, TESTSIG, 0, NULL, interruptCallback, NULL); + ck_assert_uint_ne(res, UA_STATUSCODE_GOOD); + + /* Stop the EventLoop */ + el->stop(el); + while(el->state != UA_EVENTLOOPSTATE_STOPPED) { + UA_DateTime next = el->run(el, 1); + UA_fakeSleep((UA_UInt32)((next - UA_DateTime_now()) / UA_DATETIME_MSEC)); + } + el->free(el); + el = NULL; +} END_TEST + +int main(void) { + Suite *s = suite_create("Test EventLoop Interrupts"); + TCase *tc = tcase_create("test cases"); + tcase_add_test(tc, catchInterrupt); + tcase_add_test(tc, registerDuplicate); + suite_add_tcase(s, tc); + + SRunner *sr = srunner_create(s); + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all (sr, CK_NORMAL); + int number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt index 2b990250065..4a914101c0b 100644 --- a/tests/fuzz/CMakeLists.txt +++ b/tests/fuzz/CMakeLists.txt @@ -63,7 +63,10 @@ link_libraries("-fsanitize=fuzzer") # Use different plugins for testing set(fuzzing_plugin_sources ${PROJECT_SOURCE_DIR}/arch/network_tcp.c ${PROJECT_SOURCE_DIR}/arch/eventloop_posix.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_select.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_epoll.c ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_tcp.c + ${PROJECT_SOURCE_DIR}/arch/eventloop_posix_interrupt.c ${PROJECT_SOURCE_DIR}/tests/testing-plugins/testing_clock.c ${PROJECT_SOURCE_DIR}/tests/testing-plugins/testing_networklayers.c ${PROJECT_SOURCE_DIR}/plugins/ua_log_stdout.c From 74e441ef83101c18f03e4a172cd84739923bd9fa Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 3 Jan 2022 01:50:12 +0100 Subject: [PATCH 0039/1963] fix(el): Fix unlocking with mt enabled --- arch/eventloop_posix.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/eventloop_posix.c b/arch/eventloop_posix.c index d61eef1d62a..9fffeca7b58 100644 --- a/arch/eventloop_posix.c +++ b/arch/eventloop_posix.c @@ -172,6 +172,7 @@ UA_EventLoopPOSIX_stop(UA_EventLoopPOSIX *el) { if(el->eventLoop.state != UA_EVENTLOOPSTATE_STARTED) { UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, "The EventLoop is not running, cannot be stopped"); + UA_UNLOCK(&el->elMutex); return; } @@ -193,6 +194,7 @@ UA_EventLoopPOSIX_stop(UA_EventLoopPOSIX *el) { *(UA_EventLoopState*)(uintptr_t)&el->eventLoop.state = UA_EVENTLOOPSTATE_STOPPING; checkClosed(el); + UA_UNLOCK(&el->elMutex); } From a0a5fe2433b83931a22b10f1c261cc881bbba57d Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 1 Jan 2022 23:30:21 +0100 Subject: [PATCH 0040/1963] fix(build): Improve finding the check library on win32 --- tools/cmake/FindCheck.cmake | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tools/cmake/FindCheck.cmake b/tools/cmake/FindCheck.cmake index 5220315bcde..3eccef70061 100644 --- a/tools/cmake/FindCheck.cmake +++ b/tools/cmake/FindCheck.cmake @@ -16,11 +16,17 @@ # BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -find_package(PkgConfig REQUIRED) - -# Take care about check.pc settings -PKG_SEARCH_MODULE( CHECK check ) +if(WIN32) + # Manually define CHECK_INSTALL_DIR if vcpkg is not used + if(DEFINED VCPKG_INSTALLED_DIR) + set(CHECK_INSTALL_DIR "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}") + # find_package(check CONFIG REQUIRED) + endif() +else() + # Take care about check.pc settings + find_package(PkgConfig REQUIRED) + PKG_SEARCH_MODULE( CHECK check ) +endif() # Look for CHECK include dir and libraries IF( NOT CHECK_FOUND ) From f00bcac6dfbe9ca8a45aa765b3b78ac3f02e08a3 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 3 Jan 2022 22:27:40 +0100 Subject: [PATCH 0041/1963] feat(el): Use a pre-allocated receive buffer for the TCP ConnectionManager --- arch/eventloop_posix_tcp.c | 95 +++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index b8176ba2782..06ce9c7a604 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -24,7 +24,8 @@ typedef struct { size_t fdsSize; LIST_HEAD(, UA_RegisteredFD) fds; - size_t recvBufferSize; + UA_ByteString rxBuffer; /* Reuse the receiver buffer. The size is configured + * via the recv-bufsize parameter.*/ } TCPConnectionManager; static UA_StatusCode @@ -97,6 +98,19 @@ TCP_setNoNagle(UA_FD sockfd) { return UA_STATUSCODE_GOOD; } +/* Test if the ConnectionManager can be stopped */ +static void +TCP_checkStopped(TCPConnectionManager *tcm) { + if(tcm->fdsSize == 0 && tcm->cm.eventSource.state == UA_EVENTSOURCESTATE_STOPPING) { + UA_LOG_DEBUG(tcm->cm.eventSource.eventLoop->logger, + UA_LOGCATEGORY_NETWORK, + "TCP\t| All sockets closed, the EventLoop has stopped"); + + UA_ByteString_clear(&tcm->rxBuffer); + tcm->cm.eventSource.state = UA_EVENTSOURCESTATE_STOPPED; + } +} + static UA_StatusCode TCP_close(TCPConnectionManager *tcm, UA_RegisteredFD *rfd) { UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)tcm->cm.eventSource.eventLoop; @@ -121,13 +135,9 @@ TCP_close(TCPConnectionManager *tcm, UA_RegisteredFD *rfd) { /* Free the rfd */ UA_free(rfd); - /* Stopped? */ - if(tcm->fdsSize == 0 && tcm->cm.eventSource.state == UA_EVENTSOURCESTATE_STOPPING) { - UA_LOG_DEBUG(tcm->cm.eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, - "TCP\t| All sockets closed, the EventLoop has stopped"); - tcm->cm.eventSource.state = UA_EVENTSOURCESTATE_STOPPED; - } + /* Stop if the tcm is stopping and this was the last open socket */ + TCP_checkStopped(tcm); + return UA_STATUSCODE_GOOD; } @@ -135,13 +145,11 @@ TCP_close(TCPConnectionManager *tcm, UA_RegisteredFD *rfd) { static void TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_RegisteredFD *rfd, short event) { - TCPConnectionManager *tcm = (TCPConnectionManager*)cm; UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Activity on the socket", (unsigned)rfd->fd); /* Write-Event, a new connection has opened. */ - UA_StatusCode res = UA_STATUSCODE_GOOD; if(event == UA_FDEVENT_OUT) { UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Opening a new connection", (unsigned)rfd->fd); @@ -159,11 +167,9 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_RegisteredFD *rfd, UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Allocate receive buffer", (unsigned)rfd->fd); - /* Allocate the receive-buffer */ - UA_ByteString response; - res = UA_ByteString_allocBuffer(&response, tcm->recvBufferSize); - if(res != UA_STATUSCODE_GOOD) - return; /* Retry in the next iteration */ + /* Use the already allocated receive-buffer */ + TCPConnectionManager *tcm = (TCPConnectionManager*)cm; + UA_ByteString response = tcm->rxBuffer; /* Receive */ #ifndef _WIN32 @@ -172,33 +178,37 @@ TCP_connectionSocketCallback(UA_ConnectionManager *cm, UA_RegisteredFD *rfd, int ret = UA_recv(rfd->fd, (char*)response.data, response.length, MSG_DONTWAIT); #endif - if(ret > 0) { - UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, - "TCP %u\t| Received message of size %u", - (unsigned)rfd->fd, (unsigned)ret); + /* Receive has failed */ + if(ret <= 0) { + if(UA_ERRNO == UA_INTERRUPTED || + UA_ERRNO == UA_WOULDBLOCK || + UA_ERRNO == UA_AGAIN) + return; /* Temporary error on an non-blocking socket */ - /* Callback to the application layer */ - response.length = (size_t)ret; /* Set the length of the received buffer */ - cm->connectionCallback(cm, (uintptr_t)rfd->fd, &rfd->context, - UA_STATUSCODE_GOOD, 0, NULL, response); - } else if(UA_ERRNO != UA_INTERRUPTED && - UA_ERRNO != UA_WOULDBLOCK && - UA_ERRNO != UA_AGAIN) { - /* Orderly shutdown of the connection. Signal to the application and - * then close the connection. We end up in this path after shutdown was + /* Orderly shutdown of the socket. Signal to the application and then + * close the socket. We end up in this code-path after shutdown was * called on the socket. Here, we then are in the next EventLoop * iteration and the socket is known to be unused. */ + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, - "TCP %u\t| recv signaled closed connection", (unsigned)rfd->fd); + "TCP %u\t| recv signaled the socket was shutdown", + (unsigned)rfd->fd); - /* Close the connection if not a temporary error on a nonblocking socket */ cm->connectionCallback(cm, (uintptr_t)rfd->fd, &rfd->context, UA_STATUSCODE_BADCONNECTIONCLOSED, 0, NULL, UA_BYTESTRING_NULL); TCP_close(tcm, rfd); + return; } - UA_ByteString_clear(&response); + UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, + "TCP %u\t| Received message of size %u", + (unsigned)rfd->fd, (unsigned)ret); + + /* Callback to the application layer */ + response.length = (size_t)ret; /* Set the length of the received buffer */ + cm->connectionCallback(cm, (uintptr_t)rfd->fd, &rfd->context, + UA_STATUSCODE_GOOD, 0, NULL, response); } /* Gets called when a new connection opens or if the listenSocket is closed */ @@ -768,13 +778,18 @@ TCP_eventSourceStart(UA_ConnectionManager *cm) { } /* The receive buffersize was configured? */ - const UA_UInt16 *bufSize = (const UA_UInt16*) + TCPConnectionManager *tcm = (TCPConnectionManager*)cm; + UA_UInt16 rxBufSize = 2u << 14; /* The default is 16kb */ + const UA_UInt16 *configRxBufSize = (const UA_UInt16*) UA_KeyValueMap_getScalar(cm->eventSource.params, cm->eventSource.paramsSize, UA_QUALIFIEDNAME(0, "recv-bufsize"), &UA_TYPES[UA_TYPES_UINT16]); - if(bufSize) - ((TCPConnectionManager*)cm)->recvBufferSize = *bufSize; + if(configRxBufSize) + rxBufSize = *configRxBufSize; + UA_StatusCode res = UA_ByteString_allocBuffer(&tcm->rxBuffer, rxBufSize); + if(res != UA_STATUSCODE_GOOD) + return res; /* Set the EventSource to the started state */ cm->eventSource.state = UA_EVENTSOURCESTATE_STARTED; @@ -787,6 +802,8 @@ TCP_eventSourceStop(UA_ConnectionManager *cm) { UA_LOG_INFO(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, "TCP\t| Shutting down the ConnectionManager"); + cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPING; + /* Shut down all registered fd. The cm is set to "stopped" when the last fd * is closed and deregistered in the callback from the EventLoop. */ UA_RegisteredFD *rfd, *rfd_tmp; @@ -798,14 +815,9 @@ TCP_eventSourceStop(UA_ConnectionManager *cm) { TCP_shutdownConnection(cm, (uintptr_t)rfd->fd); } } - cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPING; - /* Closed? */ - if(tcm->fdsSize == 0 && cm->eventSource.state == UA_EVENTSOURCESTATE_STOPPING) - cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPED; - - UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, - UA_LOGCATEGORY_NETWORK, "TCP\t| EventSource successfully stopped"); + /* All sockets closed? Otherwise iterate some more. */ + TCP_checkStopped(tcm); } static UA_StatusCode @@ -848,6 +860,5 @@ UA_ConnectionManager_new_POSIX_TCP(const UA_String eventSourceName) { cm->cm.freeNetworkBuffer = TCP_freeNetworkBuffer; cm->cm.sendWithConnection = TCP_sendWithConnection; cm->cm.closeConnection = TCP_shutdownConnection; - cm->recvBufferSize = 1 << 14; /* TODO: Read from the config */ return &cm->cm; } From c97fac6a3aaebe449e8bb11172fba9a312693130 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 3 Jan 2022 22:44:13 +0100 Subject: [PATCH 0042/1963] refactor(el): Simplify the TCP connection parameters --- arch/eventloop_posix_tcp.c | 10 +++++----- include/open62541/plugin/eventloop.h | 5 ++--- tests/check_eventloop_tcp.c | 8 ++++---- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index 06ce9c7a604..d021382493e 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -586,11 +586,11 @@ TCP_openConnection(UA_ConnectionManager *cm, /* Prepare the port parameter as a string */ const UA_UInt16 *port = (const UA_UInt16*) UA_KeyValueMap_getScalar(params, paramsSize, - UA_QUALIFIEDNAME(0, "target-port"), + UA_QUALIFIEDNAME(0, "port"), &UA_TYPES[UA_TYPES_UINT16]); if(!port) { UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, - "TCP\t| Open TCP Connection: No target port defined, aborting"); + "TCP\t| Open TCP Connection: No port defined, aborting"); return UA_STATUSCODE_BADINTERNALERROR; } UA_snprintf(portStr, 6, "%d", *port); @@ -598,16 +598,16 @@ TCP_openConnection(UA_ConnectionManager *cm, /* Prepare the hostname string */ const UA_String *host = (const UA_String*) UA_KeyValueMap_getScalar(params, paramsSize, - UA_QUALIFIEDNAME(0, "target-hostname"), + UA_QUALIFIEDNAME(0, "hostname"), &UA_TYPES[UA_TYPES_STRING]); if(!host) { UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, - "TCP\t| Open TCP Connection: No target hostname defined, aborting"); + "TCP\t| Open TCP Connection: No hostname defined, aborting"); return UA_STATUSCODE_BADINTERNALERROR; } if(host->length >= 256) { UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, - "TCP\t| Open TCP Connection: No target hostname too long, aborting"); + "TCP\t| Open TCP Connection: Hostname too long, aborting"); return UA_STATUSCODE_BADINTERNALERROR; } strncpy(hostname, (const char*)host->data, host->length); diff --git a/include/open62541/plugin/eventloop.h b/include/open62541/plugin/eventloop.h index 82f91cd3c7c..97f49fd7edc 100644 --- a/include/open62541/plugin/eventloop.h +++ b/include/open62541/plugin/eventloop.h @@ -383,9 +383,8 @@ UA_EventLoop_new_POSIX(const UA_Logger *logger); * messages (default 16kB). * * Open Connection Parameters: - * - 0:target-hostname [string]: Hostname (or IPv4/IPv6 address) of the target - * (required). - * - 0:target-port [uint16]: Port of the target host (required). + * - 0:hostname [string]: Hostname (or IPv4/v6 address) to connect to (required). + * - 0:port [uint16]: Port of the target host (required). * * Send Parameters: * No additional parameters for sending over an established TCP socket defined. */ diff --git a/tests/check_eventloop_tcp.c b/tests/check_eventloop_tcp.c index c7024e4e390..fd421c7836d 100644 --- a/tests/check_eventloop_tcp.c +++ b/tests/check_eventloop_tcp.c @@ -117,9 +117,9 @@ START_TEST(runEventloopFailsIfCalledFromCallback) { UA_String targetHost = UA_STRING("localhost"); UA_KeyValuePair params[2]; - params[0].key = UA_QUALIFIEDNAME(0, "target-port"); + params[0].key = UA_QUALIFIEDNAME(0, "port"); params[0].value = portVar; - params[1].key = UA_QUALIFIEDNAME(0, "target-hostname"); + params[1].key = UA_QUALIFIEDNAME(0, "hostname"); UA_Variant_setScalar(¶ms[1].value, &targetHost, &UA_TYPES[UA_TYPES_STRING]); UA_StatusCode retval = cm->openConnection(cm, 2, params, (void*)0x01); @@ -190,9 +190,9 @@ START_TEST(connectTCP) { UA_String targetHost = UA_STRING("localhost"); UA_KeyValuePair params[2]; - params[0].key = UA_QUALIFIEDNAME(0, "target-port"); + params[0].key = UA_QUALIFIEDNAME(0, "port"); params[0].value = portVar; - params[1].key = UA_QUALIFIEDNAME(0, "target-hostname"); + params[1].key = UA_QUALIFIEDNAME(0, "hostname"); UA_Variant_setScalar(¶ms[1].value, &targetHost, &UA_TYPES[UA_TYPES_STRING]); UA_StatusCode retval = cm->openConnection(cm, 2, params, (void*)0x01); From 9893e54b4488a40cf66f3654aba991ea83db27f7 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 3 Jan 2022 23:01:30 +0100 Subject: [PATCH 0043/1963] feat(el): Forward the remote hostname to the application on first connect --- arch/eventloop_posix_tcp.c | 13 ++++++++----- include/open62541/plugin/eventloop.h | 5 +++++ tests/check_eventloop_tcp.c | 12 +++++++++++- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/arch/eventloop_posix_tcp.c b/arch/eventloop_posix_tcp.c index d021382493e..3be99b4ec86 100644 --- a/arch/eventloop_posix_tcp.c +++ b/arch/eventloop_posix_tcp.c @@ -241,7 +241,6 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_RegisteredFD *rfd, } /* Log the name of the remote host */ -#if UA_LOGLEVEL <= 300 char hoststr[256]; int get_res = UA_getnameinfo((struct sockaddr *)&remote, sizeof(remote), hoststr, sizeof(hoststr), NULL, 0, 0); @@ -261,7 +260,6 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_RegisteredFD *rfd, UA_LOGCATEGORY_NETWORK, "TCP %u\t| Connection opened from \"%s\" via the server socket %u", (unsigned)newsockfd, hoststr, (unsigned)rfd->fd); -#endif /* Configure the new socket */ UA_StatusCode res = UA_STATUSCODE_GOOD; @@ -307,10 +305,15 @@ TCP_listenSocketCallback(UA_ConnectionManager *cm, UA_RegisteredFD *rfd, return; } - /* The socket has opened. Signal it to the application. The callback can - * switch out the context. So put it into a temp variable. */ + /* Forward the remote hostname to the application */ + UA_KeyValuePair kvp; + kvp.key = UA_QUALIFIEDNAME(0, "remote-hostname"); + UA_String hostName = UA_STRING(hoststr); + UA_Variant_setScalar(&kvp.value, &hostName, &UA_TYPES[UA_TYPES_STRING]); + + /* The socket has opened. Signal it to the application. */ cm->connectionCallback(cm, (uintptr_t)newsockfd, &newrfd->context, - UA_STATUSCODE_GOOD, 0, NULL, UA_BYTESTRING_NULL); + UA_STATUSCODE_GOOD, 1, &kvp, UA_BYTESTRING_NULL); } static void diff --git a/include/open62541/plugin/eventloop.h b/include/open62541/plugin/eventloop.h index 97f49fd7edc..51249fb2dfc 100644 --- a/include/open62541/plugin/eventloop.h +++ b/include/open62541/plugin/eventloop.h @@ -386,6 +386,11 @@ UA_EventLoop_new_POSIX(const UA_Logger *logger); * - 0:hostname [string]: Hostname (or IPv4/v6 address) to connect to (required). * - 0:port [uint16]: Port of the target host (required). * + * Connection Callback Paramters: + * - 0:remote-hostname [string]: When a new connection is opened by listening on + * a port, the first callback contains the remote + * hostname parameter. + * * Send Parameters: * No additional parameters for sending over an established TCP socket defined. */ UA_EXPORT UA_ConnectionManager * diff --git a/tests/check_eventloop_tcp.c b/tests/check_eventloop_tcp.c index fd421c7836d..f770531a9bc 100644 --- a/tests/check_eventloop_tcp.c +++ b/tests/check_eventloop_tcp.c @@ -63,8 +63,18 @@ connectionCallback(UA_ConnectionManager *cm, uintptr_t connectionId, UA_ByteString msg) { if(*connectionContext != NULL) clientId = connectionId; - if(msg.length == 0 && status == UA_STATUSCODE_GOOD) + if(msg.length == 0 && status == UA_STATUSCODE_GOOD) { connCount++; + + /* The remote-hostname is set during the first callback */ + if(paramsSize > 0) { + const void *hn = + UA_KeyValueMap_getScalar(params, paramsSize, + UA_QUALIFIEDNAME(0, "remote-hostname"), + &UA_TYPES[UA_TYPES_STRING]); + ck_assert(hn != NULL); + } + } if(status != UA_STATUSCODE_GOOD) { connCount--; } From a8191ecab0091d3e8397a40668ea2ce7aece880e Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 4 Jan 2022 23:13:49 +0100 Subject: [PATCH 0044/1963] feat(el): EventLoops manage their own time domain --- arch/eventloop_posix.c | 34 ++++++++++++++++++++++++++-- include/open62541/plugin/eventloop.h | 19 ++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/arch/eventloop_posix.c b/arch/eventloop_posix.c index 9fffeca7b58..8afa28621ac 100644 --- a/arch/eventloop_posix.c +++ b/arch/eventloop_posix.c @@ -225,7 +225,8 @@ UA_EventLoopPOSIX_run(UA_EventLoopPOSIX *el, UA_UInt32 timeout) { } /* Process cyclic callbacks */ - UA_DateTime dateBefore = UA_DateTime_nowMonotonic(); + UA_DateTime dateBefore = + el->eventLoop.dateTime_nowMonotonic(&el->eventLoop); UA_UNLOCK(&el->elMutex); UA_DateTime dateNext = @@ -237,7 +238,8 @@ UA_EventLoopPOSIX_run(UA_EventLoopPOSIX *el, UA_UInt32 timeout) { UA_DateTime maxDate = dateBefore + (timeout * UA_DATETIME_MSEC); if(dateNext > maxDate) dateNext = maxDate; - UA_DateTime listenTimeout = dateNext - UA_DateTime_nowMonotonic(); + UA_DateTime listenTimeout = + dateNext - el->eventLoop.dateTime_nowMonotonic(&el->eventLoop); if(listenTimeout < 0) listenTimeout = 0; @@ -331,6 +333,28 @@ UA_EventLoopPOSIX_findEventSource(UA_EventLoopPOSIX *el, return s; } +/***************/ +/* Time Domain */ +/***************/ + +/* No special synchronization with an external source, just use the globally + * defined functions. */ + +static UA_DateTime +UA_EventLoopPOSIX_DateTime_now(UA_EventLoop *el) { + return UA_DateTime_now(); +} + +static UA_DateTime +UA_EventLoopPOSIX_DateTime_nowMonotonic(UA_EventLoop *el) { + return UA_DateTime_nowMonotonic(); +} + +static UA_Int64 +UA_EventLoopPOSIX_DateTime_localTimeUtcOffset(UA_EventLoop *el) { + return UA_DateTime_localTimeUtcOffset(); +} + /*************************/ /* Initialize and Delete */ /*************************/ @@ -399,6 +423,12 @@ UA_EventLoop_new_POSIX(const UA_Logger *logger) { el->eventLoop.run = (UA_StatusCode (*)(UA_EventLoop*, UA_UInt32))UA_EventLoopPOSIX_run; el->eventLoop.free = (UA_StatusCode (*)(UA_EventLoop*))UA_EventLoopPOSIX_free; + el->eventLoop.dateTime_now = UA_EventLoopPOSIX_DateTime_now; + el->eventLoop.dateTime_nowMonotonic = + UA_EventLoopPOSIX_DateTime_nowMonotonic; + el->eventLoop.dateTime_localTimeUtcOffset = + UA_EventLoopPOSIX_DateTime_localTimeUtcOffset; + el->eventLoop.nextCyclicTime = UA_EventLoopPOSIX_nextCyclicTime; el->eventLoop.addCyclicCallback = UA_EventLoopPOSIX_addCyclicCallback; el->eventLoop.modifyCyclicCallback = UA_EventLoopPOSIX_modifyCyclicCallback; diff --git a/include/open62541/plugin/eventloop.h b/include/open62541/plugin/eventloop.h index 51249fb2dfc..1f2a921cfaf 100644 --- a/include/open62541/plugin/eventloop.h +++ b/include/open62541/plugin/eventloop.h @@ -97,6 +97,25 @@ struct UA_EventLoop { * EventLoop is not stopped. */ UA_StatusCode (*free)(UA_EventLoop *el); + /* EventLoop Time Domain + * ~~~~~~~~~~~~~~~~~~~~~ + * Each EventLoop instance can manage its own time domain. This affects the + * execution of timed/cyclic callbacks and time-based sending of network + * packets (if this is implemented). Managing independent time domains is + * important when different parts of a system a synchronized to different + * external (network-wide) clocks. + * + * Note that the logger configured in the EventLoop generates timestamps + * internally as well. If the logger uses a different time domain than the + * EventLoop, discrepancies may appear in the logs. + * + * The time domain of the EventLoop is exposed via the following functons. + * See `open62541/types.h` for the documentation of their equivalent + * globally defined functions. */ + UA_DateTime (*dateTime_now)(UA_EventLoop *el); + UA_DateTime (*dateTime_nowMonotonic)(UA_EventLoop *el); + UA_Int64 (*dateTime_localTimeUtcOffset)(UA_EventLoop *el); + /* Cyclic and Delayed Callbacks * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Cyclic callbacks are executed regularly with an interval. A delayed From 9e5d2af6f047770eacb068a799ad446496c5a3e1 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 8 Jan 2022 17:25:42 +0100 Subject: [PATCH 0045/1963] fix(el): Always use epoll_wait instead of epoll_pwait2 Support is documented (https://man7.org/linux/man-pages/man2/epoll_pwait.2.html) but actually lacking in glibc. --- arch/eventloop_posix_epoll.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/arch/eventloop_posix_epoll.c b/arch/eventloop_posix_epoll.c index a393499d6a9..89729f0e30e 100644 --- a/arch/eventloop_posix_epoll.c +++ b/arch/eventloop_posix_epoll.c @@ -6,9 +6,6 @@ */ #include "eventloop_posix.h" -#ifdef __linux__ -#include -#endif #if defined(UA_HAVE_EPOLL) @@ -72,17 +69,17 @@ UA_EventLoopPOSIX_pollFDs(UA_EventLoopPOSIX *el, UA_DateTime listenTimeout) { /* Poll the registered sockets */ struct epoll_event epoll_events[64]; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5,11,0) - int events = epoll_pwait(el->epollfd, epoll_events, 64, - (int)(listenTimeout / UA_DATETIME_MSEC), NULL); -#else - struct timespec precisionTimeout = { - (long)(listenTimeout / UA_DATETIME_SEC), - (long)((listenTimeout % UA_DATETIME_SEC) * 100) - }; - int events = epoll_pwait2(el->epollfd, epoll_events, 64, - precisionTimeout, NULL); -#endif + int events = epoll_wait(el->epollfd, epoll_events, 64, + (int)(listenTimeout / UA_DATETIME_MSEC)); + /* TODO: Replace with pwait2 for higher-precision timeouts once this is + * available in the standard library. + * + * struct timespec precisionTimeout = { + * (long)(listenTimeout / UA_DATETIME_SEC), + * (long)((listenTimeout % UA_DATETIME_SEC) * 100) + * }; + * int events = epoll_pwait2(el->epollfd, epoll_events, 64, + * precisionTimeout, NULL); */ /* Handle error conditions */ if(events == -1) { From 7ca561e4a3d38c651224c71cbe822ae6a3e1c7b4 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 9 Jan 2022 15:17:47 +0100 Subject: [PATCH 0046/1963] refactor(server): Remove unnecessary #include --- src/server/ua_server_ns0.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/server/ua_server_ns0.c b/src/server/ua_server_ns0.c index ee1a319c6d9..cb349e1240c 100644 --- a/src/server/ua_server_ns0.c +++ b/src/server/ua_server_ns0.c @@ -13,9 +13,6 @@ */ #include "open62541/namespace0_generated.h" -#include "open62541/nodeids.h" -#include "open62541/types.h" -#include "open62541/types_generated.h" #include "ua_server_internal.h" #include "ua_session.h" From c4098bb0ab6d178aac71e099ba44e932f00647af Mon Sep 17 00:00:00 2001 From: Mark Giraud Date: Wed, 15 Dec 2021 17:25:42 +0100 Subject: [PATCH 0047/1963] fix(server): Fix default initialization for arrays (#4852) --- .idea/csv-plugin.xml | 21 ++++++++ include/open62541/plugin/nodestore.h | 12 +++-- src/server/ua_server_internal.h | 2 +- src/server/ua_services_attribute.c | 8 ++- src/server/ua_services_nodemanagement.c | 49 ++++++++----------- .../check_nodeset_compiler_testnodeset.c | 3 +- tests/server/check_services_nodemanagement.c | 25 +++++++++- 7 files changed, 84 insertions(+), 36 deletions(-) diff --git a/.idea/csv-plugin.xml b/.idea/csv-plugin.xml index e85272a2a04..a875089f079 100644 --- a/.idea/csv-plugin.xml +++ b/.idea/csv-plugin.xml @@ -3,6 +3,13 @@