浏览代码

Merge remote-tracking branch 'upstream/master' into feature/architectures

# Conflicts:
#	src/server/ua_services_discovery_multicast.c
#	src/ua_util.h
Stefan Profanter 6 年之前
父节点
当前提交
443283f8e4
共有 84 个文件被更改,包括 8147 次插入982 次删除
  1. 8 0
      .cquery
  2. 40 16
      CMakeLists.txt
  3. 219 2
      arch/ua_network_tcp.c
  4. 5 1
      arch/ua_network_tcp.h
  5. 9 0
      doc/CMakeLists.txt
  6. 4 0
      doc/building.rst
  7. 2 0
      doc/tutorials.rst
  8. 二进制
      doc/ua-wireshark-pubsub.png
  9. 13 1
      examples/CMakeLists.txt
  10. 2 2
      examples/client.c
  11. 224 0
      examples/client_async.c
  12. 73 0
      examples/client_connectivitycheck_loop.c
  13. 7 1
      examples/client_subscription_loop.c
  14. 1 0
      examples/discovery/client_find_servers.c
  15. 124 27
      examples/pubsub/tutorial_pubsub_publish.c
  16. 1 1
      examples/tutorial_client_events.c
  17. 10 10
      examples/tutorial_datatypes.c
  18. 0 15
      examples/tutorial_server_datasource.c
  19. 177 0
      examples/tutorial_server_events.c
  20. 65 0
      examples/tutorial_server_monitoreditems.c
  21. 1 1
      examples/tutorial_server_variable.c
  22. 55 43
      include/ua_client.h
  23. 58 6
      include/ua_client_config.h
  24. 628 0
      include/ua_client_highlevel_async.h
  25. 9 1
      include/ua_config.h.in
  26. 2 1
      include/ua_plugin_network.h
  27. 17 0
      include/ua_plugin_nodestore.h
  28. 108 0
      include/ua_server.h
  29. 3 0
      include/ua_server_config.h
  30. 205 53
      include/ua_server_pubsub.h
  31. 3 1
      include/ua_types.h
  32. 13 9
      plugins/ua_config_default.c
  33. 56 25
      plugins/ua_nodestore_default.c
  34. 117 29
      src/client/ua_client.c
  35. 48 11
      src/client/ua_client_connect.c
  36. 680 0
      src/client/ua_client_connect_async.c
  37. 4 0
      src/client/ua_client_discovery.c
  38. 204 0
      src/client/ua_client_highlevel.c
  39. 71 3
      src/client/ua_client_internal.h
  40. 34 14
      src/client/ua_client_subscriptions.c
  41. 213 0
      src/client/ua_client_worker.c
  42. 889 36
      src/pubsub/ua_pubsub.c
  43. 94 4
      src/pubsub/ua_pubsub.h
  44. 86 61
      src/pubsub/ua_pubsub_manager.c
  45. 16 2
      src/pubsub/ua_pubsub_manager.h
  46. 48 26
      src/pubsub/ua_pubsub_networkmessage.c
  47. 12 0
      src/server/ua_server.c
  48. 20 0
      src/server/ua_server_internal.h
  49. 18 0
      src/server/ua_server_ns0.c
  50. 10 5
      src/server/ua_server_utils.c
  51. 1 0
      src/server/ua_server_worker.c
  52. 2 0
      src/server/ua_services.h
  53. 74 120
      src/server/ua_services_discovery.c
  54. 3 3
      src/server/ua_services_discovery_multicast.c
  55. 141 38
      src/server/ua_services_subscription.c
  56. 1 1
      src/server/ua_session.c
  57. 122 33
      src/server/ua_subscription.c
  58. 26 14
      src/server/ua_subscription.h
  59. 335 175
      src/server/ua_subscription_datachange.c
  60. 462 0
      src/server/ua_subscription_events.c
  61. 16 0
      src/server/ua_subscription_events.h
  62. 23 1
      src/ua_connection.c
  63. 19 0
      src/ua_connection_internal.h
  64. 42 12
      src/ua_types.c
  65. 19 3
      src/ua_types_encoding_binary.c
  66. 4 4
      src/ua_util.h
  67. 11 0
      tests/CMakeLists.txt
  68. 6 1
      tests/check_types_memory.c
  69. 188 32
      tests/client/check_client_async.c
  70. 158 0
      tests/client/check_client_async_connect.c
  71. 2 0
      tests/client/check_client_highlevel.c
  72. 23 20
      tests/client/check_client_subscriptions.c
  73. 3 3
      tests/pubsub/check_pubsub_connection_udp.c
  74. 1 1
      tests/pubsub/check_pubsub_pds.c
  75. 458 0
      tests/pubsub/check_pubsub_publish.c
  76. 846 0
      tests/server/check_monitoreditem_filter.c
  77. 4 4
      tests/server/check_services_subscriptions.c
  78. 19 21
      tools/generate_datatypes.py
  79. 9 2
      tools/nodeset_compiler/backend_open62541_nodes.py
  80. 7 5
      tools/nodeset_compiler/nodes.py
  81. 394 67
      tools/schema/Opc.Ua.Types.bsd
  82. 10 0
      tools/schema/datatypes_discovery.txt
  83. 5 0
      tools/schema/datatypes_method.txt
  84. 7 15
      tools/schema/datatypes_minimal.txt

+ 8 - 0
.cquery

@@ -0,0 +1,8 @@
+%clang
+%c -std=c99
+
+# Includes
+-I./include
+-I./src
+-I./deps
+-I./plugins

+ 40 - 16
CMakeLists.txt

@@ -115,11 +115,17 @@ set(UA_LOGLEVEL 300 CACHE STRING "Level at which logs shall be reported")
 option(UA_ENABLE_METHODCALLS "Enable the Method service set" ON)
 option(UA_ENABLE_NODEMANAGEMENT "Enable dynamic addition and removal of nodes at runtime" ON)
 option(UA_ENABLE_SUBSCRIPTIONS "Enable subscriptions support." ON)
+option(UA_ENABLE_SUBSCRIPTIONS_EVENTS "Enable the use of events." OFF)
 option(UA_ENABLE_DISCOVERY "Enable Discovery Service (LDS)" ON)
 option(UA_ENABLE_DISCOVERY_MULTICAST "Enable Discovery Service with multicast support (LDS-ME)" OFF)
 option(UA_ENABLE_COVERAGE "Enable gcov coverage" OFF)
 option(BUILD_SHARED_LIBS "Enable building of shared libraries (dll/so)" OFF)
 
+# It should not be possible to enable events without enabling subscriptions and full ns0
+if ((UA_ENABLE_SUBSCRIPTIONS_EVENTS) AND (NOT (UA_ENABLE_SUBSCRIPTIONS AND UA_ENABLE_FULL_NS0)))
+    message(FATAL_ERROR "Unable to enable events without UA_ENABLE_SUBSCRIPTIONS and UA_ENABLE_FULL_NS0")
+endif()
+
 # Encryption Options
 option(UA_ENABLE_ENCRYPTION "Enable encryption support (uses mbedTLS)" OFF)
 
@@ -146,6 +152,12 @@ endif()
 option(UA_ENABLE_MULTITHREADING "Enable multithreading (experimental)" OFF)
 mark_as_advanced(UA_ENABLE_MULTITHREADING)
 
+option(UA_ENABLE_IMMUTABLE_NODES "Nodes in the information model are not edited but copied and replaced" OFF)
+mark_as_advanced(UA_ENABLE_IMMUTABLE_NODES)
+if(UA_ENABLE_MULTITHREADING)
+    set(UA_ENABLE_IMMUTABLE_NODES ON)
+endif()
+
 option(UA_ENABLE_PUBSUB "Enable publish/subscribe (experimental)" OFF)
 mark_as_advanced(UA_ENABLE_PUBSUB)
 
@@ -387,7 +399,8 @@ set(exported_headers ${exported_headers}
                      ${PROJECT_SOURCE_DIR}/include/ua_client_config.h
                      ${PROJECT_SOURCE_DIR}/include/ua_client.h
                      ${PROJECT_SOURCE_DIR}/include/ua_client_highlevel.h
-                     ${PROJECT_SOURCE_DIR}/include/ua_client_subscriptions.h)
+                     ${PROJECT_SOURCE_DIR}/include/ua_client_subscriptions.h
+                     ${PROJECT_SOURCE_DIR}/include/ua_client_highlevel_async.h)
 
 set(internal_headers ${PROJECT_SOURCE_DIR}/deps/queue.h
                      ${PROJECT_SOURCE_DIR}/deps/pcg_basic.h
@@ -405,10 +418,14 @@ set(internal_headers ${PROJECT_SOURCE_DIR}/deps/queue.h
                      ${PROJECT_SOURCE_DIR}/src/server/ua_subscription.h
                      ${PROJECT_SOURCE_DIR}/src/server/ua_session_manager.h
                      ${PROJECT_SOURCE_DIR}/src/server/ua_securechannel_manager.h
+                     ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage.h
+                     ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub.h
+                     ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_manager.h
                      ${PROJECT_SOURCE_DIR}/src/server/ua_server_internal.h
                      ${PROJECT_SOURCE_DIR}/src/server/ua_services.h
                      ${PROJECT_BINARY_DIR}/src_generated/ua_namespace0.h
-                     ${PROJECT_SOURCE_DIR}/src/client/ua_client_internal.h)
+                     ${PROJECT_SOURCE_DIR}/src/client/ua_client_internal.h
+                     ${PROJECT_SOURCE_DIR}/src/server/ua_subscription_events.h)
 
 # TODO: make client optional
 set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
@@ -433,6 +450,10 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_session_manager.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_subscription.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_subscription_datachange.c
+                ${PROJECT_SOURCE_DIR}/src/server/ua_subscription_events.c
+                ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage.c
+                ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub.c
+                ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_manager.c
                 # services
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_view.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_call.c
@@ -446,9 +467,11 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
                 # client
                 ${PROJECT_SOURCE_DIR}/src/client/ua_client.c
                 ${PROJECT_SOURCE_DIR}/src/client/ua_client_connect.c
+                ${PROJECT_SOURCE_DIR}/src/client/ua_client_connect_async.c
                 ${PROJECT_SOURCE_DIR}/src/client/ua_client_discovery.c
                 ${PROJECT_SOURCE_DIR}/src/client/ua_client_highlevel.c
                 ${PROJECT_SOURCE_DIR}/src/client/ua_client_subscriptions.c
+                ${PROJECT_SOURCE_DIR}/src/client/ua_client_worker.c
 
                 # dependencies
                 ${PROJECT_SOURCE_DIR}/deps/libc_time.c
@@ -478,12 +501,6 @@ if(UA_ENABLE_ENCRYPTION)
 endif()
 
 if(UA_ENABLE_PUBSUB)
-    list(APPEND internal_headers ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage.h
-            ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_manager.h)
-    list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage.c
-            ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub.c
-            ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_manager.c)
-
     list(APPEND default_plugin_headers ${PROJECT_SOURCE_DIR}/plugins/ua_network_pubsub_udp.h)
     list(APPEND default_plugin_sources ${PROJECT_SOURCE_DIR}/plugins/ua_network_pubsub_udp.c)
 endif()
@@ -522,7 +539,7 @@ if (UA_ENABLE_FULL_NS0)
     if(NOT UA_FILE_NS0)
         set(UA_FILE_NS0 ${PROJECT_SOURCE_DIR}/deps/ua-nodeset/Schema/Opc.Ua.NodeSet2.xml)
     endif()
-    set(UA_FILE_DATATYPES "")
+
     set(UA_FILE_NODEIDS ${PROJECT_SOURCE_DIR}/deps/ua-nodeset/Schema/NodeIds.csv)
     set(UA_FILE_STATUSCODES ${PROJECT_SOURCE_DIR}/deps/ua-nodeset/Schema/StatusCode.csv)
     set(UA_FILE_TYPES_BSD ${PROJECT_SOURCE_DIR}/deps/ua-nodeset/Schema/Opc.Ua.Types.bsd)
@@ -530,21 +547,28 @@ else()
     if(NOT UA_FILE_NS0)
         set(UA_FILE_NS0 ${PROJECT_SOURCE_DIR}/tools/schema/Opc.Ua.NodeSet2.Minimal.xml)
     endif()
-    set(UA_FILE_DATATYPES "${PROJECT_SOURCE_DIR}/tools/schema/datatypes_minimal.txt")
+    set(UA_FILE_DATATYPES ${PROJECT_SOURCE_DIR}/tools/schema/datatypes_minimal.txt)
     set(UA_FILE_NODEIDS ${PROJECT_SOURCE_DIR}/tools/schema/NodeIds.csv)
     set(UA_FILE_STATUSCODES ${PROJECT_SOURCE_DIR}/tools/schema/StatusCode.csv)
     set(UA_FILE_TYPES_BSD ${PROJECT_SOURCE_DIR}/tools/schema/Opc.Ua.Types.bsd)
+
+    if(UA_ENABLE_METHODCALLS)
+        list(APPEND UA_FILE_DATATYPES ${PROJECT_SOURCE_DIR}/tools/schema/datatypes_method.txt)
+    endif()
+
+    if(UA_ENABLE_DISCOVERY)
+        list(APPEND UA_FILE_DATATYPES ${PROJECT_SOURCE_DIR}/tools/schema/datatypes_discovery.txt)
+    endif()
 endif()
+
 if(NOT EXISTS "${UA_FILE_NS0}")
     message(FATAL_ERROR "File ${UA_FILE_NS0} not found. You probably need to initialize the git submodule for deps/ua-nodeset.")
 endif()
 
-
-if (UA_FILE_DATATYPES STREQUAL "")
-    set(SELECTED_TYPES_TMP "")
-else()
-  set(SELECTED_TYPES_TMP "--selected-types=${UA_FILE_DATATYPES}")
-endif()
+set(SELECTED_TYPES_TMP "")
+foreach(f ${UA_FILE_DATATYPES})
+    set(SELECTED_TYPES_TMP ${SELECTED_TYPES_TMP} "--selected-types=${f}")
+endforeach()
 
 # standard-defined data types
 add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated.c

+ 219 - 2
arch/ua_network_tcp.c

@@ -1,5 +1,5 @@
 /* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
- * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. 
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
  *
  *    Copyright 2016-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
  *    Copyright 2016-2017 (c) Stefan Profanter, fortiss GmbH
@@ -536,6 +536,13 @@ UA_ServerNetworkLayerTCP(UA_ConnectionConfig conf, UA_UInt16 port, UA_Logger log
     return nl;
 }
 
+typedef struct TCPClientConnection {
+	struct addrinfo hints, *server;
+	UA_DateTime connStart;
+	char* endpointURL;
+	UA_UInt32 timeout;
+} TCPClientConnection;
+
 /***************************/
 /* Client NetworkLayer TCP */
 /***************************/
@@ -549,6 +556,215 @@ ClientNetworkLayerTCP_close(UA_Connection *connection) {
     connection->state = UA_CONNECTION_CLOSED;
 }
 
+static void
+ClientNetworkLayerTCP_free(UA_Connection *connection) {
+    if (connection->handle){
+        TCPClientConnection *tcpConnection = (TCPClientConnection *)connection->handle;
+        if(tcpConnection->server)
+            freeaddrinfo(tcpConnection->server);
+        free(tcpConnection);
+    }
+}
+
+UA_StatusCode UA_ClientConnectionTCP_poll(UA_Client *client, void *data) {
+    UA_Connection *connection = (UA_Connection*) data;
+
+    if (connection->state == UA_CONNECTION_CLOSED)
+        return UA_STATUSCODE_BADDISCONNECT;
+
+    TCPClientConnection *tcpConnection =
+                    (TCPClientConnection*) connection->handle;
+
+    UA_DateTime connStart = UA_DateTime_nowMonotonic();
+    SOCKET clientsockfd;
+
+    if (connection->state == UA_CONNECTION_ESTABLISHED) {
+            UA_Client_removeRepeatedCallback(client, connection->connectCallbackID);
+            connection->connectCallbackID = 0;
+            return UA_STATUSCODE_GOOD;
+    }
+    if ((UA_Double) (UA_DateTime_nowMonotonic() - tcpConnection->connStart)
+                    > tcpConnection->timeout* UA_DATETIME_MSEC ) {
+            // connection timeout
+            ClientNetworkLayerTCP_close(connection);
+            UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
+                            "Timed out");
+            return UA_STATUSCODE_BADDISCONNECT;
+
+    }
+    /* On linux connect may immediately return with ECONNREFUSED but we still want to try to connect */
+    /* Thus use a loop and retry until timeout is reached */
+
+    /* Get a socket */
+    clientsockfd = socket(tcpConnection->server->ai_family,
+                    tcpConnection->server->ai_socktype,
+                    tcpConnection->server->ai_protocol);
+    connection->sockfd = (UA_Int32) clientsockfd; /* cast for win32 */
+
+#ifdef _WIN32
+    if(clientsockfd == INVALID_SOCKET) {
+#else
+    if (clientsockfd < 0) {
+#endif
+            UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
+                            "Could not create client socket: %s", strerror(errno__));
+            ClientNetworkLayerTCP_close(connection);
+            return UA_STATUSCODE_BADDISCONNECT;
+    }
+
+    /* Non blocking connect to be able to timeout */
+    if (socket_set_nonblocking(clientsockfd) != UA_STATUSCODE_GOOD) {
+            UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
+                            "Could not set the client socket to nonblocking");
+            ClientNetworkLayerTCP_close(connection);
+            return UA_STATUSCODE_BADDISCONNECT;
+    }
+
+    /* Non blocking connect */
+    int error = connect(clientsockfd, tcpConnection->server->ai_addr,
+                    WIN32_INT tcpConnection->server->ai_addrlen);
+
+    if ((error == -1) && (errno__ != ERR_CONNECTION_PROGRESS)) {
+            ClientNetworkLayerTCP_close(connection);
+            UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
+                            "Connection to  failed with error: %s", strerror(errno__));
+            return UA_STATUSCODE_BADDISCONNECT;
+    }
+
+    /* Use select to wait and check if connected */
+    if (error == -1 && (errno__ == ERR_CONNECTION_PROGRESS)) {
+        /* connection in progress. Wait until connected using select */
+
+        UA_UInt32 timeSinceStart =
+                        (UA_UInt32) ((UA_Double) (UA_DateTime_nowMonotonic() - connStart)
+                                        * UA_DATETIME_MSEC);
+
+        fd_set fdset;
+        FD_ZERO(&fdset);
+        UA_fd_set(clientsockfd, &fdset);
+        UA_UInt32 timeout_usec = (tcpConnection->timeout - timeSinceStart)
+                        * 1000;
+        struct timeval tmptv = { (long int) (timeout_usec / 1000000),
+                        (long int) (timeout_usec % 1000000) };
+
+        int resultsize = select((UA_Int32) (clientsockfd + 1), NULL, &fdset,
+        NULL, &tmptv);
+
+        if (resultsize == 1) {
+            /* Windows does not have any getsockopt equivalent and it is not needed there */
+#ifdef _WIN32
+            connection->sockfd = clientsockfd;
+            connection->state = UA_CONNECTION_ESTABLISHED;
+            return UA_STATUSCODE_GOOD;
+#else
+            OPTVAL_TYPE so_error;
+            socklen_t len = sizeof so_error;
+
+            int ret = getsockopt(clientsockfd, SOL_SOCKET, SO_ERROR, &so_error,
+                            &len);
+
+            if (ret != 0 || so_error != 0) {
+                /* on connection refused we should still try to connect */
+                /* connection refused happens on localhost or local ip without timeout */
+                if (so_error != ECONNREFUSED) {
+                        // general error
+                        ClientNetworkLayerTCP_close(connection);
+                        UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
+                                        "Connection to failed with error: %s",
+                                        strerror(ret == 0 ? so_error : errno__));
+                        return UA_STATUSCODE_BADDISCONNECT;
+                }
+                /* wait until we try a again. Do not make this too small, otherwise the
+                 * timeout is somehow wrong */
+
+        } else {
+                connection->state = UA_CONNECTION_ESTABLISHED;
+                return UA_STATUSCODE_GOOD;
+            }
+#endif
+        }
+    } else {
+        connection->state = UA_CONNECTION_ESTABLISHED;
+        return UA_STATUSCODE_GOOD;
+    }
+
+#ifdef SO_NOSIGPIPE
+    int val = 1;
+    int sso_result = setsockopt(connection->sockfd, SOL_SOCKET,
+                    SO_NOSIGPIPE, (void*)&val, sizeof(val));
+    if(sso_result < 0)
+    UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
+                    "Couldn't set SO_NOSIGPIPE");
+#endif
+
+    return UA_STATUSCODE_GOOD;
+
+}
+
+UA_Connection UA_ClientConnectionTCP_init(UA_ConnectionConfig conf,
+		const char *endpointUrl, const UA_UInt32 timeout,
+                UA_Logger logger) {
+    UA_Connection connection;
+    memset(&connection, 0, sizeof(UA_Connection));
+
+    connection.state = UA_CONNECTION_OPENING;
+    connection.localConf = conf;
+    connection.remoteConf = conf;
+    connection.send = connection_write;
+    connection.recv = connection_recv;
+    connection.close = ClientNetworkLayerTCP_close;
+    connection.free = ClientNetworkLayerTCP_free;
+    connection.getSendBuffer = connection_getsendbuffer;
+    connection.releaseSendBuffer = connection_releasesendbuffer;
+    connection.releaseRecvBuffer = connection_releaserecvbuffer;
+
+    TCPClientConnection *tcpClientConnection = (TCPClientConnection*) malloc(
+                    sizeof(TCPClientConnection));
+    connection.handle = (void*) tcpClientConnection;
+    tcpClientConnection->timeout = timeout;
+    UA_String endpointUrlString = UA_STRING((char*) (uintptr_t) endpointUrl);
+    UA_String hostnameString = UA_STRING_NULL;
+    UA_String pathString = UA_STRING_NULL;
+    UA_UInt16 port = 0;
+    char hostname[512];
+    tcpClientConnection->connStart = UA_DateTime_nowMonotonic();
+
+    UA_StatusCode parse_retval = UA_parseEndpointUrl(&endpointUrlString,
+                    &hostnameString, &port, &pathString);
+    if (parse_retval != UA_STATUSCODE_GOOD || hostnameString.length > 511) {
+            UA_LOG_WARNING(logger, UA_LOGCATEGORY_NETWORK,
+                            "Server url is invalid: %s", endpointUrl);
+            return connection;
+    }
+    memcpy(hostname, hostnameString.data, hostnameString.length);
+    hostname[hostnameString.length] = 0;
+
+    if (port == 0) {
+            port = 4840;
+            UA_LOG_INFO(logger, UA_LOGCATEGORY_NETWORK,
+                            "No port defined, using default port %d", port);
+    }
+
+    memset(&tcpClientConnection->hints, 0, sizeof(tcpClientConnection->hints));
+    tcpClientConnection->hints.ai_family = AF_UNSPEC;
+    tcpClientConnection->hints.ai_socktype = SOCK_STREAM;
+    char portStr[6];
+#ifndef _MSC_VER
+    snprintf(portStr, 6, "%d", port);
+#else
+    _snprintf_s(portStr, 6, _TRUNCATE, "%d", port);
+#endif
+    int error = getaddrinfo(hostname, portStr, &tcpClientConnection->hints,
+                    &tcpClientConnection->server);
+    if (error != 0 || !tcpClientConnection->server) {
+            UA_LOG_WARNING(logger, UA_LOGCATEGORY_NETWORK,
+                            "DNS lookup of %s failed with error %s", hostname,
+                            gai_strerror(error));
+            return connection;
+    }
+    return connection;
+}
+
 UA_Connection
 UA_ClientConnectionTCP(UA_ConnectionConfig conf,
                        const char *endpointUrl, const UA_UInt32 timeout,
@@ -568,10 +784,11 @@ UA_ClientConnectionTCP(UA_ConnectionConfig conf,
     connection.send = connection_write;
     connection.recv = connection_recv;
     connection.close = ClientNetworkLayerTCP_close;
-    connection.free = NULL;
+    connection.free = ClientNetworkLayerTCP_free;
     connection.getSendBuffer = connection_getsendbuffer;
     connection.releaseSendBuffer = connection_releasesendbuffer;
     connection.releaseRecvBuffer = connection_releaserecvbuffer;
+    connection.handle = NULL;
 
     UA_String endpointUrlString = UA_STRING((char*)(uintptr_t)endpointUrl);
     UA_String hostnameString = UA_STRING_NULL;

+ 5 - 1
arch/ua_network_tcp.h

@@ -19,8 +19,12 @@ UA_ServerNetworkLayer UA_EXPORT
 UA_ServerNetworkLayerTCP(UA_ConnectionConfig conf, UA_UInt16 port, UA_Logger logger);
 
 UA_Connection UA_EXPORT
-UA_ClientConnectionTCP(UA_ConnectionConfig conf, const char *endpointUrl, const UA_UInt32 timeout, UA_Logger logger);
+UA_ClientConnectionTCP(UA_ConnectionConfig conf, const char *endpointUrl, const UA_UInt32 timeout,
+                       UA_Logger logger);
 
+UA_StatusCode UA_ClientConnectionTCP_poll(UA_Client *client, void *data);
+UA_Connection UA_EXPORT UA_ClientConnectionTCP_init(UA_ConnectionConfig conf,
+                const char *endpointUrl, const UA_UInt32 timeout, UA_Logger logger);
 #ifdef __cplusplus
 } // extern "C"
 #endif

+ 9 - 0
doc/CMakeLists.txt

@@ -41,9 +41,12 @@ generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_firststeps.c ${DOC_S
 generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_variable.c ${DOC_SRC_DIR}/tutorial_server_variable.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_variabletype.c ${DOC_SRC_DIR}/tutorial_server_variabletype.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_datasource.c ${DOC_SRC_DIR}/tutorial_server_datasource.rst)
+generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_events.c ${DOC_SRC_DIR}/tutorial_server_events.rst)
+generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_monitoreditems.c ${DOC_SRC_DIR}/tutorial_server_monitoreditems.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_object.c ${DOC_SRC_DIR}/tutorial_server_object.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_method.c ${DOC_SRC_DIR}/tutorial_server_method.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_client_firststeps.c ${DOC_SRC_DIR}/tutorial_client_firststeps.rst)
+generate_rst(${PROJECT_SOURCE_DIR}/examples/pubsub/tutorial_pubsub_publish.c ${DOC_SRC_DIR}/tutorial_pubsub_publish.rst)
 
 add_custom_target(doc_latex ${SPHINX_EXECUTABLE}
   -b latex "${DOC_SRC_DIR}" "${DOC_LATEX_DIR}"
@@ -62,10 +65,13 @@ add_custom_target(doc_latex ${SPHINX_EXECUTABLE}
           ${DOC_SRC_DIR}/tutorial_server_variable.rst
           ${DOC_SRC_DIR}/tutorial_server_variabletype.rst
           ${DOC_SRC_DIR}/tutorial_server_datasource.rst
+          ${DOC_SRC_DIR}/tutorial_server_monitoreditems.rst
           ${DOC_SRC_DIR}/tutorial_server_object.rst
           ${DOC_SRC_DIR}/tutorial_server_method.rst
+          ${DOC_SRC_DIR}/tutorial_pubsub_publish.rst
           ${DOC_SRC_DIR}/plugin_pubsub_connection.rst
           ${DOC_SRC_DIR}/pubsub.rst
+          ${DOC_SRC_DIR}/tutorial_server_events.rst
   COMMENT "Building LaTeX sources for documentation with Sphinx")
 add_dependencies(doc_latex open62541)
 
@@ -94,10 +100,13 @@ add_custom_target(doc ${SPHINX_EXECUTABLE}
           ${DOC_SRC_DIR}/tutorial_server_variable.rst
           ${DOC_SRC_DIR}/tutorial_server_variabletype.rst
           ${DOC_SRC_DIR}/tutorial_server_datasource.rst
+          ${DOC_SRC_DIR}/tutorial_server_monitoreditems.rst
           ${DOC_SRC_DIR}/tutorial_server_object.rst
           ${DOC_SRC_DIR}/tutorial_server_method.rst
+          ${DOC_SRC_DIR}/tutorial_pubsub_publish.rst
           ${DOC_SRC_DIR}/plugin_pubsub_connection.rst
           ${DOC_SRC_DIR}/pubsub.rst
+          ${DOC_SRC_DIR}/tutorial_server_events.rst
   COMMENT "Building HTML documentation with Sphinx")
 add_dependencies(doc open62541)
 

+ 4 - 0
doc/building.rst

@@ -178,6 +178,8 @@ This group contains build options related to the supported OPC UA features.
 
 **UA_ENABLE_SUBSCRIPTIONS**
    Enable subscriptions
+**UA_ENABLE_SUBSCRIPTIONS_EVENTS**
+    Enable the use of events for subscriptions
 **UA_ENABLE_METHODCALLS**
    Enable the Method service set
 **UA_ENABLE_NODEMANAGEMENT**
@@ -186,6 +188,8 @@ This group contains build options related to the supported OPC UA features.
    Compile a single-file release into the files :file:`open62541.c` and :file:`open62541.h`
 **UA_ENABLE_MULTITHREADING**
    Enable multi-threading support
+**UA_ENABLE_IMMUTABLE_NODES**
+   Nodes in the information model are not edited but copied and replaced. The replacement is done with atomic operations so that the information model is always consistent and can be accessed from an interrupt or parallel thread (depends on the node storage plugin implementation). This feature is a prerequisite for ``UA_ENABLE_MULTITHREADING``.
 **UA_ENABLE_COVERAGE**
    Measure the coverage of unit tests
 

+ 2 - 0
doc/tutorials.rst

@@ -12,4 +12,6 @@ Tutorials
    tutorial_server_variabletype.rst
    tutorial_server_object.rst
    tutorial_server_method.rst
+   tutorial_server_events.rst
    tutorial_client_firststeps.rst
+   tutorial_pubsub_publish.rst

二进制
doc/ua-wireshark-pubsub.png


+ 13 - 1
examples/CMakeLists.txt

@@ -38,6 +38,10 @@ add_example(tutorial_server_variable tutorial_server_variable.c)
 
 add_example(tutorial_server_datasource tutorial_server_datasource.c)
 
+if(UA_ENABLE_SUBSCRIPTIONS)
+add_example(tutorial_server_monitoreditems tutorial_server_monitoreditems.c)
+endif()
+
 add_example(tutorial_server_variabletype tutorial_server_variabletype.c)
 
 add_example(tutorial_server_object tutorial_server_object.c)
@@ -50,6 +54,10 @@ add_example(tutorial_client_firststeps tutorial_client_firststeps.c)
 
 add_example(tutorial_client_events tutorial_client_events.c)
 
+if(UA_ENABLE_SUBSCRIPTIONS_EVENTS)
+  add_example(tutorial_server_events tutorial_server_events.c)
+endif()
+
 ##################
 # Example Server #
 ##################
@@ -62,8 +70,12 @@ add_example(server_ctt server_ctt.c)
 
 add_example(client client.c)
 
+add_example(client_async client_async.c)
+
 add_example(client_connect_loop client_connect_loop.c)
 
+add_example(client_connectivitycheck_loop client_connectivitycheck_loop.c)
+
 if(UA_ENABLE_SUBSCRIPTIONS)
 add_example(client_subscription_loop client_subscription_loop.c)
 endif()
@@ -127,4 +139,4 @@ add_subdirectory(nodeset)
 if(UA_ENABLE_PUBSUB)
     add_example(tutorial_pubsub_connection pubsub/tutorial_pubsub_connection.c)
     add_example(tutorial_pubsub_publish pubsub/tutorial_pubsub_publish.c)
-endif()
+endif()

+ 2 - 2
examples/client.c

@@ -114,7 +114,7 @@ int main(int argc, char *argv[]) {
 
 
     /* The first publish request should return the initial value of the variable */
-    UA_Client_runAsync(client, 1000);
+    UA_Client_run_iterate(client, 1000);
 #endif
 
     /* Read attribute */
@@ -157,7 +157,7 @@ int main(int argc, char *argv[]) {
 
 #ifdef UA_ENABLE_SUBSCRIPTIONS
     /* Take another look at the.answer */
-    UA_Client_runAsync(client, 100);
+    UA_Client_run_iterate(client, 100);
     /* Delete the subscription */
     if(UA_Client_Subscriptions_deleteSingle(client, subId) == UA_STATUSCODE_GOOD)
         printf("Subscription removed\n");

+ 224 - 0
examples/client_async.c

@@ -0,0 +1,224 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+#ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 600
+#endif
+#ifndef _DEFAULT_SOURCE
+# define _DEFAULT_SOURCE
+#endif
+/* On older systems we need to define _BSD_SOURCE.
+ * _DEFAULT_SOURCE is an alias for that. */
+#ifndef _BSD_SOURCE
+# define _BSD_SOURCE
+#endif
+
+#include <stdio.h>
+#include "open62541.h"
+
+#ifdef _WIN32
+# include <windows.h>
+# define UA_sleep_ms(X) Sleep(X)
+#else
+# include <unistd.h>
+# define UA_sleep_ms(X) usleep(X * 1000)
+#endif
+
+#define NODES_EXIST
+/* async connection callback, it only gets called after the completion of the whole
+ * connection process*/
+static void
+onConnect (UA_Client *client, void *userdata, UA_UInt32 requestId,
+           void *status) {
+    printf ("Async connect returned with status code %s\n",
+            UA_StatusCode_name (*(UA_StatusCode *) status));
+}
+
+static
+void
+fileBrowsed (UA_Client *client, void *userdata, UA_UInt32 requestId,
+             UA_BrowseResponse *response) {
+    printf ("%-50s%i\n", "Received BrowseResponse for request ", requestId);
+    UA_String us = *(UA_String *) userdata;
+    printf ("---%.*s passed safely \n", (int) us.length, us.data);
+}
+
+/*high-level function callbacks*/
+static
+void
+readValueAttributeCallback (UA_Client *client, void *userdata,
+                            UA_UInt32 requestId, UA_Variant *var) {
+    printf ("%-50s%i\n", "Read value attribute for request", requestId);
+
+    if (UA_Variant_hasScalarType (var, &UA_TYPES[UA_TYPES_INT32])) {
+        UA_Int32 int_val = *(UA_Int32*) var->data;
+        printf ("---%-40s%-8i\n",
+                "Reading the value of node (1, \"the.answer\"):", int_val);
+    }
+
+    /*more type distinctions possible*/
+    return;
+}
+
+static
+void
+attrWritten (UA_Client *client, void *userdata, UA_UInt32 requestId,
+             UA_WriteResponse *response) {
+    /*assuming no data to be retrieved by writing attributes*/
+    printf ("%-50s%i\n", "Wrote value attribute for request ", requestId);
+    UA_WriteResponse_deleteMembers(response);
+}
+
+#ifdef NODES_EXIST
+static void
+methodCalled (UA_Client *client, void *userdata, UA_UInt32 requestId,
+              UA_CallResponse *response) {
+
+    printf ("%-50s%i\n", "Called method for request ", requestId);
+    size_t outputSize;
+    UA_Variant *output;
+    UA_StatusCode retval = response->responseHeader.serviceResult;
+    if (retval == UA_STATUSCODE_GOOD) {
+        if (response->resultsSize == 1)
+            retval = response->results[0].statusCode;
+        else
+            retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
+    }
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_CallResponse_deleteMembers (response);
+    }
+
+    /* Move the output arguments */
+    output = response->results[0].outputArguments;
+    outputSize = response->results[0].outputArgumentsSize;
+    response->results[0].outputArguments = NULL;
+    response->results[0].outputArgumentsSize = 0;
+
+    if (retval == UA_STATUSCODE_GOOD) {
+        printf ("---Method call was successful, returned %lu values.\n",
+                (unsigned long) outputSize);
+        UA_Array_delete (output, outputSize, &UA_TYPES[UA_TYPES_VARIANT]);
+    }
+    else {
+        printf ("---Method call was unsuccessful, returned %x values.\n",
+                retval);
+    }
+    UA_CallResponse_deleteMembers (response);
+}
+
+static void
+translateCalled (UA_Client *client, void *userdata, UA_UInt32 requestId,
+                 UA_TranslateBrowsePathsToNodeIdsResponse *response) {
+    printf ("%-50s%i\n", "Translated path for request ", requestId);
+
+    if (response->results[0].targetsSize == 1) {
+        return;
+    }
+    UA_TranslateBrowsePathsToNodeIdsResponse_deleteMembers (response);
+}
+#endif
+
+int
+main (int argc, char *argv[]) {
+    UA_Client *client = UA_Client_new (UA_ClientConfig_default);
+    UA_UInt32 reqId = 0;
+    UA_String userdata = UA_STRING ("userdata");
+
+    UA_BrowseRequest bReq;
+    UA_BrowseRequest_init (&bReq);
+    bReq.requestedMaxReferencesPerNode = 0;
+    bReq.nodesToBrowse = UA_BrowseDescription_new ();
+    bReq.nodesToBrowseSize = 1;
+    bReq.nodesToBrowse[0].nodeId = UA_NODEID_NUMERIC (0,
+    UA_NS0ID_OBJECTSFOLDER); /* browse objects folder */
+    bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
+
+    UA_Client_connect_async (client, "opc.tcp://localhost:4840", onConnect,
+                             NULL);
+
+    /*Windows needs time to response*/
+    UA_sleep_ms(100);
+
+    /* What happens if client tries to send request before connected? */
+    UA_Client_sendAsyncBrowseRequest (client, &bReq, fileBrowsed, &userdata,
+                                      &reqId);
+
+    UA_DateTime startTime = UA_DateTime_nowMonotonic();
+    do {
+        /*TODO: fix memory-related bugs if condition not checked*/
+        if (UA_Client_getState (client) == UA_CLIENTSTATE_SESSION) {
+            /* If not connected requests are not sent */
+            UA_Client_sendAsyncBrowseRequest (client, &bReq, fileBrowsed,
+                                              &userdata, &reqId);
+        }
+        /* Requests are processed */
+        UA_BrowseRequest_deleteMembers(&bReq);
+        UA_Client_run_iterate (client, 0);
+        UA_sleep_ms(100);
+
+        /* Break loop if server cannot be connected within 2s -- prevents build timeout */
+        if (UA_DateTime_nowMonotonic() - startTime > 2000 * UA_DATETIME_MSEC)
+            break;
+    }
+    while (reqId < 10);
+
+    /* Demo: high-level functions */
+    UA_Int32 value = 0;
+    UA_Variant myVariant;
+    UA_Variant_init(&myVariant);
+
+    UA_Variant input;
+    UA_Variant_init (&input);
+
+    for (UA_UInt16 i = 0; i < 5; i++) {
+        if (UA_Client_getState (client) == UA_CLIENTSTATE_SESSION) {
+            /* writing and reading value 1 to 5 */
+            UA_Variant_setScalarCopy (&myVariant, &value, &UA_TYPES[UA_TYPES_INT32]);
+            value++;
+            UA_Client_writeValueAttribute_async(client,
+                                                UA_NODEID_STRING (1, "the.answer"),
+                                                &myVariant, attrWritten, NULL,
+                                                &reqId);
+            UA_Variant_deleteMembers (&myVariant);
+
+            UA_Client_readValueAttribute_async(client,
+                                               UA_NODEID_STRING (1, "the.answer"),
+                                               readValueAttributeCallback, NULL,
+                                               &reqId);
+
+//TODO: check the existance of the nodes inside these functions (otherwise seg faults)
+#ifdef NODES_EXIST
+            UA_String stringValue = UA_String_fromChars ("World");
+            UA_Variant_setScalar (&input, &stringValue, &UA_TYPES[UA_TYPES_STRING]);
+
+            UA_Client_call_async(client,
+                                 UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER),
+                                 UA_NODEID_NUMERIC (1, 62541), 1, &input,
+                                 methodCalled, NULL, &reqId);
+            UA_String_deleteMembers(&stringValue);
+
+    #define pathSize 3
+            char *paths[pathSize] = { "Server", "ServerStatus", "State" };
+            UA_UInt32 ids[pathSize] = { UA_NS0ID_ORGANIZES,
+            UA_NS0ID_HASCOMPONENT, UA_NS0ID_HASCOMPONENT };
+
+            UA_Cient_translateBrowsePathsToNodeIds_async (client, paths, ids,
+                                                          pathSize,
+                                                          translateCalled, NULL,
+                                                          &reqId);
+#endif
+            /* How often UA_Client_run_iterate is called depends on the number of request sent */
+            UA_Client_run_iterate(client, 0);
+            UA_Client_run_iterate(client, 0);
+        }
+    }
+    UA_Client_run_iterate (client, 0);
+
+    /* Async disconnect kills unprocessed requests */
+    // UA_Client_disconnect_async (client, &reqId); //can only be used when connected = true
+    // UA_Client_run_iterate (client, &timedOut);
+    UA_Client_disconnect(client);
+    UA_Client_delete (client);
+
+    return (int) UA_STATUSCODE_GOOD;
+}

+ 73 - 0
examples/client_connectivitycheck_loop.c

@@ -0,0 +1,73 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+/* Enable POSIX features */
+#if !defined(_XOPEN_SOURCE) && !defined(_WRS_KERNEL)
+# define _XOPEN_SOURCE 600
+#endif
+#ifndef _DEFAULT_SOURCE
+# define _DEFAULT_SOURCE
+#endif
+/* On older systems we need to define _BSD_SOURCE.
+ * _DEFAULT_SOURCE is an alias for that. */
+#ifndef _BSD_SOURCE
+# define _BSD_SOURCE
+#endif
+
+#include "open62541.h"
+#include <signal.h>
+
+#ifdef _WIN32
+# include <windows.h>
+# define UA_sleep_ms(X) Sleep(X)
+#else
+# include <unistd.h>
+# define UA_sleep_ms(X) usleep(X * 1000)
+#endif
+
+UA_Boolean running = true;
+UA_Logger logger = UA_Log_Stdout;
+
+static void stopHandler(int sign) {
+    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "Received Ctrl-C");
+    running = 0;
+}
+
+static void
+inactivityCallback (UA_Client *client) {
+    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "Server Inactivity");
+}
+
+int main(void) {
+    signal(SIGINT, stopHandler); /* catches ctrl-c */
+
+    UA_ClientConfig config = UA_ClientConfig_default;
+    /* Set stateCallback */
+    config.inactivityCallback = inactivityCallback;
+
+    /* Perform a connectivity check every 2 seconds */
+    config.connectivityCheckInterval = 2000;
+
+    UA_Client *client = UA_Client_new(config);
+
+    /* Endless loop runAsync */
+    while (running) {
+        /* if already connected, this will return GOOD and do nothing */
+        /* if the connection is closed/errored, the connection will be reset and then reconnected */
+        /* Alternatively you can also use UA_Client_getState to get the current state */
+        UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
+        if(retval != UA_STATUSCODE_GOOD) {
+            UA_LOG_ERROR(logger, UA_LOGCATEGORY_USERLAND, "Not connected. Retrying to connect in 1 second");
+            /* The connect may timeout after 1 second (see above) or it may fail immediately on network errors */
+            /* E.g. name resolution errors or unreachable network. Thus there should be a small sleep here */
+            UA_sleep_ms(1000);
+            continue;
+        }
+
+        UA_Client_run_iterate(client, 1000);
+    };
+
+    /* Clean up */
+    UA_Client_delete(client); /* Disconnects the client internally */
+    return UA_STATUSCODE_GOOD;
+}

+ 7 - 1
examples/client_subscription_loop.c

@@ -49,6 +49,9 @@ stateCallback (UA_Client *client, UA_ClientState clientState) {
         case UA_CLIENTSTATE_DISCONNECTED:
             UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "The client is disconnected");
         break;
+        case UA_CLIENTSTATE_WAITING_FOR_ACK:
+            UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "Waiting for ack");
+        break;
         case UA_CLIENTSTATE_CONNECTED:
             UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "A TCP connection to the server is open");
         break;
@@ -84,6 +87,9 @@ stateCallback (UA_Client *client, UA_ClientState clientState) {
             UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "A session with the server is open (renewed)");
             /* The session was renewed. We don't need to recreate the subscription. */
         break;
+        case UA_CLIENTSTATE_SESSION_DISCONNECTED:
+            UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "Session disconnected");
+        break;
     }
     return;
 }
@@ -112,7 +118,7 @@ int main(void) {
             continue;
         }
 
-        UA_Client_runAsync(client, 1000);
+        UA_Client_run_iterate(client, 1000);
     };
 
     /* Clean up */

+ 1 - 0
examples/discovery/client_find_servers.c

@@ -136,6 +136,7 @@ int main(void) {
 
         UA_EndpointDescription *endpointArray = NULL;
         size_t endpointArraySize = 0;
+        //TODO: adapt to the new async getEndpoint
         retval = UA_Client_getEndpoints(client, discoveryUrl, &endpointArraySize, &endpointArray);
         UA_free(discoveryUrl);
         if(retval != UA_STATUSCODE_GOOD) {

+ 124 - 27
examples/pubsub/tutorial_pubsub_publish.c

@@ -1,20 +1,34 @@
 /* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
  * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
 
+/**
+ * .. _pubsub-tutorial:
+ *
+ * Working with Publish/Subscribe
+ * ------------------------------
+ *
+ * Work in progress:
+ * This Tutorial will be continuously extended during the next PubSub batches. More details about
+ * the PubSub extension and corresponding open62541 API are located here: :ref:`pubsub`.
+ *
+ * Publishing Fields
+ * ^^^^^^^^^^^^^^^^^
+ * The PubSub publish example demonstrate the simplest way to publish
+ * informations from the information model over UDP multicast using
+ * the UADP encoding.
+ *
+ * **Connection handling**
+ * PubSubConnections can be created and deleted on runtime. More details about the system preconfiguration and
+ * connection can be found in ``tutorial_pubsub_connection.c``.
+ */
 #include <signal.h>
 #include "open62541.h"
+UA_NodeId connectionIdent, publishedDataSetIdent, writerGroupIdent;
 
-/* Work in progress: This Tutorial/Example will be continuously extended during the next PubSub batches */
-
-UA_Boolean running = true;
-static void stopHandler(int sign) {
-    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
-    running = false;
-}
-
-static UA_StatusCode
+static void
 addPubSubConnection(UA_Server *server){
-    /* Details about the connection configuration and handling are located in the pubsub connection tutorial */
+    /* Details about the connection configuration and handling are located
+     * in the pubsub connection tutorial */
     UA_PubSubConnectionConfig connectionConfig;
     memset(&connectionConfig, 0, sizeof(connectionConfig));
     connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
@@ -23,15 +37,104 @@ addPubSubConnection(UA_Server *server){
     UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")};
     UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
     connectionConfig.publisherId.numeric = UA_UInt32_random();
-    UA_NodeId connectionIdentifier;
-    UA_StatusCode retval = UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
-    return retval;
+    UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
 }
 
 /**
- * The PubSub publish example demonstrate the simplest way to publish
- * informations from the information model over UDP Multicast.
+ * **PublishedDataSet handling**
+ * The PublishedDataSet (PDS) and PubSubConnection are the toplevel entities and can exist alone. The PDS contains
+ * the collection of the published fields.
+ * All other PubSub elements are directly or indirectly linked with the PDS or connection.
+ */
+static void
+addPublishedDataSet(UA_Server *server) {
+    /* The PublishedDataSetConfig contains all necessary public
+    * informations for the creation of a new PublishedDataSet */
+    UA_PublishedDataSetConfig publishedDataSetConfig;
+    memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
+    publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
+    publishedDataSetConfig.name = UA_STRING("Demo PDS");
+    /* Create new PublishedDataSet based on the PublishedDataSetConfig. */
+    UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent);
+}
+
+/**
+ * **DataSetField handling**
+ * The DataSetField (DSF) is part of the PDS and describes exactly one published field.
+ */
+static void
+addDataSetField(UA_Server *server) {
+    /* Add a field to the previous created PublishedDataSet */
+    UA_NodeId dataSetFieldIdent;
+    UA_DataSetFieldConfig dataSetFieldConfig;
+    memset(&dataSetFieldConfig, 0, sizeof(UA_DataSetFieldConfig));
+    dataSetFieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
+    dataSetFieldConfig.field.variable.fieldNameAlias = UA_STRING("Server localtime");
+    dataSetFieldConfig.field.variable.promotedField = UA_FALSE;
+    dataSetFieldConfig.field.variable.publishParameters.publishedVariable =
+            UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_LOCALTIME);
+    dataSetFieldConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
+    UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldConfig, &dataSetFieldIdent);
+}
+
+/**
+ * **WriterGroup handling**
+ * The WriterGroup (WG) is part of the connection and contains the primary configuration
+ * parameters for the message creation.
+ */
+static void
+addWriterGroup(UA_Server *server) {
+    /* Now we create a new WriterGroupConfig and add the group to the existing PubSubConnection. */
+    UA_WriterGroupConfig writerGroupConfig;
+    memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
+    writerGroupConfig.name = UA_STRING("Demo WriterGroup");
+    writerGroupConfig.publishingInterval = 100;
+    writerGroupConfig.enabled = UA_FALSE;
+    writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
+    /* The configuration flags for the messages are encapsulated inside the
+     * message- and transport settings extension objects. These extension objects
+     * are defined by the standard. e.g. UadpWriterGroupMessageDataType */
+    UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent);
+}
+
+/**
+ * **DataSetWriter handling**
+ * A DataSetWriter (DSW) is the glue between the WG and the PDS. The DSW is linked to exactly one
+ * PDS and contains additional informations for the message generation.
  */
+static void
+addDataSetWriter(UA_Server *server) {
+    /* We need now a DataSetWriter within the WriterGroup. This means we must
+     * create a new DataSetWriterConfig and add call the addWriterGroup function. */
+    UA_NodeId dataSetWriterIdent;
+    UA_DataSetWriterConfig dataSetWriterConfig;
+    memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
+    dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
+    dataSetWriterConfig.dataSetWriterId = 62541;
+    dataSetWriterConfig.keyFrameCount = 10;
+    UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
+                               &dataSetWriterConfig, &dataSetWriterIdent);
+}
+
+/**
+ * That's it! You're now publishing the selected fields.
+ * Open a packet inspection tool of trust e.g. wireshark and take a look on the outgoing packages.
+ * The following graphic figures out the packages created by this tutorial.
+ *
+ * .. figure:: ua-wireshark-pubsub.png
+ *     :figwidth: 100 %
+ *     :alt: OPC UA PubSub communication in wireshark
+ *
+ * The open62541 subscriber API will be released later. If you want to process the the datagrams,
+ * take a look on the ua_network_pubsub_networkmessage.c which already contains the decoding code for UADP messages.
+ *
+ * It follows the main server code, making use of the above definitions. */
+UA_Boolean running = true;
+static void stopHandler(int sign) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
+    running = false;
+}
+
 int main(void) {
     signal(SIGINT, stopHandler);
     signal(SIGTERM, stopHandler);
@@ -48,21 +151,15 @@ int main(void) {
     config->pubsubTransportLayersSize++;
     UA_Server *server = UA_Server_new(config);
 
-    if(addPubSubConnection(server) != UA_STATUSCODE_GOOD)
-        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub Connection creation failed!");
-
-    /* The PublishedDataSetConfig contains all necessary public informations for the creation of a new PublishedDataSet */
-    UA_PublishedDataSetConfig publishedDataSetConfig;
-    publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
-    publishedDataSetConfig.name = UA_STRING("Robot Axis");
-
-    /* Create new PublishedDataSet based on the PublishedDataSetConfig. */
-    UA_NodeId publishedDataSetIdent;
-    if(UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent).addResult == UA_STATUSCODE_GOOD)
-        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PubSub PublishedDataSet creation successful!");
+    addPubSubConnection(server);
+    addPublishedDataSet(server);
+    addDataSetField(server);
+    addWriterGroup(server);
+    addDataSetWriter(server);
 
     retval |= UA_Server_run(server, &running);
     UA_Server_delete(server);
     UA_ServerConfig_delete(config);
     return (int)retval;
 }
+

+ 1 - 1
examples/tutorial_client_events.c

@@ -158,7 +158,7 @@ int main(int argc, char *argv[]) {
     monId = result.monitoredItemId;
 
     while(running)
-        UA_Client_runAsync(client, 100);
+        UA_Client_run_iterate(client, 100);
 
     /* Delete the subscription */
  cleanup:

+ 10 - 10
examples/tutorial_datatypes.c

@@ -49,19 +49,19 @@ variables_basic(void) {
         return;
 
     /* Structured Type */
-    UA_CallRequest cr;
-    UA_init(&cr, &UA_TYPES[UA_TYPES_CALLREQUEST]); /* Generic method */
-    UA_CallRequest_init(&cr); /* Shorthand for the previous line */
+    UA_ReadRequest rr;
+    UA_init(&rr, &UA_TYPES[UA_TYPES_READREQUEST]); /* Generic method */
+    UA_ReadRequest_init(&rr); /* Shorthand for the previous line */
 
-    cr.requestHeader.timestamp = UA_DateTime_now(); /* Members of a structure */
+    rr.requestHeader.timestamp = UA_DateTime_now(); /* Members of a structure */
 
-    cr.methodsToCall = (UA_CallMethodRequest *)UA_Array_new(5, &UA_TYPES[UA_TYPES_CALLMETHODREQUEST]);
-    cr.methodsToCallSize = 5; /* Array size needs to be made known */
+    rr.nodesToRead = (UA_ReadValueId *)UA_Array_new(5, &UA_TYPES[UA_TYPES_READVALUEID]);
+    rr.nodesToReadSize = 5; /* Array size needs to be made known */
 
-    UA_CallRequest *cr2 = UA_CallRequest_new();
-    UA_copy(&cr, cr2, &UA_TYPES[UA_TYPES_CALLREQUEST]);
-    UA_CallRequest_deleteMembers(&cr);
-    UA_CallRequest_delete(cr2);
+    UA_ReadRequest *rr2 = UA_ReadRequest_new();
+    UA_copy(&rr, rr2, &UA_TYPES[UA_TYPES_READREQUEST]);
+    UA_ReadRequest_deleteMembers(&rr);
+    UA_ReadRequest_delete(rr2);
 }
 
 /**

+ 0 - 15
examples/tutorial_server_datasource.c

@@ -175,18 +175,3 @@ int main(void) {
     UA_ServerConfig_delete(config);
     return (int)retval;
 }
-
-/**
- * DataChange Notifications
- * ^^^^^^^^^^^^^^^^^^^^^^^^
- * A client that is interested in the current value of a variable does not need
- * to regularly poll the variable. Instead, he can use the Subscription
- * mechanism to be notified about changes.
- *
- * Within a Subscription, the client adds so-called MonitoredItems. A DataChange
- * MonitoredItem defines a node attribute (usually the value attribute) that is
- * monitored for changes. The server internally reads the value in the defined
- * interval and generates the appropriate notifications. The three ways of
- * updating node values discussed above are all usable in combination with
- * notifications. That is because notifications use the standard *Read* service
- * to look for value changes. */

+ 177 - 0
examples/tutorial_server_events.c

@@ -0,0 +1,177 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+/**
+ * Generating events
+ * -----------------
+ * To make sense of the many things going on in a server, monitoring items can be useful. Though in many cases, data
+ * change does not convey enough information to be the optimal solution. Events can be generated at any time,
+ * hold a lot of information and can be filtered so the client only receives the specific attributes he is interested in.
+ *
+ * Emitting events by calling methods
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * The following example will be based on the server method tutorial. We will be
+ * creating a method node which generates an event from the server node.
+ */
+
+#include <signal.h>
+#include "open62541.h"
+
+UA_Boolean running = true;
+static void stopHandler(int sig) {
+    running = false;
+}
+
+/** The event we want to generate should be very simple. Since the `BaseEventType` is abstract,
+ * we will have to create our own event type. `EventTypes` are saved internally as `ObjectTypes`,
+ * so add the type as you would a new `ObjectType`.
+ */
+
+static UA_NodeId eventType;
+
+static UA_StatusCode addNewEventType(UA_Server *server) {
+    UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default;
+    attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", "SimpleEventType");
+    attr.description = UA_LOCALIZEDTEXT_ALLOC("en-US", "The simple event type we created");
+
+    UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
+                                UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE),
+                                UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
+                                UA_QUALIFIEDNAME(0, "SimpleEventType"),
+                                attr, NULL, &eventType);
+    UA_LocalizedText_deleteMembers(&attr.displayName);
+    UA_LocalizedText_deleteMembers(&attr.description);
+    return UA_STATUSCODE_GOOD;
+}
+
+/** Setting up an event
+ * ^^^^^^^^^^^^^^^^^^^^^^
+ * In order to set up the event, we can first use ``UA_Server_createEvent`` to give us a node representation of the event.
+ * All we need for this is our `EventType`. Once we have our event node, which is saved internally as an `ObjectNode`,
+ * we can define the attributes the event has the same way we would define the attributes of an object node. It is not
+ * necessary to define the attributes `EventId`, `ReceiveTime`, `SourceNode` or `EventType` since these are set
+ * automatically by the server. In this example, we will only be setting `Severity` and `Message`.
+ */
+static UA_StatusCode setUpEvent(UA_Server *server, UA_NodeId *outId) {
+    UA_StatusCode retval;
+    retval = UA_Server_createEvent(server, eventType, outId);
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
+                       "createEvent failed. StatusCode %s", UA_StatusCode_name(retval));
+        return retval;
+    }
+
+    UA_Variant value;
+    UA_RelativePathElement rpe;
+    UA_RelativePathElement_init(&rpe);
+    rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY);
+    rpe.isInverse = false;
+    rpe.includeSubtypes = false;
+
+    UA_BrowsePath bp;
+    UA_BrowsePath_init(&bp);
+    bp.startingNode = *outId;
+    bp.relativePath.elementsSize = 1;
+    bp.relativePath.elements = &rpe;
+
+    /* severity */
+    rpe.targetName = UA_QUALIFIEDNAME(0, "Severity");
+    UA_BrowsePathResult bpr = UA_Server_translateBrowsePathToNodeIds(server, &bp);
+    if (bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
+        UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Event is missing severity attribute.\n");
+        return bpr.statusCode;
+    }
+    UA_UInt16 eventSeverity = 100;
+    UA_Variant_setScalar(&value, &eventSeverity, &UA_TYPES[UA_TYPES_UINT16]);
+    UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
+
+    UA_BrowsePathResult_deleteMembers(&bpr);
+
+    /* message */
+    rpe.targetName = UA_QUALIFIEDNAME(0, "Message");
+    bpr = UA_Server_translateBrowsePathToNodeIds(server, &bp);
+    if (bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
+        UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Event is missing message attribute.\n");
+        return bpr.statusCode;
+    }
+    UA_LocalizedText eventMessage = UA_LOCALIZEDTEXT("en-US", "An event has been generated.");
+    UA_Variant_setScalar(&value, &eventMessage, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
+    UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
+
+    UA_BrowsePathResult_deleteMembers(&bpr);
+
+    return UA_STATUSCODE_GOOD;
+}
+
+/** Triggering an event
+ * ^^^^^^^^^^^^^^^^^^^^
+ * First a node representing an event is generated using ``setUpEvent``. Once our event is good to go, we specify
+ * a node which emits the event - in this case the server node. We can use ``UA_Server_triggerEvent`` to trigger our
+ * event onto said node. Passing ``NULL`` as the second-last argument means we will not receive the `EventId`.
+ * The last boolean argument states whether the node should be deleted. */
+static UA_StatusCode
+generateEventMethodCallback(UA_Server *server,
+                         const UA_NodeId *sessionId, void *sessionHandle,
+                         const UA_NodeId *methodId, void *methodContext,
+                         const UA_NodeId *objectId, void *objectContext,
+                         size_t inputSize, const UA_Variant *input,
+                         size_t outputSize, UA_Variant *output) {
+    /* set up event */
+    UA_NodeId eventNodeId;
+    UA_StatusCode retval = setUpEvent(server, &eventNodeId);
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+                       "Creating event failed. StatusCode %s", UA_StatusCode_name(retval));
+        return retval;
+    }
+
+    retval = UA_Server_triggerEvent(server, eventNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), NULL, UA_TRUE);
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, 
+                       "Triggering event failed. StatusCode %s", UA_StatusCode_name(retval));
+        return retval;
+    }
+
+    return retval;
+}
+
+/** Now, all that is left to do is to create a method node which uses our callback. We do not
+ * require any input and as output we will be using the `EventId` we receive from ``triggerEvent``. The `EventId` is
+ * generated by the server internally and is a random unique ID which identifies that specific event.
+ *
+ * This method node will be added to a basic server setup.
+ */
+
+static void
+addGenerateEventMethod(UA_Server *server) {
+    UA_MethodAttributes generateAttr = UA_MethodAttributes_default;
+    generateAttr.description = UA_LOCALIZEDTEXT("en-US","Generate an event.");
+    generateAttr.displayName = UA_LOCALIZEDTEXT("en-US","Generate Event");
+    generateAttr.executable = true;
+    generateAttr.userExecutable = true;
+    UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1, 62541),
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_HASORDEREDCOMPONENT),
+                            UA_QUALIFIEDNAME(1, "Generate Event"),
+                            generateAttr, &generateEventMethodCallback,
+                            0, NULL, 0, NULL, NULL, NULL);
+}
+
+int main (void) {
+    /* default server values */
+    signal(SIGINT, stopHandler);
+    signal(SIGTERM, stopHandler);
+
+    UA_ServerConfig *config = UA_ServerConfig_new_default();
+    UA_Server *server = UA_Server_new(config);
+
+    addNewEventType(server);
+    addGenerateEventMethod(server);
+
+    /* return value */
+    UA_StatusCode retval = UA_Server_run(server, &running);
+    UA_Server_delete(server);
+    UA_ServerConfig_delete(config);
+
+    return (int) retval;
+}

+ 65 - 0
examples/tutorial_server_monitoreditems.c

@@ -0,0 +1,65 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+/**
+ * Observing Attributes with Local MonitoredItems
+ * ----------------------------------------------
+ *
+ * A client that is interested in the current value of a variable does not need
+ * to regularly poll the variable. Instead, he can use the Subscription
+ * mechanism to be notified about changes.
+ *
+ * So-called MonitoredItems define which values (node attributes) and events the
+ * client wants to monitor. Under the right conditions, a notification is
+ * created and added to the Subscription. The notifications currently in the
+ * queue are regularly send to the client.
+ *
+ * The local user can add MonitoredItems as well. Locally, the MonitoredItems to
+ * not go via a Subscription and each have an individual callback method and a
+ * context pointer.
+ */
+
+#include <signal.h>
+#include "open62541.h"
+
+static void
+dataChangeNotificationCallback(UA_Server *server, UA_UInt32 monitoredItemId,
+                               void *monitoredItemContext, const UA_NodeId *nodeId,
+                               void *nodeContext, UA_UInt32 attributeId,
+                               const UA_DataValue *value) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Received Notification");
+}
+
+static void
+addMonitoredItemToCurrentTimeVariable(UA_Server *server) {
+    UA_NodeId currentTimeNodeId =
+        UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
+    UA_MonitoredItemCreateRequest monRequest =
+        UA_MonitoredItemCreateRequest_default(currentTimeNodeId);
+    monRequest.requestedParameters.samplingInterval = 100.0; /* 100 ms interval */
+    UA_Server_createDataChangeMonitoredItem(server, UA_TIMESTAMPSTORETURN_SOURCE,
+                                            monRequest, NULL, dataChangeNotificationCallback);
+}
+
+/** It follows the main server code, making use of the above definitions. */
+
+UA_Boolean running = true;
+static void stopHandler(int sign) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
+    running = false;
+}
+
+int main(void) {
+    signal(SIGINT, stopHandler);
+    signal(SIGTERM, stopHandler);
+
+    UA_ServerConfig *config = UA_ServerConfig_new_default();
+    UA_Server *server = UA_Server_new(config);
+
+    addMonitoredItemToCurrentTimeVariable(server);
+
+    UA_StatusCode retval = UA_Server_run(server, &running);
+    UA_Server_delete(server);
+    UA_ServerConfig_delete(config);
+    return (int)retval;
+}

+ 1 - 1
examples/tutorial_server_variable.c

@@ -7,7 +7,7 @@
  *
  * This tutorial shows how to work with data types and how to add variable nodes
  * to a server. First, we add a new variable to the server. Take a look at the
- * definition of the ``UA_VariableAttrbitues`` structure to see the list of all
+ * definition of the ``UA_VariableAttributes`` structure to see the list of all
  * attributes defined for VariableNodes.
  *
  * Note that the default settings have the AccessLevel of the variable value as

+ 55 - 43
include/ua_client.h

@@ -10,7 +10,7 @@
  *    Copyright 2015 (c) Oleksiy Vasylyev
  *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
  *    Copyright 2017 (c) Mark Giraud, Fraunhofer IOSB
- *    Copyright 2018 (c) Thomas Stalder
+ *    Copyright 2018 (c) Thomas Stalder, Blue Time Concept SA
  *    Copyright 2018 (c) Kalycito Infotech Private Limited
  */
 
@@ -105,6 +105,11 @@ UA_Client_delete(UA_Client *client);
 UA_StatusCode UA_EXPORT
 UA_Client_connect(UA_Client *client, const char *endpointUrl);
 
+UA_StatusCode UA_EXPORT
+UA_Client_connect_async (UA_Client *client, const char *endpointUrl,
+                         UA_ClientAsyncServiceCallback callback,
+                         void *connected);
+
 /* Connect to the server without creating a session
  *
  * @param client to use
@@ -128,6 +133,9 @@ UA_Client_connect_username(UA_Client *client, const char *endpointUrl,
 UA_StatusCode UA_EXPORT
 UA_Client_disconnect(UA_Client *client);
 
+UA_StatusCode UA_EXPORT
+UA_Client_disconnect_async(UA_Client *client, UA_UInt32 *requestId);
+
 /* Close a connection to the selected server */
 UA_StatusCode UA_EXPORT
 UA_Client_close(UA_Client *client);
@@ -181,6 +189,7 @@ UA_Client_findServers(UA_Client *client, const char *serverUrl,
                       size_t *registeredServersSize,
                       UA_ApplicationDescription **registeredServers);
 
+#ifdef UA_ENABLE_DISCOVERY
 /* Get a list of all known server in the network. Only supported by LDS servers.
  *
  * @param client to use. Must be connected to the same endpoint given in
@@ -204,6 +213,7 @@ UA_Client_findServersOnNetwork(UA_Client *client, const char *serverUrl,
                                UA_UInt32 startingRecordId, UA_UInt32 maxRecordsToReturn,
                                size_t serverCapabilityFilterSize, UA_String *serverCapabilityFilter,
                                size_t *serverOnNetworkSize, UA_ServerOnNetwork **serverOnNetwork);
+#endif
 
 /**
  * .. _client-services:
@@ -373,13 +383,9 @@ UA_Client_Service_queryNext(UA_Client *client,
 /* Listen on the network and process arriving asynchronous responses in the
  * background. Internal housekeeping and subscription management is done as
  * well. */
-UA_StatusCode UA_EXPORT
-UA_Client_runAsync(UA_Client *client, UA_UInt16 timeout);
 
-typedef void
-(*UA_ClientAsyncServiceCallback)(UA_Client *client, void *userdata,
-                                 UA_UInt32 requestId, void *response,
-                                 const UA_DataType *responseType);
+/*UA_StatusCode UA_EXPORT
+UA_Client_runAsync(UA_Client *client, UA_UInt16 timeout);*/
 
 /* Use the type versions of this method. See below. However, the general
  * mechanism of async service calls is explained here.
@@ -393,6 +399,13 @@ typedef void
  * The statusCode received when the client is shutting down is
  * UA_STATUSCODE_BADSHUTDOWN.
  *
+ * The statusCode received when the client don't receive response
+ * after specified config->timeout (in ms) is
+ * UA_STATUSCODE_BADTIMEOUT.
+ *
+ * Instead, you can use __UA_Client_AsyncServiceEx to specify
+ * a custom timeout
+ *
  * The userdata and requestId arguments can be NULL. */
 UA_StatusCode UA_EXPORT
 __UA_Client_AsyncService(UA_Client *client, const void *request,
@@ -401,45 +414,44 @@ __UA_Client_AsyncService(UA_Client *client, const void *request,
                          const UA_DataType *responseType,
                          void *userdata, UA_UInt32 *requestId);
 
-static UA_INLINE UA_StatusCode
-UA_Client_AsyncService_read(UA_Client *client, const UA_ReadRequest *request,
-                            UA_ClientAsyncServiceCallback callback,
-                            void *userdata, UA_UInt32 *requestId) {
-    return __UA_Client_AsyncService(client, (const void*)request,
-                                    &UA_TYPES[UA_TYPES_READREQUEST], callback,
-                                    &UA_TYPES[UA_TYPES_READRESPONSE],
-                                    userdata, requestId);
-}
+/* For async connecting
+ * */
+UA_StatusCode UA_EXPORT
+UA_Client_sendAsyncRequest(UA_Client *client, const void *request,
+        const UA_DataType *requestType, UA_ClientAsyncServiceCallback callback,
+const UA_DataType *responseType, void *userdata, UA_UInt32 *requestId);
 
-static UA_INLINE UA_StatusCode
-UA_Client_AsyncService_write(UA_Client *client, const UA_WriteRequest *request,
-                             UA_ClientAsyncServiceCallback callback,
-                             void *userdata, UA_UInt32 *requestId) {
-    return __UA_Client_AsyncService(client, (const void*)request,
-                                    &UA_TYPES[UA_TYPES_WRITEREQUEST], callback, 
-                                    &UA_TYPES[UA_TYPES_WRITERESPONSE],
-                                    userdata, requestId);
-}
 
-static UA_INLINE UA_StatusCode
-UA_Client_AsyncService_call(UA_Client *client, const UA_CallRequest *request,
-                            UA_ClientAsyncServiceCallback callback,
-                            void *userdata, UA_UInt32 *requestId) {
-    return __UA_Client_AsyncService(client, (const void*)request,
-                                    &UA_TYPES[UA_TYPES_CALLREQUEST], callback,
-                                    &UA_TYPES[UA_TYPES_CALLRESPONSE],
-                                    userdata, requestId);
-}
+UA_StatusCode UA_EXPORT
+UA_Client_run_iterate(UA_Client *client, UA_UInt16 timeout);
+
+/* Use the type versions of this method. See below. However, the general
+ * mechanism of async service calls is explained here.
+ *
+ * We say that an async service call has been dispatched once this method
+ * returns UA_STATUSCODE_GOOD. If there is an error after an async service has
+ * been dispatched, the callback is called with an "empty" response where the
+ * statusCode has been set accordingly. This is also done if the client is
+ * shutting down and the list of dispatched async services is emptied.
+ *
+ * The statusCode received when the client is shutting down is
+ * UA_STATUSCODE_BADSHUTDOWN.
+ *
+ * The statusCode received when the client don't receive response
+ * after specified timeout (in ms) is
+ * UA_STATUSCODE_BADTIMEOUT.
+ *
+ * The timeout can be disabled by setting timeout to 0
+ *
+ * The userdata and requestId arguments can be NULL. */
+UA_StatusCode UA_EXPORT
+__UA_Client_AsyncServiceEx(UA_Client *client, const void *request,
+                           const UA_DataType *requestType,
+                           UA_ClientAsyncServiceCallback callback,
+                           const UA_DataType *responseType,
+                           void *userdata, UA_UInt32 *requestId,
+                           UA_UInt32 timeout);
 
-static UA_INLINE UA_StatusCode
-UA_Client_AsyncService_browse(UA_Client *client, const UA_BrowseRequest *request,
-                              UA_ClientAsyncServiceCallback callback,
-                              void *userdata, UA_UInt32 *requestId) {
-    return __UA_Client_AsyncService(client, (const void*)request,
-                                    &UA_TYPES[UA_TYPES_BROWSEREQUEST], callback,
-                                    &UA_TYPES[UA_TYPES_BROWSERESPONSE],
-                                    userdata, requestId);
-}
 
 /**
  * .. toctree::

+ 58 - 6
include/ua_client_config.h

@@ -3,6 +3,7 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  *    Copyright 2018 (c) Stefan Profanter, fortiss GmbH
+ *    Copyright 2018 (c) Thomas Stalder, Blue Time Concept SA
  */
 
 #ifndef UA_CLIENT_CONFIG_H
@@ -36,17 +37,37 @@ extern "C" {
  * The :ref:`tutorials` provide a good starting point for this. */
 
 typedef enum {
-    UA_CLIENTSTATE_DISCONNECTED,        /* The client is disconnected */
-    UA_CLIENTSTATE_CONNECTED,           /* A TCP connection to the server is open */
-    UA_CLIENTSTATE_SECURECHANNEL,       /* A SecureChannel to the server is open */
-    UA_CLIENTSTATE_SESSION,             /* A session with the server is open */
-    UA_CLIENTSTATE_SESSION_RENEWED      /* A session with the server is open (renewed) */
+    UA_CLIENTSTATE_DISCONNECTED,         /* The client is disconnected */
+    UA_CLIENTSTATE_WAITING_FOR_ACK,      /* The Client has sent HEL and waiting */
+    UA_CLIENTSTATE_CONNECTED,            /* A TCP connection to the server is open */
+    UA_CLIENTSTATE_SECURECHANNEL,        /* A SecureChannel to the server is open */
+    UA_CLIENTSTATE_SESSION,              /* A session with the server is open */
+    UA_CLIENTSTATE_SESSION_DISCONNECTED, /* Disconnected vs renewed? */
+    UA_CLIENTSTATE_SESSION_RENEWED       /* A session with the server is open (renewed) */
 } UA_ClientState;
 
 
 struct UA_Client;
 typedef struct UA_Client UA_Client;
 
+typedef void (*UA_ClientAsyncServiceCallback)(UA_Client *client, void *userdata,
+        UA_UInt32 requestId, void *response);
+/*
+ * Repeated Callbacks
+ * ------------------ */
+typedef UA_StatusCode (*UA_ClientCallback)(UA_Client *client, void *data);
+
+UA_StatusCode
+UA_Client_addRepeatedCallback(UA_Client *Client, UA_ClientCallback callback,
+        void *data, UA_UInt32 interval, UA_UInt64 *callbackId);
+
+UA_StatusCode
+UA_Client_changeRepeatedCallbackInterval(UA_Client *Client,
+        UA_UInt64 callbackId, UA_UInt32 interval);
+
+UA_StatusCode UA_Client_removeRepeatedCallback(UA_Client *Client,
+        UA_UInt64 callbackId);
+
 /**
  * Client Lifecycle callback
  * ^^^^^^^^^^^^^^^^^^^^^^^^^ */
@@ -61,17 +82,25 @@ typedef void (*UA_ClientStateCallback)(UA_Client *client, UA_ClientState clientS
 typedef void (*UA_SubscriptionInactivityCallback)(UA_Client *client, UA_UInt32 subscriptionId, void *subContext);
 #endif
 
+/**
+ * Inactivity callback
+ * ^^^^^^^^^^^^^^^^^^^ */
+
+typedef void (*UA_InactivityCallback)(UA_Client *client);
+
 /**
  * Client Configuration Data
  * ^^^^^^^^^^^^^^^^^^^^^^^^^ */
 
 typedef struct UA_ClientConfig {
-    UA_UInt32 timeout;               /* Sync response timeout in ms */
+    UA_UInt32 timeout;               /* ASync + Sync response timeout in ms */
     UA_UInt32 secureChannelLifeTime; /* Lifetime in ms (then the channel needs
                                         to be renewed) */
     UA_Logger logger;
     UA_ConnectionConfig localConnectionConfig;
     UA_ConnectClientConnection connectionFunc;
+    UA_ConnectClientConnection initConnectionFunc;
+    UA_ClientCallback pollConnectionFunc;
 
     /* Custom DataTypes */
     size_t customDataTypesSize;
@@ -80,9 +109,28 @@ typedef struct UA_ClientConfig {
     /* Callback function */
     UA_ClientStateCallback stateCallback;
 #ifdef UA_ENABLE_SUBSCRIPTIONS
+    /**
+     * When outStandingPublishRequests is greater than 0,
+     * the server automatically create publishRequest when
+     * UA_Client_runAsync is called. If the client don't receive
+     * a publishResponse after :
+     *     (sub->publishingInterval * sub->maxKeepAliveCount) +
+     *     client->config.timeout)
+     * then, the client call subscriptionInactivityCallback
+     * The connection can be closed, this in an attempt to
+     * recreate a healthy connection. */
     UA_SubscriptionInactivityCallback subscriptionInactivityCallback;
 #endif
 
+    /** 
+     * When connectivityCheckInterval is greater than 0,
+     * every connectivityCheckInterval (in ms), a async read request
+     * is performed on the server. inactivityCallback is called
+     * when the client receive no response for this read request
+     * The connection can be closed, this in an attempt to
+     * recreate a healthy connection. */
+    UA_InactivityCallback inactivityCallback;
+
     void *clientContext;
 
 #ifdef UA_ENABLE_SUBSCRIPTIONS
@@ -90,6 +138,10 @@ typedef struct UA_ClientConfig {
     /* 0 = background task disabled                    */
     UA_UInt16 outStandingPublishRequests;
 #endif
+   /**
+     * connectivity check interval in ms
+     * 0 = background task disabled */
+    UA_UInt32 connectivityCheckInterval;
 } UA_ClientConfig;
 
 #ifdef __cplusplus

+ 628 - 0
include/ua_client_highlevel_async.h

@@ -0,0 +1,628 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#ifndef UA_CLIENT_HIGHLEVEL_ASYNC_H_
+#define UA_CLIENT_HIGHLEVEL_ASYNC_H_
+#include "ua_client.h"
+
+/*Raw Services
+ * ^^^^^^^^^^^^^^ */
+typedef void (*UA_ClientAsyncReadCallback)(UA_Client *client, void *userdata,
+		UA_UInt32 requestId, UA_ReadResponse *rr);
+static UA_INLINE UA_StatusCode UA_Client_sendAsyncReadRequest(UA_Client *client,
+		UA_ReadRequest *request, UA_ClientAsyncReadCallback readCallback,
+		void *userdata, UA_UInt32 *reqId) {
+	return UA_Client_sendAsyncRequest(client, request,
+			&UA_TYPES[UA_TYPES_READREQUEST],
+			(UA_ClientAsyncServiceCallback) readCallback,
+			&UA_TYPES[UA_TYPES_READRESPONSE], userdata, reqId);
+}
+
+typedef void (*UA_ClientAsyncWriteCallback)(UA_Client *client, void *userdata,
+		UA_UInt32 requestId, UA_WriteResponse *wr);
+static UA_INLINE UA_StatusCode UA_Client_sendAsyncWriteRequest(
+		UA_Client *client, UA_WriteRequest *request,
+		UA_ClientAsyncWriteCallback writeCallback, void *userdata,
+		UA_UInt32 *reqId) {
+	return UA_Client_sendAsyncRequest(client, request,
+			&UA_TYPES[UA_TYPES_WRITEREQUEST],
+			(UA_ClientAsyncServiceCallback) writeCallback,
+			&UA_TYPES[UA_TYPES_WRITERESPONSE], userdata, reqId);
+}
+
+typedef void (*UA_ClientAsyncBrowseCallback)(UA_Client *client, void *userdata,
+		UA_UInt32 requestId, UA_BrowseResponse *wr);
+static UA_INLINE UA_StatusCode UA_Client_sendAsyncBrowseRequest(
+		UA_Client *client, UA_BrowseRequest *request,
+		UA_ClientAsyncBrowseCallback browseCallback, void *userdata,
+		UA_UInt32 *reqId) {
+	return UA_Client_sendAsyncRequest(client, request,
+			&UA_TYPES[UA_TYPES_BROWSEREQUEST],
+			(UA_ClientAsyncServiceCallback) browseCallback,
+			&UA_TYPES[UA_TYPES_BROWSERESPONSE], userdata, reqId);
+}
+
+/**
+ * Read Attribute
+ * ^^^^^^^^^^^^^^ */
+UA_StatusCode UA_EXPORT
+__UA_Client_readAttribute_async(UA_Client *client, const UA_NodeId *nodeId,
+		UA_AttributeId attributeId, const UA_DataType *outDataType,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId);
+
+typedef void (*UA_ClientAsyncReadDataTypeAttributeCallback)(UA_Client *client,
+		void *userdata, UA_UInt32 requestId, UA_NodeId *var);
+static UA_INLINE UA_StatusCode UA_Client_readDataTypeAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadDataTypeAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_DATATYPE, &UA_TYPES[UA_TYPES_NODEID],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+
+typedef void (*UA_ClientAsyncReadValueAttributeCallback)(UA_Client *client,
+		void *userdata, UA_UInt32 requestId, UA_Variant *var);
+static UA_INLINE UA_StatusCode UA_Client_readValueAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadValueAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_VALUE, &UA_TYPES[UA_TYPES_VARIANT],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+
+typedef void (*UA_ClientAsyncReadNodeIdAttributeCallback)(UA_Client *client,
+		void *userdata, UA_UInt32 requestId, UA_NodeId *out);
+static UA_INLINE UA_StatusCode UA_Client_readNodeIdAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadNodeIdAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_NODEID, &UA_TYPES[UA_TYPES_NODEID],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadNodeClassAttributeCallback)(UA_Client *client,
+		void *userdata, UA_UInt32 requestId, UA_NodeClass *out);
+static UA_INLINE UA_StatusCode UA_Client_readNodeClassAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadNodeClassAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_NODECLASS, &UA_TYPES[UA_TYPES_NODECLASS],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadBrowseNameAttributeCallback)(UA_Client *client,
+		void *userdata, UA_UInt32 requestId, UA_QualifiedName *out);
+static UA_INLINE UA_StatusCode UA_Client_readBrowseNameAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadBrowseNameAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_BROWSENAME, &UA_TYPES[UA_TYPES_QUALIFIEDNAME],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadDisplayNameAttributeCallback)(
+		UA_Client *client, void *userdata, UA_UInt32 requestId,
+		UA_LocalizedText *out);
+static UA_INLINE UA_StatusCode UA_Client_readDisplayNameAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadDisplayNameAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_DISPLAYNAME, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadDescriptionAttributeCallback)(
+		UA_Client *client, void *userdata, UA_UInt32 requestId,
+		UA_LocalizedText *out);
+static UA_INLINE UA_StatusCode UA_Client_readDescriptionAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadDescriptionAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_DESCRIPTION, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadWriteMaskAttributeCallback)(UA_Client *client,
+		void *userdata, UA_UInt32 requestId, UA_UInt32 *out);
+static UA_INLINE UA_StatusCode UA_Client_readWriteMaskAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadWriteMaskAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_WRITEMASK, &UA_TYPES[UA_TYPES_UINT32],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadUserWriteMaskAttributeCallback)(
+		UA_Client *client, void *userdata, UA_UInt32 requestId, UA_UInt32 *out);
+static UA_INLINE UA_StatusCode UA_Client_readUserWriteMaskAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadUserWriteMaskAttributeCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_USERWRITEMASK, &UA_TYPES[UA_TYPES_UINT32],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadIsAbstractAttributeCallback)(UA_Client *client,
+		void *userdata, UA_UInt32 requestId, UA_Boolean *out);
+static UA_INLINE UA_StatusCode UA_Client_readIsAbstractAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadIsAbstractAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_ISABSTRACT, &UA_TYPES[UA_TYPES_BOOLEAN],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadSymmetricAttributeCallback)(UA_Client *client,
+		void *userdata, UA_UInt32 requestId, UA_Boolean *out);
+static UA_INLINE UA_StatusCode UA_Client_readSymmetricAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadSymmetricAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_SYMMETRIC, &UA_TYPES[UA_TYPES_BOOLEAN],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadInverseNameAttributeCallback)(
+		UA_Client *client, void *userdata, UA_UInt32 requestId,
+		UA_LocalizedText *out);
+static UA_INLINE UA_StatusCode UA_Client_readInverseNameAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadInverseNameAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_INVERSENAME, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadContainsNoLoopsAttributeCallback)(
+		UA_Client *client, void *userdata, UA_UInt32 requestId,
+		UA_Boolean *out);
+static UA_INLINE UA_StatusCode UA_Client_readContainsNoLoopsAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadContainsNoLoopsAttributeCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_CONTAINSNOLOOPS, &UA_TYPES[UA_TYPES_BOOLEAN],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadEventNotifierAttributeCallback)(
+		UA_Client *client, void *userdata, UA_UInt32 requestId, UA_Byte *out);
+static UA_INLINE UA_StatusCode UA_Client_readEventNotifierAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadEventNotifierAttributeCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_EVENTNOTIFIER, &UA_TYPES[UA_TYPES_BYTE],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadValueRankAttributeCallback)(UA_Client *client,
+		void *userdata, UA_UInt32 requestId, UA_Int32 *out);
+static UA_INLINE UA_StatusCode UA_Client_readValueRankAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadValueRankAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_VALUERANK, &UA_TYPES[UA_TYPES_INT32],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadAccessLevelAttributeCallback)(
+		UA_Client *client, void *userdata, UA_UInt32 requestId, UA_Byte *out);
+static UA_INLINE UA_StatusCode UA_Client_readAccessLevelAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadAccessLevelAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_ACCESSLEVEL, &UA_TYPES[UA_TYPES_BYTE],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadUserAccessLevelAttributeCallback)(
+		UA_Client *client, void *userdata, UA_UInt32 requestId, UA_Byte *out);
+static UA_INLINE UA_StatusCode UA_Client_readUserAccessLevelAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadUserAccessLevelAttributeCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_USERACCESSLEVEL, &UA_TYPES[UA_TYPES_BYTE],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadMinimumSamplingIntervalAttributeCallback)(
+		UA_Client *client, void *userdata, UA_UInt32 requestId, UA_Double *out);
+static UA_INLINE UA_StatusCode UA_Client_readMinimumSamplingIntervalAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadMinimumSamplingIntervalAttributeCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL, &UA_TYPES[UA_TYPES_DOUBLE],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadHistorizingAttributeCallback)(
+		UA_Client *client, void *userdata, UA_UInt32 requestId,
+		UA_Boolean *out);
+static UA_INLINE UA_StatusCode UA_Client_readHistorizingAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadHistorizingAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_HISTORIZING, &UA_TYPES[UA_TYPES_BOOLEAN],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadExecutableAttributeCallback)(UA_Client *client,
+		void *userdata, UA_UInt32 requestId, UA_Boolean *out);
+static UA_INLINE UA_StatusCode UA_Client_readExecutableAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadExecutableAttributeCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_EXECUTABLE, &UA_TYPES[UA_TYPES_BOOLEAN],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+typedef void (*UA_ClientAsyncReadUserExecutableAttributeCallback)(
+		UA_Client *client, void *userdata, UA_UInt32 requestId,
+		UA_Boolean *out);
+static UA_INLINE UA_StatusCode UA_Client_readUserExecutableAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		UA_ClientAsyncReadUserExecutableAttributeCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_readAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_USEREXECUTABLE, &UA_TYPES[UA_TYPES_BOOLEAN],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+/**
+ * Write Attribute
+ * ^^^^^^^^^^^^^^ */
+
+UA_StatusCode UA_EXPORT
+__UA_Client_writeAttribute_async(UA_Client *client, const UA_NodeId *nodeId,
+		UA_AttributeId attributeId, const void *in,
+		const UA_DataType *inDataType, UA_ClientAsyncServiceCallback callback,
+		void *userdata, UA_UInt32 *reqId);
+
+static UA_INLINE UA_StatusCode UA_Client_writeValueAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId, const UA_Variant *newValue,
+		UA_ClientAsyncWriteCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_VALUE, newValue, &UA_TYPES[UA_TYPES_VARIANT],
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+
+static UA_INLINE UA_StatusCode UA_Client_writeNodeIdAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId, const UA_NodeId *outNodeId,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_NODEID, outNodeId, &UA_TYPES[UA_TYPES_NODEID],
+			callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeNodeClassAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_NodeClass *outNodeClass,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_NODECLASS, outNodeClass,
+			&UA_TYPES[UA_TYPES_NODECLASS], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeBrowseNameAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_QualifiedName *outBrowseName,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_BROWSENAME, outBrowseName,
+			&UA_TYPES[UA_TYPES_QUALIFIEDNAME], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeDisplayNameAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_LocalizedText *outDisplayName,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_DISPLAYNAME, outDisplayName,
+			&UA_TYPES[UA_TYPES_LOCALIZEDTEXT], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeDescriptionAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_LocalizedText *outDescription,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_DESCRIPTION, outDescription,
+			&UA_TYPES[UA_TYPES_LOCALIZEDTEXT], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeWriteMaskAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_UInt32 *outWriteMask, UA_ClientAsyncServiceCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_WRITEMASK, outWriteMask, &UA_TYPES[UA_TYPES_UINT32],
+			callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeUserWriteMaskAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_UInt32 *outUserWriteMask,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_USERWRITEMASK, outUserWriteMask,
+			&UA_TYPES[UA_TYPES_UINT32], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeIsAbstractAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_Boolean *outIsAbstract, UA_ClientAsyncServiceCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_ISABSTRACT, outIsAbstract,
+			&UA_TYPES[UA_TYPES_BOOLEAN], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeSymmetricAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_Boolean *outSymmetric, UA_ClientAsyncServiceCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_SYMMETRIC, outSymmetric, &UA_TYPES[UA_TYPES_BOOLEAN],
+			callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeInverseNameAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_LocalizedText *outInverseName,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_INVERSENAME, outInverseName,
+			&UA_TYPES[UA_TYPES_LOCALIZEDTEXT], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeContainsNoLoopsAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_Boolean *outContainsNoLoops,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_CONTAINSNOLOOPS, outContainsNoLoops,
+			&UA_TYPES[UA_TYPES_BOOLEAN], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeEventNotifierAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_Byte *outEventNotifier, UA_ClientAsyncServiceCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_EVENTNOTIFIER, outEventNotifier,
+			&UA_TYPES[UA_TYPES_BYTE], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeDataTypeAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId, const UA_NodeId *outDataType,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_DATATYPE, outDataType, &UA_TYPES[UA_TYPES_NODEID],
+			callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeValueRankAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId, const UA_Int32 *outValueRank,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_VALUERANK, outValueRank, &UA_TYPES[UA_TYPES_INT32],
+			callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeAccessLevelAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_Byte *outAccessLevel, UA_ClientAsyncServiceCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_ACCESSLEVEL, outAccessLevel,
+			&UA_TYPES[UA_TYPES_BYTE], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeUserAccessLevelAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_Byte *outUserAccessLevel,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_USERACCESSLEVEL, outUserAccessLevel,
+			&UA_TYPES[UA_TYPES_BYTE], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeMinimumSamplingIntervalAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_Double *outMinimumSamplingInterval,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL, outMinimumSamplingInterval,
+			&UA_TYPES[UA_TYPES_DOUBLE], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeHistorizingAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_Boolean *outHistorizing,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_HISTORIZING, outHistorizing,
+			&UA_TYPES[UA_TYPES_BOOLEAN], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeExecutableAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_Boolean *outExecutable, UA_ClientAsyncServiceCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_EXECUTABLE, outExecutable,
+			&UA_TYPES[UA_TYPES_BOOLEAN], callback, userdata, reqId);
+}
+static UA_INLINE UA_StatusCode UA_Client_writeUserExecutableAttribute_async(
+		UA_Client *client, const UA_NodeId nodeId,
+		const UA_Boolean *outUserExecutable,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_writeAttribute_async(client, &nodeId,
+			UA_ATTRIBUTEID_USEREXECUTABLE, outUserExecutable,
+			&UA_TYPES[UA_TYPES_BOOLEAN], callback, userdata, reqId);
+}
+
+/**
+ * Method Calling
+ * ^^^^^^^^^^^^^^ */
+
+UA_StatusCode UA_EXPORT __UA_Client_call_async(UA_Client *client,
+		const UA_NodeId objectId, const UA_NodeId methodId, size_t inputSize,
+		const UA_Variant *input, UA_ClientAsyncServiceCallback callback,
+		void *userdata, UA_UInt32 *reqId);
+
+typedef void (*UA_ClientAsyncCallCallback)(UA_Client *client, void *userdata,
+		UA_UInt32 requestId, UA_CallResponse *cr);
+static UA_INLINE UA_StatusCode UA_Client_call_async(UA_Client *client,
+		const UA_NodeId objectId, const UA_NodeId methodId, size_t inputSize,
+		const UA_Variant *input, UA_ClientAsyncCallCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+
+	return __UA_Client_call_async(client, objectId, methodId, inputSize, input,
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+
+/*Node Management
+ * ^^^^^^^^^^^^^*/
+typedef void (*UA_ClientAsyncAddNodesCallback)(UA_Client *client,
+		void *userdata, UA_UInt32 requestId, UA_AddNodesResponse *ar);
+
+UA_StatusCode UA_EXPORT
+__UA_Client_addNode_async(UA_Client *client, const UA_NodeClass nodeClass,
+		const UA_NodeId requestedNewNodeId, const UA_NodeId parentNodeId,
+		const UA_NodeId referenceTypeId, const UA_QualifiedName browseName,
+		const UA_NodeId typeDefinition, const UA_NodeAttributes *attr,
+		const UA_DataType *attributeType, UA_NodeId *outNewNodeId,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId);
+
+static UA_INLINE UA_StatusCode UA_Client_addVariableNode_async(
+		UA_Client *client, const UA_NodeId requestedNewNodeId,
+		const UA_NodeId parentNodeId, const UA_NodeId referenceTypeId,
+		const UA_QualifiedName browseName, const UA_NodeId typeDefinition,
+		const UA_VariableAttributes attr, UA_NodeId *outNewNodeId,
+		UA_ClientAsyncAddNodesCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_addNode_async(client, UA_NODECLASS_VARIABLE,
+			requestedNewNodeId, parentNodeId, referenceTypeId, browseName,
+			typeDefinition, (const UA_NodeAttributes*) &attr,
+			&UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], outNewNodeId,
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+
+static UA_INLINE UA_StatusCode UA_Client_addVariableTypeNode_async(
+		UA_Client *client, const UA_NodeId requestedNewNodeId,
+		const UA_NodeId parentNodeId, const UA_NodeId referenceTypeId,
+		const UA_QualifiedName browseName, const UA_VariableTypeAttributes attr,
+		UA_NodeId *outNewNodeId, UA_ClientAsyncAddNodesCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_addNode_async(client, UA_NODECLASS_VARIABLETYPE,
+			requestedNewNodeId, parentNodeId, referenceTypeId, browseName,
+			UA_NODEID_NULL, (const UA_NodeAttributes*) &attr,
+			&UA_TYPES[UA_TYPES_VARIABLETYPEATTRIBUTES], outNewNodeId,
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+
+static UA_INLINE UA_StatusCode UA_Client_addObjectNode_async(UA_Client *client,
+		const UA_NodeId requestedNewNodeId, const UA_NodeId parentNodeId,
+		const UA_NodeId referenceTypeId, const UA_QualifiedName browseName,
+		const UA_NodeId typeDefinition, const UA_ObjectAttributes attr,
+		UA_NodeId *outNewNodeId, UA_ClientAsyncAddNodesCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_addNode_async(client, UA_NODECLASS_OBJECT,
+			requestedNewNodeId, parentNodeId, referenceTypeId, browseName,
+			typeDefinition, (const UA_NodeAttributes*) &attr,
+			&UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], outNewNodeId,
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+
+static UA_INLINE UA_StatusCode UA_Client_addObjectTypeNode_async(
+		UA_Client *client, const UA_NodeId requestedNewNodeId,
+		const UA_NodeId parentNodeId, const UA_NodeId referenceTypeId,
+		const UA_QualifiedName browseName, const UA_ObjectTypeAttributes attr,
+		UA_NodeId *outNewNodeId, UA_ClientAsyncAddNodesCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_addNode_async(client, UA_NODECLASS_OBJECTTYPE,
+			requestedNewNodeId, parentNodeId, referenceTypeId, browseName,
+			UA_NODEID_NULL, (const UA_NodeAttributes*) &attr,
+			&UA_TYPES[UA_TYPES_OBJECTTYPEATTRIBUTES], outNewNodeId,
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+
+static UA_INLINE UA_StatusCode UA_Client_addViewNode_async(UA_Client *client,
+		const UA_NodeId requestedNewNodeId, const UA_NodeId parentNodeId,
+		const UA_NodeId referenceTypeId, const UA_QualifiedName browseName,
+		const UA_ViewAttributes attr, UA_NodeId *outNewNodeId,
+		UA_ClientAsyncAddNodesCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_addNode_async(client, UA_NODECLASS_VIEW,
+			requestedNewNodeId, parentNodeId, referenceTypeId, browseName,
+			UA_NODEID_NULL, (const UA_NodeAttributes*) &attr,
+			&UA_TYPES[UA_TYPES_VIEWATTRIBUTES], outNewNodeId,
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+
+static UA_INLINE UA_StatusCode UA_Client_addReferenceTypeNode_async(
+		UA_Client *client, const UA_NodeId requestedNewNodeId,
+		const UA_NodeId parentNodeId, const UA_NodeId referenceTypeId,
+		const UA_QualifiedName browseName,
+		const UA_ReferenceTypeAttributes attr, UA_NodeId *outNewNodeId,
+		UA_ClientAsyncAddNodesCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_addNode_async(client, UA_NODECLASS_REFERENCETYPE,
+			requestedNewNodeId, parentNodeId, referenceTypeId, browseName,
+			UA_NODEID_NULL, (const UA_NodeAttributes*) &attr,
+			&UA_TYPES[UA_TYPES_REFERENCETYPEATTRIBUTES], outNewNodeId,
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+
+static UA_INLINE UA_StatusCode UA_Client_addDataTypeNode_async(
+		UA_Client *client, const UA_NodeId requestedNewNodeId,
+		const UA_NodeId parentNodeId, const UA_NodeId referenceTypeId,
+		const UA_QualifiedName browseName, const UA_DataTypeAttributes attr,
+		UA_NodeId *outNewNodeId, UA_ClientAsyncAddNodesCallback callback,
+		void *userdata, UA_UInt32 *reqId) {
+	return __UA_Client_addNode_async(client, UA_NODECLASS_DATATYPE,
+			requestedNewNodeId, parentNodeId, referenceTypeId, browseName,
+			UA_NODEID_NULL, (const UA_NodeAttributes*) &attr,
+			&UA_TYPES[UA_TYPES_DATATYPEATTRIBUTES], outNewNodeId,
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+
+static UA_INLINE UA_StatusCode UA_Client_addMethodNode_async(UA_Client *client,
+		const UA_NodeId requestedNewNodeId, const UA_NodeId parentNodeId,
+		const UA_NodeId referenceTypeId, const UA_QualifiedName browseName,
+		const UA_MethodAttributes attr, UA_NodeId *outNewNodeId,
+		UA_ClientAsyncAddNodesCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_addNode_async(client, UA_NODECLASS_METHOD,
+			requestedNewNodeId, parentNodeId, referenceTypeId, browseName,
+			UA_NODEID_NULL, (const UA_NodeAttributes*) &attr,
+			&UA_TYPES[UA_TYPES_METHODATTRIBUTES], outNewNodeId,
+			(UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+
+/**
+ * Misc Functionalities
+ * ^^^^^^^^^^^^^^ */
+
+UA_StatusCode UA_EXPORT __UA_Client_translateBrowsePathsToNodeIds_async(
+		UA_Client *client, char *paths[], UA_UInt32 ids[], size_t pathSize,
+		UA_ClientAsyncServiceCallback callback, void *userdata,
+		UA_UInt32 *reqId);
+
+typedef void (*UA_ClientAsyncTranslateCallback)(UA_Client *client,
+		void *userdata, UA_UInt32 requestId,
+		UA_TranslateBrowsePathsToNodeIdsResponse *tr);
+static UA_INLINE UA_StatusCode UA_Cient_translateBrowsePathsToNodeIds_async(
+		UA_Client *client, char **paths, UA_UInt32 *ids, size_t pathSize,
+		UA_ClientAsyncTranslateCallback callback, void *userdata,
+		UA_UInt32 *reqId) {
+	return __UA_Client_translateBrowsePathsToNodeIds_async(client, paths, ids,
+			pathSize, (UA_ClientAsyncServiceCallback) callback, userdata, reqId);
+}
+
+
+#endif /* UA_CLIENT_HIGHLEVEL_ASYNC_H_ */

+ 9 - 1
include/ua_config.h.in

@@ -22,13 +22,21 @@ extern "C" {
  * Feature Options
  * ---------------
  * Changing the feature options has no effect on a pre-compiled library. */
+
 #define UA_LOGLEVEL ${UA_LOGLEVEL}
 #cmakedefine UA_ENABLE_METHODCALLS
 #cmakedefine UA_ENABLE_NODEMANAGEMENT
 #cmakedefine UA_ENABLE_SUBSCRIPTIONS
-#cmakedefine UA_ENABLE_MULTITHREADING
 #cmakedefine UA_ENABLE_PUBSUB
 #cmakedefine UA_ENABLE_ENCRYPTION
+#cmakedefine UA_ENABLE_SUBSCRIPTIONS_EVENTS
+
+/* Multithreading */
+#cmakedefine UA_ENABLE_MULTITHREADING
+#cmakedefine UA_ENABLE_IMMUTABLE_NODES
+#if defined(UA_ENABLE_MULTITHREADING) && !defined(UA_ENABLE_IMMUTABLE_NODES)
+#error "The multithreading feature requires nodes to be immutable"
+#endif
 
 /* Advanced Options */
 #cmakedefine UA_ENABLE_STATUSCODE_DESCRIPTIONS

+ 2 - 1
include/ua_plugin_network.h

@@ -57,6 +57,7 @@ typedef enum {
                                 * is not done */
     UA_CONNECTION_ESTABLISHED  /* The socket is open and the connection
                                 * configured */
+
 } UA_ConnectionState;
 
 struct UA_Connection {
@@ -72,7 +73,7 @@ struct UA_Connection {
     void *handle;                    /* A pointer to internal data */
     UA_ByteString incompleteMessage; /* A half-received message (TCP is a
                                       * streaming protocol) is stored here */
-
+    UA_UInt64 connectCallbackID;     /* Callback Id, for the connect-loop */
     /* Get a buffer for sending */
     UA_StatusCode (*getSendBuffer)(UA_Connection *connection, size_t length,
                                    UA_ByteString *buf);

+ 17 - 0
include/ua_plugin_nodestore.h

@@ -22,6 +22,12 @@ extern "C" {
 #endif
 
 #include "ua_server.h"
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+/* forward declaration */
+struct UA_MonitoredItem;
+#endif
+
+#include "ua_types.h"
 
 /**
  * .. _information-modelling:
@@ -233,6 +239,14 @@ typedef struct {
     UA_MethodCallback method;
 } UA_MethodNode;
 
+
+/** Attributes for nodes which are capable of generating events */
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+/* Store active monitoredItems on this node */
+# define UA_EVENT_ATTRIBUTES                                         \
+    struct UA_MonitoredItem *monitoredItemQueue;
+#endif
+
 /**
  * ObjectNode
  * ----------
@@ -244,6 +258,9 @@ typedef struct {
 
 typedef struct {
     UA_NODE_BASEATTRIBUTES
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+    UA_EVENT_ATTRIBUTES
+#endif
     UA_Byte eventNotifier;
 } UA_ObjectNode;
 

+ 108 - 0
include/ua_server.h

@@ -757,6 +757,57 @@ UA_Server_setVariableNode_valueCallback(UA_Server *server,
                                         const UA_NodeId nodeId,
                                         const UA_ValueCallback callback);
 
+/**
+ * .. _local-monitoreditems:
+ *
+ * Local MonitoredItems
+ * ^^^^^^^^^^^^^^^^^^^^
+ *
+ * MonitoredItems are used with the Subscription mechanism of OPC UA to
+ * transported notifications for data changes and events. MonitoredItems can
+ * also be registered locally. Notifications are then forwarded to a
+ * user-defined callback instead of a remote client. */
+
+typedef void (*UA_Server_DataChangeNotificationCallback)
+    (UA_Server *server, UA_UInt32 monitoredItemId, void *monitoredItemContext,
+     const UA_NodeId *nodeId, void *nodeContext, UA_UInt32 attributeId,
+     const UA_DataValue *value);
+
+typedef void (*UA_Server_EventNotificationCallback)
+    (UA_Server *server, UA_UInt32 monId, void *monContext,
+     size_t nEventFields, const UA_Variant *eventFields);
+
+/* Create a local MonitoredItem with a sampling interval that detects data
+ * changes.
+ *
+ * @param server The server executing the MonitoredItem
+ * @timestampsToReturn Shall timestamps be added to the value for the callback?
+ * @item The parameters of the new MonitoredItem. Note that the attribute of the
+ *       ReadValueId (the node that is monitored) can not be
+ *       ``UA_ATTRIBUTEID_EVENTNOTIFIER``. A different callback type needs to be
+ *       registered for event notifications.
+ * @monitoredItemContext A pointer that is forwarded with the callback
+ * @callback The callback that is executed on detected data changes
+ *
+ * @return Returns a description of the created MonitoredItem. The structure
+ * also contains a StatusCode (in case of an error) and the identifier of the
+ * new MonitoredItem. */
+UA_MonitoredItemCreateResult UA_EXPORT
+UA_Server_createDataChangeMonitoredItem(UA_Server *server,
+          UA_TimestampsToReturn timestampsToReturn,
+          const UA_MonitoredItemCreateRequest item,
+          void *monitoredItemContext,
+          UA_Server_DataChangeNotificationCallback callback);
+
+/* UA_MonitoredItemCreateResult UA_EXPORT */
+/* UA_Server_createEventMonitoredItem(UA_Server *server, */
+/*           UA_TimestampsToReturn timestampsToReturn, */
+/*           const UA_MonitoredItemCreateRequest item, void *context, */
+/*           UA_Server_EventNotificationCallback callback); */
+
+UA_StatusCode UA_EXPORT
+UA_Server_deleteMonitoredItem(UA_Server *server, UA_UInt32 monitoredItemId);
+
 /**
  * Method Callbacks
  * ^^^^^^^^^^^^^^^^
@@ -955,6 +1006,8 @@ UA_Server_addDataSourceVariableNode(UA_Server *server,
                                     const UA_DataSource dataSource,
                                     void *nodeContext, UA_NodeId *outNewNodeId);
 
+#ifdef UA_ENABLE_METHODCALLS
+
 UA_StatusCode UA_EXPORT
 UA_Server_addMethodNodeEx(UA_Server *server, const UA_NodeId requestedNewNodeId,
                           const UA_NodeId parentNodeId,
@@ -984,6 +1037,8 @@ UA_Server_addMethodNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
                                      nodeContext, outNewNodeId);
 }
 
+#endif
+
 
 /**
  * The method pair UA_Server_addNode_begin and _finish splits the AddNodes
@@ -1031,12 +1086,16 @@ UA_Server_addNode_begin(UA_Server *server, const UA_NodeClass nodeClass,
 UA_StatusCode UA_EXPORT
 UA_Server_addNode_finish(UA_Server *server, const UA_NodeId nodeId);
 
+#ifdef UA_ENABLE_METHODCALLS
+
 UA_StatusCode UA_EXPORT
 UA_Server_addMethodNode_finish(UA_Server *server, const UA_NodeId nodeId,
                          UA_MethodCallback method,
                          size_t inputArgumentsSize, const UA_Argument* inputArguments,
                          size_t outputArgumentsSize, const UA_Argument* outputArguments);
 
+#endif
+
 /* Deletes a node and optionally all references leading to the node. */
 UA_StatusCode UA_EXPORT
 UA_Server_deleteNode(UA_Server *server, const UA_NodeId nodeId,
@@ -1056,6 +1115,55 @@ UA_Server_deleteReference(UA_Server *server, const UA_NodeId sourceNodeId,
                           const UA_ExpandedNodeId targetNodeId,
                           UA_Boolean deleteBidirectional);
 
+/**
+ * .. _events:
+ *
+ * Events
+ * ------
+ * The method ``UA_Server_createEvent`` creates an event and represents it as node. The node receives a unique `EventId`
+ * which is automatically added to the node.
+ * The method returns a `NodeId` to the object node which represents the event through ``outNodeId``. The `NodeId` can
+ * be used to set the attributes of the event. The generated `NodeId` is always numeric. ``outNodeId`` cannot be 
+ * ``NULL``.
+ *
+ * The method ``UA_Server_triggerEvent`` "triggers" an event by adding it to all monitored items of the specified
+ * origin node and those of all its parents. Any filters specified by the monitored items are automatically applied.
+ * Using this method deletes the node generated by ``UA_Server_createEvent``. The `EventId` for the new event is
+ * generated automatically and is returned through ``outEventId``.``NULL`` can be passed if the `EventId` is not
+ * needed. ``deleteEventNode`` specifies whether the node representation of the event should be deleted after invoking
+ * the method. This can be useful if events with the similar attributes are triggered frequently. ``UA_TRUE`` would
+ * cause the node to be deleted. */
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+
+/* The EventQueueOverflowEventType is defined as abstract, therefore we can not create an instance of that type
+ * directly, but need to create a subtype. The following is an arbitrary number which shall refer to our internal
+ * overflow type.
+ * This is already posted on the OPC Foundation bug tracker under the following link for clarification:
+ * https://opcfoundation-onlineapplications.org/mantis/view.php?id=4206 */
+# define UA_NS0ID_SIMPLEOVERFLOWEVENTTYPE 4035
+
+/* Creates a node representation of an event
+ *
+ * @param server The server object
+ * @param eventType The type of the event for which a node should be created
+ * @param outNodeId The NodeId of the newly created node for the event
+ * @return The StatusCode of the UA_Server_createEvent method */
+UA_StatusCode UA_EXPORT
+UA_Server_createEvent(UA_Server *server, const UA_NodeId eventType,
+                      UA_NodeId *outNodeId);
+
+/* Triggers a node representation of an event by applying EventFilters and adding the event to the appropriate queues.
+ * @param server The server object
+ * @param eventNodeId The NodeId of the node representation of the event which should be triggered
+ * @param outEvent the EventId of the new event
+ * @param deleteEventNode Specifies whether the node representation of the event should be deleted
+ * @return The StatusCode of the UA_Server_triggerEvent method */
+UA_StatusCode UA_EXPORT
+UA_Server_triggerEvent(UA_Server *server, const UA_NodeId eventNodeId, const UA_NodeId originId,
+                       UA_ByteString *outEventId, const UA_Boolean deleteEventNode);
+
+#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
+
 /**
  * Utility Functions
  * ----------------- */

+ 3 - 0
include/ua_server_config.h

@@ -143,6 +143,9 @@ struct UA_ServerConfig {
     UA_UInt32Range keepAliveCountLimits;
     UA_UInt32 maxNotificationsPerPublish;
     UA_UInt32 maxRetransmissionQueueSize; /* 0 -> unlimited size */
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+    UA_UInt32 maxEventsPerNode; /* 0 -> unlimited size */
+#endif
 
     /* Limits for MonitoredItems */
     UA_UInt32 maxMonitoredItemsPerSubscription;

+ 205 - 53
include/ua_server_pubsub.h

@@ -34,7 +34,7 @@ extern "C" {
  * 1. Create a configuration for the needed PubSub element.
  *
  * 2. Call the add[element] function and pass in the configuration.
- * 
+ *
  * 3. The add[element] function returns the unique nodeId of the internally created element.
  *
  * Take a look on the PubSub Tutorials for mor details about the API usage.::
@@ -46,20 +46,28 @@ extern "C" {
  *   |    |
  *   |    |
  *   |    |  +----------------------+
- *   |    +->| UA_PubSubConnections |  UA_Server_addPubSubConnection
+ *   |    +--> UA_PubSubConnection  |  UA_Server_addPubSubConnection
  *   |       +----------------------+
  *   |        |    |
- *   |        |    |    +-----------------------+
- *   |        |    +--->| UA_PubSubWriterGroups |
- *   |        |         +-----------------------+
- *   |        |
- *   |        |         +-----------------------+
- *   |        +-------->| UA_PubSubReaderGroups |
- *   |                  +-----------------------+
- *   |
- *   |       +---------------------------+
- *   +------>| UA_PubSubPublishedDataSet |  UA_Server_addPublishedDataSet
+ *   |        |    |    +----------------+
+ *   |        |    +----> UA_WriterGroup |  UA_PubSubConnection_addWriterGroup
+ *   |        |         +----------------+
+ *   |        |              |
+ *   |        |              |    +------------------+
+ *   |        |              +----> UA_DataSetWriter |  UA_WriterGroup_addDataSetWriter  +-+
+ *   |        |                   +------------------+                                     |
+ *   |        |                                                                            |
+ *   |        |         +----------------+                                                 | r
+ *   |        +---------> UA_ReaderGroup |                                                 | e
+ *   |                  +----------------+                                                 | f
+ *   |                                                                                     |
+ *   |       +---------------------------+                                                 |
+ *   +-------> UA_PubSubPublishedDataSet |  UA_Server_addPublishedDataSet                <-+
  *           +---------------------------+
+ *                 |
+ *                 |    +-----------------+
+ *                 +----> UA_DataSetField |  UA_PublishedDataSet_addDataSetField
+ *                      +-----------------+
  *
  * Connections
  * -----------
@@ -68,17 +76,15 @@ extern "C" {
  * different transport protocols at runtime.
  *
  * Take a look on the PubSub Tutorials for mor details about the API usage.
- * Connections
- * -----------
  */
 
-typedef struct{
+typedef struct {
     UA_String name;
     UA_Boolean enabled;
-    union{ //std: valid types UInt or String
+    union { /* std: valid types UInt or String */
         UA_UInt32 numeric;
         UA_String string;
-    }publisherId;
+    } publisherId;
     UA_String transportProfileUri;
     UA_Variant address;
     size_t connectionPropertiesSize;
@@ -86,38 +92,34 @@ typedef struct{
     UA_Variant connectionTransportSettings;
 } UA_PubSubConnectionConfig;
 
-/**
- * Connection handling
- * ^^^^^^^^^^^^^^^^^^^
- */
-
 UA_StatusCode
-UA_Server_addPubSubConnection(UA_Server *server, const UA_PubSubConnectionConfig *connectionConfig,
+UA_Server_addPubSubConnection(UA_Server *server,
+                              const UA_PubSubConnectionConfig *connectionConfig,
                               UA_NodeId *connectionIdentifier);
 
+/* Returns a deep copy of the config */
 UA_StatusCode
-UA_Server_removePubSubConnection(UA_Server *server, UA_NodeId connectionIdentifier);
+UA_Server_getPubSubConnectionConfig(UA_Server *server,
+                                    const UA_NodeId connection,
+                                    UA_PubSubConnectionConfig *config);
 
+/* Remove Connection, identified by the NodeId. Deletion of Connection
+ * removes all contained WriterGroups and Writers. */
 UA_StatusCode
-UA_PubSubConnection_getConfig(UA_Server *server, UA_NodeId connectionIdentifier,
-                              UA_PubSubConnectionConfig *config);
+UA_Server_removePubSubConnection(UA_Server *server, const UA_NodeId connection);
 
 /**
  * PublishedDataSets
  * -----------------
- * The PublishedDataSets (PDS) are containers for the published information. The PDS contain
- * the published variables and meta informations. The metainformations are commonly autogenerated
- * or given as constant argument as part of the template functions. The template functions are standard
- * defined and should only be used for configuration tools. You should normally
- * create a empty PDS and call the functions to add new fields.
- */
+ * The PublishedDataSets (PDS) are containers for the published information. The
+ * PDS contain the published variables and meta informations. The metadata is
+ * commonly autogenerated or given as constant argument as part of the template
+ * functions. The template functions are standard defined and intended for
+ * configuration tools. You should normally create a empty PDS and call the
+ * functions to add new fields. */
 
-typedef struct {
-    UA_StatusCode addResult;
-    size_t fieldAddResultsSize;
-    UA_StatusCode *fieldAddResults;
-    UA_ConfigurationVersionDataType configurationVersion;
-} UA_AddPublishedDataSetResult;
+/* The UA_PUBSUB_DATASET_PUBLISHEDITEMS has currently no additional members and
+ * thus no dedicated config structure. */
 
 typedef enum {
     UA_PUBSUB_DATASET_PUBLISHEDITEMS,
@@ -126,10 +128,6 @@ typedef enum {
     UA_PUBSUB_DATASET_PUBLISHEDEVENTS_TEMPLATE,
 } UA_PublishedDataSetType;
 
-/* The UA_PUBSUB_DATASET_PUBLISHEDITEMS has currently no additional members
- * and thus no dedicated config structure.
- */
-
 typedef struct {
     UA_DataSetMetaDataType metaData;
     size_t variablesToAddSize;
@@ -149,11 +147,11 @@ typedef struct {
     UA_ContentFilter filter;
 } UA_PublishedEventTemplateConfig;
 
-/* Configuration structure for PubSubDataSet */
+/* Configuration structure for PublishedDataSet */
 typedef struct {
     UA_String name;
     UA_PublishedDataSetType publishedDataSetType;
-    union{
+    union {
         /* The UA_PUBSUB_DATASET_PUBLISHEDITEMS has currently no additional members
          * and thus no dedicated config structure.*/
         UA_PublishedDataItemsTemplateConfig itemsTemplate;
@@ -162,25 +160,179 @@ typedef struct {
     } config;
 } UA_PublishedDataSetConfig;
 
-/**
- * PublishedDataSet handling
- * ^^^^^^^^^^^^^^^^^^^^^^^^^
- */
+void
+UA_PublishedDataSetConfig_deleteMembers(UA_PublishedDataSetConfig *pdsConfig);
+
+typedef struct {
+    UA_StatusCode addResult;
+    size_t fieldAddResultsSize;
+    UA_StatusCode *fieldAddResults;
+    UA_ConfigurationVersionDataType configurationVersion;
+} UA_AddPublishedDataSetResult;
 
 UA_AddPublishedDataSetResult
-UA_Server_addPublishedDataSet(UA_Server *server, const UA_PublishedDataSetConfig *publishedDataSetConfig,
+UA_Server_addPublishedDataSet(UA_Server *server,
+                              const UA_PublishedDataSetConfig *publishedDataSetConfig,
                               UA_NodeId *pdsIdentifier);
 
+/* Returns a deep copy of the config */
 UA_StatusCode
-UA_Server_removePublishedDataSet(UA_Server *server, UA_NodeId pdsIdentifier);
+UA_Server_getPublishedDataSetConfig(UA_Server *server, const UA_NodeId pds,
+                                    UA_PublishedDataSetConfig *config);
 
+/* Remove PublishedDataSet, identified by the NodeId. Deletion of PDS removes
+ * all contained and linked PDS Fields. Connected WriterGroups will be also
+ * removed. */
 UA_StatusCode
-UA_PublishedDataSet_getConfig(UA_Server *server, UA_NodeId publishedDataSetIdentifier,
-                              UA_PublishedDataSetConfig *config);
+UA_Server_removePublishedDataSet(UA_Server *server, const UA_NodeId pds);
+
+/**
+ * DataSetFields
+ * -------------
+ * The description of published variables is named DataSetField. Each
+ * DataSetField contains the selection of one information model node. The
+ * DataSetField has additional parameters for the publishing, sampling and error
+ * handling process. */
+
+typedef struct{
+    UA_ConfigurationVersionDataType configurationVersion;
+    UA_String fieldNameAlias;
+    UA_Boolean promotedField;
+    UA_PublishedVariableDataType publishParameters;
+} UA_DataSetVariableConfig;
+
+typedef enum {
+    UA_PUBSUB_DATASETFIELD_VARIABLE,
+    UA_PUBSUB_DATASETFIELD_EVENT
+} UA_DataSetFieldType;
+
+typedef struct {
+    UA_DataSetFieldType dataSetFieldType;
+    union {
+        UA_DataSetVariableConfig variable;
+        //events need other config later
+    } field;
+} UA_DataSetFieldConfig;
+    
 void
-UA_PublishedDataSetConfig_deleteMembers(UA_PublishedDataSetConfig *pdsConfig);
+UA_DataSetFieldConfig_deleteMembers(UA_DataSetFieldConfig *dataSetFieldConfig);
 
+typedef struct {
+    UA_StatusCode result;
+    UA_ConfigurationVersionDataType configurationVersion;
+} UA_DataSetFieldResult;
+
+UA_DataSetFieldResult
+UA_Server_addDataSetField(UA_Server *server,
+                          const UA_NodeId publishedDataSet,
+                          const UA_DataSetFieldConfig *fieldConfig,
+                          UA_NodeId *fieldIdentifier);
+
+/* Returns a deep copy of the config */
+UA_StatusCode
+UA_Server_getDataSetFieldConfig(UA_Server *server, const UA_NodeId dsf,
+                                UA_DataSetFieldConfig *config);
+
+UA_DataSetFieldResult
+UA_Server_removeDataSetField(UA_Server *server, const UA_NodeId dsf);
+
+/**
+ * WriterGroup
+ * -----------
+ * All WriterGroups are created within a PubSubConnection and automatically
+ * deleted if the connection is removed. The WriterGroup is primary used as
+ * container for :ref:`dsw` and network message settings. The WriterGroup can be
+ * imagined as producer of the network messages. The creation of network
+ * messages is controlled by parameters like the publish interval, which is e.g.
+ * contained in the WriterGroup. */
+
+typedef enum {
+    UA_PUBSUB_ENCODING_BINARY,
+    UA_PUBSUB_ENCODING_JSON,
+    UA_PUBSUB_ENCODING_UADP
+} UA_PubSubEncodingType;
+
+typedef struct {
+    UA_String name;
+    UA_Boolean enabled;
+    UA_UInt16 writerGroupId;
+    UA_Double publishingInterval;
+    UA_Double keepAliveTime;
+    UA_Byte priority;
+    UA_MessageSecurityMode securityMode;
+    UA_ExtensionObject transportSettings;
+    UA_ExtensionObject messageSettings;
+    size_t groupPropertiesSize;
+    UA_KeyValuePair *groupProperties;
+    UA_PubSubEncodingType encodingMimeType;
+
+    /* non std. config parameter. maximum count of embedded DataSetMessage in
+     * one NetworkMessage */
+    UA_UInt16 maxEncapsulatedDataSetMessageCount;
+} UA_WriterGroupConfig;
+
+void
+UA_WriterGroupConfig_deleteMembers(UA_WriterGroupConfig *writerGroupConfig);
+
+/* Add a new WriterGroup to an existing Connection */
+UA_StatusCode
+UA_Server_addWriterGroup(UA_Server *server, const UA_NodeId connection,
+                         const UA_WriterGroupConfig *writerGroupConfig,
+                         UA_NodeId *writerGroupIdentifier);
+
+/* Returns a deep copy of the config */
+UA_StatusCode
+UA_Server_getWriterGroupConfig(UA_Server *server, const UA_NodeId writerGroup,
+                               UA_WriterGroupConfig *config);
+
+UA_StatusCode
+UA_Server_removeWriterGroup(UA_Server *server, const UA_NodeId writerGroup);
 
+/**
+ * .. _dsw:
+ *
+ * DataSetWriter
+ * -------------
+ * The DataSetWriters are the glue between the WriterGroups and the
+ * PublishedDataSets. The DataSetWriter contain configuration parameters and
+ * flags which influence the creation of DataSet messages. These messages are
+ * encapsulated inside the network message. The DataSetWriter must be linked
+ * with an existing PublishedDataSet and be contained within a WriterGroup. */
+
+typedef struct {
+    UA_String name;
+    UA_UInt16 dataSetWriterId;
+    UA_DataSetFieldContentMask dataSetFieldContentMask;
+    UA_UInt32 keyFrameCount;
+    UA_ExtensionObject messageSettings;
+    UA_String dataSetName;
+    size_t dataSetWriterPropertiesSize;
+    UA_KeyValuePair *dataSetWriterProperties;
+} UA_DataSetWriterConfig;
+
+void
+UA_DataSetWriterConfig_deleteMembers(UA_DataSetWriterConfig *pdsConfig);
+
+/* Add a new DataSetWriter to a existing WriterGroup. The DataSetWriter must be
+ * coupled with a PublishedDataSet on creation.
+ *
+ * Part 14, 7.1.5.2.1 defines: The link between the PublishedDataSet and
+ * DataSetWriter shall be created when an instance of the DataSetWriterType is
+ * created. */
+UA_StatusCode
+UA_Server_addDataSetWriter(UA_Server *server,
+                           const UA_NodeId writerGroup, const UA_NodeId dataSet,
+                           const UA_DataSetWriterConfig *dataSetWriterConfig,
+                           UA_NodeId *writerIdentifier);
+
+/* Returns a deep copy of the config */
+UA_StatusCode
+UA_Server_getDataSetWriterConfig(UA_Server *server, const UA_NodeId dsw,
+                                 UA_DataSetWriterConfig *config);
+
+UA_StatusCode
+UA_Server_removeDataSetWriter(UA_Server *server, const UA_NodeId dsw);
+    
 #ifdef __cplusplus
 } // extern "C"
 #endif

+ 3 - 1
include/ua_types.h

@@ -10,7 +10,7 @@
  *    Copyright 2015 (c) Nick Goossens
  *    Copyright 2015-2016 (c) Oleksiy Vasylyev
  *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
- *    Copyright 2017 (c) Thomas Stalder
+ *    Copyright 2017 (c) Thomas Stalder, Blue Time Concept SA
  */
 
 #ifndef UA_TYPES_H_
@@ -814,6 +814,8 @@ struct UA_DataType {
     UA_DataTypeMember *members;
 };
 
+UA_Boolean isDataTypeNumeric(const UA_DataType *type);
+
 /* The following is used to exclude type names in the definition of UA_DataType
  * structures if the feature is disabled. */
 #ifdef UA_ENABLE_TYPENAMES

+ 13 - 9
plugins/ua_config_default.c

@@ -268,6 +268,9 @@ createDefaultConfig(void) {
     conf->keepAliveCountLimits = UA_UINT32RANGE(1, 100);
     conf->maxNotificationsPerPublish = 1000;
     conf->maxRetransmissionQueueSize = 0; /* unlimited */
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+    conf->maxEventsPerNode = 0; /* unlimited */
+#endif
 
     /* Limits for MonitoredItems */
     conf->samplingIntervalLimits = UA_DURATIONRANGE(50.0, 24.0 * 3600.0 * 1000.0);
@@ -645,19 +648,20 @@ const UA_ClientConfig UA_ClientConfig_default = {
         0, /* .maxMessageSize, 0 -> unlimited */
         0 /* .maxChunkCount, 0 -> unlimited */
     },
-    UA_ClientConnectionTCP, /* .connectionFunc */
-
+    UA_ClientConnectionTCP, /* .connectionFunc (for sync connection) */
+    UA_ClientConnectionTCP_init, /* .initConnectionFunc (for async client) */
+    UA_ClientConnectionTCP_poll, /* .pollConnectionFunc (for async connection) */
     0, /* .customDataTypesSize */
-    NULL, /*.customDataTypes */
+    NULL, /* .customDataTypes */
 
-    NULL, /*.stateCallback */
+    NULL, /* .stateCallback */
 #ifdef UA_ENABLE_SUBSCRIPTIONS
-    NULL, /*.subscriptionInactivityCallback */
+    NULL, /* .subscriptionInactivityCallback */
 #endif
-
-    NULL,  /*.clientContext */
-
+    NULL, /* .inactivityCallback */
+    NULL, /* .clientContext */
 #ifdef UA_ENABLE_SUBSCRIPTIONS
-    10 /* .outStandingPublishRequests */
+    10, /* .outStandingPublishRequests */
 #endif
+    0 /* .connectivityCheckInterval */
 };

+ 56 - 25
plugins/ua_nodestore_default.c

@@ -8,6 +8,8 @@
 
 #include "ua_nodestore_default.h"
 
+#include "../src/ua_util.h" /* TOOO: Move atomic operations to arch definitions */
+
 /* container_of */
 #define container_of(ptr, type, member) \
     (type *)((uintptr_t)ptr - offsetof(type,member))
@@ -26,7 +28,15 @@
  *
  * - Tombstone or non-matching NodeId: continue searching
  * - Matching NodeId: Return the entry
- * - NULL: Abort the search */
+ * - NULL: Abort the search
+ *
+ * The nodestore uses atomic operations to set entries of the hash-map. If
+ * UA_ENABLE_IMMUTABLE_NODES is configured, the nodestore allows read-access
+ * from an interrupt without seeing corrupted nodes. For true multi-threaded
+ * access, a mutex is used.
+ *
+ * Multi-threading without a mutex could be realized with the Linux RCU mechanism.
+ * But this is not done for this implementation of the nodestore. */
 
 typedef struct UA_NodeMapEntry {
     struct UA_NodeMapEntry *orig; /* the version this is a copy from (or NULL) */
@@ -195,9 +205,11 @@ cleanupEntry(UA_NodeMapEntry *entry) {
 
 static UA_StatusCode
 clearSlot(UA_NodeMap *ns, UA_NodeMapEntry **slot) {
-    (*slot)->deleted = true;
-    cleanupEntry(*slot);
-    *slot = UA_NODEMAP_TOMBSTONE;
+    UA_NodeMapEntry *entry = *slot;
+    if(UA_atomic_cmpxchg((void**)slot, entry, UA_NODEMAP_TOMBSTONE) != entry)
+        return UA_STATUSCODE_BADINTERNALERROR;
+    entry->deleted = true;
+    cleanupEntry(entry);
     --ns->count;
     /* Downsize the hashmap if it is very empty */
     if(ns->count * 8 < ns->size && ns->size > 32)
@@ -347,7 +359,7 @@ UA_NodeMap_insertNode(void *context, UA_Node *node,
         /* E.g. adding a nodeset will create children while there are still other nodes which need to be created */
         /* Thus the node ids may collide */
         UA_UInt32 size = ns->size;
-        UA_UInt64 identifier = mod(50000 + size+1, size); // start value, use 64 bit container to avoid overflow
+        UA_UInt64 identifier = mod(50000 + size+1, UA_UINT32_MAX); // start value, use 64 bit container to avoid overflow
         UA_UInt32 increase = mod2(ns->count+1, size);
         UA_UInt32 startId = (UA_UInt32)identifier; // mod ensures us that the id is a valid 32 bit
 
@@ -360,31 +372,38 @@ UA_NodeMap_insertNode(void *context, UA_Node *node,
             if(identifier >= size)
                 identifier -= size;
         } while((UA_UInt32)identifier != startId);
-        
-        if (!slot) {
-            END_CRITSECT(ns);
-            return UA_STATUSCODE_BADOUTOFMEMORY;
-        }
     } else {
         slot = findFreeSlot(ns, &node->nodeId);
-        if(!slot) {
-            deleteEntry(container_of(node, UA_NodeMapEntry, node));
-            END_CRITSECT(ns);
-            return UA_STATUSCODE_BADNODEIDEXISTS;
-        }
     }
 
-    *slot = container_of(node, UA_NodeMapEntry, node);
-    ++ns->count;
-    UA_assert(&(*slot)->node == node);
+    if(!slot) {
+        deleteEntry(container_of(node, UA_NodeMapEntry, node));
+        END_CRITSECT(ns);
+        return UA_STATUSCODE_BADNODEIDEXISTS;
+    }
 
+    /* Copy the NodeId */
     UA_StatusCode retval = UA_STATUSCODE_GOOD;
     if(addedNodeId) {
         retval = UA_NodeId_copy(&node->nodeId, addedNodeId);
-        if(retval != UA_STATUSCODE_GOOD)
-            clearSlot(ns, slot);
+        if(retval != UA_STATUSCODE_GOOD) {
+            deleteEntry(container_of(node, UA_NodeMapEntry, node));
+            END_CRITSECT(ns);
+            return retval;
+        }
     }
 
+    /* Insert the node */
+    UA_NodeMapEntry *oldEntry = *slot;
+    UA_NodeMapEntry *newEntry = container_of(node, UA_NodeMapEntry, node);
+    if(oldEntry > UA_NODEMAP_TOMBSTONE ||
+       UA_atomic_cmpxchg((void**)slot, oldEntry,
+                         newEntry) != oldEntry) {
+        deleteEntry(container_of(node, UA_NodeMapEntry, node));
+        END_CRITSECT(ns);
+        return UA_STATUSCODE_BADNODEIDEXISTS;
+    }
+    ++ns->count;
     END_CRITSECT(ns);
     return retval;
 }
@@ -393,21 +412,33 @@ static UA_StatusCode
 UA_NodeMap_replaceNode(void *context, UA_Node *node) {
     UA_NodeMap *ns = (UA_NodeMap*)context;
     BEGIN_CRITSECT(ns);
+
+    /* Find the node */
     UA_NodeMapEntry **slot = findOccupiedSlot(ns, &node->nodeId);
     if(!slot) {
         END_CRITSECT(ns);
         return UA_STATUSCODE_BADNODEIDUNKNOWN;
     }
     UA_NodeMapEntry *newEntryContainer = container_of(node, UA_NodeMapEntry, node);
-    if(*slot != newEntryContainer->orig) {
-        /* The node was updated since the copy was made */
+    UA_NodeMapEntry *oldEntryContainer = *slot;
+
+    /* The node was already updated since the copy was made? */
+    if(oldEntryContainer != newEntryContainer->orig) {
         deleteEntry(newEntryContainer);
         END_CRITSECT(ns);
         return UA_STATUSCODE_BADINTERNALERROR;
     }
-    (*slot)->deleted = true;
-    cleanupEntry(*slot);
-    *slot = newEntryContainer;
+
+    /* Replace the entry with an atomic operation */
+    if(UA_atomic_cmpxchg((void**)slot, oldEntryContainer,
+                         newEntryContainer) != oldEntryContainer) {
+        deleteEntry(newEntryContainer);
+        END_CRITSECT(ns);
+        return UA_STATUSCODE_BADINTERNALERROR;
+    }
+
+    oldEntryContainer->deleted = true;
+    cleanupEntry(oldEntryContainer);
     END_CRITSECT(ns);
     return UA_STATUSCODE_GOOD;
 }

+ 117 - 29
src/client/ua_client.c

@@ -43,6 +43,15 @@ UA_Client_init(UA_Client* client, UA_ClientConfig config) {
     client->config = config;
     if(client->config.stateCallback)
         client->config.stateCallback(client, client->state);
+    /* Catch error during async connection */
+    client->connectStatus = UA_STATUSCODE_GOOD;
+
+    /* Needed by async client */
+    UA_Timer_init(&client->timer);
+
+#ifndef UA_ENABLE_MULTITHREADING
+    SLIST_INIT(&client->delayedClientCallbacks);
+#endif
 }
 
 UA_Client *
@@ -108,6 +117,15 @@ UA_Client_secure_init(UA_Client* client, UA_ClientConfig config,
     if(client->config.stateCallback)
         client->config.stateCallback(client, client->state);
 
+    /* Catch error during async connection */
+    client->connectStatus = UA_STATUSCODE_GOOD;
+
+    /* Needed by async client */
+    UA_Timer_init(&client->timer);
+
+#ifndef UA_ENABLE_MULTITHREADING
+    SLIST_INIT(&client->delayedClientCallbacks);
+#endif
     /* Verify remote certificate if trust list given to the application */
     if(trustListSize > 0) {
         retval = client->channel.securityPolicy->certificateVerification->
@@ -195,6 +213,8 @@ UA_Client_deleteMembers(UA_Client* client) {
     /* Commented as UA_SecureChannel_deleteMembers already done
      * in UA_Client_disconnect function */
     //UA_SecureChannel_deleteMembersCleanup(&client->channel);
+    if (client->connection.free)
+        client->connection.free(&client->connection);
     UA_Connection_deleteMembers(&client->connection);
     if(client->endpointUrl.data)
         UA_String_deleteMembers(&client->endpointUrl);
@@ -212,6 +232,9 @@ UA_Client_deleteMembers(UA_Client* client) {
 #ifdef UA_ENABLE_SUBSCRIPTIONS
     UA_Client_Subscriptions_clean(client);
 #endif
+
+    /* Delete the timed work */
+    UA_Timer_deleteMembers(&client->timer);
 }
 
 void
@@ -282,6 +305,9 @@ sendSymmetricServiceRequest(UA_Client *client, const void *request,
     UA_UInt32 rqId = ++client->requestId;
     UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT,
                  "Sending a request of type %i", requestType->typeId.identifier.numeric);
+
+    if (client->channel.nextSecurityToken.tokenId != 0) // Change to the new security token if the secure channel has been renewed.
+        UA_SecureChannel_revolveTokens(&client->channel);
     retval = UA_SecureChannel_sendSymmetricMessage(&client->channel, rqId, UA_MESSAGETYPE_MSG,
                                                    rr, requestType);
     UA_NodeId_init(&rr->authenticationToken); /* Do not return the token to the user */
@@ -345,7 +371,7 @@ processAsyncResponse(UA_Client *client, UA_UInt32 requestId, const UA_NodeId *re
     }
 
     /* Call the callback */
-    ac->callback(client, ac->userdata, requestId, response, ac->responseType);
+    ac->callback(client, ac->userdata, requestId, response);
     UA_deleteMembers(response, ac->responseType);
 
     /* Remove the callback */
@@ -379,6 +405,9 @@ processServiceResponse(void *application, UA_SecureChannel *channel,
        < UA_DateTime_nowMonotonic())
         return UA_STATUSCODE_BADSECURECHANNELCLOSED;
 
+    /* Forward declaration for the goto */
+    UA_NodeId expectedNodeId = UA_NODEID_NULL;
+
     /* Decode the data type identifier of the response */
     size_t offset = 0;
     UA_NodeId responseId;
@@ -396,10 +425,8 @@ processServiceResponse(void *application, UA_SecureChannel *channel,
     /* Got the synchronous response */
     rd->received = true;
 
-    /* Forward declaration for the goto */
-    UA_NodeId expectedNodeId = UA_NODEID_NUMERIC(0, rd->responseType->binaryEncodingId);
-
     /* Check that the response type matches */
+    expectedNodeId = UA_NODEID_NUMERIC(0, rd->responseType->binaryEncodingId);
     if(!UA_NodeId_equal(&responseId, &expectedNodeId)) {
         if(UA_NodeId_equal(&responseId, &serviceFaultId)) {
             UA_LOG_INFO(rd->client->config.logger, UA_LOGCATEGORY_CLIENT,
@@ -519,6 +546,44 @@ __UA_Client_Service(UA_Client *client, const void *request,
         respHeader->serviceResult = retval;
 }
 
+UA_StatusCode
+receiveServiceResponseAsync(UA_Client *client, void *response,
+                             const UA_DataType *responseType) {
+    SyncResponseDescription rd = { client, false, 0, response, responseType };
+
+    UA_StatusCode retval = UA_Connection_receiveChunksNonBlocking(
+            &client->connection, &rd, client_processChunk);
+    /*let client run when non critical timeout*/
+    if(retval != UA_STATUSCODE_GOOD
+            && retval != UA_STATUSCODE_GOODNONCRITICALTIMEOUT) {
+        if(retval == UA_STATUSCODE_BADCONNECTIONCLOSED)
+            setClientState(client, UA_CLIENTSTATE_DISCONNECTED);
+        UA_Client_close(client);
+    }
+    return retval;
+}
+
+UA_StatusCode
+receivePacketAsync(UA_Client *client) {
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+    if (UA_Client_getState(client) == UA_CLIENTSTATE_DISCONNECTED ||
+            UA_Client_getState(client) == UA_CLIENTSTATE_WAITING_FOR_ACK) {
+        retval = UA_Connection_receiveChunksNonBlocking(
+                &client->connection, client, client->ackResponseCallback);
+    }
+    else if(UA_Client_getState(client) == UA_CLIENTSTATE_CONNECTED) {
+        retval = UA_Connection_receiveChunksNonBlocking(
+                &client->connection, client,
+                client->openSecureChannelResponseCallback);
+    }
+    if(retval != UA_STATUSCODE_GOOD && retval != UA_STATUSCODE_GOODNONCRITICALTIMEOUT) {
+        if(retval == UA_STATUSCODE_BADCONNECTIONCLOSED)
+            setClientState(client, UA_CLIENTSTATE_DISCONNECTED);
+        UA_Client_close(client);
+    }
+    return retval;
+}
+
 void
 UA_Client_AsyncService_cancel(UA_Client *client, AsyncServiceCall *ac,
                               UA_StatusCode statusCode) {
@@ -528,7 +593,7 @@ UA_Client_AsyncService_cancel(UA_Client *client, AsyncServiceCall *ac,
     UA_init(resp, ac->responseType);
     ((UA_ResponseHeader*)resp)->serviceResult = statusCode;
 
-    ac->callback(client, ac->userdata, ac->requestId, resp, ac->responseType);
+    ac->callback(client, ac->userdata, ac->requestId, resp);
 
     /* Clean up the response. Users might move data into it. For whatever reasons. */
     UA_deleteMembers(resp, ac->responseType);
@@ -544,11 +609,12 @@ void UA_Client_AsyncService_removeAll(UA_Client *client, UA_StatusCode statusCod
 }
 
 UA_StatusCode
-__UA_Client_AsyncService(UA_Client *client, const void *request,
-                         const UA_DataType *requestType,
-                         UA_ClientAsyncServiceCallback callback,
-                         const UA_DataType *responseType,
-                         void *userdata, UA_UInt32 *requestId) {
+__UA_Client_AsyncServiceEx(UA_Client *client, const void *request,
+                           const UA_DataType *requestType,
+                           UA_ClientAsyncServiceCallback callback,
+                           const UA_DataType *responseType,
+                           void *userdata, UA_UInt32 *requestId,
+                           UA_UInt32 timeout) {
     /* Prepare the entry for the linked list */
     AsyncServiceCall *ac = (AsyncServiceCall*)UA_malloc(sizeof(AsyncServiceCall));
     if(!ac)
@@ -556,6 +622,7 @@ __UA_Client_AsyncService(UA_Client *client, const void *request,
     ac->callback = callback;
     ac->responseType = responseType;
     ac->userdata = userdata;
+    ac->timeout = timeout;
 
     /* Call the service and set the requestId */
     UA_StatusCode retval = sendSymmetricServiceRequest(client, request, requestType, &ac->requestId);
@@ -564,6 +631,8 @@ __UA_Client_AsyncService(UA_Client *client, const void *request,
         return retval;
     }
 
+    ac->start = UA_DateTime_nowMonotonic();
+
     /* Store the entry for async processing */
     LIST_INSERT_HEAD(&client->asyncServiceCalls, ac, pointers);
     if(requestId)
@@ -572,24 +641,43 @@ __UA_Client_AsyncService(UA_Client *client, const void *request,
 }
 
 UA_StatusCode
-UA_Client_runAsync(UA_Client *client, UA_UInt16 timeout) {
-    /* TODO: Call repeated jobs that are scheduled */
-#ifdef UA_ENABLE_SUBSCRIPTIONS
-    UA_StatusCode retvalPublish = UA_Client_Subscriptions_backgroundPublish(client);
-    if (retvalPublish != UA_STATUSCODE_GOOD)
-        return retvalPublish;
-#endif
-    UA_StatusCode retval = UA_Client_manuallyRenewSecureChannel(client);
-    if (retval != UA_STATUSCODE_GOOD)
-        return retval;
+__UA_Client_AsyncService(UA_Client *client, const void *request,
+                         const UA_DataType *requestType,
+                         UA_ClientAsyncServiceCallback callback,
+                         const UA_DataType *responseType,
+                         void *userdata, UA_UInt32 *requestId) {
+    return __UA_Client_AsyncServiceEx(client, request, requestType, callback,
+                                      responseType, userdata, requestId,
+                                      client->config.timeout);
+}
 
-    UA_DateTime maxDate = UA_DateTime_nowMonotonic() + (timeout * UA_DATETIME_MSEC);
-    retval = receiveServiceResponse(client, NULL, NULL, maxDate, NULL);
-    if(retval == UA_STATUSCODE_GOODNONCRITICALTIMEOUT)
-        retval = UA_STATUSCODE_GOOD;
-#ifdef UA_ENABLE_SUBSCRIPTIONS
-    /* The inactivity check must be done after receiveServiceResponse */
-    UA_Client_Subscriptions_backgroundPublishInactivityCheck(client);
-#endif
-    return retval;
+
+UA_StatusCode
+UA_Client_sendAsyncRequest(UA_Client *client, const void *request,
+                           const UA_DataType *requestType,
+                           UA_ClientAsyncServiceCallback callback,
+                           const UA_DataType *responseType, void *userdata,
+                           UA_UInt32 *requestId) {
+    if (UA_Client_getState(client) < UA_CLIENTSTATE_SECURECHANNEL) {
+        UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                    "Cient must be connected to send high-level requests");
+        return UA_STATUSCODE_GOOD;
+    }
+    return __UA_Client_AsyncService(client, request, requestType, callback,
+                                    responseType, userdata, requestId);
+}
+
+UA_StatusCode
+UA_Client_addRepeatedCallback(UA_Client *Client, UA_ClientCallback callback,
+                              void *data, UA_UInt32 interval,
+                              UA_UInt64 *callbackId) {
+    return UA_Timer_addRepeatedCallback(&Client->timer,
+                                        (UA_TimerCallback) callback, data,
+                                        interval, callbackId);
+}
+
+
+UA_StatusCode
+UA_Client_removeRepeatedCallback(UA_Client *Client, UA_UInt64 callbackId) {
+    return UA_Timer_removeRepeatedCallback(&Client->timer, callbackId);
 }

+ 48 - 11
src/client/ua_client_connect.c

@@ -38,6 +38,9 @@ setClientState(UA_Client *client, UA_ClientState state) {
 /* Open the Connection */
 /***********************/
 
+#define UA_BITMASK_MESSAGETYPE 0x00ffffff
+#define UA_BITMASK_CHUNKTYPE 0xff000000
+
 static UA_StatusCode
 processACKResponse(void *application, UA_Connection *connection, UA_ByteString *chunk) {
     UA_Client *client = (UA_Client*)application;
@@ -48,6 +51,30 @@ processACKResponse(void *application, UA_Connection *connection, UA_ByteString *
     UA_TcpMessageHeader messageHeader;
     UA_TcpAcknowledgeMessage ackMessage;
     retval = UA_TcpMessageHeader_decodeBinary(chunk, &offset, &messageHeader);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_NETWORK,
+                    "Decoding ACK message failed");
+        return retval;
+    }
+
+    // check if we got an error response from the server
+    UA_MessageType messageType = (UA_MessageType)
+        (messageHeader.messageTypeAndChunkType & UA_BITMASK_MESSAGETYPE);
+    UA_ChunkType chunkType = (UA_ChunkType)
+        (messageHeader.messageTypeAndChunkType & UA_BITMASK_CHUNKTYPE);
+    if (messageType == UA_MESSAGETYPE_ERR) {
+        // Header + ErrorMessage (error + reasonLength_field + length)
+        UA_StatusCode error = *(UA_StatusCode*)(&chunk->data[offset]);
+        UA_UInt32 len = *((UA_UInt32*)&chunk->data[offset + 4]);
+        UA_Byte *data = (UA_Byte*)&chunk->data[offset + 4+4];
+        UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_NETWORK,
+                    "Received ERR response. %s - %.*s", UA_StatusCode_name(error), len, data);
+        return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
+    }
+    if (chunkType != UA_CHUNKTYPE_FINAL) {
+        return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
+    }
+
     retval |= UA_TcpAcknowledgeMessage_decodeBinary(chunk, &offset, &ackMessage);
     if(retval != UA_STATUSCODE_GOOD) {
         UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_NETWORK,
@@ -130,11 +157,12 @@ HelAckHandshake(UA_Client *client) {
 }
 
 static void
-processDecodedOPNResponse(UA_Client *client, UA_OpenSecureChannelResponse *response) {
+processDecodedOPNResponse(UA_Client *client, UA_OpenSecureChannelResponse *response, UA_Boolean renew) {
     /* Replace the token */
-    UA_ChannelSecurityToken_deleteMembers(&client->channel.securityToken);
-    client->channel.securityToken = response->securityToken;
-    UA_ChannelSecurityToken_init(&response->securityToken);
+    if (renew)
+        client->channel.nextSecurityToken = response->securityToken; // Set the next token
+    else
+        client->channel.securityToken = response->securityToken; // Set initial token
 
     /* Replace the nonce */
     UA_ByteString_deleteMembers(&client->channel.remoteNonce);
@@ -201,6 +229,11 @@ openSecureChannel(UA_Client *client, UA_Boolean renew) {
 
     UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "OPN message sent");
 
+    /* Increase nextChannelRenewal to avoid that we re-start renewal when
+     * publish responses are received before the OPN response arrives. */
+    client->nextChannelRenewal = UA_DateTime_nowMonotonic() +
+        (2 * ((UA_DateTime)client->config.timeout * UA_DATETIME_MSEC));
+
     /* Receive / decrypt / decode the OPN response. Process async services in
      * the background until the OPN response arrives. */
     UA_OpenSecureChannelResponse response;
@@ -215,7 +248,7 @@ openSecureChannel(UA_Client *client, UA_Boolean renew) {
         return retval;
     }
 
-    processDecodedOPNResponse(client, &response);
+    processDecodedOPNResponse(client, &response, renew);
     UA_OpenSecureChannelResponse_deleteMembers(&response);
     return retval;
 }
@@ -226,7 +259,7 @@ openSecureChannel(UA_Client *client, UA_Boolean renew) {
  * @param  channel      current channel in which the client runs
  * @param  response     create session response from the server
  * @return Returns an error code or UA_STATUSCODE_GOOD. */
-static UA_StatusCode
+UA_StatusCode
 checkClientSignature(const UA_SecureChannel *channel, const UA_CreateSessionResponse *response) {
     if(channel == NULL || response == NULL)
         return UA_STATUSCODE_BADINTERNALERROR;
@@ -265,7 +298,7 @@ checkClientSignature(const UA_SecureChannel *channel, const UA_CreateSessionResp
  * @param  channel      current channel in which the client runs
  * @param  request      activate session request message to server
  * @return Returns an error or UA_STATUSCODE_GOOD */
-static UA_StatusCode
+UA_StatusCode
 signActivateSessionRequest(UA_SecureChannel *channel,
                            UA_ActivateSessionRequest *request) {
     if(channel == NULL || request == NULL)
@@ -715,13 +748,17 @@ UA_Client_disconnect(UA_Client *client) {
     }
 
     /* Close the TCP connection */
-    if(client->connection.state != UA_CONNECTION_CLOSED)
-        client->connection.close(&client->connection);
+    if(client->connection.state != UA_CONNECTION_CLOSED
+            && client->connection.state != UA_CONNECTION_OPENING)
+        /*UA_ClientConnectionTCP_init sets initial state to opening */
+        if(client->connection.close != NULL)
+            client->connection.close(&client->connection);
+
 
 #ifdef UA_ENABLE_SUBSCRIPTIONS
 // TODO REMOVE WHEN UA_SESSION_RECOVERY IS READY
-        /* We need to clean up the subscriptions */
-        UA_Client_Subscriptions_clean(client);
+    /* We need to clean up the subscriptions */
+    UA_Client_Subscriptions_clean(client);
 #endif
 
     setClientState(client, UA_CLIENTSTATE_DISCONNECTED);

+ 680 - 0
src/client/ua_client_connect_async.c

@@ -0,0 +1,680 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ua_client.h"
+#include "ua_client_internal.h"
+#include "ua_transport_generated.h"
+#include "ua_transport_generated_handling.h"
+#include "ua_transport_generated_encoding_binary.h"
+#include "ua_types_encoding_binary.h"
+#include "ua_types_generated_encoding_binary.h"
+
+#define UA_MINMESSAGESIZE                8192
+#define UA_SESSION_LOCALNONCELENGTH      32
+#define MAX_DATA_SIZE 4096
+
+/* Asynchronous client connection
+ * To prepare an async connection, UA_Client_connectAsync() is called, which does not connect the
+ * client directly. UA_Client_run_iterate() takes care of actually connecting the client:
+ * if client is disconnected:
+ *      send hello msg and set the client state to be WAITING_FOR_ACK
+ *      (see UA_Client_connect_iterate())
+ * if client is waiting for the ACK:
+ *      call the non-blocking receiving function and register processACKResponseAsync() as its callback
+ *      (see receivePacketAsync())
+ * if ACK is processed (callback called):
+ *      processACKResponseAsync() calls openSecureChannelAsync() at the end, which prepares the request
+ *      to open secure channel and the client is connected
+ * if client is connected:
+ *      call the non-blocking receiving function and register processOPNResponse() as its callback
+ *      (see receivePacketAsync())
+ * if OPN-request processed (callback called)
+ *      send session request, where the session response is put into a normal AsyncServiceCall, and when
+ *      called, request to activate session is sent, where its response is again put into an AsyncServiceCall
+ * in the very last step responseActivateSession():
+ *      the user defined callback that is passed into UA_Client_connectAsync() is called and the
+ *      async connection finalized.
+ * */
+
+/***********************/
+/* Open the Connection */
+/***********************/
+static UA_StatusCode
+openSecureChannelAsync(UA_Client *client, UA_Boolean renew);
+
+static UA_StatusCode
+requestSession(UA_Client *client, UA_UInt32 *requestId);
+
+static UA_StatusCode
+requestGetEndpoints(UA_Client *client, UA_UInt32 *requestId);
+
+/*receives hello ack, opens secure channel*/
+static UA_StatusCode
+processACKResponseAsync(void *application, UA_Connection *connection,
+                         UA_ByteString *chunk) {
+    UA_Client *client = (UA_Client*)application;
+
+    /* Decode the message */
+    size_t offset = 0;
+    UA_TcpMessageHeader messageHeader;
+    UA_TcpAcknowledgeMessage ackMessage;
+    client->connectStatus = UA_TcpMessageHeader_decodeBinary (chunk, &offset,
+                                                              &messageHeader);
+    client->connectStatus |= UA_TcpAcknowledgeMessage_decodeBinary(
+            chunk, &offset, &ackMessage);
+    if (client->connectStatus != UA_STATUSCODE_GOOD) {
+        UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_NETWORK,
+                     "Decoding ACK message failed");
+        return client->connectStatus;
+    }
+
+    /* Store remote connection settings and adjust local configuration to not
+     * exceed the limits */
+    UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_NETWORK,
+                 "Received ACK message");
+    connection->remoteConf.maxChunkCount = ackMessage.maxChunkCount; /* may be zero -> unlimited */
+    connection->remoteConf.maxMessageSize = ackMessage.maxMessageSize; /* may be zero -> unlimited */
+    connection->remoteConf.protocolVersion = ackMessage.protocolVersion;
+    connection->remoteConf.sendBufferSize = ackMessage.sendBufferSize;
+    connection->remoteConf.recvBufferSize = ackMessage.receiveBufferSize;
+    if (connection->remoteConf.recvBufferSize
+            < connection->localConf.sendBufferSize)
+        connection->localConf.sendBufferSize =
+                connection->remoteConf.recvBufferSize;
+    if (connection->remoteConf.sendBufferSize
+            < connection->localConf.recvBufferSize)
+        connection->localConf.recvBufferSize =
+                connection->remoteConf.sendBufferSize;
+    connection->state = UA_CONNECTION_ESTABLISHED;
+
+    client->state = UA_CLIENTSTATE_CONNECTED;
+
+    /* Open a SecureChannel. TODO: Select with endpoint  */
+    client->channel.connection = &client->connection;
+    client->connectStatus = openSecureChannelAsync(client, false);
+    return client->connectStatus;
+}
+
+static UA_StatusCode
+sendHELMessage(UA_Client *client) {
+    /* Get a buffer */
+    UA_ByteString message;
+    UA_Connection *conn = &client->connection;
+    client->connectStatus = conn->getSendBuffer(conn, UA_MINMESSAGESIZE,
+                                                &message);
+
+    if (client->connectStatus != UA_STATUSCODE_GOOD)
+        return client->connectStatus;
+
+    /* Prepare the HEL message and encode at offset 8 */
+    UA_TcpHelloMessage hello;
+    UA_String_copy(&client->endpointUrl, &hello.endpointUrl); /* must be less than 4096 bytes */
+    hello.maxChunkCount = conn->localConf.maxChunkCount;
+    hello.maxMessageSize = conn->localConf.maxMessageSize;
+    hello.protocolVersion = conn->localConf.protocolVersion;
+    hello.receiveBufferSize = conn->localConf.recvBufferSize;
+    hello.sendBufferSize = conn->localConf.sendBufferSize;
+
+    UA_Byte *bufPos = &message.data[8]; /* skip the header */
+    const UA_Byte *bufEnd = &message.data[message.length];
+    client->connectStatus = UA_TcpHelloMessage_encodeBinary(&hello, &bufPos,
+                                                            bufEnd);
+    UA_TcpHelloMessage_deleteMembers (&hello);
+
+    /* Encode the message header at offset 0 */
+    UA_TcpMessageHeader messageHeader;
+    messageHeader.messageTypeAndChunkType = UA_CHUNKTYPE_FINAL
+            + UA_MESSAGETYPE_HEL;
+    messageHeader.messageSize = (UA_UInt32) ((uintptr_t)bufPos
+            - (uintptr_t)message.data);
+    bufPos = message.data;
+    client->connectStatus |= UA_TcpMessageHeader_encodeBinary(&messageHeader,
+                                                              &bufPos,
+                                                              bufEnd);
+    if (client->connectStatus != UA_STATUSCODE_GOOD) {
+        conn->releaseSendBuffer(conn, &message);
+        return client->connectStatus;
+    }
+
+    /* Send the HEL message */
+    message.length = messageHeader.messageSize;
+    client->connectStatus = conn->send (conn, &message);
+
+    if (client->connectStatus != UA_STATUSCODE_GOOD) {
+        UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_NETWORK,
+                    "Sending HEL failed");
+        return client->connectStatus;
+    }
+    UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_NETWORK,
+                 "Sent HEL message");
+    setClientState(client, UA_CLIENTSTATE_WAITING_FOR_ACK);
+    return client->connectStatus;
+}
+
+static UA_StatusCode
+processDecodedOPNResponseAsync(void *application, UA_SecureChannel *channel,
+                                UA_MessageType messageType,
+                                UA_UInt32 requestId,
+                                const UA_ByteString *message) {
+    /* Does the request id match? */
+    UA_Client *client = (UA_Client*)application;
+    if (requestId != client->requestId)
+        return UA_STATUSCODE_BADCOMMUNICATIONERROR;
+
+    /* Is the content of the expected type? */
+    size_t offset = 0;
+    UA_NodeId responseId;
+    UA_NodeId expectedId = UA_NODEID_NUMERIC(
+            0, UA_TYPES[UA_TYPES_OPENSECURECHANNELRESPONSE].binaryEncodingId);
+    UA_StatusCode retval = UA_NodeId_decodeBinary(message, &offset,
+                                                  &responseId);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
+    if(!UA_NodeId_equal(&responseId, &expectedId)) {
+        UA_NodeId_deleteMembers(&responseId);
+        return UA_STATUSCODE_BADCOMMUNICATIONERROR;
+    }
+    UA_NodeId_deleteMembers (&responseId);
+
+    /* Decode the response */
+    UA_OpenSecureChannelResponse response;
+    retval = UA_OpenSecureChannelResponse_decodeBinary(message, &offset,
+                                                       &response);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
+
+    /* Response.securityToken.revisedLifetime is UInt32 we need to cast it to
+     * DateTime=Int64 we take 75% of lifetime to start renewing as described in
+     * standard */
+    client->nextChannelRenewal = UA_DateTime_nowMonotonic()
+            + (UA_DateTime) (response.securityToken.revisedLifetime
+                    * (UA_Double) UA_DATETIME_MSEC * 0.75);
+
+    /* Replace the token and nonce */
+    UA_ChannelSecurityToken_deleteMembers(&client->channel.securityToken);
+    UA_ByteString_deleteMembers(&client->channel.remoteNonce);
+    client->channel.securityToken = response.securityToken;
+    client->channel.remoteNonce = response.serverNonce;
+    UA_ResponseHeader_deleteMembers(&response.responseHeader); /* the other members were moved */
+    if(client->channel.state == UA_SECURECHANNELSTATE_OPEN)
+        UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_SECURECHANNEL,
+                     "SecureChannel renewed");
+    else
+        UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_SECURECHANNEL,
+                     "SecureChannel opened");
+    client->channel.state = UA_SECURECHANNELSTATE_OPEN;
+    return UA_STATUSCODE_GOOD;
+}
+
+static UA_StatusCode processOPNResponse
+    (void *application, UA_Connection *connection,
+                    UA_ByteString *chunk) {
+    UA_Client *client = (UA_Client*) application;
+    UA_StatusCode retval = UA_SecureChannel_processChunk (
+            &client->channel, chunk, processDecodedOPNResponseAsync, client);
+    client->connectStatus = retval;
+    if(retval != UA_STATUSCODE_GOOD) {
+        return retval;
+    }
+    setClientState(client, UA_CLIENTSTATE_SECURECHANNEL);
+    retval |= UA_SecureChannel_generateNewKeys(&client->channel);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
+    /* Following requests and responses */
+    UA_UInt32 reqId;
+    if(client->endpointsHandshake)
+        retval = requestGetEndpoints (client, &reqId);
+    else
+        retval = requestSession (client, &reqId);
+
+    client->connectStatus = retval;
+    return retval;
+
+}
+
+/* OPN messges to renew the channel are sent asynchronous */
+static UA_StatusCode
+openSecureChannelAsync(UA_Client *client, UA_Boolean renew) {
+    /* Check if sc is still valid */
+    if(renew && client->nextChannelRenewal - UA_DateTime_nowMonotonic () > 0)
+        return UA_STATUSCODE_GOOD;
+
+    UA_Connection *conn = &client->connection;
+    if(conn->state != UA_CONNECTION_ESTABLISHED)
+        return UA_STATUSCODE_BADSERVERNOTCONNECTED;
+
+    /* Prepare the OpenSecureChannelRequest */
+    UA_OpenSecureChannelRequest opnSecRq;
+    UA_OpenSecureChannelRequest_init(&opnSecRq);
+    opnSecRq.requestHeader.timestamp = UA_DateTime_now();
+    opnSecRq.requestHeader.authenticationToken = client->authenticationToken;
+    if(renew) {
+        opnSecRq.requestType = UA_SECURITYTOKENREQUESTTYPE_RENEW;
+        UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_SECURECHANNEL,
+                     "Requesting to renew the SecureChannel");
+    } else {
+        opnSecRq.requestType = UA_SECURITYTOKENREQUESTTYPE_ISSUE;
+        UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_SECURECHANNEL,
+                     "Requesting to open a SecureChannel");
+    }
+    opnSecRq.securityMode = client->channel.securityMode;
+
+    opnSecRq.clientNonce = client->channel.localNonce;
+    opnSecRq.requestedLifetime = client->config.secureChannelLifeTime;
+
+    /* Prepare the entry for the linked list */
+    UA_UInt32 requestId = ++client->requestId;
+    AsyncServiceCall *ac = NULL;
+    if(renew) {
+        ac = (AsyncServiceCall*)UA_malloc(sizeof(AsyncServiceCall));
+        if (!ac)
+            return UA_STATUSCODE_BADOUTOFMEMORY;
+        ac->callback =
+                (UA_ClientAsyncServiceCallback) processDecodedOPNResponseAsync;
+        ac->responseType = &UA_TYPES[UA_TYPES_OPENSECURECHANNELRESPONSE];
+        ac->requestId = requestId;
+        ac->userdata = NULL;
+    }
+
+    /* Send the OPN message */
+    UA_StatusCode retval = UA_SecureChannel_sendAsymmetricOPNMessage (
+            &client->channel, requestId, &opnSecRq,
+            &UA_TYPES[UA_TYPES_OPENSECURECHANNELREQUEST]);
+    client->connectStatus = retval;
+
+    if(retval != UA_STATUSCODE_GOOD) {
+        client->connectStatus = retval;
+        UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_SECURECHANNEL,
+                      "Sending OPN message failed with error %s",
+                      UA_StatusCode_name(retval));
+        UA_Client_close(client);
+        if(renew)
+            UA_free(ac);
+        return retval;
+    }
+
+    UA_LOG_DEBUG (client->config.logger, UA_LOGCATEGORY_SECURECHANNEL,
+                  "OPN message sent");
+
+    /* Store the entry for async processing and return */
+    if(renew) {
+        LIST_INSERT_HEAD(&client->asyncServiceCalls, ac, pointers);
+        return retval;
+    }
+    return retval;
+}
+
+static void
+responseActivateSession(UA_Client *client, void *userdata, UA_UInt32 requestId,
+                        void *response) {
+    UA_ActivateSessionResponse *activateResponse =
+            (UA_ActivateSessionResponse *) response;
+    if(activateResponse->responseHeader.serviceResult) {
+        UA_LOG_ERROR(
+                client->config.logger,
+                UA_LOGCATEGORY_CLIENT,
+                "ActivateSession failed with error code %s",
+                UA_StatusCode_name(activateResponse->responseHeader.serviceResult));
+    }
+    client->connection.state = UA_CONNECTION_ESTABLISHED;
+    setClientState(client, UA_CLIENTSTATE_SESSION);
+
+#ifdef UA_ENABLE_SUBSCRIPTIONS
+        /* A new session has been created. We need to clean up the subscriptions */
+        UA_Client_Subscriptions_clean(client);
+#endif
+
+     /* call onConnect (client_async.c) callback */
+    AsyncServiceCall ac = client->asyncConnectCall;
+
+    ac.callback(client, ac.userdata, requestId + 1,
+                &activateResponse->responseHeader.serviceResult);
+}
+
+static UA_StatusCode
+requestActivateSession (UA_Client *client, UA_UInt32 *requestId) {
+    UA_ActivateSessionRequest request;
+    UA_ActivateSessionRequest_init(&request);
+    request.requestHeader.requestHandle = ++client->requestHandle;
+    request.requestHeader.timestamp = UA_DateTime_now ();
+    request.requestHeader.timeoutHint = 600000;
+
+    /* Manual ExtensionObject encoding of the identityToken */
+    if (client->authenticationMethod == UA_CLIENTAUTHENTICATION_NONE) {
+        UA_AnonymousIdentityToken* identityToken =
+                UA_AnonymousIdentityToken_new();
+        UA_AnonymousIdentityToken_init (identityToken);
+        UA_String_copy(&client->token.policyId, &identityToken->policyId);
+        request.userIdentityToken.encoding = UA_EXTENSIONOBJECT_DECODED;
+        request.userIdentityToken.content.decoded.type =
+                &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN];
+        request.userIdentityToken.content.decoded.data = identityToken;
+    } else {
+        UA_UserNameIdentityToken* identityToken =
+                UA_UserNameIdentityToken_new();
+        UA_UserNameIdentityToken_init (identityToken);
+        UA_String_copy(&client->token.policyId, &identityToken->policyId);
+        UA_String_copy(&client->username, &identityToken->userName);
+        UA_String_copy(&client->password, &identityToken->password);
+        request.userIdentityToken.encoding = UA_EXTENSIONOBJECT_DECODED;
+        request.userIdentityToken.content.decoded.type =
+                &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN];
+        request.userIdentityToken.content.decoded.data = identityToken;
+    }
+    /* This function call is to prepare a client signature */
+    if(client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGN ||
+       client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) {
+        signActivateSessionRequest(&client->channel, &request);
+    }
+
+    UA_StatusCode retval = UA_Client_sendAsyncRequest (
+            client, &request, &UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST],
+            (UA_ClientAsyncServiceCallback) responseActivateSession,
+            &UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE], NULL, requestId);
+    UA_ActivateSessionRequest_deleteMembers(&request);
+    client->connectStatus = retval;
+    return retval;
+}
+
+/* Combination of UA_Client_getEndpointsInternal and getEndpoints */
+static void
+responseGetEndpoints(UA_Client *client, void *userdata, UA_UInt32 requestId,
+                     void *response) {
+    UA_EndpointDescription* endpointArray = NULL;
+    size_t endpointArraySize = 0;
+    UA_GetEndpointsResponse* resp;
+    resp = (UA_GetEndpointsResponse*)response;
+
+    if (resp->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+        client->connectStatus = resp->responseHeader.serviceResult;
+        UA_LOG_ERROR (client->config.logger, UA_LOGCATEGORY_CLIENT,
+                      "GetEndpointRequest failed with error code %s",
+                      UA_StatusCode_name (client->connectStatus));
+        UA_GetEndpointsResponse_deleteMembers(resp);
+        return;
+    }
+    endpointArray = resp->endpoints;
+    endpointArraySize = resp->endpointsSize;
+    resp->endpoints = NULL;
+    resp->endpointsSize = 0;
+
+    UA_Boolean endpointFound = false;
+    UA_Boolean tokenFound = false;
+    UA_String securityNone = UA_STRING("http://opcfoundation.org/UA/SecurityPolicy#None");
+    UA_String binaryTransport = UA_STRING("http://opcfoundation.org/UA-Profile/"
+                                          "Transport/uatcp-uasc-uabinary");
+
+    // TODO: compare endpoint information with client->endpointUri
+    for(size_t i = 0; i < endpointArraySize; ++i) {
+        UA_EndpointDescription* endpoint = &endpointArray[i];
+        /* look out for binary transport endpoints */
+        /* Note: Siemens returns empty ProfileUrl, we will accept it as binary */
+        if(endpoint->transportProfileUri.length != 0
+                && !UA_String_equal (&endpoint->transportProfileUri,
+                                     &binaryTransport))
+            continue;
+
+        /* Look for an endpoint corresponding to the client security policy */
+        if(!UA_String_equal(&endpoint->securityPolicyUri, &client->securityPolicy.policyUri))
+            continue;
+
+        endpointFound = true;
+
+        /* Look for a user token policy with an anonymous token */
+        for(size_t j = 0; j < endpoint->userIdentityTokensSize; ++j) {
+            UA_UserTokenPolicy* userToken = &endpoint->userIdentityTokens[j];
+
+            /* Usertokens also have a security policy... */
+            if(userToken->securityPolicyUri.length > 0
+                    && !UA_String_equal(&userToken->securityPolicyUri,
+                                         &securityNone))
+                continue;
+
+            /* UA_CLIENTAUTHENTICATION_NONE == UA_USERTOKENTYPE_ANONYMOUS
+             * UA_CLIENTAUTHENTICATION_USERNAME == UA_USERTOKENTYPE_USERNAME
+             * TODO: Check equivalence for other types when adding the support */
+            if((int)client->authenticationMethod
+                    != (int)userToken->tokenType)
+                continue;
+
+            /* Endpoint with matching usertokenpolicy found */
+            tokenFound = true;
+            UA_UserTokenPolicy_deleteMembers(&client->token);
+            UA_UserTokenPolicy_copy(userToken, &client->token);
+            break;
+        }
+    }
+
+    UA_Array_delete(endpointArray, endpointArraySize,
+                    &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
+
+    if(!endpointFound) {
+        UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                      "No suitable endpoint found");
+        client->connectStatus = UA_STATUSCODE_BADINTERNALERROR;
+    } else if(!tokenFound) {
+        UA_LOG_ERROR(
+                client->config.logger, UA_LOGCATEGORY_CLIENT,
+                "No suitable UserTokenPolicy found for the possible endpoints");
+        client->connectStatus = UA_STATUSCODE_BADINTERNALERROR;
+    }
+    requestSession(client, &requestId);
+}
+
+static UA_StatusCode
+requestGetEndpoints(UA_Client *client, UA_UInt32 *requestId) {
+    UA_GetEndpointsRequest request;
+    UA_GetEndpointsRequest_init(&request);
+    request.requestHeader.timestamp = UA_DateTime_now();
+    request.requestHeader.timeoutHint = 10000;
+    /* assume the endpointurl outlives the service call */
+    UA_String_copy (&client->endpointUrl, &request.endpointUrl);
+
+    client->connectStatus = UA_Client_sendAsyncRequest(
+            client, &request, &UA_TYPES[UA_TYPES_GETENDPOINTSREQUEST],
+            (UA_ClientAsyncServiceCallback) responseGetEndpoints,
+            &UA_TYPES[UA_TYPES_GETENDPOINTSRESPONSE], NULL, requestId);
+    UA_GetEndpointsRequest_deleteMembers(&request);
+    return client->connectStatus;
+
+}
+
+static void
+responseSessionCallback(UA_Client *client, void *userdata, UA_UInt32 requestId,
+                        void *response) {
+    UA_CreateSessionResponse *sessionResponse =
+            (UA_CreateSessionResponse *)response;
+    UA_NodeId_copy(&sessionResponse->authenticationToken,
+                   &client->authenticationToken);
+    requestActivateSession(client, &requestId);
+}
+
+static UA_StatusCode
+requestSession(UA_Client *client, UA_UInt32 *requestId) {
+    UA_CreateSessionRequest request;
+    UA_CreateSessionRequest_init(&request);
+
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+    if(client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGN ||
+       client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) {
+        if(client->channel.localNonce.length != UA_SESSION_LOCALNONCELENGTH) {
+           UA_ByteString_deleteMembers(&client->channel.localNonce);
+            retval = UA_ByteString_allocBuffer(&client->channel.localNonce,
+                                               UA_SESSION_LOCALNONCELENGTH);
+            if(retval != UA_STATUSCODE_GOOD)
+                return retval;
+        }
+
+        retval = client->channel.securityPolicy->symmetricModule.
+                 generateNonce(client->channel.securityPolicy, &client->channel.localNonce);
+        if(retval != UA_STATUSCODE_GOOD)
+            return retval;
+    }
+
+    request.requestHeader.requestHandle = ++client->requestHandle;
+    request.requestHeader.timestamp = UA_DateTime_now();
+    request.requestHeader.timeoutHint = 10000;
+    UA_ByteString_copy(&client->channel.localNonce, &request.clientNonce);
+    request.requestedSessionTimeout = 1200000;
+    request.maxResponseMessageSize = UA_INT32_MAX;
+    UA_String_copy(&client->endpointUrl, &request.endpointUrl);
+
+    retval = UA_Client_sendAsyncRequest (
+            client, &request, &UA_TYPES[UA_TYPES_CREATESESSIONREQUEST],
+            (UA_ClientAsyncServiceCallback) responseSessionCallback,
+            &UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE], NULL, requestId);
+    UA_CreateSessionRequest_deleteMembers(&request);
+    client->connectStatus = retval;
+    return client->connectStatus;
+}
+
+UA_StatusCode
+UA_Client_connectInternalAsync(UA_Client *client, const char *endpointUrl,
+                               UA_ClientAsyncServiceCallback callback,
+                               void *userdata, UA_Boolean endpointsHandshake,
+                               UA_Boolean createNewSession) {
+    if(client->state >= UA_CLIENTSTATE_WAITING_FOR_ACK)
+        return UA_STATUSCODE_GOOD;
+    UA_ChannelSecurityToken_init(&client->channel.securityToken);
+    client->channel.state = UA_SECURECHANNELSTATE_FRESH;
+    /* Set up further callback function to handle secure channel and session establishment  */
+    client->ackResponseCallback = processACKResponseAsync;
+    client->openSecureChannelResponseCallback = processOPNResponse;
+    client->endpointsHandshake = endpointsHandshake;
+
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+    client->connection = client->config.initConnectionFunc(
+            client->config.localConnectionConfig, endpointUrl,
+            client->config.timeout, client->config.logger);
+    if(client->connection.state != UA_CONNECTION_OPENING) {
+        retval = UA_STATUSCODE_BADCONNECTIONCLOSED;
+        goto cleanup;
+    }
+
+    UA_String_deleteMembers(&client->endpointUrl);
+    client->endpointUrl = UA_STRING_ALLOC(endpointUrl);
+    if(!client->endpointUrl.data) {
+        retval = UA_STATUSCODE_BADOUTOFMEMORY;
+        goto cleanup;
+    }
+
+    client->asyncConnectCall.callback = callback;
+    client->asyncConnectCall.userdata = userdata;
+
+    if(!client->connection.connectCallbackID) {
+        retval = UA_Client_addRepeatedCallback(
+                     client, client->config.pollConnectionFunc, &client->connection, 100,
+                     &client->connection.connectCallbackID);
+    }
+
+    retval |= UA_SecureChannel_generateLocalNonce(&client->channel);
+
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
+
+    /* Delete async service. TODO: Move this from connect to the disconnect/cleanup phase */
+    UA_Client_AsyncService_removeAll(client, UA_STATUSCODE_BADSHUTDOWN);
+
+#ifdef UA_ENABLE_SUBSCRIPTIONS
+    client->currentlyOutStandingPublishRequests = 0;
+#endif
+
+    UA_NodeId_deleteMembers(&client->authenticationToken);
+
+    /* Generate new local and remote key */
+    retval = UA_SecureChannel_generateNewKeys(&client->channel);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
+
+    return retval;
+
+    cleanup: UA_Client_close(client);
+        return retval;
+}
+
+UA_StatusCode
+UA_Client_connect_iterate(UA_Client *client) {
+    if (client->connection.state == UA_CONNECTION_ESTABLISHED){
+        if (client->state < UA_CLIENTSTATE_WAITING_FOR_ACK)
+            return sendHELMessage(client);
+    }
+
+    /* If server is not connected */
+    if (client->connection.state == UA_CONNECTION_CLOSED) {
+        client->connectStatus = UA_STATUSCODE_BADCONNECTIONCLOSED;
+        UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_NETWORK,
+                     "No connection to server.");
+    }
+
+    return client->connectStatus;
+}
+
+UA_StatusCode
+UA_Client_connect_async(UA_Client *client, const char *endpointUrl,
+                        UA_ClientAsyncServiceCallback callback,
+                        void *userdata) {
+    return UA_Client_connectInternalAsync(client, endpointUrl, callback,
+            userdata, UA_TRUE, UA_TRUE);
+}
+
+/* Async disconnection */
+static void
+sendCloseSecureChannelAsync(UA_Client *client, void *userdata,
+                             UA_UInt32 requestId, void *response) {
+    UA_NodeId_deleteMembers (&client->authenticationToken);
+    client->requestHandle = 0;
+
+    UA_SecureChannel *channel = &client->channel;
+    UA_CloseSecureChannelRequest request;
+    UA_CloseSecureChannelRequest_init(&request);
+    request.requestHeader.requestHandle = ++client->requestHandle;
+    request.requestHeader.timestamp = UA_DateTime_now();
+    request.requestHeader.timeoutHint = 10000;
+    request.requestHeader.authenticationToken = client->authenticationToken;
+    UA_SecureChannel_sendSymmetricMessage(
+            channel, ++client->requestId, UA_MESSAGETYPE_CLO, &request,
+            &UA_TYPES[UA_TYPES_CLOSESECURECHANNELREQUEST]);
+    UA_SecureChannel_deleteMembersCleanup(&client->channel);
+}
+
+static void
+sendCloseSessionAsync(UA_Client *client, UA_UInt32 *requestId) {
+    UA_CloseSessionRequest request;
+    UA_CloseSessionRequest_init(&request);
+
+    request.requestHeader.timestamp = UA_DateTime_now();
+    request.requestHeader.timeoutHint = 10000;
+    request.deleteSubscriptions = true;
+
+    UA_Client_sendAsyncRequest(
+            client, &request, &UA_TYPES[UA_TYPES_CLOSESESSIONREQUEST],
+            (UA_ClientAsyncServiceCallback) sendCloseSecureChannelAsync,
+            &UA_TYPES[UA_TYPES_CLOSESESSIONRESPONSE], NULL, requestId);
+
+}
+
+UA_StatusCode
+UA_Client_disconnect_async(UA_Client *client, UA_UInt32 *requestId) {
+    /* Is a session established? */
+    if (client->state == UA_CLIENTSTATE_SESSION) {
+        client->state = UA_CLIENTSTATE_SESSION_DISCONNECTED;
+        sendCloseSessionAsync(client, requestId);
+    }
+
+    /* Close the TCP connection
+     * shutdown and close (in tcp.c) are already async*/
+    if (client->state >= UA_CLIENTSTATE_CONNECTED)
+        client->connection.close(&client->connection);
+    else
+        UA_Client_removeRepeatedCallback(client, client->connection.connectCallbackID);
+
+#ifdef UA_ENABLE_SUBSCRIPTIONS
+// TODO REMOVE WHEN UA_SESSION_RECOVERY IS READY
+    /* We need to clean up the subscriptions */
+    UA_Client_Subscriptions_clean(client);
+#endif
+
+    setClientState(client, UA_CLIENTSTATE_DISCONNECTED);
+    return UA_STATUSCODE_GOOD;
+}

+ 4 - 0
src/client/ua_client_discovery.c

@@ -85,6 +85,8 @@ UA_Client_findServers(UA_Client *client, const char *serverUrl,
     return retval;
 }
 
+#ifdef UA_ENABLE_DISCOVERY
+
 UA_StatusCode
 UA_Client_findServersOnNetwork(UA_Client *client, const char *serverUrl,
                                UA_UInt32 startingRecordId, UA_UInt32 maxRecordsToReturn,
@@ -134,3 +136,5 @@ UA_Client_findServersOnNetwork(UA_Client *client, const char *serverUrl,
         UA_Client_disconnect(client);
     return retval;
 }
+
+#endif

+ 204 - 0
src/client/ua_client_highlevel.c

@@ -10,7 +10,9 @@
  */
 
 #include "ua_client.h"
+#include "ua_client_internal.h"
 #include "ua_client_highlevel.h"
+#include "ua_client_highlevel_async.h"
 #include "ua_util.h"
 
 UA_StatusCode
@@ -454,3 +456,205 @@ UA_Client_readArrayDimensionsAttribute(UA_Client *client, const UA_NodeId nodeId
     UA_ReadResponse_deleteMembers(&response);
     return retval;
 }
+/*Async Functions*/
+
+/*highlevel callbacks
+ * used to hide response handling details*/
+static
+void ValueAttributeRead(UA_Client *client, void *userdata, UA_UInt32 requestId,
+        void *response) {
+
+    if (response == NULL) {
+        return;
+    }
+
+    CustomCallback *cc;
+    LIST_FOREACH(cc, &client->customCallbacks, pointers)
+    {
+        if (cc->callbackId == requestId)
+            break;
+    }
+    if (!cc)
+        return;
+
+    UA_ReadResponse rr = *(UA_ReadResponse *) response;
+    if (rr.results[0].status != UA_STATUSCODE_GOOD)
+        UA_ReadResponse_deleteMembers((UA_ReadResponse*) response);
+
+    UA_Variant out;
+    UA_Variant_init(&out);
+    UA_DataValue *res = rr.results;
+    if (!res->hasValue) {
+        return;
+    }
+
+    /*__UA_Client_readAttribute*/
+    memcpy(&out, &res->value, sizeof(UA_Variant));
+    /* Copy value into out */
+    if (cc->attributeId == UA_ATTRIBUTEID_VALUE) {
+        memcpy(&out, &res->value, sizeof(UA_Variant));
+        UA_Variant_init(&res->value);
+    } else if (cc->attributeId == UA_ATTRIBUTEID_NODECLASS) {
+        memcpy(&out, (UA_NodeClass*) res->value.data, sizeof(UA_NodeClass));
+    } else if (UA_Variant_isScalar(&res->value)
+            && res->value.type == cc->outDataType) {
+        memcpy(&out, res->value.data, res->value.type->memSize);
+        UA_free(res->value.data);
+        res->value.data = NULL;
+    }
+
+    //use callbackId to find the right custom callback
+    cc->callback(client, userdata, requestId, &out);
+    LIST_REMOVE(cc, pointers);
+    UA_free(cc);
+    UA_ReadResponse_deleteMembers((UA_ReadResponse*) response);
+    UA_Variant_deleteMembers(&out);
+}
+
+/*Read Attributes*/
+UA_StatusCode __UA_Client_readAttribute_async(UA_Client *client,
+        const UA_NodeId *nodeId, UA_AttributeId attributeId,
+        const UA_DataType *outDataType, UA_ClientAsyncServiceCallback callback,
+        void *userdata, UA_UInt32 *reqId) {
+    UA_ReadValueId item;
+    UA_ReadValueId_init(&item);
+    item.nodeId = *nodeId;
+    item.attributeId = attributeId;
+    UA_ReadRequest request;
+    UA_ReadRequest_init(&request);
+    request.nodesToRead = &item;
+    request.nodesToReadSize = 1;
+
+    __UA_Client_AsyncService(client, &request, &UA_TYPES[UA_TYPES_READREQUEST],
+            ValueAttributeRead, &UA_TYPES[UA_TYPES_READRESPONSE], userdata,
+            reqId);
+
+    CustomCallback *cc = (CustomCallback*) UA_malloc(sizeof(CustomCallback));
+    if (!cc)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    cc->callback = callback;
+    cc->callbackId = *reqId;
+
+    cc->attributeId = attributeId;
+    cc->outDataType = outDataType;
+
+    LIST_INSERT_HEAD(&client->customCallbacks, cc, pointers);
+
+    return UA_STATUSCODE_GOOD;
+}
+
+/*Write Attributes*/
+UA_StatusCode __UA_Client_writeAttribute_async(UA_Client *client,
+        const UA_NodeId *nodeId, UA_AttributeId attributeId, const void *in,
+        const UA_DataType *inDataType, UA_ClientAsyncServiceCallback callback,
+        void *userdata, UA_UInt32 *reqId) {
+    if (!in)
+        return UA_STATUSCODE_BADTYPEMISMATCH;
+
+    UA_WriteValue wValue;
+    UA_WriteValue_init(&wValue);
+    wValue.nodeId = *nodeId;
+    wValue.attributeId = attributeId;
+    if (attributeId == UA_ATTRIBUTEID_VALUE)
+        wValue.value.value = *(const UA_Variant*) in;
+    else
+        /* hack. is never written into. */
+        UA_Variant_setScalar(&wValue.value.value, (void*) (uintptr_t) in,
+                inDataType);
+    wValue.value.hasValue = true;
+    UA_WriteRequest wReq;
+    UA_WriteRequest_init(&wReq);
+    wReq.nodesToWrite = &wValue;
+    wReq.nodesToWriteSize = 1;
+
+    return __UA_Client_AsyncService(client, &wReq,
+            &UA_TYPES[UA_TYPES_WRITEREQUEST], callback,
+            &UA_TYPES[UA_TYPES_WRITERESPONSE], userdata, reqId);
+}
+
+/*Node Management*/
+
+UA_StatusCode UA_EXPORT
+__UA_Client_addNode_async(UA_Client *client, const UA_NodeClass nodeClass,
+        const UA_NodeId requestedNewNodeId, const UA_NodeId parentNodeId,
+        const UA_NodeId referenceTypeId, const UA_QualifiedName browseName,
+        const UA_NodeId typeDefinition, const UA_NodeAttributes *attr,
+        const UA_DataType *attributeType, UA_NodeId *outNewNodeId,
+        UA_ClientAsyncServiceCallback callback, void *userdata,
+        UA_UInt32 *reqId) {
+    UA_AddNodesRequest request;
+    UA_AddNodesRequest_init(&request);
+    UA_AddNodesItem item;
+    UA_AddNodesItem_init(&item);
+    item.parentNodeId.nodeId = parentNodeId;
+    item.referenceTypeId = referenceTypeId;
+    item.requestedNewNodeId.nodeId = requestedNewNodeId;
+    item.browseName = browseName;
+    item.nodeClass = nodeClass;
+    item.typeDefinition.nodeId = typeDefinition;
+    item.nodeAttributes.encoding = UA_EXTENSIONOBJECT_DECODED_NODELETE;
+    item.nodeAttributes.content.decoded.type = attributeType;
+    item.nodeAttributes.content.decoded.data = (void*) (uintptr_t) attr; // hack. is not written into.
+    request.nodesToAdd = &item;
+    request.nodesToAddSize = 1;
+
+    return __UA_Client_AsyncService(client, &request,
+            &UA_TYPES[UA_TYPES_ADDNODESREQUEST], callback,
+            &UA_TYPES[UA_TYPES_ADDNODESRESPONSE], userdata, reqId);
+
+}
+
+/*Misc Highlevel Functions*/
+UA_StatusCode __UA_Client_call_async(UA_Client *client,
+        const UA_NodeId objectId, const UA_NodeId methodId, size_t inputSize,
+        const UA_Variant *input, UA_ClientAsyncServiceCallback callback,
+        void *userdata, UA_UInt32 *reqId) {
+
+    UA_CallRequest request;
+    UA_CallRequest_init(&request);
+    UA_CallMethodRequest item;
+    UA_CallMethodRequest_init(&item);
+    item.methodId = methodId;
+    item.objectId = objectId;
+    item.inputArguments = (UA_Variant *) (void*) (uintptr_t) input; // cast const...
+    item.inputArgumentsSize = inputSize;
+    request.methodsToCall = &item;
+    request.methodsToCallSize = 1;
+
+    return __UA_Client_AsyncService(client, &request,
+            &UA_TYPES[UA_TYPES_CALLREQUEST], callback,
+            &UA_TYPES[UA_TYPES_CALLRESPONSE], userdata, reqId);
+}
+
+UA_StatusCode __UA_Client_translateBrowsePathsToNodeIds_async(UA_Client *client,
+        char *paths[], UA_UInt32 ids[], size_t pathSize,
+        UA_ClientAsyncServiceCallback callback, void *userdata,
+        UA_UInt32 *reqId) {
+
+    UA_BrowsePath browsePath;
+    UA_BrowsePath_init(&browsePath);
+    browsePath.startingNode = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
+    browsePath.relativePath.elements = (UA_RelativePathElement*) UA_Array_new(
+            pathSize, &UA_TYPES[UA_TYPES_RELATIVEPATHELEMENT]);
+    if (!browsePath.relativePath.elements)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    browsePath.relativePath.elementsSize = pathSize;
+
+    UA_TranslateBrowsePathsToNodeIdsRequest request;
+    UA_TranslateBrowsePathsToNodeIdsRequest_init(&request);
+    request.browsePaths = &browsePath;
+    request.browsePathsSize = 1;
+
+    UA_StatusCode retval = __UA_Client_AsyncService(client, &request,
+            &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSREQUEST], callback,
+            &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSRESPONSE], userdata,
+            reqId);
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_Array_delete(browsePath.relativePath.elements,
+                browsePath.relativePath.elementsSize,
+                &UA_TYPES[UA_TYPES_RELATIVEPATHELEMENT]);
+        return retval;
+    }
+    UA_BrowsePath_deleteMembers(&browsePath);
+    return retval;
+}

+ 71 - 3
src/client/ua_client_internal.h

@@ -17,6 +17,7 @@
 #include "ua_client_highlevel.h"
 #include "ua_client_subscriptions.h"
 #include "../../deps/queue.h"
+#include "ua_timer.h"
 
 /**************************/
 /* Subscriptions Handling */
@@ -78,6 +79,16 @@ UA_Client_Subscriptions_backgroundPublishInactivityCheck(UA_Client *client);
 
 #endif /* UA_ENABLE_SUBSCRIPTIONS */
 
+/**************/
+/* Encryption */
+/**************/
+
+UA_StatusCode
+checkClientSignature(const UA_SecureChannel *channel, const UA_CreateSessionResponse *response);
+
+UA_StatusCode
+signActivateSessionRequest(UA_SecureChannel *channel,
+                           UA_ActivateSessionRequest *request);
 /**********/
 /* Client */
 /**********/
@@ -88,6 +99,9 @@ typedef struct AsyncServiceCall {
     UA_ClientAsyncServiceCallback callback;
     const UA_DataType *responseType;
     void *userdata;
+    UA_DateTime start;
+    UA_UInt32 timeout;
+    void *responsedata;
 } AsyncServiceCall;
 
 void UA_Client_AsyncService_cancel(UA_Client *client, AsyncServiceCall *ac,
@@ -95,6 +109,23 @@ void UA_Client_AsyncService_cancel(UA_Client *client, AsyncServiceCall *ac,
 
 void UA_Client_AsyncService_removeAll(UA_Client *client, UA_StatusCode statusCode);
 
+typedef struct CustomCallback {
+    LIST_ENTRY(CustomCallback)
+    pointers;
+    //to find the correct callback
+    UA_UInt32 callbackId;
+
+    UA_ClientAsyncServiceCallback callback;
+
+    UA_AttributeId attributeId;
+    const UA_DataType *outDataType;
+} CustomCallback;
+
+typedef enum {
+    UA_CHUNK_COMPLETED,
+    UA_CHUNK_NOT_COMPLETED
+} UA_ChunkState;
+
 typedef enum {
     UA_CLIENTAUTHENTICATION_NONE,
     UA_CLIENTAUTHENTICATION_USERNAME
@@ -105,6 +136,8 @@ struct UA_Client {
     UA_ClientState state;
 
     UA_ClientConfig config;
+    UA_Timer timer;
+    UA_StatusCode connectStatus;
 
     /* Connection */
     UA_Connection connection;
@@ -125,10 +158,19 @@ struct UA_Client {
     UA_UserTokenPolicy token;
     UA_NodeId authenticationToken;
     UA_UInt32 requestHandle;
+    /* Connection Establishment (async) */
+    UA_Connection_processChunk ackResponseCallback;
+    UA_Connection_processChunk openSecureChannelResponseCallback;
+    UA_Boolean endpointsHandshake;
 
     /* Async Service */
+    AsyncServiceCall asyncConnectCall;
     LIST_HEAD(ListOfAsyncServiceCall, AsyncServiceCall) asyncServiceCalls;
+    /*When using highlevel functions these are the callbacks that can be accessed by the user*/
+    LIST_HEAD(ListOfCustomCallback, CustomCallback) customCallbacks;
 
+    /* Delayed callbacks */
+    SLIST_HEAD(DelayedClientCallbacksList, UA_DelayedClientCallback) delayedClientCallbacks;
     /* Subscriptions */
 #ifdef UA_ENABLE_SUBSCRIPTIONS
     UA_UInt32 monitoredItemHandles;
@@ -136,6 +178,10 @@ struct UA_Client {
     LIST_HEAD(ListOfClientSubscriptionItems, UA_Client_Subscription) subscriptions;
     UA_UInt16 currentlyOutStandingPublishRequests;
 #endif
+
+    /* Connectivity check */
+    UA_DateTime lastConnectivityCheck;
+    UA_Boolean pendingConnectivityCheck;
 };
 
 void
@@ -146,13 +192,35 @@ UA_Client_connectInternal(UA_Client *client, const char *endpointUrl,
                           UA_Boolean endpointsHandshake, UA_Boolean createNewSession);
 
 UA_StatusCode
-UA_Client_getEndpointsInternal(UA_Client *client, size_t* endpointDescriptionsSize,
+UA_Client_connectInternalAsync(UA_Client *client, const char *endpointUrl,
+                               UA_ClientAsyncServiceCallback callback,
+                               void *connected, UA_Boolean endpointsHandshake,
+                               UA_Boolean createNewSession);
+
+UA_StatusCode
+UA_Client_getEndpointsInternal(UA_Client *client,
+                               size_t* endpointDescriptionsSize,
                                UA_EndpointDescription** endpointDescriptions);
 
 /* Receive and process messages until a synchronous message arrives or the
  * timout finishes */
 UA_StatusCode
-receiveServiceResponse(UA_Client *client, void *response, const UA_DataType *responseType,
-                       UA_DateTime maxDate, UA_UInt32 *synchronousRequestId);
+receivePacketAsync(UA_Client *client);
+
+UA_StatusCode
+receiveServiceResponse(UA_Client *client, void *response,
+                       const UA_DataType *responseType, UA_DateTime maxDate,
+                       UA_UInt32 *synchronousRequestId);
 
+UA_StatusCode
+receiveServiceResponseAsync(UA_Client *client, void *response,
+                             const UA_DataType *responseType);
+void
+UA_Client_workerCallback(UA_Client *client, UA_ClientCallback callback,
+                         void *data);
+UA_StatusCode
+UA_Client_delayedCallback(UA_Client *client, UA_ClientCallback callback,
+                          void *data);
+UA_StatusCode
+UA_Client_connect_iterate (UA_Client *client);
 #endif /* UA_CLIENT_INTERNAL_H_ */

+ 34 - 14
src/client/ua_client_subscriptions.c

@@ -613,6 +613,15 @@ UA_Client_Subscriptions_processPublishResponse(UA_Client *client, UA_PublishRequ
         return;
     }
 
+    if(response->responseHeader.serviceResult == UA_STATUSCODE_BADSESSIONCLOSED) {
+        if(client->state >= UA_CLIENTSTATE_SESSION) {
+            UA_LOG_WARNING(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                           "Received Publish Response with code %s",
+                            UA_StatusCode_name(response->responseHeader.serviceResult));
+        }
+        return;
+    }
+
     if(response->responseHeader.serviceResult == UA_STATUSCODE_BADSESSIONIDINVALID) {
         UA_Client_close(client); /* TODO: This should be handled before the process callback */
         UA_LOG_WARNING(client->config.logger, UA_LOGCATEGORY_CLIENT,
@@ -638,14 +647,23 @@ UA_Client_Subscriptions_processPublishResponse(UA_Client *client, UA_PublishRequ
     sub->lastActivity = UA_DateTime_nowMonotonic();
 
     /* Detect missing message - OPC Unified Architecture, Part 4 5.13.1.1 e) */
-    if((sub->sequenceNumber != msg->sequenceNumber) && (msg->sequenceNumber != 0) &&
-        (UA_Client_Subscriptions_nextSequenceNumber(sub->sequenceNumber) != msg->sequenceNumber)) {
-        UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                     "Invalid subscritpion sequenceNumber");
-        UA_Client_close(client);
-        return;
-    }
-    sub->sequenceNumber = msg->sequenceNumber;
+    if(UA_Client_Subscriptions_nextSequenceNumber(sub->sequenceNumber) != msg->sequenceNumber) {
+        UA_LOG_WARNING(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                     "Invalid subscription sequence number: expected %u but got %u",
+                     UA_Client_Subscriptions_nextSequenceNumber(sub->sequenceNumber),
+                     msg->sequenceNumber);
+        /* This is an error. But we do not abort the connection. Some server
+         * SDKs misbehave from time to time and send out-of-order sequence
+         * numbers. (Probably some multi-threading synchronization issue.) */
+        /* UA_Client_close(client);
+           return; */
+    }
+    /* According to f), a keep-alive message contains no notifications and has the sequence number
+     * of the next NotificationMessage that is to be sent => More than one consecutive keep-alive
+     * message or a NotificationMessage following a keep-alive message will share the same sequence
+     * number. */
+    if (msg->notificationDataSize)
+        sub->sequenceNumber = msg->sequenceNumber;
 
     /* Process the notification messages */
     for(size_t k = 0; k < msg->notificationDataSize; ++k)
@@ -672,7 +690,7 @@ UA_Client_Subscriptions_processPublishResponse(UA_Client *client, UA_PublishRequ
 
 static void
 processPublishResponseAsync(UA_Client *client, void *userdata, UA_UInt32 requestId,
-                            void *response, const UA_DataType *responseType) {
+                            void *response) {
     UA_PublishRequest *req = (UA_PublishRequest*)userdata;
     UA_PublishResponse *res = (UA_PublishResponse*)response;
 
@@ -719,7 +737,7 @@ UA_Client_Subscriptions_backgroundPublishInactivityCheck(UA_Client *client) {
             /* Reset activity */
             sub->lastActivity = UA_DateTime_nowMonotonic();
 
-            if (client->config.subscriptionInactivityCallback)
+            if(client->config.subscriptionInactivityCallback)
                 client->config.subscriptionInactivityCallback(client, sub->subscriptionId, sub->context);
             UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_CLIENT,
                          "Inactivity for Subscription %u.", sub->subscriptionId);
@@ -749,10 +767,12 @@ UA_Client_Subscriptions_backgroundPublish(UA_Client *client) {
     
         UA_UInt32 requestId;
         client->currentlyOutStandingPublishRequests++;
-        retval = __UA_Client_AsyncService(client, request, &UA_TYPES[UA_TYPES_PUBLISHREQUEST],
-                                          processPublishResponseAsync,
-                                          &UA_TYPES[UA_TYPES_PUBLISHRESPONSE],
-                                          (void*)request, &requestId);
+
+        /* Disable the timeout, it is treat in UA_Client_Subscriptions_backgroundPublishInactivityCheck */
+        retval = __UA_Client_AsyncServiceEx(client, request, &UA_TYPES[UA_TYPES_PUBLISHREQUEST],
+                                            processPublishResponseAsync,
+                                            &UA_TYPES[UA_TYPES_PUBLISHRESPONSE],
+                                            (void*)request, &requestId, 0);
         if(retval != UA_STATUSCODE_GOOD) {
             UA_PublishRequest_delete(request);
             return retval;

+ 213 - 0
src/client/ua_client_worker.c

@@ -0,0 +1,213 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ua_util.h"
+#include "ua_client.h"
+#include "ua_client_internal.h"
+
+/**
+ * Worker Threads and Dispatch Queue
+ * ---------------------------------
+ * The worker threads dequeue callbacks from a central Multi-Producer
+ * Multi-Consumer Queue (MPMC). When there are no callbacks, workers go idle.
+ * The condition to wake them up is triggered whenever a callback is
+ * dispatched.
+ *
+ * Future Plans: Use work-stealing to load-balance between cores.
+ * Le, Nhat Minh, et al. "Correct and efficient work-stealing for weak memory
+ * models." ACM SIGPLAN Notices. Vol. 48. No. 8. ACM, 2013. */
+
+/**
+ * Repeated Callbacks
+ * ------------------
+ * Repeated Callbacks are handled by UA_Timer (used in both client and client).
+ * In the multi-threaded case, callbacks are dispatched to workers. Otherwise,
+ * they are executed immediately. */
+
+void UA_Client_workerCallback(UA_Client *client, UA_ClientCallback callback,
+        void *data) {
+    /* Execute immediately */
+    callback(client, data);
+}
+
+/**
+ * Delayed Callbacks
+ * -----------------
+ *
+ * Delayed Callbacks are called only when all callbacks that were dispatched
+ * prior are finished. In the single-threaded case, the callback is added to a
+ * singly-linked list that is processed at the end of the client's main-loop. In
+ * the multi-threaded case, the delay is ensure by a three-step procedure:
+ *
+ * 1. The delayed callback is dispatched to the worker queue. So it is only
+ *    dequeued when all prior callbacks have been dequeued.
+ *
+ * 2. When the callback is first dequeued by a worker, sample the counter of all
+ *    workers. Once all counters have advanced, the callback is ready.
+ *
+ * 3. Check regularly if the callback is ready by adding it back to the dispatch
+ *    queue. */
+
+typedef struct UA_DelayedClientCallback {
+    SLIST_ENTRY(UA_DelayedClientCallback)
+    next;
+    UA_ClientCallback callback;
+    void *data;
+} UA_DelayedClientCallback;
+
+UA_StatusCode UA_Client_delayedCallback(UA_Client *client,
+        UA_ClientCallback callback, void *data) {
+    UA_DelayedClientCallback *dc = (UA_DelayedClientCallback*) UA_malloc(
+            sizeof(UA_DelayedClientCallback));
+    if (!dc)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+
+    dc->callback = callback;
+    dc->data = data;
+    SLIST_INSERT_HEAD(&client->delayedClientCallbacks, dc, next);
+    return UA_STATUSCODE_GOOD;
+}
+
+void
+processDelayedClientCallbacks(UA_Client *client);
+
+void processDelayedClientCallbacks(UA_Client *client) {
+    UA_DelayedClientCallback *dc, *dc_tmp;
+    SLIST_FOREACH_SAFE(dc, &client->delayedClientCallbacks, next, dc_tmp)
+    {
+        SLIST_REMOVE(&client->delayedClientCallbacks, dc,
+                UA_DelayedClientCallback, next);
+        dc->callback(client, dc->data);
+        UA_free(dc);
+    }
+}
+
+static void
+asyncServiceTimeoutCheck(UA_Client *client) {
+    UA_DateTime now = UA_DateTime_nowMonotonic();
+
+    /* Timeout occurs, remove the callback */
+    AsyncServiceCall *ac, *ac_tmp;
+    LIST_FOREACH_SAFE(ac, &client->asyncServiceCalls, pointers, ac_tmp) {
+        if(!ac->timeout)
+           continue;
+
+        if(ac->start + (UA_DateTime)(ac->timeout * UA_DATETIME_MSEC) <= now) {
+            LIST_REMOVE(ac, pointers);
+            UA_Client_AsyncService_cancel(client, ac, UA_STATUSCODE_BADTIMEOUT);
+            UA_free(ac);
+        }
+    }
+}
+
+static void
+backgroundConnectivityCallback(UA_Client *client, void *userdata,
+                               UA_UInt32 requestId, const UA_ReadResponse *response) {
+    if(response->responseHeader.serviceResult == UA_STATUSCODE_BADTIMEOUT) {
+        if (client->config.inactivityCallback)
+            client->config.inactivityCallback(client);
+    }
+    client->pendingConnectivityCheck = false;
+    client->lastConnectivityCheck = UA_DateTime_nowMonotonic();
+}
+
+static UA_StatusCode
+UA_Client_backgroundConnectivity(UA_Client *client) {
+    if(!client->config.connectivityCheckInterval)
+        return UA_STATUSCODE_GOOD;
+
+    if (client->pendingConnectivityCheck)
+        return UA_STATUSCODE_GOOD;
+
+    UA_DateTime now = UA_DateTime_nowMonotonic();
+    UA_DateTime nextDate = client->lastConnectivityCheck + (UA_DateTime)(client->config.connectivityCheckInterval * UA_DATETIME_MSEC);
+
+    if(now <= nextDate)
+        return UA_STATUSCODE_GOOD;
+
+    UA_ReadRequest request;
+    UA_ReadRequest_init(&request);
+
+    UA_ReadValueId rvid;
+    UA_ReadValueId_init(&rvid);
+    rvid.attributeId = UA_ATTRIBUTEID_VALUE;
+    rvid.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE);
+
+    request.nodesToRead = &rvid;
+    request.nodesToReadSize = 1;
+
+    UA_StatusCode retval = __UA_Client_AsyncService(client, &request, &UA_TYPES[UA_TYPES_READREQUEST],
+                                                    (UA_ClientAsyncServiceCallback)backgroundConnectivityCallback,
+                                                    &UA_TYPES[UA_TYPES_READRESPONSE], NULL, NULL);
+
+    client->pendingConnectivityCheck = true;
+
+    return retval;
+}
+
+/**
+ * Main Client Loop
+ * ----------------
+ * Start: Spin up the workers and the network layer
+ * Iterate: Process repeated callbacks and events in the network layer.
+ *          This part can be driven from an external main-loop in an
+ *          event-driven single-threaded architecture.
+ * Stop: Stop workers, finish all callbacks, stop the network layer,
+ *       clean up */
+
+UA_StatusCode UA_Client_run_iterate(UA_Client *client, UA_UInt16 timeout) {
+// TODO connectivity check & timeout features for the async implementation (timeout == 0)
+    UA_StatusCode retval;
+#ifdef UA_ENABLE_SUBSCRIPTIONS
+    UA_StatusCode retvalPublish = UA_Client_Subscriptions_backgroundPublish(client);
+    if(client->state >= UA_CLIENTSTATE_SESSION && retvalPublish != UA_STATUSCODE_GOOD)
+        return retvalPublish;
+#endif
+    if(timeout){
+        retval = UA_Client_manuallyRenewSecureChannel(client);
+        if(retval != UA_STATUSCODE_GOOD)
+            return retval;
+
+        retval = UA_Client_backgroundConnectivity(client);
+        if(retval != UA_STATUSCODE_GOOD)
+            return retval;
+
+        UA_DateTime maxDate = UA_DateTime_nowMonotonic() + (timeout * UA_DATETIME_MSEC);
+        retval = receiveServiceResponse(client, NULL, NULL, maxDate, NULL);
+        if(retval == UA_STATUSCODE_GOODNONCRITICALTIMEOUT)
+            retval = UA_STATUSCODE_GOOD;
+    }
+
+    else{
+        UA_DateTime now = UA_DateTime_nowMonotonic();
+        UA_Timer_process(&client->timer, now,
+                         (UA_TimerDispatchCallback) UA_Client_workerCallback, client);
+
+        UA_ClientState cs = UA_Client_getState(client);
+        retval = UA_Client_connect_iterate(client);
+
+        /* Connection failed, drop the rest */
+        if(retval != UA_STATUSCODE_GOOD)
+            return retval;
+        if((cs == UA_CLIENTSTATE_SECURECHANNEL) || (cs == UA_CLIENTSTATE_SESSION)) {
+            /* Check for new data */
+            retval = receiveServiceResponseAsync(client, NULL, NULL);
+        } else {
+            retval = receivePacketAsync(client);
+        }
+
+    }
+#ifdef UA_ENABLE_SUBSCRIPTIONS
+        /* The inactivity check must be done after receiveServiceResponse*/
+        UA_Client_Subscriptions_backgroundPublishInactivityCheck(client);
+#endif
+        asyncServiceTimeoutCheck(client);
+
+#ifndef UA_ENABLE_MULTITHREADING
+/* Process delayed callbacks when all callbacks and
+ * network events are done */
+    processDelayedClientCallbacks(client);
+#endif
+    return retval;
+}

文件差异内容过多而无法显示
+ 889 - 36
src/pubsub/ua_pubsub.c


+ 94 - 4
src/pubsub/ua_pubsub.h

@@ -12,12 +12,16 @@
 extern "C" {
 #endif
 
-#include <queue.h>
+#include "../deps/queue.h"
 #include "ua_plugin_pubsub.h"
 #include "ua_pubsub_networkmessage.h"
 #include "ua_server.h"
 #include "ua_server_pubsub.h"
 
+//forward declarations
+struct UA_WriterGroup;
+typedef struct UA_WriterGroup UA_WriterGroup;
+
 /* The configuration structs (public part of PubSub entities) are defined in include/ua_plugin_pubsub.h */
 
 /**********************************************/
@@ -26,7 +30,7 @@ extern "C" {
 typedef struct{
     UA_PublishedDataSetConfig config;
     UA_DataSetMetaDataType dataSetMetaData;
-    LIST_HEAD(UA_ListOfPubSubDataSetField, UA_PubSubDataSetField) fields;
+    LIST_HEAD(UA_ListOfDataSetField, UA_DataSetField) fields;
     UA_NodeId identifier;
     UA_UInt16 fieldSize;
     UA_UInt16 promotedFieldsCount;
@@ -37,7 +41,7 @@ UA_PublishedDataSetConfig_copy(const UA_PublishedDataSetConfig *src, UA_Publishe
 UA_PublishedDataSet *
 UA_PublishedDataSet_findPDSbyId(UA_Server *server, UA_NodeId identifier);
 void
-UA_PublishedDataSet_delete(UA_PublishedDataSet *publishedDataSet);
+UA_PublishedDataSet_deleteMembers(UA_Server *server, UA_PublishedDataSet *publishedDataSet);
 
 /**********************************************/
 /*               Connection                   */
@@ -48,6 +52,7 @@ typedef struct{
     //internal fields
     UA_PubSubChannel *channel;
     UA_NodeId identifier;
+    LIST_HEAD(UA_ListOfWriterGroup, UA_WriterGroup) writerGroups;
 } UA_PubSubConnection;
 
 UA_StatusCode
@@ -57,7 +62,92 @@ UA_PubSubConnection_findConnectionbyId(UA_Server *server, UA_NodeId connectionId
 void
 UA_PubSubConnectionConfig_deleteMembers(UA_PubSubConnectionConfig *connectionConfig);
 void
-UA_PubSubConnection_delete(UA_PubSubConnection *connection);
+UA_PubSubConnection_deleteMembers(UA_Server *server, UA_PubSubConnection *connection);
+
+/**********************************************/
+/*              DataSetWriter                 */
+/**********************************************/
+
+typedef struct UA_DataSetWriterSample{
+    UA_Boolean valeChanged;
+    UA_DataValue *value;
+} UA_DataSetWriterSample;
+
+typedef struct UA_DataSetWriter{
+    UA_DataSetWriterConfig config;
+    //internal fields
+    LIST_ENTRY(UA_DataSetWriter) listEntry;
+    UA_NodeId identifier;
+    UA_NodeId linkedWriterGroup;
+    UA_NodeId connectedDataSet;
+    UA_ConfigurationVersionDataType connectedDataSetVersion;
+    UA_UInt16 deltaFrameCounter;            //actual count of sent deltaFrames
+    size_t lastSamplesCount;
+    UA_DataSetWriterSample *lastSamples;
+    UA_UInt16 actualDataSetMessageSequenceCount;
+} UA_DataSetWriter;
+
+UA_StatusCode
+UA_DataSetWriterConfig_copy(const UA_DataSetWriterConfig *src, UA_DataSetWriterConfig *dst);
+UA_DataSetWriter *
+UA_DataSetWriter_findDSWbyId(UA_Server *server, UA_NodeId identifier);
+void
+UA_DataSetWriter_deleteMembers(UA_Server *server, UA_DataSetWriter *dataSetWriter);
+
+/**********************************************/
+/*               WriterGroup                  */
+/**********************************************/
+
+struct UA_WriterGroup{
+    UA_WriterGroupConfig config;
+    //internal fields
+    LIST_ENTRY(UA_WriterGroup) listEntry;
+    UA_NodeId identifier;
+    UA_NodeId linkedConnection;
+    LIST_HEAD(UA_ListOfDataSetWriter, UA_DataSetWriter) writers;
+    UA_UInt32 writersCount;
+    UA_UInt64 publishCallbackId;
+    UA_Boolean publishCallbackIsRegistered;
+};
+
+UA_StatusCode
+UA_WriterGroupConfig_copy(const UA_WriterGroupConfig *src, UA_WriterGroupConfig *dst);
+UA_WriterGroup *
+UA_WriterGroup_findWGbyId(UA_Server *server, UA_NodeId identifier);
+void
+UA_WriterGroup_deleteMembers(UA_Server *server, UA_WriterGroup *writerGroup);
+
+/**********************************************/
+/*               DataSetField                 */
+/**********************************************/
+
+typedef struct UA_DataSetField{
+    UA_DataSetFieldConfig config;
+    //internal fields
+    LIST_ENTRY(UA_DataSetField) listEntry;
+    UA_NodeId identifier;
+    UA_NodeId publishedDataSet;             //ref to parent pds
+    UA_FieldMetaData fieldMetaData;
+    UA_UInt64 sampleCallbackId;
+    UA_Boolean sampleCallbackIsRegistered;
+    UA_DataValue lastValue;
+} UA_DataSetField;
+
+UA_StatusCode
+UA_DataSetFieldConfig_copy(const UA_DataSetFieldConfig *src, UA_DataSetFieldConfig *dst);
+UA_DataSetField *
+UA_DataSetField_findDSFbyId(UA_Server *server, UA_NodeId identifier);
+void
+UA_DataSetField_deleteMembers(UA_DataSetField *field);
+
+/*********************************************************/
+/*               PublishValues handling                  */
+/*********************************************************/
+
+UA_StatusCode
+UA_WriterGroup_addPublishCallback(UA_Server *server, UA_WriterGroup *writerGroup);
+void
+UA_WriterGroup_publishCallback(UA_Server *server, UA_WriterGroup *writerGroup);
 
 #ifdef __cplusplus
 } // extern "C"

+ 86 - 61
src/pubsub/ua_pubsub_manager.c

@@ -5,19 +5,12 @@
  * Copyright (c) 2017-2018 Fraunhofer IOSB (Author: Andreas Ebner)
  */
 
-#include "ua_pubsub_manager.h"
 #include "server/ua_server_internal.h"
 
+#ifdef UA_ENABLE_PUBSUB /* conditional compilation */
+
 #define UA_DATETIMESTAMP_2000 125911584000000000
 
-/**
- * Add new Connection to the current PubSub configuration.
- *
- * @param server
- * @param connectionConfig config of the new Connection
- * @param connectionIdentifier nodeId of the generated Connection (NULL if not needed)
- * @return UA_STATUSCODE_GOOD if success
- */
 UA_StatusCode
 UA_Server_addPubSubConnection(UA_Server *server, const UA_PubSubConnectionConfig *connectionConfig,
                               UA_NodeId *connectionIdentifier) {
@@ -53,10 +46,17 @@ UA_Server_addPubSubConnection(UA_Server *server, const UA_PubSubConnectionConfig
             server->pubSubManager.connections = newConnectionsField;
             UA_PubSubConnection *newConnection = &server->pubSubManager.connections[server->pubSubManager.connectionsSize];
             memset(newConnection, 0, sizeof(UA_PubSubConnection));
+            LIST_INIT(&newConnection->writerGroups);
+            //workaround - fixing issue with queue.h and realloc.
+            for(size_t n = 0; n < server->pubSubManager.connectionsSize; n++){
+                if(server->pubSubManager.connections[n].writerGroups.lh_first){
+                    server->pubSubManager.connections[n].writerGroups.lh_first->listEntry.le_prev = &server->pubSubManager.connections[n].writerGroups.lh_first;
+                }
+            }
             newConnection->config = tmpConnectionConfig;
             newConnection->channel = server->config.pubsubTransportLayers[i].createPubSubChannel(newConnection->config);
             if(!newConnection->channel){
-                UA_PubSubConnection_delete(newConnection);
+                UA_PubSubConnection_deleteMembers(server, newConnection);
                 if(server->pubSubManager.connectionsSize > 0){
                     newConnectionsField = (UA_PubSubConnection *)
                             UA_realloc(server->pubSubManager.connections,
@@ -86,55 +86,49 @@ UA_Server_addPubSubConnection(UA_Server *server, const UA_PubSubConnectionConfig
     return UA_STATUSCODE_BADNOTFOUND;
 }
 
-/**
- * Remove Connection, identified by the NodeId. Deletion of Connection
- * removes all contained WriterGroups and Writers.
- *
- * @param server
- * @param connectionIdentifier
- * @return UA_STATUSCODE_GOOD on success
- */
-UA_StatusCode UA_Server_removePubSubConnection(UA_Server *server, UA_NodeId connectionIdentifier) {
+UA_StatusCode
+UA_Server_removePubSubConnection(UA_Server *server, const UA_NodeId connection) {
     //search the identified Connection and store the Connection index
     size_t connectionIndex;
     UA_PubSubConnection *currentConnection = NULL;
     for(connectionIndex = 0; connectionIndex < server->pubSubManager.connectionsSize; connectionIndex++){
-        if(UA_NodeId_equal(&connectionIdentifier, &server->pubSubManager.connections[connectionIndex].identifier)){
+        if(UA_NodeId_equal(&connection, &server->pubSubManager.connections[connectionIndex].identifier)){
             currentConnection = &server->pubSubManager.connections[connectionIndex];
             break;
         }
     }
-    if(!currentConnection){
+    if(!currentConnection)
         return UA_STATUSCODE_BADNOTFOUND;
-    }
-    UA_PubSubConnection_delete(currentConnection);
+
+    UA_PubSubConnection_deleteMembers(server, currentConnection);
     server->pubSubManager.connectionsSize--;
-    //remove the connection from the pubSubManager, move the last connection into the allocated memory of the deleted connection
+    //remove the connection from the pubSubManager, move the last connection
+    //into the allocated memory of the deleted connection
     if(server->pubSubManager.connectionsSize != connectionIndex){
         memcpy(&server->pubSubManager.connections[connectionIndex],
-               &server->pubSubManager.connections[server->pubSubManager.connectionsSize], sizeof(UA_PubSubConnection));
+               &server->pubSubManager.connections[server->pubSubManager.connectionsSize],
+               sizeof(UA_PubSubConnection));
     }
+
     if(server->pubSubManager.connectionsSize <= 0){
         UA_free(server->pubSubManager.connections);
         server->pubSubManager.connections = NULL;
-    }  else {
+    } else {
         server->pubSubManager.connections = (UA_PubSubConnection *)
                 UA_realloc(server->pubSubManager.connections, sizeof(UA_PubSubConnection) * server->pubSubManager.connectionsSize);
         if(!server->pubSubManager.connections){
             return UA_STATUSCODE_BADINTERNALERROR;
         }
+        //workaround - fixing issue with queue.h and realloc.
+        for(size_t n = 0; n < server->pubSubManager.connectionsSize; n++){
+            if(server->pubSubManager.connections[n].writerGroups.lh_first){
+                server->pubSubManager.connections[n].writerGroups.lh_first->listEntry.le_prev = &server->pubSubManager.connections[n].writerGroups.lh_first;
+            }
+        }
     }
     return UA_STATUSCODE_GOOD;
 }
 
-/**
- * Add PublishedDataSet to the current PubSub configuration.
- *
- * @param server
- * @param publishedDataSetConfig config of the new PDS
- * @param pdsIdentifier nodeId of the generated PDS (NULL if not needed)
- * @return UA_STATUSCODE_GOOD on success
- */
 UA_AddPublishedDataSetResult
 UA_Server_addPublishedDataSet(UA_Server *server, const UA_PublishedDataSetConfig *publishedDataSetConfig,
                               UA_NodeId *pdsIdentifier) {
@@ -169,6 +163,13 @@ UA_Server_addPublishedDataSet(UA_Server *server, const UA_PublishedDataSetConfig
     server->pubSubManager.publishedDataSets = newPubSubDataSetField;
     UA_PublishedDataSet *newPubSubDataSet = &server->pubSubManager.publishedDataSets[server->pubSubManager.publishedDataSetsSize];
     memset(newPubSubDataSet, 0, sizeof(UA_PublishedDataSet));
+    LIST_INIT(&newPubSubDataSet->fields);
+    //workaround - fixing issue with queue.h and realloc.
+    for(size_t n = 0; n < server->pubSubManager.publishedDataSetsSize; n++){
+        if(server->pubSubManager.publishedDataSets[n].fields.lh_first){
+            server->pubSubManager.publishedDataSets[n].fields.lh_first->listEntry.le_prev = &server->pubSubManager.publishedDataSets[n].fields.lh_first;
+        }
+    }
     newPubSubDataSet->config = tmpPublishedDataSetConfig;
     if(tmpPublishedDataSetConfig.publishedDataSetType == UA_PUBSUB_DATASET_PUBLISHEDITEMS_TEMPLATE){
         //parse template config and add fields (later PubSub batch)
@@ -185,22 +186,13 @@ UA_Server_addPublishedDataSet(UA_Server *server, const UA_PublishedDataSetConfig
     return result;
 }
 
-/**
- * Remove PublishedDataSet, identified by the NodeId. Deletion of PDS
- * removes all contained and linked PDS Fields. Connected WriterGroups
- * will be also removed.
- *
- * @param server
- * @param pdsIdentifier
- * @return UA_STATUSCODE_GOOD on success
- */
 UA_StatusCode
-UA_Server_removePublishedDataSet(UA_Server *server, UA_NodeId pdsIdentifier) {
+UA_Server_removePublishedDataSet(UA_Server *server, UA_NodeId pds) {
     //search the identified PublishedDataSet and store the PDS index
     UA_PublishedDataSet *publishedDataSet = NULL;
     size_t publishedDataSetIndex;
     for(publishedDataSetIndex = 0; publishedDataSetIndex < server->pubSubManager.publishedDataSetsSize; publishedDataSetIndex++){
-        if(UA_NodeId_equal(&server->pubSubManager.publishedDataSets[publishedDataSetIndex].identifier, &pdsIdentifier)){
+        if(UA_NodeId_equal(&server->pubSubManager.publishedDataSets[publishedDataSetIndex].identifier, &pds)){
             publishedDataSet = &server->pubSubManager.publishedDataSets[publishedDataSetIndex];
             break;
         };
@@ -208,7 +200,19 @@ UA_Server_removePublishedDataSet(UA_Server *server, UA_NodeId pdsIdentifier) {
     if(!publishedDataSet){
         return UA_STATUSCODE_BADNOTFOUND;
     }
-    UA_PublishedDataSet_delete(publishedDataSet);
+    //search for referenced writers -> delete this writers. (Standard: writer must be connected with PDS)
+    for(size_t i = 0; i < server->pubSubManager.connectionsSize; i++){
+        UA_WriterGroup *writerGroup;
+        LIST_FOREACH(writerGroup, &server->pubSubManager.connections[i].writerGroups, listEntry){
+            UA_DataSetWriter *currentWriter, *tmpWriterGroup;
+            LIST_FOREACH_SAFE(currentWriter, &writerGroup->writers, listEntry, tmpWriterGroup){
+                if(UA_NodeId_equal(&currentWriter->identifier, &publishedDataSet->identifier)){
+                    UA_Server_removeDataSetWriter(server, currentWriter->identifier);
+                }
+            }
+        }
+    }
+    UA_PublishedDataSet_deleteMembers(server, publishedDataSet);
     server->pubSubManager.publishedDataSetsSize--;
     //copy the last PDS to the removed PDS inside the allocated memory block
     if(server->pubSubManager.publishedDataSetsSize != publishedDataSetIndex){
@@ -222,26 +226,29 @@ UA_Server_removePublishedDataSet(UA_Server *server, UA_NodeId pdsIdentifier) {
     } else {
         server->pubSubManager.publishedDataSets = (UA_PublishedDataSet *)
                 UA_realloc(server->pubSubManager.publishedDataSets, sizeof(UA_PublishedDataSet) * server->pubSubManager.publishedDataSetsSize);
-        if (!server->pubSubManager.publishedDataSets) {
+        if(!server->pubSubManager.publishedDataSets){
             return UA_STATUSCODE_BADINTERNALERROR;
         }
+        //workaround - fixing issue with queue.h and realloc.
+        for(size_t n = 0; n < server->pubSubManager.publishedDataSetsSize; n++){
+            if(server->pubSubManager.publishedDataSets[n].fields.lh_first){
+                server->pubSubManager.publishedDataSets[n].fields.lh_first->listEntry.le_prev = &server->pubSubManager.publishedDataSets[n].fields.lh_first;
+            }
+        }
     }
     return UA_STATUSCODE_GOOD;
 }
 
-/**
- * Calculate the time difference between current time and UTC (00:00) on January 1, 2000.
- */
+/* Calculate the time difference between current time and UTC (00:00) on January
+ * 1, 2000. */
 UA_UInt32
 UA_PubSubConfigurationVersionTimeDifference() {
     UA_UInt32 timeDiffSince2000 = (UA_UInt32) (UA_DateTime_now() - UA_DATETIMESTAMP_2000);
     return timeDiffSince2000;
 }
 
-/**
- * Generate a new unique NodeId. This NodeId will be used for the
- * information model representation of PubSub entities.
- */
+/* Generate a new unique NodeId. This NodeId will be used for the information
+ * model representation of PubSub entities. */
 void
 UA_PubSubManager_generateUniqueNodeId(UA_Server *server, UA_NodeId *nodeId) {
     UA_NodeId newNodeId = UA_NODEID_NUMERIC(0, 0);
@@ -250,13 +257,8 @@ UA_PubSubManager_generateUniqueNodeId(UA_Server *server, UA_NodeId *nodeId) {
     UA_NodeId_copy(&newNodeId, nodeId);
 }
 
-/**
- * Delete the current PubSub configuration including all nested members. This action also delete
- * the configured PubSub transport Layers.
- *
- * @param server
- * @param pubSubManager
- */
+/* Delete the current PubSub configuration including all nested members. This
+ * action also delete the configured PubSub transport Layers. */
 void
 UA_PubSubManager_delete(UA_Server *server, UA_PubSubManager *pubSubManager) {
     UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER, "PubSub cleanup was called.");
@@ -272,3 +274,26 @@ UA_PubSubManager_delete(UA_Server *server, UA_PubSubManager *pubSubManager) {
         UA_free(&server->config.pubsubTransportLayers[i]);
     }
 }
+
+/***********************************/
+/*      PubSub Jobs abstraction    */
+/***********************************/
+UA_StatusCode
+UA_PubSubManager_addRepeatedCallback(UA_Server *server, UA_ServerCallback callback,
+                                     void *data, UA_UInt32 interval, UA_UInt64 *callbackId) {
+    return UA_Timer_addRepeatedCallback(&server->timer, (UA_TimerCallback)callback,
+                                        data, interval, callbackId);
+}
+
+UA_StatusCode
+UA_PubSubManager_changeRepeatedCallbackInterval(UA_Server *server, UA_UInt64 callbackId,
+                                                UA_UInt32 interval) {
+    return UA_Timer_changeRepeatedCallbackInterval(&server->timer, callbackId, interval);
+}
+
+UA_StatusCode
+UA_PubSubManager_removeRepeatedPubSubCallback(UA_Server *server, UA_UInt64 callbackId) {
+    return UA_Timer_removeRepeatedCallback(&server->timer, callbackId);
+}
+
+#endif /* UA_ENABLE_PUBSUB */

+ 16 - 2
src/pubsub/ua_pubsub_manager.h

@@ -23,13 +23,27 @@ typedef struct UA_PubSubManager{
     UA_PublishedDataSet *publishedDataSets;
 } UA_PubSubManager;
 
-void UA_PubSubManager_delete(UA_Server *server, UA_PubSubManager *pubSubManager);
+void
+UA_PubSubManager_delete(UA_Server *server, UA_PubSubManager *pubSubManager);
 
-void UA_PubSubManager_generateUniqueNodeId(UA_Server *server, UA_NodeId *nodeId);
+void
+UA_PubSubManager_generateUniqueNodeId(UA_Server *server, UA_NodeId *nodeId);
 
 UA_UInt32
 UA_PubSubConfigurationVersionTimeDifference(void);
 
+/***********************************/
+/*      PubSub Jobs abstraction    */
+/***********************************/
+UA_StatusCode
+UA_PubSubManager_addRepeatedCallback(UA_Server *server, UA_ServerCallback callback,
+                                     void *data, UA_UInt32 interval, UA_UInt64 *callbackId);
+UA_StatusCode
+UA_PubSubManager_changeRepeatedCallbackInterval(UA_Server *server, UA_UInt64 callbackId,
+                                                UA_UInt32 interval);
+UA_StatusCode
+UA_PubSubManager_removeRepeatedPubSubCallback(UA_Server *server, UA_UInt64 callbackId);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif

+ 48 - 26
src/pubsub/ua_pubsub_networkmessage.c

@@ -13,6 +13,8 @@
 #include "ua_types_generated_handling.h"
 #include "ua_log_stdout.h"
 
+#ifdef UA_ENABLE_PUBSUB /* conditional compilation */
+
 const UA_Byte NM_VERSION_MASK = 15;
 const UA_Byte NM_PUBLISHER_ID_ENABLED_MASK = 16;
 const UA_Byte NM_GROUP_HEADER_ENABLED_MASK = 32;
@@ -325,20 +327,26 @@ UA_NetworkMessage_encodeBinary(const UA_NetworkMessage* src, UA_Byte **bufPos,
     if(rv != UA_STATUSCODE_GOOD)
         return rv;
 
-    // SecurityFooter
-    if(src->securityHeader.securityFooterEnabled) {
-        for (UA_Byte i = 0; i < src->securityHeader.securityFooterSize; i++) {
-            rv = UA_Byte_encodeBinary(&(src->securityFooter.data[i]), bufPos, bufEnd);
-            if(rv != UA_STATUSCODE_GOOD)
+    if (src->securityEnabled)
+    {
+        // SecurityFooter
+        if (src->securityHeader.securityFooterEnabled) {
+            for (UA_Byte i = 0; i < src->securityHeader.securityFooterSize; i++) {
+                rv = UA_Byte_encodeBinary(&(src->securityFooter.data[i]), bufPos, bufEnd);
+                if (rv != UA_STATUSCODE_GOOD)
+                    return rv;
+            }
+        }
+
+        // Signature
+        if (src->securityHeader.networkMessageSigned)
+        {
+            rv = UA_ByteString_encodeBinary(&(src->signature), bufPos, bufEnd);
+            if (rv != UA_STATUSCODE_GOOD)
                 return rv;
         }
     }
 
-    // Signature
-    rv = UA_ByteString_encodeBinary(&(src->signature), bufPos, bufEnd);
-    if(rv != UA_STATUSCODE_GOOD)
-        return rv;
-
     retval = UA_STATUSCODE_GOOD;
     return retval;
 }
@@ -631,24 +639,30 @@ UA_NetworkMessage_decodeBinaryInternal(const UA_ByteString *src, size_t *offset,
     if(rv != UA_STATUSCODE_GOOD)
         return rv;
 
-    // SecurityFooter
-    if(dst->securityHeader.securityFooterEnabled && (dst->securityHeader.securityFooterSize > 0)) {
-        rv = UA_ByteString_allocBuffer(&dst->securityFooter, dst->securityHeader.securityFooterSize);
-        if(rv != UA_STATUSCODE_GOOD)
-            return rv;
+    if (dst->securityEnabled)
+    {
+        // SecurityFooter
+        if (dst->securityHeader.securityFooterEnabled && (dst->securityHeader.securityFooterSize > 0)) {
+            rv = UA_ByteString_allocBuffer(&dst->securityFooter, dst->securityHeader.securityFooterSize);
+            if (rv != UA_STATUSCODE_GOOD)
+                return rv;
 
-        for (UA_Byte i = 0; i < dst->securityHeader.securityFooterSize; i++) {
-            rv = UA_Byte_decodeBinary(src, offset, &(dst->securityFooter.data[i]));
-            if(rv != UA_STATUSCODE_GOOD)
+            for (UA_Byte i = 0; i < dst->securityHeader.securityFooterSize; i++) {
+                rv = UA_Byte_decodeBinary(src, offset, &(dst->securityFooter.data[i]));
+                if (rv != UA_STATUSCODE_GOOD)
+                    return rv;
+            }
+        }
+
+        // Signature
+        if (dst->securityHeader.networkMessageSigned)
+        {
+            rv = UA_ByteString_decodeBinary(src, offset, &(dst->signature));
+            if (rv != UA_STATUSCODE_GOOD)
                 return rv;
         }
     }
 
-    // Signature.
-    rv = UA_ByteString_decodeBinary(src, offset, &(dst->signature));
-    if(rv != UA_STATUSCODE_GOOD)
-        return rv;
-    
     retval = UA_STATUSCODE_GOOD;
     return retval;
 }
@@ -766,10 +780,16 @@ size_t UA_NetworkMessage_calcSizeBinary(const UA_NetworkMessage* p) {
             size += UA_DataSetMessage_calcSizeBinary(&(p->payload.dataSetPayload.dataSetMessages[i]));
     }
 
-    if(p->securityHeader.securityFooterEnabled)
-        size += p->securityHeader.securityFooterSize;
+    if (p->securityEnabled)
+    {
+        if (p->securityHeader.securityFooterEnabled)
+            size += p->securityHeader.securityFooterSize;
 
-    size += UA_ByteString_calcSizeBinary(&p->signature);
+        if (p->securityHeader.networkMessageSigned)
+        {
+            size += UA_ByteString_calcSizeBinary(&p->signature);
+        }
+    }
 
     retval = size;
     return retval;
@@ -1288,3 +1308,5 @@ void UA_DataSetMessage_free(const UA_DataSetMessage* p) {
         }
     }
 }
+
+#endif /* UA_ENABLE_PUBSUB */

+ 12 - 0
src/server/ua_server.c

@@ -22,6 +22,10 @@
 #include "ua_namespaceinit_generated.h"
 #endif
 
+#ifdef UA_ENABLE_SUBSCRIPTIONS
+#include "ua_subscription.h"
+#endif
+
 /**********************/
 /* Namespace Handling */
 /**********************/
@@ -105,6 +109,14 @@ void UA_Server_delete(UA_Server *server) {
     UA_SessionManager_deleteMembers(&server->sessionManager);
     UA_Array_delete(server->namespaces, server->namespacesSize, &UA_TYPES[UA_TYPES_STRING]);
 
+#ifdef UA_ENABLE_SUBSCRIPTIONS
+    UA_MonitoredItem *mon, *mon_tmp;
+    LIST_FOREACH_SAFE(mon, &server->localMonitoredItems, listEntry, mon_tmp) {
+        LIST_REMOVE(mon, listEntry);
+        UA_MonitoredItem_delete(server, mon);
+    }
+#endif
+
 #ifdef UA_ENABLE_PUBSUB
     UA_PubSubManager_delete(server, &server->pubSubManager);
 #endif

+ 20 - 0
src/server/ua_server_internal.h

@@ -30,6 +30,20 @@ extern "C" {
 #include "ua_pubsub_manager.h"
 #endif
 
+#ifdef UA_ENABLE_SUBSCRIPTIONS
+#include "ua_subscription.h"
+
+typedef struct {
+    UA_MonitoredItem monitoredItem;
+    void *context;
+    union {
+        UA_Server_DataChangeNotificationCallback dataChangeCallback;
+        /* UA_Server_EventNotificationCallback eventCallback; */
+    } callback;
+} UA_LocalMonitoredItem;
+
+#endif
+
 #ifdef UA_ENABLE_MULTITHREADING
 
 #include <pthread.h>
@@ -141,6 +155,12 @@ struct UA_Server {
      * the parent and member instantiation */
     UA_Boolean bootstrapNS0;
 
+#ifdef UA_ENABLE_SUBSCRIPTIONS
+    /* To be cast to UA_LocalMonitoredItem to get the callback and context */
+    LIST_HEAD(LocalMonitoredItems, UA_MonitoredItem) localMonitoredItems;
+    UA_UInt32 lastLocalMonitoredItemId;
+#endif
+
 #ifdef UA_ENABLE_PUBSUB
     /* Publish/Subscribe toplevel container */
     UA_PubSubManager pubSubManager;

+ 18 - 0
src/server/ua_server_ns0.c

@@ -13,6 +13,8 @@
 #include "ua_namespace0.h"
 #include "ua_subscription.h"
 #include "ua_session.h"
+#include "ua_subscription_events.h"
+
 
 /*****************/
 /* Node Creation */
@@ -741,6 +743,22 @@ UA_Server_initNS0(UA_Server *server) {
                         UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS), readMonitoredItems);
 #endif
 
+
+    /* create the OverFlowEventType
+     * The EventQueueOverflowEventType is defined as abstract, therefore we can not create an instance of that type
+     * directly, but need to create a subtype. This is already posted on the OPC Foundation bug tracker under the
+     * following link for clarification: https://opcfoundation-onlineapplications.org/mantis/view.php?id=4206 */
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+    UA_ObjectTypeAttributes overflowAttr = UA_ObjectTypeAttributes_default;
+    overflowAttr.description = UA_LOCALIZEDTEXT("en-US", "A simple event for indicating a queue overflow.");
+    overflowAttr.displayName = UA_LOCALIZEDTEXT("en-US", "SimpleOverflowEventType");
+    UA_Server_addObjectTypeNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SIMPLEOVERFLOWEVENTTYPE),
+                                UA_NODEID_NUMERIC(0, UA_NS0ID_EVENTQUEUEOVERFLOWEVENTTYPE),
+                                UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
+                                UA_QUALIFIEDNAME(0, "SimpleOverflowEventType"),
+                                overflowAttr, NULL, NULL);
+#endif
+
     if(retVal != UA_STATUSCODE_GOOD)
         UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
                      "Initialization of Namespace 0 (after bootstrapping) "

+ 10 - 5
src/server/ua_server_utils.c

@@ -354,27 +354,32 @@ UA_StatusCode
 UA_Server_editNode(UA_Server *server, UA_Session *session,
                    const UA_NodeId *nodeId, UA_EditNodeCallback callback,
                    void *data) {
-#ifndef UA_ENABLE_MULTITHREADING
+#ifndef UA_ENABLE_IMMUTABLE_NODES
+    /* Get the node and process it in-situ */
     const UA_Node *node = UA_Nodestore_get(server, nodeId);
     if(!node)
         return UA_STATUSCODE_BADNODEIDUNKNOWN;
-    UA_StatusCode retval = callback(server, session,
-                                    (UA_Node*)(uintptr_t)node, data);
+    UA_StatusCode retval = callback(server, session, (UA_Node*)(uintptr_t)node, data);
     UA_Nodestore_release(server, node);
     return retval;
 #else
     UA_StatusCode retval;
     do {
+        /* Get an editable copy of the node */
         UA_Node *node;
-        retval = server->config.nodestore.getNodeCopy(server->config.nodestore.context,
-                                                      nodeId, &node);
+        retval = server->config.nodestore.
+            getNodeCopy(server->config.nodestore.context, nodeId, &node);
         if(retval != UA_STATUSCODE_GOOD)
             return retval;
+
+        /* Run the operation on the copy */
         retval = callback(server, session, node, data);
         if(retval != UA_STATUSCODE_GOOD) {
             server->config.nodestore.deleteNode(server->config.nodestore.context, node);
             return retval;
         }
+
+        /* Replace the node */
         retval = server->config.nodestore.replaceNode(server->config.nodestore.context, node);
     } while(retval != UA_STATUSCODE_GOOD);
     return retval;

+ 1 - 0
src/server/ua_server_worker.c

@@ -16,6 +16,7 @@
 
 #include "ua_util.h"
 #include "ua_server_internal.h"
+
 #ifdef UA_ENABLE_VALGRIND_INTERACTIVE
 #include <valgrind/memcheck.h>
 #endif

+ 2 - 0
src/server/ua_services.h

@@ -350,9 +350,11 @@ void Service_Write(UA_Server *server, UA_Session *session,
  * Used to call (invoke) a 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 discarded. */
+#ifdef UA_ENABLE_METHODCALLS
 void Service_Call(UA_Server *server, UA_Session *session,
                   const UA_CallRequest *request,
                   UA_CallResponse *response);
+#endif
 
 /**
  * MonitoredItem Service Set

+ 74 - 120
src/server/ua_services_discovery.c

@@ -74,12 +74,10 @@ setApplicationDescriptionFromRegisteredServer(const UA_FindServersRequest *reque
 static UA_StatusCode
 setApplicationDescriptionFromServer(UA_ApplicationDescription *target, const UA_Server *server) {
     /* Copy ApplicationDescription from the config */
-
-    UA_StatusCode result = UA_ApplicationDescription_copy(&server->config.applicationDescription,
-                                                          target);
-    if(result != UA_STATUSCODE_GOOD) {
+    UA_StatusCode result = UA_ApplicationDescription_copy(&server->config.applicationDescription, target);
+    if(result != UA_STATUSCODE_GOOD)
         return result;
-    }
+
     // UaExpert does not list DiscoveryServer, thus set it to Server
     // See http://forum.unified-automation.com/topic1987.html
     if(target->applicationType == UA_APPLICATIONTYPE_DISCOVERYSERVER)
@@ -88,9 +86,8 @@ setApplicationDescriptionFromServer(UA_ApplicationDescription *target, const UA_
     /* add the discoveryUrls from the networklayers */
     size_t discSize = sizeof(UA_String) * (target->discoveryUrlsSize + server->config.networkLayersSize);
     UA_String* disc = (UA_String *)UA_realloc(target->discoveryUrls, discSize);
-    if(!disc) {
+    if(!disc)
         return UA_STATUSCODE_BADOUTOFMEMORY;
-    }
     size_t existing = target->discoveryUrlsSize;
     target->discoveryUrls = disc;
     target->discoveryUrlsSize += server->config.networkLayersSize;
@@ -106,139 +103,96 @@ setApplicationDescriptionFromServer(UA_ApplicationDescription *target, const UA_
 void Service_FindServers(UA_Server *server, UA_Session *session,
                          const UA_FindServersRequest *request,
                          UA_FindServersResponse *response) {
-    UA_LOG_DEBUG_SESSION(server->config.logger, session,
-                         "Processing FindServersRequest");
-
-    size_t foundServersSize = 0;
-    UA_ApplicationDescription *foundServers = NULL;
-
-    UA_Boolean addSelf = UA_FALSE;
-    // temporarily store all the pointers which we found to avoid reiterating
-    // through the list
-    UA_RegisteredServer **foundServerFilteredPointer = NULL;
+    UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing FindServersRequest");
 
-#ifdef UA_ENABLE_DISCOVERY
-    // check if client only requested a specific set of servers
-    if(request->serverUrisSize) {
-        size_t fsfpSize = sizeof(UA_RegisteredServer*) * server->registeredServersSize;
-        foundServerFilteredPointer = (UA_RegisteredServer **)UA_malloc(fsfpSize);
-        if(!foundServerFilteredPointer) {
-            response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
-            return;
-        }
-
-        for(size_t i = 0; i < request->serverUrisSize; i++) {
-            if(!addSelf && UA_String_equal(&request->serverUris[i],
-                                           &server->config.applicationDescription.applicationUri)) {
-                addSelf = UA_TRUE;
-            } else {
-                registeredServer_list_entry* current;
-                LIST_FOREACH(current, &server->registeredServers, pointers) {
-                    if(UA_String_equal(&current->registeredServer.serverUri, &request->serverUris[i])) {
-                        // check if entry already in list:
-                        UA_Boolean existing = false;
-                        for(size_t j=0; j<foundServersSize; j++) {
-                            if(UA_String_equal(&foundServerFilteredPointer[j]->serverUri, &request->serverUris[i])) {
-                                existing = true;
-                                break;
-                            }
-                        }
-                        if(!existing)
-                            foundServerFilteredPointer[foundServersSize++] = &current->registeredServer;
-                        break;
-                    }
-                }
-            }
-        }
-
-        if(addSelf)
-            foundServersSize++;
-
-    } else {
-        addSelf = true;
-        // self + registered servers
-        foundServersSize = 1 + server->registeredServersSize;
-    }
-#else
+    /* Return the server itself? */
+    UA_Boolean foundSelf = false;
     if(request->serverUrisSize) {
         for(size_t i = 0; i < request->serverUrisSize; i++) {
             if(UA_String_equal(&request->serverUris[i],
                                &server->config.applicationDescription.applicationUri)) {
-                addSelf = UA_TRUE;
-                foundServersSize = 1;
+                foundSelf = true;
                 break;
             }
         }
     } else {
-        addSelf = UA_TRUE;
-        foundServersSize = 1;
+        foundSelf = true;
     }
-#endif
 
-    if(foundServersSize) {
-        size_t fsSize = sizeof(UA_ApplicationDescription) * foundServersSize;
-        foundServers = (UA_ApplicationDescription *)UA_malloc(fsSize);
-        if(!foundServers) {
-            if(foundServerFilteredPointer)
-                UA_free(foundServerFilteredPointer);
-            response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
-            return;
-        }
+#ifndef UA_ENABLE_DISCOVERY
+    if(!foundSelf)
+        return;
 
-        if(addSelf) {
-            response->responseHeader.serviceResult =
-                setApplicationDescriptionFromServer(&foundServers[0], server);
-            if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
-                UA_free(foundServers);
-                if(foundServerFilteredPointer)
-                    UA_free(foundServerFilteredPointer);
-                return;
-            }
-        }
+    UA_ApplicationDescription *ad = UA_ApplicationDescription_new();
+    if(!ad) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+        return;
+    }
 
-#ifdef UA_ENABLE_DISCOVERY
-        size_t currentIndex = 0;
-        if(addSelf)
-            currentIndex++;
-
-        // add all the registered servers to the list
-
-        if(foundServerFilteredPointer) {
-            // use filtered list because client only requested specific uris
-            // -1 because foundServersSize also includes this self server
-            size_t iterCount = addSelf ? foundServersSize - 1 : foundServersSize;
-            for(size_t i = 0; i < iterCount; i++) {
-                response->responseHeader.serviceResult =
-                        setApplicationDescriptionFromRegisteredServer(request, &foundServers[currentIndex++],
-                                                                      foundServerFilteredPointer[i]);
-                if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
-                    UA_free(foundServers);
-                    UA_free(foundServerFilteredPointer);
-                    return;
+    UA_StatusCode retval = setApplicationDescriptionFromServer(ad, server);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_ApplicationDescription_delete(ad);
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+        return;
+    }
+
+    response->servers = ad;
+    response->serversSize = 1;
+    return;
+
+#else
+
+    /* Temporarily store all the pointers which we found to avoid reiterating
+     * through the list */
+    size_t foundServersSize = 0;
+    UA_STACKARRAY(UA_RegisteredServer*, foundServers, server->registeredServersSize+1);
+
+    registeredServer_list_entry* current;
+    LIST_FOREACH(current, &server->registeredServers, pointers) {
+        if(request->serverUrisSize) {
+            /* If client only requested a specific set of servers */
+            for(size_t i = 0; i < request->serverUrisSize; i++) {
+                if(UA_String_equal(&current->registeredServer.serverUri, &request->serverUris[i])) {
+                    foundServers[foundServersSize] = &current->registeredServer;
+                    foundServersSize++;
+                    break;
                 }
             }
-            UA_free(foundServerFilteredPointer);
-            foundServerFilteredPointer = NULL;
         } else {
-            registeredServer_list_entry* current;
-            LIST_FOREACH(current, &server->registeredServers, pointers) {
-                response->responseHeader.serviceResult =
-                        setApplicationDescriptionFromRegisteredServer(request, &foundServers[currentIndex++],
-                                                                      &current->registeredServer);
-                if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
-                    UA_free(foundServers);
-                    return;
-                }
-            }
+            /* Return all registered servers */
+            foundServers[foundServersSize] = &current->registeredServer;
+            foundServersSize++;
         }
-#endif
     }
 
-    if(foundServerFilteredPointer)
-        UA_free(foundServerFilteredPointer);
+    size_t allocSize = foundServersSize;
+    if(foundSelf)
+        allocSize++;
+
+    /* Nothing to do? */
+    if(allocSize == 0)
+        return;
 
-    response->servers = foundServers;
-    response->serversSize = foundServersSize;
+    /* Allocate memory */
+    response->servers = (UA_ApplicationDescription*)UA_Array_new(allocSize, &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
+    if(!response->servers) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+        return;
+    }
+    response->serversSize = allocSize;
+
+    /* Copy into the response. TODO: Evaluate return codes */
+    size_t pos = 0;
+    if(foundSelf) {
+        setApplicationDescriptionFromServer(&response->servers[0], server);
+        pos = 1;
+    }
+    for(size_t i = 0; i < foundServersSize; i++) {
+        setApplicationDescriptionFromRegisteredServer(request, &response->servers[pos], foundServers[i]);
+        pos++;
+    }
+
+#endif
 }
 
 void Service_GetEndpoints(UA_Server *server, UA_Session *session,

+ 3 - 3
src/server/ua_services_discovery_multicast.c

@@ -63,7 +63,7 @@ static UA_StatusCode
 multicastListenStop(UA_Server* server) {
     mdnsd_shutdown(server->mdnsDaemon);
     // wake up select
-    write(server->mdnsSocket, "\0", 1); //TODO: move to arch?
+    if(write(server->mdnsSocket, "\0", 1)){}; //TODO: move to arch?
     if(pthread_join(server->mdnsThread, NULL)) {
         UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
                      "Multicast error: Can not stop thread.");
@@ -152,7 +152,7 @@ void Service_FindServersOnNetwork(UA_Server *server, UA_Session *session,
     if(request->startingRecordId < server->serverOnNetworkRecordIdCounter)
         recordCount = server->serverOnNetworkRecordIdCounter - request->startingRecordId;
     if(request->maxRecordsToReturn && recordCount > request->maxRecordsToReturn)
-        recordCount = MIN(recordCount, request->maxRecordsToReturn);
+        recordCount = UA_MIN(recordCount, request->maxRecordsToReturn);
     if(recordCount == 0) {
         response->serversSize = 0;
         return;
@@ -456,7 +456,7 @@ UA_Discovery_addRecord(UA_Server *server, const UA_String *servername,
     }
 
     /* The first 63 characters of the hostname (or less) */
-    size_t maxHostnameLen = MIN(hostnameLen, 63);
+    size_t maxHostnameLen = UA_MIN(hostnameLen, 63);
     char localDomain[65];
     memcpy(localDomain, hostname->data, maxHostnameLen);
     localDomain[maxHostnameLen] = '.';

+ 141 - 38
src/server/ua_services_subscription.c

@@ -8,6 +8,7 @@
  *    Copyright 2015-2016 (c) Sten Grüner
  *    Copyright 2015-2016 (c) Oleksiy Vasylyev
  *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
+ *    Copyright 2018 (c) Ari Breitkreuz, fortiss GmbH
  *    Copyright 2017 (c) Mattias Bornhager
  *    Copyright 2017 (c) Henrik Norrman
  *    Copyright 2017-2018 (c) Thomas Stalder, Blue Time Concept SA
@@ -153,11 +154,39 @@ Service_SetPublishingMode(UA_Server *server, UA_Session *session,
                                            &response->resultsSize, &UA_TYPES[UA_TYPES_STATUSCODE]);
 }
 
-static void
+static UA_StatusCode
 setMonitoredItemSettings(UA_Server *server, UA_MonitoredItem *mon,
                          UA_MonitoringMode monitoringMode,
-                         const UA_MonitoringParameters *params) {
-    MonitoredItem_unregisterSampleCallback(server, mon);
+                         const UA_MonitoringParameters *params,
+                         // This parameter is optional and used only if mon->lastValue is not set yet.
+                         // Then numeric type will be detected from this value. Set null as defaut.
+                         const UA_DataType* dataType) {
+
+    /* Filter */
+    if(params->filter.encoding != UA_EXTENSIONOBJECT_DECODED) {
+        UA_DataChangeFilter_init(&(mon->filter.dataChangeFilter));
+        mon->filter.dataChangeFilter.trigger = UA_DATACHANGETRIGGER_STATUSVALUE;
+    } else if(params->filter.content.decoded.type == &UA_TYPES[UA_TYPES_DATACHANGEFILTER]) {
+        UA_DataChangeFilter *filter = (UA_DataChangeFilter *)params->filter.content.decoded.data;
+        // TODO implement EURange to support UA_DEADBANDTYPE_PERCENT
+        if (filter->deadbandType == UA_DEADBANDTYPE_PERCENT) {
+            return UA_STATUSCODE_BADMONITOREDITEMFILTERUNSUPPORTED;
+        }
+        if (UA_Variant_isEmpty(&mon->lastValue)) {
+            if (!dataType || !isDataTypeNumeric(dataType))
+                return UA_STATUSCODE_BADFILTERNOTALLOWED;
+        } else
+        if (!isDataTypeNumeric(mon->lastValue.type)) {
+            return UA_STATUSCODE_BADFILTERNOTALLOWED;
+        }
+        UA_DataChangeFilter_copy(filter, &(mon->filter.dataChangeFilter));
+    } else if (params->filter.content.decoded.type == &UA_TYPES[UA_TYPES_EVENTFILTER]) {
+        UA_EventFilter_copy((UA_EventFilter *)params->filter.content.decoded.data, &(mon->filter.eventFilter));
+    } else {
+        return UA_STATUSCODE_BADMONITOREDITEMFILTERINVALID;
+    }
+
+    UA_MonitoredItem_unregisterSampleCallback(server, mon);
     mon->monitoringMode = monitoringMode;
 
     /* ClientHandle */
@@ -166,6 +195,7 @@ setMonitoredItemSettings(UA_Server *server, UA_MonitoredItem *mon,
     /* SamplingInterval */
     UA_Double samplingInterval = params->samplingInterval;
     if(mon->attributeId == UA_ATTRIBUTEID_VALUE) {
+        mon->monitoredItemType = UA_MONITOREDITEMTYPE_CHANGENOTIFY;
         const UA_VariableNode *vn = (const UA_VariableNode *)
             UA_Nodestore_get(server, &mon->monitoredNodeId);
         if(vn) {
@@ -177,6 +207,9 @@ setMonitoredItemSettings(UA_Server *server, UA_MonitoredItem *mon,
     } else if(mon->attributeId == UA_ATTRIBUTEID_EVENTNOTIFIER) {
         /* TODO: events should not need a samplinginterval */
         samplingInterval = 10000.0f; // 10 seconds to reduce the load
+        mon->monitoredItemType = UA_MONITOREDITEMTYPE_EVENTNOTIFY;
+    } else {
+        mon->monitoredItemType = UA_MONITOREDITEMTYPE_CHANGENOTIFY;
     }
     mon->samplingInterval = samplingInterval;
     UA_BOUNDEDVALUE_SETWBOUNDS(server->config.samplingIntervalLimits,
@@ -184,16 +217,6 @@ setMonitoredItemSettings(UA_Server *server, UA_MonitoredItem *mon,
     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 = (UA_DataChangeFilter *)params->filter.content.decoded.data;
-        mon->trigger = filter->trigger;
-    }
-
     /* QueueSize */
     UA_BOUNDEDVALUE_SETWBOUNDS(server->config.queueSizeLimits,
                                params->queueSize, mon->maxQueueSize);
@@ -203,15 +226,31 @@ setMonitoredItemSettings(UA_Server *server, UA_MonitoredItem *mon,
 
     /* Register sample callback if reporting is enabled */
     if(monitoringMode == UA_MONITORINGMODE_REPORTING)
-        MonitoredItem_registerSampleCallback(server, mon);
+        UA_MonitoredItem_registerSampleCallback(server, mon);
+    return UA_STATUSCODE_GOOD;
 }
 
 static const UA_String binaryEncoding = {sizeof("Default Binary") - 1, (UA_Byte *)"Default Binary"};
 
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+static UA_StatusCode UA_Server_addMonitoredItemToNodeEditNodeCallback(UA_Server *server, UA_Session *session,
+                                                                      UA_Node *node, void *data) {
+    /* data is the MonitoredItem */
+    /* SLIST_INSERT_HEAD */
+    ((UA_MonitoredItem *)data)->next = ((UA_ObjectNode *)node)->monitoredItemQueue;
+    ((UA_ObjectNode *)node)->monitoredItemQueue = (UA_MonitoredItem *)data;
+    return UA_STATUSCODE_GOOD;
+}
+#endif
+
 /* Thread-local variables to pass additional arguments into the operation */
 struct createMonContext {
     UA_Subscription *sub;
     UA_TimestampsToReturn timestampsToReturn;
+
+    /* If sub is NULL, use local callbacks */
+    UA_Server_DataChangeNotificationCallback dataChangeCallback;
+    void *context;
 };
 
 static void
@@ -219,7 +258,7 @@ Operation_CreateMonitoredItem(UA_Server *server, UA_Session *session, struct cre
                               const UA_MonitoredItemCreateRequest *request,
                               UA_MonitoredItemCreateResult *result) {
     /* Check available capacity */
-    if(server->config.maxMonitoredItemsPerSubscription != 0 &&
+    if(server->config.maxMonitoredItemsPerSubscription != 0 && cmc->sub &&
        cmc->sub->monitoredItemsSize >= server->config.maxMonitoredItemsPerSubscription) {
         result->statusCode = UA_STATUSCODE_BADTOOMANYMONITOREDITEMS;
         return;
@@ -241,13 +280,13 @@ Operation_CreateMonitoredItem(UA_Server *server, UA_Session *session, struct cre
         UA_DataValue_deleteMembers(&v);
         return;
     }
-    UA_DataValue_deleteMembers(&v);
 
     /* Check if the encoding is supported */
     if(request->itemToMonitor.dataEncoding.name.length > 0 &&
        (!UA_String_equal(&binaryEncoding, &request->itemToMonitor.dataEncoding.name) ||
         request->itemToMonitor.dataEncoding.namespaceIndex != 0)) {
         result->statusCode = UA_STATUSCODE_BADDATAENCODINGUNSUPPORTED;
+        UA_DataValue_deleteMembers(&v);
         return;
     }
 
@@ -255,35 +294,63 @@ Operation_CreateMonitoredItem(UA_Server *server, UA_Session *session, struct cre
     if(request->itemToMonitor.attributeId != UA_ATTRIBUTEID_VALUE &&
        request->itemToMonitor.dataEncoding.name.length > 0) {
         result->statusCode = UA_STATUSCODE_BADDATAENCODINGINVALID;
+        UA_DataValue_deleteMembers(&v);
         return;
     }
 
-    /* Create the monitoreditem */
-    UA_MonitoredItem *newMon = UA_MonitoredItem_new(UA_MONITOREDITEMTYPE_CHANGENOTIFY);
+    /* Allocate the MonitoredItem */
+    size_t nmsize = sizeof(UA_MonitoredItem);
+    if(!cmc->sub)
+        nmsize = sizeof(UA_LocalMonitoredItem);
+    UA_MonitoredItem *newMon = (UA_MonitoredItem*)UA_malloc(nmsize);
     if(!newMon) {
         result->statusCode = UA_STATUSCODE_BADOUTOFMEMORY;
+        UA_DataValue_deleteMembers(&v);
         return;
     }
-    UA_StatusCode retval = UA_NodeId_copy(&request->itemToMonitor.nodeId,
-                                          &newMon->monitoredNodeId);
+
+    /* Initialize the MonitoredItem */
+    UA_MonitoredItem_init(newMon, cmc->sub);
+    newMon->monitoredItemType = UA_MONITOREDITEMTYPE_CHANGENOTIFY;
+    newMon->attributeId = request->itemToMonitor.attributeId;
+    newMon->timestampsToReturn = cmc->timestampsToReturn;
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+    retval |= UA_NodeId_copy(&request->itemToMonitor.nodeId, &newMon->monitoredNodeId);
+    retval |= UA_String_copy(&request->itemToMonitor.indexRange, &newMon->indexRange);
+    retval |= setMonitoredItemSettings(server, newMon, request->monitoringMode,
+                                       &request->requestedParameters, v.value.type);
+    UA_DataValue_deleteMembers(&v);
     if(retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_INFO_SESSION(server->config.logger, session, "Could not create MonitoredItem "
+                            "with status code %s", UA_StatusCode_name(retval));
         result->statusCode = retval;
-        MonitoredItem_delete(server, newMon);
+        UA_MonitoredItem_delete(server, newMon);
         return;
     }
-    newMon->subscription = cmc->sub;
-    newMon->attributeId = request->itemToMonitor.attributeId;
-    UA_String_copy(&request->itemToMonitor.indexRange, &newMon->indexRange);
-    newMon->monitoredItemId = ++cmc->sub->lastMonitoredItemId;
-    newMon->timestampsToReturn = cmc->timestampsToReturn;
-    setMonitoredItemSettings(server, newMon, request->monitoringMode,
-                             &request->requestedParameters);
 
-    UA_Subscription_addMonitoredItem(cmc->sub, newMon);
+    /* Add to the subscriptions or the local MonitoredItems */
+    if(cmc->sub) {
+        newMon->monitoredItemId = ++cmc->sub->lastMonitoredItemId;
+        UA_Subscription_addMonitoredItem(cmc->sub, newMon);
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+        if (newMon->monitoredItemType == UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
+        /* insert the monitored item into the node's queue */
+        UA_Server_editNode(server, NULL, &newMon->monitoredNodeId, UA_Server_addMonitoredItemToNodeEditNodeCallback,
+                           newMon);
+    }
+#endif
+    } else {
+        //TODO support events for local monitored items
+        UA_LocalMonitoredItem *localMon = (UA_LocalMonitoredItem*)newMon;
+        localMon->context = cmc->context;
+        localMon->callback.dataChangeCallback = cmc->dataChangeCallback;
+        newMon->monitoredItemId = ++server->lastLocalMonitoredItemId;
+        LIST_INSERT_HEAD(&server->localMonitoredItems, newMon, listEntry);
+    }
 
     /* Create the first sample */
     if(request->monitoringMode == UA_MONITORINGMODE_REPORTING)
-        UA_MonitoredItem_SampleCallback(server, newMon);
+        UA_MonitoredItem_sampleCallback(server, newMon);
 
     /* Prepare the response */
     result->revisedSamplingInterval = newMon->samplingInterval;
@@ -327,6 +394,24 @@ Service_CreateMonitoredItems(UA_Server *server, UA_Session *session,
                                            &response->resultsSize, &UA_TYPES[UA_TYPES_MONITOREDITEMCREATERESULT]);
 }
 
+UA_MonitoredItemCreateResult
+UA_Server_createDataChangeMonitoredItem(UA_Server *server,
+                                        UA_TimestampsToReturn timestampsToReturn,
+                                        const UA_MonitoredItemCreateRequest item,
+                                        void *monitoredItemContext,
+                                        UA_Server_DataChangeNotificationCallback callback) {
+    struct createMonContext cmc;
+    cmc.sub = NULL;
+    cmc.context = monitoredItemContext;
+    cmc.dataChangeCallback = callback;
+    cmc.timestampsToReturn = timestampsToReturn;
+
+    UA_MonitoredItemCreateResult result;
+    UA_MonitoredItemCreateResult_init(&result);
+    Operation_CreateMonitoredItem(server, &adminSession, &cmc, &item, &result);
+    return result;
+}
+
 static void
 Operation_ModifyMonitoredItem(UA_Server *server, UA_Session *session, UA_Subscription *sub,
                               const UA_MonitoredItemModifyRequest *request,
@@ -337,13 +422,18 @@ Operation_ModifyMonitoredItem(UA_Server *server, UA_Session *session, UA_Subscri
         result->statusCode = UA_STATUSCODE_BADMONITOREDITEMIDINVALID;
         return;
     }
+    UA_StatusCode retval;
+    retval = setMonitoredItemSettings(server, mon, mon->monitoringMode, &request->requestedParameters, NULL);
+    if(retval != UA_STATUSCODE_GOOD) {
+        result->statusCode = retval;
+        return;
+    }
 
-    setMonitoredItemSettings(server, mon, mon->monitoringMode, &request->requestedParameters);
     result->revisedSamplingInterval = mon->samplingInterval;
     result->revisedQueueSize = mon->maxQueueSize;
 
     /* Remove some notifications if the queue is now too small */
-    MonitoredItem_ensureQueueSpace(mon);
+    MonitoredItem_ensureQueueSpace(server, mon);
 }
 
 void
@@ -395,7 +485,8 @@ Operation_SetMonitoringMode(UA_Server *server, UA_Session *session,
         return;
     }
 
-    if(mon->monitoredItemType != UA_MONITOREDITEMTYPE_CHANGENOTIFY) {
+    if(mon->monitoredItemType != UA_MONITOREDITEMTYPE_CHANGENOTIFY
+           && mon->monitoredItemType != UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
         *result = UA_STATUSCODE_BADNOTIMPLEMENTED;
         return;
     }
@@ -411,9 +502,9 @@ Operation_SetMonitoringMode(UA_Server *server, UA_Session *session,
 
     mon->monitoringMode = smc->monitoringMode;
     if(mon->monitoringMode == UA_MONITORINGMODE_REPORTING) {
-        MonitoredItem_registerSampleCallback(server, mon);
+        UA_MonitoredItem_registerSampleCallback(server, mon);
     } else {
-        MonitoredItem_unregisterSampleCallback(server, mon);
+        UA_MonitoredItem_unregisterSampleCallback(server, mon);
 
         // TODO correctly implement SAMPLING
         /*  Setting the mode to DISABLED or SAMPLING causes all queued Notifications to be deleted */
@@ -422,14 +513,13 @@ Operation_SetMonitoringMode(UA_Server *server, UA_Session *session,
             TAILQ_REMOVE(&mon->queue, notification, listEntry);
             TAILQ_REMOVE(&smc->sub->notificationQueue, notification, globalEntry);
             --smc->sub->notificationQueueSize;
-
-            UA_DataValue_deleteMembers(&notification->data.value);
-            UA_free(notification);
+            UA_Notification_delete(notification);
         }
         mon->queueSize = 0;
 
         /* Initialize lastSampledValue */
         UA_ByteString_deleteMembers(&mon->lastSampledValue);
+        UA_Variant_deleteMembers(&mon->lastValue);
     }
 }
 
@@ -670,6 +760,19 @@ Service_DeleteMonitoredItems(UA_Server *server, UA_Session *session,
                   &response->resultsSize, &UA_TYPES[UA_TYPES_STATUSCODE]);
 }
 
+UA_StatusCode
+UA_Server_deleteMonitoredItem(UA_Server *server, UA_UInt32 monitoredItemId) {
+    UA_MonitoredItem *mon;
+    LIST_FOREACH(mon, &server->localMonitoredItems, listEntry) {
+        if(mon->monitoredItemId != monitoredItemId)
+            continue;
+        LIST_REMOVE(mon, listEntry);
+        UA_MonitoredItem_delete(server, mon);
+        return UA_STATUSCODE_GOOD;
+    }
+    return UA_STATUSCODE_BADMONITOREDITEMIDINVALID;
+}
+
 void
 Service_Republish(UA_Server *server, UA_Session *session, const UA_RepublishRequest *request,
                   UA_RepublishResponse *response) {

+ 1 - 1
src/server/ua_session.c

@@ -8,8 +8,8 @@
 
 #include "ua_session.h"
 #ifdef UA_ENABLE_SUBSCRIPTIONS
-#include "ua_subscription.h"
 #include "ua_server_internal.h"
+#include "ua_subscription.h"
 #endif
 
 #define UA_SESSION_NONCELENTH 32

+ 122 - 33
src/server/ua_subscription.c

@@ -11,14 +11,26 @@
  *    Copyright 2015-2016 (c) Oleksiy Vasylyev
  *    Copyright 2017 (c) frax2222
  *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
+ *    Copyright 2017 (c) Ari Breitkreuz, fortiss GmbH
  *    Copyright 2017 (c) Mattias Bornhager
  */
 
-#include "ua_subscription.h"
 #include "ua_server_internal.h"
+#include "ua_subscription_events.h"
+#include "ua_subscription.h"
 
 #ifdef UA_ENABLE_SUBSCRIPTIONS /* conditional compilation */
 
+void UA_Notification_delete(UA_Notification *n) {
+    if(n->mon->monitoredItemType == UA_MONITOREDITEMTYPE_CHANGENOTIFY) {
+        UA_DataValue_deleteMembers(&n->data.value);
+    } else if (n->mon->monitoredItemType == UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
+        UA_Array_delete(n->data.event.fields.eventFields, n->data.event.fields.eventFieldsSize,
+                        &UA_TYPES[UA_TYPES_VARIANT]);
+    }
+    UA_free(n);
+}
+
 UA_Subscription *
 UA_Subscription_new(UA_Session *session, UA_UInt32 subscriptionId) {
     /* Allocate the memory */
@@ -46,7 +58,8 @@ UA_Subscription_deleteMembers(UA_Server *server, UA_Subscription *sub) {
     /* Delete monitored Items */
     UA_MonitoredItem *mon, *tmp_mon;
     LIST_FOREACH_SAFE(mon, &sub->monitoredItems, listEntry, tmp_mon) {
-        MonitoredItem_delete(server, mon);
+        LIST_REMOVE(mon, listEntry);
+        UA_MonitoredItem_delete(server, mon);
     }
     sub->monitoredItemsSize = 0;
 
@@ -83,8 +96,12 @@ UA_Subscription_deleteMonitoredItem(UA_Server *server, UA_Subscription *sub,
         return UA_STATUSCODE_BADMONITOREDITEMIDINVALID;
 
     /* Remove the MonitoredItem */
-    MonitoredItem_delete(server, mon);
+    LIST_REMOVE(mon, listEntry);
     sub->monitoredItemsSize--;
+
+    /* Remove content and delayed free */
+    UA_MonitoredItem_delete(server, mon);
+
     return UA_STATUSCODE_GOOD;
 }
 
@@ -132,11 +149,58 @@ UA_Subscription_removeRetransmissionMessage(UA_Subscription *sub, UA_UInt32 sequ
     return UA_STATUSCODE_GOOD;
 }
 
-/* Iterate over the monitoreditems of the subscription, starting at mon, and
- * move notifications into the response. */
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+/* EventChange: Iterate over the monitoredItems of the subscription, starting at mon, and
+ *              move notifications into the response. */
 static void
-moveNotificationsFromMonitoredItems(UA_Subscription *sub, UA_MonitoredItemNotification *mins,
-                                    size_t minsSize) {
+Events_moveNotificationsFromMonitoredItems(UA_Server *server, UA_Subscription *sub, UA_EventFieldList *efls,
+                                           size_t eflsSize) {
+    UA_StatusCode retval;
+    size_t pos = 0;
+    UA_Notification *notification, *notification_tmp;
+    TAILQ_FOREACH_SAFE(notification, &sub->notificationQueue, globalEntry, notification_tmp) {
+        if (pos >= eflsSize) {
+            return;
+        }
+        UA_MonitoredItem *mon = notification->mon;
+
+        /* Remove the notification from the queues */
+        TAILQ_REMOVE(&sub->notificationQueue, notification, globalEntry);
+        TAILQ_REMOVE(&mon->queue, notification, listEntry);
+
+        /* removing an overflowEvent should not reduce the queueSize */
+        UA_NodeId overflowId = UA_NODEID_NUMERIC(0, UA_NS0ID_SIMPLEOVERFLOWEVENTTYPE);
+        if (!(notification->data.event.fields.eventFieldsSize == 1
+                && notification->data.event.fields.eventFields->type == &UA_TYPES[UA_TYPES_NODEID]
+                && UA_NodeId_equal((UA_NodeId *)notification->data.event.fields.eventFields->data, &overflowId))) {
+            --mon->queueSize;
+            --sub->notificationQueueSize;
+        }
+
+        /* Move the content to the response */
+        UA_EventFieldList *efl = &efls[pos];
+        efl->clientHandle = mon->clientHandle;
+        efl->eventFieldsSize = notification->data.event.fields.eventFieldsSize;
+        retval = UA_Array_copy(notification->data.event.fields.eventFields,
+                               notification->data.event.fields.eventFieldsSize,
+                               (void **) &efl->eventFields, &UA_TYPES[UA_TYPES_VARIANT]);
+        if (retval != UA_STATUSCODE_GOOD) {
+            return;
+        }
+
+        /* EventFilterResult currently isn't being used
+        UA_EventFilterResult_deleteMembers(&notification->data.event.result); */
+        UA_EventFieldList_deleteMembers(&notification->data.event.fields);
+        UA_free(notification);
+    }
+}
+#endif
+
+/* DataChange: Iterate over the monitoreditems of the subscription, starting at mon, and
+ *             move notifications into the response. */
+static void
+DataChange_moveNotificationsFromMonitoredItems(UA_Subscription *sub, UA_MonitoredItemNotification *mins,
+                                               size_t minsSize) {
     size_t pos = 0;
     UA_Notification *notification, *notification_tmp;
     TAILQ_FOREACH_SAFE(notification, &sub->notificationQueue, globalEntry, notification_tmp) {
@@ -154,18 +218,14 @@ moveNotificationsFromMonitoredItems(UA_Subscription *sub, UA_MonitoredItemNotifi
         /* Move the content to the response */
         UA_MonitoredItemNotification *min = &mins[pos];
         min->clientHandle = mon->clientHandle;
-        if(mon->monitoredItemType == UA_MONITOREDITEMTYPE_CHANGENOTIFY) {
-            min->value = notification->data.value;
-        } else {
-            /* TODO implementation for events */
-        }
+        min->value = notification->data.value;
         UA_free(notification);
         ++pos;
     }
 }
 
 static UA_StatusCode
-prepareNotificationMessage(UA_Subscription *sub, UA_NotificationMessage *message,
+prepareNotificationMessage(UA_Server *server, UA_Subscription *sub, UA_NotificationMessage *message,
                            size_t notifications) {
     /* Array of ExtensionObject to hold different kinds of notifications
      * (currently only DataChangeNotifications) */
@@ -174,31 +234,60 @@ prepareNotificationMessage(UA_Subscription *sub, UA_NotificationMessage *message
         return UA_STATUSCODE_BADOUTOFMEMORY;
     message->notificationDataSize = 1;
 
-    /* Allocate Notification */
-    UA_DataChangeNotification *dcn = UA_DataChangeNotification_new();
-    if(!dcn) {
-        UA_NotificationMessage_deleteMembers(message);
-        return UA_STATUSCODE_BADOUTOFMEMORY;
-    }
     UA_ExtensionObject *data = message->notificationData;
     data->encoding = UA_EXTENSIONOBJECT_DECODED;
-    data->content.decoded.data = dcn;
-    data->content.decoded.type = &UA_TYPES[UA_TYPES_DATACHANGENOTIFICATION];
-
-    /* Allocate array of notifications */
-    dcn->monitoredItems = (UA_MonitoredItemNotification *)
-        UA_Array_new(notifications,
-                     &UA_TYPES[UA_TYPES_MONITOREDITEMNOTIFICATION]);
-    if(!dcn->monitoredItems) {
-        UA_NotificationMessage_deleteMembers(message);
-        return UA_STATUSCODE_BADOUTOFMEMORY;
+    /* TODO: basing type of notificationtype off of first monitoredItem in subscription which isnt very good */
+    /* Allocate Notification */
+    if (LIST_FIRST(&sub->monitoredItems)->monitoredItemType == UA_MONITOREDITEMTYPE_CHANGENOTIFY) {
+        UA_DataChangeNotification *dcn = UA_DataChangeNotification_new();
+        if (!dcn) {
+            UA_NotificationMessage_deleteMembers(message);
+            return UA_STATUSCODE_BADOUTOFMEMORY;
+        }
+        data->content.decoded.data = dcn;
+        data->content.decoded.type = &UA_TYPES[UA_TYPES_DATACHANGENOTIFICATION];
+
+        /* Allocate array of notifications */
+        dcn->monitoredItems = (UA_MonitoredItemNotification *)
+                UA_Array_new(notifications,
+                             &UA_TYPES[UA_TYPES_MONITOREDITEMNOTIFICATION]);
+        if(!dcn->monitoredItems) {
+            UA_NotificationMessage_deleteMembers(message);
+            return UA_STATUSCODE_BADOUTOFMEMORY;
+        }
+        dcn->monitoredItemsSize = notifications;
+
+        /* Move notifications into the response .. the point of no return */
+        DataChange_moveNotificationsFromMonitoredItems(sub, dcn->monitoredItems, notifications);
     }
-    dcn->monitoredItemsSize = notifications;
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+    else if (LIST_FIRST(&sub->monitoredItems)->monitoredItemType == UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
 
-    /* Move notifications into the response .. the point of no return */
+        UA_EventNotificationList *enl = UA_EventNotificationList_new();
+        if (!enl) {
+            UA_NotificationMessage_deleteMembers(message);
+            return UA_STATUSCODE_BADOUTOFMEMORY;
+        }
+        UA_EventNotificationList_init(enl);
+
+        data->content.decoded.data = enl;
+        data->content.decoded.type = &UA_TYPES[UA_TYPES_EVENTNOTIFICATIONLIST];
 
-    moveNotificationsFromMonitoredItems(sub, dcn->monitoredItems, notifications);
+        /* Allocate array of notifications */
+        enl->events = (UA_EventFieldList *) UA_Array_new(notifications, &UA_TYPES[UA_TYPES_EVENTFIELDLIST]);
+        if (!enl->events) {
+            UA_NotificationMessage_deleteMembers(message);
+            return UA_STATUSCODE_BADOUTOFMEMORY;
+        }
+        enl->eventsSize = notifications;
 
+        /* Move the list into the response .. the point of no return */
+        Events_moveNotificationsFromMonitoredItems(server, sub, enl->events, notifications);
+    }
+#endif
+    else {
+        return UA_STATUSCODE_BADNOTIMPLEMENTED;
+    }
     return UA_STATUSCODE_GOOD;
 }
 
@@ -298,7 +387,7 @@ UA_Subscription_publish(UA_Server *server, UA_Subscription *sub) {
         }
 
         /* Prepare the response */
-        UA_StatusCode retval = prepareNotificationMessage(sub, message, notifications);
+        UA_StatusCode retval = prepareNotificationMessage(server, sub, message, notifications);
         if(retval != UA_STATUSCODE_GOOD) {
             UA_LOG_WARNING_SESSION(server->config.logger, sub->session,
                                    "Subscription %u | Could not prepare the notification message. "

+ 26 - 14
src/server/ua_subscription.h

@@ -41,27 +41,32 @@ typedef enum {
     UA_MONITOREDITEMTYPE_EVENTNOTIFY = 4
 } UA_MonitoredItemType;
 
-/* Not used yet. Placeholder for a future event implementation. */
-typedef struct UA_Event {
-   UA_Int32 eventId;
-} UA_Event;
 
 struct UA_MonitoredItem;
 typedef struct UA_MonitoredItem UA_MonitoredItem;
 
+typedef struct UA_EventNotification {
+    UA_EventFieldList fields;
+    /* EventFilterResult currently isn't being used
+    UA_EventFilterResult result; */
+} UA_EventNotification;
+
 typedef struct UA_Notification {
-    TAILQ_ENTRY(UA_Notification) listEntry;
-    TAILQ_ENTRY(UA_Notification) globalEntry;
+    TAILQ_ENTRY(UA_Notification) listEntry; /* Notification list for the MonitoredItem */
+    TAILQ_ENTRY(UA_Notification) globalEntry; /* Notification list for the Subscription */
 
     UA_MonitoredItem *mon;
 
     /* See the monitoredItemType of the MonitoredItem */
     union {
-        UA_Event event;
+        UA_EventNotification event;
         UA_DataValue value;
     } data;
 } UA_Notification;
 
+/* Clean up the notification. Must be removed from the lists first. */
+void UA_Notification_delete(UA_Notification *n);
+
 typedef TAILQ_HEAD(NotificationQueue, UA_Notification) NotificationQueue;
 
 struct UA_MonitoredItem {
@@ -81,7 +86,11 @@ struct UA_MonitoredItem {
     UA_UInt32 maxQueueSize;
     UA_Boolean discardOldest;
     // TODO: dataEncoding is hardcoded to UA binary
-    UA_DataChangeTrigger trigger;
+    union {
+        UA_EventFilter eventFilter;
+        UA_DataChangeFilter dataChangeFilter;
+    } filter;
+    UA_Variant lastValue;
 
     /* Sample Callback */
     UA_UInt64 sampleCallbackId;
@@ -91,17 +100,20 @@ struct UA_MonitoredItem {
     /* Notification Queue */
     NotificationQueue queue;
     UA_UInt32 queueSize;
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+    UA_MonitoredItem *next;
+#endif
 };
 
-UA_MonitoredItem * UA_MonitoredItem_new(UA_MonitoredItemType);
-void MonitoredItem_delete(UA_Server *server, UA_MonitoredItem *monitoredItem);
-void UA_MonitoredItem_SampleCallback(UA_Server *server, UA_MonitoredItem *monitoredItem);
-UA_StatusCode MonitoredItem_registerSampleCallback(UA_Server *server, UA_MonitoredItem *mon);
-UA_StatusCode MonitoredItem_unregisterSampleCallback(UA_Server *server, UA_MonitoredItem *mon);
+void UA_MonitoredItem_init(UA_MonitoredItem *mon, UA_Subscription *sub);
+void UA_MonitoredItem_delete(UA_Server *server, UA_MonitoredItem *mon);
+void UA_MonitoredItem_sampleCallback(UA_Server *server, UA_MonitoredItem *mon);
+UA_StatusCode UA_MonitoredItem_registerSampleCallback(UA_Server *server, UA_MonitoredItem *mon);
+UA_StatusCode UA_MonitoredItem_unregisterSampleCallback(UA_Server *server, UA_MonitoredItem *mon);
 
 /* Remove entries until mon->maxQueueSize is reached. Sets infobits for lost
  * data if required. */
-void MonitoredItem_ensureQueueSpace(UA_MonitoredItem *mon);
+UA_StatusCode MonitoredItem_ensureQueueSpace(UA_Server *server, UA_MonitoredItem *mon);
 
 /****************/
 /* Subscription */

+ 335 - 175
src/server/ua_subscription_datachange.c

@@ -4,73 +4,100 @@
  *
  *    Copyright 2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
  *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
+ *    Copyright 2018 (c) Ari Breitkreuz, fortiss GmbH
  *    Copyright 2018 (c) Thomas Stalder, Blue Time Concept SA
  */
 
-#include "ua_subscription.h"
 #include "ua_server_internal.h"
+#include "ua_subscription.h"
 #include "ua_types_encoding_binary.h"
+#include "ua_subscription_events.h"
 
 #ifdef UA_ENABLE_SUBSCRIPTIONS /* conditional compilation */
 
 #define UA_VALUENCODING_MAXSTACK 512
 
-UA_MonitoredItem *
-UA_MonitoredItem_new(UA_MonitoredItemType monType) {
-    /* Allocate the memory */
-    UA_MonitoredItem *newItem =
-            (UA_MonitoredItem *) UA_calloc(1, sizeof(UA_MonitoredItem));
-    if(!newItem)
-        return NULL;
-
-    /* Remaining members are covered by calloc zeroing out the memory */
-    newItem->monitoredItemType = monType; /* currently hardcoded */
-    newItem->timestampsToReturn = UA_TIMESTAMPSTORETURN_SOURCE;
-    TAILQ_INIT(&newItem->queue);
-    return newItem;
+void UA_MonitoredItem_init(UA_MonitoredItem *mon, UA_Subscription *sub) {
+    memset(mon, 0, sizeof(UA_MonitoredItem));
+    mon->subscription = sub;
+    TAILQ_INIT(&mon->queue);
 }
 
-void
-MonitoredItem_delete(UA_Server *server, UA_MonitoredItem *monitoredItem) {
-    UA_Subscription *sub = monitoredItem->subscription;
-    UA_LOG_DEBUG_SESSION(server->config.logger, sub->session,
-                        "Subscription %u | MonitoredItem %i | "
-                        "Delete the MonitoredItem", sub->subscriptionId,
-                        monitoredItem->monitoredItemId);
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+static UA_StatusCode removeMonitoredItemFromNodeCallback(UA_Server *server, UA_Session *session, UA_Node *node,
+                                                         void *data) {
+    /* data is the monitoredItemID */
+    /* catch edge case that it's the first element */
+    if (data == ((UA_ObjectNode *) node)->monitoredItemQueue) {
+        ((UA_ObjectNode *)node)->monitoredItemQueue = ((UA_MonitoredItem *)data)->next;
+        return UA_STATUSCODE_GOOD;
+    }
+    /* SLIST_FOREACH */
+    for (UA_MonitoredItem *entry = ((UA_ObjectNode *) node)->monitoredItemQueue->next;
+         entry != NULL; entry=entry->next) {
+        if (entry == (UA_MonitoredItem *)data) {
+            /* SLIST_REMOVE */
+            UA_MonitoredItem *iter = ((UA_ObjectNode *) node)->monitoredItemQueue;
+            for (; iter->next != entry; iter=iter->next) {}
+            iter->next = entry->next;
+            UA_free(entry);
+            break;
+        }
+    }
+    return UA_STATUSCODE_GOOD;
+}
+#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
 
+void UA_MonitoredItem_delete(UA_Server *server, UA_MonitoredItem *monitoredItem) {
     if(monitoredItem->monitoredItemType == UA_MONITOREDITEMTYPE_CHANGENOTIFY) {
         /* Remove the sampling callback */
-        MonitoredItem_unregisterSampleCallback(server, monitoredItem);
+        UA_MonitoredItem_unregisterSampleCallback(server, monitoredItem);
+    } else if (monitoredItem->monitoredItemType != UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
+        /* TODO: Access val data.event */
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                     "MonitoredItemTypes other than ChangeNotify or EventNotify are not supported yet");
+    }
 
-        /* Clear the queued notifications */
+    /* Remove the queued notifications if attached to a subscription */
+    if(monitoredItem->subscription) {
+        UA_Subscription *sub = monitoredItem->subscription;
         UA_Notification *notification, *notification_tmp;
-        TAILQ_FOREACH_SAFE(notification, &monitoredItem->queue, listEntry, notification_tmp) {
+        TAILQ_FOREACH_SAFE(notification, &monitoredItem->queue,
+                           listEntry, notification_tmp) {
             /* Remove the item from the queues */
             TAILQ_REMOVE(&monitoredItem->queue, notification, listEntry);
             TAILQ_REMOVE(&sub->notificationQueue, notification, globalEntry);
             --sub->notificationQueueSize;
-
-            UA_DataValue_deleteMembers(&notification->data.value);
-            UA_free(notification);
+            /*
+            if (monitoredItem->monitoredItemType == UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
+                 EventFilterResult currently isn't being used
+                UA_EventFilterResult_delete(notification->data.event->result);
+            }
+            */
+            UA_Notification_delete(notification);
         }
         monitoredItem->queueSize = 0;
-    } else {
-        /* TODO: Access val data.event */
-        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
-                     "MonitoredItemTypes other than ChangeNotify are not supported yet");
     }
-
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+    if (monitoredItem->monitoredItemType == UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
+        /* Remove the monitored item from the node queue */
+        UA_Server_editNode(server, NULL, &monitoredItem->monitoredNodeId, removeMonitoredItemFromNodeCallback,
+                           monitoredItem);
+        /* Delete the event filter */
+        UA_EventFilter_deleteMembers(&monitoredItem->filter.eventFilter);
+    }
+#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
     /* Remove the monitored item */
-    LIST_REMOVE(monitoredItem, listEntry);
     UA_String_deleteMembers(&monitoredItem->indexRange);
     UA_ByteString_deleteMembers(&monitoredItem->lastSampledValue);
+    UA_Variant_deleteMembers(&monitoredItem->lastValue);
     UA_NodeId_deleteMembers(&monitoredItem->monitoredNodeId);
     UA_Server_delayedFree(server, monitoredItem);
 }
 
-void MonitoredItem_ensureQueueSpace(UA_MonitoredItem *mon) {
+UA_StatusCode MonitoredItem_ensureQueueSpace(UA_Server *server, UA_MonitoredItem *mon) {
     if(mon->queueSize <= mon->maxQueueSize)
-        return;
+        return UA_STATUSCODE_GOOD;
 
     /* Remove notifications until the queue size is reached */
     UA_Subscription *sub = mon->subscription;
@@ -106,20 +133,62 @@ void MonitoredItem_ensureQueueSpace(UA_MonitoredItem *mon) {
         /* Remove the notification from the queues */
         TAILQ_REMOVE(&mon->queue, del, listEntry);
         TAILQ_REMOVE(&sub->notificationQueue, del, globalEntry);
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+        /* TODO: provide additional protection for overflowEvents according to specification */
+        /* removing an overflowEvent should not reduce the queueSize */
+        UA_NodeId overflowId = UA_NODEID_NUMERIC(0, UA_NS0ID_SIMPLEOVERFLOWEVENTTYPE);
+        if (!(del->data.event.fields.eventFieldsSize == 1
+              && del->data.event.fields.eventFields->type == &UA_TYPES[UA_TYPES_NODEID]
+              && UA_NodeId_equal((UA_NodeId *)del->data.event.fields.eventFields->data, &overflowId))) {
+            --mon->queueSize;
+            --sub->notificationQueueSize;
+        }
+#else
         --mon->queueSize;
         --sub->notificationQueueSize;
+#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
 
         /* Free the notification */
-        if(mon->monitoredItemType == UA_MONITOREDITEMTYPE_CHANGENOTIFY) {
-            UA_DataValue_deleteMembers(&del->data.value);
-        } else {
-            /* TODO: event implemantation */
+        if (mon->monitoredItemType == UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+            /* EventFilterResult currently isn't being used
+            UA_EventFilterResult_deleteMembers(&del->data.event->result); */
+            UA_EventFieldList_deleteMembers(&del->data.event.fields);
+
+            /* cause an overflowEvent */
+            /* an overflowEvent does not care about event filters and as such will not be "triggered" correctly.
+             * Instead, a notification will be inserted into the queue which includes only the nodeId of the
+             * overflowEventType. It is up to the client to check for possible overflows.
+             */
+            UA_Notification *overflowNotification = (UA_Notification *) UA_malloc(sizeof(UA_Notification));
+            if (!overflowNotification) {
+                return UA_STATUSCODE_BADOUTOFMEMORY;
+            }
+
+            UA_EventFieldList_init(&overflowNotification->data.event.fields);
+
+            overflowNotification->data.event.fields.eventFields = UA_Variant_new();
+            if (!overflowNotification->data.event.fields.eventFields) {
+                UA_EventFieldList_deleteMembers(&overflowNotification->data.event.fields);
+                UA_free(overflowNotification);
+                return UA_STATUSCODE_BADOUTOFMEMORY;
+            }
+            UA_Variant_init(overflowNotification->data.event.fields.eventFields);
+
+            overflowNotification->data.event.fields.eventFieldsSize = 1;
+            UA_Variant_setScalarCopy(overflowNotification->data.event.fields.eventFields,
+                                              &overflowId, &UA_TYPES[UA_TYPES_NODEID]);
+            overflowNotification->mon = mon;
+            if (mon->discardOldest) {
+                TAILQ_INSERT_HEAD(&mon->queue, overflowNotification, listEntry);
+                TAILQ_INSERT_HEAD(&mon->subscription->notificationQueue, overflowNotification, globalEntry);
+            } else {
+                TAILQ_INSERT_TAIL(&mon->queue, overflowNotification, listEntry);
+                TAILQ_INSERT_TAIL(&mon->subscription->notificationQueue, overflowNotification, globalEntry);
+            }
+#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
         }
-
-        /* Work around a false positive in clang analyzer */
-#ifndef __clang_analyzer__
-        UA_free(del);
-#endif
+        UA_Notification_delete(del);
     }
 
     if(mon->monitoredItemType == UA_MONITOREDITEMTYPE_CHANGENOTIFY) {
@@ -144,197 +213,288 @@ void MonitoredItem_ensureQueueSpace(UA_MonitoredItem *mon) {
     }
 
     /* TODO: Infobits for Events? */
+    return UA_STATUSCODE_GOOD;
 }
 
-/* Errors are returned as no change detected */
+#define ABS_SUBTRACT_TYPE_INDEPENDENT(a,b) ((a)>(b)?(a)-(b):(b)-(a))
+
+static UA_INLINE UA_Boolean
+outOfDeadBand(const void *data1, const void *data2, const size_t index, const UA_DataType *type, const UA_Double deadbandValue) {
+    if (type == &UA_TYPES[UA_TYPES_SBYTE]) {
+        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_SByte*)data1)[index], ((const UA_SByte*)data2)[index]) <= deadbandValue)
+            return false;
+    } else
+    if (type == &UA_TYPES[UA_TYPES_BYTE]) {
+        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Byte*)data1)[index], ((const UA_Byte*)data2)[index]) <= deadbandValue)
+                return false;
+    } else
+    if (type == &UA_TYPES[UA_TYPES_INT16]) {
+        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Int16*)data1)[index], ((const UA_Int16*)data2)[index]) <= deadbandValue)
+            return false;
+    } else
+    if (type == &UA_TYPES[UA_TYPES_UINT16]) {
+        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_UInt16*)data1)[index], ((const UA_UInt16*)data2)[index]) <= deadbandValue)
+            return false;
+    } else
+    if (type == &UA_TYPES[UA_TYPES_INT32]) {
+        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Int32*)data1)[index], ((const UA_Int32*)data2)[index]) <= deadbandValue)
+            return false;
+    } else
+    if (type == &UA_TYPES[UA_TYPES_UINT32]) {
+        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_UInt32*)data1)[index], ((const UA_UInt32*)data2)[index]) <= deadbandValue)
+            return false;
+    } else
+    if (type == &UA_TYPES[UA_TYPES_INT64]) {
+        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Int64*)data1)[index], ((const UA_Int64*)data2)[index]) <= deadbandValue)
+            return false;
+    } else
+    if (type == &UA_TYPES[UA_TYPES_UINT64]) {
+        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_UInt64*)data1)[index], ((const UA_UInt64*)data2)[index]) <= deadbandValue)
+            return false;
+    } else
+    if (type == &UA_TYPES[UA_TYPES_FLOAT]) {
+        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Float*)data1)[index], ((const UA_Float*)data2)[index]) <= deadbandValue)
+            return false;
+    } else
+    if (type == &UA_TYPES[UA_TYPES_DOUBLE]) {
+        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Double*)data1)[index], ((const UA_Double*)data2)[index]) <= deadbandValue)
+            return false;
+    }
+    return true;
+}
+
+static UA_INLINE UA_Boolean
+updateNeededForFilteredValue(const UA_Variant *value, const UA_Variant *oldValue, const UA_Double deadbandValue) {
+    if (value->arrayLength != oldValue->arrayLength) {
+        return true;
+    }
+    if (value->type != oldValue->type) {
+        return true;
+    }
+    if (UA_Variant_isScalar(value)) {
+        return outOfDeadBand(value->data, oldValue->data, 0, value->type, deadbandValue);
+    } else {
+        for (size_t i = 0; i < value->arrayLength; ++i) {
+            if (outOfDeadBand(value->data, oldValue->data, i, value->type, deadbandValue))
+                return true;
+        }
+    }
+    return false;
+}
+
+/* When a change is detected, encoding contains the heap-allocated binary encoded value */
 static UA_Boolean
-detectValueChangeWithFilter(UA_MonitoredItem *mon, UA_DataValue *value,
+detectValueChangeWithFilter(UA_Server *server, UA_MonitoredItem *mon, UA_DataValue *value,
                             UA_ByteString *encoding) {
-    /* Encode the data for comparison */
-    size_t binsize = UA_calcSizeBinary(value, &UA_TYPES[UA_TYPES_DATAVALUE]);
-    if(binsize == 0)
-        return false;
+    UA_Session *session = &adminSession;
+    UA_UInt32 subscriptionId = 0;
+    UA_Subscription *sub = mon->subscription;
+    if(sub) {
+        session = sub->session;
+        subscriptionId = sub->subscriptionId;
+    }
 
-    /* Allocate buffer on the heap if necessary */
-    if(binsize > UA_VALUENCODING_MAXSTACK &&
-       UA_ByteString_allocBuffer(encoding, binsize) != UA_STATUSCODE_GOOD)
-        return false;
+    if(isDataTypeNumeric(value->value.type) &&
+       (mon->filter.dataChangeFilter.trigger == UA_DATACHANGETRIGGER_STATUSVALUE ||
+        mon->filter.dataChangeFilter.trigger == UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP)) {
+        if(mon->filter.dataChangeFilter.deadbandType == UA_DEADBANDTYPE_ABSOLUTE) {
+            if(!updateNeededForFilteredValue(&value->value, &mon->lastValue, mon->filter.dataChangeFilter.deadbandValue))
+                return false;
+        }
+        /* else if (mon->filter.deadbandType == UA_DEADBANDTYPE_PERCENT) {
+            // TODO where do this EURange come from ?
+            UA_Double deadbandValue = fabs(mon->filter.deadbandValue * (EURange.high-EURange.low));
+            if (!updateNeededForFilteredValue(value->value, mon->lastValue, deadbandValue))
+                return false;
+        }*/
+    }
+
+    /* Stack-allocate some memory for the value encoding. We might heap-allocate
+     * more memory if needed. This is just enough for scalars and small
+     * structures. */
+    UA_STACKARRAY(UA_Byte, stackValueEncoding, UA_VALUENCODING_MAXSTACK);
+    UA_ByteString valueEncoding;
+    valueEncoding.data = stackValueEncoding;
+    valueEncoding.length = UA_VALUENCODING_MAXSTACK;
 
     /* Encode the value */
-    UA_Byte *bufPos = encoding->data;
-    const UA_Byte *bufEnd = &encoding->data[encoding->length];
+    UA_Byte *bufPos = valueEncoding.data;
+    const UA_Byte *bufEnd = &valueEncoding.data[valueEncoding.length];
     UA_StatusCode retval = UA_encodeBinary(value, &UA_TYPES[UA_TYPES_DATAVALUE],
                                            &bufPos, &bufEnd, NULL, NULL);
-    if(retval != UA_STATUSCODE_GOOD)
+    if(retval == UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED) {
+        size_t binsize = UA_calcSizeBinary(value, &UA_TYPES[UA_TYPES_DATAVALUE]);
+        if(binsize == 0)
+            return false;
+        retval = UA_ByteString_allocBuffer(&valueEncoding, binsize);
+        if(retval == UA_STATUSCODE_GOOD) {
+            bufPos = valueEncoding.data;
+            bufEnd = &valueEncoding.data[valueEncoding.length];
+            retval = UA_encodeBinary(value, &UA_TYPES[UA_TYPES_DATAVALUE],
+                                     &bufPos, &bufEnd, NULL, NULL);
+        }
+    }
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_WARNING_SESSION(server->config.logger, session,
+                               "Subscription %u | MonitoredItem %i | "
+                               "Could not encode the value the MonitoredItem with status %s",
+                               subscriptionId, mon->monitoredItemId, UA_StatusCode_name(retval));
+        return false;
+    }
+
+    /* Has the value changed? */
+    valueEncoding.length = (uintptr_t)bufPos - (uintptr_t)valueEncoding.data;
+    UA_Boolean changed = (!mon->lastSampledValue.data ||
+                          !UA_String_equal(&valueEncoding, &mon->lastSampledValue));
+
+    /* No change */
+    if(!changed) {
+        if(valueEncoding.data != stackValueEncoding)
+            UA_ByteString_deleteMembers(&valueEncoding);
         return false;
+    }
 
-    /* The value has changed */
-    encoding->length = (uintptr_t)bufPos - (uintptr_t)encoding->data;
-    return !mon->lastSampledValue.data || !UA_String_equal(encoding, &mon->lastSampledValue);
+    /* Change detected. Copy encoding on the heap if necessary. */
+    if(valueEncoding.data == stackValueEncoding) {
+        retval = UA_ByteString_copy(&valueEncoding, encoding);
+        if(retval != UA_STATUSCODE_GOOD) {
+            UA_LOG_WARNING_SESSION(server->config.logger, session,
+                                   "Subscription %u | MonitoredItem %i | "
+                                   "Detected change, but could not allocate memory for the notification"
+                                   "with status %s", subscriptionId, mon->monitoredItemId,
+                                   UA_StatusCode_name(retval));
+            return false;
+        }
+        return true;
+    }
+
+    *encoding = valueEncoding;
+    return true;
 }
 
 /* Has this sample changed from the last one? The method may allocate additional
  * space for the encoding buffer. Detect the change in encoding->data. */
 static UA_Boolean
-detectValueChange(UA_MonitoredItem *mon, UA_DataValue *value, UA_ByteString *encoding) {
+detectValueChange(UA_Server *server, UA_MonitoredItem *mon,
+                  UA_DataValue value, UA_ByteString *encoding) {
     /* Apply Filter */
-    UA_Boolean hasValue = value->hasValue;
-    if(mon->trigger == UA_DATACHANGETRIGGER_STATUS)
-        value->hasValue = false;
-
-    UA_Boolean hasServerTimestamp = value->hasServerTimestamp;
-    UA_Boolean hasServerPicoseconds = value->hasServerPicoseconds;
-    value->hasServerTimestamp = false;
-    value->hasServerPicoseconds = false;
-
-    UA_Boolean hasSourceTimestamp = value->hasSourceTimestamp;
-    UA_Boolean hasSourcePicoseconds = value->hasSourcePicoseconds;
-    if(mon->trigger < UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP) {
-        value->hasSourceTimestamp = false;
-        value->hasSourcePicoseconds = false;
+    if(mon->filter.dataChangeFilter.trigger == UA_DATACHANGETRIGGER_STATUS)
+        value.hasValue = false;
+
+    value.hasServerTimestamp = false;
+    value.hasServerPicoseconds = false;
+    if(mon->filter.dataChangeFilter.trigger < UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP) {
+        value.hasSourceTimestamp = false;
+        value.hasSourcePicoseconds = false;
     }
 
-    /* Detect the Value Change */
-    UA_Boolean res = detectValueChangeWithFilter(mon, value, encoding);
-
-    /* Reset the filter */
-    value->hasValue = hasValue;
-    value->hasServerTimestamp = hasServerTimestamp;
-    value->hasServerPicoseconds = hasServerPicoseconds;
-    value->hasSourceTimestamp = hasSourceTimestamp;
-    value->hasSourcePicoseconds = hasSourcePicoseconds;
-    return res;
+    /* Detect the value change */
+    return detectValueChangeWithFilter(server, mon, &value, encoding);
 }
 
-/* Returns whether a new sample was created */
+/* Returns whether the sample was stored in the MonitoredItem */
 static UA_Boolean
-sampleCallbackWithValue(UA_Server *server, UA_Subscription *sub,
-                        UA_MonitoredItem *monitoredItem,
-                        UA_DataValue *value,
-                        UA_ByteString *valueEncoding) {
+sampleCallbackWithValue(UA_Server *server, UA_MonitoredItem *monitoredItem,
+                        UA_DataValue *value) {
     UA_assert(monitoredItem->monitoredItemType == UA_MONITOREDITEMTYPE_CHANGENOTIFY);
-    /* Store the pointer to the stack-allocated bytestring to see if a heap-allocation
-     * was necessary */
-    UA_Byte *stackValueEncoding = valueEncoding->data;
+    UA_Subscription *sub = monitoredItem->subscription;
 
-    /* Has the value changed? */
-    UA_Boolean changed = detectValueChange(monitoredItem, value, valueEncoding);
-    if(!changed)
-        return false;
+    /* Contains heap-allocated binary encoding of the value if a change was detected */
+    UA_ByteString binaryEncoding = UA_BYTESTRING_NULL;
 
-    /* Allocate the entry for the publish queue */
-    UA_Notification *newNotification =
-        (UA_Notification *)UA_malloc(sizeof(UA_Notification));
-    if(!newNotification) {
-        UA_LOG_WARNING_SESSION(server->config.logger, sub->session,
-                               "Subscription %u | MonitoredItem %i | "
-                               "Item for the publishing queue could not be allocated",
-                               sub->subscriptionId, monitoredItem->monitoredItemId);
+    /* Has the value changed? Allocates memory in binaryEncoding if necessary.
+     * value is edited internally so we make a shallow copy. */
+    UA_Boolean changed = detectValueChange(server, monitoredItem, *value, &binaryEncoding);
+    if(!changed)
         return false;
-    }
 
-    /* Copy valueEncoding on the heap for the next comparison (if not already done) */
-    if(valueEncoding->data == stackValueEncoding) {
-        UA_ByteString cbs;
-        if(UA_ByteString_copy(valueEncoding, &cbs) != UA_STATUSCODE_GOOD) {
+    UA_Boolean storedValue = false;
+    if(sub) {
+        /* Allocate a new notification */
+        UA_Notification *newNotification = (UA_Notification *)UA_malloc(sizeof(UA_Notification));
+        if(!newNotification) {
             UA_LOG_WARNING_SESSION(server->config.logger, sub->session,
                                    "Subscription %u | MonitoredItem %i | "
-                                   "ByteString to compare values could not be created",
+                                   "Item for the publishing queue could not be allocated",
                                    sub->subscriptionId, monitoredItem->monitoredItemId);
-            UA_free(newNotification);
+            UA_ByteString_deleteMembers(&binaryEncoding);
             return false;
         }
-        *valueEncoding = cbs;
-    }
 
-    /* Prepare the newQueueItem */
-    if(value->hasValue && value->value.storageType == UA_VARIANT_DATA_NODELETE) {
-        /* Make a deep copy of the value */
-        UA_StatusCode retval = UA_DataValue_copy(value, &newNotification->data.value);
-        if(retval != UA_STATUSCODE_GOOD) {
-            UA_LOG_WARNING_SESSION(server->config.logger, sub->session,
-                                   "Subscription %u | MonitoredItem %i | "
-                                   "Item for the publishing queue could not be prepared",
-                                   sub->subscriptionId, monitoredItem->monitoredItemId);
-            UA_free(newNotification);
-            return false;
-        }
-    } else {
-        newNotification->data.value = *value; /* Just copy the value and do not release it */
-    }
+        /* <-- Point of no return --> */
 
-    /* <-- Point of no return --> */
+        newNotification->mon = monitoredItem;
+        newNotification->data.value = *value; /* Move the value to the notification */
+        storedValue = true;
 
-    UA_LOG_DEBUG_SESSION(server->config.logger, sub->session,
-                         "Subscription %u | MonitoredItem %u | Sampled a new value",
-                         sub->subscriptionId, monitoredItem->monitoredItemId);
+        /* Add the notification to the end of local and global queue */
+        TAILQ_INSERT_TAIL(&monitoredItem->queue, newNotification, listEntry);
+        TAILQ_INSERT_TAIL(&sub->notificationQueue, newNotification, globalEntry);
+        ++monitoredItem->queueSize;
+        ++sub->notificationQueueSize;
 
-    newNotification->mon = monitoredItem;
+        /* Remove some notifications if the queue is beyond maximum capacity */
+        MonitoredItem_ensureQueueSpace(server, monitoredItem);
+    } else {
+        /* Call the local callback if not attached to a subscription */
+        UA_LocalMonitoredItem *localMon = (UA_LocalMonitoredItem*) monitoredItem;
+        void *nodeContext = NULL;
+        UA_Server_getNodeContext(server, monitoredItem->monitoredNodeId, &nodeContext);
+        localMon->callback.dataChangeCallback(server, monitoredItem->monitoredItemId,
+                                              localMon->context,
+                                              &monitoredItem->monitoredNodeId,
+                                              nodeContext, monitoredItem->attributeId,
+                                              value);
+    }
 
-    /* Replace the encoding for comparison */
+    /* Store the encoding for comparison */
     UA_ByteString_deleteMembers(&monitoredItem->lastSampledValue);
-    monitoredItem->lastSampledValue = *valueEncoding;
-
-    /* Add the notification to the end of local and global queue */
-    TAILQ_INSERT_TAIL(&monitoredItem->queue, newNotification, listEntry);
-    TAILQ_INSERT_TAIL(&sub->notificationQueue, newNotification, globalEntry);
-    ++monitoredItem->queueSize;
-    ++sub->notificationQueueSize;
-
-    /* Remove some notifications if the queue is beyond maximum capacity */
-    MonitoredItem_ensureQueueSpace(monitoredItem);
+    monitoredItem->lastSampledValue = binaryEncoding;
+    UA_Variant_deleteMembers(&monitoredItem->lastValue);
+    UA_Variant_copy(&value->value, &monitoredItem->lastValue);
 
-    return true;
+    return storedValue;
 }
 
 void
-UA_MonitoredItem_SampleCallback(UA_Server *server,
-                                UA_MonitoredItem *monitoredItem) {
+UA_MonitoredItem_sampleCallback(UA_Server *server, UA_MonitoredItem *monitoredItem) {
+    UA_Session *session = &adminSession;
+    UA_UInt32 subscriptionId = 0;
     UA_Subscription *sub = monitoredItem->subscription;
+    if(sub) {
+        session = sub->session;
+        subscriptionId = sub->subscriptionId;
+    }
+
     if(monitoredItem->monitoredItemType != UA_MONITOREDITEMTYPE_CHANGENOTIFY) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, sub->session,
-                             "Subscription %u | MonitoredItem %i | "
-                             "Not a data change notification",
-                             sub->subscriptionId, monitoredItem->monitoredItemId);
+        UA_LOG_DEBUG_SESSION(server->config.logger, session, "Subscription %u | "
+                             "MonitoredItem %i | Not a data change notification",
+                             subscriptionId, monitoredItem->monitoredItemId);
         return;
     }
 
-    /* Read the value */
+    /* Sample the value */
     UA_ReadValueId rvid;
     UA_ReadValueId_init(&rvid);
     rvid.nodeId = monitoredItem->monitoredNodeId;
     rvid.attributeId = monitoredItem->attributeId;
     rvid.indexRange = monitoredItem->indexRange;
-    UA_DataValue value =
-        UA_Server_readWithSession(server, sub->session,
-                                  &rvid, monitoredItem->timestampsToReturn);
-
-    /* Stack-allocate some memory for the value encoding. We might heap-allocate
-     * more memory if needed. This is just enough for scalars and small
-     * structures. */
-    UA_STACKARRAY(UA_Byte, stackValueEncoding, UA_VALUENCODING_MAXSTACK);
-    UA_ByteString valueEncoding;
-    valueEncoding.data = stackValueEncoding;
-    valueEncoding.length = UA_VALUENCODING_MAXSTACK;
+    UA_DataValue value = UA_Server_readWithSession(server, session, &rvid, monitoredItem->timestampsToReturn);
 
-    /* Create a sample and compare with the last value */
-    UA_Boolean newNotification = sampleCallbackWithValue(server, sub, monitoredItem,
-                                                         &value, &valueEncoding);
+    /* Operate on the sample */
+    UA_Boolean storedValue = sampleCallbackWithValue(server, monitoredItem, &value);
 
-    /* Clean up */
-    if(!newNotification) {
-        if(valueEncoding.data != stackValueEncoding)
-            UA_ByteString_deleteMembers(&valueEncoding);
+    /* Delete the sample if it was not stored in the MonitoredItem  */
+    if(!storedValue)
         UA_DataValue_deleteMembers(&value);
-    }
 }
 
 UA_StatusCode
-MonitoredItem_registerSampleCallback(UA_Server *server, UA_MonitoredItem *mon) {
+UA_MonitoredItem_registerSampleCallback(UA_Server *server, UA_MonitoredItem *mon) {
     if(mon->sampleCallbackIsRegistered)
         return UA_STATUSCODE_GOOD;
     UA_StatusCode retval =
-        UA_Server_addRepeatedCallback(server, (UA_ServerCallback)UA_MonitoredItem_SampleCallback,
+        UA_Server_addRepeatedCallback(server, (UA_ServerCallback)UA_MonitoredItem_sampleCallback,
                                       mon, (UA_UInt32)mon->samplingInterval, &mon->sampleCallbackId);
     if(retval == UA_STATUSCODE_GOOD)
         mon->sampleCallbackIsRegistered = true;
@@ -342,7 +502,7 @@ MonitoredItem_registerSampleCallback(UA_Server *server, UA_MonitoredItem *mon) {
 }
 
 UA_StatusCode
-MonitoredItem_unregisterSampleCallback(UA_Server *server, UA_MonitoredItem *mon) {
+UA_MonitoredItem_unregisterSampleCallback(UA_Server *server, UA_MonitoredItem *mon) {
     if(!mon->sampleCallbackIsRegistered)
         return UA_STATUSCODE_GOOD;
     mon->sampleCallbackIsRegistered = false;

+ 462 - 0
src/server/ua_subscription_events.c

@@ -0,0 +1,462 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ *    Copyright 2018 (c) Ari Breitkreuz, fortiss GmbH
+ */
+
+#include "ua_server_internal.h"
+#include "ua_subscription.h"
+#include "ua_subscription_events.h"
+
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+
+typedef struct Events_nodeListElement {
+    LIST_ENTRY(Events_nodeListElement) listEntry;
+    UA_NodeId *node;
+} Events_nodeListElement;
+
+typedef LIST_HEAD(Events_nodeList, Events_nodeListElement) Events_nodeList;
+
+struct getNodesHandle {
+    UA_Server *server;
+    Events_nodeList *nodes;
+};
+
+/* generates a unique event id */
+static UA_StatusCode UA_Event_generateEventId(UA_Server *server, UA_ByteString *generatedId) {
+    /* EventId is a ByteString, which is basically just a string
+     * We will use a 16-Byte ByteString as an identifier */
+    generatedId->length = 16;
+    generatedId->data = (UA_Byte *) UA_malloc(16 * sizeof(UA_Byte));
+    if (!generatedId->data) {
+        UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_USERLAND,
+                       "Server unable to allocate memory for EventId data.");
+        UA_free(generatedId);
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    /* GUIDs are unique, have a size of 16 byte and already have
+     * a generator so use that.
+     * Make sure GUIDs really do have 16 byte, in case someone may
+     * have changed that struct */
+    UA_assert(sizeof(UA_Guid) == 16);
+    UA_Guid tmpGuid = UA_Guid_random();
+    memcpy(generatedId->data, &tmpGuid, 16);
+    return UA_STATUSCODE_GOOD;
+}
+
+static UA_StatusCode findAllSubtypesNodeIteratorCallback(UA_NodeId parentId, UA_Boolean isInverse,
+                                                         UA_NodeId referenceTypeId, void *handle) {
+    /* only subtypes of hasSubtype */
+    UA_NodeId hasSubtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
+    if (isInverse || !UA_NodeId_equal(&referenceTypeId, &hasSubtypeId)) {
+        return UA_STATUSCODE_GOOD;
+    }
+
+    Events_nodeListElement *entry = (Events_nodeListElement *) UA_malloc(sizeof(Events_nodeListElement));
+    if (!entry) {
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    entry->node = UA_NodeId_new();
+    if (!entry->node) {
+        UA_free(entry);
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    UA_NodeId_copy(&parentId, entry->node);
+    LIST_INSERT_HEAD(((struct getNodesHandle *) handle)->nodes, entry, listEntry);
+
+    /* recursion */
+    UA_Server_forEachChildNodeCall(((struct getNodesHandle *) handle)->server,
+                                   parentId, findAllSubtypesNodeIteratorCallback, handle);
+    return UA_STATUSCODE_GOOD;
+}
+
+/* Searches for an attribute of an event with the name 'name' and the depth from the event relativePathSize.
+ * Returns the browsePathResult of searching for that node */
+static void UA_Event_findVariableNode(UA_Server *server, UA_QualifiedName *name, size_t relativePathSize,
+                                      const UA_NodeId *event, UA_BrowsePathResult *out) {
+    /* get a list with all subtypes of aggregates */
+    struct getNodesHandle handle;
+    Events_nodeList list;
+    LIST_INIT(&list);
+    handle.server = server;
+    handle.nodes = &list;
+    UA_StatusCode retval = UA_Server_forEachChildNodeCall(server, UA_NODEID_NUMERIC(0, UA_NS0ID_AGGREGATES),
+                                                          findAllSubtypesNodeIteratorCallback, &handle);
+    if (retval != UA_STATUSCODE_GOOD) {
+        out->statusCode = retval;
+    }
+
+    /* check if you can find the node with any of the subtypes of aggregates */
+    UA_Boolean nodeFound = UA_FALSE;
+    Events_nodeListElement *iter, *tmp_iter;
+    LIST_FOREACH_SAFE(iter, &list, listEntry, tmp_iter) {
+        if (!nodeFound) {
+            UA_RelativePathElement rpe;
+            UA_RelativePathElement_init(&rpe);
+            rpe.referenceTypeId = *iter->node;
+            rpe.isInverse = false;
+            rpe.includeSubtypes = false;
+            rpe.targetName = *name;
+            /* TODO: test larger browsepath perhaps put browsepath in a loop */
+            UA_BrowsePath bp;
+            UA_BrowsePath_init(&bp);
+            bp.relativePath.elementsSize = relativePathSize;
+            bp.startingNode = *event;
+            bp.relativePath.elements = &rpe;
+
+            *out = UA_Server_translateBrowsePathToNodeIds(server, &bp);
+            if (out->statusCode == UA_STATUSCODE_GOOD) {
+                nodeFound = UA_TRUE;
+            }
+        }
+        LIST_REMOVE(iter, listEntry);
+        UA_NodeId_delete(iter->node);
+        UA_free(iter);
+    }
+}
+
+UA_StatusCode UA_EXPORT
+UA_Server_createEvent(UA_Server *server, const UA_NodeId eventType, UA_NodeId *outNodeId) {
+    if (!outNodeId) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_USERLAND, "outNodeId cannot be NULL!");
+        return UA_STATUSCODE_BADINVALIDARGUMENT;
+    }
+
+    UA_StatusCode retval;
+
+    /* make sure the eventType is a subtype of BaseEventType */
+    UA_NodeId hasSubtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
+    UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE);
+    if (!isNodeInTree(&server->config.nodestore, &eventType, &baseEventTypeId, &hasSubtypeId, 1)) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_USERLAND, "Event type must be a subtype of BaseEventType!");
+        return UA_STATUSCODE_BADINVALIDARGUMENT;
+    }
+
+    UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
+    oAttr.displayName.locale = UA_STRING_NULL;
+    oAttr.displayName.text = UA_STRING_NULL;
+    oAttr.description.locale = UA_STRING_NULL;
+    oAttr.description.text = UA_STRING_NULL;
+
+    UA_QualifiedName name;
+    UA_QualifiedName_init(&name);
+
+    /* create an ObjectNode which represents the event */
+    retval = UA_Server_addObjectNode(server,
+                                     UA_NODEID_NULL, /* the user may not have control over the nodeId */
+                                     UA_NODEID_NULL, /* an event does not have a parent */
+                                     UA_NODEID_NULL, /* an event does not have any references */
+                                     name,           /* an event does not have a name */
+                                     eventType,      /* the type of the event */
+                                     oAttr,          /* default attributes are fine */
+                                     NULL,           /* no node context */
+                                     outNodeId);
+
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_USERLAND,
+                     "Adding event failed. StatusCode %s", UA_StatusCode_name(retval));
+        return retval;
+    }
+    /* find the eventType variableNode */
+    name = UA_QUALIFIEDNAME(0, "EventType");
+    UA_BrowsePathResult bpr;
+    UA_BrowsePathResult_init(&bpr);
+    UA_Event_findVariableNode(server, &name, 1, outNodeId, &bpr);
+    if (bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
+        return bpr.statusCode;
+    }
+    UA_Variant value;
+    UA_Variant_init(&value);
+    UA_Variant_setScalarCopy(&value, &eventType, &UA_TYPES[UA_TYPES_NODEID]);
+    UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
+    UA_Variant_deleteMembers(&value);
+    UA_BrowsePathResult_deleteMembers(&bpr);
+
+    /* the object is not put in any queues until it is triggered */
+    return retval;
+}
+
+static UA_Boolean isValidEvent(UA_Server *server, const UA_NodeId *validEventParent, const UA_NodeId *eventId) {
+    /* find the eventType variableNode */
+    UA_BrowsePathResult bpr;
+    UA_BrowsePathResult_init(&bpr);
+    UA_QualifiedName findName = UA_QUALIFIEDNAME(0, "EventType");
+    UA_Event_findVariableNode(server, &findName, 1, eventId, &bpr);
+    if (bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
+        return UA_FALSE;
+    }
+    UA_NodeId hasSubtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
+    UA_Boolean tmp = isNodeInTree(&server->config.nodestore, &bpr.targets[0].targetId.nodeId, validEventParent,
+                                  &hasSubtypeId, 1);
+    UA_BrowsePathResult_deleteMembers(&bpr);
+    return tmp;
+}
+
+
+static UA_StatusCode whereClausesApply(UA_Server *server, const UA_ContentFilter whereClause, UA_EventFieldList *efl,
+                                       UA_Boolean *result) {
+    /* if the where clauses aren't specified leave everything as is */
+    if (whereClause.elementsSize == 0) {
+        *result = UA_TRUE;
+        return UA_STATUSCODE_GOOD;
+    }
+    /* where clauses were specified */
+    UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_USERLAND, "Where clauses are not supported by the server.");
+    *result = UA_TRUE;
+    return UA_STATUSCODE_BADNOTSUPPORTED;
+}
+
+/* filters the given event with the given filter and writes the results into a notification */
+static UA_StatusCode UA_Server_filterEvent(UA_Server *server, const UA_NodeId *eventNode, UA_EventFilter *filter,
+                                           UA_EventNotification *notification) {
+    if (filter->selectClausesSize == 0) {
+        return UA_STATUSCODE_BADEVENTFILTERINVALID;
+    }
+    UA_StatusCode retval;
+    /* setup */
+    UA_EventFieldList_init(&notification->fields);
+
+    /* EventFilterResult isn't being used currently
+    UA_EventFilterResult_init(&notification->result); */
+
+    notification->fields.eventFieldsSize = filter->selectClausesSize;
+    notification->fields.eventFields = (UA_Variant *) UA_Array_new(notification->fields.eventFieldsSize,
+                                                                    &UA_TYPES[UA_TYPES_VARIANT]);
+    if (!notification->fields.eventFields) {
+        /* EventFilterResult currently isn't being used
+        UA_EventFiterResult_deleteMembers(&notification->result); */
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    /* EventFilterResult currently isn't being used
+    notification->result.selectClauseResultsSize = filter->selectClausesSize;
+    notification->result.selectClauseResults = (UA_StatusCode *) UA_Array_new(filter->selectClausesSize,
+                                                                               &UA_TYPES[UA_TYPES_VARIANT]);
+    if (!notification->result->selectClauseResults) {
+        UA_EventFieldList_deleteMembers(&notification->fields);
+        UA_EventFilterResult_deleteMembers(&notification->result);
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    */
+
+    /* ================ apply the filter ===================== */
+    /* check if the browsePath is BaseEventType, in which case nothing more needs to be checked */
+    UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE);
+    /* iterate over the selectClauses */
+    for (size_t i = 0; i < filter->selectClausesSize; i++) {
+        if (!UA_NodeId_equal(&filter->selectClauses[i].typeDefinitionId, &baseEventTypeId)
+            && !isValidEvent(server, &filter->selectClauses[0].typeDefinitionId, eventNode)) {
+            UA_Variant_init(&notification->fields.eventFields[i]);
+            /* EventFilterResult currently isn't being used
+            notification->result.selectClauseResults[i] = UA_STATUSCODE_BADTYPEDEFINITIONINVALID; */
+            continue;
+        }
+        /* type is correct */
+        /* find the variable node with the data being looked for */
+        UA_BrowsePathResult bpr;
+        UA_BrowsePathResult_init(&bpr);
+        UA_Event_findVariableNode(server, filter->selectClauses[i].browsePath, filter->selectClauses[i].browsePathSize,
+                                  eventNode, &bpr);
+        if (bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
+            UA_Variant_init(&notification->fields.eventFields[i]);
+            continue;
+        }
+        /* copy the value */
+        UA_Boolean whereClauseResult = UA_TRUE;
+        UA_Boolean whereClausesUsed = UA_FALSE;     /* placeholder until whereClauses are implemented */
+        retval = whereClausesApply(server, filter->whereClause, &notification->fields, &whereClauseResult);
+        if (retval == UA_STATUSCODE_BADNOTSUPPORTED) {
+            whereClausesUsed = UA_TRUE;
+        }
+        if (whereClauseResult) {
+            retval = UA_Server_readValue(server, bpr.targets[0].targetId.nodeId, &notification->fields.eventFields[i]);
+            if (retval != UA_STATUSCODE_GOOD) {
+                UA_Variant_init(&notification->fields.eventFields[i]);
+            }
+            if (whereClausesUsed) {
+                return UA_STATUSCODE_BADNOTSUPPORTED;
+            }
+        } else {
+            UA_Variant_init(&notification->fields.eventFields[i]);
+            /* TODO: better statuscode for failing at where clauses */
+            /* EventFilterResult currently isn't being used
+            notification->result.selectClauseResults[i] = UA_STATUSCODE_BADDATAUNAVAILABLE; */
+        }
+        UA_BrowsePathResult_deleteMembers(&bpr);
+    }
+    return UA_STATUSCODE_GOOD;
+}
+
+static UA_StatusCode eventSetConstants(UA_Server *server, const UA_NodeId *event, const UA_NodeId *origin,
+                                       UA_ByteString *outEventId) {
+    UA_BrowsePathResult bpr;
+    UA_BrowsePathResult_init(&bpr);
+    /* set the source */
+    UA_QualifiedName name = UA_QUALIFIEDNAME(0, "SourceNode");
+    UA_Event_findVariableNode(server, &name, 1, event, &bpr);
+    if (bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
+        UA_StatusCode tmp = bpr.statusCode;
+        UA_BrowsePathResult_deleteMembers(&bpr);
+        return tmp;
+    }
+    UA_Variant value;
+    UA_Variant_init(&value);
+    UA_Variant_setScalarCopy(&value, origin, &UA_TYPES[UA_TYPES_NODEID]);
+    UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
+    UA_Variant_deleteMembers(&value);
+    UA_BrowsePathResult_deleteMembers(&bpr);
+
+    /* set the receive time */
+    name = UA_QUALIFIEDNAME(0, "ReceiveTime");
+    UA_Event_findVariableNode(server, &name, 1, event, &bpr);
+    if (bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
+        UA_StatusCode tmp = bpr.statusCode;
+        UA_BrowsePathResult_deleteMembers(&bpr);
+        return tmp;
+    }
+    UA_DateTime time = UA_DateTime_now();
+    UA_Variant_setScalar(&value, &time, &UA_TYPES[UA_TYPES_DATETIME]);
+    UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
+    UA_BrowsePathResult_deleteMembers(&bpr);
+
+    /* set the eventId attribute */
+    UA_ByteString eventId;
+    UA_ByteString_init(&eventId);
+    UA_StatusCode retval = UA_Event_generateEventId(server, &eventId);
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_ByteString_deleteMembers(&eventId);
+        return retval;
+    }
+    if (outEventId) {
+        UA_ByteString_copy(&eventId, outEventId);
+    }
+    name = UA_QUALIFIEDNAME(0, "EventId");
+    UA_Event_findVariableNode(server, &name, 1, event, &bpr);
+    if (bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
+        UA_StatusCode tmp = bpr.statusCode;
+        UA_BrowsePathResult_deleteMembers(&bpr);
+        return tmp;
+    }
+    UA_Variant_init(&value);
+    UA_Variant_setScalar(&value, &eventId, &UA_TYPES[UA_TYPES_BYTESTRING]);
+    UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
+    UA_ByteString_deleteMembers(&eventId);
+    UA_BrowsePathResult_deleteMembers(&bpr);
+
+    return UA_STATUSCODE_GOOD;
+}
+
+
+/* insert each node into the list (passed as handle) */
+static UA_StatusCode getParentsNodeIteratorCallback(UA_NodeId parentId, UA_Boolean isInverse,
+                                                    UA_NodeId referenceTypeId, void *handle) {
+    /* parents have an inverse reference */
+    if (!isInverse) {
+        return UA_STATUSCODE_GOOD;
+    }
+
+    Events_nodeListElement *entry = (Events_nodeListElement *) UA_malloc(sizeof(Events_nodeListElement));
+    if (!entry) {
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    entry->node = UA_NodeId_new();
+    if (!entry->node) {
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    UA_NodeId_copy(&parentId, entry->node);
+    LIST_INSERT_HEAD(((struct getNodesHandle *) handle)->nodes, entry, listEntry);
+
+    /* recursion */
+    UA_Server_forEachChildNodeCall(((struct getNodesHandle *) handle)->server,
+                                   parentId, getParentsNodeIteratorCallback, handle);
+    return UA_STATUSCODE_GOOD;
+}
+
+/* filters an event according to the filter specified by mon and then adds it to mons notification queue */
+static UA_StatusCode
+UA_Event_addEventToMonitoredItem(UA_Server *server, const UA_NodeId *event, UA_MonitoredItem *mon) {
+    UA_Notification *notification = (UA_Notification *) UA_malloc(sizeof(UA_Notification));
+    if (!notification) {
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    /* apply the filter */
+    UA_StatusCode retval = UA_Server_filterEvent(server, event, &mon->filter.eventFilter, &notification->data.event);
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_free(notification);
+        return retval;
+    }
+    notification->mon = mon;
+
+    /* add to the monitored item queue */
+    MonitoredItem_ensureQueueSpace(server, mon);
+    TAILQ_INSERT_TAIL(&mon->queue, notification, listEntry);
+    ++mon->queueSize;
+    /* add to the subscription queue */
+    TAILQ_INSERT_TAIL(&mon->subscription->notificationQueue, notification, globalEntry);
+    ++mon->subscription->notificationQueueSize;
+    return UA_STATUSCODE_GOOD;
+}
+
+UA_StatusCode UA_EXPORT
+UA_Server_triggerEvent(UA_Server *server, const UA_NodeId eventNodeId, const UA_NodeId origin,
+                       UA_ByteString *outEventId, const UA_Boolean deleteEventNode) {
+    /* make sure the origin is in the ObjectsFolder (TODO: or in the ViewsFolder) */
+    UA_NodeId objectsFolderId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
+    UA_NodeId references[2] = {
+            {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ORGANIZES}},
+            {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASCOMPONENT}}
+    };
+    if (!isNodeInTree(&server->config.nodestore, &origin, &objectsFolderId, references, 2)) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_USERLAND, "Node for event must be in ObjectsFolder!");
+        return UA_STATUSCODE_BADINVALIDARGUMENT;
+    }
+
+    UA_StatusCode retval = eventSetConstants(server, &eventNodeId, &origin, outEventId);
+    if (retval != UA_STATUSCODE_GOOD) {
+        return retval;
+    }
+
+    /* get an array with all parents */
+    struct getNodesHandle parentHandle;
+    Events_nodeList parentList;
+    LIST_INIT(&parentList);
+    parentHandle.server = server;
+    parentHandle.nodes = &parentList;
+    retval = getParentsNodeIteratorCallback(origin, UA_TRUE, UA_NODEID_NULL, &parentHandle);
+    if (retval != UA_STATUSCODE_GOOD) {
+        return retval;
+    }
+
+    /* add the event to each node's monitored items */
+    Events_nodeListElement *parentIter, *tmp_parentIter;
+    LIST_FOREACH_SAFE(parentIter, &parentList, listEntry, tmp_parentIter) {
+        const UA_ObjectNode *node = (const UA_ObjectNode *) UA_Nodestore_get(server, parentIter->node);
+        /* SLIST_FOREACH */
+        for (UA_MonitoredItem *monIter = node->monitoredItemQueue; monIter != NULL; monIter = monIter->next) {
+            retval = UA_Event_addEventToMonitoredItem(server, &eventNodeId, monIter);
+            if (retval != UA_STATUSCODE_GOOD) {
+                UA_Nodestore_release(server, (const UA_Node *) node);
+                return retval;
+            }
+        }
+        UA_Nodestore_release(server, (const UA_Node *) node);
+        LIST_REMOVE(parentIter, listEntry);
+        UA_NodeId_delete(parentIter->node);
+        UA_free(parentIter);
+    }
+
+    /* delete the node representation of the event */
+    if (deleteEventNode) {
+        retval = UA_Server_deleteNode(server, eventNodeId, UA_TRUE);
+        if (retval != UA_STATUSCODE_GOOD) {
+            UA_LOG_WARNING(server->config.logger,
+                           UA_LOGCATEGORY_SERVER,
+                           "Attempt to remove event using deleteNode failed. StatusCode %s",
+                           UA_StatusCode_name(retval));
+            return retval;
+        }
+    }
+    return UA_STATUSCODE_GOOD;
+}
+
+#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */

+ 16 - 0
src/server/ua_subscription_events.h

@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ *    Copyright 2018 (c) Ari Breitkreuz, fortiss GmbH
+ */
+
+#ifndef UA_SUBSCRIPTION_EVENTS_H_
+#define UA_SUBSCRIPTION_EVENTS_H_
+
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+
+
+#endif
+
+#endif /* UA_SUBSCRIPTION_EVENTS_H_ */

+ 23 - 1
src/ua_connection.c

@@ -175,7 +175,7 @@ UA_Connection_processChunks(UA_Connection *connection, void *application,
     return retval;
 }
 
-/* In order to know whether a chunk was processed, we insert an indirection into
+/* In order to know whether a chunk was processed, we insert an redirection into
  * the callback. */
 struct completeChunkTrampolineData {
     UA_Boolean called;
@@ -234,6 +234,28 @@ UA_Connection_receiveChunksBlocking(UA_Connection *connection, void *application
     return retval;
 }
 
+UA_StatusCode
+UA_Connection_receiveChunksNonBlocking(UA_Connection *connection, void *application,
+                                    UA_Connection_processChunk processCallback) {
+    struct completeChunkTrampolineData data;
+    data.called = false;
+    data.application = application;
+    data.processCallback = processCallback;
+
+    /* Listen for messages to arrive */
+    UA_ByteString packet = UA_BYTESTRING_NULL;
+    UA_StatusCode retval = connection->recv(connection, &packet, 1);
+
+    if((retval != UA_STATUSCODE_GOOD) && (retval != UA_STATUSCODE_GOODNONCRITICALTIMEOUT))
+        return retval;
+
+    /* Try to process one complete chunk */
+    retval = UA_Connection_processChunks(connection, &data, completeChunkTrampoline, &packet);
+    connection->releaseRecvBuffer(connection, &packet);
+
+    return retval;
+}
+
 void UA_Connection_detachSecureChannel(UA_Connection *connection) {
     UA_SecureChannel *channel = connection->channel;
     if(channel)

+ 19 - 0
src/ua_connection_internal.h

@@ -44,6 +44,21 @@ UA_StatusCode
 UA_Connection_processChunks(UA_Connection *connection, void *application,
                             UA_Connection_processChunk processCallback,
                             const UA_ByteString *packet);
+/*
+ * @param connection The connection
+ * @param message The received message. The content may be overwritten when a
+ *        previsouly received buffer is completed.
+ * @param realloced The Boolean value is set to true if the outgoing message has
+ *        been reallocated from the network layer.
+ * @return Returns UA_STATUSCODE_GOOD or an error code. When an error occurs,
+ *         the ingoing message and the current buffer in the connection are
+ *         freed. */
+UA_StatusCode
+UA_Connection_completeMessages(UA_Connection *connection,
+                               UA_ByteString * UA_RESTRICT message,
+                               UA_Boolean * UA_RESTRICT realloced);
+
+
 
 /* Try to receive at least one complete chunk on the connection. This blocks the
  * current thread up to the given timeout.
@@ -59,6 +74,10 @@ UA_Connection_receiveChunksBlocking(UA_Connection *connection, void *application
                                     UA_Connection_processChunk processCallback,
                                     UA_UInt32 timeout);
 
+UA_StatusCode
+UA_Connection_receiveChunksNonBlocking(UA_Connection *connection, void *application,
+                                    UA_Connection_processChunk processCallback);
+
 /* When a fatal error occurs the Server shall send an Error Message to the
  * Client and close the socket. When a Client encounters one of these errors, it
  * shall also close the socket but does not send an Error Message. After the

+ 42 - 12
src/ua_types.c

@@ -114,9 +114,30 @@ UA_String_equal(const UA_String *s1, const UA_String *s2) {
     return (is == 0) ? true : false;
 }
 
+static UA_StatusCode
+String_copy(UA_String const *src, UA_String *dst, const UA_DataType *_) {
+    UA_StatusCode retval = UA_Array_copy(src->data, src->length, (void**)&dst->data,
+                                         &UA_TYPES[UA_TYPES_BYTE]);
+    if(retval == UA_STATUSCODE_GOOD)
+        dst->length = src->length;
+    return retval;
+}
+
 static void
 String_deleteMembers(UA_String *s, const UA_DataType *_) {
-    UA_free((void*)((uintptr_t)s->data & ~(uintptr_t)UA_EMPTY_ARRAY_SENTINEL));
+    UA_Array_delete(s->data, s->length, &UA_TYPES[UA_TYPES_BYTE]);
+}
+
+/* QualifiedName */
+static UA_StatusCode
+QualifiedName_copy(const UA_QualifiedName *src, UA_QualifiedName *dst, const UA_DataType *_) {
+    dst->namespaceIndex = src->namespaceIndex;
+    return String_copy(&src->name, &dst->name, NULL);
+}
+
+static void
+QualifiedName_deleteMembers(UA_QualifiedName *p, const UA_DataType *_) {
+    String_deleteMembers(&p->name, NULL);
 }
 
 UA_Boolean
@@ -548,9 +569,9 @@ computeStrides(const UA_Variant *v, const UA_NumericRange range,
 /* Is the type string-like? */
 static bool
 isStringLike(const UA_DataType *type) {
-    if(type->membersSize == 1 && type->members[0].isArray &&
-       type->members[0].namespaceZero &&
-       type->members[0].memberTypeIndex == UA_TYPES_BYTE)
+    if(type == &UA_TYPES[UA_TYPES_STRING] ||
+       type == &UA_TYPES[UA_TYPES_BYTESTRING] ||
+       type == &UA_TYPES[UA_TYPES_XMLELEMENT])
         return true;
     return false;
 }
@@ -885,16 +906,16 @@ static const UA_copySignature copyJumpTable[UA_BUILTIN_TYPES_COUNT + 1] = {
     (UA_copySignature)copy8Byte, // UInt64
     (UA_copySignature)copy4Byte, // Float
     (UA_copySignature)copy8Byte, // Double
-    (UA_copySignature)copy_noInit, // String
+    (UA_copySignature)String_copy,
     (UA_copySignature)copy8Byte, // DateTime
     (UA_copySignature)copyGuid, // Guid
-    (UA_copySignature)copy_noInit, // ByteString
-    (UA_copySignature)copy_noInit, // XmlElement
+    (UA_copySignature)String_copy, // ByteString
+    (UA_copySignature)String_copy, // XmlElement
     (UA_copySignature)NodeId_copy,
     (UA_copySignature)ExpandedNodeId_copy,
     (UA_copySignature)copy4Byte, // StatusCode
-    (UA_copySignature)copy_noInit, // QualifiedName
-    (UA_copySignature)LocalizedText_copy, // LocalizedText
+    (UA_copySignature)QualifiedName_copy,
+    (UA_copySignature)LocalizedText_copy,
     (UA_copySignature)ExtensionObject_copy,
     (UA_copySignature)DataValue_copy,
     (UA_copySignature)Variant_copy,
@@ -970,10 +991,10 @@ UA_deleteMembersSignature deleteMembersJumpTable[UA_BUILTIN_TYPES_COUNT + 1] = {
     (UA_deleteMembersSignature)String_deleteMembers, // ByteString
     (UA_deleteMembersSignature)String_deleteMembers, // XmlElement
     (UA_deleteMembersSignature)NodeId_deleteMembers,
-    (UA_deleteMembersSignature)ExpandedNodeId_deleteMembers, // ExpandedNodeId
+    (UA_deleteMembersSignature)ExpandedNodeId_deleteMembers,
     (UA_deleteMembersSignature)nopDeleteMembers, // StatusCode
-    (UA_deleteMembersSignature)deleteMembers_noInit, // QualifiedName
-    (UA_deleteMembersSignature)LocalizedText_deleteMembers, // LocalizedText
+    (UA_deleteMembersSignature)QualifiedName_deleteMembers,
+    (UA_deleteMembersSignature)LocalizedText_deleteMembers,
     (UA_deleteMembersSignature)ExtensionObject_deleteMembers,
     (UA_deleteMembersSignature)DataValue_deleteMembers,
     (UA_deleteMembersSignature)Variant_deletemembers,
@@ -1079,3 +1100,12 @@ UA_Array_delete(void *p, size_t size, const UA_DataType *type) {
     }
     UA_free((void*)((uintptr_t)p & ~(uintptr_t)UA_EMPTY_ARRAY_SENTINEL));
 }
+
+UA_Boolean
+isDataTypeNumeric(const UA_DataType *type) {
+    // All data types ids between UA_TYPES_SBYTE and UA_TYPES_DOUBLE are numeric
+    for (int i = UA_TYPES_SBYTE; i <= UA_TYPES_DOUBLE; ++i)
+        if (&UA_TYPES[i] == type)
+            return true;
+    return false;
+}

+ 19 - 3
src/ua_types_encoding_binary.c

@@ -753,6 +753,18 @@ DECODE_BINARY(ExpandedNodeId) {
     return ret;
 }
 
+/* QualifiedName */
+
+ENCODE_BINARY(QualifiedName) {
+    return ENCODE_DIRECT(&src->namespaceIndex, UInt16) |
+           ENCODE_DIRECT(&src->name, String);
+}
+
+DECODE_BINARY(QualifiedName) {
+    return DECODE_DIRECT(&dst->namespaceIndex, UInt16) |
+        DECODE_DIRECT(&dst->name, String);
+}
+
 /* LocalizedText */
 #define UA_LOCALIZEDTEXT_ENCODINGMASKTYPE_LOCALE 0x01
 #define UA_LOCALIZEDTEXT_ENCODINGMASKTYPE_TEXT 0x02
@@ -1350,7 +1362,7 @@ const encodeBinarySignature encodeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1] =
     (encodeBinarySignature)NodeId_encodeBinary,
     (encodeBinarySignature)ExpandedNodeId_encodeBinary,
     (encodeBinarySignature)UInt32_encodeBinary, /* StatusCode */
-    (encodeBinarySignature)encodeBinaryInternal, /* QualifiedName */
+    (encodeBinarySignature)QualifiedName_encodeBinary,
     (encodeBinarySignature)LocalizedText_encodeBinary,
     (encodeBinarySignature)ExtensionObject_encodeBinary,
     (encodeBinarySignature)DataValue_encodeBinary,
@@ -1447,7 +1459,7 @@ const decodeBinarySignature decodeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1] =
     (decodeBinarySignature)NodeId_decodeBinary,
     (decodeBinarySignature)ExpandedNodeId_decodeBinary,
     (decodeBinarySignature)UInt32_decodeBinary, /* StatusCode */
-    (decodeBinarySignature)decodeBinaryInternal, /* QualifiedName */
+    (decodeBinarySignature)QualifiedName_decodeBinary,
     (decodeBinarySignature)LocalizedText_decodeBinary,
     (decodeBinarySignature)ExtensionObject_decodeBinary,
     (decodeBinarySignature)DataValue_decodeBinary,
@@ -1586,6 +1598,10 @@ CALCSIZE_BINARY(ExpandedNodeId) {
     return s;
 }
 
+CALCSIZE_BINARY(QualifiedName) {
+    return 2 + String_calcSizeBinary(&src->name, NULL);
+}
+
 CALCSIZE_BINARY(LocalizedText) {
     size_t s = 1; /* encoding byte */
     if(src->locale.data)
@@ -1715,7 +1731,7 @@ const calcSizeBinarySignature calcSizeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1
     (calcSizeBinarySignature)NodeId_calcSizeBinary,
     (calcSizeBinarySignature)ExpandedNodeId_calcSizeBinary,
     (calcSizeBinarySignature)calcSizeBinaryMemSize, /* StatusCode */
-    (calcSizeBinarySignature)UA_calcSizeBinary, /* QualifiedName */
+    (calcSizeBinarySignature)QualifiedName_calcSizeBinary,
     (calcSizeBinarySignature)LocalizedText_calcSizeBinary,
     (calcSizeBinarySignature)ExtensionObject_calcSizeBinary,
     (calcSizeBinarySignature)DataValue_calcSizeBinary,

+ 4 - 4
src/ua_util.h

@@ -152,12 +152,12 @@ UA_atomic_subSize(volatile size_t *addr, size_t decrease) {
  * up to that point. */
 size_t UA_readNumber(u8 *buf, size_t buflen, u32 *number);
 
-#ifndef MIN
-#define MIN(A,B) (A > B ? B : A)
+#ifndef UA_MIN
+#define UA_MIN(A,B) (A > B ? B : A)
 #endif
 
-#ifndef MAX
-#define MAX(A,B) (A > B ? A : B)
+#ifndef UA_MAX
+#define UA_MAX(A,B) (A > B ? A : B)
 #endif
 
 #ifdef UA_DEBUG_DUMP_PKGS

+ 11 - 0
tests/CMakeLists.txt

@@ -141,6 +141,10 @@ add_executable(check_services_subscriptions server/check_services_subscriptions.
 target_link_libraries(check_services_subscriptions ${LIBS})
 add_test_valgrind(services_subscriptions ${TESTS_BINARY_DIR}/check_services_subscriptions)
 
+add_executable(check_monitoreditem_filter server/check_monitoreditem_filter.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+target_link_libraries(check_monitoreditem_filter ${LIBS})
+add_test_valgrind(monitoreditem_filter ${TESTS_BINARY_DIR}/check_monitoreditem_filter)
+
 add_executable(check_nodestore server/check_nodestore.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
 target_link_libraries(check_nodestore ${LIBS})
 add_test_valgrind(nodestore ${TESTS_BINARY_DIR}/check_nodestore)
@@ -177,6 +181,9 @@ if(UA_ENABLE_PUBSUB)
     add_executable(check_pubsub_pds pubsub/check_pubsub_pds.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-plugins>)
     target_link_libraries(check_pubsub_pds ${LIBS})
     add_test(check_pubsub_pds ${TESTS_BINARY_DIR}/check_pubsub_pds)
+    add_executable(check_pubsub_publish pubsub/check_pubsub_publish.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-plugins>)
+    target_link_libraries(check_pubsub_publish ${LIBS})
+    add_test(check_pubsub_publish ${TESTS_BINARY_DIR}/check_pubsub_publish)
 endif()
 
 add_executable(check_server_readspeed server/check_server_readspeed.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
@@ -197,6 +204,10 @@ add_executable(check_client_async client/check_client_async.c $<TARGET_OBJECTS:o
 target_link_libraries(check_client_async ${LIBS})
 add_test_valgrind(client_async ${TESTS_BINARY_DIR}/check_client_async)
 
+add_executable(check_client_async_connect client/check_client_async_connect.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+target_link_libraries(check_client_async_connect ${LIBS})
+add_test_valgrind(client_async_connect ${TESTS_BINARY_DIR}/check_client_async_connect)
+
 add_executable(check_client_subscriptions client/check_client_subscriptions.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
 target_link_libraries(check_client_subscriptions ${LIBS})
 add_test_valgrind(client_subscriptions ${TESTS_BINARY_DIR}/check_client_subscriptions)

+ 6 - 1
tests/check_types_memory.c

@@ -158,7 +158,10 @@ END_TEST
 
 START_TEST(decodeShallFailWithTruncatedBufferButSurvive) {
     //Skip test for void*
-    if (_i == UA_TYPES_DISCOVERYCONFIGURATION ||
+    if (
+#ifdef UA_ENABLE_DISCOVERY
+        _i == UA_TYPES_DISCOVERYCONFIGURATION ||
+#endif
         _i == UA_TYPES_FILTEROPERAND ||
         _i == UA_TYPES_UNION ||
         _i == UA_TYPES_HISTORYREADDETAILS ||
@@ -281,7 +284,9 @@ START_TEST(calcSizeBinaryShallBeCorrect) {
        _i == UA_TYPES_VARIABLEATTRIBUTES ||
        _i == UA_TYPES_VARIABLETYPEATTRIBUTES ||
        _i == UA_TYPES_FILTEROPERAND ||
+#ifdef UA_ENABLE_DISCOVERY
        _i == UA_TYPES_DISCOVERYCONFIGURATION ||
+#endif
        _i == UA_TYPES_UNION ||
        _i == UA_TYPES_HISTORYREADDETAILS ||
        _i == UA_TYPES_NOTIFICATIONDATA ||

+ 188 - 32
tests/client/check_client_async.c

@@ -8,10 +8,13 @@
 #include "ua_types.h"
 #include "ua_server.h"
 #include "ua_client.h"
+#include "ua_client_highlevel_async.h"
 #include "ua_config_default.h"
 #include "ua_network_tcp.h"
 #include "check.h"
 #include "testing_clock.h"
+#include "testing_networklayers.h"
+#include "client/ua_client_internal.h"
 
 #include "thread_wrapper.h"
 
@@ -22,7 +25,7 @@ UA_ServerNetworkLayer nl;
 THREAD_HANDLE server_thread;
 
 THREAD_CALLBACK(serverloop) {
-    while(running)
+    while (running)
         UA_Server_run_iterate(server, true);
     return 0;
 }
@@ -43,56 +46,209 @@ static void teardown(void) {
     UA_ServerConfig_delete(config);
 }
 
-static void
-asyncReadCallback(UA_Client *client, void *userdata,
-                  UA_UInt32 requestId, const UA_ReadResponse *response) {
-    UA_UInt16 *asyncCounter = (UA_UInt16*)userdata;
+static void asyncReadCallback(UA_Client *client, void *userdata,
+        UA_UInt32 requestId, const UA_ReadResponse *response) {
+    UA_UInt16 *asyncCounter = (UA_UInt16*) userdata;
+    if (response->responseHeader.serviceResult == UA_STATUSCODE_BADTIMEOUT) {
+        (*asyncCounter) = 9999;
+        UA_fakeSleep(10);
+    } else {
+        (*asyncCounter)++;
+        UA_fakeSleep(10);
+    }
+}
+
+static void asyncReadValueAtttributeCallback(UA_Client *client, void *userdata,
+        UA_UInt32 requestId, UA_Variant *var) {
+    UA_UInt16 *asyncCounter = (UA_UInt16*) userdata;
     (*asyncCounter)++;
     UA_fakeSleep(10);
 }
 
-START_TEST(Client_read_async) {
-    UA_Client *client = UA_Client_new(UA_ClientConfig_default);
-    UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
-    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+START_TEST(Client_highlevel_async_readValue)
+    {
+        UA_ClientConfig clientConfig = UA_ClientConfig_default;
+        clientConfig.outStandingPublishRequests = 0;
+
+        UA_Client *client = UA_Client_new(clientConfig);
+
+        UA_StatusCode retval = UA_Client_connect(client,
+                "opc.tcp://localhost:4840");
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+        UA_Client_recv = client->connection.recv;
+        client->connection.recv = UA_Client_recvTesting;
 
-    UA_UInt16 asyncCounter = 0;
+        UA_UInt16 asyncCounter = 0;
 
-    UA_ReadRequest rr;
-    UA_ReadRequest_init(&rr);
+        UA_UInt32 reqId = 0;
+        retval = UA_Client_readValueAttribute_async(client,
+                UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME),
+                (UA_ClientAsyncReadValueAttributeCallback) asyncReadValueAtttributeCallback,
+                (void*)&asyncCounter, &reqId);
 
-    UA_ReadValueId rvid;
-    UA_ReadValueId_init(&rvid);
-    rvid.attributeId = UA_ATTRIBUTEID_VALUE;
-    rvid.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
-    rr.nodesToRead = &rvid;
-    rr.nodesToReadSize = 1;
+        /* Process async responses during 1s */
+        UA_Client_run_iterate(client, 999 + 1);
 
-    /* Send 100 requests */
-    for(size_t i = 0; i < 100; i++) {
-        retval = __UA_Client_AsyncService(client, &rr, &UA_TYPES[UA_TYPES_READREQUEST],
-                                          (UA_ClientAsyncServiceCallback)asyncReadCallback,
-                                          &UA_TYPES[UA_TYPES_READRESPONSE], &asyncCounter, NULL);
         ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+        ck_assert_uint_eq(asyncCounter, 1);
+
+        /* Simulate network cable unplugged (no response from server) */
+        UA_Client_recvTesting_result = UA_STATUSCODE_GOODNONCRITICALTIMEOUT;
+
+        UA_Client_disconnect(client);
+        UA_Client_delete(client);
     }
+}
+
+
+
+START_TEST(Client_read_async)
+    {
+        UA_Client *client = UA_Client_new(UA_ClientConfig_default);
+        UA_StatusCode retval = UA_Client_connect(client,
+                "opc.tcp://localhost:4840");
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+        UA_UInt16 asyncCounter = 0;
+
+        UA_ReadRequest rr;
+        UA_ReadRequest_init(&rr);
+
+        UA_ReadValueId rvid;
+        UA_ReadValueId_init(&rvid);
+        rvid.attributeId = UA_ATTRIBUTEID_VALUE;
+        rvid.nodeId = UA_NODEID_NUMERIC(0,
+                UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
+
+        rr.nodesToRead = &rvid;
+        rr.nodesToReadSize = 1;
+
+        /* Send 100 requests */
+        for (size_t i = 0; i < 100; i++) {
+            retval = __UA_Client_AsyncService(client, &rr,
+                    &UA_TYPES[UA_TYPES_READREQUEST],
+                    (UA_ClientAsyncServiceCallback) asyncReadCallback,
+                    &UA_TYPES[UA_TYPES_READRESPONSE], &asyncCounter, NULL);
+            ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+        }
+
+        /* Process async responses during 1s */
+        retval = UA_Client_run_iterate(client, 999);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+        ck_assert_uint_eq(asyncCounter, 100);
+
+        UA_Client_disconnect(client);
+        UA_Client_delete(client);
+    }END_TEST
+
+START_TEST(Client_read_async_timed)
+    {
+        UA_ClientConfig clientConfig = UA_ClientConfig_default;
+        clientConfig.outStandingPublishRequests = 0;
+
+        UA_Client *client = UA_Client_new(clientConfig);
+
+        UA_StatusCode retval = UA_Client_connect(client,
+                "opc.tcp://localhost:4840");
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
-    /* Process async responses during 1s */
-    retval = UA_Client_runAsync(client, 999);
-    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
-    ck_assert_uint_eq(asyncCounter, 100);
+        UA_Client_recv = client->connection.recv;
+        client->connection.recv = UA_Client_recvTesting;
 
-    UA_Client_disconnect(client);
-    UA_Client_delete(client);
+        UA_UInt16 asyncCounter = 0;
+
+        UA_ReadRequest rr;
+        UA_ReadRequest_init(&rr);
+
+        UA_ReadValueId rvid;
+        UA_ReadValueId_init(&rvid);
+        rvid.attributeId = UA_ATTRIBUTEID_VALUE;
+        rvid.nodeId = UA_NODEID_NUMERIC(0,
+                UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
+
+        rr.nodesToRead = &rvid;
+        rr.nodesToReadSize = 1;
+
+        retval = __UA_Client_AsyncServiceEx(client, &rr,
+                &UA_TYPES[UA_TYPES_READREQUEST],
+                (UA_ClientAsyncServiceCallback) asyncReadCallback,
+                &UA_TYPES[UA_TYPES_READRESPONSE], &asyncCounter, NULL, 999);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+        /* Process async responses during 1s */
+        retval = UA_Client_run_iterate(client, 999 + 1);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+        ck_assert_uint_eq(asyncCounter, 1);
+
+        /* Simulate network cable unplugged (no response from server) */
+        UA_Client_recvTesting_result = UA_STATUSCODE_GOODNONCRITICALTIMEOUT;
+
+        retval = __UA_Client_AsyncServiceEx(client, &rr,
+                &UA_TYPES[UA_TYPES_READREQUEST],
+                (UA_ClientAsyncServiceCallback) asyncReadCallback,
+                &UA_TYPES[UA_TYPES_READRESPONSE], &asyncCounter, NULL, 100);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+        /* Process async responses during 1s */
+        UA_Client_run_iterate(client, 100 + 1);
+        ck_assert_uint_eq(asyncCounter, 9999);
+
+        UA_Client_disconnect(client);
+        UA_Client_delete(client);
+    }END_TEST
+
+static UA_Boolean inactivityCallbackTriggered = false;
+
+static void inactivityCallback(UA_Client *client) {
+    inactivityCallbackTriggered = true;
 }
-END_TEST
+
+START_TEST(Client_connectivity_check)
+    {
+        UA_ClientConfig clientConfig = UA_ClientConfig_default;
+        clientConfig.outStandingPublishRequests = 0;
+        clientConfig.inactivityCallback = inactivityCallback;
+        clientConfig.connectivityCheckInterval = 1000;
+
+        UA_Client *client = UA_Client_new(clientConfig);
+
+        UA_StatusCode retval = UA_Client_connect(client,
+                "opc.tcp://localhost:4840");
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+        UA_Client_recv = client->connection.recv;
+        client->connection.recv = UA_Client_recvTesting;
+
+        inactivityCallbackTriggered = false;
+
+        retval = UA_Client_run_iterate(client, 1000 + 1);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+        ck_assert_uint_eq(inactivityCallbackTriggered, false);
+
+        /* Simulate network cable unplugged (no response from server) */
+        UA_Client_recvTesting_result = UA_STATUSCODE_GOODNONCRITICALTIMEOUT;
+
+        retval = UA_Client_run_iterate(client,
+                (UA_UInt16) (1000 + 1 + clientConfig.timeout));
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+        ck_assert_uint_eq(inactivityCallbackTriggered, true);
+
+        UA_Client_disconnect(client);
+        UA_Client_delete(client);
+    }END_TEST
 
 static Suite* testSuite_Client(void) {
     Suite *s = suite_create("Client");
     TCase *tc_client = tcase_create("Client Basic");
     tcase_add_checked_fixture(tc_client, setup, teardown);
     tcase_add_test(tc_client, Client_read_async);
-    suite_add_tcase(s,tc_client);
+    tcase_add_test(tc_client, Client_read_async_timed);
+    tcase_add_test(tc_client, Client_connectivity_check);
+    tcase_add_test(tc_client, Client_highlevel_async_readValue);
+
+    suite_add_tcase(s, tc_client);
     return s;
 }
 
@@ -100,7 +256,7 @@ int main(void) {
     Suite *s = testSuite_Client();
     SRunner *sr = srunner_create(s);
     srunner_set_fork_status(sr, CK_NOFORK);
-    srunner_run_all(sr,CK_NORMAL);
+    srunner_run_all(sr, CK_NORMAL);
     int number_failed = srunner_ntests_failed(sr);
     srunner_free(sr);
     return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;

+ 158 - 0
tests/client/check_client_async_connect.c

@@ -0,0 +1,158 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifndef WIN32
+#include <unistd.h>
+#endif
+
+#include "ua_types.h"
+#include "ua_server.h"
+#include "ua_client.h"
+#include "client/ua_client_internal.h"
+#include "ua_client_highlevel_async.h"
+#include "ua_config_default.h"
+#include "ua_network_tcp.h"
+#include "check.h"
+#include "testing_clock.h"
+#include "testing_networklayers.h"
+#include "thread_wrapper.h"
+
+UA_Server *server;
+UA_ServerConfig *config;
+UA_Boolean running;
+UA_ServerNetworkLayer nl;
+THREAD_HANDLE server_thread;
+
+THREAD_CALLBACK(serverloop) {
+    while(running)
+        UA_Server_run_iterate(server, true);
+    return 0;
+}
+
+static void
+onConnect (UA_Client *Client, void *connected, UA_UInt32 requestId,
+           void *response) {
+    if (UA_Client_getState (Client) == UA_CLIENTSTATE_SESSION)
+        *(UA_Boolean *)connected = true;
+}
+
+static void setup(void) {
+    running = true;
+    config = UA_ServerConfig_new_default();
+    server = UA_Server_new(config);
+    UA_Server_run_startup(server);
+    THREAD_CREATE(server_thread, serverloop);
+    /* Waiting server is up */
+    UA_comboSleep(1000);
+}
+
+static void teardown(void) {
+    running = false;
+    THREAD_JOIN(server_thread);
+    UA_Server_run_shutdown(server);
+    UA_Server_delete(server);
+    UA_ServerConfig_delete(config);
+}
+
+static void
+asyncBrowseCallback(UA_Client *Client, void *userdata,
+                  UA_UInt32 requestId, UA_BrowseResponse *response) {
+    UA_UInt16 *asyncCounter = (UA_UInt16*)userdata;
+    (*asyncCounter)++;
+}
+
+START_TEST(Client_connect_async){
+    UA_StatusCode retval;
+    UA_Client *client = UA_Client_new(UA_ClientConfig_default);
+    UA_Boolean connected = false;
+    UA_Client_connect_async(client, "opc.tcp://localhost:4840", onConnect,
+                            &connected);
+    /*Windows needs time to response*/
+    UA_sleep_ms(100);
+    UA_UInt32 reqId = 0;
+    UA_UInt16 asyncCounter = 0;
+    UA_BrowseRequest bReq;
+    UA_BrowseRequest_init (&bReq);
+    bReq.requestedMaxReferencesPerNode = 0;
+    bReq.nodesToBrowse = UA_BrowseDescription_new ();
+    bReq.nodesToBrowseSize = 1;
+    bReq.nodesToBrowse[0].nodeId = UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER);
+    bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
+    /* Connected gets updated when client is connected */
+
+    do{
+        if(connected) {
+            /* If not connected requests are not sent */
+            UA_Client_sendAsyncBrowseRequest (client, &bReq, asyncBrowseCallback,
+                                              &asyncCounter, &reqId);
+        }
+        /* Manual clock for unit tests */
+        UA_comboSleep(20);
+        retval = UA_Client_run_iterate(client, 0);
+        /*fix infinite loop, but why is server occasionally shut down in Appveyor?!*/
+        if(retval == UA_STATUSCODE_BADCONNECTIONCLOSED)
+            break;
+    } while(reqId < 10);
+
+    UA_BrowseRequest_deleteMembers(&bReq);
+    ck_assert_uint_eq(connected, true);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    /* With default setting the client uses 4 requests to connect */
+    ck_assert_uint_eq(asyncCounter, 10-4);
+    UA_Client_disconnect(client);
+    UA_Client_delete (client);
+}
+END_TEST
+
+START_TEST(Client_no_connection) {
+    UA_Client *client = UA_Client_new(UA_ClientConfig_default);
+
+    UA_Boolean connected = false;
+    UA_StatusCode retval = UA_Client_connect_async(client, "opc.tcp://localhost:4840", onConnect,
+            &connected);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_Client_recv = client->connection.recv;
+    client->connection.recv = UA_Client_recvTesting;
+    //simulating unconnected server
+    UA_Client_recvTesting_result = UA_STATUSCODE_BADCONNECTIONCLOSED;
+    retval = UA_Client_run_iterate(client, 0);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_BADCONNECTIONCLOSED);
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+}
+END_TEST
+
+START_TEST(Client_without_run_iterate) {
+    UA_Client *client = UA_Client_new(UA_ClientConfig_default);
+    UA_Boolean connected = false;
+    UA_Client_connect_async(client, "opc.tcp://localhost:4840", onConnect,
+                            &connected);
+    UA_Client_delete(client);
+}
+END_TEST
+
+static Suite* testSuite_Client(void) {
+    Suite *s = suite_create("Client");
+    TCase *tc_client_connect = tcase_create("Client Connect Async");
+    tcase_add_checked_fixture(tc_client_connect, setup, teardown);
+    tcase_add_test(tc_client_connect, Client_connect_async);
+    tcase_add_test(tc_client_connect, Client_no_connection);
+    tcase_add_test(tc_client_connect, Client_without_run_iterate);
+    suite_add_tcase(s,tc_client_connect);
+    return s;
+}
+
+int main(void) {
+    Suite *s = testSuite_Client();
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr, CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}

+ 2 - 0
tests/client/check_client_highlevel.c

@@ -539,7 +539,9 @@ static void checkNodeClass(UA_Client *clientNc, const UA_NodeId nodeId, const UA
 START_TEST(Node_ReadWrite_Class) {
     checkNodeClass(client, nodeReadWriteInt, UA_NODECLASS_VARIABLE);
     checkNodeClass(client, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), UA_NODECLASS_OBJECT);
+#ifdef UA_ENABLE_METHODCALLS
     checkNodeClass(client, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS), UA_NODECLASS_METHOD);
+#endif
 
     UA_NodeClass newClass = UA_NODECLASS_METHOD;
     UA_StatusCode retval = UA_Client_writeNodeClassAttribute(client, nodeReadWriteInt, &newClass);

+ 23 - 20
tests/client/check_client_subscriptions.c

@@ -90,7 +90,7 @@ START_TEST(Client_subscription) {
 
     notificationReceived = false;
 
-    retval = UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
+    retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_uint_eq(notificationReceived, true);
 
@@ -169,13 +169,13 @@ START_TEST(Client_subscription_createDataChanges) {
 
     notificationReceived = false;
     countNotificationReceived = 0;
-    retval = UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
+    retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_uint_eq(notificationReceived, true);
     ck_assert_uint_eq(countNotificationReceived, 2);
 
     notificationReceived = false;
-    retval = UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
+    retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_uint_eq(notificationReceived, true);
     ck_assert_uint_eq(countNotificationReceived, 3);
@@ -293,13 +293,13 @@ START_TEST(Client_subscription_connectionClose) {
 
     UA_fakeSleep((UA_UInt32)publishingInterval + 1);
 
-    retval = UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 60));
+    retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 60));
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
     /* Simulate BADCONNECTIONCLOSE */
     UA_Client_recvTesting_result = UA_STATUSCODE_BADCONNECTIONCLOSED;
 
-    retval = UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 60));
+    retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 60));
     ck_assert_uint_eq(retval, UA_STATUSCODE_BADCONNECTIONCLOSED);
 
     UA_Client_disconnect(client);
@@ -337,12 +337,12 @@ START_TEST(Client_subscription_without_notification) {
     UA_fakeSleep((UA_UInt32)publishingInterval + 1);
 
     notificationReceived = false;
-    retval = UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
+    retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_uint_eq(notificationReceived, true);
 
     notificationReceived = false;
-    retval = UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
+    retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_uint_eq(notificationReceived, false);
 
@@ -420,27 +420,27 @@ START_TEST(Client_subscription_async_sub) {
     countNotificationReceived = 0;
 
     notificationReceived = false;
-    UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
+    UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(notificationReceived, true);
     ck_assert_uint_eq(countNotificationReceived, 1);
 
     notificationReceived = false;
-    UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
+    UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(notificationReceived, true);
     ck_assert_uint_eq(countNotificationReceived, 2);
 
     notificationReceived = false;
-    UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
+    UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(notificationReceived, true);
     ck_assert_uint_eq(countNotificationReceived, 3);
 
     notificationReceived = false;
-    UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
+    UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(notificationReceived, true);
     ck_assert_uint_eq(countNotificationReceived, 4);
 
     notificationReceived = false;
-    UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
+    UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(notificationReceived, true);
     ck_assert_uint_eq(countNotificationReceived, 5);
 
@@ -449,14 +449,14 @@ START_TEST(Client_subscription_async_sub) {
     notificationReceived = false;
     /* Simulate network cable unplugged (no response from server) */
     UA_Client_recvTesting_result = UA_STATUSCODE_GOODNONCRITICALTIMEOUT;
-    UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
+    UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(notificationReceived, false);
     ck_assert_uint_eq(callbackClientState, UA_CLIENTSTATE_SESSION);
 
     /* Simulate network cable unplugged (no response from server) */
     ck_assert_uint_eq(inactivityCallbackCalled, false);
     UA_Client_recvTesting_result = UA_STATUSCODE_GOODNONCRITICALTIMEOUT;
-    UA_Client_runAsync(client, (UA_UInt16)clientConfig.timeout);
+    UA_Client_run_iterate(client, (UA_UInt16)clientConfig.timeout);
     ck_assert_uint_eq(inactivityCallbackCalled, true);
     ck_assert_uint_eq(callbackClientState, UA_CLIENTSTATE_SESSION);
 
@@ -464,6 +464,7 @@ START_TEST(Client_subscription_async_sub) {
 }
 END_TEST
 
+#ifdef UA_ENABLE_METHODCALLS
 START_TEST(Client_methodcall) {
     UA_Client *client = UA_Client_new(UA_ClientConfig_default);
     UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
@@ -521,30 +522,32 @@ START_TEST(Client_methodcall) {
     UA_Client_delete(client);
 }
 END_TEST
+#endif /* UA_ENABLE_METHODCALLS */
 
 #endif /* UA_ENABLE_SUBSCRIPTIONS */
 
 static Suite* testSuite_Client(void) {
+    Suite *s = suite_create("Client Subscription");
+
+#ifdef UA_ENABLE_SUBSCRIPTIONS
     TCase *tc_client = tcase_create("Client Subscription Basic");
     tcase_add_checked_fixture(tc_client, setup, teardown);
-#ifdef UA_ENABLE_SUBSCRIPTIONS
     tcase_add_test(tc_client, Client_subscription);
     tcase_add_test(tc_client, Client_subscription_connectionClose);
     tcase_add_test(tc_client, Client_subscription_createDataChanges);
     tcase_add_test(tc_client, Client_subscription_keepAlive);
     tcase_add_test(tc_client, Client_subscription_without_notification);
     tcase_add_test(tc_client, Client_subscription_async_sub);
+    suite_add_tcase(s,tc_client);
 #endif /* UA_ENABLE_SUBSCRIPTIONS */
 
+#if defined(UA_ENABLE_SUBSCRIPTIONS) && defined(UA_ENABLE_METHODCALLS)
     TCase *tc_client2 = tcase_create("Client Subscription + Method Call of GetMonitoredItmes");
     tcase_add_checked_fixture(tc_client2, setup, teardown);
-#ifdef UA_ENABLE_SUBSCRIPTIONS
     tcase_add_test(tc_client2, Client_methodcall);
-#endif /* UA_ENABLE_SUBSCRIPTIONS */
-
-    Suite *s = suite_create("Client Subscription");
-    suite_add_tcase(s,tc_client);
     suite_add_tcase(s,tc_client2);
+#endif
+
     return s;
 }
 

+ 3 - 3
tests/pubsub/check_pubsub_connection_udp.c

@@ -5,8 +5,8 @@
  * Copyright (c) 2017 - 2018 Fraunhofer IOSB (Author: Andreas Ebner)
  */
 
-#include <ua_server_pubsub.h>
-#include <src_generated/ua_types_generated_encoding_binary.h>
+#include "ua_server_pubsub.h"
+#include "src_generated/ua_types_generated_encoding_binary.h"
 #include "ua_config_default.h"
 #include "ua_network_pubsub_udp.h"
 #include "ua_server_internal.h"
@@ -174,7 +174,7 @@ START_TEST(GetMaximalConnectionConfigurationAndCompareValues){
     ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
     UA_PubSubConnectionConfig connectionConfig;
     memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig));
-    retVal |= UA_PubSubConnection_getConfig(server, connection, &connectionConfig);
+    retVal |= UA_Server_getPubSubConnectionConfig(server, connection, &connectionConfig);
     ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
     ck_assert(connectionConfig.connectionPropertiesSize == connectionConf.connectionPropertiesSize);
     ck_assert(UA_String_equal(&connectionConfig.name, &connectionConf.name) == UA_TRUE);

+ 1 - 1
tests/pubsub/check_pubsub_pds.c

@@ -105,7 +105,7 @@ START_TEST(GetPDSConfigurationAndCompareValues){
     ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
     UA_PublishedDataSetConfig pdsConfigCopy;
     memset(&pdsConfigCopy, 0, sizeof(UA_PublishedDataSetConfig));
-        UA_PublishedDataSet_getConfig(server, pdsIdentifier, &pdsConfigCopy);
+        UA_Server_getPublishedDataSetConfig(server, pdsIdentifier, &pdsConfigCopy);
     ck_assert_int_eq(UA_String_equal(&pdsConfig.name, &pdsConfigCopy.name), UA_TRUE);
     UA_PublishedDataSetConfig_deleteMembers(&pdsConfigCopy);
 } END_TEST

+ 458 - 0
tests/pubsub/check_pubsub_publish.c

@@ -0,0 +1,458 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Copyright (c) 2017 - 2018 Fraunhofer IOSB (Author: Andreas Ebner)
+ */
+
+#include "ua_server_pubsub.h"
+#include "src_generated/ua_types_generated_encoding_binary.h"
+#include "ua_types.h"
+#include "ua_pubsub.h"
+#include "ua_config_default.h"
+#include "ua_network_pubsub_udp.h"
+#include "ua_server_internal.h"
+#include "check.h"
+#include "stdio.h"
+
+UA_Server *server = NULL;
+UA_ServerConfig *config = NULL;
+UA_NodeId connection1, connection2, writerGroup1, writerGroup2, writerGroup3,
+        publishedDataSet1, publishedDataSet2, dataSetWriter1, dataSetWriter2, dataSetWriter3;
+
+static void setup(void) {
+    config = UA_ServerConfig_new_default();
+    config->pubsubTransportLayers = (UA_PubSubTransportLayer *) UA_malloc(sizeof(UA_PubSubTransportLayer));
+    if(!config->pubsubTransportLayers) {
+        UA_ServerConfig_delete(config);
+    }
+    config->pubsubTransportLayers[0] = UA_PubSubTransportLayerUDPMP();
+    config->pubsubTransportLayersSize++;
+    server = UA_Server_new(config);
+    UA_Server_run_startup(server);
+    //add 2 connections
+    UA_PubSubConnectionConfig connectionConfig;
+    memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig));
+    connectionConfig.name = UA_STRING("UADP Connection");
+    UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")};
+    UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
+                         &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
+    connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
+    UA_Server_addPubSubConnection(server, &connectionConfig, &connection1);
+    UA_Server_addPubSubConnection(server, &connectionConfig, &connection2);
+}
+
+static void teardown(void) {
+    UA_Server_run_shutdown(server);
+    UA_Server_delete(server);
+    UA_ServerConfig_delete(config);
+}
+
+START_TEST(AddWriterGroupWithValidConfiguration){
+        UA_StatusCode retVal;
+        UA_WriterGroupConfig writerGroupConfig;
+        memset(&writerGroupConfig, 0, sizeof(writerGroupConfig));
+        writerGroupConfig.name = UA_STRING("WriterGroup 1");
+        writerGroupConfig.publishingInterval = 10;
+        UA_NodeId localWriterGroup;
+        retVal = UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &localWriterGroup);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        size_t writerGroupCount = 0;
+        UA_WriterGroup *writerGroup;
+        LIST_FOREACH(writerGroup, &UA_PubSubConnection_findConnectionbyId(server, connection1)->writerGroups, listEntry){
+            writerGroupCount++;
+        }
+        ck_assert_int_eq(writerGroupCount, 1);
+    } END_TEST
+
+START_TEST(AddRemoveAddWriterGroupWithMinimalValidConfiguration){
+        UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+        UA_WriterGroupConfig writerGroupConfig;
+        memset(&writerGroupConfig, 0, sizeof(writerGroupConfig));
+        writerGroupConfig.name = UA_STRING("WriterGroup 1");
+        writerGroupConfig.publishingInterval = 10;
+        UA_NodeId localWriterGroup;
+        retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &localWriterGroup);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        retVal |= UA_Server_removeWriterGroup(server, localWriterGroup);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        size_t writerGroupCount = 0;
+        UA_WriterGroup *writerGroup;
+        LIST_FOREACH(writerGroup, &UA_PubSubConnection_findConnectionbyId(server, connection1)->writerGroups, listEntry){
+            writerGroupCount++;
+        }
+        ck_assert_int_eq(writerGroupCount, 0);
+        retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &localWriterGroup);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        writerGroupCount = 0;
+        LIST_FOREACH(writerGroup, &UA_PubSubConnection_findConnectionbyId(server, connection1)->writerGroups, listEntry){
+            writerGroupCount++;
+        }
+        ck_assert_int_eq(writerGroupCount, 1);
+        retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &localWriterGroup);
+        writerGroupCount = 0;
+        LIST_FOREACH(writerGroup, &UA_PubSubConnection_findConnectionbyId(server, connection1)->writerGroups, listEntry){
+            writerGroupCount++;
+        }
+        ck_assert_int_eq(writerGroupCount, 2);
+    } END_TEST
+
+START_TEST(AddWriterGroupWithNullConfig){
+        UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+        retVal |= UA_Server_addWriterGroup(server, connection1, NULL, NULL);
+        size_t writerGroupCount = 0;
+        UA_WriterGroup *writerGroup;
+        LIST_FOREACH(writerGroup, &UA_PubSubConnection_findConnectionbyId(server, connection1)->writerGroups, listEntry){
+            writerGroupCount++;
+        }
+        ck_assert_int_eq(writerGroupCount, 0);
+        ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD);
+    } END_TEST
+
+START_TEST(AddWriterGroupWithInvalidConnectionId){
+        UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+        UA_WriterGroupConfig writerGroupConfig;
+        memset(&writerGroupConfig, 0, sizeof(writerGroupConfig));
+        writerGroupConfig.name = UA_STRING("WriterGroup 1");
+        writerGroupConfig.publishingInterval = 10;
+        retVal |= UA_Server_addWriterGroup(server, UA_NODEID_NUMERIC(0, UA_UINT32_MAX), &writerGroupConfig, NULL);
+        size_t writerGroupCount = 0;
+        UA_WriterGroup *writerGroup;
+        LIST_FOREACH(writerGroup, &UA_PubSubConnection_findConnectionbyId(server, connection1)->writerGroups, listEntry){
+            writerGroupCount++;
+        }
+        ck_assert_int_eq(writerGroupCount, 0);
+        ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD);
+    } END_TEST
+
+START_TEST(GetWriterGroupConfigurationAndCompareValues){
+        UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+        UA_WriterGroupConfig writerGroupConfig;
+        memset(&writerGroupConfig, 0, sizeof(writerGroupConfig));
+        writerGroupConfig.name = UA_STRING("WriterGroup 1");
+        writerGroupConfig.publishingInterval = 10;
+        UA_NodeId localWriterGroup;
+        retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &localWriterGroup);
+        UA_WriterGroupConfig writerGroupConfigCopy;
+        retVal |= UA_Server_getWriterGroupConfig(server, localWriterGroup, &writerGroupConfigCopy);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(UA_String_equal(&writerGroupConfig.name, &writerGroupConfigCopy.name), UA_TRUE);
+        //todo remove == for floating point compare
+        ck_assert(writerGroupConfig.publishingInterval == writerGroupConfig.publishingInterval);
+        UA_WriterGroupConfig_deleteMembers(&writerGroupConfigCopy);
+    } END_TEST
+
+static void setupDataSetWriterTestEnvironment(void){
+    UA_WriterGroupConfig writerGroupConfig;
+    memset(&writerGroupConfig, 0, sizeof(writerGroupConfig));
+    writerGroupConfig.name = UA_STRING("WriterGroup 1");
+    writerGroupConfig.publishingInterval = 10;
+    writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
+    UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup1);
+    writerGroupConfig.name = UA_STRING("WriterGroup 2");
+    writerGroupConfig.publishingInterval = 50;
+    writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
+    UA_Server_addWriterGroup(server, connection2, &writerGroupConfig, &writerGroup2);
+    writerGroupConfig.name = UA_STRING("WriterGroup 3");
+    writerGroupConfig.publishingInterval = 100;
+    writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
+    UA_Server_addWriterGroup(server, connection2, &writerGroupConfig, &writerGroup3);
+    UA_PublishedDataSetConfig pdsConfig;
+    memset(&pdsConfig, 0, sizeof(UA_PublishedDataSetConfig));
+    pdsConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
+    pdsConfig.name = UA_STRING("PublishedDataSet 1");
+    UA_Server_addPublishedDataSet(server, &pdsConfig, &publishedDataSet1);
+    UA_Server_addPublishedDataSet(server, &pdsConfig, &publishedDataSet2);
+}
+
+START_TEST(AddDataSetWriterWithValidConfiguration){
+        setupDataSetWriterTestEnvironment();
+        UA_StatusCode retVal;
+        UA_DataSetWriterConfig dataSetWriterConfig;
+        memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig));
+        dataSetWriterConfig.name = UA_STRING("DataSetWriter 1 ");
+        UA_NodeId localDataSetWriter;
+        retVal = UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &localDataSetWriter);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        UA_DataSetWriter *dsw1 = UA_DataSetWriter_findDSWbyId(server, localDataSetWriter);
+        ck_assert_ptr_ne(dsw1, NULL);
+        UA_WriterGroup *wg1 = UA_WriterGroup_findWGbyId(server, writerGroup1);
+        ck_assert_ptr_ne(wg1, NULL);
+        ck_assert_int_eq(wg1->writersCount, 1);
+    } END_TEST
+
+START_TEST(AddRemoveAddDataSetWriterWithValidConfiguration){
+        setupDataSetWriterTestEnvironment();
+        UA_StatusCode retVal;
+        UA_WriterGroup *wg1 = UA_WriterGroup_findWGbyId(server, writerGroup1);
+        ck_assert_ptr_ne(wg1, NULL);
+        UA_DataSetWriterConfig dataSetWriterConfig;
+        memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig));
+        dataSetWriterConfig.name = UA_STRING("DataSetWriter 1 ");
+        UA_NodeId dataSetWriter;
+        ck_assert_int_eq(wg1->writersCount, 0);
+        retVal = UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(wg1->writersCount, 1);
+        retVal = UA_Server_removeDataSetWriter(server, dataSetWriter);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(wg1->writersCount, 0);
+        retVal = UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(wg1->writersCount, 1);
+        retVal = UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(wg1->writersCount, 2);
+
+        UA_WriterGroup *wg2 = UA_WriterGroup_findWGbyId(server, writerGroup2);
+        ck_assert_ptr_ne(wg2, NULL);
+        retVal = UA_Server_addDataSetWriter(server, writerGroup2, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(wg2->writersCount, 1);
+    } END_TEST
+
+START_TEST(AddDataSetWriterWithNullConfig){
+        setupDataSetWriterTestEnvironment();
+        UA_StatusCode retVal;
+        retVal = UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, NULL, NULL);
+        UA_WriterGroup *wg1 = UA_WriterGroup_findWGbyId(server, writerGroup1);
+        ck_assert_ptr_ne(wg1, NULL);
+        ck_assert_int_eq(wg1->writersCount, 0);
+        ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD);
+    } END_TEST
+
+START_TEST(AddDataSetWriterWithInvalidPDSId){
+        setupDataSetWriterTestEnvironment();
+        UA_StatusCode retVal;
+        UA_DataSetWriterConfig dataSetWriterConfig;
+        memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig));
+        dataSetWriterConfig.name = UA_STRING("DataSetWriter 1 ");
+        retVal = UA_Server_addDataSetWriter(server, writerGroup1, UA_NODEID_NUMERIC(0, UA_UINT32_MAX), &dataSetWriterConfig, NULL);
+        UA_WriterGroup *wg1 = UA_WriterGroup_findWGbyId(server, writerGroup1);
+        ck_assert_ptr_ne(wg1, NULL);
+        ck_assert_int_eq(wg1->writersCount, 0);
+        ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD);
+    } END_TEST
+
+START_TEST(GetDataSetWriterConfigurationAndCompareValues){
+        setupDataSetWriterTestEnvironment();
+        UA_StatusCode retVal;
+        UA_DataSetWriterConfig dataSetWriterConfig;
+        memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig));
+        dataSetWriterConfig.name = UA_STRING("DataSetWriter 1 ");
+        UA_NodeId localDataSetWriter;
+        retVal = UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &localDataSetWriter);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        UA_DataSetWriterConfig dataSetWiterConfigCopy;
+        retVal |= UA_Server_getDataSetWriterConfig(server, localDataSetWriter, &dataSetWiterConfigCopy);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(UA_String_equal(&dataSetWiterConfigCopy.name, &dataSetWiterConfigCopy.name), UA_TRUE);
+        UA_DataSetWriterConfig_deleteMembers(&dataSetWiterConfigCopy);
+    } END_TEST
+
+static void setupDataSetFieldTestEnvironment(void){
+    setupDataSetWriterTestEnvironment();
+    UA_DataSetWriterConfig dataSetWriterConfig;
+    memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig));
+    dataSetWriterConfig.name = UA_STRING("DataSetWriter 1");
+    UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter1);
+    dataSetWriterConfig.name = UA_STRING("DataSetWriter 2");
+    UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter2);
+    dataSetWriterConfig.name = UA_STRING("DataSetWriter 3");
+    UA_Server_addDataSetWriter(server, writerGroup2, publishedDataSet2, &dataSetWriterConfig, &dataSetWriter3);
+}
+
+START_TEST(AddDataSetFieldWithValidConfiguration){
+        setupDataSetFieldTestEnvironment();
+        UA_StatusCode retVal;
+        UA_DataSetFieldConfig fieldConfig;
+        memset(&fieldConfig, 0, sizeof(UA_DataSetFieldConfig));
+        fieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
+        fieldConfig.field.variable.fieldNameAlias = UA_STRING("field 1");
+        UA_NodeId localDataSetField;
+        UA_PublishedDataSet *pds = UA_PublishedDataSet_findPDSbyId(server, publishedDataSet1);
+        ck_assert_ptr_ne(pds, NULL);
+        ck_assert_int_eq(pds->fieldSize, 0);
+        retVal = UA_Server_addDataSetField(server, publishedDataSet1, &fieldConfig, &localDataSetField).result;
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(pds->fieldSize, 1);
+    } END_TEST
+
+START_TEST(AddRemoveAddDataSetFieldWithValidConfiguration){
+        setupDataSetFieldTestEnvironment();
+        UA_StatusCode retVal;
+        UA_DataSetFieldConfig fieldConfig;
+        memset(&fieldConfig, 0, sizeof(UA_DataSetFieldConfig));
+        fieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
+        fieldConfig.field.variable.fieldNameAlias = UA_STRING("field 1");
+        UA_NodeId localDataSetField;
+        UA_PublishedDataSet *pds1 = UA_PublishedDataSet_findPDSbyId(server, publishedDataSet1);
+        ck_assert_ptr_ne(pds1, NULL);
+        ck_assert_int_eq(pds1->fieldSize, 0);
+        retVal = UA_Server_addDataSetField(server, publishedDataSet1, &fieldConfig, &localDataSetField).result;
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(pds1->fieldSize, 1);
+        retVal = UA_Server_removeDataSetField(server, localDataSetField).result;
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(pds1->fieldSize, 0);
+        retVal = UA_Server_addDataSetField(server, publishedDataSet1, &fieldConfig, &localDataSetField).result;
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(pds1->fieldSize, 1);
+        retVal = UA_Server_addDataSetField(server, publishedDataSet1, &fieldConfig, &localDataSetField).result;
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(pds1->fieldSize, 2);
+        UA_PublishedDataSet *pds2 = UA_PublishedDataSet_findPDSbyId(server, publishedDataSet2);
+        ck_assert_ptr_ne(pds2, NULL);
+        retVal = UA_Server_addDataSetField(server, publishedDataSet2, &fieldConfig, &localDataSetField).result;
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(pds2->fieldSize, 1);
+    } END_TEST
+
+START_TEST(AddDataSetFieldWithNullConfig){
+        setupDataSetFieldTestEnvironment();
+        UA_StatusCode retVal;
+        retVal = UA_Server_addDataSetField(server, publishedDataSet1, NULL, NULL).result;
+        ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD);
+        UA_PublishedDataSet *pds1 = UA_PublishedDataSet_findPDSbyId(server, publishedDataSet1);
+        ck_assert_ptr_ne(pds1, NULL);
+        ck_assert_int_eq(pds1->fieldSize, 0);
+    } END_TEST
+
+START_TEST(AddDataSetFieldWithInvalidPDSId){
+        setupDataSetFieldTestEnvironment();
+        UA_StatusCode retVal;
+        UA_DataSetFieldConfig fieldConfig;
+        memset(&fieldConfig, 0, sizeof(UA_DataSetFieldConfig));
+        fieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
+        fieldConfig.field.variable.fieldNameAlias = UA_STRING("field 1");
+        retVal = UA_Server_addDataSetField(server, UA_NODEID_NUMERIC(0, UA_UINT32_MAX), &fieldConfig, NULL).result;
+        ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD);
+        UA_PublishedDataSet *pds1 = UA_PublishedDataSet_findPDSbyId(server, publishedDataSet1);
+        ck_assert_ptr_ne(pds1, NULL);
+        ck_assert_int_eq(pds1->fieldSize, 0);
+    } END_TEST
+
+START_TEST(GetDataSetFieldConfigurationAndCompareValues){
+        setupDataSetFieldTestEnvironment();
+        UA_StatusCode retVal;
+        UA_DataSetFieldConfig fieldConfig;
+        memset(&fieldConfig, 0, sizeof(UA_DataSetFieldConfig));
+        fieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
+        fieldConfig.field.variable.fieldNameAlias = UA_STRING("field 1");
+        UA_NodeId dataSetFieldId;
+        retVal = UA_Server_addDataSetField(server, publishedDataSet1, &fieldConfig, &dataSetFieldId).result;
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        UA_DataSetFieldConfig fieldConfigCopy;
+        retVal |= UA_Server_getDataSetFieldConfig(server, dataSetFieldId, &fieldConfigCopy);
+        ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(fieldConfig.dataSetFieldType, fieldConfigCopy.dataSetFieldType);
+        ck_assert_int_eq(UA_String_equal(&fieldConfig.field.variable.fieldNameAlias, &fieldConfigCopy.field.variable.fieldNameAlias), UA_TRUE);
+        UA_DataSetFieldConfig_deleteMembers(&fieldConfigCopy);
+    } END_TEST
+
+
+START_TEST(SinglePublishDataSetField){
+        UA_WriterGroupConfig writerGroupConfig;
+        memset(&writerGroupConfig, 0, sizeof(writerGroupConfig));
+        writerGroupConfig.name = UA_STRING("WriterGroup 1");
+        writerGroupConfig.publishingInterval = 10;
+        writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
+        UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup1);
+        writerGroupConfig.name = UA_STRING("WriterGroup 2");
+        writerGroupConfig.publishingInterval = 50;
+        writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
+        UA_Server_addWriterGroup(server, connection2, &writerGroupConfig, &writerGroup2);
+        writerGroupConfig.name = UA_STRING("WriterGroup 3");
+        writerGroupConfig.publishingInterval = 100;
+        writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
+        UA_Server_addWriterGroup(server, connection2, &writerGroupConfig, &writerGroup3);
+        UA_PublishedDataSetConfig pdsConfig;
+        memset(&pdsConfig, 0, sizeof(UA_PublishedDataSetConfig));
+        pdsConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
+        pdsConfig.name = UA_STRING("PublishedDataSet 1");
+        UA_Server_addPublishedDataSet(server, &pdsConfig, &publishedDataSet1);
+        UA_Server_addPublishedDataSet(server, &pdsConfig, &publishedDataSet2);
+        UA_DataSetWriterConfig dataSetWriterConfig;
+        memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig));
+        dataSetWriterConfig.name = UA_STRING("DataSetWriter 1");
+        UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter1);
+        UA_DataSetFieldConfig dataSetFieldConfig;
+        memset(&dataSetFieldConfig, 0, sizeof(UA_DataSetFieldConfig));
+        dataSetFieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
+        dataSetFieldConfig.field.variable.fieldNameAlias = UA_STRING("Server localtime");
+        dataSetFieldConfig.field.variable.promotedField = UA_FALSE;
+        dataSetFieldConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_LOCALTIME);
+        dataSetFieldConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
+        UA_Server_addDataSetField(server, publishedDataSet1, &dataSetFieldConfig, NULL);
+
+        UA_WriterGroup *wg = UA_WriterGroup_findWGbyId(server, writerGroup1);
+        UA_WriterGroup_publishCallback(server, wg);
+    } END_TEST
+
+START_TEST(PublishDataSetFieldAsDeltaFrame){
+            setupDataSetFieldTestEnvironment();
+            UA_DataSetFieldConfig dataSetFieldConfig;
+            memset(&dataSetFieldConfig, 0, sizeof(UA_DataSetFieldConfig));
+            dataSetFieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
+            dataSetFieldConfig.field.variable.fieldNameAlias = UA_STRING("Server localtime");
+            dataSetFieldConfig.field.variable.promotedField = UA_FALSE;
+            dataSetFieldConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_LOCALTIME);
+            dataSetFieldConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
+            UA_Server_addDataSetField(server, publishedDataSet1, &dataSetFieldConfig, NULL);
+            UA_Server_addDataSetField(server, publishedDataSet1, &dataSetFieldConfig, NULL);
+
+            UA_WriterGroup *wg = UA_WriterGroup_findWGbyId(server, writerGroup1);
+            wg->config.maxEncapsulatedDataSetMessageCount = 3;
+            UA_DataSetWriter *dsw = UA_DataSetWriter_findDSWbyId(server, dataSetWriter1);
+            dsw->config.keyFrameCount = 3;
+
+            UA_WriterGroup_publishCallback(server, wg);
+            UA_WriterGroup_publishCallback(server, wg);
+            UA_WriterGroup_publishCallback(server, wg);
+            UA_WriterGroup_publishCallback(server, wg);
+            UA_WriterGroup_publishCallback(server, wg);
+        } END_TEST
+
+int main(void) {
+    TCase *tc_add_pubsub_writergroup = tcase_create("PubSub WriterGroup items handling");
+    tcase_add_checked_fixture(tc_add_pubsub_writergroup, setup, teardown);
+    tcase_add_test(tc_add_pubsub_writergroup, AddWriterGroupWithValidConfiguration);
+    tcase_add_test(tc_add_pubsub_writergroup, AddRemoveAddWriterGroupWithMinimalValidConfiguration);
+    tcase_add_test(tc_add_pubsub_writergroup, AddWriterGroupWithNullConfig);
+    tcase_add_test(tc_add_pubsub_writergroup, AddWriterGroupWithInvalidConnectionId);
+    tcase_add_test(tc_add_pubsub_writergroup, GetWriterGroupConfigurationAndCompareValues);
+
+    TCase *tc_add_pubsub_datasetwriter = tcase_create("PubSub DataSetWriter items handling");
+    tcase_add_checked_fixture(tc_add_pubsub_datasetwriter, setup, teardown);
+    tcase_add_test(tc_add_pubsub_datasetwriter, AddDataSetWriterWithValidConfiguration);
+    tcase_add_test(tc_add_pubsub_datasetwriter, AddRemoveAddDataSetWriterWithValidConfiguration);
+    tcase_add_test(tc_add_pubsub_datasetwriter, AddDataSetWriterWithNullConfig);
+    tcase_add_test(tc_add_pubsub_datasetwriter, AddDataSetWriterWithInvalidPDSId);
+    tcase_add_test(tc_add_pubsub_datasetwriter, GetDataSetWriterConfigurationAndCompareValues);
+
+    TCase *tc_add_pubsub_datasetfields = tcase_create("PubSub DataSetField items handling");
+    tcase_add_checked_fixture(tc_add_pubsub_datasetfields, setup, teardown);
+    tcase_add_test(tc_add_pubsub_datasetfields, AddDataSetFieldWithValidConfiguration);
+    tcase_add_test(tc_add_pubsub_datasetfields, AddRemoveAddDataSetFieldWithValidConfiguration);
+    tcase_add_test(tc_add_pubsub_datasetfields, AddDataSetFieldWithNullConfig);
+    tcase_add_test(tc_add_pubsub_datasetfields, AddDataSetFieldWithInvalidPDSId);
+    tcase_add_test(tc_add_pubsub_datasetfields, GetDataSetFieldConfigurationAndCompareValues);
+
+    TCase *tc_pubsub_publish = tcase_create("PubSub publish DataSetFields");
+    tcase_add_checked_fixture(tc_pubsub_publish, setup, teardown);
+    tcase_add_test(tc_pubsub_publish, SinglePublishDataSetField);
+    tcase_add_test(tc_pubsub_publish, PublishDataSetFieldAsDeltaFrame);
+
+    Suite *s = suite_create("PubSub WriterGroups/Writer/Fields handling and publishing");
+    suite_add_tcase(s, tc_add_pubsub_writergroup);
+    suite_add_tcase(s, tc_add_pubsub_datasetwriter);
+    suite_add_tcase(s, tc_add_pubsub_datasetfields);
+    suite_add_tcase(s, tc_pubsub_publish);
+
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr,CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}

+ 846 - 0
tests/server/check_monitoreditem_filter.c

@@ -0,0 +1,846 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ *    Copyright 2018 (c) basysKom GmbH <opensource@basyskom.com> (Author: Peter Rustler)
+ */
+
+#include "ua_types.h"
+#include "ua_server.h"
+#include "ua_client.h"
+#include "client/ua_client_internal.h"
+#include "ua_client_highlevel.h"
+#include "ua_config_default.h"
+#include "ua_network_tcp.h"
+
+#include "check.h"
+#include "testing_clock.h"
+#include "testing_networklayers.h"
+#include "thread_wrapper.h"
+
+UA_Server *server;
+UA_ServerConfig *config;
+UA_Boolean running;
+THREAD_HANDLE server_thread;
+
+UA_Client *client;
+UA_UInt32 subId;
+UA_NodeId parentNodeId;
+UA_NodeId parentReferenceNodeId;
+UA_NodeId outNodeId;
+
+UA_Boolean notificationReceived = false;
+UA_UInt32 countNotificationReceived = 0;
+UA_Double publishingInterval = 500.0;
+UA_DataValue lastValue;
+
+THREAD_CALLBACK(serverloop) {
+    while(running)
+        UA_Server_run_iterate(server, true);
+    return 0;
+}
+
+static void setup(void) {
+    UA_DataValue_init(&lastValue);
+    running = true;
+    config = UA_ServerConfig_new_default();
+    server = UA_Server_new(config);
+    UA_Server_run_startup(server);
+    THREAD_CREATE(server_thread, serverloop);
+    /* Define the attribute of the double variable node */
+    UA_VariableAttributes attr = UA_VariableAttributes_default;
+    UA_Double myDouble = 40.0;
+    UA_Variant_setScalar(&attr.value, &myDouble, &UA_TYPES[UA_TYPES_DOUBLE]);
+    attr.description = UA_LOCALIZEDTEXT("en-US","the answer");
+    attr.displayName = UA_LOCALIZEDTEXT("en-US","the answer");
+    attr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
+    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
+
+    /* Add the variable node to the information model */
+    UA_NodeId doubleNodeId = UA_NODEID_STRING(1, "the.answer");
+    UA_QualifiedName doubleName = UA_QUALIFIEDNAME(1, "the answer");
+    parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
+    parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
+    UA_NodeId_init(&outNodeId);
+    ck_assert_uint_eq(UA_Server_addVariableNode(server,
+                                                doubleNodeId, parentNodeId,
+                                                parentReferenceNodeId,
+                                                doubleName,
+                                                UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
+                                                attr,
+                                                NULL,
+                                                &outNodeId)
+                      , UA_STATUSCODE_GOOD);
+
+    client = UA_Client_new(UA_ClientConfig_default);
+    UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_Client_recv = client->connection.recv;
+    client->connection.recv = UA_Client_recvTesting;
+
+    UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
+    request.requestedMaxKeepAliveCount = 100;
+    UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request,
+                                                                            NULL, NULL, NULL);
+    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    subId = response.subscriptionId;
+    notificationReceived = false;
+    countNotificationReceived = 0;
+}
+
+static void teardown(void) {
+    ck_assert_uint_eq(UA_Client_Subscriptions_deleteSingle(client, subId)
+                      , UA_STATUSCODE_GOOD);
+
+    /* cleanup */
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+    UA_NodeId_deleteMembers(&parentNodeId);
+    UA_NodeId_deleteMembers(&parentReferenceNodeId);
+    UA_NodeId_deleteMembers(&outNodeId);
+    running = false;
+    THREAD_JOIN(server_thread);
+    UA_Server_run_shutdown(server);
+    UA_Server_delete(server);
+    UA_ServerConfig_delete(config);
+    UA_DataValue_deleteMembers(&lastValue);
+}
+
+#ifdef UA_ENABLE_SUBSCRIPTIONS
+
+
+static void
+dataChangeHandler(UA_Client *thisClient, UA_UInt32 thisSubId, void *subContext,
+                  UA_UInt32 monId, void *monContext, UA_DataValue *value) {
+    notificationReceived = true;
+    ++countNotificationReceived;
+    UA_DataValue_deleteMembers(&lastValue);
+    UA_DataValue_copy(value, &lastValue);
+}
+
+static UA_StatusCode
+setDouble(UA_Client *thisClient, UA_NodeId node, UA_Double value) {
+    UA_Variant variant;
+    UA_Variant_setScalar(&variant, &value, &UA_TYPES[UA_TYPES_DOUBLE]);
+    return UA_Client_writeValueAttribute(thisClient, node, &variant);
+}
+
+static UA_StatusCode waitForNotification(UA_UInt32 notifications, UA_UInt32 maxTries) {
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+    for (UA_UInt32 i = 0; i < maxTries; ++i) {
+        UA_fakeSleep((UA_UInt32)publishingInterval + 100);
+        retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 100));
+        if (retval != UA_STATUSCODE_GOOD)
+            return retval;
+        if (countNotificationReceived == notifications)
+            return retval;
+    }
+    return retval;
+}
+static UA_Boolean fuzzyLastValueIsEqualTo(UA_Double value) {
+    double offset = 0.001;
+    if(lastValue.hasValue
+            && lastValue.value.type == &UA_TYPES[UA_TYPES_DOUBLE]) {
+        double lastDouble = *((UA_Double*)(lastValue.value.data));
+        if (lastDouble > value - offset && lastDouble < value + offset) {
+            return true;
+        }
+    }
+    return false;
+}
+
+START_TEST(Server_MonitoredItemsAbsoluteFilterSetLater) {
+    UA_DataValue_init(&lastValue);
+    /* define a monitored item with no filter */
+    UA_MonitoredItemCreateRequest item = UA_MonitoredItemCreateRequest_default(outNodeId);;
+    UA_UInt32 newMonitoredItemIds[1];
+    UA_Client_DataChangeNotificationCallback callbacks[1];
+    callbacks[0] = dataChangeHandler;
+    UA_Client_DeleteMonitoredItemCallback deleteCallbacks[1] = {NULL};
+    void *contexts[1];
+    contexts[0] = NULL;
+
+    UA_CreateMonitoredItemsRequest createRequest;
+    UA_CreateMonitoredItemsRequest_init(&createRequest);
+    createRequest.subscriptionId = subId;
+    createRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
+    createRequest.itemsToCreate = &item;
+    createRequest.itemsToCreateSize = 1;
+    UA_CreateMonitoredItemsResponse createResponse =
+       UA_Client_MonitoredItems_createDataChanges(client, createRequest, contexts,
+                                                   callbacks, deleteCallbacks);
+
+    ck_assert_uint_eq(createResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(createResponse.resultsSize, 1);
+    ck_assert_uint_eq(createResponse.results[0].statusCode, UA_STATUSCODE_GOOD);
+    newMonitoredItemIds[0] = createResponse.results[0].monitoredItemId;
+    UA_CreateMonitoredItemsResponse_deleteMembers(&createResponse);
+
+    // Do we get initial value ?
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 1);
+
+    ck_assert(fuzzyLastValueIsEqualTo(40.0));
+
+    // This should trigger because no filter
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 41.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 42.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(2, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 2);
+
+    ck_assert(fuzzyLastValueIsEqualTo(42.0));
+
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 43.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 44.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(2, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 2);
+
+    ck_assert(fuzzyLastValueIsEqualTo(44.0));
+
+    // set back to 40.0
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 40.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 1);
+
+    ck_assert(fuzzyLastValueIsEqualTo(40.0));
+
+    /* modify the monitored item with an absolute filter with deadbandvalue = 2.0 */
+    UA_MonitoredItemModifyRequest itemModify;
+    UA_MonitoredItemModifyRequest_init(&itemModify);
+
+    itemModify.monitoredItemId = newMonitoredItemIds[0];
+    itemModify.requestedParameters.samplingInterval = 250;
+    itemModify.requestedParameters.discardOldest = true;
+    itemModify.requestedParameters.queueSize = 1;
+    itemModify.requestedParameters.clientHandle = newMonitoredItemIds[0];
+    UA_DataChangeFilter filter;
+    UA_DataChangeFilter_init(&filter);
+    filter.trigger = UA_DATACHANGETRIGGER_STATUSVALUE;
+    filter.deadbandType = UA_DEADBANDTYPE_ABSOLUTE;
+    filter.deadbandValue = 2.0;
+    itemModify.requestedParameters.filter.encoding = UA_EXTENSIONOBJECT_DECODED;
+    itemModify.requestedParameters.filter.content.decoded.type = &UA_TYPES[UA_TYPES_DATACHANGEFILTER];
+    itemModify.requestedParameters.filter.content.decoded.data = &filter;
+
+    UA_ModifyMonitoredItemsRequest modifyRequest;
+    UA_ModifyMonitoredItemsRequest_init(&modifyRequest);
+    modifyRequest.subscriptionId = subId;
+    modifyRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
+    modifyRequest.itemsToModify = &itemModify;
+    modifyRequest.itemsToModifySize = 1;
+
+    UA_ModifyMonitoredItemsResponse modifyResponse =
+       UA_Client_MonitoredItems_modify(client, modifyRequest);
+
+    ck_assert_uint_eq(modifyResponse.resultsSize, 1);
+    ck_assert_uint_eq(modifyResponse.results[0].statusCode, UA_STATUSCODE_GOOD);
+
+    UA_ModifyMonitoredItemsResponse_deleteMembers(&modifyResponse);
+
+    // This should not trigger because now we filter
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 41.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(0, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 42.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(0, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, false);
+    ck_assert_uint_eq(countNotificationReceived, 0);
+
+    ck_assert(fuzzyLastValueIsEqualTo(40.0));
+
+    // This should trigger once at 43.0.
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 43.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 44.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 1);
+
+    ck_assert(fuzzyLastValueIsEqualTo(43.0));
+
+    // remove monitored item
+    UA_DeleteMonitoredItemsRequest deleteRequest;
+    UA_DeleteMonitoredItemsRequest_init(&deleteRequest);
+    deleteRequest.subscriptionId = subId;
+    deleteRequest.monitoredItemIds = newMonitoredItemIds;
+    deleteRequest.monitoredItemIdsSize = 1;
+
+    UA_DeleteMonitoredItemsResponse deleteResponse =
+        UA_Client_MonitoredItems_delete(client, deleteRequest);
+
+    ck_assert_uint_eq(deleteResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(deleteResponse.resultsSize, 1);
+    ck_assert_uint_eq(deleteResponse.results[0], UA_STATUSCODE_GOOD);
+
+    UA_DeleteMonitoredItemsResponse_deleteMembers(&deleteResponse);
+
+}
+END_TEST
+
+START_TEST(Server_MonitoredItemsAbsoluteFilterSetOnCreateRemoveLater) {
+    UA_DataValue_init(&lastValue);
+    /* define a monitored item with absolute filter */
+    UA_MonitoredItemCreateRequest item = UA_MonitoredItemCreateRequest_default(outNodeId);;
+    UA_DataChangeFilter filter;
+    UA_DataChangeFilter_init(&filter);
+    filter.trigger = UA_DATACHANGETRIGGER_STATUSVALUE;
+    filter.deadbandType = UA_DEADBANDTYPE_ABSOLUTE;
+    filter.deadbandValue = 2.0;
+    item.requestedParameters.filter.encoding = UA_EXTENSIONOBJECT_DECODED;
+    item.requestedParameters.filter.content.decoded.type = &UA_TYPES[UA_TYPES_DATACHANGEFILTER];
+    item.requestedParameters.filter.content.decoded.data = &filter;
+    UA_UInt32 newMonitoredItemIds[1];
+    UA_Client_DataChangeNotificationCallback callbacks[1];
+    callbacks[0] = dataChangeHandler;
+    UA_Client_DeleteMonitoredItemCallback deleteCallbacks[1] = {NULL};
+    void *contexts[1];
+    contexts[0] = NULL;
+
+    UA_CreateMonitoredItemsRequest createRequest;
+    UA_CreateMonitoredItemsRequest_init(&createRequest);
+    createRequest.subscriptionId = subId;
+    createRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
+    createRequest.itemsToCreate = &item;
+    createRequest.itemsToCreateSize = 1;
+    UA_CreateMonitoredItemsResponse createResponse =
+       UA_Client_MonitoredItems_createDataChanges(client, createRequest, contexts,
+                                                   callbacks, deleteCallbacks);
+
+    ck_assert_uint_eq(createResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(createResponse.resultsSize, 1);
+    ck_assert_uint_eq(createResponse.results[0].statusCode, UA_STATUSCODE_GOOD);
+    newMonitoredItemIds[0] = createResponse.results[0].monitoredItemId;
+    UA_CreateMonitoredItemsResponse_deleteMembers(&createResponse);
+
+    // Do we get initial value ?
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 1);
+
+    ck_assert(fuzzyLastValueIsEqualTo(40.0));
+
+    // This should not trigger because of filter
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 41.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(0, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 42.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(0, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, false);
+    ck_assert_uint_eq(countNotificationReceived, 0);
+
+    ck_assert(fuzzyLastValueIsEqualTo(40.0));
+
+    // This should trigger once at 43.0.
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 43.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 44.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 1);
+
+    ck_assert(fuzzyLastValueIsEqualTo(43.0));
+
+    // set back to 40.0
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 40.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 1);
+
+    ck_assert(fuzzyLastValueIsEqualTo(40.0));
+
+    /* modify the monitored item with no filter */
+    UA_MonitoredItemModifyRequest itemModify;
+    UA_MonitoredItemModifyRequest_init(&itemModify);
+
+    itemModify.monitoredItemId = newMonitoredItemIds[0];
+    itemModify.requestedParameters.samplingInterval = 250;
+    itemModify.requestedParameters.discardOldest = true;
+    itemModify.requestedParameters.queueSize = 1;
+    itemModify.requestedParameters.clientHandle = newMonitoredItemIds[0];
+    UA_DataChangeFilter unsetfilter;
+    UA_DataChangeFilter_init(&unsetfilter);
+    unsetfilter.trigger = UA_DATACHANGETRIGGER_STATUSVALUE;
+    unsetfilter.deadbandType = UA_DEADBANDTYPE_NONE;
+    unsetfilter.deadbandValue = 0.0;
+    itemModify.requestedParameters.filter.encoding = UA_EXTENSIONOBJECT_DECODED;
+    itemModify.requestedParameters.filter.content.decoded.type = &UA_TYPES[UA_TYPES_DATACHANGEFILTER];
+    itemModify.requestedParameters.filter.content.decoded.data = &unsetfilter;
+
+    UA_ModifyMonitoredItemsRequest modifyRequest;
+    UA_ModifyMonitoredItemsRequest_init(&modifyRequest);
+    modifyRequest.subscriptionId = subId;
+    modifyRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
+    modifyRequest.itemsToModify = &itemModify;
+    modifyRequest.itemsToModifySize = 1;
+
+    UA_ModifyMonitoredItemsResponse modifyResponse =
+       UA_Client_MonitoredItems_modify(client, modifyRequest);
+
+    ck_assert_uint_eq(modifyResponse.resultsSize, 1);
+    ck_assert_uint_eq(modifyResponse.results[0].statusCode, UA_STATUSCODE_GOOD);
+
+    UA_ModifyMonitoredItemsResponse_deleteMembers(&modifyResponse);
+
+    // This should trigger because now we do not filter
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 41.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 42.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(2, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 2);
+
+    ck_assert(fuzzyLastValueIsEqualTo(42.0));
+
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 43.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 44.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(2, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 2);
+
+    ck_assert(fuzzyLastValueIsEqualTo(44.0));
+
+    // remove monitored item
+    UA_DeleteMonitoredItemsRequest deleteRequest;
+    UA_DeleteMonitoredItemsRequest_init(&deleteRequest);
+    deleteRequest.subscriptionId = subId;
+    deleteRequest.monitoredItemIds = newMonitoredItemIds;
+    deleteRequest.monitoredItemIdsSize = 1;
+
+    UA_DeleteMonitoredItemsResponse deleteResponse =
+        UA_Client_MonitoredItems_delete(client, deleteRequest);
+
+    ck_assert_uint_eq(deleteResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(deleteResponse.resultsSize, 1);
+    ck_assert_uint_eq(deleteResponse.results[0], UA_STATUSCODE_GOOD);
+
+    UA_DeleteMonitoredItemsResponse_deleteMembers(&deleteResponse);
+
+}
+END_TEST
+
+START_TEST(Server_MonitoredItemsPercentFilterSetLater) {
+    UA_DataValue_init(&lastValue);
+    /* define a monitored item with no filter */
+    UA_MonitoredItemCreateRequest item = UA_MonitoredItemCreateRequest_default(outNodeId);;
+    UA_UInt32 newMonitoredItemIds[1];
+    UA_Client_DataChangeNotificationCallback callbacks[1];
+    callbacks[0] = dataChangeHandler;
+    UA_Client_DeleteMonitoredItemCallback deleteCallbacks[1] = {NULL};
+    void *contexts[1];
+    contexts[0] = NULL;
+
+    UA_CreateMonitoredItemsRequest createRequest;
+    UA_CreateMonitoredItemsRequest_init(&createRequest);
+    createRequest.subscriptionId = subId;
+    createRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
+    createRequest.itemsToCreate = &item;
+    createRequest.itemsToCreateSize = 1;
+    UA_CreateMonitoredItemsResponse createResponse =
+       UA_Client_MonitoredItems_createDataChanges(client, createRequest, contexts,
+                                                   callbacks, deleteCallbacks);
+
+    ck_assert_uint_eq(createResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(createResponse.resultsSize, 1);
+    ck_assert_uint_eq(createResponse.results[0].statusCode, UA_STATUSCODE_GOOD);
+    newMonitoredItemIds[0] = createResponse.results[0].monitoredItemId;
+    UA_CreateMonitoredItemsResponse_deleteMembers(&createResponse);
+
+    // Do we get initial value ?
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 1);
+
+    ck_assert(fuzzyLastValueIsEqualTo(40.0));
+
+    // This should trigger because no filter
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 41.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 42.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(2, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 2);
+
+    ck_assert(fuzzyLastValueIsEqualTo(42.0));
+
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 43.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 44.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(2, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 2);
+
+    ck_assert(fuzzyLastValueIsEqualTo(44.0));
+
+    // set back to 40.0
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 40.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 1);
+
+    ck_assert(fuzzyLastValueIsEqualTo(40.0));
+
+    /* modify the monitored item with an percent filter with deadbandvalue = 2.0 */
+    UA_MonitoredItemModifyRequest itemModify;
+    UA_MonitoredItemModifyRequest_init(&itemModify);
+
+    itemModify.monitoredItemId = newMonitoredItemIds[0];
+    itemModify.requestedParameters.samplingInterval = 250;
+    itemModify.requestedParameters.discardOldest = true;
+    itemModify.requestedParameters.queueSize = 1;
+    itemModify.requestedParameters.clientHandle = newMonitoredItemIds[0];
+    UA_DataChangeFilter filter;
+    UA_DataChangeFilter_init(&filter);
+    filter.trigger = UA_DATACHANGETRIGGER_STATUSVALUE;
+    filter.deadbandType = UA_DEADBANDTYPE_PERCENT;
+    filter.deadbandValue = 2.0;
+    itemModify.requestedParameters.filter.encoding = UA_EXTENSIONOBJECT_DECODED;
+    itemModify.requestedParameters.filter.content.decoded.type = &UA_TYPES[UA_TYPES_DATACHANGEFILTER];
+    itemModify.requestedParameters.filter.content.decoded.data = &filter;
+
+    UA_ModifyMonitoredItemsRequest modifyRequest;
+    UA_ModifyMonitoredItemsRequest_init(&modifyRequest);
+    modifyRequest.subscriptionId = subId;
+    modifyRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
+    modifyRequest.itemsToModify = &itemModify;
+    modifyRequest.itemsToModifySize = 1;
+
+    UA_ModifyMonitoredItemsResponse modifyResponse =
+       UA_Client_MonitoredItems_modify(client, modifyRequest);
+
+    ck_assert_uint_eq(modifyResponse.resultsSize, 1);
+    ck_assert_uint_eq(modifyResponse.results[0].statusCode, UA_STATUSCODE_BADMONITOREDITEMFILTERUNSUPPORTED);
+
+    UA_ModifyMonitoredItemsResponse_deleteMembers(&modifyResponse);
+
+    // This should trigger because setting filter failed
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 41.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 42.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(2, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 2);
+
+    ck_assert(fuzzyLastValueIsEqualTo(42.0));
+
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 43.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 44.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(2, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 2);
+
+    ck_assert(fuzzyLastValueIsEqualTo(44.0));
+
+    // remove monitored item
+    UA_DeleteMonitoredItemsRequest deleteRequest;
+    UA_DeleteMonitoredItemsRequest_init(&deleteRequest);
+    deleteRequest.subscriptionId = subId;
+    deleteRequest.monitoredItemIds = newMonitoredItemIds;
+    deleteRequest.monitoredItemIdsSize = 1;
+
+    UA_DeleteMonitoredItemsResponse deleteResponse =
+        UA_Client_MonitoredItems_delete(client, deleteRequest);
+
+    ck_assert_uint_eq(deleteResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(deleteResponse.resultsSize, 1);
+    ck_assert_uint_eq(deleteResponse.results[0], UA_STATUSCODE_GOOD);
+
+    UA_DeleteMonitoredItemsResponse_deleteMembers(&deleteResponse);
+
+}
+END_TEST
+
+START_TEST(Server_MonitoredItemsNoFilter) {
+    UA_DataValue_init(&lastValue);
+    /* define a monitored item with an absolute filter with deadbandvalue = 2.0 */
+    UA_MonitoredItemCreateRequest item = UA_MonitoredItemCreateRequest_default(outNodeId);;
+    UA_UInt32 newMonitoredItemIds[1];
+    UA_Client_DataChangeNotificationCallback callbacks[1];
+    callbacks[0] = dataChangeHandler;
+    UA_Client_DeleteMonitoredItemCallback deleteCallbacks[1] = {NULL};
+    void *contexts[1];
+    contexts[0] = NULL;
+
+    UA_CreateMonitoredItemsRequest createRequest;
+    UA_CreateMonitoredItemsRequest_init(&createRequest);
+    createRequest.subscriptionId = subId;
+    createRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
+    createRequest.itemsToCreate = &item;
+    createRequest.itemsToCreateSize = 1;
+    UA_CreateMonitoredItemsResponse createResponse =
+       UA_Client_MonitoredItems_createDataChanges(client, createRequest, contexts,
+                                                   callbacks, deleteCallbacks);
+
+    ck_assert_uint_eq(createResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(createResponse.resultsSize, 1);
+    ck_assert_uint_eq(createResponse.results[0].statusCode, UA_STATUSCODE_GOOD);
+    newMonitoredItemIds[0] = createResponse.results[0].monitoredItemId;
+    UA_CreateMonitoredItemsResponse_deleteMembers(&createResponse);
+
+    // Do we get initial value ?
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 1);
+
+    ck_assert(fuzzyLastValueIsEqualTo(40.0));
+
+    // This should trigger because no filter
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 41.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 42.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(2, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived,  2);
+
+    ck_assert(fuzzyLastValueIsEqualTo(42.0));
+
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 43.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 44.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(2, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 2);
+
+    ck_assert(fuzzyLastValueIsEqualTo(44.0));
+
+    // remove monitored item
+    UA_DeleteMonitoredItemsRequest deleteRequest;
+    UA_DeleteMonitoredItemsRequest_init(&deleteRequest);
+    deleteRequest.subscriptionId = subId;
+    deleteRequest.monitoredItemIds = newMonitoredItemIds;
+    deleteRequest.monitoredItemIdsSize = 1;
+
+    UA_DeleteMonitoredItemsResponse deleteResponse =
+        UA_Client_MonitoredItems_delete(client, deleteRequest);
+
+    ck_assert_uint_eq(deleteResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(deleteResponse.resultsSize, 1);
+    ck_assert_uint_eq(deleteResponse.results[0], UA_STATUSCODE_GOOD);
+
+    UA_DeleteMonitoredItemsResponse_deleteMembers(&deleteResponse);
+}
+END_TEST
+
+START_TEST(Server_MonitoredItemsAbsoluteFilterSetOnCreate) {
+    UA_DataValue_init(&lastValue);
+    /* define a monitored item with an absolute filter with deadbandvalue = 2.0 */
+    UA_MonitoredItemCreateRequest item = UA_MonitoredItemCreateRequest_default(outNodeId);;
+    UA_DataChangeFilter filter;
+    UA_DataChangeFilter_init(&filter);
+    filter.trigger = UA_DATACHANGETRIGGER_STATUSVALUE;
+    filter.deadbandType = UA_DEADBANDTYPE_ABSOLUTE;
+    filter.deadbandValue = 2.0;
+    item.requestedParameters.filter.encoding = UA_EXTENSIONOBJECT_DECODED;
+    item.requestedParameters.filter.content.decoded.type = &UA_TYPES[UA_TYPES_DATACHANGEFILTER];
+    item.requestedParameters.filter.content.decoded.data = &filter;
+    UA_UInt32 newMonitoredItemIds[1];
+    UA_Client_DataChangeNotificationCallback callbacks[1];
+    callbacks[0] = dataChangeHandler;
+    UA_Client_DeleteMonitoredItemCallback deleteCallbacks[1] = {NULL};
+    void *contexts[1];
+    contexts[0] = NULL;
+
+    UA_CreateMonitoredItemsRequest createRequest;
+    UA_CreateMonitoredItemsRequest_init(&createRequest);
+    createRequest.subscriptionId = subId;
+    createRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
+    createRequest.itemsToCreate = &item;
+    createRequest.itemsToCreateSize = 1;
+    UA_CreateMonitoredItemsResponse createResponse =
+       UA_Client_MonitoredItems_createDataChanges(client, createRequest, contexts,
+                                                   callbacks, deleteCallbacks);
+
+    ck_assert_uint_eq(createResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(createResponse.resultsSize, 1);
+    ck_assert_uint_eq(createResponse.results[0].statusCode, UA_STATUSCODE_GOOD);
+    newMonitoredItemIds[0] = createResponse.results[0].monitoredItemId;
+    UA_CreateMonitoredItemsResponse_deleteMembers(&createResponse);
+
+    // Do we get initial value ?
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 1);
+
+    ck_assert(fuzzyLastValueIsEqualTo(40.0));
+
+    // This should not trigger because of filter
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 41.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(0, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 42.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(0, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, false);
+    ck_assert_uint_eq(countNotificationReceived, 0);
+
+    ck_assert(fuzzyLastValueIsEqualTo(40.0));
+
+    // This should trigger once at 43.0.
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(setDouble(client, outNodeId, 43.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(setDouble(client, outNodeId, 44.0), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(waitForNotification(1, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 1);
+
+    ck_assert(fuzzyLastValueIsEqualTo(43.0));
+
+    // remove monitored item
+    UA_DeleteMonitoredItemsRequest deleteRequest;
+    UA_DeleteMonitoredItemsRequest_init(&deleteRequest);
+    deleteRequest.subscriptionId = subId;
+    deleteRequest.monitoredItemIds = newMonitoredItemIds;
+    deleteRequest.monitoredItemIdsSize = 1;
+
+    UA_DeleteMonitoredItemsResponse deleteResponse =
+        UA_Client_MonitoredItems_delete(client, deleteRequest);
+
+    ck_assert_uint_eq(deleteResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(deleteResponse.resultsSize, 1);
+    ck_assert_uint_eq(deleteResponse.results[0], UA_STATUSCODE_GOOD);
+
+    UA_DeleteMonitoredItemsResponse_deleteMembers(&deleteResponse);
+
+}
+END_TEST
+
+START_TEST(Server_MonitoredItemsPercentFilterSetOnCreate) {
+    UA_DataValue_init(&lastValue);
+    /* define a monitored item with an percent filter with deadbandvalue = 2.0 */
+    UA_MonitoredItemCreateRequest item = UA_MonitoredItemCreateRequest_default(outNodeId);;
+    UA_DataChangeFilter filter;
+    UA_DataChangeFilter_init(&filter);
+    filter.trigger = UA_DATACHANGETRIGGER_STATUSVALUE;
+    filter.deadbandType = UA_DEADBANDTYPE_PERCENT;
+    filter.deadbandValue = 2.0;
+    item.requestedParameters.filter.encoding = UA_EXTENSIONOBJECT_DECODED;
+    item.requestedParameters.filter.content.decoded.type = &UA_TYPES[UA_TYPES_DATACHANGEFILTER];
+    item.requestedParameters.filter.content.decoded.data = &filter;
+    UA_UInt32 newMonitoredItemIds[1];
+    UA_Client_DataChangeNotificationCallback callbacks[1];
+    callbacks[0] = dataChangeHandler;
+    UA_Client_DeleteMonitoredItemCallback deleteCallbacks[1] = {NULL};
+    void *contexts[1];
+    contexts[0] = NULL;
+
+    UA_CreateMonitoredItemsRequest createRequest;
+    UA_CreateMonitoredItemsRequest_init(&createRequest);
+    createRequest.subscriptionId = subId;
+    createRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
+    createRequest.itemsToCreate = &item;
+    createRequest.itemsToCreateSize = 1;
+    UA_CreateMonitoredItemsResponse createResponse =
+       UA_Client_MonitoredItems_createDataChanges(client, createRequest, contexts,
+                                                   callbacks, deleteCallbacks);
+
+    ck_assert_uint_eq(createResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(createResponse.resultsSize, 1);
+    ck_assert_uint_eq(createResponse.results[0].statusCode, UA_STATUSCODE_BADMONITOREDITEMFILTERUNSUPPORTED);
+    newMonitoredItemIds[0] = createResponse.results[0].monitoredItemId;
+    UA_CreateMonitoredItemsResponse_deleteMembers(&createResponse);
+
+    // Do we get initial value ? (must fail)
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    ck_assert_uint_eq(waitForNotification(0, 10), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, false);
+    ck_assert_uint_eq(countNotificationReceived, 0);
+
+    // remove monitored item (must fail)
+    UA_DeleteMonitoredItemsRequest deleteRequest;
+    UA_DeleteMonitoredItemsRequest_init(&deleteRequest);
+    deleteRequest.subscriptionId = subId;
+    deleteRequest.monitoredItemIds = newMonitoredItemIds;
+    deleteRequest.monitoredItemIdsSize = 1;
+
+    UA_DeleteMonitoredItemsResponse deleteResponse =
+        UA_Client_MonitoredItems_delete(client, deleteRequest);
+
+    ck_assert_uint_eq(deleteResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(deleteResponse.resultsSize, 1);
+    ck_assert_uint_eq(deleteResponse.results[0], UA_STATUSCODE_BADMONITOREDITEMIDINVALID);
+
+    UA_DeleteMonitoredItemsResponse_deleteMembers(&deleteResponse);
+}
+END_TEST
+
+#endif /*UA_ENABLE_SUBSCRIPTIONS*/
+
+static Suite* testSuite_Client(void) {
+    Suite *s = suite_create("Server monitored item filter");
+    TCase *tc_server = tcase_create("Server monitored item filter Basic");
+    tcase_add_checked_fixture(tc_server, setup, teardown);
+#ifdef UA_ENABLE_SUBSCRIPTIONS
+    tcase_add_test(tc_server, Server_MonitoredItemsNoFilter);
+    tcase_add_test(tc_server, Server_MonitoredItemsAbsoluteFilterSetOnCreate);
+    tcase_add_test(tc_server, Server_MonitoredItemsAbsoluteFilterSetLater);
+    tcase_add_test(tc_server, Server_MonitoredItemsAbsoluteFilterSetOnCreateRemoveLater);
+    tcase_add_test(tc_server, Server_MonitoredItemsPercentFilterSetOnCreate);
+    tcase_add_test(tc_server, Server_MonitoredItemsPercentFilterSetLater);
+#endif /* UA_ENABLE_SUBSCRIPTIONS */
+    suite_add_tcase(s, tc_server);
+
+    return s;
+}
+
+int main(void) {
+    Suite *s = testSuite_Client();
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr,CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}

+ 4 - 4
tests/server/check_services_subscriptions.c

@@ -334,21 +334,21 @@ START_TEST(Server_overflow) {
     ck_assert_uint_eq(notification->data.value.hasStatus, false);
 
     UA_ByteString_deleteMembers(&mon->lastSampledValue);
-    UA_MonitoredItem_SampleCallback(server, mon);
+    UA_MonitoredItem_sampleCallback(server, mon);
     ck_assert_uint_eq(mon->queueSize, 2); 
     ck_assert_uint_eq(mon->maxQueueSize, 3); 
     notification = TAILQ_LAST(&mon->queue, NotificationQueue);
     ck_assert_uint_eq(notification->data.value.hasStatus, false);
 
     UA_ByteString_deleteMembers(&mon->lastSampledValue);
-    UA_MonitoredItem_SampleCallback(server, mon);
+    UA_MonitoredItem_sampleCallback(server, mon);
     ck_assert_uint_eq(mon->queueSize, 3); 
     ck_assert_uint_eq(mon->maxQueueSize, 3); 
     notification = TAILQ_LAST(&mon->queue, NotificationQueue);
     ck_assert_uint_eq(notification->data.value.hasStatus, false);
 
     UA_ByteString_deleteMembers(&mon->lastSampledValue);
-    UA_MonitoredItem_SampleCallback(server, mon);
+    UA_MonitoredItem_sampleCallback(server, mon);
     ck_assert_uint_eq(mon->queueSize, 3); 
     ck_assert_uint_eq(mon->maxQueueSize, 3); 
     notification = TAILQ_FIRST(&mon->queue);
@@ -446,7 +446,7 @@ START_TEST(Server_overflow) {
     UA_MonitoredItemModifyRequest_deleteMembers(&itemToModify);
     UA_ModifyMonitoredItemsResponse_deleteMembers(&modifyMonitoredItemsResponse);
 
-    UA_MonitoredItem_SampleCallback(server, mon);
+    UA_MonitoredItem_sampleCallback(server, mon);
     ck_assert_uint_eq(mon->queueSize, 1); 
     ck_assert_uint_eq(mon->maxQueueSize, 1); 
     notification = TAILQ_FIRST(&mon->queue);

+ 19 - 21
tools/generate_datatypes.py

@@ -33,21 +33,20 @@ builtin_types = ["Boolean", "SByte", "Byte", "Int16", "UInt16", "Int32", "UInt32
                  "QualifiedName", "LocalizedText", "ExtensionObject", "DataValue",
                  "Variant", "DiagnosticInfo"]
 
-# If the type does not contain pointers, it can be copied with memcpy
-# (internally, not into the protocol message). This dict contains the sizes of
-# pointer-free types. Parsed types are added if they apply.
-builtin_pointerfree = ["Boolean", "SByte", "Byte", "Int16", "UInt16", "Int32", "UInt32",
-                       "Int64", "UInt64", "Float", "Double", "DateTime", "Guid", "StatusCode"]
-
 # Some types can be memcpy'd off the binary stream. That's especially important
 # for arrays. But we need to check if they contain padding and whether the
 # endianness is correct. This dict gives the C-statement that must be true for the
 # type to be overlayable. Parsed types are added if they apply.
-builtin_overlayable = {"Boolean": "true", "SByte": "true", "Byte": "true",
-                       "Int16": "UA_BINARY_OVERLAYABLE_INTEGER", "UInt16": "UA_BINARY_OVERLAYABLE_INTEGER",
-                       "Int32": "UA_BINARY_OVERLAYABLE_INTEGER", "UInt32": "UA_BINARY_OVERLAYABLE_INTEGER",
-                       "Int64": "UA_BINARY_OVERLAYABLE_INTEGER", "UInt64": "UA_BINARY_OVERLAYABLE_INTEGER",
-                       "Float": "UA_BINARY_OVERLAYABLE_FLOAT", "Double": "UA_BINARY_OVERLAYABLE_FLOAT",
+builtin_overlayable = {"Boolean": "true",
+                       "SByte": "true", "Byte": "true",
+                       "Int16": "UA_BINARY_OVERLAYABLE_INTEGER",
+                       "UInt16": "UA_BINARY_OVERLAYABLE_INTEGER",
+                       "Int32": "UA_BINARY_OVERLAYABLE_INTEGER",
+                       "UInt32": "UA_BINARY_OVERLAYABLE_INTEGER",
+                       "Int64": "UA_BINARY_OVERLAYABLE_INTEGER",
+                       "UInt64": "UA_BINARY_OVERLAYABLE_INTEGER",
+                       "Float": "UA_BINARY_OVERLAYABLE_FLOAT",
+                       "Double": "UA_BINARY_OVERLAYABLE_FLOAT",
                        "DateTime": "UA_BINARY_OVERLAYABLE_INTEGER",
                        "StatusCode": "UA_BINARY_OVERLAYABLE_INTEGER",
                        "Guid": "(UA_BINARY_OVERLAYABLE_INTEGER && " + \
@@ -114,7 +113,10 @@ class Type(object):
         size = len(self.members)
         for index, member in enumerate(self.members):
             i += 1
-            m = "\n{\n    UA_TYPENAME(\"%s\") /* .memberName */\n" % member.name
+            membername = member.name
+            if len(membername) > 0:
+                membername = member.name[0].upper() + member.name[1:]
+            m = "\n{\n    UA_TYPENAME(\"%s\") /* .memberName */\n" % membername
             m += "    %s_%s, /* .memberTypeIndex */\n" % (member.memberType.outname.upper(), member.memberType.name.upper())
             m += "    "
             if not before:
@@ -167,18 +169,13 @@ class BuiltinType(Type):
         self.outname = "ua_types"
         self.description = ""
         self.pointerfree = "false"
-        if self.name in builtin_pointerfree:
+        if self.name in builtin_overlayable.keys():
             self.pointerfree = "true"
         self.overlayable = "false"
         if name in builtin_overlayable:
             self.overlayable = builtin_overlayable[name]
         self.builtin = "true"
-        if self.name == "QualifiedName":
-            self.members = [StructMember("namespaceIndex", types["Int16"], False), StructMember("name", types["String"], False)]
-        elif self.name in ["String", "ByteString", "XmlElement"]:
-            self.members = [StructMember("", types["Byte"], True)]
-        else:
-            self.members = [StructMember("", self, False)]
+        self.members = [StructMember("", self, False)] # builtin types contain only one member: themselves (drops into the jumptable during processing)
 
 class EnumerationType(Type):
     def __init__(self, outname, xml, namespace):
@@ -434,14 +431,15 @@ for builtin in builtin_types:
 for f in args.type_bsd:
     parseTypeDefinitions(outname, f, args.namespace)
 
-
 typedescriptions = {}
 for f in args.type_csv:
     typedescriptions = merge_dicts(typedescriptions, parseTypeDescriptions(f, args.namespace))
 
+# Read the selected data types
 selected_types = []
 for f in args.selected_types:
-    selected_types = list(filter(len, [line.strip() for line in f]))
+    selected_types += list(filter(len, [line.strip() for line in f]))
+# Use all types if none are selected
 if len(selected_types) == 0:
     selected_types = types.keys()
 

+ 9 - 2
tools/nodeset_compiler/backend_open62541_nodes.py

@@ -90,8 +90,12 @@ def generateVariableNodeCode(node, nodeset, max_string_length, encode_binary_siz
         code.append("attr.arrayDimensionsSize = %d;" % node.valueRank)
         code.append("attr.arrayDimensions = (UA_UInt32 *)UA_Array_new({}, &UA_TYPES[UA_TYPES_UINT32]);".format(node.valueRank))
         codeCleanup.append("UA_Array_delete(attr.arrayDimensions, {}, &UA_TYPES[UA_TYPES_UINT32]);".format(node.valueRank))
-        for dim in range(0, node.valueRank):
-            code.append("attr.arrayDimensions[{}] = 0;".format(dim))
+        if len(node.arrayDimensions) == node.valueRank:
+            for idx, v in enumerate(node.arrayDimensions):
+                code.append("attr.arrayDimensions[{}] = {};".format(idx, int(unicode(v))))
+        else:
+            for dim in range(0, node.valueRank):
+                code.append("attr.arrayDimensions[{}] = 0;".format(dim))
 
     if node.dataType is not None:
         if isinstance(node.dataType, NodeId) and node.dataType.ns == 0 and node.dataType.i == 0:
@@ -110,6 +114,9 @@ def generateVariableNodeCode(node, nodeset, max_string_length, encode_binary_siz
                     [code1, codeCleanup1] = generateValueCode(node.value, nodeset.nodes[node.id], nodeset, max_string_length=max_string_length, encode_binary_size=encode_binary_size)
                     code += code1
                     codeCleanup += codeCleanup1
+                    if node.valueRank > 0 and len(node.arrayDimensions) == node.valueRank:
+                        code.append("attr.value.arrayDimensionsSize = attr.arrayDimensionsSize;")
+                        code.append("attr.value.arrayDimensions = attr.arrayDimensions;")
                 else:
                     code += generateValueCodeDummy(dataTypeNode, nodeset.nodes[node.id], nodeset)
     return [code, codeCleanup]

+ 7 - 5
tools/nodeset_compiler/nodes.py

@@ -254,6 +254,8 @@ class VariableNode(Node):
                 self.minimumSamplingInterval = float(av)
             elif at == "DataType":
                 self.dataType = RefOrAlias(av)
+            elif  at == "ArrayDimensions":
+                self.arrayDimensions = av.split(",")
 
         for x in xmlelement.childNodes:
             if x.nodeType != x.ELEMENT_NODE:
@@ -264,8 +266,11 @@ class VariableNode(Node):
                 self.dataType = RefOrAlias(av)
             elif x.localName == "ValueRank":
                 self.valueRank = int(unicode(x.firstChild.data))
-            elif x.localName == "ArrayDimensions":
-                self.arrayDimensions = int(unicode(x.firstChild.data))
+            elif x.localName == "ArrayDimensions" and len(self.arrayDimensions) == 0:
+                elements = x.getElementsByTagName("ListOfUInt32");
+                if len(elements):
+                    for idx, v in enumerate(elements[0].getElementsByTagName("UInt32")):
+                        self.arrayDimensions.append(v.firstChild.data)
             elif x.localName == "AccessLevel":
                 self.accessLevel = int(unicode(x.firstChild.data))
             elif x.localName == "UserAccessLevel":
@@ -292,9 +297,6 @@ class VariableNode(Node):
         # reflect the exaxt dimensions attached binary stream.
         if not isinstance(self.value, Value) or len(self.value.value) == 0:
             self.arrayDimensions = []
-        else:
-            # Parser only permits 1-d arrays, which means we do not have to check further dimensions
-            self.arrayDimensions = [len(self.value.value)]
         return True
 
 

+ 394 - 67
tools/schema/Opc.Ua.Types.bsd

@@ -218,6 +218,13 @@
     <opc:Field Name="Value" TypeName="ua:Variant" />
   </opc:StructuredType>
 
+  <opc:StructuredType Name="EndpointType" BaseType="ua:ExtensionObject">
+    <opc:Field Name="EndpointUrl" TypeName="opc:String" />
+    <opc:Field Name="SecurityMode" TypeName="tns:MessageSecurityMode" />
+    <opc:Field Name="SecurityPolicyUri" TypeName="opc:String" />
+    <opc:Field Name="TransportProfileUri" TypeName="opc:String" />
+  </opc:StructuredType>
+
   <opc:EnumeratedType Name="OpenFileMode" LengthInBits="32">
     <opc:EnumeratedValue Name="Read" Value="1" />
     <opc:EnumeratedValue Name="Write" Value="2" />
@@ -228,7 +235,7 @@
   <opc:EnumeratedType Name="IdentityCriteriaType" LengthInBits="32">
     <opc:EnumeratedValue Name="UserName" Value="1" />
     <opc:EnumeratedValue Name="Thumbprint" Value="2" />
-    <opc:EnumeratedValue Name="Scope" Value="3" />
+    <opc:EnumeratedValue Name="Role" Value="3" />
     <opc:EnumeratedValue Name="GroupId" Value="4" />
     <opc:EnumeratedValue Name="Anonymous" Value="5" />
     <opc:EnumeratedValue Name="AuthenticatedUser" Value="6" />
@@ -265,30 +272,81 @@
     <opc:Field Name="Value" TypeName="opc:ByteString" />
   </opc:StructuredType>
 
-  <opc:StructuredType Name="ConfigurationVersionDataType" BaseType="ua:ExtensionObject">
-    <opc:Field Name="MajorVersion" TypeName="opc:UInt32" />
-    <opc:Field Name="MinorVersion" TypeName="opc:UInt32" />
+  <opc:StructuredType Name="DataTypeSchemaHeader" BaseType="ua:ExtensionObject">
+    <opc:Field Name="NoOfNamespaces" TypeName="opc:Int32" />
+    <opc:Field Name="Namespaces" TypeName="opc:String" LengthField="NoOfNamespaces" />
+    <opc:Field Name="NoOfStructureDataTypes" TypeName="opc:Int32" />
+    <opc:Field Name="StructureDataTypes" TypeName="tns:StructureDescription" LengthField="NoOfStructureDataTypes" />
+    <opc:Field Name="NoOfEnumDataTypes" TypeName="opc:Int32" />
+    <opc:Field Name="EnumDataTypes" TypeName="tns:EnumDescription" LengthField="NoOfEnumDataTypes" />
+    <opc:Field Name="NoOfSimpleDataTypes" TypeName="opc:Int32" />
+    <opc:Field Name="SimpleDataTypes" TypeName="tns:SimpleTypeDescription" LengthField="NoOfSimpleDataTypes" />
   </opc:StructuredType>
 
-  <opc:StructuredType Name="DataSetMetaDataType" BaseType="ua:ExtensionObject">
-    <opc:Field Name="Name" TypeName="opc:String" />
-    <opc:Field Name="Description" TypeName="ua:LocalizedText" />
-    <opc:Field Name="NoOfFields" TypeName="opc:Int32" />
-    <opc:Field Name="Fields" TypeName="tns:FieldMetaData" LengthField="NoOfFields" />
-    <opc:Field Name="DataSetClassId" TypeName="opc:Guid" />
+  <opc:StructuredType Name="DataTypeDescription" BaseType="ua:ExtensionObject">
+    <opc:Field Name="DataTypeId" TypeName="ua:NodeId" />
+    <opc:Field Name="Name" TypeName="ua:QualifiedName" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="StructureDescription" BaseType="tns:DataTypeDescription">
+    <opc:Field Name="DataTypeId" TypeName="ua:NodeId" SourceType="tns:DataTypeDescription" />
+    <opc:Field Name="Name" TypeName="ua:QualifiedName" SourceType="tns:DataTypeDescription" />
+    <opc:Field Name="StructureDefinition" TypeName="tns:StructureDefinition" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="EnumDescription" BaseType="tns:DataTypeDescription">
+    <opc:Field Name="DataTypeId" TypeName="ua:NodeId" SourceType="tns:DataTypeDescription" />
+    <opc:Field Name="Name" TypeName="ua:QualifiedName" SourceType="tns:DataTypeDescription" />
+    <opc:Field Name="EnumDefinition" TypeName="tns:EnumDefinition" />
+    <opc:Field Name="BuiltInType" TypeName="opc:Byte" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="SimpleTypeDescription" BaseType="tns:DataTypeDescription">
+    <opc:Field Name="DataTypeId" TypeName="ua:NodeId" SourceType="tns:DataTypeDescription" />
+    <opc:Field Name="Name" TypeName="ua:QualifiedName" SourceType="tns:DataTypeDescription" />
+    <opc:Field Name="BaseDataType" TypeName="ua:NodeId" />
+    <opc:Field Name="BuiltInType" TypeName="opc:Byte" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="UABinaryFileDataType" BaseType="tns:DataTypeSchemaHeader">
     <opc:Field Name="NoOfNamespaces" TypeName="opc:Int32" />
     <opc:Field Name="Namespaces" TypeName="opc:String" LengthField="NoOfNamespaces" />
     <opc:Field Name="NoOfStructureDataTypes" TypeName="opc:Int32" />
     <opc:Field Name="StructureDataTypes" TypeName="tns:StructureDescription" LengthField="NoOfStructureDataTypes" />
     <opc:Field Name="NoOfEnumDataTypes" TypeName="opc:Int32" />
     <opc:Field Name="EnumDataTypes" TypeName="tns:EnumDescription" LengthField="NoOfEnumDataTypes" />
-    <opc:Field Name="ConfigurationVersion" TypeName="tns:ConfigurationVersionDataType" />
+    <opc:Field Name="NoOfSimpleDataTypes" TypeName="opc:Int32" />
+    <opc:Field Name="SimpleDataTypes" TypeName="tns:SimpleTypeDescription" LengthField="NoOfSimpleDataTypes" />
+    <opc:Field Name="SchemaLocation" TypeName="opc:String" />
+    <opc:Field Name="NoOfFileHeader" TypeName="opc:Int32" />
+    <opc:Field Name="FileHeader" TypeName="tns:KeyValuePair" LengthField="NoOfFileHeader" />
+    <opc:Field Name="Body" TypeName="ua:Variant" />
   </opc:StructuredType>
 
-  <opc:EnumeratedType Name="DataSetFieldFlags" LengthInBits="32">
-    <opc:EnumeratedValue Name="PromotedField" Value="1" />
+  <opc:EnumeratedType Name="PubSubState" LengthInBits="32">
+    <opc:EnumeratedValue Name="Disabled" Value="0" />
+    <opc:EnumeratedValue Name="Paused" Value="1" />
+    <opc:EnumeratedValue Name="Operational" Value="2" />
+    <opc:EnumeratedValue Name="Error" Value="3" />
   </opc:EnumeratedType>
 
+  <opc:StructuredType Name="DataSetMetaDataType" BaseType="tns:DataTypeSchemaHeader">
+    <opc:Field Name="NoOfNamespaces" TypeName="opc:Int32" />
+    <opc:Field Name="Namespaces" TypeName="opc:String" LengthField="NoOfNamespaces" />
+    <opc:Field Name="NoOfStructureDataTypes" TypeName="opc:Int32" />
+    <opc:Field Name="StructureDataTypes" TypeName="tns:StructureDescription" LengthField="NoOfStructureDataTypes" />
+    <opc:Field Name="NoOfEnumDataTypes" TypeName="opc:Int32" />
+    <opc:Field Name="EnumDataTypes" TypeName="tns:EnumDescription" LengthField="NoOfEnumDataTypes" />
+    <opc:Field Name="NoOfSimpleDataTypes" TypeName="opc:Int32" />
+    <opc:Field Name="SimpleDataTypes" TypeName="tns:SimpleTypeDescription" LengthField="NoOfSimpleDataTypes" />
+    <opc:Field Name="Name" TypeName="opc:String" />
+    <opc:Field Name="Description" TypeName="ua:LocalizedText" />
+    <opc:Field Name="NoOfFields" TypeName="opc:Int32" />
+    <opc:Field Name="Fields" TypeName="tns:FieldMetaData" LengthField="NoOfFields" />
+    <opc:Field Name="DataSetClassId" TypeName="opc:Guid" />
+    <opc:Field Name="ConfigurationVersion" TypeName="tns:ConfigurationVersionDataType" />
+  </opc:StructuredType>
+
   <opc:StructuredType Name="FieldMetaData" BaseType="ua:ExtensionObject">
     <opc:Field Name="Name" TypeName="opc:String" />
     <opc:Field Name="Description" TypeName="ua:LocalizedText" />
@@ -304,50 +362,27 @@
     <opc:Field Name="Properties" TypeName="tns:KeyValuePair" LengthField="NoOfProperties" />
   </opc:StructuredType>
 
-  <opc:StructuredType Name="DataTypeDescription" BaseType="ua:ExtensionObject">
-    <opc:Field Name="DataTypeId" TypeName="ua:NodeId" />
-    <opc:Field Name="Name" TypeName="ua:QualifiedName" />
-  </opc:StructuredType>
+  <opc:EnumeratedType Name="DataSetFieldFlags" LengthInBits="32">
+    <opc:EnumeratedValue Name="PromotedField" Value="1" />
+  </opc:EnumeratedType>
 
-  <opc:StructuredType Name="StructureDescription" BaseType="tns:DataTypeDescription">
-    <opc:Field Name="DataTypeId" TypeName="ua:NodeId" SourceType="tns:DataTypeDescription" />
-    <opc:Field Name="Name" TypeName="ua:QualifiedName" SourceType="tns:DataTypeDescription" />
-    <opc:Field Name="StructureDefinition" TypeName="tns:StructureDefinition" />
+  <opc:StructuredType Name="ConfigurationVersionDataType" BaseType="ua:ExtensionObject">
+    <opc:Field Name="MajorVersion" TypeName="opc:UInt32" />
+    <opc:Field Name="MinorVersion" TypeName="opc:UInt32" />
   </opc:StructuredType>
 
-  <opc:StructuredType Name="EnumDescription" BaseType="tns:DataTypeDescription">
-    <opc:Field Name="DataTypeId" TypeName="ua:NodeId" SourceType="tns:DataTypeDescription" />
-    <opc:Field Name="Name" TypeName="ua:QualifiedName" SourceType="tns:DataTypeDescription" />
-    <opc:Field Name="EnumDefinition" TypeName="tns:EnumDefinition" />
+  <opc:StructuredType Name="PublishedDataSetDataType" BaseType="ua:ExtensionObject">
+    <opc:Field Name="Name" TypeName="opc:String" />
+    <opc:Field Name="NoOfDataSetFolder" TypeName="opc:Int32" />
+    <opc:Field Name="DataSetFolder" TypeName="opc:String" LengthField="NoOfDataSetFolder" />
+    <opc:Field Name="DataSetMetaData" TypeName="tns:DataSetMetaDataType" />
+    <opc:Field Name="NoOfExtensionFields" TypeName="opc:Int32" />
+    <opc:Field Name="ExtensionFields" TypeName="tns:KeyValuePair" LengthField="NoOfExtensionFields" />
+    <opc:Field Name="DataSetSource" TypeName="ua:ExtensionObject" />
   </opc:StructuredType>
 
-  <opc:EnumeratedType Name="DataSetMessageContentMask" LengthInBits="32">
-    <opc:EnumeratedValue Name="FieldStatusCode" Value="1" />
-    <opc:EnumeratedValue Name="FieldSourceTimestamp" Value="2" />
-    <opc:EnumeratedValue Name="FieldServerTimestamp" Value="4" />
-    <opc:EnumeratedValue Name="FieldSourcePicoSeconds" Value="8" />
-    <opc:EnumeratedValue Name="FieldServerPicoSeconds" Value="16" />
-    <opc:EnumeratedValue Name="FieldRawDataEncoding" Value="32" />
-    <opc:EnumeratedValue Name="HeaderTimestamp" Value="65536" />
-    <opc:EnumeratedValue Name="HeaderPicoSeconds" Value="131072" />
-    <opc:EnumeratedValue Name="HeaderStatusCode" Value="262144" />
-    <opc:EnumeratedValue Name="HeaderMajorVersion" Value="524288" />
-    <opc:EnumeratedValue Name="HeaderMinorVersion" Value="1048576" />
-  </opc:EnumeratedType>
-
-  <opc:EnumeratedType Name="NetworkMessageContentMask" LengthInBits="32">
-    <opc:EnumeratedValue Name="PublisherId" Value="1" />
-    <opc:EnumeratedValue Name="GroupHeader" Value="2" />
-    <opc:EnumeratedValue Name="GroupId" Value="4" />
-    <opc:EnumeratedValue Name="GroupVersion" Value="8" />
-    <opc:EnumeratedValue Name="NetworkMessageNumber" Value="16" />
-    <opc:EnumeratedValue Name="SequenceNumber" Value="32" />
-    <opc:EnumeratedValue Name="PayloadHeader" Value="64" />
-    <opc:EnumeratedValue Name="Timestamp" Value="128" />
-    <opc:EnumeratedValue Name="Picoseconds" Value="256" />
-    <opc:EnumeratedValue Name="DataSetClassId" Value="512" />
-    <opc:EnumeratedValue Name="PromotedFields" Value="1024" />
-  </opc:EnumeratedType>
+  <opc:StructuredType Name="PublishedDataSetSourceDataType" BaseType="ua:ExtensionObject">
+  </opc:StructuredType>
 
   <opc:StructuredType Name="PublishedVariableDataType" BaseType="ua:ExtensionObject">
     <opc:Field Name="PublishedVariable" TypeName="ua:NodeId" />
@@ -361,6 +396,169 @@
     <opc:Field Name="MetaDataProperties" TypeName="ua:QualifiedName" LengthField="NoOfMetaDataProperties" />
   </opc:StructuredType>
 
+  <opc:StructuredType Name="PublishedDataItemsDataType" BaseType="tns:PublishedDataSetSourceDataType">
+    <opc:Field Name="NoOfPublishedData" TypeName="opc:Int32" />
+    <opc:Field Name="PublishedData" TypeName="tns:PublishedVariableDataType" LengthField="NoOfPublishedData" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="PublishedEventsDataType" BaseType="tns:PublishedDataSetSourceDataType">
+    <opc:Field Name="EventNotifier" TypeName="ua:NodeId" />
+    <opc:Field Name="NoOfSelectedFields" TypeName="opc:Int32" />
+    <opc:Field Name="SelectedFields" TypeName="tns:SimpleAttributeOperand" LengthField="NoOfSelectedFields" />
+    <opc:Field Name="Filter" TypeName="tns:ContentFilter" />
+  </opc:StructuredType>
+
+  <opc:EnumeratedType Name="DataSetFieldContentMask" LengthInBits="32">
+    <opc:EnumeratedValue Name="StatusCode" Value="1" />
+    <opc:EnumeratedValue Name="SourceTimestamp" Value="2" />
+    <opc:EnumeratedValue Name="ServerTimestamp" Value="4" />
+    <opc:EnumeratedValue Name="SourcePicoSeconds" Value="8" />
+    <opc:EnumeratedValue Name="ServerPicoSeconds" Value="16" />
+    <opc:EnumeratedValue Name="RawDataEncoding" Value="32" />
+  </opc:EnumeratedType>
+
+  <opc:StructuredType Name="DataSetWriterDataType" BaseType="ua:ExtensionObject">
+    <opc:Field Name="Name" TypeName="opc:String" />
+    <opc:Field Name="Enabled" TypeName="opc:Boolean" />
+    <opc:Field Name="DataSetWriterId" TypeName="opc:UInt16" />
+    <opc:Field Name="DataSetFieldContentMask" TypeName="tns:DataSetFieldContentMask" />
+    <opc:Field Name="KeyFrameCount" TypeName="opc:UInt32" />
+    <opc:Field Name="DataSetName" TypeName="opc:String" />
+    <opc:Field Name="NoOfDataSetWriterProperties" TypeName="opc:Int32" />
+    <opc:Field Name="DataSetWriterProperties" TypeName="tns:KeyValuePair" LengthField="NoOfDataSetWriterProperties" />
+    <opc:Field Name="TransportSettings" TypeName="ua:ExtensionObject" />
+    <opc:Field Name="MessageSettings" TypeName="ua:ExtensionObject" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="DataSetWriterTransportDataType" BaseType="ua:ExtensionObject">
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="DataSetWriterMessageDataType" BaseType="ua:ExtensionObject">
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="PubSubGroupDataType" BaseType="ua:ExtensionObject">
+    <opc:Field Name="Name" TypeName="opc:String" />
+    <opc:Field Name="Enabled" TypeName="opc:Boolean" />
+    <opc:Field Name="SecurityMode" TypeName="tns:MessageSecurityMode" />
+    <opc:Field Name="SecurityGroupId" TypeName="opc:String" />
+    <opc:Field Name="NoOfSecurityKeyServices" TypeName="opc:Int32" />
+    <opc:Field Name="SecurityKeyServices" TypeName="tns:EndpointDescription" LengthField="NoOfSecurityKeyServices" />
+    <opc:Field Name="MaxNetworkMessageSize" TypeName="opc:UInt32" />
+    <opc:Field Name="NoOfGroupProperties" TypeName="opc:Int32" />
+    <opc:Field Name="GroupProperties" TypeName="tns:KeyValuePair" LengthField="NoOfGroupProperties" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="WriterGroupDataType" BaseType="tns:PubSubGroupDataType">
+    <opc:Field Name="Name" TypeName="opc:String" SourceType="tns:PubSubGroupDataType" />
+    <opc:Field Name="Enabled" TypeName="opc:Boolean" SourceType="tns:PubSubGroupDataType" />
+    <opc:Field Name="SecurityMode" TypeName="tns:MessageSecurityMode" SourceType="tns:PubSubGroupDataType" />
+    <opc:Field Name="SecurityGroupId" TypeName="opc:String" SourceType="tns:PubSubGroupDataType" />
+    <opc:Field Name="NoOfSecurityKeyServices" TypeName="opc:Int32" />
+    <opc:Field Name="SecurityKeyServices" TypeName="tns:EndpointDescription" LengthField="NoOfSecurityKeyServices" />
+    <opc:Field Name="MaxNetworkMessageSize" TypeName="opc:UInt32" SourceType="tns:PubSubGroupDataType" />
+    <opc:Field Name="NoOfGroupProperties" TypeName="opc:Int32" />
+    <opc:Field Name="GroupProperties" TypeName="tns:KeyValuePair" LengthField="NoOfGroupProperties" />
+    <opc:Field Name="WriterGroupId" TypeName="opc:UInt16" />
+    <opc:Field Name="PublishingInterval" TypeName="opc:Double" />
+    <opc:Field Name="KeepAliveTime" TypeName="opc:Double" />
+    <opc:Field Name="Priority" TypeName="opc:Byte" />
+    <opc:Field Name="NoOfLocaleIds" TypeName="opc:Int32" />
+    <opc:Field Name="LocaleIds" TypeName="opc:String" LengthField="NoOfLocaleIds" />
+    <opc:Field Name="TransportSettings" TypeName="ua:ExtensionObject" />
+    <opc:Field Name="MessageSettings" TypeName="ua:ExtensionObject" />
+    <opc:Field Name="NoOfDataSetWriters" TypeName="opc:Int32" />
+    <opc:Field Name="DataSetWriters" TypeName="tns:DataSetWriterDataType" LengthField="NoOfDataSetWriters" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="WriterGroupTransportDataType" BaseType="ua:ExtensionObject">
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="WriterGroupMessageDataType" BaseType="ua:ExtensionObject">
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="PubSubConnectionDataType" BaseType="ua:ExtensionObject">
+    <opc:Field Name="Name" TypeName="opc:String" />
+    <opc:Field Name="Enabled" TypeName="opc:Boolean" />
+    <opc:Field Name="PublisherId" TypeName="ua:Variant" />
+    <opc:Field Name="TransportProfileUri" TypeName="opc:String" />
+    <opc:Field Name="Address" TypeName="ua:ExtensionObject" />
+    <opc:Field Name="NoOfConnectionProperties" TypeName="opc:Int32" />
+    <opc:Field Name="ConnectionProperties" TypeName="tns:KeyValuePair" LengthField="NoOfConnectionProperties" />
+    <opc:Field Name="TransportSettings" TypeName="ua:ExtensionObject" />
+    <opc:Field Name="NoOfWriterGroups" TypeName="opc:Int32" />
+    <opc:Field Name="WriterGroups" TypeName="tns:WriterGroupDataType" LengthField="NoOfWriterGroups" />
+    <opc:Field Name="NoOfReaderGroups" TypeName="opc:Int32" />
+    <opc:Field Name="ReaderGroups" TypeName="tns:ReaderGroupDataType" LengthField="NoOfReaderGroups" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="ConnectionTransportDataType" BaseType="ua:ExtensionObject">
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="NetworkAddressDataType" BaseType="ua:ExtensionObject">
+    <opc:Field Name="NetworkInterface" TypeName="opc:String" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="NetworkAddressUrlDataType" BaseType="tns:NetworkAddressDataType">
+    <opc:Field Name="NetworkInterface" TypeName="opc:String" SourceType="tns:NetworkAddressDataType" />
+    <opc:Field Name="Url" TypeName="opc:String" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="ReaderGroupDataType" BaseType="tns:PubSubGroupDataType">
+    <opc:Field Name="Name" TypeName="opc:String" SourceType="tns:PubSubGroupDataType" />
+    <opc:Field Name="Enabled" TypeName="opc:Boolean" SourceType="tns:PubSubGroupDataType" />
+    <opc:Field Name="SecurityMode" TypeName="tns:MessageSecurityMode" SourceType="tns:PubSubGroupDataType" />
+    <opc:Field Name="SecurityGroupId" TypeName="opc:String" SourceType="tns:PubSubGroupDataType" />
+    <opc:Field Name="NoOfSecurityKeyServices" TypeName="opc:Int32" />
+    <opc:Field Name="SecurityKeyServices" TypeName="tns:EndpointDescription" LengthField="NoOfSecurityKeyServices" />
+    <opc:Field Name="MaxNetworkMessageSize" TypeName="opc:UInt32" SourceType="tns:PubSubGroupDataType" />
+    <opc:Field Name="NoOfGroupProperties" TypeName="opc:Int32" />
+    <opc:Field Name="GroupProperties" TypeName="tns:KeyValuePair" LengthField="NoOfGroupProperties" />
+    <opc:Field Name="TransportSettings" TypeName="ua:ExtensionObject" />
+    <opc:Field Name="MessageSettings" TypeName="ua:ExtensionObject" />
+    <opc:Field Name="NoOfDataSetReaders" TypeName="opc:Int32" />
+    <opc:Field Name="DataSetReaders" TypeName="tns:DataSetReaderDataType" LengthField="NoOfDataSetReaders" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="ReaderGroupTransportDataType" BaseType="ua:ExtensionObject">
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="ReaderGroupMessageDataType" BaseType="ua:ExtensionObject">
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="DataSetReaderDataType" BaseType="ua:ExtensionObject">
+    <opc:Field Name="Name" TypeName="opc:String" />
+    <opc:Field Name="Enabled" TypeName="opc:Boolean" />
+    <opc:Field Name="PublisherId" TypeName="ua:Variant" />
+    <opc:Field Name="WriterGroupId" TypeName="opc:UInt16" />
+    <opc:Field Name="DataSetWriterId" TypeName="opc:UInt16" />
+    <opc:Field Name="DataSetMetaData" TypeName="tns:DataSetMetaDataType" />
+    <opc:Field Name="DataSetFieldContentMask" TypeName="tns:DataSetFieldContentMask" />
+    <opc:Field Name="MessageReceiveTimeout" TypeName="opc:Double" />
+    <opc:Field Name="SecurityMode" TypeName="tns:MessageSecurityMode" />
+    <opc:Field Name="SecurityGroupId" TypeName="opc:String" />
+    <opc:Field Name="NoOfSecurityKeyServices" TypeName="opc:Int32" />
+    <opc:Field Name="SecurityKeyServices" TypeName="tns:EndpointDescription" LengthField="NoOfSecurityKeyServices" />
+    <opc:Field Name="NoOfDataSetReaderProperties" TypeName="opc:Int32" />
+    <opc:Field Name="DataSetReaderProperties" TypeName="tns:KeyValuePair" LengthField="NoOfDataSetReaderProperties" />
+    <opc:Field Name="TransportSettings" TypeName="ua:ExtensionObject" />
+    <opc:Field Name="MessageSettings" TypeName="ua:ExtensionObject" />
+    <opc:Field Name="SubscribedDataSet" TypeName="ua:ExtensionObject" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="DataSetReaderTransportDataType" BaseType="ua:ExtensionObject">
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="DataSetReaderMessageDataType" BaseType="ua:ExtensionObject">
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="SubscribedDataSetDataType" BaseType="ua:ExtensionObject">
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="TargetVariablesDataType" BaseType="tns:SubscribedDataSetDataType">
+    <opc:Field Name="NoOfTargetVariables" TypeName="opc:Int32" />
+    <opc:Field Name="TargetVariables" TypeName="tns:FieldTargetDataType" LengthField="NoOfTargetVariables" />
+  </opc:StructuredType>
+
   <opc:StructuredType Name="FieldTargetDataType" BaseType="ua:ExtensionObject">
     <opc:Field Name="DataSetFieldId" TypeName="opc:Guid" />
     <opc:Field Name="ReceiverIndexRange" TypeName="opc:String" />
@@ -377,13 +575,152 @@
     <opc:EnumeratedValue Name="OverrideValue" Value="2" />
   </opc:EnumeratedType>
 
-  <opc:EnumeratedType Name="PubSubState" LengthInBits="32">
-    <opc:EnumeratedValue Name="Disabled" Value="0" />
-    <opc:EnumeratedValue Name="Paused" Value="1" />
-    <opc:EnumeratedValue Name="Operational" Value="2" />
-    <opc:EnumeratedValue Name="Error" Value="3" />
+  <opc:StructuredType Name="SubscribedDataSetMirrorDataType" BaseType="tns:SubscribedDataSetDataType">
+    <opc:Field Name="ParentNodeName" TypeName="opc:String" />
+    <opc:Field Name="NoOfRolePermissions" TypeName="opc:Int32" />
+    <opc:Field Name="RolePermissions" TypeName="tns:RolePermissionType" LengthField="NoOfRolePermissions" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="PubSubConfigurationDataType" BaseType="ua:ExtensionObject">
+    <opc:Field Name="NoOfPublishedDataSets" TypeName="opc:Int32" />
+    <opc:Field Name="PublishedDataSets" TypeName="tns:PublishedDataSetDataType" LengthField="NoOfPublishedDataSets" />
+    <opc:Field Name="NoOfConnections" TypeName="opc:Int32" />
+    <opc:Field Name="Connections" TypeName="tns:PubSubConnectionDataType" LengthField="NoOfConnections" />
+    <opc:Field Name="Enabled" TypeName="opc:Boolean" />
+  </opc:StructuredType>
+
+  <opc:EnumeratedType Name="DataSetOrderingType" LengthInBits="32">
+    <opc:EnumeratedValue Name="Undefined" Value="0" />
+    <opc:EnumeratedValue Name="AscendingWriterId" Value="1" />
+    <opc:EnumeratedValue Name="AscendingWriterIdSingle" Value="2" />
+  </opc:EnumeratedType>
+
+  <opc:EnumeratedType Name="UadpNetworkMessageContentMask" LengthInBits="32">
+    <opc:EnumeratedValue Name="PublisherId" Value="1" />
+    <opc:EnumeratedValue Name="GroupHeader" Value="2" />
+    <opc:EnumeratedValue Name="WriterGroupId" Value="4" />
+    <opc:EnumeratedValue Name="GroupVersion" Value="8" />
+    <opc:EnumeratedValue Name="NetworkMessageNumber" Value="16" />
+    <opc:EnumeratedValue Name="SequenceNumber" Value="32" />
+    <opc:EnumeratedValue Name="PayloadHeader" Value="64" />
+    <opc:EnumeratedValue Name="Timestamp" Value="128" />
+    <opc:EnumeratedValue Name="Picoseconds" Value="256" />
+    <opc:EnumeratedValue Name="DataSetClassId" Value="512" />
+    <opc:EnumeratedValue Name="PromotedFields" Value="1024" />
   </opc:EnumeratedType>
 
+  <opc:StructuredType Name="UadpWriterGroupMessageDataType" BaseType="tns:WriterGroupMessageDataType">
+    <opc:Field Name="GroupVersion" TypeName="opc:UInt32" />
+    <opc:Field Name="DataSetOrdering" TypeName="tns:DataSetOrderingType" />
+    <opc:Field Name="NetworkMessageContentMask" TypeName="tns:UadpNetworkMessageContentMask" />
+    <opc:Field Name="SamplingOffset" TypeName="opc:Double" />
+    <opc:Field Name="NoOfPublishingOffset" TypeName="opc:Int32" />
+    <opc:Field Name="PublishingOffset" TypeName="opc:Double" LengthField="NoOfPublishingOffset" />
+  </opc:StructuredType>
+
+  <opc:EnumeratedType Name="UadpDataSetMessageContentMask" LengthInBits="32">
+    <opc:EnumeratedValue Name="Timestamp" Value="1" />
+    <opc:EnumeratedValue Name="PicoSeconds" Value="2" />
+    <opc:EnumeratedValue Name="Status" Value="4" />
+    <opc:EnumeratedValue Name="MajorVersion" Value="8" />
+    <opc:EnumeratedValue Name="MinorVersion" Value="16" />
+    <opc:EnumeratedValue Name="SequenceNumber" Value="32" />
+  </opc:EnumeratedType>
+
+  <opc:StructuredType Name="UadpDataSetWriterMessageDataType" BaseType="tns:DataSetWriterMessageDataType">
+    <opc:Field Name="DataSetMessageContentMask" TypeName="tns:UadpDataSetMessageContentMask" />
+    <opc:Field Name="ConfiguredSize" TypeName="opc:UInt16" />
+    <opc:Field Name="NetworkMessageNumber" TypeName="opc:UInt16" />
+    <opc:Field Name="DataSetOffset" TypeName="opc:UInt16" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="UadpDataSetReaderMessageDataType" BaseType="tns:DataSetReaderMessageDataType">
+    <opc:Field Name="GroupVersion" TypeName="opc:UInt32" />
+    <opc:Field Name="NetworkMessageNumber" TypeName="opc:UInt16" />
+    <opc:Field Name="DataSetOffset" TypeName="opc:UInt16" />
+    <opc:Field Name="DataSetClassId" TypeName="opc:Guid" />
+    <opc:Field Name="NetworkMessageContentMask" TypeName="tns:UadpNetworkMessageContentMask" />
+    <opc:Field Name="DataSetMessageContentMask" TypeName="tns:UadpDataSetMessageContentMask" />
+    <opc:Field Name="PublishingInterval" TypeName="opc:Double" />
+    <opc:Field Name="ReceiveOffset" TypeName="opc:Double" />
+    <opc:Field Name="ProcessingOffset" TypeName="opc:Double" />
+  </opc:StructuredType>
+
+  <opc:EnumeratedType Name="JsonNetworkMessageContentMask" LengthInBits="32">
+    <opc:EnumeratedValue Name="NetworkMessageHeader" Value="1" />
+    <opc:EnumeratedValue Name="DataSetMessageHeader" Value="2" />
+    <opc:EnumeratedValue Name="SingleDataSetMessage" Value="4" />
+    <opc:EnumeratedValue Name="PublisherId" Value="8" />
+    <opc:EnumeratedValue Name="DataSetClassId" Value="16" />
+    <opc:EnumeratedValue Name="ReplyTo" Value="32" />
+  </opc:EnumeratedType>
+
+  <opc:StructuredType Name="JsonWriterGroupMessageDataType" BaseType="tns:WriterGroupMessageDataType">
+    <opc:Field Name="NetworkMessageContentMask" TypeName="tns:JsonNetworkMessageContentMask" />
+  </opc:StructuredType>
+
+  <opc:EnumeratedType Name="JsonDataSetMessageContentMask" LengthInBits="32">
+    <opc:EnumeratedValue Name="DataSetWriterId" Value="1" />
+    <opc:EnumeratedValue Name="MetaDataVersion" Value="2" />
+    <opc:EnumeratedValue Name="SequenceNumber" Value="4" />
+    <opc:EnumeratedValue Name="Timestamp" Value="8" />
+    <opc:EnumeratedValue Name="Status" Value="16" />
+  </opc:EnumeratedType>
+
+  <opc:StructuredType Name="JsonDataSetWriterMessageDataType" BaseType="tns:DataSetWriterMessageDataType">
+    <opc:Field Name="DataSetMessageContentMask" TypeName="tns:JsonDataSetMessageContentMask" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="JsonDataSetReaderMessageDataType" BaseType="tns:DataSetReaderMessageDataType">
+    <opc:Field Name="NetworkMessageContentMask" TypeName="tns:JsonNetworkMessageContentMask" />
+    <opc:Field Name="DataSetMessageContentMask" TypeName="tns:JsonDataSetMessageContentMask" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="DatagramConnectionTransportDataType" BaseType="tns:ConnectionTransportDataType">
+    <opc:Field Name="DiscoveryAddress" TypeName="ua:ExtensionObject" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="DatagramWriterGroupTransportDataType" BaseType="tns:WriterGroupTransportDataType">
+    <opc:Field Name="MessageRepeatCount" TypeName="opc:Byte" />
+    <opc:Field Name="MessageRepeatDelay" TypeName="opc:Double" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="BrokerConnectionTransportDataType" BaseType="tns:ConnectionTransportDataType">
+    <opc:Field Name="ResourceUri" TypeName="opc:String" />
+    <opc:Field Name="AuthenticationProfileUri" TypeName="opc:String" />
+  </opc:StructuredType>
+
+  <opc:EnumeratedType Name="BrokerTransportQualityOfService" LengthInBits="32">
+    <opc:EnumeratedValue Name="NotSpecified" Value="0" />
+    <opc:EnumeratedValue Name="BestEffort" Value="1" />
+    <opc:EnumeratedValue Name="AtLeastOnce" Value="2" />
+    <opc:EnumeratedValue Name="AtMostOnce" Value="3" />
+    <opc:EnumeratedValue Name="ExactlyOnce" Value="4" />
+  </opc:EnumeratedType>
+
+  <opc:StructuredType Name="BrokerWriterGroupTransportDataType" BaseType="tns:WriterGroupTransportDataType">
+    <opc:Field Name="QueueName" TypeName="opc:String" />
+    <opc:Field Name="ResourceUri" TypeName="opc:String" />
+    <opc:Field Name="AuthenticationProfileUri" TypeName="opc:String" />
+    <opc:Field Name="RequestedDeliveryGuarantee" TypeName="tns:BrokerTransportQualityOfService" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="BrokerDataSetWriterTransportDataType" BaseType="tns:DataSetWriterTransportDataType">
+    <opc:Field Name="QueueName" TypeName="opc:String" />
+    <opc:Field Name="ResourceUri" TypeName="opc:String" />
+    <opc:Field Name="AuthenticationProfileUri" TypeName="opc:String" />
+    <opc:Field Name="MetaDataQueueName" TypeName="opc:String" />
+    <opc:Field Name="MetaDataUpdateTime" TypeName="opc:Double" />
+  </opc:StructuredType>
+
+  <opc:StructuredType Name="BrokerDataSetReaderTransportDataType" BaseType="tns:DataSetReaderTransportDataType">
+    <opc:Field Name="QueueName" TypeName="opc:String" />
+    <opc:Field Name="ResourceUri" TypeName="opc:String" />
+    <opc:Field Name="AuthenticationProfileUri" TypeName="opc:String" />
+    <opc:Field Name="RequestedDeliveryGuarantee" TypeName="tns:BrokerTransportQualityOfService" />
+    <opc:Field Name="MetaDataQueueName" TypeName="opc:String" />
+  </opc:StructuredType>
+
   <opc:EnumeratedType Name="DiagnosticsLevel" LengthInBits="32">
     <opc:EnumeratedValue Name="Basic" Value="0" />
     <opc:EnumeratedValue Name="Advanced" Value="1" />
@@ -397,11 +734,6 @@
     <opc:EnumeratedValue Name="Error" Value="1" />
   </opc:EnumeratedType>
 
-  <opc:EnumeratedType Name="DataSetOrderingType" LengthInBits="32">
-    <opc:EnumeratedValue Name="Undefined" Value="1" />
-    <opc:EnumeratedValue Name="AscendingWriterId" Value="2" />
-  </opc:EnumeratedType>
-
   <opc:EnumeratedType Name="IdType" LengthInBits="32">
     <opc:Documentation>The type of identifier used in a node id.</opc:Documentation>
     <opc:EnumeratedValue Name="Numeric" Value="0" />
@@ -2703,9 +3035,4 @@
     <opc:EnumeratedValue Name="Unknown" Value="4" />
   </opc:EnumeratedType>
 
-  <opc:StructuredType Name="NetworkAddressUrlDataType" BaseType="tns:NetworkAddressDataType">
-    <opc:Field Name="NetworkInterface" TypeName="opc:String" SourceType="tns:NetworkAddressDataType" />
-    <opc:Field Name="Url" TypeName="opc:String" />
-  </opc:StructuredType>
-
 </opc:TypeDictionary>

+ 10 - 0
tools/schema/datatypes_discovery.txt

@@ -0,0 +1,10 @@
+ServerOnNetwork
+FindServersOnNetworkRequest
+FindServersOnNetworkResponse
+RegisteredServer
+RegisterServerRequest
+RegisterServerResponse
+DiscoveryConfiguration
+MdnsDiscoveryConfiguration
+RegisterServer2Request
+RegisterServer2Response

+ 5 - 0
tools/schema/datatypes_method.txt

@@ -0,0 +1,5 @@
+CallMethodRequest
+CallMethodResult
+CallResponse
+CallRequest
+Argument

+ 7 - 15
tools/schema/datatypes_minimal.txt

@@ -121,11 +121,6 @@ UserIdentityToken
 UserNameIdentityToken
 AnonymousIdentityToken
 ServiceFault
-CallMethodRequest
-CallMethodResult
-CallResponse
-CallRequest
-Argument
 FilterOperator
 ContentFilterElement
 ContentFilter
@@ -159,16 +154,6 @@ MonitoredItemModifyResult
 ModifyMonitoredItemsResponse
 SetMonitoringModeRequest
 SetMonitoringModeResponse
-RegisteredServer
-RegisterServerRequest
-RegisterServerResponse
-DiscoveryConfiguration
-MdnsDiscoveryConfiguration
-RegisterServer2Request
-RegisterServer2Response
-ServerOnNetwork
-FindServersOnNetworkRequest
-FindServersOnNetworkResponse
 DataChangeTrigger
 DeadbandType
 DataChangeFilter
@@ -215,3 +200,10 @@ StructureType
 StructureField
 EnumField
 PublishedVariableDataType
+UadpDataSetWriterMessageDataType
+UadpWriterGroupMessageDataType
+DataSetOrderingType
+DataSetFieldContentMask
+UadpDataSetMessageContentMask
+UadpNetworkMessageContentMask
+SimpleTypeDescription