Browse Source

Merge branch '0.2'

Julius Pfrommer 8 years ago
parent
commit
24604543cd
66 changed files with 5076 additions and 2648 deletions
  1. 1 0
      .gitignore
  2. 1 0
      AUTHORS
  3. 46 6
      CMakeLists.txt
  4. 18 4
      doc/CMakeLists.txt
  5. 37 11
      doc/building.rst
  6. 0 132
      doc/in_a_nutshell.rst
  7. 28 30
      doc/index.rst
  8. 15 0
      doc/index_html.rst
  9. 5 7
      doc/internal.rst
  10. 67 0
      doc/introduction.rst
  11. 7 7
      doc/tutorial_client_firstSteps.rst
  12. 33 173
      doc/tutorial_noderelations.rst
  13. 4 4
      doc/tutorial_server_firstSteps.rst
  14. 3 3
      doc/tutorial_server_method.rst
  15. 21 67
      doc/tutorial_server_variables.rst
  16. 8 6
      doc/tutorials.rst
  17. 3 0
      examples/CMakeLists.txt
  18. 1 0
      examples/server.c
  19. 4 3
      examples/server_firstSteps.c
  20. 95 0
      examples/server_instantiation.c
  21. 3 1
      include/ua_client.h
  22. 11 13
      include/ua_client_highlevel.h
  23. 22 21
      include/ua_connection.h
  24. 3 2
      include/ua_constants.h
  25. 2 1
      include/ua_log.h
  26. 29 9
      include/ua_server.h
  27. 197 168
      include/ua_types.h
  28. 7 1
      plugins/ua_network_tcp.c
  29. 6 0
      plugins/ua_network_udp.c
  30. 5 1
      src/client/ua_client_highlevel.c
  31. 63 36
      src/server/ua_nodes.c
  32. 244 96
      src/server/ua_nodes.h
  33. 95 64
      src/server/ua_nodestore.c
  34. 5 5
      src/server/ua_nodestore.h
  35. 1 0
      src/server/ua_nodestore_concurrent.c
  36. 347 312
      src/server/ua_server.c
  37. 7 6
      src/server/ua_server_binary.c
  38. 112 7
      src/server/ua_server_internal.h
  39. 278 0
      src/server/ua_server_utils.c
  40. 24 58
      src/server/ua_services.h
  41. 589 358
      src/server/ua_services_attribute.c
  42. 47 113
      src/server/ua_services_call.c
  43. 687 677
      src/server/ua_services_nodemanagement.c
  44. 104 59
      src/server/ua_services_subscription.c
  45. 101 46
      src/server/ua_subscription.c
  46. 1 0
      src/server/ua_subscription.h
  47. 1 1
      src/ua_connection.c
  48. 3 2
      src/ua_session.c
  49. 3 1
      src/ua_session.h
  50. 6 8
      src/ua_types.c
  51. 2 2
      src/ua_types_encoding_binary.h
  52. 13 8
      src/ua_util.h
  53. 96 78
      tests/check_services_attributes.c
  54. 13 17
      tests/check_session.c
  55. 5 5
      tests/check_types_builtin.c
  56. 1 1
      tests/check_types_range.c
  57. 1 1
      tools/c2rst.py
  58. 6 8
      tools/generate_datatypes.py
  59. 0 0
      tools/pyUANamespace/NodeID_AssumeExternal.txt
  60. 0 0
      tools/pyUANamespace/NodeID_Blacklist.txt
  61. 0 0
      tools/pyUANamespace/NodeID_Blacklist_FullNS0.txt
  62. 1514 0
      tools/pyUANamespace/NodeID_NameSpace0_All.txt
  63. 15 5
      tools/pyUANamespace/open62541_MacroHelper.py
  64. 3 0
      tools/pyUANamespace/ua_namespace.py
  65. 4 4
      tools/pyUANamespace/ua_node_types.py
  66. 3 0
      tools/schema/datatypes_minimal.txt

+ 1 - 0
.gitignore

@@ -76,3 +76,4 @@ Makefile
 /exampleClient
 /exampleClient_legacy
 /src_generated/
+/build

+ 1 - 0
AUTHORS

@@ -3,6 +3,7 @@ The authors of open62541 are (in alphabetical order)
 Bauer, Maximilian
 Graube, Markus <markus.graube (at) tu-dresden.de>
 Gruener, Sten <s.gruener (at) plt.rwth-aachen.de>
+Iatrou, Chris Paul <chris_paul.iatrou (at) tu-dresden.de>
 Jeromin, Holger
 Palm, Florian <f.palm (at) plt.rwth-aachen.de>
 Pfrommer, Julius <julius.pfrommer (at) kit.edu>

+ 46 - 6
CMakeLists.txt

@@ -204,26 +204,29 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
                 ${PROJECT_SOURCE_DIR}/src/ua_session.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_server.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_server_binary.c
-                ${PROJECT_SOURCE_DIR}/src/server/ua_nodes.c
+                ${PROJECT_SOURCE_DIR}/src/server/ua_server_utils.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_server_worker.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_securechannel_manager.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_session_manager.c
+                ${PROJECT_SOURCE_DIR}/src/server/ua_nodes.c
+                # nodestores
+                ${PROJECT_SOURCE_DIR}/src/server/ua_nodestore.c
+                ${PROJECT_SOURCE_DIR}/src/server/ua_nodestore_concurrent.c
+                # services
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_discovery.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_securechannel.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_session.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_attribute.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_nodemanagement.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_view.c
-                ${PROJECT_SOURCE_DIR}/src/client/ua_client.c
-                ${PROJECT_SOURCE_DIR}/src/client/ua_client_highlevel.c
-                # nodestores
-                ${PROJECT_SOURCE_DIR}/src/server/ua_nodestore.c
-                ${PROJECT_SOURCE_DIR}/src/server/ua_nodestore_concurrent.c
                 # method call
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_call.c
                 # subscriptions
                 ${PROJECT_SOURCE_DIR}/src/server/ua_subscription.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_subscription.c
+                # client
+                ${PROJECT_SOURCE_DIR}/src/client/ua_client.c
+                ${PROJECT_SOURCE_DIR}/src/client/ua_client_highlevel.c
                 ${PROJECT_SOURCE_DIR}/src/client/ua_client_highlevel_subscriptions.c
                 # plugins and dependencies
                 ${PROJECT_SOURCE_DIR}/plugins/ua_network_tcp.c
@@ -350,6 +353,7 @@ 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})
 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)
@@ -386,3 +390,39 @@ if(UA_BUILD_EXAMPLES_NODESET_COMPILER)
   add_custom_target(generate_informationmodel ALL
                     DEPENDS ${PROJECT_BINARY_DIR}/src_generated/nodeset.h ${PROJECT_BINARY_DIR}/src_generated/nodeset.c)
 endif()
+
+
+##########################
+# Installation           #
+##########################
+# invoke via `make install`
+# specify install location with `-DCMAKE_INSTALL_PREFIX=xyz`
+# Enable shared library with `-DBUILD_SHARED_LIBS=ON`
+
+# export library (either static or shared depending on BUILD_SHARED_LIBS)
+install(TARGETS open62541
+        LIBRARY DESTINATION lib
+        ARCHIVE DESTINATION lib
+    )
+# export amalgamated header open62541.h which is generated due to build of open62541-object
+install(FILES ${PROJECT_BINARY_DIR}/open62541.h DESTINATION include)
+
+
+
+##########################
+# Packaging (DEB/RPM)    #
+##########################
+# invoke via `make package`
+
+SET(CPACK_GENERATOR "TGZ;DEB;RPM")
+SET(CPACK_PACKAGE_VENDOR "open62541 team")
+SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "OPC UA implementation")
+SET(CPACK_PACKAGE_DESCRIPTION "open62541 is a C-based library (linking with C++ projects is possible) with all necessary tools to implement dedicated OPC UA clients and servers, or to integrate OPC UA-based communication into existing applications.")
+SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
+SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
+SET(CPACK_PACKAGE_VERSION_MAJOR "0")
+SET(CPACK_PACKAGE_VERSION_MINOR "2")
+SET(CPACK_PACKAGE_VERSION_PATCH "0")
+SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "open62541 team") #required
+
+INCLUDE(CPack)

+ 18 - 4
doc/CMakeLists.txt

@@ -8,8 +8,20 @@ set(DOC_SRC_DIR   ${PROJECT_BINARY_DIR}/doc_src)
 make_directory(${DOC_SRC_DIR})
 file(GLOB DOC_SRC "${PROJECT_SOURCE_DIR}/doc/*")
 list(REMOVE_ITEM DOC_SRC "${PROJECT_SOURCE_DIR}/doc/conf.py")
-configure_file("${PROJECT_SOURCE_DIR}/doc/conf.py" "${DOC_SRC_DIR}/conf.py")
+list(REMOVE_ITEM DOC_SRC "${PROJECT_SOURCE_DIR}/doc/tutorial_server_variables.rst")
+list(REMOVE_ITEM DOC_SRC "${PROJECT_SOURCE_DIR}/doc/tutorial_server_method.rst")
+list(REMOVE_ITEM DOC_SRC "${PROJECT_SOURCE_DIR}/doc/tutorial_server_firstSteps.rst")
+list(REMOVE_ITEM DOC_SRC "${PROJECT_SOURCE_DIR}/doc/tutorial_client_firstSteps.rst")
 file(COPY ${DOC_SRC} DESTINATION ${DOC_SRC_DIR})
+configure_file("${PROJECT_SOURCE_DIR}/doc/conf.py" "${DOC_SRC_DIR}/conf.py")
+configure_file("${PROJECT_SOURCE_DIR}/doc/tutorial_server_variables.rst"
+               "${DOC_SRC_DIR}/tutorial_server_variables.rst")
+configure_file("${PROJECT_SOURCE_DIR}/doc/tutorial_server_method.rst"
+               "${DOC_SRC_DIR}/tutorial_server_method.rst")
+configure_file("${PROJECT_SOURCE_DIR}/doc/tutorial_server_firstSteps.rst"
+               "${DOC_SRC_DIR}/tutorial_server_firstSteps.rst")
+configure_file("${PROJECT_SOURCE_DIR}/doc/tutorial_client_firstSteps.rst"
+               "${DOC_SRC_DIR}/tutorial_client_firstSteps.rst")
 
 function(generate_rst in out)
   add_custom_command(OUTPUT ${out} DEPENDS ${PROJECT_SOURCE_DIR}/tools/c2rst.py ${in}
@@ -26,16 +38,17 @@ generate_rst(${PROJECT_SOURCE_DIR}/include/ua_log.h ${DOC_SRC_DIR}/log.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/include/ua_connection.h ${DOC_SRC_DIR}/connection.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/src/server/ua_services.h ${DOC_SRC_DIR}/services.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/src/server/ua_nodestore.h ${DOC_SRC_DIR}/nodestore.rst)
+generate_rst(${PROJECT_SOURCE_DIR}/src/server/ua_nodes.h ${DOC_SRC_DIR}/information_modelling.rst)
 
 add_custom_target(doc_latex ${SPHINX_EXECUTABLE}
   -b latex "${DOC_SRC_DIR}" "${DOC_LATEX_DIR}"
   DEPENDS ${DOC_SRC_DIR}/types.rst ${DOC_SRC_DIR}/constants.rst ${DOC_SRC_DIR}/types_generated.rst
           ${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}/log.rst ${DOC_SRC_DIR}/connection.rst ${DOC_SRC_DIR}/services.rst
+          ${DOC_SRC_DIR}/nodestore.rst ${DOC_SRC_DIR}/information_modelling.rst
   COMMENT "Building LaTeX sources for documentation with Sphinx")
 add_dependencies(doc_latex open62541)
 
-
 add_custom_target(doc_pdf ${PDFLATEX_COMPILER} -q "open62541.tex"
   WORKING_DIRECTORY ${DOC_LATEX_DIR}
   # compile it twice so that the contents pages are correct
@@ -48,6 +61,7 @@ add_custom_target(doc ${SPHINX_EXECUTABLE}
   -b html "${DOC_SRC_DIR}" "${DOC_HTML_DIR}"
   DEPENDS ${DOC_SRC_DIR}/types.rst ${DOC_SRC_DIR}/constants.rst ${DOC_SRC_DIR}/types_generated.rst
           ${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}/log.rst ${DOC_SRC_DIR}/connection.rst ${DOC_SRC_DIR}/services.rst
+          ${DOC_SRC_DIR}/nodestore.rst ${DOC_SRC_DIR}/information_modelling.rst
   COMMENT "Building HTML documentation with Sphinx")
 add_dependencies(doc open62541)

+ 37 - 11
doc/building.rst

@@ -1,10 +1,10 @@
 .. _building:
 
 Building open62541
-^^^^^^^^^^^^^^^^^^
+==================
 
 Building the Examples
-=====================
+---------------------
 
 Using the GCC compiler, the following calls build the examples on Linux.
 
@@ -15,10 +15,10 @@ Using the GCC compiler, the following calls build the examples on Linux.
    gcc -std=c99 open62541.c server_variable.c -o server
 
 Building the Library
-====================
+--------------------
 
 Building with CMake on Ubuntu or Debian
----------------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 .. code-block:: bash
 
@@ -41,7 +41,7 @@ Building with CMake on Ubuntu or Debian
    make
 
 Building with CMake on Windows
-------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Here we explain the build process for Visual Studio (2013 or newer). To build
 with MinGW, just replace the compiler selection in the call to CMake.
@@ -66,7 +66,7 @@ with MinGW, just replace the compiler selection in the call to CMake.
 - Then open :file:`build\open62541.sln` in Visual Studio 2015 and build as usual
 
 Building on OS X
-----------------
+^^^^^^^^^^^^^^^^
 
 - Download and install
 
@@ -87,11 +87,37 @@ 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:
+
+.. code-block:: bash
+   
+   pkg_add gcc python cmake
+
+- Tell the system to actually use the recent gcc (it gets installed as egcc on OpenBSD): 
+
+.. code-block:: bash
+   
+   export CC=egcc CXX=eg++
+
+- Now procede as described for Ubuntu/Debian:
+
+.. code-block:: bash
+
+   cd open62541
+   mkdir build
+   cd build
+   cmake ..
+   make
+
 Build Options
-=============
+-------------
 
 Build Type and Logging
-----------------------
+^^^^^^^^^^^^^^^^^^^^^^
 
 **CMAKE_BUILD_TYPE**
   - ``RelWithDebInfo`` -O2 optimization with debug symbols
@@ -113,7 +139,7 @@ Further options that are not inherited from the CMake configuration are defined
 in :file:`ua_config.h`. Usually there is no need to adjust them.
 
 UA_BUILD_* group
-----------------
+^^^^^^^^^^^^^^^^
 
 By default only the shared object libopen62541.so or the library open62541.dll
 and open62541.dll.a resp. open62541.lib are build. Additional artifacts can be
@@ -139,7 +165,7 @@ specified by the following options:
    Generate a self-signed certificate for the server (openSSL required)
 
 UA_ENABLE_* group
------------------
+^^^^^^^^^^^^^^^^^
 
 This group contains build options related to the supported OPC UA features.
 
@@ -174,7 +200,7 @@ be visible in the cmake GUIs.
    Enable udp extension
 
 Building a shared library
--------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^
 
 open62541 is small enough that most users will want to statically link the library into their programs. If a shared library (.dll, .so) is required, this can be enabled in CMake with the `BUILD_SHARED_LIBS` option.
 Note that this option modifies the :file:`ua_config.h` file that is also included in :file:`open62541.h` for the single-file distribution.

+ 0 - 132
doc/in_a_nutshell.rst

@@ -1,132 +0,0 @@
-.. _introduction:
-
-Introduction to OPC UA
-======================
-
-OPC UA, a collection of services
---------------------------------
-
-In OPC UA, all communication is based on service calls, each consisting of a request and a response
-message. Be careful to note the difference between services and methods. Services are pre-defined in
-the standard and cannot be changed. But you can use the *Call* service to invoke user-defined
-methods on the server.
-
-For completeness, the following tables contain all services defined in the standard. Do not bother
-with their details yet. We will introduce the different services later in the text. In open62541,
-each service is implemented in a single function. See the :ref:`services` section for details.
-
-**Establishing communication**
-
-+-----------------------------+-----------------------------+------------------------------+
-| Discovery Service Set       | SecureChannel Service Set   | Session Service Set          |
-+=============================+=============================+==============================+
-| FindServers                 | OpenSecureChannel           | CreateSession                |
-+-----------------------------+-----------------------------+------------------------------+
-| GetEndpoints                | CloseSecureChannel          | ActivateSession              |
-+-----------------------------+-----------------------------+------------------------------+
-| RegisterServer              |                             | CloseSession                 |
-+-----------------------------+-----------------------------+------------------------------+
-|                             |                             | Cancel                       |
-+-----------------------------+-----------------------------+------------------------------+
-
-**Interaction with the information model**
-
-+-----------------------------+-------------------------------+------------------------------+------------------------------+----------------------+
-| Attribute Service Set       | View Service Set              | Method Service Set           | NodeManagement Service Set   | Query Service Set    |
-+=============================+===============================+==============================+==============================+======================+
-| Read                        | Browse                        | Call                         | AddNodes                     | QueryFirst           |
-+-----------------------------+-------------------------------+------------------------------+------------------------------+----------------------+
-| HistoryRead                 | BrowseNext                    |                              | AddReferences                | QueryNext            |
-+-----------------------------+-------------------------------+------------------------------+------------------------------+----------------------+
-| Write                       | TranslateBrowsePathsToNodeids |                              | DeleteNodes                  |                      |
-+-----------------------------+-------------------------------+------------------------------+------------------------------+----------------------+
-| HistoryUpdate               | RegisterNodes                 |                              | DeleteReferences             |                      |
-+-----------------------------+-------------------------------+------------------------------+------------------------------+----------------------+
-|                             | UnregisterNodes               |                              |                              |                      |
-+-----------------------------+-------------------------------+------------------------------+------------------------------+----------------------+
-
-**Notifications**
-
-+-----------------------------+-------------------------------+
-| MonitoredItem Service Set   | Subscription Service Set      |
-+=============================+===============================+
-| CreateMonitoredItems        | CreateSubscription            |
-+-----------------------------+-------------------------------+
-| ModifyMonitoreditems        | ModifySubscription            |
-+-----------------------------+-------------------------------+
-| SetMonitoringMode           | SetPublishingMode             |
-+-----------------------------+-------------------------------+
-| SetTriggering               | Publish                       |
-+-----------------------------+-------------------------------+
-| DeleteMonitoredItems        | Republish                     |
-+-----------------------------+-------------------------------+
-|                             | TransferSubscription          |
-+-----------------------------+-------------------------------+
-|                             | DeleteSubscription            |
-+-----------------------------+-------------------------------+
-
-OPC UA, a web of nodes
-----------------------
-
-The information model in each OPC UA server is a web of interconnected nodes.
-There are eight different types of nodes.
-
-+-----------------------+-------------------+
-| ReferenceTypeNode     | MethodNode        |
-+-----------------------+-------------------+
-| DataTypeNode          | ObjectTypeNode    |
-+-----------------------+-------------------+
-| VariableTypeNode      | ObjectNode        |
-+-----------------------+-------------------+
-| VariableNode          | ViewNode          |
-+-----------------------+-------------------+
-
-Depending on its type, every node contains different attributes. Some
-attributes, are contained in all node types:
-
-+----------------+---------------+
-| Name           | Type          |
-+================+===============+
-| nodeID         | NodeId        |
-+----------------+---------------+
-| nodeClass      | NodeClass     |
-+----------------+---------------+
-| browseName     | QualifiedName |
-+----------------+---------------+
-| displayName    | LocalizedText |
-+----------------+---------------+
-| description    | LocalizedText |
-+----------------+---------------+
-| writeMask      | UInt32        |
-+----------------+---------------+
-| userWriteMask  | UInt32        |
-+----------------+---------------+
-| referencesSize | Int32         |
-+----------------+---------------+
-| references     |ReferenceNode[]|
-+----------------+---------------+
-
-Nodes are interconnected by directed reference-triples of the form ``(nodeid,
-referencetype, target-nodeid)``. Therefore an OPC UA information model is
-easiest imagined as a *web of nodes*. Reference types can be
-
-- standard- or user-defined and
-- either non-hierarchical (e.g., indicating the type of a variable-node) or
-  hierarchical (e.g., indicating a parent-child relationship).
-
-OPC UA, a protocol
-------------------
-
-The OPC UA protocol (both binary and XML-based) is based on 25 *built-in*
-datatypes. In open62541, these are defined in ua_types.h.
-
-The builtin datatypes are combined to more complex structures. When the structure contains an array,
-then the size of the array is stored in an Int32 value just before the array itself. A size of -1
-indicates an undefined array. Positive sizes (and zero) have the usual semantics.
-
-Most importantly, every service has a request and a response message defined as such a data
-structure. The entire OPC UA protocol revolves around the exchange of these request and response
-messages. Their exact definitions can be looked up here:
-https://opcfoundation.org/UA/schemas/Opc.Ua.Types.bsd.xml. In open62541, we autogenerate the
-C-structs to handle the standard-defined structures automatically. See ua_types_generated.h for
-comparison.

+ 28 - 30
doc/index.rst

@@ -1,30 +1,28 @@
-Welcome to open62541's documentation!
-=====================================
-
-`OPC UA <http://en.wikipedia.org/wiki/OPC_Unified_Architecture>`_ (short for OPC
-Unified Architecture) is a protocol for industrial communication and has been
-standardized in the IEC62541. At its core, OPC UA defines a set of services to
-interact with a server-side object-oriented information model. Besides the
-service-calls initiated by the client, push-notification of remote events (such
-as data changes) can be negotiated with the server. The client/server
-interaction is mapped either to a binary encoding and TCP-based transmission or
-to SOAP-based webservices. As of late, OPC UA is marketed as the one standard
-for non-realtime industrial communication.
-
-We believe that it is best to understand OPC UA *from the inside out*, building
-upon conceptually simple first principles. After establishing a first
-understanding, we go on explaining how these principles are realized in detail.
-Examples are given based on the *open62541* implementation of the
-standard.
-
-.. toctree::
-   :maxdepth: 3
-
-   in_a_nutshell
-   building
-   tutorials
-   types
-   constants
-   server
-   client
-   internal
+.. only:: html
+
+   .. include:: index_html.rst
+
+.. only:: latex
+
+   open62541 Documentation
+   #######################
+
+   .. include:: introduction.rst
+
+   .. include:: building.rst
+
+   .. include:: tutorials.rst
+
+   .. include:: types.rst
+
+   .. include:: information_modelling.rst
+
+   .. include:: services.rst
+
+   .. include:: server.rst
+
+   .. include:: client.rst
+
+   .. include:: constants.rst
+
+   .. include:: internal.rst

+ 15 - 0
doc/index_html.rst

@@ -0,0 +1,15 @@
+.. include:: introduction.rst
+
+.. toctree::
+    :hidden:
+
+    self
+    building
+    tutorials
+    types
+    information_modelling
+    services
+    server
+    client
+    constants
+    internal

+ 5 - 7
doc/internal.rst

@@ -1,10 +1,8 @@
 Internals
-#########
+=========
 
-.. toctree::
+.. include:: nodestore.rst
 
-   services
-   nodestore
-   connection
-   log
-   types_generated
+.. include:: connection.rst
+
+.. include:: log.rst

File diff suppressed because it is too large
+ 67 - 0
doc/introduction.rst


+ 7 - 7
doc/tutorial_client_firstSteps.rst

@@ -1,5 +1,5 @@
-5. Building a simple client
-===========================
+Building a simple client
+------------------------
 
 You should already have a basic server from the previous tutorials. open62541
 provides both a server- and clientside API, so creating a client is as easy as
@@ -29,24 +29,24 @@ Compilation is very much similar to the server example.
    $ gcc -std=c99 open6251.c myClient.c -o myClient
 
 Reading a node attibute
------------------------
+^^^^^^^^^^^^^^^^^^^^^^^
 
 In this example we are going to connect to the server from the second tutorial
 and read the value-attribute of the added variable node.
 
-.. literalinclude:: ../../examples/client_firstSteps.c
+.. literalinclude:: ${PROJECT_SOURCE_DIR}/examples/client_firstSteps.c
    :language: c
    :linenos:
    :lines: 4,5,12,14-
 
 
 Further tasks
--------------
+^^^^^^^^^^^^^
 * Try to connect to some other OPC UA server by changing
-  "opc.tcp://localhost:16664" to an appropriate address (remember that the
+  ``opc.tcp://localhost:16664`` to an appropriate address (remember that the
   queried node is contained in any OPC UA server).
 * Try to set the value of the variable node (ns=1,i="the.answer") containing an
-  "Int32" from the example server (which is built in
+  ``Int32`` from the example server (which is built in
   :doc:`tutorial_server_firstSteps`) using "UA_Client_write" function. The
   example server needs some more modifications, i.e., changing request types.
   The answer can be found in "examples/exampleClient.c".

File diff suppressed because it is too large
+ 33 - 173
doc/tutorial_noderelations.rst


+ 4 - 4
doc/tutorial_server_firstSteps.rst

@@ -1,12 +1,12 @@
-1. Building a simple server
-===========================
+Building a simple server
+------------------------
 
 This series of tutorial guide you through your first steps with open62541. For
 compiling the examples, you need a compiler (MS Visual Studio 2015 or newer,
 GCC, Clang and MinGW32 are all known to be working). The compilation
 instructions are given for GCC but should be straightforward to adapt.
 
-It will also be very helpfull to install an OPC UA Client with a graphical
+It will also be very helpful to install an OPC UA Client with a graphical
 frontend, such as UAExpert by Unified Automation. That will enable you to
 examine the information model of any OPC UA server.
 
@@ -17,7 +17,7 @@ have the ``open62541.c/.h`` files in the current folder.
 
 Now create a new C source-file called ``myServer.c`` with the following content:
 
-.. literalinclude:: ../../examples/server_firstSteps.c
+.. literalinclude:: ${PROJECT_SOURCE_DIR}/examples/server_firstSteps.c
    :language: c
    :linenos:
    :lines: 4,12,14-34

+ 3 - 3
doc/tutorial_server_method.rst

@@ -1,5 +1,5 @@
-3. Adding a server-side method
-==============================
+Adding a server-side method
+---------------------------
 
 This tutorial demonstrates how to add method nodes to the server. Use an UA
 client, e.g., UaExpert to call the method (right-click on the method node ->
@@ -16,7 +16,7 @@ The last example presents a way to bind a new method callback to an already
 instantiated method node.
 
 
-.. literalinclude:: ../../examples/server_method.c
+.. literalinclude:: ${PROJECT_SOURCE_DIR}/examples/server_method.c
    :language: c
    :linenos:
    :lines: 4,5,14,16-

+ 21 - 67
doc/tutorial_server_variables.rst

@@ -1,30 +1,27 @@
 .. role:: ccode(code)
       :language: c
 
-2. Adding variables to a server
-===============================
+Adding variables to a server
+----------------------------
 
 This tutorial shows how to add variable nodes to a server and how these can be
-connected to a physical process in the background. Make sure to read the
-:ref:`introduction <introduction>` first.
+connected to a physical process in the background.
 
 This is the code for a server with a single variable node holding an integer. We
 will take this example to explain some of the fundamental concepts of open62541.
 
-.. literalinclude:: ../../examples/server_variable.c
+.. literalinclude:: ${PROJECT_SOURCE_DIR}/examples/server_variable.c
    :language: c
    :linenos:
    :lines: 4,13,15-
 
-
 Variants and Datatypes
-----------------------
+^^^^^^^^^^^^^^^^^^^^^^
 
-The datatype *variant* belongs to the built-in datatypes of OPC UA and is used
-as a container type. A variant can hold any other datatype as a scalar (except
-variant) or as an array. Array variants can additionally denote the
+The datatype :ref:`variant` belongs to the built-in datatypes of OPC UA and is
+used as a container type. A variant can hold any other datatype as a scalar
+(except variant) or as an array. Array variants can additionally denote the
 dimensionality of the data (e.g. a 2x3 matrix) in an additional integer array.
-You can find the code that defines the variant datatype :ref:`here <variant>`.
 
 The `UA_VariableAttributes` type contains a variant member `value`. The command
 :ccode:`UA_Variant_setScalar(&attr.value, &myInteger,
@@ -33,20 +30,9 @@ this does not make a copy of the integer (for which `UA_Variant_setScalarCopy`
 can be used). The variant (and its content) is then copied into the newly
 created node.
 
-Since it is a bit involved to set variants by hand, there are four basic
-functions you should be aware of:
-
-  * **UA_Variant_setScalar** will set the contents of the variant to a pointer
-    to the object that you pass with the call. Make sure to never deallocate
-    that object while the variant exists!
-  * **UA_Variant_setScalarCopy** will copy the object pointed to into a new
-    object of the same type and attach that to the variant.
-  * **UA_Variant_setArray** will set the contents of the variant to be an array
-    and point to the exact pointer/object that you passed the call.
-  * **UA_Variant_setArrayCopy** will create a copy of the array passed with the
-    call.
-
-The equivalent code using allocations is as follows:
+The above code could have used allocations by making copies of all entries of
+the attribute variant. Then, of course, the variant content needs to be deleted
+to prevent memleaks.
 
 .. code-block:: c
 
@@ -65,25 +51,11 @@ information model. For that, we state the NodeId of the parent node and the
 (hierarchical) reference to the parent node.
 
 NodeIds
--------
-
-A node ID is a unique identifier in server's context. It is composed of two members:
-
-+-------------+-----------------+---------------------------+
-| Type        | Name            | Notes                     |
-+=============+=================+===========================+
-| UInt16      | namespaceIndex  |  Number of the namespace  |
-+-------------+-----------------+---------------------------+
-| Union       | identifier      |  One idenifier of the     |
-|             |  * String       |  listed types             |
-|             |  * Integer      |                           |
-|             |  * GUID         |                           |
-|             |  * ByteString   |                           |
-+-------------+-----------------+---------------------------+
-
-The first parameter is the number of node's namespace, the second one may be a
-numeric, a string or a GUID (Globally Unique ID) identifier. The following are
-some examples for their usage.
+^^^^^^^
+
+A :ref:`nodeid` is a unique identifier in server's context. It contains the
+number of node's namespace and either a numeric, string or GUID (Globally
+Unique ID) identifier. The following are some examples for their usage.
 
 .. code-block:: c
 
@@ -91,30 +63,12 @@ some examples for their usage.
 
    UA_NodeId id2 = UA_NODEID_STRING(1, "testid"); /* points to the static string */
 
-   UA_NodeId id3 = UA_NODEID_STRING_ALLOC(1, "testid");
+   UA_NodeId id3 = UA_NODEID_STRING_ALLOC(1, "testid"); /* copy to memory */
    UA_NodeId_deleteMembers(&id3); /* free the allocated string */
 
 
-Adding a variable node to the server that contains a user-defined callback
---------------------------------------------------------------------------
-
-The latter case allows to define callback functions that are executed on read or
-write of the node. In this case an "UA_DataSource" containing the respective
-callback pointer is intserted into the node.
-
-Consider ``examples/server_datasource.c`` in the repository. The examples are
-compiled if the Cmake option UA_BUILD_EXAMPLE is turned on.
-
-
-UA_Server_addVariableNode vs. UA_Server_addDataSourceVariableNode
-UA_ValueCallback
-UA_DataSource
-
-
-Asserting success/failure
--------------------------
+Adding a Variable with an external Data Source
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Almost all functions of the open62541 API will return a `StatusCode` 32-bit
-integer. The actual statuscodes are defined :ref:`here <statuscodes>`. Normally,
-the functions should return `UA_STATUSCODE_GOOD`, which maps to the zero
-integer.
+In order to couple a running process to a variable, a :ref:`datasource` is used.
+Consider ``examples/server_datasource.c`` in the repository for an example.

+ 8 - 6
doc/tutorials.rst

@@ -1,10 +1,12 @@
 Tutorials
 =========
 
-.. toctree::
+.. include:: tutorial_server_firstSteps.rst
 
-   tutorial_server_firstSteps
-   tutorial_server_variables
-   tutorial_server_method
-   tutorial_noderelations
-   tutorial_client_firstSteps
+.. include:: tutorial_server_variables.rst
+
+.. include:: tutorial_server_method.rst
+
+.. include:: tutorial_noderelations.rst
+
+.. include:: tutorial_client_firstSteps.rst

+ 3 - 0
examples/CMakeLists.txt

@@ -55,6 +55,9 @@ target_link_libraries(server_datasource ${LIBS})
 add_executable(server_firstSteps server_firstSteps.c $<TARGET_OBJECTS:open62541-object>)
 target_link_libraries(server_firstSteps ${LIBS})
 
+add_executable(server_instantiation server_instantiation.c $<TARGET_OBJECTS:open62541-object>)
+target_link_libraries(server_instantiation ${LIBS})
+
 add_executable(server_repeated_job server_repeated_job.c $<TARGET_OBJECTS:open62541-object>)
 target_link_libraries(server_repeated_job ${LIBS})
 

+ 1 - 0
examples/server.c

@@ -248,6 +248,7 @@ int main(int argc, char** argv) {
 
         UA_VariableAttributes attr;
         UA_VariableAttributes_init(&attr);
+        attr.valueRank = -2;
         char name[15];
 #if defined(_WIN32) && !defined(__MINGW32__)
         sprintf_s(name, 15, "%02d", type);

+ 4 - 3
examples/server_firstSteps.c

@@ -22,13 +22,14 @@ int main(void) {
     signal(SIGTERM, stopHandler);
 
     UA_ServerConfig config = UA_ServerConfig_standard;
-    UA_ServerNetworkLayer nl = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
+    UA_ServerNetworkLayer nl;
+    nl = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
     config.networkLayers = &nl;
     config.networkLayersSize = 1;
     UA_Server *server = UA_Server_new(config);
 
-    UA_StatusCode retval = UA_Server_run(server, &running);
+    UA_Server_run(server, &running);
     UA_Server_delete(server);
     nl.deleteMembers(&nl);
-    return (int)retval;
+    return 0;
 }

+ 95 - 0
examples/server_instantiation.c

@@ -0,0 +1,95 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+#include <signal.h>
+
+#ifdef UA_NO_AMALGAMATION
+#include "ua_types.h"
+#include "ua_server.h"
+#include "ua_config_standard.h"
+#include "ua_network_tcp.h"
+#else
+#include "open62541.h"
+#endif
+
+UA_Boolean running = true;
+static void stopHandler(int sig) {
+    running = false;
+}
+
+int main(void) {
+    signal(SIGINT,  stopHandler);
+    signal(SIGTERM, stopHandler);
+
+    UA_ServerConfig config = UA_ServerConfig_standard;
+    UA_ServerNetworkLayer nl = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
+    config.networkLayers = &nl;
+    config.networkLayersSize = 1;
+    UA_Server *server = UA_Server_new(config);
+    
+    /* Create a rudimentary objectType
+     * 
+     * BaseObjectType
+     * |
+     * +- (OT) MamalType
+     *    + (V) Age
+     *    + (OT) DogType
+     *      |
+     *      + (V) Name
+     */ 
+    UA_StatusCode retval;
+    UA_ObjectTypeAttributes otAttr;
+    UA_ObjectTypeAttributes_init(&otAttr);
+    
+    otAttr.description = UA_LOCALIZEDTEXT("en_US", "A mamal");
+    otAttr.displayName = UA_LOCALIZEDTEXT("en_US", "MamalType");
+    UA_Server_addObjectTypeNode(server, UA_NODEID_NUMERIC(1, 10000), 
+                                UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE), UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
+                                UA_QUALIFIEDNAME(1, "MamalType"), otAttr, NULL, NULL);
+  
+    UA_VariableAttributes   vAttr;
+    UA_VariableAttributes_init(&vAttr);
+    vAttr.description =  UA_LOCALIZEDTEXT("en_US", "This mamals Age in months");
+    vAttr.displayName =  UA_LOCALIZEDTEXT("en_US", "Age");
+    UA_UInt32 ageVar = 0;
+    UA_Variant_setScalarCopy(&vAttr.value, &ageVar, &UA_TYPES[UA_TYPES_UINT32]);
+    UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 10001), 
+                              UA_NODEID_NUMERIC(1, 10000), UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY),
+                              UA_QUALIFIEDNAME(1, "Age"), UA_NODEID_NULL, vAttr, NULL, NULL);
+  
+    UA_ObjectTypeAttributes_init(&otAttr);
+    otAttr.description = UA_LOCALIZEDTEXT("en_US", "A dog, subtype of mamal");
+    otAttr.displayName = UA_LOCALIZEDTEXT("en_US", "DogType");
+    UA_Server_addObjectTypeNode(server, UA_NODEID_NUMERIC(1, 10002), 
+                                UA_NODEID_NUMERIC(1, 10000), UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
+                                UA_QUALIFIEDNAME(1, "DogType"), otAttr, NULL, NULL);
+    
+    UA_VariableAttributes_init(&vAttr);
+    vAttr.description =  UA_LOCALIZEDTEXT("en_US", "This mamals Age in months");
+    vAttr.displayName =  UA_LOCALIZEDTEXT("en_US", "Name");
+    UA_String defaultName = UA_STRING("unnamed dog");
+    UA_Variant_setScalarCopy(&vAttr.value, &defaultName, &UA_TYPES[UA_TYPES_STRING]);
+    UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 10003), 
+                              UA_NODEID_NUMERIC(1, 10002), UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY),
+                              UA_QUALIFIEDNAME(1, "Name"), UA_NODEID_NULL, vAttr, NULL, NULL);
+    
+    /* Instatiate a dog named bello:
+     * (O) Objects
+     *   + O Bello <DogType>
+     *     + Age 
+     *     + Name
+     */
+    
+    UA_ObjectAttributes oAttr;
+    UA_ObjectAttributes_init(&oAttr);
+    oAttr.description = UA_LOCALIZEDTEXT("en_US", "A dog named Bello");
+    oAttr.displayName = UA_LOCALIZEDTEXT("en_US", "Bello");
+    UA_Server_addObjectNode(server, UA_NODEID_NUMERIC(1, 0), 
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                            UA_QUALIFIEDNAME(1, "Bello"), UA_NODEID_NUMERIC(1, 10002), oAttr, NULL, NULL);
+    
+    retval = UA_Server_run(server, &running);
+    UA_Server_delete(server);
+    nl.deleteMembers(&nl);
+    return (int)retval;
+}

+ 3 - 1
include/ua_client.h

@@ -35,7 +35,7 @@ extern "C" {
  * convenience, some functionality has been wrapped in :ref:`high-level
  * abstractions <client-highlevel>`.
  *
- * **However**: At this point, the client does not yet contain its own thread or
+ * **However**: At this time, the client does not yet contain its own thread or
  * event-driven main-loop. So the client will not perform any actions
  * automatically in the background. This is especially relevant for
  * subscriptions. The user will have to periodically call
@@ -172,6 +172,7 @@ UA_Client_Service_write(UA_Client *client, const UA_WriteRequest request) {
 /**
  * Method Service Set
  * ^^^^^^^^^^^^^^^^^^ */
+#ifdef UA_ENABLE_METHODCALLS
 static UA_INLINE UA_CallResponse
 UA_Client_Service_call(UA_Client *client, const UA_CallRequest request) {
     UA_CallResponse response;
@@ -179,6 +180,7 @@ UA_Client_Service_call(UA_Client *client, const UA_CallRequest request) {
                         &response, &UA_TYPES[UA_TYPES_CALLRESPONSE]);
     return response;
 }
+#endif
 
 /**
  * NodeManagement Service Set

+ 11 - 13
include/ua_client_highlevel.h

@@ -27,22 +27,19 @@ extern "C" {
  *
  * Highlevel Client Functionality
  * ------------------------------
- * The following definitions are convenience functions making use of the
- * standard OPC UA services in the background.
- *
- * The high level abstractions concetrate on getting the job done in a simple
- * manner for the user. This is a less flexible way of handling the stack,
- * because at many places sensible defaults are presumed; at the same time using
- * these functions is the easiest way of implementing an OPC UA application, as
- * you will not have to consider all the details that go into the OPC UA
- * services. A concept of how nodes and datatypes are used are completely
- * sufficient to use OPC UA with this layer.
  *
- * If more flexibility is needed, you can always achieve the same functionality
- * using the raw :ref:`OPC UA services <client-services>`.
+ * The following definitions are convenience functions making use of the
+ * standard OPC UA services in the background. This is a less flexible way of
+ * handling the stack, because at many places sensible defaults are presumed; at
+ * the same time using these functions is the easiest way of implementing an OPC
+ * UA application, as you will not have to consider all the details that go into
+ * the OPC UA services. If more flexibility is needed, you can always achieve
+ * the same functionality using the raw :ref:`OPC UA services
+ * <client-services>`.
  *
  * Read Attributes
  * ^^^^^^^^^^^^^^^
+ *
  * The following functions can be used to retrieve a single node attribute. Use
  * the regular service to read several attributes at once. */
 /* Don't call this function, use the typed versions */
@@ -221,6 +218,7 @@ UA_Client_readUserExecutableAttribute(UA_Client *client, const UA_NodeId nodeId,
 /**
  * Write Attributes
  * ^^^^^^^^^^^^^^^^
+ *
  * The following functions can be use to write a single node attribute at a
  * time. Use the regular write service to write several attributes at once. */
 /* Don't call this function, use the typed versions */
@@ -555,7 +553,7 @@ UA_Client_addMethodNode(UA_Client *client, const UA_NodeId requestedNewNodeId,
  *
  * Subscriptions Handling
  * ^^^^^^^^^^^^^^^^^^^^^^
- * At this point, the client does not yet contain its own thread or event-driven
+ * At this time, the client does not yet contain its own thread or event-driven
  * main-loop. So the client will not perform any actions automatically in the
  * background. This is especially relevant for subscriptions. The user will have
  * to periodically call `UA_Client_Subscriptions_manuallySendPublishRequest`.

+ 22 - 21
include/ua_connection.h

@@ -43,7 +43,7 @@ typedef struct UA_SecureChannel UA_SecureChannel;
  * without being aware of the underlying transport technology.
  *
  * Connection Config
- * ================= */
+ * ^^^^^^^^^^^^^^^^^ */
 typedef struct UA_ConnectionConfig {
     UA_UInt32 protocolVersion;
     UA_UInt32 sendBufferSize;
@@ -56,7 +56,7 @@ extern const UA_EXPORT UA_ConnectionConfig UA_ConnectionConfig_standard;
 
 /**
  * Connection Structure
- * ==================== */
+ * ^^^^^^^^^^^^^^^^^^^^ */
 typedef enum UA_ConnectionState {
     UA_CONNECTION_OPENING,     /* The socket is open, but the HEL/ACK handshake
                                   is not done */
@@ -119,28 +119,29 @@ void UA_EXPORT UA_Connection_deleteMembers(UA_Connection *connection);
 
 
 /**
- * EndpointURL helper
- */
-
-/**
- * Split the given endpoint url into hostname and port
+ * EndpointURL Helper
+ * ^^^^^^^^^^^^^^^^^^ */
+/* Split the given endpoint url into hostname and port
  * @param endpointUrl The endpoint URL to split up
- * @param hostname the target array for hostname. Has to be at least 256 size. If an IPv6 address is given, hostname contains e.g. '[2001:0db8:85a3::8a2e:0370:7334]'
+ * @param hostname the target array for hostname. Has to be at least 256 size.
+ *        If an IPv6 address is given, hostname contains e.g.
+ *        '[2001:0db8:85a3::8a2e:0370:7334]'
  * @param port set to the port of the url or 0
- * @param path pointing to the end of given endpointUrl or to NULL if no path given. The starting '/' is NOT included in path
- * @return UA_STATUSCODE_BADOUTOFRANGE if url too long, UA_STATUSCODE_BADATTRIBUTEIDINVALID if url not starting with 'opc.tcp://', UA_STATUSCODE_GOOD on success
- */
-UA_StatusCode UA_EXPORT UA_EndpointUrl_split(const char *endpointUrl, char *hostname, UA_UInt16 * port, const char ** path);
-
-/**
- * Convert given byte string to a number. Returns the number of valid digits.
- * Stops if a non-digit char is found and returns the number of digits up to that point.
- * @param buf
- * @param buflen
- * @param number
- * @return
+ * @param path pointing to the end of given endpointUrl or to NULL if no
+ *        path given. The starting '/' is NOT included in path
+ * @return UA_STATUSCODE_BADOUTOFRANGE if url too long,
+ *         UA_STATUSCODE_BADATTRIBUTEIDINVALID if url not starting with
+ *         'opc.tcp://', UA_STATUSCODE_GOOD on success
  */
-size_t UA_EXPORT UA_readNumber(UA_Byte *buf, size_t buflen, UA_UInt32 *number);
+UA_StatusCode UA_EXPORT
+UA_EndpointUrl_split(const char *endpointUrl, char *hostname,
+                     UA_UInt16 * port, const char ** path);
+
+/* Convert given byte string to a positive number. Returns the number of valid
+ * digits. Stops if a non-digit char is found and returns the number of digits
+ * up to that point. */
+size_t UA_EXPORT
+UA_readNumber(UA_Byte *buf, size_t buflen, UA_UInt32 *number);
 
 #ifdef __cplusplus
 } // extern "C"

+ 3 - 2
include/ua_constants.h

@@ -1,5 +1,4 @@
-/*
- * Copyright (C) 2013-2015 the contributors as stated in the AUTHORS file
+/* Copyright (C) 2013-2016 the contributors as stated in the AUTHORS file
  *
  * This file is part of open62541. open62541 is free software: you can
  * redistribute it and/or modify it under the terms of the GNU Lesser General
@@ -26,6 +25,8 @@ extern "C" {
  * This section contains numerical and string constants that are defined in the
  * OPC UA standard.
  *
+ * .. _attribute-id:
+ *
  * Attribute Id
  * ------------
  * Every node in an OPC UA information model contains attributes depending on

+ 2 - 1
include/ua_log.h

@@ -25,6 +25,7 @@ extern "C" {
 /**
  * Logging
  * -------
+ *
  * Servers and clients may contain a logger. Every logger needs to implement the
  * `UA_Logger` signature. An example logger that writes to stdout is provided in
  * the plugins folder.
@@ -105,7 +106,7 @@ typedef void (*UA_Logger)(UA_LogLevel level, UA_LogCategory category,
 
 /**
  * Convenience macros for complex types
- * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
 #define UA_PRINTF_GUID_FORMAT "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}"
 #define UA_PRINTF_GUID_DATA(GUID) (GUID).data1, (GUID).data2, (GUID).data3, \
         (GUID).data4[0], (GUID).data4[1], (GUID).data4[2], (GUID).data4[3], \

+ 29 - 9
include/ua_server.h

@@ -30,6 +30,8 @@ extern "C" {
 #include "ua_connection.h"
 
 /**
+ * .. _server:
+ *
  * Server
  * ======
  *
@@ -393,13 +395,6 @@ UA_Server_readExecutable(UA_Server *server, const UA_NodeId nodeId,
  * - UserAccessLevel
  * - UserExecutable
  *
- * The following attributes are currently taken from the value variant and not
- * stored separately in the nodes:
- *
- * - DataType
- * - ValueRank
- * - ArrayDimensions
- *
  * Historizing is currently unsupported */
 /* Overwrite an attribute of a node. The specialized functions below provide a
  * more concise syntax.
@@ -477,6 +472,27 @@ UA_Server_writeValue(UA_Server *server, const UA_NodeId nodeId,
                              &UA_TYPES[UA_TYPES_VARIANT], &value);
 }
 
+static UA_INLINE UA_StatusCode
+UA_Server_writeDataType(UA_Server *server, const UA_NodeId nodeId,
+                        const UA_NodeId dataType) {
+    return __UA_Server_write(server, &nodeId, UA_ATTRIBUTEID_DATATYPE,
+                             &UA_TYPES[UA_TYPES_NODEID], &dataType);
+}
+
+static UA_INLINE UA_StatusCode
+UA_Server_writeValueRank(UA_Server *server, const UA_NodeId nodeId,
+                         const UA_Int32 valueRank) {
+    return __UA_Server_write(server, &nodeId, UA_ATTRIBUTEID_VALUERANK,
+                             &UA_TYPES[UA_TYPES_INT32], &valueRank);
+}
+
+static UA_INLINE UA_StatusCode
+UA_Server_writeArrayDimensions(UA_Server *server, const UA_NodeId nodeId,
+                               const UA_Variant arrayDimensions) {
+    return __UA_Server_write(server, &nodeId, UA_ATTRIBUTEID_VALUE,
+                             &UA_TYPES[UA_TYPES_VARIANT], &arrayDimensions);
+}
+
 static UA_INLINE UA_StatusCode
 UA_Server_writeAccessLevel(UA_Server *server, const UA_NodeId nodeId,
                            const UA_UInt32 accessLevel) {
@@ -575,6 +591,8 @@ UA_Server_call(UA_Server *server, const UA_CallMethodRequest *request);
  * - Method callbacks, where a user-defined method is exposed in the information
  *   model
  *
+ * .. _datasource:
+ *
  * Data Source Callback
  * ~~~~~~~~~~~~~~~~~~~~
  *
@@ -647,6 +665,8 @@ UA_Server_setVariableNode_valueCallback(UA_Server *server, const UA_NodeId nodeI
                                         const UA_ValueCallback callback);
 
 /**
+ * .. _object-lifecycle:
+ *
  * Object Lifecycle Management Callbacks
  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  * Lifecycle management adds constructor and destructor callbacks to
@@ -691,7 +711,6 @@ UA_Server_setMethodNode_callback(UA_Server *server, const UA_NodeId methodNodeId
  * contain the nodeId of the new node. You may also pass NULL pointer if this
  * result is not relevant. The namespace index for nodes you create should never
  * be 0, as that index is reserved for OPC UA's self-description (namespace 0). */
-
 /* The instantiation callback is used to track the addition of new nodes. It is
  * also called for all sub-nodes contained in an object or variable type node
  * that is instantiated. */
@@ -736,12 +755,13 @@ UA_Server_addVariableTypeNode(UA_Server *server,
                               const UA_NodeId parentNodeId,
                               const UA_NodeId referenceTypeId,
                               const UA_QualifiedName browseName,
+                              const UA_NodeId typeDefinition,
                               const UA_VariableTypeAttributes attr,
                               UA_InstantiationCallback *instantiationCallback,
                               UA_NodeId *outNewNodeId) {
     return __UA_Server_addNode(server, UA_NODECLASS_VARIABLETYPE,
                                requestedNewNodeId, parentNodeId, referenceTypeId,
-                               browseName, UA_NODEID_NULL,
+                               browseName, typeDefinition,
                                (const UA_NodeAttributes*)&attr,
                                &UA_TYPES[UA_TYPES_VARIABLETYPEATTRIBUTES],
                                instantiationCallback, outNewNodeId);

+ 197 - 168
include/ua_types.h

@@ -1,5 +1,4 @@
-/*
- * Copyright (C) 2013-2015 the contributors as stated in the AUTHORS file
+/* Copyright (C) 2013-2016 the contributors as stated in the AUTHORS file
  *
  * This file is part of open62541. open62541 is free software: you can
  * redistribute it and/or modify it under the terms of the GNU Lesser General
@@ -10,8 +9,7 @@
  * open62541 is distributed in the hope that it will be useful, but WITHOUT ANY
  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
- * details.
- */
+ * details. */
 
 #ifndef UA_TYPES_H_
 #define UA_TYPES_H_
@@ -29,35 +27,39 @@ extern "C" {
  * Data Types
  * ==========
  *
- * In open62541, all data types share the same basic API for creation, copying
- * and deletion. The header ua_types.h defines the builtin types. In addition,
- * we auto-generate ua_types_generated.h with additional types as well as the
- * following function definitions for all (builtin and generated) data types
- * ``T``.
- *
- * ``void T_init(T *ptr)``
- *   Initialize the data type. This is synonymous with zeroing out the memory,
- *   i.e. ``memset(dataptr, 0, sizeof(T))``.
- * ``T* T_new()``
- *   Allocate and return the memory for the data type. The memory is already
- *   initialized.
- * ``UA_StatusCode T_copy(const T *src, T *dst)``
- *   Copy the content of the data type. Returns ``UA_STATUSCODE_GOOD`` or
- *   ``UA_STATUSCODE_BADOUTOFMEMORY``.
- * ``void T_deleteMembers(T *ptr)``
- *   Delete the dynamically allocated content of the data type and perform a
- *   ``T_init`` to reset the type.
- * ``void T_delete(T *ptr)``
- *   Delete the content of the data type and the memory for the data type itself.
+ * The OPC UA protocol defines 25 builtin data types and three ways of combining
+ * them into higher-order types: arrays, structures and unions. In open62541,
+ * the builtin data types are defined manually. All other data types are
+ * generated from standard XML definitions. Their exact definitions can be
+ * looked up at https://opcfoundation.org/UA/schemas/Opc.Ua.Types.bsd.xml.
+ *
+ * Note that arrays can only be part of a scalar data type and never constitute
+ * a data type on their own. Also, open62541 does not implement unions so far.
+ * They are a recent addition to the protocol (since OPC UA v1.03). And so far,
+ * no service definition makes of unions in the request / response message
+ * definition. Instead, :ref:`Variants <variant>` are used when values of
+ * different types are possible.
  *
- * OPC UA defines 25 builtin data types. All other data types are combinations
- * of the 25 builtin data types. */
+ * All data types ``T`` (builtin and generated) share the same basic API for
+ * creation, copying and deletion:
+ *
+ * - ``void T_init(T *ptr)``: Initialize the data type. This is synonymous with
+ *   zeroing out the memory, i.e. ``memset(ptr, 0, sizeof(T))``.
+ * - ``T* T_new()``: Allocate and return the memory for the data type. The
+ *   value is already initialized.
+ * - ``UA_StatusCode T_copy(const T *src, T *dst)``: Copy the content of the
+ *   data type. Returns ``UA_STATUSCODE_GOOD`` or
+ *   ``UA_STATUSCODE_BADOUTOFMEMORY``.
+ * - ``void T_deleteMembers(T *ptr)``: Delete the dynamically allocated content
+ *   of the data type and perform a ``T_init`` to reset the type.
+ * - ``void T_delete(T *ptr)``: Delete the content of the data type and the
+ *   memory for the data type itself. */
 
 #define UA_BUILTIN_TYPES_COUNT 25U
 
 /**
- * Builtin Types Part 1
- * --------------------
+ * Builtin Types
+ * -------------
  *
  * Boolean
  * ^^^^^^^
@@ -71,65 +73,65 @@ typedef bool UA_Boolean;
  * ^^^^^
  * An integer value between -128 and 127. */
 typedef int8_t UA_SByte;
-#define UA_SBYTE_MAX 127
 #define UA_SBYTE_MIN (-128)
+#define UA_SBYTE_MAX 127
 
 /**
  * Byte
  * ^^^^
  * An integer value between 0 and 256. */
 typedef uint8_t UA_Byte;
-#define UA_BYTE_MAX 256
 #define UA_BYTE_MIN 0
+#define UA_BYTE_MAX 256
 
 /**
  * Int16
  * ^^^^^
  * An integer value between -32 768 and 32 767. */
 typedef int16_t UA_Int16;
-#define UA_INT16_MAX 32767
 #define UA_INT16_MIN (-32768)
+#define UA_INT16_MAX 32767
 
 /**
  * UInt16
  * ^^^^^^
  * An integer value between 0 and 65 535. */
 typedef uint16_t UA_UInt16;
-#define UA_UINT16_MAX 65535
 #define UA_UINT16_MIN 0
+#define UA_UINT16_MAX 65535
 
 /**
  * Int32
  * ^^^^^
  * An integer value between -2 147 483 648 and 2 147 483 647. */
 typedef int32_t UA_Int32;
-#define UA_INT32_MAX 2147483647
 #define UA_INT32_MIN (-2147483648)
+#define UA_INT32_MAX 2147483647
 
 /**
  * UInt32
  * ^^^^^^
  * An integer value between 0 and 4 294 967 295. */
 typedef uint32_t UA_UInt32;
-#define UA_UINT32_MAX 4294967295
 #define UA_UINT32_MIN 0
+#define UA_UINT32_MAX 4294967295
 
 /**
  * Int64
  * ^^^^^
- * An integer value between -10 223 372 036 854 775 808 and
+ * An integer value between -9 223 372 036 854 775 808 and
  * 9 223 372 036 854 775 807. */
 typedef int64_t UA_Int64;
-#define UA_INT64_MAX (int64_t)9223372036854775807
 #define UA_INT64_MIN ((int64_t)-9223372036854775808)
+#define UA_INT64_MAX (int64_t)9223372036854775807
 
 /**
  * UInt64
  * ^^^^^^
  * An integer value between 0 and 18 446 744 073 709 551 615. */
 typedef uint64_t UA_UInt64;
-#define UA_UINT64_MAX (int64_t)18446744073709551615
 #define UA_UINT64_MIN (int64_t)0
+#define UA_UINT64_MAX (int64_t)18446744073709551615
 
 /**
  * Float
@@ -154,76 +156,6 @@ typedef double UA_Double;
 typedef uint32_t UA_StatusCode;
 
 /**
- * Array handling
- * --------------
- * In OPC UA, arrays can have a length of zero or more with the usual meaning.
- * In addition, arrays can be undefined. Then, they don't even have a length. In
- * the binary encoding, this is indicated by an array of length -1.
- *
- * In open62541 however, we use ``size_t`` for array lengths. An undefined array
- * has length 0 and the data pointer is NULL. An array of length 0 also has
- * length 0 but points to a sentinel memory address. */
-#define UA_EMPTY_ARRAY_SENTINEL ((void*)0x01)
-
-/** Forward Declaration of UA_DataType. See Section `Generic Type Handling`_
-    for details. */
-struct UA_DataType;
-typedef struct UA_DataType UA_DataType;
-
-/** The following functions are used for handling arrays of any data type. */
-
-/* Allocates and initializes an array of variables of a specific type
- *
- * @param size The requested array length
- * @param type The datatype description
- * @return Returns the memory location of the variable or (void*)0 if no memory
-           could be allocated */
-void UA_EXPORT * UA_Array_new(size_t size, const UA_DataType *type) UA_FUNC_ATTR_MALLOC;
-
-/* Allocates and copies an array
- *
- * @param src The memory location of the source array
- * @param size The size of the array
- * @param dst The location of the pointer to the new array
- * @param type The datatype of the array members
- * @return Returns UA_STATUSCODE_GOOD or UA_STATUSCODE_BADOUTOFMEMORY */
-UA_StatusCode UA_EXPORT
-UA_Array_copy(const void *src, size_t size, void **dst,
-              const UA_DataType *type) UA_FUNC_ATTR_WARN_UNUSED_RESULT;
-
-/* Deletes an array.
- *
- * @param p The memory location of the array
- * @param size The size of the array
- * @param type The datatype of the array members */
-void UA_EXPORT UA_Array_delete(void *p, size_t size, const UA_DataType *type);
-
-/**
- * .. _numericrange:
- *
- * NumericRange
- * ^^^^^^^^^^^^
- *
- * NumericRanges are used to indicate subsets of a (multidimensional) variant
- * array. NumericRange has no official type structure in the standard. On the
- * wire, it only exists as an encoded string, such as "1:2,0:3,5". The colon
- * separates min/max index and the comma separates dimensions. A single value
- * indicates a range with a single element (min==max). */
-
-typedef struct {
-    UA_UInt32 min;
-    UA_UInt32 max;
-} UA_NumericRangeDimension;
-    
-typedef struct {
-    size_t dimensionsSize;
-    UA_NumericRangeDimension *dimensions;
-} UA_NumericRange;
-
-/**
- * Builtin Types, Part 2
- * ---------------------
- *
  * String
  * ^^^^^^
  * A sequence of Unicode characters. Strings are just an array of UA_Byte. */
@@ -342,6 +274,8 @@ UA_BYTESTRING_ALLOC(const char *chars) {
 typedef UA_String UA_XmlElement;
 
 /**
+ * .. _nodeid:
+ *
  * NodeId
  * ^^^^^^
  * An identifier for a node in the address space of an OPC UA Server. */
@@ -462,6 +396,8 @@ UA_EXPANDEDNODEID_BYTESTRING_ALLOC(UA_UInt16 nsIndex, const char *chars) {
 }
 
 /**
+ * .. _qualifiedname:
+ *
  * QualifiedName
  * ^^^^^^^^^^^^^
  * A name qualified by a namespace. */
@@ -508,63 +444,52 @@ UA_LOCALIZEDTEXT_ALLOC(const char *locale, const char *text) {
     lt.text = UA_STRING_ALLOC(text); return lt;
 }
 
-/**
- * ExtensionObject
- * ^^^^^^^^^^^^^^^
- * ExtensionObjects may contain scalars of any data type. Even those that are
- * unknown to the receiver. See the Section `Generic Type Handling`_ on how
- * types are described. An ExtensionObject always contains the NodeId of the
- * Data Type. If the data cannot be decoded, we keep the encoded string and the
- * NodeId. */
-typedef struct {
-    enum {
-        UA_EXTENSIONOBJECT_ENCODED_NOBODY     = 0,
-        UA_EXTENSIONOBJECT_ENCODED_BYTESTRING = 1,
-        UA_EXTENSIONOBJECT_ENCODED_XML        = 2,
-        UA_EXTENSIONOBJECT_DECODED            = 3,
-        UA_EXTENSIONOBJECT_DECODED_NODELETE   = 4 /* Don't delete the content
-                                                     together with the
-                                                     ExtensionObject */
-    } encoding;
-    union {
-        struct {
-            UA_NodeId typeId;   /* The nodeid of the datatype */
-            UA_ByteString body; /* The bytestring of the encoded data */
-        } encoded;
-        struct {
-            const UA_DataType *type;
-            void *data;
-        } decoded;
-    } content;
-} UA_ExtensionObject;
-
 /**
  * .. _variant:
  *
  * Variant
  * ^^^^^^^
- * Variants may contain data of any type. See the Section `Generic Type
- * Handling`_ on how types are described. If the data is not of one of the 25
- * builtin types, it will be encoded as an `ExtensionObject`_ on the wire. (The
- * standard says that a variant is a union of the built-in types. open62541
- * generalizes this to any data type by transparently de- and encoding
- * ExtensionObjects in the background. If the decoding fails, the variant
- * contains the original ExtensionObject.)
- *
- * Variants can contain a single scalar or an array. For details on the handling
- * of arrays, see the Section `Array Handling`_. Array variants can have an
- * additional dimensionality (matrix, 3-tensor, ...) defined in an array of
- * dimension sizes. Higher rank dimensions are serialized first.
- *
- * The differentiation between variants containing a scalar, an array or no data
- * is as follows:
- *
- * - arrayLength == 0 && data == NULL: no existing data
- * - arrayLength == 0 && data == UA_EMPTY_ARRAY_SENTINEL: array of length 0
- * - arrayLength == 0 && data > UA_EMPTY_ARRAY_SENTINEL: scalar value
- * - arrayLength > 0: array of the given length */
+ *
+ * Variants may contain values of any type together with a description of the
+ * content. See the section on :ref:`generic-types` on how types are described.
+ * The standard mandates that variants contain built-in data types only. If the
+ * value is not of a builtin type, it is wrapped into an :ref:`extensionobject`.
+ * open62541 hides this wrapping transparently in the encoding layer. If the
+ * data type is unknown to the receiver, the variant contains the original
+ * ExtensionObject in binary or XML encoding.
+ *
+ * Variants may contain a scalar value or an array. For details on the handling
+ * of arrays, see the section on :ref:`array-handling`. Array variants can have
+ * an additional dimensionality (matrix, 3-tensor, ...) defined in an array of
+ * dimension lengths. The actual values are kept in an array of dimensions one.
+ * For users who work with higher-dimensions arrays directly, keep in mind that
+ * dimensions of higher rank are serialized first (the highest rank dimension
+ * has stride 1 and elements follow each other directly). Usually it is simplest
+ * to interact with higher-dimensional arrays via ``UA_NumericRange``
+ * descriptions (see :ref:`array-handling`).
+ *
+ * To differentiate between scalar / array variants, the following definition is
+ * used. ``UA_Variant_isScalar`` provides simplified access to these checks.
+ *
+ * - ``arrayLength == 0 && data == NULL``: undefined array of length -1
+ * - ``arrayLength == 0 && data == UA_EMPTY_ARRAY_SENTINEL``: array of length 0
+ * - ``arrayLength == 0 && data > UA_EMPTY_ARRAY_SENTINEL``: scalar value
+ * - ``arrayLength > 0``: array of the given length
+ *
+ * Variants can also be *empty*. Then, the pointer to the type description is
+ * ``NULL``. */
+/* Forward declaration. See the section on Generic Type Handling */
+struct UA_DataType;
+typedef struct UA_DataType UA_DataType;
+
+/* Forward declaration. See the section on Array Handling */
+struct UA_NumericRange;
+typedef struct UA_NumericRange UA_NumericRange;
+
+#define UA_EMPTY_ARRAY_SENTINEL ((void*)0x01)
+
 typedef struct {
-    const UA_DataType *type; /* The data type description */
+    const UA_DataType *type;      /* The data type description */
     enum {
         UA_VARIANT_DATA,          /* The data has the same lifecycle as the
                                      variant */
@@ -572,10 +497,10 @@ typedef struct {
                                      shall not be deleted at the end of the
                                      variant's lifecycle. */
     } 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 the data-array has */
-    UA_Int32 *arrayDimensions;  /* The length of each dimension */
+    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 */
+    UA_UInt32 *arrayDimensions;   /* The length of each dimension */
 } UA_Variant;
 
 /* Returns true if the variant contains a scalar value. Note that empty variants
@@ -668,6 +593,40 @@ UA_Variant_setRangeCopy(UA_Variant *v, const void *array,
                         size_t arraySize, const UA_NumericRange range);
 
 /**
+ * .. _extensionobject:
+ *
+ * ExtensionObject
+ * ^^^^^^^^^^^^^^^
+ *
+ * ExtensionObjects may contain scalars of any data type. Even those that are
+ * 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 {
+        UA_EXTENSIONOBJECT_ENCODED_NOBODY     = 0,
+        UA_EXTENSIONOBJECT_ENCODED_BYTESTRING = 1,
+        UA_EXTENSIONOBJECT_ENCODED_XML        = 2,
+        UA_EXTENSIONOBJECT_DECODED            = 3,
+        UA_EXTENSIONOBJECT_DECODED_NODELETE   = 4 /* Don't delete the content
+                                                     together with the
+                                                     ExtensionObject */
+    } encoding;
+    union {
+        struct {
+            UA_NodeId typeId;   /* The nodeid of the datatype */
+            UA_ByteString body; /* The bytestring of the encoded data */
+        } encoded;
+        struct {
+            const UA_DataType *type;
+            void *data;
+        } decoded;
+    } content;
+} UA_ExtensionObject;
+
+/**
+ * .. _datavalue:
+ *
  * DataValue
  * ^^^^^^^^^
  * A data value with an associated status code and timestamps. */
@@ -709,6 +668,8 @@ typedef struct UA_DiagnosticInfo {
 } UA_DiagnosticInfo;
 
 /**
+ * .. _generic-types:
+ *
  * Generic Type Handling
  * ---------------------
  * The builtin types can be combined to data structures. All information about a
@@ -729,8 +690,8 @@ typedef struct {
     UA_Boolean namespaceZero : 1; /* The type of the member is defined in
                                      namespace zero. In this implementation,
                                      types from custom namespace may contain
-                                     members from the same namespace or ns0
-                                     only.*/
+                                     members from the same namespace or
+                                     namespace zero only.*/
     UA_Boolean isArray       : 1; /* The member is an array */
 } UA_DataTypeMember;
 
@@ -748,8 +709,8 @@ struct UA_DataType {
                                     pointers */
     UA_Boolean overlayable  : 1; /* The type has the identical memory layout in
                                     memory and on the binary stream. */
-    //UA_UInt16  xmlEncodingId;    /* NodeId of datatype when encoded as XML */
-    UA_UInt16  binaryEncodingId;    /* NodeId of datatype when encoded as binary */
+    UA_UInt16  binaryEncodingId; /* NodeId of datatype when encoded as binary */
+    //UA_UInt16  xmlEncodingId;  /* NodeId of datatype when encoded as XML */
     UA_DataTypeMember *members;
 };
 
@@ -758,8 +719,8 @@ struct UA_DataType {
 /* Allocates and initializes a variable of type dataType
  *
  * @param type The datatype description
- * @return Returns the memory location of the variable or (void*)0 if no
- *         memory is available */
+ * @return Returns the memory location of the variable or NULL if no
+ *         memory could be allocated */
 void UA_EXPORT * UA_new(const UA_DataType *type) UA_FUNC_ATTR_MALLOC;
 
 /* Initializes a variable to default values
@@ -797,6 +758,65 @@ void UA_EXPORT UA_deleteMembers(void *p, const UA_DataType *type);
  * @param type The datatype description of the variable */
 void UA_EXPORT UA_delete(void *p, const UA_DataType *type);
 
+/**
+ * .. _array-handling:
+ *
+ * Array handling
+ * --------------
+ * In OPC UA, arrays can have a length of zero or more with the usual meaning.
+ * In addition, arrays can be undefined. Then, they don't even have a length. In
+ * the binary encoding, this is indicated by an array of length -1.
+ *
+ * In open62541 however, we use ``size_t`` for array lengths. An undefined array
+ * has length 0 and the data pointer is ``NULL``. An array of length 0 also has
+ * length 0 but a data pointer ``UA_EMPTY_ARRAY_SENTINEL``. */
+/* Allocates and initializes an array of variables of a specific type
+ *
+ * @param size The requested array length
+ * @param type The datatype description
+ * @return Returns the memory location of the variable or NULL if no memory
+           could be allocated */
+void UA_EXPORT * UA_Array_new(size_t size, const UA_DataType *type) UA_FUNC_ATTR_MALLOC;
+
+/* Allocates and copies an array
+ *
+ * @param src The memory location of the source array
+ * @param size The size of the array
+ * @param dst The location of the pointer to the new array
+ * @param type The datatype of the array members
+ * @return Returns UA_STATUSCODE_GOOD or UA_STATUSCODE_BADOUTOFMEMORY */
+UA_StatusCode UA_EXPORT
+UA_Array_copy(const void *src, size_t size, void **dst,
+              const UA_DataType *type) UA_FUNC_ATTR_WARN_UNUSED_RESULT;
+
+/* Deletes an array.
+ *
+ * @param p The memory location of the array
+ * @param size The size of the array
+ * @param type The datatype of the array members */
+void UA_EXPORT UA_Array_delete(void *p, size_t size, const UA_DataType *type);
+
+/**
+ * .. _numericrange:
+ *
+ * NumericRange
+ * ^^^^^^^^^^^^
+ *
+ * NumericRanges are used to indicate subsets of a (multidimensional) variant
+ * array. NumericRange has no official type structure in the standard. On the
+ * wire, it only exists as an encoded string, such as "1:2,0:3,5". The colon
+ * separates min/max index and the comma separates dimensions. A single value
+ * indicates a range with a single element (min==max). */
+typedef struct {
+    UA_UInt32 min;
+    UA_UInt32 max;
+} UA_NumericRangeDimension;
+    
+struct UA_NumericRange {
+    size_t dimensionsSize;
+    UA_NumericRangeDimension *dimensions;
+};
+
 /**
  * Random Number Generator
  * -----------------------
@@ -807,6 +827,15 @@ void UA_EXPORT UA_random_seed(UA_UInt64 seed);
 UA_UInt32 UA_EXPORT UA_UInt32_random(void); /* no cryptographic entropy */
 UA_Guid UA_EXPORT UA_Guid_random(void);     /* no cryptographic entropy */
 
+/**
+ * .. _generated-types:
+ *
+ * Generated Data Type Definitions
+ * -------------------------------
+ *
+ * The following data types were auto-generated from a definition in XML format.
+ *
+ * .. include:: types_generated.rst */
 #ifdef __cplusplus
 } // extern "C"
 #endif

+ 7 - 1
plugins/ua_network_tcp.c

@@ -42,13 +42,19 @@
 # ifdef __QNX__
 #  include <sys/socket.h>
 # endif
+#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
+# include <sys/param.h>
+# if defined(BSD)
+#  include<sys/socket.h>
+# endif
+#endif
 # ifndef __CYGWIN__
 #  include <netinet/tcp.h>
 # endif
 #endif
 
 /* unsigned int for windows and workaround to a glibc bug */
-#if defined(_WIN32) || (defined(__GNU_LIBRARY__) && (__GNU_LIBRARY__ <= 6) && (__GLIBC__ <= 2) && (__GLIBC_MINOR__ < 16))
+#if defined(_WIN32) || defined(__OpenBSD__) || (defined(__GNU_LIBRARY__) && (__GNU_LIBRARY__ <= 6) && (__GLIBC__ <= 2) && (__GLIBC_MINOR__ < 16))
 # define UA_fd_set(fd, fds) FD_SET((unsigned int)fd, fds)
 # define UA_fd_isset(fd, fds) FD_ISSET((unsigned int)fd, fds)
 #else

+ 6 - 0
plugins/ua_network_udp.c

@@ -24,6 +24,12 @@
 #ifdef __QNX__
 #include <sys/socket.h>
 #endif
+#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
+# include <sys/param.h>
+# if defined(BSD)
+#  include<sys/socket.h>
+# endif
+#endif
 # define CLOSESOCKET(S) close(S)
 
 #define MAXBACKLOG 100

+ 5 - 1
src/client/ua_client_highlevel.c

@@ -211,6 +211,8 @@ __UA_Client_addNode(UA_Client *client, const UA_NodeClass nodeClass, const UA_No
 /* Call */
 /********/
 
+#ifdef UA_ENABLE_METHODCALLS
+
 UA_StatusCode
 UA_Client_call(UA_Client *client, const UA_NodeId objectId, const UA_NodeId methodId, size_t inputSize,
                const UA_Variant *input, size_t *outputSize, UA_Variant **output) {
@@ -247,6 +249,8 @@ UA_Client_call(UA_Client *client, const UA_NodeId objectId, const UA_NodeId meth
     return retval;
 }
 
+#endif
+
 /********************/
 /* Write Attributes */
 /********************/
@@ -328,7 +332,7 @@ __UA_Client_readAttribute(UA_Client *client, const UA_NodeId *nodeId, UA_Attribu
     UA_DataValue *res = response.results;
     if(res->hasStatus != UA_STATUSCODE_GOOD)
         retval = res->hasStatus;
-    else if(!res->hasValue || !UA_Variant_isScalar(&res->value))
+    else if(!res->hasValue)
         retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
     if(retval != UA_STATUSCODE_GOOD) {
         UA_ReadResponse_deleteMembers(&response);

+ 63 - 36
src/server/ua_nodes.c

@@ -1,4 +1,6 @@
+#include "ua_server_internal.h"
 #include "ua_nodes.h"
+#include "ua_nodestore.h"
 #include "ua_util.h"
 
 void UA_Node_deleteMembersAnyNodeClass(UA_Node *node) {
@@ -7,7 +9,8 @@ void UA_Node_deleteMembersAnyNodeClass(UA_Node *node) {
     UA_QualifiedName_deleteMembers(&node->browseName);
     UA_LocalizedText_deleteMembers(&node->displayName);
     UA_LocalizedText_deleteMembers(&node->description);
-    UA_Array_delete(node->references, node->referencesSize, &UA_TYPES[UA_TYPES_REFERENCENODE]);
+    UA_Array_delete(node->references, node->referencesSize,
+                    &UA_TYPES[UA_TYPES_REFERENCENODE]);
     node->references = NULL;
     node->referencesSize = 0;
 
@@ -22,8 +25,13 @@ void UA_Node_deleteMembersAnyNodeClass(UA_Node *node) {
     case UA_NODECLASS_VARIABLE:
     case UA_NODECLASS_VARIABLETYPE: {
         UA_VariableNode *p = (UA_VariableNode*)node;
-        if(p->valueSource == UA_VALUESOURCE_VARIANT)
-            UA_Variant_deleteMembers(&p->value.variant.value);
+        UA_NodeId_deleteMembers(&p->dataType);
+        UA_Array_delete(p->arrayDimensions, p->arrayDimensionsSize,
+                        &UA_TYPES[UA_TYPES_INT32]);
+        p->arrayDimensions = NULL;
+        p->arrayDimensionsSize = 0;
+        if(p->valueSource == UA_VALUESOURCE_DATA)
+            UA_DataValue_deleteMembers(&p->value.data.value);
         break;
     }
     case UA_NODECLASS_REFERENCETYPE: {
@@ -48,21 +56,43 @@ UA_ObjectNode_copy(const UA_ObjectNode *src, UA_ObjectNode *dst) {
 }
 
 static UA_StatusCode
-UA_VariableNode_copy(const UA_VariableNode *src, UA_VariableNode *dst) {
+UA_CommonVariableNode_copy(const UA_VariableNode *src, UA_VariableNode *dst) {
+    UA_StatusCode retval = UA_Array_copy(src->arrayDimensions,
+                                         src->arrayDimensionsSize,
+                                         (void**)&dst->arrayDimensions,
+                                         &UA_TYPES[UA_TYPES_INT32]);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
+    dst->arrayDimensionsSize = src->arrayDimensionsSize;
+    retval = UA_NodeId_copy(&src->dataType, &dst->dataType);
     dst->valueRank = src->valueRank;
     dst->valueSource = src->valueSource;
-    if(src->valueSource == UA_VALUESOURCE_VARIANT) {
-        UA_StatusCode retval = UA_Variant_copy(&src->value.variant.value, &dst->value.variant.value);
-        if(retval != UA_STATUSCODE_GOOD)
-            return retval;
-        dst->value.variant.callback = src->value.variant.callback;
+    if(src->valueSource == UA_VALUESOURCE_DATA) {
+        retval |= UA_DataValue_copy(&src->value.data.value,
+                                    &dst->value.data.value);
+        dst->value.data.callback = src->value.data.callback;
     } else
         dst->value.dataSource = src->value.dataSource;
+    return retval;
+}
+
+static UA_StatusCode
+UA_VariableNode_copy(const UA_VariableNode *src, UA_VariableNode *dst) {
+    UA_StatusCode retval = UA_CommonVariableNode_copy(src, dst);
     dst->accessLevel = src->accessLevel;
-    dst->userAccessLevel = src->accessLevel;
+    dst->userAccessLevel = src->userAccessLevel;
     dst->minimumSamplingInterval = src->minimumSamplingInterval;
     dst->historizing = src->historizing;
-    return UA_STATUSCODE_GOOD;
+    return retval;
+}
+
+static UA_StatusCode
+UA_VariableTypeNode_copy(const UA_VariableTypeNode *src,
+                         UA_VariableTypeNode *dst) {
+    UA_StatusCode retval = UA_CommonVariableNode_copy((const UA_VariableNode*)src,
+                                                      (UA_VariableNode*)dst);
+    dst->isAbstract = src->isAbstract;
+    return retval;
 }
 
 static UA_StatusCode
@@ -82,23 +112,10 @@ UA_ObjectTypeNode_copy(const UA_ObjectTypeNode *src, UA_ObjectTypeNode *dst) {
 }
 
 static UA_StatusCode
-UA_VariableTypeNode_copy(const UA_VariableTypeNode *src, UA_VariableTypeNode *dst) {
-    dst->valueRank = src->valueRank;
-    dst->valueSource = src->valueSource;
-    if(src->valueSource == UA_VALUESOURCE_VARIANT){
-        UA_StatusCode retval = UA_Variant_copy(&src->value.variant.value, &dst->value.variant.value);
-        if(retval != UA_STATUSCODE_GOOD)
-            return retval;
-        dst->value.variant.callback = src->value.variant.callback;
-    } else
-        dst->value.dataSource = src->value.dataSource;
-    dst->isAbstract = src->isAbstract;
-    return UA_STATUSCODE_GOOD;
-}
-
-static UA_StatusCode
-UA_ReferenceTypeNode_copy(const UA_ReferenceTypeNode *src, UA_ReferenceTypeNode *dst) {
-    UA_StatusCode retval = UA_LocalizedText_copy(&src->inverseName, &dst->inverseName);
+UA_ReferenceTypeNode_copy(const UA_ReferenceTypeNode *src,
+                          UA_ReferenceTypeNode *dst) {
+    UA_StatusCode retval = UA_LocalizedText_copy(&src->inverseName,
+                                                 &dst->inverseName);
     dst->isAbstract = src->isAbstract;
     dst->symmetric = src->symmetric;
     return retval;
@@ -133,7 +150,8 @@ UA_StatusCode UA_Node_copyAnyNodeClass(const UA_Node *src, UA_Node *dst) {
         UA_Node_deleteMembersAnyNodeClass(dst);
         return retval;
     }
-    retval |= UA_Array_copy(src->references, src->referencesSize, (void**)&dst->references,
+    retval |= UA_Array_copy(src->references, src->referencesSize,
+                            (void**)&dst->references,
                             &UA_TYPES[UA_TYPES_REFERENCENODE]);
     if(retval != UA_STATUSCODE_GOOD) {
         UA_Node_deleteMembersAnyNodeClass(dst);
@@ -144,25 +162,32 @@ UA_StatusCode UA_Node_copyAnyNodeClass(const UA_Node *src, UA_Node *dst) {
     /* copy unique content of the nodeclass */
     switch(src->nodeClass) {
     case UA_NODECLASS_OBJECT:
-        retval = UA_ObjectNode_copy((const UA_ObjectNode*)src, (UA_ObjectNode*)dst);
+        retval = UA_ObjectNode_copy((const UA_ObjectNode*)src,
+                                    (UA_ObjectNode*)dst);
         break;
     case UA_NODECLASS_VARIABLE:
-        retval = UA_VariableNode_copy((const UA_VariableNode*)src, (UA_VariableNode*)dst);
+        retval = UA_VariableNode_copy((const UA_VariableNode*)src,
+                                      (UA_VariableNode*)dst);
         break;
     case UA_NODECLASS_METHOD:
-        retval = UA_MethodNode_copy((const UA_MethodNode*)src, (UA_MethodNode*)dst);
+        retval = UA_MethodNode_copy((const UA_MethodNode*)src,
+                                    (UA_MethodNode*)dst);
         break;
     case UA_NODECLASS_OBJECTTYPE:
-        retval = UA_ObjectTypeNode_copy((const UA_ObjectTypeNode*)src, (UA_ObjectTypeNode*)dst);
+        retval = UA_ObjectTypeNode_copy((const UA_ObjectTypeNode*)src,
+                                        (UA_ObjectTypeNode*)dst);
         break;
     case UA_NODECLASS_VARIABLETYPE:
-        retval = UA_VariableTypeNode_copy((const UA_VariableTypeNode*)src, (UA_VariableTypeNode*)dst);
+        retval = UA_VariableTypeNode_copy((const UA_VariableTypeNode*)src,
+                                          (UA_VariableTypeNode*)dst);
         break;
     case UA_NODECLASS_REFERENCETYPE:
-        retval = UA_ReferenceTypeNode_copy((const UA_ReferenceTypeNode*)src, (UA_ReferenceTypeNode*)dst);
+        retval = UA_ReferenceTypeNode_copy((const UA_ReferenceTypeNode*)src,
+                                           (UA_ReferenceTypeNode*)dst);
         break;
     case UA_NODECLASS_DATATYPE:
-        retval = UA_DataTypeNode_copy((const UA_DataTypeNode*)src, (UA_DataTypeNode*)dst);
+        retval = UA_DataTypeNode_copy((const UA_DataTypeNode*)src,
+                                      (UA_DataTypeNode*)dst);
         break;
     case UA_NODECLASS_VIEW:
         retval = UA_ViewNode_copy((const UA_ViewNode*)src, (UA_ViewNode*)dst);
@@ -170,7 +195,9 @@ UA_StatusCode UA_Node_copyAnyNodeClass(const UA_Node *src, UA_Node *dst) {
     default:
         break;
     }
+
     if(retval != UA_STATUSCODE_GOOD)
         UA_Node_deleteMembersAnyNodeClass(dst);
+
     return retval;
 }

+ 244 - 96
src/server/ua_nodes.h

@@ -1,17 +1,51 @@
 #ifndef UA_NODES_H_
 #define UA_NODES_H_
 
-#include "ua_server.h"
-#include "ua_types_generated.h"
-#include "ua_types_encoding_binary.h"
+#ifdef __cplusplus
+extern "C" {
+#endif
 
-/*
- * Most APIs take and return UA_EditNode and UA_ConstNode. By looking up the
- * nodeclass, nodes can be cast to their "true" class, i.e. UA_VariableNode,
- * UA_ObjectNode, and so on.
- */
+#include "ua_server.h"
 
-#define UA_STANDARD_NODEMEMBERS                 \
+/**
+ * .. _information-modelling:
+ *
+ * Information Modelling
+ * =====================
+ *
+ * Information modelling in OPC UA combines concepts from object-orientation and
+ * semantic modelling. At the core, an OPC UA information model is a graph made
+ * up of
+ *
+ * - Nodes: There are eight possible Node types (variable, object, method, ...)
+ * - References: Typed and directed relations between two nodes
+ *
+ * Every node is identified by a unique (within the server) :ref:`nodeid`.
+ * 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.
+ *
+ * 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`.
+ * Still, we reproduce how nodes are represented internally so that users may
+ * have a clear mental model.
+ *
+ * Base Node Attributes
+ * --------------------
+ *
+ * Nodes contain attributes according to their node type. The base node
+ * attributes are common to all node types. In the OPC UA :ref:`services`,
+ * attributes are referred to via the :ref:`nodeid` of the containing node and
+ * an integer :ref:`attribute-id`.
+ *
+ * Internally, open62541 uses ``UA_Node`` in places where the exact node type is
+ * not known or not important. The ``nodeClass`` attribute is used to ensure the
+ * correctness of casting from ``UA_Node`` to a specific node type. */
+#define UA_NODE_BASEATTRIBUTES                  \
     UA_NodeId nodeId;                           \
     UA_NodeClass nodeClass;                     \
     UA_QualifiedName browseName;                \
@@ -23,123 +57,237 @@
     UA_ReferenceNode *references;
 
 typedef struct {
-    UA_STANDARD_NODEMEMBERS
+    UA_NODE_BASEATTRIBUTES
 } UA_Node;
 
-void UA_Node_deleteMembersAnyNodeClass(UA_Node *node);
-UA_StatusCode UA_Node_copyAnyNodeClass(const UA_Node *src, UA_Node *dst);
-
-/**************/
-/* ObjectNode */
-/**************/
-
-typedef struct {
-    UA_STANDARD_NODEMEMBERS
-    UA_Byte eventNotifier;
-    void *instanceHandle;
-} UA_ObjectNode;
-
-/******************/
-/* ObjectTypeNode */
-/******************/
-
-typedef struct {
-    UA_STANDARD_NODEMEMBERS
-    UA_Boolean isAbstract;
-    UA_ObjectLifecycleManagement lifecycleManagement;
-} UA_ObjectTypeNode;
-
+/**
+ * VariableNode
+ * ------------
+ *
+ * Variables store values in a :ref:`datavalue` together with
+ * metadata for introspection. Most notably, the attributes data type, value
+ * rank and array dimensions constrain the possible values the variable can take
+ * on.
+ *
+ * Variables come in two flavours: properties and datavariables. Properties are
+ * related to a parent with a ``hasProperty`` reference and may not have child
+ * nodes themselves. Datavariables may contain properties (``hasProperty``) and
+ * also datavariables (``hasComponents``).
+ *
+ * All variables are instances of some :ref:`variabletypenode` in return
+ * constraining the possible data type, value rank and array dimensions
+ * attributes.
+ *
+ * Data Type
+ * ^^^^^^^^^
+ *
+ * The (scalar) data type of the variable is constrained to be of a specific
+ * type or one of its children in the type hierarchy. The data type is given as
+ * a NodeId pointing to a :ref:`datatypenode` in the type hierarchy. See the
+ * Section :ref:`datatypenode` for more details.
+ *
+ * If the data type attribute points to ``UInt32``, then the value attribute
+ * must be of that exact type since ``UInt32`` does not have children in the
+ * type hierarchy. If the data type attribute points ``Number``, then the type
+ * of the value attribute may still be ``UInt32``, but also ``Float`` or
+ * ``Byte``.
+ *
+ * Consistency between the data type attribute in the variable and its
+ * :ref:`VariableTypeNode` is ensured.
+ *
+ * Value Rank
+ * ^^^^^^^^^^
+ *
+ * This attribute indicates whether the value attribute of the variable is an
+ * array and how many dimensions the array has. It may have the following
+ * values:
+ *
+ * - ``n >= 1``: the value is an array with the specified number of dimensions
+ * - ``n =  0``: the value is an array with one or more dimensions
+ * - ``n = -1``: the value is a scalar
+ * - ``n = -2``: the value can be a scalar or an array with any number of dimensions
+ * - ``n = -3``: the value can be a scalar or a one dimensional array
+ *
+ * Consistency between the value rank attribute in the variable and its
+ * :ref:`variabletypenode` is ensured.
+ *
+ * Array Dimensions
+ * ^^^^^^^^^^^^^^^^
+ *
+ * If the value rank permits the value to be a (multi-dimensional) array, the
+ * exact length in each dimensions can be further constrained with this
+ * attribute.
+ *
+ * - For positive lengths, the variable value is guaranteed to be of the same
+ *   length in this dimension.
+ * - The dimension length zero is a wildcard and the actual value may have any
+ *   length in this dimension.
+ *
+ * Consistency between the array dimensions attribute in the variable and its
+ * :ref:`variabletypenode` is ensured. */
+/* Indicates whether a variable contains data inline or whether it points to an
+ * external data source */
 typedef enum {
-    UA_VALUESOURCE_VARIANT,
+    UA_VALUESOURCE_DATA,
     UA_VALUESOURCE_DATASOURCE
 } UA_ValueSource;
 
-/****************/
-/* VariableNode */
-/****************/
+#define UA_NODE_VARIABLEATTRIBUTES                                      \
+    /* Constraints on possible values */                                \
+    UA_NodeId dataType;                                                 \
+    UA_Int32 valueRank;                                                 \
+    size_t arrayDimensionsSize;                                         \
+    UA_UInt32 *arrayDimensions;                                         \
+                                                                        \
+    /* The current value */                                             \
+    UA_ValueSource valueSource;                                         \
+    union {                                                             \
+        struct {                                                        \
+            UA_DataValue value;                                         \
+            UA_ValueCallback callback;                                  \
+        } data;                                                         \
+        UA_DataSource dataSource;                                       \
+    } value;
 
 typedef struct {
-    UA_STANDARD_NODEMEMBERS
-    UA_Int32 valueRank; /**< n >= 1: the value is an array with the specified number of dimensions.
-                             n = 0: the value is an array with one or more dimensions.
-                             n = -1: the value is a scalar.
-                             n = -2: the value can be a scalar or an array with any number of dimensions.
-                             n = -3:  the value can be a scalar or a one dimensional array. */
-    UA_ValueSource valueSource;
-    union {
-        struct {
-        UA_Variant value;
-        UA_ValueCallback callback;
-        } variant;
-        UA_DataSource dataSource;
-    } value;
-    /* <--- similar to variabletypenodes up to there--->*/
+    UA_NODE_BASEATTRIBUTES
+    UA_NODE_VARIABLEATTRIBUTES
     UA_Byte accessLevel;
     UA_Byte userAccessLevel;
     UA_Double minimumSamplingInterval;
-    UA_Boolean historizing;
+    UA_Boolean historizing; /* currently unsupported */
 } UA_VariableNode;
 
-/********************/
-/* VariableTypeNode */
-/********************/
-
+/**
+ * .. _variabletypenode:
+ *
+ * VariableTypeNode
+ * ----------------
+ *
+ * VariableTypes are used to provide type definitions for variables.
+ * VariableTypes constrain the data type, value rank and array dimensions
+ * attributes of variable instances. Furthermore, instantiating from a specific
+ * variable type may provide semantic information. For example, an instance from
+ * ``MotorTemperatureVariableType`` is more meaningful than a float variable
+ * instantiated from ``BaseDataVariable``. */
 typedef struct {
-    UA_STANDARD_NODEMEMBERS
-    UA_Int32 valueRank;
-    UA_ValueSource valueSource;
-    union {
-        struct {
-            UA_Variant value;
-            UA_ValueCallback callback;
-        } variant;
-        UA_DataSource dataSource;
-    } value;
-    /* <--- similar to variablenodes up to there--->*/
+    UA_NODE_BASEATTRIBUTES
+    UA_NODE_VARIABLEATTRIBUTES
     UA_Boolean isAbstract;
 } UA_VariableTypeNode;
 
-/*********************/
-/* ReferenceTypeNode */
-/*********************/
-
-typedef struct {
-    UA_STANDARD_NODEMEMBERS
-    UA_Boolean isAbstract;
-    UA_Boolean symmetric;
-    UA_LocalizedText inverseName;
-} UA_ReferenceTypeNode;
-
-/**************/
-/* MethodNode */
-/**************/
-
+/**
+ * .. _methodnode:
+ *
+ * MethodNode
+ * ----------
+ *
+ * Methods define callable functions and are invoked using the :ref:`Call
+ * <method-services>` service. MethodNodes may have special properties (variable
+ * childen with a ``hasProperty`` reference) with the :ref:`qualifiedname` ``(0,
+ * "InputArguments")`` and ``(0, "OutputArguments")``. The input and output
+ * arguments are both described via an array of ``UA_Argument``. While the Call
+ * service uses a generic array of :ref:`variant` for input and output, the
+ * actual argument values are checked to match the signature of the MethodNode.
+ *
+ * Note that the same MethodNode may be referenced from several objects (and
+ * object types). For this, the NodeId of the method *and of the object
+ * providing context* is part of a Call request message.
+ */
 typedef struct {
-    UA_STANDARD_NODEMEMBERS
+    UA_NODE_BASEATTRIBUTES
     UA_Boolean executable;
     UA_Boolean userExecutable;
+
+    /* Members specific to open62541 */
     void *methodHandle;
     UA_MethodCallback attachedMethod;
 } UA_MethodNode;
 
-/************/
-/* ViewNode */
-/************/
-
+/**
+ * ObjectNode
+ * ----------
+ *
+ * Objects are used to represent systems, system components, real-world objects
+ * and software objects. Objects are instances of an :ref:`object
+ * type<objecttypenode>` and may contain variables, methods and further
+ * objects. */
 typedef struct {
-    UA_STANDARD_NODEMEMBERS
+    UA_NODE_BASEATTRIBUTES
     UA_Byte eventNotifier;
-    /* <-- the same as objectnode until here --> */
-    UA_Boolean containsNoLoops;
-} UA_ViewNode;
 
-/****************/
-/* DataTypeNode */
-/****************/
+    /* Members specific to open62541 */
+    void *instanceHandle;
+} UA_ObjectNode;
+
+/**
+ * .. _objecttypenode:
+ *
+ * ObjectTypeNode
+ * --------------
+ *
+ * ObjectTypes provide definitions for Objects. Abstract objects cannot be
+ * instantiated. See :ref:`object-lifecycle` for the use of constructor and
+ * destructor callbacks. */
+typedef struct {
+    UA_NODE_BASEATTRIBUTES
+    UA_Boolean isAbstract;
+
+    /* Members specific to open62541 */
+    UA_ObjectLifecycleManagement lifecycleManagement;
+} UA_ObjectTypeNode;
+
+/**
+ * .. _referencetypenode:
+ *
+ * ReferenceTypeNode
+ * -----------------
+ *
+ * The OPC UA standard defines a set of ReferenceTypes as a mandatory part of
+ * OPC UA information models. */
+typedef struct {
+    UA_NODE_BASEATTRIBUTES
+    UA_Boolean isAbstract;
+    UA_Boolean symmetric;
+    UA_LocalizedText inverseName;
+} UA_ReferenceTypeNode;
 
+/**
+ * .. _datatypenode:
+ *
+ * DataTypeNode
+ * ------------
+ *
+ * DataTypes represent simple and structured data types. DataTypes may contain
+ * arrays. But they always describe the structure of a single instance. In
+ * open62541, DataTypeNodes in the information model hierarchy are matched to
+ * ``UA_DataType`` type descriptions for :ref:`generic-types` via their NodeId.
+ *
+ * Abstract DataTypes (e.g. ``Number``) cannot be the type of actual values.
+ * They are used to constrain values to possible child DataTypes (e.g.
+ * ``UInt32``). */
 typedef struct {
-    UA_STANDARD_NODEMEMBERS
+    UA_NODE_BASEATTRIBUTES
     UA_Boolean isAbstract;
 } UA_DataTypeNode;
 
+/**
+ * ViewNode
+ * --------
+ *
+ * Each View defines a subset of the Nodes in the AddressSpace. Views can be
+ * used when browsing an informatin model to focus on a subset of nodes and
+ * references only. ViewNodes can be created and be interacted with. But their
+ * use in the :ref:`Browse<view-services>` service is currently unsupported in
+ * open62541. */
+typedef struct {
+    UA_NODE_BASEATTRIBUTES
+    UA_Byte eventNotifier;
+    UA_Boolean containsNoLoops;
+} UA_ViewNode;
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
 #endif /* UA_NODES_H_ */

+ 95 - 64
src/server/ua_nodestore.c

@@ -1,4 +1,5 @@
 #include "ua_nodestore.h"
+#include "ua_server_internal.h"
 #include "ua_util.h"
 
 #ifndef UA_ENABLE_MULTITHREADING /* conditional compilation */
@@ -10,6 +11,8 @@ typedef struct UA_NodeStoreEntry {
     UA_Node node;
 } UA_NodeStoreEntry;
 
+#define UA_NODESTORE_TOMBSTONE ((UA_NodeStoreEntry*)0x01)
+
 struct UA_NodeStore {
     UA_NodeStoreEntry **entries;
     UA_UInt32 size;
@@ -20,7 +23,7 @@ struct UA_NodeStore {
 #include "ua_nodestore_hash.inc"
 
 /* The size of the hash-map is always a prime number. They are chosen to be
-   close to the next power of 2. So the size ca. doubles with each prime. */
+ * close to the next power of 2. So the size ca. doubles with each prime. */
 static hash_t const primes[] = {
     7,         13,         31,         61,         127,         251,
     509,       1021,       2039,       4093,       8191,        16381,
@@ -29,7 +32,8 @@ static hash_t const primes[] = {
     134217689, 268435399,  536870909,  1073741789, 2147483647,  4294967291
 };
 
-static UA_UInt16 higher_prime_index(hash_t n) {
+static UA_UInt16
+higher_prime_index(hash_t n) {
     UA_UInt16 low  = 0;
     UA_UInt16 high = (UA_UInt16)(sizeof(primes) / sizeof(hash_t));
     while(low != high) {
@@ -42,7 +46,8 @@ static UA_UInt16 higher_prime_index(hash_t n) {
     return low;
 }
 
-static UA_NodeStoreEntry * instantiateEntry(UA_NodeClass nodeClass) {
+static UA_NodeStoreEntry *
+instantiateEntry(UA_NodeClass nodeClass) {
     size_t size = sizeof(UA_NodeStoreEntry) - sizeof(UA_Node);
     switch(nodeClass) {
     case UA_NODECLASS_OBJECT:
@@ -79,63 +84,75 @@ static UA_NodeStoreEntry * instantiateEntry(UA_NodeClass nodeClass) {
     return entry;
 }
 
-static void deleteEntry(UA_NodeStoreEntry *entry) {
+static void
+deleteEntry(UA_NodeStoreEntry *entry) {
     UA_Node_deleteMembersAnyNodeClass(&entry->node);
     UA_free(entry);
 }
 
-/* Returns true if an entry was found under the nodeid. Otherwise, returns
-   false and sets slot to a pointer to the next free slot. */
-static UA_Boolean
-containsNodeId(const UA_NodeStore *ns, const UA_NodeId *nodeid, UA_NodeStoreEntry ***entry) {
+/* returns slot of a valid node or null */
+static UA_NodeStoreEntry **
+findNode(const UA_NodeStore *ns, const UA_NodeId *nodeid) {
     hash_t h = hash(nodeid);
     UA_UInt32 size = ns->size;
     hash_t idx = mod(h, size);
-    UA_NodeStoreEntry *e = ns->entries[idx];
+    hash_t hash2 = mod2(h, size);
 
-    if(!e) {
-        *entry = &ns->entries[idx];
-        return false;
+    while(true) {
+        UA_NodeStoreEntry *e = ns->entries[idx];
+        if(!e)
+            return NULL;
+        if(e > UA_NODESTORE_TOMBSTONE &&
+           UA_NodeId_equal(&e->node.nodeId, nodeid))
+            return &ns->entries[idx];
+        idx += hash2;
+        if(idx >= size)
+            idx -= size;
     }
 
-    if(UA_NodeId_equal(&e->node.nodeId, nodeid)) {
-        *entry = &ns->entries[idx];
-        return true;
-    }
+    /* NOTREACHED */
+    return NULL;
+}
 
+/* returns an empty slot or null if the nodeid exists */
+static UA_NodeStoreEntry **
+findSlot(const UA_NodeStore *ns, const UA_NodeId *nodeid) {
+    hash_t h = hash(nodeid);
+    UA_UInt32 size = ns->size;
+    hash_t idx = mod(h, size);
     hash_t hash2 = mod2(h, size);
-    for(;;) {
+
+    while(true) {
+        UA_NodeStoreEntry *e = ns->entries[idx];
+        if(e > UA_NODESTORE_TOMBSTONE &&
+           UA_NodeId_equal(&e->node.nodeId, nodeid))
+            return NULL;
+        if(ns->entries[idx] <= UA_NODESTORE_TOMBSTONE)
+            return &ns->entries[idx];
         idx += hash2;
         if(idx >= size)
             idx -= size;
-        e = ns->entries[idx];
-        if(!e) {
-            *entry = &ns->entries[idx];
-            return false;
-        }
-        if(UA_NodeId_equal(&e->node.nodeId, nodeid)) {
-            *entry = &ns->entries[idx];
-            return true;
-        }
     }
 
     /* NOTREACHED */
-    return true;
+    return NULL;
 }
 
 /* The occupancy of the table after the call will be about 50% */
-static UA_StatusCode expand(UA_NodeStore *ns) {
+static UA_StatusCode
+expand(UA_NodeStore *ns) {
     UA_UInt32 osize = ns->size;
     UA_UInt32 count = ns->count;
-    /* Resize only when table after removal of unused elements is either too full or too empty  */
+    /* Resize only when table after removal of unused elements is either too
+       full or too empty */
     if(count * 2 < osize && (count * 8 > osize || osize <= UA_NODESTORE_MINSIZE))
         return UA_STATUSCODE_GOOD;
 
     UA_NodeStoreEntry **oentries = ns->entries;
     UA_UInt32 nindex = higher_prime_index(count * 2);
     UA_UInt32 nsize = primes[nindex];
-    UA_NodeStoreEntry **nentries;
-    if(!(nentries = UA_calloc(nsize, sizeof(UA_NodeStoreEntry*))))
+    UA_NodeStoreEntry **nentries = UA_calloc(nsize, sizeof(UA_NodeStoreEntry*));
+    if(!nentries)
         return UA_STATUSCODE_BADOUTOFMEMORY;
 
     ns->entries = nentries;
@@ -144,10 +161,9 @@ static UA_StatusCode expand(UA_NodeStore *ns) {
 
     /* recompute the position of every entry and insert the pointer */
     for(size_t i = 0, j = 0; i < osize && j < count; i++) {
-        if(!oentries[i])
+        if(oentries[i] <= UA_NODESTORE_TOMBSTONE)
             continue;
-        UA_NodeStoreEntry **e;
-        containsNodeId(ns, &oentries[i]->node.nodeId, &e);  /* We know this returns an empty entry here */
+        UA_NodeStoreEntry **e = findSlot(ns, &oentries[i]->node.nodeId);
         *e = oentries[i];
         j++;
     }
@@ -160,43 +176,51 @@ static UA_StatusCode expand(UA_NodeStore *ns) {
 /* Exported functions */
 /**********************/
 
-UA_NodeStore * UA_NodeStore_new(void) {
-    UA_NodeStore *ns;
-    if(!(ns = UA_malloc(sizeof(UA_NodeStore))))
+UA_NodeStore *
+UA_NodeStore_new(void) {
+    UA_NodeStore *ns = UA_malloc(sizeof(UA_NodeStore));
+    if(!ns)
         return NULL;
     ns->sizePrimeIndex = higher_prime_index(UA_NODESTORE_MINSIZE);
     ns->size = primes[ns->sizePrimeIndex];
     ns->count = 0;
-    if(!(ns->entries = UA_calloc(ns->size, sizeof(UA_NodeStoreEntry*)))) {
+    ns->entries = UA_calloc(ns->size, sizeof(UA_NodeStoreEntry*));
+    if(!ns->entries) {
         UA_free(ns);
         return NULL;
     }
     return ns;
 }
 
-void UA_NodeStore_delete(UA_NodeStore *ns) {
+void
+UA_NodeStore_delete(UA_NodeStore *ns) {
     UA_UInt32 size = ns->size;
     UA_NodeStoreEntry **entries = ns->entries;
     for(UA_UInt32 i = 0; i < size; i++) {
-        if(entries[i])
+        if(entries[i] > UA_NODESTORE_TOMBSTONE)
             deleteEntry(entries[i]);
     }
     UA_free(ns->entries);
     UA_free(ns);
 }
 
-UA_Node * UA_NodeStore_newNode(UA_NodeClass class) {
+UA_Node *
+UA_NodeStore_newNode(UA_NodeClass class) {
     UA_NodeStoreEntry *entry = instantiateEntry(class);
     if(!entry)
         return NULL;
-    return (UA_Node*)&entry->node;
+    return &entry->node;
 }
 
-void UA_NodeStore_deleteNode(UA_Node *node) {
-    deleteEntry(container_of(node, UA_NodeStoreEntry, node));
+void
+UA_NodeStore_deleteNode(UA_Node *node) {
+    UA_NodeStoreEntry *entry = container_of(node, UA_NodeStoreEntry, node);
+    UA_assert(&entry->node == node);
+    deleteEntry(entry);
 }
 
-UA_StatusCode UA_NodeStore_insert(UA_NodeStore *ns, UA_Node *node) {
+UA_StatusCode
+UA_NodeStore_insert(UA_NodeStore *ns, UA_Node *node) {
     if(ns->size * 3 <= ns->count * 4) {
         if(expand(ns) != UA_STATUSCODE_GOOD)
             return UA_STATUSCODE_BADINTERNALERROR;
@@ -207,22 +231,24 @@ UA_StatusCode UA_NodeStore_insert(UA_NodeStore *ns, UA_Node *node) {
     tempNodeid.namespaceIndex = 0;
     UA_NodeStoreEntry **entry;
     if(UA_NodeId_isNull(&tempNodeid)) {
+        /* create a random nodeid */
         if(node->nodeId.namespaceIndex == 0)
             node->nodeId.namespaceIndex = 1;
-        /* find a free nodeid */
         UA_UInt32 identifier = ns->count+1; // start value
         UA_UInt32 size = ns->size;
         hash_t increase = mod2(identifier, size);
         while(true) {
             node->nodeId.identifier.numeric = identifier;
-            if(!containsNodeId(ns, &node->nodeId, &entry))
+            entry = findSlot(ns, &node->nodeId);
+            if(entry)
                 break;
             identifier += increase;
             if(identifier >= size)
                 identifier -= size;
         }
     } else {
-        if(containsNodeId(ns, &node->nodeId, &entry)) {
+        entry = findSlot(ns, &node->nodeId);
+        if(!entry) {
             deleteEntry(container_of(node, UA_NodeStoreEntry, node));
             return UA_STATUSCODE_BADNODEIDEXISTS;
         }
@@ -235,29 +261,32 @@ UA_StatusCode UA_NodeStore_insert(UA_NodeStore *ns, UA_Node *node) {
 
 UA_StatusCode
 UA_NodeStore_replace(UA_NodeStore *ns, UA_Node *node) {
-    UA_NodeStoreEntry **entry;
-    if(!containsNodeId(ns, &node->nodeId, &entry))
+    UA_NodeStoreEntry **entry = findNode(ns, &node->nodeId);
+    if(!entry)
         return UA_STATUSCODE_BADNODEIDUNKNOWN;
     UA_NodeStoreEntry *newEntry = container_of(node, UA_NodeStoreEntry, node);
     if(*entry != newEntry->orig) {
+        // the node was replaced since the copy was made
         deleteEntry(newEntry);
-        return UA_STATUSCODE_BADINTERNALERROR; // the node was replaced since the copy was made
+        return UA_STATUSCODE_BADINTERNALERROR;
     }
     deleteEntry(*entry);
     *entry = newEntry;
     return UA_STATUSCODE_GOOD;
 }
 
-const UA_Node * UA_NodeStore_get(UA_NodeStore *ns, const UA_NodeId *nodeid) {
-    UA_NodeStoreEntry **entry;
-    if(!containsNodeId(ns, nodeid, &entry))
+const UA_Node *
+UA_NodeStore_get(UA_NodeStore *ns, const UA_NodeId *nodeid) {
+    UA_NodeStoreEntry **entry = findNode(ns, nodeid);
+    if(!entry)
         return NULL;
     return (const UA_Node*)&(*entry)->node;
 }
 
-UA_Node * UA_NodeStore_getCopy(UA_NodeStore *ns, const UA_NodeId *nodeid) {
-    UA_NodeStoreEntry **slot;
-    if(!containsNodeId(ns, nodeid, &slot))
+UA_Node *
+UA_NodeStore_getCopy(UA_NodeStore *ns, const UA_NodeId *nodeid) {
+    UA_NodeStoreEntry **slot = findNode(ns, nodeid);
+    if(!slot)
         return NULL;
     UA_NodeStoreEntry *entry = *slot;
     UA_NodeStoreEntry *new = instantiateEntry(entry->node.nodeClass);
@@ -267,16 +296,17 @@ UA_Node * UA_NodeStore_getCopy(UA_NodeStore *ns, const UA_NodeId *nodeid) {
         deleteEntry(new);
         return NULL;
     }
-    new->orig = entry;
+    new->orig = entry; // store the pointer to the original
     return &new->node;
 }
 
-UA_StatusCode UA_NodeStore_remove(UA_NodeStore *ns, const UA_NodeId *nodeid) {
-    UA_NodeStoreEntry **slot;
-    if(!containsNodeId(ns, nodeid, &slot))
+UA_StatusCode
+UA_NodeStore_remove(UA_NodeStore *ns, const UA_NodeId *nodeid) {
+    UA_NodeStoreEntry **slot = findNode(ns, nodeid);
+    if(!slot)
         return UA_STATUSCODE_BADNODEIDUNKNOWN;
     deleteEntry(*slot);
-    *slot = NULL;
+    *slot = UA_NODESTORE_TOMBSTONE;
     ns->count--;
     /* Downsize the hashmap if it is very empty */
     if(ns->count * 8 < ns->size && ns->size > 32)
@@ -284,9 +314,10 @@ UA_StatusCode UA_NodeStore_remove(UA_NodeStore *ns, const UA_NodeId *nodeid) {
     return UA_STATUSCODE_GOOD;
 }
 
-void UA_NodeStore_iterate(UA_NodeStore *ns, UA_NodeStore_nodeVisitor visitor) {
+void
+UA_NodeStore_iterate(UA_NodeStore *ns, UA_NodeStore_nodeVisitor visitor) {
     for(UA_UInt32 i = 0; i < ns->size; i++) {
-        if(ns->entries[i])
+        if(ns->entries[i] > UA_NODESTORE_TOMBSTONE)
             visitor((UA_Node*)&ns->entries[i]->node);
     }
 }

+ 5 - 5
src/server/ua_nodestore.h

@@ -10,7 +10,7 @@ extern "C" {
 
 /**
  * Nodestore
- * =========
+ * ---------
  * Stores nodes that can be indexed by their NodeId. Internally, it is based on
  * a hash-map implementation. */
 struct UA_NodeStore;
@@ -18,7 +18,7 @@ typedef struct UA_NodeStore UA_NodeStore;
 
 /**
  * Nodestore Lifecycle
- * ------------------- */
+ * ^^^^^^^^^^^^^^^^^^^ */
 /* Create a new nodestore */
 UA_NodeStore * UA_NodeStore_new(void);
 
@@ -28,7 +28,7 @@ void UA_NodeStore_delete(UA_NodeStore *ns);
 
 /**
  * Node Lifecycle
- * ---------------
+ * ^^^^^^^^^^^^^^
  *
  * The following definitions are used to create empty nodes of the different
  * node types. The memory is managed by the nodestore. Therefore, the node has
@@ -58,7 +58,7 @@ void UA_NodeStore_deleteNode(UA_Node *node);
 
 /**
  * Insert / Get / Replace / Remove
- * ------------------------------- */
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
 /* Inserts a new node into the nodestore. If the nodeid is zero, then a fresh
  * numeric nodeid from namespace 1 is assigned. If insertion fails, the node is
  * deleted. */
@@ -83,7 +83,7 @@ UA_StatusCode UA_NodeStore_remove(UA_NodeStore *ns, const UA_NodeId *nodeid);
 
 /**
  * Iteration
- * ---------
+ * ^^^^^^^^^
  * The following definitions are used to call a callback for every node in the
  * nodestore. */
 typedef void (*UA_NodeStore_nodeVisitor)(const UA_Node *node);

+ 1 - 0
src/server/ua_nodestore_concurrent.c

@@ -1,5 +1,6 @@
 #include "ua_util.h"
 #include "ua_nodestore.h"
+#include "ua_server_internal.h"
 
 #ifdef UA_ENABLE_MULTITHREADING /* conditional compilation */
 

File diff suppressed because it is too large
+ 347 - 312
src/server/ua_server.c


+ 7 - 6
src/server/ua_server_binary.c

@@ -353,16 +353,15 @@ processOPN(UA_Connection *connection, UA_Server *server,
     if(connection->channel && channelId != connection->channel->securityToken.channelId)
         retval |= UA_STATUSCODE_BADREQUESTTYPEINVALID;
 
+    /* Decode the request */
     UA_AsymmetricAlgorithmSecurityHeader asymHeader;
-    retval |= UA_AsymmetricAlgorithmSecurityHeader_decodeBinary(msg, offset, &asymHeader);
-
     UA_SequenceHeader seqHeader;
-    retval |= UA_SequenceHeader_decodeBinary(msg, offset, &seqHeader);
-
     UA_NodeId requestType;
-    retval |= UA_NodeId_decodeBinary(msg, offset, &requestType);
-
     UA_OpenSecureChannelRequest r;
+
+    retval |= UA_AsymmetricAlgorithmSecurityHeader_decodeBinary(msg, offset, &asymHeader);
+    retval |= UA_SequenceHeader_decodeBinary(msg, offset, &seqHeader);
+    retval |= UA_NodeId_decodeBinary(msg, offset, &requestType);
     retval |= UA_OpenSecureChannelRequest_decodeBinary(msg, offset, &r);
 
     /* Could not decode or wrong service type */
@@ -599,9 +598,11 @@ processRequest(UA_SecureChannel *channel, UA_Server *server,
         UA_LOG_INFO_CHANNEL(server->config.logger, channel, "Could not send the message over "
                              "the SecureChannel with error code 0x%08x", retval);
 
+#ifdef UA_ENABLE_SUBSCRIPTIONS
     /* See if we need to return publish requests without a subscription */
     if(session && requestType == &UA_TYPES[UA_TYPES_DELETESUBSCRIPTIONSREQUEST])
         UA_Session_answerPublishRequestsWithoutSubscription(session);
+#endif
 
     /* Clean up */
     UA_deleteMembers(request, requestType);

+ 112 - 7
src/server/ua_server_internal.h

@@ -92,6 +92,13 @@ struct UA_Server {
     UA_ServerConfig config;
 };
 
+/*****************/
+/* Node Handling */
+/*****************/
+
+void UA_Node_deleteMembersAnyNodeClass(UA_Node *node);
+UA_StatusCode UA_Node_copyAnyNodeClass(const UA_Node *src, UA_Node *dst);
+
 typedef UA_StatusCode (*UA_EditNodeCallback)(UA_Server*, UA_Session*, UA_Node*, const void*);
 
 /* Calls callback on the node. In the multithreaded case, the node is copied before and replaced in
@@ -105,17 +112,115 @@ UA_StatusCode UA_Server_delayedCallback(UA_Server *server, UA_ServerCallback cal
 UA_StatusCode UA_Server_delayedFree(UA_Server *server, void *data);
 void UA_Server_deleteAllRepeatedJobs(UA_Server *server);
 
-#ifdef UA_BUILD_UNIT_TESTS
-UA_StatusCode parse_numericrange(const UA_String *str, UA_NumericRange *range);
-#endif
+/* Add an existing node. The node is assumed to be "finished", i.e. no
+ * instantiation from inheritance is necessary. Instantiationcallback and
+ * addedNodeId may be NULL. */
+UA_StatusCode
+Service_AddNodes_existing(UA_Server *server, UA_Session *session, UA_Node *node,
+                          const UA_NodeId *parentNodeId,
+                          const UA_NodeId *referenceTypeId,
+                          const UA_NodeId *typeDefinition,
+                          UA_InstantiationCallback *instantiationCallback,
+                          UA_NodeId *addedNodeId);
+
+/*********************/
+/* Utility Functions */
+/*********************/
+
+UA_StatusCode
+parse_numericrange(const UA_String *str, UA_NumericRange *range);
+
+UA_Boolean
+UA_Node_hasSubTypeOrInstances(const UA_Node *node);
+
+const UA_VariableTypeNode *
+getVariableNodeType(UA_Server *server, const UA_VariableNode *node);
+
+const UA_ObjectTypeNode *
+getObjectNodeType(UA_Server *server, const UA_ObjectNode *node);
 
 UA_StatusCode
-getTypeHierarchy(UA_NodeStore *ns, const UA_NodeId *root, UA_NodeId **reftypes, size_t *reftypes_count);
+getTypeHierarchy(UA_NodeStore *ns, const UA_NodeId *root,
+                 UA_NodeId **reftypes, size_t *reftypes_count);
 
 UA_StatusCode
-isNodeInTree(UA_NodeStore *ns, const UA_NodeId *rootNode, const UA_NodeId *nodeToFind,
-             const UA_NodeId *referenceTypeIds, size_t referenceTypeIdsSize,
-             size_t maxDepth, UA_Boolean *found);
+isNodeInTree(UA_NodeStore *ns, const UA_NodeId *rootNode,
+             const UA_NodeId *nodeToFind, const UA_NodeId *referenceTypeIds,
+             size_t referenceTypeIdsSize, UA_Boolean *found);
+
+const UA_Node *
+getNodeType(UA_Server *server, const UA_Node *node);
+
+/***************************************/
+/* Check Information Model Consistency */
+/***************************************/
+
+UA_StatusCode
+UA_VariableNode_setArrayDimensions(UA_Server *server, UA_VariableNode *node,
+                                   const UA_VariableTypeNode *vt,
+                                   size_t arrayDimensionsSize, UA_UInt32 *arrayDimensions);
+
+UA_StatusCode
+UA_VariableNode_setValueRank(UA_Server *server, UA_VariableNode *node,
+                             const UA_VariableTypeNode *vt,
+                             const UA_Int32 valueRank);
+
+UA_StatusCode
+UA_VariableNode_setDataType(UA_Server *server, UA_VariableNode *node,
+                            const UA_VariableTypeNode *vt,
+                            const UA_NodeId *dataType);
+
+UA_StatusCode
+UA_VariableNode_setValue(UA_Server *server, UA_VariableNode *node,
+                         const UA_DataValue *value, const UA_String *indexRange);
+
+UA_StatusCode
+UA_Variant_matchVariableDefinition(UA_Server *server, const UA_NodeId *variableDataTypeId,
+                                   UA_Int32 variableValueRank, size_t variableArrayDimensionsSize,
+                                   const UA_UInt32 *variableArrayDimensions, const UA_Variant *value,
+                                   const UA_NumericRange *range, UA_Variant *equivalent);
+
+/*******************/
+/* Single-Services */
+/*******************/
+
+/* Some services take an array of "independent" requests. The single-services
+   are stored here to keep ua_services.h clean for documentation purposes. */
+
+UA_StatusCode
+Service_AddReferences_single(UA_Server *server, UA_Session *session,
+                             const UA_AddReferencesItem *item);
+
+UA_StatusCode
+Service_DeleteNodes_single(UA_Server *server, UA_Session *session,
+                           const UA_NodeId *nodeId, UA_Boolean deleteReferences);
+
+UA_StatusCode
+Service_DeleteReferences_single(UA_Server *server, UA_Session *session,
+                                const UA_DeleteReferencesItem *item);
+
+void Service_Browse_single(UA_Server *server, UA_Session *session,
+                           struct ContinuationPointEntry *cp,
+                           const UA_BrowseDescription *descr,
+                           UA_UInt32 maxrefs, UA_BrowseResult *result);
+
+void
+Service_TranslateBrowsePathsToNodeIds_single(UA_Server *server, UA_Session *session,
+                                             const UA_BrowsePath *path,
+                                             UA_BrowsePathResult *result);
+
+void Service_Read_single(UA_Server *server, UA_Session *session,
+                         UA_TimestampsToReturn timestamps,
+                         const UA_ReadValueId *id, UA_DataValue *v);
+
+UA_StatusCode Service_Write_single(UA_Server *server, UA_Session *session,
+                                   const UA_WriteValue *wvalue);
+
+void Service_Call_single(UA_Server *server, UA_Session *session,
+                         const UA_CallMethodRequest *request,
+                         UA_CallMethodResult *result);
+
+
 
 /* Periodic task to clean up the discovery registry */
 void UA_Discovery_cleanupTimedOut(UA_Server *server, UA_DateTime now);

+ 278 - 0
src/server/ua_server_utils.c

@@ -0,0 +1,278 @@
+#include "ua_server_internal.h"
+
+/**********************/
+/* Parse NumericRange */
+/**********************/
+
+static size_t
+readDimension(UA_Byte *buf, size_t buflen, UA_NumericRangeDimension *dim) {
+    size_t progress = UA_readNumber(buf, buflen, &dim->min);
+    if(progress == 0)
+        return 0;
+    if(buflen <= progress + 1 || buf[progress] != ':') {
+        dim->max = dim->min;
+        return progress;
+    }
+
+    progress++;
+    size_t progress2 = UA_readNumber(&buf[progress], buflen - progress, &dim->max);
+    if(progress2 == 0)
+        return 0;
+
+    /* invalid range */
+    if(dim->min >= dim->max)
+        return 0;
+    
+    return progress + progress2;
+}
+
+UA_StatusCode
+parse_numericrange(const UA_String *str, UA_NumericRange *range) {
+    size_t idx = 0;
+    size_t dimensionsMax = 0;
+    UA_NumericRangeDimension *dimensions = NULL;
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+    size_t offset = 0;
+    while(true) {
+        /* alloc dimensions */
+        if(idx >= dimensionsMax) {
+            UA_NumericRangeDimension *newds;
+            size_t newdssize = sizeof(UA_NumericRangeDimension) * (dimensionsMax + 2);
+            newds = UA_realloc(dimensions, newdssize);
+            if(!newds) {
+                retval = UA_STATUSCODE_BADOUTOFMEMORY;
+                break;
+            }
+            dimensions = newds;
+            dimensionsMax = dimensionsMax + 2;
+        }
+
+        /* read the dimension */
+        size_t progress = readDimension(&str->data[offset], str->length - offset,
+                                        &dimensions[idx]);
+        if(progress == 0) {
+            retval = UA_STATUSCODE_BADINDEXRANGEINVALID;
+            break;
+        }
+        offset += progress;
+        idx++;
+
+        /* loop into the next dimension */
+        if(offset >= str->length)
+            break;
+
+        if(str->data[offset] != ',') {
+            retval = UA_STATUSCODE_BADINDEXRANGEINVALID;
+            break;
+        }
+        offset++;
+    }
+
+    if(retval == UA_STATUSCODE_GOOD && idx > 0) {
+        range->dimensions = dimensions;
+        range->dimensionsSize = idx;
+    } else
+        UA_free(dimensions);
+
+    return retval;
+}
+
+/********************************/
+/* Information Model Operations */
+/********************************/
+
+/* Returns the type and all subtypes. We start with an array with a single root
+ * nodeid. When a relevant reference is found, we add the nodeids to the back of
+ * the array and increase the size. Since the hierarchy is not cyclic, we can
+ * safely progress in the array to process the newly found referencetype
+ * nodeids. */
+UA_StatusCode
+getTypeHierarchy(UA_NodeStore *ns, const UA_NodeId *root,
+                 UA_NodeId **typeHierarchy, size_t *typeHierarchySize) {
+    const UA_Node *node = UA_NodeStore_get(ns, root);
+    if(!node)
+        return UA_STATUSCODE_BADNOMATCH;
+    if(node->nodeClass != UA_NODECLASS_REFERENCETYPE)
+        return UA_STATUSCODE_BADREFERENCETYPEIDINVALID;
+
+    size_t results_size = 20; // probably too big, but saves mallocs
+    UA_NodeId *results = UA_malloc(sizeof(UA_NodeId) * results_size);
+    if(!results)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+
+    UA_StatusCode retval = UA_NodeId_copy(root, &results[0]);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_free(results);
+        return retval;
+    }
+
+    size_t idx = 0; // where are we currently in the array?
+    size_t last = 0; // where is the last element in the array?
+    const UA_NodeId hasSubtypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
+    do {
+        node = UA_NodeStore_get(ns, &results[idx]);
+        if(!node || node->nodeClass != UA_NODECLASS_REFERENCETYPE)
+            continue;
+        for(size_t i = 0; i < node->referencesSize; i++) {
+            if(node->references[i].isInverse == true ||
+               !UA_NodeId_equal(&hasSubtypeNodeId, &node->references[i].referenceTypeId))
+                continue;
+
+            if(++last >= results_size) { // is the array big enough?
+                UA_NodeId *new_results = UA_realloc(results, sizeof(UA_NodeId) * results_size * 2);
+                if(!new_results) {
+                    retval = UA_STATUSCODE_BADOUTOFMEMORY;
+                    break;
+                }
+                results = new_results;
+                results_size *= 2;
+            }
+
+            retval = UA_NodeId_copy(&node->references[i].targetId.nodeId, &results[last]);
+            if(retval != UA_STATUSCODE_GOOD) {
+                last--; // for array_delete
+                break;
+            }
+        }
+    } while(++idx <= last && retval == UA_STATUSCODE_GOOD);
+
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_Array_delete(results, last, &UA_TYPES[UA_TYPES_NODEID]);
+        return retval;
+    }
+
+    *typeHierarchy = results;
+    *typeHierarchySize = last + 1;
+    return UA_STATUSCODE_GOOD;
+}
+
+/* Recursively searches "upwards" in the tree following specific reference types */
+UA_StatusCode
+isNodeInTree(UA_NodeStore *ns, const UA_NodeId *leafNode, const UA_NodeId *nodeToFind,
+             const UA_NodeId *referenceTypeIds, size_t referenceTypeIdsSize, UA_Boolean *found) {
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+    if(UA_NodeId_equal(leafNode, nodeToFind)) {
+        *found = true;
+        return UA_STATUSCODE_GOOD;
+    }
+
+    const UA_Node *node = UA_NodeStore_get(ns,leafNode);
+    if(!node)
+        return UA_STATUSCODE_BADINTERNALERROR;
+
+    /* Search upwards in the tree */
+    for(size_t i = 0; i < node->referencesSize; i++) {
+        if(!node->references[i].isInverse)
+            continue;
+
+        /* Recurse only for valid reference types */
+        for(size_t j = 0; j < referenceTypeIdsSize; j++) {
+            if(!UA_NodeId_equal(&node->references[i].referenceTypeId, &referenceTypeIds[j]))
+                continue;
+            retval = isNodeInTree(ns, &node->references[i].targetId.nodeId, nodeToFind,
+                                  referenceTypeIds, referenceTypeIdsSize, found);
+            if(*found || retval != UA_STATUSCODE_GOOD)
+                return retval;
+            break;
+        }
+    }
+
+    /* Dead end */
+    *found = false;
+    return UA_STATUSCODE_GOOD;
+}
+
+const UA_Node *
+getNodeType(UA_Server *server, const UA_Node *node) {
+    /* The reference to the parent is different for variable and variabletype */ 
+    UA_NodeId parentRef;
+    UA_Boolean inverse;
+    if(node->nodeClass == UA_NODECLASS_VARIABLE ||
+       node->nodeClass == UA_NODECLASS_OBJECT) {
+        parentRef = UA_NODEID_NUMERIC(0, UA_NS0ID_HASTYPEDEFINITION);
+        inverse = false;
+    } else if(node->nodeClass == UA_NODECLASS_VARIABLETYPE ||
+              /* node->nodeClass == UA_NODECLASS_OBJECTTYPE || // objecttype may have multiple parents */
+              node->nodeClass == UA_NODECLASS_REFERENCETYPE ||
+              node->nodeClass == UA_NODECLASS_DATATYPE) {
+        parentRef = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
+        inverse = true;
+    } else {
+        return NULL;
+    }
+
+    /* stop at the first matching candidate */
+    UA_NodeId *parentId = NULL;
+    for(size_t i = 0; i < node->referencesSize; i++) {
+        if(node->references[i].isInverse == inverse &&
+           UA_NodeId_equal(&node->references[i].referenceTypeId, &parentRef)) {
+            parentId = &node->references[i].targetId.nodeId;
+            break;
+        }
+    }
+
+    if(!parentId)
+        return NULL;
+    return UA_NodeStore_get(server->nodestore, parentId);
+}
+
+const UA_VariableTypeNode *
+getVariableNodeType(UA_Server *server, const UA_VariableNode *node) {
+    const UA_Node *type = getNodeType(server, (const UA_Node*)node);
+    if(!type || type->nodeClass != UA_NODECLASS_VARIABLETYPE)
+        return NULL;
+    return (const UA_VariableTypeNode*)type;
+}
+
+const UA_ObjectTypeNode *
+getObjectNodeType(UA_Server *server, const UA_ObjectNode *node) {
+    const UA_Node *type = getNodeType(server, (const UA_Node*)node);
+    if(type->nodeClass != UA_NODECLASS_OBJECTTYPE)
+        return NULL;
+    return (const UA_ObjectTypeNode*)type;
+}
+
+UA_Boolean
+UA_Node_hasSubTypeOrInstances(const UA_Node *node) {
+    const UA_NodeId hasSubType = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
+    const UA_NodeId hasTypeDefinition = UA_NODEID_NUMERIC(0, UA_NS0ID_HASTYPEDEFINITION);
+    for(size_t i = 0; i < node->referencesSize; i++) {
+        if(node->references[i].isInverse == false &&
+           UA_NodeId_equal(&node->references[i].referenceTypeId, &hasSubType))
+            return true;
+        if(node->references[i].isInverse == true &&
+           UA_NodeId_equal(&node->references[i].referenceTypeId, &hasTypeDefinition))
+            return true;
+    }
+    return false;
+}
+
+/* For mulithreading: make a copy of the node, edit and replace.
+ * For singletrheading: edit the original */
+UA_StatusCode
+UA_Server_editNode(UA_Server *server, UA_Session *session,
+                   const UA_NodeId *nodeId, UA_EditNodeCallback callback,
+                   const void *data) {
+    UA_StatusCode retval;
+    do {
+#ifndef UA_ENABLE_MULTITHREADING
+        const UA_Node *node = UA_NodeStore_get(server->nodestore, nodeId);
+        if(!node)
+            return UA_STATUSCODE_BADNODEIDUNKNOWN;
+        UA_Node *editNode = (UA_Node*)(uintptr_t)node; // dirty cast
+        retval = callback(server, session, editNode, data);
+        return retval;
+#else
+        UA_Node *copy = UA_NodeStore_getCopy(server->nodestore, nodeId);
+        if(!copy)
+            return UA_STATUSCODE_BADOUTOFMEMORY;
+        retval = callback(server, session, copy, data);
+        if(retval != UA_STATUSCODE_GOOD) {
+            UA_NodeStore_deleteNode(copy);
+            return retval;
+        }
+        retval = UA_NodeStore_replace(server->nodestore, copy);
+#endif
+    } while(retval != UA_STATUSCODE_GOOD);
+    return UA_STATUSCODE_GOOD;
+}

+ 24 - 58
src/server/ua_services.h

@@ -14,11 +14,24 @@ extern "C" {
  *
  * Services
  * ========
- * The services defined in the OPC UA standard. */
+ *
+ * In OPC UA, all communication is based on service calls, each consisting of a
+ * request and a response message. These messages are defined as data structures
+ * with a binary encoding and listed in :ref:`generated-types`. Since all
+ * Services are pre-defined in the standard, they cannot be modified by the
+ * user. But you can use the :ref:`Call <method-services>` service to invoke
+ * user-defined methods on the server.
+ *
+ * The following service signatures are internal and *not visible to users*.
+ * Still, we present them here for an overview of the capabilities of OPC UA.
+ * Please refer to the :ref:`client` and :ref:`server` API where the services
+ * 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. The possible error codes are returned as part of
-   the response. */
-typedef void (*UA_Service)(UA_Server*, UA_Session*, const void*, void*);
+   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);
 
 /**
  * Discovery Service Set
@@ -55,7 +68,7 @@ void Service_OpenSecureChannel(UA_Server *server, UA_Connection *connection,
                                const UA_OpenSecureChannelRequest *request,
                                UA_OpenSecureChannelResponse *response);
 
-/** Used to terminate a SecureChannel. */
+/* Used to terminate a SecureChannel. */
 void Service_CloseSecureChannel(UA_Server *server, UA_SecureChannel *channel);
 
 /**
@@ -103,43 +116,24 @@ void Service_AddNodes(UA_Server *server, UA_Session *session,
                       const UA_AddNodesRequest *request,
                       UA_AddNodesResponse *response);
 
-/* Add an existing node. The node is assumed to be "finished", i.e. no
- * instantiation from inheritance is necessary */
-void
-Service_AddNodes_existing(UA_Server *server, UA_Session *session, UA_Node *node,
-                          const UA_NodeId *parentNodeId,
-                          const UA_NodeId *referenceTypeId,
-                          const UA_NodeId *typeDefinition,
-                          UA_InstantiationCallback *instantiationCallback,
-                          UA_AddNodesResult *result);
-
 /* Used to add one or more References to one or more Nodes. */
 void Service_AddReferences(UA_Server *server, UA_Session *session,
                            const UA_AddReferencesRequest *request,
                            UA_AddReferencesResponse *response);
 
-UA_StatusCode Service_AddReferences_single(UA_Server *server,
-                                           UA_Session *session,
-                                           const UA_AddReferencesItem *item);
-
 /* Used to delete one or more Nodes from the AddressSpace. */
 void Service_DeleteNodes(UA_Server *server, UA_Session *session,
                          const UA_DeleteNodesRequest *request,
                          UA_DeleteNodesResponse *response);
 
-UA_StatusCode Service_DeleteNodes_single(UA_Server *server, UA_Session *session,
-                                         const UA_NodeId *nodeId,
-                                         UA_Boolean deleteReferences);
-
 /* Used to delete one or more References of a Node. */
 void Service_DeleteReferences(UA_Server *server, UA_Session *session,
                               const UA_DeleteReferencesRequest *request,
                               UA_DeleteReferencesResponse *response);
 
-UA_StatusCode Service_DeleteReferences_single(UA_Server *server,
-                      UA_Session *session, const UA_DeleteReferencesItem *item);
-
 /**
+ * .. _view-services:
+ *
  * View Service Set
  * ----------------
  * Clients use the browse Services of the View Service Set to navigate through
@@ -152,11 +146,6 @@ void Service_Browse(UA_Server *server, UA_Session *session,
                     const UA_BrowseRequest *request,
                     UA_BrowseResponse *response);
 
-void Service_Browse_single(UA_Server *server, UA_Session *session,
-                           struct ContinuationPointEntry *cp,
-                           const UA_BrowseDescription *descr,
-                           UA_UInt32 maxrefs, UA_BrowseResult *result);
-
 /* Used to request the next set of Browse or BrowseNext response information
  * that is too large to be sent in a single response. "Too large" in this
  * context means that the Server is not able to return a larger response or that
@@ -171,11 +160,6 @@ void Service_TranslateBrowsePathsToNodeIds(UA_Server *server, UA_Session *sessio
              const UA_TranslateBrowsePathsToNodeIdsRequest *request,
              UA_TranslateBrowsePathsToNodeIdsResponse *response);
 
-void Service_TranslateBrowsePathsToNodeIds_single(UA_Server *server,
-                                                  UA_Session *session,
-                                                  const UA_BrowsePath *path,
-                                                  UA_BrowsePathResult *result);
-
 /* Used by Clients to register the Nodes that they know they will access
  * repeatedly (e.g. Write, Call). It allows Servers to set up anything needed so
  * that the access operations will be more efficient. */
@@ -216,10 +200,6 @@ void Service_Read(UA_Server *server, UA_Session *session,
                   const UA_ReadRequest *request,
                   UA_ReadResponse *response);
 
-void Service_Read_single(UA_Server *server, UA_Session *session,
-                         UA_TimestampsToReturn timestamps,
-                         const UA_ReadValueId *id, UA_DataValue *v);
-
 /* Used to write 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 write the entire set of indexed values as a composite, to
@@ -228,18 +208,17 @@ void Service_Write(UA_Server *server, UA_Session *session,
                    const UA_WriteRequest *request,
                    UA_WriteResponse *response);
 
-UA_StatusCode Service_Write_single(UA_Server *server, UA_Session *session,
-                                   const UA_WriteValue *wvalue);
-
 /* Not Implemented: Service_HistoryRead */
 /* Not Implemented: Service_HistoryUpdate */
 
 /**
+ * .. _method-services:
+ *
  * Method Service Set
  * ------------------
  * The Method Service Set defines the means to invoke methods. A method shall be
- * a component of an Object. */
-#ifdef UA_ENABLE_METHODCALLS
+ * a component of an Object. See the section on :ref:`MethodNodes <methodnode>`
+ * for more information. */
 /* Used to call (invoke) a list of Methods. Each method call is invoked within
  * the context of an existing Session. If the Session is terminated, the results
  * of the method's execution cannot be returned to the Client and are
@@ -248,19 +227,12 @@ void Service_Call(UA_Server *server, UA_Session *session,
                   const UA_CallRequest *request,
                   UA_CallResponse *response);
 
-void Service_Call_single(UA_Server *server, UA_Session *session,
-                         const UA_CallMethodRequest *request,
-                         UA_CallMethodResult *result);
-#endif
-
 /**
  * MonitoredItem Service Set
  * -------------------------
  * Clients define MonitoredItems to subscribe to data and Events. Each
  * MonitoredItem identifies the item to be monitored and the Subscription to use
  * to send Notifications. The item to be monitored may be any Node Attribute. */
-#ifdef UA_ENABLE_SUBSCRIPTIONS
-
 /* Used to create and add one or more MonitoredItems to a Subscription. A
  * MonitoredItem is deleted automatically by the Server when the Subscription is
  * deleted. Deleting a MonitoredItem causes its entire set of triggered item
@@ -288,14 +260,10 @@ void Service_SetMonitoringMode(UA_Server *server, UA_Session *session,
 
 /* Not Implemented: Service_SetTriggering */
 
-#endif
-
 /**
  * Subscription Service Set
  * ------------------------
  * Subscriptions are used to report Notifications to the Client. */
-#ifdef UA_ENABLE_SUBSCRIPTIONS
-
 /* Used to create a Subscription. Subscriptions monitor a set of MonitoredItems
  * for Notifications and return them to the Client in response to Publish
  * requests. */
@@ -338,8 +306,6 @@ void Service_DeleteSubscriptions(UA_Server *server, UA_Session *session,
 
 /* Not Implemented: Service_TransferSubscription */
 
-#endif
-
 #ifdef __cplusplus
 } // extern "C"
 #endif

File diff suppressed because it is too large
+ 589 - 358
src/server/ua_services_attribute.c


+ 47 - 113
src/server/ua_services_call.c

@@ -25,90 +25,15 @@ getArgumentsVariableNode(UA_Server *server, const UA_MethodNode *ofMethod,
 }
 
 static UA_StatusCode
-satisfySignature(UA_Server *server, const UA_Variant *var, const UA_Argument *arg) {
-  if(var == NULL || var->type == NULL) 
-    return UA_STATUSCODE_BADINVALIDARGUMENT;
-  
-  if(!UA_NodeId_equal(&var->type->typeId, &arg->dataType)){
-        if(!UA_NodeId_equal(&var->type->typeId, &UA_TYPES[UA_TYPES_INT32].typeId))
-            return UA_STATUSCODE_BADINVALIDARGUMENT;
-
-        /* enumerations are encoded as int32 -> if provided var is integer, check if arg is an enumeration type */
-        UA_NodeId enumerationNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ENUMERATION);
-        UA_NodeId hasSubTypeNodeId = UA_NODEID_NUMERIC(0,UA_NS0ID_HASSUBTYPE);
-        UA_Boolean found = false;
-        UA_StatusCode retval = isNodeInTree(server->nodestore, &arg->dataType, &enumerationNodeId, &hasSubTypeNodeId, 1, 1, &found);
-        if(retval != UA_STATUSCODE_GOOD)
-            return UA_STATUSCODE_BADINTERNALERROR;
-        if(!found)
-            return UA_STATUSCODE_BADINVALIDARGUMENT;
-    }
-
-    // Note: The namespace compiler will compile nodes with their actual array dimensions
-    // Todo: Check if this is standard conform for scalars
-    if(arg->arrayDimensionsSize > 0 && var->arrayDimensionsSize > 0)
-        if(var->arrayDimensionsSize != arg->arrayDimensionsSize)
-            return UA_STATUSCODE_BADINVALIDARGUMENT;
-
-    UA_Int32 *varDims = var->arrayDimensions;
-    size_t varDimsSize = var->arrayDimensionsSize;
-    UA_Boolean scalar = UA_Variant_isScalar(var);
-
-    /* The dimension 1 is implicit in the array length */
-    UA_Int32 fakeDims;
-    if(!scalar && !varDims) {
-        fakeDims = (UA_Int32)var->arrayLength;
-        varDims = &fakeDims;
-        varDimsSize = 1;
-    }
-
-    /* ValueRank Semantics
-     *  n >= 1: the value is an array with the specified number of dimens*ions.
-     *  n = 0: the value is an array with one or more dimensions.
-     *  n = -1: the value is a scalar.
-     *  n = -2: the value can be a scalar or an array with any number of dimensions.
-     *  n = -3:  the value can be a scalar or a one dimensional array. */
-    switch(arg->valueRank) {
-    case -3:
-        if(varDimsSize > 1)
-            return UA_STATUSCODE_BADINVALIDARGUMENT;
-        break;
-    case -2:
-        break;
-    case -1:
-        if(!scalar)
-            return UA_STATUSCODE_BADINVALIDARGUMENT;
-        break;
-    case 0:
-        if(scalar || !varDims)
-            return UA_STATUSCODE_BADINVALIDARGUMENT;
-        break;
-    default:
-        break;
-    }
-
-    /* Do the variants dimensions match? Check only if defined in the argument. */
-    if(arg->arrayDimensionsSize > 0) {
-        if(arg->arrayDimensionsSize != varDimsSize)
-            return UA_STATUSCODE_BADINVALIDARGUMENT;
-        for(size_t i = 0; i < varDimsSize; i++) {
-            // A value of 0 for an individual dimension indicates that the dimension has a variable	length.
-            if((UA_Int32)arg->arrayDimensions[i]!=0 && (UA_Int32)arg->arrayDimensions[i] != varDims[i])
-                return UA_STATUSCODE_BADINVALIDARGUMENT;
-        }
-    }
-    return UA_STATUSCODE_GOOD;
-}
-
-static UA_StatusCode
-argConformsToDefinition(UA_Server *server, const UA_VariableNode *argRequirements, size_t argsSize, const UA_Variant *args) {
-    if(argRequirements->value.variant.value.type != &UA_TYPES[UA_TYPES_ARGUMENT])
+argumentsConformsToDefinition(UA_Server *server, const UA_VariableNode *argRequirements,
+                              size_t argsSize, const UA_Variant *args) {
+    if(argRequirements->value.data.value.value.type != &UA_TYPES[UA_TYPES_ARGUMENT])
         return UA_STATUSCODE_BADINTERNALERROR;
-    UA_Argument *argReqs = (UA_Argument*)argRequirements->value.variant.value.data;
-    size_t argReqsSize = argRequirements->value.variant.value.arrayLength;
-    if(argRequirements->valueSource != UA_VALUESOURCE_VARIANT)
+    UA_Argument *argReqs = (UA_Argument*)argRequirements->value.data.value.value.data;
+    size_t argReqsSize = argRequirements->value.data.value.value.arrayLength;
+    if(argRequirements->valueSource != UA_VALUESOURCE_DATA)
         return UA_STATUSCODE_BADINTERNALERROR;
-    if(UA_Variant_isScalar(&argRequirements->value.variant.value))
+    if(UA_Variant_isScalar(&argRequirements->value.data.value.value))
         argReqsSize = 1;
     if(argReqsSize > argsSize)
         return UA_STATUSCODE_BADARGUMENTSMISSING;
@@ -117,14 +42,18 @@ argConformsToDefinition(UA_Server *server, const UA_VariableNode *argRequirement
 
     UA_StatusCode retval = UA_STATUSCODE_GOOD;
     for(size_t i = 0; i < argReqsSize; i++)
-        retval |= satisfySignature(server, &args[i], &argReqs[i]);
+        retval |= UA_Variant_matchVariableDefinition(server, &argReqs[i].dataType,
+                                                     argReqs[i].valueRank,
+                                                     argReqs[i].arrayDimensionsSize,
+                                                     argReqs[i].arrayDimensions,
+                                                     &args[i], NULL, NULL);
     return retval;
 }
 
 void
-Service_Call_single(UA_Server *server, UA_Session *session, const UA_CallMethodRequest *request,
+Service_Call_single(UA_Server *server, UA_Session *session,
+                    const UA_CallMethodRequest *request,
                     UA_CallMethodResult *result) {
-
     /* Get/verify the method node */
     const UA_MethodNode *methodCalled =
         (const UA_MethodNode*)UA_NodeStore_get(server->nodestore, &request->methodId);
@@ -153,71 +82,76 @@ Service_Call_single(UA_Server *server, UA_Session *session, const UA_CallMethodR
         return;
     }
 
-    /* Verify method/object relations. Object must have a hasComponent or a subtype of hasComponent reference to the method node. */
-    /* Therefore, check every reference between the parent object and the method node if there is a hasComponent (or subtype) reference */
+    /* Verify method/object relations. Object must have a hasComponent or a
+     * subtype of hasComponent reference to the method node. Therefore, check
+     * every reference between the parent object and the method node if there is
+     * a hasComponent (or subtype) reference */
     UA_Boolean found = false;
     UA_NodeId hasComponentNodeId = UA_NODEID_NUMERIC(0,UA_NS0ID_HASCOMPONENT);
     UA_NodeId hasSubTypeNodeId = UA_NODEID_NUMERIC(0,UA_NS0ID_HASSUBTYPE);
-    for(size_t i=0;i<methodCalled->referencesSize;i++){
-        if (methodCalled->references[i].isInverse && UA_NodeId_equal(&methodCalled->references[i].targetId.nodeId,&withObject->nodeId)){
+    for(size_t i = 0; i < methodCalled->referencesSize; i++) {
+        if(methodCalled->references[i].isInverse &&
+           UA_NodeId_equal(&methodCalled->references[i].targetId.nodeId, &withObject->nodeId)) {
             //TODO adjust maxDepth to needed tree depth (define a variable in config?)
-            isNodeInTree(server->nodestore, &methodCalled->references[i].referenceTypeId, &hasComponentNodeId,
-                 &hasSubTypeNodeId, 1, 1, &found);
-            if(found){
+            isNodeInTree(server->nodestore, &methodCalled->references[i].referenceTypeId,
+                         &hasComponentNodeId, &hasSubTypeNodeId, 1, &found);
+            if(found)
                 break;
-            }
         }
     }
-    if(!found)
+    if(!found) {
         result->statusCode = UA_STATUSCODE_BADMETHODINVALID;
-    if(result->statusCode != UA_STATUSCODE_GOOD)
         return;
+    }
 
     /* Verify Input Argument count, types and sizes */
-    //check inputAgruments only if there are any
-    if(request->inputArgumentsSize > 0){
-        const UA_VariableNode *inputArguments = getArgumentsVariableNode(server, methodCalled, UA_STRING("InputArguments"));
+    const UA_VariableNode *inputArguments =
+        getArgumentsVariableNode(server, methodCalled, UA_STRING("InputArguments"));
 
-        if(!inputArguments) {
+    if(!inputArguments) {
+        if(request->inputArgumentsSize > 0) {
             result->statusCode = UA_STATUSCODE_BADINVALIDARGUMENT;
             return;
         }
-            result->statusCode = argConformsToDefinition(server, inputArguments, request->inputArgumentsSize,
-                                                     request->inputArguments);
+    } else {
+        result->statusCode = argumentsConformsToDefinition(server, inputArguments,
+                                                           request->inputArgumentsSize,
+                                                           request->inputArguments);
         if(result->statusCode != UA_STATUSCODE_GOOD)
             return;
     }
 
     /* Allocate the output arguments */
-    const UA_VariableNode *outputArguments = getArgumentsVariableNode(server, methodCalled, UA_STRING("OutputArguments"));
-    if(!outputArguments) {
-        result->outputArgumentsSize=0;
-    }else{
-        result->outputArguments = UA_Array_new(outputArguments->value.variant.value.arrayLength, &UA_TYPES[UA_TYPES_VARIANT]);
+    result->outputArgumentsSize = 0; /* the default */
+    const UA_VariableNode *outputArguments =
+        getArgumentsVariableNode(server, methodCalled, UA_STRING("OutputArguments"));
+    if(outputArguments) {
+        result->outputArguments = UA_Array_new(outputArguments->value.data.value.value.arrayLength,
+                                               &UA_TYPES[UA_TYPES_VARIANT]);
         if(!result->outputArguments) {
             result->statusCode = UA_STATUSCODE_BADOUTOFMEMORY;
             return;
         }
-        result->outputArgumentsSize = outputArguments->value.variant.value.arrayLength;
+        result->outputArgumentsSize = outputArguments->value.data.value.value.arrayLength;
     }
 
+    /* Call the method */
 #if defined(UA_ENABLE_METHODCALLS) && defined(UA_ENABLE_SUBSCRIPTIONS)
     methodCallSession = session;
 #endif
-
-    /* Call the method */
     result->statusCode = methodCalled->attachedMethod(methodCalled->methodHandle, withObject->nodeId,
                                                       request->inputArgumentsSize, request->inputArguments,
                                                       result->outputArgumentsSize, result->outputArguments);
-
 #if defined(UA_ENABLE_METHODCALLS) && defined(UA_ENABLE_SUBSCRIPTIONS)
     methodCallSession = NULL;
 #endif
-    /* TODO: Verify Output Argument count, types and sizes */
+
+    /* TODO: Verify Output matches the argument definition */
 }
-void Service_Call(UA_Server *server, UA_Session *session, const UA_CallRequest *request,
-                  UA_CallResponse *response) {
 
+void Service_Call(UA_Server *server, UA_Session *session,
+                  const UA_CallRequest *request,
+                  UA_CallResponse *response) {
     UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing CallRequest");
     if(request->methodsToCallSize <= 0) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;

File diff suppressed because it is too large
+ 687 - 677
src/server/ua_services_nodemanagement.c


+ 104 - 59
src/server/ua_services_subscription.c

@@ -15,8 +15,7 @@ setSubscriptionSettings(UA_Server *server, UA_Subscription *subscription,
                         UA_Double requestedPublishingInterval,
                         UA_UInt32 requestedLifetimeCount,
                         UA_UInt32 requestedMaxKeepAliveCount,
-                        UA_UInt32 maxNotificationsPerPublish, UA_Byte priority)
-{
+                        UA_UInt32 maxNotificationsPerPublish, UA_Byte priority) {
     /* deregister the job if required */
     UA_StatusCode retval = Subscription_unregisterPublishJob(server, subscription);
     if(retval != UA_STATUSCODE_GOOD)
@@ -53,12 +52,12 @@ setSubscriptionSettings(UA_Server *server, UA_Subscription *subscription,
 void
 Service_CreateSubscription(UA_Server *server, UA_Session *session,
                            const UA_CreateSubscriptionRequest *request,
-                           UA_CreateSubscriptionResponse *response)
-{
+                           UA_CreateSubscriptionResponse *response) {
     /* Create the subscription */
     UA_Subscription *newSubscription = UA_Subscription_new(session, response->subscriptionId);
     if(!newSubscription) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing CreateSubscriptionRequest failed");
+        UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                             "Processing CreateSubscriptionRequest failed");
         response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
         return;
     }
@@ -78,15 +77,18 @@ Service_CreateSubscription(UA_Server *server, UA_Session *session,
     response->revisedLifetimeCount = newSubscription->lifeTimeCount;
     response->revisedMaxKeepAliveCount = newSubscription->maxKeepAliveCount;
 
-    UA_LOG_DEBUG_SESSION(server->config.logger, session, "CreateSubscriptionRequest: Created Subscription %u "
+    UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                         "CreateSubscriptionRequest: Created Subscription %u "
                          "with a publishing interval of %f ms", response->subscriptionId,
                          newSubscription->publishingInterval);
 }
 
-void Service_ModifySubscription(UA_Server *server, UA_Session *session,
-                                const UA_ModifySubscriptionRequest *request,
-                                UA_ModifySubscriptionResponse *response) {
-    UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing ModifySubscriptionRequest");
+void
+Service_ModifySubscription(UA_Server *server, UA_Session *session,
+                           const UA_ModifySubscriptionRequest *request,
+                           UA_ModifySubscriptionResponse *response) {
+    UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                         "Processing ModifySubscriptionRequest");
     UA_Subscription *sub = UA_Session_getSubscriptionByID(session, request->subscriptionId);
     if(!sub) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
@@ -103,10 +105,12 @@ void Service_ModifySubscription(UA_Server *server, UA_Session *session,
     return;
 }
 
-void Service_SetPublishingMode(UA_Server *server, UA_Session *session,
-                               const UA_SetPublishingModeRequest *request,
-                               UA_SetPublishingModeResponse *response) {
-    UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing SetPublishingModeRequest");
+void
+Service_SetPublishingMode(UA_Server *server, UA_Session *session,
+                          const UA_SetPublishingModeRequest *request,
+                          UA_SetPublishingModeResponse *response) {
+    UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                         "Processing SetPublishingModeRequest");
     if(request->subscriptionIdsSize <= 0) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
         return;
@@ -121,7 +125,8 @@ void Service_SetPublishingMode(UA_Server *server, UA_Session *session,
 
     response->resultsSize = size;
     for(size_t i = 0; i < size; i++) {
-        UA_Subscription *sub = UA_Session_getSubscriptionByID(session, request->subscriptionIds[i]);
+        UA_Subscription *sub =
+            UA_Session_getSubscriptionByID(session, request->subscriptionIds[i]);
         if(!sub) {
             response->results[i] = UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
             continue;
@@ -139,38 +144,68 @@ void Service_SetPublishingMode(UA_Server *server, UA_Session *session,
 
 static void
 setMonitoredItemSettings(UA_Server *server, UA_MonitoredItem *mon,
-                         UA_MonitoringMode monitoringMode, UA_UInt32 clientHandle,
-                         UA_Double samplingInterval, UA_UInt32 queueSize,
-                         UA_Boolean discardOldest) {
+                         UA_MonitoringMode monitoringMode,
+                         const UA_MonitoringParameters *params) {
     MonitoredItem_unregisterSampleJob(server, mon);
     mon->monitoringMode = monitoringMode;
-    mon->clientHandle = clientHandle;
+
+    /* ClientHandle */
+    mon->clientHandle = params->clientHandle;
+
+    /* SamplingInterval */
+    UA_Double samplingInterval = params->samplingInterval;
+    if(mon->attributeID == UA_ATTRIBUTEID_VALUE) {
+        const UA_VariableNode *vn = (const UA_VariableNode*)
+            UA_NodeStore_get(server->nodestore, &mon->monitoredNodeId);
+        if(vn && vn->nodeClass == UA_NODECLASS_VARIABLE &&
+           samplingInterval <  vn->minimumSamplingInterval)
+            samplingInterval = vn->minimumSamplingInterval;
+    } else if(mon->attributeID == UA_ATTRIBUTEID_EVENTNOTIFIER) {
+        /* TODO: events should not need a samplinginterval */
+        samplingInterval = 10000.0f; // 10 seconds to reduce the load
+    }
     mon->samplingInterval = samplingInterval;
     UA_BOUNDEDVALUE_SETWBOUNDS(server->config.samplingIntervalLimits,
         samplingInterval, mon->samplingInterval);
-    /* Check for nan */
-    if(samplingInterval != samplingInterval)
+    if(samplingInterval != samplingInterval) /* Check for nan */
         mon->samplingInterval = server->config.samplingIntervalLimits.min;
+
+    /* Filter */
+    if(params->filter.encoding != UA_EXTENSIONOBJECT_DECODED ||
+       params->filter.content.decoded.type != &UA_TYPES[UA_TYPES_DATACHANGEFILTER]) {
+        /* Default: Trigger only on the value and the statuscode */
+        mon->trigger = UA_DATACHANGETRIGGER_STATUSVALUE;
+    } else {
+        UA_DataChangeFilter *filter = params->filter.content.decoded.data;
+        mon->trigger = filter->trigger;
+    }
+
+    /* QueueSize */
     UA_BOUNDEDVALUE_SETWBOUNDS(server->config.queueSizeLimits,
-                               queueSize, mon->maxQueueSize);
-    mon->discardOldest = discardOldest;
+                               params->queueSize, mon->maxQueueSize);
+
+    /* DiscardOldest */
+    mon->discardOldest = params->discardOldest;
+
+    /* Register sample job if reporting is enabled */
     if(monitoringMode == UA_MONITORINGMODE_REPORTING)
         MonitoredItem_registerSampleJob(server, mon);
 }
 
 static const UA_String binaryEncoding = {sizeof("Default Binary")-1, (UA_Byte*)"Default Binary"};
+
 static void
-Service_CreateMonitoredItems_single(UA_Server *server, UA_Session *session, UA_Subscription *sub,
+Service_CreateMonitoredItems_single(UA_Server *server, UA_Session *session,
+                                    UA_Subscription *sub,
                                     const UA_TimestampsToReturn timestampsToReturn,
                                     const UA_MonitoredItemCreateRequest *request,
                                     UA_MonitoredItemCreateResult *result) {
-    /* Make an example read to get errors in the itemToMonitor */
+    /* Make an example read to get errors in the itemToMonitor. Allow return
+     * codes "good" and "uncertain", as well as a list of statuscodes that might
+     * be repaired inside the data source. */
     UA_DataValue v;
     UA_DataValue_init(&v);
     Service_Read_single(server, session, timestampsToReturn, &request->itemToMonitor, &v);
-
-    /* Allow return codes "good" and "uncertain", as well as a list of
-       statuscodes that might be repaired by the data source. */
     if(v.hasStatus && (v.status >> 30) > 1 &&
        v.status != UA_STATUSCODE_BADRESOURCEUNAVAILABLE &&
        v.status != UA_STATUSCODE_BADCOMMUNICATIONERROR &&
@@ -190,7 +225,8 @@ Service_CreateMonitoredItems_single(UA_Server *server, UA_Session *session, UA_S
     }
 
     /* Check if the encoding is set for a value */
-    if(request->itemToMonitor.attributeId != UA_ATTRIBUTEID_VALUE && request->itemToMonitor.dataEncoding.name.length > 0){
+    if(request->itemToMonitor.attributeId != UA_ATTRIBUTEID_VALUE &&
+       request->itemToMonitor.dataEncoding.name.length > 0) {
         result->statusCode = UA_STATUSCODE_BADDATAENCODINGINVALID;
         return;
     }
@@ -201,7 +237,8 @@ Service_CreateMonitoredItems_single(UA_Server *server, UA_Session *session, UA_S
         result->statusCode = UA_STATUSCODE_BADOUTOFMEMORY;
         return;
     }
-    UA_StatusCode retval = UA_NodeId_copy(&request->itemToMonitor.nodeId, &newMon->monitoredNodeId);
+    UA_StatusCode retval = UA_NodeId_copy(&request->itemToMonitor.nodeId,
+                                          &newMon->monitoredNodeId);
     if(retval != UA_STATUSCODE_GOOD) {
         result->statusCode = retval;
         MonitoredItem_delete(server, newMon);
@@ -212,10 +249,7 @@ Service_CreateMonitoredItems_single(UA_Server *server, UA_Session *session, UA_S
     newMon->itemId = ++(sub->lastMonitoredItemId);
     newMon->timestampsToReturn = timestampsToReturn;
     setMonitoredItemSettings(server, newMon, request->monitoringMode,
-                             request->requestedParameters.clientHandle,
-                             request->requestedParameters.samplingInterval,
-                             request->requestedParameters.queueSize,
-                             request->requestedParameters.discardOldest);
+                             &request->requestedParameters);
     LIST_INSERT_HEAD(&sub->MonitoredItems, newMon, listEntry);
 
     /* Create the first sample */
@@ -233,9 +267,10 @@ void
 Service_CreateMonitoredItems(UA_Server *server, UA_Session *session,
                              const UA_CreateMonitoredItemsRequest *request,
                              UA_CreateMonitoredItemsResponse *response) {
-    UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing CreateMonitoredItemsRequest");
+    UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                         "Processing CreateMonitoredItemsRequest");
 
-    /* check if the timestampstoreturn is valid */
+    /* Check if the timestampstoreturn is valid */
     if(request->timestampsToReturn > UA_TIMESTAMPSTORETURN_NEITHER) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADTIMESTAMPSTORETURNINVALID;
         return;
@@ -278,10 +313,7 @@ Service_ModifyMonitoredItems_single(UA_Server *server, UA_Session *session, UA_S
     }
 
     setMonitoredItemSettings(server, mon, mon->monitoringMode,
-                             request->requestedParameters.clientHandle,
-                             request->requestedParameters.samplingInterval,
-                             request->requestedParameters.queueSize,
-                             request->requestedParameters.discardOldest);
+                             &request->requestedParameters);
     result->revisedSamplingInterval = mon->samplingInterval;
     result->revisedQueueSize = mon->maxQueueSize;
 }
@@ -289,7 +321,8 @@ Service_ModifyMonitoredItems_single(UA_Server *server, UA_Session *session, UA_S
 void Service_ModifyMonitoredItems(UA_Server *server, UA_Session *session,
                                   const UA_ModifyMonitoredItemsRequest *request,
                                   UA_ModifyMonitoredItemsResponse *response) {
-    UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing ModifyMonitoredItemsRequest");
+    UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                         "Processing ModifyMonitoredItemsRequest");
 
     /* check if the timestampstoreturn is valid */
     if(request->timestampsToReturn > UA_TIMESTAMPSTORETURN_NEITHER) {
@@ -297,6 +330,7 @@ void Service_ModifyMonitoredItems(UA_Server *server, UA_Session *session,
         return;
     }
 
+    /* Get the subscription */
     UA_Subscription *sub = UA_Session_getSubscriptionByID(session, request->subscriptionId);
     if(!sub) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
@@ -347,12 +381,19 @@ void Service_SetMonitoringMode(UA_Server *server, UA_Session *session,
     response->resultsSize = request->monitoredItemIdsSize;
 
     for(size_t i = 0; i < response->resultsSize; i++) {
-        UA_MonitoredItem *mon = UA_Subscription_getMonitoredItem(sub, request->monitoredItemIds[i]);
-        if(mon)
-            setMonitoredItemSettings(server, mon, request->monitoringMode, mon->clientHandle,
-                                     mon->samplingInterval, mon->maxQueueSize, mon->discardOldest);
-        else
+        UA_MonitoredItem *mon =
+            UA_Subscription_getMonitoredItem(sub, request->monitoredItemIds[i]);
+        if(!mon) {
             response->results[i] = UA_STATUSCODE_BADMONITOREDITEMIDINVALID;
+            continue;
+        }
+        if(request->monitoringMode == mon->monitoringMode)
+            continue;
+        mon->monitoringMode = request->monitoringMode;
+        if(mon->monitoringMode == UA_MONITORINGMODE_REPORTING)
+            MonitoredItem_registerSampleJob(server, mon);
+        else
+            MonitoredItem_unregisterSampleJob(server, mon);
     }
 }
 
@@ -390,18 +431,21 @@ Service_Publish(UA_Server *server, UA_Session *session,
     UA_PublishResponse *response = &entry->response;
     UA_PublishResponse_init(response);
     response->responseHeader.requestHandle = request->requestHeader.requestHandle;
-    response->results = UA_malloc(request->subscriptionAcknowledgementsSize * sizeof(UA_StatusCode));
-    if(!response->results) {
-        /* Respond immediately with the error code */
-        response->responseHeader.timestamp = UA_DateTime_now();
-        response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
-        UA_SecureChannel_sendBinaryMessage(session->channel, requestId, response,
-                                           &UA_TYPES[UA_TYPES_PUBLISHRESPONSE]);
-        UA_PublishResponse_deleteMembers(response);
-        UA_free(entry);
-        return;
+    if(request->subscriptionAcknowledgementsSize > 0) {
+        response->results = UA_Array_new(request->subscriptionAcknowledgementsSize,
+                                         &UA_TYPES[UA_TYPES_STATUSCODE]);
+        if(!response->results) {
+            /* Respond immediately with the error code */
+            response->responseHeader.timestamp = UA_DateTime_now();
+            response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+            UA_SecureChannel_sendBinaryMessage(session->channel, requestId, response,
+                                               &UA_TYPES[UA_TYPES_PUBLISHRESPONSE]);
+            UA_PublishResponse_deleteMembers(response);
+            UA_free(entry);
+            return;
+        }
+        response->resultsSize = request->subscriptionAcknowledgementsSize;
     }
-    response->resultsSize = request->subscriptionAcknowledgementsSize;
 
     /* Delete Acknowledged Subscription Messages */
     for(size_t i = 0; i < request->subscriptionAcknowledgementsSize; i++) {
@@ -450,9 +494,10 @@ void
 Service_DeleteSubscriptions(UA_Server *server, UA_Session *session,
                             const UA_DeleteSubscriptionsRequest *request,
                             UA_DeleteSubscriptionsResponse *response) {
-    UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing DeleteSubscriptionsRequest");
+    UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                         "Processing DeleteSubscriptionsRequest");
 
-    if(request->subscriptionIdsSize == 0){
+    if(request->subscriptionIdsSize == 0) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
         return;
     }

+ 101 - 46
src/server/ua_subscription.c

@@ -1,5 +1,6 @@
 #include "ua_subscription.h"
 #include "ua_server_internal.h"
+#include "ua_types_encoding_binary.h"
 #include "ua_services.h"
 #include "ua_nodestore.h"
 
@@ -46,33 +47,57 @@ void MonitoredItem_delete(UA_Server *server, UA_MonitoredItem *monitoredItem) {
 void UA_MoniteredItem_SampleCallback(UA_Server *server, UA_MonitoredItem *monitoredItem) {
     UA_Subscription *sub = monitoredItem->subscription;
     if(monitoredItem->monitoredItemType != UA_MONITOREDITEMTYPE_CHANGENOTIFY) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, sub->session, "Subscription %u | MonitoredItem %i | "
-                             "Cannot process a monitoreditem that is not a data change notification",
+        UA_LOG_DEBUG_SESSION(server->config.logger, sub->session,
+                             "Subscription %u | MonitoredItem %i | "
+                             "Cannot process a monitoreditem that is not "
+                             "a data change notification",
                              sub->subscriptionID, monitoredItem->itemId);
         return;
     }
 
     MonitoredItem_queuedValue *newvalue = UA_malloc(sizeof(MonitoredItem_queuedValue));
     if(!newvalue) {
-        UA_LOG_WARNING_SESSION(server->config.logger, sub->session, "Subscription %u | MonitoredItem %i | "
-                               "Skipped a sample due to lack of memory", sub->subscriptionID,
-                               monitoredItem->itemId);
+        UA_LOG_WARNING_SESSION(server->config.logger, sub->session,
+                               "Subscription %u | MonitoredItem %i | "
+                               "Skipped a sample due to lack of memory",
+                               sub->subscriptionID, monitoredItem->itemId);
         return;
     }
     UA_DataValue_init(&newvalue->value);
     newvalue->clientHandle = monitoredItem->clientHandle;
 
+    /* Adjust timestampstoreturn to get source timestamp for triggering */
+    UA_TimestampsToReturn ts = monitoredItem->timestampsToReturn;
+    if(ts == UA_TIMESTAMPSTORETURN_SERVER)
+        ts = UA_TIMESTAMPSTORETURN_BOTH;
+    else if(ts == UA_TIMESTAMPSTORETURN_NEITHER)
+        ts = UA_TIMESTAMPSTORETURN_SOURCE;
+
     /* Read the value */
     UA_ReadValueId rvid;
     UA_ReadValueId_init(&rvid);
     rvid.nodeId = monitoredItem->monitoredNodeId;
     rvid.attributeId = monitoredItem->attributeID;
     rvid.indexRange = monitoredItem->indexRange;
-    Service_Read_single(server, sub->session, monitoredItem->timestampsToReturn,
-                        &rvid, &newvalue->value);
+    Service_Read_single(server, sub->session, ts, &rvid, &newvalue->value);
+
+    /* Apply Filter */
+    UA_Boolean hasValue = newvalue->value.hasValue;
+    UA_Boolean hasServerTimestamp = newvalue->value.hasServerTimestamp;
+    UA_Boolean hasServerPicoseconds = newvalue->value.hasServerPicoseconds;
+    UA_Boolean hasSourceTimestamp = newvalue->value.hasSourceTimestamp;
+    UA_Boolean hasSourcePicoseconds = newvalue->value.hasSourcePicoseconds;
+    newvalue->value.hasServerTimestamp = false;
+    newvalue->value.hasServerPicoseconds = false;
+    if(monitoredItem->trigger == UA_DATACHANGETRIGGER_STATUS)
+        newvalue->value.hasValue = false;
+    if(monitoredItem->trigger < UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP) {
+        newvalue->value.hasSourceTimestamp = false;
+        newvalue->value.hasSourcePicoseconds = false;
+    }
 
-    /* encode to see if the data has changed */
-    size_t binsize = UA_calcSizeBinary(&newvalue->value.value, &UA_TYPES[UA_TYPES_VARIANT]);
+    /* Encode the data for comparison */
+    size_t binsize = UA_calcSizeBinary(&newvalue->value, &UA_TYPES[UA_TYPES_DATAVALUE]);
     UA_ByteString newValueAsByteString;
     UA_StatusCode retval = UA_ByteString_allocBuffer(&newValueAsByteString, binsize);
     if(retval != UA_STATUSCODE_GOOD) {
@@ -81,26 +106,37 @@ void UA_MoniteredItem_SampleCallback(UA_Server *server, UA_MonitoredItem *monito
         return;
     }
     size_t encodingOffset = 0;
-    retval = UA_encodeBinary(&newvalue->value.value, &UA_TYPES[UA_TYPES_VARIANT],
+    retval = UA_encodeBinary(&newvalue->value, &UA_TYPES[UA_TYPES_DATAVALUE],
                              NULL, NULL, &newValueAsByteString, &encodingOffset);
 
-    /* error or the content has not changed */
+    /* Restore the settings changed for the filter */
+    newvalue->value.hasValue = hasValue;
+    newvalue->value.hasServerTimestamp = hasServerTimestamp;
+    newvalue->value.hasServerPicoseconds = hasServerPicoseconds;
+    if(monitoredItem->timestampsToReturn == UA_TIMESTAMPSTORETURN_SERVER ||
+       monitoredItem->timestampsToReturn == UA_TIMESTAMPSTORETURN_NEITHER) {
+        newvalue->value.hasSourceTimestamp = false;
+        newvalue->value.hasSourcePicoseconds = false;
+    } else {
+        newvalue->value.hasSourceTimestamp = hasSourceTimestamp;
+        newvalue->value.hasSourcePicoseconds = hasSourcePicoseconds;
+    }
+
+    /* Error or the value has not changed */
     if(retval != UA_STATUSCODE_GOOD ||
        (monitoredItem->lastSampledValue.data &&
         UA_String_equal(&newValueAsByteString, &monitoredItem->lastSampledValue))) {
-        UA_ByteString_deleteMembers(&newValueAsByteString);
-        UA_DataValue_deleteMembers(&newvalue->value);
-        UA_free(newvalue);
-        UA_LOG_DEBUG_SESSION(server->config.logger, sub->session, "Subscription %u | "
+        UA_LOG_TRACE_SESSION(server->config.logger, sub->session, "Subscription %u | "
                              "MonitoredItem %u | Do not sample an unchanged value",
                              sub->subscriptionID, monitoredItem->itemId);
-        return;
+        goto cleanup;
     }
 
-    UA_LOG_DEBUG_SESSION(server->config.logger, sub->session, "Subscription %u | MonitoredItem %u | "
+    UA_LOG_DEBUG_SESSION(server->config.logger, sub->session,
+                         "Subscription %u | MonitoredItem %u | "
                          "Sampling the value", sub->subscriptionID, monitoredItem->itemId);
 
-    /* Do we have space in the queue? */
+    /* Is enough space in the queue? */
     if(monitoredItem->currentQueueSize >= monitoredItem->maxQueueSize) {
         MonitoredItem_queuedValue *queueItem;
         if(monitoredItem->discardOldest)
@@ -109,13 +145,11 @@ void UA_MoniteredItem_SampleCallback(UA_Server *server, UA_MonitoredItem *monito
             queueItem = TAILQ_LAST(&monitoredItem->queue, QueueOfQueueDataValues);
 
         if(!queueItem) {
-            UA_LOG_WARNING_SESSION(server->config.logger, sub->session, "Subscription %u | MonitoredItem %u | "
-                                   "Cannot remove an element from the full queue. Internal error!",
-                                   sub->subscriptionID, monitoredItem->itemId);
-            UA_ByteString_deleteMembers(&newValueAsByteString);
-            UA_DataValue_deleteMembers(&newvalue->value);
-            UA_free(newvalue);
-            return;
+            UA_LOG_WARNING_SESSION(server->config.logger, sub->session, "Subscription %u | "
+                                   "MonitoredItem %u | Cannot remove an element from the full "
+                                   "queue. Internal error!", sub->subscriptionID,
+                                   monitoredItem->itemId);
+            goto cleanup;
         }
 
         TAILQ_REMOVE(&monitoredItem->queue, queueItem, listEntry);
@@ -125,23 +159,36 @@ void UA_MoniteredItem_SampleCallback(UA_Server *server, UA_MonitoredItem *monito
     }
 
     /* If the read request returned a datavalue pointing into the nodestore, we
-       must make a copy to keep the datavalue across mainloop iterations */
-    if(newvalue->value.hasValue && newvalue->value.value.storageType == UA_VARIANT_DATA_NODELETE) {
+     * must make a deep copy to keep the datavalue across mainloop iterations */
+    if(newvalue->value.hasValue &&
+       newvalue->value.value.storageType == UA_VARIANT_DATA_NODELETE) {
         UA_Variant tempv = newvalue->value.value;
         UA_Variant_copy(&tempv, &newvalue->value.value);
     }
 
-    /* add the sample */
+    /* Replace the comparison bytestring with the current sample */
     UA_ByteString_deleteMembers(&monitoredItem->lastSampledValue);
     monitoredItem->lastSampledValue = newValueAsByteString;
+
+    /* Add the sample to the queue for publication */
     TAILQ_INSERT_TAIL(&monitoredItem->queue, newvalue, listEntry);
     monitoredItem->currentQueueSize++;
+    return;
+
+ cleanup:
+    UA_ByteString_deleteMembers(&newValueAsByteString);
+    UA_DataValue_deleteMembers(&newvalue->value);
+    UA_free(newvalue);
 }
 
-UA_StatusCode MonitoredItem_registerSampleJob(UA_Server *server, UA_MonitoredItem *mon) {
-    UA_Job job = {.type = UA_JOBTYPE_METHODCALL,
-                  .job.methodCall = {.method = (UA_ServerCallback)UA_MoniteredItem_SampleCallback, .data = mon} };
-    UA_StatusCode retval = UA_Server_addRepeatedJob(server, job, (UA_UInt32)mon->samplingInterval,
+UA_StatusCode
+MonitoredItem_registerSampleJob(UA_Server *server, UA_MonitoredItem *mon) {
+    UA_Job job;
+    job.type = UA_JOBTYPE_METHODCALL;
+    job.job.methodCall.method = (UA_ServerCallback)UA_MoniteredItem_SampleCallback;
+    job.job.methodCall.data = mon;
+    UA_StatusCode retval = UA_Server_addRepeatedJob(server, job,
+                                                    (UA_UInt32)mon->samplingInterval,
                                                     &mon->sampleJobGuid);
     if(retval == UA_STATUSCODE_GOOD)
         mon->sampleJobIsRegistered = true;
@@ -301,7 +348,8 @@ void UA_Subscription_publishCallback(UA_Server *server, UA_Subscription *sub) {
         message->notificationDataSize = 1;
         UA_ExtensionObject *data = message->notificationData;
         UA_DataChangeNotification *dcn = UA_DataChangeNotification_new();
-        dcn->monitoredItems = UA_Array_new(notifications, &UA_TYPES[UA_TYPES_MONITOREDITEMNOTIFICATION]);
+        dcn->monitoredItems =
+            UA_Array_new(notifications, &UA_TYPES[UA_TYPES_MONITOREDITEMNOTIFICATION]);
         dcn->monitoredItemsSize = notifications;
         size_t l = 0;
         UA_MonitoredItem *mon;
@@ -319,10 +367,12 @@ void UA_Subscription_publishCallback(UA_Server *server, UA_Subscription *sub) {
                 mon->currentQueueSize--;
                 mon_l++;
             }
-            UA_LOG_DEBUG_SESSION(server->config.logger, sub->session, "Subscription %u | MonitoredItem %u | " \
-                                 "Adding %u notifications to the publish response. " \
+            UA_LOG_DEBUG_SESSION(server->config.logger, sub->session,
+                                 "Subscription %u | MonitoredItem %u | "
+                                 "Adding %u notifications to the publish response. "
                                  "%u notifications remain in the queue",
-                                 sub->subscriptionID, mon->itemId, mon_l, mon->currentQueueSize);
+                                 sub->subscriptionID, mon->itemId, mon_l,
+                                 mon->currentQueueSize);
             l += mon_l;
         }
         data->encoding = UA_EXTENSIONOBJECT_DECODED;
@@ -330,13 +380,16 @@ void UA_Subscription_publishCallback(UA_Server *server, UA_Subscription *sub) {
         data->content.decoded.type = &UA_TYPES[UA_TYPES_DATACHANGENOTIFICATION];
 
         /* Put the notification message into the retransmission queue */
-        UA_NotificationMessageEntry *retransmission = malloc(sizeof(UA_NotificationMessageEntry));
+        UA_NotificationMessageEntry *retransmission =
+            malloc(sizeof(UA_NotificationMessageEntry));
         if(retransmission) {
-            UA_NotificationMessage_copy(&response->notificationMessage, &retransmission->message);
+            UA_NotificationMessage_copy(&response->notificationMessage,
+                                        &retransmission->message);
             LIST_INSERT_HEAD(&sub->retransmissionQueue, retransmission, listEntry);
         } else {
-            UA_LOG_WARNING_SESSION(server->config.logger, sub->session, "Subscription %u | "
-                                   "Could not allocate memory for retransmission", sub->subscriptionID);
+            UA_LOG_WARNING_SESSION(server->config.logger, sub->session,
+                                   "Subscription %u | Could not allocate memory "
+                                   "for retransmission", sub->subscriptionID);
         }
     }
 
@@ -357,8 +410,8 @@ void UA_Subscription_publishCallback(UA_Server *server, UA_Subscription *sub) {
 
     /* Send the response */
     UA_LOG_DEBUG_SESSION(server->config.logger, sub->session,
-                         "Subscription %u | Sending out a publish response with %u notifications",
-                         sub->subscriptionID, (UA_UInt32)notifications);
+                         "Subscription %u | Sending out a publish response with %u "
+                         "notifications", sub->subscriptionID, (UA_UInt32)notifications);
     UA_SecureChannel_sendBinaryMessage(sub->session->channel, requestId, response,
                                        &UA_TYPES[UA_TYPES_PUBLISHRESPONSE]);
 
@@ -379,10 +432,12 @@ UA_StatusCode Subscription_registerPublishJob(UA_Server *server, UA_Subscription
     if(!sub->publishingEnabled)
         return UA_STATUSCODE_GOOD;
 
-    UA_Job job = (UA_Job) {.type = UA_JOBTYPE_METHODCALL,
-                           .job.methodCall = {.method = (UA_ServerCallback)UA_Subscription_publishCallback,
-                                              .data = sub} };
-    UA_StatusCode retval = UA_Server_addRepeatedJob(server, job, (UA_UInt32)sub->publishingInterval,
+    UA_Job job;
+    job.type = UA_JOBTYPE_METHODCALL;
+    job.job.methodCall.method = (UA_ServerCallback)UA_Subscription_publishCallback;
+    job.job.methodCall.data = sub;
+    UA_StatusCode retval = UA_Server_addRepeatedJob(server, job,
+                                                    (UA_UInt32)sub->publishingInterval,
                                                     &sub->publishJobGuid);
     if(retval == UA_STATUSCODE_GOOD)
         sub->publishJobIsRegistered = true;

+ 1 - 0
src/server/ua_subscription.h

@@ -41,6 +41,7 @@ typedef struct UA_MonitoredItem {
     UA_Boolean discardOldest;
     UA_String indexRange;
     // TODO: dataEncoding is hardcoded to UA binary
+    UA_DataChangeTrigger trigger;
 
     /* Sample Job */
     UA_Guid sampleJobGuid;

+ 1 - 1
src/ua_connection.c

@@ -113,7 +113,7 @@ UA_Connection_completeMessages(UA_Connection *connection, UA_ByteString * UA_RES
         retval = UA_ByteString_allocBuffer(&connection->incompleteMessage, incomplete_length);
         if(retval != UA_STATUSCODE_GOOD)
             goto cleanup;
-        memcpy(&connection->incompleteMessage.data, &message->data[complete_until], incomplete_length);
+        memcpy(connection->incompleteMessage.data, &message->data[complete_until], incomplete_length);
         message->length = complete_until;
     }
 

+ 3 - 2
src/ua_session.c

@@ -30,7 +30,7 @@ void UA_Session_init(UA_Session *session) {
     session->timeout = 0;
     UA_DateTime_init(&session->validTill);
     session->channel = NULL;
-    session->availableContinuationPoints = MAXCONTINUATIONPOINTS;
+    session->availableContinuationPoints = UA_MAXCONTINUATIONPOINTS;
     LIST_INIT(&session->continuationPoints);
 #ifdef UA_ENABLE_SUBSCRIPTIONS
     LIST_INIT(&session->serverSubscriptions);
@@ -107,7 +107,7 @@ UA_UInt32 UA_Session_getUniqueSubscriptionID(UA_Session *session) {
 }
 
 void UA_Session_answerPublishRequestsWithoutSubscription(UA_Session *session) {
-    /* Do we have publish requests but no subscriptions? */
+    /* Are there remaining subscriptions? */
     if(LIST_FIRST(&session->serverSubscriptions))
         return;
 
@@ -121,6 +121,7 @@ void UA_Session_answerPublishRequestsWithoutSubscription(UA_Session *session) {
         response->responseHeader.timestamp = UA_DateTime_now();
         UA_SecureChannel_sendBinaryMessage(session->channel, requestId, response,
                                            &UA_TYPES[UA_TYPES_PUBLISHRESPONSE]);
+        UA_PublishResponse_deleteMembers(response);
         UA_free(pre);
     }
 }

+ 3 - 1
src/ua_session.h

@@ -6,7 +6,7 @@
 #include "ua_securechannel.h"
 #include "ua_server.h"
 
-#define MAXCONTINUATIONPOINTS 5
+#define UA_MAXCONTINUATIONPOINTS 5
 
 struct ContinuationPointEntry {
     LIST_ENTRY(ContinuationPointEntry) pointers;
@@ -19,11 +19,13 @@ struct ContinuationPointEntry {
 struct UA_Subscription;
 typedef struct UA_Subscription UA_Subscription;
 
+#ifdef UA_ENABLE_SUBSCRIPTIONS
 typedef struct UA_PublishResponseEntry {
     SIMPLEQ_ENTRY(UA_PublishResponseEntry) listEntry;
     UA_UInt32 requestId;
     UA_PublishResponse response;
 } UA_PublishResponseEntry;
+#endif
 
 struct UA_Session {
     UA_ApplicationDescription clientDescription;

+ 6 - 8
src/ua_types.c

@@ -16,10 +16,12 @@
 /* Static definition of NULL type instances */
 UA_EXPORT const UA_String UA_STRING_NULL = {.length = 0, .data = NULL };
 UA_EXPORT const UA_ByteString UA_BYTESTRING_NULL = {.length = 0, .data = NULL };
-UA_EXPORT const UA_Guid UA_GUID_NULL = {.data1 = 0, .data2 = 0, .data3 = 0, .data4 = {0,0,0,0,0,0,0,0}};
+UA_EXPORT const UA_Guid UA_GUID_NULL = {.data1 = 0, .data2 = 0, .data3 = 0,
+                                        .data4 = {0,0,0,0,0,0,0,0}};
 UA_EXPORT const UA_NodeId UA_NODEID_NULL = {0, UA_NODEIDTYPE_NUMERIC, {0}};
 UA_EXPORT const UA_ExpandedNodeId UA_EXPANDEDNODEID_NULL = {
-    .nodeId = { .namespaceIndex = 0, .identifierType = UA_NODEIDTYPE_NUMERIC, .identifier.numeric = 0 },
+    .nodeId = { .namespaceIndex = 0, .identifierType = UA_NODEIDTYPE_NUMERIC,
+                .identifier.numeric = 0 },
     .namespaceUri = {.length = 0, .data = NULL}, .serverIndex = 0 };
 
 static void UA_deleteMembers_noInit(void *p, const UA_DataType *type);
@@ -419,12 +421,8 @@ computeStrides(const UA_Variant *v, const UA_NumericRange range,
     if(v->arrayDimensionsSize > 0) {
         dims_count = v->arrayDimensionsSize;
         dims = (UA_UInt32*)v->arrayDimensions;
-        for(size_t i = 0; i < dims_count; i++) {
-            /* dimensions can have negative size similar to array lengths */
-            if(v->arrayDimensions[i] < 0)
-                return UA_STATUSCODE_BADINDEXRANGEINVALID;
+        for(size_t i = 0; i < dims_count; i++)
             elements *= dims[i];
-        }
         if(elements != v->arrayLength)
             return UA_STATUSCODE_BADINTERNALERROR;
     }
@@ -611,7 +609,7 @@ UA_Variant_copyRange(const UA_Variant *orig_src, UA_Variant *dst,
         dst->arrayDimensionsSize = thisrange.dimensionsSize;
         for(size_t k = 0; k < thisrange.dimensionsSize; k++)
             dst->arrayDimensions[k] =
-                (UA_Int32)(thisrange.dimensions[k].max - thisrange.dimensions[k].min + 1);
+                thisrange.dimensions[k].max - thisrange.dimensions[k].min + 1;
     }
     return UA_STATUSCODE_GOOD;
 }

+ 2 - 2
src/ua_types_encoding_binary.h

@@ -5,12 +5,12 @@
 
 typedef UA_StatusCode (*UA_exchangeEncodeBuffer)(void *handle, UA_ByteString *buf, size_t offset);
 
-UA_StatusCode
+UA_StatusCode UA_EXPORT
 UA_encodeBinary(const void *src, const UA_DataType *type,
                 UA_exchangeEncodeBuffer exchangeBufferCallback, void *exchangeBufferCallbackHandle,
                 UA_ByteString *dst, size_t *offset) UA_FUNC_ATTR_WARN_UNUSED_RESULT;
 
-UA_StatusCode
+UA_StatusCode UA_EXPORT
 UA_decodeBinary(const UA_ByteString *src, size_t *offset, void *dst,
                 const UA_DataType *type) UA_FUNC_ATTR_WARN_UNUSED_RESULT;
 

+ 13 - 8
src/ua_util.h

@@ -47,15 +47,20 @@
 /* Thread Local Storage */
 /************************/
 
-#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
-# define UA_THREAD_LOCAL _Thread_local /* C11 */
-#elif defined(__GNUC__)
-# define UA_THREAD_LOCAL __thread /* GNU extension */
-#elif defined(_MSC_VER)
-# define UA_THREAD_LOCAL __declspec(thread) /* MSVC extension */
-#else
+#ifdef UA_ENABLE_MULTITHREADING
+# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+#  define UA_THREAD_LOCAL _Thread_local /* C11 */
+# elif defined(__GNUC__)
+#  define UA_THREAD_LOCAL __thread /* GNU extension */
+# elif defined(_MSC_VER)
+#  define UA_THREAD_LOCAL __declspec(thread) /* MSVC extension */
+# else
+#  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
-# warning The compiler does not allow thread-local variables. The library can be built, but will not be thread safe.
 #endif
 
 /*************************/

+ 96 - 78
tests/check_services_attributes.c

@@ -17,14 +17,18 @@
 #endif
 
 static UA_StatusCode
-readCPUTemperature_broken(void *handle, const UA_NodeId nodeid, UA_Boolean sourceTimeStamp,
-                          const UA_NumericRange *range, UA_DataValue *dataValue) {
-  dataValue->hasValue = true;
-  return UA_STATUSCODE_GOOD;
+readCPUTemperature(void *handle, const UA_NodeId nodeid, UA_Boolean sourceTimeStamp,
+                   const UA_NumericRange *range, UA_DataValue *dataValue) {
+    UA_Float temp = 20.5f;
+    UA_Variant_setScalarCopy(&dataValue->value, &temp, &UA_TYPES[UA_TYPES_FLOAT]);
+    dataValue->hasValue = true;
+    return UA_STATUSCODE_GOOD;
 }
 
-static UA_Server* makeTestSequence(void) {
-    UA_Server *server = UA_Server_new(UA_ServerConfig_standard);
+static UA_Server *
+makeTestSequence(void) {
+    UA_Server * server = UA_Server_new(UA_ServerConfig_standard);
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
 
     /* VariableNode */
     UA_VariableAttributes vattr;
@@ -38,37 +42,30 @@ static UA_Server* makeTestSequence(void) {
     UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
     UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
     UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
-    UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
-                              parentReferenceNodeId, myIntegerName,
-                              UA_NODEID_NULL, vattr, NULL, NULL);
+    retval = UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
+                                       parentReferenceNodeId, myIntegerName,
+                                       UA_NODEID_NULL, vattr, NULL, NULL);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
 
     /* DataSource VariableNode */
     UA_VariableAttributes_init(&vattr);
-    UA_DataSource temperatureDataSource = (UA_DataSource) {
-                                           .handle = NULL, .read = NULL, .write = NULL};
+    UA_DataSource temperatureDataSource =
+        (UA_DataSource) {.handle = NULL, .read = readCPUTemperature, .write = NULL};
     vattr.description = UA_LOCALIZEDTEXT("en_US","temperature");
     vattr.displayName = UA_LOCALIZEDTEXT("en_US","temperature");
-    UA_Server_addDataSourceVariableNode(server, UA_NODEID_STRING(1, "cpu.temperature"),
-                                        UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-                                        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
-                                        UA_QUALIFIEDNAME(1, "cpu temperature"),
-                                        UA_NODEID_NULL, vattr, temperatureDataSource, NULL);
-
-    /* DataSource Variable returning no value */
-    UA_DataSource temperatureDataSource1 = (UA_DataSource) {
-                                            .handle = NULL, .read = readCPUTemperature_broken, .write = NULL};
-    vattr.description = UA_LOCALIZEDTEXT("en_US","temperature1");
-    vattr.displayName = UA_LOCALIZEDTEXT("en_US","temperature1");
-    UA_Server_addDataSourceVariableNode(server, UA_NODEID_STRING(1, "cpu.temperature1"),
-                                        UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-                                        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
-                                        UA_QUALIFIEDNAME(1, "cpu temperature bogus"),
-                                        UA_NODEID_NULL, vattr, temperatureDataSource1, NULL);
+    retval = UA_Server_addDataSourceVariableNode(server, UA_NODEID_STRING(1, "cpu.temperature"),
+                                                 UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                                                 UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                                                 UA_QUALIFIEDNAME(1, "cpu temperature"),
+                                                 UA_NODEID_NULL, vattr, temperatureDataSource, NULL);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
+
     /* VariableNode with array */
     UA_VariableAttributes_init(&vattr);
     UA_Int32 myIntegerArray[9] = {1,2,3,4,5,6,7,8,9};
     UA_Variant_setArray(&vattr.value, &myIntegerArray, 9, &UA_TYPES[UA_TYPES_INT32]);
-    UA_Int32 myIntegerDimensions[2] = {3,3};
+    vattr.valueRank = -2;
+    UA_UInt32 myIntegerDimensions[2] = {3,3};
     vattr.value.arrayDimensions = myIntegerDimensions;
     vattr.value.arrayDimensionsSize = 2;
     vattr.displayName = UA_LOCALIZEDTEXT("locale","myarray");
@@ -76,31 +73,34 @@ static UA_Server* makeTestSequence(void) {
     myIntegerNodeId = UA_NODEID_STRING(1, "myarray");
     parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
     parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
-    UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
-                              parentReferenceNodeId, myIntegerName,
-                              UA_NODEID_NULL, vattr, NULL, NULL);
+    retval = UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
+                                       parentReferenceNodeId, myIntegerName,
+                                       UA_NODEID_NULL, vattr, NULL, NULL);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
 
     /* ObjectNode */
     UA_ObjectAttributes obj_attr;
     UA_ObjectAttributes_init(&obj_attr);
     obj_attr.description = UA_LOCALIZEDTEXT("en_US","Demo");
     obj_attr.displayName = UA_LOCALIZEDTEXT("en_US","Demo");
-    UA_Server_addObjectNode(server, UA_NODEID_NUMERIC(1, 50),
-                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-                            UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
-                            UA_QUALIFIEDNAME(1, "Demo"),
-                            UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE),
-                            obj_attr, NULL, NULL);
+    retval = UA_Server_addObjectNode(server, UA_NODEID_NUMERIC(1, 50),
+                                     UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                                     UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                                     UA_QUALIFIEDNAME(1, "Demo"),
+                                     UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE),
+                                     obj_attr, NULL, NULL);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
 
     /* ViewNode */
     UA_ViewAttributes view_attr;
     UA_ViewAttributes_init(&view_attr);
     view_attr.description = UA_LOCALIZEDTEXT("en_US", "Viewtest");
     view_attr.displayName = UA_LOCALIZEDTEXT("en_US", "Viewtest");
-    UA_Server_addViewNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_VIEWNODE),
-                          UA_NODEID_NUMERIC(0, UA_NS0ID_VIEWSFOLDER),
-                          UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
-                          UA_QUALIFIEDNAME(0, "Viewtest"), view_attr, NULL, NULL);
+    retval = UA_Server_addViewNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_VIEWNODE),
+                                   UA_NODEID_NUMERIC(0, UA_NS0ID_VIEWSFOLDER),
+                                   UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                                   UA_QUALIFIEDNAME(0, "Viewtest"), view_attr, NULL, NULL);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
 
 #ifdef UA_ENABLE_METHODCALLS
     /* MethodNode */
@@ -108,13 +108,12 @@ static UA_Server* makeTestSequence(void) {
     UA_MethodAttributes_init(&ma);
     ma.description = UA_LOCALIZEDTEXT("en_US", "Methodtest");
     ma.displayName = UA_LOCALIZEDTEXT("en_US", "Methodtest");
-    UA_QualifiedName qualifiedName = UA_QUALIFIEDNAME_ALLOC(0, "Methodtest");
-    UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_METHODNODE),
-                            UA_NODEID_NUMERIC(0, 3),
-                            UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
-                            qualifiedName, ma,
-                            NULL, NULL, 0, NULL, 0, NULL, NULL);
-    UA_QualifiedName_deleteMembers(&qualifiedName);
+    retval = UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_METHODNODE),
+                                     UA_NODEID_NUMERIC(0, 3),
+                                     UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                                     UA_QUALIFIEDNAME(0, "Methodtest"), ma,
+                                     NULL, NULL, 0, NULL, 0, NULL, NULL);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
 #endif
 
     return server;
@@ -124,7 +123,8 @@ static UA_VariableNode* makeCompareSequence(void) {
     UA_VariableNode *node = UA_NodeStore_newVariableNode();
 
     UA_Int32 myInteger = 42;
-    UA_Variant_setScalarCopy(&node->value.variant.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
+    UA_Variant_setScalarCopy(&node->value.data.value.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
+    node->value.data.value.hasValue = true;
 
     const UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer");
     UA_QualifiedName_copy(&myIntegerName,&node->browseName);
@@ -437,11 +437,13 @@ START_TEST(ReadSingleAttributeDataTypeWithoutTimestamp) {
     rReq.nodesToRead[0].nodeId = UA_NODEID_STRING_ALLOC(1, "the.answer");
     rReq.nodesToRead[0].attributeId = UA_ATTRIBUTEID_DATATYPE;
     Service_Read_single(server, &adminSession, UA_TIMESTAMPSTORETURN_NEITHER, &rReq.nodesToRead[0], &resp);
-    UA_NodeId* respval = (UA_NodeId*) resp.value.data;
     ck_assert_int_eq(0, resp.value.arrayLength);
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, resp.status);
+    ck_assert_int_eq(true, resp.hasValue);
     ck_assert_ptr_eq(&UA_TYPES[UA_TYPES_NODEID], resp.value.type);
+    UA_NodeId* respval = (UA_NodeId*)resp.value.data;
     ck_assert_int_eq(respval->namespaceIndex,0);
-    ck_assert_int_eq(respval->identifier.numeric,UA_NS0ID_INT32);
+    ck_assert_int_eq(respval->identifier.numeric, UA_NS0ID_BASEDATATYPE);
     UA_ReadRequest_deleteMembers(&rReq);
     UA_DataValue_deleteMembers(&resp);
     UA_Server_delete(server);
@@ -609,23 +611,6 @@ START_TEST(ReadSingleAttributeUserExecutableWithoutTimestamp) {
 #endif
 } END_TEST
 
-START_TEST(ReadSingleDataSourceAttributeDataTypeWithoutTimestampFromBrokenSource) {
-    UA_Server *server = makeTestSequence();
-    UA_DataValue resp;
-    UA_DataValue_init(&resp);
-    UA_ReadRequest rReq;
-    UA_ReadRequest_init(&rReq);
-    rReq.nodesToRead = UA_ReadValueId_new();
-    rReq.nodesToReadSize = 1;
-    rReq.nodesToRead[0].nodeId = UA_NODEID_STRING_ALLOC(1, "cpu.temperature1");
-    rReq.nodesToRead[0].attributeId = UA_ATTRIBUTEID_DATATYPE;
-    Service_Read_single(server, &adminSession, UA_TIMESTAMPSTORETURN_NEITHER, &rReq.nodesToRead[0], &resp);
-    ck_assert_int_eq(UA_STATUSCODE_GOOD, resp.status);
-    UA_Server_delete(server);
-    UA_ReadRequest_deleteMembers(&rReq);
-    UA_DataValue_deleteMembers(&resp);
-} END_TEST
-
 START_TEST(ReadSingleDataSourceAttributeValueWithoutTimestamp) {
     UA_Server *server = makeTestSequence();
     UA_DataValue resp;
@@ -637,7 +622,7 @@ START_TEST(ReadSingleDataSourceAttributeValueWithoutTimestamp) {
     rReq.nodesToRead[0].nodeId = UA_NODEID_STRING_ALLOC(1, "cpu.temperature");
     rReq.nodesToRead[0].attributeId = UA_ATTRIBUTEID_VALUE;
     Service_Read_single(server, &adminSession, UA_TIMESTAMPSTORETURN_NEITHER, &rReq.nodesToRead[0], &resp);
-    ck_assert_int_eq(UA_STATUSCODE_BADINTERNALERROR, resp.status);
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, resp.status);
     UA_Server_delete(server);
     UA_ReadRequest_deleteMembers(&rReq);
     UA_DataValue_deleteMembers(&resp);
@@ -654,7 +639,8 @@ START_TEST(ReadSingleDataSourceAttributeDataTypeWithoutTimestamp) {
     rReq.nodesToRead[0].nodeId = UA_NODEID_STRING_ALLOC(1, "cpu.temperature");
     rReq.nodesToRead[0].attributeId = UA_ATTRIBUTEID_DATATYPE;
     Service_Read_single(server, &adminSession, UA_TIMESTAMPSTORETURN_NEITHER, &rReq.nodesToRead[0], &resp);
-    ck_assert_int_eq(UA_STATUSCODE_BADINTERNALERROR, resp.status);
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, resp.status);
+    ck_assert_int_eq(resp.hasServerTimestamp, false);
     UA_Server_delete(server);
     UA_ReadRequest_deleteMembers(&rReq);
     UA_DataValue_deleteMembers(&resp);
@@ -671,7 +657,7 @@ START_TEST (ReadSingleDataSourceAttributeArrayDimensionsWithoutTimestamp) {
     rReq.nodesToRead[0].nodeId = UA_NODEID_STRING_ALLOC(1, "cpu.temperature");
     rReq.nodesToRead[0].attributeId = UA_ATTRIBUTEID_ARRAYDIMENSIONS;
     Service_Read_single(server, &adminSession, UA_TIMESTAMPSTORETURN_NEITHER, &rReq.nodesToRead[0], &resp);
-    ck_assert_int_eq(UA_STATUSCODE_BADINTERNALERROR, resp.status);
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, resp.status);
     UA_Server_delete(server);
     UA_ReadRequest_deleteMembers(&rReq);
     UA_DataValue_deleteMembers(&resp);
@@ -862,6 +848,7 @@ START_TEST(WriteSingleAttributeValue) {
     wValue.nodeId = UA_NODEID_STRING(1, "the.answer");
     wValue.attributeId = UA_ATTRIBUTEID_VALUE;
     UA_StatusCode retval = Service_Write_single(server, &adminSession, &wValue);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
 
     UA_DataValue resp;
     UA_DataValue_init(&resp);
@@ -871,12 +858,42 @@ START_TEST(WriteSingleAttributeValue) {
     id.attributeId = UA_ATTRIBUTEID_VALUE;
     Service_Read_single(server, &adminSession, UA_TIMESTAMPSTORETURN_NEITHER, &id, &resp);
     ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
-    ck_assert(wValue.value.hasValue);
+    ck_assert(resp.hasValue);
     ck_assert_int_eq(20, *(UA_Int32*)resp.value.data);
     UA_DataValue_deleteMembers(&resp);
     UA_Server_delete(server);
 } END_TEST
 
+START_TEST(WriteSingleAttributeValueRangeFromScalar) {
+    UA_Server *server = makeTestSequence();
+    UA_WriteValue wValue;
+    UA_WriteValue_init(&wValue);
+    UA_Int32 myInteger = 20;
+    UA_Variant_setScalar(&wValue.value.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
+    wValue.value.hasValue = true;
+    wValue.nodeId = UA_NODEID_STRING(1, "myarray");
+    wValue.indexRange = UA_STRING("0,0");
+    wValue.attributeId = UA_ATTRIBUTEID_VALUE;
+    UA_StatusCode retval = Service_Write_single(server, &adminSession, &wValue);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
+    UA_Server_delete(server);
+} END_TEST
+
+START_TEST(WriteSingleAttributeValueRangeFromArray) {
+    UA_Server *server = makeTestSequence();
+    UA_WriteValue wValue;
+    UA_WriteValue_init(&wValue);
+    UA_Int32 myInteger = 20;
+    UA_Variant_setArray(&wValue.value.value, &myInteger, 1, &UA_TYPES[UA_TYPES_INT32]);
+    wValue.value.hasValue = true;
+    wValue.nodeId = UA_NODEID_STRING(1, "myarray");
+    wValue.indexRange = UA_STRING("0,0");
+    wValue.attributeId = UA_ATTRIBUTEID_VALUE;
+    UA_StatusCode retval = Service_Write_single(server, &adminSession, &wValue);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
+    UA_Server_delete(server);
+} END_TEST
+
 START_TEST(WriteSingleAttributeDataType) {
     UA_Server *server = makeTestSequence();
     UA_WriteValue wValue;
@@ -888,7 +905,7 @@ START_TEST(WriteSingleAttributeDataType) {
     wValue.value.hasValue = true;
     UA_Variant_setScalar(&wValue.value.value, &typeId, &UA_TYPES[UA_TYPES_NODEID]);
     UA_StatusCode retval = Service_Write_single(server, &adminSession, &wValue);
-    ck_assert_int_eq(retval, UA_STATUSCODE_BADWRITENOTSUPPORTED);
+    ck_assert_int_eq(retval, UA_STATUSCODE_BADTYPEMISMATCH);
     UA_Server_delete(server);
 } END_TEST
 
@@ -903,7 +920,7 @@ START_TEST(WriteSingleAttributeValueRank) {
     wValue.value.hasValue = true;
     UA_StatusCode retval = Service_Write_single(server, &adminSession, &wValue);
     // Returns attributeInvalid, since variant/value may be writable
-    ck_assert_int_eq(retval, UA_STATUSCODE_BADATTRIBUTEIDINVALID);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
     UA_Server_delete(server);
 } END_TEST
 
@@ -911,14 +928,14 @@ START_TEST(WriteSingleAttributeArrayDimensions) {
     UA_Server *server = makeTestSequence();
     UA_WriteValue wValue;
     UA_WriteValue_init(&wValue);
-    UA_Int32 testValue[] = {-1,-1,-1};
-    UA_Variant_setArray(&wValue.value.value, &testValue, 3, &UA_TYPES[UA_TYPES_INT32]);
+    UA_UInt32 testValue[] = {1,1,1};
+    UA_Variant_setArray(&wValue.value.value, &testValue, 3, &UA_TYPES[UA_TYPES_UINT32]);
     wValue.nodeId = UA_NODEID_STRING(1, "the.answer");
     wValue.attributeId = UA_ATTRIBUTEID_ARRAYDIMENSIONS;
     wValue.value.hasValue = true;
     UA_StatusCode retval = Service_Write_single(server, &adminSession, &wValue);
     // Returns attributeInvalid, since variant/value may be writable
-    ck_assert_int_eq(retval, UA_STATUSCODE_BADATTRIBUTEIDINVALID);
+    ck_assert_int_eq(retval, UA_STATUSCODE_BADTYPEMISMATCH);
     UA_Server_delete(server);
 } END_TEST
 
@@ -1047,7 +1064,6 @@ static Suite * testSuite_services_attributes(void) {
     tcase_add_test(tc_readSingleAttributes, ReadSingleAttributeHistorizingWithoutTimestamp);
     tcase_add_test(tc_readSingleAttributes, ReadSingleAttributeExecutableWithoutTimestamp);
     tcase_add_test(tc_readSingleAttributes, ReadSingleAttributeUserExecutableWithoutTimestamp);
-    tcase_add_test(tc_readSingleAttributes, ReadSingleDataSourceAttributeDataTypeWithoutTimestampFromBrokenSource);
     tcase_add_test(tc_readSingleAttributes, ReadSingleDataSourceAttributeValueWithoutTimestamp);
     tcase_add_test(tc_readSingleAttributes, ReadSingleDataSourceAttributeDataTypeWithoutTimestamp);
     tcase_add_test(tc_readSingleAttributes, ReadSingleDataSourceAttributeArrayDimensionsWithoutTimestamp);
@@ -1069,6 +1085,8 @@ static Suite * testSuite_services_attributes(void) {
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeEventNotifier);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeValue);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeDataType);
+    tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeValueRangeFromScalar);
+    tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeValueRangeFromArray);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeValueRank);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeArrayDimensions);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeAccessLevel);

+ 13 - 17
tests/check_session.c

@@ -5,8 +5,7 @@
 #include "server/ua_services.h"
 #include "check.h"
 
-START_TEST(Session_init_ShallWork)
-{
+START_TEST(Session_init_ShallWork) {
     UA_Session session;
     UA_Session_init(&session);
 
@@ -16,23 +15,22 @@ START_TEST(Session_init_ShallWork)
     UA_ApplicationDescription_init(&tmpAppDescription);
     UA_DateTime tmpDateTime;
     UA_DateTime_init(&tmpDateTime);
-    ck_assert_int_eq(session.activated,false);
-    ck_assert_int_eq(session.authenticationToken.identifier.numeric,tmpNodeId.identifier.numeric);
-    ck_assert_int_eq(session.availableContinuationPoints,MAXCONTINUATIONPOINTS);
-    ck_assert_ptr_eq(session.channel,NULL);
-    ck_assert_ptr_eq(session.clientDescription.applicationName.locale.data,NULL);
+    ck_assert_int_eq(session.activated, false);
+    ck_assert_int_eq(session.authenticationToken.identifier.numeric, tmpNodeId.identifier.numeric);
+    ck_assert_int_eq(session.availableContinuationPoints, UA_MAXCONTINUATIONPOINTS);
+    ck_assert_ptr_eq(session.channel, NULL);
+    ck_assert_ptr_eq(session.clientDescription.applicationName.locale.data, NULL);
     ck_assert_ptr_eq(session.continuationPoints.lh_first, NULL);
-    ck_assert_int_eq(session.maxRequestMessageSize,0);
-    ck_assert_int_eq(session.maxResponseMessageSize,0);
-    ck_assert_int_eq(session.sessionId.identifier.numeric,tmpNodeId.identifier.numeric);
-    ck_assert_ptr_eq(session.sessionName.data,NULL);
-    ck_assert_int_eq((int)session.timeout,0);
-    ck_assert_int_eq(session.validTill,tmpDateTime);
+    ck_assert_int_eq(session.maxRequestMessageSize, 0);
+    ck_assert_int_eq(session.maxResponseMessageSize, 0);
+    ck_assert_int_eq(session.sessionId.identifier.numeric, tmpNodeId.identifier.numeric);
+    ck_assert_ptr_eq(session.sessionName.data, NULL);
+    ck_assert_int_eq((int)session.timeout, 0);
+    ck_assert_int_eq(session.validTill, tmpDateTime);
 }
 END_TEST
 
-START_TEST(Session_updateLifetime_ShallWork)
-{
+START_TEST(Session_updateLifetime_ShallWork) {
     UA_Session session;
     UA_Session_init(&session);
     UA_DateTime tmpDateTime;
@@ -68,5 +66,3 @@ int main(void) {
 
     return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
 }
-
-

+ 5 - 5
tests/check_types_builtin.c

@@ -1322,8 +1322,8 @@ START_TEST(UA_Variant_copyShallWorkOn1DArrayExample) {
     srcArray[1] = UA_STRING_ALLOC("_62541");
     srcArray[2] = UA_STRING_ALLOC("opc ua");
 
-    UA_Int32 *dimensions;
-    dimensions = UA_malloc(sizeof(UA_Int32));
+    UA_UInt32 *dimensions;
+    dimensions = UA_malloc(sizeof(UA_UInt32));
     dimensions[0] = 3;
 
     UA_Variant value, copiedValue;
@@ -1373,9 +1373,9 @@ START_TEST(UA_Variant_copyShallWorkOn2DArrayExample) {
     srcArray[4] = 4;
     srcArray[5] = 5;
 
-    UA_Int32 *dimensions = UA_Array_new(2, &UA_TYPES[UA_TYPES_INT32]);
-    UA_Int32 dim1 = 3;
-    UA_Int32 dim2 = 2;
+    UA_UInt32 *dimensions = UA_Array_new(2, &UA_TYPES[UA_TYPES_INT32]);
+    UA_UInt32 dim1 = 3;
+    UA_UInt32 dim2 = 2;
     dimensions[0] = dim1;
     dimensions[1] = dim2;
 

+ 1 - 1
tests/check_types_range.c

@@ -23,7 +23,7 @@ START_TEST(parseRange) {
 
 START_TEST(parseRangeMinEqualMax) {
     UA_NumericRange range;
-    UA_String str = UA_STRING("1:2,1:1");
+    UA_String str = UA_STRING("1:2,1");
     UA_StatusCode retval = parse_numericrange(&str, &range);
     ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_int_eq(range.dimensionsSize,2);

+ 1 - 1
tools/c2rst.py

@@ -13,7 +13,7 @@ import re
 # - Find the last line beginning with "#ifdef" -> end of the documentation
 
 remove_keyword = [" UA_EXPORT", " UA_FUNC_ATTR_WARN_UNUSED_RESULT",
-                  " UA_FUNC_ATTR_MALLOC"]
+                  " UA_FUNC_ATTR_MALLOC", " UA_RESTRICT "]
 
 def clean_comment(line):
     m = re.search("^( \* |/\*\* )(.*?)( \*/)?$", line)

+ 6 - 8
tools/generate_datatypes.py

@@ -414,21 +414,19 @@ extern "C" {
 #endif
 
 #include "ua_types.h"
-''' + ('\n#include "ua_types_generated.h"\n' if outname != "ua_types" else '') + '''
-
-/**
- * Additional Data Type Definitions
- * ================================
- */
-''')
+''' + ('\n#include "ua_types_generated.h"\n' if outname != "ua_types" else ''))
 
+printh('''/**
+ * Every type is assigned an index in an array containing the type descriptions.
+ * These descriptions are used during type handling (copying, deletion,
+ * binary encoding, ...). */''')
 printh("#define " + outname.upper() + "_COUNT %s" % (str(len(selected_types))))
 printh("extern UA_EXPORT const UA_DataType " + outname.upper() + "[" + outname.upper() + "_COUNT];")
 
 i = 0
 for t in iter_types(types):
     printh("\n/**\n * " +  t.name)
-    printh(" * " + "-" * len(t.name))
+    printh(" * " + "^" * len(t.name))
     if t.description == "":
         printh(" */")
     else:

+ 0 - 0
tools/pyUANamespace/NodeID_AssumeExternal.txt


+ 0 - 0
tools/pyUANamespace/NodeID_Blacklist.txt


+ 0 - 0
tools/pyUANamespace/NodeID_Blacklist_FullNS0.txt


File diff suppressed because it is too large
+ 1514 - 0
tools/pyUANamespace/NodeID_NameSpace0_All.txt


+ 15 - 5
tools/pyUANamespace/open62541_MacroHelper.py

@@ -199,11 +199,13 @@ class open62541_MacroHelper():
     code.append("UA_%sAttributes_init(&attr);" %  nodetype);
     code.append("attr.displayName = UA_LOCALIZEDTEXT(\"\", \"" + str(node.displayName()) + "\");")
     code.append("attr.description = UA_LOCALIZEDTEXT(\"\", \"" + str(node.description()) + "\");")
-
+    
     if nodetype == "Variable":
       code.append("attr.accessLevel = %s;"     % str(node.accessLevel()))
       code.append("attr.userAccessLevel = %s;" % str(node.userAccessLevel()))
-
+    if nodetype in ["Variable", "VariableType"]:
+      code.append("attr.valueRank = %s;"       % str(node.valueRank()))
+      
     if nodetype in ["Variable", "VariableType"]:
       code = code + node.printOpen62541CCode_SubtypeEarly(bootstrapping = False)
     elif nodetype == "Method":
@@ -213,8 +215,16 @@ class open62541_MacroHelper():
         code.append("attr.userExecutable = true;")
 
     code.append("UA_NodeId nodeId = " + str(self.getCreateNodeIDMacro(node)) + ";")
-    if nodetype in ["Object", "Variable"]:
-      code.append("UA_NodeId typeDefinition = UA_NODEID_NULL;") #due to the current API we cannot set types here since the API will generate nodes with random IDs
+    if nodetype in ["Object", "Variable", "VariableType"]:
+      typeDefinition = None
+      for r in node.getReferences():
+        if r.isForward() and r.referenceType().id().ns == 0 and r.referenceType().id().i == 40:
+          typeDefinition = r.target()
+      if typeDefinition == None:
+        # FIXME: We might want to resort to BaseXYTTypes here?
+        code.append("UA_NodeId typeDefinition = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);")
+      else:
+        code.append("UA_NodeId typeDefinition = " + str(self.getCreateNodeIDMacro(typeDefinition)) + ";")
     code.append("UA_NodeId parentNodeId = " + str(self.getCreateNodeIDMacro(parentNode)) + ";")
     code.append("UA_NodeId parentReferenceNodeId = " + str(self.getCreateNodeIDMacro(parentReference.referenceType())) + ";")
     extrNs = node.browseName().split(":")
@@ -227,7 +237,7 @@ class open62541_MacroHelper():
     # Djikstra (check that arguments have not been printed). (@ichrispa)
     code.append("UA_Server_add%sNode(server, nodeId, parentNodeId, parentReferenceNodeId, nodeName" % nodetype)
 
-    if nodetype in ["Object", "Variable"]:
+    if nodetype in ["Object", "Variable", "VariableType"]:
       code.append("       , typeDefinition")
     if nodetype != "Method":
       code.append("       , attr, NULL, NULL);")

+ 3 - 0
tools/pyUANamespace/ua_namespace.py

@@ -653,6 +653,9 @@ class opcua_namespace():
     header.append('#include "open62541.h"')
     header.append('#define NULL ((void *)0)')
     header.append('#endif')
+    header.append('#ifndef UA_ENCODINGOFFSET_BINARY')
+    header.append('#define UA_ENCODINGOFFSET_BINARY 2')
+    header.append('#endif')
 
     code.append('#include "'+outfilename+'.h"')
     code.append("UA_INLINE void "+outfilename+"(UA_Server *server) {")

+ 4 - 4
tools/pyUANamespace/ua_node_types.py

@@ -1105,8 +1105,8 @@ class opcua_node_variable_t(opcua_node_t):
     code.append(self.getCodePrintableID() + "->accessLevel = (UA_Int32) " + str(self.accessLevel()) + ";")
     code.append(self.getCodePrintableID() + "->valueRank = (UA_Int32) " + str(self.valueRank()) + ";")
     # The variant is guaranteed to exist by SubtypeEarly()
-    code.append(self.getCodePrintableID() + "->value.variant.value = *" + self.getCodePrintableID() + "_variant;")
-    code.append(self.getCodePrintableID() + "->valueSource = UA_VALUESOURCE_VARIANT;")
+    code.append("UA_Variant_copy(" + self.getCodePrintableID() + "_variant, &" + self.getCodePrintableID() + "->value.data.value.value );")
+    code.append(self.getCodePrintableID() + "->valueSource = UA_VALUESOURCE_DATA;")
     return code
 
 class opcua_node_method_t(opcua_node_t):
@@ -1360,8 +1360,8 @@ class opcua_node_variableType_t(opcua_node_t):
       code.append(self.getCodePrintableID() + "->isAbstract = false;")
 
     # The variant is guaranteed to exist by SubtypeEarly()
-    code.append(self.getCodePrintableID() + "->value.variant.value = *" + self.getCodePrintableID() + "_variant;")
-    code.append(self.getCodePrintableID() + "->valueSource = UA_VALUESOURCE_VARIANT;")
+    code.append("UA_Variant_copy(" + self.getCodePrintableID() + "_variant, &" + self.getCodePrintableID() + "->value.data.value.value );")
+    code.append(self.getCodePrintableID() + "->valueSource = UA_VALUESOURCE_DATA;")
     return code
 
 class opcua_node_dataType_t(opcua_node_t):

+ 3 - 0
tools/schema/datatypes_minimal.txt

@@ -161,3 +161,6 @@ SetMonitoringModeResponse
 RegisteredServer
 RegisterServerRequest
 RegisterServerResponse
+DataChangeTrigger
+DeadbandType
+DataChangeFilter