Browse Source

Added first part of the pubsub information model representation

Andreas Ebner 6 years ago
parent
commit
6aeb2fddf6

+ 14 - 0
CMakeLists.txt

@@ -100,6 +100,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)
@@ -398,6 +405,7 @@ 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
@@ -430,6 +438,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
@@ -539,6 +548,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}")
@@ -662,6 +675,7 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_namespace0.c
                            --max-string-length=${NODESET_MAX_STR_LEN}
                            --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

+ 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

+ 115 - 0
examples/pubsub/tutorial_pubsub_subscribe.c

@@ -0,0 +1,115 @@
+/* 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 <stdio.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;
+    }
+    connection->channel->receive(connection->channel, &buffer, NULL, 300000);
+    if (buffer.length > 0) {
+        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Message received:");
+        UA_NetworkMessage *actualNetworkMessage = (UA_NetworkMessage *) UA_calloc(1, sizeof(UA_NetworkMessage));
+        size_t currentPosition = 0;
+        if (!actualNetworkMessage) {
+            UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Networkmessage allocation failed!");
+            UA_ByteString_deleteMembers(&buffer);
+            return;
+        }
+        UA_NetworkMessage_decodeBinary(&buffer, &currentPosition, actualNetworkMessage);
+        printf("Message length: %zu\n", buffer.length);
+        if (actualNetworkMessage->networkMessageType == UA_NETWORKMESSAGE_DATASET) {
+            if ((actualNetworkMessage->payloadHeaderEnabled && (actualNetworkMessage->payloadHeader.dataSetPayloadHeader.count >= 1)) ||
+                (!actualNetworkMessage->payloadHeaderEnabled)) {
+                if (actualNetworkMessage->payload.dataSetPayload.dataSetMessages[0].header.dataSetMessageType ==
+                    UA_DATASETMESSAGE_DATAKEYFRAME) {
+                    for (int i = 0; i < actualNetworkMessage->payload.dataSetPayload.dataSetMessages[0].data.keyFrameData.fieldCount; i++) {
+                        const UA_DataType *currentType = actualNetworkMessage->payload.dataSetPayload.dataSetMessages[0].data.keyFrameData.dataSetFields[i].value.type;
+                        if (currentType == &UA_TYPES[UA_TYPES_BYTE]) {
+                            printf("Message content: [Byte] \n\tReceived data: %i\n",
+                                   *((UA_Byte *) actualNetworkMessage->payload.dataSetPayload.dataSetMessages[0].data.keyFrameData.dataSetFields[i].value.data));
+                        } else if (currentType == &UA_TYPES[UA_TYPES_DATETIME]) {
+                            UA_DateTimeStruct receivedTime = UA_DateTime_toStruct(
+                                    *((UA_DateTime *) actualNetworkMessage->payload.dataSetPayload.dataSetMessages[0].data.keyFrameData.dataSetFields[i].value.data));
+                            printf("Message content: [DateTime] \n\tReceived date: %02i-%02i-%02i Received time: %02i:%02i:%02i\n", receivedTime.year, receivedTime.month,
+                                   receivedTime.day, receivedTime.hour, receivedTime.min, receivedTime.sec);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+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, 5, &subscriptionCallbackId);
+        } else {
+            UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "register channel failed: 0x%x!", rv);
+        }
+    }
+
+    retval |= UA_Server_run(server, &running);
+    UA_Server_delete(server);
+    UA_ServerConfig_delete(config);
+    return (int)retval;
+}

+ 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
 

+ 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

+ 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"
@@ -292,6 +295,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;
 }

+ 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"

File diff suppressed because it is too large
+ 1475 - 0
tools/schema/Opc.Ua.NodeSet2.PubSubMinimal.xml