/*
 * Created by jiaqiang.ye@lnxall.com
 *
 * OPC UA Server for LNXALL devices
 *
 * 2022/01/07
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "opcua_lnxall.h"
#include "dy_pp.h"
#include "ipc_session.h"
#include "cJSON.h"

oulnx_server_t * oulnx_server_init(UA_Server * uas)
{
    int ret;
    oulnx_server_t * ous;

    ous = NULL;
    if (uas == NULL)
        return ous;

    ous = (oulnx_server_t *) calloc(0x1, sizeof(*ous));
    if (ous == NULL) {
        fputs("Error, system out of memory!\n", stderr);
        fflush(stderr);
        goto err0;
    }

    ous->ua_server = uas;
    ret = get_board_sn(ous->board_sn);
    if (ret < 0) {
        fputs("Error, failed to get board-sn!\n", stderr);
        fflush(stderr);
        goto err0;
    }

    return ous;

err0:
    if (ous != NULL) {
        free(ous);
        ous = NULL;
    }
    return NULL;
}

static void oulnx_prop_free(oulnx_prop_t * props, uint32_t count)
{
    uint32_t idx;
    oulnx_prop_t * prop;

    if (props == NULL)
        return;

    for (idx = 0; idx < count; ++idx) {
        prop = &props[idx];
        prop->prop_cfg = NULL;
        if (prop->prop_path != NULL) {
            free(prop->prop_path);
            prop->prop_path = NULL;
        }
        prop->prop_nid = UA_NODEID_NULL;
    }
    free(props);
}

static void oulnx_dev_free(oulnx_dev_t * pdevnod, uint32_t count)
{
    uint32_t idx;

    if (pdevnod == NULL)
        return;

    for (idx = 0; idx < count; ++idx) {
        oulnx_dev_t * pdev;
        pdev = &pdevnod[idx];

        if (pdev->prop_num > 0) {
            oulnx_prop_free(pdev->props, pdev->prop_num);
            pdev->prop_num = 0;
            pdev->props = NULL;
        }

        pdev->dev_cfg = NULL;
        pdev->prop_tab = NULL;
        if (pdev->dev_path != NULL) {
            free(pdev->dev_path);
            pdev->dev_path = NULL;
        }
    }

    free(pdevnod);
}

static void oulnx_server_free1(oulnx_server_t * ous, int dofree)
{
    if (ous->session != NULL) {
        ipc_session_t * sess;
        sess = (ipc_session_t *) ous->session;
        ipc_session_destroy_and_free(sess);
        ous->session = NULL;
    }

    if (ous->pdev != NULL) {
        oulnx_dev_free(ous->pdev, ous->dev_num);
        ous->dev_num = 0;
        ous->pdev = NULL;
    }

    ous->node_ctab = NULL;
    ous->object_ctab = NULL;
    if (dofree)
        free(ous);
}

void oulnx_server_free(oulnx_server_t * ous)
{
    if (ous == NULL)
        return;
    oulnx_server_free1(ous, 1);
}

static void oulnx_cfg_file(char * outp, size_t outlen,
    const char * pdir, const char * cfgfile)
{
    int ret;
    ret = snprintf(outp, outlen, "%s/%s", pdir, cfgfile);
    if (ret >= (int) outlen)
        ret = (int) (outlen - 1);
    if (ret >= 0)
        outp[ret] = '\0';
}

static int oulnx_create_folder(UA_Server * uas,
    const char * name, const char * desc,
    const UA_NodeId * parent, UA_NodeId * fNodeId)
{
    char * fname;
    UA_StatusCode rval;
    UA_ObjectAttributes obj_attr;

    if (name == NULL || name[0] == '\0')
        name = "lnxall-dir";
    fname = (char *) name;

    obj_attr = UA_ObjectAttributes_default;
    if (desc && desc[0])
        obj_attr.description = UA_LOCALIZEDTEXT("en-US", (char *) desc);
    obj_attr.displayName = UA_LOCALIZEDTEXT("en-US", fname);

    if (UA_NodeId_isNull(fNodeId)) {
        rval = UA_Server_addObjectNode(uas, UA_NODEID_NULL, *parent,
            UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
            UA_QUALIFIEDNAME(1, fname),
            UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE),
            obj_attr, NULL, fNodeId);
    } else {
        rval = UA_Server_addObjectNode(uas, *fNodeId, *parent,
            UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
            UA_QUALIFIEDNAME(1, fname),
            UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE),
            obj_attr, NULL, NULL);
    }

    if (rval != UA_STATUSCODE_GOOD) {
        fprintf(stderr, "Error, failed to create folder '%s': %#x, %s\n",
            name, rval, UA_StatusCode_name(rval));
        fflush(stderr);
        return -1;
    }
    return 0;
}

static int ulnx_create_double(UA_Server * uas,
    const char * name, UA_Double value,
    const char * desc, const UA_NodeId * parent,
    UA_NodeId * pid, UA_Boolean readonly)
{
    char * dname;
    UA_StatusCode rval;
    UA_VariableAttributes vattr;

    if (name == NULL || name[0] == '\0')
        name = "lnxall-double";
    dname = (char *) name;

    vattr = UA_VariableAttributes_default;
    if (desc && desc[0])
        vattr.description = UA_LOCALIZEDTEXT("en-US", (char *) desc);
    vattr.displayName = UA_LOCALIZEDTEXT("en-US", dname);
    vattr.accessLevel = UA_ACCESSLEVELMASK_READ;
    if (!readonly)
        vattr.accessLevel |= UA_ACCESSLEVELMASK_WRITE;
    vattr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
    UA_Variant_setScalar(&vattr.value, &value, &UA_TYPES[UA_TYPES_DOUBLE]);

    rval = UA_Server_addVariableNode(uas, *pid, *parent,
        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
        UA_QUALIFIEDNAME(1, dname),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vattr, NULL, NULL);
    if (rval != UA_STATUSCODE_GOOD) {
        fprintf(stderr, "Error, failed to add double [%s => %.02lf]: %#x, %s\n",
            name, value, rval, UA_StatusCode_name(rval));
        fflush(stderr);
        return -1;
    }
    return 0;
}

static int oulnx_create_string(UA_Server * uas,
    const char * key, const char * value, const char * desc,
    const UA_NodeId * parent, UA_NodeId * pid, UA_Boolean readonly)
{
    UA_StatusCode rval;
    UA_String string;
    char * keystr, * valstr;
    UA_VariableAttributes vattr;

    if (key == NULL || key[0] == '\0')
        key = "lnxall-string";
    if (value == NULL)
        value = "";
    keystr = (char *) key;
    valstr = (char *) value;

    string = UA_STRING(valstr);
    vattr = UA_VariableAttributes_default;
    if (desc && desc[0])
        vattr.description = UA_LOCALIZEDTEXT("en-US", (char *) desc);
    vattr.displayName = UA_LOCALIZEDTEXT("en-US", keystr);
    vattr.accessLevel = UA_ACCESSLEVELMASK_READ;
    if (!readonly)
        vattr.accessLevel |= UA_ACCESSLEVELMASK_WRITE;
    vattr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    UA_Variant_setScalar(&vattr.value, &string, &UA_TYPES[UA_TYPES_STRING]);

    rval = UA_Server_addVariableNode(uas, *pid, *parent,
        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
        UA_QUALIFIEDNAME(1, keystr),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vattr, NULL, NULL);

    if (rval != UA_STATUSCODE_GOOD) {
        fprintf(stderr, "Error, failed to add string [%s => %s]: %#x, %s\n",
            key, value, rval, UA_StatusCode_name(rval));
        fflush(stderr);
        return -1;
    }
    return 0;
}

static int oulnx_create_int32(UA_Server * uas,
    const char * name, UA_Int32 value,
    const char * desc, const UA_NodeId * parent,
    UA_NodeId * pid, UA_Boolean readonly)
{
    char * dname;
    UA_StatusCode rval;
    UA_VariableAttributes vattr;

    if (name == NULL || name[0] == '\0')
        name = "lnxall-int32";
    dname = (char *) name;

    vattr = UA_VariableAttributes_default;
    if (desc && desc[0])
        vattr.description = UA_LOCALIZEDTEXT("en-US", (char *) desc);
    vattr.displayName = UA_LOCALIZEDTEXT("en-US", dname);
    vattr.accessLevel = UA_ACCESSLEVELMASK_READ;
    if (!readonly)
        vattr.accessLevel |= UA_ACCESSLEVELMASK_WRITE;
    vattr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
    UA_Variant_setScalar(&vattr.value, &value, &UA_TYPES[UA_TYPES_INT32]);

    rval = UA_Server_addVariableNode(uas, *pid, *parent,
        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
        UA_QUALIFIEDNAME(1, dname),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vattr, NULL, NULL);
    if (rval != UA_STATUSCODE_GOOD) {
        fprintf(stderr, "Error, failed to add int32 [%s => %d]: %#x, %s\n",
            name, value, rval, UA_StatusCode_name(rval));
        fflush(stderr);
        return -1;
    }
    return 0;
}

static int oulnx_create_uint32(UA_Server * uas,
    const char * name, UA_UInt32 value,
    const char * desc, const UA_NodeId * parent,
    UA_NodeId * pid, UA_Boolean readonly)
{
    char * dname;
    UA_StatusCode rval;
    UA_VariableAttributes vattr;

    if (name == NULL || name[0] == '\0')
        name = "lnxall-uint32";
    dname = (char *) name;

    vattr = UA_VariableAttributes_default;
    if (desc && desc[0])
        vattr.description = UA_LOCALIZEDTEXT("en-US", (char *) desc);
    vattr.displayName = UA_LOCALIZEDTEXT("en-US", dname);
    vattr.accessLevel = UA_ACCESSLEVELMASK_READ;
    if (!readonly)
        vattr.accessLevel |= UA_ACCESSLEVELMASK_WRITE;
    vattr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
    UA_Variant_setScalar(&vattr.value, &value, &UA_TYPES[UA_TYPES_UINT32]);

    rval = UA_Server_addVariableNode(uas, *pid, *parent,
        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
        UA_QUALIFIEDNAME(1, dname),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vattr, NULL, NULL);
    if (rval != UA_STATUSCODE_GOOD) {
        fprintf(stderr, "Error, failed to add uint32 [%s => %u]: %#x, %s\n",
            name, value, rval, UA_StatusCode_name(rval));
        fflush(stderr);
        return -1;
    }
    return 0;
}

static int oulnx_create_int64(UA_Server * uas,
    const char * name, UA_Int64 value,
    const char * desc, const UA_NodeId * parent,
    UA_NodeId * pid, UA_Boolean readonly)
{
    char * dname;
    UA_StatusCode rval;
    UA_VariableAttributes vattr;

    if (name == NULL || name[0] == '\0')
        name = "lnxall-int64";
    dname = (char *) name;

    vattr = UA_VariableAttributes_default;
    if (desc && desc[0])
        vattr.description = UA_LOCALIZEDTEXT("en-US", (char *) desc);
    vattr.displayName = UA_LOCALIZEDTEXT("en-US", dname);
    vattr.accessLevel = UA_ACCESSLEVELMASK_READ;
    if (!readonly)
        vattr.accessLevel |= UA_ACCESSLEVELMASK_WRITE;
    vattr.dataType = UA_TYPES[UA_TYPES_INT64].typeId;
    UA_Variant_setScalar(&vattr.value, &value, &UA_TYPES[UA_TYPES_INT64]);

    rval = UA_Server_addVariableNode(uas, *pid, *parent,
        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
        UA_QUALIFIEDNAME(1, dname),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vattr, NULL, NULL);
    if (rval != UA_STATUSCODE_GOOD) {
        fprintf(stderr, "Error, failed to add int64 [%s => %lld]: %#x, %s\n",
            name, (long long) value, rval, UA_StatusCode_name(rval));
        fflush(stderr);
        return -1;
    }
    return 0;
}

static int oulnx_create_uint64(UA_Server * uas,
    const char * name, UA_UInt64 value,
    const char * desc, const UA_NodeId * parent,
    UA_NodeId * pid, UA_Boolean readonly)
{
    char * dname;
    UA_StatusCode rval;
    UA_VariableAttributes vattr;

    if (name == NULL || name[0] == '\0')
        name = "lnxall-uint64";
    dname = (char *) name;

    vattr = UA_VariableAttributes_default;
    if (desc && desc[0])
        vattr.description = UA_LOCALIZEDTEXT("en-US", (char *) desc);
    vattr.displayName = UA_LOCALIZEDTEXT("en-US", dname);
    vattr.accessLevel = UA_ACCESSLEVELMASK_READ;
    if (!readonly)
        vattr.accessLevel |= UA_ACCESSLEVELMASK_WRITE;
    vattr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId;
    UA_Variant_setScalar(&vattr.value, &value, &UA_TYPES[UA_TYPES_UINT64]);

    rval = UA_Server_addVariableNode(uas, *pid, *parent,
        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
        UA_QUALIFIEDNAME(1, dname),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vattr, NULL, NULL);
    if (rval != UA_STATUSCODE_GOOD) {
        fprintf(stderr, "Error, failed to add uint64 [%s => %llu]: %#x, %s\n",
            name, (unsigned long long) value, rval, UA_StatusCode_name(rval));
        fflush(stderr);
        return -1;
    }
    return 0;
}

static void oulnx_write_property(oulnx_server_t * ous,
    cJSON * json, oulnx_prop_t * prop)
{
    int typerror;
    UA_Variant myvar;
    UA_StatusCode rc;
    object_property_cfg_t * pcfg;

    /* local values that have the same scope as `myvar */
    UA_Int32 myval_int32 = 0;
    UA_UInt32 myval_uint32 = 0;
    UA_Int64 myval_int64 = 0;
    UA_UInt64 myval_uint64 = 0;
    UA_Double myval_double = 0;
    UA_String myval_string = UA_STRING("EMPTY");

    typerror = 0;
    UA_Variant_init(&myvar);
    pcfg = (object_property_cfg_t *) prop->prop_cfg;

    switch (pcfg->raw_data_type) {
    case TAG_TYPE_INT8:
    case TAG_TYPE_INT16:
    case TAG_TYPE_INT32:
    case TAG_TYPE_INT16I:
    case TAG_TYPE_INT32I: {
        if (!cJSON_IsNumber(json)) {
            typerror = 1;
            break;
        }
        myval_int32 = (UA_Int32) json->valueint;
        UA_Variant_setScalar(&myvar, &myval_int32, &UA_TYPES[UA_TYPES_INT32]);
        break;
    }

    case TAG_TYPE_UINT8:
    case TAG_TYPE_UINT16:
    case TAG_TYPE_UINT32:
    case TAG_TYPE_UINT16I:
    case TAG_TYPE_UINT32I: {
        if (!cJSON_IsNumber(json)) {
            typerror = 1;
            break;
        }
        myval_uint32 = (UA_UInt32) json->valuedouble;
        UA_Variant_setScalar(&myvar, &myval_uint32, &UA_TYPES[UA_TYPES_UINT32]);
        break;
    }

    case TAG_TYPE_INT64: {
        if (!cJSON_IsNumber(json)) {
            typerror = 1;
            break;
        }
        myval_int64 = (UA_Int64) json->valuedouble;
        UA_Variant_setScalar(&myvar, &myval_int64, &UA_TYPES[UA_TYPES_INT64]);
        break;
    }

    case TAG_TYPE_UINT64: {
        if (!cJSON_IsNumber(json)) {
            typerror = 1;
            break;
        }
        myval_uint64 = (UA_UInt64) json->valuedouble;
        UA_Variant_setScalar(&myvar, &myval_uint64, &UA_TYPES[UA_TYPES_UINT64]);
        break;
    }

    case TAG_TYPE_FLOAT:
    case TAG_TYPE_FLOATI: {
        if (!cJSON_IsNumber(json)) {
            typerror = 1;
            break;
        }
        myval_double = (UA_Double) json->valuedouble;
        UA_Variant_setScalar(&myvar, &myval_double, &UA_TYPES[UA_TYPES_DOUBLE]);
        break;
    }

    case TAG_TYPE_VLENGTH: {
        if (!cJSON_IsString(json)) {
            typerror = 1;
            break;
        }
        if (json->string == NULL) {
            fprintf(stderr, "Error, invalid null string for %s\n",
                pcfg->identifier);
            fflush(stderr);
            break;
        }
        myval_string = UA_STRING(json->valuestring);
        UA_Variant_setScalar(&myvar, &myval_string, &UA_TYPES[UA_TYPES_STRING]);
        break;
    }

    default:
        typerror = 1;
        fprintf(stderr, "Error, invalid property raw-data-type: %#x\n",
            (unsigned int) pcfg->raw_data_type);
        fflush(stderr);
        break;
    }

    if (typerror) {
        fprintf(stderr, "Error, invalid type for property [%s]: %#x\n",
            pcfg->identifier, (unsigned int) json->type);
        fflush(stderr);
        return;
    }

    rc = UA_Server_writeValue(ous->ua_server, prop->prop_nid, myvar);
    if (rc != UA_STATUSCODE_GOOD) {
        fprintf(stderr, "Error, failed to update property %s: %#x, %s\n",
            pcfg->identifier, rc, UA_StatusCode_name(rc));
        fflush(stderr);
    }
}

static object_property_table_t * oulnx_find_proptab(
    object_table_t * objtable, const char * productKey, int * offp)
{
    int idx, count, offs;
    object_cfg_t * objcfg = NULL;

    offs = *offp;
    count = objtable->object_cnt;
    for (idx = offs; idx < count; ++idx) {
        object_cfg_t * cfg;
        cfg = &objtable->object[idx];

        /* compare the productKey with `object_model_id */
        if (strcmp(productKey, cfg->object_model_id) == 0) {
            objcfg = cfg;
            offs = idx + 1;
            break;
        }
    }

    if (objcfg == NULL) {
        for (idx = 0; idx < offs; ++idx) {
            object_cfg_t * cfg;
            cfg = &objtable->object[idx];

            /* compare the productKey with `object_model_id */
            if (strcmp(productKey, cfg->object_model_id) == 0) {
                objcfg = cfg;
                offs = idx + 1;
                break;
            }
        }
    }

    if (objcfg == NULL)
        return NULL; /* not found */
    if (offs >= count)
        offs = 0;
    *offp = offs;
    return objcfg->property_tab;
}

static int opcua_clk(char * clkout, size_t buflen)
{
    int ret;
    struct tm tim;
    struct timespec spec;

    ret = clock_gettime(CLOCK_REALTIME, &spec);
    if (ret == -1) {
        spec.tv_sec = 0;
        spec.tv_nsec = 0;
    }

    memset(&tim, 0, sizeof(tim));
    localtime_r(&spec.tv_sec, &tim);

    snprintf(clkout, buflen, "%02d:%02d:%02d.%03d",
        tim.tm_hour, tim.tm_min, tim.tm_sec, (int) (spec.tv_nsec / 1000000));
    return 0;
}

static int oulnx_create_properties(UA_Server * uas,
    object_property_table_t * proptab, oulnx_dev_t * pdevnod)
{
    int ret, rval;
    uint32_t idx, count;

    ret = 0;
    rval = 0;
    count = proptab->propertyCnt;
    for (idx = 0; idx < count; ++idx) {
        char timstr[32];
        oulnx_prop_t * uaprop;
        object_property_cfg_t * propcfg;

        uaprop = &pdevnod->props[idx];
        propcfg = &proptab->property[idx];

        /* setup `object_property_cfg_t pointer member */
        uaprop->prop_cfg = propcfg;

        /* create UA_NodeId for the property */
        ret = asprintf(&uaprop->prop_path, "%s/%s", pdevnod->dev_path,
            propcfg->identifier);
        if (ret < 0) {
            fprintf(stderr, "Error, failed to construct NodeId for %s\n",
                propcfg->identifier);
            fflush(stderr);
            continue;
        }
        uaprop->prop_nid = UA_NODEID_STRING(1, uaprop->prop_path);

        /* call specific function to create the property value */
        switch (propcfg->raw_data_type) {
        case TAG_TYPE_INT8:
        case TAG_TYPE_INT16:
        case TAG_TYPE_INT32:
        case TAG_TYPE_INT16I:
        case TAG_TYPE_INT32I:
            ret = oulnx_create_int32(uas, propcfg->identifier, 0,
                propcfg->desc, &pdevnod->dev_nid,
                &uaprop->prop_nid, false);
            break;

        case TAG_TYPE_UINT8:
        case TAG_TYPE_UINT16:
        case TAG_TYPE_UINT32:
        case TAG_TYPE_UINT16I:
        case TAG_TYPE_UINT32I:
            ret = oulnx_create_uint32(uas, propcfg->identifier, 0,
                propcfg->desc, &pdevnod->dev_nid,
                &uaprop->prop_nid, false);
            break;

        case TAG_TYPE_INT64:
            ret = oulnx_create_int64(uas, propcfg->identifier, 0,
                propcfg->desc, &pdevnod->dev_nid,
                &uaprop->prop_nid, false);
            break;

        case TAG_TYPE_UINT64:
            ret = oulnx_create_uint64(uas, propcfg->identifier, 0,
                propcfg->desc, &pdevnod->dev_nid,
                &uaprop->prop_nid, false);
            break;

        case TAG_TYPE_FLOAT:
        case TAG_TYPE_FLOATI:
            ret = ulnx_create_double(uas, propcfg->identifier, 0,
                propcfg->desc, &pdevnod->dev_nid,
                &uaprop->prop_nid, false);
            break;

        case TAG_TYPE_VLENGTH:
            ret = oulnx_create_string(uas, propcfg->identifier, "",
                propcfg->desc, &pdevnod->dev_nid,
                &uaprop->prop_nid, false);
            break;

        default:
            ret = 0;
            /* node not created: */
            uaprop->prop_nid = UA_NODEID_NULL;
            fprintf(stderr, "Warning, invalid property raw-data-type: %#x\n",
                (unsigned int) propcfg->raw_data_type);
            fflush(stderr);
            break;
        }

        if (ret < 0) {
            rval = ret;
            break;
        }

        opcua_clk(timstr, sizeof(timstr));
        fprintf(stdout, "[%s] Property added: %s\n",
            timstr, propcfg->identifier);
        fflush(stdout);
    }

    return rval;
}

static int oulnx_server_compose(oulnx_server_t * ous, int pathdir_nid)
{
    int ret;
    int idx, nodcnt, offSet;
    object_table_t * objtab;
    nodes_cfg_table_t * nodtab;
    oulnx_dev_t * noddev = NULL;

    /* object root folder node-id */
    const UA_NodeId root_objid = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);

    objtab = (object_table_t *) ous->object_ctab;
    nodtab = (nodes_cfg_table_t *) ous->node_ctab;

    /*
     * get the number of device nodes,
     * we've checked that is a postive integer
     */
    nodcnt = nodtab->node_cnt;

    /* create an array of OPC UA devices nodes */
    noddev = (oulnx_dev_t *) calloc((size_t) nodcnt,
        sizeof(*noddev));
    if (noddev == NULL) {
        fputs("Error, system out of memory!\n", stderr);
        fflush(stderr);
        return -1;
    }
    ous->dev_num = (uint32_t) nodcnt;
    ous->pdev = noddev;

    offSet = 0;
    /* iterate the device nodes and construct them into OPC UA Server */
    for (idx = 0; idx < nodcnt; ++idx) {
        node_cfg_t * nodcfg;
        oulnx_dev_t * nodev;
        const char * productKey;
        object_property_table_t * proptab;

        nodev = &noddev[idx];
        nodcfg = &nodtab->node[idx];

        /* setup node_cfg_t pointer member */
        nodev->dev_cfg = (void *) nodcfg;

        productKey = nodcfg->product_key;
        /* check whether the node device has a valid product_key */
        if (productKey[0] == '\0') {
            fputs("Error, invalid empty device-node product-key!\n", stderr);
            fflush(stderr);
            continue;
        }

        /* allocate path as UA_NodeId */
        ret = asprintf(&nodev->dev_path, "/%s", nodcfg->sn);
        if (ret < 0) {
            fprintf(stderr, "Error, failed to allocate device-node-path for %s\n",
                nodcfg->sn);
            fflush(stderr);
            continue;
        }
        if (pathdir_nid)
            nodev->dev_nid = UA_NODEID_STRING(1, nodev->dev_path);
        else
            nodev->dev_nid = UA_NODEID_NULL;

        /* create the device node in the OPC UA server */
        ret = oulnx_create_folder(ous->ua_server, nodcfg->sn,
            nodcfg->product_key, &root_objid, &nodev->dev_nid);
        if (ret < 0)
            goto err0;

        /* find corresponding property table from object table */
        proptab = oulnx_find_proptab(objtab, productKey, &offSet);
        if (proptab == NULL) {
            fprintf(stderr, "Error, property table not found for [%s]\n",
                productKey);
            fflush(stderr);
            continue;
        }

        /* setup object_property_table_t pointer member */
        nodev->prop_tab = proptab;

        /* check whether we need to setup the properties */
        if (proptab->propertyCnt > 0) {
            /* allocate property nodes for the device-node */
            nodev->props = (oulnx_prop_t *) calloc(
                (size_t) proptab->propertyCnt, sizeof(oulnx_prop_t));
            if (nodev->props == NULL) {
                fputs("Error, system out of memory!\n", stderr);
                fflush(stderr);
                goto err0;
            }
            nodev->prop_num = proptab->propertyCnt;

            /* populate the OPC UA server with the properties */
            ret = oulnx_create_properties(ous->ua_server, proptab, nodev);
            if (ret < 0)
                goto err0;
        }
    }

    return 0;

err0:
    oulnx_server_free1(ous, 0);
    return -1;
}

static void oulnx_handle_tagnode(oulnx_server_t * ous,
    oulnx_dev_t * devnode, tag_table_t * mtag)
{
    uint32_t offSet;
    uint32_t idx, count;
    cJSON * root = NULL;
    cJSON * node, * next;

    if (mtag->tag_node == NULL ||
        mtag->tag_node[0] == '\0') {
        fprintf(stderr, "Warning, invalid tag_node: %p\n", mtag->tag_node);
        fflush(stderr);
        return;
    }

    if (devnode->prop_num == 0 ||
        devnode->props == NULL) {
        fputs("Warning, empty device node given!\n", stderr);
        fflush(stderr);
        return;
    }

    root = cJSON_Parse(mtag->tag_node);
    if (root == NULL) {
        fprintf(stderr, "Error, failed to parse tag_node: %s\n",
            mtag->tag_node);
        fflush(stderr);
        return;
    }

    if (!cJSON_IsObject(root) || root->child == NULL) {
        fprintf(stderr, "Error, invalid tag_node: %s\n", mtag->tag_node);
        fflush(stderr);
        goto err0;
    }

    offSet = 0;
    next = node = root->child;
    count = devnode->prop_num;
    do {
        const char * item;
        oulnx_prop_t * prop = NULL;
        object_property_cfg_t * pcfg = NULL;

        item = next->string;
        if (item == NULL || item[0] == '\0')
            goto next_item;

        /* find the corresponding property */
        for (idx = offSet; idx < count; ++idx) {
            oulnx_prop_t * prot;
            prot = &devnode->props[idx];
            pcfg = (object_property_cfg_t *) prot->prop_cfg;
            if (pcfg && strcmp(item, pcfg->identifier) == 0) {
                prop = prot;
                offSet = idx + 1;
                break;
            }
        }
        if (prop == NULL) {
            for (idx = 0; idx < offSet; ++idx) {
                oulnx_prop_t * prot;
                prot = &devnode->props[idx];
                pcfg = (object_property_cfg_t *) prot->prop_cfg;
                if (pcfg && strcmp(item, pcfg->identifier) == 0) {
                    prop = prot;
                    offSet = idx + 1;
                    break;
                }
            }
        }

        if (prop == NULL) {
            /* property not found */
            fprintf(stderr, "Error, property not found: %s\n", item);
            fflush(stderr);
            goto next_item;
        }
        oulnx_write_property(ous, next, prop);
        if (offSet >= count)
            offSet = 0;
next_item:
        next = next->next;
    } while (next && next != node);

err0:
    cJSON_Delete(root);
}

static int oulnx_handle_msg(void * obj, ipc_msg_t * msg)
{
    int plen, rval;
    uint32_t idx, count;
    oulnx_server_t * ous;
    cJSON * root = NULL;
    char * payload = NULL;
    tag_table_t * msgtag = NULL;
    oulnx_dev_t * devnod = NULL;

    rval = 0;
    ous = (oulnx_server_t *) obj;
    if (msg == NULL)
        return -1;

    /* check the payload message and length */
    plen = msg->payloadLen;
    payload = msg->payload;
    if (plen <= 0 || payload == NULL) {
        fprintf(stderr, "Error, invalid IPC payload: %p, length: %d\n",
            payload, plen);
        fflush(stderr);
        goto err0;
    }

    /* decode the JSON buffer */
    root = cJSON_Parse(payload);
    if (root == NULL) {
        fprintf(stderr, "Error, failed to parse payload, length: %d\n", plen);
        fflush(stderr);
        goto err0;
    }

    /* allocate tag_table_t object */
    msgtag = (tag_table_t *) calloc(0x1, sizeof(*msgtag));
    if (msgtag == NULL) {
        fputs("Error, system out of memory!\n", stderr);
        fflush(stderr);
        goto err0;
    }

    /* construct tag_table_t structure */
    do {
        char buf[128];
        GET_JSON_VALUE_STRING(root, "sn", msgtag->sn);
        GET_JSON_VALUE_STRING(root, "requester", msgtag->requester);
        GET_JSON_VALUE_STRING(root, "identifier", msgtag->identifier);
        GET_JSON_VALUE_DY_STRING(root, "tag_node", msgtag->tag_node);
        msgtag->tag_len = strlen(msgtag->tag_node);

        GET_JSON_VALUE_INT(root, "mi", msgtag->mi);
        GET_JSON_VALUE_INT(root, "time", msgtag->time);
        GET_JSON_VALUE_INT(root, "report", msgtag->report);
        GET_JSON_VALUE_INT(root, "res", msgtag->res);
        GET_JSON_VALUE_INT(root, "report_period", msgtag->report_period);
        GET_JSON_VALUE_INT(root, "data_type", msgtag->data_type);

        buf[0] = '\0'; buf[1] = '\0';
        GET_JSON_VALUE_STRING(root, "port", buf);
        msgtag->port = port_char2enum(buf);
        GET_JSON_VALUE_STRING(root, "app_key", msgtag->app_key);
        GET_JSON_VALUE_DY_STRING(root, "ext", msgtag->ext);
    } while (0);

    /* find the corresponding device node by msgtag->sn */
    count = ous->dev_num;
    for (idx = 0; idx < count; ++idx) {
        oulnx_dev_t * pdev;
        node_cfg_t * nodecfg;

        pdev = (ous->pdev != NULL) ? &ous->pdev[idx] : NULL;
        nodecfg = (node_cfg_t *) ((pdev != NULL) ? pdev->dev_cfg : NULL);
        if (nodecfg == NULL) {
            /* fatal internal error, practically impossible  */
            fputs("Warning, invalid NULL node_cfg_t found!\n", stderr);
            fflush(stderr);
            continue;
        }

        if (strcmp(msgtag->sn, nodecfg->sn) == 0) {
            devnod = pdev;
            break;
        }
    }

    if (devnod == NULL) {
        fprintf(stderr, "Error, cannot find targeted device with sn: %s\n",
            msgtag->sn);
        fflush(stderr);
        goto err0;
    }

    /* handle the specific tagnode message */
    oulnx_handle_tagnode(ous, devnod, msgtag);

#if 0
    fprintf(stdout, "Received a message, obj: %p\n", ous);
    if (msg != NULL) {
        const char * empty = "";
        fprintf(stdout, "Received topic: %s\n", msg->topic ? : empty);
        fprintf(stdout, "Payload length: %d\n", msg->payloadLen);
        if (msg->payloadLen > 0) {
            fprintf(stdout, "Payload content: \n%s\n", msg->payload ? : empty);
        }
    }
    fflush(stdout);
#endif

err0:
    if (msgtag != NULL) {
        if (msgtag->tag_node != NULL) {
            free(msgtag->tag_node);
            msgtag->tag_node = NULL;
        }
        if (msgtag->ext != NULL) {
            free(msgtag->ext);
            msgtag->ext = NULL;
        }
        free(msgtag);
        msgtag = NULL;
    }
    if (root != NULL) {
        cJSON_Delete(root);
        root = NULL;
    }
    return rval;
}

/*
 * find nodes_cfg.json for OPC UA Server username/password
 */
static void oulnx_server_auth_find(oulnx_server_t * ous,
    nodes_cfg_table_t * ncfg, UA_ServerConfig * ua_scfg)
{
    int found;
    int idx, count;
    node_cfg_t * cfgp;

    found = 0;
    count = ncfg->node_cnt;
    for (idx = 0; idx < count; ++idx) {
        cJSON * extj;
        cJSON * opcua_svr;
        cJSON * opcua_pwd;
        cfgp = &ncfg->node[idx];
        if (cfgp->ext_data == NULL ||
            cfgp->ext_data[0] == '\0')
            continue;
        extj = cJSON_Parse(cfgp->ext_data);
        if (cJSON_IsObject(extj) == 0)
            goto next;

        opcua_svr = cJSON_GetObjectItem(extj, "opcua_server_name");
        opcua_pwd = cJSON_GetObjectItem(extj, "opcua_server_pass");
        if (cJSON_IsString(opcua_svr) && cJSON_IsString(opcua_pwd)) {
            found = 1;
            idx = count; /* force break out of loop */
            strncpy(ous->userName, opcua_svr->valuestring, OPCUA_USERPW_MAX - 1);
            strncpy(ous->userPass, opcua_pwd->valuestring, OPCUA_USERPW_MAX - 1);
        }
next:
        if (extj != NULL)
            cJSON_Delete(extj);
    }

    if (found) {
        UA_StatusCode usc;
        UA_UsernamePasswordLogin * pwd;

        pwd = &ous->pw_login;
        pwd->username = UA_STRING(ous->userName);
        pwd->password = UA_STRING(ous->userPass);
        ua_scfg->accessControl.clear(&ua_scfg->accessControl);
        usc = UA_AccessControl_default(ua_scfg, false,NULL,
            &ua_scfg->securityPolicies[ua_scfg->securityPoliciesSize-1].policyUri,
            0x1, pwd);
        if (usc != UA_STATUSCODE_GOOD) {
            fprintf(stderr, "Error, failed to Add OPCUA Server user/passwd: %#x\n",
                (unsigned int) usc);
            fflush(stderr);
        } else {
            fprintf(stdout, "NOTE: Anonymous access to OPCUA server not allowed\n");
            fflush(stdout);
        }
    } else {
        fprintf(stdout, "NOTE: Anonymous access to OPCUA server allowed\n");
        fflush(stdout);
    }
}

int oulnx_server_load(oulnx_server_t * ous,
    const char * pdir, UA_ServerConfig * ua_scfg)
{
    int ret;
    char tmpstr[128];
    int use_path_nid;
    ipc_session_t * ipcs = NULL;
    object_table_t * objtab = NULL;
    nodes_cfg_table_t * nodtab = NULL;

    use_path_nid = 1;
    if (ous == NULL)
        return -1;
    do {
        const char * use_path;
        use_path = getenv("OPCUA_NOUSE_PATH_NID");
        if (use_path && use_path[0] == '1')
            use_path_nid = 0;
    } while (0);

    /* determine configuration files directory */
    if (pdir == NULL || pdir[0] == '\0')
        pdir = "/app/node";

    /* load nodes_cfg.json file */
    oulnx_cfg_file(tmpstr, sizeof(tmpstr),
        pdir, "nodes_cfg.json");
    ret = load_nodes_cfg(&nodtab, tmpstr);
    if (ret < 0) {
        fprintf(stderr, "Error, failed to load '%s': %d\n",
            tmpstr, ret);
        fflush(stderr);
        goto err0;
    }

    /* check the number of devices in the node */
    if (nodtab->node_cnt <= 0) {
        fprintf(stderr, "Error, invalid number of device node: %d\n",
            nodtab->node_cnt);
        fflush(stderr);
        goto err0;
    }

    /* load object model_cfg.json file */
    oulnx_cfg_file(tmpstr, sizeof(tmpstr),
        pdir, "object_model_cfg.json");
    ret = load_objects_cfg(&objtab, tmpstr);
    if (ret < 0) {
        fprintf(stderr, "Error, failed to load '%s': %d\n",
            tmpstr, ret);
        fflush(stderr);
        goto err0;
    }

    ous->node_ctab = (void *) nodtab;
    ous->object_ctab = (void *) objtab;
    oulnx_server_auth_find(ous, nodtab, ua_scfg);
    ret = oulnx_server_compose(ous, use_path_nid);
    if (ret < 0)
        goto err0;

    /* create IPC session for MQTT */
    snprintf(tmpstr, sizeof(tmpstr) - 0x1, "OPCUASvr_%s", ous->board_sn);
    ipcs = ipc_session_new(tmpstr, ous, IPC_MQTT);
    if (ipcs == NULL) {
        fputs("Error, failed to create IPC session!\n", stderr);
        fflush(stderr);
        goto err0;
    }
    ous->session = ipcs;

    /* register MQTT message callback function */
    ret = ipc_session_set_callbacks(ipcs, oulnx_handle_msg, NULL);
    if (ret < 0) {
        fputs("Error, failed to register IPC callback!\n", stderr);
        fflush(stderr);
        goto err0;
    }

    /* we are only interested in `data/property topics */
    ret = ipc_session_subscribe(ipcs, "ipc/+/+/device/+/data/property/+");
    if (ret < 0) {
        fputs("Error, failed to subscribe concerned topic!\n", stderr);
        fflush(stderr);
        goto err0;
    }

    ret = ipc_session_start(ipcs);
    if (ret < 0) {
        fputs("Error, failed to start IPC session!\n", stderr);
        fflush(stderr);
        goto err0;
    }
    return 0;

err0:
    oulnx_server_free1(ous, 0);
    return -1;
}
