Ver código fonte

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

Stefan Profanter 6 anos atrás
pai
commit
5486ba35a7

+ 15 - 2
CMakeLists.txt

@@ -160,6 +160,13 @@ endif()
 
 option(UA_ENABLE_PUBSUB "Enable publish/subscribe (experimental)" OFF)
 mark_as_advanced(UA_ENABLE_PUBSUB)
+option(UA_ENABLE_PUBSUB_INFORMATIONMODEL "Enable PubSub information model twin" OFF)
+mark_as_advanced(UA_ENABLE_PUBSUB_INFORMATIONMODEL)
+if(UA_ENABLE_PUBSUB_INFORMATIONMODEL)
+    if(NOT UA_ENABLE_PUBSUB)
+    message(FATAL_ERROR "PubSub information model representation cannot be used with disabled PubSub function.")
+    endif()
+endif()
 
 option(UA_ENABLE_STATUSCODE_DESCRIPTIONS "Enable conversion of StatusCode to human-readable error message" ON)
 mark_as_advanced(UA_ENABLE_STATUSCODE_DESCRIPTIONS)
@@ -421,11 +428,11 @@ set(internal_headers ${PROJECT_SOURCE_DIR}/deps/queue.h
                      ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage.h
                      ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub.h
                      ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_manager.h
+                     ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_ns0.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/server/ua_subscription_events.h)
+                     ${PROJECT_SOURCE_DIR}/src/client/ua_client_internal.h)
 
 # TODO: make client optional
 set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
@@ -454,6 +461,7 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.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
+                ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_ns0.c
                 # services
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_view.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_call.c
@@ -559,6 +567,10 @@ else()
     if(UA_ENABLE_DISCOVERY)
         list(APPEND UA_FILE_DATATYPES ${PROJECT_SOURCE_DIR}/tools/schema/datatypes_discovery.txt)
     endif()
+
+    if(UA_ENABLE_PUBSUB_INFORMATIONMODEL)
+        set(UA_FILE_NSPUBSUB --xml ${PROJECT_SOURCE_DIR}/tools/schema/Opc.Ua.NodeSet2.PubSubMinimal.xml)
+    endif()
 endif()
 
 if(NOT EXISTS "${UA_FILE_NS0}")
@@ -687,6 +699,7 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_namespace0.c
                            --encode-binary-size=${UA_NODESET_ENCODE_BINARY_SIZE}
                            --ignore ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/NodeID_NS0_Base.txt
                            --xml ${UA_FILE_NS0}
+                           ${UA_FILE_NSPUBSUB}
                            ${PROJECT_BINARY_DIR}/src_generated/ua_namespace0
                    DEPENDS ${UA_FILE_NS0}
                            ${PROJECT_SOURCE_DIR}/tools/nodeset_compiler/nodeset_compiler.py

+ 61 - 61
FEATURES.md

@@ -4,94 +4,94 @@ open62541 Supported Features
 | __**Service**__             |                                 |                      | Comment              |
 |:----------------------------|:--------------------------------|:--------------------:|:---------------------|
 | Discovery Service Set       |                                 |                      |                      |
-|                             | FindServers()                   |  :white_check_mark:  |                      |
-|                             | FindServersOnNetwork()          |     :full_moon:      | Branch: master, Release 0.3  |
-|                             | GetEndpoints()                  |  :white_check_mark:  |                      |
-|                             | RegisterServer()                |  :white_check_mark:  |                      |
-|                             | RegisterServer2()               |     :full_moon:      | Branch: master, Release 0.3  |
+|                             | FindServers()                   |  :heavy_check_mark:  |                      |
+|                             | FindServersOnNetwork()          |     :full_moon:      | master, Release 0.3  |
+|                             | GetEndpoints()                  |  :heavy_check_mark:  |                      |
+|                             | RegisterServer()                |  :heavy_check_mark:  |                      |
+|                             | RegisterServer2()               |     :full_moon:      | master, Release 0.3  |
 | Secure Channel Service Set  |                                 |                      |                      |
-|                             | OpenSecureChannel()             |  :white_check_mark:  |                      |
-|                             | CloseSecureChannel()            |  :white_check_mark:  |                      |
+|                             | OpenSecureChannel()             |  :heavy_check_mark:  |                      |
+|                             | CloseSecureChannel()            |  :heavy_check_mark:  |                      |
 | Session Service Set         |                                 |                      |                      |
-|                             | CreateSession()                 |  :white_check_mark:  |                      |
-|                             | CloseSession()                  |  :white_check_mark:  |                      |
-|                             | ActivateSession()               |  :white_check_mark:  |                      |
+|                             | CreateSession()                 |  :heavy_check_mark:  |                      |
+|                             | CloseSession()                  |  :heavy_check_mark:  |                      |
+|                             | ActivateSession()               |  :heavy_check_mark:  |                      |
 |                             | Cancel()                        |      :new_moon:      |                      |
 | Node Management Service Set |                                 |                      |                      |
-|                             | AddNodes()                      |  :white_check_mark:  |                      |
-|                             | AddReferences()                 |  :white_check_mark:  |                      |
-|                             | DeleteNodes()                   |  :white_check_mark:  |                      |
-|                             | DeleteReferences()              |  :white_check_mark:  |                      |
+|                             | AddNodes()                      |  :heavy_check_mark:  |                      |
+|                             | AddReferences()                 |  :heavy_check_mark:  |                      |
+|                             | DeleteNodes()                   |  :heavy_check_mark:  |                      |
+|                             | DeleteReferences()              |  :heavy_check_mark:  |                      |
 | View Service Set            |                                 |                      |                      |
-|                             | Browse()                        |  :white_check_mark:  |                      |
-|                             | BrowseNext()                    |  :white_check_mark:  |                      |
-|                             | TranslateBrowsePathsToNodeIds() |  :white_check_mark:  |                      |
-|                             | RegisterNodes()                 |  :white_check_mark:  |                      |
-|                             | UnregisterNodes()               |  :white_check_mark:  |                      |
+|                             | Browse()                        |  :heavy_check_mark:  |                      |
+|                             | BrowseNext()                    |  :heavy_check_mark:  |                      |
+|                             | TranslateBrowsePathsToNodeIds() |  :heavy_check_mark:  |                      |
+|                             | RegisterNodes()                 |  :heavy_check_mark:  |                      |
+|                             | UnregisterNodes()               |  :heavy_check_mark:  |                      |
 | Query Service Set           |                                 |                      |                      |
 |                             | QueryFirst()                    |      :new_moon:      |                      |
 |                             | QueryNext()                     |      :new_moon:      |                      |
 | Attribute Service Set       |                                 |                      |                      |
-|                             | Read()                          |  :white_check_mark:  |                      |
-|                             | Write()                         |  :white_check_mark:  |                      |
-|                             | HistoryRead()                   |      :new_moon:      |                      |
-|                             | HistoryUpdate()                 |      :new_moon:      |                      |
+|                             | Read()                          |  :heavy_check_mark:  |                      |
+|                             | Write()                         |  :heavy_check_mark:  |                      |
+|                             | HistoryRead()                   | :waning_gibbous_moon: | [WIP](https://github.com/open62541/open62541/pull/1740), Release 0.4     |
+|                             | HistoryUpdate()                 | :waning_gibbous_moon: | [WIP](https://github.com/open62541/open62541/pull/1740), Release 0.4     |
 | Method Service Set          |                                 |                      |                      |
-|                             | Call()                          |  :white_check_mark:  |                      |
+|                             | Call()                          |  :heavy_check_mark:  |                      |
 | MonitoredItems Service Set  |                                 |                      |                      |
-|                             | CreateMonitoredItems()          |  :white_check_mark:  |                      |
-|                             | DeleteMonitoredItems()          |  :white_check_mark:  |                      |
-|                             | ModifyMonitoredItems()          |  :white_check_mark:  |                      |
-|                             | SetMonitoringMode()             |  :white_check_mark:  |                      |
+|                             | CreateMonitoredItems()          |  :heavy_check_mark:  |                      |
+|                             | DeleteMonitoredItems()          |  :heavy_check_mark:  |                      |
+|                             | ModifyMonitoredItems()          |  :heavy_check_mark:  |                      |
+|                             | SetMonitoringMode()             |  :heavy_check_mark:  |                      |
 |                             | SetTriggering()                 |      :new_moon:      |                      |
 | Subscription Service Set    |                                 |                      |                      |
-|                             | CreateSubscription()            |  :white_check_mark:  |                      |
-|                             | ModifySubscription()            |  :white_check_mark:  |                      |
-|                             | SetPublishingMode()             |  :white_check_mark:  |                      |
-|                             | Publish()                       |  :white_check_mark:  |                      |
-|                             | Republish()                     |  :white_check_mark:  |                      |
-|                             | DeleteSubscriptions()           |  :white_check_mark:  |                      |
+|                             | CreateSubscription()            |  :heavy_check_mark:  |                      |
+|                             | ModifySubscription()            |  :heavy_check_mark:  |                      |
+|                             | SetPublishingMode()             |  :heavy_check_mark:  |                      |
+|                             | Publish()                       |  :heavy_check_mark:  |                      |
+|                             | Republish()                     |  :heavy_check_mark:  |                      |
+|                             | DeleteSubscriptions()           |  :heavy_check_mark:  |                      |
 |                             | TransferSubscriptions()         |      :new_moon:      |                      |
 
 
 |                                         |                      |                      |
 |:----------------------------------------|:--------------------:|:---------------------|
 | **Transport**                           |                      |                      |
-| UA-TCP UA-SC UA Binary                  |  :white_check_mark:  | OPC.TCP - Binary     |
+| UA-TCP UA-SC UA Binary                  |  :heavy_check_mark:  | OPC.TCP - Binary     |
 | SOAP-HTTP WS-SC UA Binary               |      :new_moon:      | HTTP/HTTPS - Binary  |
 | SOAP-HTTP WS-SC UA XML                  |      :new_moon:      |                      |
 | SOAP-HTTP WS-SC UA XML-UA Binary        |      :new_moon:      |                      |
 | **Encryption**                          |                      |                      |
-| None                                    |  :white_check_mark:  |                      |
-| Basic128Rsa15                           |  :white_check_mark:  | master, Release 0.3  |
-| Basic256                                | :waning_gibbous_moon: | [WIP](https://github.com/open62541/open62541/pull/1282), Release 0.3     |
-| Basic256Sha256                          |  :waning_gibbous_moon: | [WIP](https://github.com/open62541/open62541/pull/1282), Release 0.3     |
+| None                                    |  :heavy_check_mark:  |                      |
+| Basic128Rsa15                           |  :heavy_check_mark:  | master, Release 0.3  |
+| Basic256                                |  :heavy_check_mark:  | master               |
+| Basic256Sha256                          |  :heavy_check_mark:  | master               |
 | **Authentication**                      |                      |                      |
-| Anonymous                               |  :white_check_mark:  |                      |
-| User Name Password                      |  :white_check_mark:  |                      |
+| Anonymous                               |  :heavy_check_mark:  |                      |
+| User Name Password                      |  :heavy_check_mark:  |                      |
 | X509 Certificate                        |      :new_moon:      |                      |
 | **Server Facets**                       |                      |                      |
-| Core Server                             |  :white_check_mark:  |                      |
-| Data Access Server                      |  :white_check_mark:  |                      |
-| Embedded Server                         |  :white_check_mark:  |                      |
-| Nano Embedded Device Server             |  :white_check_mark:  |                      |
-| Micro Embedded Device Server            |  :white_check_mark:  |                      |
-| Method Server                           |  :white_check_mark:  |                      |
-| Embedded DataChange Subscription Server |  :white_check_mark:  |                      |
-| Node Management Server                  |  :white_check_mark:  |                      |
+| Core Server                             |  :heavy_check_mark:  |                      |
+| Data Access Server                      |  :heavy_check_mark:  |                      |
+| Embedded Server                         |  :heavy_check_mark:  |                      |
+| Nano Embedded Device Server             |  :heavy_check_mark:  |                      |
+| Micro Embedded Device Server            |  :heavy_check_mark:  |                      |
+| Method Server                           |  :heavy_check_mark:  |                      |
+| Embedded DataChange Subscription Server |  :heavy_check_mark:  |                      |
+| Node Management Server                  |  :heavy_check_mark:  |                      |
 | Standard DataChange Subscription Server | :waning_gibbous_moon: | Only Deadband Filter missing |
-| Event Subscription Server               |     :new_moon:       |     WIP              |
+| Event Subscription Server               |     :full_moon:      | master               |
 | **Client Facets**                       |                      |                      |
-| Base Client Behaviour                   |  :white_check_mark:  |                      |
-| AddressSpace Lookup                     |  :white_check_mark:  |                      |
-| Attribute Read                          |  :white_check_mark:  |                      |
-| DataChange Subscription                 |  :white_check_mark:  |                      |
-| DataAccess                              |  :white_check_mark:  |                      |
-| Discovery                               |  :white_check_mark:  |                      |
-| Event Subscription                      |  :white_check_mark:  |                      |
-| Method call                             |  :white_check_mark:  |                      |
-| Advanced Type                           |  :white_check_mark:  |                      |
+| Base Client Behaviour                   |  :heavy_check_mark:  |                      |
+| AddressSpace Lookup                     |  :heavy_check_mark:  |                      |
+| Attribute Read                          |  :heavy_check_mark:  |                      |
+| DataChange Subscription                 |  :heavy_check_mark:  |                      |
+| DataAccess                              |  :heavy_check_mark:  |                      |
+| Discovery                               |  :heavy_check_mark:  |                      |
+| Event Subscription                      |  :heavy_check_mark:  |                      |
+| Method call                             |  :heavy_check_mark:  |                      |
+| Advanced Type                           |  :heavy_check_mark:  |                      |
 | **Discovery**                           |                      | See Discovery Service Set |
-| Local Disovery Server                   |     :full_moon:      | Branch: master, Release 0.3  |
-| Local Discovery Server Multicast Ext.   |     :full_moon:      | Branch: master, Release 0.3  |
+| Local Disovery Server                   |  :heavy_check_mark:  | master, Release 0.3  |
+| Local Discovery Server Multicast Ext.   |  :heavy_check_mark:  | master, Release 0.3  |
 | Global Discovery Server                 |      :new_moon:      |                      |

+ 1 - 1
README.md

@@ -44,7 +44,7 @@ Features currently being implemented:
   - Access control for individual nodes (Done)
   - Asynchronous service requests in the client (Done)
 - Target 0.4 release:
-  - Events (notifications emitted by objects, data change notifications are implemented), WIP by @Pro
+  - Events (notifications emitted by objects, data change notifications are implemented), (Done)
   - Event-loop (background tasks) in the client
   - Publish/Subscribe based on UDP (Specification Part 14), WIP @ Fraunhofer IOSB
 

+ 1 - 1
doc/nodeset_compiler.rst

@@ -270,7 +270,7 @@ And the output of the command:
 
 The first argument ``--types-array=UA_TYPES`` defines the name of the global array in open62541 which contains the corresponding types used within the nodeset in ``NodeSet2.xml``. If you do not define your own datatypes, you can always use the ``UA_TYPES`` value. More on that later in this tutorial.
 The next argument ``--existing ../../deps/ua-nodeset/Schema/Opc.Ua.NodeSet2.xml`` points to the XML definition of the standard-defined namespace 0 (NS0). Namespace 0 is assumed to be loaded beforehand and provides definitions for data type, reference types, and so. Since we reference nodes from NS0 in our myNS.xml we need to tell the nodeset compiler that it should also load that nodeset, but not compile it into the output.
-Note that you may need to initialize the git submodule to get the ``deps/ua-nodeset`` folder (``git submodule --init --update``) or download the full ``NodeSet2.xml`` manually.
+Note that you may need to initialize the git submodule to get the ``deps/ua-nodeset`` folder (``git submodule update --init``) or download the full ``NodeSet2.xml`` manually.
 The argument ``--xml myNS.xml`` points to the user-defined information model, whose nodes will be added to the abstract syntax tree. The script will then create the files ``myNS.c`` and ``myNS.h`` (indicated by the last argument ``myNS``) containing the C code necessary to instantiate those namespaces.
 
 Although it is possible to run the compiler this way, it is highly discouraged. If you care to examine the CMakeLists.txt (examples/nodeset/CMakeLists.txt), you will find out that the file ``server_nodeset.xml`` is compiled with the command::

+ 5 - 0
examples/CMakeLists.txt

@@ -139,4 +139,9 @@ add_subdirectory(nodeset)
 if(UA_ENABLE_PUBSUB)
     add_example(tutorial_pubsub_connection pubsub/tutorial_pubsub_connection.c)
     add_example(tutorial_pubsub_publish pubsub/tutorial_pubsub_publish.c)
+    if(UA_ENABLE_AMALGAMATION)
+        message(WARNING "PubSub subscriber tutorial (preview) can not be used with AMALGAMATION. Skipping tutorial_pubsub_subscribe.")
+    else(NOT UA_ENABLE_AMALGAMATION)
+        add_example(tutorial_pubsub_subscribe pubsub/tutorial_pubsub_subscribe.c)
+    endif()
 endif()

+ 2 - 1
examples/pubsub/tutorial_pubsub_publish.c

@@ -72,7 +72,7 @@ addDataSetField(UA_Server *server) {
     dataSetFieldConfig.field.variable.fieldNameAlias = UA_STRING("Server localtime");
     dataSetFieldConfig.field.variable.promotedField = UA_FALSE;
     dataSetFieldConfig.field.variable.publishParameters.publishedVariable =
-            UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_LOCALTIME);
+    UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
     dataSetFieldConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
     UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldConfig, &dataSetFieldIdent);
 }
@@ -90,6 +90,7 @@ addWriterGroup(UA_Server *server) {
     writerGroupConfig.name = UA_STRING("Demo WriterGroup");
     writerGroupConfig.publishingInterval = 100;
     writerGroupConfig.enabled = UA_FALSE;
+    writerGroupConfig.writerGroupId = 100;
     writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
     /* The configuration flags for the messages are encapsulated inside the
      * message- and transport settings extension objects. These extension objects

+ 150 - 0
examples/pubsub/tutorial_pubsub_subscribe.c

@@ -0,0 +1,150 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
+ */
+
+/**
+ * IMPORTANT ANNOUNCEMENT
+ * The PubSub subscriber API is currently not finished. This examples can be used to receive
+ * and print the values, which are published by the tutorial_pubsub_publish example.
+ * The following code uses internal API which will be later replaced by the higher-level
+ * PubSub subscriber API.
+*/
+#include <signal.h>
+#include "ua_pubsub_networkmessage.h"
+#include "ua_log_stdout.h"
+#include "ua_server.h"
+#include "ua_config_default.h"
+#include "ua_pubsub.h"
+#include "ua_network_pubsub_udp.h"
+#include "src_generated/ua_types_generated.h"
+
+UA_Boolean running = true;
+static void stopHandler(int sign) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
+    running = false;
+}
+
+static void
+subscriptionPollingCallback(UA_Server *server, UA_PubSubConnection *connection) {
+    UA_ByteString buffer;
+    if (UA_ByteString_allocBuffer(&buffer, 512) != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
+                     "Message buffer allocation failed!");
+        return;
+    }
+
+    /* Receive the message. Blocks for 5ms */
+    UA_StatusCode retval =
+        connection->channel->receive(connection->channel, &buffer, NULL, 5);
+    if(retval != UA_STATUSCODE_GOOD || buffer.length == 0) {
+        /* Workaround!! Reset buffer length. Receive can set the length to zero.
+         * Then the buffer is not deleted because no memory allocation is
+         * assumed.
+         * TODO: Return an error code in 'receive' instead of setting the buf
+         * length to zero. */
+        buffer.length = 512;
+        UA_ByteString_deleteMembers(&buffer);
+        return;
+    }
+
+    /* Decode the message */
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+                "Message length: %zu", buffer.length);
+    UA_NetworkMessage networkMessage;
+    memset(&networkMessage, 0, sizeof(UA_NetworkMessage));
+    size_t currentPosition = 0;
+    UA_NetworkMessage_decodeBinary(&buffer, &currentPosition, &networkMessage);
+    UA_ByteString_deleteMembers(&buffer);
+
+    /* Is this the correct message type? */
+    if(networkMessage.networkMessageType != UA_NETWORKMESSAGE_DATASET)
+        goto cleanup;
+
+    /* At least one DataSetMessage in the NetworkMessage? */
+    if(networkMessage.payloadHeaderEnabled &&
+       networkMessage.payloadHeader.dataSetPayloadHeader.count < 1)
+        goto cleanup;
+
+    /* Is this a KeyFrame-DataSetMessage? */
+    UA_DataSetMessage *dsm = &networkMessage.payload.dataSetPayload.dataSetMessages[0];
+    if(dsm->header.dataSetMessageType != UA_DATASETMESSAGE_DATAKEYFRAME)
+        goto cleanup;
+
+    /* Loop over the fields and print well-known content types */
+    for(int i = 0; i < dsm->data.keyFrameData.fieldCount; i++) {
+        const UA_DataType *currentType = dsm->data.keyFrameData.dataSetFields[i].value.type;
+        if(currentType == &UA_TYPES[UA_TYPES_BYTE]) {
+            UA_Byte value = *(UA_Byte *)dsm->data.keyFrameData.dataSetFields[i].value.data;
+            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+                        "Message content: [Byte] \tReceived data: %i", value);
+        } else if (currentType == &UA_TYPES[UA_TYPES_DATETIME]) {
+            UA_DateTime value = *(UA_DateTime *)dsm->data.keyFrameData.dataSetFields[i].value.data;
+            UA_DateTimeStruct receivedTime = UA_DateTime_toStruct(value);
+            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+                        "Message content: [DateTime] \t"
+                        "Received date: %02i-%02i-%02i Received time: %02i:%02i:%02i",
+                        receivedTime.year, receivedTime.month, receivedTime.day,
+                        receivedTime.hour, receivedTime.min, receivedTime.sec);
+        }
+    }
+
+ cleanup:
+    UA_NetworkMessage_deleteMembers(&networkMessage);
+}
+
+int main(void) {
+    signal(SIGINT, stopHandler);
+    signal(SIGTERM, stopHandler);
+
+    UA_ServerConfig *config = UA_ServerConfig_new_minimal(4801, NULL);
+    /* Details about the PubSubTransportLayer can be found inside the
+     * tutorial_pubsub_connection */
+    config->pubsubTransportLayers = (UA_PubSubTransportLayer *)
+        UA_malloc(sizeof(UA_PubSubTransportLayer));
+    if (!config->pubsubTransportLayers) {
+        UA_ServerConfig_delete(config);
+        return -1;
+    }
+    config->pubsubTransportLayers[0] = UA_PubSubTransportLayerUDPMP();
+    config->pubsubTransportLayersSize++;
+    UA_Server *server = UA_Server_new(config);
+
+    UA_PubSubConnectionConfig connectionConfig;
+    memset(&connectionConfig, 0, sizeof(connectionConfig));
+    connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
+    connectionConfig.transportProfileUri =
+        UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
+    connectionConfig.enabled = UA_TRUE;
+    UA_NetworkAddressUrlDataType networkAddressUrl =
+        {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")};
+    UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
+                         &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
+    UA_NodeId connectionIdent;
+    UA_StatusCode retval =
+        UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
+    if(retval == UA_STATUSCODE_GOOD)
+        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
+                    "The PubSub Connection was created successfully!");
+
+    /* The following lines register the listening on the configured multicast
+     * address and configure a repeated job, which is used to handle received
+     * messages. */
+    UA_PubSubConnection *connection =
+        UA_PubSubConnection_findConnectionbyId(server, connectionIdent);
+    if(connection != NULL) {
+        UA_StatusCode rv = connection->channel->regist(connection->channel, NULL);
+        if (rv == UA_STATUSCODE_GOOD) {
+            UA_UInt64 subscriptionCallbackId;
+            UA_Server_addRepeatedCallback(server, (UA_ServerCallback)subscriptionPollingCallback,
+                                          connection, 100, &subscriptionCallbackId);
+        } else {
+            UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "register channel failed: %s!",
+                           UA_StatusCode_name(rv));
+        }
+    }
+
+    retval |= UA_Server_run(server, &running);
+    UA_Server_delete(server);
+    UA_ServerConfig_delete(config);
+    return (int)retval;
+}

+ 46 - 73
examples/tutorial_server_events.c

@@ -1,6 +1,9 @@
 /* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
  * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
 
+#include <signal.h>
+#include "open62541.h"
+
 /**
  * Generating events
  * -----------------
@@ -12,99 +15,58 @@
  * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  * 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,
+ *
+ * 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`.
- */
+ * so add the type as you would a new `ObjectType`. */
 
 static UA_NodeId eventType;
 
-static UA_StatusCode addNewEventType(UA_Server *server) {
+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;
+    attr.displayName = UA_LOCALIZEDTEXT("en-US", "SimpleEventType");
+    attr.description = UA_LOCALIZEDTEXT("en-US", "The simple event type we created");
+    return 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);
 }
 
-/** Setting up an event
- * ^^^^^^^^^^^^^^^^^^^^^^
+/**
+ * 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);
+static UA_StatusCode
+setUpEvent(UA_Server *server, UA_NodeId *outId) {
+    UA_StatusCode 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;
-    }
+    /* Set the Event Attributes */
     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);
+    UA_Server_writeObjectProperty_scalar(server, *outId, UA_QUALIFIEDNAME(0, "Severity"),
+                                         &eventSeverity, &UA_TYPES[UA_TYPES_UINT16]);
 
-    /* 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);
+    UA_Server_writeObjectProperty_scalar(server, *outId, UA_QUALIFIEDNAME(0, "Message"),
+                                         &eventMessage, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
 
     return UA_STATUSCODE_GOOD;
 }
 
-/** Triggering an event
- * ^^^^^^^^^^^^^^^^^^^^
+/**
+ * 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`.
@@ -116,26 +78,30 @@ generateEventMethodCallback(UA_Server *server,
                          const UA_NodeId *objectId, void *objectContext,
                          size_t inputSize, const UA_Variant *input,
                          size_t outputSize, UA_Variant *output) {
+
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Creating event");
+
     /* set up event */
     UA_NodeId eventNodeId;
     UA_StatusCode retval = setUpEvent(server, &eventNodeId);
-    if (retval != UA_STATUSCODE_GOOD) {
+    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) {
+    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
+/**
+ * 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.
  *
@@ -157,6 +123,13 @@ addGenerateEventMethod(UA_Server *server) {
                             0, NULL, 0, NULL, NULL, NULL);
 }
 
+/** It follows the main server code, making use of the above definitions. */
+
+UA_Boolean running = true;
+static void stopHandler(int sig) {
+    running = false;
+}
+
 int main (void) {
     /* default server values */
     signal(SIGINT, stopHandler);

+ 1 - 0
include/ua_config.h.in

@@ -28,6 +28,7 @@ extern "C" {
 #cmakedefine UA_ENABLE_NODEMANAGEMENT
 #cmakedefine UA_ENABLE_SUBSCRIPTIONS
 #cmakedefine UA_ENABLE_PUBSUB
+#cmakedefine UA_ENABLE_PUBSUB_INFORMATIONMODEL
 #cmakedefine UA_ENABLE_ENCRYPTION
 #cmakedefine UA_ENABLE_SUBSCRIPTIONS_EVENTS
 

+ 57 - 11
include/ua_server.h

@@ -811,10 +811,11 @@ UA_Server_deleteMonitoredItem(UA_Server *server, UA_UInt32 monitoredItemId);
 /**
  * Method Callbacks
  * ^^^^^^^^^^^^^^^^
- * Method callbacks are set to `NULL` (not executable) when a method node is added
- * over the network. In theory, it is possible to add a callback via
- * ``UA_Server_setMethodNode_callback`` within the global constructor when adding
- * methods over the network is really wanted. */
+ * Method callbacks are set to `NULL` (not executable) when a method node is
+ * added over the network. In theory, it is possible to add a callback via
+ * ``UA_Server_setMethodNode_callback`` within the global constructor when
+ * adding methods over the network is really wanted. See the Section
+ * :ref:`object-interaction` for calling methods on an object. */
 
 typedef UA_StatusCode
 (*UA_MethodCallback)(UA_Server *server, const UA_NodeId *sessionId,
@@ -825,14 +826,57 @@ typedef UA_StatusCode
                      UA_Variant *output);
 
 #ifdef UA_ENABLE_METHODCALLS
-
 UA_StatusCode UA_EXPORT
 UA_Server_setMethodNode_callback(UA_Server *server,
                                  const UA_NodeId methodNodeId,
                                  UA_MethodCallback methodCallback);
+#endif
+
+/**
+ * .. _object-interaction:
+ *
+ * Interacting with Objects
+ * ------------------------
+ * Objects in the information model are represented as ObjectNodes. Some
+ * convenience functions are provided to simplify the interaction with objects.
+ */
+
+/* Write an object property. The property is represented as a VariableNode with
+ * a ``HasProperty`` reference from the ObjectNode. The VariableNode is
+ * identified by its BrowseName. Writing the property sets the value attribute
+ * of the VariableNode.
+ *
+ * @param server The server object
+ * @param objectId The identifier of the object (node)
+ * @param propertyName The name of the property
+ * @param value The value to be set for the event attribute
+ * @return The StatusCode for setting the event attribute */
+UA_StatusCode UA_EXPORT
+UA_Server_writeObjectProperty(UA_Server *server, const UA_NodeId objectId,
+                              const UA_QualifiedName propertyName,
+                              const UA_Variant value);
+
+/* Directly point to the scalar value instead of a variant */
+UA_StatusCode UA_EXPORT
+UA_Server_writeObjectProperty_scalar(UA_Server *server, const UA_NodeId objectId,
+                                     const UA_QualifiedName propertyName,
+                                     const void *value, const UA_DataType *type);
+
+/* Read an object property.
+ *
+ * @param server The server object
+ * @param objectId The identifier of the object (node)
+ * @param propertyName The name of the property
+ * @param value Contains the property value after reading. Must not be NULL.
+ * @return The StatusCode for setting the event attribute */
+UA_StatusCode UA_EXPORT
+UA_Server_readObjectProperty(UA_Server *server, const UA_NodeId objectId,
+                             const UA_QualifiedName propertyName,
+                             UA_Variant *value);
+
+#ifdef UA_ENABLE_METHODCALLS
 UA_CallMethodResult UA_EXPORT
 UA_Server_call(UA_Server *server, const UA_CallMethodRequest *request);
-
 #endif
 
 /**
@@ -1135,10 +1179,11 @@ UA_Server_deleteReference(UA_Server *server, const UA_NodeId sourceNodeId,
  * 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:
+/* 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
 
@@ -1152,7 +1197,8 @@ 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.
+/* 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

+ 13 - 1
include/ua_server_pubsub.h

@@ -68,6 +68,14 @@ extern "C" {
  *                 |    +-----------------+
  *                 +----> UA_DataSetField |  UA_PublishedDataSet_addDataSetField
  *                      +-----------------+
+ * PubSub Information Model Representation
+ * -----------
+ * The complete PubSub configuration is available inside the information model.
+ * The entry point is the node 'PublishSubscribe, located under the Server node.
+ * The standard defines for PubSub no new Service set. The configuration can optionally
+ * done over methods inside the information model. The information model representation
+ * of the current PubSub configuration is generated automatically. This feature
+ * can enabled/disable by changing the UA_ENABLE_PUBSUB_INFORMATIONMODEL option.
  *
  * Connections
  * -----------
@@ -256,7 +264,7 @@ typedef struct {
     UA_String name;
     UA_Boolean enabled;
     UA_UInt16 writerGroupId;
-    UA_Double publishingInterval;
+    UA_Duration publishingInterval;
     UA_Double keepAliveTime;
     UA_Byte priority;
     UA_MessageSecurityMode securityMode;
@@ -285,6 +293,10 @@ UA_StatusCode
 UA_Server_getWriterGroupConfig(UA_Server *server, const UA_NodeId writerGroup,
                                UA_WriterGroupConfig *config);
 
+UA_StatusCode
+UA_Server_updateWriterGroupConfig(UA_Server *server, UA_NodeId writerGroupIdentifier,
+                                  const UA_WriterGroupConfig *config);
+
 UA_StatusCode
 UA_Server_removeWriterGroup(UA_Server *server, const UA_NodeId writerGroup);
 

+ 36 - 1
src/pubsub/ua_pubsub.c

@@ -13,6 +13,9 @@
 #include "ua_pubsub_networkmessage.h"
 
 #ifdef UA_ENABLE_PUBSUB /* conditional compilation */
+#ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL
+#include "ua_pubsub_ns0.h"
+#endif
 
 /**********************************************/
 /*               Connection                   */
@@ -129,6 +132,9 @@ UA_Server_addWriterGroup(UA_Server *server, const UA_NodeId connection,
     newWriterGroup->config = tmpWriterGroupConfig;
     retVal |= UA_WriterGroup_addPublishCallback(server, newWriterGroup);
     LIST_INSERT_HEAD(&currentConnectionContext->writerGroups, newWriterGroup, listEntry);
+#ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL
+    addWriterGroupRepresentation(server, newWriterGroup);
+#endif
     return retVal;
 }
 
@@ -146,7 +152,9 @@ UA_Server_removeWriterGroup(UA_Server *server, const UA_NodeId writerGroup){
     //unregister the publish callback
     if(UA_PubSubManager_removeRepeatedPubSubCallback(server, wg->publishCallbackId) != UA_STATUSCODE_GOOD)
         return UA_STATUSCODE_BADINTERNALERROR;
-
+#ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL
+    removeWriterGroupRepresentation(server, wg);
+#endif
     UA_WriterGroup_deleteMembers(server, wg);
     UA_free(wg);
     return UA_STATUSCODE_GOOD;
@@ -432,6 +440,27 @@ UA_Server_getWriterGroupConfig(UA_Server *server, const UA_NodeId writerGroup,
     return retVal;
 }
 
+UA_StatusCode
+UA_Server_updateWriterGroupConfig(UA_Server *server, UA_NodeId writerGroupIdentifier,
+                                  const UA_WriterGroupConfig *config){
+    if(!config)
+        return UA_STATUSCODE_BADINVALIDARGUMENT;
+
+    UA_WriterGroup *currentWriterGroup = UA_WriterGroup_findWGbyId(server, writerGroupIdentifier);
+    if(!currentWriterGroup)
+        return UA_STATUSCODE_BADNOTFOUND;
+    //The update functionality will be extended during the next PubSub batches.
+    //Currently is only a change of the publishing interval possible.
+    if(currentWriterGroup->config.publishingInterval != config->publishingInterval) {
+        UA_PubSubManager_removeRepeatedPubSubCallback(server, currentWriterGroup->publishCallbackId);
+        currentWriterGroup->config.publishingInterval = config->publishingInterval;
+        UA_WriterGroup_addPublishCallback(server, currentWriterGroup);
+    } else if (currentWriterGroup->config.priority != config->priority) {
+        UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER, "No or unsupported WriterGroup update.");
+    }
+    return UA_STATUSCODE_GOOD;
+}
+
 UA_WriterGroup *
 UA_WriterGroup_findWGbyId(UA_Server *server, UA_NodeId identifier){
     for(size_t i = 0; i < server->pubSubManager.connectionsSize; i++){
@@ -526,6 +555,9 @@ UA_Server_addDataSetWriter(UA_Server *server,
     //add the new writer to the group
     LIST_INSERT_HEAD(&wg->writers, newDataSetWriter, listEntry);
     wg->writersCount++;
+#ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL
+    addDataSetWriterRepresentation(server, newDataSetWriter);
+#endif
     return retVal;
 }
 
@@ -540,6 +572,9 @@ UA_Server_removeDataSetWriter(UA_Server *server, const UA_NodeId dsw){
         return UA_STATUSCODE_BADNOTFOUND;
 
     linkedWriterGroup->writersCount--;
+#ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL
+    removeDataSetWriterRepresentation(server, dataSetWriter);
+#endif
     //remove DataSetWriter from group
     UA_DataSetWriter_deleteMembers(server, dataSetWriter);
     UA_free(dataSetWriter);

+ 13 - 0
src/pubsub/ua_pubsub_manager.c

@@ -6,6 +6,7 @@
  */
 
 #include "server/ua_server_internal.h"
+#include "ua_pubsub_ns0.h"
 
 #ifdef UA_ENABLE_PUBSUB /* conditional compilation */
 
@@ -78,6 +79,9 @@ UA_Server_addPubSubConnection(UA_Server *server, const UA_PubSubConnectionConfig
                 UA_NodeId_copy(&newConnection->identifier, connectionIdentifier);
             }
             server->pubSubManager.connectionsSize++;
+#ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL
+        addPubSubConnectionRepresentation(server, newConnection);
+#endif
             return UA_STATUSCODE_GOOD;
         }
     }
@@ -100,6 +104,9 @@ UA_Server_removePubSubConnection(UA_Server *server, const UA_NodeId connection)
     if(!currentConnection)
         return UA_STATUSCODE_BADNOTFOUND;
 
+#ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL
+    removePubSubConnectionRepresentation(server, currentConnection);
+#endif
     UA_PubSubConnection_deleteMembers(server, currentConnection);
     server->pubSubManager.connectionsSize--;
     //remove the connection from the pubSubManager, move the last connection
@@ -183,6 +190,9 @@ UA_Server_addPublishedDataSet(UA_Server *server, const UA_PublishedDataSetConfig
     UA_AddPublishedDataSetResult result = {UA_STATUSCODE_GOOD, 0, NULL,
                                                  {UA_PubSubConfigurationVersionTimeDifference(),
                                                   UA_PubSubConfigurationVersionTimeDifference()}};
+#ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL
+    addPublishedDataItemsRepresentation(server, newPubSubDataSet);
+#endif
     return result;
 }
 
@@ -212,6 +222,9 @@ UA_Server_removePublishedDataSet(UA_Server *server, UA_NodeId pds) {
             }
         }
     }
+#ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL
+    removePublishedDataSetRepresentation(server, publishedDataSet);
+#endif
     UA_PublishedDataSet_deleteMembers(server, publishedDataSet);
     server->pubSubManager.publishedDataSetsSize--;
     //copy the last PDS to the removed PDS inside the allocated memory block

+ 96 - 128
src/pubsub/ua_pubsub_networkmessage.c

@@ -11,7 +11,6 @@
 #include "ua_types_generated.h"
 #include "ua_types_generated_encoding_binary.h"
 #include "ua_types_generated_handling.h"
-#include "ua_log_stdout.h"
 
 #ifdef UA_ENABLE_PUBSUB /* conditional compilation */
 
@@ -57,8 +56,6 @@ static UA_Boolean UA_DataSetMessageHeader_DataSetFlags2Enabled(const UA_DataSetM
 UA_StatusCode
 UA_NetworkMessage_encodeBinary(const UA_NetworkMessage* src, UA_Byte **bufPos,
                                const UA_Byte *bufEnd) {
-    UA_StatusCode retval = UA_STATUSCODE_BADNOTIMPLEMENTED;
-
     /* UADPVersion + UADP Flags */
     UA_Byte v = src->version;
     if(src->publisherIdEnabled)
@@ -204,36 +201,29 @@ UA_NetworkMessage_encodeBinary(const UA_NetworkMessage* src, UA_Byte **bufPos,
 
     // Payload-Header
     if(src->payloadHeaderEnabled) {
-        if(src->networkMessageType == UA_NETWORKMESSAGE_DATASET) {
-            rv = UA_Byte_encodeBinary(&(src->payloadHeader.dataSetPayloadHeader.count), bufPos, bufEnd);
+        if(src->networkMessageType != UA_NETWORKMESSAGE_DATASET)
+            return UA_STATUSCODE_BADNOTIMPLEMENTED;
+            
+        rv = UA_Byte_encodeBinary(&(src->payloadHeader.dataSetPayloadHeader.count), bufPos, bufEnd);
 
-            if(src->payloadHeader.dataSetPayloadHeader.dataSetWriterIds != NULL) {
-                for (UA_Byte i = 0; i < src->payloadHeader.dataSetPayloadHeader.count; i++) {
-                    rv = UA_UInt16_encodeBinary(&(src->payloadHeader.dataSetPayloadHeader.dataSetWriterIds[i]), bufPos, bufEnd);
-                    if(rv != UA_STATUSCODE_GOOD)
-                        return rv;
-                }
-            } else {
-                rv = UA_STATUSCODE_BADENCODINGERROR;
-                UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "no dataSetWriterIds given!");
-            }
-        } else {
-            rv = UA_STATUSCODE_BADNOTIMPLEMENTED;
+        if(src->payloadHeader.dataSetPayloadHeader.dataSetWriterIds == NULL)
+            return UA_STATUSCODE_BADENCODINGERROR;
+            
+        for(UA_Byte i = 0; i < src->payloadHeader.dataSetPayloadHeader.count; i++) {
+            rv = UA_UInt16_encodeBinary(&(src->payloadHeader.dataSetPayloadHeader.dataSetWriterIds[i]),
+                                        bufPos, bufEnd);
+            if(rv != UA_STATUSCODE_GOOD)
+                return rv;
         }
     }
 
-    if(rv != UA_STATUSCODE_GOOD)
-        return rv;
-
     // Timestamp
-    if(src->timestampEnabled) {
+    if(src->timestampEnabled)
         rv = UA_DateTime_encodeBinary(&(src->timestamp), bufPos, bufEnd);
-    }
 
     // Picoseconds
-    if(src->picosecondsEnabled) {
+    if(src->picosecondsEnabled)
         rv = UA_UInt16_encodeBinary(&(src->picoseconds), bufPos, bufEnd);
-    }
 
     // PromotedFields
     if(src->promotedFieldsEnabled) {
@@ -293,69 +283,61 @@ UA_NetworkMessage_encodeBinary(const UA_NetworkMessage* src, UA_Byte **bufPos,
     }
 
     // Payload
-    if(src->networkMessageType == UA_NETWORKMESSAGE_DATASET) {
-        UA_Byte count = 1;
+    if(src->networkMessageType != UA_NETWORKMESSAGE_DATASET)
+        return UA_STATUSCODE_BADNOTIMPLEMENTED;
+        
+    UA_Byte count = 1;
 
-        if(src->payloadHeaderEnabled) {
-            count = src->payloadHeader.dataSetPayloadHeader.count;
-            if(count > 1) {
-                for (UA_Byte i = 0; i < count; i++) {
-                    // initially calculate the size, if not specified
-                    UA_UInt16 sz = 0;
-                    if((src->payload.dataSetPayload.sizes != NULL) && (src->payload.dataSetPayload.sizes[i] != 0)) {
-                        sz = src->payload.dataSetPayload.sizes[i];
-                    } else {
-                        sz = (UA_UInt16)UA_DataSetMessage_calcSizeBinary(&(src->payload.dataSetPayload.dataSetMessages[i]));
-                    }
-
-                    rv = UA_UInt16_encodeBinary(&sz, bufPos, bufEnd);
-                    if(rv != UA_STATUSCODE_GOOD)
-                        return rv;
+    if(src->payloadHeaderEnabled) {
+        count = src->payloadHeader.dataSetPayloadHeader.count;
+        if(count > 1) {
+            for (UA_Byte i = 0; i < count; i++) {
+                // initially calculate the size, if not specified
+                UA_UInt16 sz = 0;
+                if((src->payload.dataSetPayload.sizes != NULL) &&
+                   (src->payload.dataSetPayload.sizes[i] != 0)) {
+                    sz = src->payload.dataSetPayload.sizes[i];
+                } else {
+                    sz = (UA_UInt16)UA_DataSetMessage_calcSizeBinary(&src->payload.dataSetPayload.dataSetMessages[i]);
                 }
-            }
-        }
 
-        for (UA_Byte i = 0; i < count; i++) {
-            rv = UA_DataSetMessage_encodeBinary(&(src->payload.dataSetPayload.dataSetMessages[i]), bufPos, bufEnd);
-            if(rv != UA_STATUSCODE_GOOD)
-                return rv;
+                rv = UA_UInt16_encodeBinary(&sz, bufPos, bufEnd);
+                if(rv != UA_STATUSCODE_GOOD)
+                    return rv;
+            }
         }
-    } else {
-        rv = UA_STATUSCODE_BADNOTIMPLEMENTED;
     }
 
-    if(rv != UA_STATUSCODE_GOOD)
-        return rv;
+    for(UA_Byte i = 0; i < count; i++) {
+        rv = UA_DataSetMessage_encodeBinary(&(src->payload.dataSetPayload.dataSetMessages[i]), bufPos, bufEnd);
+        if(rv != UA_STATUSCODE_GOOD)
+            return rv;
+    }
 
-    if (src->securityEnabled)
-    {
+    if(src->securityEnabled) {
         // SecurityFooter
-        if (src->securityHeader.securityFooterEnabled) {
-            for (UA_Byte i = 0; i < src->securityHeader.securityFooterSize; i++) {
+        if(src->securityHeader.securityFooterEnabled) {
+            for(UA_Byte i = 0; i < src->securityHeader.securityFooterSize; i++) {
                 rv = UA_Byte_encodeBinary(&(src->securityFooter.data[i]), bufPos, bufEnd);
-                if (rv != UA_STATUSCODE_GOOD)
+                if(rv != UA_STATUSCODE_GOOD)
                     return rv;
             }
         }
 
         // Signature
-        if (src->securityHeader.networkMessageSigned)
-        {
+        if(src->securityHeader.networkMessageSigned) {
             rv = UA_ByteString_encodeBinary(&(src->signature), bufPos, bufEnd);
-            if (rv != UA_STATUSCODE_GOOD)
+            if(rv != UA_STATUSCODE_GOOD)
                 return rv;
         }
     }
 
-    retval = UA_STATUSCODE_GOOD;
-    return retval;
+    return UA_STATUSCODE_GOOD;
 }
 
 static UA_StatusCode
 UA_NetworkMessage_decodeBinaryInternal(const UA_ByteString *src, size_t *offset,
                                        UA_NetworkMessage* dst) {
-    UA_StatusCode retval = UA_STATUSCODE_BADNOTIMPLEMENTED;
-
     memset(dst, 0, sizeof(UA_NetworkMessage));
     UA_Byte v = 0;
     UA_StatusCode rv = UA_Byte_decodeBinary(src, offset, &v);
@@ -493,21 +475,21 @@ UA_NetworkMessage_decodeBinaryInternal(const UA_ByteString *src, size_t *offset,
 
     // Payload-Header
     if(dst->payloadHeaderEnabled) {
-        if(dst->networkMessageType == UA_NETWORKMESSAGE_DATASET) {
-            rv = UA_Byte_decodeBinary(src, offset, &dst->payloadHeader.dataSetPayloadHeader.count);
-            if(rv != UA_STATUSCODE_GOOD)
-                return rv;
+        if(dst->networkMessageType != UA_NETWORKMESSAGE_DATASET)
+            return UA_STATUSCODE_BADNOTIMPLEMENTED;
 
-            dst->payloadHeader.dataSetPayloadHeader.dataSetWriterIds =
-                (UA_UInt16 *)UA_Array_new(dst->payloadHeader.dataSetPayloadHeader.count, &UA_TYPES[UA_TYPES_UINT16]);
-            for (UA_Byte i = 0; i < dst->payloadHeader.dataSetPayloadHeader.count; i++) {
-                rv = UA_UInt16_decodeBinary(src, offset, &(dst->payloadHeader.dataSetPayloadHeader.dataSetWriterIds[i]));
-                if(rv != UA_STATUSCODE_GOOD)
-                    return rv;
-            }
-        } else {
-            rv = UA_STATUSCODE_BADNOTIMPLEMENTED;
+        rv = UA_Byte_decodeBinary(src, offset, &dst->payloadHeader.dataSetPayloadHeader.count);
+        if(rv != UA_STATUSCODE_GOOD)
             return rv;
+
+        dst->payloadHeader.dataSetPayloadHeader.dataSetWriterIds =
+            (UA_UInt16 *)UA_Array_new(dst->payloadHeader.dataSetPayloadHeader.count,
+                                      &UA_TYPES[UA_TYPES_UINT16]);
+        for (UA_Byte i = 0; i < dst->payloadHeader.dataSetPayloadHeader.count; i++) {
+            rv = UA_UInt16_decodeBinary(src, offset,
+                                        &dst->payloadHeader.dataSetPayloadHeader.dataSetWriterIds[i]);
+            if(rv != UA_STATUSCODE_GOOD)
+                return rv;
         }
     }
 
@@ -545,8 +527,9 @@ UA_NetworkMessage_decodeBinaryInternal(const UA_ByteString *src, size_t *offset,
                     // set promotedFieldsSize to the number of objects
                     dst->promotedFieldsSize = (UA_UInt16) (counter + 1);
                 } else {
-                    dst->promotedFields = (UA_Variant*)UA_realloc(dst->promotedFields,
-                                                                  UA_TYPES[UA_TYPES_VARIANT].memSize * (counter + 1));
+                    dst->promotedFields = (UA_Variant*)
+                        UA_realloc(dst->promotedFields,
+                                   UA_TYPES[UA_TYPES_VARIANT].memSize * (counter + 1));
                     // set promotedFieldsSize to the number of objects
                     dst->promotedFieldsSize = (UA_UInt16) (counter + 1);
                 }
@@ -592,7 +575,8 @@ UA_NetworkMessage_decodeBinaryInternal(const UA_ByteString *src, size_t *offset,
 
         // MessageNonce
         if(dst->securityHeader.nonceLength > 0) {
-            rv = UA_ByteString_allocBuffer(&dst->securityHeader.messageNonce, dst->securityHeader.nonceLength);
+            rv = UA_ByteString_allocBuffer(&dst->securityHeader.messageNonce,
+                                           dst->securityHeader.nonceLength);
             if(rv != UA_STATUSCODE_GOOD)
                 return rv;
 
@@ -612,37 +596,36 @@ UA_NetworkMessage_decodeBinaryInternal(const UA_ByteString *src, size_t *offset,
     }
 
     // Payload
-    if(dst->networkMessageType == UA_NETWORKMESSAGE_DATASET) {
-        UA_Byte count = 1;
-        if(dst->payloadHeaderEnabled) {
-            count = dst->payloadHeader.dataSetPayloadHeader.count;
-            if(count > 1) {
-                dst->payload.dataSetPayload.sizes = (UA_UInt16 *)UA_Array_new(count, &UA_TYPES[UA_TYPES_UINT16]);
-                for (UA_Byte i = 0; i < count; i++) {
-                    rv = UA_UInt16_decodeBinary(src, offset, &(dst->payload.dataSetPayload.sizes[i]));
-                    if(rv != UA_STATUSCODE_GOOD)
-                        return rv;
-                }
+    if(dst->networkMessageType != UA_NETWORKMESSAGE_DATASET)
+        return UA_STATUSCODE_BADNOTIMPLEMENTED;
+
+    UA_Byte count = 1;
+    if(dst->payloadHeaderEnabled) {
+        count = dst->payloadHeader.dataSetPayloadHeader.count;
+        if(count > 1) {
+            dst->payload.dataSetPayload.sizes = (UA_UInt16 *)UA_Array_new(count, &UA_TYPES[UA_TYPES_UINT16]);
+            for (UA_Byte i = 0; i < count; i++) {
+                rv = UA_UInt16_decodeBinary(src, offset, &(dst->payload.dataSetPayload.sizes[i]));
+                if(rv != UA_STATUSCODE_GOOD)
+                    return rv;
             }
         }
+    }
 
-        dst->payload.dataSetPayload.dataSetMessages = (UA_DataSetMessage*)UA_calloc(count, sizeof(UA_DataSetMessage));
-        for (UA_Byte i = 0; i < count; i++) {
-            rv = UA_DataSetMessage_decodeBinary(src, offset, &(dst->payload.dataSetPayload.dataSetMessages[i]));
-            if(rv != UA_STATUSCODE_GOOD)
-                return rv;
-        }
-    } else {
-        rv = UA_STATUSCODE_BADNOTIMPLEMENTED;
+    dst->payload.dataSetPayload.dataSetMessages = (UA_DataSetMessage*)
+        UA_calloc(count, sizeof(UA_DataSetMessage));
+    for(UA_Byte i = 0; i < count; i++) {
+        rv = UA_DataSetMessage_decodeBinary(src, offset, &(dst->payload.dataSetPayload.dataSetMessages[i]));
+        if(rv != UA_STATUSCODE_GOOD)
+            return rv;
     }
 
     if(rv != UA_STATUSCODE_GOOD)
         return rv;
 
-    if (dst->securityEnabled)
-    {
+    if(dst->securityEnabled) {
         // SecurityFooter
-        if (dst->securityHeader.securityFooterEnabled && (dst->securityHeader.securityFooterSize > 0)) {
+        if(dst->securityHeader.securityFooterEnabled && (dst->securityHeader.securityFooterSize > 0)) {
             rv = UA_ByteString_allocBuffer(&dst->securityFooter, dst->securityHeader.securityFooterSize);
             if (rv != UA_STATUSCODE_GOOD)
                 return rv;
@@ -655,16 +638,14 @@ UA_NetworkMessage_decodeBinaryInternal(const UA_ByteString *src, size_t *offset,
         }
 
         // Signature
-        if (dst->securityHeader.networkMessageSigned)
-        {
+        if(dst->securityHeader.networkMessageSigned) {
             rv = UA_ByteString_decodeBinary(src, offset, &(dst->signature));
             if (rv != UA_STATUSCODE_GOOD)
                 return rv;
         }
     }
 
-    retval = UA_STATUSCODE_GOOD;
-    return retval;
+    return UA_STATUSCODE_GOOD;
 }
 
 UA_StatusCode
@@ -739,7 +720,7 @@ size_t UA_NetworkMessage_calcSizeBinary(const UA_NetworkMessage* p) {
                 size += UA_UInt16_calcSizeBinary(&p->payloadHeader.dataSetPayloadHeader.dataSetWriterIds[0]) *
                     p->payloadHeader.dataSetPayloadHeader.count;
             } else {
-                UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "no dataSetWriterIds given!");
+                return 0; /* no dataSetWriterIds given! */
             }
         } else {
             // not implemented
@@ -780,15 +761,12 @@ size_t UA_NetworkMessage_calcSizeBinary(const UA_NetworkMessage* p) {
             size += UA_DataSetMessage_calcSizeBinary(&(p->payload.dataSetPayload.dataSetMessages[i]));
     }
 
-    if (p->securityEnabled)
-    {
+    if (p->securityEnabled) {
         if (p->securityHeader.securityFooterEnabled)
             size += p->securityHeader.securityFooterSize;
 
         if (p->securityHeader.networkMessageSigned)
-        {
             size += UA_ByteString_calcSizeBinary(&p->signature);
-        }
     }
 
     retval = size;
@@ -857,28 +835,18 @@ UA_NetworkMessage_ExtendedFlags1Enabled(const UA_NetworkMessage* src) {
 
 UA_Boolean
 UA_NetworkMessage_ExtendedFlags2Enabled(const UA_NetworkMessage* src) {
-    UA_Boolean retval = false;
-
-    if(src->chunkMessage 
-        || src->promotedFieldsEnabled
-        || (src->networkMessageType != UA_NETWORKMESSAGE_DATASET))
-    {
-        retval = true;
-    }
-
-    return retval;
+    if(src->chunkMessage || src->promotedFieldsEnabled ||
+       src->networkMessageType != UA_NETWORKMESSAGE_DATASET)
+        return true;
+    return false;
 }
 
 UA_Boolean
 UA_DataSetMessageHeader_DataSetFlags2Enabled(const UA_DataSetMessageHeader* src) {
-    UA_Boolean retval = false;
-
-    if((src->dataSetMessageType != UA_DATASETMESSAGE_DATAKEYFRAME) || src->timestampEnabled || src->picoSecondsIncluded)
-    {
-        retval = true;
-    }
-
-    return retval;
+    if(src->dataSetMessageType != UA_DATASETMESSAGE_DATAKEYFRAME ||
+       src->timestampEnabled || src->picoSecondsIncluded)
+        return true;
+    return false;
 }
 
 UA_StatusCode

+ 362 - 0
src/pubsub/ua_pubsub_ns0.c

@@ -0,0 +1,362 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Copyright (c) 2017-2018 Fraunhofer IOSB (Author: Andreas Ebner)
+ */
+
+#include "ua_server_pubsub.h"
+#include "ua_types.h"
+#include "ua_types.h"
+#include "ua_pubsub_ns0.h"
+#include "ua_pubsub.h"
+
+#ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL /* conditional compilation */
+
+typedef struct{
+    UA_NodeId parentNodeId;
+    UA_UInt32 parentCalssifier;
+    UA_UInt32 elementClassiefier;
+} UA_NodePropertyContext;
+
+static UA_StatusCode
+addPubSubObjectNode(UA_Server *server, char* name, UA_UInt32 objectid,
+              UA_UInt32 parentid, UA_UInt32 referenceid, UA_UInt32 type_id) {
+    UA_ObjectAttributes object_attr = UA_ObjectAttributes_default;
+    object_attr.displayName = UA_LOCALIZEDTEXT("", name);
+    return UA_Server_addObjectNode(server, UA_NODEID_NUMERIC(0, objectid),
+                                   UA_NODEID_NUMERIC(0, parentid),
+                                   UA_NODEID_NUMERIC(0, referenceid),
+                                   UA_QUALIFIEDNAME(0, name),
+                                   UA_NODEID_NUMERIC(0, type_id),
+                                   object_attr, NULL, NULL);
+}
+
+static UA_StatusCode
+writePubSubNs0VariableArray(UA_Server *server, UA_UInt32 id, void *v,
+                      size_t length, const UA_DataType *type) {
+    UA_Variant var;
+    UA_Variant_init(&var);
+    UA_Variant_setArray(&var, v, length, type);
+    return UA_Server_writeValue(server, UA_NODEID_NUMERIC(0, id), var);
+}
+
+static UA_NodeId
+findSingleChildNode(UA_Server *server, UA_QualifiedName targetName,
+                    UA_NodeId referenceTypeId, UA_NodeId startingNode){
+    UA_NodeId resultNodeId;
+    UA_RelativePathElement rpe;
+    UA_RelativePathElement_init(&rpe);
+    rpe.referenceTypeId = referenceTypeId;
+    rpe.isInverse = false;
+    rpe.includeSubtypes = false;
+    rpe.targetName = targetName;
+    UA_BrowsePath bp;
+    UA_BrowsePath_init(&bp);
+    bp.startingNode = startingNode;
+    bp.relativePath.elementsSize = 1;
+    bp.relativePath.elements = &rpe;
+    UA_BrowsePathResult bpr =
+            UA_Server_translateBrowsePathToNodeIds(server, &bp);
+    if(bpr.statusCode != UA_STATUSCODE_GOOD ||
+       bpr.targetsSize < 1)
+        return UA_NODEID_NULL;
+    if(UA_NodeId_copy(&bpr.targets[0].targetId.nodeId, &resultNodeId) != UA_STATUSCODE_GOOD){
+        UA_BrowsePathResult_deleteMembers(&bpr);
+        return UA_NODEID_NULL;
+    }
+    UA_BrowsePathResult_deleteMembers(&bpr);
+    return resultNodeId;
+}
+
+static void
+onRead(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext,
+       const UA_NodeId *nodeid, void *nodeContext,
+       const UA_NumericRange *range, const UA_DataValue *data) {
+    UA_Variant value;
+    UA_Variant_init(&value);
+    UA_NodeId myNodeId;
+    switch(((UA_NodePropertyContext *) nodeContext)->parentCalssifier){
+        case UA_NS0ID_PUBSUBCONNECTIONTYPE:
+            break;
+        case UA_NS0ID_WRITERGROUPTYPE:
+            myNodeId = ((UA_NodePropertyContext *) nodeContext)->parentNodeId;
+            UA_WriterGroup *writerGroup = UA_WriterGroup_findWGbyId(server, myNodeId);
+            if(!writerGroup)
+                return;
+            switch(((UA_NodePropertyContext *) nodeContext)->elementClassiefier){
+                case UA_NS0ID_WRITERGROUPTYPE_PUBLISHINGINTERVAL:
+                    UA_Variant_setScalar(&value, &writerGroup->config.publishingInterval, &UA_TYPES[UA_TYPES_DURATION]);
+                    break;
+                default:
+                    UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER, "Read error! Unknown property.");
+            }
+            break;
+        default:
+            UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER, "Read error! Unknown parent element.");
+    }
+    UA_Server_writeValue(server, *nodeid, value);
+}
+
+static void
+onWrite(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext,
+        const UA_NodeId *nodeId, void *nodeContext,
+        const UA_NumericRange *range, const UA_DataValue *data){
+    UA_Variant value;
+    UA_NodeId myNodeId;
+    switch(((UA_NodePropertyContext *) nodeContext)->parentCalssifier){
+        case UA_NS0ID_PUBSUBCONNECTIONTYPE:
+            break;
+        case UA_NS0ID_WRITERGROUPTYPE:
+            myNodeId = ((UA_NodePropertyContext *) nodeContext)->parentNodeId;
+            UA_WriterGroup *writerGroup = UA_WriterGroup_findWGbyId(server, myNodeId);
+            UA_WriterGroupConfig writerGroupConfig;
+            memset(&writerGroupConfig, 0, sizeof(writerGroupConfig));
+            if(!writerGroup)
+                return;
+            switch(((UA_NodePropertyContext *) nodeContext)->elementClassiefier){
+                case UA_NS0ID_WRITERGROUPTYPE_PUBLISHINGINTERVAL:
+                    UA_Server_getWriterGroupConfig(server, writerGroup->identifier, &writerGroupConfig);
+                    writerGroupConfig.publishingInterval = *((UA_Duration *) data->value.data);
+                    UA_Server_updateWriterGroupConfig(server, writerGroup->identifier, &writerGroupConfig);
+                    UA_Variant_setScalar(&value, data->value.data, &UA_TYPES[UA_TYPES_DURATION]);
+                    UA_WriterGroupConfig_deleteMembers(&writerGroupConfig);
+                    break;
+                default:
+                    UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER, "Write error! Unknown property element.");
+            }
+            break;
+        default:
+            UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER, "Read error! Unknown parent element.");
+    }
+}
+
+static UA_StatusCode
+addVariableValueSource(UA_Server *server, UA_ValueCallback valueCallback,
+                       UA_NodeId node, UA_NodePropertyContext *context){
+    UA_Server_setNodeContext(server, node, context);
+    return UA_Server_setVariableNode_valueCallback(server, node, valueCallback);
+}
+
+/*************************************************/
+/*            PubSubConnection                   */
+/*************************************************/
+UA_StatusCode
+addPubSubConnectionRepresentation(UA_Server *server, UA_PubSubConnection *connection){
+    UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+    if(connection->config->name.length > 512)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    UA_STACKARRAY(char, connectionName, sizeof(char) * connection->config->name.length +1);
+    memcpy(connectionName, connection->config->name.data, connection->config->name.length);
+    connectionName[connection->config->name.length] = '\0';
+    //This code block must use a lock
+    UA_Nodestore_remove(server, &connection->identifier);
+    retVal |= addPubSubObjectNode(server, connectionName, connection->identifier.identifier.numeric, UA_NS0ID_PUBLISHSUBSCRIBE,
+                            UA_NS0ID_HASPUBSUBCONNECTION, UA_NS0ID_PUBSUBCONNECTIONTYPE);
+    //End lock zone
+    UA_NodeId addressNode, urlNode, interfaceNode;
+    addressNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "Address"),
+                                      UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                                      UA_NODEID_NUMERIC(0, connection->identifier.identifier.numeric));
+    urlNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "Url"),
+                                  UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), addressNode);
+    interfaceNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "NetworkInterface"),
+                                        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), addressNode);
+
+    UA_NetworkAddressUrlDataType *networkAddressUrlDataType = ((UA_NetworkAddressUrlDataType *) connection->config->address.data);
+    UA_Variant value;
+    UA_Variant_init(&value);
+    UA_Variant_setScalar(&value, &networkAddressUrlDataType->url, &UA_TYPES[UA_TYPES_STRING]);
+    UA_Server_writeValue(server, urlNode, value);
+    UA_Variant_setScalar(&value, &networkAddressUrlDataType->networkInterface, &UA_TYPES[UA_TYPES_STRING]);
+    UA_Server_writeValue(server, interfaceNode, value);
+    return retVal;
+}
+
+UA_StatusCode
+removePubSubConnectionRepresentation(UA_Server *server, UA_PubSubConnection *connection){
+    UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+    retVal |= UA_Server_deleteNode(server, connection->identifier, true);
+    return retVal;
+}
+
+/*************************************************/
+/*                PublishedDataSet               */
+/*************************************************/
+UA_StatusCode
+addPublishedDataItemsRepresentation(UA_Server *server, UA_PublishedDataSet *publishedDataSet){
+    UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+    if(publishedDataSet->config.name.length > 512)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    UA_STACKARRAY(char, pdsName, sizeof(char) * publishedDataSet->config.name.length +1);
+    memcpy(pdsName, publishedDataSet->config.name.data, publishedDataSet->config.name.length);
+    pdsName[publishedDataSet->config.name.length] = '\0';
+    //This code block must use a lock
+    UA_Nodestore_remove(server, &publishedDataSet->identifier);
+    retVal |= addPubSubObjectNode(server, pdsName, publishedDataSet->identifier.identifier.numeric, UA_NS0ID_PUBLISHSUBSCRIBE_PUBLISHEDDATASETS,
+                            UA_NS0ID_HASPROPERTY, UA_NS0ID_PUBLISHEDDATAITEMSTYPE);
+    //End lock zone
+    UA_NodeId configurationVersionNode;
+    configurationVersionNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "ConfigurationVersion"),
+                                                   UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                                                   UA_NODEID_NUMERIC(0, publishedDataSet->identifier.identifier.numeric));
+    UA_Variant value;
+    UA_Variant_init(&value);
+    UA_Variant_setScalar(&value, &publishedDataSet->dataSetMetaData.configurationVersion, &UA_TYPES[UA_TYPES_CONFIGURATIONVERSIONDATATYPE]);
+    UA_Server_writeValue(server, configurationVersionNode, value);
+    return retVal;
+}
+
+UA_StatusCode
+removePublishedDataSetRepresentation(UA_Server *server, UA_PublishedDataSet *publishedDataSet){
+    UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+    retVal |= UA_Server_deleteNode(server, publishedDataSet->identifier, false);
+    return retVal;
+}
+
+/**********************************************/
+/*               WriterGroup                  */
+/**********************************************/
+UA_StatusCode
+addWriterGroupRepresentation(UA_Server *server, UA_WriterGroup *writerGroup){
+    UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+    if(writerGroup->config.name.length > 512)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    UA_STACKARRAY(char, wgName, sizeof(char) * writerGroup->config.name.length + 1);
+    memcpy(wgName, writerGroup->config.name.data, writerGroup->config.name.length);
+    wgName[writerGroup->config.name.length] = '\0';
+    //This code block must use a lock
+    UA_Nodestore_remove(server, &writerGroup->identifier);
+    retVal |= addPubSubObjectNode(server, wgName, writerGroup->identifier.identifier.numeric, writerGroup->linkedConnection.identifier.numeric,
+                            UA_NS0ID_HASCOMPONENT, UA_NS0ID_WRITERGROUPTYPE);
+    //End lock zone
+    UA_NodeId keepAliveNode, publishingIntervalNode, priorityNode, writerGroupIdNode;
+    keepAliveNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "KeepAliveTime"),
+                                        UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY),
+                                        UA_NODEID_NUMERIC(0, writerGroup->identifier.identifier.numeric));
+    publishingIntervalNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "PublishingInterval"),
+                                                 UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY),
+                                                 UA_NODEID_NUMERIC(0, writerGroup->identifier.identifier.numeric));
+    UA_NodePropertyContext * publishingIntervalContext = (UA_NodePropertyContext *) UA_malloc(sizeof(UA_NodePropertyContext));
+    publishingIntervalContext->parentNodeId = writerGroup->identifier;
+    publishingIntervalContext->parentCalssifier = UA_NS0ID_WRITERGROUPTYPE;
+    publishingIntervalContext->elementClassiefier = UA_NS0ID_WRITERGROUPTYPE_PUBLISHINGINTERVAL;
+    retVal |= addVariableValueSource(server,(UA_ValueCallback) {onRead, onWrite}, publishingIntervalNode, publishingIntervalContext);
+    UA_Server_writeAccessLevel(server, publishingIntervalNode, (UA_ACCESSLEVELMASK_READ ^ UA_ACCESSLEVELMASK_WRITE));
+
+    priorityNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "Priority"),
+                                       UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY),
+                                       UA_NODEID_NUMERIC(0, writerGroup->identifier.identifier.numeric));
+    writerGroupIdNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "WriterGroupId"),
+                                            UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY),
+                                            UA_NODEID_NUMERIC(0, writerGroup->identifier.identifier.numeric));
+    UA_Variant value;
+    UA_Variant_init(&value);
+    UA_Variant_setScalar(&value, &writerGroup->config.publishingInterval, &UA_TYPES[UA_TYPES_DURATION]);
+    UA_Server_writeValue(server, publishingIntervalNode, value);
+    UA_Variant_setScalar(&value, &writerGroup->config.keepAliveTime, &UA_TYPES[UA_TYPES_DURATION]);
+    UA_Server_writeValue(server, keepAliveNode, value);
+    UA_Variant_setScalar(&value, &writerGroup->config.priority, &UA_TYPES[UA_TYPES_BYTE]);
+    UA_Server_writeValue(server, priorityNode, value);
+    UA_Variant_setScalar(&value, &writerGroup->config.writerGroupId, &UA_TYPES[UA_TYPES_UINT16]);
+    UA_Server_writeValue(server, writerGroupIdNode, value);
+    return retVal;
+}
+
+UA_StatusCode
+removeWriterGroupRepresentation(UA_Server *server, UA_WriterGroup *writerGroup) {
+    UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+    retVal |= UA_Server_deleteNode(server, writerGroup->identifier, false);
+    return retVal;
+}
+
+/**********************************************/
+/*               DataSetWriter                */
+/**********************************************/
+UA_StatusCode
+addDataSetWriterRepresentation(UA_Server *server, UA_DataSetWriter *dataSetWriter){
+    UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+    if(dataSetWriter->config.name.length > 512)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    UA_STACKARRAY(char, dswName, sizeof(char) * dataSetWriter->config.name.length + 1);
+    memcpy(dswName, dataSetWriter->config.name.data, dataSetWriter->config.name.length);
+    dswName[dataSetWriter->config.name.length] = '\0';
+    //This code block must use a lock
+    UA_Nodestore_remove(server, &dataSetWriter->identifier);
+    retVal |= addPubSubObjectNode(server, dswName, dataSetWriter->identifier.identifier.numeric, dataSetWriter->linkedWriterGroup.identifier.numeric,
+                            UA_NS0ID_HASDATASETWRITER, UA_NS0ID_DATASETWRITERTYPE);
+    //End lock zone
+    retVal |= UA_Server_addReference(server, dataSetWriter->connectedDataSet,
+                                     UA_NODEID_NUMERIC(0, UA_NS0ID_DATASETTOWRITER),
+                                     UA_EXPANDEDNODEID_NUMERIC(0, dataSetWriter->identifier.identifier.numeric), true);
+    retVal |= UA_Server_addReference(server, dataSetWriter->connectedDataSet,
+                                     UA_NODEID_NUMERIC(0, UA_NS0ID_DATASETTOWRITER),
+                                     UA_EXPANDEDNODEID_NUMERIC(0, dataSetWriter->identifier.identifier.numeric), false);
+    return retVal;
+}
+
+UA_StatusCode
+removeDataSetWriterRepresentation(UA_Server *server, UA_DataSetWriter *dataSetWriter) {
+    UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+    retVal |= UA_Server_deleteNode(server, dataSetWriter->identifier, false);
+    return retVal;
+}
+
+/**********************************************/
+/*                Destructors                 */
+/**********************************************/
+
+static void
+connectionTypeDestructor(UA_Server *server,
+                         const UA_NodeId *sessionId, void *sessionContext,
+                         const UA_NodeId *typeId, void *typeContext,
+                         const UA_NodeId *nodeId, void **nodeContext) {
+    UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_USERLAND, "Connection destructor called!");
+}
+
+static void
+writerGroupTypeDestructor(UA_Server *server,
+                          const UA_NodeId *sessionId, void *sessionContext,
+                          const UA_NodeId *typeId, void *typeContext,
+                          const UA_NodeId *nodeId, void **nodeContext) {
+    UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_USERLAND, "WriterGroup destructor called!");
+    UA_NodeId intervalNode;
+    intervalNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "PublishingInterval"),
+                                       UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY), *nodeId);
+    UA_NodePropertyContext *internalConnectionContext;
+    UA_Server_getNodeContext(server, intervalNode, (void **) &internalConnectionContext);
+    if(!UA_NodeId_equal(&UA_NODEID_NULL , &intervalNode)){
+        UA_free(internalConnectionContext);
+    }
+}
+
+static void
+dataSetWriterTypeDestructor(UA_Server *server,
+                            const UA_NodeId *sessionId, void *sessionContext,
+                            const UA_NodeId *typeId, void *typeContext,
+                            const UA_NodeId *nodeId, void **nodeContext) {
+    UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_USERLAND, "DataSetWriter destructor called!");
+}
+
+UA_StatusCode
+UA_Server_initPubSubNS0(UA_Server *server) {
+    UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+    UA_String profileArray[1];
+    profileArray[0] = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
+
+    retVal |= writePubSubNs0VariableArray(server, UA_NS0ID_PUBLISHSUBSCRIBE_SUPPORTEDTRANSPORTPROFILES,
+                                    profileArray,
+                                    1, &UA_TYPES[UA_TYPES_STRING]);
+    UA_NodeTypeLifecycle liveCycle;
+    liveCycle.constructor = NULL;
+    liveCycle.destructor = connectionTypeDestructor;
+    UA_Server_setNodeTypeLifecycle(server, UA_NODEID_NUMERIC(0, UA_NS0ID_PUBSUBCONNECTIONTYPE), liveCycle);
+    liveCycle.destructor = writerGroupTypeDestructor;
+    UA_Server_setNodeTypeLifecycle(server, UA_NODEID_NUMERIC(0, UA_NS0ID_WRITERGROUPTYPE), liveCycle);
+    liveCycle.destructor = dataSetWriterTypeDestructor;
+    UA_Server_setNodeTypeLifecycle(server, UA_NODEID_NUMERIC(0, UA_NS0ID_DATASETWRITERDATATYPE), liveCycle);
+
+    return retVal;
+}
+
+#endif /* UA_ENABLE_PUBSUB_INFORMATIONMODEL */

+ 42 - 0
src/pubsub/ua_pubsub_ns0.h

@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Copyright (c) 2017-2018 Fraunhofer IOSB (Author: Andreas Ebner)
+ */
+
+#ifndef OPEN62541_UA_PUBSUB_NS0_H
+#define OPEN62541_UA_PUBSUB_NS0_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "server/ua_server_internal.h"
+#include "ua_pubsub.h"
+
+UA_StatusCode
+UA_Server_initPubSubNS0(UA_Server *server);
+
+UA_StatusCode
+addPubSubConnectionRepresentation(UA_Server *server, UA_PubSubConnection *connection);
+UA_StatusCode
+removePubSubConnectionRepresentation(UA_Server *server, UA_PubSubConnection *connection);
+UA_StatusCode
+addWriterGroupRepresentation(UA_Server *server, UA_WriterGroup *writerGroup);
+UA_StatusCode
+removeWriterGroupRepresentation(UA_Server *server, UA_WriterGroup *writerGroup);
+UA_StatusCode
+addDataSetWriterRepresentation(UA_Server *server, UA_DataSetWriter *dataSetWriter);
+UA_StatusCode
+removeDataSetWriterRepresentation(UA_Server *server, UA_DataSetWriter *dataSetWriter);
+UA_StatusCode
+addPublishedDataItemsRepresentation(UA_Server *server, UA_PublishedDataSet *publishedDataSet);
+UA_StatusCode
+removePublishedDataSetRepresentation(UA_Server *server, UA_PublishedDataSet *publishedDataSet);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //OPEN62541_UA_PUBSUB_NS0_H

+ 7 - 0
src/server/ua_server.c

@@ -21,6 +21,9 @@
 #ifdef UA_ENABLE_GENERATE_NAMESPACE0
 #include "ua_namespaceinit_generated.h"
 #endif
+#ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL
+#include "ua_pubsub_ns0.h"
+#endif
 
 #ifdef UA_ENABLE_SUBSCRIPTIONS
 #include "ua_subscription.h"
@@ -288,6 +291,10 @@ UA_Server_new(const UA_ServerConfig *config) {
         UA_Server_delete(server);
         return NULL;
     }
+    /* Build PubSub information model */
+#ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL
+    UA_Server_initPubSubNS0(server);
+#endif
 
     return server;
 }

+ 0 - 1
src/server/ua_server_ns0.c

@@ -13,7 +13,6 @@
 #include "ua_namespace0.h"
 #include "ua_subscription.h"
 #include "ua_session.h"
-#include "ua_subscription_events.h"
 
 
 /*****************/

+ 72 - 0
src/server/ua_services_attribute.c

@@ -518,6 +518,37 @@ __UA_Server_read(UA_Server *server, const UA_NodeId *nodeId,
     return retval;
 }
 
+UA_StatusCode
+UA_Server_readObjectProperty(UA_Server *server, const UA_NodeId objectId,
+                             const UA_QualifiedName propertyName,
+                             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;
+    rpe.targetName = propertyName;
+
+    UA_BrowsePath bp;
+    UA_BrowsePath_init(&bp);
+    bp.startingNode = objectId;
+    bp.relativePath.elementsSize = 1;
+    bp.relativePath.elements = &rpe;
+
+    UA_StatusCode retval;
+    UA_BrowsePathResult bpr = UA_Server_translateBrowsePathToNodeIds(server, &bp);
+    if(bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
+        retval = bpr.statusCode;
+        UA_BrowsePathResult_deleteMembers(&bpr);
+        return retval;
+    }
+
+    retval = UA_Server_readValue(server, bpr.targets[0].targetId.nodeId, value);
+
+    UA_BrowsePathResult_deleteMembers(&bpr);
+    return retval;
+}
+
 /*****************/
 /* Type Checking */
 /*****************/
@@ -1342,3 +1373,44 @@ __UA_Server_write(UA_Server *server, const UA_NodeId *nodeId,
     }
     return UA_Server_write(server, &wvalue);
 }
+
+UA_StatusCode UA_EXPORT
+UA_Server_writeObjectProperty(UA_Server *server, const UA_NodeId objectId,
+                              const UA_QualifiedName propertyName,
+                              const 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;
+    rpe.targetName = propertyName;
+
+    UA_BrowsePath bp;
+    UA_BrowsePath_init(&bp);
+    bp.startingNode = objectId;
+    bp.relativePath.elementsSize = 1;
+    bp.relativePath.elements = &rpe;
+
+    UA_StatusCode retval;
+    UA_BrowsePathResult bpr = UA_Server_translateBrowsePathToNodeIds(server, &bp);
+    if(bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
+        retval = bpr.statusCode;
+        UA_BrowsePathResult_deleteMembers(&bpr);
+        return retval;
+    }
+
+    retval = UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
+
+    UA_BrowsePathResult_deleteMembers(&bpr);
+    return retval;
+}
+
+UA_StatusCode UA_EXPORT
+UA_Server_writeObjectProperty_scalar(UA_Server *server, const UA_NodeId objectId,
+                                     const UA_QualifiedName propertyName,
+                                     const void *value, const UA_DataType *type) {
+    UA_Variant var;
+    UA_Variant_init(&var);
+    UA_Variant_setScalar(&var, (void*)(uintptr_t)value, type);
+    return UA_Server_writeObjectProperty(server, objectId, propertyName, var);
+}

+ 3 - 1
src/server/ua_subscription.c

@@ -16,7 +16,6 @@
  */
 
 #include "ua_server_internal.h"
-#include "ua_subscription_events.h"
 #include "ua_subscription.h"
 
 #ifdef UA_ENABLE_SUBSCRIPTIONS /* conditional compilation */
@@ -27,6 +26,9 @@ void UA_Notification_delete(UA_Notification *n) {
     } 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]);
+        /* EventFilterResult currently isn't being used
+         * UA_EventFilterResult_delete(notification->data.event->result);
+         */
     }
     UA_free(n);
 }

+ 76 - 66
src/server/ua_subscription_datachange.c

@@ -11,7 +11,6 @@
 #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 */
 
@@ -24,14 +23,16 @@ void UA_MonitoredItem_init(UA_MonitoredItem *mon, UA_Subscription *sub) {
 }
 
 #ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
-static UA_StatusCode removeMonitoredItemFromNodeCallback(UA_Server *server, UA_Session *session, UA_Node *node,
-                                                         void *data) {
+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) {
@@ -68,25 +69,21 @@ 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) {
+    if(monitoredItem->monitoredItemType == UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
         /* Remove the monitored item from the node queue */
-        UA_Server_editNode(server, NULL, &monitoredItem->monitoredNodeId, removeMonitoredItemFromNodeCallback,
-                           monitoredItem);
+        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);
@@ -95,7 +92,8 @@ void UA_MonitoredItem_delete(UA_Server *server, UA_MonitoredItem *monitoredItem)
     UA_Server_delayedFree(server, monitoredItem);
 }
 
-UA_StatusCode MonitoredItem_ensureQueueSpace(UA_Server *server, UA_MonitoredItem *mon) {
+UA_StatusCode
+MonitoredItem_ensureQueueSpace(UA_Server *server, UA_MonitoredItem *mon) {
     if(mon->queueSize <= mon->maxQueueSize)
         return UA_STATUSCODE_GOOD;
 
@@ -133,13 +131,14 @@ UA_StatusCode MonitoredItem_ensureQueueSpace(UA_Server *server, UA_MonitoredItem
         /* 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))) {
+        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;
         }
@@ -148,46 +147,47 @@ UA_StatusCode MonitoredItem_ensureQueueSpace(UA_Server *server, UA_MonitoredItem
         --sub->notificationQueueSize;
 #endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
 
-        /* Free the notification */
-        if (mon->monitoredItemType == UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
+        /* Create an overflow notification */
 #ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
+        if(mon->monitoredItemType == UA_MONITOREDITEMTYPE_EVENTNOTIFY) {
             /* 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.
-             */
+            /* 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) {
+            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) {
+            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);
 
+            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]);
+                                     &overflowId, &UA_TYPES[UA_TYPES_NODEID]);
             overflowNotification->mon = mon;
-            if (mon->discardOldest) {
+            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 */
         }
+#endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
+
+        /* Free the notification */
         UA_Notification_delete(del);
     }
 
@@ -219,58 +219,61 @@ UA_StatusCode MonitoredItem_ensureQueueSpace(UA_Server *server, UA_MonitoredItem
 #define ABS_SUBTRACT_TYPE_INDEPENDENT(a,b) ((a)>(b)?(a)-(b):(b)-(a))
 
 static UA_INLINE UA_Boolean
-outOfDeadBand(const void *data1, const void *data2, const size_t index, const UA_DataType *type, const UA_Double deadbandValue) {
-    if (type == &UA_TYPES[UA_TYPES_SBYTE]) {
-        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_SByte*)data1)[index], ((const UA_SByte*)data2)[index]) <= deadbandValue)
+outOfDeadBand(const void *data1, const void *data2, const size_t index,
+              const UA_DataType *type, const UA_Double deadbandValue) {
+    if(type == &UA_TYPES[UA_TYPES_SBYTE]) {
+        if(ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_SByte*)data1)[index],
+                                         ((const UA_SByte*)data2)[index]) <= deadbandValue)
             return false;
-    } else
-    if (type == &UA_TYPES[UA_TYPES_BYTE]) {
-        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Byte*)data1)[index], ((const UA_Byte*)data2)[index]) <= deadbandValue)
+    } else if(type == &UA_TYPES[UA_TYPES_BYTE]) {
+        if(ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Byte*)data1)[index],
+                                         ((const UA_Byte*)data2)[index]) <= deadbandValue)
                 return false;
-    } else
-    if (type == &UA_TYPES[UA_TYPES_INT16]) {
-        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Int16*)data1)[index], ((const UA_Int16*)data2)[index]) <= deadbandValue)
+    } else if(type == &UA_TYPES[UA_TYPES_INT16]) {
+        if(ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Int16*)data1)[index],
+                                          ((const UA_Int16*)data2)[index]) <= deadbandValue)
             return false;
-    } else
-    if (type == &UA_TYPES[UA_TYPES_UINT16]) {
-        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_UInt16*)data1)[index], ((const UA_UInt16*)data2)[index]) <= deadbandValue)
+    } else if(type == &UA_TYPES[UA_TYPES_UINT16]) {
+        if(ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_UInt16*)data1)[index],
+                                          ((const UA_UInt16*)data2)[index]) <= deadbandValue)
             return false;
-    } else
-    if (type == &UA_TYPES[UA_TYPES_INT32]) {
-        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Int32*)data1)[index], ((const UA_Int32*)data2)[index]) <= deadbandValue)
+    } else if(type == &UA_TYPES[UA_TYPES_INT32]) {
+        if(ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Int32*)data1)[index],
+                                         ((const UA_Int32*)data2)[index]) <= deadbandValue)
             return false;
-    } else
-    if (type == &UA_TYPES[UA_TYPES_UINT32]) {
-        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_UInt32*)data1)[index], ((const UA_UInt32*)data2)[index]) <= deadbandValue)
+    } else if(type == &UA_TYPES[UA_TYPES_UINT32]) {
+        if(ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_UInt32*)data1)[index],
+                                         ((const UA_UInt32*)data2)[index]) <= deadbandValue)
             return false;
-    } else
-    if (type == &UA_TYPES[UA_TYPES_INT64]) {
-        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Int64*)data1)[index], ((const UA_Int64*)data2)[index]) <= deadbandValue)
+    } else if(type == &UA_TYPES[UA_TYPES_INT64]) {
+        if(ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Int64*)data1)[index],
+                                         ((const UA_Int64*)data2)[index]) <= deadbandValue)
             return false;
-    } else
-    if (type == &UA_TYPES[UA_TYPES_UINT64]) {
-        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_UInt64*)data1)[index], ((const UA_UInt64*)data2)[index]) <= deadbandValue)
+    } else if(type == &UA_TYPES[UA_TYPES_UINT64]) {
+        if(ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_UInt64*)data1)[index],
+                                         ((const UA_UInt64*)data2)[index]) <= deadbandValue)
             return false;
-    } else
-    if (type == &UA_TYPES[UA_TYPES_FLOAT]) {
-        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Float*)data1)[index], ((const UA_Float*)data2)[index]) <= deadbandValue)
+    } else if(type == &UA_TYPES[UA_TYPES_FLOAT]) {
+        if(ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Float*)data1)[index],
+                                         ((const UA_Float*)data2)[index]) <= deadbandValue)
             return false;
-    } else
-    if (type == &UA_TYPES[UA_TYPES_DOUBLE]) {
-        if (ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Double*)data1)[index], ((const UA_Double*)data2)[index]) <= deadbandValue)
+    } else if(type == &UA_TYPES[UA_TYPES_DOUBLE]) {
+        if(ABS_SUBTRACT_TYPE_INDEPENDENT(((const UA_Double*)data1)[index],
+                                         ((const UA_Double*)data2)[index]) <= deadbandValue)
             return false;
     }
     return true;
 }
 
 static UA_INLINE UA_Boolean
-updateNeededForFilteredValue(const UA_Variant *value, const UA_Variant *oldValue, const UA_Double deadbandValue) {
-    if (value->arrayLength != oldValue->arrayLength) {
+updateNeededForFilteredValue(const UA_Variant *value, const UA_Variant *oldValue,
+                             const UA_Double deadbandValue) {
+    if(value->arrayLength != oldValue->arrayLength)
         return true;
-    }
-    if (value->type != oldValue->type) {
+
+    if(value->type != oldValue->type)
         return true;
-    }
+
     if (UA_Variant_isScalar(value)) {
         return outOfDeadBand(value->data, oldValue->data, 0, value->type, deadbandValue);
     } else {
@@ -298,7 +301,8 @@ detectValueChangeWithFilter(UA_Server *server, UA_MonitoredItem *mon, UA_DataVal
        (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))
+            if(!updateNeededForFilteredValue(&value->value, &mon->lastValue,
+                                             mon->filter.dataChangeFilter.deadbandValue))
                 return false;
         }
         /* else if (mon->filter.deadbandType == UA_DEADBANDTYPE_PERCENT) {
@@ -334,6 +338,7 @@ detectValueChangeWithFilter(UA_Server *server, UA_MonitoredItem *mon, UA_DataVal
                                      &bufPos, &bufEnd, NULL, NULL);
         }
     }
+
     if(retval != UA_STATUSCODE_GOOD) {
         UA_LOG_WARNING_SESSION(server->config.logger, session,
                                "Subscription %u | MonitoredItem %i | "
@@ -493,6 +498,11 @@ UA_StatusCode
 UA_MonitoredItem_registerSampleCallback(UA_Server *server, UA_MonitoredItem *mon) {
     if(mon->sampleCallbackIsRegistered)
         return UA_STATUSCODE_GOOD;
+
+    /* Only DataChange MonitoredItems have a callback with a sampling interval */
+    if(mon->monitoredItemType != UA_MONITOREDITEMTYPE_CHANGENOTIFY)
+        return UA_STATUSCODE_GOOD;
+
     UA_StatusCode retval =
         UA_Server_addRepeatedCallback(server, (UA_ServerCallback)UA_MonitoredItem_sampleCallback,
                                       mon, (UA_UInt32)mon->samplingInterval, &mon->sampleCallbackId);

+ 109 - 98
src/server/ua_subscription_events.c

@@ -7,34 +7,33 @@
 
 #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;
+    UA_NodeId nodeId;
 } Events_nodeListElement;
 
-typedef LIST_HEAD(Events_nodeList, Events_nodeListElement) Events_nodeList;
-
 struct getNodesHandle {
     UA_Server *server;
-    Events_nodeList *nodes;
+    LIST_HEAD(Events_nodeList, Events_nodeListElement) nodes;
 };
 
 /* generates a unique event id */
-static UA_StatusCode UA_Event_generateEventId(UA_Server *server, UA_ByteString *generatedId) {
+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) {
+    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
@@ -45,25 +44,25 @@ static UA_StatusCode UA_Event_generateEventId(UA_Server *server, UA_ByteString *
     return UA_STATUSCODE_GOOD;
 }
 
-static UA_StatusCode findAllSubtypesNodeIteratorCallback(UA_NodeId parentId, UA_Boolean isInverse,
-                                                         UA_NodeId referenceTypeId, void *handle) {
+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)) {
+    if(isInverse || !UA_NodeId_equal(&referenceTypeId, &hasSubtypeId))
         return UA_STATUSCODE_GOOD;
-    }
 
     Events_nodeListElement *entry = (Events_nodeListElement *) UA_malloc(sizeof(Events_nodeListElement));
-    if (!entry) {
+    if(!entry)
         return UA_STATUSCODE_BADOUTOFMEMORY;
-    }
-    entry->node = UA_NodeId_new();
-    if (!entry->node) {
+
+    UA_StatusCode retval = UA_NodeId_copy(&parentId, &entry->nodeId);
+    if(retval != UA_STATUSCODE_GOOD) {
         UA_free(entry);
-        return UA_STATUSCODE_BADOUTOFMEMORY;
+        return retval;
     }
-    UA_NodeId_copy(&parentId, entry->node);
-    LIST_INSERT_HEAD(((struct getNodesHandle *) handle)->nodes, entry, listEntry);
+
+    LIST_INSERT_HEAD(&((struct getNodesHandle *) handle)->nodes, entry, listEntry);
 
     /* recursion */
     UA_Server_forEachChildNodeCall(((struct getNodesHandle *) handle)->server,
@@ -73,28 +72,27 @@ static UA_StatusCode findAllSubtypesNodeIteratorCallback(UA_NodeId parentId, UA_
 
 /* 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) {
+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) {
+    LIST_INIT(&handle.nodes);
+    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) {
+    LIST_FOREACH_SAFE(iter, &handle.nodes, listEntry, tmp_iter) {
         if (!nodeFound) {
             UA_RelativePathElement rpe;
             UA_RelativePathElement_init(&rpe);
-            rpe.referenceTypeId = *iter->node;
+            rpe.referenceTypeId = iter->nodeId;
             rpe.isInverse = false;
             rpe.includeSubtypes = false;
             rpe.targetName = *name;
@@ -106,12 +104,12 @@ static void UA_Event_findVariableNode(UA_Server *server, UA_QualifiedName *name,
             bp.relativePath.elements = &rpe;
 
             *out = UA_Server_translateBrowsePathToNodeIds(server, &bp);
-            if (out->statusCode == UA_STATUSCODE_GOOD) {
+            if(out->statusCode == UA_STATUSCODE_GOOD)
                 nodeFound = UA_TRUE;
-            }
         }
+
         LIST_REMOVE(iter, listEntry);
-        UA_NodeId_delete(iter->node);
+        UA_NodeId_deleteMembers(&iter->nodeId);
         UA_free(iter);
     }
 }
@@ -123,8 +121,6 @@ UA_Server_createEvent(UA_Server *server, const UA_NodeId eventType, UA_NodeId *o
         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);
@@ -143,27 +139,30 @@ UA_Server_createEvent(UA_Server *server, const UA_NodeId eventType, UA_NodeId *o
     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);
+    UA_StatusCode 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) {
+    if(bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
+        UA_BrowsePathResult_deleteMembers(&bpr);
         return bpr.statusCode;
     }
     UA_Variant value;
@@ -177,43 +176,48 @@ UA_Server_createEvent(UA_Server *server, const UA_NodeId eventType, UA_NodeId *o
     return retval;
 }
 
-static UA_Boolean isValidEvent(UA_Server *server, const UA_NodeId *validEventParent, const UA_NodeId *eventId) {
+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) {
+    if(bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
+        UA_BrowsePathResult_deleteMembers(&bpr);
         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_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) {
+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) {
+    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.");
+    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) {
+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);
 
@@ -243,39 +247,42 @@ static UA_StatusCode UA_Server_filterEvent(UA_Server *server, const UA_NodeId *e
     /* 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)) {
+    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);
+        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) {
+        UA_StatusCode 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) {
+
+        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) {
+
+            if(whereClausesUsed)
                 return UA_STATUSCODE_BADNOTSUPPORTED;
-            }
         } else {
             UA_Variant_init(&notification->fields.eventFields[i]);
             /* TODO: better statuscode for failing at where clauses */
@@ -287,8 +294,9 @@ static UA_StatusCode UA_Server_filterEvent(UA_Server *server, const UA_NodeId *e
     return UA_STATUSCODE_GOOD;
 }
 
-static UA_StatusCode eventSetConstants(UA_Server *server, const UA_NodeId *event, const UA_NodeId *origin,
-                                       UA_ByteString *outEventId) {
+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 */
@@ -348,23 +356,25 @@ static UA_StatusCode eventSetConstants(UA_Server *server, const UA_NodeId *event
 
 
 /* insert each node into the list (passed as handle) */
-static UA_StatusCode getParentsNodeIteratorCallback(UA_NodeId parentId, UA_Boolean isInverse,
-                                                    UA_NodeId referenceTypeId, void *handle) {
+static UA_StatusCode
+getParentsNodeIteratorCallback(UA_NodeId parentId, UA_Boolean isInverse,
+                               UA_NodeId referenceTypeId, void *handle) {
     /* parents have an inverse reference */
-    if (!isInverse) {
+    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_StatusCode retval = UA_NodeId_copy(&parentId, &entry->nodeId);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_free(entry);
+        return retval;
     }
-    UA_NodeId_copy(&parentId, entry->node);
-    LIST_INSERT_HEAD(((struct getNodesHandle *) handle)->nodes, entry, listEntry);
+
+    LIST_INSERT_HEAD(&((struct getNodesHandle *) handle)->nodes, entry, listEntry);
 
     /* recursion */
     UA_Server_forEachChildNodeCall(((struct getNodesHandle *) handle)->server,
@@ -374,13 +384,15 @@ static UA_StatusCode getParentsNodeIteratorCallback(UA_NodeId parentId, UA_Boole
 
 /* 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_Event_addEventToMonitoredItem(UA_Server *server, const UA_NodeId *event,
+                                 UA_MonitoredItem *mon) {
     UA_Notification *notification = (UA_Notification *) UA_malloc(sizeof(UA_Notification));
-    if (!notification) {
+    if(!notification)
         return UA_STATUSCODE_BADOUTOFMEMORY;
-    }
+
     /* apply the filter */
-    UA_StatusCode retval = UA_Server_filterEvent(server, event, &mon->filter.eventFilter, &notification->data.event);
+    UA_StatusCode retval = UA_Server_filterEvent(server, event, &mon->filter.eventFilter,
+                                                 &notification->data.event);
     if (retval != UA_STATUSCODE_GOOD) {
         UA_free(notification);
         return retval;
@@ -406,31 +418,29 @@ UA_Server_triggerEvent(UA_Server *server, const UA_NodeId eventNodeId, const UA_
             {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!");
+
+    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) {
+    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;
+    LIST_INIT(&parentHandle.nodes);
     retval = getParentsNodeIteratorCallback(origin, UA_TRUE, UA_NODEID_NULL, &parentHandle);
-    if (retval != UA_STATUSCODE_GOOD) {
+    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);
+    LIST_FOREACH_SAFE(parentIter, &parentHandle.nodes, listEntry, tmp_parentIter) {
+        const UA_ObjectNode *node = (const UA_ObjectNode *) UA_Nodestore_get(server, &parentIter->nodeId);
         /* SLIST_FOREACH */
         for (UA_MonitoredItem *monIter = node->monitoredItemQueue; monIter != NULL; monIter = monIter->next) {
             retval = UA_Event_addEventToMonitoredItem(server, &eventNodeId, monIter);
@@ -440,8 +450,9 @@ UA_Server_triggerEvent(UA_Server *server, const UA_NodeId eventNodeId, const UA_
             }
         }
         UA_Nodestore_release(server, (const UA_Node *) node);
+
         LIST_REMOVE(parentIter, listEntry);
-        UA_NodeId_delete(parentIter->node);
+        UA_NodeId_delete(&parentIter->nodeId);
         UA_free(parentIter);
     }
 

+ 0 - 16
src/server/ua_subscription_events.h

@@ -1,16 +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 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_ */

+ 5 - 0
tests/CMakeLists.txt

@@ -184,6 +184,11 @@ if(UA_ENABLE_PUBSUB)
     add_executable(check_pubsub_publish pubsub/check_pubsub_publish.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-plugins>)
     target_link_libraries(check_pubsub_publish ${LIBS})
     add_test(check_pubsub_publish ${TESTS_BINARY_DIR}/check_pubsub_publish)
+    if(UA_ENABLE_PUBSUB_INFORMATIONMODEL)
+        add_executable(check_pubsub_informationmodel pubsub/check_pubsub_informationmodel.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-plugins>)
+        target_link_libraries(check_pubsub_informationmodel ${LIBS})
+        add_test(check_pubsub_informationmodel ${TESTS_BINARY_DIR}/check_pubsub_informationmodel)
+    endif()
 endif()
 
 add_executable(check_server_readspeed server/check_server_readspeed.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)

+ 321 - 0
tests/pubsub/check_pubsub_informationmodel.c

@@ -0,0 +1,321 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Copyright (c) 2017 - 2018 Fraunhofer IOSB (Author: Andreas Ebner)
+ */
+
+#include <string.h>
+#include <math.h>
+#include "ua_types.h"
+#include "ua_server_pubsub.h"
+#include "src_generated/ua_types_generated.h"
+#include "ua_network_pubsub_udp.h"
+#include "ua_server_internal.h"
+#include "check.h"
+#include "ua_plugin_pubsub.h"
+#include "ua_config_default.h"
+
+UA_Server *server = NULL;
+UA_ServerConfig *config = NULL;
+
+UA_NodeId connection1, connection2, writerGroup1, writerGroup2, writerGroup3,
+        publishedDataSet1, publishedDataSet2, dataSetWriter1, dataSetWriter2, dataSetWriter3;
+
+static void setup(void) {
+    config = UA_ServerConfig_new_default();
+    config->pubsubTransportLayers = (UA_PubSubTransportLayer *) UA_malloc(sizeof(UA_PubSubTransportLayer));
+    if(!config->pubsubTransportLayers) {
+        UA_ServerConfig_delete(config);
+    }
+    config->pubsubTransportLayers[0] = UA_PubSubTransportLayerUDPMP();
+    config->pubsubTransportLayersSize++;
+    server = UA_Server_new(config);
+    UA_Server_run_startup(server);
+}
+
+static void teardown(void) {
+    UA_Server_run_shutdown(server);
+    UA_Server_delete(server);
+    UA_ServerConfig_delete(config);
+}
+
+static void addPublishedDataSet(UA_String pdsName, UA_NodeId *assignedId){
+    UA_PublishedDataSetConfig pdsConfig;
+    memset(&pdsConfig, 0, sizeof(UA_PublishedDataSetConfig));
+    pdsConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
+    pdsConfig.name = pdsName;
+    UA_Server_addPublishedDataSet(server, &pdsConfig, assignedId);
+}
+
+static void addPubSubConnection(UA_String connectionName, UA_String addressUrl, UA_NodeId *assignedId){
+    UA_PubSubConnectionConfig connectionConfig;
+    memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig));
+    connectionConfig.name = connectionName;
+    UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL, addressUrl};
+    UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
+                         &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
+    connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
+    UA_Server_addPubSubConnection(server, &connectionConfig, assignedId);
+}
+
+static void addWriterGroup(UA_NodeId parentConnection, UA_String name, UA_Duration interval, UA_NodeId *assignedId){
+    UA_WriterGroupConfig writerGroupConfig;
+    memset(&writerGroupConfig, 0, sizeof(writerGroupConfig));
+    writerGroupConfig.name = name;
+    writerGroupConfig.publishingInterval = interval;
+    writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
+    UA_Server_addWriterGroup(server, parentConnection, &writerGroupConfig, assignedId);
+}
+
+static void addDataSetWriter(UA_NodeId parentWriterGroup, UA_NodeId connectedPDS, UA_String name, UA_NodeId *assignedId){
+    UA_DataSetWriterConfig dataSetWriterConfig;
+    memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig));
+    dataSetWriterConfig.name = name;
+    UA_Server_addDataSetWriter(server, parentWriterGroup, connectedPDS, &dataSetWriterConfig, assignedId);
+}
+
+static UA_Boolean doubleEqual(UA_Double a, UA_Double b, UA_Double maxAbsDelta){
+    return fabs(a-b) < maxAbsDelta;
+}
+
+static UA_NodeId
+findSingleChildNode(UA_Server *server_, UA_QualifiedName targetName, UA_NodeId referenceTypeId, UA_NodeId startingNode){
+    UA_NodeId resultNodeId;
+    UA_RelativePathElement rpe;
+    UA_RelativePathElement_init(&rpe);
+    rpe.referenceTypeId = referenceTypeId;
+    rpe.isInverse = false;
+    rpe.includeSubtypes = false;
+    rpe.targetName = targetName;
+    UA_BrowsePath bp;
+    UA_BrowsePath_init(&bp);
+    bp.startingNode = startingNode;
+    bp.relativePath.elementsSize = 1;
+    bp.relativePath.elements = &rpe;
+    UA_BrowsePathResult bpr =
+            UA_Server_translateBrowsePathToNodeIds(server_, &bp);
+    if(bpr.statusCode != UA_STATUSCODE_GOOD ||
+       bpr.targetsSize < 1)
+        return UA_NODEID_NULL;
+    UA_NodeId_copy(&bpr.targets[0].targetId.nodeId, &resultNodeId);
+    UA_BrowsePathResult_deleteMembers(&bpr);
+    return resultNodeId;
+}
+
+static void setupBasicPubSubConfiguration(void){
+    addPubSubConnection(UA_STRING("Connection 1"), UA_STRING("opc.udp://224.0.0.22:4840/"), &connection1);
+    addPubSubConnection(UA_STRING("Connection 2"), UA_STRING("opc.udp://224.0.0.22:4840/"), &connection2);
+    addPublishedDataSet(UA_STRING("PublishedDataSet 1"), &publishedDataSet1);
+    addPublishedDataSet(UA_STRING("PublishedDataSet 2"), &publishedDataSet2);
+    addWriterGroup(connection1, UA_STRING("WriterGroup 1"), 10, &writerGroup1);
+    addWriterGroup(connection1, UA_STRING("WriterGroup 2"), 100, &writerGroup2);
+    addWriterGroup(connection2, UA_STRING("WriterGroup 3"), 1000, &writerGroup3);
+    addDataSetWriter(writerGroup1, publishedDataSet1, UA_STRING("DataSetWriter 1"), &dataSetWriter1);
+    addDataSetWriter(writerGroup1, publishedDataSet2, UA_STRING("DataSetWriter 2"), &dataSetWriter2);
+    addDataSetWriter(writerGroup2, publishedDataSet2, UA_STRING("DataSetWriter 3"), &dataSetWriter3);
+}
+
+START_TEST(AddSignlePubSubConnectionAndCheckInformationModelRepresentation){
+    UA_String connectionName = UA_STRING("Connection 1");
+    addPubSubConnection(connectionName, UA_STRING("opc.udp://224.0.0.22:4840/"), &connection1);
+    UA_QualifiedName browseName;
+    UA_StatusCode retVal = UA_STATUSCODE_GOOD;
+    retVal |= UA_Server_readBrowseName(server, connection1, &browseName);
+    ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(UA_String_equal(&browseName.name, &connectionName), UA_TRUE);
+    UA_QualifiedName_deleteMembers(&browseName);
+    } END_TEST
+
+START_TEST(AddRemoveAddSignlePubSubConnectionAndCheckInformationModelRepresentation){
+    UA_String connectionName = UA_STRING("Connection 1");
+    addPubSubConnection(connectionName, UA_STRING("opc.udp://224.0.0.22:4840/"), &connection1);
+    UA_QualifiedName browseName;
+    UA_StatusCode retVal;
+    ck_assert_int_eq(UA_Server_removePubSubConnection(server, connection1), UA_STATUSCODE_GOOD);
+    retVal = UA_Server_readBrowseName(server, connection1, &browseName);
+    ck_assert_int_eq(retVal, UA_STATUSCODE_BADNODEIDUNKNOWN);
+    addPubSubConnection(connectionName, UA_STRING("opc.udp://224.0.0.22:4840/"), &connection1);
+    retVal = UA_Server_readBrowseName(server, connection1, &browseName);
+    ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(UA_String_equal(&browseName.name, &connectionName), UA_TRUE);
+    UA_QualifiedName_deleteMembers(&browseName);
+    } END_TEST
+
+START_TEST(AddSinglePublishedDataSetAndCheckInformationModelRepresentation){
+    UA_String pdsName = UA_STRING("PDS 1");
+    addPublishedDataSet(pdsName, &publishedDataSet1);
+    UA_QualifiedName browseName;
+    ck_assert_int_eq(UA_Server_readBrowseName(server, publishedDataSet1, &browseName), UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(UA_String_equal(&browseName.name, &pdsName), UA_TRUE);
+    UA_QualifiedName_deleteMembers(&browseName);
+    } END_TEST
+
+START_TEST(AddRemoveAddSinglePublishedDataSetAndCheckInformationModelRepresentation){
+    UA_String pdsName = UA_STRING("PDS 1");
+    addPublishedDataSet(pdsName, &publishedDataSet1);
+    UA_QualifiedName browseName;
+    UA_StatusCode retVal;
+    ck_assert_int_eq(UA_Server_removePublishedDataSet(server, publishedDataSet1), UA_STATUSCODE_GOOD);
+    retVal = UA_Server_readBrowseName(server, publishedDataSet1, &browseName);
+    ck_assert_int_eq(retVal, UA_STATUSCODE_BADNODEIDUNKNOWN);
+    addPublishedDataSet(pdsName, &publishedDataSet1);
+    retVal = UA_Server_readBrowseName(server, publishedDataSet1, &browseName);
+    ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(UA_String_equal(&browseName.name, &pdsName), UA_TRUE);
+    UA_QualifiedName_deleteMembers(&browseName);
+    } END_TEST
+
+START_TEST(AddSingleWriterGroupAndCheckInformationModelRepresentation){
+    UA_String connectionName = UA_STRING("Connection 1");
+    addPubSubConnection(connectionName, UA_STRING("opc.udp://224.0.0.22:4840/"), &connection1);
+    UA_String pdsName = UA_STRING("PDS 1");
+    addPublishedDataSet(pdsName, &publishedDataSet1);
+    UA_String wgName = UA_STRING("WriterGroup 1");
+    addWriterGroup(connection1, wgName, 10, &writerGroup1);
+    UA_QualifiedName browseName;
+    ck_assert_int_eq(UA_Server_readBrowseName(server, writerGroup1, &browseName), UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(UA_String_equal(&browseName.name, &wgName), UA_TRUE);
+    UA_QualifiedName_deleteMembers(&browseName);
+    } END_TEST
+
+START_TEST(AddRemoveAddSingleWriterGroupAndCheckInformationModelRepresentation){
+    UA_String connectionName = UA_STRING("Connection 1");
+    addPubSubConnection(connectionName, UA_STRING("opc.udp://224.0.0.22:4840/"), &connection1);
+    UA_String pdsName = UA_STRING("PDS 1");
+    addPublishedDataSet(pdsName, &publishedDataSet1);
+    UA_String wgName = UA_STRING("WriterGroup 1");
+    addWriterGroup(connection1, wgName, 10, &writerGroup1);
+    UA_QualifiedName browseName;
+    UA_StatusCode retVal;
+    ck_assert_int_eq(UA_Server_removeWriterGroup(server, writerGroup1), UA_STATUSCODE_GOOD);
+    retVal = UA_Server_readBrowseName(server, writerGroup1, &browseName);
+    ck_assert_int_eq(retVal, UA_STATUSCODE_BADNODEIDUNKNOWN);
+    addWriterGroup(connection1, wgName, 10, &writerGroup1);
+    retVal = UA_Server_readBrowseName(server, writerGroup1, &browseName);
+    ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(UA_String_equal(&browseName.name, &wgName), UA_TRUE);
+    UA_QualifiedName_deleteMembers(&browseName);
+    } END_TEST
+
+START_TEST(AddSingleDataSetWriterAndCheckInformationModelRepresentation){
+    UA_String connectionName = UA_STRING("Connection 1");
+    addPubSubConnection(connectionName, UA_STRING("opc.udp://224.0.0.22:4840/"), &connection1);
+    UA_String pdsName = UA_STRING("PDS 1");
+    addPublishedDataSet(pdsName, &publishedDataSet1);
+    UA_String wgName = UA_STRING("WriterGroup 1");
+    addWriterGroup(connection1, wgName, 10, &writerGroup1);
+    UA_String dswName = UA_STRING("DataSetWriter 1");
+    addDataSetWriter(writerGroup1, publishedDataSet1, dswName, &dataSetWriter1);
+    UA_QualifiedName browseName;
+    ck_assert_int_eq(UA_Server_readBrowseName(server, dataSetWriter1, &browseName), UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(UA_String_equal(&browseName.name, &dswName), UA_TRUE);
+    UA_QualifiedName_deleteMembers(&browseName);
+    } END_TEST
+
+START_TEST(AddRemoveAddSingleDataSetWriterAndCheckInformationModelRepresentation){
+    UA_String connectionName = UA_STRING("Connection 1");
+    addPubSubConnection(connectionName, UA_STRING("opc.udp://224.0.0.22:4840/"), &connection1);
+    UA_String pdsName = UA_STRING("PDS 1");
+    addPublishedDataSet(pdsName, &publishedDataSet1);
+    UA_String wgName = UA_STRING("WriterGroup 1");
+    addWriterGroup(connection1, wgName, 10, &writerGroup1);
+    UA_String dswName = UA_STRING("DataSetWriter 1");
+    addDataSetWriter(writerGroup1, publishedDataSet1, dswName, &dataSetWriter1);
+    UA_QualifiedName browseName;
+    UA_StatusCode retVal;
+    ck_assert_int_eq(UA_Server_removeDataSetWriter(server, dataSetWriter1), UA_STATUSCODE_GOOD);
+    retVal = UA_Server_readBrowseName(server, dataSetWriter1, &browseName);
+    ck_assert_int_eq(retVal, UA_STATUSCODE_BADNODEIDUNKNOWN);
+    addDataSetWriter(writerGroup1, publishedDataSet1, dswName, &dataSetWriter1);
+    retVal = UA_Server_readBrowseName(server, dataSetWriter1, &browseName);
+    ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(UA_String_equal(&browseName.name, &dswName), UA_TRUE);
+    UA_QualifiedName_deleteMembers(&browseName);
+    } END_TEST
+
+START_TEST(ReadPublishIntervalAndCompareWithInternalValue){
+    setupBasicPubSubConfiguration();
+    UA_NodeId publishIntervalId = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "PublishingInterval"),
+                                                      UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY), writerGroup1);
+    UA_Variant value;
+    UA_Variant_init(&value);
+    ck_assert_int_eq(UA_Server_readValue(server, publishIntervalId, &value), UA_STATUSCODE_GOOD);
+    ck_assert(UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_DURATION]));
+    ck_assert(doubleEqual((UA_Double) *((UA_Duration *) value.data), 10, 0.05));
+    UA_Variant_deleteMembers(&value);
+    } END_TEST
+
+START_TEST(WritePublishIntervalAndCompareWithInternalValue){
+        setupBasicPubSubConfiguration();
+        UA_NodeId publishIntervalId = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "PublishingInterval"),
+                                                          UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY), writerGroup1);
+        UA_Variant value;
+        UA_Variant_init(&value);
+        UA_Duration interval = 100;
+        UA_Variant_setScalar(&value, &interval, &UA_TYPES[UA_TYPES_DURATION]);
+        ck_assert_int_eq(UA_Server_writeValue(server, publishIntervalId, value), UA_STATUSCODE_GOOD);
+
+        ck_assert_int_eq(UA_Server_readValue(server, publishIntervalId, &value), UA_STATUSCODE_GOOD);
+        ck_assert(UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_DURATION]));
+        ck_assert(doubleEqual((UA_Double) *((UA_Duration *) value.data), 100, 0.05));
+        UA_Variant_deleteMembers(&value);
+    } END_TEST
+
+START_TEST(ReadAddressAndCompareWithInternalValue){
+        setupBasicPubSubConfiguration();
+        UA_NodeId address = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "Address"),
+                                                          UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), connection1);
+        UA_NodeId url = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "Url"),
+                                            UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), address);
+        UA_NodeId networkInterface = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "NetworkInterface"),
+                                            UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), address);
+        UA_PubSubConnectionConfig connectionConfig;
+        memset(&connectionConfig, 0, sizeof(connectionConfig));
+        UA_Server_getPubSubConnectionConfig(server, connection1, &connectionConfig);
+        UA_Variant value;
+        UA_Variant_init(&value);
+        ck_assert_int_eq(UA_Server_readValue(server, url, &value), UA_STATUSCODE_GOOD);
+        UA_NetworkAddressUrlDataType *networkAddressUrlDataType = (UA_NetworkAddressUrlDataType *)connectionConfig.address.data;
+        ck_assert(UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_STRING]));
+        ck_assert(UA_String_equal(((UA_String *) value.data), &networkAddressUrlDataType->url));
+        UA_Variant_deleteMembers(&value);
+        ck_assert_int_eq(UA_Server_readValue(server, networkInterface, &value), UA_STATUSCODE_GOOD);
+        ck_assert(UA_String_equal(((UA_String *) value.data), &networkAddressUrlDataType->networkInterface));
+        UA_PubSubConnectionConfig_deleteMembers(&connectionConfig);
+        UA_Variant_deleteMembers(&value);
+    } END_TEST
+
+int main(void) {
+    TCase *tc_add_pubsub_informationmodel = tcase_create("PubSub add single elements and check information model representation");
+    tcase_add_checked_fixture(tc_add_pubsub_informationmodel, setup, teardown);
+    tcase_add_test(tc_add_pubsub_informationmodel, AddSignlePubSubConnectionAndCheckInformationModelRepresentation);
+    tcase_add_test(tc_add_pubsub_informationmodel, AddRemoveAddSignlePubSubConnectionAndCheckInformationModelRepresentation);
+    tcase_add_test(tc_add_pubsub_informationmodel, AddSinglePublishedDataSetAndCheckInformationModelRepresentation);
+    tcase_add_test(tc_add_pubsub_informationmodel, AddRemoveAddSinglePublishedDataSetAndCheckInformationModelRepresentation);
+    tcase_add_test(tc_add_pubsub_informationmodel, AddSingleWriterGroupAndCheckInformationModelRepresentation);
+    tcase_add_test(tc_add_pubsub_informationmodel, AddRemoveAddSingleWriterGroupAndCheckInformationModelRepresentation);
+    tcase_add_test(tc_add_pubsub_informationmodel, AddSingleDataSetWriterAndCheckInformationModelRepresentation);
+    tcase_add_test(tc_add_pubsub_informationmodel, AddRemoveAddSingleDataSetWriterAndCheckInformationModelRepresentation);
+
+    TCase *tc_add_pubsub_writergroupelements = tcase_create("PubSub WriterGroup check properties");
+    tcase_add_checked_fixture(tc_add_pubsub_writergroupelements, setup, teardown);
+    tcase_add_test(tc_add_pubsub_writergroupelements, ReadPublishIntervalAndCompareWithInternalValue);
+    tcase_add_test(tc_add_pubsub_writergroupelements, WritePublishIntervalAndCompareWithInternalValue);
+
+    TCase *tc_add_pubsub_pubsubconnectionelements = tcase_create("PubSub Connection check properties");
+    tcase_add_checked_fixture(tc_add_pubsub_pubsubconnectionelements, setup, teardown);
+    tcase_add_test(tc_add_pubsub_pubsubconnectionelements, ReadAddressAndCompareWithInternalValue);
+
+    Suite *s = suite_create("PubSub WriterGroups/Writer/Fields handling and publishing");
+    suite_add_tcase(s, tc_add_pubsub_informationmodel);
+    suite_add_tcase(s, tc_add_pubsub_writergroupelements);
+    suite_add_tcase(s, tc_add_pubsub_pubsubconnectionelements);
+
+    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;
+}

+ 3 - 3
tests/pubsub/check_pubsub_pds.c

@@ -5,9 +5,9 @@
  * Copyright (c) 2017 - 2018 Fraunhofer IOSB (Author: Andreas Ebner)
  */
 
-#include <ua_server_pubsub.h>
-#include <src_generated/ua_types_generated_encoding_binary.h>
-#include <ua_types.h>
+#include "ua_server_pubsub.h"
+#include "src_generated/ua_types_generated_encoding_binary.h"
+#include "ua_types.h"
 #include "ua_config_default.h"
 #include "ua_network_pubsub_udp.h"
 #include "ua_server_internal.h"

Diferenças do arquivo suprimidas por serem muito extensas
+ 1475 - 0
tools/schema/Opc.Ua.NodeSet2.PubSubMinimal.xml