/*******************************************************************************
 * Copyright (c) 2015-2019 Florian Froschermeier <florian.froschermeier@tum.de>,
 *               fortiss GmbH
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Florian Froschermeier
 *      - initial integration of the OPC-UA protocol
 *    Stefan Profanter
 *      - refactoring and adaption to new concept
 *    Jose Cabral:
 *      - refactoring to cleaner architecture
 *******************************************************************************/

#include "../../core/devexec.h"
#include "../../core/iec61131_functions.h"
#include "../../core/cominfra/basecommfb.h"
#include "../../core/utils/parameterParser.h"
#include "../../core/utils/string_utils.h"
#include <criticalregion.h>
#include <forte_printer.h>
#include "../../arch/utils/mainparam_utils.h"
#include "opcua_local_handler.h"

#ifndef FORTE_COM_OPC_UA_CUSTOM_HOSTNAME
#include <sockhand.h>
#endif

const char * const COPC_UA_Local_Handler::mEnglishLocaleForNodes = "en-US";
const char * const COPC_UA_Local_Handler::mDefaultDescriptionForVariableNodes = "Digital port of Function Block";

TForteUInt16 gOpcuaServerPort = FORTE_COM_OPC_UA_PORT;

using namespace forte::com_infra;

DEFINE_HANDLER(COPC_UA_Local_Handler);

COPC_UA_Local_Handler::COPC_UA_Local_Handler(CDeviceExecution &paDeviceExecution) :
    COPC_UA_HandlerAbstract(paDeviceExecution), mUaServer(0), mUaServerRunningFlag(UA_FALSE)
#ifdef FORTE_COM_OPC_UA_MULTICAST
# ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
//do nothing
# else //FORTE_COM_OPC_UA_MASTER_BRANCH
, mServerConfig(0)
# endif //FORTE_COM_OPC_UA_MASTER_BRANCH
#endif //FORTE_COM_OPC_UA_MULTICAST
{
}

COPC_UA_Local_Handler::~COPC_UA_Local_Handler() {
  stopServer();

  CCriticalRegion criticalRegion(mNodesReferencesMutex);
  for(CSinglyLinkedList<nodesReferencedByActions *>::Iterator iter = mNodesReferences.begin(); iter != mNodesReferences.end(); ++iter) {
    UA_NodeId_delete(const_cast<UA_NodeId*>((*iter)->mNodeId));
    delete *iter;
  }
  mNodesReferences.clearAll();

#ifdef FORTE_COM_OPC_UA_MULTICAST
  for(CSinglyLinkedList<UA_String*>::Iterator iter = mRegisteredWithLds.begin(); iter != mRegisteredWithLds.end(); ++iter) {
    UA_String_delete(*iter);
  }
#endif //FORTE_COM_OPC_UA_MULTICAST
}

void COPC_UA_Local_Handler::enableHandler(void) {
  startServer();
}

void COPC_UA_Local_Handler::disableHandler(void) {
  COPC_UA_Local_Handler::stopServer();
  end();
}

void COPC_UA_Local_Handler::run() {
  DEVLOG_INFO("[OPC UA LOCAL]: Starting OPC UA Server: opc.tcp://localhost:%d\n", gOpcuaServerPort);

  UA_ServerConfig *uaServerConfig = 0;

  UA_ServerStrings serverStrings;
  generateServerStrings(gOpcuaServerPort, serverStrings);
#ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
  mUaServer = UA_Server_new();
  if(mUaServer) {
    uaServerConfig = UA_Server_getConfig(mUaServer);
    UA_ServerConfig_setMinimal(uaServerConfig, gOpcuaServerPort, 0);
    configureUAServer(gOpcuaServerPort, serverStrings, *uaServerConfig);
#else
  uaServerConfig = UA_ServerConfig_new_minimal(gOpcuaServerPort, 0);
  configureUAServer(gOpcuaServerPort, serverStrings, *uaServerConfig);
  mUaServer = UA_Server_new(uaServerConfig);
  if(mUaServer) {
#endif

    if(initializeNodesets(*mUaServer)) {
#ifdef FORTE_COM_OPC_UA_MULTICAST
# ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
      //do nothing
# else
      mServerConfig = uaServerConfig;
# endif
      UA_Server_setServerOnNetworkCallback(mUaServer, serverOnNetworkCallback, this);
#endif //FORTE_COM_OPC_UA_MULTICAST
      mUaServerRunningFlag = UA_TRUE;
      mServerStarted.inc();
      UA_StatusCode retVal = UA_Server_run(mUaServer, &mUaServerRunningFlag); // server keeps iterating as long as running is true
      if(UA_STATUSCODE_GOOD != retVal) {
        DEVLOG_ERROR("[OPC UA LOCAL]: Server exited. Error: %s\n", UA_StatusCode_name(retVal));
      } else {
        DEVLOG_INFO("[OPC UA LOCAL]: Server successfully stopped\n");
      }
    } else {
      DEVLOG_ERROR("[OPC UA LOCAL]: Couldn't initialize Nodesets\n", gOpcuaServerPort);
    }
    UA_Server_delete(mUaServer);
    mUaServer = 0;
  }
#ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
  //nothing to do in master branch. Config is deleted with the server
#else
  UA_ServerConfig_delete(uaServerConfig);
#endif
  mServerStarted.inc(); //this will avoid locking startServer() for all cases where the starting of server failed
}

void COPC_UA_Local_Handler::startServer() {
  if(!isAlive()) {
    start();
    mServerStarted.waitIndefinitely();
  }
}

void COPC_UA_Local_Handler::stopServer() {
  mUaServerRunningFlag = UA_FALSE;
  end();
}

void COPC_UA_Local_Handler::generateServerStrings(TForteUInt16 paUAServerPort, UA_ServerStrings &paServerStrings) {
  char helperBuffer[scmMaxServerNameLength + 1];
  forte_snprintf(helperBuffer, scmMaxServerNameLength, "forte_%d", paUAServerPort);

  char hostname[scmMaxServerNameLength + 1];
#ifdef FORTE_COM_OPC_UA_CUSTOM_HOSTNAME
  forte_snprintf(hostname, scmMaxServerNameLength, "%s-%s", FORTE_COM_OPC_UA_CUSTOM_HOSTNAME, helperBuffer);
#else
  if(gethostname(hostname, scmMaxServerNameLength) == 0) {
    size_t offset = strlen(hostname);
    size_t nameLen = strlen(helperBuffer);
    if(offset + nameLen + 1 > scmMaxServerNameLength) {
      offset = MAX(scmMaxServerNameLength - nameLen - 1, (size_t) 0);
    }
    forte_snprintf(hostname + offset, scmMaxServerNameLength - offset, "-%s", helperBuffer);
  }
#endif

  forte_snprintf(helperBuffer, scmMaxServerNameLength, "org.eclipse.4diac.%s", hostname);

  paServerStrings.mAppURI = helperBuffer;
  paServerStrings.mHostname = hostname;
}

void COPC_UA_Local_Handler::configureUAServer(TForteUInt16 paUAServerPort, UA_ServerStrings &paServerStrings, UA_ServerConfig &paUaServerConfig) {

  paUaServerConfig.logger = COPC_UA_HandlerAbstract::getLogger();

#ifdef FORTE_COM_OPC_UA_MULTICAST
  paUaServerConfig.applicationDescription.applicationType = UA_APPLICATIONTYPE_DISCOVERYSERVER;
  // hostname will be added by mdns library
# ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
  UA_String_deleteMembers(&paUaServerConfig.discovery.mdns.mdnsServerName);
  paUaServerConfig.discovery.mdns.mdnsServerName = UA_String_fromChars(helperBuffer);
# else //FORTE_COM_OPC_UA_MASTER_BRANCH
  UA_String_deleteMembers(&paUaServerConfig.mdnsServerName);
  paUaServerConfig.mdnsServerName = UA_String_fromChars(helperBuffer);
# endif//FORTE_COM_OPC_UA_MASTER_BRANCH
#endif //FORTE_COM_OPC_UA_MULTICAST

  UA_String customHost = UA_STRING(paServerStrings.mHostname.getValue());
#ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
  UA_ServerConfig_setCustomHostname(&paUaServerConfig, customHost);
#else //FORTE_COM_OPC_UA_MASTER_BRANCH
  UA_ServerConfig_set_customHostname(&paUaServerConfig, customHost);
#endif//FORTE_COM_OPC_UA_MASTER_BRANCH

  // delete pre-initialized values
  UA_LocalizedText_deleteMembers(&paUaServerConfig.applicationDescription.applicationName);
  UA_String_deleteMembers(&paUaServerConfig.applicationDescription.applicationUri);

  paUaServerConfig.applicationDescription.applicationUri = UA_String_fromChars(paServerStrings.mAppURI.getValue());
  paUaServerConfig.applicationDescription.applicationName.locale = UA_STRING_NULL;
  paUaServerConfig.applicationDescription.applicationName.text = UA_String_fromChars(paServerStrings.mHostname.getValue());
  paUaServerConfig.publishingIntervalLimits.min = FORTE_COM_OPC_UA_SERVER_PUB_INTERVAL;

  for(size_t i = 0; i < paUaServerConfig.endpointsSize; i++) {
#ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
    UA_String_deleteMembers(&paUaServerConfig.endpoints[i].server.applicationUri);
    UA_LocalizedText_deleteMembers(&paUaServerConfig.endpoints[i].server.applicationName);
    UA_String_copy(&paUaServerConfig.applicationDescription.applicationUri, &paUaServerConfig.endpoints[i].server.applicationUri);
    UA_LocalizedText_copy(&paUaServerConfig.applicationDescription.applicationName, &paUaServerConfig.endpoints[i].server.applicationName);
#else //FORTE_COM_OPC_UA_MASTER_BRANCH
    UA_String_deleteMembers(&paUaServerConfig.endpoints[i].endpointDescription.server.applicationUri);
    UA_LocalizedText_deleteMembers(&paUaServerConfig.endpoints[i].endpointDescription.server.applicationName);
    UA_String_copy(&paUaServerConfig.applicationDescription.applicationUri, &paUaServerConfig.endpoints[i].endpointDescription.server.applicationUri);
    UA_LocalizedText_copy(&paUaServerConfig.applicationDescription.applicationName, &paUaServerConfig.endpoints[i].endpointDescription.server.applicationName);
#endif //FORTE_COM_OPC_UA_MASTER_BRANCH
  }
}

void COPC_UA_Local_Handler::referencedNodesIncrement(const CSinglyLinkedList<UA_NodeId *> &paNodes, CActionInfo &paActionInfo) {
  CCriticalRegion criticalRegion(mNodesReferencesMutex);
  for(CSinglyLinkedList<UA_NodeId *>::Iterator iterNode = paNodes.begin(); iterNode != paNodes.end(); ++iterNode) {
    bool found = false;
    for(CSinglyLinkedList<nodesReferencedByActions *>::Iterator iterRef = mNodesReferences.begin(); iterRef != mNodesReferences.end(); ++iterRef) {
      if(UA_NodeId_equal((*iterRef)->mNodeId, (*iterNode))) {
        found = true;
        (*iterRef)->mActionsReferencingIt.pushFront(&paActionInfo);
        break;
      }
    }
    if(!found) {
      nodesReferencedByActions *newRef = new nodesReferencedByActions();
      UA_NodeId *newNode = UA_NodeId_new();
      UA_NodeId_copy((*iterNode), newNode);
      newRef->mNodeId = newNode;
      newRef->mActionsReferencingIt = CSinglyLinkedList<CActionInfo*>();
      newRef->mActionsReferencingIt.pushFront(&paActionInfo);
      mNodesReferences.pushFront(newRef);
    }
  }
}

void COPC_UA_Local_Handler::referencedNodesDecrement(const CActionInfo &paActionInfo) {
  CCriticalRegion criticalRegion(mNodesReferencesMutex);
  CSinglyLinkedList<const UA_NodeId *> nodesReferencedByAction;
  getNodesReferencedByAction(paActionInfo, nodesReferencedByAction);

  for(CSinglyLinkedList<const UA_NodeId *>::Iterator iterNode = nodesReferencedByAction.begin(); iterNode != nodesReferencedByAction.end(); ++iterNode) {
    CSinglyLinkedList<nodesReferencedByActions *>::Iterator nodeReferencedToDelete = mNodesReferences.end();
    for(CSinglyLinkedList<nodesReferencedByActions *>::Iterator iterRef = mNodesReferences.begin(); iterRef != mNodesReferences.end(); ++iterRef) {
      if(UA_NodeId_equal((*iterRef)->mNodeId, (*iterNode))) {

        bool stillSomethingThere = true;
        while(stillSomethingThere) {
          CSinglyLinkedList<CActionInfo *>::Iterator itActionToDelete = (*iterRef)->mActionsReferencingIt.end();
          for(CSinglyLinkedList<CActionInfo *>::Iterator itAction = (*iterRef)->mActionsReferencingIt.begin();
              itAction != (*iterRef)->mActionsReferencingIt.end(); ++itAction) {
            if((*itAction) == &paActionInfo) {
              itActionToDelete = itAction;
              break;
            }
          }

          if((*iterRef)->mActionsReferencingIt.end() == itActionToDelete) {
            stillSomethingThere = false;
          } else {
            (*iterRef)->mActionsReferencingIt.erase(*itActionToDelete);
          }
        }

        if((*iterRef)->mActionsReferencingIt.isEmpty()) {
          nodeReferencedToDelete = iterRef;
          if(0 != (*iterRef)->mNodeId->namespaceIndex && mUaServer) {
            UA_Server_deleteNode(mUaServer, *(*iterRef)->mNodeId, UA_TRUE);
          }
        }
        break;
      }
    }
    if(mNodesReferences.end() != nodeReferencedToDelete) {
      nodesReferencedByActions *toDelete = *nodeReferencedToDelete;
      mNodesReferences.erase(toDelete);
      UA_NodeId_delete(const_cast<UA_NodeId*>(toDelete->mNodeId));
      delete toDelete;
    }
  }
}

void COPC_UA_Local_Handler::getNodesReferencedByAction(const CActionInfo &paActionInfo, CSinglyLinkedList<const UA_NodeId *> &paNodes) {
  for(CSinglyLinkedList<nodesReferencedByActions *>::Iterator iterRef = mNodesReferences.begin(); iterRef != mNodesReferences.end(); ++iterRef) {
    for(CSinglyLinkedList<CActionInfo *>::Iterator iterAction = (*iterRef)->mActionsReferencingIt.begin();
        iterAction != (*iterRef)->mActionsReferencingIt.end(); ++iterAction) {
      if((*iterAction) == &paActionInfo) {
        paNodes.pushFront((*iterRef)->mNodeId);
        break;
      }
    }
  }
}

#ifdef FORTE_COM_OPC_UA_MULTICAST

const UA_String* COPC_UA_Local_Handler::getDiscoveryUrl() const {

#ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
  UA_ServerConfig *mServerConfig = UA_Server_getConfig(mUaServer); //change mServerConfig to serverConfig when only master branch is present
#else
  //do nothing
#endif
  if(0 == mServerConfig->networkLayersSize) {
    return 0;
  }
  return &mServerConfig->networkLayers[0].discoveryUrl;
}

void COPC_UA_Local_Handler::serverOnNetworkCallback(const UA_ServerOnNetwork *paServerOnNetwork, UA_Boolean paIsServerAnnounce, UA_Boolean paIsTxtReceived,
    void *paData) { //NOSONAR
  COPC_UA_Local_Handler *handler = static_cast<COPC_UA_Local_Handler*>(paData);

  const UA_String *ownDiscoverUrl = handler->getDiscoveryUrl();

  if(!ownDiscoverUrl || UA_String_equal(&paServerOnNetwork->discoveryUrl, ownDiscoverUrl)) {
    // skip self
    return;
  }

  if(!paIsTxtReceived) {
    return; // we wait until the corresponding TXT record is announced.
  }

  DEVLOG_DEBUG("[OPC UA LOCAL]: mDNS %s '%.*s' with url '%.*s'\n", paIsServerAnnounce ? "announce" : "remove", paServerOnNetwork->serverName.length,
      paServerOnNetwork->serverName.data, paServerOnNetwork->discoveryUrl.length, paServerOnNetwork->discoveryUrl.data);

  // check if server is LDS, and then register
  UA_String ldsStr = UA_String_fromChars("LDS");
  for(unsigned int i = 0; i < paServerOnNetwork->serverCapabilitiesSize; i++) {
    if(UA_String_equal(&paServerOnNetwork->serverCapabilities[i], &ldsStr)) {
      if(paIsServerAnnounce) {
        handler->registerWithLds(&paServerOnNetwork->discoveryUrl);
      } else {
        handler->removeLdsRegister(&paServerOnNetwork->discoveryUrl);
      }
      break;
    }
  }
  UA_String_deleteMembers(&ldsStr);
}

void COPC_UA_Local_Handler::registerWithLds(const UA_String *paDiscoveryUrl) {
  // check if already registered with the given LDS
  for(CSinglyLinkedList<UA_String*>::Iterator iter = mRegisteredWithLds.begin(); iter != mRegisteredWithLds.end(); ++iter) {
    if(UA_String_equal(paDiscoveryUrl, *iter)) {
      return;
    }
  }

  // will be freed when removed from list
  UA_String *discoveryUrlChar = 0;
  UA_String_copy(paDiscoveryUrl, discoveryUrlChar);

  mRegisteredWithLds.pushFront(discoveryUrlChar);
  DEVLOG_INFO("[OPC UA LOCAL]: Registering with LDS '%.*s'\n", paDiscoveryUrl->length, paDiscoveryUrl->data);
  UA_StatusCode retVal = UA_Server_addPeriodicServerRegisterCallback(mUaServer,
#ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
      0,
#else //FORTE_COM_OPC_UA_MASTER_BRANCH
      //nothing here
#endif //FORTE_COM_OPC_UA_MASTER_BRANCH
      reinterpret_cast<const char*>(discoveryUrlChar->data), 10 * 60 * 1000, 500, 0);
  if( UA_STATUSCODE_GOOD != retVal) {
    DEVLOG_ERROR("[OPC UA LOCAL]: Could not register with LDS. Error: %s\n", UA_StatusCode_name(retVal));
  }
}

void COPC_UA_Local_Handler::removeLdsRegister(const UA_String *paDiscoveryUrl) {
  UA_String *toDelete = 0;
  for(CSinglyLinkedList<UA_String*>::Iterator iter = mRegisteredWithLds.begin(); iter != mRegisteredWithLds.end(); ++iter) {
    if(UA_String_equal(paDiscoveryUrl, *iter)) {
      toDelete = *iter;
      break;
    }
  }
  if(toDelete) {
    mRegisteredWithLds.erase(toDelete);
    UA_String_delete(toDelete);
  }
}

#endif //FORTE_COM_OPC_UA_MULTICAST

UA_StatusCode COPC_UA_Local_Handler::initializeAction(CActionInfo &paActionInfo) {
  enableHandler();
  UA_StatusCode retVal = UA_STATUSCODE_BADINTERNALERROR;
  if(mUaServer) { //if the server failed at starting, nothing will be initialized
    // other thread may currently create nodes, thus mutex
    CCriticalRegion criticalRegion(mCreateNodesMutex);
    switch(paActionInfo.getAction()){
      case CActionInfo::eRead:
        retVal = initializeVariable(paActionInfo, false);
        break;
      case CActionInfo::eWrite:
        retVal = initializeVariable(paActionInfo, true);
        break;
      case CActionInfo::eCreateMethod:
        retVal = initializeCreateMethod(paActionInfo);
        break;
      case CActionInfo::eCreateObject:
        retVal = initializeCreateObject(paActionInfo);
        break;
      case CActionInfo::eDeleteObject:
        retVal = initializeDeleteObject(paActionInfo);
        break;
      case CActionInfo::eCallMethod:
      case CActionInfo::eSubscribe:
        DEVLOG_ERROR("[OPC UA LOCAL]: Cannot perform action %s locally. Initialization failed\n", CActionInfo::mActionNames[paActionInfo.getAction()]);
        break;
      default:
        DEVLOG_ERROR("[OPC UA LOCAL]: Unknown action %d to be initialized\n", paActionInfo.getAction());
        break;
    }
  }
  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::executeAction(CActionInfo &paActionInfo) {
  UA_StatusCode retVal = UA_STATUSCODE_BADINTERNALERROR;

  switch(paActionInfo.getAction()){
    case CActionInfo::eWrite:
      retVal = executeWrite(paActionInfo);
      break;
    case CActionInfo::eCreateMethod:
      retVal = executeCreateMethod(paActionInfo);
      break;
    case CActionInfo::eCreateObject:
      retVal = executeCreateObject(paActionInfo);
      break;
    case CActionInfo::eDeleteObject:
      retVal = executeDeleteObject(paActionInfo);
      break;
    default: //eCallMethod, eSubscribe will never reach here since they weren't initialized. eRead is a Subscribe FB
      DEVLOG_ERROR("[OPC UA LOCAL]: Action %d to be executed is unknown or invalid\n", paActionInfo.getAction());
      break;
  }

  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::uninitializeAction(CActionInfo &paActionInfo) {
  UA_StatusCode retVal = UA_STATUSCODE_BADINTERNALERROR;
  switch(paActionInfo.getAction()){
    case CActionInfo::eRead:
    case CActionInfo::eWrite:
    case CActionInfo::eCreateMethod:
    case CActionInfo::eCreateObject:
    case CActionInfo::eDeleteObject:
      referencedNodesDecrement(paActionInfo);
      retVal = UA_STATUSCODE_GOOD;
      break;
    default:
      DEVLOG_ERROR("[OPC UA LOCAL]: Action %d to be uninitialized is unknown or invalid\n", paActionInfo.getAction());
      break;
  }
  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::initializeVariable(CActionInfo &paActionInfo, bool paWrite) {
  UA_StatusCode retVal = UA_STATUSCODE_GOOD;

  size_t indexOfNodePair = 0;
  CSinglyLinkedList<UA_NodeId*> referencedNodes;
  const CIEC_ANY *variables = paWrite ? paActionInfo.getDataToSend() : paActionInfo.getDataToReceive();
  for(CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator itMain = paActionInfo.getNodePairInfo().begin();
      itMain != paActionInfo.getNodePairInfo().end() && UA_STATUSCODE_GOOD == retVal; ++itMain, indexOfNodePair++) {

    CSinglyLinkedList<UA_NodeId*> presentNodes;
    bool isNodePresent = false;
    retVal = getNode(**itMain, presentNodes, &isNodePresent);

    if(UA_STATUSCODE_GOOD == retVal) {
      if(isNodePresent) {
        retVal = handleExistingVariable(paActionInfo, **itMain, variables[indexOfNodePair], indexOfNodePair, paWrite);

        handlePresentNodes(presentNodes, referencedNodes, UA_STATUSCODE_GOOD != retVal);
      } else { //node does not exist
        //presentNodes shouldn't have any allocated NodeId at this point
        retVal = handleNonExistingVariable(paActionInfo, **itMain, variables[indexOfNodePair], indexOfNodePair, referencedNodes, paWrite);
      }
    }
  }

  //we add the references first even if it fails, since some nodes might have been created,
  //and/or some might have been already there, so deleting them will be taken care of by
  //the referencedNodesDecrement function later
  referencedNodesIncrement(referencedNodes, paActionInfo);

  if(UA_STATUSCODE_GOOD != retVal) {
    referencedNodesDecrement(paActionInfo);
  }

  for(CSinglyLinkedList<UA_NodeId*>::Iterator itRerencedNodes = referencedNodes.begin(); itRerencedNodes != referencedNodes.end(); ++itRerencedNodes) {
    UA_NodeId_delete(*itRerencedNodes);
  }
  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::handleExistingVariable(CActionInfo &paActionInfo, CActionInfo::CNodePairInfo &paNodePairInfo, const CIEC_ANY &paVariable,
    size_t paIndexOfNodePair, bool paWrite) {

  UA_NodeId outDataType;
  UA_StatusCode retVal = UA_Server_readDataType(mUaServer, *paNodePairInfo.mNodeId, &outDataType);

  if(UA_STATUSCODE_GOOD == retVal) {
    if(UA_NodeId_equal(&outDataType, &COPC_UA_Helper::getOPCUATypeFromAny(paVariable)->typeId)) {
      if(!paWrite) { //If we are reading a variable, it should be writable from the outside
        retVal = addWritePermission(*paNodePairInfo.mNodeId);
        if(UA_STATUSCODE_GOOD == retVal) {
          void *handle = 0;
          retVal = UA_Server_getNodeContext(mUaServer, *paNodePairInfo.mNodeId, &handle);
          if(UA_STATUSCODE_GOOD == retVal) {
            if(!handle) {
              retVal = registerVariableCallBack(*paNodePairInfo.mNodeId, paActionInfo, paIndexOfNodePair);
            } else {
              DEVLOG_ERROR("[OPC UA LOCAL]: At FB %s RD_%d the node %s has already a FB who is reading from it. Cannot add another one\n",
                paActionInfo.getLayer().getCommFB()->getInstanceName(), paIndexOfNodePair, paNodePairInfo.mBrowsePath.getValue());
              retVal = UA_STATUSCODE_BADUNEXPECTEDERROR;
            }
          } else {
            DEVLOG_ERROR("[OPC UA LOCAL]: At FB %s RD_%d the node %s could not retrieve context. Error: %s\n",
              paActionInfo.getLayer().getCommFB()->getInstanceName(), paIndexOfNodePair, paNodePairInfo.mBrowsePath.getValue(), UA_StatusCode_name(retVal));
          }
        } else {
          DEVLOG_ERROR("[OPC UA LOCAL]: Cannot set write permission of node for port %d. Error: %s\n", paIndexOfNodePair, UA_StatusCode_name(retVal));
        }
      }
    } else {
      DEVLOG_ERROR("[OPC UA LOCAL]: At FB %s index %d there was the type of the existing node doesn't match the new one\n",
        paActionInfo.getLayer().getCommFB()->getInstanceName(), paIndexOfNodePair);
      retVal = UA_STATUSCODE_BADUNEXPECTEDERROR;
    }
  } else {
    DEVLOG_ERROR("[OPC UA LOCAL]: At FB %s index %d there was a problem reading the type of the existing node. Error: %s\n",
      paActionInfo.getLayer().getCommFB()->getInstanceName(), paIndexOfNodePair, UA_StatusCode_name(retVal));
  }

  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::handleNonExistingVariable(CActionInfo &paActionInfo, CActionInfo::CNodePairInfo &paNodePairInfo,
    const CIEC_ANY &paVariable, size_t paIndexOfNodePair, CSinglyLinkedList<UA_NodeId*> &paReferencedNodes, bool paWrite) {

  CIEC_STRING nodeName;
  UA_StatusCode retVal = splitAndCreateFolders(paNodePairInfo.mBrowsePath, nodeName, paReferencedNodes);
  if(UA_STATUSCODE_GOOD == retVal) {
    CCreateVariableInfo variableInformation;
    initializeCreateInfo(nodeName, paNodePairInfo, paReferencedNodes.isEmpty() ? 0 : *(paReferencedNodes.back()), variableInformation);

    variableInformation.mTypeConvert = COPC_UA_Helper::getOPCUATypeFromAny(paVariable);
    variableInformation.mInitData = &paVariable;
    variableInformation.mAllowWrite = !paWrite; // write FB here means that from the outside should not be possible to write and the other way around for read

    retVal = createVariableNode(variableInformation);
    if(UA_STATUSCODE_GOOD == retVal) {
      UA_NodeId *tmp = UA_NodeId_new();
      UA_NodeId_copy(variableInformation.mReturnedNodeId, tmp);
      paReferencedNodes.pushBack(tmp);
      if(!paWrite) {
        retVal = registerVariableCallBack(*variableInformation.mReturnedNodeId, paActionInfo, paIndexOfNodePair);
      }
      if(UA_STATUSCODE_GOOD == retVal) {
        if(!paNodePairInfo.mNodeId) {
          paNodePairInfo.mNodeId = UA_NodeId_new();
          UA_NodeId_copy(variableInformation.mReturnedNodeId, paNodePairInfo.mNodeId);
        }
      }
    }
  }

  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::createVariableNode(CCreateVariableInfo &paCreateVariableInfo) {

  UA_NodeId requestedNodeId;
  if(paCreateVariableInfo.mRequestedNodeId) {
    UA_NodeId_copy(paCreateVariableInfo.mRequestedNodeId, &requestedNodeId);
  } else {
    requestedNodeId = UA_NODEID_NUMERIC(paCreateVariableInfo.mBrowseName->namespaceIndex, 0); //if not provided, let the server generate the NodeId using the namespace from the browsename
  }
  UA_NodeId parentNodeId;
  if(paCreateVariableInfo.mParentNodeId) {
    UA_NodeId_copy(paCreateVariableInfo.mParentNodeId, &parentNodeId);
  } else {
    parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ROOTFOLDER);
    DEVLOG_WARNING(
      "[OPC UA LOCAL]: You are creating a variable %.*s in the /Root folder. This is not a good practice. Try to create them in the /Root/Objects folder\n",
      paCreateVariableInfo.mBrowseName->name.length, reinterpret_cast<const char*>(paCreateVariableInfo.mBrowseName->name.data));
  }

  void *paVarValue = UA_new(paCreateVariableInfo.mTypeConvert);

  UA_init(paVarValue, paCreateVariableInfo.mTypeConvert);
  COPC_UA_Helper::convertToOPCUAType(*paCreateVariableInfo.mInitData, paVarValue);

  // create variable attributes
  UA_VariableAttributes variableAttributes;
  UA_VariableAttributes_init(&variableAttributes);
  variableAttributes.dataType = paCreateVariableInfo.mTypeConvert->typeId;
  variableAttributes.valueRank = -1; // value is a scalar
  variableAttributes.displayName.locale = UA_STRING_ALLOC(mEnglishLocaleForNodes);
  UA_String_copy(&paCreateVariableInfo.mBrowseName->name, &variableAttributes.displayName.text);
  variableAttributes.description = UA_LOCALIZEDTEXT_ALLOC(mEnglishLocaleForNodes, mDefaultDescriptionForVariableNodes);
  variableAttributes.userAccessLevel = UA_ACCESSLEVELMASK_READ;
  if(paCreateVariableInfo.mAllowWrite) {
    variableAttributes.userAccessLevel |= UA_ACCESSLEVELMASK_WRITE;
  }
  variableAttributes.accessLevel = variableAttributes.userAccessLevel;
  UA_Variant_setScalar(&variableAttributes.value, paVarValue, paCreateVariableInfo.mTypeConvert);

  UA_StatusCode retVal = UA_Server_addVariableNode(mUaServer, // server
    requestedNodeId, // requestedNewNodeId
    parentNodeId, // parentNodeId
    UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), // referenceTypeId   Reference to the type definition for the variable node
    *paCreateVariableInfo.mBrowseName, // browseName
    UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), // typeDefinition
    variableAttributes, // Variable attributes
    0, // instantiation callback
    paCreateVariableInfo.mReturnedNodeId); // return Node Id

  if(UA_STATUSCODE_GOOD != retVal) {
    DEVLOG_ERROR("[OPC UA LOCAL]: AddressSpace adding Variable Node %s failed. Error: %s\n", paCreateVariableInfo.mBrowseName->name.data,
      UA_StatusCode_name(retVal));
  }
  UA_NodeId_deleteMembers(&parentNodeId);
  UA_NodeId_deleteMembers(&requestedNodeId);
  UA_VariableAttributes_deleteMembers(&variableAttributes);
  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::updateNodeValue(const UA_NodeId &paNodeId, const CIEC_ANY *paData) {
  UA_Variant *nodeValue = UA_Variant_new();
  UA_Variant_init(nodeValue);

  const UA_DataType* dataType = COPC_UA_Helper::getOPCUATypeFromAny(*paData);
  void *varValue = UA_new(dataType);
  COPC_UA_Helper::convertToOPCUAType(*paData, varValue);
  UA_Variant_setScalarCopy(nodeValue, varValue, dataType);
  UA_StatusCode retVal = UA_Server_writeValue(mUaServer, paNodeId, *nodeValue);
  UA_delete(varValue, dataType);
  UA_Variant_delete(nodeValue);
  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::registerVariableCallBack(const UA_NodeId &paNodeId, CActionInfo &paActionInfo, size_t paPortIndex) {

  UA_StatusCode retVal = UA_Server_setVariableNode_valueCallback(mUaServer, paNodeId, { 0, COPC_UA_Local_Handler::CUA_LocalCallbackFunctions::onWrite });
  if(UA_STATUSCODE_GOOD == retVal) {

    UA_VariableContext_Handle variableContext(paActionInfo, paPortIndex);
    mNodeCallbackHandles.pushFront(variableContext);
    CSinglyLinkedList<UA_VariableContext_Handle>::Iterator contextIterator = mNodeCallbackHandles.begin();

    retVal = UA_Server_setNodeContext(mUaServer, paNodeId, &(*contextIterator));
    if(UA_STATUSCODE_GOOD != retVal) {
      DEVLOG_ERROR("[OPC UA LOCAL]: Could not set callback context for node. Error: %s\n", UA_StatusCode_name(retVal));
      mNodeCallbackHandles.popFront();
      retVal = UA_STATUSCODE_BADUNEXPECTEDERROR;
    }
  } else {
    DEVLOG_ERROR("[OPC UA LOCAL]: Could not set callback function for node. Error: %s\n", UA_StatusCode_name(retVal));
    retVal = UA_STATUSCODE_BADUNEXPECTEDERROR;
  }
  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::addWritePermission(const UA_NodeId &paNodeId) {
  UA_StatusCode retVal = UA_Server_writeAccessLevel(mUaServer, paNodeId, UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE);
  if(UA_STATUSCODE_GOOD != retVal) {
    DEVLOG_WARNING("[OPC UA LOCAL]: Cannot set write permission of node. Error: %s\n", UA_StatusCode_name(retVal));
  }
  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::initializeCreateMethod(CActionInfo &paActionInfo) {

  CSinglyLinkedList<UA_NodeId*> referencedNodes;
  CSinglyLinkedList<UA_NodeId*> presentNodes;

  CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator itMethodNodePairInfo = paActionInfo.getNodePairInfo().begin();

  bool isNodePresent = false;
  UA_StatusCode retVal = getNode(**itMethodNodePairInfo, presentNodes, &isNodePresent);

  if(UA_STATUSCODE_GOOD == retVal) {
    if(isNodePresent) {

      UA_NodeId parent = UA_NODEID_NUMERIC(0, UA_NS0ID_ROOTFOLDER);

      //look for parent object
      for(CSinglyLinkedList<UA_NodeId*>::Iterator itPresentNodes = presentNodes.begin(); itPresentNodes != presentNodes.end();) {
        CSinglyLinkedList<UA_NodeId*>::Iterator currentIterator = itPresentNodes;
        ++itPresentNodes;
        if(itPresentNodes == presentNodes.back()) {
          parent = **currentIterator;
          break;
        }
      }

      retVal = handleExistingMethod(paActionInfo, parent);

      handlePresentNodes(presentNodes, referencedNodes, UA_STATUSCODE_GOOD != retVal);
    } else { //node does not exist
      //presentNodes shouldn't have any allocated NodeId at this point

      CIEC_STRING nodeName;
      retVal = splitAndCreateFolders((*itMethodNodePairInfo)->mBrowsePath, nodeName, referencedNodes);
      if(UA_STATUSCODE_GOOD == retVal) {
        CCreateMethodInfo methodInformation(static_cast<CLocalMethodInfo&>(paActionInfo));
        initializeCreateInfo(nodeName, (**itMethodNodePairInfo), referencedNodes.isEmpty() ? 0 : *(referencedNodes.back()), methodInformation);

        methodInformation.mOutputSize = paActionInfo.getSendSize();
        methodInformation.mInputSize = paActionInfo.getReceiveSize();

        createMethodArguments(paActionInfo, methodInformation);

        retVal = createMethodNode(methodInformation, &(*itMethodNodePairInfo)->mNodeId);
        if(UA_STATUSCODE_GOOD == retVal) {
          UA_NodeId *tmp = UA_NodeId_new();
          UA_NodeId_copy(methodInformation.mReturnedNodeId, tmp);
          referencedNodes.pushBack(tmp);
        }
      }
    }
  }

  //we add the references first even if it fails, since some nodes might have been created,
  //and/or some might have been already there, so deleting them will be taken care of by
  //the referencedNodesDecrement function later
  referencedNodesIncrement(referencedNodes, paActionInfo);

  if(UA_STATUSCODE_GOOD != retVal) {
    referencedNodesDecrement(paActionInfo);
  }

  for(CSinglyLinkedList<UA_NodeId*>::Iterator itRerencedNodes = referencedNodes.begin(); itRerencedNodes != referencedNodes.end(); ++itRerencedNodes) {
    UA_NodeId_delete(*itRerencedNodes);
  }
  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::handleExistingMethod(CActionInfo &paActionInfo, UA_NodeId &paParentNode) {
  CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator it = paActionInfo.getNodePairInfo().begin();
  UA_StatusCode retVal = UA_STATUSCODE_GOOD;

  DEVLOG_INFO("[OPC UA LOCAL]: Adding a callback for an existing method at %s\n", (*it)->mBrowsePath.getValue());

  //check if the method was already referenced by another FB
  for(CSinglyLinkedList<UA_ParentNodeHandler>::Iterator iter = mMethodsContexts.begin(); iter != mMethodsContexts.end(); ++iter) {
    if(UA_NodeId_equal(&paParentNode, (*iter).mParentNodeId) && UA_NodeId_equal((*it)->mNodeId, (*iter).mMethodNodeId)) {
      DEVLOG_ERROR(
        "[OPC UA LOCAL]: The FB %s is trying to reference a local method at %s which has already a FB who is referencing it. Cannot add another one\n",
        paActionInfo.getLayer().getCommFB()->getInstanceName(), (*it)->mBrowsePath.getValue());
      retVal = UA_STATUSCODE_BADINTERNALERROR;
      break;
    }
  }

  //TODO: check types of existing method to this layer

  if(UA_STATUSCODE_GOOD == retVal) {
    retVal = UA_Server_setMethodNode_callback(mUaServer, *(*it)->mNodeId, COPC_UA_Local_Handler::CUA_LocalCallbackFunctions::onServerMethodCall);
    if(UA_STATUSCODE_GOOD == retVal) {
      retVal = UA_Server_setNodeContext(mUaServer, *(*it)->mNodeId, &mMethodsContexts);
      if(UA_STATUSCODE_GOOD == retVal) {
        UA_ParentNodeHandler parentNodeContext(paParentNode, (*it)->mNodeId, static_cast<CLocalMethodInfo&>(paActionInfo));
        mMethodsContexts.pushBack(parentNodeContext);
      } else {
        DEVLOG_ERROR("[OPC UA LOCAL]: Could not set context function for method at %s. Error: %s\n", paActionInfo.getLayer().getCommFB()->getInstanceName(),
          UA_StatusCode_name(retVal));
      }
    } else {
      DEVLOG_ERROR("[OPC UA LOCAL]: Could not set callback function for method at %s. Error: %s\n", paActionInfo.getLayer().getCommFB()->getInstanceName(),
        UA_StatusCode_name(retVal));
    }
  }
  return retVal;
}

void COPC_UA_Local_Handler::createMethodArguments(CActionInfo &paActionInfo, CCreateMethodInfo &paCreateMethodInfo) {
  const SFBInterfaceSpec *interfaceFB = paActionInfo.getLayer().getCommFB()->getFBInterfaceSpec();

  const CIEC_ANY *dataToSend = paActionInfo.getDataToSend();
  CIEC_ANY *dataToReceive = paActionInfo.getDataToReceive();

  paCreateMethodInfo.mOutputArguments = static_cast<UA_Argument *>(UA_Array_new(paCreateMethodInfo.mOutputSize, &UA_TYPES[UA_TYPES_ARGUMENT]));
  paCreateMethodInfo.mInputArguments = static_cast<UA_Argument *>(UA_Array_new(paCreateMethodInfo.mInputSize, &UA_TYPES[UA_TYPES_ARGUMENT]));

  for(size_t i = 0; i < paCreateMethodInfo.mOutputSize + paCreateMethodInfo.mInputSize; i++) {
    UA_Argument *arg;
    if(i < paCreateMethodInfo.mOutputSize) {
      arg = &(paCreateMethodInfo.mOutputArguments)[i];
      UA_Argument_init(arg);
      arg->name = UA_STRING_ALLOC(CStringDictionary::getInstance().get(interfaceFB->m_aunDINames[i + 2])); //we store the names of the SDs/RDs as names for the arguments names. Not so nice. + 2 skips the QI and ID
      arg->dataType = COPC_UA_Helper::getOPCUATypeFromAny(dataToSend[i])->typeId;
    } else {
      arg = &(paCreateMethodInfo.mInputArguments)[i - paCreateMethodInfo.mOutputSize];
      UA_Argument_init(arg);
      arg->name = UA_STRING_ALLOC(CStringDictionary::getInstance().get(interfaceFB->m_aunDONames[i - paCreateMethodInfo.mOutputSize + 2])); // + 2 skips the QO and STATUS
      arg->dataType = COPC_UA_Helper::getOPCUATypeFromAny(dataToReceive[i - paCreateMethodInfo.mOutputSize])->typeId;
    }

    arg->arrayDimensionsSize = 0;
    arg->arrayDimensions = 0;
    arg->description = UA_LOCALIZEDTEXT_ALLOC(mEnglishLocaleForNodes, "Method parameter");
    arg->valueRank = -1;
  }
}

UA_StatusCode COPC_UA_Local_Handler::createMethodNode(CCreateMethodInfo &paCreateMethodInfo, UA_NodeId **paNodeId) {

  UA_NodeId requestedNodeId;
  if(paCreateMethodInfo.mRequestedNodeId) {
    UA_NodeId_copy(paCreateMethodInfo.mRequestedNodeId, &requestedNodeId);
  } else {
    requestedNodeId = UA_NODEID_NUMERIC(paCreateMethodInfo.mBrowseName->namespaceIndex, 0); //if not provided, let the server generate the NodeId using the namespace from the browsename
  }

  UA_NodeId parentNodeId;
  if(paCreateMethodInfo.mParentNodeId) {
    UA_NodeId_copy(paCreateMethodInfo.mParentNodeId, &parentNodeId);
  } else {
    parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ROOTFOLDER);
    DEVLOG_WARNING(
      "[OPC UA LOCAL]: You are creating a Method %.*s in the /Root folder. This is not a good practice. Try to create them in the /Root/Objects folder\n",
      paCreateMethodInfo.mBrowseName->name.length, reinterpret_cast<const char*>(paCreateMethodInfo.mBrowseName->name.data));
  }

  UA_MethodAttributes methodAttributes;
  UA_MethodAttributes_init(&methodAttributes);
  methodAttributes.description = UA_LOCALIZEDTEXT_ALLOC(mEnglishLocaleForNodes, "Method which can be called");
  methodAttributes.executable = true;
  methodAttributes.userExecutable = true;
  methodAttributes.displayName.locale = UA_STRING_ALLOC(mEnglishLocaleForNodes);
  UA_String_copy(&paCreateMethodInfo.mBrowseName->name, &methodAttributes.displayName.text);

  UA_StatusCode retVal = UA_Server_addMethodNode(mUaServer, requestedNodeId, parentNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    *paCreateMethodInfo.mBrowseName, methodAttributes, COPC_UA_Local_Handler::CUA_LocalCallbackFunctions::onServerMethodCall, paCreateMethodInfo.mInputSize,
    paCreateMethodInfo.mInputArguments, paCreateMethodInfo.mOutputSize, paCreateMethodInfo.mOutputArguments, &mMethodsContexts,
    paCreateMethodInfo.mReturnedNodeId);

  if(UA_STATUSCODE_GOOD == retVal) {
    if(!*paNodeId) {
      *paNodeId = UA_NodeId_new();
      UA_NodeId_copy(paCreateMethodInfo.mReturnedNodeId, *paNodeId);
    }

    UA_ParentNodeHandler parentNodeContext(parentNodeId, *paNodeId, paCreateMethodInfo.mLocalMethodInfo);
    mMethodsContexts.pushBack(parentNodeContext);
  } else {
    DEVLOG_ERROR("[OPC UA LOCAL]: OPC UA could not create method at %s. Error: %s\n",
      paCreateMethodInfo.mLocalMethodInfo.getLayer().getCommFB()->getInstanceName(), UA_StatusCode_name(retVal));
  }

  UA_NodeId_deleteMembers(&parentNodeId);
  UA_NodeId_deleteMembers(&requestedNodeId);
  UA_MethodAttributes_deleteMembers(&methodAttributes);
  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::initializeCreateObject(CActionInfo &paActionInfo) {
  //The main process is done in the execution
  UA_StatusCode retVal = UA_STATUSCODE_GOOD;

  CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator it = paActionInfo.getNodePairInfo().begin();
  ++it;

  if("" == (*it)->mBrowsePath) { //the browsename of the instance is mandatory
    retVal = UA_STATUSCODE_BADINTERNALERROR;
  }
  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::initializeDeleteObject(CActionInfo &) {
  //nothing to do here
  return UA_STATUSCODE_GOOD;
}

UA_StatusCode COPC_UA_Local_Handler::executeWrite(CActionInfo &paActionInfo) {
  UA_StatusCode retVal = UA_STATUSCODE_GOOD;
  const CIEC_ANY *dataToSend = paActionInfo.getDataToSend();
  size_t indexOfNodePair = 0;
  for(CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator it = paActionInfo.getNodePairInfo().begin(); it != paActionInfo.getNodePairInfo().end();
      ++it, indexOfNodePair++) {

    retVal = updateNodeValue(*(*it)->mNodeId, &dataToSend[indexOfNodePair]);
    if(UA_STATUSCODE_GOOD != retVal) {
      DEVLOG_ERROR("[OPC UA LOCAL]: Could not convert value to write for port %d at FB %s. Error: %s\n", indexOfNodePair,
        paActionInfo.getLayer().getCommFB()->getInstanceName(), UA_StatusCode_name(retVal));
      break;
    }

  }
  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::executeCreateMethod(CActionInfo &paActionInfo) {
  //This is the return of a local method call, when RSP is triggered

  UA_StatusCode retVal = UA_STATUSCODE_BADUNEXPECTEDERROR;
  CLocalMethodCall *localMethodCall = getLocalMethodCall(static_cast<CLocalMethodInfo&>(paActionInfo));
  if(localMethodCall) {
    const CIEC_ANY *dataToSend = paActionInfo.getDataToSend();
    // copy SD values to output
    for(size_t i = 0; i < localMethodCall->mSendHandle.mSize; i++) {
      COPC_UA_Helper::fillVariant(*localMethodCall->mSendHandle.mData[i], dataToSend[i]);
    }

    localMethodCall->mActionInfo.getResultReady().inc();
    retVal = UA_STATUSCODE_GOOD;
  } else {
    DEVLOG_ERROR("[OPC UA LOCAL]: The method being returned hasn't been called before of FB %s\n", paActionInfo.getLayer().getCommFB()->getInstanceName());
  }

  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::executeCreateObject(CActionInfo &paActionInfo) {
  UA_StatusCode retVal = UA_STATUSCODE_BADINTERNALERROR;

  CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator itTypeNodePairInfo = paActionInfo.getNodePairInfo().begin();

  //look for type first
  if(isTypeOfObjectPresent(**itTypeNodePairInfo)) {

    CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator itInstance = paActionInfo.getNodePairInfo().begin();
    ++itInstance;

    CSinglyLinkedList<UA_NodeId*> referencedNodes;
    bool isNodePresent = false;
    //check if an instance is already present
    retVal = getNode(**itInstance, referencedNodes, &isNodePresent);

    if(UA_STATUSCODE_GOOD == retVal) {
      if(!isNodePresent) {

        CIEC_STRING nodeName;
        retVal = splitAndCreateFolders((*itInstance)->mBrowsePath, nodeName, referencedNodes);
        if(UA_STATUSCODE_GOOD == retVal) {
          CCreateObjectInfo createInformation;

          initializeCreateInfo(nodeName, (**itInstance), referencedNodes.isEmpty() ? 0 : *(referencedNodes.back()), createInformation);
          createInformation.mTypeNodeId = (*itTypeNodePairInfo)->mNodeId;

          retVal = createObjectNode(createInformation);

          if(UA_STATUSCODE_GOOD == retVal) {
            UA_NodeId *tmp = UA_NodeId_new();
            UA_NodeId_copy(createInformation.mReturnedNodeId, tmp);
            referencedNodes.pushBack(tmp);
          }
        }
      } else {
        DEVLOG_ERROR("[OPC UA LOCAL]: You are trying to create a node which already exists at FB %s\n", paActionInfo.getLayer().getCommFB()->getInstanceName());
        retVal = UA_STATUSCODE_BADINTERNALERROR;
      }
    }

    //we add the references first even if itType fails, since some nodes might have been created,
    //and/or some might have been already there, so deleting them will be taken care of by
    //the referencedNodesDecrement function later
    referencedNodesIncrement(referencedNodes, paActionInfo);

    if(UA_STATUSCODE_GOOD != retVal) {
      referencedNodesDecrement(paActionInfo);
    }

    for(CSinglyLinkedList<UA_NodeId*>::Iterator itRerencedNodes = referencedNodes.begin(); itRerencedNodes != referencedNodes.end(); ++itRerencedNodes) {
      UA_NodeId_delete(*itRerencedNodes);
    }

  } else {
    DEVLOG_ERROR("[OPC UA LOCAL]: The type of the object to create could not be found \n", paActionInfo.getLayer().getCommFB()->getInstanceName());
  }

  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::createObjectNode(CCreateObjectInfo &paCreateVariableInfo) {

  UA_StatusCode retVal = UA_STATUSCODE_BADINTERNALERROR;

  UA_NodeId requestedNodeId;
  if(paCreateVariableInfo.mRequestedNodeId) {
    UA_NodeId_copy(paCreateVariableInfo.mRequestedNodeId, &requestedNodeId);
  } else {
    requestedNodeId = UA_NODEID_NUMERIC(paCreateVariableInfo.mBrowseName->namespaceIndex, 0); //if not provided, let the server generate the NodeId using the namespace from the browsename
  }

  UA_NodeId parentNodeId;
  if(paCreateVariableInfo.mParentNodeId) {
    UA_NodeId_copy(paCreateVariableInfo.mParentNodeId, &parentNodeId);
  } else {
    parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ROOTFOLDER);
    DEVLOG_WARNING(
      "[OPC UA LOCAL]: You are creating an Object %.*s in the /Root folder. This is not a good practice. Try to create them in the /Root/Objects folder\n",
      paCreateVariableInfo.mBrowseName->name.length, reinterpret_cast<const char*>(paCreateVariableInfo.mBrowseName->name.data));
  }

  CIEC_STRING nodeName;
  nodeName.assign(reinterpret_cast<const char*>(paCreateVariableInfo.mBrowseName->name.data),
    static_cast<TForteUInt16>(paCreateVariableInfo.mBrowseName->name.length));

  UA_ObjectAttributes oAttr;
  UA_ObjectAttributes_init(&oAttr);
  oAttr.description = UA_LOCALIZEDTEXT_ALLOC("", nodeName.getValue());
  oAttr.displayName = UA_LOCALIZEDTEXT_ALLOC("", nodeName.getValue());
  retVal = UA_Server_addObjectNode(mUaServer, requestedNodeId, parentNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), *paCreateVariableInfo.mBrowseName,
    *paCreateVariableInfo.mTypeNodeId, oAttr, 0, paCreateVariableInfo.mReturnedNodeId);
  if(UA_STATUSCODE_GOOD != retVal) {
    DEVLOG_ERROR("[OPC UA LOCAL]: Could not addObjectNode. Error: %s\n", UA_StatusCode_name(retVal));
  }
  UA_NodeId_deleteMembers(&requestedNodeId);
  UA_ObjectAttributes_deleteMembers(&oAttr);

  return retVal;
}

bool COPC_UA_Local_Handler::isTypeOfObjectPresent(CActionInfo::CNodePairInfo &paNodePairInfo) {
  bool isNodePresent = false;
  CSinglyLinkedList<UA_NodeId*> referencedNodes;

  UA_StatusCode retVal = getNode(paNodePairInfo, referencedNodes, &isNodePresent);

  if(UA_STATUSCODE_GOOD == retVal) {
    if(isNodePresent) {
      isNodePresent = true;
    }
    //we don't need the nodes from the type
    for(CSinglyLinkedList<UA_NodeId*>::Iterator itPresentNodes = referencedNodes.begin(); itPresentNodes != referencedNodes.end(); ++itPresentNodes) {
      UA_NodeId_delete(*itPresentNodes);
    }
    referencedNodes.clearAll();
  }
  return isNodePresent;
}

UA_StatusCode COPC_UA_Local_Handler::executeDeleteObject(CActionInfo &paActionInfo) {

  bool isNodePresent = false;
  CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator itInstance = paActionInfo.getNodePairInfo().begin();
  CSinglyLinkedList<UA_NodeId*> referencedNodes;

  //look for instance
  UA_StatusCode retVal = getNode(**itInstance, referencedNodes, &isNodePresent);

  if(UA_STATUSCODE_GOOD == retVal) {
    if(isNodePresent) {
      retVal = UA_Server_deleteNode(mUaServer, *(*itInstance)->mNodeId, true);
    } else {
      DEVLOG_ERROR("[OPC UA LOCAL]: The instance of the object to delete could not be found for FB %s\n",
        paActionInfo.getLayer().getCommFB()->getInstanceName());
      retVal = UA_STATUSCODE_BADINTERNALERROR;
    }

    for(CSinglyLinkedList<UA_NodeId*>::Iterator itPresendNodes = referencedNodes.begin(); itPresendNodes != referencedNodes.end(); ++itPresendNodes) {
      UA_NodeId_delete(*itPresendNodes);
    }
    referencedNodes.clearAll();
  }

  return retVal;
}

void COPC_UA_Local_Handler::initializeCreateInfo(CIEC_STRING &paNodeName, CActionInfo::CNodePairInfo &paNodePairInfo, const UA_NodeId *paParentNodeId,
    CCreateInfo &paResult) {
  COPC_UA_Helper::getBrowsenameFromNodeName(paNodeName.getValue(), scmDefaultBrowsenameNameSpace, *paResult.mBrowseName); //this cannot fail here anymore, since it was checked already with getNode
  paResult.mRequestedNodeId = paNodePairInfo.mNodeId;
  paResult.mParentNodeId = paParentNodeId;
}

UA_StatusCode COPC_UA_Local_Handler::getNode(CActionInfo::CNodePairInfo &paNodePairInfo, CSinglyLinkedList<UA_NodeId*> &paFoundNodeIds, bool *paIsPresent) {
  UA_StatusCode retVal = UA_STATUSCODE_GOOD;
  *paIsPresent = false;

  if("" != paNodePairInfo.mBrowsePath) {
    UA_BrowsePath *browsePaths = 0;
    size_t pathCount = 0;
    size_t firstNonExistingNode = 0;

    CSinglyLinkedList<UA_NodeId*> existingNodeIds;
    retVal = translateBrowseNameAndStore(paNodePairInfo.mBrowsePath.getValue(), &browsePaths, &pathCount, &firstNonExistingNode, existingNodeIds);
    if(UA_STATUSCODE_GOOD == retVal) {
      if(firstNonExistingNode == pathCount) { //all nodes exist
        *paIsPresent = true;
        if(paNodePairInfo.mNodeId) { //nodeID was provided
          if(!UA_NodeId_equal(paNodePairInfo.mNodeId, *existingNodeIds.back())) {
            *paIsPresent = false; // the found Node has not the same NodeId.
          }
        } else { //if no nodeID was provided, the found Node is stored in the nodePairInfo
          paNodePairInfo.mNodeId = UA_NodeId_new();
          UA_NodeId_copy(*existingNodeIds.back(), paNodePairInfo.mNodeId);
        }
      }

      for(CSinglyLinkedList<UA_NodeId*>::Iterator it = existingNodeIds.begin(); it != existingNodeIds.end(); ++it) {
        if(!*paIsPresent) {
          UA_NodeId_delete(*it);
        } else {
          paFoundNodeIds.pushBack((*it));
        }
      }
      COPC_UA_Helper::releaseBrowseArgument(*browsePaths, pathCount);
    }
  } else {
    void *handle = 0;
    //we use UA_Server_getNodeContext just to check if the node exist in the addressspace
    if(UA_STATUSCODE_BADNODEIDUNKNOWN != UA_Server_getNodeContext(mUaServer, *paNodePairInfo.mNodeId, &handle)) {
      *paIsPresent = true;
    }
  }

  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::translateBrowseNameAndStore(const char *paBrowsePath, UA_BrowsePath **paBrowsePaths, size_t *paFoldercount,
    size_t *paFirstNonExistingNode, CSinglyLinkedList<UA_NodeId*> &paFoundNodeIds) {

  UA_StatusCode retVal = COPC_UA_Helper::prepareBrowseArgument(paBrowsePath, paBrowsePaths, paFoldercount);

  if(UA_STATUSCODE_GOOD == retVal) {
    UA_BrowsePathResult *browsePathsResults = 0;
    CSinglyLinkedList<UA_NodeId*> storedNodeIds;

    browsePathsResults = static_cast<UA_BrowsePathResult *>(UA_Array_new(*paFoldercount * 2, &UA_TYPES[UA_TYPES_BROWSEPATHRESULT]));
    for(unsigned int i = 0; i < *paFoldercount * 2; i++) {
      browsePathsResults[i] = UA_Server_translateBrowsePathToNodeIds(mUaServer, &(*paBrowsePaths)[i]);
    }
    retVal = storeAlreadyExistingNodes(browsePathsResults, *paFoldercount, paFirstNonExistingNode, storedNodeIds);

    for(CSinglyLinkedList<UA_NodeId*>::Iterator it = storedNodeIds.begin(); it != storedNodeIds.end(); ++it) {
      if(UA_STATUSCODE_GOOD != retVal) {
        UA_NodeId_delete(*it);
      } else {
        paFoundNodeIds.pushBack(*it);
      }
    }

    UA_Array_delete(browsePathsResults, *paFoldercount * 2, &UA_TYPES[UA_TYPES_BROWSEPATHRESULT]);
  }

  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::storeAlreadyExistingNodes(UA_BrowsePathResult *paBrowsePathsResults, size_t paFolderCnt, size_t *paFirstNonExistingNode,
    CSinglyLinkedList<UA_NodeId*> &paCreatedNodeIds) {
  size_t foundFolderOffset = 0;
  int retVal; // no unsigned, because we need to check for -1
  bool offsetFound = false;
  for(retVal = static_cast<int>(paFolderCnt) - 1; retVal >= 0; retVal--) {
    if(UA_STATUSCODE_GOOD == paBrowsePathsResults[retVal].statusCode) { // find first existing node. Check for isInverse = TRUE
      foundFolderOffset = 0;
      offsetFound = true;
    } else if(UA_STATUSCODE_GOOD == paBrowsePathsResults[paFolderCnt + retVal].statusCode) { // and for isInverse = FALSE
      foundFolderOffset = paFolderCnt;
      offsetFound = true;
    }

    if(offsetFound) {
      break;
    }
  }

  if(-1 != retVal) {
    if(paBrowsePathsResults[foundFolderOffset + retVal].targetsSize == 0) {
      DEVLOG_ERROR("[OPC UA LOCAL]: Could not translate browse paths to node IDs. Target size is 0.\n");
      return UA_STATUSCODE_BADINTERNALERROR;
    } else {
      if(paBrowsePathsResults[foundFolderOffset + retVal].targetsSize > 1) {
        DEVLOG_WARNING("[OPC UA LOCAL]: The given browse path has multiple results for the same path. Taking the first result.\n");
      }
    }
  }
  retVal++;

  for(int j = 0; j < retVal; j++) {
    UA_NodeId *tmp = UA_NodeId_new();
    UA_NodeId_copy(&paBrowsePathsResults[foundFolderOffset + j].targets[0].targetId.nodeId, tmp);
    paCreatedNodeIds.pushBack(tmp);
  }

  *paFirstNonExistingNode = static_cast<size_t>(retVal);
  return UA_STATUSCODE_GOOD;
}

UA_StatusCode COPC_UA_Local_Handler::createFolders(const char *paFolders, CSinglyLinkedList<UA_NodeId*> &paCreatedNodeIds) {

  UA_BrowsePath *browsePaths = 0;
  size_t folderCnt = 0;
  size_t firstNonExistingNode;
  CSinglyLinkedList<UA_NodeId*> existingNodeIds;
  UA_StatusCode retVal = translateBrowseNameAndStore(paFolders, &browsePaths, &folderCnt, &firstNonExistingNode, existingNodeIds);

  if(UA_STATUSCODE_GOOD == retVal) {
    UA_NodeId parentNodeId;
    if(existingNodeIds.isEmpty()) {
      UA_NodeId objectsNode = UA_NODEID_NUMERIC(0, UA_NS0ID_ROOTFOLDER);
      UA_NodeId_copy(&objectsNode, &parentNodeId);
    } else {
      UA_NodeId_copy(*existingNodeIds.back(), &parentNodeId);
    }

    CSinglyLinkedList<UA_NodeId*> createdNodeIds;

    // create all the nodes on the way
    for(size_t j = firstNonExistingNode; j < folderCnt; j++) {

      UA_NodeId type = UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE);
      CCreateObjectInfo createInformation;

      createInformation.mRequestedNodeId = 0;
      createInformation.mParentNodeId = &parentNodeId;
      UA_QualifiedName_copy(&browsePaths[folderCnt - 1].relativePath.elements[j].targetName, createInformation.mBrowseName);
      createInformation.mTypeNodeId = &type;

      retVal = createObjectNode(createInformation);

      if(UA_STATUSCODE_GOOD != retVal) {
        DEVLOG_ERROR("[OPC UA LOCAL]: Could not create folder %.*s in path %s. Error: %s\n", createInformation.mBrowseName->name.length,
          reinterpret_cast<const char*>(createInformation.mBrowseName->name.data), paFolders, UA_StatusCode_name(retVal));
        break;
      }

      UA_NodeId *tmp = UA_NodeId_new();
      UA_NodeId_copy(createInformation.mReturnedNodeId, tmp);
      createdNodeIds.pushBack(tmp);

      UA_NodeId_deleteMembers(&parentNodeId);
      UA_NodeId_copy(createInformation.mReturnedNodeId, &parentNodeId);
    }

    UA_NodeId_deleteMembers(&parentNodeId);
    for(CSinglyLinkedList<UA_NodeId*>::Iterator it = existingNodeIds.begin(); it != existingNodeIds.end(); ++it) {
      if(UA_STATUSCODE_GOOD != retVal) {
        UA_NodeId_delete(*it);
      } else {
        paCreatedNodeIds.pushBack(*it);
      }
    }

    for(CSinglyLinkedList<UA_NodeId*>::Iterator it = createdNodeIds.begin(); it != createdNodeIds.end(); ++it) {
      paCreatedNodeIds.pushBack(*it); //store the NodeId because the object was created, and if the next fails, the node should be referenced
    }

    COPC_UA_Helper::releaseBrowseArgument(*browsePaths, folderCnt);
  }
  return retVal;
}

UA_StatusCode COPC_UA_Local_Handler::splitAndCreateFolders(CIEC_STRING &paBrowsePath, CIEC_STRING &paNodeName,
    CSinglyLinkedList<UA_NodeId*> &paRreferencedNodes) {
  CIEC_STRING folders;
  UA_StatusCode retVal = UA_STATUSCODE_BADINTERNALERROR;
  if(splitFoldersFromNode(paBrowsePath, folders, paNodeName)) {
    retVal = UA_STATUSCODE_GOOD;
    if("" != folders) {
      retVal = createFolders(folders.getValue(), paRreferencedNodes);
    }
  }
  return retVal;
}

bool COPC_UA_Local_Handler::splitFoldersFromNode(const CIEC_STRING &paOriginal, CIEC_STRING &paFolder, CIEC_STRING &paNodeName) {

  bool retVal = false;

  if(COPC_UA_Helper::isBrowsePathValid(paOriginal.getValue())) {
    retVal = true;

    CIEC_STRING copyOfOriginal(paOriginal);

    char *begin = copyOfOriginal.getValue();
    char *runner = begin + copyOfOriginal.length() - 1;

    if('/' == *runner) { // remove tailing slash
      *runner = '\0';
      runner--;
    }

    while('/' != *runner) {
      runner--;
    }

    *runner = '\0';
    paNodeName = runner + 1;

    if(begin != runner) {
      paFolder = begin;
    }
  }

  return retVal;
}

void COPC_UA_Local_Handler::handlePresentNodes(CSinglyLinkedList<UA_NodeId*> &paPresentNodes, CSinglyLinkedList<UA_NodeId*> &paReferencedNodes, bool paFailed) {
  for(CSinglyLinkedList<UA_NodeId*>::Iterator itPresendNodes = paPresentNodes.begin(); itPresendNodes != paPresentNodes.end(); ++itPresendNodes) {
    if(paFailed) {
      UA_NodeId_delete(*itPresendNodes);
    } else {
      paReferencedNodes.pushBack(*itPresendNodes);
    }
  }
}

COPC_UA_Local_Handler::CLocalMethodCall& COPC_UA_Local_Handler::addMethodCall(CLocalMethodInfo &paActionInfo,
    COPC_UA_Helper::UA_SendVariable_handle &paHandleRecv) {
  CCriticalRegion criticalRegion(mMethodCallsMutex);
  mMethodCalls.pushBack(CLocalMethodCall(paActionInfo, paHandleRecv));
  CSinglyLinkedList<CLocalMethodCall>::Iterator justPushed = mMethodCalls.back();
  return *justPushed;
}

void COPC_UA_Local_Handler::removeMethodCall(CLocalMethodCall &toRemove) {
  CCriticalRegion criticalRegion(mMethodCallsMutex);
  mMethodCalls.erase(toRemove);
}

COPC_UA_Local_Handler::CLocalMethodCall *COPC_UA_Local_Handler::getLocalMethodCall(CLocalMethodInfo &paActionInfo) {
  CCriticalRegion criticalRegion(mMethodCallsMutex);
  CLocalMethodCall *retVal = 0;
  for(CSinglyLinkedList<CLocalMethodCall>::Iterator it = mMethodCalls.begin(); it != mMethodCalls.end(); ++it) {
    if(&(*it).mActionInfo == &paActionInfo) {
      retVal = &(*it);
      break;
    }
  }
  return retVal;
}

// ******************** CALLBACKS *************************

UA_StatusCode COPC_UA_Local_Handler::CUA_LocalCallbackFunctions::onServerMethodCall(UA_Server *, const UA_NodeId *, void *, const UA_NodeId *paMethodNodeId,
    void *paMethodContext, //NOSONAR
    const UA_NodeId *paParentNodeId, void *, //NOSONAR
    size_t paInputSize, const UA_Variant *paInput, size_t paOutputSize, UA_Variant *paOutput) {

  UA_StatusCode retVal = UA_STATUSCODE_BADUNEXPECTEDERROR;
  CLocalMethodInfo *localMethodHandle = 0;
  CSinglyLinkedList<UA_ParentNodeHandler> *methodsContexts = static_cast<CSinglyLinkedList<UA_ParentNodeHandler>*>(paMethodContext);
  for(CSinglyLinkedList<UA_ParentNodeHandler>::Iterator iter = methodsContexts->begin(); iter != methodsContexts->end(); ++iter) {
    if(UA_NodeId_equal(paParentNodeId, (*iter).mParentNodeId) && UA_NodeId_equal(paMethodNodeId, (*iter).mMethodNodeId)) {
      localMethodHandle = &(*iter).mActionInfo;
      break;
    }
  }

  if(!localMethodHandle) {
    DEVLOG_ERROR("[OPC UA LOCAL]: Method doesn't have any FB referencing it\n");
  } else {
    if(paInputSize != localMethodHandle->getReceiveSize() || paOutputSize != localMethodHandle->getSendSize()) {
      DEVLOG_ERROR("[OPC UA LOCAL]: method call got invalid number of arguments. In: %d==%d, Out: %d==%d\n", localMethodHandle->getReceiveSize(), paInputSize,
        localMethodHandle->getSendSize(), paOutput);
    } else {
      // other thread may currently create nodes for the same path, thus mutex
      CCriticalRegion criticalRegion(localMethodHandle->getMutex()); //TODO: do we need this mutex?

      COPC_UA_Helper::UA_SendVariable_handle sendHandle(paOutputSize);
      COPC_UA_Helper::UA_RecvVariable_handle recvHandle(paInputSize);

      for(size_t i = 0; i < paInputSize; i++) {
        recvHandle.mData[i] = &paInput[i];
      }

      for(size_t i = 0; i < paOutputSize; i++) {
        sendHandle.mData[i] = &paOutput[i];
      }

      // Handle return of receive mData
      if(e_ProcessDataOk == localMethodHandle->getLayer().recvData(static_cast<const void *>(&recvHandle), 0)) { //TODO: add multidimensional mData handling with 'range'.

        localMethodHandle->getLayer().getCommFB()->interruptCommFB(&localMethodHandle->getLayer());

        //when the method finishes, and RSP is triggered, sendHandle will be filled with the right information
        CLocalMethodCall &localMethodCall = ::getExtEvHandler<COPC_UA_Local_Handler>(*localMethodHandle->getLayer().getCommFB()).addMethodCall(
          *localMethodHandle, sendHandle);

        ::getExtEvHandler<COPC_UA_Local_Handler>(*localMethodHandle->getLayer().getCommFB()).startNewEventChain(localMethodHandle->getLayer().getCommFB());

        //wait For semaphore, which will be released by execute Local Method in this handler
        if(!localMethodHandle->getResultReady().timedWait(scmMethodCallTimeoutInNanoSeconds)) {
          DEVLOG_ERROR("[OPC UA LOCAL]: method call did not get result values within timeout of %u nanoseconds.\n", scmMethodCallTimeoutInNanoSeconds);
          retVal = UA_STATUSCODE_BADTIMEOUT;
        } else {
          retVal = sendHandle.mFailed ? UA_STATUSCODE_BADUNEXPECTEDERROR : UA_STATUSCODE_GOOD;
        }

        ::getExtEvHandler<COPC_UA_Local_Handler>(*localMethodHandle->getLayer().getCommFB()).removeMethodCall(localMethodCall);
      }
    }
  }
  return retVal;
}

void COPC_UA_Local_Handler::CUA_LocalCallbackFunctions::onWrite(UA_Server *, const UA_NodeId *, void *, const UA_NodeId *, void *nodeContext, //NOSONAR
    const UA_NumericRange *, const UA_DataValue *data) {

  UA_VariableContext_Handle *variableCallbackHandle = static_cast<UA_VariableContext_Handle *>(nodeContext);

  COPC_UA_Helper::UA_RecvVariable_handle handleRecv(1);

  handleRecv.mData[0] = data->hasValue ? &data->value : 0; //TODO: check this empty data
  handleRecv.mOffset = variableCallbackHandle->mPortIndex;

  EComResponse retVal = variableCallbackHandle->mActionInfo.getLayer().recvData(static_cast<const void *>(&handleRecv), 0); //TODO: add multidimensional mData handling with 'range'.

  if(e_Nothing != retVal) {
    variableCallbackHandle->mActionInfo.getLayer().getCommFB()->interruptCommFB(&variableCallbackHandle->mActionInfo.getLayer());
    ::getExtEvHandler<COPC_UA_Local_Handler>(*variableCallbackHandle->mActionInfo.getLayer().getCommFB()).startNewEventChain(
      variableCallbackHandle->mActionInfo.getLayer().getCommFB());
  }
}
