/*
 * Copyright (C) 2022 EXOLIGENT (www.exoligent.com)
 * Author: Maxime Coroyer <maxime.coroyer@exoligent.com>
 */

#include <stdlib.h>     // malloc(), free()
#include <string.h>     // memset()
#include <unistd.h>     // close()
#include <pthread.h>    // pthread()

#include <open62541/server_config_default.h>                // UA_Server_new()

#include "test.h"

#include <open62541/plugin/pubsub_udp.h>                    // UA_PubSubTransportLayerUDPMP()
#ifdef UA_ENABLE_PUBSUB_ETH_UADP
#   include <open62541/plugin/pubsub_ethernet.h>            // UA_PubSubTransportLayerEthernet()
#   include <linux/if_link.h>
#   include <linux/if_xdp.h>
#   include "opc-rt.h"
#endif

static void opc_cleanup_task(void *arg);
static void *opc_task(void *arg);

int opc_init(struct tst_session *sess)
{
    if (!sess)
        return -1;

    if (sess->opc.running)
        return -1;

    /* => publisher */
    strcpy(sess->opc.pub.net_str, OPC_PUB_NET_INTERFACE);
    strcpy(sess->opc.pub.url_str, OPC_PUB_URL);
    sess->opc.pub.publisher_id = OPC_PUB_PUB_ID;
    sess->opc.pub.dataset_writer_id = OPC_PUB_DATASET_WRITE_ID;
    sess->opc.pub.writer_group_id = OPC_PUB_WRITER_GROUP_ID;
    sess->opc.pub.publish_period_us = OPC_PUB_PUBLISH_PERDIOD_US;
    /* + pub var */
    strcpy(sess->opc.pub.data.name, OPC_PUB_FIP_DATA_NAME);
    sess->opc.pub.data.fip_id = OPC_PUB_FIP_DATA_ID;

    /* => subscriber */
    strcpy(sess->opc.sub.net_str, OPC_SUB_NET_INTERFACE);
    strcpy(sess->opc.sub.url_str, OPC_SUB_URL);
    sess->opc.sub.publisher_id = OPC_SUB_PUB_ID;
    sess->opc.sub.dataset_writer_id = OPC_SUB_DATASET_WRITE_ID;
    sess->opc.sub.writer_group_id = OPC_SUB_WRITER_GROUP_ID;
    /* + sub var */
    strcpy(sess->opc.sub.data.name, OPC_SUB_FIP_DATA_NAME);
    sess->opc.sub.data.fip_id = OPC_SUB_FIP_DATA_ID;

    return 0;
}

int opc_pub_start(struct tst_session *sess)
{
    pthread_attr_t thr_attr;
    struct sched_param thr_param;

    if (!sess)
        return -1;

    if (sess->opc.running)
        return -1;

    /* app - thread creation */
    if (pthread_attr_init(&thr_attr)) {
        _print(opc, err, "[failed] pthread_attr_init()\n");
        return -1; // errno is already set
    }

    pthread_attr_setinheritsched(&thr_attr, PTHREAD_EXPLICIT_SCHED);
    pthread_attr_getschedparam (&thr_attr, &thr_param);
    //pthread_attr_setschedparam(&thr_attr, &thr_param);

    /* thread: loop task */
    if (pthread_create(&sess->opc.thr_task, &thr_attr, opc_task, (void*) sess)) {
        pthread_attr_destroy(&thr_attr);
        _print(opc, err, "[failed] pthread_create()\n");
        return -1; // errno is already set
    }

    pthread_attr_destroy(&thr_attr);
    return 0;
}

int opc_pub_stop(struct tst_session *sess)
{
    if (!sess)
        return -1;

    if (!sess->opc.running)
        return -1;

    sess->opc.running = false;
    pthread_join(sess->opc.thr_task, NULL);
    opc_cleanup_task(sess);
    return sess->opc.status == UA_STATUSCODE_GOOD ? 0 : -1;
}

void opc_display_update(struct tst_session *sess)
{
    printf("OPCUA: Publisher(%s) Subscriber(%s)\n",
        sess->opc.pub.url_str, sess->opc.sub.url_str);
    /* TODO */
}

static void opc_log(void *ctx, UA_LogLevel level, UA_LogCategory cat,
    const char *msg, va_list args)
{
    msg_type type;

    if (ctx != NULL && (UA_LogLevel)(uintptr_t)ctx > level)
        return;

    if (level < UA_LOGLEVEL_WARNING)
        type = info;
    else if (level > UA_LOGLEVEL_WARNING)
        type = err;
    else
        type = warn;

#if UA_MULTITHREADING >= 200
    pthread_mutex_lock(&printf_mutex);
#endif

    _vprint(opc, type, msg, args);
    printf("\n");
    //fflush(stdout);

#if UA_MULTITHREADING >= 200
    pthread_mutex_unlock(&printf_mutex);
#endif
}

static void opc_info_model_create(struct tst_session *sess)
{
    UA_Variant value;
    UA_ByteString default_bstr_val = UA_BYTESTRING_NULL;

    /**
     * Ping/Pong counters folder
     */
    UA_NodeId f_id;
    UA_ObjectAttributes f_attr = UA_ObjectAttributes_default;
    f_attr.description = UA_LOCALIZEDTEXT("en_US", "Ping/Pong folder");
    f_attr.displayName = UA_LOCALIZEDTEXT("en_US", "Ping/Pong counters");
    UA_Server_addObjectNode(sess->opc.srv,              /* server */
        UA_NODEID_NULL,                                 /* requestedNewNodeId */
        UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),   /* parentNodeId */
        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),       /* referenceTypeId */
        UA_QUALIFIEDNAME(1, "Ping/Pong counters"),      /* browseName */
        UA_NODEID_NULL,                                 /* typeDefinition */
        f_attr,                                         /* attr */
        NULL,                                           /* nodeContext */
        &f_id);                                         /* outNewNodeId */

    /**
     * Ping counter
     */
    struct opc_data *r_data = (struct opc_data *) &sess->opc.sub.data;
    UA_VariableAttributes ping_attr = UA_VariableAttributes_default;
    ping_attr.description = UA_LOCALIZEDTEXT("en_US", "Ping counter");
    ping_attr.displayName = UA_LOCALIZEDTEXT("en-US", r_data->name);
    ping_attr.dataType = UA_TYPES[UA_TYPES_BYTESTRING].typeId;
    ping_attr.valueRank = UA_VALUERANK_SCALAR;
    ping_attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    UA_Variant_setScalar(&ping_attr.value, &default_bstr_val,
        &UA_TYPES[UA_TYPES_BYTESTRING]);
    UA_Server_addVariableNode(sess->opc.srv,
        UA_NODEID_NULL,
        f_id,
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "sub.counter"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        ping_attr,
        NULL,
        &r_data->v_node_id);
    /* + data init */
    UA_Variant_init(&value);
    UA_ByteString_allocBuffer(&r_data->data, OPC_SUB_FIP_DATA_BSZ);
    memset(r_data->data.data, 0, OPC_SUB_FIP_DATA_BSZ);
    UA_Variant_setScalar(&value, &r_data->data, &UA_TYPES[UA_TYPES_BYTESTRING]);
    UA_Server_writeValue(sess->opc.srv, r_data->v_node_id, value);
    /* + realtime data value */
    UA_ByteString_allocBuffer(&r_data->rt_data, OPC_SUB_FIP_DATA_BSZ);
    memset(r_data->rt_data.data, 0, OPC_SUB_FIP_DATA_BSZ);

    r_data->rt_value = UA_DataValue_new();
    UA_Variant_setScalar(&r_data->rt_value->value, &r_data->rt_data,
        &UA_TYPES[UA_TYPES_BYTESTRING]);
    r_data->rt_value->hasValue = UA_TRUE;
    /* set the value backend of the node to 'external value source' */
    UA_ValueBackend be_r_value;
    be_r_value.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
    be_r_value.backend.external.value = &r_data->rt_value;
    be_r_value.backend.external.callback.userWrite = opc_rt_cb_ext_datawrite;
    be_r_value.backend.external.callback.notificationRead = \
        opc_rt_cb_ext_dataread_notif;
    UA_Server_setVariableNode_valueBackend(sess->opc.srv, r_data->v_node_id,
        be_r_value);

    /**
     * Pong counter
     */
    struct opc_data *w_data = (struct opc_data *) &sess->opc.pub.data;
    UA_VariableAttributes pong_attr = UA_VariableAttributes_default;
    pong_attr.description = UA_LOCALIZEDTEXT("en_US", "Pong counter");
    pong_attr.displayName = UA_LOCALIZEDTEXT("en-US", w_data->name);
    pong_attr.dataType = UA_TYPES[UA_TYPES_BYTESTRING].typeId;
    pong_attr.valueRank = UA_VALUERANK_SCALAR;
    pong_attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    UA_Variant_setScalar(&pong_attr.value, &default_bstr_val,
        &UA_TYPES[UA_TYPES_BYTESTRING]);
    UA_Server_addVariableNode(sess->opc.srv,
        UA_NODEID_NULL,
        f_id,
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "pub.counter"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        pong_attr,
        NULL,
        &w_data->v_node_id);
    /* + data init */
    UA_Variant_init(&value);
    UA_ByteString_allocBuffer(&w_data->data, OPC_PUB_FIP_DATA_BSZ);
    memset(w_data->data.data, 0, OPC_PUB_FIP_DATA_BSZ);
    UA_Variant_setScalar(&value, &w_data->data, &UA_TYPES[UA_TYPES_BYTESTRING]);
    UA_Server_writeValue(sess->opc.srv, w_data->v_node_id, value);
    /* + realtime data value */
    UA_ByteString_allocBuffer(&w_data->rt_data, OPC_PUB_FIP_DATA_BSZ);
    memset(w_data->rt_data.data, 0, OPC_PUB_FIP_DATA_BSZ);

    w_data->rt_value = UA_DataValue_new();
    UA_Variant_setScalar(&w_data->rt_value->value, &w_data->rt_data,
        &UA_TYPES[UA_TYPES_BYTESTRING]);
    w_data->rt_value->hasValue = UA_TRUE;
    /* set the value backend of the node to 'external value source' */
    UA_ValueBackend be_w_value;
    be_w_value.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
    be_w_value.backend.external.value = &w_data->rt_value;
    be_w_value.backend.external.callback.userWrite = opc_rt_cb_ext_datawrite;
    be_w_value.backend.external.callback.notificationRead = \
        opc_rt_cb_ext_dataread_notif;
    UA_Server_setVariableNode_valueBackend(sess->opc.srv, w_data->v_node_id,
        be_w_value);
}

static void opc_pubsub_co_add(struct tst_session *sess)
{
    /**
     * Add Publisher Connection
     */

#ifdef UA_ENABLE_PUBSUB_ETH_UADP
    /* accept eth transport layer for publisher */
    UA_ServerConfig_addPubSubTransportLayer(sess->opc.cfg,
        UA_PubSubTransportLayerEthernet());
#else
    /* accept udp transport layer for publisher */
    UA_ServerConfig_addPubSubTransportLayer(sess->opc.cfg,
        UA_PubSubTransportLayerUDPMP());
#endif

    UA_PubSubConnectionConfig pub_co_cfg;
    memset(&pub_co_cfg, 0, sizeof(pub_co_cfg));
    /* connection name */
    pub_co_cfg.name = UA_STRING(OPC_PUB_NAME);
    /* transport profile */
    pub_co_cfg.transportProfileUri = UA_STRING(OPC_TRANSPORT_PROFILE);
    pub_co_cfg.enabled = UA_TRUE;
    /* network address url -
     * ex: opc.udp://224.0.0.22:4840 */
    sess->opc.pub.url.networkInterface = UA_STRING(sess->opc.pub.net_str);
    sess->opc.pub.url.url = UA_STRING(sess->opc.pub.url_str);
    UA_Variant_setScalar(&pub_co_cfg.address, &sess->opc.pub.url,
        &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
    /* publisher identifier - ex: 1234 */
    pub_co_cfg.publisherId.numeric = sess->opc.pub.publisher_id;

#ifdef UA_ENABLE_PUBSUB_ETH_UADP
    /* connection options are given as key/value pairs */
    UA_KeyValuePair co_opts[2];
    UA_Int32 socket_prio = OPC_RT_SOCKET_PRIORITY;
    UA_Boolean so_tx_time_cap = UA_TRUE;
    /* ETF configuration settings */
    co_opts[0].key = UA_QUALIFIEDNAME(0, "sockpriority");
    UA_Variant_setScalar(&co_opts[0].value, &socket_prio,
        &UA_TYPES[UA_TYPES_UINT32]);
    co_opts[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime");
    UA_Variant_setScalar(&co_opts[1].value, &so_tx_time_cap,
        &UA_TYPES[UA_TYPES_BOOLEAN]);
    pub_co_cfg.connectionProperties = co_opts;
    pub_co_cfg.connectionPropertiesSize = 2;
#endif
    /* add connection */
    UA_Server_addPubSubConnection(sess->opc.srv, &pub_co_cfg,
        &sess->opc.pub.co_ident);

    /**
     * Add Subscriber Connection
     */

#ifdef UA_ENABLE_PUBSUB_ETH_UADP
    /* accept eth transport layer for publisher */
    UA_ServerConfig_addPubSubTransportLayer(sess->opc.cfg,
        UA_PubSubTransportLayerEthernet());
#else
    /* accept udp transport layer for subscriber */
    UA_ServerConfig_addPubSubTransportLayer(sess->opc.cfg,
        UA_PubSubTransportLayerUDPMP());
#endif

    UA_PubSubConnectionConfig sub_co_cfg;
    memset(&sub_co_cfg, 0, sizeof(sub_co_cfg));
    /* connection name */
    sub_co_cfg.name = UA_STRING(OPC_SUB_NAME);

#ifdef UA_ENABLE_PUBSUB_ETH_UADP
# ifdef OPC_RX_XDP_ENABLE
    /* connection options are given as key/value pairs */
    UA_KeyValuePair co_opt[4];
    UA_Boolean xdp_cap = UA_TRUE;
    UA_UInt32 flags = XDP_FLAGS_SKB_MODE;
    UA_UInt32 rx_queue = 2;
    UA_UInt32 bind_flags = XDP_COPY;
    /* XDP configuration settings */
    co_opt[0].key = UA_QUALIFIEDNAME(0, "enableXdpSocket");
    UA_Variant_setScalar(&co_opt[0].value, &xdp_cap, &UA_TYPES[UA_TYPES_BOOLEAN]);
    co_opt[1].key = UA_QUALIFIEDNAME(0, "xdpflag");
    UA_Variant_setScalar(&co_opt[1].value, &flags, &UA_TYPES[UA_TYPES_UINT32]);
    co_opt[2].key = UA_QUALIFIEDNAME(0, "hwreceivequeue");
    UA_Variant_setScalar(&co_opt[2].value, &rx_queue, &UA_TYPES[UA_TYPES_UINT32]);
    co_opt[3].key = UA_QUALIFIEDNAME(0, "xdpbindflag");
    UA_Variant_setScalar(&co_opt[3].value, &bind_flags, &UA_TYPES[UA_TYPES_UINT16]);
    sub_co_cfg.connectionProperties = co_opt;
    sub_co_cfg.connectionPropertiesSize = 4;
# endif
#endif

    /* transport profile */
    sub_co_cfg.transportProfileUri = UA_STRING(OPC_TRANSPORT_PROFILE);
    sub_co_cfg.enabled = UA_TRUE;
    /* network address url -
     * ex: opc.udp://224.0.0.22:4840 */
    sess->opc.sub.url.networkInterface = UA_STRING(sess->opc.sub.net_str);
    sess->opc.sub.url.url = UA_STRING(sess->opc.sub.url_str);
    UA_Variant_setScalar(&sub_co_cfg.address, &sess->opc.sub.url,
        &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
    /* publisher identifier - ex: 1234 */
    sub_co_cfg.publisherId.numeric = sess->opc.sub.publisher_id;
    /* add connection */
    UA_Server_addPubSubConnection(sess->opc.srv, &sub_co_cfg,
        &sess->opc.sub.co_ident);
}

static void opc_pub_dataset_n_group_create(struct tst_session *sess)
{
    UA_NodeId w_id;
    UA_NodeId w_ds_id;

    /* => create a new published dataset */
    UA_PublishedDataSetConfig pub_dataset_cfg;
    memset(&pub_dataset_cfg, 0, sizeof(pub_dataset_cfg));
    pub_dataset_cfg.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
    pub_dataset_cfg.name = UA_STRING("PubDataSet");
    UA_Server_addPublishedDataSet(sess->opc.srv,
        &pub_dataset_cfg, &w_ds_id);

    /* => add variable to the publisher dataset */
    {
        struct opc_data *w_data = &sess->opc.pub.data;
        char alias_str[255];

        /* here we simulate the name of a consumed fip variable */
        memset(alias_str, 0, sizeof(alias_str));
        sprintf(alias_str, "fip.var.cons.%04x.data", w_data->fip_id);

        UA_DataSetFieldConfig dsf_cfg;
        memset(&dsf_cfg, 0, sizeof(dsf_cfg));

        /* note: for realtime cap, the node_id in publishParameters must
         * point to a node with external data source backend defined */
        dsf_cfg.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE;
        //note: test[3]
        //dsf_cfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
        //dsf_cfg.field.variable.rtValueSource.staticValueSource = &w_data->rt_value;
        dsf_cfg.field.variable.publishParameters.publishedVariable = \
            w_data->v_node_id;
        UA_Server_addDataSetField(sess->opc.srv, w_ds_id, &dsf_cfg, NULL);
    }

    /* => create a new writer group */
    UA_UadpWriterGroupMessageDataType *w_group_msg = \
        UA_UadpWriterGroupMessageDataType_new();
    UA_WriterGroupConfig w_group_cfg;
    memset(&w_group_cfg, 0, sizeof(w_group_cfg));
    w_group_cfg.name = UA_STRING(OPC_WRITER_GROUP_NAME);
    w_group_cfg.publishingInterval = \
        ((UA_Double) sess->opc.pub.publish_period_us) / 1000;
    w_group_cfg.enabled = UA_FALSE;
    w_group_cfg.writerGroupId = sess->opc.pub.writer_group_id;
    w_group_cfg.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
    w_group_cfg.rtLevel = UA_PUBSUB_RT_DIRECT_VALUE_ACCESS;
    w_group_cfg.pubsubManagerCallback.addCustomCallback = opc_rt_cb_add;
    w_group_cfg.pubsubManagerCallback.changeCustomCallback = opc_rt_cb_change;
    w_group_cfg.pubsubManagerCallback.removeCustomCallback = opc_rt_cb_remove;
    w_group_cfg.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
    w_group_cfg.messageSettings.content.decoded.type = \
        &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE];
    w_group_cfg.messageSettings.content.decoded.data = w_group_msg;
    w_group_msg->networkMessageContentMask = \
        (UA_UadpNetworkMessageContentMask)
            (UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
        (UA_UadpNetworkMessageContentMask)
            UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
        (UA_UadpNetworkMessageContentMask)
            UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
        (UA_UadpNetworkMessageContentMask)
            UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
    UA_Server_addWriterGroup(sess->opc.srv, sess->opc.pub.co_ident, &w_group_cfg,
        &sess->opc.pub.w_group_id);

    /* => add dataset to the writer group */
    UA_DataSetWriterConfig dataset_w_cfg;
    memset(&dataset_w_cfg, 0, sizeof(dataset_w_cfg));
    dataset_w_cfg.name = UA_STRING(OPC_DATASET_WRITER_NAME);
    dataset_w_cfg.dataSetWriterId = sess->opc.pub.dataset_writer_id;
    dataset_w_cfg.keyFrameCount = 10;
    UA_Server_addDataSetWriter(sess->opc.srv, sess->opc.pub.w_group_id,
        w_ds_id, &dataset_w_cfg, &w_id);

    /* => operational + freeze writer group */
    UA_Server_freezeWriterGroupConfiguration(sess->opc.srv, sess->opc.pub.w_group_id);
    UA_Server_setWriterGroupOperational(sess->opc.srv, sess->opc.pub.w_group_id);
    UA_UadpWriterGroupMessageDataType_delete(w_group_msg);
}

static void opc_sub_dataset_n_group_create(struct tst_session *sess)
{
    UA_NodeId r_id;

    /* => create a new reader group */
    UA_ReaderGroupConfig r_group_cfg;
    memset(&r_group_cfg, 0, sizeof(r_group_cfg));
    r_group_cfg.name = UA_STRING(OPC_READER_GROUP_NAME);
    r_group_cfg.subscribingInterval = \
        ((UA_Double) sess->opc.pub.publish_period_us) / 1000; /* in ms */
#if defined OPC_RX_BLOCKING_SOCKET
    r_group_cfg.enableBlockingSocket = UA_TRUE; /* blocking socket */
    r_group_cfg.timeout = 0;
#else
    /* not a blocking socket mode, so we set the socket timeout to 1/4
     * of the suscribing interval */
    r_group_cfg.enableBlockingSocket = UA_FALSE;
    r_group_cfg.timeout = \
        ((UA_Double) sess->opc.pub.publish_period_us) / 5;
#endif
    r_group_cfg.rtLevel = UA_PUBSUB_RT_DIRECT_VALUE_ACCESS;
    r_group_cfg.pubsubManagerCallback.addCustomCallback = opc_rt_cb_add;
    r_group_cfg.pubsubManagerCallback.changeCustomCallback = opc_rt_cb_change;
    r_group_cfg.pubsubManagerCallback.removeCustomCallback = opc_rt_cb_remove;
    //r_group_cfg.securityParameters = ;
    UA_Server_addReaderGroup(sess->opc.srv, sess->opc.sub.co_ident, &r_group_cfg,
        &sess->opc.sub.r_group_id);

    /* => add dataset to the reader group */
    UA_UadpDataSetReaderMessageDataType *dataset_r_msg = \
        UA_UadpDataSetReaderMessageDataType_new();
    UA_DataSetReaderConfig dataset_r_cfg;
    UA_UInt16 pub_id = sess->opc.sub.publisher_id;
    memset(&dataset_r_cfg, 0, sizeof(dataset_r_cfg));
    dataset_r_cfg.name = UA_STRING(OPC_DATASET_READER_NAME);
    dataset_r_cfg.publisherId.type = &UA_TYPES[UA_TYPES_UINT16];
    dataset_r_cfg.publisherId.data = &pub_id;
    dataset_r_cfg.writerGroupId    = sess->opc.sub.writer_group_id;
    dataset_r_cfg.dataSetWriterId  = sess->opc.sub.dataset_writer_id;
    //dataset_r_cfg.expectedEncoding = UA_PUBSUB_RT_DATA_VALUE;
    dataset_r_cfg.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
    dataset_r_cfg.messageSettings.content.decoded.type = \
        &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE];
    dataset_r_cfg.messageSettings.content.decoded.data = dataset_r_msg;
    dataset_r_msg->networkMessageContentMask = \
        (UA_UadpNetworkMessageContentMask)
            (UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
        (UA_UadpNetworkMessageContentMask)
            UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
        (UA_UadpNetworkMessageContentMask)
            UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
        (UA_UadpNetworkMessageContentMask)
            UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);

    /* note: here, we setting up meta data configuration for dataset reader */
    UA_DataSetMetaDataType *dataset_mdata = &dataset_r_cfg.dataSetMetaData;
    UA_DataSetMetaDataType_init(dataset_mdata);
    dataset_mdata->name = UA_STRING("DataSet 1");
    dataset_mdata->fieldsSize = 1; /* only one var */
    dataset_mdata->fields = \
        (UA_FieldMetaData*) UA_Array_new(dataset_mdata->fieldsSize,
            &UA_TYPES[UA_TYPES_FIELDMETADATA]);
    UA_FieldTargetVariable *tgt_vars = (UA_FieldTargetVariable *)
        UA_calloc(dataset_mdata->fieldsSize, sizeof(UA_FieldTargetVariable));

    /* => add variable to the subscriber dataset */
    {
        struct opc_data *r_data = &sess->opc.sub.data;
        char alias_str[255];

        /* here we simulate the name of a produced fip variable */
        memset(alias_str, 0, sizeof(alias_str));
        sprintf(alias_str, "fip.var.prod.%04x.data", r_data->fip_id);

        /* => meta data configuration for dataset reader */
        UA_FieldMetaData_init(&dataset_mdata->fields[0]);
        UA_NodeId_copy(&UA_TYPES[UA_TYPES_BYTESTRING].typeId,
            &dataset_mdata->fields[0].dataType);
        dataset_mdata->fields[0].builtInType = UA_NS0ID_BYTESTRING;
        dataset_mdata->fields[0].name = UA_STRING(alias_str);
        dataset_mdata->fields[0].valueRank = UA_VALUERANK_SCALAR;
        /* => create target variable to attach to the subscriber */
        UA_FieldTargetDataType_init(&tgt_vars[0].targetVariable);
        tgt_vars[0].targetVariable.attributeId  = UA_ATTRIBUTEID_VALUE;
        tgt_vars[0].targetVariable.targetNodeId = r_data->v_node_id;
    }

    /* finally, here we add dataset to the reader group */
    UA_Server_addDataSetReader(sess->opc.srv, sess->opc.sub.r_group_id,
        &dataset_r_cfg, &r_id);

    /* => add all fip produced variables to the subscriber dataset */
    UA_Server_DataSetReader_createTargetVariables(sess->opc.srv, r_id,
        dataset_mdata->fieldsSize, tgt_vars);

    /* => operational + freeze reader group */
    UA_Server_freezeReaderGroupConfiguration(sess->opc.srv, sess->opc.sub.r_group_id);
    UA_Server_setReaderGroupOperational(sess->opc.srv, sess->opc.sub.r_group_id);

    /* free temporary objects */
    for (UA_Int32 i = 0; i < dataset_mdata->fieldsSize; ++i)
        UA_FieldTargetDataType_clear(&tgt_vars[i].targetVariable);
    UA_free(tgt_vars);
    UA_free(dataset_mdata->fields);
    UA_UadpDataSetReaderMessageDataType_delete(dataset_r_msg);
}

static void opc_cleanup_task(void *arg)
{
    struct tst_session *sess = (struct tst_session *) arg;
    struct opc_data *w_data = &sess->opc.pub.data;
    struct opc_data *r_data = &sess->opc.sub.data;

    {
        /* clear rt_data */
        //UA_ByteString_clear(&w_data->rt_data);
        //UA_ByteString_clear(&r_data->rt_data);

        /* clear rt_value */
        if (w_data->rt_value) {
            UA_free(w_data->rt_value);
            w_data->rt_value = NULL;
        }
        if (r_data->rt_value) {
            UA_free(r_data->rt_value);
            r_data->rt_value = NULL;
        }
        UA_ByteString_delete(&w_data->rt_data);
        UA_ByteString_delete(&r_data->rt_data);
    }

    /* clear data */
    UA_ByteString_clear(&w_data->data);
    UA_ByteString_clear(&r_data->data);

    if (sess->opc.srv)
        UA_Server_delete(sess->opc.srv);

    sess->opc.srv = NULL;
}

static void *opc_task(void *arg)
{
    struct tst_session *sess = (struct tst_session *) arg;

    pthread_cleanup_push(opc_cleanup_task, (void *) sess);

    /* opcua server creation */
    sess->opc.srv = UA_Server_new();
    sess->opc.cfg = UA_Server_getConfig(sess->opc.srv);
    sess->opc.cfg->logger.log = opc_log;
    UA_ServerConfig_setDefault(sess->opc.cfg);

    opc_info_model_create(sess);

    sess->opc.running = true;
    opc_pubsub_co_add(sess);
    /* => output */
    opc_pub_dataset_n_group_create(sess);
    /* => input */
    opc_sub_dataset_n_group_create(sess);

    opc_rt_app_start(sess);

    _print(opc, info, "[ok] publisher  start (%s) \n", sess->opc.pub.url_str);
    _print(opc, info, "[ok] subscriber start (%s) \n", sess->opc.sub.url_str);
    sess->opc.status = UA_Server_run(sess->opc.srv, &sess->opc.running);

    opc_rt_app_stop(sess);

    UA_Server_unfreezeReaderGroupConfiguration(sess->opc.srv,
        sess->opc.sub.r_group_id);

    pthread_cleanup_pop(1);
    return NULL;
}
