Pārlūkot izejas kodu

[FEATURE] First implementation of events (#1739)

* implement a first version of events

events are briefly stored as nodes, enabling the developer to easily set attributes. Upon triggering the event the filter is immediately applied

* added copyright

* various fixes for events

proper license, full function names in comments and small improvements to comprehension in tutorial_server_events.c. Added a message for when an EventFiler cant be malloc'd. Name improvement for the lists generated by callForEachChild. Events no longer have a name and description. Declared methods only used in subscription_events.c as static.

* further fixes for events

Rename UA_ENABLE_EVENTS to UA_ENABLE_SUBSCRIPTION_EVENTS. Do not #include <queue.h> in ua_plugin_nodestore.h. Ensure either ChangeNotify or EventNotify are being passed to a monitored item. Traded several pointers for their actual objects. Added constness to the public API. Events can only be enabled together with subscriptions and the full ns0

* Fix problems being caused in check_client_subscriptions

* even more fixes for events

removed useless backslashes for the monitoredItemList declaration, moved comment outside of the define for UA_EVENT_ATTRIBUTES, added UA_EXPORT to the declaration of UA_Server_createEvent and UA_Server_triggerEvent, renamed outId to outEventId, fixed up argument definitions for the functions, moved the generation of the overflow event to server_ns0.c, added catches for NULL as return parameters, EventId is returned in createEvent. Fixed up tutorial.

* solve merge conflicts

* fixup api
Ari 6 gadi atpakaļ
vecāks
revīzija
81a1ed350e

+ 9 - 1
CMakeLists.txt

@@ -54,12 +54,18 @@ set(UA_LOGLEVEL 300 CACHE STRING "Level at which logs shall be reported")
 option(UA_ENABLE_METHODCALLS "Enable the Method service set" ON)
 option(UA_ENABLE_NODEMANAGEMENT "Enable dynamic addition and removal of nodes at runtime" ON)
 option(UA_ENABLE_SUBSCRIPTIONS "Enable subscriptions support." ON)
+option(UA_ENABLE_SUBSCRIPTIONS_EVENTS "Enable the use of events." OFF)
 option(UA_ENABLE_DISCOVERY "Enable Discovery Service (LDS)" ON)
 option(UA_ENABLE_DISCOVERY_MULTICAST "Enable Discovery Service with multicast support (LDS-ME)" OFF)
 option(UA_ENABLE_AMALGAMATION "Concatenate the library to a single file open62541.h/.c" OFF)
 option(UA_ENABLE_COVERAGE "Enable gcov coverage" OFF)
 option(BUILD_SHARED_LIBS "Enable building of shared libraries (dll/so)" OFF)
 
+# It should not be possible to enable events without enabling subscriptions and full ns0
+if ((UA_ENABLE_SUBSCRIPTIONS_EVENTS) AND (NOT (UA_ENABLE_SUBSCRIPTIONS AND UA_ENABLE_FULL_NS0)))
+    message(FATAL_ERROR "Unable to enable events without UA_ENABLE_SUBSCRIPTIONS and UA_ENABLE_FULL_NS0")
+endif()
+
 # Encryption Options
 option(UA_ENABLE_ENCRYPTION "Enable encryption support (uses mbedTLS)" OFF)
 
@@ -395,7 +401,8 @@ set(internal_headers ${PROJECT_SOURCE_DIR}/deps/queue.h
                      ${PROJECT_SOURCE_DIR}/src/server/ua_server_internal.h
                      ${PROJECT_SOURCE_DIR}/src/server/ua_services.h
                      ${PROJECT_BINARY_DIR}/src_generated/ua_namespace0.h
-                     ${PROJECT_SOURCE_DIR}/src/client/ua_client_internal.h)
+                     ${PROJECT_SOURCE_DIR}/src/client/ua_client_internal.h
+                     ${PROJECT_SOURCE_DIR}/src/server/ua_subscription_events.h)
 
 # TODO: make client optional
 set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
@@ -420,6 +427,7 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_session_manager.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_subscription.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_subscription_datachange.c
+                ${PROJECT_SOURCE_DIR}/src/server/ua_subscription_events.c
                 ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage.c
                 ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub.c
                 ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_manager.c

+ 3 - 0
doc/CMakeLists.txt

@@ -41,6 +41,7 @@ generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_firststeps.c ${DOC_S
 generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_variable.c ${DOC_SRC_DIR}/tutorial_server_variable.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_variabletype.c ${DOC_SRC_DIR}/tutorial_server_variabletype.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_datasource.c ${DOC_SRC_DIR}/tutorial_server_datasource.rst)
+generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_events.c ${DOC_SRC_DIR}/tutorial_server_events.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_monitoreditems.c ${DOC_SRC_DIR}/tutorial_server_monitoreditems.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_object.c ${DOC_SRC_DIR}/tutorial_server_object.rst)
 generate_rst(${PROJECT_SOURCE_DIR}/examples/tutorial_server_method.c ${DOC_SRC_DIR}/tutorial_server_method.rst)
@@ -70,6 +71,7 @@ add_custom_target(doc_latex ${SPHINX_EXECUTABLE}
           ${DOC_SRC_DIR}/tutorial_pubsub_publish.rst
           ${DOC_SRC_DIR}/plugin_pubsub_connection.rst
           ${DOC_SRC_DIR}/pubsub.rst
+          ${DOC_SRC_DIR}/tutorial_server_events.rst
   COMMENT "Building LaTeX sources for documentation with Sphinx")
 add_dependencies(doc_latex open62541)
 
@@ -104,6 +106,7 @@ add_custom_target(doc ${SPHINX_EXECUTABLE}
           ${DOC_SRC_DIR}/tutorial_pubsub_publish.rst
           ${DOC_SRC_DIR}/plugin_pubsub_connection.rst
           ${DOC_SRC_DIR}/pubsub.rst
+          ${DOC_SRC_DIR}/tutorial_server_events.rst
   COMMENT "Building HTML documentation with Sphinx")
 add_dependencies(doc open62541)
 

+ 2 - 0
doc/building.rst

@@ -178,6 +178,8 @@ This group contains build options related to the supported OPC UA features.
 
 **UA_ENABLE_SUBSCRIPTIONS**
    Enable subscriptions
+**UA_ENABLE_SUBSCRIPTIONS_EVENTS**
+    Enable the use of events for subscriptions
 **UA_ENABLE_METHODCALLS**
    Enable the Method service set
 **UA_ENABLE_NODEMANAGEMENT**

+ 1 - 0
doc/tutorials.rst

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

+ 4 - 0
examples/CMakeLists.txt

@@ -54,6 +54,10 @@ add_example(tutorial_client_firststeps tutorial_client_firststeps.c)
 
 add_example(tutorial_client_events tutorial_client_events.c)
 
+if(UA_ENABLE_SUBSCRIPTIONS_EVENTS)
+  add_example(tutorial_server_events tutorial_server_events.c)
+endif()
+
 ##################
 # Example Server #
 ##################

+ 177 - 0
examples/tutorial_server_events.c

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

+ 1 - 0
include/ua_config.h.in

@@ -29,6 +29,7 @@ extern "C" {
 #cmakedefine UA_ENABLE_SUBSCRIPTIONS
 #cmakedefine UA_ENABLE_PUBSUB
 #cmakedefine UA_ENABLE_ENCRYPTION
+#cmakedefine UA_ENABLE_SUBSCRIPTIONS_EVENTS
 
 /* Multithreading */
 #cmakedefine UA_ENABLE_MULTITHREADING

+ 17 - 0
include/ua_plugin_nodestore.h

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

+ 49 - 0
include/ua_server.h

@@ -1115,6 +1115,55 @@ UA_Server_deleteReference(UA_Server *server, const UA_NodeId sourceNodeId,
                           const UA_ExpandedNodeId targetNodeId,
                           UA_Boolean deleteBidirectional);
 
+/**
+ * .. _events:
+ *
+ * Events
+ * ------
+ * The method ``UA_Server_createEvent`` creates an event and represents it as node. The node receives a unique `EventId`
+ * which is automatically added to the node.
+ * The method returns a `NodeId` to the object node which represents the event through ``outNodeId``. The `NodeId` can
+ * be used to set the attributes of the event. The generated `NodeId` is always numeric. ``outNodeId`` cannot be 
+ * ``NULL``.
+ *
+ * The method ``UA_Server_triggerEvent`` "triggers" an event by adding it to all monitored items of the specified
+ * origin node and those of all its parents. Any filters specified by the monitored items are automatically applied.
+ * Using this method deletes the node generated by ``UA_Server_createEvent``. The `EventId` for the new event is
+ * generated automatically and is returned through ``outEventId``.``NULL`` can be passed if the `EventId` is not
+ * needed. ``deleteEventNode`` specifies whether the node representation of the event should be deleted after invoking
+ * the method. This can be useful if events with the similar attributes are triggered frequently. ``UA_TRUE`` would
+ * cause the node to be deleted. */
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+
+/* The EventQueueOverflowEventType is defined as abstract, therefore we can not create an instance of that type
+ * directly, but need to create a subtype. The following is an arbitrary number which shall refer to our internal
+ * overflow type.
+ * This is already posted on the OPC Foundation bug tracker under the following link for clarification:
+ * https://opcfoundation-onlineapplications.org/mantis/view.php?id=4206 */
+# define UA_NS0ID_SIMPLEOVERFLOWEVENTTYPE 4035
+
+/* Creates a node representation of an event
+ *
+ * @param server The server object
+ * @param eventType The type of the event for which a node should be created
+ * @param outNodeId The NodeId of the newly created node for the event
+ * @return The StatusCode of the UA_Server_createEvent method */
+UA_StatusCode UA_EXPORT
+UA_Server_createEvent(UA_Server *server, const UA_NodeId eventType,
+                      UA_NodeId *outNodeId);
+
+/* Triggers a node representation of an event by applying EventFilters and adding the event to the appropriate queues.
+ * @param server The server object
+ * @param eventNodeId The NodeId of the node representation of the event which should be triggered
+ * @param outEvent the EventId of the new event
+ * @param deleteEventNode Specifies whether the node representation of the event should be deleted
+ * @return The StatusCode of the UA_Server_triggerEvent method */
+UA_StatusCode UA_EXPORT
+UA_Server_triggerEvent(UA_Server *server, const UA_NodeId eventNodeId, const UA_NodeId originId,
+                       UA_ByteString *outEventId, const UA_Boolean deleteEventNode);
+
+#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
+
 /**
  * Utility Functions
  * ----------------- */

+ 3 - 0
include/ua_server_config.h

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

+ 3 - 0
plugins/ua_config_default.c

@@ -268,6 +268,9 @@ createDefaultConfig(void) {
     conf->keepAliveCountLimits = UA_UINT32RANGE(1, 100);
     conf->maxNotificationsPerPublish = 1000;
     conf->maxRetransmissionQueueSize = 0; /* unlimited */
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+    conf->maxEventsPerNode = 0; /* unlimited */
+#endif
 
     /* Limits for MonitoredItems */
     conf->samplingIntervalLimits = UA_DURATIONRANGE(50.0, 24.0 * 3600.0 * 1000.0);

+ 18 - 0
src/server/ua_server_ns0.c

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

+ 1 - 0
src/server/ua_server_worker.c

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

+ 35 - 8
src/server/ua_services_subscription.c

@@ -8,6 +8,7 @@
  *    Copyright 2015-2016 (c) Sten Grüner
  *    Copyright 2015-2016 (c) Oleksiy Vasylyev
  *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
+ *    Copyright 2018 (c) Ari Breitkreuz, fortiss GmbH
  *    Copyright 2017 (c) Mattias Bornhager
  *    Copyright 2017 (c) Henrik Norrman
  *    Copyright 2017-2018 (c) Thomas Stalder, Blue Time Concept SA
@@ -163,11 +164,9 @@ setMonitoredItemSettings(UA_Server *server, UA_MonitoredItem *mon,
 
     /* Filter */
     if(params->filter.encoding != UA_EXTENSIONOBJECT_DECODED) {
-        UA_DataChangeFilter_init(&(mon->filter));
-        mon->filter.trigger = UA_DATACHANGETRIGGER_STATUSVALUE;
-    } else if(params->filter.content.decoded.type != &UA_TYPES[UA_TYPES_DATACHANGEFILTER]) {
-        return UA_STATUSCODE_BADMONITOREDITEMFILTERINVALID;
-    } else {
+        UA_DataChangeFilter_init(&(mon->filter.dataChangeFilter));
+        mon->filter.dataChangeFilter.trigger = UA_DATACHANGETRIGGER_STATUSVALUE;
+    } else if(params->filter.content.decoded.type == &UA_TYPES[UA_TYPES_DATACHANGEFILTER]) {
         UA_DataChangeFilter *filter = (UA_DataChangeFilter *)params->filter.content.decoded.data;
         // TODO implement EURange to support UA_DEADBANDTYPE_PERCENT
         if (filter->deadbandType == UA_DEADBANDTYPE_PERCENT) {
@@ -180,7 +179,11 @@ setMonitoredItemSettings(UA_Server *server, UA_MonitoredItem *mon,
         if (!isDataTypeNumeric(mon->lastValue.type)) {
             return UA_STATUSCODE_BADFILTERNOTALLOWED;
         }
-        UA_DataChangeFilter_copy(filter, &(mon->filter));
+        UA_DataChangeFilter_copy(filter, &(mon->filter.dataChangeFilter));
+    } else if (params->filter.content.decoded.type == &UA_TYPES[UA_TYPES_EVENTFILTER]) {
+        UA_EventFilter_copy((UA_EventFilter *)params->filter.content.decoded.data, &(mon->filter.eventFilter));
+    } else {
+        return UA_STATUSCODE_BADMONITOREDITEMFILTERINVALID;
     }
 
     UA_MonitoredItem_unregisterSampleCallback(server, mon);
@@ -192,6 +195,7 @@ setMonitoredItemSettings(UA_Server *server, UA_MonitoredItem *mon,
     /* SamplingInterval */
     UA_Double samplingInterval = params->samplingInterval;
     if(mon->attributeId == UA_ATTRIBUTEID_VALUE) {
+        mon->monitoredItemType = UA_MONITOREDITEMTYPE_CHANGENOTIFY;
         const UA_VariableNode *vn = (const UA_VariableNode *)
             UA_Nodestore_get(server, &mon->monitoredNodeId);
         if(vn) {
@@ -203,6 +207,9 @@ setMonitoredItemSettings(UA_Server *server, UA_MonitoredItem *mon,
     } else if(mon->attributeId == UA_ATTRIBUTEID_EVENTNOTIFIER) {
         /* TODO: events should not need a samplinginterval */
         samplingInterval = 10000.0f; // 10 seconds to reduce the load
+        mon->monitoredItemType = UA_MONITOREDITEMTYPE_EVENTNOTIFY;
+    } else {
+        mon->monitoredItemType = UA_MONITOREDITEMTYPE_CHANGENOTIFY;
     }
     mon->samplingInterval = samplingInterval;
     UA_BOUNDEDVALUE_SETWBOUNDS(server->config.samplingIntervalLimits,
@@ -225,6 +232,17 @@ setMonitoredItemSettings(UA_Server *server, UA_MonitoredItem *mon,
 
 static const UA_String binaryEncoding = {sizeof("Default Binary") - 1, (UA_Byte *)"Default Binary"};
 
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+static UA_StatusCode UA_Server_addMonitoredItemToNodeEditNodeCallback(UA_Server *server, UA_Session *session,
+                                                                      UA_Node *node, void *data) {
+    /* data is the MonitoredItem */
+    /* SLIST_INSERT_HEAD */
+    ((UA_MonitoredItem *)data)->next = ((UA_ObjectNode *)node)->monitoredItemQueue;
+    ((UA_ObjectNode *)node)->monitoredItemQueue = (UA_MonitoredItem *)data;
+    return UA_STATUSCODE_GOOD;
+}
+#endif
+
 /* Thread-local variables to pass additional arguments into the operation */
 struct createMonContext {
     UA_Subscription *sub;
@@ -314,7 +332,15 @@ Operation_CreateMonitoredItem(UA_Server *server, UA_Session *session, struct cre
     if(cmc->sub) {
         newMon->monitoredItemId = ++cmc->sub->lastMonitoredItemId;
         UA_Subscription_addMonitoredItem(cmc->sub, newMon);
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+        if (newMon->monitoredItemType == UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
+        /* insert the monitored item into the node's queue */
+        UA_Server_editNode(server, NULL, &newMon->monitoredNodeId, UA_Server_addMonitoredItemToNodeEditNodeCallback,
+                           newMon);
+    }
+#endif
     } else {
+        //TODO support events for local monitored items
         UA_LocalMonitoredItem *localMon = (UA_LocalMonitoredItem*)newMon;
         localMon->context = cmc->context;
         localMon->callback.dataChangeCallback = cmc->dataChangeCallback;
@@ -407,7 +433,7 @@ Operation_ModifyMonitoredItem(UA_Server *server, UA_Session *session, UA_Subscri
     result->revisedQueueSize = mon->maxQueueSize;
 
     /* Remove some notifications if the queue is now too small */
-    MonitoredItem_ensureQueueSpace(mon);
+    MonitoredItem_ensureQueueSpace(server, mon);
 }
 
 void
@@ -459,7 +485,8 @@ Operation_SetMonitoringMode(UA_Server *server, UA_Session *session,
         return;
     }
 
-    if(mon->monitoredItemType != UA_MONITOREDITEMTYPE_CHANGENOTIFY) {
+    if(mon->monitoredItemType != UA_MONITOREDITEMTYPE_CHANGENOTIFY
+           && mon->monitoredItemType != UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
         *result = UA_STATUSCODE_BADNOTIMPLEMENTED;
         return;
     }

+ 107 - 33
src/server/ua_subscription.c

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

+ 15 - 7
src/server/ua_subscription.h

@@ -41,14 +41,16 @@ typedef enum {
     UA_MONITOREDITEMTYPE_EVENTNOTIFY = 4
 } UA_MonitoredItemType;
 
-/* Not used yet. Placeholder for a future event implementation. */
-typedef struct UA_Event {
-   UA_Int32 eventId;
-} UA_Event;
 
 struct UA_MonitoredItem;
 typedef struct UA_MonitoredItem UA_MonitoredItem;
 
+typedef struct UA_EventNotification {
+    UA_EventFieldList fields;
+    /* EventFilterResult currently isn't being used
+    UA_EventFilterResult result; */
+} UA_EventNotification;
+
 typedef struct UA_Notification {
     TAILQ_ENTRY(UA_Notification) listEntry; /* Notification list for the MonitoredItem */
     TAILQ_ENTRY(UA_Notification) globalEntry; /* Notification list for the Subscription */
@@ -57,7 +59,7 @@ typedef struct UA_Notification {
 
     /* See the monitoredItemType of the MonitoredItem */
     union {
-        UA_Event event;
+        UA_EventNotification event;
         UA_DataValue value;
     } data;
 } UA_Notification;
@@ -84,7 +86,10 @@ struct UA_MonitoredItem {
     UA_UInt32 maxQueueSize;
     UA_Boolean discardOldest;
     // TODO: dataEncoding is hardcoded to UA binary
-    UA_DataChangeFilter filter;
+    union {
+        UA_EventFilter eventFilter;
+        UA_DataChangeFilter dataChangeFilter;
+    } filter;
     UA_Variant lastValue;
 
     /* Sample Callback */
@@ -95,6 +100,9 @@ struct UA_MonitoredItem {
     /* Notification Queue */
     NotificationQueue queue;
     UA_UInt32 queueSize;
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+    UA_MonitoredItem *next;
+#endif
 };
 
 void UA_MonitoredItem_init(UA_MonitoredItem *mon, UA_Subscription *sub);
@@ -105,7 +113,7 @@ UA_StatusCode UA_MonitoredItem_unregisterSampleCallback(UA_Server *server, UA_Mo
 
 /* Remove entries until mon->maxQueueSize is reached. Sets infobits for lost
  * data if required. */
-void MonitoredItem_ensureQueueSpace(UA_MonitoredItem *mon);
+UA_StatusCode MonitoredItem_ensureQueueSpace(UA_Server *server, UA_MonitoredItem *mon);
 
 /****************/
 /* Subscription */

+ 105 - 12
src/server/ua_subscription_datachange.c

@@ -4,12 +4,14 @@
  *
  *    Copyright 2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
  *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
+ *    Copyright 2018 (c) Ari Breitkreuz, fortiss GmbH
  *    Copyright 2018 (c) Thomas Stalder, Blue Time Concept SA
  */
 
 #include "ua_server_internal.h"
 #include "ua_subscription.h"
 #include "ua_types_encoding_binary.h"
+#include "ua_subscription_events.h"
 
 #ifdef UA_ENABLE_SUBSCRIPTIONS /* conditional compilation */
 
@@ -21,14 +23,39 @@ void UA_MonitoredItem_init(UA_MonitoredItem *mon, UA_Subscription *sub) {
     TAILQ_INIT(&mon->queue);
 }
 
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+static UA_StatusCode removeMonitoredItemFromNodeCallback(UA_Server *server, UA_Session *session, UA_Node *node,
+                                                         void *data) {
+    /* data is the monitoredItemID */
+    /* catch edge case that it's the first element */
+    if (data == ((UA_ObjectNode *) node)->monitoredItemQueue) {
+        ((UA_ObjectNode *)node)->monitoredItemQueue = ((UA_MonitoredItem *)data)->next;
+        return UA_STATUSCODE_GOOD;
+    }
+    /* SLIST_FOREACH */
+    for (UA_MonitoredItem *entry = ((UA_ObjectNode *) node)->monitoredItemQueue->next;
+         entry != NULL; entry=entry->next) {
+        if (entry == (UA_MonitoredItem *)data) {
+            /* SLIST_REMOVE */
+            UA_MonitoredItem *iter = ((UA_ObjectNode *) node)->monitoredItemQueue;
+            for (; iter->next != entry; iter=iter->next) {}
+            iter->next = entry->next;
+            UA_free(entry);
+            break;
+        }
+    }
+    return UA_STATUSCODE_GOOD;
+}
+#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
+
 void UA_MonitoredItem_delete(UA_Server *server, UA_MonitoredItem *monitoredItem) {
     if(monitoredItem->monitoredItemType == UA_MONITOREDITEMTYPE_CHANGENOTIFY) {
         /* Remove the sampling callback */
         UA_MonitoredItem_unregisterSampleCallback(server, monitoredItem);
-    } else {
+    } else if (monitoredItem->monitoredItemType != UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
         /* TODO: Access val data.event */
         UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
-                     "MonitoredItemTypes other than ChangeNotify are not supported yet");
+                     "MonitoredItemTypes other than ChangeNotify or EventNotify are not supported yet");
     }
 
     /* Remove the queued notifications if attached to a subscription */
@@ -41,11 +68,25 @@ void UA_MonitoredItem_delete(UA_Server *server, UA_MonitoredItem *monitoredItem)
             TAILQ_REMOVE(&monitoredItem->queue, notification, listEntry);
             TAILQ_REMOVE(&sub->notificationQueue, notification, globalEntry);
             --sub->notificationQueueSize;
+            /*
+            if (monitoredItem->monitoredItemType == UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
+                 EventFilterResult currently isn't being used
+                UA_EventFilterResult_delete(notification->data.event->result);
+            }
+            */
             UA_Notification_delete(notification);
         }
         monitoredItem->queueSize = 0;
     }
-
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+    if (monitoredItem->monitoredItemType == UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
+        /* Remove the monitored item from the node queue */
+        UA_Server_editNode(server, NULL, &monitoredItem->monitoredNodeId, removeMonitoredItemFromNodeCallback,
+                           monitoredItem);
+        /* Delete the event filter */
+        UA_EventFilter_deleteMembers(&monitoredItem->filter.eventFilter);
+    }
+#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
     /* Remove the monitored item */
     UA_String_deleteMembers(&monitoredItem->indexRange);
     UA_ByteString_deleteMembers(&monitoredItem->lastSampledValue);
@@ -54,9 +95,9 @@ void UA_MonitoredItem_delete(UA_Server *server, UA_MonitoredItem *monitoredItem)
     UA_Server_delayedFree(server, monitoredItem);
 }
 
-void MonitoredItem_ensureQueueSpace(UA_MonitoredItem *mon) {
+UA_StatusCode MonitoredItem_ensureQueueSpace(UA_Server *server, UA_MonitoredItem *mon) {
     if(mon->queueSize <= mon->maxQueueSize)
-        return;
+        return UA_STATUSCODE_GOOD;
 
     /* Remove notifications until the queue size is reached */
     UA_Subscription *sub = mon->subscription;
@@ -92,10 +133,61 @@ void MonitoredItem_ensureQueueSpace(UA_MonitoredItem *mon) {
         /* Remove the notification from the queues */
         TAILQ_REMOVE(&mon->queue, del, listEntry);
         TAILQ_REMOVE(&sub->notificationQueue, del, globalEntry);
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+        /* TODO: provide additional protection for overflowEvents according to specification */
+        /* removing an overflowEvent should not reduce the queueSize */
+        UA_NodeId overflowId = UA_NODEID_NUMERIC(0, UA_NS0ID_SIMPLEOVERFLOWEVENTTYPE);
+        if (!(del->data.event.fields.eventFieldsSize == 1
+              && del->data.event.fields.eventFields->type == &UA_TYPES[UA_TYPES_NODEID]
+              && UA_NodeId_equal((UA_NodeId *)del->data.event.fields.eventFields->data, &overflowId))) {
+            --mon->queueSize;
+            --sub->notificationQueueSize;
+        }
+#else
         --mon->queueSize;
         --sub->notificationQueueSize;
+#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
 
         /* Free the notification */
+        if (mon->monitoredItemType == UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
+#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+            /* EventFilterResult currently isn't being used
+            UA_EventFilterResult_deleteMembers(&del->data.event->result); */
+            UA_EventFieldList_deleteMembers(&del->data.event.fields);
+
+            /* cause an overflowEvent */
+            /* an overflowEvent does not care about event filters and as such will not be "triggered" correctly.
+             * Instead, a notification will be inserted into the queue which includes only the nodeId of the
+             * overflowEventType. It is up to the client to check for possible overflows.
+             */
+            UA_Notification *overflowNotification = (UA_Notification *) UA_malloc(sizeof(UA_Notification));
+            if (!overflowNotification) {
+                return UA_STATUSCODE_BADOUTOFMEMORY;
+            }
+
+            UA_EventFieldList_init(&overflowNotification->data.event.fields);
+
+            overflowNotification->data.event.fields.eventFields = UA_Variant_new();
+            if (!overflowNotification->data.event.fields.eventFields) {
+                UA_EventFieldList_deleteMembers(&overflowNotification->data.event.fields);
+                UA_free(overflowNotification);
+                return UA_STATUSCODE_BADOUTOFMEMORY;
+            }
+            UA_Variant_init(overflowNotification->data.event.fields.eventFields);
+
+            overflowNotification->data.event.fields.eventFieldsSize = 1;
+            UA_Variant_setScalarCopy(overflowNotification->data.event.fields.eventFields,
+                                              &overflowId, &UA_TYPES[UA_TYPES_NODEID]);
+            overflowNotification->mon = mon;
+            if (mon->discardOldest) {
+                TAILQ_INSERT_HEAD(&mon->queue, overflowNotification, listEntry);
+                TAILQ_INSERT_HEAD(&mon->subscription->notificationQueue, overflowNotification, globalEntry);
+            } else {
+                TAILQ_INSERT_TAIL(&mon->queue, overflowNotification, listEntry);
+                TAILQ_INSERT_TAIL(&mon->subscription->notificationQueue, overflowNotification, globalEntry);
+            }
+#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
+        }
         UA_Notification_delete(del);
     }
 
@@ -121,6 +213,7 @@ void MonitoredItem_ensureQueueSpace(UA_MonitoredItem *mon) {
     }
 
     /* TODO: Infobits for Events? */
+    return UA_STATUSCODE_GOOD;
 }
 
 #define ABS_SUBTRACT_TYPE_INDEPENDENT(a,b) ((a)>(b)?(a)-(b):(b)-(a))
@@ -202,10 +295,10 @@ detectValueChangeWithFilter(UA_Server *server, UA_MonitoredItem *mon, UA_DataVal
     }
 
     if(isDataTypeNumeric(value->value.type) &&
-       (mon->filter.trigger == UA_DATACHANGETRIGGER_STATUSVALUE ||
-        mon->filter.trigger == UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP)) {
-        if(mon->filter.deadbandType == UA_DEADBANDTYPE_ABSOLUTE) {
-            if(!updateNeededForFilteredValue(&value->value, &mon->lastValue, mon->filter.deadbandValue))
+       (mon->filter.dataChangeFilter.trigger == UA_DATACHANGETRIGGER_STATUSVALUE ||
+        mon->filter.dataChangeFilter.trigger == UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP)) {
+        if(mon->filter.dataChangeFilter.deadbandType == UA_DEADBANDTYPE_ABSOLUTE) {
+            if(!updateNeededForFilteredValue(&value->value, &mon->lastValue, mon->filter.dataChangeFilter.deadbandValue))
                 return false;
         }
         /* else if (mon->filter.deadbandType == UA_DEADBANDTYPE_PERCENT) {
@@ -285,12 +378,12 @@ static UA_Boolean
 detectValueChange(UA_Server *server, UA_MonitoredItem *mon,
                   UA_DataValue value, UA_ByteString *encoding) {
     /* Apply Filter */
-    if(mon->filter.trigger == UA_DATACHANGETRIGGER_STATUS)
+    if(mon->filter.dataChangeFilter.trigger == UA_DATACHANGETRIGGER_STATUS)
         value.hasValue = false;
 
     value.hasServerTimestamp = false;
     value.hasServerPicoseconds = false;
-    if(mon->filter.trigger < UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP) {
+    if(mon->filter.dataChangeFilter.trigger < UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP) {
         value.hasSourceTimestamp = false;
         value.hasSourcePicoseconds = false;
     }
@@ -341,7 +434,7 @@ sampleCallbackWithValue(UA_Server *server, UA_MonitoredItem *monitoredItem,
         ++sub->notificationQueueSize;
 
         /* Remove some notifications if the queue is beyond maximum capacity */
-        MonitoredItem_ensureQueueSpace(monitoredItem);
+        MonitoredItem_ensureQueueSpace(server, monitoredItem);
     } else {
         /* Call the local callback if not attached to a subscription */
         UA_LocalMonitoredItem *localMon = (UA_LocalMonitoredItem*) monitoredItem;

+ 462 - 0
src/server/ua_subscription_events.c

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

+ 16 - 0
src/server/ua_subscription_events.h

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