Browse Source

Merge branch '0.2'

Julius Pfrommer 7 years ago
parent
commit
fbac663ef4
45 changed files with 1375 additions and 711 deletions
  1. 24 0
      .github/ISSUE_TEMPLATE
  2. 7 3
      .travis.yml
  3. 48 28
      CMakeLists.txt
  4. 51 82
      appveyor.yml
  5. 255 0
      deps/ms_stdint.h
  6. 1 1
      deps/pcg_basic.h
  7. 2 0
      doc/CMakeLists.txt
  8. 1 1
      doc/building.rst
  9. 4 2
      doc/conf.py
  10. 48 9
      doc/index.rst
  11. 136 0
      doc/protocol.rst
  12. 1 0
      doc/toc.rst
  13. BIN
      doc/ua-wireshark.png
  14. 8 8
      include/ua_client_highlevel.h
  15. 50 105
      include/ua_config.h.in
  16. 26 5
      include/ua_server.h
  17. 22 8
      include/ua_types.h
  18. 4 4
      plugins/ua_accesscontrol_default.c
  19. 9 1
      plugins/ua_config_standard.c
  20. 3 1
      plugins/ua_network_tcp.c
  21. 12 12
      src/client/ua_client.c
  22. 24 10
      src/server/ua_nodes.h
  23. 4 2
      src/server/ua_server.c
  24. 2 2
      src/server/ua_server_internal.h
  25. 78 35
      src/server/ua_server_worker.c
  26. 2 7
      src/server/ua_services.h
  27. 183 161
      src/server/ua_services_attribute.c
  28. 132 118
      src/server/ua_services_nodemanagement.c
  29. 3 13
      src/server/ua_services_subscription.c
  30. 53 20
      src/server/ua_subscription.c
  31. 11 4
      src/server/ua_subscription.h
  32. 1 1
      src/ua_connection.c
  33. 30 23
      src/ua_types.c
  34. 9 9
      src/ua_types_encoding_binary.c
  35. 63 3
      src/ua_util.h
  36. 19 14
      tests/CMakeLists.txt
  37. 1 0
      tests/check_services_attributes.c
  38. 1 1
      tools/amalgamate.py
  39. 10 5
      tools/c2rst.py
  40. 18 0
      tools/cmake/FindValgrind.cmake
  41. 2 2
      tools/generate_datatypes.py
  42. 1 0
      tools/pyUANamespace/ua_namespace.py
  43. 11 8
      tools/travis/travis_linux_script.sh
  44. 2 2
      tools/travis/travis_push_doc.sh
  45. 3 1
      tools/travis/travis_push_release.sh

+ 24 - 0
.github/ISSUE_TEMPLATE

@@ -0,0 +1,24 @@
+Description
+===========
+
+
+
+Background Information / Reproduction Steps
+===========================================
+
+See here for the kinds of information we need to reproduce the bug and how to obtain it:
+https://github.com/open62541/open62541/wiki/Writing-Good-Issue-Reports
+
+
+
+Checklist
+=========
+Please provide the following information:
+
+ - [ ] open62541 Version (release number or git tag):
+ - [ ] Other OPC UA SDKs used (client or server): 
+ - [ ] Operating system:
+ - [ ] Logs (with `UA_LOGLEVEL` set as low as necessary) attached
+ - [ ] Wireshark network dump attached
+ - [ ] Self-contained code example attached
+ - [ ] Critical issue

+ 7 - 3
.travis.yml

@@ -43,18 +43,22 @@ addons:
       - clang-3.7
       - cmake
       - gcc-4.8
-      - gcc-mingw-w64-i686
       - gcc-multilib
       - g++-4.8
+      - g++-multilib
+      - mingw-w64
+      - g++-mingw-w64-x86-64
+      - g++-mingw-w64-i686
       - libc6-dbg # for valgrind compilation
       - libsubunit-dev
       - libx11-dev
-      - mingw-w64
       - wget
       - xutils-dev
       - zip
       - graphviz
-    # - libsubunit-dev #for check_0.10.0
+      - texlive-latex-recommended
+      - texlive-latex-extra
+      - texlive-fonts-recommended
   coverity_scan:
     project:
       name: acplt/open62541

+ 48 - 28
CMakeLists.txt

@@ -1,33 +1,31 @@
 cmake_minimum_required(VERSION 2.8.11)
-project(open62541 C)
-set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/tools/cmake")
+project(open62541)
 # set(CMAKE_VERBOSE_MAKEFILE ON)
 
-######################
-# Check Dependencies #
-######################
-
+set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/tools/cmake")
 find_package(PythonInterp REQUIRED)
 find_package(Git)
+
+###########
+# Version #
+###########
+
+set(OPEN62541_VER_MAJOR 0)
+set(OPEN62541_VER_MINOR 2)
+set(OPEN62541_VER_PATCH 0)
+set(OPEN62541_VER_LABEL "-rc2") # Appended to the X.Y.Z version format. For example "-rc1" or an empty string
+
+# Set OPEN62541_VER_COMMIT
 if(GIT_FOUND)
   execute_process(COMMAND ${GIT_EXECUTABLE} describe --always --tags
                   RESULT_VARIABLE res_var OUTPUT_VARIABLE GIT_COM_ID WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-  execute_process(COMMAND ${GIT_EXECUTABLE} describe --abbrev=0 --always --tags
-                  RESULT_VARIABLE res_var OUTPUT_VARIABLE GIT_REL_ID WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-    if(NOT ${res_var} EQUAL 0)
-        set(GIT_COMMIT_ID "commit id unknown")
-        set(GIT_RELEASE_ID "release unknown")
-        message(STATUS "Git failed (not a repo, or no tags). Build will not contain git revision info." )
-    else()
-        string(REPLACE "\n" "" GIT_COMMIT_ID ${GIT_COM_ID} )
-        string(REPLACE "\n" "" GIT_RELEASE_ID ${GIT_REL_ID} )
+    if(${res_var} EQUAL 0)
+        string(REPLACE "\n" "" OPEN62541_VER_COMMIT ${GIT_COM_ID} )
     endif()
-else()
-    set(GIT_COMMIT_ID "commit id unknown")
-    set(GIT_RELEASE_ID "release unknown")
-    message(STATUS "Git not found. Build will not contain git revision info." )
 endif()
-message(STATUS "Git version: "  ${GIT_COMMIT_ID})
+if(NOT ${OPEN62541_VER_COMMIT})
+    set(OPEN62541_VER_COMMIT "undefined")
+endif()
 
 ############
 # Settings #
@@ -44,7 +42,6 @@ set(UA_LOGLEVEL 300 CACHE STRING "Level at which logs shall be reported")
 option(UA_ENABLE_METHODCALLS "Enable the Method service set" ON)
 option(UA_ENABLE_NODEMANAGEMENT "Enable dynamic addition and removal of nodes at runtime" ON)
 option(UA_ENABLE_SUBSCRIPTIONS "Enable subscriptions support." ON)
-option(UA_ENABLE_MULTITHREADING "Enable multithreading" OFF)
 option(UA_ENABLE_DISCOVERY "Enable Discovery Service (LDS)" ON)
 option(UA_ENABLE_AMALGAMATION "Concatenate the library to a single file open62541.h/.c" OFF)
 option(UA_ENABLE_COVERAGE "Enable gcov coverage" OFF)
@@ -58,6 +55,9 @@ if(UA_ENABLE_COVERAGE)
 endif()
 
 # Advanced options
+option(UA_ENABLE_MULTITHREADING "Enable multithreading (experimental)" OFF)
+mark_as_advanced(UA_ENABLE_MULTITHREADING)
+
 option(UA_ENABLE_STATUSCODE_DESCRIPTIONS "Enable conversion of StatusCode to human-readable error message" ON)
 mark_as_advanced(UA_ENABLE_STATUSCODE_DESCRIPTIONS)
 
@@ -73,10 +73,13 @@ mark_as_advanced(UA_ENABLE_GENERATE_NAMESPACE0)
 option(UA_ENABLE_EXTERNAL_NAMESPACES "Enable namespace handling by an external component (experimental)" OFF)
 mark_as_advanced(UA_ENABLE_EXTERNAL_NAMESPACES)
 
-option(UA_ENABLE_NONSTANDARD_STATELESS "Enable stateless extension" OFF)
+option(UA_ENABLE_VALGRIND_UNIT_TESTS "Use Valgrind to detect memory leaks when running the unit tests" OFF)
+mark_as_advanced(UA_ENABLE_VALGRIND_UNIT_TESTS)
+
+option(UA_ENABLE_NONSTANDARD_STATELESS "Enable stateless extension (non-standard)" OFF)
 mark_as_advanced(UA_ENABLE_NONSTANDARD_STATELESS)
 
-option(UA_ENABLE_NONSTANDARD_UDP "Enable udp extension" OFF)
+option(UA_ENABLE_NONSTANDARD_UDP "Enable udp extension (non-standard)" OFF)
 mark_as_advanced(UA_ENABLE_NONSTANDARD_UDP)
 if(UA_ENABLE_NONSTANDARD_UDP)
   set(UA_ENABLE_NONSTANDARD_STATELESS ON)
@@ -90,6 +93,7 @@ option(UA_BUILD_EXAMPLES_NODESET_COMPILER "Generate an OPC UA information model
 
 # Advanced Build Targets
 option(UA_BUILD_SELFSIGNED_CERTIFICATE "Generate self-signed certificate" OFF)
+mark_as_advanced(UA_BUILD_SELFSIGNED_CERTIFICATE)
 
 # Building shared libs (dll, so). This option is written into ua_config.h.
 set(UA_DYNAMIC_LINKING OFF)
@@ -97,6 +101,10 @@ if(BUILD_SHARED_LIBS)
   set(UA_DYNAMIC_LINKING ON)
 endif()
 
+# Force compilation with as C++
+option(UA_COMPILE_AS_CXX "Force compilation with a C++ compiler" OFF)
+mark_as_advanced(UA_COMPILE_AS_CXX)
+
 #####################
 # Compiler Settings #
 #####################
@@ -161,9 +169,12 @@ endif()
 
 file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/src_generated")
 configure_file("include/ua_config.h.in" "${PROJECT_BINARY_DIR}/src_generated/ua_config.h")
-include_directories(${PROJECT_BINARY_DIR}/src_generated)
+include_directories(${PROJECT_BINARY_DIR}/src_generated
+                    ${PROJECT_SOURCE_DIR}/include
+                    ${PROJECT_SOURCE_DIR}/deps)
 
 set(exported_headers ${PROJECT_BINARY_DIR}/src_generated/ua_config.h
+                     ${PROJECT_SOURCE_DIR}/deps/ms_stdint.h
                      ${PROJECT_SOURCE_DIR}/include/ua_constants.h
                      ${PROJECT_SOURCE_DIR}/include/ua_types.h
                      ${PROJECT_BINARY_DIR}/src_generated/ua_nodeids.h
@@ -332,7 +343,7 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_namespaceinit_g
 add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/open62541.h
                    PRE_BUILD
                    COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py
-                           ${GIT_COMMIT_ID} ${CMAKE_CURRENT_BINARY_DIR}/open62541.h ${exported_headers}
+                           ${OPEN62541_VER_COMMIT} ${CMAKE_CURRENT_BINARY_DIR}/open62541.h ${exported_headers}
                    DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py
                            ${exported_headers}
                            ${internal_headers})
@@ -340,7 +351,7 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/open62541.h
 add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/open62541.c
                    PRE_BUILD
                    COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py
-                           ${GIT_COMMIT_ID} ${CMAKE_CURRENT_BINARY_DIR}/open62541.c ${internal_headers}
+                           ${OPEN62541_VER_COMMIT} ${CMAKE_CURRENT_BINARY_DIR}/open62541.c ${internal_headers}
                            ${PROJECT_SOURCE_DIR}/src/server/ua_nodestore_hash.inc ${lib_sources}
                    DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py ${internal_headers}
                            ${PROJECT_SOURCE_DIR}/src/server/ua_nodestore_hash.inc ${lib_sources})
@@ -369,11 +380,17 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/nodeset.h ${PROJEC
 if(UA_ENABLE_AMALGAMATION)
     add_library(open62541-object OBJECT ${PROJECT_BINARY_DIR}/open62541.c ${PROJECT_BINARY_DIR}/open62541.h)
     target_include_directories(open62541-object PRIVATE ${PROJECT_BINARY_DIR})
+    if(UA_COMPILE_AS_CXX)
+        set_source_files_properties(${PROJECT_BINARY_DIR}/open62541.c PROPERTIES LANGUAGE CXX)
+    endif()
 else()
     add_definitions(-DUA_NO_AMALGAMATION)
     add_library(open62541-object OBJECT ${lib_sources} ${internal_headers} ${exported_headers})
-    target_include_directories(open62541-object PRIVATE ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/src
-                                                        ${PROJECT_SOURCE_DIR}/plugins ${PROJECT_SOURCE_DIR}/deps)
+    target_include_directories(open62541-object PRIVATE ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/plugins)
+
+    if(UA_COMPILE_AS_CXX)
+        set_source_files_properties(${lib_sources} PROPERTIES LANGUAGE CXX)
+    endif()
 endif()
 add_library(open62541 $<TARGET_OBJECTS:open62541-object>)
 target_link_libraries(open62541 ${open62541_LIBRARIES})
@@ -381,6 +398,9 @@ target_link_libraries(open62541 ${open62541_LIBRARIES})
 target_compile_definitions(open62541-object PRIVATE -DUA_DYNAMIC_LINKING_EXPORT)
 target_compile_definitions(open62541 PRIVATE -DUA_DYNAMIC_LINKING_EXPORT)
 
+# Generate properly versioned shared library links on Linux
+SET_TARGET_PROPERTIES(open62541 PROPERTIES SOVERSION 0 VERSION "${OPEN62541_VER_MAJOR}.${OPEN62541_VER_MINOR}.${OPEN62541_VER_PATCH}")
+
 if(WIN32)
     target_link_libraries(open62541 ws2_32)
 endif()

+ 51 - 82
appveyor.yml

@@ -1,102 +1,71 @@
 version: '{build}'
 
-os: Visual Studio 2015 RC
-
 clone_folder: c:\projects\open62541
 clone_depth: 20
 
 environment:
-  matrix:
-  - Compiler: msvc
-    Arch: x86
-  - Compiler: msvc
-    Arch: x64
-  - Compiler: mingw
-    Arch: x86
-  - Compiler: mingw
-    Arch: x64
-#    cygwin cmake stopped working on 05.07.2016 -- commented out until a fix appears
-#  - Compiler: cygwin
-#    Arch: x86
+    global:
+        CYG_ARCH: x86
+        CYG_ROOT: C:/cygwin
+        CYG_SETUP_URL: http://cygwin.com/setup-x86.exe
+        CYG_MIRROR: http://cygwin.mirror.constant.com
+        CYG_CACHE: C:\cygwin\var\cache\setup
+        CYG_BASH: C:/cygwin/bin/bash
 
-#
-# Initialisation prior to pulling the Mono repository
-# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
-#
-init:
-  - git config --global core.autocrlf input
+    matrix:
+        - CC_NAME: MinGW Makefiles
+          CC_SHORTNAME: mingw
+          MAKE: mingw32-make
+          FORCE_CXX: OFF
+        # - CC_NAME: Visual Studio 9 2008
+        #   CC_SHORTNAME: vs2008
+        #   MAKE: msbuild open62541.sln /m
+        #   FORCE_CXX: ON
+        - CC_NAME: Visual Studio 12 2013
+          CC_SHORTNAME: vs2013
+          MAKE: msbuild open62541.sln /m
+          FORCE_CXX: OFF
+        - CC_NAME: Visual Studio 12 2013 Win64
+          CC_SHORTNAME: vs2013-x64
+          MAKE: msbuild open62541.sln /m
+          FORCE_CXX: OFF
 
+cache:
+  - '%CYG_CACHE%'
+
+init:
+  - git config --global core.autocrlf input # Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
 
-#
 # Install needed build dependencies
-#
 install:
   - git submodule update --init --recursive
+  - if not exist "%CYG_ROOT%" mkdir "%CYG_ROOT%"
+  - ps: echo "Installing Cygwin from $env:CYG_SETUP_URL to $env:CYG_ROOT/setup-x86.exe"
+  - appveyor DownloadFile %CYG_SETUP_URL% -FileName %CYG_ROOT%/setup-x86.exe
+  - ps: echo "Downloaded. Now ready to install."
+  - cmd: '"%CYG_ROOT%/setup-x86.exe" --quiet-mode --no-shortcuts --only-site -R "%CYG_ROOT%" -s "%CYG_MIRROR%" -l "%CYG_CACHE%" --packages cmake,python'
+  - cmd: '%CYG_BASH% -lc "cygcheck -dc cygwin"'
 
 before_build:
   # Workaround for CMake not wanting sh.exe on PATH for MinGW
   - set PATH=%PATH:C:\Program Files\Git\usr\bin;=%
 
 build_script:
-  - ps: |
-        cd c:\projects\open62541
-        md build
-        cd build
-        if ($env:Compiler -eq "mingw") {
-          if ($env:Arch -eq "x64") {
-            echo "Testing MinGW64"
-            $env:Path = "C:\mingw-w64\i686-5.3.0-posix-dwarf-rt_v4-rev0\mingw32\bin;" + $env:Path
-          } else {
-            echo "Testing MinGW32"
-            $env:Path = "C:\MinGW\bin;" + $env:Path
-          }
-          cmake -DUA_BUILD_EXAMPLES:BOOL=ON -G"MinGW Makefiles" ..
-          mingw32-make -j4
-        } elseif ($env:Compiler -eq "cygwin") {
-          echo "Testing cygwin"
-          $env:Path = "C:\cygwin\bin;" + $env:Path
-          C:\cygwin\bin\bash -lc "cygcheck -dc cygwin"
-          C:\cygwin\bin\bash --login -lc "cmake.exe --version"
-          C:\cygwin\bin\bash --login -lc "cd /cygdrive/c/projects/open62541/build; cmake -DUA_BUILD_EXAMPLES:BOOL=ON -G\"Unix Makefiles\" .."
-          C:\cygwin\bin\bash --login -lc "cd /cygdrive/c/projects/open62541/build; make -j"
-        } else {
-          if ($env:Arch -eq "x64") {
-            echo "Testing MSVC with amalgamation (x64)"
-            cd ..
-            md build64
-            cd build64
-            cmake -DUA_BUILD_EXAMPLES:BOOL=ON -DUA_ENABLE_AMALGAMATION:BOOL=ON -G"Visual Studio 12 2013 Win64" ..
-            msbuild open62541.sln /m
-            copy C:\projects\open62541\build64\open62541.c C:\projects\open62541\build64\Debug\open62541.c
-            copy C:\projects\open62541\build64\open62541.h C:\projects\open62541\build64\Debug\open62541.h
-            copy C:\projects\open62541\build64\examples\server_cert.der C:\projects\open62541\build64\Debug\server_cert.der
-          } else {
-            echo "Testing MSVC without amalgamation (x86)"
-            cmake -DUA_BUILD_EXAMPLES:BOOL=ON -G"Visual Studio 12 2013" ..
-            msbuild open62541.sln /m
-            cd ..
-            Remove-Item .\build -Force -Recurse
-            md build
-            cd build
-
-            echo "Testing MSVC with amalgamation (x86)"
-            cmake -DUA_BUILD_EXAMPLES:BOOL=ON -DUA_ENABLE_AMALGAMATION:BOOL=ON -G"Visual Studio 12 2013" ..
-            msbuild open62541.sln /m
-            copy C:\projects\open62541\build\open62541.c C:\projects\open62541\build\Debug\open62541.c
-            copy C:\projects\open62541\build\open62541.h C:\projects\open62541\build\Debug\open62541.h
-            copy C:\projects\open62541\build\examples\server_cert.der C:\projects\open62541\build\Debug\server_cert.der
-          }
-        }
-        echo "Build done"
+  - cd c:\projects\open62541
+  - md build
+  - cd build
+  - echo "Testing %CC_NAME%"
+  - cmake -DUA_BUILD_EXAMPLES:BOOL=ON -DUA_COMPILE_AS_CXX:BOOL=%FORCE_CXX% -G"%CC_NAME%" ..
+  - '%MAKE%'
+  - cd ..
+  - rd /s /q build
+  - md build
+  - cd build
+  - echo "Testing %CC_NAME% with amalgamation"
+  - cmake -DUA_BUILD_EXAMPLES:BOOL=ON -DUA_ENABLE_AMALGAMATION:BOOL=ON -DUA_COMPILE_AS_CXX:BOOL=%FORCE_CXX% -G"%CC_NAME%" ..
+  - '%MAKE%'
+  - cd ..
 
 after_build:
-  - ps: |
-        if ($env:Compiler -eq "msvc") {
-          if ($env:Arch -eq "x64") {
-            7z a open62541-win64.zip C:\projects\open62541\build64\Debug\*
-            appveyor PushArtifact open62541-win64.zip
-          } else {
-            7z a open62541-win32.zip C:\projects\open62541\build\Debug\*
-            appveyor PushArtifact open62541-win32.zip
-          }
-        }
+  - 7z a open62541-%CC_SHORTNAME%.zip %APPVEYOR_BUILD_FOLDER%\build*
+  - appveyor PushArtifact open62541-%CC_SHORTNAME%.zip

+ 255 - 0
deps/ms_stdint.h

@@ -0,0 +1,255 @@
+// ISO C9x  compliant stdint.h for Microsoft Visual Studio
+// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 
+// 
+//  Copyright (c) 2006-2013 Alexander Chemeris
+// 
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+// 
+//   1. Redistributions of source code must retain the above copyright notice,
+//      this list of conditions and the following disclaimer.
+// 
+//   2. Redistributions in binary form must reproduce the above copyright
+//      notice, this list of conditions and the following disclaimer in the
+//      documentation and/or other materials provided with the distribution.
+// 
+//   3. Neither the name of the product nor the names of its contributors may
+//      be used to endorse or promote products derived from this software
+//      without specific prior written permission.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// 
+///////////////////////////////////////////////////////////////////////////////
+
+#if !defined(_MSC_VER) || _MSC_VER >= 1600 // [
+#include <stdint.h>
+#else
+
+#ifndef _MSC_STDINT_H_ // [
+#define _MSC_STDINT_H_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif
+
+#include <limits.h>
+
+// For Visual Studio 6 in C++ mode and for many Visual Studio versions when
+// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}'
+// or compiler give many errors like this:
+//   error C2733: second C linkage of overloaded function 'wmemchr' not allowed
+#ifdef __cplusplus
+extern "C" {
+#endif
+#  include <wchar.h>
+#ifdef __cplusplus
+}
+#endif
+
+// Define _W64 macros to mark types changing their size, like intptr_t.
+#ifndef _W64
+#  if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
+#     define _W64 __w64
+#  else
+#     define _W64
+#  endif
+#endif
+
+
+// 7.18.1 Integer types
+
+// 7.18.1.1 Exact-width integer types
+
+// Visual Studio 6 and Embedded Visual C++ 4 doesn't
+// realize that, e.g. char has the same size as __int8
+// so we give up on __intX for them.
+#if (_MSC_VER < 1300)
+   typedef signed char       int8_t;
+   typedef signed short      int16_t;
+   typedef signed int        int32_t;
+   typedef unsigned char     uint8_t;
+   typedef unsigned short    uint16_t;
+   typedef unsigned int      uint32_t;
+#else
+   typedef signed __int8     int8_t;
+   typedef signed __int16    int16_t;
+   typedef signed __int32    int32_t;
+   typedef unsigned __int8   uint8_t;
+   typedef unsigned __int16  uint16_t;
+   typedef unsigned __int32  uint32_t;
+#endif
+typedef signed __int64       int64_t;
+typedef unsigned __int64     uint64_t;
+
+
+// 7.18.1.2 Minimum-width integer types
+typedef int8_t    int_least8_t;
+typedef int16_t   int_least16_t;
+typedef int32_t   int_least32_t;
+typedef int64_t   int_least64_t;
+typedef uint8_t   uint_least8_t;
+typedef uint16_t  uint_least16_t;
+typedef uint32_t  uint_least32_t;
+typedef uint64_t  uint_least64_t;
+
+// 7.18.1.3 Fastest minimum-width integer types
+typedef int8_t    int_fast8_t;
+typedef int16_t   int_fast16_t;
+typedef int32_t   int_fast32_t;
+typedef int64_t   int_fast64_t;
+typedef uint8_t   uint_fast8_t;
+typedef uint16_t  uint_fast16_t;
+typedef uint32_t  uint_fast32_t;
+typedef uint64_t  uint_fast64_t;
+
+// 7.18.1.4 Integer types capable of holding object pointers
+#ifdef _WIN64 // [
+   typedef signed __int64    intptr_t;
+   typedef unsigned __int64  uintptr_t;
+#else // _WIN64 ][
+   typedef _W64 signed int   intptr_t;
+   typedef _W64 unsigned int uintptr_t;
+#endif // _WIN64 ]
+
+// 7.18.1.5 Greatest-width integer types
+typedef int64_t   intmax_t;
+typedef uint64_t  uintmax_t;
+
+
+// 7.18.2 Limits of specified-width integer types
+
+#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [   See footnote 220 at page 257 and footnote 221 at page 259
+
+// 7.18.2.1 Limits of exact-width integer types
+#define INT8_MIN     ((int8_t)_I8_MIN)
+#define INT8_MAX     _I8_MAX
+#define INT16_MIN    ((int16_t)_I16_MIN)
+#define INT16_MAX    _I16_MAX
+#define INT32_MIN    ((int32_t)_I32_MIN)
+#define INT32_MAX    _I32_MAX
+#define INT64_MIN    ((int64_t)_I64_MIN)
+#define INT64_MAX    _I64_MAX
+#define UINT8_MAX    _UI8_MAX
+#define UINT16_MAX   _UI16_MAX
+#define UINT32_MAX   _UI32_MAX
+#define UINT64_MAX   _UI64_MAX
+
+// 7.18.2.2 Limits of minimum-width integer types
+#define INT_LEAST8_MIN    INT8_MIN
+#define INT_LEAST8_MAX    INT8_MAX
+#define INT_LEAST16_MIN   INT16_MIN
+#define INT_LEAST16_MAX   INT16_MAX
+#define INT_LEAST32_MIN   INT32_MIN
+#define INT_LEAST32_MAX   INT32_MAX
+#define INT_LEAST64_MIN   INT64_MIN
+#define INT_LEAST64_MAX   INT64_MAX
+#define UINT_LEAST8_MAX   UINT8_MAX
+#define UINT_LEAST16_MAX  UINT16_MAX
+#define UINT_LEAST32_MAX  UINT32_MAX
+#define UINT_LEAST64_MAX  UINT64_MAX
+
+// 7.18.2.3 Limits of fastest minimum-width integer types
+#define INT_FAST8_MIN    INT8_MIN
+#define INT_FAST8_MAX    INT8_MAX
+#define INT_FAST16_MIN   INT16_MIN
+#define INT_FAST16_MAX   INT16_MAX
+#define INT_FAST32_MIN   INT32_MIN
+#define INT_FAST32_MAX   INT32_MAX
+#define INT_FAST64_MIN   INT64_MIN
+#define INT_FAST64_MAX   INT64_MAX
+#define UINT_FAST8_MAX   UINT8_MAX
+#define UINT_FAST16_MAX  UINT16_MAX
+#define UINT_FAST32_MAX  UINT32_MAX
+#define UINT_FAST64_MAX  UINT64_MAX
+
+// 7.18.2.4 Limits of integer types capable of holding object pointers
+#ifdef _WIN64 // [
+#  define INTPTR_MIN   INT64_MIN
+#  define INTPTR_MAX   INT64_MAX
+#  define UINTPTR_MAX  UINT64_MAX
+#else // _WIN64 ][
+#  define INTPTR_MIN   INT32_MIN
+#  define INTPTR_MAX   INT32_MAX
+#  define UINTPTR_MAX  UINT32_MAX
+#endif // _WIN64 ]
+
+// 7.18.2.5 Limits of greatest-width integer types
+#define INTMAX_MIN   INT64_MIN
+#define INTMAX_MAX   INT64_MAX
+#define UINTMAX_MAX  UINT64_MAX
+
+// 7.18.3 Limits of other integer types
+
+#ifdef _WIN64 // [
+#  define PTRDIFF_MIN  _I64_MIN
+#  define PTRDIFF_MAX  _I64_MAX
+#else  // _WIN64 ][
+#  define PTRDIFF_MIN  _I32_MIN
+#  define PTRDIFF_MAX  _I32_MAX
+#endif  // _WIN64 ]
+
+#define SIG_ATOMIC_MIN  INT_MIN
+#define SIG_ATOMIC_MAX  INT_MAX
+
+#ifndef SIZE_MAX // [
+#  ifdef _WIN64 // [
+#     define SIZE_MAX  _UI64_MAX
+#  else // _WIN64 ][
+#     define SIZE_MAX  _UI32_MAX
+#  endif // _WIN64 ]
+#endif // SIZE_MAX ]
+
+// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h>
+#ifndef WCHAR_MIN // [
+#  define WCHAR_MIN  0
+#endif  // WCHAR_MIN ]
+#ifndef WCHAR_MAX // [
+#  define WCHAR_MAX  _UI16_MAX
+#endif  // WCHAR_MAX ]
+
+#define WINT_MIN  0
+#define WINT_MAX  _UI16_MAX
+
+#endif // __STDC_LIMIT_MACROS ]
+
+
+// 7.18.4 Limits of other integer types
+
+#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [   See footnote 224 at page 260
+
+// 7.18.4.1 Macros for minimum-width integer constants
+
+#define INT8_C(val)  val##i8
+#define INT16_C(val) val##i16
+#define INT32_C(val) val##i32
+#define INT64_C(val) val##i64
+
+#define UINT8_C(val)  val##ui8
+#define UINT16_C(val) val##ui16
+#define UINT32_C(val) val##ui32
+#define UINT64_C(val) val##ui64
+
+// 7.18.4.2 Macros for greatest-width integer constants
+// These #ifndef's are needed to prevent collisions with <boost/cstdint.hpp>.
+// Check out Issue 9 for the details.
+#ifndef INTMAX_C //   [
+#  define INTMAX_C   INT64_C
+#endif // INTMAX_C    ]
+#ifndef UINTMAX_C //  [
+#  define UINTMAX_C  UINT64_C
+#endif // UINTMAX_C   ]
+
+#endif // __STDC_CONSTANT_MACROS ]
+
+#endif // _MSC_STDINT_H_ ]
+
+#endif // !defined(_MSC_VER) || _MSC_VER >= 1600 ]

+ 1 - 1
deps/pcg_basic.h

@@ -24,7 +24,7 @@
 #ifndef PCG_BASIC_H_INCLUDED
 #define PCG_BASIC_H_INCLUDED 1
 
-#include <stdint.h>
+#include "ms_stdint.h"
 
 #if __cplusplus
 extern "C" {

+ 2 - 0
doc/CMakeLists.txt

@@ -53,6 +53,7 @@ add_custom_target(doc_latex ${SPHINX_EXECUTABLE}
           ${DOC_SRC_DIR}/server.rst ${DOC_SRC_DIR}/client.rst ${DOC_SRC_DIR}/client_highlevel.rst
           ${DOC_SRC_DIR}/log.rst ${DOC_SRC_DIR}/connection.rst ${DOC_SRC_DIR}/services.rst
           ${DOC_SRC_DIR}/nodestore.rst ${DOC_SRC_DIR}/information_modelling.rst
+          ${DOC_SRC_DIR}/protocol.rst
   COMMENT "Building LaTeX sources for documentation with Sphinx")
 add_dependencies(doc_latex open62541)
 
@@ -70,5 +71,6 @@ add_custom_target(doc ${SPHINX_EXECUTABLE}
           ${DOC_SRC_DIR}/server.rst ${DOC_SRC_DIR}/client.rst ${DOC_SRC_DIR}/client_highlevel.rst
           ${DOC_SRC_DIR}/log.rst ${DOC_SRC_DIR}/connection.rst ${DOC_SRC_DIR}/services.rst
           ${DOC_SRC_DIR}/nodestore.rst ${DOC_SRC_DIR}/information_modelling.rst
+          ${DOC_SRC_DIR}/protocol.rst
   COMMENT "Building HTML documentation with Sphinx")
 add_dependencies(doc open62541)

+ 1 - 1
doc/building.rst

@@ -88,7 +88,7 @@ Building on OS X
 Follow Ubuntu instructions without the ``apt-get`` commands as these are taken care of by the above packages.
 
 Building on OpenBSD
--------------------
+^^^^^^^^^^^^^^^^^^^
 The procedure below works on OpenBSD 5.8 with gcc version 4.8.4, cmake version 3.2.3 and Python version 2.7.10.
 
 - Install a recent gcc, python and cmake:

+ 4 - 2
doc/conf.py

@@ -31,6 +31,8 @@ import shlex
 # ones.
 extensions = ['sphinx.ext.graphviz']
 
+numfig = True
+
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
 
@@ -55,9 +57,9 @@ author = u'The open62541 authors'
 # built documents.
 #
 # The short X.Y version.
-version = "${GIT_RELEASE_ID}"
+version = "${OPEN62541_VER_MAJOR}.${OPEN62541_VER_MINOR}"
 # The full version, including alpha/beta/rc tags.
-release = "${GIT_COMMIT_ID}"
+release = "${OPEN62541_VER_MAJOR}.${OPEN62541_VER_MINOR}.${OPEN62541_VER_PATCH}${OPEN62541_VER_LABEL}"
 # The full version, including alpha/beta/rc tags.
 
 # The language for content autogenerated by Sphinx. Refer to documentation

File diff suppressed because it is too large
+ 48 - 9
doc/index.rst


+ 136 - 0
doc/protocol.rst

@@ -0,0 +1,136 @@
+.. _protocol:
+
+Protocol
+========
+
+In this section, we give an overview on the OPC UA binary protocol. We focus on
+binary since that is what has been implemented in open62541. The TCP-based
+binary protocol is by far the most common transport layer for OPC UA. The
+general concepts also translate to HTTP and SOAP-based communication defined in
+the standard. Communication in OPC UA is best understood by starting with the
+following key principles:
+
+Request / Response
+  All communication is based on the Request/Response pattern. Only clients can
+  send a request to a server. And servers can only send responses to a request.
+  Usually, the server is hosted on the (physical) device, such as a sensor or a
+  machine tool.
+
+Asynchronous Responses
+  A server does not have to immediately respond to requests and responses may be
+  sent in a different order. This keeps the server responsive when it takes time
+  until a specific request has been processed (e.g. a method call or when
+  reading from a sensor with delay). Furthermore, Subscriptions (aka
+  push-notifications) are implemented via special requests where the response is
+  delayed until a notification is generated.
+
+Establishing a Connection
+-------------------------
+
+A client-server connection in OPC UA consists of three nested levels: The raw
+connection, a SecureChannel and the Session. For full details, see Part 6 of the
+OPC UA standard.
+
+Raw Connection
+  The raw connection is created by opening a TCP connection to the corresponding
+  hostname and port and an initial HEL/ACK handshake. The handshake establishes
+  the basic settings of the connection, such as the maximum message length.
+
+SecureChannel
+  SecureChannels are created on top of the raw TCP connection. A SecureChannel
+  is established with an *OpenSecureChannel* request and response message pair.
+  **Attention!** Even though a SecureChannel is mandatory, encryption might
+  still be disabled. The *SecurityMode* of a SecureChannel can be either
+  ``None``, ``Sign``, or ``SignAndEncrypt``. As of version 0.2 of open6251,
+  message signing and encryption is still under ongoing development.
+
+  With message signing or encryption enabled, the *OpenSecureChannel* messages
+  are encrypted using an asymmetric encryption algorithm (public-key
+  cryptography) [#key-mgmnt]_. As part of the *OpenSecureChannel* messages,
+  client and server establish a common secret over an initially unsecure
+  channel. For subsequent messages, the common secret is used for symmetric
+  encryption, which has the advantage of being much faster.
+
+  Different *SecurityPolicies* -- defined in part 7 of the OPC UA standard --
+  specify the algorithms for asymmetric and symmetric encryption, encryption key
+  lengths, hash functions for message signing, and so on. Example
+  SecurityPolicies are ``None`` for transmission of cleartext and
+  ``Basic256Sha256`` which mandates a variant of RSA with SHA256 certificate
+  hashing for asymmetric encryption and AES256 for symmetric encryption.
+
+  The possible SecurityPolicies of a server are described with a list of
+  *Endpoints*. An endpoint jointly defines the SecurityMode, SecurityPolicy and
+  means for authenticating a session (discussed in the next section) in order to
+  connect to a certain server. The *GetEndpoints* service returns a list of
+  available endpoints. This service can usually be invoked without a session and
+  from an unencrypted SecureChannel. This allows clients to first discover
+  available endpoints and then use an appropriate SecurityPolicy that might be
+  required to open a session.
+
+Session
+  Sessions are created on top of a SecureChannel. This ensures that users may
+  authenticate without sending their credentials, such as username and password,
+  in cleartext. Currently defined authentication mechanisms are anonymous login,
+  username/password, Kerberos and x509 certificates. The latter requires that
+  the request message is accompanied by a signature to prove that the sender is
+  in posession of the private key with which the certificate was created.
+
+  There are two message exchanges required to establish a session:
+  *CreateSession* and *ActicateSession*. The ActivateSession service can be used
+  to switch an existing session to a different SecureChannel. This is important,
+  for example when the connection broke down and the existing session is
+  reused with a new SecureChannel.
+
+.. [#key-mgmnt] This entails that the client and server exchange so-called
+   public keys. The public keys might come with a certificate from a key-signing
+   authority or be verified against an external key repository. But we will not
+   discuss certificate management in detail in this section.
+
+Structure of a protocol message
+-------------------------------
+
+For the following introduction to the structure of OPC UA protocol messages,
+consider the example OPC UA binary conversation, recorded and displayed with the
+`Wireshark <https://www.wireshark.org/>`_ tool, shown in :numref:`ua-wireshark`.
+
+.. _ua-wireshark:
+
+.. figure:: ua-wireshark.png
+   :figwidth: 100 %
+   :alt: OPC UA conversation in Wireshark
+
+   OPC UA conversation displayed in Wireshark
+
+The top part of the Wireshark window shows the messages from the conversation in
+order. The green line contains the applied filter. Here, we want to see the OPC
+UA protocol messages only. The first messages (from TCP packets 49 to 56) show
+the client opening an unencrypted SecureChannel and retrieving the server's
+endpoints. Then, starting with packet 63, a new connection and SecureChannel are
+created in conformance with one of the endpoints. On top of this SecureChannel,
+the client can then create and activate a session. The following *ReadRequest*
+message is selected and covered in more detail in the bottom windows.
+
+The bottom left window shows the structure of the selected *ReadRequest*
+message. The purpose of the message is invoking the *Read* :ref:`service
+<services>`. The message is structured into a header and a message body. Note
+that we do not consider encryption or signing of messages here.
+
+Message Header
+  As stated before, OPC UA defines an asynchronous protocol. So responses may be
+  out of order. The message header contains some basic information, such as the
+  length of the message, as well as necessary information to relate messages to
+  a SecureChannel and each request to the corresponding response. "Chunking"
+  refers to the splitting and reassembling of messages that are longer than the
+  maximum network packet size.
+
+Message Body
+  Every OPC UA :ref:`service <services>` has a signature in the form of a
+  request and response data structure. These are defined according to the OPC UA
+  protocol :ref:`type system <types>`. See especially the :ref:`auto-generated
+  type definitions<generated-types>` for the data types corresponding to service
+  requests and responses. The message body begins with the identifier of the
+  following data type. Then, the main payload of the message follows.
+
+The bottom right window shows the binary payload of the selected *ReadRequest*
+message. The message header is highlighted in light-grey. The message body in
+blue highlighting shows the encoded *ReadRequest* data structure.

+ 1 - 0
doc/toc.rst

@@ -6,6 +6,7 @@ open62541 Documentation
    index
    building
    tutorials
+   protocol
    types
    information_modelling
    services

BIN
doc/ua-wireshark.png


+ 8 - 8
include/ua_client_highlevel.h

@@ -168,18 +168,18 @@ UA_Client_readArrayDimensionsAttribute(UA_Client *client, const UA_NodeId nodeId
 
 static UA_INLINE UA_StatusCode
 UA_Client_readAccessLevelAttribute(UA_Client *client, const UA_NodeId nodeId,
-                                   UA_UInt32 *outAccessLevel) {
+                                   UA_Byte *outAccessLevel) {
     return __UA_Client_readAttribute(client, &nodeId, UA_ATTRIBUTEID_ACCESSLEVEL,
-                                     outAccessLevel, &UA_TYPES[UA_TYPES_UINT32]);
+                                     outAccessLevel, &UA_TYPES[UA_TYPES_BYTE]);
 }
 
 static UA_INLINE UA_StatusCode
 UA_Client_readUserAccessLevelAttribute(UA_Client *client, const UA_NodeId nodeId,
-                                       UA_UInt32 *outUserAccessLevel) {
+                                       UA_Byte *outUserAccessLevel) {
     return __UA_Client_readAttribute(client, &nodeId,
                                      UA_ATTRIBUTEID_USERACCESSLEVEL,
                                      outUserAccessLevel,
-                                     &UA_TYPES[UA_TYPES_UINT32]);
+                                     &UA_TYPES[UA_TYPES_BYTE]);
 }
 
 static UA_INLINE UA_StatusCode
@@ -349,18 +349,18 @@ UA_Client_writeArrayDimensionsAttribute(UA_Client *client, const UA_NodeId nodeI
 
 static UA_INLINE UA_StatusCode
 UA_Client_writeAccessLevelAttribute(UA_Client *client, const UA_NodeId nodeId,
-                                    const UA_UInt32 *newAccessLevel) {
+                                    const UA_Byte *newAccessLevel) {
     return __UA_Client_writeAttribute(client, &nodeId, UA_ATTRIBUTEID_ACCESSLEVEL,
-                                      newAccessLevel, &UA_TYPES[UA_TYPES_UINT32]);
+                                      newAccessLevel, &UA_TYPES[UA_TYPES_BYTE]);
 }
 
 static UA_INLINE UA_StatusCode
 UA_Client_writeUserAccessLevelAttribute(UA_Client *client, const UA_NodeId nodeId,
-                                        const UA_UInt32 *newUserAccessLevel) {
+                                        const UA_Byte *newUserAccessLevel) {
     return __UA_Client_writeAttribute(client, &nodeId,
                                       UA_ATTRIBUTEID_USERACCESSLEVEL,
                                       newUserAccessLevel,
-                                      &UA_TYPES[UA_TYPES_UINT32]);
+                                      &UA_TYPES[UA_TYPES_BYTE]);
 }
 
 static UA_INLINE UA_StatusCode

+ 50 - 105
include/ua_config.h.in

@@ -23,7 +23,11 @@ extern "C" {
 /**
  * Library Version
  * --------------- */
-#define UA_GIT_COMMIT_ID "${GIT_COMMIT_ID}"
+#define UA_OPEN62541_VER_MAJOR ${OPEN62541_VER_MAJOR}
+#define UA_OPEN62541_VER_MINOR ${OPEN62541_VER_MINOR}
+#define UA_OPEN62541_VER_PATCH ${OPEN62541_VER_PATCH}
+#define UA_OPEN62541_VER_LABEL "${OPEN62541_VER_LABEL}" /* Release candidate label, etc. */
+#define UA_OPEN62541_VER_COMMIT "${OPEN62541_VER_COMMIT}"
 
 /**
  * Options
@@ -55,8 +59,51 @@ extern "C" {
 #ifndef _DEFAULT_SOURCE
 # define _DEFAULT_SOURCE
 #endif
+
 #include <stddef.h>
-#include <stdint.h>
+#include "ms_stdint.h" /* Includes stdint.h or workaround for older Visual Studios */
+
+#ifndef __cplusplus
+# include <stdbool.h> /* C99 Boolean */
+/* Manual Boolean:
+typedef uint8_t bool;
+#define true 1
+#define false 0 */
+#endif
+
+/* Manually define some function on libc */
+#ifndef UA_ENABLE_EMBEDDED_LIBC
+# include <string.h>
+#else
+  void *memcpy(void *UA_RESTRICT dest, const void *UA_RESTRICT src, size_t n);
+  void *memset(void *dest, int c, size_t n);
+  size_t strlen(const char *s);
+  int memcmp(const void *vl, const void *vr, size_t n);
+#endif
+
+/* Memory Management */
+#include <stdlib.h>
+#ifdef _WIN32
+# ifndef __clang__
+#  include <malloc.h>
+# endif
+#endif
+
+#define UA_free(ptr) free(ptr)
+#define UA_malloc(size) malloc(size)
+#define UA_calloc(num, size) calloc(num, size)
+#define UA_realloc(ptr, size) realloc(ptr, size)
+
+#ifndef NO_ALLOCA
+# if defined(__GNUC__) || defined(__clang__)
+#  define UA_alloca(size) __builtin_alloca (size)
+# elif defined(_WIN32)
+#  define UA_alloca(SIZE) _alloca(SIZE)
+# else
+#  include <alloca.h>
+#  define UA_alloca(SIZE) alloca(SIZE)
+# endif
+#endif
 
 /**
  * Function Export
@@ -123,96 +170,6 @@ extern "C" {
 # define UA_FUNC_ATTR_WARN_UNUSED_RESULT
 #endif
 
-/**
- * Memory Management
- * -----------------
- * Replace the macros for custom memory allocators if necessary */
-#include <stdlib.h>
-#ifdef _WIN32
-# ifndef __clang__
-#  include <malloc.h>
-# endif
-#endif
-
-#define UA_free(ptr) free(ptr)
-#define UA_malloc(size) malloc(size)
-#define UA_calloc(num, size) calloc(num, size)
-#define UA_realloc(ptr, size) realloc(ptr, size)
-
-#ifndef NO_ALLOCA
-# if defined(__GNUC__) || defined(__clang__)
-#  define UA_alloca(size) __builtin_alloca (size)
-# elif defined(_WIN32)
-#  define UA_alloca(SIZE) _alloca(SIZE)
-# else
-#  include <alloca.h>
-#  define UA_alloca(SIZE) alloca(SIZE)
-# endif
-#endif
-
-/**
- * Atomic Operations
- * -----------------
- * Atomic operations that synchronize across processor cores (for
- * multithreading). Only the inline-functions defined next are used. Replace
- * with architecture-specific operations if necessary. */
-
-#ifndef UA_ENABLE_MULTITHREADING
-# define UA_atomic_sync()
-#else
-# ifdef _MSC_VER /* Visual Studio */
-#  define UA_atomic_sync() _ReadWriteBarrier()
-# else /* GCC/Clang */
-#  define UA_atomic_sync() __sync_synchronize()
-# endif
-#endif
-
-static UA_INLINE void *
-UA_atomic_xchg(void * volatile * addr, void *newptr) {
-#ifndef UA_ENABLE_MULTITHREADING
-    void *old = *addr;
-    *addr = newptr;
-    return old;
-#else
-# ifdef _MSC_VER /* Visual Studio */
-    return _InterlockedExchangePointer(addr, newptr);
-# else /* GCC/Clang */
-    return __sync_lock_test_and_set(addr, newptr);
-# endif
-#endif
-}
-
-static UA_INLINE void *
-UA_atomic_cmpxchg(void * volatile * addr, void *expected, void *newptr) {
-#ifndef UA_ENABLE_MULTITHREADING
-    void *old = *addr;
-    if(old == expected) {
-        *addr = newptr;
-    }
-    return old;
-#else
-# ifdef _MSC_VER /* Visual Studio */
-    return _InterlockedCompareExchangePointer(addr, expected, newptr);
-# else /* GCC/Clang */
-    return __sync_val_compare_and_swap(addr, expected, newptr);
-# endif
-#endif
-}
-
-static UA_INLINE uint32_t
-UA_atomic_add(volatile uint32_t *addr, uint32_t increase) {
-#ifndef UA_ENABLE_MULTITHREADING
-    *addr += increase;
-    return *addr;
-#else
-# ifdef _MSC_VER /* Visual Studio */
-    return _InterlockedExchangeAdd(addr, increase) + increase;
-# else /* GCC/Clang */
-    return __sync_add_and_fetch(addr, increase);
-# endif
-#endif
-}
-
 /**
  * Binary Encoding Overlays
  * ------------------------
@@ -224,7 +181,7 @@ UA_atomic_add(volatile uint32_t *addr, uint32_t increase) {
  * Integer Endianness
  * ^^^^^^^^^^^^^^^^^^ */
 #if defined(_WIN32) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \
-                        (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) /* little endian detected */
+                        (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
 # define UA_BINARY_OVERLAYABLE_INTEGER true
 #elif defined(__ANDROID__) /* Andoid */
 # include <endian.h>
@@ -282,18 +239,6 @@ UA_atomic_add(volatile uint32_t *addr, uint32_t increase) {
 # define UA_BINARY_OVERLAYABLE_FLOAT false
 #endif
 
-/**
- * Embed unavailable libc functions
- * -------------------------------- */
-#ifdef UA_ENABLE_EMBEDDED_LIBC
-  void *memcpy(void *UA_RESTRICT dest, const void *UA_RESTRICT src, size_t n);
-  void *memset(void *dest, int c, size_t n);
-  size_t strlen(const char *s);
-  int memcmp(const void *vl, const void *vr, size_t n);
-#else
-# include <string.h>
-#endif
-
 #ifdef __cplusplus
 } // extern "C"
 #endif

+ 26 - 5
include/ua_server.h

@@ -171,10 +171,11 @@ typedef struct {
     UA_UInt32Range lifeTimeCountLimits;
     UA_UInt32Range keepAliveCountLimits;
     UA_UInt32 maxNotificationsPerPublish;
+    UA_UInt32 maxRetransmissionQueueSize; /* 0 -> unlimited size */
 
     /* Limits for MonitoredItems */
     UA_DoubleRange samplingIntervalLimits;
-    UA_UInt32Range queueSizeLimits;
+    UA_UInt32Range queueSizeLimits; /* Negotiated with the client */
 
 #ifdef UA_ENABLE_DISCOVERY
     /* Discovery */
@@ -185,7 +186,6 @@ typedef struct {
     // The server will still be removed depending on the state of the semaphore file.
     UA_UInt32 discoveryCleanupTimeout;
 #endif
-
 } UA_ServerConfig;
 
 /* Add a new namespace to the server. Returns the index of the new namespace */
@@ -393,7 +393,7 @@ UA_Server_readArrayDimensions(UA_Server *server, const UA_NodeId nodeId,
 
 static UA_INLINE UA_StatusCode
 UA_Server_readAccessLevel(UA_Server *server, const UA_NodeId nodeId,
-                          UA_UInt32 *outAccessLevel) {
+                          UA_Byte *outAccessLevel) {
     return __UA_Server_read(server, &nodeId, UA_ATTRIBUTEID_ACCESSLEVEL,
                             outAccessLevel);
 }
@@ -535,9 +535,9 @@ UA_Server_writeArrayDimensions(UA_Server *server, const UA_NodeId nodeId,
 
 static UA_INLINE UA_StatusCode
 UA_Server_writeAccessLevel(UA_Server *server, const UA_NodeId nodeId,
-                           const UA_UInt32 accessLevel) {
+                           const UA_Byte accessLevel) {
     return __UA_Server_write(server, &nodeId, UA_ATTRIBUTEID_ACCESSLEVEL,
-                             &UA_TYPES[UA_TYPES_UINT32], &accessLevel);
+                             &UA_TYPES[UA_TYPES_BYTE], &accessLevel);
 }
 
 static UA_INLINE UA_StatusCode
@@ -693,9 +693,30 @@ UA_Server_setVariableNode_dataSource(UA_Server *server, const UA_NodeId nodeId,
  * Value Callbacks can be attached to variable and variable type nodes. If
  * not-null, they are called before reading and after writing respectively. */
 typedef struct {
+    /* Pointer to user-provided data for the callback */
     void *handle;
+
+    /* Called before the value attribute is read. It is possible to write into the
+     * value attribute during onRead (using the write service). The node is
+     * re-opened afterwards so that changes are considered in the following read
+     * operation.
+     *
+     * @param handle Points to user-provided data for the callback.
+     * @param nodeid The identifier of the node.
+     * @param data Points to the current node value.
+     * @param range Points to the numeric range the client wants to read from
+     *        (or NULL). */
     void (*onRead)(void *handle, const UA_NodeId nodeid,
                    const UA_Variant *data, const UA_NumericRange *range);
+
+    /* Called after writing the value attribute. The node is re-opened after
+     * writing so that the new value is visible in the callback.
+     *
+     * @param handle Points to user-provided data for the callback.
+     * @param nodeid The identifier of the node.
+     * @param data Points to the current node value (after writing).
+     * @param range Points to the numeric range the client wants to write to (or
+     *        NULL). */
     void (*onWrite)(void *handle, const UA_NodeId nodeid,
                     const UA_Variant *data, const UA_NumericRange *range);
 } UA_ValueCallback;

+ 22 - 8
include/ua_types.h

@@ -20,9 +20,10 @@ extern "C" {
 
 #include "ua_config.h"
 #include "ua_constants.h"
-#include <stdbool.h>
 
 /**
+ * .. _types:
+ *
  * Data Types
  * ==========
  *
@@ -508,15 +509,17 @@ typedef struct UA_NumericRange UA_NumericRange;
 
 #define UA_EMPTY_ARRAY_SENTINEL ((void*)0x01)
 
-typedef struct {
-    const UA_DataType *type;      /* The data type description */
-    enum {
+typedef enum {
         UA_VARIANT_DATA,          /* The data has the same lifecycle as the
                                      variant */
         UA_VARIANT_DATA_NODELETE, /* The data is "borrowed" by the variant and
                                      shall not be deleted at the end of the
                                      variant's lifecycle. */
-    } storageType;
+} UA_VariantStorageType;
+
+typedef struct {
+    const UA_DataType *type;      /* The data type description */
+    UA_VariantStorageType storageType;
     size_t arrayLength;           /* The number of elements in the data array */
     void *data;                   /* Points to the scalar or array data */
     size_t arrayDimensionsSize;   /* The number of dimensions */
@@ -652,8 +655,7 @@ UA_Variant_setRangeCopy(UA_Variant *v, const void *array,
  * unknown to the receiver. See the section on :ref:`generic-types` on how types
  * are described. If the received data type is unkown, the encoded string and
  * target NodeId is stored instead of the decoded value. */
-typedef struct {
-    enum {
+typedef enum {
         UA_EXTENSIONOBJECT_ENCODED_NOBODY     = 0,
         UA_EXTENSIONOBJECT_ENCODED_BYTESTRING = 1,
         UA_EXTENSIONOBJECT_ENCODED_XML        = 2,
@@ -661,7 +663,10 @@ typedef struct {
         UA_EXTENSIONOBJECT_DECODED_NODELETE   = 4 /* Don't delete the content
                                                      together with the
                                                      ExtensionObject */
-    } encoding;
+} UA_ExtensionObjectEncoding;
+
+typedef struct {
+    UA_ExtensionObjectEncoding encoding;
     union {
         struct {
             UA_NodeId typeId;   /* The nodeid of the datatype */
@@ -764,6 +769,15 @@ struct UA_DataType {
     UA_DataTypeMember *members;
 };
 
+/**
+ * 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
+ * following method to retrieve the data type description. */
+/* Returns the data type description for the type's identifier or NULL if no
+ * matching data type was found. */
+const UA_DataType UA_EXPORT *
+UA_findDataType(const UA_NodeId *typeId);
+
 /** The following functions are used for generic handling of data types. */
 
 /* Allocates and initializes a variable of type dataType

+ 4 - 4
plugins/ua_accesscontrol_default.c

@@ -10,8 +10,8 @@
 #define USERNAME_POLICY "open62541-username-policy"
 
 #define UA_STRING_STATIC(s) {sizeof(s)-1, (UA_Byte*)s}
-const UA_String ap = UA_STRING_STATIC(ANONYMOUS_POLICY);
-const UA_String up = UA_STRING_STATIC(USERNAME_POLICY);
+const UA_String anonymous_policy = UA_STRING_STATIC(ANONYMOUS_POLICY);
+const UA_String username_policy = UA_STRING_STATIC(USERNAME_POLICY);
 
 UA_StatusCode
 activateSession_default(const UA_NodeId *sessionId, const UA_ExtensionObject *userIdentityToken,
@@ -28,7 +28,7 @@ activateSession_default(const UA_NodeId *sessionId, const UA_ExtensionObject *us
         /* Compatibility notice: Siemens OPC Scout v10 provides an empty
          * policyId. This is not compliant. For compatibility we will assume
          * that empty policyId == ANONYMOUS_POLICY */
-        if(token->policyId.data && !UA_String_equal(&token->policyId, &ap))
+        if(token->policyId.data && !UA_String_equal(&token->policyId, &anonymous_policy))
             return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
 
         *sessionHandle = NULL;
@@ -39,7 +39,7 @@ activateSession_default(const UA_NodeId *sessionId, const UA_ExtensionObject *us
     if(enableUsernamePasswordLogin &&
        userIdentityToken->content.decoded.type == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) {
         const UA_UserNameIdentityToken *token = userIdentityToken->content.decoded.data;
-        if(!UA_String_equal(&token->policyId, &up))
+        if(!UA_String_equal(&token->policyId, &username_policy))
             return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
 
         /* empty username and password */

+ 9 - 1
plugins/ua_config_standard.c

@@ -30,6 +30,9 @@ const UA_EXPORT UA_ConnectionConfig UA_ConnectionConfig_standard = {
 
 #define UA_STRING_STATIC(s) {sizeof(s)-1, (UA_Byte*)s}
 #define UA_STRING_STATIC_NULL {0, NULL}
+#define STRINGIFY(arg) #arg
+#define VERSION(MAJOR, MINOR, PATCH, LABEL) \
+    STRINGIFY(MAJOR) "." STRINGIFY(MINOR) "." STRINGIFY(PATCH) LABEL
 
 /* Access Control. The following definitions are defined as "extern" in
    ua_accesscontrol_default.h */
@@ -51,7 +54,10 @@ const UA_EXPORT UA_ServerConfig UA_ServerConfig_standard = {
         .productUri = UA_STRING_STATIC(PRODUCT_URI),
         .manufacturerName = UA_STRING_STATIC(MANUFACTURER_NAME),
         .productName = UA_STRING_STATIC(PRODUCT_NAME),
-        .softwareVersion = UA_STRING_STATIC(UA_GIT_COMMIT_ID),
+        .softwareVersion = UA_STRING_STATIC(VERSION(UA_OPEN62541_VER_MAJOR,
+                                                    UA_OPEN62541_VER_MINOR,
+                                                    UA_OPEN62541_VER_PATCH,
+                                                    UA_OPEN62541_VER_LABEL)),
         .buildNumber = UA_STRING_STATIC(__DATE__ " " __TIME__),
         .buildDate = 0 },
     .applicationDescription = {
@@ -99,6 +105,7 @@ const UA_EXPORT UA_ServerConfig UA_ServerConfig_standard = {
     .lifeTimeCountLimits = { .max = 15000, .min = 3 },
     .keepAliveCountLimits = { .max = 100, .min = 1 },
     .maxNotificationsPerPublish = 1000,
+    .maxRetransmissionQueueSize = 0, /* unlimited */
 
     /* Limits for MonitoredItems */
     .samplingIntervalLimits = { .min = 50.0, .max = 24.0 * 3600.0 * 1000.0 },
@@ -126,6 +133,7 @@ const UA_EXPORT UA_ClientConfig UA_ClientConfig_standard = {
     },
     .connectionFunc = UA_ClientConnectionTCP
 };
+
 /****************************************/
 /* Default Client Subscription Settings */
 /****************************************/

+ 3 - 1
plugins/ua_network_tcp.c

@@ -18,7 +18,9 @@
 #include <string.h> // memset
 #include <errno.h>
 #ifdef _WIN32
-# include <malloc.h>
+# ifndef __clang__
+#  include <malloc.h>
+# endif
 /* Fix redefinition of SLIST_ENTRY on mingw winnt.h */
 # ifdef SLIST_ENTRY
 #  undef SLIST_ENTRY

+ 12 - 12
src/client/ua_client.c

@@ -38,9 +38,9 @@ static void UA_Client_init(UA_Client* client, UA_ClientConfig config) {
     memset(client, 0, sizeof(UA_Client));
 
     client->state = UA_CLIENTSTATE_READY;
-    client->connection = UA_malloc(sizeof(UA_Connection));
+    client->connection = (UA_Connection*)UA_malloc(sizeof(UA_Connection));
     memset(client->connection, 0, sizeof(UA_Connection));
-    client->channel = UA_malloc(sizeof(UA_SecureChannel));
+    client->channel = (UA_SecureChannel*)UA_malloc(sizeof(UA_SecureChannel));
     UA_SecureChannel_init(client->channel);
     client->channel->connection = client->connection;
     client->authenticationMethod = UA_CLIENTAUTHENTICATION_NONE;
@@ -52,7 +52,7 @@ static void UA_Client_init(UA_Client* client, UA_ClientConfig config) {
 }
 
 UA_Client * UA_Client_new(UA_ClientConfig config) {
-    UA_Client *client = UA_calloc(1, sizeof(UA_Client));
+    UA_Client *client = (UA_Client*)UA_calloc(1, sizeof(UA_Client));
     if(!client)
         return NULL;
 
@@ -367,14 +367,14 @@ static UA_StatusCode ActivateSession(UA_Client *client) {
 
     //manual ExtensionObject encoding of the identityToken
     if(client->authenticationMethod == UA_CLIENTAUTHENTICATION_NONE) {
-        UA_AnonymousIdentityToken* identityToken = UA_malloc(sizeof(UA_AnonymousIdentityToken));
+        UA_AnonymousIdentityToken* identityToken = UA_AnonymousIdentityToken_new();
         UA_AnonymousIdentityToken_init(identityToken);
         UA_String_copy(&client->token.policyId, &identityToken->policyId);
         request.userIdentityToken.encoding = UA_EXTENSIONOBJECT_DECODED;
         request.userIdentityToken.content.decoded.type = &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN];
         request.userIdentityToken.content.decoded.data = identityToken;
     } else {
-        UA_UserNameIdentityToken* identityToken = UA_malloc(sizeof(UA_UserNameIdentityToken));
+        UA_UserNameIdentityToken* identityToken = UA_UserNameIdentityToken_new();
         UA_UserNameIdentityToken_init(identityToken);
         UA_String_copy(&client->token.policyId, &identityToken->policyId);
         UA_String_copy(&client->username, &identityToken->userName);
@@ -718,6 +718,11 @@ static void
 processServiceResponse(struct ResponseDescription *rd, UA_SecureChannel *channel,
                        UA_MessageType messageType, UA_UInt32 requestId,
                        UA_ByteString *message) {
+	const UA_NodeId expectedNodeId =
+        UA_NODEID_NUMERIC(0, rd->responseType->binaryEncodingId);
+    const UA_NodeId serviceFaultNodeId =
+        UA_NODEID_NUMERIC(0, UA_TYPES[UA_TYPES_SERVICEFAULT].binaryEncodingId);
+
     UA_StatusCode retval = UA_STATUSCODE_GOOD;
     UA_ResponseHeader *respHeader = (UA_ResponseHeader*)rd->response;
     rd->processed = true;
@@ -741,10 +746,6 @@ processServiceResponse(struct ResponseDescription *rd, UA_SecureChannel *channel
     }
 
     /* Check that the response type matches */
-    const UA_NodeId expectedNodeId =
-        UA_NODEID_NUMERIC(0, rd->responseType->binaryEncodingId);
-    const UA_NodeId serviceFaultNodeId =
-        UA_NODEID_NUMERIC(0, UA_TYPES[UA_TYPES_SERVICEFAULT].binaryEncodingId);
     size_t offset = 0;
     UA_NodeId responseId;
     retval = UA_NodeId_decodeBinary(message, &offset, &responseId);
@@ -799,7 +800,7 @@ __UA_Client_Service(UA_Client *client, const void *r, const UA_DataType *request
 
     /* Handling request parameters */
     //here const *r is 'violated'
-    UA_RequestHeader *request = (void*)(uintptr_t)r;
+    UA_RequestHeader *request = (UA_RequestHeader*)(uintptr_t)r;
     UA_NodeId_copy(&client->authenticationToken, &request->authenticationToken);
     request->timestamp = UA_DateTime_now();
     request->requestHandle = ++client->requestHandle;
@@ -821,8 +822,7 @@ __UA_Client_Service(UA_Client *client, const void *r, const UA_DataType *request
 
     /* Prepare the response and the structure we give into processServiceResponse */
     UA_init(response, responseType);
-    struct ResponseDescription rd = (struct ResponseDescription){
-        client, false, requestId, response, responseType};
+    struct ResponseDescription rd = {client, false, requestId, response, responseType};
 
     /* Retrieve the response */
     UA_DateTime maxDate = UA_DateTime_nowMonotonic() + (client->config.timeout * UA_MSEC_TO_DATETIME);

+ 24 - 10
src/server/ua_nodes.h

@@ -24,10 +24,9 @@ extern "C" {
  * Reference are triples of the form ``(source-nodeid, referencetype-nodeid,
  * target-nodeid)``. An example reference between nodes is a
  * ``hasTypeDefinition`` reference between a Variable and its VariableType. Some
- * ReferenceTypes are *hierarchic* and must not form *directed loops*. Every
- * node (except ``Root``) must have at least one hierarchic reference to a
- * parent. See the section on :ref:`ReferenceTypes <referencetypenode>` for more
- * details on possible references and their semantics.
+ * ReferenceTypes are *hierarchic* and must not form *directed loops*. See the
+ * section on :ref:`ReferenceTypes <referencetypenode>` for more details on
+ * possible references and their semantics.
  *
  * The structures defined in this section are *not user-facing*. The interaction
  * with the information model is possible only via the OPC UA :ref:`services`.
@@ -250,11 +249,8 @@ typedef struct {
  *   source and target node
  *
  * The figure below shows the hierarchy of the standard ReferenceTypes (arrows
- * indicate a ``hasSubType`` relation). Please refer to Part 3 of the OPC UA
- * specification for the full semantics of each ReferenceType. The ReferenceType
- * hierarchy can be extended with user-defined ReferenceTypes. Many Companion
- * Specifications for OPC UA define new ReferenceTypes to be used in their
- * domain of interest.
+ * indicate a ``hasSubType`` relation). Refer to Part 3 of the OPC UA
+ * specification for the full semantics of each ReferenceType.
  *
  * .. graphviz::
  *
@@ -320,7 +316,25 @@ typedef struct {
  *    {rank=same alwaysgeneratesevent hasproperty}
  *
  *    }
- */
+ *
+ * The ReferenceType hierarchy can be extended with user-defined ReferenceTypes.
+ * Many Companion Specifications for OPC UA define new ReferenceTypes to be used
+ * in their domain of interest.
+ *
+ * For the following example of custom ReferenceTypes, we attempt to model the
+ * structure of a technical system. For this, we introduce two custom
+ * ReferenceTypes. First, the hierarchical ``contains`` ReferenceType indicates
+ * that a system (represented by an OPC UA object) contains a component (or
+ * subsystem). This gives rise to a tree-structure of containment relations. For
+ * example, the motor (object) is contained in the car and the crankshaft is
+ * contained in the motor. Second, the symmetric ``connectedTo`` ReferenceType
+ * indicates that two components are connected. For example, the motor's
+ * crankshaft is connected to the gear box. Connections are independent of the
+ * containment hierarchy and can induce a general graph-structure. Further
+ * subtypes of ``connectedTo`` could be used to differentiate between physical,
+ * electrical and information related connections. A client can then learn the
+ * layout of a (physical) system represented in an OPC UA information model
+ * based on a common understanding of just two custom reference types. */
 typedef struct {
     UA_NODE_BASEATTRIBUTES
     UA_Boolean isAbstract;

+ 4 - 2
src/server/ua_server.c

@@ -179,6 +179,7 @@ addNodeInternalWithType(UA_Server *server, UA_Node *node, const UA_NodeId parent
 
 // delete any children of an instance without touching the object itself
 static void deleteInstanceChildren(UA_Server *server, UA_NodeId *objectNodeId) {
+    UA_RCU_LOCK();
   UA_BrowseDescription bDes;
   UA_BrowseDescription_init(&bDes);
   UA_NodeId_copy(objectNodeId, &bDes.nodeId );
@@ -208,6 +209,7 @@ static void deleteInstanceChildren(UA_Server *server, UA_NodeId *objectNodeId) {
     }
   }
   UA_BrowseResult_deleteMembers(&bRes); 
+  UA_RCU_UNLOCK();
 }
 
 /**********/
@@ -431,7 +433,7 @@ GetMonitoredItems(void *handle, const UA_NodeId *objectId,
 
     UA_UInt32 sizeOfOutput = 0;
     UA_MonitoredItem* monitoredItem;
-    LIST_FOREACH(monitoredItem, &subscription->MonitoredItems, listEntry) {
+    LIST_FOREACH(monitoredItem, &subscription->monitoredItems, listEntry) {
         ++sizeOfOutput;
     }
     if(sizeOfOutput==0)
@@ -440,7 +442,7 @@ GetMonitoredItems(void *handle, const UA_NodeId *objectId,
     UA_UInt32* clientHandles = UA_Array_new(sizeOfOutput, &UA_TYPES[UA_TYPES_UINT32]);
     UA_UInt32* serverHandles = UA_Array_new(sizeOfOutput, &UA_TYPES[UA_TYPES_UINT32]);
     UA_UInt32 i = 0;
-    LIST_FOREACH(monitoredItem, &subscription->MonitoredItems, listEntry) {
+    LIST_FOREACH(monitoredItem, &subscription->monitoredItems, listEntry) {
         clientHandles[i] = monitoredItem->clientHandle;
         serverHandles[i] = monitoredItem->itemId;
         ++i;

+ 2 - 2
src/server/ua_server_internal.h

@@ -192,7 +192,7 @@ getNodeType(UA_Server *server, const UA_Node *node);
 /***************************************/
 
 UA_StatusCode
-readValueAttribute(const UA_VariableNode *vn, UA_DataValue *v);
+readValueAttribute(UA_Server *server, const UA_VariableNode *vn, UA_DataValue *v);
 
 UA_StatusCode
 typeCheckValue(UA_Server *server, const UA_NodeId *variableDataTypeId,
@@ -211,7 +211,7 @@ compatibleArrayDimensions(size_t constraintArrayDimensionsSize,
                           const UA_UInt32 *testArrayDimensions);
 
 UA_StatusCode
-writeValueRankAttribute(UA_VariableNode *node, UA_Int32 valueRank,
+writeValueRankAttribute(UA_Server *server, UA_VariableNode *node, UA_Int32 valueRank,
                         UA_Int32 constraintValueRank);
 
 UA_StatusCode

+ 78 - 35
src/server/ua_server_worker.c

@@ -30,6 +30,10 @@
  * [1] Fraser, K. 2003. Practical lock freedom. Ph.D. thesis. Computer Laboratory, University of Cambridge.
  * [2] Hart, T. E., McKenney, P. E., Brown, A. D., & Walpole, J. (2007). Performance of memory reclamation
  *     for lockless synchronization. Journal of Parallel and Distributed Computing, 67(12), 1270-1285.
+ *
+ * Future Plans: Use work-stealing to load-balance between cores.
+ * [3] Le, Nhat Minh, et al. "Correct and efficient work-stealing for weak
+ *     memory models." ACM SIGPLAN Notices. Vol. 48. No. 8. ACM, 2013.
  */
 
 #define MAXTIMEOUT 50 // max timeout in millisec until the next main loop iteration
@@ -153,21 +157,31 @@ struct RepeatedJob {
 
 /* internal. call only from the main loop. */
 static void
-addRepeatedJob(UA_Server *server, struct RepeatedJob * UA_RESTRICT rj)
-{
-    /* search for matching entry */
-    struct RepeatedJob *lastRj = NULL; /* Add after this entry or at LIST_HEAD if NULL */
-    struct RepeatedJob *tempRj = LIST_FIRST(&server->repeatedJobs);
-    while(tempRj) {
-        if(tempRj->nextTime > rj->nextTime)
+addRepeatedJob(UA_Server *server, struct RepeatedJob * UA_RESTRICT rj) {
+    /* Search for the best position on the repeatedJobs sorted list. The goal is
+     * to have many repeated jobs with the same repetition interval in a
+     * "block". This helps to reduce the (linear) search to find the next entry
+     * in the repeatedJobs list when dispatching the repeated jobs.
+     * For this, we search between "nexttime_max - 1s" and "nexttime_max" for
+     * entries with the same repetition interval and adjust the "nexttime".
+     * Otherwise, add entry after the first element before "nexttime_max". */
+    UA_DateTime nextTime_max = UA_DateTime_nowMonotonic() + (UA_Int64) rj->interval;
+
+    struct RepeatedJob *afterRj = NULL;
+    struct RepeatedJob *tmpRj;
+    LIST_FOREACH(tmpRj, &server->repeatedJobs, next) {
+        if(tmpRj->nextTime >= nextTime_max)
             break;
-        lastRj = tempRj;
-        tempRj = LIST_NEXT(lastRj, next);
+        if(tmpRj->interval == rj->interval &&
+           tmpRj->nextTime > (nextTime_max - UA_SEC_TO_DATETIME))
+            nextTime_max = tmpRj->nextTime; /* break in the next iteration */
+        afterRj = tmpRj;
     }
 
     /* add the repeated job */
-    if(lastRj)
-        LIST_INSERT_AFTER(lastRj, rj, next);
+    rj->nextTime = nextTime_max;
+    if(afterRj)
+        LIST_INSERT_AFTER(afterRj, rj, next);
     else
         LIST_INSERT_HEAD(&server->repeatedJobs, rj, next);
 }
@@ -184,7 +198,8 @@ UA_Server_addRepeatedJob(UA_Server *server, UA_Job job,
     struct RepeatedJob *rj = UA_malloc(sizeof(struct RepeatedJob));
     if(!rj)
         return UA_STATUSCODE_BADOUTOFMEMORY;
-    rj->nextTime = UA_DateTime_nowMonotonic() + (UA_Int64) interval;
+    /* done inside addRepeatedJob:
+     * rj->nextTime = UA_DateTime_nowMonotonic() + interval; */
     rj->interval = interval;
     rj->id = UA_Guid_random();
     rj->job = job;
@@ -209,10 +224,12 @@ UA_Server_addRepeatedJob(UA_Server *server, UA_Job job,
     return UA_STATUSCODE_GOOD;
 }
 
-/* Returns the next datetime when a repeated job is scheduled */
+/* - Dispatches all repeated jobs that have timed out
+ * - Reinserts dispatched job at their new position in the sorted list
+ * - Returns the next datetime when a repeated job is scheduled */
 static UA_DateTime
-processRepeatedJobs(UA_Server *server, UA_DateTime current) {
-    /* Find the last job that is executed after this iteration */
+processRepeatedJobs(UA_Server *server, UA_DateTime current, UA_Boolean *dispatched) {
+    /* Find the last job that is executed in this iteration */
     struct RepeatedJob *lastNow = NULL, *tmp;
     LIST_FOREACH(tmp, &server->repeatedJobs, next) {
         if(tmp->nextTime > current)
@@ -220,23 +237,28 @@ processRepeatedJobs(UA_Server *server, UA_DateTime current) {
         lastNow = tmp;
     }
 
-    /* Iterate over the list of elements (sorted according to the next execution timestamp) */
+    /* Keep pointer to the previously dispatched job to avoid linear search for
+     * "batched" jobs with the same nexttime and interval */
+    struct RepeatedJob tmp_last;
+    tmp_last.nextTime = current-1; /* never matches. just to avoid if(last_added && ...) */
+    struct RepeatedJob *last_dispatched = &tmp_last;
+
+    /* Iterate over the list of elements (sorted according to the nextTime timestamp) */
     struct RepeatedJob *rj, *tmp_rj;
     LIST_FOREACH_SAFE(rj, &server->repeatedJobs, next, tmp_rj) {
         if(rj->nextTime > current)
             break;
 
-        UA_assert(lastNow); /* at least one element at the current time */
-
         /* Dispatch/process job */
 #ifdef UA_ENABLE_MULTITHREADING
         dispatchJob(server, &rj->job);
+        *dispatched = true;
 #else
         struct RepeatedJob **previousNext = rj->next.le_prev;
         processJob(server, &rj->job);
         /* See if the current job was deleted during processJob. That means the
-           le_next field of the previous repeated job (could also be the list
-           head) does no longer point to the current repeated job */
+         * le_next field of the previous repeated job (could also be the list
+         * head) does no longer point to the current repeated job */
         if((void*)*previousNext != (void*)rj) {
             UA_LOG_DEBUG(server->config.logger, UA_LOGCATEGORY_SERVER,
                          "The current repeated job removed itself");
@@ -246,23 +268,40 @@ processRepeatedJobs(UA_Server *server, UA_DateTime current) {
 
         /* Set the time for the next execution */
         rj->nextTime += (UA_Int64)rj->interval;
+
+        /* Prevent an infinite loop when the repeated jobs took more time than
+         * rj->interval */
         if(rj->nextTime < current)
-            rj->nextTime = current + 1; /* prevent to rerun the job right now
-                                           when the repeated jobs took more time
-                                           than rj->interval */
-
-        /* Keep the list sorted */
-        struct RepeatedJob *prev_rj = lastNow;
-        while(true) {
-            struct RepeatedJob *n = LIST_NEXT(prev_rj, next);
-            if(!n || n->nextTime >= rj->nextTime)
-                break;
-            prev_rj = n;
+            rj->nextTime = current + 1;
+
+        /* Find new position for rj to keep the list sorted */
+        struct RepeatedJob *prev_rj;
+        if(last_dispatched->nextTime == rj->nextTime) {
+            /* We "batch" repeatedJobs with the same interval in
+             * addRepeatedJobs. So this might occur quite often. */
+            UA_assert(last_dispatched != &tmp_last);
+            prev_rj = last_dispatched;
+        } else {
+            /* Find the position by a linear search starting at the first
+             * possible job */
+            UA_assert(lastNow); /* Not NULL. Otherwise, we never reach this point. */
+            prev_rj = lastNow;
+            while(true) {
+                struct RepeatedJob *n = LIST_NEXT(prev_rj, next);
+                if(!n || n->nextTime >= rj->nextTime)
+                    break;
+                prev_rj = n;
+            }
         }
+
+        /* Add entry */
         if(prev_rj != rj) {
             LIST_REMOVE(rj, next);
             LIST_INSERT_AFTER(prev_rj, rj, next);
         }
+
+        /* Update last_dispatched and loop */
+        last_dispatched = rj;
     }
 
     /* Check if the next repeated job is sooner than the usual timeout */
@@ -556,7 +595,8 @@ UA_UInt16 UA_Server_run_iterate(UA_Server *server, UA_Boolean waitInternal) {
 #endif
     /* Process repeated work */
     UA_DateTime now = UA_DateTime_nowMonotonic();
-    UA_DateTime nextRepeated = processRepeatedJobs(server, now);
+    UA_Boolean dispatched = false; /* to wake up worker threads */
+    UA_DateTime nextRepeated = processRepeatedJobs(server, now, &dispatched);
 
     UA_UInt16 timeout = 0;
     if(waitInternal)
@@ -591,18 +631,21 @@ UA_UInt16 UA_Server_run_iterate(UA_Server *server, UA_Boolean waitInternal) {
         for(size_t j = 0; j < jobsSize; ++j) {
 #ifdef UA_ENABLE_MULTITHREADING
             dispatchJob(server, &jobs[j]);
+            dispatched = true;
 #else
             processJob(server, &jobs[j]);
 #endif
         }
 
-        if(jobsSize > 0) {
 #ifdef UA_ENABLE_MULTITHREADING
-            /* Wake up worker threads */
+        /* Wake up worker threads */
+        if(dispatched)
             pthread_cond_broadcast(&server->dispatchQueue_condition);
 #endif
+
+        /* Clean up jobs list */
+        if(jobsSize > 0)
             UA_free(jobs);
-        }
     }
 
 #ifndef UA_ENABLE_MULTITHREADING

+ 2 - 7
src/server/ua_services.h

@@ -28,8 +28,8 @@ extern "C" {
  * are exposed to end users. Please see part 4 of the OPC UA standard for the
  * authoritative definition of the service and their behaviour. */
 /* Most services take as input the server, the current session and pointers to
-   the request and response structures. Possible error codes are returned as
-   part of the response. */
+ * the request and response structures. Possible error codes are returned as
+ * part of the response. */
 typedef void (*UA_Service)(UA_Server*, UA_Session*,
                            const void *request, void *response);
 
@@ -61,7 +61,6 @@ void Service_RegisterServer(UA_Server *server, UA_Session *session,
  * This Service Set defines Services used to open a communication channel that
  * ensures the confidentiality and Integrity of all Messages exchanged with the
  * Server. */
-
 /* Open or renew a SecureChannel that can be used to ensure Confidentiality and
  * Integrity for Message exchange during a Session. */
 void Service_OpenSecureChannel(UA_Server *server, UA_Connection *connection,
@@ -76,7 +75,6 @@ void Service_CloseSecureChannel(UA_Server *server, UA_SecureChannel *channel);
  * -------------------
  * This Service Set defines Services for an application layer connection
  * establishment in the context of a Session. */
-
 /* Used by an OPC UA Client to create a Session and the Server returns two
  * values which uniquely identify the Session. The first value is the sessionId
  * which is used to identify the Session in the audit logs and in the Server's
@@ -110,7 +108,6 @@ void Service_CloseSession(UA_Server *server, UA_Session *session,
  * References between them. All added Nodes continue to exist in the
  * AddressSpace even if the Client that created them disconnects from the
  * Server. */
-
 /* Used to add one or more Nodes into the AddressSpace hierarchy. */
 void Service_AddNodes(UA_Server *server, UA_Session *session,
                       const UA_AddNodesRequest *request,
@@ -138,7 +135,6 @@ void Service_DeleteReferences(UA_Server *server, UA_Session *session,
  * ----------------
  * Clients use the browse Services of the View Service Set to navigate through
  * the AddressSpace or through a View which is a subset of the AddressSpace. */
-
 /* Used to discover the References of a specified Node. The browse can be
  * further limited by the use of a View. This Browse Service also supports a
  * primitive filtering capability. */
@@ -191,7 +187,6 @@ void Service_UnregisterNodes(UA_Server *server, UA_Session *session,
  * ---------------------
  * This Service Set provides Services to access Attributes that are part of
  * Nodes. */
-
 /* Used to read one or more Attributes of one or more Nodes. For constructed
  * Attribute values whose elements are indexed, such as an array, this Service
  * allows Clients to read the entire set of indexed values as a composite, to

+ 183 - 161
src/server/ua_services_attribute.c

@@ -35,15 +35,6 @@ typeEquivalence(const UA_DataType *t) {
     return TYPE_EQUIVALENCE_NONE;
 }
 
-static const UA_DataType *
-findDataType(const UA_NodeId *typeId) {
-    for(size_t i = 0; i < UA_TYPES_COUNT; ++i) {
-        if(UA_TYPES[i].typeId.identifier.numeric == typeId->identifier.numeric)
-            return &UA_TYPES[i];
-    }
-    return NULL;
-}
-
 /* Test whether a valurank and the given arraydimensions are compatible. zero
  * array dimensions indicate a scalar */
 static UA_StatusCode
@@ -103,6 +94,43 @@ compatibleArrayDimensions(size_t constraintArrayDimensionsSize,
     return UA_STATUSCODE_GOOD;
 }
 
+/* Returns the pointer to a datavalue with a possibly transformed type to match
+   the description */
+static const UA_Variant *
+convertToMatchingValue(UA_Server *server, const UA_Variant *value,
+                       const UA_NodeId *targetDataTypeId, UA_Variant *editableValue) {
+    const UA_DataType *targetDataType = UA_findDataType(targetDataTypeId);
+    if(!targetDataType)
+        return NULL;
+
+    /* A string is written to a byte array. the valuerank and array
+       dimensions are checked later */
+    if(targetDataType == &UA_TYPES[UA_TYPES_BYTE] &&
+       value->type == &UA_TYPES[UA_TYPES_BYTESTRING] &&
+       UA_Variant_isScalar(value)) {
+        UA_ByteString *str = (UA_ByteString*)value->data;
+        editableValue->storageType = UA_VARIANT_DATA_NODELETE;
+        editableValue->type = &UA_TYPES[UA_TYPES_BYTE];
+        editableValue->arrayLength = str->length;
+        editableValue->data = str->data;
+        return editableValue;
+    }
+
+    /* An enum was sent as an int32, or an opaque type as a bytestring. This
+     * is detected with the typeIndex indicating the "true" datatype. */
+    enum type_equivalence te1 = typeEquivalence(targetDataType);
+    enum type_equivalence te2 = typeEquivalence(value->type);
+    if(te1 != TYPE_EQUIVALENCE_NONE && te1 == te2) {
+        *editableValue = *value;
+        editableValue->storageType = UA_VARIANT_DATA_NODELETE;
+        editableValue->type = targetDataType;
+        return editableValue;
+    }
+
+    /* No more possible equivalencies */
+    return NULL;
+}
+
 /* Test whether the value matches a variable definition given by
  * - datatype
  * - valueranke
@@ -111,77 +139,51 @@ compatibleArrayDimensions(size_t constraintArrayDimensionsSize,
  * byte array to bytestring or uint32 to some enum. If editableValue is non-NULL,
  * we try to create a matching variant that points to the original data. */
 UA_StatusCode
-typeCheckValue(UA_Server *server, const UA_NodeId *variableDataTypeId,
-               UA_Int32 variableValueRank, size_t variableArrayDimensionsSize,
-               const UA_UInt32 *variableArrayDimensions, const UA_Variant *value,
+typeCheckValue(UA_Server *server, const UA_NodeId *targetDataTypeId,
+               UA_Int32 targetValueRank, size_t targetArrayDimensionsSize,
+               const UA_UInt32 *targetArrayDimensions, const UA_Variant *value,
                const UA_NumericRange *range, UA_Variant *editableValue) {
-    /* No content is only allowed for BaseDataType */
-    const UA_NodeId *valueDataTypeId;
+    /* Empty variant is only allowed for BaseDataType */
     UA_NodeId basedatatype = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE);
-    if(value->type)
-        valueDataTypeId = &value->type->typeId;
-    else
-        valueDataTypeId = &basedatatype;
-    
-    /* See if the types match. The nodeid on the wire may be != the nodeid in
-     * the node for opaque types, enums and bytestrings. value contains the
-     * correct type definition after the following paragraph */
-    if(!UA_NodeId_equal(valueDataTypeId, variableDataTypeId)) {
-        /* contains the value a subtype of the required type? */
-        const UA_NodeId subtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
-        if(isNodeInTree(server->nodestore, valueDataTypeId,
-                        variableDataTypeId, &subtypeId, 1))
+    if(!value->type) {
+        if(UA_NodeId_equal(targetDataTypeId, &basedatatype))
             goto check_array;
-
-        const UA_DataType *variableDataType = findDataType(variableDataTypeId);
-        const UA_DataType *valueDataType = findDataType(valueDataTypeId);
-        if(!editableValue || !variableDataType || !valueDataType)
+        else
             return UA_STATUSCODE_BADTYPEMISMATCH;
+    }
 
-        /* a string is written to a byte array. the valuerank and array
-           dimensions are checked later */
-        if(variableDataType == &UA_TYPES[UA_TYPES_BYTE] &&
-           valueDataType == &UA_TYPES[UA_TYPES_BYTESTRING] &&
-           !range && UA_Variant_isScalar(value)) {
-            UA_ByteString *str = (UA_ByteString*)value->data;
-            editableValue->storageType = UA_VARIANT_DATA_NODELETE;
-            editableValue->type = &UA_TYPES[UA_TYPES_BYTE];
-            editableValue->arrayLength = str->length;
-            editableValue->data = str->data;
-            value = editableValue;
-            goto check_array;
-        }
+    /* See if the types match. The nodeid on the wire may be != the nodeid in
+     * the node for opaque types, enums and bytestrings. value contains the
+     * correct type definition after the following paragraph */
+    if(UA_NodeId_equal(&value->type->typeId, targetDataTypeId))
+        goto check_array;
 
-        /* An enum was sent as an int32, or an opaque type as a bytestring. This
-         * is detected with the typeIndex indicating the "true" datatype. */
-        enum type_equivalence te1 = typeEquivalence(variableDataType);
-        enum type_equivalence te2 = typeEquivalence(valueDataType);
-        if(te1 != TYPE_EQUIVALENCE_NONE && te1 == te2) {
-            *editableValue = *value;
-            editableValue->storageType = UA_VARIANT_DATA_NODELETE;
-            editableValue->type = variableDataType;
-            value = editableValue;
-            goto check_array;
-        }
+    /* Has the value a subtype of the required type? */
+    const UA_NodeId subtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
+    if(isNodeInTree(server->nodestore, &value->type->typeId, targetDataTypeId, &subtypeId, 1))
+        goto check_array;
 
-        /* No more possible equivalencies */
+    /* Try to convert to a matching value if this is wanted */
+    if(!editableValue)
+        return UA_STATUSCODE_BADTYPEMISMATCH;
+    value = convertToMatchingValue(server, value, targetDataTypeId, editableValue);
+    if(!value)
         return UA_STATUSCODE_BADTYPEMISMATCH;
-    }
 
  check_array:
-    if(range) /* array dimensions are checked when writing the range */
+    if(range) /* array dimensions are checked later when writing the range */
         return UA_STATUSCODE_GOOD;
 
     /* See if the array dimensions match. When arrayDimensions are defined, they
      * already hold the valuerank. */
-    if(variableArrayDimensionsSize > 0)
-        return compatibleArrayDimensions(variableArrayDimensionsSize,
-                                         variableArrayDimensions,
+    if(targetArrayDimensionsSize > 0)
+        return compatibleArrayDimensions(targetArrayDimensionsSize,
+                                         targetArrayDimensions,
                                          value->arrayDimensionsSize,
                                          value->arrayDimensions);
 
     /* Check if the valuerank allows for the value dimension */
-    return compatibleValueRankValue(variableValueRank, value);
+    return compatibleValueRankValue(targetValueRank, value);
 }
 
 /*****************************/
@@ -234,7 +236,7 @@ writeArrayDimensionsAttribute(UA_Server *server, UA_VariableNode *node,
     /* Check if the current value is compatible with the array dimensions */
     UA_DataValue value;
     UA_DataValue_init(&value);
-    retval = readValueAttribute(node, &value);
+    retval = readValueAttribute(server, node, &value);
     if(retval != UA_STATUSCODE_GOOD)
         return retval;
     if(value.hasValue) {
@@ -270,18 +272,18 @@ writeValueRankAttributeWithVT(UA_Server *server, UA_VariableNode *node,
     const UA_VariableTypeNode *vt = getVariableNodeType(server, node);
     if(!vt)
         return UA_STATUSCODE_BADINTERNALERROR;
-    return writeValueRankAttribute(node, valueRank, vt->valueRank);
+    return writeValueRankAttribute(server, node, valueRank, vt->valueRank);
 }
 
 UA_StatusCode
-writeValueRankAttribute(UA_VariableNode *node, UA_Int32 valueRank,
+writeValueRankAttribute(UA_Server *server, UA_VariableNode *node, UA_Int32 valueRank,
                         UA_Int32 constraintValueRank) {
     /* If this is a variabletype, there must be no instances or subtypes of it
        when we do the change */
     if(node->nodeClass == UA_NODECLASS_VARIABLETYPE &&
        UA_Node_hasSubTypeOrInstances((const UA_Node*)node))
         return UA_STATUSCODE_BADINTERNALERROR;
-        
+
     /* Check if the valuerank of the variabletype allows the change. */
     switch(constraintValueRank) {
     case -3: /* the value can be a scalar or a one dimensional array */
@@ -313,7 +315,7 @@ writeValueRankAttribute(UA_VariableNode *node, UA_Int32 valueRank,
            dimensions zero indicate a scalar for compatibleValueRankArrayDimensions. */
         UA_DataValue value;
         UA_DataValue_init(&value);
-        retval = readValueAttribute(node, &value);
+        retval = readValueAttribute(server, node, &value);
         if(retval != UA_STATUSCODE_GOOD)
             return retval;
         if(!value.hasValue || value.value.data == NULL)
@@ -364,7 +366,7 @@ writeDataTypeAttribute(UA_Server *server, UA_VariableNode *node,
     /* Check if the current value would match the new type */
     UA_DataValue value;
     UA_DataValue_init(&value);
-    UA_StatusCode retval = readValueAttribute(node, &value);
+    UA_StatusCode retval = readValueAttribute(server, node, &value);
     if(retval != UA_STATUSCODE_GOOD)
         return retval;
     if(value.hasValue) {
@@ -378,7 +380,7 @@ writeDataTypeAttribute(UA_Server *server, UA_VariableNode *node,
             return retval;
         }
     }
-    
+
     /* Replace the datatype nodeid */
     UA_NodeId dtCopy = node->dataType;
     retval = UA_NodeId_copy(dataType, &node->dataType);
@@ -387,7 +389,7 @@ writeDataTypeAttribute(UA_Server *server, UA_VariableNode *node,
         return retval;
     }
     UA_NodeId_deleteMembers(&dtCopy);
-    return UA_STATUSCODE_GOOD; 
+    return UA_STATUSCODE_GOOD;
 }
 
 /*******************/
@@ -395,41 +397,55 @@ writeDataTypeAttribute(UA_Server *server, UA_VariableNode *node,
 /*******************/
 
 static UA_StatusCode
-readValueAttributeComplete(const UA_VariableNode *vn, UA_TimestampsToReturn timestamps,
-                           const UA_ReadValueId *id, UA_DataValue *v) {
+readValueAttributeFromNode(UA_Server *server, const UA_VariableNode *vn, UA_DataValue *v,
+                           UA_NumericRange *rangeptr) {
+    if(vn->value.data.callback.onRead) {
+        vn->value.data.callback.onRead(vn->value.data.callback.handle,
+                                       vn->nodeId, &vn->value.data.value.value, rangeptr);
+#ifdef UA_ENABLE_MULTITHREADING
+        /* Reopen the node to see the changes (multithreading only) */
+        vn = (const UA_VariableNode*)UA_NodeStore_get(server->nodestore, &vn->nodeId);
+#endif
+    }
+    if(rangeptr)
+        return UA_Variant_copyRange(&vn->value.data.value.value, &v->value, *rangeptr);
+    *v = vn->value.data.value;
+    v->value.storageType = UA_VARIANT_DATA_NODELETE;
+    return UA_STATUSCODE_GOOD;
+}
+
+static UA_StatusCode
+readValueAttributeFromDataSource(const UA_VariableNode *vn, UA_DataValue *v,
+                                 UA_TimestampsToReturn timestamps,
+                                 UA_NumericRange *rangeptr) {
+    if(!vn->value.dataSource.read)
+        return UA_STATUSCODE_BADINTERNALERROR;
+    UA_Boolean sourceTimeStamp = (timestamps == UA_TIMESTAMPSTORETURN_SOURCE ||
+                                  timestamps == UA_TIMESTAMPSTORETURN_BOTH);
+    return vn->value.dataSource.read(vn->value.dataSource.handle, vn->nodeId,
+                                     sourceTimeStamp, rangeptr, v);
+}
+
+static UA_StatusCode
+readValueAttributeComplete(UA_Server *server, const UA_VariableNode *vn,
+                           UA_TimestampsToReturn timestamps, const UA_String *indexRange,
+                           UA_DataValue *v) {
     /* Compute the index range */
     UA_NumericRange range;
     UA_NumericRange *rangeptr = NULL;
     UA_StatusCode retval = UA_STATUSCODE_GOOD;
-    if(id->indexRange.length > 0) {
-        retval = parse_numericrange(&id->indexRange, &range);
+    if(indexRange && indexRange->length > 0) {
+        retval = parse_numericrange(indexRange, &range);
         if(retval != UA_STATUSCODE_GOOD)
             return retval;
         rangeptr = &range;
     }
 
-    if(vn->valueSource == UA_VALUESOURCE_DATA) {
-        /* Data in the node */
-        if(vn->value.data.callback.onRead)
-            vn->value.data.callback.onRead(vn->value.data.callback.handle,
-                                           vn->nodeId, &v->value, rangeptr);
-        if(!rangeptr) {
-            *v = vn->value.data.value;
-            v->value.storageType = UA_VARIANT_DATA_NODELETE;
-        } else {
-            retval = UA_Variant_copyRange(&vn->value.data.value.value, &v->value, range);
-        }
-    } else {
-        /* Data in a value source */
-        if(!vn->value.dataSource.read) {
-            retval = UA_STATUSCODE_BADINTERNALERROR;
-        } else {
-            UA_Boolean sourceTimeStamp = (timestamps == UA_TIMESTAMPSTORETURN_SOURCE ||
-                                          timestamps == UA_TIMESTAMPSTORETURN_BOTH);
-            retval = vn->value.dataSource.read(vn->value.dataSource.handle, vn->nodeId,
-                                               sourceTimeStamp, rangeptr, v);
-        }
-    }
+    /* Read the value */
+    if(vn->valueSource == UA_VALUESOURCE_DATA)
+        retval = readValueAttributeFromNode(server, vn, v, rangeptr);
+    else
+        retval = readValueAttributeFromDataSource(vn, v, timestamps, rangeptr);
 
     /* Clean up */
     if(rangeptr)
@@ -438,56 +454,52 @@ readValueAttributeComplete(const UA_VariableNode *vn, UA_TimestampsToReturn time
 }
 
 UA_StatusCode
-readValueAttribute(const UA_VariableNode *vn, UA_DataValue *v) {
-    UA_TimestampsToReturn timestamps = UA_TIMESTAMPSTORETURN_NEITHER;
-    UA_ReadValueId id;
-    UA_ReadValueId_init(&id);
-    return readValueAttributeComplete(vn, timestamps, &id, v);
+readValueAttribute(UA_Server *server, const UA_VariableNode *vn, UA_DataValue *v) {
+    return readValueAttributeComplete(server, vn, UA_TIMESTAMPSTORETURN_NEITHER, NULL, v);
 }
 
 static UA_StatusCode
-writeValueAttributeAfterTypeCheck(UA_VariableNode *node, const UA_DataValue *value,
-                                  const UA_NumericRange *rangeptr) {
-    UA_StatusCode retval = UA_STATUSCODE_GOOD;
-    if(!rangeptr) {
-        /* Replace without a range */
-        UA_DataValue old_value = node->value.data.value; /* keep the pointers for restoring */
-        retval = UA_DataValue_copy(value, &node->value.data.value);
-        if(retval == UA_STATUSCODE_GOOD)
-            UA_DataValue_deleteMembers(&old_value);
-        else
-            node->value.data.value = old_value; 
-    } else {
-        /* Write into a range */
-        if(value->status == UA_STATUSCODE_GOOD) {
-            if(!node->value.data.value.hasValue || !value->hasValue)
-                return UA_STATUSCODE_BADINDEXRANGEINVALID;
-
-            /* Make scalar a one-entry array for range matching */
-            UA_Variant editableValue;
-            const UA_Variant *v = &value->value;
-            if(UA_Variant_isScalar(&value->value)) {
-                editableValue = value->value;
-                editableValue.arrayLength = 1;
-                v = &editableValue;
-            }
-                
-            /* Write the value */
-            retval = UA_Variant_setRangeCopy(&node->value.data.value.value,
-                                             v->data, v->arrayLength, *rangeptr);
-            if(retval != UA_STATUSCODE_GOOD)
-                return retval;
-        }
+writeValueAttributeWithoutRange(UA_VariableNode *node, const UA_DataValue *value) {
+    UA_DataValue old_value = node->value.data.value; /* keep the pointers for restoring */
+    UA_StatusCode retval = UA_DataValue_copy(value, &node->value.data.value);
+    if(retval == UA_STATUSCODE_GOOD)
+        UA_DataValue_deleteMembers(&old_value);
+    else
+        node->value.data.value = old_value;
+    return retval;
+}
 
-        /* Write the status and timestamps */
-        node->value.data.value.hasStatus = value->hasStatus;
-        node->value.data.value.status = value->status;
-        node->value.data.value.hasSourceTimestamp = value->hasSourceTimestamp;
-        node->value.data.value.sourceTimestamp = value->sourceTimestamp;
-        node->value.data.value.hasSourcePicoseconds = value->hasSourcePicoseconds;
-        node->value.data.value.sourcePicoseconds = value->sourcePicoseconds;
+static UA_StatusCode
+writeValueAttributeWithRange(UA_VariableNode *node, const UA_DataValue *value,
+                             const UA_NumericRange *rangeptr) {
+    /* Value on both sides? */
+    if(value->status != node->value.data.value.status ||
+       !value->hasValue || !node->value.data.value.hasValue)
+        return UA_STATUSCODE_BADINDEXRANGEINVALID;
+
+    /* Make scalar a one-entry array for range matching */
+    UA_Variant editableValue;
+    const UA_Variant *v = &value->value;
+    if(UA_Variant_isScalar(&value->value)) {
+        editableValue = value->value;
+        editableValue.arrayLength = 1;
+        v = &editableValue;
     }
-    return retval;
+
+    /* Write the value */
+    UA_StatusCode retval = UA_Variant_setRangeCopy(&node->value.data.value.value,
+                                                   v->data, v->arrayLength, *rangeptr);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
+
+    /* Write the status and timestamps */
+    node->value.data.value.hasStatus = value->hasStatus;
+    node->value.data.value.status = value->status;
+    node->value.data.value.hasSourceTimestamp = value->hasSourceTimestamp;
+    node->value.data.value.sourceTimestamp = value->sourceTimestamp;
+    node->value.data.value.hasSourcePicoseconds = value->hasSourcePicoseconds;
+    node->value.data.value.sourcePicoseconds = value->sourcePicoseconds;
+    return UA_STATUSCODE_GOOD;
 }
 
 UA_StatusCode
@@ -509,30 +521,41 @@ writeValueAttribute(UA_Server *server, UA_VariableNode *node,
     UA_DataValue editableValue = *value;
     editableValue.value.storageType = UA_VARIANT_DATA_NODELETE;
 
-    /* Set the source timestamp if there is none */
-    if(!editableValue.hasSourceTimestamp) {
-        editableValue.sourceTimestamp = UA_DateTime_now();
-        editableValue.hasSourceTimestamp = true;
-    }
-
     /* Type checking. May change the type of editableValue */
     if(value->hasValue) {
         retval = typeCheckValue(server, &node->dataType, node->valueRank,
                                 node->arrayDimensionsSize, node->arrayDimensions,
                                 &value->value, rangeptr, &editableValue.value);
-        if(retval != UA_STATUSCODE_GOOD) {
-            UA_LOG_DEBUG(server->config.logger, UA_LOGCATEGORY_SERVER,
-                         "The new value does not match the variable definiton");
+        if(retval != UA_STATUSCODE_GOOD)
             goto cleanup;
-        }
+    }
+
+    /* Set the source timestamp if there is none */
+    if(!editableValue.hasSourceTimestamp) {
+        editableValue.sourceTimestamp = UA_DateTime_now();
+        editableValue.hasSourceTimestamp = true;
     }
 
     /* Ok, do it */
     if(node->valueSource == UA_VALUESOURCE_DATA) {
-        retval = writeValueAttributeAfterTypeCheck(node, &editableValue, rangeptr);
-        if(retval == UA_STATUSCODE_GOOD && node->value.data.callback.onWrite)
-            node->value.data.callback.onWrite(node->value.data.callback.handle, node->nodeId,
-                                              &node->value.data.value.value, rangeptr);
+        if(!rangeptr)
+            retval = writeValueAttributeWithoutRange(node, &editableValue);
+        else
+            retval = writeValueAttributeWithRange(node, &editableValue, rangeptr);
+
+        /* Callback after writing */
+        if(retval == UA_STATUSCODE_GOOD && node->value.data.callback.onWrite) {
+            const UA_VariableNode *writtenNode;
+#ifdef UA_ENABLE_MULTITHREADING
+            /* Reopen the node to see the changes (multithreading only) */
+            writtenNode = (const UA_VariableNode*)UA_NodeStore_get(server->nodestore, &node->nodeId);
+#else
+            writtenNode = node; /* The node is written in-situ (TODO: this might
+                                   change with the nodestore plugin approach) */
+#endif
+            writtenNode->value.data.callback.onWrite(writtenNode->value.data.callback.handle, writtenNode->nodeId,
+                                                     &writtenNode->value.data.value.value, rangeptr);
+        }
     } else {
         if(node->value.dataSource.write)
             retval = node->value.dataSource.write(node->value.dataSource.handle,
@@ -601,7 +624,6 @@ writeIsAbstractAttribute(UA_Node *node, UA_Boolean value) {
 /****************/
 
 static const UA_String binEncoding = {sizeof("DefaultBinary")-1, (UA_Byte*)"DefaultBinary"};
-/* clang complains about unused variables */
 /* static const UA_String xmlEncoding = {sizeof("DefaultXml")-1, (UA_Byte*)"DefaultXml"}; */
 
 #define CHECK_NODECLASS(CLASS)                                  \
@@ -624,8 +646,8 @@ void Service_Read_single(UA_Server *server, UA_Session *session,
            return;
     }
 
-    /* index range for a non-value */
-    if(id->indexRange.length > 0 && id->attributeId != UA_ATTRIBUTEID_VALUE){
+    /* Index range for an attribute other than value */
+    if(id->indexRange.length > 0 && id->attributeId != UA_ATTRIBUTEID_VALUE) {
         v->hasStatus = true;
         v->status = UA_STATUSCODE_BADINDEXRANGENODATA;
         return;
@@ -639,7 +661,7 @@ void Service_Read_single(UA_Server *server, UA_Session *session,
         return;
     }
 
-    /* Read the value */
+    /* Read the attribute */
     UA_StatusCode retval = UA_STATUSCODE_GOOD;
     switch(id->attributeId) {
     case UA_ATTRIBUTEID_NODEID:
@@ -692,8 +714,8 @@ void Service_Read_single(UA_Server *server, UA_Session *session,
         break;
     case UA_ATTRIBUTEID_VALUE:
         CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
-        retval = readValueAttributeComplete((const UA_VariableNode*)node,
-                                            timestamps, id, v);
+        retval = readValueAttributeComplete(server, (const UA_VariableNode*)node,
+                                            timestamps, &id->indexRange, v);
         break;
     case UA_ATTRIBUTEID_DATATYPE:
         CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);

+ 132 - 118
src/server/ua_services_nodemanagement.c

@@ -1,6 +1,83 @@
 #include "ua_server_internal.h"
 #include "ua_services.h"
 
+/**********************/
+/* Consistency Checks */
+/**********************/
+
+/* Check if the requested parent node exists, has the right node class and is
+ * referenced with an allowed (hierarchical) reference type. For "type" nodes,
+ * only hasSubType references are allowed. */
+static UA_StatusCode
+checkParentReference(UA_Server *server, UA_Session *session, UA_NodeClass nodeClass,
+                     const UA_NodeId *parentNodeId, const UA_NodeId *referenceTypeId) {
+    /* See if the parent exists */
+    const UA_Node *parent = UA_NodeStore_get(server->nodestore, parentNodeId);
+    if(!parent) {
+        UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                             "AddNodes: Parent node not found");
+        return UA_STATUSCODE_BADPARENTNODEIDINVALID;
+    }
+
+    /* Check the referencetype exists */
+    const UA_ReferenceTypeNode *referenceType =
+        (const UA_ReferenceTypeNode*)UA_NodeStore_get(server->nodestore, referenceTypeId);
+    if(!referenceType) {
+        UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                             "AddNodes: Reference type to the parent not found");
+        return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
+    }
+
+    /* Check if the referencetype is a reference type node */
+    if(referenceType->nodeClass != UA_NODECLASS_REFERENCETYPE) {
+        UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                             "AddNodes: Reference type to the parent invalid");
+        return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
+    }
+
+    /* Check that the reference type is not abstract */
+    if(referenceType->isAbstract == true) {
+        UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                             "AddNodes: Abstract reference type to the parent invalid");
+        return UA_STATUSCODE_BADREFERENCENOTALLOWED;
+    }
+
+    /* Check hassubtype relation for type nodes */
+    const UA_NodeId subtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
+    if(nodeClass == UA_NODECLASS_DATATYPE ||
+       nodeClass == UA_NODECLASS_VARIABLETYPE ||
+       nodeClass == UA_NODECLASS_OBJECTTYPE ||
+       nodeClass == UA_NODECLASS_REFERENCETYPE) {
+        /* type needs hassubtype reference to the supertype */
+        if(!UA_NodeId_equal(referenceTypeId, &subtypeId)) {
+            UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                                 "AddNodes: New type node need to have a "
+                                 "hassubtype reference");
+            return UA_STATUSCODE_BADREFERENCENOTALLOWED;
+        }
+        /* supertype needs to be of the same node type  */
+        if(parent->nodeClass != nodeClass) {
+            UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                                 "AddNodes: New type node needs to be of the same "
+                                 "node type as the parent");
+            return UA_STATUSCODE_BADPARENTNODEIDINVALID;
+        }
+        return UA_STATUSCODE_GOOD;
+    }
+
+    /* Test if the referencetype is hierarchical */
+    const UA_NodeId hierarchicalReference =
+        UA_NODEID_NUMERIC(0, UA_NS0ID_HIERARCHICALREFERENCES);
+    if(!isNodeInTree(server->nodestore, referenceTypeId,
+                     &hierarchicalReference, &subtypeId, 1)) {
+        UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                             "AddNodes: Reference type is not hierarchical");
+        return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
+    }
+
+    return UA_STATUSCODE_GOOD;
+}
+
 /************/
 /* Add Node */
 /************/
@@ -31,7 +108,7 @@ copyExistingVariable(UA_Server *server, UA_Session *session, const UA_NodeId *va
     /* Get the current value */
     UA_DataValue value;
     UA_DataValue_init(&value);
-    UA_StatusCode retval = readValueAttribute(node, &value);
+    UA_StatusCode retval = readValueAttribute(server, node, &value);
     if(retval != UA_STATUSCODE_GOOD)
         return retval;
 
@@ -326,76 +403,6 @@ copyChildNodesToNode(UA_Server* server, UA_Session* session,
     return retval;
 }
 
-static UA_StatusCode
-checkParentReference(UA_Server *server, UA_Session *session, UA_NodeClass nodeClass,
-                     const UA_NodeId *parentNodeId, const UA_NodeId *referenceTypeId) {
-    /* See if the parent exists */
-    const UA_Node *parent = UA_NodeStore_get(server->nodestore, parentNodeId);
-    if(!parent) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                             "AddNodes: Parent node not found");
-        return UA_STATUSCODE_BADPARENTNODEIDINVALID;
-    }
-
-    /* Check the referencetype exists */
-    const UA_ReferenceTypeNode *referenceType =
-        (const UA_ReferenceTypeNode*)UA_NodeStore_get(server->nodestore, referenceTypeId);
-    if(!referenceType) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                             "AddNodes: Reference type to the parent not found");
-        return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
-    }
-
-    /* Check if the referencetype is a reference type node */
-    if(referenceType->nodeClass != UA_NODECLASS_REFERENCETYPE) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                             "AddNodes: Reference type to the parent invalid");
-        return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
-    }
-
-    /* Check that the reference type is not abstract */
-    if(referenceType->isAbstract == true) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                             "AddNodes: Abstract reference type to the parent invalid");
-        return UA_STATUSCODE_BADREFERENCENOTALLOWED;
-    }
-
-    /* Check hassubtype relation for type nodes */
-    const UA_NodeId subtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
-    if(nodeClass == UA_NODECLASS_DATATYPE ||
-       nodeClass == UA_NODECLASS_VARIABLETYPE ||
-       nodeClass == UA_NODECLASS_OBJECTTYPE ||
-       nodeClass == UA_NODECLASS_REFERENCETYPE) {
-        /* type needs hassubtype reference to the supertype */
-        if(!UA_NodeId_equal(referenceTypeId, &subtypeId)) {
-            UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                                 "AddNodes: New type node need to have a "
-                                 "hassubtype reference");
-            return UA_STATUSCODE_BADREFERENCENOTALLOWED;
-        }
-        /* supertype needs to be of the same node type  */
-        if(parent->nodeClass != nodeClass) {
-            UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                                 "AddNodes: New type node needs to be of the same "
-                                 "node type as the parent");
-            return UA_STATUSCODE_BADPARENTNODEIDINVALID;
-        }
-        return UA_STATUSCODE_GOOD;
-    }
-
-    /* Test if the referencetype is hierarchical */
-    const UA_NodeId hierarchicalReference =
-        UA_NODEID_NUMERIC(0, UA_NS0ID_HIERARCHICALREFERENCES);
-    if(!isNodeInTree(server->nodestore, referenceTypeId,
-                     &hierarchicalReference, &subtypeId, 1)) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                             "AddNodes: Reference type is not hierarchical");
-        return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
-    }
-
-    return UA_STATUSCODE_GOOD;
-}
-
 UA_StatusCode
 Service_AddNodes_existing(UA_Server *server, UA_Session *session, UA_Node *node,
                           const UA_NodeId *parentNodeId, const UA_NodeId *referenceTypeId,
@@ -488,9 +495,9 @@ Service_AddNodes_existing(UA_Server *server, UA_Session *session, UA_Node *node,
     return retval;
 }
 
-/***********************************************/
-/* Create a node from an attribute description */
-/***********************************************/
+/*******************************************/
+/* Create nodes from attribute description */
+/*******************************************/
 
 static UA_StatusCode
 copyStandardAttributes(UA_Node *node, const UA_AddNodesItem *item,
@@ -551,7 +558,7 @@ copyCommonVariableAttributes(UA_Server *server, UA_VariableNode *node,
 
     /* Set the valuerank */
     if(attr->valueRank != 0 || !UA_Variant_isScalar(&attr->value))
-        retval = writeValueRankAttribute(node, attr->valueRank, vt->valueRank);
+        retval = writeValueRankAttribute(server, node, attr->valueRank, vt->valueRank);
     else /* workaround common error where the valuerank is left as 0 */
         node->valueRank = vt->valueRank;
     if(retval != UA_STATUSCODE_GOOD)
@@ -567,7 +574,7 @@ copyCommonVariableAttributes(UA_Server *server, UA_VariableNode *node,
 }
 
 static UA_StatusCode
-variableNodeFromAttributes(UA_Server *server, UA_VariableNode *vnode,
+copyVariableNodeAttributes(UA_Server *server, UA_VariableNode *vnode,
                            const UA_AddNodesItem *item,
                            const UA_VariableAttributes *attr) {
     vnode->accessLevel = attr->accessLevel;
@@ -577,7 +584,7 @@ variableNodeFromAttributes(UA_Server *server, UA_VariableNode *vnode,
 }
 
 static UA_StatusCode
-variableTypeNodeFromAttributes(UA_Server *server, UA_VariableTypeNode *vtnode,
+copyVariableTypeNodeAttributes(UA_Server *server, UA_VariableTypeNode *vtnode,
                                const UA_AddNodesItem *item,
                                const UA_VariableTypeAttributes *attr) {
     vtnode->isAbstract = attr->isAbstract;
@@ -586,13 +593,13 @@ variableTypeNodeFromAttributes(UA_Server *server, UA_VariableTypeNode *vtnode,
 }
 
 static UA_StatusCode
-objectNodeFromAttributes(UA_ObjectNode *onode, const UA_ObjectAttributes *attr) {
+copyObjectNodeAttributes(UA_ObjectNode *onode, const UA_ObjectAttributes *attr) {
     onode->eventNotifier = attr->eventNotifier;
     return UA_STATUSCODE_GOOD;
 }
 
 static UA_StatusCode
-referenceTypeNodeFromAttributes(UA_ReferenceTypeNode *rtnode,
+copyReferenceTypeNodeAttributes(UA_ReferenceTypeNode *rtnode,
                                 const UA_ReferenceTypeAttributes *attr) {
     rtnode->isAbstract = attr->isAbstract;
     rtnode->symmetric = attr->symmetric;
@@ -600,21 +607,21 @@ referenceTypeNodeFromAttributes(UA_ReferenceTypeNode *rtnode,
 }
 
 static UA_StatusCode
-objectTypeNodeFromAttributes(UA_ObjectTypeNode *otnode,
+copyObjectTypeNodeAttributes(UA_ObjectTypeNode *otnode,
                              const UA_ObjectTypeAttributes *attr) {
     otnode->isAbstract = attr->isAbstract;
     return UA_STATUSCODE_GOOD;
 }
 
 static UA_StatusCode
-viewNodeFromAttributes(UA_ViewNode *vnode, const UA_ViewAttributes *attr) {
+copyViewNodeAttributes(UA_ViewNode *vnode, const UA_ViewAttributes *attr) {
     vnode->containsNoLoops = attr->containsNoLoops;
     vnode->eventNotifier = attr->eventNotifier;
     return UA_STATUSCODE_GOOD;
 }
 
 static UA_StatusCode
-dataTypeNodeFromAttributes(UA_DataTypeNode *dtnode,
+copyDataTypeNodeAttributes(UA_DataTypeNode *dtnode,
                            const UA_DataTypeAttributes *attr) {
     dtnode->isAbstract = attr->isAbstract;
     return UA_STATUSCODE_GOOD;
@@ -622,77 +629,83 @@ dataTypeNodeFromAttributes(UA_DataTypeNode *dtnode,
 
 #define CHECK_ATTRIBUTES(TYPE)                                          \
     if(item->nodeAttributes.content.decoded.type != &UA_TYPES[UA_TYPES_##TYPE]) { \
-        result->statusCode = UA_STATUSCODE_BADNODEATTRIBUTESINVALID;    \
+        retval = UA_STATUSCODE_BADNODEATTRIBUTESINVALID;                \
         break;                                                          \
     }
 
-static void
-Service_AddNodes_single(UA_Server *server, UA_Session *session,
-                        const UA_AddNodesItem *item, UA_AddNodesResult *result,
-                        UA_InstantiationCallback *instantiationCallback) {
+static UA_StatusCode
+createNodeFromAttributes(UA_Server *server, const UA_AddNodesItem *item, UA_Node **newNode) {
+    /* Check that we can read the attributes */
     if(item->nodeAttributes.encoding < UA_EXTENSIONOBJECT_DECODED ||
-       !item->nodeAttributes.content.decoded.type) {
-        result->statusCode = UA_STATUSCODE_BADNODEATTRIBUTESINVALID;
-        return;
-    }
+       !item->nodeAttributes.content.decoded.type)
+        return UA_STATUSCODE_BADNODEATTRIBUTESINVALID;
 
     /* Create the node */
-    UA_Node *node = UA_NodeStore_newNode(item->nodeClass);
-    if(!node) {
-        // todo: case where the nodeclass is faulty
-        result->statusCode = UA_STATUSCODE_BADOUTOFMEMORY;
-        return;
-    }
+    // todo: error case where the nodeclass is faulty
+    void *node = UA_NodeStore_newNode(item->nodeClass);
+    if(!node)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
 
+    /* Copy the attributes into the node */
     void *data = item->nodeAttributes.content.decoded.data;
-    result->statusCode = copyStandardAttributes(node, item, data);
+    UA_StatusCode retval = copyStandardAttributes(node, item, data);
     switch(item->nodeClass) {
     case UA_NODECLASS_OBJECT:
         CHECK_ATTRIBUTES(OBJECTATTRIBUTES);
-        result->statusCode |= objectNodeFromAttributes((UA_ObjectNode*)node, data);
+        retval |= copyObjectNodeAttributes(node, data);
         break;
     case UA_NODECLASS_VARIABLE:
         CHECK_ATTRIBUTES(VARIABLEATTRIBUTES);
-        result->statusCode |= variableNodeFromAttributes(server, (UA_VariableNode*)node,
-                                                         item, data);
+        retval |= copyVariableNodeAttributes(server, node, item, data);
         break;
     case UA_NODECLASS_OBJECTTYPE:
         CHECK_ATTRIBUTES(OBJECTTYPEATTRIBUTES);
-        result->statusCode |= objectTypeNodeFromAttributes((UA_ObjectTypeNode*)node, data);
+        retval |= copyObjectTypeNodeAttributes(node, data);
         break;
     case UA_NODECLASS_VARIABLETYPE:
         CHECK_ATTRIBUTES(VARIABLETYPEATTRIBUTES);
-        result->statusCode |= variableTypeNodeFromAttributes(server, (UA_VariableTypeNode*)node,
-                                                             item, data);
+        retval |= copyVariableTypeNodeAttributes(server, node, item, data);
         break;
     case UA_NODECLASS_REFERENCETYPE:
         CHECK_ATTRIBUTES(REFERENCETYPEATTRIBUTES);
-        result->statusCode |= referenceTypeNodeFromAttributes((UA_ReferenceTypeNode*)node, data);
+        retval |= copyReferenceTypeNodeAttributes(node, data);
         break;
     case UA_NODECLASS_DATATYPE:
         CHECK_ATTRIBUTES(DATATYPEATTRIBUTES);
-        result->statusCode |= dataTypeNodeFromAttributes((UA_DataTypeNode*)node, data);
+        retval |= copyDataTypeNodeAttributes(node, data);
         break;
     case UA_NODECLASS_VIEW:
         CHECK_ATTRIBUTES(VIEWATTRIBUTES);
-        result->statusCode |= viewNodeFromAttributes((UA_ViewNode*)node, data);
+        retval |= copyViewNodeAttributes(node, data);
         break;
     case UA_NODECLASS_METHOD:
     case UA_NODECLASS_UNSPECIFIED:
     default:
-        result->statusCode = UA_STATUSCODE_BADNODECLASSINVALID;
+        retval = UA_STATUSCODE_BADNODECLASSINVALID;
     }
 
-    if(result->statusCode == UA_STATUSCODE_GOOD) {
-        result->statusCode = Service_AddNodes_existing(server, session, node, &item->parentNodeId.nodeId,
-                                                       &item->referenceTypeId, &item->typeDefinition.nodeId,
-                                                       instantiationCallback, &result->addedNodeId);
-    } else {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session, "AddNodes: Could not "
-                             "prepare the new node with status code %s",
-                             UA_StatusCode_name(result->statusCode));
+    if(retval == UA_STATUSCODE_GOOD)
+        *newNode = node;
+    else
         UA_NodeStore_deleteNode(node);
-    }
+    return retval;
+}
+
+static void
+Service_AddNodes_single(UA_Server *server, UA_Session *session,
+                        const UA_AddNodesItem *item, UA_AddNodesResult *result,
+                        UA_InstantiationCallback *instantiationCallback) {
+    /* Create the node from the attributes*/
+    UA_Node *node;
+    result->statusCode = createNodeFromAttributes(server, item, &node);
+    if(result->statusCode != UA_STATUSCODE_GOOD)
+        return;
+
+    /* Run consistency checks and add the node */
+    UA_assert(node != NULL);
+    result->statusCode = Service_AddNodes_existing(server, session, node, &item->parentNodeId.nodeId,
+                                                   &item->referenceTypeId, &item->typeDefinition.nodeId,
+                                                   instantiationCallback, &result->addedNodeId);
 }
 
 void Service_AddNodes(UA_Server *server, UA_Session *session,
@@ -817,6 +830,7 @@ UA_Server_addDataSourceVariableNode(UA_Server *server, const UA_NodeId requested
     }
 
     /* Copy attributes into node */
+    UA_RCU_LOCK();
     UA_AddNodesItem item;
     UA_AddNodesItem_init(&item);
     item.requestedNewNodeId.nodeId = requestedNewNodeId;
@@ -831,13 +845,13 @@ UA_Server_addDataSourceVariableNode(UA_Server *server, const UA_NodeId requested
     UA_DataValue_deleteMembers(&value);
     if(retval != UA_STATUSCODE_GOOD) {
         UA_NodeStore_deleteNode((UA_Node*)node);
+        UA_RCU_UNLOCK();
         return retval;
     }
 
     /* Add the node */
     UA_AddNodesResult result;
     UA_AddNodesResult_init(&result);
-    UA_RCU_LOCK();
     retval = Service_AddNodes_existing(server, &adminSession, (UA_Node*)node, &parentNodeId,
                                        &referenceTypeId, &typeDefinition, NULL, outNewNodeId);
     UA_RCU_UNLOCK();

+ 3 - 13
src/server/ua_services_subscription.c

@@ -246,7 +246,7 @@ Service_CreateMonitoredItems_single(UA_Server *server, UA_Session *session,
     newMon->timestampsToReturn = timestampsToReturn;
     setMonitoredItemSettings(server, newMon, request->monitoringMode,
                              &request->requestedParameters);
-    LIST_INSERT_HEAD(&sub->MonitoredItems, newMon, listEntry);
+    LIST_INSERT_HEAD(&sub->monitoredItems, newMon, listEntry);
 
     /* Create the first sample */
     if(request->monitoringMode == UA_MONITORINGMODE_REPORTING)
@@ -439,17 +439,7 @@ Service_Publish(UA_Server *server, UA_Session *session,
             continue;
         }
         /* Remove the acked transmission from the retransmission queue */
-        response->results[i] = UA_STATUSCODE_BADSEQUENCENUMBERUNKNOWN;
-        UA_NotificationMessageEntry *pre, *pre_tmp;
-        LIST_FOREACH_SAFE(pre, &sub->retransmissionQueue, listEntry, pre_tmp) {
-            if(pre->message.sequenceNumber == ack->sequenceNumber) {
-                LIST_REMOVE(pre, listEntry);
-                response->results[i] = UA_STATUSCODE_GOOD;
-                UA_NotificationMessage_deleteMembers(&pre->message);
-                UA_free(pre);
-                break;
-            }
-        }
+        response->results[i] = UA_Subscription_removeRetransmissionMessage(sub, ack->sequenceNumber);
     }
 
     /* Queue the publish response */
@@ -570,7 +560,7 @@ void Service_Republish(UA_Server *server, UA_Session *session, const UA_Republis
 
     /* Find the notification in the retransmission queue  */
     UA_NotificationMessageEntry *entry;
-    LIST_FOREACH(entry, &sub->retransmissionQueue, listEntry) {
+    TAILQ_FOREACH(entry, &sub->retransmissionQueue, listEntry) {
         if(entry->message.sequenceNumber == request->retransmitSequenceNumber)
             break;
     }

+ 53 - 20
src/server/ua_subscription.c

@@ -260,8 +260,9 @@ UA_Subscription * UA_Subscription_new(UA_Session *session, UA_UInt32 subscriptio
     new->currentLifetimeCount = 0;
     new->lastMonitoredItemId = 0;
     new->state = UA_SUBSCRIPTIONSTATE_NORMAL; /* The first publish response is sent immediately */
-    LIST_INIT(&new->retransmissionQueue);
-    LIST_INIT(&new->MonitoredItems);
+    LIST_INIT(&new->monitoredItems);
+    TAILQ_INIT(&new->retransmissionQueue);
+    new->retransmissionQueueSize = 0;
     return new;
 }
 
@@ -270,24 +271,25 @@ void UA_Subscription_deleteMembers(UA_Subscription *subscription, UA_Server *ser
 
     /* Delete monitored Items */
     UA_MonitoredItem *mon, *tmp_mon;
-    LIST_FOREACH_SAFE(mon, &subscription->MonitoredItems, listEntry, tmp_mon) {
+    LIST_FOREACH_SAFE(mon, &subscription->monitoredItems, listEntry, tmp_mon) {
         LIST_REMOVE(mon, listEntry);
         MonitoredItem_delete(server, mon);
     }
 
     /* Delete Retransmission Queue */
     UA_NotificationMessageEntry *nme, *nme_tmp;
-    LIST_FOREACH_SAFE(nme, &subscription->retransmissionQueue, listEntry, nme_tmp) {
-        LIST_REMOVE(nme, listEntry);
+    TAILQ_FOREACH_SAFE(nme, &subscription->retransmissionQueue, listEntry, nme_tmp) {
+        TAILQ_REMOVE(&subscription->retransmissionQueue, nme, listEntry);
         UA_NotificationMessage_deleteMembers(&nme->message);
         UA_free(nme);
     }
+    subscription->retransmissionQueueSize = 0;
 }
 
 UA_MonitoredItem *
 UA_Subscription_getMonitoredItem(UA_Subscription *sub, UA_UInt32 monitoredItemID) {
     UA_MonitoredItem *mon;
-    LIST_FOREACH(mon, &sub->MonitoredItems, listEntry) {
+    LIST_FOREACH(mon, &sub->monitoredItems, listEntry) {
         if(mon->itemId == monitoredItemID)
             break;
     }
@@ -298,7 +300,7 @@ UA_StatusCode
 UA_Subscription_deleteMonitoredItem(UA_Server *server, UA_Subscription *sub,
                                     UA_UInt32 monitoredItemID) {
     UA_MonitoredItem *mon;
-    LIST_FOREACH(mon, &sub->MonitoredItems, listEntry) {
+    LIST_FOREACH(mon, &sub->monitoredItems, listEntry) {
         if(mon->itemId == monitoredItemID) {
             LIST_REMOVE(mon, listEntry);
             MonitoredItem_delete(server, mon);
@@ -313,7 +315,7 @@ countQueuedNotifications(UA_Subscription *sub, UA_Boolean *moreNotifications) {
     size_t notifications = 0;
     if(sub->publishingEnabled) {
         UA_MonitoredItem *mon;
-        LIST_FOREACH(mon, &sub->MonitoredItems, listEntry) {
+        LIST_FOREACH(mon, &sub->monitoredItems, listEntry) {
             MonitoredItem_queuedValue *qv;
             TAILQ_FOREACH(qv, &mon->queue, listEntry) {
                 if(notifications >= sub->notificationsPerPublish) {
@@ -327,6 +329,40 @@ countQueuedNotifications(UA_Subscription *sub, UA_Boolean *moreNotifications) {
     return notifications;
 }
 
+static void
+UA_Subscription_addRetransmissionMessage(UA_Server *server, UA_Subscription *sub,
+                                         UA_NotificationMessageEntry *entry) {
+    /* Release the oldest entry if there is not enough space */
+    if(server->config.maxRetransmissionQueueSize > 0 &&
+       sub->retransmissionQueueSize >= server->config.maxRetransmissionQueueSize) {
+        UA_NotificationMessageEntry *lastentry =
+            TAILQ_LAST(&sub->retransmissionQueue, UA_ListOfNotificationMessages);
+        TAILQ_REMOVE(&sub->retransmissionQueue, lastentry, listEntry);
+        --sub->retransmissionQueueSize;
+        UA_NotificationMessage_deleteMembers(&lastentry->message);
+        UA_free(lastentry);
+    }
+
+    /* Add entry */
+    TAILQ_INSERT_HEAD(&sub->retransmissionQueue, entry, listEntry);
+    ++sub->retransmissionQueueSize;
+}
+
+UA_StatusCode
+UA_Subscription_removeRetransmissionMessage(UA_Subscription *sub, UA_UInt32 sequenceNumber) {
+    UA_NotificationMessageEntry *entry, *entry_tmp;
+    TAILQ_FOREACH_SAFE(entry, &sub->retransmissionQueue, listEntry, entry_tmp) {
+        if(entry->message.sequenceNumber != sequenceNumber)
+            continue;
+        TAILQ_REMOVE(&sub->retransmissionQueue, entry, listEntry);
+        --sub->retransmissionQueueSize;
+        UA_NotificationMessage_deleteMembers(&entry->message);
+        UA_free(entry);
+        return UA_STATUSCODE_GOOD;
+    }
+    return UA_STATUSCODE_BADSEQUENCENUMBERUNKNOWN;
+}
+
 static UA_StatusCode
 prepareNotificationMessage(UA_Subscription *sub, UA_NotificationMessage *message,
                            size_t notifications) {
@@ -356,7 +392,7 @@ prepareNotificationMessage(UA_Subscription *sub, UA_NotificationMessage *message
     /* Move notifications into the response .. the point of no return */
     size_t l = 0;
     UA_MonitoredItem *mon;
-    LIST_FOREACH(mon, &sub->MonitoredItems, listEntry) {
+    LIST_FOREACH(mon, &sub->monitoredItems, listEntry) {
         MonitoredItem_queuedValue *qv, *qv_tmp;
         TAILQ_FOREACH_SAFE(qv, &mon->queue, listEntry, qv_tmp) {
             if(l >= notifications)
@@ -466,23 +502,20 @@ void UA_Subscription_publishCallback(UA_Server *server, UA_Subscription *sub) {
          * be done here, so that the message itself is included in the available
          * sequence numbers for acknowledgement. */
         retransmission->message = response->notificationMessage;
-        LIST_INSERT_HEAD(&sub->retransmissionQueue, retransmission, listEntry);
+        UA_Subscription_addRetransmissionMessage(server, sub, retransmission);
     }
 
     /* Get the available sequence numbers from the retransmission queue */
-    size_t available = 0;
-    UA_NotificationMessageEntry *nme;
-    LIST_FOREACH(nme, &sub->retransmissionQueue, listEntry)
-        ++available;
-    // cppcheck-suppress knownConditionTrueFalse
+    size_t available = sub->retransmissionQueueSize;
     if(available > 0) {
         response->availableSequenceNumbers = UA_alloca(available * sizeof(UA_UInt32));
         response->availableSequenceNumbersSize = available;
-    }
-    size_t i = 0;
-    LIST_FOREACH(nme, &sub->retransmissionQueue, listEntry) {
-        response->availableSequenceNumbers[i] = nme->message.sequenceNumber;
-        ++i;
+        size_t i = 0;
+        UA_NotificationMessageEntry *nme;
+        TAILQ_FOREACH(nme, &sub->retransmissionQueue, listEntry) {
+            response->availableSequenceNumbers[i] = nme->message.sequenceNumber;
+            ++i;
+        }
     }
 
     /* Send the response */

+ 11 - 4
src/server/ua_subscription.h

@@ -63,7 +63,7 @@ UA_StatusCode MonitoredItem_unregisterSampleJob(UA_Server *server, UA_MonitoredI
 /****************/
 
 typedef struct UA_NotificationMessageEntry {
-    LIST_ENTRY(UA_NotificationMessageEntry) listEntry;
+    TAILQ_ENTRY(UA_NotificationMessageEntry) listEntry;
     UA_NotificationMessage message;
 } UA_NotificationMessageEntry;
 
@@ -83,7 +83,7 @@ struct UA_Subscription {
     UA_Session *session;
     UA_UInt32 lifeTimeCount;
     UA_UInt32 maxKeepAliveCount;
-    UA_Double publishingInterval;     // [ms]
+    UA_Double publishingInterval; /* in ms */
     UA_UInt32 subscriptionID;
     UA_UInt32 notificationsPerPublish;
     UA_Boolean publishingEnabled;
@@ -100,8 +100,12 @@ struct UA_Subscription {
     UA_Guid publishJobGuid;
     UA_Boolean publishJobIsRegistered;
 
-    LIST_HEAD(UA_ListOfUAMonitoredItems, UA_MonitoredItem) MonitoredItems;
-    LIST_HEAD(UA_ListOfNotificationMessages, UA_NotificationMessageEntry) retransmissionQueue;
+    /* MonitoredItems */
+    LIST_HEAD(UA_ListOfUAMonitoredItems, UA_MonitoredItem) monitoredItems;
+
+    /* Retransmission Queue */
+    TAILQ_HEAD(UA_ListOfNotificationMessages, UA_NotificationMessageEntry) retransmissionQueue;
+    UA_UInt32 retransmissionQueueSize;
 };
 
 UA_Subscription *UA_Subscription_new(UA_Session *session, UA_UInt32 subscriptionID);
@@ -118,6 +122,9 @@ UA_Subscription_getMonitoredItem(UA_Subscription *sub, UA_UInt32 monitoredItemID
 
 void UA_Subscription_publishCallback(UA_Server *server, UA_Subscription *sub);
 
+UA_StatusCode
+UA_Subscription_removeRetransmissionMessage(UA_Subscription *sub, UA_UInt32 sequenceNumber);
+
 void
 UA_Subscription_answerPublishRequestsNoSubscription(UA_Server *server,
                                                     UA_NodeId *sessionToken);

+ 1 - 1
src/ua_connection.c

@@ -18,7 +18,7 @@ UA_Connection_completeMessages(UA_Connection *connection, UA_ByteString * UA_RES
      * After this block, connection->incompleteMessage is always empty. */
     if(connection->incompleteMessage.length > 0) {
         size_t length = connection->incompleteMessage.length + message->length;
-        UA_Byte *data = UA_realloc(connection->incompleteMessage.data, length);
+        UA_Byte *data = (UA_Byte*)UA_realloc(connection->incompleteMessage.data, length);
         if(!data) {
             retval = UA_STATUSCODE_BADOUTOFMEMORY;
             goto cleanup;

+ 30 - 23
src/ua_types.c

@@ -13,16 +13,24 @@
  * UA_DataType structure. The UA_DataType structures as well as all non-builtin
  * datatypes are autogenerated. */
 
-/* Static definition of NULL type instances */
-const UA_String UA_STRING_NULL = {.length = 0, .data = NULL };
-const UA_ByteString UA_BYTESTRING_NULL = {.length = 0, .data = NULL };
-const UA_Guid UA_GUID_NULL = {.data1 = 0, .data2 = 0, .data3 = 0,
-                              .data4 = {0,0,0,0,0,0,0,0}};
-const UA_NodeId UA_NODEID_NULL = {0, UA_NODEIDTYPE_NUMERIC, {0}};
-const UA_ExpandedNodeId UA_EXPANDEDNODEID_NULL = {
-       .nodeId = {.namespaceIndex = 0, .identifierType = UA_NODEIDTYPE_NUMERIC,
-                  .identifier.numeric = 0 },
-       .namespaceUri = {.length = 0, .data = NULL}, .serverIndex = 0 };
+/* Global definition of NULL type instances. These are always zeroed out, as
+ * mandated by the C/C++ standard for global values with no initializer. */
+const UA_String UA_STRING_NULL;
+const UA_ByteString UA_BYTESTRING_NULL;
+const UA_Guid UA_GUID_NULL;
+const UA_NodeId UA_NODEID_NULL;
+const UA_ExpandedNodeId UA_EXPANDEDNODEID_NULL;
+
+/* TODO: The standard-defined types are ordered. See if binary search is more
+ * efficient. */
+const UA_DataType *
+UA_findDataType(const UA_NodeId *typeId) {
+    for(size_t i = 0; i < UA_TYPES_COUNT; ++i) {
+        if(UA_TYPES[i].typeId.identifier.numeric == typeId->identifier.numeric)
+            return &UA_TYPES[i];
+    }
+    return NULL;
+}
 
 /***************************/
 /* Random Number Generator */
@@ -52,11 +60,11 @@ UA_String_fromChars(char const src[]) {
     UA_String str = UA_STRING_NULL;
     size_t length = strlen(src);
     if(length > 0) {
-        str.data = UA_malloc(length);
+        str.data = (UA_Byte*)UA_malloc(length);
         if(!str.data)
             return str;
     } else {
-        str.data = UA_EMPTY_ARRAY_SENTINEL;
+        str.data = (UA_Byte*)UA_EMPTY_ARRAY_SENTINEL;
     }
     memcpy(str.data, src, length);
     str.length = length;
@@ -113,7 +121,7 @@ UA_String
 UA_DateTime_toString(UA_DateTime t) {
     UA_String str = UA_STRING_NULL;
     // length of the string is 31 (plus \0 at the end)
-    if(!(str.data = UA_malloc(32)))
+    if(!(str.data = (UA_Byte*)UA_malloc(32)))
         return str;
     str.length = 31;
     UA_DateTimeStruct tSt = UA_DateTime_toStruct(t);
@@ -171,7 +179,7 @@ UA_ByteString_allocBuffer(UA_ByteString *bs, size_t length) {
     UA_ByteString_init(bs);
     if(length == 0)
         return UA_STATUSCODE_GOOD;
-    if(!(bs->data = UA_malloc(length)))
+    if(!(bs->data = (UA_Byte*)UA_malloc(length)))
         return UA_STATUSCODE_BADOUTOFMEMORY;
     bs->length = length;
     return UA_STATUSCODE_GOOD;
@@ -375,16 +383,16 @@ UA_Variant_setScalar(UA_Variant *v, void * UA_RESTRICT p,
 UA_StatusCode
 UA_Variant_setScalarCopy(UA_Variant *v, const void *p,
                          const UA_DataType *type) {
-    void *new = UA_malloc(type->memSize);
-    if(!new)
+    void *n = UA_malloc(type->memSize);
+    if(!n)
         return UA_STATUSCODE_BADOUTOFMEMORY;
-    UA_StatusCode retval = UA_copy(p, new, type);
+    UA_StatusCode retval = UA_copy(p, n, type);
     if(retval != UA_STATUSCODE_GOOD) {
-        UA_free(new);
+        UA_free(n);
         //cppcheck-suppress memleak
         return retval;
     }
-    UA_Variant_setScalar(v, new, type);
+    UA_Variant_setScalar(v, n, type);
     //cppcheck-suppress memleak
     return UA_STATUSCODE_GOOD;
 }
@@ -513,8 +521,7 @@ UA_Variant_copyRange(const UA_Variant *orig_src, UA_Variant *dst,
      * with in the "scalar" type that may define an array by itself (string,
      * variant, ...). */
     UA_NumericRange thisrange, nextrange;
-    UA_NumericRangeDimension scalarThisDimension = (UA_NumericRangeDimension){
-        .min = 0, .max = 0}; /* a single entry */
+    UA_NumericRangeDimension scalarThisDimension = {0,0}; /* a single entry */
     if(isScalar) {
         /* Replace scalar src with array of length 1 */
         arraySrc = *src;
@@ -618,7 +625,7 @@ UA_Variant_copyRange(const UA_Variant *orig_src, UA_Variant *dst,
     dst->arrayLength = count;
     if(src->arrayDimensionsSize > 0) {
         dst->arrayDimensions =
-            UA_Array_new(thisrange.dimensionsSize, &UA_TYPES[UA_TYPES_UINT32]);
+            (UA_UInt32*)UA_Array_new(thisrange.dimensionsSize, &UA_TYPES[UA_TYPES_UINT32]);
         if(!dst->arrayDimensions) {
             Variant_deletemembers(dst, NULL);
             return UA_STATUSCODE_BADOUTOFMEMORY;
@@ -740,7 +747,7 @@ DiagnosticInfo_copy(UA_DiagnosticInfo const *src, UA_DiagnosticInfo *dst,
     if(src->hasAdditionalInfo)
        retval = UA_String_copy(&src->additionalInfo, &dst->additionalInfo);
     if(src->hasInnerDiagnosticInfo && src->innerDiagnosticInfo) {
-        dst->innerDiagnosticInfo = UA_malloc(sizeof(UA_DiagnosticInfo));
+        dst->innerDiagnosticInfo = (UA_DiagnosticInfo*)UA_malloc(sizeof(UA_DiagnosticInfo));
         if(dst->innerDiagnosticInfo) {
             retval |= DiagnosticInfo_copy(src->innerDiagnosticInfo,
                                           dst->innerDiagnosticInfo, NULL);

+ 9 - 9
src/ua_types_encoding_binary.c

@@ -40,13 +40,13 @@
 
 /* Jumptables for de-/encoding and computing the buffer length */
 typedef UA_StatusCode (*UA_encodeBinarySignature)(const void *UA_RESTRICT src, const UA_DataType *type);
-static const UA_encodeBinarySignature encodeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1];
+extern const UA_encodeBinarySignature encodeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1];
 
 typedef UA_StatusCode (*UA_decodeBinarySignature)(void *UA_RESTRICT dst, const UA_DataType *type);
-static const UA_decodeBinarySignature decodeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1];
+extern const UA_decodeBinarySignature decodeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1];
 
 typedef size_t (*UA_calcSizeBinarySignature)(const void *UA_RESTRICT p, const UA_DataType *contenttype);
-static const UA_calcSizeBinarySignature calcSizeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1];
+extern const UA_calcSizeBinarySignature calcSizeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1];
 
 /* We give pointers to the current position and the last position in the buffer
    instead of a string with an offset. */
@@ -831,11 +831,11 @@ ExtensionObject_decodeBinary(UA_ExtensionObject *dst, const UA_DataType *_) {
     }
 
     if(encoding == UA_EXTENSIONOBJECT_ENCODED_NOBODY) {
-        dst->encoding = encoding;
+        dst->encoding = (UA_ExtensionObjectEncoding)encoding;
         dst->content.encoded.typeId = typeId;
         dst->content.encoded.body = UA_BYTESTRING_NULL;
     } else if(encoding == UA_EXTENSIONOBJECT_ENCODED_XML) {
-        dst->encoding = encoding;
+        dst->encoding = (UA_ExtensionObjectEncoding)encoding;
         dst->content.encoded.typeId = typeId;
         retval = ByteString_decodeBinary(&dst->content.encoded.body);
     } else {
@@ -1140,7 +1140,7 @@ DiagnosticInfo_decodeBinary(UA_DiagnosticInfo *dst, const UA_DataType *_) {
     }
     if(encodingMask & 0x40) {
         /* innerDiagnosticInfo is allocated on the heap */
-        dst->innerDiagnosticInfo = UA_calloc(1, sizeof(UA_DiagnosticInfo));
+        dst->innerDiagnosticInfo = (UA_DiagnosticInfo*)UA_calloc(1, sizeof(UA_DiagnosticInfo));
         if(!dst->innerDiagnosticInfo)
             return UA_STATUSCODE_BADOUTOFMEMORY;
         dst->hasInnerDiagnosticInfo = true;
@@ -1159,7 +1159,7 @@ UA_encodeBinaryInternal(const void *src, const UA_DataType *type);
 static UA_StatusCode
 UA_decodeBinaryInternal(void *dst, const UA_DataType *type);
 
-static const UA_encodeBinarySignature encodeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1] = {
+const UA_encodeBinarySignature encodeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1] = {
     (UA_encodeBinarySignature)Boolean_encodeBinary,
     (UA_encodeBinarySignature)Byte_encodeBinary, // SByte
     (UA_encodeBinarySignature)Byte_encodeBinary,
@@ -1244,7 +1244,7 @@ UA_encodeBinary(const void *src, const UA_DataType *type,
     return retval;
 }
 
-static const UA_decodeBinarySignature decodeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1] = {
+const UA_decodeBinarySignature decodeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1] = {
     (UA_decodeBinarySignature)Boolean_decodeBinary,
     (UA_decodeBinarySignature)Byte_decodeBinary, // SByte
     (UA_decodeBinarySignature)Byte_decodeBinary,
@@ -1514,7 +1514,7 @@ DiagnosticInfo_calcSizeBinary(const UA_DiagnosticInfo *src, UA_DataType *_) {
     return s;
 }
 
-static const UA_calcSizeBinarySignature calcSizeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1] = {
+const UA_calcSizeBinarySignature calcSizeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1] = {
     (UA_calcSizeBinarySignature)calcSizeBinaryMemSize, // Boolean
     (UA_calcSizeBinarySignature)calcSizeBinaryMemSize, // Byte
     (UA_calcSizeBinarySignature)calcSizeBinaryMemSize,

+ 63 - 3
src/ua_util.h

@@ -7,6 +7,9 @@
 #include <assert.h>
 #define UA_assert(ignore) assert(ignore)
 
+/* BSD Queue Macros */
+#include "queue.h"
+
 /* container_of */
 #define container_of(ptr, type, member) \
     (type *)((uintptr_t)ptr - offsetof(type,member))
@@ -23,12 +26,69 @@
 #  warning The compiler does not allow thread-local variables. The library can be built, but will not be thread-safe.
 # endif
 #endif
-
 #ifndef UA_THREAD_LOCAL
 # define UA_THREAD_LOCAL
 #endif
 
-/* BSD Queue Macros */
-#include "queue.h"
+/* Atomic Operations
+ * -----------------
+ * Atomic operations that synchronize across processor cores (for
+ * multithreading). Only the inline-functions defined next are used. Replace
+ * with architecture-specific operations if necessary. */
+#ifndef UA_ENABLE_MULTITHREADING
+# define UA_atomic_sync()
+#else
+# ifdef _MSC_VER /* Visual Studio */
+#  define UA_atomic_sync() _ReadWriteBarrier()
+# else /* GCC/Clang */
+#  define UA_atomic_sync() __sync_synchronize()
+# endif
+#endif
+
+static UA_INLINE void *
+UA_atomic_xchg(void * volatile * addr, void *newptr) {
+#ifndef UA_ENABLE_MULTITHREADING
+    void *old = *addr;
+    *addr = newptr;
+    return old;
+#else
+# ifdef _MSC_VER /* Visual Studio */
+    return _InterlockedExchangePointer(addr, newptr);
+# else /* GCC/Clang */
+    return __sync_lock_test_and_set(addr, newptr);
+# endif
+#endif
+}
+
+static UA_INLINE void *
+UA_atomic_cmpxchg(void * volatile * addr, void *expected, void *newptr) {
+#ifndef UA_ENABLE_MULTITHREADING
+    void *old = *addr;
+    if(old == expected) {
+        *addr = newptr;
+    }
+    return old;
+#else
+# ifdef _MSC_VER /* Visual Studio */
+    return _InterlockedCompareExchangePointer(addr, expected, newptr);
+# else /* GCC/Clang */
+    return __sync_val_compare_and_swap(addr, expected, newptr);
+# endif
+#endif
+}
+
+static UA_INLINE uint32_t
+UA_atomic_add(volatile uint32_t *addr, uint32_t increase) {
+#ifndef UA_ENABLE_MULTITHREADING
+    *addr += increase;
+    return *addr;
+#else
+# ifdef _MSC_VER /* Visual Studio */
+    return _InterlockedExchangeAdd(addr, increase) + increase;
+# else /* GCC/Clang */
+    return __sync_add_and_fetch(addr, increase);
+# endif
+#endif
+}
 
 #endif /* UA_UTIL_H_ */

+ 19 - 14
tests/CMakeLists.txt

@@ -7,11 +7,14 @@ include_directories(${CHECK_INCLUDE_DIRS})
 
 find_package(Check REQUIRED)
 find_package(Threads REQUIRED)
+if(UA_ENABLE_VALGRIND_UNIT_TESTS)
+    find_package(Valgrind REQUIRED)
+endif()
 
 set(LIBS ${CHECK_LIBRARIES} ${open62541_LIBRARIES} open62541)
 if(NOT WIN32)
   list(APPEND LIBS pthread m)
-  if (NOT APPLE)
+  if(NOT APPLE)
     list(APPEND LIBS rt subunit)
   endif()
 else()
@@ -25,16 +28,14 @@ if(CMAKE_COMPILER_IS_GNUCC OR "x${CMAKE_C_COMPILER_ID}" STREQUAL "xClang")
     add_definitions(-Wno-sign-conversion)
 endif()
 
-# Valgrind definition
-set(UA_TEST_WITH_VALGRIND ON)
-SET(VALGRIND_FLAGS --quiet --trace-children=yes --leak-check=full)
+# Unit Test Definition Macro
+set(VALGRIND_FLAGS --quiet --trace-children=yes --leak-check=full)
 macro(add_test_valgrind TEST_NAME)
-    IF(UA_TEST_WITH_VALGRIND)
-        add_test(${TEST_NAME}
-                valgrind --error-exitcode=1 ${VALGRIND_FLAGS} ${ARGN} )
-    ELSE()
+    if(UA_ENABLE_VALGRIND_UNIT_TESTS)
+        add_test(${TEST_NAME} valgrind --error-exitcode=1 ${VALGRIND_FLAGS} ${ARGN})
+    else()
         add_test(${TEST_NAME} ${ARGN})
-    ENDIF()
+    endif()
 endmacro()
 
 # the unit test are built directly on the open62541 object files. so they can
@@ -56,6 +57,12 @@ add_executable(check_chunking check_chunking.c $<TARGET_OBJECTS:open62541-object
 target_link_libraries(check_chunking ${LIBS})
 add_test_valgrind(chunking ${CMAKE_CURRENT_BINARY_DIR}/check_chunking)
 
+add_executable(check_utils check_utils.c $<TARGET_OBJECTS:open62541-object>)
+target_link_libraries(check_utils ${LIBS})
+add_test_valgrind(check_utils ${CMAKE_CURRENT_BINARY_DIR}/check_utils)
+
+# Test Server
+
 add_executable(check_services_view check_services_view.c $<TARGET_OBJECTS:open62541-object>)
 target_link_libraries(check_services_view ${LIBS})
 add_test_valgrind(services_view ${CMAKE_CURRENT_BINARY_DIR}/check_services_view)
@@ -92,7 +99,7 @@ add_executable(check_discovery check_discovery.c $<TARGET_OBJECTS:open62541-obje
 target_link_libraries(check_discovery ${LIBS})
 add_test_valgrind(discovery ${CMAKE_CURRENT_BINARY_DIR}/check_discovery)
 
-# test with canned interactions from files
+# Test server with network dumps from files
 
 add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/client_HELOPN.bin
                           ${CMAKE_CURRENT_BINARY_DIR}/client_CLO.bin
@@ -141,6 +148,8 @@ add_test_valgrind(check_server_binary_messages_write ${CMAKE_CURRENT_BINARY_DIR}
                                            ${CMAKE_CURRENT_BINARY_DIR}/client_Write.bin
                                            ${CMAKE_CURRENT_BINARY_DIR}/client_CLO.bin)
 
+# Test Client
+
 add_executable(check_client check_client.c $<TARGET_OBJECTS:open62541-object>)
 target_link_libraries(check_client ${LIBS})
 add_test_valgrind(check_client ${CMAKE_CURRENT_BINARY_DIR}/check_client)
@@ -148,7 +157,3 @@ add_test_valgrind(check_client ${CMAKE_CURRENT_BINARY_DIR}/check_client)
 add_executable(check_client_subscriptions check_client_subscriptions.c $<TARGET_OBJECTS:open62541-object>)
 target_link_libraries(check_client_subscriptions ${LIBS})
 add_test_valgrind(check_client_subscriptions ${CMAKE_CURRENT_BINARY_DIR}/check_client_subscriptions)
-
-add_executable(check_utils check_utils.c $<TARGET_OBJECTS:open62541-object>)
-target_link_libraries(check_utils ${LIBS})
-add_test_valgrind(check_utils ${CMAKE_CURRENT_BINARY_DIR}/check_utils)

+ 1 - 0
tests/check_services_attributes.c

@@ -150,6 +150,7 @@ START_TEST(ReadSingleAttributeValueWithoutTimestamp) {
     rReq.nodesToRead[0].nodeId = UA_NODEID_STRING_ALLOC(1, "the.answer");
     rReq.nodesToRead[0].attributeId = UA_ATTRIBUTEID_VALUE;
     Service_Read_single(server, &adminSession, UA_TIMESTAMPSTORETURN_NEITHER, &rReq.nodesToRead[0], &resp);
+    ck_assert_int_eq(resp.status, UA_STATUSCODE_GOOD);
     ck_assert_int_eq(0, resp.value.arrayLength);
     ck_assert_ptr_eq(&UA_TYPES[UA_TYPES_INT32], resp.value.type);
     ck_assert_int_eq(42, *(UA_Int32* )resp.value.data);

+ 1 - 1
tools/amalgamate.py

@@ -75,7 +75,7 @@ if not is_c:
 } // extern "C"
 #endif
 
-#endif /* %s */''' % (outname.upper() + u"_H_"))
+#endif /* %s */\n''' % (outname.upper() + u"_H_"))
 file.close()
 
 print ("The size of "+args.outfile+" is "+ str(os.path.getsize(args.outfile))+" Bytes.")

+ 10 - 5
tools/c2rst.py

@@ -48,11 +48,16 @@ def first_line(c):
 def last_line(c):
     "Searches for the latest ifdef (closing the include guard)"
     last = 1
-    for i in range(1, len(c)):
+    for i in range(len(c)-1,1,-1):
         m = re.search("^#ifdef", c[i])
         if m:
             last = i
-    return last
+            break
+    # skip empty lines at the end
+    for i in range(last-1,1,-1):
+        if len(c[i].strip()) > 0:
+            return i
+    return 1
 
 if len(sys.argv) < 2:
     print("Usage: python c2rst.py input.c/h output.rst")
@@ -63,7 +68,8 @@ with open(sys.argv[1]) as f:
 
 with open(sys.argv[2], 'w') as rst:
     in_doc = False
-    for i in range(first_line(c), last_line(c)):
+    last = last_line(c)
+    for i in range(first_line(c), last+1):
         line = c[i]
         doc_start = False
         doc_end = False
@@ -84,7 +90,6 @@ with open(sys.argv[2], 'w') as rst:
                 line = "   " + line
             rst.write(clean_line(line))
 
-        if doc_end:
+        if doc_end and i < last:
             rst.write("\n.. code-block:: c\n\n")
             in_doc = False
-    rst.write("\n")

+ 18 - 0
tools/cmake/FindValgrind.cmake

@@ -0,0 +1,18 @@
+# Find Valgrind.
+#
+# This module defines:
+#  VALGRIND_INCLUDE_DIR, where to find valgrind/memcheck.h, etc.
+#  VALGRIND_PROGRAM, the valgrind executable.
+#  VALGRIND_FOUND, If false, do not try to use valgrind.
+#
+# If you have valgrind installed in a non-standard place, you can define
+# VALGRIND_PREFIX to tell cmake where it is.
+
+find_path(VALGRIND_INCLUDE_DIR memcheck.h
+  /usr/include /usr/include/valgrind /usr/local/include /usr/local/include/valgrind
+  ${VALGRIND_PREFIX}/include ${VALGRIND_PREFIX}/include/valgrind)
+find_program(VALGRIND_PROGRAM NAMES valgrind PATH /usr/bin /usr/local/bin ${VALGRIND_PREFIX}/bin)
+
+find_package_handle_standard_args(VALGRIND DEFAULT_MSG VALGRIND_INCLUDE_DIR VALGRIND_PROGRAM)
+
+mark_as_advanced(VALGRIND_INCLUDE_DIR VALGRIND_PROGRAM)

+ 2 - 2
tools/generate_datatypes.py

@@ -461,7 +461,7 @@ extern "C" {
 
 #include "''' + outname + '''_generated.h"
 
-#if defined(__GNUC__) && __GNUC__ <= 4
+#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 6
 # pragma GCC diagnostic push
 # pragma GCC diagnostic ignored "-Wmissing-field-initializers"
 # pragma GCC diagnostic ignored "-Wmissing-braces"
@@ -473,7 +473,7 @@ for t in iter_types(types):
     printf(t.functions_c())
 
 printf('''
-#if defined(__GNUC__) && __GNUC__ <= 4
+#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 6
 # pragma GCC diagnostic pop
 #endif
 

+ 1 - 0
tools/pyUANamespace/ua_namespace.py

@@ -676,6 +676,7 @@ class opcua_namespace():
     code.append('#include "'+outfilename+'.h"')
     code.append("UA_INLINE UA_StatusCode "+outfilename+"(UA_Server *server) {")
     code.append('UA_StatusCode retval = UA_STATUSCODE_GOOD; ')
+    code.append('if(retval == UA_STATUSCODE_GOOD){retval = UA_STATUSCODE_GOOD;} //ensure that retval is used');
 
     # Before printing nodes, we need to request additional namespace arrays from the server
     for nsid in self.namespaceIdentifiers:

+ 11 - 8
tools/travis/travis_linux_script.sh

@@ -10,7 +10,7 @@ if [ $ANALYZE = "true" ]; then
         scan-build-3.7 -enable-checker security.FloatLoopCounter \
           -enable-checker security.insecureAPI.UncheckedReturn \
           --status-bugs -v \
-          make -j 8
+          make -j
         cd .. && rm build -rf
 
         mkdir -p build
@@ -19,7 +19,7 @@ if [ $ANALYZE = "true" ]; then
         scan-build-3.7 -enable-checker security.FloatLoopCounter \
           -enable-checker security.insecureAPI.UncheckedReturn \
           --status-bugs -v \
-          make -j 8
+          make -j
         cd .. && rm build -rf
     else
         cppcheck --template "{file}({line}): {severity} ({id}): {message}" \
@@ -41,8 +41,10 @@ else
     cd build
     cmake -DCMAKE_BUILD_TYPE=Release -DUA_BUILD_EXAMPLES=ON -DUA_BUILD_DOCUMENTATION=ON -DUA_BUILD_SELFSIGNED_CERTIFICATE=ON ..
     make doc
+    make doc_pdf
     make selfsigned
     cp -r doc ../../
+    cp -r doc_latex ../../
     cp ./examples/server_cert.der ../../
     cd .. && rm build -rf
     
@@ -50,7 +52,7 @@ else
     mkdir -p build
     cd build
     cmake -DCMAKE_BUILD_TYPE=Debug -DUA_ENABLE_GENERATE_NAMESPACE0=On -DUA_BUILD_EXAMPLES=ON  ..
-    make -j8
+    make -j
     cd .. && rm build -rf
     
     # cross compilation only with gcc
@@ -59,7 +61,7 @@ else
         mkdir -p build && cd build
         cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-mingw32.cmake -DUA_ENABLE_AMALGAMATION=ON -DCMAKE_BUILD_TYPE=Release -DUA_BUILD_EXAMPLES=ON ..
         make -j
-        zip -r open62541-win32.zip ../../doc ../../server_cert.der ../LICENSE ../AUTHORS ../README.md examples/server.exe examples/client.exe libopen62541.dll.a open62541.h open62541.c
+        zip -r open62541-win32.zip ../../doc ../../doc_latex/open62541.pdf ../../server_cert.der ../LICENSE ../AUTHORS ../README.md examples/server.exe examples/client.exe libopen62541.dll.a open62541.h open62541.c
         cp open62541-win32.zip ..
         cd .. && rm build -rf
 
@@ -67,7 +69,7 @@ else
         mkdir -p build && cd build
         cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-mingw64.cmake -DUA_ENABLE_AMALGAMATION=ON -DCMAKE_BUILD_TYPE=Release -DUA_BUILD_EXAMPLES=ON ..
         make -j
-        zip -r open62541-win64.zip ../../doc ../../server_cert.der ../LICENSE ../AUTHORS ../README.md examples/server.exe examples/client.exe libopen62541.dll.a open62541.h open62541.c
+        zip -r open62541-win64.zip ../../doc ../../doc_latex/open62541.pdf ../../server_cert.der ../LICENSE ../AUTHORS ../README.md examples/server.exe examples/client.exe libopen62541.dll.a open62541.h open62541.c
         cp open62541-win64.zip ..
         cd .. && rm build -rf
 
@@ -75,7 +77,7 @@ else
         mkdir -p build && cd build
         cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-gcc-m32.cmake -DUA_ENABLE_AMALGAMATION=ON -DCMAKE_BUILD_TYPE=Release -DUA_BUILD_EXAMPLES=ON ..
         make -j
-        tar -pczf open62541-linux32.tar.gz ../../doc ../../server_cert.der ../LICENSE ../AUTHORS ../README.md examples/server examples/client libopen62541.a open62541.h open62541.c
+        tar -pczf open62541-linux32.tar.gz ../../doc ../../doc_latex/open62541.pdf ../../server_cert.der ../LICENSE ../AUTHORS ../README.md examples/server examples/client libopen62541.a open62541.h open62541.c
         cp open62541-linux32.tar.gz ..
         cd .. && rm build -rf
     fi
@@ -84,7 +86,7 @@ else
     mkdir -p build && cd build
     cmake -DCMAKE_BUILD_TYPE=Release -DUA_ENABLE_AMALGAMATION=ON -DUA_BUILD_EXAMPLES=ON ..
     make -j
-    tar -pczf open62541-linux64.tar.gz ../../doc ../../server_cert.der ../LICENSE ../AUTHORS ../README.md examples/server examples/client libopen62541.a open62541.h open62541.c
+    tar -pczf open62541-linux64.tar.gz ../../doc ../../doc_latex/open62541.pdf ../../server_cert.der ../LICENSE ../AUTHORS ../README.md examples/server examples/client libopen62541.a open62541.h open62541.c
     cp open62541-linux64.tar.gz ..
     cp open62541.h .. # copy single file-release
     cp open62541.c .. # copy single file-release
@@ -114,11 +116,12 @@ else
     make -j
     cd .. && rm build -rf
 
-    #this run inclides full examples and methodcalls
     echo "Debug build and unit tests (64 bit)"
     mkdir -p build && cd build
     cmake -DCMAKE_BUILD_TYPE=Debug -DUA_BUILD_EXAMPLES=ON -DUA_BUILD_UNIT_TESTS=ON -DUA_ENABLE_COVERAGE=ON ..
     make -j && make test ARGS="-V"
+    cmake -DCMAKE_BUILD_TYPE=Debug -DUA_BUILD_EXAMPLES=ON -DUA_BUILD_UNIT_TESTS=ON -DUA_ENABLE_COVERAGE=ON -DUA_ENABLE_VALGRIND_UNIT_TESTS=ON ..
+    make -j && make test ARGS="-V"
     echo "Run valgrind to see if the server leaks memory (just starting up and closing..)"
     (valgrind --leak-check=yes --error-exitcode=3 ./examples/server & export pid=$!; sleep 2; kill -INT $pid; wait $pid);
     # only run coveralls on main repo, otherwise it fails uploading the files

+ 2 - 2
tools/travis/travis_push_doc.sh

@@ -5,9 +5,9 @@ git clone --depth=5 -b gh-pages https://$GITAUTH@github.com/open62541/open62541-
 cd open62541-www
 
 rm -r -f ./doc/current/* || true # ignore result
-mkdir ./doc/current
 cp -r ../../doc/* ./doc/current/
-git add -A ./doc/current
+cp -r ../../doc_latex/open62541.pdf ./doc/open62541-current.pdf
+git add -A ./doc
 git config --global user.email "open62541-travis-ci@users.noreply.github.com"
 git config --global user.name "Open62541 travis-ci"
 git config --global push.default simple

+ 3 - 1
tools/travis/travis_push_release.sh

@@ -25,9 +25,11 @@ if [ ! -e "$TAG.zip" ]; then
     #create a zip for single-file release and copy the files
     cp ../../open62541.c .
     cp ../../open62541.h .
-    zip -r "$TAG.zip" open62541.c open62541.h
+    cp ../../../doc_latex/open62541.pdf .
+    zip -r "$TAG.zip" open62541.c open62541.h open62541.pdf
     rm open62541.c
     rm open62541.h
+    rm open62541.pdf
     git add "$TAG.zip"
 
     echo "$TAG.zip" | cat - raw.txt > temp && mv temp raw.txt