text: /**
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  // OPC includes
  #include "opc.h"
  
  // MiNiFi includes
  #include "utils/StringUtils.h"
  #include "logging/Logger.h"
  #include "Exception.h"
  
  #include "utils/gsl.h"
  
  // Standard includes
  #include <stdlib.h>
  #include <iostream>
  #include <memory>
  #include <vector>
  #include <string>
  #include <functional>
  
  namespace org {
  namespace apache {
  namespace nifi {
  namespace minifi {
  namespace opc {
  
  /*
   * The following functions are only used internally in OPC lib, not to be exported
   */
  
  namespace {
  
    void add_value_to_variant(UA_Variant *variant, std::string &value) {
      UA_String ua_value = UA_STRING(&value[0]);
      UA_Variant_setScalarCopy(variant, &ua_value, &UA_TYPES[UA_TYPES_STRING]);
    }
  
    void add_value_to_variant(UA_Variant *variant, const char *value) {
      std::string strvalue(value);
      add_value_to_variant(variant, strvalue);
    }
  
    void add_value_to_variant(UA_Variant *variant, int64_t value) {
      UA_Int64 ua_value = value;
      UA_Variant_setScalarCopy(variant, &ua_value, &UA_TYPES[UA_TYPES_INT64]);
    }
  
    void add_value_to_variant(UA_Variant *variant, uint64_t value) {
      UA_UInt64 ua_value = value;
      UA_Variant_setScalarCopy(variant, &ua_value, &UA_TYPES[UA_TYPES_UINT64]);
    }
  
    void add_value_to_variant(UA_Variant *variant, int32_t value) {
      UA_Int32 ua_value = value;
      UA_Variant_setScalarCopy(variant, &ua_value, &UA_TYPES[UA_TYPES_INT32]);
    }
  
    void add_value_to_variant(UA_Variant *variant, uint32_t value) {
      UA_UInt32 ua_value = value;
      UA_Variant_setScalarCopy(variant, &ua_value, &UA_TYPES[UA_TYPES_UINT32]);
    }
  
    void add_value_to_variant(UA_Variant *variant, bool value) {
      UA_Boolean ua_value = value;
      UA_Variant_setScalarCopy(variant, &ua_value, &UA_TYPES[UA_TYPES_BOOLEAN]);
    }
  
    void add_value_to_variant(UA_Variant *variant, float value) {
      UA_Float ua_value = value;
      UA_Variant_setScalarCopy(variant, &ua_value, &UA_TYPES[UA_TYPES_FLOAT]);
    }
  
    void add_value_to_variant(UA_Variant *variant, double value) {
      UA_Double ua_value = value;
      UA_Variant_setScalarCopy(variant, &ua_value, &UA_TYPES[UA_TYPES_DOUBLE]);
    }
  
    core::logging::LOG_LEVEL MapOPCLogLevel(UA_LogLevel ualvl) {
      switch (ualvl) {
        case UA_LOGLEVEL_TRACE:
          return core::logging::trace;
        case UA_LOGLEVEL_DEBUG:
          return core::logging::debug;
        case UA_LOGLEVEL_INFO:
          return core::logging::info;
        case UA_LOGLEVEL_WARNING:
          return core::logging::warn;
        case UA_LOGLEVEL_ERROR:
          return core::logging::err;
        case UA_LOGLEVEL_FATAL:
          return core::logging::critical;
        default:
          return core::logging::critical;
      }
    }
  }
  
  /*
   * End of internal functions
   */
  
  Client::Client(std::shared_ptr<core::logging::Logger> logger, const std::string& applicationURI,
                 const std::vector<char>& certBuffer, const std::vector<char>& keyBuffer,
                 const std::vector<std::vector<char>>& trustBuffers) {
  
    client_ = UA_Client_new();
    if (certBuffer.empty()) {
      UA_ClientConfig_setDefault(UA_Client_getConfig(client_));
    } else {
      UA_ClientConfig *cc = UA_Client_getConfig(client_);
      cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
  
      // Certificate
      UA_ByteString certByteString = UA_STRING_NULL;
      certByteString.length = certBuffer.size();
      certByteString.data = (UA_Byte*)UA_malloc(certByteString.length * sizeof(UA_Byte));
      memcpy(certByteString.data, certBuffer.data(), certByteString.length);
  
      // Key
      UA_ByteString keyByteString = UA_STRING_NULL;
      keyByteString.length = keyBuffer.size();
      keyByteString.data = (UA_Byte*)UA_malloc(keyByteString.length * sizeof(UA_Byte));
      memcpy(keyByteString.data, keyBuffer.data(), keyByteString.length);
  
      // Trusted certificates
      UA_STACKARRAY(UA_ByteString, trustList, trustBuffers.size());
      for (size_t i = 0; i < trustBuffers.size(); i++) {
        trustList[i] = UA_STRING_NULL;
        trustList[i].length = trustBuffers[i].size();
        trustList[i].data = (UA_Byte*)UA_malloc(trustList[i].length * sizeof(UA_Byte));
        memcpy(trustList[i].data, trustBuffers[i].data(), trustList[i].length);
      }
      UA_StatusCode sc = UA_ClientConfig_setDefaultEncryption(cc, certByteString, keyByteString,
                                                              trustList, trustBuffers.size(),
                                                              nullptr, 0);
      UA_ByteString_clear(&certByteString);
      UA_ByteString_clear(&keyByteString);
      for (size_t i = 0; i < trustBuffers.size(); i++) {
        UA_ByteString_clear(&trustList[i]);
      }
      if (sc != UA_STATUSCODE_GOOD) {
        logger->log_error("Configuring the client for encryption failed: %s", UA_StatusCode_name(sc));
        UA_Client_delete(client_);
        throw OPCException(GENERAL_EXCEPTION, std::string("Failed to created client with the provided encryption settings: ") + UA_StatusCode_name(sc));
      }
    }
  
    const UA_Logger MinifiUALogger = {logFunc, logger.get(), logClear};
  
    UA_ClientConfig *configPtr = UA_Client_getConfig(client_);
    configPtr->logger = MinifiUALogger;
  
    if(applicationURI.length() > 0) {
      UA_String_clear(&configPtr->clientDescription.applicationUri);
      configPtr->clientDescription.applicationUri = UA_STRING_ALLOC(applicationURI.c_str());
    }
  
    logger_ = logger;
  }
  
  Client::~Client() {
    if(client_ == nullptr) {
      return;
    }
    if(UA_Client_getState(client_) != UA_CLIENTSTATE_DISCONNECTED) {
      auto sc = UA_Client_disconnect(client_);
      if(sc != UA_STATUSCODE_GOOD) {
        logger_->log_warn("Failed to disconnect OPC client: %s", UA_StatusCode_name(sc));
      }
    }
    UA_Client_delete(client_);
  }
  
  bool Client::isConnected() {
    if(!client_) {
      return false;
    }
    return UA_Client_getState(client_) != UA_CLIENTSTATE_DISCONNECTED;
  }
  
  UA_StatusCode Client::connect(const std::string& url, const std::string& username, const std::string& password) {
    if (username.empty()) {
      return UA_Client_connect(client_, url.c_str());
    } else {
      return UA_Client_connect_username(client_, url.c_str(), username.c_str(), password.c_str());
    }
  }
  
  NodeData Client::getNodeData(const UA_ReferenceDescription *ref, const std::string& basePath) {
    if(ref->nodeClass == UA_NODECLASS_VARIABLE) {
      opc::NodeData nodedata;
      std::string browsename(reinterpret_cast<const char*>(ref->browseName.name.data), ref->browseName.name.length);
  
      if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING) {
        std::string nodeidstr(reinterpret_cast<const char*>(ref->nodeId.nodeId.identifier.string.data),
                              ref->nodeId.nodeId.identifier.string.length);
        nodedata.attributes["NodeID"] = nodeidstr;
        nodedata.attributes["NodeID type"] = "string";
      } else if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_BYTESTRING) {
        std::string nodeidstr(reinterpret_cast<const char*>(ref->nodeId.nodeId.identifier.byteString.data), ref->nodeId.nodeId.identifier.byteString.length);
        nodedata.attributes["NodeID"] = nodeidstr;
        nodedata.attributes["NodeID type"] = "bytestring";
      } else if (ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC) {
        nodedata.attributes["NodeID"] = std::to_string(ref->nodeId.nodeId.identifier.numeric);
        nodedata.attributes["NodeID type"] = "numeric";
      }
      nodedata.attributes["Browsename"] = browsename;
      nodedata.attributes["Full path"] = basePath + "/" + browsename;
      nodedata.dataTypeID = UA_TYPES_COUNT;
      UA_Variant* var = UA_Variant_new();
      if(UA_Client_readValueAttribute(client_, ref->nodeId.nodeId, var) == UA_STATUSCODE_GOOD && var->type != NULL && var->data != NULL) {
        // Because the timestamps are eliminated in readValueAttribute for simplification
        // We need to call the inner function UA_Client_Service_read.
        UA_ReadValueId item;
        UA_ReadValueId_init(&item);
        item.nodeId = ref->nodeId.nodeId;
        item.attributeId = UA_ATTRIBUTEID_VALUE;
        UA_ReadRequest request;
        UA_ReadRequest_init(&request);
        request.nodesToRead = &item;
        request.nodesToReadSize = 1;
        // Differ from ua_client_highlevel.c src
        request.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
        UA_ReadResponse response = UA_Client_Service_read(client_, request);
        UA_DataValue *dv = response.results;
        auto server_timestamp = OPCDateTime2String(dv->serverTimestamp);
        auto source_timestamp = OPCDateTime2String(dv->sourceTimestamp);
        nodedata.attributes["Sourcetimestamp"] = source_timestamp;
        UA_ReadResponse_deleteMembers(&response);
  
        nodedata.dataTypeID = var->type->typeIndex;
        nodedata.addVariant(var);
        if(var->type->typeName) {
          nodedata.attributes["Typename"] = std::string(var->type->typeName);
        }
        if(var->type->memSize) {
          nodedata.attributes["Datasize"] = std::to_string(var->type->memSize);
          nodedata.data = std::vector<uint8_t>(var->type->memSize);
          memcpy(nodedata.data.data(), var->data, var->type->memSize);
        }
        return nodedata;
      }
      UA_Variant_delete(var);
      throw OPCException(GENERAL_EXCEPTION, "Failed to read value of node: " + browsename);
    } else {
      throw OPCException(GENERAL_EXCEPTION, "Only variable nodes are supported!");
    }
  }
  
  UA_ReferenceDescription * Client::getNodeReference(UA_NodeId nodeId) {
    UA_ReferenceDescription *ref = UA_ReferenceDescription_new();
    UA_ReferenceDescription_init(ref);
    UA_NodeId_copy(&nodeId, &ref->nodeId.nodeId);
    auto sc = UA_Client_readNodeClassAttribute(client_, nodeId, &ref->nodeClass);
    if (sc == UA_STATUSCODE_GOOD) {
      sc = UA_Client_readBrowseNameAttribute(client_, nodeId, &ref->browseName);
    }
    if (sc == UA_STATUSCODE_GOOD) {
      UA_Client_readDisplayNameAttribute(client_, nodeId, &ref->displayName);
    }
    return ref;
  }
  
  void Client::traverse(UA_NodeId nodeId, std::function<nodeFoundCallBackFunc> cb, const std::string& basePath, uint64_t maxDepth, bool fetchRoot) {
    if (fetchRoot) {
      UA_ReferenceDescription *rootRef = getNodeReference(nodeId);
      if ((rootRef->nodeClass == UA_NODECLASS_VARIABLE || rootRef->nodeClass == UA_NODECLASS_OBJECT) && rootRef->browseName.name.length > 0) {
        cb(*this, rootRef, basePath);
      }
      UA_ReferenceDescription_delete(rootRef);
    }
  
    if(maxDepth != 0) {
      maxDepth--;
      if(maxDepth == 0) {
        return;
      }
    }
    UA_BrowseRequest bReq;
    UA_BrowseRequest_init(&bReq);
    bReq.requestedMaxReferencesPerNode = 0;
    bReq.nodesToBrowse = UA_BrowseDescription_new();
    bReq.nodesToBrowseSize = 1;
  
    UA_NodeId_copy(&nodeId, &bReq.nodesToBrowse[0].nodeId);
    bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL;
  
    UA_BrowseResponse bResp = UA_Client_Service_browse(client_, bReq);
  
    const auto guard = gsl::finally([&bResp]() {
      UA_BrowseResponse_deleteMembers(&bResp);
    });
  
    UA_BrowseRequest_deleteMembers(&bReq);
  
    for(size_t i = 0; i < bResp.resultsSize; ++i) {
      for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
        UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
        if (cb(*this, ref, basePath)) {
          if (ref->nodeClass == UA_NODECLASS_VARIABLE || ref->nodeClass == UA_NODECLASS_OBJECT) {
            std::string browsename((char *) ref->browseName.name.data, ref->browseName.name.length);
            traverse(ref->nodeId.nodeId, cb, basePath + browsename, maxDepth, false);
          }
        } else {
          return;
        }
      }
    }
  };
  
  bool Client::exists(UA_NodeId nodeId) {
    bool retval = false;
    auto callback = [&retval](Client& client, const UA_ReferenceDescription *ref, const std::string& pat) -> bool {
      retval = true;
      return false;  // If any node is found, the given node exists, so traverse can be stopped
    };
    traverse(nodeId, callback, "", 1);
    return retval;
  };
  
  UA_StatusCode Client::translateBrowsePathsToNodeIdsRequest(const std::string& path, std::vector<UA_NodeId>& foundNodeIDs, const std::shared_ptr<core::logging::Logger>& logger) {
    logger->log_trace("Trying to find node id for %s", path.c_str());
  
    auto tokens = utils::StringUtils::split(path, "/");
    std::vector<UA_UInt32> ids;
    for(size_t i = 0; i < tokens.size(); ++i) {
      UA_UInt32 val = (i ==0) ? UA_NS0ID_ORGANIZES : UA_NS0ID_HASCOMPONENT;
      ids.push_back(val);
    }
  
    UA_BrowsePath browsePath;
    UA_BrowsePath_init(&browsePath);
    browsePath.startingNode = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
  
    browsePath.relativePath.elements = (UA_RelativePathElement*)UA_Array_new(tokens.size(), &UA_TYPES[UA_TYPES_RELATIVEPATHELEMENT]);
    browsePath.relativePath.elementsSize = tokens.size();
  
    for(size_t i = 0; i < tokens.size(); ++i) {
      UA_RelativePathElement *elem = &browsePath.relativePath.elements[i];
      elem->referenceTypeId = UA_NODEID_NUMERIC(0, ids[i]);
      elem->targetName = UA_QUALIFIEDNAME_ALLOC(0, tokens[i].c_str());
    }
  
    UA_TranslateBrowsePathsToNodeIdsRequest request;
    UA_TranslateBrowsePathsToNodeIdsRequest_init(&request);
    request.browsePaths = &browsePath;
    request.browsePathsSize = 1;
  
    UA_TranslateBrowsePathsToNodeIdsResponse response = UA_Client_Service_translateBrowsePathsToNodeIds(client_, request);
  
    const auto guard = gsl::finally([&browsePath]() {
      UA_BrowsePath_deleteMembers(&browsePath);
    });
  
    if(response.resultsSize < 1) {
      logger->log_warn("No node id in response for %s", path.c_str());
      return UA_STATUSCODE_BADNODATAAVAILABLE;
    }
  
    bool foundData = false;
  
    for(size_t i = 0; i < response.resultsSize; ++i) {
      UA_BrowsePathResult res = response.results[i];
      for(size_t j = 0; j < res.targetsSize; ++j) {
        foundData = true;
        UA_NodeId resultId;
        UA_NodeId_copy(&res.targets[j].targetId.nodeId, &resultId);
        foundNodeIDs.push_back(resultId);
        std::string namespaceUri((char*)res.targets[j].targetId.namespaceUri.data, res.targets[j].targetId.namespaceUri.length);
      }
    }
  
    UA_TranslateBrowsePathsToNodeIdsResponse_deleteMembers(&response);
  
    if(foundData) {
      logger->log_debug("Found %lu nodes for path %s", foundNodeIDs.size(), path.c_str());
      return UA_STATUSCODE_GOOD;
    } else {
      logger->log_warn("No node id found for path %s", path.c_str());
      return UA_STATUSCODE_BADNODATAAVAILABLE;
    }
  }
  
  template<typename T>
  UA_StatusCode Client::add_node(const UA_NodeId parentNodeId, const UA_NodeId targetNodeId, std::string browseName, T value, OPCNodeDataType dt, UA_NodeId *receivedNodeId) {
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    add_value_to_variant(&attr.value, value);
    char local[6] = "en-US";
    attr.displayName = UA_LOCALIZEDTEXT(local, const_cast<char*>(browseName.c_str()));
    UA_StatusCode sc = UA_Client_addVariableNode(client_,
                                                 targetNodeId,
                                                 parentNodeId,
                                                 UA_NODEID_NUMERIC(0, OPCNodeDataTypeToTypeID(dt)),
                                                 UA_QUALIFIEDNAME(1, const_cast<char*>(browseName.c_str())),
                                                 UA_NODEID_NULL,
                                                 attr, receivedNodeId);
    UA_Variant_clear(&attr.value);
    return sc;
  }
  
  template<typename T>
  UA_StatusCode Client::update_node(const UA_NodeId nodeId, T value) {
    UA_Variant *variant = UA_Variant_new();
    add_value_to_variant(variant, value);
    UA_StatusCode sc = UA_Client_writeValueAttribute(client_, nodeId, variant);
    UA_Variant_delete(variant);
    return sc;
  };
  
  std::unique_ptr<Client> Client::createClient(std::shared_ptr<core::logging::Logger> logger, const std::string& applicationURI,
                                               const std::vector<char>& certBuffer, const std::vector<char>& keyBuffer,
                                               const std::vector<std::vector<char>>& trustBuffers) {
    try {
      return ClientPtr(new Client(logger, applicationURI, certBuffer, keyBuffer, trustBuffers));
    } catch (const std::exception& exception) {
      logger->log_error("Failed to create client: %s", exception.what());
    }
    return nullptr;
  }
  
  template UA_StatusCode Client::update_node<int64_t>(const UA_NodeId nodeId, int64_t value);
  template UA_StatusCode Client::update_node<uint64_t>(const UA_NodeId nodeId, uint64_t value);
  template UA_StatusCode Client::update_node<int32_t>(const UA_NodeId nodeId, int32_t value);
  template UA_StatusCode Client::update_node<uint32_t>(const UA_NodeId nodeId, uint32_t value);
  template UA_StatusCode Client::update_node<float>(const UA_NodeId nodeId, float value);
  template UA_StatusCode Client::update_node<double>(const UA_NodeId nodeId, double value);
  template UA_StatusCode Client::update_node<bool>(const UA_NodeId nodeId, bool value);
  template UA_StatusCode Client::update_node<const char *>(const UA_NodeId nodeId, const char * value);
  template UA_StatusCode Client::update_node<std::string>(const UA_NodeId nodeId, std::string value);
  
  template UA_StatusCode Client::add_node<int64_t>(const UA_NodeId parentNodeId, const UA_NodeId targetNodeId, std::string browseName, int64_t value, OPCNodeDataType dt, UA_NodeId *receivedNodeId);
  template UA_StatusCode Client::add_node<uint64_t>(const UA_NodeId parentNodeId, const UA_NodeId targetNodeId, std::string browseName, uint64_t value, OPCNodeDataType dt, UA_NodeId *receivedNodeId);
  template UA_StatusCode Client::add_node<int32_t>(const UA_NodeId parentNodeId, const UA_NodeId targetNodeId, std::string browseName, int32_t value, OPCNodeDataType dt, UA_NodeId *receivedNodeId);
  template UA_StatusCode Client::add_node<uint32_t>(const UA_NodeId parentNodeId, const UA_NodeId targetNodeId, std::string browseName, uint32_t value, OPCNodeDataType dt, UA_NodeId *receivedNodeId);
  template UA_StatusCode Client::add_node<float>(const UA_NodeId parentNodeId, const UA_NodeId targetNodeId, std::string browseName, float value, OPCNodeDataType dt, UA_NodeId *receivedNodeId);
  template UA_StatusCode Client::add_node<double>(const UA_NodeId parentNodeId, const UA_NodeId targetNodeId, std::string browseName, double value, OPCNodeDataType dt, UA_NodeId *receivedNodeId);
  template UA_StatusCode Client::add_node<bool>(const UA_NodeId parentNodeId, const UA_NodeId targetNodeId, std::string browseName, bool value, OPCNodeDataType dt, UA_NodeId *receivedNodeId);
  template UA_StatusCode Client::add_node<const char *>(const UA_NodeId parentNodeId, const UA_NodeId targetNodeId, std::string browseName, const char * value, OPCNodeDataType dt, UA_NodeId *receivedNodeId);
  template UA_StatusCode Client::add_node<std::string>(const UA_NodeId parentNodeId, const UA_NodeId targetNodeId, std::string browseName, std::string value, OPCNodeDataType dt, UA_NodeId *receivedNodeId);
  
  int32_t OPCNodeDataTypeToTypeID(OPCNodeDataType dt) {
    switch(dt) {
      case OPCNodeDataType::Boolean:
        return UA_NS0ID_BOOLEAN;
      case OPCNodeDataType::Int32:
        return UA_NS0ID_INT32;
      case OPCNodeDataType::UInt32:
        return UA_NS0ID_UINT32;
      case OPCNodeDataType::Int64:
        return UA_NS0ID_INT64;
      case OPCNodeDataType::UInt64:
        return UA_NS0ID_UINT64;
      case OPCNodeDataType::Float:
        return UA_NS0ID_FLOAT;
      case OPCNodeDataType::Double:
        return UA_NS0ID_DOUBLE;
      case OPCNodeDataType::String:
        return UA_NS0ID_STRING;
      default:
        throw OPCException(GENERAL_EXCEPTION, "Data type is not supported");
    }
  }
  
  std::string nodeValue2String(const NodeData& nd) {
    std::string ret_val;
    switch (nd.dataTypeID) {
      case UA_TYPES_STRING:
      case UA_TYPES_LOCALIZEDTEXT:
      case UA_TYPES_BYTESTRING: {
        UA_String value = *(UA_String *)(nd.var_->data);
        ret_val = std::string(reinterpret_cast<const char *>(value.data), value.length);
        break;
      }
      case UA_TYPES_BOOLEAN:
        bool b;
        memcpy(&b, nd.data.data(), sizeof(bool));
        ret_val = b ? "True" : "False";
        break;
      case UA_TYPES_SBYTE:
        int8_t i8t;
        memcpy(&i8t, nd.data.data(), sizeof(i8t));
        ret_val = std::to_string(i8t);
        break;
      case UA_TYPES_BYTE:
        uint8_t ui8t;
        memcpy(&ui8t, nd.data.data(), sizeof(ui8t));
        ret_val = std::to_string(ui8t);
        break;
      case UA_TYPES_INT16:
        int16_t i16t;
        memcpy(&i16t, nd.data.data(), sizeof(i16t));
        ret_val = std::to_string(i16t);
        break;
      case UA_TYPES_UINT16:
        uint16_t ui16t;
        memcpy(&ui16t, nd.data.data(), sizeof(ui16t));
        ret_val = std::to_string(ui16t);
        break;
      case UA_TYPES_INT32:
        int32_t i32t;
        memcpy(&i32t, nd.data.data(), sizeof(i32t));
        ret_val = std::to_string(i32t);
        break;
      case UA_TYPES_UINT32:
        uint32_t ui32t;
        memcpy(&ui32t, nd.data.data(), sizeof(ui32t));
        ret_val = std::to_string(ui32t);
        break;
      case UA_TYPES_INT64:
        int64_t i64t;
        memcpy(&i64t, nd.data.data(), sizeof(i64t));
        ret_val = std::to_string(i64t);
        break;
      case UA_TYPES_UINT64:
        uint64_t ui64t;
        memcpy(&ui64t, nd.data.data(), sizeof(ui64t));
        ret_val = std::to_string(ui64t);
        break;
      case UA_TYPES_FLOAT:
        if(sizeof(float) == 4 && std::numeric_limits<float>::is_iec559){
          float f;
          memcpy(&f, nd.data.data(), sizeof(float));
          ret_val = std::to_string(f);
        } else {
          throw OPCException(GENERAL_EXCEPTION, "Float is non-standard on this system, OPC data cannot be extracted!");
        }
        break;
      case UA_TYPES_DOUBLE:
        if(sizeof(double) == 8 && std::numeric_limits<double>::is_iec559){
          double d;
          memcpy(&d, nd.data.data(), sizeof(double));
          ret_val = std::to_string(d);
        } else {
          throw OPCException(GENERAL_EXCEPTION, "Double is non-standard on this system, OPC data cannot be extracted!");
        }
        break;
      case UA_TYPES_DATETIME: {
        UA_DateTime dt;
        memcpy(&dt, nd.data.data(), sizeof(UA_DateTime));
        ret_val = opc::OPCDateTime2String(dt);
        break;
      }
      default:
        throw OPCException(GENERAL_EXCEPTION, "Data type is not supported ");
    }
    return ret_val;
  }
  
  std::string OPCDateTime2String(UA_DateTime raw_date) {
    UA_DateTimeStruct dts = UA_DateTime_toStruct(raw_date);
    std::array<char, 100> charBuf;
  
    int sz = snprintf(charBuf.data(), charBuf.size(), "%02hu-%02hu-%04hu %02hu:%02hu:%02hu.%03hu", dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);
  
    return std::string(charBuf.data(), sz);
  }
  
  void logFunc(void *context, UA_LogLevel level, UA_LogCategory category, const char *msg, va_list args) {
    char buffer[1024];
    vsnprintf(buffer, 1024, msg, args);
    auto loggerPtr = reinterpret_cast<core::logging::BaseLogger*>(context);
    loggerPtr->log_string(MapOPCLogLevel(level), buffer);
  }
  
  } /* namespace opc */
  } /* namespace minifi */
  } /* namespace nifi */
  } /* namespace apache */
  } /* namespace org */
  