Sfoglia il codice sorgente

New Subscriptions and MonitoredItems Client API

Co-authored-by: StalderT <github@netsolux.ch>
Julius Pfrommer 6 anni fa
parent
commit
746ad7ede8

+ 4 - 2
CMakeLists.txt

@@ -341,7 +341,8 @@ set(exported_headers ${PROJECT_BINARY_DIR}/src_generated/ua_config.h
                      ${PROJECT_SOURCE_DIR}/include/ua_plugin_nodestore.h
                      ${PROJECT_SOURCE_DIR}/include/ua_server_config.h
                      ${PROJECT_SOURCE_DIR}/include/ua_client.h
-                     ${PROJECT_SOURCE_DIR}/include/ua_client_highlevel.h)
+                     ${PROJECT_SOURCE_DIR}/include/ua_client_highlevel.h
+                     ${PROJECT_SOURCE_DIR}/include/ua_client_subscriptions.h)
 
 set(internal_headers ${PROJECT_SOURCE_DIR}/deps/queue.h
                      ${PROJECT_SOURCE_DIR}/deps/pcg_basic.h
@@ -402,7 +403,8 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
                 ${PROJECT_SOURCE_DIR}/src/client/ua_client_connect.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_highlevel_subscriptions.c
+                ${PROJECT_SOURCE_DIR}/src/client/ua_client_subscriptions.c
+                ${PROJECT_SOURCE_DIR}/src/client/ua_client_subscriptions_deprecated.c
 
                 # dependencies
                 ${PROJECT_SOURCE_DIR}/deps/libc_time.c

+ 3 - 2
examples/client.c

@@ -94,7 +94,8 @@ int main(int argc, char *argv[]) {
 #ifdef UA_ENABLE_SUBSCRIPTIONS
     /* Create a subscription */
     UA_UInt32 subId = 0;
-    UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
+    UA_Client_Subscription_create(client, &UA_SubscriptionParameters_default,
+                                  NULL, NULL, NULL, &subId);
     if(subId)
         printf("Create subscription succeeded, id %u\n", subId);
     /* Add a MonitoredItem */
@@ -150,7 +151,7 @@ int main(int argc, char *argv[]) {
     /* Take another look at the.answer */
     UA_Client_runAsync(client, 100);
     /* Delete the subscription */
-    if(!UA_Client_Subscriptions_remove(client, subId))
+    if(!UA_Client_Subscription_delete(client, subId))
         printf("Subscription removed\n");
 #endif
 

+ 8 - 3
examples/client_subscription_loop.c

@@ -54,8 +54,12 @@ handler_currentTimeChanged(UA_Client *client, UA_UInt32 monId, UA_DataValue *val
 }
 
 static void
-stateCallback (UA_Client *client, UA_ClientState clientState) {
+deleteSubscriptionCallback(UA_Client *client, UA_UInt32 subscriptionId, void *subscriptionContext) {
+    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "Subscription Id %u was deleted", subscriptionId);
+}
 
+static void
+stateCallback (UA_Client *client, UA_ClientState clientState) {
     switch(clientState) {
         case UA_CLIENTSTATE_DISCONNECTED:
             UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "The client is disconnected");
@@ -71,8 +75,9 @@ stateCallback (UA_Client *client, UA_ClientState clientState) {
             /* A new session was created. We need to create the subscription. */
             /* Create a subscription */
             UA_UInt32 subId = 0;
-            UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
-            if(subId)
+            UA_StatusCode retval = UA_Client_Subscription_create(client, &UA_SubscriptionParameters_default,
+                                                   NULL, NULL, deleteSubscriptionCallback, &subId);
+            if(retval == UA_STATUSCODE_GOOD)
                 UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "Create subscription succeeded, id %u", subId);
             else
                 return;

+ 4 - 3
examples/tutorial_client_events.c

@@ -100,8 +100,9 @@ int main(int argc, char *argv[]) {
 #ifdef UA_ENABLE_SUBSCRIPTIONS
     /* Create a subscription */
     UA_UInt32 subId = 0;
-    retval = UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
-    if(!subId) {
+    retval = UA_Client_Subscription_create(client, &UA_SubscriptionParameters_default,
+                                                   NULL, NULL, NULL, &subId);
+    if(retval != UA_STATUSCODE_GOOD) {
         UA_Client_disconnect(client);
         UA_Client_delete(client);
         return (int)retval;
@@ -132,7 +133,7 @@ int main(int argc, char *argv[]) {
 
     /* Delete the subscription */
  cleanup:
-    UA_Client_Subscriptions_remove(client, subId);
+    UA_Client_Subscription_delete(client, subId);
     UA_Array_delete(selectClauses, nSelectClauses, &UA_TYPES[UA_TYPES_SIMPLEATTRIBUTEOPERAND]);
 #endif
 

+ 2 - 73
include/ua_client.h

@@ -84,10 +84,6 @@ typedef struct UA_ClientConfig {
 
     void *clientContext;
 
-    /* PublishResponse Timeout for background async process in ms */
-    /* 0 = time out disabled                                      */
-    UA_UInt32 backgroundPublishResponseTimeout;
-
     /* number of PublishResponse standing in the sever */
     /* 0 = background task disabled                    */
     UA_UInt16 outStandingPublishRequests;
@@ -373,74 +369,6 @@ UA_Client_Service_queryNext(UA_Client *client,
     return response;
 }
 
-#ifdef UA_ENABLE_SUBSCRIPTIONS
-
-/**
- * MonitoredItem Service Set
- * ^^^^^^^^^^^^^^^^^^^^^^^^^ */
-static UA_INLINE UA_CreateMonitoredItemsResponse
-UA_Client_Service_createMonitoredItems(UA_Client *client,
-                                 const UA_CreateMonitoredItemsRequest request) {
-    UA_CreateMonitoredItemsResponse response;
-    __UA_Client_Service(client, &request,
-                        &UA_TYPES[UA_TYPES_CREATEMONITOREDITEMSREQUEST], &response,
-                        &UA_TYPES[UA_TYPES_CREATEMONITOREDITEMSRESPONSE]);
-    return response;
-}
-
-static UA_INLINE UA_DeleteMonitoredItemsResponse
-UA_Client_Service_deleteMonitoredItems(UA_Client *client,
-                                 const UA_DeleteMonitoredItemsRequest request) {
-    UA_DeleteMonitoredItemsResponse response;
-    __UA_Client_Service(client, &request,
-                        &UA_TYPES[UA_TYPES_DELETEMONITOREDITEMSREQUEST], &response,
-                        &UA_TYPES[UA_TYPES_DELETEMONITOREDITEMSRESPONSE]);
-    return response;
-}
-
-/**
- * Subscription Service Set
- * ^^^^^^^^^^^^^^^^^^^^^^^^ */
-static UA_INLINE UA_CreateSubscriptionResponse
-UA_Client_Service_createSubscription(UA_Client *client,
-                                   const UA_CreateSubscriptionRequest request) {
-    UA_CreateSubscriptionResponse response;
-    __UA_Client_Service(client, &request,
-                        &UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONREQUEST], &response,
-                        &UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONRESPONSE]);
-    return response;
-}
-
-static UA_INLINE UA_ModifySubscriptionResponse
-UA_Client_Service_modifySubscription(UA_Client *client,
-                                   const UA_ModifySubscriptionRequest request) {
-    UA_ModifySubscriptionResponse response;
-    __UA_Client_Service(client, &request,
-                        &UA_TYPES[UA_TYPES_MODIFYSUBSCRIPTIONREQUEST], &response,
-                        &UA_TYPES[UA_TYPES_MODIFYSUBSCRIPTIONRESPONSE]);
-    return response;
-}
-
-static UA_INLINE UA_DeleteSubscriptionsResponse
-UA_Client_Service_deleteSubscriptions(UA_Client *client,
-                                  const UA_DeleteSubscriptionsRequest request) {
-    UA_DeleteSubscriptionsResponse response;
-    __UA_Client_Service(client, &request,
-                        &UA_TYPES[UA_TYPES_DELETESUBSCRIPTIONSREQUEST], &response,
-                        &UA_TYPES[UA_TYPES_DELETESUBSCRIPTIONSRESPONSE]);
-    return response;
-}
-
-static UA_INLINE UA_PublishResponse
-UA_Client_Service_publish(UA_Client *client, const UA_PublishRequest request) {
-    UA_PublishResponse response;
-    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_PUBLISHREQUEST],
-                        &response, &UA_TYPES[UA_TYPES_PUBLISHRESPONSE]);
-    return response;
-}
-
-#endif
-
 /**
  * .. _client-async-services:
  *
@@ -524,7 +452,8 @@ UA_Client_AsyncService_browse(UA_Client *client, const UA_BrowseRequest *request
 /**
  * .. toctree::
  *
- *    client_highlevel */
+ *    client_highlevel
+ *    client_subscriptions */
 
 #ifdef __cplusplus
 } // extern "C"

+ 2 - 96
include/ua_client_highlevel.h

@@ -562,98 +562,6 @@ UA_Client_addMethodNode(UA_Client *client, const UA_NodeId requestedNewNodeId,
                                &UA_TYPES[UA_TYPES_METHODATTRIBUTES], outNewNodeId);
 }
 
-/**
- * .. _client-subscriptions:
- *
- * Subscriptions Handling
- * ^^^^^^^^^^^^^^^^^^^^^^
- * At this time, the client does not yet contain its own thread or event-driven
- * main-loop. So the client will not perform any actions automatically in the
- * background. This is especially relevant for subscriptions. The user will have
- * to periodically call `UA_Client_Subscriptions_manuallySendPublishRequest`.
- * See also :ref:`here <client-subscriptions>`. */
-#ifdef UA_ENABLE_SUBSCRIPTIONS
-
-typedef struct {
-    UA_Double requestedPublishingInterval;
-    UA_UInt32 requestedLifetimeCount;
-    UA_UInt32 requestedMaxKeepAliveCount;
-    UA_UInt32 maxNotificationsPerPublish;
-    UA_Boolean publishingEnabled;
-    UA_Byte priority;
-} UA_SubscriptionSettings;
-
-extern const UA_EXPORT UA_SubscriptionSettings UA_SubscriptionSettings_default;
-
-UA_StatusCode UA_EXPORT
-UA_Client_Subscriptions_new(UA_Client *client, UA_SubscriptionSettings settings,
-                            UA_UInt32 *newSubscriptionId);
-
-UA_StatusCode UA_EXPORT
-UA_Client_Subscriptions_remove(UA_Client *client, UA_UInt32 subscriptionId);
-
-/* Send a publish request and wait until a response to the request is processed.
- * Note that other publish responses may be processed in the background until
- * then. */
-UA_DEPRECATED UA_StatusCode UA_EXPORT
-UA_Client_Subscriptions_manuallySendPublishRequest(UA_Client *client);
-
-/* Addition of monitored DataChanges */
-/* TODO for v0.4: Rename method to _DataChange. */
-typedef void (*UA_MonitoredItemHandlingFunction)(UA_Client *client, UA_UInt32 monId,
-                                                 UA_DataValue *value, void *context);
-
-UA_StatusCode UA_EXPORT
-UA_Client_Subscriptions_addMonitoredItems(UA_Client *client, const UA_UInt32 subscriptionId,
-                                          UA_MonitoredItemCreateRequest *items, size_t itemsSize,
-                                          UA_MonitoredItemHandlingFunction *hfs,
-                                          void **hfContexts, UA_StatusCode *itemResults,
-                                          UA_UInt32 *newMonitoredItemIds);
-
-UA_StatusCode UA_EXPORT
-UA_Client_Subscriptions_addMonitoredItem(UA_Client *client, UA_UInt32 subscriptionId,
-                                         UA_NodeId nodeId, UA_UInt32 attributeID,
-                                         UA_MonitoredItemHandlingFunction hf,
-                                         void *hfContext,
-                                         UA_UInt32 *newMonitoredItemId,
-                                         UA_Double samplingInterval);
-
-/* Monitored Events have different payloads from DataChanges. So they use a
- * different callback method signature. */
-typedef void (*UA_MonitoredEventHandlingFunction)(UA_Client *client,
-                                                  const UA_UInt32 monId,
-                                                  const size_t nEventFields,
-                                                  const UA_Variant *eventFields,
-                                                  void *context);
-
-UA_StatusCode UA_EXPORT
-UA_Client_Subscriptions_addMonitoredEvents(UA_Client *client, const UA_UInt32 subscriptionId,
-                                           UA_MonitoredItemCreateRequest *items, size_t itemsSize,
-                                           UA_MonitoredEventHandlingFunction *hfs,
-                                           void **hfContexts, UA_StatusCode *itemResults,
-                                           UA_UInt32 *newMonitoredItemIds);
-
-/* TODO for 0.4: attribute is fix for events. */
-UA_StatusCode UA_EXPORT
-UA_Client_Subscriptions_addMonitoredEvent(UA_Client *client, UA_UInt32 subscriptionId,
-                                          const UA_NodeId nodeId, UA_UInt32 attributeID,
-                                          const UA_SimpleAttributeOperand *selectClauses,
-                                          size_t selectClausesSize,
-                                          const UA_ContentFilterElement *whereClauses,
-                                          size_t whereClausesSize,
-                                          const UA_MonitoredEventHandlingFunction hf,
-                                          void *hfContext, UA_UInt32 *newMonitoredItemId);
-
-UA_StatusCode UA_EXPORT
-UA_Client_Subscriptions_removeMonitoredItem(UA_Client *client, UA_UInt32 subscriptionId,
-                                            UA_UInt32 monitoredItemId);
-
-UA_StatusCode UA_EXPORT
-UA_Client_Subscriptions_removeMonitoredItems(UA_Client *client, UA_UInt32 subscriptionId,
-                                             UA_UInt32 *monitoredItemId, size_t itemsSize,
-                                             UA_StatusCode *itemResults);
-#endif
-
 /**
  * Misc Highlevel Functionality
  * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
@@ -672,10 +580,8 @@ UA_Client_NamespaceGetIndex(UA_Client *client, UA_String *namespaceUri,
 #define HAVE_NODEITER_CALLBACK
 /* Iterate over all nodes referenced by parentNodeId by calling the callback
    function for each child node */
-typedef UA_StatusCode (*UA_NodeIteratorCallback)(UA_NodeId childId,
-                                                 UA_Boolean isInverse,
-                                                 UA_NodeId referenceTypeId,
-                                                 void *handle);
+typedef UA_StatusCode (*UA_NodeIteratorCallback)(UA_NodeId childId, UA_Boolean isInverse,
+                                                 UA_NodeId referenceTypeId, void *handle);
 #endif
 
 UA_StatusCode UA_EXPORT

+ 294 - 0
include/ua_client_subscriptions.h

@@ -0,0 +1,294 @@
+/* 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_SUBSCRIPTIONS_H_
+#define UA_CLIENT_SUBSCRIPTIONS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "ua_client.h"
+
+#ifdef UA_ENABLE_SUBSCRIPTIONS
+
+/**
+ * .. _client-subscriptions:
+ *
+ * Subscriptions
+ * -------------
+ *
+ * Subscriptions in OPC UA are asynchronous. That is, the client sends several
+ * PublishRequests to the server. The server returns PublishResponses with
+ * notifications. But only when a notification has been generated. The client
+ * does not wait for the responses and continues normal operations.
+ *
+ * Note the difference between Subscriptions and MonitoredItems. Subscriptions
+ * are used to report back notifications. MonitoredItems are used to generate
+ * notifications. Every MonitoredItem is attached to exactly one Subscription.
+ * And a Subscription can contain many MonitoredItems.
+ *
+ * The client automatically processes PublishResponses (with a callback) in the
+ * background and keeps enough PublishRequests in transit. The PublishResponses
+ * may be recieved during a synchronous service call or in
+ * ``UA_Client_runAsync``. */
+
+/* Callbacks defined for Subscriptions */
+typedef void (*UA_Client_DeleteSubscriptionCallback)
+    (UA_Client *client, UA_UInt32 subId, void *subContext);
+
+typedef void (*UA_Client_StatusChangeNotificationCallback)
+    (UA_Client *client, UA_UInt32 subId, void *subContext,
+     UA_StatusChangeNotification *notification);
+
+/* Provides default values for a new subscription.
+ *
+ * RequestedPublishingInterval:  500.0 [ms]
+ * RequestedLifetimeCount: 10000
+ * RequestedMaxKeepAliveCount: 10
+ * MaxNotificationsPerPublish: 0 (unlimited)
+ * PublishingEnabled: true
+ * Priority: 0 */
+static UA_INLINE UA_CreateSubscriptionRequest
+UA_CreateSubscriptionRequest_default(void) {
+    UA_CreateSubscriptionRequest request;
+    UA_CreateSubscriptionRequest_init(&request);
+
+    request.requestedPublishingInterval = 500.0;
+    request.requestedLifetimeCount = 10000;
+    request.requestedMaxKeepAliveCount = 10;
+    request.maxNotificationsPerPublish = 0;
+    request.publishingEnabled = true;
+    request.priority = 0;
+    return request;
+}
+
+UA_CreateSubscriptionResponse UA_EXPORT
+UA_Client_Subscriptions_create(UA_Client *client,
+                               const UA_CreateSubscriptionRequest request,
+                               void *subscriptionContext,
+                               UA_Client_StatusChangeNotificationCallback statusChangeCallback,
+                               UA_Client_DeleteSubscriptionCallback deleteCallback);
+
+UA_ModifySubscriptionResponse UA_EXPORT
+UA_Client_Subscriptions_modify(UA_Client *client, const UA_ModifySubscriptionRequest request);
+
+UA_DeleteSubscriptionsResponse UA_EXPORT
+UA_Client_Subscriptions_delete(UA_Client *client,
+                               const UA_DeleteSubscriptionsRequest request);
+
+/* Delete a single subscription */
+UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_deleteSingle(UA_Client *client, UA_UInt32 subscriptionId);
+
+static UA_INLINE UA_SetPublishingModeResponse
+UA_Client_Subscriptions_setPublishingMode(UA_Client *client,
+                                          const UA_SetPublishingModeRequest request) {
+    UA_SetPublishingModeResponse response;
+    __UA_Client_Service(client, &request,
+                        &UA_TYPES[UA_TYPES_SETPUBLISHINGMODEREQUEST], &response,
+                        &UA_TYPES[UA_TYPES_SETPUBLISHINGMODERESPONSE]);
+    return response;
+}
+
+/**
+ * MonitoredItems
+ * --------------
+ *
+ * MonitoredItems for Events indicate the ``EventNotifier`` attribute. This
+ * indicates to the server not to monitor changes of the attribute, but to
+ * forward Event notifications from that node.
+ *
+ * During the creation of a MonitoredItem, the server may return changed
+ * adjusted parameters. Use ``UA_Client_MonitoredItem_getParameters`` to get the
+ * current parameters. */
+
+/* Provides default values for a new monitored item. */
+static UA_INLINE UA_MonitoredItemCreateRequest
+UA_MonitoredItemCreateRequest_default(UA_NodeId nodeId) {
+    UA_MonitoredItemCreateRequest request;
+    UA_MonitoredItemCreateRequest_init(&request);
+    request.itemToMonitor.nodeId = nodeId;
+    request.itemToMonitor.attributeId = UA_ATTRIBUTEID_VALUE;
+    request.monitoringMode = UA_MONITORINGMODE_REPORTING;
+    request.requestedParameters.samplingInterval = 250;
+    request.requestedParameters.discardOldest = true;
+    request.requestedParameters.queueSize = 1;
+    return request;
+}
+
+/* Callback for the deletion of a MonitoredItem */
+typedef void (*UA_Client_DeleteMonitoredItemCallback)
+    (UA_Client *client, UA_UInt32 subId, void *subContext,
+     UA_UInt32 monId, void *monContext);
+
+/* Callback for DataChange notifications */
+typedef void (*UA_Client_DataChangeNotificationCallback)
+    (UA_Client *client, UA_UInt32 subId, void *subContext,
+     UA_UInt32 monId, void *monContext,
+     UA_DataValue *value);
+
+/* Callback for Event notifications */
+typedef void (*UA_Client_EventNotificationCallback)
+    (UA_Client *client, UA_UInt32 subId, void *subContext,
+     UA_UInt32 monId, void *monContext,
+     size_t nEventFields, UA_Variant *eventFields);
+
+/* Don't use to monitor the EventNotifier attribute */
+UA_CreateMonitoredItemsResponse UA_EXPORT
+UA_Client_MonitoredItems_createDataChanges(UA_Client *client,
+            const UA_CreateMonitoredItemsRequest request, void **contexts,
+            UA_Client_DataChangeNotificationCallback *callbacks,
+            UA_Client_DeleteMonitoredItemCallback *deleteCallbacks);
+
+UA_MonitoredItemCreateResult UA_EXPORT
+UA_Client_MonitoredItems_createDataChange(UA_Client *client, UA_UInt32 subscriptionId,
+          UA_TimestampsToReturn timestampsToReturn, const UA_MonitoredItemCreateRequest item,
+          void *context, UA_Client_DataChangeNotificationCallback callback,
+          UA_Client_DeleteMonitoredItemCallback deleteCallback);
+
+/* Monitor the EventNotifier attribute only */
+UA_CreateMonitoredItemsResponse UA_EXPORT
+UA_Client_MonitoredItems_createEvents(UA_Client *client,
+            const UA_CreateMonitoredItemsRequest request, void **contexts,
+            UA_Client_EventNotificationCallback *callbacks,
+            UA_Client_DeleteMonitoredItemCallback *deleteCallback);
+
+UA_MonitoredItemCreateResult UA_EXPORT
+UA_Client_MonitoredItems_createEvent(UA_Client *client, UA_UInt32 subscriptionId,
+          UA_TimestampsToReturn timestampsToReturn, const UA_MonitoredItemCreateRequest item,
+          void *context, UA_Client_EventNotificationCallback callback,
+          UA_Client_DeleteMonitoredItemCallback deleteCallback);
+
+UA_DeleteMonitoredItemsResponse UA_EXPORT
+UA_Client_MonitoredItems_delete(UA_Client *client, const UA_DeleteMonitoredItemsRequest);
+
+UA_StatusCode UA_EXPORT
+UA_Client_MonitoredItems_deleteSingle(UA_Client *client, UA_UInt32 subscriptionId, UA_UInt32 monitoredItemId);
+
+/**
+ * The following service calls go directly to the server. The MonitoredItem settings are
+ * not stored in the client. */
+
+static UA_INLINE UA_ModifyMonitoredItemsResponse
+UA_Client_MonitoredItems_modify(UA_Client *client,
+                                const UA_ModifyMonitoredItemsRequest request) {
+    UA_ModifyMonitoredItemsResponse response;
+    __UA_Client_Service(client,
+                        &request, &UA_TYPES[UA_TYPES_MODIFYMONITOREDITEMSREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_MODIFYSUBSCRIPTIONRESPONSE]);
+    return response;
+}
+
+static UA_INLINE UA_SetMonitoringModeResponse
+UA_Client_MonitoredItems_setMonitoringMode(UA_Client *client,
+                                           const UA_SetMonitoringModeRequest request) {
+    UA_SetMonitoringModeResponse response;
+    __UA_Client_Service(client,
+                        &request, &UA_TYPES[UA_TYPES_SETMONITORINGMODEREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_SETMONITORINGMODERESPONSE]);
+    return response;
+}
+
+static UA_INLINE UA_SetTriggeringResponse
+UA_Client_MonitoredItems_setTriggering(UA_Client *client,
+                                       const UA_SetTriggeringRequest request) {
+    UA_SetTriggeringResponse response;
+    __UA_Client_Service(client,
+                        &request, &UA_TYPES[UA_TYPES_SETTRIGGERINGREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_SETTRIGGERINGRESPONSE]);
+    return response;
+}
+
+/**
+ * Deprecated API
+ * --------------
+ * The following API is kept for backwards compatibility. It will be removed in
+ * future releases. */
+
+typedef struct {
+    UA_Double requestedPublishingInterval;
+    UA_UInt32 requestedLifetimeCount;
+    UA_UInt32 requestedMaxKeepAliveCount;
+    UA_UInt32 maxNotificationsPerPublish;
+    UA_Boolean publishingEnabled;
+    UA_Byte priority;
+} UA_SubscriptionSettings;
+
+extern const UA_EXPORT UA_SubscriptionSettings UA_SubscriptionSettings_default;
+
+UA_DEPRECATED UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_new(UA_Client *client, UA_SubscriptionSettings settings,
+                            UA_UInt32 *newSubscriptionId);
+
+UA_DEPRECATED UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_remove(UA_Client *client, UA_UInt32 subscriptionId);
+
+/* Send a publish request and wait until a response to the request is processed.
+ * Note that other publish responses may be processed in the background until
+ * then. */
+UA_DEPRECATED UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_manuallySendPublishRequest(UA_Client *client);
+
+/* For monitoring DataChanges */
+typedef void (*UA_MonitoredItemHandlingFunction)(UA_Client *client, UA_UInt32 monId,
+                                                 UA_DataValue *value, void *context);
+
+UA_DEPRECATED UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_addMonitoredItems(UA_Client *client, const UA_UInt32 subscriptionId,
+                                          UA_MonitoredItemCreateRequest *items, size_t itemsSize,
+                                          UA_MonitoredItemHandlingFunction *hfs,
+                                          void **hfContexts, UA_StatusCode *itemResults,
+                                          UA_UInt32 *newMonitoredItemIds);
+
+UA_DEPRECATED UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_addMonitoredItem(UA_Client *client, UA_UInt32 subscriptionId,
+                                         UA_NodeId nodeId, UA_UInt32 attributeID,
+                                         UA_MonitoredItemHandlingFunction hf,
+                                         void *hfContext,
+                                         UA_UInt32 *newMonitoredItemId,
+                                         UA_Double samplingInterval);
+
+/* Monitored Events have different payloads from DataChanges. So they use a
+ * different callback method signature. */
+typedef void (*UA_MonitoredEventHandlingFunction)(UA_Client *client,
+                                                  const UA_UInt32 monId,
+                                                  const size_t nEventFields,
+                                                  const UA_Variant *eventFields,
+                                                  void *context);
+
+UA_DEPRECATED UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_addMonitoredEvents(UA_Client *client, const UA_UInt32 subscriptionId,
+                                           UA_MonitoredItemCreateRequest *items, size_t itemsSize,
+                                           UA_MonitoredEventHandlingFunction *hfs,
+                                           void **hfContexts, UA_StatusCode *itemResults,
+                                           UA_UInt32 *newMonitoredItemIds);
+
+/* TODO for 0.4: attribute is fix for events. */
+UA_DEPRECATED UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_addMonitoredEvent(UA_Client *client, UA_UInt32 subscriptionId,
+                                          const UA_NodeId nodeId, UA_UInt32 attributeID,
+                                          const UA_SimpleAttributeOperand *selectClauses,
+                                          size_t selectClausesSize,
+                                          const UA_ContentFilterElement *whereClauses,
+                                          size_t whereClausesSize,
+                                          const UA_MonitoredEventHandlingFunction hf,
+                                          void *hfContext, UA_UInt32 *newMonitoredItemId);
+
+UA_DEPRECATED UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_removeMonitoredItem(UA_Client *client, UA_UInt32 subscriptionId,
+                                            UA_UInt32 monitoredItemId);
+
+UA_DEPRECATED UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_removeMonitoredItems(UA_Client *client, UA_UInt32 subscriptionId,
+                                             UA_UInt32 *monitoredItemIds, size_t itemsSize,
+                                             UA_StatusCode *itemResults);
+
+#endif
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif /* UA_CLIENT_SUBSCRIPTIONS_H_ */

+ 2 - 20
plugins/ua_config_default.c

@@ -24,7 +24,7 @@
 
 #include "ua_types.h"
 #include "ua_types_generated_handling.h"
-#include "ua_client_highlevel.h"
+#include "ua_client_subscriptions.h"
 
 #define ANONYMOUS_POLICY "open62541-anonymous-policy"
 #define USERNAME_POLICY "open62541-username-policy"
@@ -470,23 +470,5 @@ const UA_ClientConfig UA_ClientConfig_default = {
     NULL, /*.stateCallback */
     NULL,  /*.clientContext */
 
-    0, /* .backgroundPublishResponseTimeout */
-    0 /* .outStandingPublishRequests */
+    10 /* .outStandingPublishRequests */
 };
-
-/****************************************/
-/* Default Client Subscription Settings */
-/****************************************/
-
-#ifdef UA_ENABLE_SUBSCRIPTIONS
-
-const UA_SubscriptionSettings UA_SubscriptionSettings_default = {
-    500.0, /* .requestedPublishingInterval */
-    10000, /* .requestedLifetimeCount */
-    1, /* .requestedMaxKeepAliveCount */
-    0, /* .maxNotificationsPerPublish */
-    true, /* .publishingEnabled */
-    0 /* .priority */
-};
-
-#endif

+ 10 - 5
src/client/ua_client.c

@@ -24,9 +24,9 @@
 #include "ua_util.h"
 #include "ua_securitypolicy_none.h"
 
- /********************/
- /* Client Lifecycle */
- /********************/
+/********************/
+/* Client Lifecycle */
+/********************/
 
 static void
 UA_Client_init(UA_Client* client, UA_ClientConfig config) {
@@ -166,7 +166,8 @@ processAsyncResponse(UA_Client *client, UA_UInt32 requestId, UA_NodeId *response
         UA_deleteMembers(response, ac->responseType);
     } else {
         UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                    "Could not decodee the response with Id %u", requestId);
+                    "Could not decode the response with Id %u", requestId);
+        UA_Client_AsyncService_cancel(client, ac, UA_STATUSCODE_BADCOMMUNICATIONERROR);
     }
 
     /* Remove the callback */
@@ -360,7 +361,7 @@ void UA_Client_AsyncService_removeAll(UA_Client *client, UA_StatusCode statusCod
     AsyncServiceCall *ac, *ac_tmp;
     LIST_FOREACH_SAFE(ac, &client->asyncServiceCalls, pointers, ac_tmp) {
         LIST_REMOVE(ac, pointers);
-        UA_Client_AsyncService_cancel(client, ac, UA_STATUSCODE_BADSHUTDOWN);
+        UA_Client_AsyncService_cancel(client, ac, statusCode);
         UA_free(ac);
     }
 }
@@ -405,5 +406,9 @@ UA_Client_runAsync(UA_Client *client, UA_UInt16 timeout) {
     UA_StatusCode 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;
 }

+ 17 - 6
src/client/ua_client_connect.c

@@ -23,17 +23,17 @@
  /* Set client state */
  /********************/
 void
-setClientState(UA_Client *client, UA_ClientState state)
-{
+setClientState(UA_Client *client, UA_ClientState state) {
     if(client->state != state) {
         client->state = state;
         if(client->config.stateCallback)
             client->config.stateCallback(client, client->state);
     }
 }
- /***********************/
- /* Open the Connection */
- /***********************/
+
+/***********************/
+/* Open the Connection */
+/***********************/
 
 static UA_StatusCode
 processACKResponse(void *application, UA_Connection *connection, UA_ByteString *chunk) {
@@ -450,7 +450,6 @@ UA_Client_connectInternal(UA_Client *client, const char *endpointUrl,
     UA_NodeId_deleteMembers(&client->authenticationToken);
 #endif /* UA_SESSION_RECOVERY */
 
-
     /* Get Endpoints */
     if(endpointsHandshake) {
         retval = getEndpoints(client);
@@ -558,6 +557,12 @@ UA_Client_disconnect(UA_Client *client) {
     if(client->connection.state != UA_CONNECTION_CLOSED)
         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);
+#endif
+
     setClientState(client, UA_CLIENTSTATE_DISCONNECTED);
     return UA_STATUSCODE_GOOD;
 }
@@ -573,6 +578,12 @@ UA_Client_close(UA_Client *client) {
     if(client->connection.state != UA_CONNECTION_CLOSED)
         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);
+#endif
+
     setClientState(client, UA_CLIENTSTATE_DISCONNECTED);
     return UA_STATUSCODE_GOOD;
 }

+ 0 - 694
src/client/ua_client_highlevel_subscriptions.c

@@ -1,694 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. 
- *
- *    Copyright 2015-2018 (c) Julius Pfrommer, Fraunhofer IOSB
- *    Copyright 2015 (c) Oleksiy Vasylyev
- *    Copyright 2016 (c) Sten Grüner
- *    Copyright 2017-2018 (c) Thomas Stalder
- *    Copyright 2016-2017 (c) Florian Palm
- *    Copyright 2017 (c) Frank Meerkötter
- *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
- */
-
-#include "ua_client_highlevel.h"
-#include "ua_client_internal.h"
-#include "ua_util.h"
-
-#ifdef UA_ENABLE_SUBSCRIPTIONS /* conditional compilation */
-
-UA_StatusCode
-UA_Client_Subscriptions_new(UA_Client *client, UA_SubscriptionSettings settings,
-                            UA_UInt32 *newSubscriptionId) {
-    UA_CreateSubscriptionRequest request;
-    UA_CreateSubscriptionRequest_init(&request);
-    request.requestedPublishingInterval = settings.requestedPublishingInterval;
-    request.requestedLifetimeCount = settings.requestedLifetimeCount;
-    request.requestedMaxKeepAliveCount = settings.requestedMaxKeepAliveCount;
-    request.maxNotificationsPerPublish = settings.maxNotificationsPerPublish;
-    request.publishingEnabled = settings.publishingEnabled;
-    request.priority = settings.priority;
-
-    UA_CreateSubscriptionResponse response = UA_Client_Service_createSubscription(client, request);
-    UA_StatusCode retval = response.responseHeader.serviceResult;
-    if(retval != UA_STATUSCODE_GOOD) {
-        UA_CreateSubscriptionResponse_deleteMembers(&response);
-        return retval;
-    }
-
-    UA_Client_Subscription *newSub = (UA_Client_Subscription *)UA_malloc(sizeof(UA_Client_Subscription));
-    if(!newSub) {
-        UA_CreateSubscriptionResponse_deleteMembers(&response);
-        return UA_STATUSCODE_BADOUTOFMEMORY;
-    }
-
-    LIST_INIT(&newSub->monitoredItems);
-    newSub->sequenceNumber = 0;
-    newSub->lastActivity = UA_DateTime_nowMonotonic();
-    newSub->lifeTime = response.revisedLifetimeCount;
-    newSub->keepAliveCount = response.revisedMaxKeepAliveCount;
-    newSub->publishingInterval = response.revisedPublishingInterval;
-    newSub->subscriptionID = response.subscriptionId;
-    newSub->notificationsPerPublish = request.maxNotificationsPerPublish;
-    newSub->priority = request.priority;
-    LIST_INSERT_HEAD(&client->subscriptions, newSub, listEntry);
-
-    if(newSubscriptionId)
-        *newSubscriptionId = newSub->subscriptionID;
-
-    UA_CreateSubscriptionResponse_deleteMembers(&response);
-    return UA_STATUSCODE_GOOD;
-}
-
-static UA_Client_Subscription *
-findSubscription(const UA_Client *client, UA_UInt32 subscriptionId) {
-    UA_Client_Subscription *sub = NULL;
-    LIST_FOREACH(sub, &client->subscriptions, listEntry) {
-        if(sub->subscriptionID == subscriptionId)
-            break;
-    }
-    return sub;
-}
-
-UA_StatusCode
-UA_Client_Subscriptions_remove(UA_Client *client, UA_UInt32 subscriptionId) {
-    UA_Client_Subscription *sub = findSubscription(client, subscriptionId);
-    if(!sub)
-        return UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
-
-    /* remove the subscription from the list    */
-    /* will be reinserted after if error occurs */
-    LIST_REMOVE(sub, listEntry);
-
-    /* remove the subscription remotely */
-    UA_DeleteSubscriptionsRequest request;
-    UA_DeleteSubscriptionsRequest_init(&request);
-    request.subscriptionIdsSize = 1;
-    request.subscriptionIds = &sub->subscriptionID;
-    UA_DeleteSubscriptionsResponse response = UA_Client_Service_deleteSubscriptions(client, request);
-
-    UA_StatusCode retval = response.responseHeader.serviceResult;
-    if(retval == UA_STATUSCODE_GOOD && response.resultsSize > 0)
-        retval = response.results[0];
-    UA_DeleteSubscriptionsResponse_deleteMembers(&response);
-
-    if(retval != UA_STATUSCODE_GOOD && retval != UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID) {
-        /* error occurs re-insert the subscription in the list */
-        LIST_INSERT_HEAD(&client->subscriptions, sub, listEntry);
-        UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                    "Could not remove subscription %u with error code %s",
-                    sub->subscriptionID, UA_StatusCode_name(retval));
-        return retval;
-    }
-
-    /* remove the monitored items locally */
-    UA_Client_MonitoredItem *mon, *tmpmon;
-    LIST_FOREACH_SAFE(mon, &sub->monitoredItems, listEntry, tmpmon) {
-        UA_NodeId_deleteMembers(&mon->monitoredNodeId);
-        LIST_REMOVE(mon, listEntry);
-        UA_free(mon);
-    }
-    UA_free(sub);
-
-    return UA_STATUSCODE_GOOD;
-}
-
-void
-UA_Client_Subscriptions_forceDelete(UA_Client *client,
-                                    UA_Client_Subscription *sub) {
-    UA_Client_MonitoredItem *mon, *mon_tmp;
-    LIST_FOREACH_SAFE(mon, &sub->monitoredItems, listEntry, mon_tmp) {
-        UA_NodeId_deleteMembers(&mon->monitoredNodeId);
-        LIST_REMOVE(mon, listEntry);
-        UA_free(mon);
-    }
-    LIST_REMOVE(sub, listEntry);
-    UA_free(sub);
-}
-
-static UA_StatusCode
-addMonitoredItems(UA_Client *client, const UA_UInt32 subscriptionId,
-                  UA_MonitoredItemCreateRequest *items, size_t itemsSize,
-                  void **hfs, void **hfContexts, UA_StatusCode *itemResults,
-                  UA_UInt32 *newMonitoredItemIds, UA_Boolean isEventMonitoredItem) {
-    UA_Client_Subscription *sub = findSubscription(client, subscriptionId);
-    if(!sub)
-        return UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
-
-    UA_CreateMonitoredItemsRequest request;
-    UA_CreateMonitoredItemsRequest_init(&request);
-    UA_CreateMonitoredItemsResponse response;
-    UA_CreateMonitoredItemsResponse_init(&response);
-
-    /* Create the handlers */
-    UA_StatusCode retval = UA_STATUSCODE_GOOD;
-    UA_Client_MonitoredItem **mis = (UA_Client_MonitoredItem**)
-        UA_alloca(sizeof(void*) * itemsSize);
-    memset(mis, 0, sizeof(void*) * itemsSize);
-    for(size_t i = 0; i < itemsSize; i++) {
-        mis[i] = (UA_Client_MonitoredItem*)UA_malloc(sizeof(UA_Client_MonitoredItem));
-        if(!mis[i]) {
-            retval = UA_STATUSCODE_BADOUTOFMEMORY;
-            goto cleanup;
-        }
-    }
-
-    /* Set the clientHandle */
-    for(size_t i = 0; i < itemsSize; i++)
-        items[i].requestedParameters.clientHandle = ++(client->monitoredItemHandles);
-
-    /* Initialize the request */
-    request.subscriptionId = subscriptionId;
-    request.itemsToCreate = items;
-    request.itemsToCreateSize = itemsSize;
-
-    /* Send the request */
-    response = UA_Client_Service_createMonitoredItems(client, request);
-
-    /* Remove for _deleteMembers */
-    request.itemsToCreate = NULL;
-    request.itemsToCreateSize = 0;
-
-    retval = response.responseHeader.serviceResult;
-    if(retval != UA_STATUSCODE_GOOD)
-        goto cleanup;
-
-    if(response.resultsSize != itemsSize) {
-        retval = UA_STATUSCODE_BADINTERNALERROR;
-        goto cleanup;
-    }
-
-    for(size_t i = 0; i < itemsSize; i++) {
-        UA_MonitoredItemCreateResult *result = &response.results[i];
-        UA_Client_MonitoredItem *newMon = mis[i];
-
-        itemResults[i] = result->statusCode;
-        if(result->statusCode != UA_STATUSCODE_GOOD) {
-            newMonitoredItemIds[i] = 0;
-            UA_free(newMon);
-            continue;
-        }
-
-        /* Set the internal representation */
-        newMon->monitoringMode = UA_MONITORINGMODE_REPORTING;
-        UA_NodeId_copy(&items[i].itemToMonitor.nodeId, &newMon->monitoredNodeId);
-        newMon->attributeID = items[i].itemToMonitor.attributeId;
-        newMon->clientHandle = items[i].requestedParameters.clientHandle;
-        newMon->samplingInterval = result->revisedSamplingInterval;
-        newMon->queueSize = result->revisedQueueSize;
-        newMon->discardOldest = items[i].requestedParameters.discardOldest;
-        newMon->monitoredItemId = response.results[i].monitoredItemId;
-        newMon->isEventMonitoredItem = isEventMonitoredItem;
-        /* eventHandler is at the same position in the union */
-        newMon->handler.dataChangeHandler = (UA_MonitoredItemHandlingFunction)(uintptr_t)hfs[i];
-        newMon->handlerContext = hfContexts[i];
-
-        LIST_INSERT_HEAD(&sub->monitoredItems, newMon, listEntry);
-        newMonitoredItemIds[i] = newMon->monitoredItemId;
-        UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                     "Created a monitored item with client handle %u",
-                     newMon->clientHandle);
-    }
-
- cleanup:
-    if(retval != UA_STATUSCODE_GOOD) {
-        for(size_t i = 0; i < itemsSize; i++)
-            UA_free(mis[i]);
-    }
-    UA_CreateMonitoredItemsRequest_deleteMembers(&request);
-    UA_CreateMonitoredItemsResponse_deleteMembers(&response);
-    return retval;
-}
-
-UA_StatusCode
-UA_Client_Subscriptions_addMonitoredItems(UA_Client *client, const UA_UInt32 subscriptionId,
-                                          UA_MonitoredItemCreateRequest *items, size_t itemsSize,
-                                          UA_MonitoredItemHandlingFunction *hfs,
-                                          void **hfContexts, UA_StatusCode *itemResults,
-                                          UA_UInt32 *newMonitoredItemIds) {
-    return addMonitoredItems(client, subscriptionId, items, itemsSize, (void**)hfs,
-                             hfContexts, itemResults, newMonitoredItemIds, false);
-}
-
-UA_StatusCode UA_EXPORT
-UA_Client_Subscriptions_addMonitoredItem(UA_Client *client, UA_UInt32 subscriptionId,
-                                         UA_NodeId nodeId, UA_UInt32 attributeID,
-                                         UA_MonitoredItemHandlingFunction hf, void *hfContext,
-                                         UA_UInt32 *newMonitoredItemId, UA_Double samplingInterval) {
-    UA_MonitoredItemCreateRequest item;
-    UA_MonitoredItemCreateRequest_init(&item);
-    item.itemToMonitor.nodeId = nodeId;
-    item.itemToMonitor.attributeId = attributeID;
-    item.monitoringMode = UA_MONITORINGMODE_REPORTING;
-    item.requestedParameters.samplingInterval = samplingInterval;
-    item.requestedParameters.discardOldest = true;
-    item.requestedParameters.queueSize = 1;
-
-    UA_StatusCode retval_item = UA_STATUSCODE_GOOD;
-    UA_StatusCode retval = addMonitoredItems(client, subscriptionId, &item, 1,
-                                             (void**)(uintptr_t)&hf, &hfContext,
-                                             &retval_item, newMonitoredItemId, false);
-    return retval | retval_item;
-}
-
-UA_StatusCode
-UA_Client_Subscriptions_addMonitoredEvents(UA_Client *client, const UA_UInt32 subscriptionId,
-                                           UA_MonitoredItemCreateRequest *items, size_t itemsSize,
-                                           UA_MonitoredEventHandlingFunction *hfs,
-                                           void **hfContexts, UA_StatusCode *itemResults,
-                                           UA_UInt32 *newMonitoredItemIds) {
-    return addMonitoredItems(client, subscriptionId, items, itemsSize, (void**)hfs,
-                             hfContexts, itemResults, newMonitoredItemIds, true);
-}
-
-UA_StatusCode
-UA_Client_Subscriptions_addMonitoredEvent(UA_Client *client, UA_UInt32 subscriptionId,
-                                          const UA_NodeId nodeId, UA_UInt32 attributeID,
-                                          const UA_SimpleAttributeOperand *selectClauses,
-                                          size_t selectClausesSize,
-                                          const UA_ContentFilterElement *whereClauses,
-                                          size_t whereClausesSize,
-                                          const UA_MonitoredEventHandlingFunction hf,
-                                          void *hfContext, UA_UInt32 *newMonitoredItemId) {
-    UA_MonitoredItemCreateRequest item;
-    UA_MonitoredItemCreateRequest_init(&item);
-    item.itemToMonitor.nodeId = nodeId;
-    item.itemToMonitor.attributeId = attributeID;
-    item.monitoringMode = UA_MONITORINGMODE_REPORTING;
-    item.requestedParameters.samplingInterval = 0;
-    item.requestedParameters.discardOldest = false;
-
-    UA_EventFilter *evFilter = UA_EventFilter_new();
-    if(!evFilter)
-        return UA_STATUSCODE_BADOUTOFMEMORY;
-    UA_EventFilter_init(evFilter);
-    evFilter->selectClausesSize = selectClausesSize;
-    evFilter->selectClauses = (UA_SimpleAttributeOperand*)(uintptr_t)selectClauses;
-    evFilter->whereClause.elementsSize = whereClausesSize;
-    evFilter->whereClause.elements = (UA_ContentFilterElement*)(uintptr_t)whereClauses;
-
-    item.requestedParameters.filter.encoding = UA_EXTENSIONOBJECT_DECODED_NODELETE;
-    item.requestedParameters.filter.content.decoded.type = &UA_TYPES[UA_TYPES_EVENTFILTER];
-    item.requestedParameters.filter.content.decoded.data = evFilter;
-    UA_StatusCode retval_item = UA_STATUSCODE_GOOD;
-    UA_StatusCode retval = addMonitoredItems(client, subscriptionId, &item, 1,
-                                             (void**)(uintptr_t)&hf, &hfContext,
-                                             &retval_item, newMonitoredItemId, true);
-    UA_free(evFilter);
-    return retval | retval_item;
-}
-
-UA_StatusCode
-UA_Client_Subscriptions_removeMonitoredItems(UA_Client *client, UA_UInt32 subscriptionId,
-                                            UA_UInt32 *monitoredItemId, size_t itemsSize,
-                                            UA_StatusCode *itemResults) {
-    UA_Client_Subscription *sub = findSubscription(client, subscriptionId);
-    if(!sub)
-        return UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
-
-    /* remove the monitoreditem remotely */
-    UA_DeleteMonitoredItemsRequest request;
-    UA_DeleteMonitoredItemsRequest_init(&request);
-    request.subscriptionId = sub->subscriptionID;
-    request.monitoredItemIdsSize = itemsSize;
-    request.monitoredItemIds = monitoredItemId;
-    UA_DeleteMonitoredItemsResponse response = UA_Client_Service_deleteMonitoredItems(client, request);
-
-    UA_StatusCode retval = response.responseHeader.serviceResult;
-    if(retval != UA_STATUSCODE_GOOD)
-        goto cleanup;
-
-    if(response.resultsSize != itemsSize) {
-        retval = UA_STATUSCODE_BADINTERNALERROR;
-        goto cleanup;
-    }
-
-    for(size_t i = 0; i < itemsSize; i++) {
-        itemResults[i] = response.results[i];
-        if(response.results[i] != UA_STATUSCODE_GOOD &&
-           response.results[i] != UA_STATUSCODE_BADMONITOREDITEMIDINVALID) {
-            UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                        "Could not remove monitoreditem %u with error code %s",
-                        monitoredItemId[i], UA_StatusCode_name(response.results[i]));
-            continue;
-        }
-        UA_Client_MonitoredItem *mon;
-        LIST_FOREACH(mon, &sub->monitoredItems, listEntry) {
-            if(mon->monitoredItemId == monitoredItemId[i]) {
-                LIST_REMOVE(mon, listEntry);
-                UA_NodeId_deleteMembers(&mon->monitoredNodeId);
-                UA_free(mon);
-                break;
-            }
-        }
-    }
-
- cleanup:
-    /* Remove for _deleteMembers */
-    request.monitoredItemIdsSize = 0;
-    request.monitoredItemIds = NULL;
-
-    UA_DeleteMonitoredItemsRequest_deleteMembers(&request);
-    UA_DeleteMonitoredItemsResponse_deleteMembers(&response);
-
-    return retval;
-}
-
-UA_StatusCode
-UA_Client_Subscriptions_removeMonitoredItem(UA_Client *client, UA_UInt32 subscriptionId,
-                                            UA_UInt32 monitoredItemId) {
-    UA_StatusCode retval_item = UA_STATUSCODE_GOOD;
-    UA_StatusCode retval = UA_Client_Subscriptions_removeMonitoredItems(client, subscriptionId,
-                                                                        &monitoredItemId, 1,
-                                                                        &retval_item);
-    return retval | retval_item;
-}
-
-/* Assume the request is already initialized */
-static UA_StatusCode
-UA_Client_preparePublishRequest(UA_Client *client, UA_PublishRequest *request) {
-    /* Count acks */
-    UA_Client_NotificationsAckNumber *ack;
-    LIST_FOREACH(ack, &client->pendingNotificationsAcks, listEntry)
-        ++request->subscriptionAcknowledgementsSize;
-
-    /* Create the array. Returns a sentinel pointer if the length is zero. */
-    request->subscriptionAcknowledgements = (UA_SubscriptionAcknowledgement*)
-        UA_Array_new(request->subscriptionAcknowledgementsSize,
-                     &UA_TYPES[UA_TYPES_SUBSCRIPTIONACKNOWLEDGEMENT]);
-    if(!request->subscriptionAcknowledgements) {
-        request->subscriptionAcknowledgementsSize = 0;
-        return UA_STATUSCODE_BADOUTOFMEMORY;
-    }
-
-    size_t i = 0;
-    UA_Client_NotificationsAckNumber *ack_tmp;
-    LIST_FOREACH_SAFE(ack, &client->pendingNotificationsAcks, listEntry, ack_tmp) {
-        request->subscriptionAcknowledgements[i].sequenceNumber = ack->subAck.sequenceNumber;
-        request->subscriptionAcknowledgements[i].subscriptionId = ack->subAck.subscriptionId;
-        ++i;
-        LIST_REMOVE(ack, listEntry);
-        UA_free(ack);
-    }
-    return UA_STATUSCODE_GOOD;
-}
-
-/* According to OPC Unified Architecture, Part 4 5.13.1.1 i) */
-/* The value 0 is never used for the sequence number         */
-static UA_UInt32
-UA_Client_Subscriptions_nextSequenceNumber(UA_UInt32 sequenceNumber) {
-    UA_UInt32 nextSequenceNumber = sequenceNumber + 1;
-    if(nextSequenceNumber == 0)
-        nextSequenceNumber = 1;
-    return nextSequenceNumber;
-}
-
-static void
-processDataChangeNotification(UA_Client *client, UA_Client_Subscription *sub,
-                              UA_DataChangeNotification *dataChangeNotification) {
-    for(size_t j = 0; j < dataChangeNotification->monitoredItemsSize; ++j) {
-        UA_MonitoredItemNotification *mitemNot = &dataChangeNotification->monitoredItems[j];
-
-        /* Find the MonitoredItem */
-        UA_Client_MonitoredItem *mon;
-        LIST_FOREACH(mon, &sub->monitoredItems, listEntry) {
-            if(mon->clientHandle == mitemNot->clientHandle)
-                break;
-        }
-
-        if(!mon) {
-            UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                         "Could not process a notification with clienthandle %u on subscription %u",
-                         mitemNot->clientHandle, sub->subscriptionID);
-            continue;
-        }
-
-        if(mon->isEventMonitoredItem) {
-            UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                         "MonitoredItem is configured for Events. But received a "
-                         "DataChangeNotification.");
-            continue;
-        }
-
-        mon->handler.dataChangeHandler(client, mon->monitoredItemId,
-                                       &mitemNot->value, mon->handlerContext);
-    }
-}
-
-static void
-processEventNotification(UA_Client *client, UA_Client_Subscription *sub,
-                         UA_EventNotificationList *eventNotificationList) {
-    for(size_t j = 0; j < eventNotificationList->eventsSize; ++j) {
-        UA_EventFieldList *eventFieldList = &eventNotificationList->events[j];
-
-        /* Find the MonitoredItem */
-        UA_Client_MonitoredItem *mon;
-        LIST_FOREACH(mon, &sub->monitoredItems, listEntry) {
-            if(mon->clientHandle == eventFieldList->clientHandle)
-                break;
-        }
-
-        if(!mon) {
-            UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                         "Could not process a notification with clienthandle %u on subscription %u",
-                         eventFieldList->clientHandle, sub->subscriptionID);
-            continue;
-        }
-
-        if(!mon->isEventMonitoredItem) {
-            UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                         "MonitoredItem is configured for DataChanges. But received a "
-                         "EventNotification.");
-            continue;
-        }
-
-        mon->handler.eventHandler(client, mon->monitoredItemId, eventFieldList->eventFieldsSize,
-                                  eventFieldList->eventFields, mon->handlerContext);
-    }
-}
-
-
-static void
-processNotificationMessage(UA_Client *client, UA_Client_Subscription *sub,
-                           UA_ExtensionObject *msg) {
-    if(msg->encoding != UA_EXTENSIONOBJECT_DECODED)
-        return;
-
-    /* Handle DataChangeNotification */
-    if(msg->content.decoded.type == &UA_TYPES[UA_TYPES_DATACHANGENOTIFICATION]) {
-        UA_DataChangeNotification *dataChangeNotification =
-            (UA_DataChangeNotification *)msg->content.decoded.data;
-        processDataChangeNotification(client, sub, dataChangeNotification);
-        return;
-    }
-
-    /* Handle EventNotification */
-    if(msg->content.decoded.type == &UA_TYPES[UA_TYPES_EVENTNOTIFICATIONLIST]) {
-        UA_EventNotificationList *eventNotificationList =
-            (UA_EventNotificationList *)msg->content.decoded.data;
-        processEventNotification(client, sub, eventNotificationList);
-        return;
-    }
-}
-
-static void
-processPublishResponse(UA_Client *client, UA_PublishRequest *request,
-                       UA_PublishResponse *response) {
-    UA_NotificationMessage *msg = &response->notificationMessage;
-
-    client->currentlyOutStandingPublishRequests--;
-
-    if(response->responseHeader.serviceResult == UA_STATUSCODE_BADTOOMANYPUBLISHREQUESTS){
-        if(client->config.outStandingPublishRequests > 1){
-            client->config.outStandingPublishRequests--;
-            UA_LOG_WARNING(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                          "Too many publishrequest, we reduce outStandingPublishRequests to %d",
-                           client->config.outStandingPublishRequests);
-        } else {
-            UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                         "Too many publishrequest when outStandingPublishRequests = 1");
-            UA_Client_close(client);
-        }
-        goto cleanup;
-    }
-
-    if(response->responseHeader.serviceResult == UA_STATUSCODE_BADNOSUBSCRIPTION){
-       if(LIST_FIRST(&client->subscriptions)){
-            UA_Client_close(client);
-            UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                         "PublishRequest error : No subscription");
-        }
-        goto cleanup;
-    }
-
-    if(response->responseHeader.serviceResult == UA_STATUSCODE_BADSESSIONIDINVALID){
-        UA_Client_close(client);
-        UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                         "Received BadSessionIdInvalid");
-        goto cleanup;
-    }
-
-    UA_Client_Subscription *sub = findSubscription(client, response->subscriptionId);
-    if(!sub)
-        goto cleanup;
-    sub->lastActivity = UA_DateTime_nowMonotonic();
-
-
-    if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD)
-        goto cleanup;
-
-    /* Detect missing message - OPC Unified Architecture, Part 4 5.13.1.1 e) */
-    if((sub->sequenceNumber != msg->sequenceNumber) &&
-        (UA_Client_Subscriptions_nextSequenceNumber(sub->sequenceNumber) != msg->sequenceNumber)) {
-        UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                     "Invalid subscritpion sequenceNumber");
-        UA_Client_close(client);
-        goto cleanup;
-    }
-    sub->sequenceNumber = msg->sequenceNumber;
-
-    /* Process the notification messages */
-    for(size_t k = 0; k < msg->notificationDataSize; ++k)
-        processNotificationMessage(client, sub, &msg->notificationData[k]);
-
-    /* Add to the list of pending acks */
-    for(size_t i = 0; i < response->availableSequenceNumbersSize; i++) {
-        if(response->availableSequenceNumbers[i] != msg->sequenceNumber)
-            continue;
-        UA_Client_NotificationsAckNumber *tmpAck = (UA_Client_NotificationsAckNumber*)
-            UA_malloc(sizeof(UA_Client_NotificationsAckNumber));
-        if(!tmpAck) {
-            UA_LOG_WARNING(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                           "Not enough memory to store the acknowledgement for a publish "
-                           "message on subscription %u", sub->subscriptionID);
-            break;
-        }   
-        tmpAck->subAck.sequenceNumber = msg->sequenceNumber;
-        tmpAck->subAck.subscriptionId = sub->subscriptionID;
-        LIST_INSERT_HEAD(&client->pendingNotificationsAcks, tmpAck, listEntry);
-        break;
-    } 
-
- cleanup:
-    UA_PublishRequest_deleteMembers(request);
-}
-
-static void
-processPublishResponseAsync(UA_Client *client, void *userdata, UA_UInt32 requestId,
-                            void *response, const UA_DataType *responseType) {
-    UA_PublishRequest *req = (UA_PublishRequest*)userdata;
-    UA_PublishResponse *res = (UA_PublishResponse*)response;
-    processPublishResponse(client, req, res);
-    UA_PublishRequest_delete(req);
-    /* Fill up the outstanding publish requests */
-    UA_Client_Subscriptions_backgroundPublish(client);
-}
-
-UA_StatusCode
-UA_Client_Subscriptions_manuallySendPublishRequest(UA_Client *client) {
-    if(client->state < UA_CLIENTSTATE_SESSION)
-        return UA_STATUSCODE_BADSERVERNOTCONNECTED;
-
-    UA_StatusCode retval = UA_STATUSCODE_GOOD;
-
-    UA_DateTime now = UA_DateTime_nowMonotonic();
-    UA_DateTime maxDate = now + (UA_DateTime)(client->config.timeout * UA_DATETIME_MSEC);
-
-    UA_Boolean moreNotifications = true;
-    while(moreNotifications) {
-        UA_PublishRequest request;
-        UA_PublishRequest_init(&request);
-        retval = UA_Client_preparePublishRequest(client, &request);
-        if(retval != UA_STATUSCODE_GOOD)
-            return retval;
-
-        /* Manually increase the number of sent publish requests. Otherwise we
-         * send out one too many when we process async responses when we wait
-         * for the correct publish response. The
-         * currentlyOutStandingPublishRequests will be reduced during processing
-         * of the response. */
-        client->currentlyOutStandingPublishRequests++;
-
-        UA_PublishResponse response = UA_Client_Service_publish(client, request);
-        processPublishResponse(client, &request, &response);
-        
-        now = UA_DateTime_nowMonotonic();
-        if(now > maxDate) {
-            moreNotifications = UA_FALSE;
-            retval = UA_STATUSCODE_GOODNONCRITICALTIMEOUT;
-        } else {
-            moreNotifications = response.moreNotifications;
-        }
-
-        UA_PublishResponse_deleteMembers(&response);
-        UA_PublishRequest_deleteMembers(&request);
-    }
-
-    if(client->state < UA_CLIENTSTATE_SESSION)
-        return UA_STATUSCODE_BADSERVERNOTCONNECTED;
-
-    return retval;
-}
-
-void
-UA_Client_Subscriptions_clean(UA_Client *client) {
-    UA_Client_NotificationsAckNumber *n, *tmp;
-    LIST_FOREACH_SAFE(n, &client->pendingNotificationsAcks, listEntry, tmp) {
-        LIST_REMOVE(n, listEntry);
-        UA_free(n);
-    }
-
-    UA_Client_Subscription *sub, *tmps;
-    LIST_FOREACH_SAFE(sub, &client->subscriptions, listEntry, tmps)
-        UA_Client_Subscriptions_forceDelete(client, sub); /* force local removal */
-}
-
-UA_StatusCode
-UA_Client_Subscriptions_backgroundPublish(UA_Client *client) {
-    if(client->state < UA_CLIENTSTATE_SESSION)
-        return UA_STATUSCODE_BADSERVERNOTCONNECTED;
-
-    /* The session must have at least one subscription */
-    if(!LIST_FIRST(&client->subscriptions))
-        return UA_STATUSCODE_GOOD;
-
-    while(client->currentlyOutStandingPublishRequests < client->config.outStandingPublishRequests) {
-        UA_PublishRequest *request = UA_PublishRequest_new();
-        if (!request)
-            return UA_STATUSCODE_BADOUTOFMEMORY;
-
-        UA_StatusCode retval = UA_Client_preparePublishRequest(client, request);
-        if(retval != UA_STATUSCODE_GOOD) {
-            UA_PublishRequest_delete(request);
-            return retval;
-        }
-    
-        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);
-        if(retval != UA_STATUSCODE_GOOD) {
-            UA_PublishRequest_delete(request);
-            return retval;
-        }
-    }
-
-    /* Check subscriptions inactivity */
-    UA_Client_Subscription *sub;
-    LIST_FOREACH(sub, &client->subscriptions, listEntry) {
-        if(((UA_DateTime)(sub->publishingInterval * sub->keepAliveCount + client->config.timeout) * 
-             UA_DATETIME_MSEC + sub->lastActivity) < UA_DateTime_nowMonotonic()) {
-            UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_CLIENT,
-                         "Inactivity for Subscription %d. Closing the connection.",
-                         sub->subscriptionID);
-            UA_Client_close(client);
-            return UA_STATUSCODE_BADCONNECTIONCLOSED;
-        }
-    }
-
-    return UA_STATUSCODE_GOOD;
-}
-
-#endif /* UA_ENABLE_SUBSCRIPTIONS */

+ 33 - 24
src/client/ua_client_internal.h

@@ -15,11 +15,12 @@
 
 #include "ua_securechannel.h"
 #include "ua_client_highlevel.h"
+#include "ua_client_subscriptions.h"
 #include "../../deps/queue.h"
 
- /**************************/
- /* Subscriptions Handling */
- /**************************/
+/**************************/
+/* Subscriptions Handling */
+/**************************/
 
 #ifdef UA_ENABLE_SUBSCRIPTIONS
 
@@ -29,45 +30,53 @@ typedef struct UA_Client_NotificationsAckNumber {
 } UA_Client_NotificationsAckNumber;
 
 typedef struct UA_Client_MonitoredItem {
-    LIST_ENTRY(UA_Client_MonitoredItem)  listEntry;
+    LIST_ENTRY(UA_Client_MonitoredItem) listEntry;
     UA_UInt32 monitoredItemId;
-    UA_UInt32 monitoringMode;
-    UA_NodeId monitoredNodeId;
-    UA_UInt32 attributeID;
     UA_UInt32 clientHandle;
-    UA_Double samplingInterval;
-    UA_UInt32 queueSize;
-    UA_Boolean discardOldest;
-
-    UA_Boolean isEventMonitoredItem; /* Otherwise a DataChange MoniitoredItem */
+    void *context;
+    UA_Client_DeleteMonitoredItemCallback deleteCallback;
     union {
-        UA_MonitoredItemHandlingFunction dataChangeHandler;
-        UA_MonitoredEventHandlingFunction eventHandler;
+        UA_Client_DataChangeNotificationCallback dataChangeCallback;
+        UA_Client_EventNotificationCallback eventCallback;
     } handler;
-    void *handlerContext;
+    UA_Boolean isEventMonitoredItem; /* Otherwise a DataChange MoniitoredItem */
 } UA_Client_MonitoredItem;
 
 typedef struct UA_Client_Subscription {
     LIST_ENTRY(UA_Client_Subscription) listEntry;
-    UA_UInt32 lifeTime;
-    UA_UInt32 keepAliveCount;
+    UA_UInt32 subscriptionId;
+    void *context;
     UA_Double publishingInterval;
-    UA_UInt32 subscriptionID;
-    UA_UInt32 notificationsPerPublish;
-    UA_UInt32 priority;
+    UA_UInt32 maxKeepAliveCount;
+    UA_Client_StatusChangeNotificationCallback statusChangeCallback;
+    UA_Client_DeleteSubscriptionCallback deleteCallback;
     UA_UInt32 sequenceNumber;
     UA_DateTime lastActivity;
     LIST_HEAD(UA_ListOfClientMonitoredItems, UA_Client_MonitoredItem) monitoredItems;
 } UA_Client_Subscription;
 
-void UA_Client_Subscriptions_forceDelete(UA_Client *client, UA_Client_Subscription *sub);
+void
+UA_Client_Subscriptions_clean(UA_Client *client);
+
+void
+UA_Client_MonitoredItem_remove(UA_Client *client, UA_Client_Subscription *sub,
+                               UA_Client_MonitoredItem *mon);
+
+void
+UA_Client_Subscriptions_processPublishResponse(UA_Client *client,
+                                               UA_PublishRequest *request,
+                                               UA_PublishResponse *response);
 
-void UA_Client_Subscriptions_clean(UA_Client *client);
+UA_StatusCode
+UA_Client_preparePublishRequest(UA_Client *client, UA_PublishRequest *request);
 
 UA_StatusCode
 UA_Client_Subscriptions_backgroundPublish(UA_Client *client);
 
-#endif
+void
+UA_Client_Subscriptions_backgroundPublishInactivityCheck(UA_Client *client);
+
+#endif /* UA_ENABLE_SUBSCRIPTIONS */
 
 /**********/
 /* Client */
@@ -102,7 +111,7 @@ struct UA_Client {
     UA_String endpointUrl;
 
     /* SecureChannel */
-    UA_SecurityPolicy securityPolicy;
+    UA_SecurityPolicy securityPolicy; /* TODO: Move supported policies to the config */
     UA_SecureChannel channel;
     UA_UInt32 requestId;
     UA_DateTime nextChannelRenewal;

+ 764 - 0
src/client/ua_client_subscriptions.c

@@ -0,0 +1,764 @@
+/* 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 2015-2018 (c) Julius Pfrommer, Fraunhofer IOSB
+ *    Copyright 2015 (c) Oleksiy Vasylyev
+ *    Copyright 2016 (c) Sten Grüner
+ *    Copyright 2017-2018 (c) Thomas Stalder, Blue Time Concept SA
+ *    Copyright 2016-2017 (c) Florian Palm
+ *    Copyright 2017 (c) Frank Meerkötter
+ *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
+ */
+
+#include "ua_client_highlevel.h"
+#include "ua_client_internal.h"
+#include "ua_util.h"
+
+#ifdef UA_ENABLE_SUBSCRIPTIONS /* conditional compilation */
+
+/*****************/
+/* Subscriptions */
+/*****************/
+
+UA_CreateSubscriptionResponse UA_EXPORT
+UA_Client_Subscriptions_create(UA_Client *client,
+                               const UA_CreateSubscriptionRequest request,
+                               void *subscriptionContext,
+                               UA_Client_StatusChangeNotificationCallback statusChangeCallback,
+                               UA_Client_DeleteSubscriptionCallback deleteCallback) {
+    UA_CreateSubscriptionResponse response;
+    UA_CreateSubscriptionResponse_init(&response);
+    
+    /* Allocate the internal representation */
+    UA_Client_Subscription *newSub = (UA_Client_Subscription*)
+        UA_malloc(sizeof(UA_Client_Subscription));
+    if(!newSub) {
+        response.responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+        return response;
+    }
+
+    /* Send the request as a synchronous service call */
+    __UA_Client_Service(client,
+                        &request, &UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONRESPONSE]);
+    if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+        UA_free(newSub);
+        return response;
+    }
+
+    /* Prepare the internal representation */
+    newSub->context = subscriptionContext;
+    newSub->subscriptionId = response.subscriptionId;
+    newSub->sequenceNumber = 0;
+    newSub->lastActivity = UA_DateTime_nowMonotonic();
+    newSub->statusChangeCallback = statusChangeCallback;
+    newSub->deleteCallback = deleteCallback;
+    newSub->publishingInterval = response.revisedPublishingInterval;
+    newSub->maxKeepAliveCount = response.revisedMaxKeepAliveCount;
+    LIST_INIT(&newSub->monitoredItems);
+    LIST_INSERT_HEAD(&client->subscriptions, newSub, listEntry);
+
+    return response;
+}
+
+static UA_Client_Subscription *
+findSubscription(const UA_Client *client, UA_UInt32 subscriptionId) {
+    UA_Client_Subscription *sub = NULL;
+    LIST_FOREACH(sub, &client->subscriptions, listEntry) {
+        if(sub->subscriptionId == subscriptionId)
+            break;
+    }
+    return sub;
+}
+
+UA_ModifySubscriptionResponse UA_EXPORT
+UA_Client_Subscriptions_modify(UA_Client *client, const UA_ModifySubscriptionRequest request) {
+    UA_ModifySubscriptionResponse response;
+    UA_ModifySubscriptionResponse_init(&response);
+
+    /* Find the internal representation */
+    UA_Client_Subscription *sub = findSubscription(client, request.subscriptionId);
+    if(!sub) {
+        response.responseHeader.serviceResult = UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
+        return response;
+    }
+    
+    /* Call the service */
+    __UA_Client_Service(client,
+                        &request, &UA_TYPES[UA_TYPES_MODIFYSUBSCRIPTIONREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_MODIFYSUBSCRIPTIONRESPONSE]);
+
+    /* Adjust the internal representation */
+    sub->publishingInterval = response.revisedPublishingInterval;
+    sub->maxKeepAliveCount = response.revisedMaxKeepAliveCount;
+    return response;
+}
+
+static void
+UA_Client_Subscription_deleteInternal(UA_Client *client, UA_Client_Subscription *sub) {
+    /* Remove the MonitoredItems */
+    UA_Client_MonitoredItem *mon, *mon_tmp;
+    LIST_FOREACH_SAFE(mon, &sub->monitoredItems, listEntry, mon_tmp)
+        UA_Client_MonitoredItem_remove(client, sub, mon);
+
+    /* Call the delete callback */
+    if(sub->deleteCallback)
+        sub->deleteCallback(client, sub->subscriptionId, sub->context);
+
+    /* Remove */
+    LIST_REMOVE(sub, listEntry);
+    UA_free(sub);
+}
+
+UA_DeleteSubscriptionsResponse UA_EXPORT
+UA_Client_Subscriptions_delete(UA_Client *client, const UA_DeleteSubscriptionsRequest request) {
+    UA_Client_Subscription **subs = (UA_Client_Subscription**)
+        UA_alloca(sizeof(void*) * request.subscriptionIdsSize);
+    memset(subs, 0, sizeof(void*) * request.subscriptionIdsSize);
+
+    /* temporary remove the subscriptions from the list */
+    for(size_t i = 0; i < request.subscriptionIdsSize; i++) {
+        subs[i] = findSubscription(client, request.subscriptionIds[i]);
+        if (subs[i])
+            LIST_REMOVE(subs[i], listEntry);
+    }
+
+    /* Send the request */
+    UA_DeleteSubscriptionsResponse response;
+    __UA_Client_Service(client,
+                        &request, &UA_TYPES[UA_TYPES_DELETESUBSCRIPTIONSREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_DELETESUBSCRIPTIONSRESPONSE]);
+    if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD)
+        goto cleanup;
+
+    if(request.subscriptionIdsSize != response.resultsSize) {
+        response.responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR;
+        goto cleanup;
+    }
+
+    /* Loop over the removed subscriptions and remove internally */
+    for(size_t i = 0; i < request.subscriptionIdsSize; i++) {
+        if(response.results[i] != UA_STATUSCODE_GOOD && response.results[i] != UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID) {
+            /* Something was wrong, reinsert the subscription in the list */
+            if (subs[i])
+                LIST_INSERT_HEAD(&client->subscriptions, subs[i], listEntry);
+            continue;
+        }
+
+        if(!subs[i]) {
+            UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                        "No internal representation of subscription %u",
+                        request.subscriptionIds[i]);
+            continue;
+        } else {
+            LIST_INSERT_HEAD(&client->subscriptions, subs[i], listEntry);
+        }
+
+        UA_Client_Subscription_deleteInternal(client, subs[i]);
+    }
+
+    return response;
+
+cleanup:
+    for(size_t i = 0; i < request.subscriptionIdsSize; i++) {
+        if (subs[i]) {
+            LIST_INSERT_HEAD(&client->subscriptions, subs[i], listEntry);
+        }
+    }
+    return response;
+}
+
+UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_deleteSingle(UA_Client *client, UA_UInt32 subscriptionId) {
+    UA_DeleteSubscriptionsRequest request;
+    UA_DeleteSubscriptionsRequest_init(&request);
+    request.subscriptionIds = &subscriptionId;
+    request.subscriptionIdsSize = 1;
+    
+    UA_DeleteSubscriptionsResponse response =
+        UA_Client_Subscriptions_delete(client, request);
+
+    UA_StatusCode retval = response.responseHeader.serviceResult;
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_DeleteSubscriptionsResponse_deleteMembers(&response);
+        return retval;
+    }
+
+    if(response.resultsSize != 1) {
+        UA_DeleteSubscriptionsResponse_deleteMembers(&response);
+        return UA_STATUSCODE_BADINTERNALERROR;
+    }
+
+    retval = response.results[0];
+    UA_DeleteSubscriptionsResponse_deleteMembers(&response);
+    return retval;
+}
+
+/******************/
+/* MonitoredItems */
+/******************/
+
+void
+UA_Client_MonitoredItem_remove(UA_Client *client, UA_Client_Subscription *sub,
+                               UA_Client_MonitoredItem *mon) {
+    LIST_REMOVE(mon, listEntry);
+    if(mon->deleteCallback)
+        mon->deleteCallback(client, sub->subscriptionId, sub->context,
+                            mon->monitoredItemId, mon->context);
+    UA_free(mon);
+}
+
+static void
+__UA_Client_MonitoredItems_create(UA_Client *client,
+                                  const UA_CreateMonitoredItemsRequest *request,
+                                  void **contexts, void **handlingCallbacks,
+                                  UA_Client_DeleteMonitoredItemCallback *deleteCallbacks,
+                                  UA_CreateMonitoredItemsResponse *response) {
+    UA_CreateMonitoredItemsResponse_init(response);
+
+    if (!request->itemsToCreateSize) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR;
+        return;
+    }
+
+    /* Fix clang warning */
+    size_t itemsToCreateSize = request->itemsToCreateSize;
+    UA_Client_Subscription *sub = NULL;
+    
+    /* Allocate the memory for internal representations */
+    UA_Client_MonitoredItem **mis = (UA_Client_MonitoredItem**)
+        UA_alloca(sizeof(void*) * itemsToCreateSize);
+    memset(mis, 0, sizeof(void*) * itemsToCreateSize);
+    for(size_t i = 0; i < itemsToCreateSize; i++) {
+        mis[i] = (UA_Client_MonitoredItem*)UA_malloc(sizeof(UA_Client_MonitoredItem));
+        if(!mis[i]) {
+            response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+            goto cleanup;
+        }
+    }
+
+    /* Get the subscription */
+    sub = findSubscription(client, request->subscriptionId);
+    if(!sub) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
+        goto cleanup;
+    }
+
+    /* Set the clientHandle */
+    for(size_t i = 0; i < itemsToCreateSize; i++)
+        request->itemsToCreate[i].requestedParameters.clientHandle = ++(client->monitoredItemHandles);
+
+    /* Call the service */
+    __UA_Client_Service(client, request, &UA_TYPES[UA_TYPES_CREATEMONITOREDITEMSREQUEST],
+                        response, &UA_TYPES[UA_TYPES_CREATEMONITOREDITEMSRESPONSE]);
+    if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD)
+        goto cleanup;
+
+    if(response->resultsSize != itemsToCreateSize) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR;
+        goto cleanup;
+    }
+
+    /* Add internally */
+    for(size_t i = 0; i < itemsToCreateSize; i++) {
+        if(response->results[i].statusCode != UA_STATUSCODE_GOOD) {
+            if (deleteCallbacks[i])
+                deleteCallbacks[i](client, sub->subscriptionId, sub->context, 0, contexts[i]);
+            UA_free(mis[i]);
+            mis[i] = NULL;
+            continue;
+        }
+            
+        UA_Client_MonitoredItem *newMon = mis[i];
+        newMon->clientHandle = request->itemsToCreate[i].requestedParameters.clientHandle;
+        newMon->monitoredItemId = response->results[i].monitoredItemId;
+        newMon->context = contexts[i];
+        newMon->deleteCallback = deleteCallbacks[i];
+        newMon->handler.dataChangeCallback =
+            (UA_Client_DataChangeNotificationCallback)(uintptr_t)handlingCallbacks[i];
+        newMon->isEventMonitoredItem =
+            (request->itemsToCreate[i].itemToMonitor.attributeId == UA_ATTRIBUTEID_EVENTNOTIFIER);
+        LIST_INSERT_HEAD(&sub->monitoredItems, newMon, listEntry);
+    }
+
+    return;
+
+ cleanup:
+    for(size_t i = 0; i < itemsToCreateSize; i++) {
+        if (deleteCallbacks[i]) {
+            if (sub)
+                deleteCallbacks[i](client, sub->subscriptionId, sub->context, 0, contexts[i]);
+            else
+                deleteCallbacks[i](client, 0, NULL, 0, contexts[i]);
+        }
+        if(mis[i])
+            UA_free(mis[i]);
+    }
+}
+
+UA_CreateMonitoredItemsResponse UA_EXPORT
+UA_Client_MonitoredItems_createDataChanges(UA_Client *client,
+            const UA_CreateMonitoredItemsRequest request, void **contexts,
+            UA_Client_DataChangeNotificationCallback *callbacks,
+            UA_Client_DeleteMonitoredItemCallback *deleteCallbacks) {
+    UA_CreateMonitoredItemsResponse response;
+    __UA_Client_MonitoredItems_create(client, &request, contexts,
+                (void**)(uintptr_t)callbacks, deleteCallbacks, &response);
+    return response;
+}
+
+UA_MonitoredItemCreateResult UA_EXPORT
+UA_Client_MonitoredItems_createDataChange(UA_Client *client, UA_UInt32 subscriptionId,
+          UA_TimestampsToReturn timestampsToReturn, const UA_MonitoredItemCreateRequest item,
+          void *context, UA_Client_DataChangeNotificationCallback callback,
+          UA_Client_DeleteMonitoredItemCallback deleteCallback) {
+    UA_CreateMonitoredItemsRequest request;
+    UA_CreateMonitoredItemsRequest_init(&request);
+    request.subscriptionId = subscriptionId;
+    request.timestampsToReturn = timestampsToReturn;
+    request.itemsToCreate = (UA_MonitoredItemCreateRequest*)(uintptr_t)&item;
+    request.itemsToCreateSize = 1;
+    UA_CreateMonitoredItemsResponse response = 
+       UA_Client_MonitoredItems_createDataChanges(client, request, &context,
+                                                   &callback, &deleteCallback);
+    UA_MonitoredItemCreateResult result;
+    UA_MonitoredItemCreateResult_init(&result);
+    if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD)
+        result.statusCode = response.responseHeader.serviceResult;
+
+    if(result.statusCode == UA_STATUSCODE_GOOD &&
+       response.resultsSize != 1)
+        result.statusCode = UA_STATUSCODE_BADINTERNALERROR;
+    
+    if(result.statusCode == UA_STATUSCODE_GOOD)
+       UA_MonitoredItemCreateResult_copy(&response.results[0] , &result);
+    UA_CreateMonitoredItemsResponse_deleteMembers(&response);
+    return result;
+}
+
+UA_CreateMonitoredItemsResponse UA_EXPORT
+UA_Client_MonitoredItems_createEvents(UA_Client *client,
+            const UA_CreateMonitoredItemsRequest request, void **contexts,
+            UA_Client_EventNotificationCallback *callbacks,
+            UA_Client_DeleteMonitoredItemCallback *deleteCallbacks) {
+    UA_CreateMonitoredItemsResponse response;
+    __UA_Client_MonitoredItems_create(client, &request, contexts,
+                (void**)(uintptr_t)callbacks, deleteCallbacks, &response);
+    return response;
+}
+
+UA_MonitoredItemCreateResult UA_EXPORT
+UA_Client_MonitoredItems_createEvent(UA_Client *client, UA_UInt32 subscriptionId,
+          UA_TimestampsToReturn timestampsToReturn, const UA_MonitoredItemCreateRequest item,
+          void *context, UA_Client_EventNotificationCallback callback,
+          UA_Client_DeleteMonitoredItemCallback deleteCallback) {
+    UA_CreateMonitoredItemsRequest request;
+    UA_CreateMonitoredItemsRequest_init(&request);
+    request.subscriptionId = subscriptionId;
+    request.timestampsToReturn = timestampsToReturn;
+    request.itemsToCreate = (UA_MonitoredItemCreateRequest*)(uintptr_t)&item;
+    request.itemsToCreateSize = 1;
+    UA_CreateMonitoredItemsResponse response = 
+       UA_Client_MonitoredItems_createEvents(client, request, &context,
+                                             &callback, &deleteCallback);
+    UA_MonitoredItemCreateResult result;
+    UA_MonitoredItemCreateResult_copy(response.results , &result);
+    UA_CreateMonitoredItemsResponse_deleteMembers(&response);
+    return result;
+}
+
+UA_DeleteMonitoredItemsResponse UA_EXPORT
+UA_Client_MonitoredItems_delete(UA_Client *client, const UA_DeleteMonitoredItemsRequest request) {
+    /* Send the request */
+    UA_DeleteMonitoredItemsResponse response;
+    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_DELETEMONITOREDITEMSREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_DELETEMONITOREDITEMSRESPONSE]);
+    if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD)
+        return response;
+
+    UA_Client_Subscription *sub = findSubscription(client, request.subscriptionId);
+    if(!sub) {
+        UA_LOG_INFO(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                    "No internal representation of subscription %u",
+                    request.subscriptionId);
+        return response;
+    }
+
+    /* Loop over deleted MonitoredItems */
+    for(size_t i = 0; i < response.resultsSize; i++) {
+        if(response.results[i] != UA_STATUSCODE_GOOD &&
+           response.results[i] != UA_STATUSCODE_BADMONITOREDITEMIDINVALID) {
+            continue;
+        }
+
+#ifndef __clang_analyzer__
+        /* Delete the internal representation */
+        UA_Client_MonitoredItem *mon;
+        LIST_FOREACH(mon, &sub->monitoredItems, listEntry) {
+            if(mon->monitoredItemId == request.monitoredItemIds[i]) {
+                UA_Client_MonitoredItem_remove(client, sub, mon);
+                break;
+            }
+        }
+#endif
+    }
+
+    return response;
+}
+
+UA_StatusCode UA_EXPORT
+UA_Client_MonitoredItems_deleteSingle(UA_Client *client, UA_UInt32 subscriptionId, UA_UInt32 monitoredItemId) {
+    UA_DeleteMonitoredItemsRequest request;
+    UA_DeleteMonitoredItemsRequest_init(&request);
+    request.subscriptionId = subscriptionId;
+    request.monitoredItemIds = &monitoredItemId;
+    request.monitoredItemIdsSize = 1;
+
+    UA_DeleteMonitoredItemsResponse response =
+        UA_Client_MonitoredItems_delete(client, request);
+
+    UA_StatusCode retval = response.responseHeader.serviceResult;
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_DeleteMonitoredItemsResponse_deleteMembers(&response);
+        return retval;
+    }
+
+    if(response.resultsSize != 1) {
+        UA_DeleteMonitoredItemsResponse_deleteMembers(&response);
+        return UA_STATUSCODE_BADINTERNALERROR;
+    }
+
+    retval = response.results[0];
+    UA_DeleteMonitoredItemsResponse_deleteMembers(&response);
+    return retval;
+}
+
+/*************************************/
+/* Async Processing of Notifications */
+/*************************************/
+
+/* Assume the request is already initialized */
+UA_StatusCode
+UA_Client_preparePublishRequest(UA_Client *client, UA_PublishRequest *request) {
+    /* Count acks */
+    UA_Client_NotificationsAckNumber *ack;
+    LIST_FOREACH(ack, &client->pendingNotificationsAcks, listEntry)
+        ++request->subscriptionAcknowledgementsSize;
+
+    /* Create the array. Returns a sentinel pointer if the length is zero. */
+    request->subscriptionAcknowledgements = (UA_SubscriptionAcknowledgement*)
+        UA_Array_new(request->subscriptionAcknowledgementsSize,
+                     &UA_TYPES[UA_TYPES_SUBSCRIPTIONACKNOWLEDGEMENT]);
+    if(!request->subscriptionAcknowledgements) {
+        request->subscriptionAcknowledgementsSize = 0;
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+
+    size_t i = 0;
+    UA_Client_NotificationsAckNumber *ack_tmp;
+    LIST_FOREACH_SAFE(ack, &client->pendingNotificationsAcks, listEntry, ack_tmp) {
+        request->subscriptionAcknowledgements[i].sequenceNumber = ack->subAck.sequenceNumber;
+        request->subscriptionAcknowledgements[i].subscriptionId = ack->subAck.subscriptionId;
+        ++i;
+        LIST_REMOVE(ack, listEntry);
+        UA_free(ack);
+    }
+    return UA_STATUSCODE_GOOD;
+}
+
+/* According to OPC Unified Architecture, Part 4 5.13.1.1 i) */
+/* The value 0 is never used for the sequence number         */
+static UA_UInt32
+UA_Client_Subscriptions_nextSequenceNumber(UA_UInt32 sequenceNumber) {
+    UA_UInt32 nextSequenceNumber = sequenceNumber + 1;
+    if(nextSequenceNumber == 0)
+        nextSequenceNumber = 1;
+    return nextSequenceNumber;
+}
+
+static void
+processDataChangeNotification(UA_Client *client, UA_Client_Subscription *sub,
+                              UA_DataChangeNotification *dataChangeNotification) {
+    for(size_t j = 0; j < dataChangeNotification->monitoredItemsSize; ++j) {
+        UA_MonitoredItemNotification *min = &dataChangeNotification->monitoredItems[j];
+
+        /* Find the MonitoredItem */
+        UA_Client_MonitoredItem *mon;
+        LIST_FOREACH(mon, &sub->monitoredItems, listEntry) {
+            if(mon->clientHandle == min->clientHandle)
+                break;
+        }
+
+        if(!mon) {
+            UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                         "Could not process a notification with clienthandle %u on subscription %u",
+                         min->clientHandle, sub->subscriptionId);
+            continue;
+        }
+
+        if(mon->isEventMonitoredItem) {
+            UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                         "MonitoredItem is configured for Events. But received a "
+                         "DataChangeNotification.");
+            continue;
+        }
+
+        mon->handler.dataChangeCallback(client, sub->subscriptionId, sub->context,
+                                        mon->monitoredItemId, mon->context,
+                                        &min->value);
+    }
+}
+
+static void
+processEventNotification(UA_Client *client, UA_Client_Subscription *sub,
+                         UA_EventNotificationList *eventNotificationList) {
+    for(size_t j = 0; j < eventNotificationList->eventsSize; ++j) {
+        UA_EventFieldList *eventFieldList = &eventNotificationList->events[j];
+
+        /* Find the MonitoredItem */
+        UA_Client_MonitoredItem *mon;
+        LIST_FOREACH(mon, &sub->monitoredItems, listEntry) {
+            if(mon->monitoredItemId == eventFieldList->clientHandle)
+                break;
+        }
+
+        if(!mon) {
+            UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                         "Could not process a notification with clienthandle %u on subscription %u",
+                         eventFieldList->clientHandle, sub->subscriptionId);
+            continue;
+        }
+
+        if(!mon->isEventMonitoredItem) {
+            UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                         "MonitoredItem is configured for DataChanges. But received a "
+                         "EventNotification.");
+            continue;
+        }
+
+        mon->handler.eventCallback(client, sub->subscriptionId, sub->context,
+                                   mon->monitoredItemId, mon->context,
+                                   eventFieldList->eventFieldsSize,
+                                   eventFieldList->eventFields);
+    }
+}
+
+static void
+processNotificationMessage(UA_Client *client, UA_Client_Subscription *sub,
+                           UA_ExtensionObject *msg) {
+    if(msg->encoding != UA_EXTENSIONOBJECT_DECODED)
+        return;
+
+    /* Handle DataChangeNotification */
+    if(msg->content.decoded.type == &UA_TYPES[UA_TYPES_DATACHANGENOTIFICATION]) {
+        UA_DataChangeNotification *dataChangeNotification =
+            (UA_DataChangeNotification *)msg->content.decoded.data;
+        processDataChangeNotification(client, sub, dataChangeNotification);
+        return;
+    }
+
+    /* Handle EventNotification */
+    if(msg->content.decoded.type == &UA_TYPES[UA_TYPES_EVENTNOTIFICATIONLIST]) {
+        UA_EventNotificationList *eventNotificationList =
+            (UA_EventNotificationList *)msg->content.decoded.data;
+        processEventNotification(client, sub, eventNotificationList);
+        return;
+    }
+
+    /* Handle StatusChangeNotification */
+    if(msg->content.decoded.type == &UA_TYPES[UA_TYPES_STATUSCHANGENOTIFICATION]) {
+        if(sub->statusChangeCallback) {
+            sub->statusChangeCallback(client, sub->subscriptionId, sub->context,
+                                      (UA_StatusChangeNotification*)msg->content.decoded.data);
+        } else {
+            UA_LOG_WARNING(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                           "Dropped a StatusChangeNotification since no callback is registered");
+        }
+        return;
+    }
+
+    UA_LOG_WARNING(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                   "Unknown notification message type");
+}
+
+void
+UA_Client_Subscriptions_processPublishResponse(UA_Client *client, UA_PublishRequest *request,
+                                               UA_PublishResponse *response) {
+    UA_NotificationMessage *msg = &response->notificationMessage;
+
+    client->currentlyOutStandingPublishRequests--;
+
+    if(response->responseHeader.serviceResult == UA_STATUSCODE_BADTOOMANYPUBLISHREQUESTS) {
+        if(client->config.outStandingPublishRequests > 1) {
+            client->config.outStandingPublishRequests--;
+            UA_LOG_WARNING(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                          "Too many publishrequest, reduce outStandingPublishRequests to %d",
+                           client->config.outStandingPublishRequests);
+        } else {
+            UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                         "Too many publishrequest when outStandingPublishRequests = 1");
+            UA_Client_Subscriptions_deleteSingle(client, response->subscriptionId);
+        }
+        return;
+    }
+
+    if(response->responseHeader.serviceResult == UA_STATUSCODE_BADSHUTDOWN)
+        return;
+
+    if(!LIST_FIRST(&client->subscriptions)) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADNOSUBSCRIPTION;
+        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,
+                       "Received BadSessionIdInvalid");
+        return;
+    }
+
+    if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+        UA_LOG_WARNING(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                       "Received Publish Response with code %s",
+                       UA_StatusCode_name(response->responseHeader.serviceResult));
+        return;
+    }
+
+    UA_Client_Subscription *sub = findSubscription(client, response->subscriptionId);
+    if(!sub) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR;
+        UA_LOG_WARNING(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                       "Received Publish Response for a non-existant subscription");
+        return;
+    }
+
+    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;
+
+    /* Process the notification messages */
+    for(size_t k = 0; k < msg->notificationDataSize; ++k)
+        processNotificationMessage(client, sub, &msg->notificationData[k]);
+
+    /* Add to the list of pending acks */
+    for(size_t i = 0; i < response->availableSequenceNumbersSize; i++) {
+        if(response->availableSequenceNumbers[i] != msg->sequenceNumber)
+            continue;
+        UA_Client_NotificationsAckNumber *tmpAck = (UA_Client_NotificationsAckNumber*)
+            UA_malloc(sizeof(UA_Client_NotificationsAckNumber));
+        if(!tmpAck) {
+            UA_LOG_WARNING(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                           "Not enough memory to store the acknowledgement for a publish "
+                           "message on subscription %u", sub->subscriptionId);
+            break;
+        }   
+        tmpAck->subAck.sequenceNumber = msg->sequenceNumber;
+        tmpAck->subAck.subscriptionId = sub->subscriptionId;
+        LIST_INSERT_HEAD(&client->pendingNotificationsAcks, tmpAck, listEntry);
+        break;
+    } 
+}
+
+static void
+processPublishResponseAsync(UA_Client *client, void *userdata, UA_UInt32 requestId,
+                            void *response, const UA_DataType *responseType) {
+    UA_PublishRequest *req = (UA_PublishRequest*)userdata;
+    UA_PublishResponse *res = (UA_PublishResponse*)response;
+
+    /* Process the response */
+    UA_Client_Subscriptions_processPublishResponse(client, req, res);
+
+    /* Delete the cached request */
+    UA_PublishRequest_delete(req);
+
+    /* Fill up the outstanding publish requests */
+    UA_Client_Subscriptions_backgroundPublish(client);
+}
+
+void
+UA_Client_Subscriptions_clean(UA_Client *client) {
+    UA_Client_NotificationsAckNumber *n, *tmp;
+    LIST_FOREACH_SAFE(n, &client->pendingNotificationsAcks, listEntry, tmp) {
+        LIST_REMOVE(n, listEntry);
+        UA_free(n);
+    }
+
+    UA_Client_Subscription *sub, *tmps;
+    LIST_FOREACH_SAFE(sub, &client->subscriptions, listEntry, tmps)
+        UA_Client_Subscription_deleteInternal(client, sub); /* force local removal */
+
+    client->monitoredItemHandles = 0;
+}
+
+void
+UA_Client_Subscriptions_backgroundPublishInactivityCheck(UA_Client *client) {
+    if(client->state < UA_CLIENTSTATE_SESSION)
+        return;
+
+    /* The session must have at least one subscription */
+    if(!LIST_FIRST(&client->subscriptions))
+        return;
+
+    /* Check subscriptions inactivity */
+    if (client->config.outStandingPublishRequests) {
+        UA_Client_Subscription *sub;
+        LIST_FOREACH(sub, &client->subscriptions, listEntry) {
+            UA_DateTime maxSilence = (UA_DateTime)
+                ((sub->publishingInterval * sub->maxKeepAliveCount) +
+                 client->config.timeout) * UA_DATETIME_MSEC;
+            if(maxSilence + sub->lastActivity < UA_DateTime_nowMonotonic()) {
+                UA_LOG_ERROR(client->config.logger, UA_LOGCATEGORY_CLIENT,
+                             "Inactivity for Subscription %d. Closing the connection.",
+                             sub->subscriptionId);
+                UA_Client_close(client);
+                return;
+            }
+        }
+    }
+}
+
+UA_StatusCode
+UA_Client_Subscriptions_backgroundPublish(UA_Client *client) {
+    if(client->state < UA_CLIENTSTATE_SESSION)
+        return UA_STATUSCODE_BADSERVERNOTCONNECTED;
+
+    /* The session must have at least one subscription */
+    if(!LIST_FIRST(&client->subscriptions))
+        return UA_STATUSCODE_GOOD;
+
+    while(client->currentlyOutStandingPublishRequests < client->config.outStandingPublishRequests) {
+        UA_PublishRequest *request = UA_PublishRequest_new();
+        if (!request)
+            return UA_STATUSCODE_BADOUTOFMEMORY;
+
+        UA_StatusCode retval = UA_Client_preparePublishRequest(client, request);
+        if(retval != UA_STATUSCODE_GOOD) {
+            UA_PublishRequest_delete(request);
+            return retval;
+        }
+    
+        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);
+        if(retval != UA_STATUSCODE_GOOD) {
+            UA_PublishRequest_delete(request);
+            return retval;
+        }
+    }
+
+    return UA_STATUSCODE_GOOD;
+}
+
+#endif /* UA_ENABLE_SUBSCRIPTIONS */

+ 384 - 0
src/client/ua_client_subscriptions_deprecated.c

@@ -0,0 +1,384 @@
+/* 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 2015-2018 (c) Julius Pfrommer, Fraunhofer IOSB
+ *    Copyright 2015 (c) Oleksiy Vasylyev
+ *    Copyright 2016 (c) Sten Grüner
+ *    Copyright 2017-2018 (c) Thomas Stalder, Blue Time Concept SA
+ *    Copyright 2016-2017 (c) Florian Palm
+ *    Copyright 2017 (c) Frank Meerkötter
+ *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
+ */
+
+#include "ua_client_highlevel.h"
+#include "ua_client_internal.h"
+#include "ua_util.h"
+
+#ifdef UA_ENABLE_SUBSCRIPTIONS /* conditional compilation */
+
+const UA_SubscriptionSettings UA_SubscriptionSettings_default = {
+    500.0, /* .requestedPublishingInterval */
+    10000, /* .requestedLifetimeCount */
+    1, /* .requestedMaxKeepAliveCount */
+    0, /* .maxNotificationsPerPublish */
+    true, /* .publishingEnabled */
+    0 /* .priority */
+};
+
+UA_StatusCode
+UA_Client_Subscriptions_new(UA_Client *client, UA_SubscriptionSettings settings,
+                            UA_UInt32 *newSubscriptionId) {
+    UA_CreateSubscriptionRequest request;
+    UA_CreateSubscriptionRequest_init(&request);
+    request.requestedPublishingInterval = settings.requestedPublishingInterval;
+    request.requestedLifetimeCount = settings.requestedLifetimeCount;
+    request.requestedMaxKeepAliveCount = settings.requestedMaxKeepAliveCount;
+    request.maxNotificationsPerPublish = settings.maxNotificationsPerPublish;
+    request.publishingEnabled = settings.publishingEnabled;
+    request.priority = settings.priority;
+    
+    UA_CreateSubscriptionResponse response =
+        UA_Client_Subscriptions_create(client, request, NULL, NULL, NULL);
+
+    UA_StatusCode retval = response.responseHeader.serviceResult;
+    if(retval == UA_STATUSCODE_GOOD && newSubscriptionId)
+        *newSubscriptionId = response.subscriptionId;
+    
+    UA_CreateSubscriptionResponse_deleteMembers(&response);
+    return retval;
+}
+
+UA_StatusCode
+UA_Client_Subscriptions_remove(UA_Client *client, UA_UInt32 subscriptionId) {
+    UA_DeleteSubscriptionsRequest request;
+    UA_DeleteSubscriptionsRequest_init(&request);
+    request.subscriptionIdsSize = 1;
+    request.subscriptionIds = &subscriptionId;
+
+    UA_DeleteSubscriptionsResponse response =
+        UA_Client_Subscriptions_delete(client, request);
+
+    UA_StatusCode retval = response.responseHeader.serviceResult;
+    if(retval == UA_STATUSCODE_GOOD) {
+        if(response.resultsSize != 1)
+            retval = UA_STATUSCODE_BADINTERNALERROR;
+    }
+
+    if(retval == UA_STATUSCODE_GOOD)
+        retval = response.results[0];
+
+    UA_DeleteSubscriptionsResponse_deleteMembers(&response);
+    return retval;
+}
+
+UA_StatusCode
+UA_Client_Subscriptions_manuallySendPublishRequest(UA_Client *client) {
+    if(client->state < UA_CLIENTSTATE_SESSION)
+        return UA_STATUSCODE_BADSERVERNOTCONNECTED;
+
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+
+    UA_DateTime now = UA_DateTime_nowMonotonic();
+    UA_DateTime maxDate = now + (UA_DateTime)(client->config.timeout * UA_DATETIME_MSEC);
+
+    UA_Boolean moreNotifications = true;
+    while(moreNotifications) {
+        UA_PublishRequest request;
+        UA_PublishRequest_init(&request);
+        retval = UA_Client_preparePublishRequest(client, &request);
+        if(retval != UA_STATUSCODE_GOOD)
+            return retval;
+
+        /* Manually increase the number of sent publish requests. Otherwise we
+         * send out one too many when we process async responses when we wait
+         * for the correct publish response. The
+         * currentlyOutStandingPublishRequests will be reduced during processing
+         * of the response. */
+        client->currentlyOutStandingPublishRequests++;
+
+        UA_PublishResponse response;
+        __UA_Client_Service(client,
+                            &request, &UA_TYPES[UA_TYPES_PUBLISHREQUEST],
+                            &response, &UA_TYPES[UA_TYPES_PUBLISHRESPONSE]);
+        UA_Client_Subscriptions_processPublishResponse(client, &request, &response);
+        UA_PublishRequest_deleteMembers(&request);
+        
+        now = UA_DateTime_nowMonotonic();
+        if(now > maxDate) {
+            moreNotifications = UA_FALSE;
+            retval = UA_STATUSCODE_GOODNONCRITICALTIMEOUT;
+        } else {
+            moreNotifications = response.moreNotifications;
+        }
+
+        UA_PublishResponse_deleteMembers(&response);
+        UA_PublishRequest_deleteMembers(&request);
+    }
+
+    if(client->state < UA_CLIENTSTATE_SESSION)
+        return UA_STATUSCODE_BADSERVERNOTCONNECTED;
+
+    return retval;
+}
+
+/* Callbacks for the MonitoredItems. The callbacks for the deprecated API are
+ * wrapped. The wrapper is cleaned up upon destruction. */
+
+typedef struct {
+    UA_MonitoredItemHandlingFunction origCallback;
+    void *context;
+} dataChangeCallbackWrapper;
+
+static void
+dataChangeCallback(UA_Client *client, UA_UInt32 subId, void *subContext,
+                   UA_UInt32 monId, void *monContext, UA_DataValue *value) {
+    dataChangeCallbackWrapper *wrapper = (dataChangeCallbackWrapper*)monContext;
+    wrapper->origCallback(client, monId, value, wrapper->context);
+}
+
+typedef struct {
+    UA_MonitoredEventHandlingFunction origCallback;
+    void *context;
+} eventCallbackWrapper;
+
+static void
+eventCallback(UA_Client *client, UA_UInt32 subId, void *subContext,
+              UA_UInt32 monId, void *monContext, size_t nEventFields,
+              UA_Variant *eventFields) {
+    eventCallbackWrapper *wrapper = (eventCallbackWrapper*)monContext;
+    wrapper->origCallback(client, monId, nEventFields, eventFields, wrapper->context);
+}
+
+static void
+deleteMonitoredItemCallback(UA_Client *client, UA_UInt32 subId, void *subContext,
+                            UA_UInt32 monId, void *monContext) {
+    UA_free(monContext);
+}
+
+static UA_StatusCode
+addMonitoredItems(UA_Client *client, const UA_UInt32 subscriptionId,
+                  UA_MonitoredItemCreateRequest *items, size_t itemsSize,
+                  UA_MonitoredItemHandlingFunction *hfs, void **hfContexts,
+                  UA_StatusCode *itemResults, UA_UInt32 *newMonitoredItemIds) {
+    /* Create array of wrappers and callbacks */
+    dataChangeCallbackWrapper **wrappers = (dataChangeCallbackWrapper**)
+        UA_alloca(sizeof(dataChangeCallbackWrapper*) * itemsSize);
+    UA_Client_DeleteMonitoredItemCallback *deleteCbs = (UA_Client_DeleteMonitoredItemCallback*)
+        UA_alloca(sizeof(UA_Client_DeleteMonitoredItemCallback) * itemsSize);
+    UA_Client_DataChangeNotificationCallback *wrapperCbs = (UA_Client_DataChangeNotificationCallback*)
+        UA_alloca(sizeof(UA_MonitoredItemHandlingFunction) * itemsSize);
+
+    for(size_t i = 0; i < itemsSize; i++) {
+        wrappers[i] = (dataChangeCallbackWrapper*)UA_malloc(sizeof(dataChangeCallbackWrapper));
+        if(!wrappers[i]) {
+            for(size_t j = 0; j < i; j++)
+                UA_free(wrappers[j]);
+            return UA_STATUSCODE_BADOUTOFMEMORY;
+        }
+        wrappers[i]->origCallback = (UA_MonitoredItemHandlingFunction)(uintptr_t)hfs[i];
+        wrappers[i]->context = hfContexts[i];
+
+        deleteCbs[i] = deleteMonitoredItemCallback;
+        wrapperCbs[i] = dataChangeCallback;
+    }
+
+    /* Prepare the request */
+    UA_CreateMonitoredItemsRequest request;
+    UA_CreateMonitoredItemsRequest_init(&request);
+    request.subscriptionId = subscriptionId;
+    request.itemsToCreateSize = itemsSize;
+    request.itemsToCreate = items;
+
+    /* Process and return */
+    UA_CreateMonitoredItemsResponse response =
+        UA_Client_MonitoredItems_createDataChanges(client, request, (void**)wrappers,
+                                                   wrapperCbs, deleteCbs);
+
+    UA_StatusCode retval = response.responseHeader.serviceResult;
+    if(retval == UA_STATUSCODE_GOOD && response.resultsSize != itemsSize)
+        retval = UA_STATUSCODE_BADINTERNALERROR;
+
+    if(retval == UA_STATUSCODE_GOOD) {
+        for(size_t i = 0; i < itemsSize; i++) {
+            itemResults[i] = response.results[i].statusCode;
+            newMonitoredItemIds[i] = response.results[i].monitoredItemId;
+        }
+    }
+
+    UA_CreateMonitoredItemsResponse_deleteMembers(&response);
+    return retval;
+}
+
+UA_StatusCode
+UA_Client_Subscriptions_addMonitoredItems(UA_Client *client, const UA_UInt32 subscriptionId,
+                                          UA_MonitoredItemCreateRequest *items, size_t itemsSize,
+                                          UA_MonitoredItemHandlingFunction *hfs,
+                                          void **hfContexts, UA_StatusCode *itemResults,
+                                          UA_UInt32 *newMonitoredItemIds) {
+    return addMonitoredItems(client, subscriptionId, items, itemsSize, hfs, hfContexts, itemResults,
+                             newMonitoredItemIds);
+}
+
+UA_StatusCode
+UA_Client_Subscriptions_addMonitoredItem(UA_Client *client, UA_UInt32 subscriptionId,
+                                         UA_NodeId nodeId, UA_UInt32 attributeID,
+                                         UA_MonitoredItemHandlingFunction hf, void *hfContext,
+                                         UA_UInt32 *newMonitoredItemId, UA_Double samplingInterval) {
+    UA_MonitoredItemCreateRequest item;
+    UA_MonitoredItemCreateRequest_init(&item);
+    item.itemToMonitor.nodeId = nodeId;
+    item.itemToMonitor.attributeId = attributeID;
+    item.monitoringMode = UA_MONITORINGMODE_REPORTING;
+    item.requestedParameters.samplingInterval = samplingInterval;
+    item.requestedParameters.discardOldest = true;
+    item.requestedParameters.queueSize = 1;
+
+    UA_StatusCode retval_item = UA_STATUSCODE_GOOD;
+    UA_StatusCode retval =
+        addMonitoredItems(client, subscriptionId, &item, 1,
+                          (UA_MonitoredItemHandlingFunction*)(uintptr_t)&hf,
+                          &hfContext, &retval_item, newMonitoredItemId);
+    return retval | retval_item;
+}
+
+static UA_StatusCode
+addMonitoredEvents(UA_Client *client, const UA_UInt32 subscriptionId,
+                   UA_MonitoredItemCreateRequest *items, size_t itemsSize,
+                   UA_MonitoredEventHandlingFunction *hfs,
+                   void **hfContexts, UA_StatusCode *itemResults,
+                   UA_UInt32 *newMonitoredItemIds) {
+    /* Create array of wrappers and callbacks */
+    eventCallbackWrapper **wrappers = (eventCallbackWrapper**)
+        UA_alloca(sizeof(dataChangeCallbackWrapper*) * itemsSize);
+    UA_Client_DeleteMonitoredItemCallback *deleteCbs = (UA_Client_DeleteMonitoredItemCallback*)
+        UA_alloca(sizeof(UA_Client_DeleteMonitoredItemCallback) * itemsSize);
+    UA_Client_EventNotificationCallback *wrapperCbs = (UA_Client_EventNotificationCallback*)
+        UA_alloca(sizeof(UA_MonitoredItemHandlingFunction) * itemsSize);
+
+    for(size_t i = 0; i < itemsSize; i++) {
+        wrappers[i] = (eventCallbackWrapper*)UA_malloc(sizeof(eventCallbackWrapper));
+        if(!wrappers[i]) {
+            for(size_t j = 0; j < i; j++)
+                UA_free(wrappers[j]);
+            return UA_STATUSCODE_BADOUTOFMEMORY;
+        }
+        wrappers[i]->origCallback = (UA_MonitoredEventHandlingFunction)(uintptr_t)hfs[i];
+        wrappers[i]->context = hfContexts[i];
+
+        deleteCbs[i] = deleteMonitoredItemCallback;
+        wrapperCbs[i] = eventCallback;
+    }
+
+    /* Prepare the request */
+    UA_CreateMonitoredItemsRequest request;
+    UA_CreateMonitoredItemsRequest_init(&request);
+    request.subscriptionId = subscriptionId;
+    request.itemsToCreateSize = itemsSize;
+    request.itemsToCreate = items;
+
+    /* Process and return */
+    UA_CreateMonitoredItemsResponse response =
+        UA_Client_MonitoredItems_createEvents(client, request, (void**)wrappers,
+                                              wrapperCbs, deleteCbs);
+
+    UA_StatusCode retval = response.responseHeader.serviceResult;
+    if(retval == UA_STATUSCODE_GOOD && response.resultsSize != itemsSize)
+        retval = UA_STATUSCODE_BADINTERNALERROR;
+
+    if(retval == UA_STATUSCODE_GOOD) {
+        for(size_t i = 0; i < itemsSize; i++)
+            itemResults[i] = response.results[i].statusCode;
+    }
+
+    UA_CreateMonitoredItemsResponse_deleteMembers(&response);
+    return retval;
+}
+
+UA_StatusCode
+UA_Client_Subscriptions_addMonitoredEvents(UA_Client *client, const UA_UInt32 subscriptionId,
+                                           UA_MonitoredItemCreateRequest *items, size_t itemsSize,
+                                           UA_MonitoredEventHandlingFunction *hfs,
+                                           void **hfContexts, UA_StatusCode *itemResults,
+                                           UA_UInt32 *newMonitoredItemIds) {
+    return addMonitoredEvents(client, subscriptionId, items, itemsSize, hfs,
+                              hfContexts, itemResults, newMonitoredItemIds);
+}
+
+UA_StatusCode
+UA_Client_Subscriptions_addMonitoredEvent(UA_Client *client, UA_UInt32 subscriptionId,
+                                          const UA_NodeId nodeId, UA_UInt32 attributeID,
+                                          const UA_SimpleAttributeOperand *selectClauses,
+                                          size_t selectClausesSize,
+                                          const UA_ContentFilterElement *whereClauses,
+                                          size_t whereClausesSize,
+                                          const UA_MonitoredEventHandlingFunction hf,
+                                          void *hfContext, UA_UInt32 *newMonitoredItemId) {
+    UA_MonitoredItemCreateRequest item;
+    UA_MonitoredItemCreateRequest_init(&item);
+    item.itemToMonitor.nodeId = nodeId;
+    item.itemToMonitor.attributeId = attributeID;
+    item.monitoringMode = UA_MONITORINGMODE_REPORTING;
+    item.requestedParameters.samplingInterval = 0;
+    item.requestedParameters.discardOldest = false;
+
+    UA_EventFilter *evFilter = UA_EventFilter_new();
+    if(!evFilter)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    UA_EventFilter_init(evFilter);
+    evFilter->selectClausesSize = selectClausesSize;
+    evFilter->selectClauses = (UA_SimpleAttributeOperand*)(uintptr_t)selectClauses;
+    evFilter->whereClause.elementsSize = whereClausesSize;
+    evFilter->whereClause.elements = (UA_ContentFilterElement*)(uintptr_t)whereClauses;
+
+    item.requestedParameters.filter.encoding = UA_EXTENSIONOBJECT_DECODED_NODELETE;
+    item.requestedParameters.filter.content.decoded.type = &UA_TYPES[UA_TYPES_EVENTFILTER];
+    item.requestedParameters.filter.content.decoded.data = evFilter;
+    UA_StatusCode retval_item = UA_STATUSCODE_GOOD;
+    UA_StatusCode retval = addMonitoredEvents(client, subscriptionId, &item, 1,
+                                             (UA_MonitoredEventHandlingFunction*)(uintptr_t)&hf,
+                                              &hfContext, &retval_item, newMonitoredItemId);
+    UA_free(evFilter);
+    return retval | retval_item;
+}
+
+static UA_StatusCode
+removeMonitoredItems(UA_Client *client, UA_UInt32 subscriptionId,
+                     UA_UInt32 *monitoredItemIds, size_t itemsSize,
+                     UA_StatusCode *itemResults) {
+    UA_DeleteMonitoredItemsRequest request;
+    UA_DeleteMonitoredItemsRequest_init(&request);
+    request.subscriptionId = subscriptionId;
+    request.monitoredItemIdsSize = itemsSize;
+    request.monitoredItemIds = monitoredItemIds;
+
+    UA_DeleteMonitoredItemsResponse response = UA_Client_MonitoredItems_delete(client, request);
+    UA_StatusCode retval = response.responseHeader.serviceResult;
+    if(retval == UA_STATUSCODE_GOOD) {
+        if(response.resultsSize != itemsSize) {
+            retval = UA_STATUSCODE_BADINTERNALERROR;
+        } else {
+            for(size_t i = 0; i < itemsSize; i++)
+                itemResults[i] = response.results[i];
+        }
+    }
+    UA_DeleteMonitoredItemsResponse_deleteMembers(&response);
+    return retval;
+}
+
+UA_StatusCode
+UA_Client_Subscriptions_removeMonitoredItems(UA_Client *client, UA_UInt32 subscriptionId,
+                                             UA_UInt32 *monitoredItemIds, size_t itemsSize,
+                                             UA_StatusCode *itemResults) {
+    return removeMonitoredItems(client, subscriptionId, monitoredItemIds, itemsSize, itemResults);
+}
+
+UA_StatusCode
+UA_Client_Subscriptions_removeMonitoredItem(UA_Client *client, UA_UInt32 subscriptionId,
+                                            UA_UInt32 monitoredItemId) {
+    UA_StatusCode retval_item = UA_STATUSCODE_GOOD;
+    UA_StatusCode retval = removeMonitoredItems(client, subscriptionId, &monitoredItemId, 1, &retval_item);
+    return retval | retval_item;
+}
+
+#endif /* UA_ENABLE_SUBSCRIPTIONS */

+ 2 - 5
src/ua_securechannel.h

@@ -37,14 +37,11 @@ extern UA_THREAD_LOCAL UA_StatusCode processSym_seqNumberFailure;
  * the interface that will be used by the SecureChannel. The lifecycle of
  * Sessions is independent of the underlying SecureChannel. But every Session
  * can be attached to only one SecureChannel. */
-struct UA_SessionHeader;
-typedef struct UA_SessionHeader UA_SessionHeader;
-
-struct UA_SessionHeader {
+typedef struct UA_SessionHeader {
     LIST_ENTRY(UA_SessionHeader) pointers;
     UA_NodeId authenticationToken;
     UA_SecureChannel *channel; /* The pointer back to the SecureChannel in the session. */
-};
+} UA_SessionHeader;
 
 /* For chunked requests */
 struct ChunkEntry {

+ 4 - 0
tests/CMakeLists.txt

@@ -183,6 +183,10 @@ add_executable(check_client_subscriptions client/check_client_subscriptions.c $<
 target_link_libraries(check_client_subscriptions ${LIBS})
 add_test_valgrind(client_subscriptions ${TESTS_BINARY_DIR}/check_client_subscriptions)
 
+add_executable(check_client_subscriptions_deprecated client/check_client_subscriptions_deprecated.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+target_link_libraries(check_client_subscriptions_deprecated ${LIBS})
+add_test_valgrind(client_subscriptions_deprecated ${TESTS_BINARY_DIR}/check_client_subscriptions_deprecated)
+
 add_executable(check_client_highlevel client/check_client_highlevel.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
 target_link_libraries(check_client_highlevel ${LIBS})
 add_test_valgrind(client_highlevel ${TESTS_BINARY_DIR}/check_client_highlevel)

+ 9 - 8
tests/client/check_client.c

@@ -214,6 +214,14 @@ START_TEST(Client_reconnect) {
 }
 END_TEST
 
+START_TEST(Client_delete_without_connect) {
+    UA_ClientConfig clientConfig = UA_ClientConfig_default;
+    UA_Client *client = UA_Client_new(clientConfig);
+    ck_assert_msg(client != NULL);
+    UA_Client_delete(client);
+}
+END_TEST
+
 // TODO ACTIVATE THE TESTS WHEN SESSION RECOVERY IS GOOD
 #ifdef UA_SESSION_RECOVERY
 
@@ -252,14 +260,6 @@ START_TEST(Client_activateSessionClose) {
 }
 END_TEST
 
-START_TEST(Client_delete_without_connect) {
-    UA_ClientConfig clientConfig = UA_ClientConfig_default;
-    UA_Client *client = UA_Client_new(clientConfig);
-    ck_assert_msg(client != NULL);
-    UA_Client_delete(client);
-}
-END_TEST
-
 START_TEST(Client_activateSessionTimeout) {
     // restart server
     teardown();
@@ -314,6 +314,7 @@ static Suite* testSuite_Client(void) {
     tcase_add_checked_fixture(tc_client, setup, teardown);
     tcase_add_test(tc_client, Client_connect);
     tcase_add_test(tc_client, Client_connect_username);
+    tcase_add_test(tc_client, Client_delete_without_connect);
     tcase_add_test(tc_client, Client_endpoints);
     tcase_add_test(tc_client, Client_endpoints_empty);
     tcase_add_test(tc_client, Client_read);

+ 220 - 180
tests/client/check_client_subscriptions.c

@@ -2,10 +2,6 @@
  * 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/. */
 
-#ifdef _MSC_VER
-#pragma warning(disable:4996) // warning C4996: 'UA_Client_Subscriptions_manuallySendPublishRequest': was declared deprecated
-#endif
-
 #include <stdio.h>
 #include <stdlib.h>
 #include "ua_types.h"
@@ -56,18 +52,21 @@ static void teardown(void) {
 
 UA_Boolean notificationReceived = false;
 UA_UInt32 countNotificationReceived = 0;
+UA_Double publishingInterval = 500.0;
 
 static void
-monitoredItemHandler(UA_Client *client, UA_UInt32 monId, UA_DataValue *value, void *context) {
+dataChangeHandler(UA_Client *client, UA_UInt32 subId, void *subContext,
+                  UA_UInt32 monId, void *monContext, UA_DataValue *value) {
     notificationReceived = true;
     countNotificationReceived++;
 }
 
 static void
-monitoredItemHandlerSubSleep(UA_Client *client, UA_UInt32 monId, UA_DataValue *value, void *context) {
+dataChangeHandlerSubSleep(UA_Client *client, UA_UInt32 subId, void *subContext,
+                          UA_UInt32 monId, void *monContext, UA_DataValue *value) {
     notificationReceived = true;
     countNotificationReceived++;
-    UA_fakeSleep((UA_UInt32)(UA_SubscriptionSettings_default.requestedPublishingInterval + 2));
+    UA_fakeSleep((UA_UInt32)(publishingInterval + 2));
 }
 
 START_TEST(Client_subscription) {
@@ -75,28 +74,38 @@ START_TEST(Client_subscription) {
     UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
-    UA_UInt32 subId;
-    retval = UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
-    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();
+    UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request,
+                                                                            NULL, NULL, NULL);
+    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    UA_UInt32 subId = response.subscriptionId;
 
     /* monitor the server state */
-    UA_UInt32 monId;
-    retval = UA_Client_Subscriptions_addMonitoredItem(client, subId, UA_NODEID_NUMERIC(0, 2259),
-                                                      UA_ATTRIBUTEID_VALUE, monitoredItemHandler,
-                                                      NULL, &monId, 250);
-    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_MonitoredItemCreateRequest monRequest =
+        UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE));
 
-    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
+    UA_MonitoredItemCreateResult monResponse =
+        UA_Client_MonitoredItems_createDataChange(client, response.subscriptionId,
+                                                  UA_TIMESTAMPSTORETURN_BOTH,
+                                                  monRequest, NULL, dataChangeHandler, NULL);
+    ck_assert_uint_eq(monResponse.statusCode, UA_STATUSCODE_GOOD);
+    UA_UInt32 monId = monResponse.monitoredItemId;
+
+    UA_fakeSleep((UA_UInt32)publishingInterval + 1);
 
     notificationReceived = false;
-    retval = UA_Client_Subscriptions_manuallySendPublishRequest(client);
+
+    retval = UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_uint_eq(notificationReceived, true);
 
-    retval = UA_Client_Subscriptions_removeMonitoredItem(client, subId, monId);
+    retval = UA_Client_MonitoredItems_deleteSingle(client, subId, monId);
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
-    retval = UA_Client_Subscriptions_remove(client, subId);
+    retval = UA_Client_Subscriptions_deleteSingle(client, subId);
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
     UA_Client_disconnect(client);
@@ -104,87 +113,99 @@ START_TEST(Client_subscription) {
 }
 END_TEST
 
-START_TEST(Client_subscription_addMonitoredItems) {
+START_TEST(Client_subscription_createDataChanges) {
     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_UInt32 subId;
-    retval = UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
-    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();
+    UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request,
+                                                                            NULL, NULL, NULL);
+    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    UA_UInt32 subId = response.subscriptionId;
 
     UA_MonitoredItemCreateRequest items[3];
-    UA_MonitoredItemHandlingFunction hfs[3];
-    void *hfContexts[3];
-    UA_StatusCode itemResults[3];
     UA_UInt32 newMonitoredItemIds[3];
+    UA_Client_DataChangeNotificationCallback callbacks[3];
+    UA_Client_DeleteMonitoredItemCallback deleteCallbacks[3];
+    void *contexts[3];
 
     /* monitor the server state */
-    UA_MonitoredItemCreateRequest_init(&items[0]);
-    items[0].itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, 2259);
-    items[0].itemToMonitor.attributeId = UA_ATTRIBUTEID_VALUE;
-    items[0].monitoringMode = UA_MONITORINGMODE_REPORTING;
-    items[0].requestedParameters.samplingInterval = 250;
-    items[0].requestedParameters.discardOldest = true;
-    items[0].requestedParameters.queueSize = 1;
-    hfs[0] = (UA_MonitoredItemHandlingFunction)(uintptr_t)monitoredItemHandler;
-    hfContexts[0] = NULL;
-
-    /* monitor current time */
-    UA_MonitoredItemCreateRequest_init(&items[1]);
-    items[1].itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
-    items[1].itemToMonitor.attributeId = UA_ATTRIBUTEID_VALUE;
-    items[1].monitoringMode = UA_MONITORINGMODE_REPORTING;
-    items[1].requestedParameters.samplingInterval = 250;
-    items[1].requestedParameters.discardOldest = true;
-    items[1].requestedParameters.queueSize = 1;
-    hfs[1] = (UA_MonitoredItemHandlingFunction)(uintptr_t)monitoredItemHandler;
-    hfContexts[1] = NULL;
+    items[0] = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE));
+    callbacks[0] = dataChangeHandler;
+    contexts[0] = NULL;
+    deleteCallbacks[0] = NULL;
 
     /* monitor invalid node */
-    UA_MonitoredItemCreateRequest_init(&items[2]);
-    items[2].itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, 99999999);
-    items[2].itemToMonitor.attributeId = UA_ATTRIBUTEID_VALUE;
-    items[2].monitoringMode = UA_MONITORINGMODE_REPORTING;
-    items[2].requestedParameters.samplingInterval = 250;
-    items[2].requestedParameters.discardOldest = true;
-    items[2].requestedParameters.queueSize = 1;
-    hfs[2] = (UA_MonitoredItemHandlingFunction)(uintptr_t)monitoredItemHandler;
-    hfContexts[2] = NULL;
-
-    retval = UA_Client_Subscriptions_addMonitoredItems(client, subId, items, 3,
-                                                      hfs, hfContexts, itemResults,
-                                                      newMonitoredItemIds);
-    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
-    ck_assert_uint_eq(itemResults[0], UA_STATUSCODE_GOOD);
-    ck_assert_uint_eq(itemResults[1], UA_STATUSCODE_GOOD);
-    ck_assert_uint_eq(itemResults[2], UA_STATUSCODE_BADNODEIDUNKNOWN);
+    items[1] = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(0, 999999));
+    callbacks[1] = dataChangeHandler;
+    contexts[1] = NULL;
+    deleteCallbacks[1] = NULL;
 
-    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
+    /* monitor current time */
+    items[2] = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME));
+    callbacks[2] = dataChangeHandler;
+    contexts[2] = NULL;
+    deleteCallbacks[2] = NULL;
+
+    UA_CreateMonitoredItemsRequest createRequest;
+    UA_CreateMonitoredItemsRequest_init(&createRequest);
+    createRequest.subscriptionId = subId;
+    createRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
+    createRequest.itemsToCreate = items;
+    createRequest.itemsToCreateSize = 3;
+    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, 3);
+    ck_assert_uint_eq(createResponse.results[0].statusCode, UA_STATUSCODE_GOOD);
+    newMonitoredItemIds[0] = createResponse.results[0].monitoredItemId;
+    ck_assert_uint_eq(createResponse.results[1].statusCode, UA_STATUSCODE_BADNODEIDUNKNOWN);
+    newMonitoredItemIds[1] = createResponse.results[1].monitoredItemId;
+    ck_assert_uint_eq(newMonitoredItemIds[1], 0);
+    ck_assert_uint_eq(createResponse.results[2].statusCode, UA_STATUSCODE_GOOD);
+    newMonitoredItemIds[2] = createResponse.results[2].monitoredItemId;
+    ck_assert_uint_eq(createResponse.results[2].statusCode, UA_STATUSCODE_GOOD);
+    UA_CreateMonitoredItemsResponse_deleteMembers(&createResponse);
+
+    UA_fakeSleep((UA_UInt32)publishingInterval + 1);
 
     notificationReceived = false;
     countNotificationReceived = 0;
-    retval = UA_Client_Subscriptions_manuallySendPublishRequest(client);
+    retval = UA_Client_runAsync(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);
 
-    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
-
     notificationReceived = false;
-    retval = UA_Client_Subscriptions_manuallySendPublishRequest(client);
+    retval = UA_Client_runAsync(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);
 
-    retval = UA_Client_Subscriptions_removeMonitoredItems(client, subId, newMonitoredItemIds,
-                                                          3, itemResults);
-    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
-    ck_assert_uint_eq(itemResults[0], UA_STATUSCODE_GOOD);
-    ck_assert_uint_eq(itemResults[1], UA_STATUSCODE_GOOD);
-    ck_assert_uint_eq(itemResults[2], UA_STATUSCODE_BADMONITOREDITEMIDINVALID);
+    UA_DeleteMonitoredItemsRequest deleteRequest;
+    UA_DeleteMonitoredItemsRequest_init(&deleteRequest);
+    deleteRequest.subscriptionId = subId;
+    deleteRequest.monitoredItemIds = newMonitoredItemIds;
+    deleteRequest.monitoredItemIdsSize = 3;
+
+    UA_DeleteMonitoredItemsResponse deleteResponse =
+        UA_Client_MonitoredItems_delete(client, deleteRequest);
 
-    retval = UA_Client_Subscriptions_remove(client, subId);
+    ck_assert_uint_eq(deleteResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(deleteResponse.resultsSize, 3);
+    ck_assert_uint_eq(deleteResponse.results[0], UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(deleteResponse.results[1], UA_STATUSCODE_BADMONITOREDITEMIDINVALID);
+    ck_assert_uint_eq(deleteResponse.results[2], UA_STATUSCODE_GOOD);
+
+    UA_DeleteMonitoredItemsResponse_deleteMembers(&deleteResponse);
+
+    retval = UA_Client_Subscriptions_deleteSingle(client, subId);
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
     UA_Client_disconnect(client);
@@ -197,61 +218,56 @@ START_TEST(Client_subscription_keepAlive) {
     UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
-    UA_UInt32 subId;
-    retval = UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
-    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
-
-    UA_MonitoredItemCreateRequest items[1];
-    UA_MonitoredItemHandlingFunction hfs[1];
-    void *hfContexts[1];
-    UA_StatusCode itemResults[1];
-    UA_UInt32 newMonitoredItemIds[1];
+    UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
+    request.requestedMaxKeepAliveCount = 1;
+    UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request,
+                                                                            NULL, NULL, NULL);
+    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    UA_UInt32 subId = response.subscriptionId;
 
     /* monitor the server state */
-    UA_MonitoredItemCreateRequest_init(&items[0]);
-    items[0].itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, 2259);
-    items[0].itemToMonitor.attributeId = UA_ATTRIBUTEID_VALUE;
-    items[0].monitoringMode = UA_MONITORINGMODE_REPORTING;
-    items[0].requestedParameters.samplingInterval = 250;
-    items[0].requestedParameters.discardOldest = true;
-    items[0].requestedParameters.queueSize = 1;
-    hfs[0] = (UA_MonitoredItemHandlingFunction)(uintptr_t)monitoredItemHandler;
-    hfContexts[0] = NULL;
-
-    retval = UA_Client_Subscriptions_addMonitoredItems(client, subId, items, 1,
-                                                      hfs, hfContexts, itemResults,
-                                                      newMonitoredItemIds);
-    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
-    ck_assert_uint_eq(itemResults[0], UA_STATUSCODE_GOOD);
+    UA_MonitoredItemCreateRequest monRequest =
+        UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE));
 
-    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
+    UA_MonitoredItemCreateResult monResponse =
+        UA_Client_MonitoredItems_createDataChange(client, response.subscriptionId,
+                                                  UA_TIMESTAMPSTORETURN_BOTH,
+                                                  monRequest, NULL, dataChangeHandler, NULL);
+    ck_assert_uint_eq(monResponse.statusCode, UA_STATUSCODE_GOOD);
+    UA_UInt32 monId = monResponse.monitoredItemId;
 
-    UA_PublishRequest request;
-    UA_PublishRequest_init(&request);
-    request.subscriptionAcknowledgementsSize = 0;
+    UA_fakeSleep((UA_UInt32)(publishingInterval + 1));
 
-    UA_PublishResponse response = UA_Client_Service_publish(client, request);
-    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
-    ck_assert_uint_eq(response.notificationMessage.notificationDataSize, 1);
-    UA_PublishResponse_deleteMembers(&response);
-    UA_PublishRequest_deleteMembers(&request);
+    /* Manually send a publish request */
+    UA_PublishRequest pr;
+    UA_PublishRequest_init(&pr);
+    pr.subscriptionAcknowledgementsSize = 0;
+    UA_PublishResponse presponse;
+    UA_PublishResponse_init(&presponse);
+    __UA_Client_Service(client, &pr, &UA_TYPES[UA_TYPES_PUBLISHREQUEST],
+                        &presponse, &UA_TYPES[UA_TYPES_PUBLISHRESPONSE]);
+    ck_assert_uint_eq(presponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(presponse.notificationMessage.notificationDataSize, 1);
+    UA_PublishResponse_deleteMembers(&presponse);
+    UA_PublishRequest_deleteMembers(&pr);
 
-    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
+    UA_fakeSleep((UA_UInt32)(publishingInterval + 1));
 
-    /* by default maxKeepAlive is set to 1 we must receive a response without notification message */
-    UA_PublishRequest_init(&request);
-    request.subscriptionAcknowledgementsSize = 0;
+    UA_PublishRequest_init(&pr);
+    pr.subscriptionAcknowledgementsSize = 0;
+    UA_PublishResponse_init(&presponse);
+    __UA_Client_Service(client, &pr, &UA_TYPES[UA_TYPES_PUBLISHREQUEST],
+                        &presponse, &UA_TYPES[UA_TYPES_PUBLISHRESPONSE]);
 
-    response = UA_Client_Service_publish(client, request);
-    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
-    ck_assert_uint_eq(response.notificationMessage.notificationDataSize, 0);
-    UA_PublishResponse_deleteMembers(&response);
-    UA_PublishRequest_deleteMembers(&request);
+    ck_assert_uint_eq(presponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(presponse.notificationMessage.notificationDataSize, 0);
+    UA_PublishResponse_deleteMembers(&presponse);
+    UA_PublishRequest_deleteMembers(&pr);
 
-    retval = UA_Client_Subscriptions_removeMonitoredItem(client, subId, newMonitoredItemIds[0]);
+    retval = UA_Client_MonitoredItems_deleteSingle(client, subId, monId);
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
-    retval = UA_Client_Subscriptions_remove(client, subId);
+    retval = UA_Client_Subscriptions_deleteSingle(client, subId);
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
     UA_Client_disconnect(client);
@@ -264,30 +280,34 @@ START_TEST(Client_subscription_connectionClose) {
     UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
-    UA_UInt32 subId;
-    retval = UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
-    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();
+    UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request,
+                                                                            NULL, NULL, NULL);
+    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
 
     /* monitor the server state */
-    UA_UInt32 monId;
-    retval = UA_Client_Subscriptions_addMonitoredItem(client, subId, UA_NODEID_NUMERIC(0, 2259),
-                                                      UA_ATTRIBUTEID_VALUE, monitoredItemHandler,
-                                                      NULL, &monId, 250);
-    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_MonitoredItemCreateRequest monRequest =
+        UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE));
 
-    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
+    UA_MonitoredItemCreateResult monResponse =
+        UA_Client_MonitoredItems_createDataChange(client, response.subscriptionId,
+                                                  UA_TIMESTAMPSTORETURN_BOTH,
+                                                  monRequest, NULL, dataChangeHandler, NULL);
+    ck_assert_uint_eq(monResponse.statusCode, UA_STATUSCODE_GOOD);
 
-    retval = UA_Client_Subscriptions_manuallySendPublishRequest(client);
-    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_fakeSleep((UA_UInt32)publishingInterval + 1);
 
-    UA_Client_recv = client->connection.recv;
-    client->connection.recv = UA_Client_recvTesting;
+    retval = UA_Client_runAsync(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_Subscriptions_manuallySendPublishRequest(client);
-    ck_assert_uint_eq(retval, UA_STATUSCODE_BADSERVERNOTCONNECTED);
+    retval = UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 60));
+    ck_assert_uint_eq(retval, UA_STATUSCODE_BADCONNECTIONCLOSED);
 
     UA_Client_disconnect(client);
     UA_Client_delete(client);
@@ -299,35 +319,44 @@ START_TEST(Client_subscription_without_notification) {
     UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
-    UA_UInt32 subId;
-    retval = UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
-    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 = 1;
+    UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request,
+                                                                            NULL, NULL, NULL);
+    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    UA_UInt32 subId = response.subscriptionId;
 
     /* monitor the server state */
-    UA_UInt32 monId;
-    retval = UA_Client_Subscriptions_addMonitoredItem(client, subId, UA_NODEID_NUMERIC(0, 2259),
-                                                      UA_ATTRIBUTEID_VALUE, monitoredItemHandler,
-                                                      NULL, &monId, 9999999);
-    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_MonitoredItemCreateRequest monRequest =
+        UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE));
+    monRequest.requestedParameters.samplingInterval = 99999999.0;
 
-    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
+    UA_MonitoredItemCreateResult monResponse =
+        UA_Client_MonitoredItems_createDataChange(client, response.subscriptionId,
+                                                  UA_TIMESTAMPSTORETURN_BOTH,
+                                                  monRequest, NULL, dataChangeHandler, NULL);
+    UA_UInt32 monId = monResponse.monitoredItemId;
+    ck_assert_uint_eq(monResponse.statusCode, UA_STATUSCODE_GOOD);
+
+    UA_fakeSleep((UA_UInt32)publishingInterval + 1);
 
     notificationReceived = false;
-    retval = UA_Client_Subscriptions_manuallySendPublishRequest(client);
+    retval = UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_uint_eq(notificationReceived, true);
 
-    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
-
     notificationReceived = false;
-    retval = UA_Client_Subscriptions_manuallySendPublishRequest(client);
+    retval = UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_uint_eq(notificationReceived, false);
 
-    retval = UA_Client_Subscriptions_removeMonitoredItem(client, subId, monId);
+    retval = UA_Client_MonitoredItems_deleteSingle(client, subId, monId);
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
-    retval = UA_Client_Subscriptions_remove(client, subId);
+    retval = UA_Client_Subscriptions_deleteSingle(client, subId);
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
     UA_Client_disconnect(client);
@@ -343,16 +372,25 @@ stateCallback (UA_Client *client, UA_ClientState clientState){
 
     if (clientState == UA_CLIENTSTATE_SESSION){
         /* A new session was created. We need to create the subscription. */
-        /* Create a subscription */
-        UA_UInt32 subId = 0;
-        UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
+        UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
+        request.requestedMaxKeepAliveCount = 1;
+        UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request,
+                                                                                NULL, NULL, NULL);
+        ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+        UA_UInt32 subId = response.subscriptionId;
         ck_assert_uint_ne(subId, 0);
 
         /* Add a MonitoredItem */
-        UA_UInt32 monId = 0;
-        UA_NodeId monitorThis = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
-        UA_Client_Subscriptions_addMonitoredItem(client, subId, monitorThis, UA_ATTRIBUTEID_VALUE,
-                                                 &monitoredItemHandlerSubSleep, NULL, &monId, 250);
+        UA_MonitoredItemCreateRequest monRequest =
+            UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME));
+    
+        UA_MonitoredItemCreateResult monResponse =
+            UA_Client_MonitoredItems_createDataChange(client, response.subscriptionId,
+                                                      UA_TIMESTAMPSTORETURN_BOTH,
+                                                      monRequest, NULL, dataChangeHandlerSubSleep, NULL);
+        ck_assert_uint_eq(monResponse.statusCode, UA_STATUSCODE_GOOD);
+        UA_UInt32 monId = monResponse.monitoredItemId;
+
         ck_assert_uint_ne(monId, 0);
     }
 }
@@ -372,50 +410,44 @@ START_TEST(Client_subscription_async_sub) {
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
     ck_assert_uint_eq(callbackClientState, UA_CLIENTSTATE_SESSION);
 
-    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
+    UA_Client_recv = client->connection.recv;
+    client->connection.recv = UA_Client_recvTesting;
+
+    UA_fakeSleep((UA_UInt32)publishingInterval + 1);
 
     countNotificationReceived = 0;
 
     notificationReceived = false;
-    UA_Client_runAsync(client, (UA_UInt16)(UA_SubscriptionSettings_default.requestedPublishingInterval + 1));
+    UA_Client_runAsync(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)(UA_SubscriptionSettings_default.requestedPublishingInterval + 1));
+    UA_Client_runAsync(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)(UA_SubscriptionSettings_default.requestedPublishingInterval + 1));
+    UA_Client_runAsync(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)(UA_SubscriptionSettings_default.requestedPublishingInterval + 1));
+    UA_Client_runAsync(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)(UA_SubscriptionSettings_default.requestedPublishingInterval + 1));
+    UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(notificationReceived, true);
     ck_assert_uint_eq(countNotificationReceived, 5);
 
     ck_assert_uint_lt(client->config.outStandingPublishRequests, 10);
 
-    UA_Client_recv = client->connection.recv;
-    client->connection.recv = UA_Client_recvTesting;
-
     notificationReceived = false;
     /* Simulate network cable unplugged (no response from server) */
     UA_Client_recvTesting_result = UA_STATUSCODE_GOODNONCRITICALTIMEOUT;
-    UA_Client_runAsync(client, (UA_UInt16)(UA_SubscriptionSettings_default.requestedPublishingInterval + 1));
-    ck_assert_uint_eq(notificationReceived, false);
-    ck_assert_uint_eq(callbackClientState, UA_CLIENTSTATE_SESSION);
-
-    /* Simulate network cable unplugged (no response from server) */
-    UA_Client_recvTesting_result = UA_STATUSCODE_GOODNONCRITICALTIMEOUT;
-    UA_Client_runAsync(client, (UA_UInt16)clientConfig.timeout);
+    UA_Client_runAsync(client, (UA_UInt16)(publishingInterval + 1));
     ck_assert_uint_eq(notificationReceived, false);
     ck_assert_uint_eq(callbackClientState, UA_CLIENTSTATE_SESSION);
 
@@ -434,15 +466,23 @@ START_TEST(Client_methodcall) {
     UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
-    UA_UInt32 subId;
-    retval = UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
-    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
+    UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request,
+                                                                            NULL, NULL, NULL);
+    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    UA_UInt32 subId = response.subscriptionId;
 
     /* monitor the server state */
-    UA_UInt32 monId;
-    retval = UA_Client_Subscriptions_addMonitoredItem(client, subId, UA_NODEID_NUMERIC(0, 2259),
-                                                      UA_ATTRIBUTEID_VALUE, NULL, NULL, &monId, 250);
-    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_MonitoredItemCreateRequest monRequest =
+        UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE));
+
+    UA_MonitoredItemCreateResult monResponse =
+        UA_Client_MonitoredItems_createDataChange(client, response.subscriptionId,
+                                                  UA_TIMESTAMPSTORETURN_BOTH,
+                                                  monRequest, NULL, NULL, NULL);
+    ck_assert_uint_eq(monResponse.statusCode, UA_STATUSCODE_GOOD);
+
+    UA_UInt32 monId = monResponse.monitoredItemId;
 
     /* call a method to get monitored item id */
     UA_Variant input;
@@ -487,7 +527,7 @@ static Suite* testSuite_Client(void) {
 #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_addMonitoredItems);
+    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);

+ 370 - 0
tests/client/check_client_subscriptions_deprecated.c

@@ -0,0 +1,370 @@
+/* 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>
+#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;
+UA_ServerNetworkLayer nl;
+THREAD_HANDLE server_thread;
+
+THREAD_CALLBACK(serverloop) {
+    while(*running)
+        UA_Server_run_iterate(server, true);
+    return 0;
+}
+
+static void setup(void) {
+    running = UA_Boolean_new();
+    *running = true;
+    config = UA_ServerConfig_new_default();
+    server = UA_Server_new(config);
+    UA_Server_run_startup(server);
+    THREAD_CREATE(server_thread, serverloop);
+}
+
+static void teardown(void) {
+    *running = false;
+    THREAD_JOIN(server_thread);
+    UA_Server_run_shutdown(server);
+    UA_Boolean_delete(running);
+    UA_Server_delete(server);
+    UA_ServerConfig_delete(config);
+}
+
+#ifdef UA_ENABLE_SUBSCRIPTIONS
+
+UA_Boolean notificationReceived;
+UA_UInt32 countNotificationReceived = 0;
+
+static void monitoredItemHandler(UA_Client *client, UA_UInt32 monId, UA_DataValue *value, void *context) {
+    notificationReceived = true;
+    countNotificationReceived++;
+}
+
+START_TEST(Client_subscription) {
+    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_UInt32 subId;
+    retval = UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    /* monitor the server state */
+    UA_UInt32 monId;
+    retval = UA_Client_Subscriptions_addMonitoredItem(client, subId, UA_NODEID_NUMERIC(0, 2259),
+                                                      UA_ATTRIBUTEID_VALUE, monitoredItemHandler,
+                                                      NULL, &monId, 250);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
+
+    notificationReceived = false;
+    retval = UA_Client_Subscriptions_manuallySendPublishRequest(client);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+
+    retval = UA_Client_Subscriptions_removeMonitoredItem(client, subId, monId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    retval = UA_Client_Subscriptions_remove(client, subId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+}
+END_TEST
+
+START_TEST(Client_subscription_addMonitoredItems) {
+    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_UInt32 subId;
+    retval = UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_MonitoredItemCreateRequest items[3];
+    UA_MonitoredItemHandlingFunction hfs[3];
+    void *hfContexts[3];
+    UA_StatusCode itemResults[3];
+    UA_UInt32 newMonitoredItemIds[3];
+
+    /* monitor the server state */
+    UA_MonitoredItemCreateRequest_init(&items[0]);
+    items[0].itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, 2259);
+    items[0].itemToMonitor.attributeId = UA_ATTRIBUTEID_VALUE;
+    items[0].monitoringMode = UA_MONITORINGMODE_REPORTING;
+    items[0].requestedParameters.samplingInterval = 250;
+    items[0].requestedParameters.discardOldest = true;
+    items[0].requestedParameters.queueSize = 1;
+    hfs[0] = (UA_MonitoredItemHandlingFunction)(uintptr_t)monitoredItemHandler;
+    hfContexts[0] = NULL;
+
+    /* monitor current time */
+    UA_MonitoredItemCreateRequest_init(&items[1]);
+    items[1].itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
+    items[1].itemToMonitor.attributeId = UA_ATTRIBUTEID_VALUE;
+    items[1].monitoringMode = UA_MONITORINGMODE_REPORTING;
+    items[1].requestedParameters.samplingInterval = 250;
+    items[1].requestedParameters.discardOldest = true;
+    items[1].requestedParameters.queueSize = 1;
+    hfs[1] = (UA_MonitoredItemHandlingFunction)(uintptr_t)monitoredItemHandler;
+    hfContexts[1] = NULL;
+
+    /* monitor invalid node */
+    UA_MonitoredItemCreateRequest_init(&items[2]);
+    items[2].itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, 99999999);
+    items[2].itemToMonitor.attributeId = UA_ATTRIBUTEID_VALUE;
+    items[2].monitoringMode = UA_MONITORINGMODE_REPORTING;
+    items[2].requestedParameters.samplingInterval = 250;
+    items[2].requestedParameters.discardOldest = true;
+    items[2].requestedParameters.queueSize = 1;
+    hfs[2] = (UA_MonitoredItemHandlingFunction)(uintptr_t)monitoredItemHandler;
+    hfContexts[2] = NULL;
+
+    retval = UA_Client_Subscriptions_addMonitoredItems(client, subId, items, 3,
+                                                      hfs, hfContexts, itemResults,
+                                                      newMonitoredItemIds);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(itemResults[0], UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(itemResults[1], UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(itemResults[2], UA_STATUSCODE_BADNODEIDUNKNOWN);
+
+    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
+
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    retval = UA_Client_Subscriptions_manuallySendPublishRequest(client);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 2);
+
+    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
+
+    notificationReceived = false;
+    retval = UA_Client_Subscriptions_manuallySendPublishRequest(client);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 3);
+
+    retval = UA_Client_Subscriptions_removeMonitoredItem(client, subId, newMonitoredItemIds[0]);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    retval = UA_Client_Subscriptions_removeMonitoredItem(client, subId, newMonitoredItemIds[1]);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    retval = UA_Client_Subscriptions_remove(client, subId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+}
+END_TEST
+
+START_TEST(Client_subscription_keepAlive) {
+    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_UInt32 subId;
+    retval = UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_MonitoredItemCreateRequest items[1];
+    UA_MonitoredItemHandlingFunction hfs[1];
+    void *hfContexts[1];
+    UA_StatusCode itemResults[1];
+    UA_UInt32 newMonitoredItemIds[1];
+
+    /* monitor the server state */
+    UA_MonitoredItemCreateRequest_init(&items[0]);
+    items[0].itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, 2259);
+    items[0].itemToMonitor.attributeId = UA_ATTRIBUTEID_VALUE;
+    items[0].monitoringMode = UA_MONITORINGMODE_REPORTING;
+    items[0].requestedParameters.samplingInterval = 250;
+    items[0].requestedParameters.discardOldest = true;
+    items[0].requestedParameters.queueSize = 1;
+    hfs[0] = (UA_MonitoredItemHandlingFunction)(uintptr_t)monitoredItemHandler;
+    hfContexts[0] = NULL;
+
+    retval = UA_Client_Subscriptions_addMonitoredItems(client, subId, items, 1,
+                                                      hfs, hfContexts, itemResults,
+                                                      newMonitoredItemIds);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(itemResults[0], UA_STATUSCODE_GOOD);
+
+    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
+
+    UA_PublishRequest request;
+    UA_PublishRequest_init(&request);
+    request.subscriptionAcknowledgementsSize = 0;
+
+    UA_PublishResponse response;
+    UA_PublishResponse_init(&response);
+    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_PUBLISHREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_PUBLISHRESPONSE]);
+    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(response.notificationMessage.notificationDataSize, 1);
+    UA_PublishResponse_deleteMembers(&response);
+    UA_PublishRequest_deleteMembers(&request);
+
+    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
+
+    /* by default maxKeepAlive is set to 1 we must receive a response without notification message */
+    UA_PublishRequest_init(&request);
+    request.subscriptionAcknowledgementsSize = 0;
+
+    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_PUBLISHREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_PUBLISHRESPONSE]);
+    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(response.notificationMessage.notificationDataSize, 0);
+    UA_PublishResponse_deleteMembers(&response);
+    UA_PublishRequest_deleteMembers(&request);
+
+    retval = UA_Client_Subscriptions_removeMonitoredItem(client, subId, newMonitoredItemIds[0]);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    retval = UA_Client_Subscriptions_remove(client, subId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+}
+END_TEST
+
+START_TEST(Client_subscription_connectionClose) {
+    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_UInt32 subId;
+    retval = UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    /* monitor the server state */
+    UA_UInt32 monId;
+    retval = UA_Client_Subscriptions_addMonitoredItem(client, subId, UA_NODEID_NUMERIC(0, 2259),
+                                                      UA_ATTRIBUTEID_VALUE, monitoredItemHandler,
+                                                      NULL, &monId, 250);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_fakeSleep((UA_UInt32)UA_SubscriptionSettings_default.requestedPublishingInterval + 1);
+
+    retval = UA_Client_Subscriptions_manuallySendPublishRequest(client);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_Client_recv = client->connection.recv;
+    client->connection.recv = UA_Client_recvTesting;
+
+    /* Simulate BADCONNECTIONCLOSE */
+    UA_Client_recvTesting_result = UA_STATUSCODE_BADCONNECTIONCLOSED;
+
+    retval = UA_Client_Subscriptions_manuallySendPublishRequest(client);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_BADSERVERNOTCONNECTED);
+
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+}
+END_TEST
+
+START_TEST(Client_methodcall) {
+    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_UInt32 subId;
+    retval = UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    /* monitor the server state */
+    UA_UInt32 monId;
+    retval = UA_Client_Subscriptions_addMonitoredItem(client, subId, UA_NODEID_NUMERIC(0, 2259),
+                                                      UA_ATTRIBUTEID_VALUE, NULL, NULL, &monId, 250);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    /* call a method to get monitored item id */
+    UA_Variant input;
+    UA_Variant_init(&input);
+    UA_Variant_setScalarCopy(&input, &subId, &UA_TYPES[UA_TYPES_UINT32]);
+    size_t outputSize;
+    UA_Variant *output;
+    retval = UA_Client_call(client, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER),
+                UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS), 1, &input, &outputSize, &output);
+
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(outputSize, 2);
+
+    ck_assert_uint_eq(output[0].arrayLength, 1);
+
+    ck_assert_uint_eq(*((UA_UInt32*)output[0].data), monId);
+
+    UA_Array_delete(output, outputSize, &UA_TYPES[UA_TYPES_VARIANT]);
+    UA_Variant_deleteMembers(&input);
+
+    /* call with invalid subscription id */
+    UA_Variant_init(&input);
+    subId = 0;
+    UA_Variant_setScalarCopy(&input, &subId, &UA_TYPES[UA_TYPES_UINT32]);
+    retval = UA_Client_call(client, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER),
+                UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS), 1, &input, &outputSize, &output);
+
+    ck_assert_uint_eq(retval, UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID);
+
+    UA_Variant_deleteMembers(&input);
+
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+}
+END_TEST
+
+#endif /* UA_ENABLE_SUBSCRIPTIONS */
+
+static Suite* testSuite_Client(void) {
+    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_addMonitoredItems);
+    tcase_add_test(tc_client, Client_subscription_keepAlive);
+#endif /* UA_ENABLE_SUBSCRIPTIONS */
+
+    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);
+    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;
+}

+ 25 - 13
tests/fuzz/corpus_generator.c

@@ -28,6 +28,7 @@
 #include <fcntl.h>
 #include <unistd.h>
 #include <ua_client_highlevel.h>
+#include <ua_client_subscriptions.h>
 
 #include "ua_config_default.h"
 
@@ -364,26 +365,30 @@ translateBrowsePathsToNodeIdsRequest(UA_Client *client) {
 
 
 static void
-monitoredItemHandler(UA_Client *client, UA_UInt32 monId, UA_DataValue *value, void *context) {
-
+monitoredItemHandler(UA_Client *client, UA_UInt32 subId, void *subContext,
+                     UA_UInt32 monId, void *monContext, UA_DataValue *value) {
 }
 
 static UA_StatusCode
 subscriptionRequests(UA_Client *client) {
     UA_UInt32 subId;
     // createSubscriptionRequest
-    ASSERT_GOOD(UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_default, &subId));
+    UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
+    UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request,
+                                                                            NULL, NULL, NULL);
 
+    ASSERT_GOOD(response.responseHeader.serviceResult);
+    subId = response.subscriptionId;
 
     // modifySubscription
     UA_ModifySubscriptionRequest modifySubscriptionRequest;
     UA_ModifySubscriptionRequest_init(&modifySubscriptionRequest);
     modifySubscriptionRequest.subscriptionId = subId;
-    modifySubscriptionRequest.maxNotificationsPerPublish = UA_SubscriptionSettings_default.maxNotificationsPerPublish;
-    modifySubscriptionRequest.priority = UA_SubscriptionSettings_default.priority;
-    modifySubscriptionRequest.requestedLifetimeCount = UA_SubscriptionSettings_default.requestedLifetimeCount;
-    modifySubscriptionRequest.requestedMaxKeepAliveCount = UA_SubscriptionSettings_default.requestedMaxKeepAliveCount;
-    modifySubscriptionRequest.requestedPublishingInterval = UA_SubscriptionSettings_default.requestedPublishingInterval;
+    modifySubscriptionRequest.maxNotificationsPerPublish = request.maxNotificationsPerPublish;
+    modifySubscriptionRequest.priority = request.priority;
+    modifySubscriptionRequest.requestedLifetimeCount = request.requestedLifetimeCount;
+    modifySubscriptionRequest.requestedMaxKeepAliveCount = request.requestedMaxKeepAliveCount;
+    modifySubscriptionRequest.requestedPublishingInterval = request.requestedPublishingInterval;
     UA_ModifySubscriptionResponse modifySubscriptionResponse;
     __UA_Client_Service(client, &modifySubscriptionRequest, &UA_TYPES[UA_TYPES_MODIFYSUBSCRIPTIONREQUEST],
                         &modifySubscriptionResponse, &UA_TYPES[UA_TYPES_MODIFYSUBSCRIPTIONRESPONSE]);
@@ -408,9 +413,16 @@ subscriptionRequests(UA_Client *client) {
 
     // createMonitoredItemsRequest
     UA_UInt32 monId;
-    ASSERT_GOOD(UA_Client_Subscriptions_addMonitoredItem(client, subId, UA_NODEID_NUMERIC(0, 2259),
-                                                      UA_ATTRIBUTEID_VALUE, monitoredItemHandler,
-                                                      NULL, &monId, 250));
+    UA_MonitoredItemCreateRequest monRequest =
+        UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE));
+
+    UA_MonitoredItemCreateResult monResponse =
+        UA_Client_MonitoredItems_createDataChange(client, response.subscriptionId,
+                                                  UA_TIMESTAMPSTORETURN_BOTH,
+                                                  monRequest, NULL, monitoredItemHandler, NULL);
+
+    ASSERT_GOOD(monResponse.statusCode);
+    monId = monResponse.monitoredItemId;
 
     // publishRequest
     ASSERT_GOOD(UA_Client_Subscriptions_manuallySendPublishRequest(client));
@@ -459,11 +471,11 @@ subscriptionRequests(UA_Client *client) {
     UA_SetMonitoringModeResponse_deleteMembers(&setMonitoringModeResponse);
 
     // deleteMonitoredItemsRequest
-    ASSERT_GOOD(UA_Client_Subscriptions_removeMonitoredItem(client, subId, monId));
+    ASSERT_GOOD(UA_Client_MonitoredItems_deleteSingle(client, subId, monId));
 
 
     // deleteSubscriptionRequest
-    ASSERT_GOOD(UA_Client_Subscriptions_remove(client, subId));
+    ASSERT_GOOD(UA_Client_Subscriptions_deleteSingle(client, subId));
 
     return UA_STATUSCODE_GOOD;
 }

+ 2 - 0
tools/schema/datatypes_minimal.txt

@@ -195,3 +195,5 @@ SemanticChangeStructureDataType
 TimeZoneDataType
 AggregateConfiguration
 AggregateFilter
+SetTriggeringRequest
+SetTriggeringResponse