From 2cd4a3f24ac73355f3242f389fa02d221115d802 Mon Sep 17 00:00:00 2001 From: cochicde Date: Wed, 18 Dec 2024 15:55:41 +0100 Subject: [PATCH] fix and move ldsMe to its own class (#239) * fix and move ldsMe to its own class * add documentation * update for v1.4.5 * remove not needed include --- src/com/opc_ua/CMakeLists.txt | 2 + src/com/opc_ua/detail/CMakeLists.txt | 4 + src/com/opc_ua/detail/lds_me_handler.cpp | 100 +++++++++++++++++++++ src/com/opc_ua/detail/lds_me_handler.h | 101 +++++++++++++++++++++ src/com/opc_ua/opcua_local_handler.cpp | 106 +++-------------------- src/com/opc_ua/opcua_local_handler.h | 17 ---- 6 files changed, 220 insertions(+), 110 deletions(-) create mode 100644 src/com/opc_ua/detail/CMakeLists.txt create mode 100644 src/com/opc_ua/detail/lds_me_handler.cpp create mode 100644 src/com/opc_ua/detail/lds_me_handler.h diff --git a/src/com/opc_ua/CMakeLists.txt b/src/com/opc_ua/CMakeLists.txt index d85964658..1389fe357 100644 --- a/src/com/opc_ua/CMakeLists.txt +++ b/src/com/opc_ua/CMakeLists.txt @@ -91,6 +91,8 @@ if (FORTE_COM_OPC_UA) add_subdirectory(FBs) add_subdirectory(types) + add_subdirectory(detail) + if(FORTE_COM_OPC_UA_ENCRYPTION) SET(FORTE_COM_OPC_UA_ENCRYPTION_INCLUDE_DIR "" CACHE STRING "ABSOLUTE path to encryption headers") diff --git a/src/com/opc_ua/detail/CMakeLists.txt b/src/com/opc_ua/detail/CMakeLists.txt new file mode 100644 index 000000000..a2a028a80 --- /dev/null +++ b/src/com/opc_ua/detail/CMakeLists.txt @@ -0,0 +1,4 @@ + +if(FORTE_COM_OPC_UA_MULTICAST) + forte_add_sourcefile_hcpp(lds_me_handler) +endif(FORTE_COM_OPC_UA_MULTICAST) \ No newline at end of file diff --git a/src/com/opc_ua/detail/lds_me_handler.cpp b/src/com/opc_ua/detail/lds_me_handler.cpp new file mode 100644 index 000000000..ef84b82e4 --- /dev/null +++ b/src/com/opc_ua/detail/lds_me_handler.cpp @@ -0,0 +1,100 @@ + +#include "lds_me_handler.h" + +#include "devlog.h" + +using namespace forte::com::opc_ua::detail; + +LdsMeHandler::LdsMeHandler(UA_Server& paUaServer) : mUaServer{paUaServer} { + UA_Server_setServerOnNetworkCallback(&mUaServer, serverOnNetworkCallback, this); +} + +LdsMeHandler::~LdsMeHandler() { + for(const auto& registeredServer : mRegisteredServers){ + deregisterDiscoveryServer(registeredServer.mString); + } + UA_Server_setServerOnNetworkCallback(&mUaServer, nullptr, nullptr); +} + +void LdsMeHandler::configureServer(UA_ServerConfig& paUaServerConfig, const std::string& paServerName) { + UA_String_clear(&paUaServerConfig.mdnsConfig.mdnsServerName); + paUaServerConfig.mdnsConfig.mdnsServerName = UA_String_fromChars(paServerName.c_str()); + // Enable the mDNS announce and response functionality + paUaServerConfig.mdnsEnabled = true; +} + +void LdsMeHandler::serverOnNetworkCallback(const UA_ServerOnNetwork* paServerOnNetwork, UA_Boolean paIsServerAnnounce, UA_Boolean paIsTxtReceived, void* paData) { + + if(!paIsTxtReceived) { + return; // we wait until the corresponding TXT record is announced. + } + + DEVLOG_DEBUG("[OPC UA LDS ME]: 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 + bool isServerLDS = false; + + UA_StringRAII ldsStr("LDS"); + for(unsigned int i = 0; i < paServerOnNetwork->serverCapabilitiesSize; i++) { + if(UA_String_equal(&paServerOnNetwork->serverCapabilities[i], &ldsStr.mString)) { + isServerLDS = true; + break; + } + } + + // skip non-LDS servers + if(!isServerLDS) { + return; + } + + auto handler = static_cast(paData); + + auto foundDiscoveryUrl = handler->mRegisteredServers.end(); + for(auto iter = handler->mRegisteredServers.begin(); iter != handler->mRegisteredServers.end(); iter++) { + if(UA_String_equal(&paServerOnNetwork->discoveryUrl, &iter->mString)) { + foundDiscoveryUrl = iter; + break; + } + } + + // if a new server is being announced + if(paIsServerAnnounce && foundDiscoveryUrl == handler->mRegisteredServers.end()) { + if(handler->registerDiscoveryServer(paServerOnNetwork->discoveryUrl)){ + handler->mRegisteredServers.emplace_back(paServerOnNetwork->discoveryUrl); + } + // if a known server is being un-announced + } else if(!paIsServerAnnounce && foundDiscoveryUrl != handler->mRegisteredServers.end()) { + handler->deregisterDiscoveryServer(foundDiscoveryUrl->mString); + handler->mRegisteredServers.erase(foundDiscoveryUrl); + } +} + +bool LdsMeHandler::registerDiscoveryServer(const UA_String& paDiscoveryUrl) { + + UA_ClientConfig clientConfig; + memset(&clientConfig, 0, sizeof(UA_ClientConfig)); + UA_ClientConfig_setDefault(&clientConfig); + auto retVal = UA_Server_registerDiscovery(&mUaServer, &clientConfig, paDiscoveryUrl, UA_STRING_NULL); + if( UA_STATUSCODE_GOOD != retVal) { + DEVLOG_ERROR("[OPC UA LDSME]: Could not register with LDS. Error: %s\n", UA_StatusCode_name(retVal)); + return false; + } + DEVLOG_INFO("[OPC UA LDSME]: Registered to LDS '%.*s'\n", paDiscoveryUrl.length, paDiscoveryUrl.data); + return true; +} + +void LdsMeHandler::deregisterDiscoveryServer(const UA_String& paDiscoveryUrl) { + UA_ClientConfig clientConfig; + memset(&clientConfig, 0, sizeof(UA_ClientConfig)); + UA_ClientConfig_setDefault(&clientConfig); + + auto retVal = UA_Server_deregisterDiscovery(&mUaServer, &clientConfig, paDiscoveryUrl); + // if unregister fails, we don't do anything else + if( UA_STATUSCODE_GOOD != retVal) { + DEVLOG_ERROR("[OPC UA LDSME]: Could not deregister with LDS. Error: %s\n", UA_StatusCode_name(retVal)); + return; + } + + DEVLOG_INFO("[OPC UA LDSME]: Unregistered from LDS '%.*s'\n", paDiscoveryUrl.length, paDiscoveryUrl.data); +} \ No newline at end of file diff --git a/src/com/opc_ua/detail/lds_me_handler.h b/src/com/opc_ua/detail/lds_me_handler.h new file mode 100644 index 000000000..ccfb91670 --- /dev/null +++ b/src/com/opc_ua/detail/lds_me_handler.h @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2024 Jose Cabral + * + * 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: + * Jose Cabral + * - initial integration of the OPC-UA protocol + *******************************************************************************/ + +#ifndef SRC_COM_OPC_UA_DETAIL_LDSMEHANDLER_H_ +#define SRC_COM_OPC_UA_DETAIL_LDSMEHANDLER_H_ + +#include + +#include +#include + +namespace forte::com::opc_ua::detail { + +/** + * @brief Handles all things related to LDS-ME + * + * Configures the server to accept LDS-ME messages and handles them accordingly. + */ +class LdsMeHandler { + public: + + /** + * @brief Register this instance as handler for incoming discovery message + * + * @param paUaServer OpcUa server to handle + */ + LdsMeHandler(UA_Server& paUaServer); + + /** + * @brief Un-register this instance as handler incoming discovery message + * + */ + ~LdsMeHandler(); + + + /** + * @brief Sets the proper values to the server configuration so it accepts LDS-ME messages + * + * @param paUaServerConfig configuration to be set + * @param paServerName server name to be used when registering to the discovery server + */ + static void configureServer(UA_ServerConfig& paUaServerConfig, const std::string& paServerName); + + private: + + bool registerDiscoveryServer(const UA_String& paDiscoveryUrl); + void deregisterDiscoveryServer(const UA_String& paDiscoveryUrl); + + static void serverOnNetworkCallback(const UA_ServerOnNetwork* paServerOnNetwork, UA_Boolean paIsServerAnnounce, UA_Boolean paIsTxtReceived, void* paData); + + UA_Server& mUaServer; + + // Handle the lifetime of a UA_String nicely + class UA_StringRAII { + public: + UA_StringRAII(const UA_String& paString) { + UA_String_copy(&paString, &mString); + } + + UA_StringRAII(const char* paString) { + mString = UA_String_fromChars(paString); + } + + ~UA_StringRAII() { + UA_String_clear(&mString); + } + + UA_StringRAII(UA_StringRAII&& paOther) { + *this = std::move(paOther); + } + + UA_StringRAII& operator=(UA_StringRAII&& paOther) { + mString = paOther.mString; + UA_String_init(&paOther.mString); + return *this; + }; + + + UA_StringRAII(const UA_StringRAII&) = delete; + UA_StringRAII& operator=(const UA_StringRAII&) = delete; + + UA_String mString; + }; + + /// List of discovery servers where this instance is already registered + std::vector mRegisteredServers; +}; +} + +#endif // SRC_COM_OPC_UA_DETAIL_LDSMEHANDLER_H_ diff --git a/src/com/opc_ua/opcua_local_handler.cpp b/src/com/opc_ua/opcua_local_handler.cpp index eafc14927..916442536 100644 --- a/src/com/opc_ua/opcua_local_handler.cpp +++ b/src/com/opc_ua/opcua_local_handler.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015, 2023 Florian Froschermeier , + * Copyright (c) 2015, 2024 Florian Froschermeier , * fortiss GmbH, Primetals Technologies Austria GmbH * * This program and the accompanying materials are made available under the @@ -30,6 +30,9 @@ #include "forte_printer.h" #include "../../arch/utils/mainparam_utils.h" #include "opcua_local_handler.h" +#ifdef FORTE_COM_OPC_UA_MULTICAST +#include "detail/lds_me_handler.h" +#endif //FORTE_COM_OPC_UA_MULTICAST #include #ifndef FORTE_COM_OPC_UA_CUSTOM_HOSTNAME @@ -57,12 +60,6 @@ COPC_UA_Local_Handler::~COPC_UA_Local_Handler() { UA_NodeId_delete(const_cast(iter->mNodeId)); } mNodesReferences.clear(); - -#ifdef FORTE_COM_OPC_UA_MULTICAST - for(CSinglyLinkedList::Iterator iter = mRegisteredWithLds.begin(); iter != mRegisteredWithLds.end(); ++iter) { - UA_String_delete(*iter); - } -#endif //FORTE_COM_OPC_UA_MULTICAST } void COPC_UA_Local_Handler::enableHandler() { @@ -91,12 +88,15 @@ void COPC_UA_Local_Handler::run() { configureUAServer(serverStrings, *uaServerConfig); if(initializeNodesets(*mUaServer)) { -#ifdef FORTE_COM_OPC_UA_MULTICAST - UA_Server_setServerOnNetworkCallback(mUaServer, serverOnNetworkCallback, this); -#endif //FORTE_COM_OPC_UA_MULTICAST - UA_StatusCode retVal = UA_Server_run_startup(mUaServer); if(UA_STATUSCODE_GOOD == retVal) { + { +#ifdef FORTE_COM_OPC_UA_MULTICAST + // the extra curly brace on top is to limit the lifetime of this object. + // it was avoided to have inside this ifdef to also avoid the closing brace + // with the exta ifdef making it cleaner this way + auto mLdsMeHandler = forte::com::opc_ua::detail::LdsMeHandler(*mUaServer); +#endif //FORTE_COM_OPC_UA_MULTICAST mServerStarted.inc(); while(isAlive()) { UA_UInt16 timeToSleepMs; @@ -110,6 +110,7 @@ void COPC_UA_Local_Handler::run() { mServerNeedsIteration.timedWait(static_cast(timeToSleepMs) * 1000000ULL); } + } retVal = UA_Server_run_shutdown(mUaServer); if(UA_STATUSCODE_GOOD == retVal) { DEVLOG_INFO("[OPC UA LOCAL]: Server successfully stopped\n"); @@ -166,10 +167,7 @@ void COPC_UA_Local_Handler::generateServerStrings(TForteUInt16 paUAServerPort, U void COPC_UA_Local_Handler::configureUAServer(UA_ServerStrings &paServerStrings, UA_ServerConfig &paUaServerConfig) const { #ifdef FORTE_COM_OPC_UA_MULTICAST - paUaServerConfig.applicationDescription.applicationType = UA_APPLICATIONTYPE_DISCOVERYSERVER; - // hostname will be added by mdns library - UA_String_clear(&paUaServerConfig.mdnsConfig.mdnsServerName); - paUaServerConfig.mdnsConfig.mdnsServerName = UA_String_fromChars(paServerStrings.mMdnsServerName.c_str()); + forte::com::opc_ua::detail::LdsMeHandler::configureServer(paUaServerConfig, paServerStrings.mMdnsServerName); #endif //FORTE_COM_OPC_UA_MULTICAST UA_Array_delete(paUaServerConfig.serverUrls, paUaServerConfig.serverUrlsSize, &UA_TYPES[UA_TYPES_STRING]); @@ -280,85 +278,7 @@ void COPC_UA_Local_Handler::getNodesReferencedByAction(const CActionInfo &paActi } } -#ifdef FORTE_COM_OPC_UA_MULTICAST - -const UA_String* COPC_UA_Local_Handler::getDiscoveryUrl() const { - - UA_ServerConfig *mServerConfig = UA_Server_getConfig(mUaServer); //change mServerConfig to serverConfig when only master branch is present - 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(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_clear(&ldsStr); -} - -void COPC_UA_Local_Handler::registerWithLds(const UA_String *paDiscoveryUrl) { - // check if already registered with the given LDS - for(CSinglyLinkedList::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, 0, reinterpret_cast(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::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(); diff --git a/src/com/opc_ua/opcua_local_handler.h b/src/com/opc_ua/opcua_local_handler.h index a376eabd3..836b2ce20 100644 --- a/src/com/opc_ua/opcua_local_handler.h +++ b/src/com/opc_ua/opcua_local_handler.h @@ -694,23 +694,6 @@ class COPC_UA_Local_Handler : public COPC_UA_HandlerAbstract, public CThread { * Default description for variable nodes */ static const char *const mDefaultDescriptionForVariableNodes; - -#ifdef FORTE_COM_OPC_UA_MULTICAST -# ifndef UA_ENABLE_DISCOVERY_MULTICAST -# error open62541 needs to be built with UA_ENABLE_DISCOVERY=ON and UA_ENABLE_DISCOVERY_MULTICAST=ON -# else // UA_ENABLE_DISCOVERY_MULTICAST - /** - * List of LDS servers where this instance is already registered. - */ - CSinglyLinkedList mRegisteredWithLds; - - const UA_String* getDiscoveryUrl() const; - void registerWithLds(const UA_String *paDiscoveryUrl); - void removeLdsRegister(const UA_String *paDiscoveryUrl); - static void serverOnNetworkCallback(const UA_ServerOnNetwork *paServerOnNetwork, UA_Boolean paIsServerAnnounce, UA_Boolean paIsTxtReceived, void *paData); -# endif //UA_ENABLE_DISCOVERY_MULTICAST -#endif //FORTE_COM_OPC_UA_MULTICAST - }; #endif /* SRC_MODULES_OPC_UA_OPCUALOCALHANDLER_H_ */