blob: a4574763d81cfcb15d7ec4b5be467507defaa78f [file] [log] [blame]
/**
* 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.
*/
#include "opc.h"
#include <cstdlib>
#include <memory>
#include <vector>
#include <string>
#include <functional>
#include <array>
#include "utils/StringUtils.h"
#include "core/logging/Logger.h"
#include "Exception.h"
#include "utils/gsl.h"
#include "open62541/client_highlevel.h"
#include "open62541/client_config_default.h"
namespace org::apache::nifi::minifi::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.data());
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:
default:
return core::logging::critical;
}
}
} // namespace
/*
* End of internal functions
*/
Client::Client(const std::shared_ptr<core::logging::Logger>& logger, const std::string& application_uri,
const std::vector<char>& cert_buffer, const std::vector<char>& key_buffer,
const std::vector<std::vector<char>>& trust_buffers)
: client_(UA_Client_new()) {
if (cert_buffer.empty()) {
UA_ClientConfig_setDefault(UA_Client_getConfig(client_));
} else {
UA_ClientConfig *cc = UA_Client_getConfig(client_);
cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
// Certificate
UA_ByteString cert_byte_string = UA_STRING_NULL;
cert_byte_string.length = cert_buffer.size();
cert_byte_string.data = reinterpret_cast<UA_Byte*>(UA_malloc(cert_byte_string.length * sizeof(UA_Byte))); // NOLINT(cppcoreguidelines-owning-memory)
memcpy(cert_byte_string.data, cert_buffer.data(), cert_byte_string.length);
// Key
UA_ByteString key_byte_string = UA_STRING_NULL;
key_byte_string.length = key_buffer.size();
key_byte_string.data = reinterpret_cast<UA_Byte*>(UA_malloc(key_byte_string.length * sizeof(UA_Byte))); // NOLINT(cppcoreguidelines-owning-memory)
memcpy(key_byte_string.data, key_buffer.data(), key_byte_string.length);
// Trusted certificates
std::vector<UA_ByteString> trust_list;
trust_list.resize(trust_buffers.size());
for (size_t i = 0; i < trust_buffers.size(); i++) {
trust_list[i] = UA_STRING_NULL;
trust_list[i].length = trust_buffers[i].size();
trust_list[i].data = reinterpret_cast<UA_Byte*>(UA_malloc(trust_list[i].length * sizeof(UA_Byte))); // NOLINT(cppcoreguidelines-owning-memory)
memcpy(trust_list[i].data, trust_buffers[i].data(), trust_list[i].length);
}
UA_StatusCode sc = UA_ClientConfig_setDefaultEncryption(cc, cert_byte_string, key_byte_string,
trust_list.data(), trust_buffers.size(),
nullptr, 0);
UA_ByteString_clear(&cert_byte_string);
UA_ByteString_clear(&key_byte_string);
for (size_t i = 0; i < trust_buffers.size(); i++) {
UA_ByteString_clear(&trust_list[i]);
}
if (sc != UA_STATUSCODE_GOOD) {
logger->log_error("Configuring the client for encryption failed: {}", 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));
}
}
minifi_ua_logger_ = {logFunc, logger.get(), [](UA_Logger*){}};
UA_ClientConfig *config_ptr = UA_Client_getConfig(client_);
config_ptr->logging = &minifi_ua_logger_;
if (application_uri.length() > 0) {
UA_String_clear(&config_ptr->clientDescription.applicationUri);
config_ptr->clientDescription.applicationUri = UA_STRING_ALLOC(application_uri.c_str());
}
logger_ = logger;
}
Client::~Client() {
if (client_ == nullptr) {
return;
}
UA_SecureChannelState channel_state = UA_SECURECHANNELSTATE_CLOSED;
UA_Client_getState(client_, &channel_state, nullptr, nullptr);
if (channel_state != UA_SECURECHANNELSTATE_CLOSED) {
auto sc = UA_Client_disconnect(client_);
if (sc != UA_STATUSCODE_GOOD) {
logger_->log_warn("Failed to disconnect OPC client: {}", UA_StatusCode_name(sc));
}
}
UA_Client_delete(client_);
}
bool Client::isConnected() {
if (!client_) {
return false;
}
UA_SessionState session_state = UA_SESSIONSTATE_CLOSED;
UA_Client_getState(client_, nullptr, &session_state, nullptr);
return session_state == UA_SESSIONSTATE_ACTIVATED;
}
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_connectUsername(client_, url.c_str(), username.c_str(), password.c_str());
}
}
NodeData Client::getNodeData(const UA_ReferenceDescription *ref, const std::string& base_path) {
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), // NOLINT(cppcoreguidelines-pro-type-union-access)
ref->nodeId.nodeId.identifier.string.length); // NOLINT(cppcoreguidelines-pro-type-union-access)
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), // NOLINT(cppcoreguidelines-pro-type-union-access)
ref->nodeId.nodeId.identifier.byteString.length); // NOLINT(cppcoreguidelines-pro-type-union-access)
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); // NOLINT(cppcoreguidelines-pro-type-union-access)
nodedata.attributes["NodeID type"] = "numeric";
}
nodedata.attributes["Browsename"] = browsename;
auto splitted_base_path = utils::string::splitAndTrimRemovingEmpty(base_path, "/");
if (!splitted_base_path.empty() && splitted_base_path.back() == browsename) {
nodedata.attributes["Full path"] = base_path;
} else {
nodedata.attributes["Full path"] = base_path + "/" + browsename;
}
nodedata.data_type_id = static_cast<UA_DataTypeKind>(UA_DATATYPEKINDS);
UA_Variant* var = UA_Variant_new();
if (UA_Client_readValueAttribute(client_, ref->nodeId.nodeId, var) == UA_STATUSCODE_GOOD && var->type != nullptr && var->data != nullptr) {
// 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_clear(&response);
nodedata.data_type_id = static_cast<UA_DataTypeKind>(var->type->typeKind);
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 node_id) {
UA_ReferenceDescription *ref = UA_ReferenceDescription_new();
UA_ReferenceDescription_init(ref);
UA_NodeId_copy(&node_id, &ref->nodeId.nodeId);
auto sc = UA_Client_readNodeClassAttribute(client_, node_id, &ref->nodeClass);
if (sc == UA_STATUSCODE_GOOD) {
sc = UA_Client_readBrowseNameAttribute(client_, node_id, &ref->browseName);
}
if (sc == UA_STATUSCODE_GOOD) {
UA_Client_readDisplayNameAttribute(client_, node_id, &ref->displayName);
}
return ref;
}
void Client::traverse(UA_NodeId node_id, const std::function<NodeFoundCallBackFunc>& cb, const std::string& base_path, uint64_t max_depth, bool fetch_root) {
if (fetch_root) {
UA_ReferenceDescription *rootRef = getNodeReference(node_id);
if ((rootRef->nodeClass == UA_NODECLASS_VARIABLE || rootRef->nodeClass == UA_NODECLASS_OBJECT) && rootRef->browseName.name.length > 0) {
cb(rootRef, base_path);
}
UA_ReferenceDescription_delete(rootRef);
}
if (max_depth != 0) {
max_depth--;
if (max_depth == 0) {
return;
}
}
UA_BrowseRequest browse_request;
UA_BrowseRequest_init(&browse_request);
browse_request.requestedMaxReferencesPerNode = 0;
browse_request.nodesToBrowse = UA_BrowseDescription_new();
browse_request.nodesToBrowseSize = 1;
UA_NodeId_copy(&node_id, &browse_request.nodesToBrowse[0].nodeId);
browse_request.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL;
UA_BrowseResponse browse_response = UA_Client_Service_browse(client_, browse_request);
const auto guard = gsl::finally([&browse_response]() {
UA_BrowseResponse_clear(&browse_response);
});
UA_BrowseRequest_clear(&browse_request);
for (size_t i = 0; i < browse_response.resultsSize; ++i) {
for (size_t j = 0; j < browse_response.results[i].referencesSize; ++j) {
UA_ReferenceDescription *ref = &(browse_response.results[i].references[j]);
if (cb(ref, base_path)) {
if (ref->nodeClass == UA_NODECLASS_VARIABLE || ref->nodeClass == UA_NODECLASS_OBJECT) {
std::string new_base_path = base_path;
new_base_path.append("/").append(reinterpret_cast<char *>(ref->browseName.name.data), ref->browseName.name.length);
traverse(ref->nodeId.nodeId, cb, new_base_path, max_depth, false);
}
} else {
return;
}
}
}
}
bool Client::exists(UA_NodeId node_id) {
bool retval = false;
auto callback = [&retval](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(node_id, callback, "", 1);
return retval;
}
UA_StatusCode Client::translateBrowsePathsToNodeIdsRequest(const std::string& path, std::vector<UA_NodeId>& found_node_ids, int32_t namespace_index,
const std::vector<UA_UInt32>& path_reference_types, const std::shared_ptr<core::logging::Logger>& logger) {
logger->log_trace("Trying to find node ids for {}", path.c_str());
auto tokens = utils::string::splitAndTrimRemovingEmpty(path, "/");
gsl_Expects(!tokens.empty());
UA_BrowsePath browse_path;
UA_BrowsePath_init(&browse_path);
browse_path.startingNode = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
browse_path.relativePath.elements = reinterpret_cast<UA_RelativePathElement*>(UA_Array_new(tokens.size(), &UA_TYPES[UA_TYPES_RELATIVEPATHELEMENT]));
browse_path.relativePath.elementsSize = tokens.size();
std::vector<UA_UInt32> ids;
ids.push_back(UA_NS0ID_ORGANIZES);
for (size_t i = 0; i < tokens.size() - 1; ++i) {
if (!path_reference_types.empty()) {
ids.push_back(path_reference_types[i]);
} else {
ids.push_back(UA_NS0ID_ORGANIZES);
}
}
for (size_t i = 0; i < tokens.size(); ++i) {
UA_RelativePathElement *elem = &browse_path.relativePath.elements[i];
elem->referenceTypeId = UA_NODEID_NUMERIC(0, ids[i]);
elem->targetName = UA_QUALIFIEDNAME_ALLOC(namespace_index, tokens[i].c_str());
}
UA_TranslateBrowsePathsToNodeIdsRequest request;
UA_TranslateBrowsePathsToNodeIdsRequest_init(&request);
request.browsePaths = &browse_path;
request.browsePathsSize = 1;
UA_TranslateBrowsePathsToNodeIdsResponse response = UA_Client_Service_translateBrowsePathsToNodeIds(client_, request);
const auto guard = gsl::finally([&browse_path, &tokens, &response]() {
for (size_t i = 0; i < tokens.size(); ++i) {
UA_RelativePathElement *elem = &browse_path.relativePath.elements[i];
UA_QualifiedName_clear(&elem->targetName);
}
UA_BrowsePath_clear(&browse_path);
UA_TranslateBrowsePathsToNodeIdsResponse_clear(&response);
});
if (response.resultsSize < 1 || response.results[0].statusCode != UA_STATUSCODE_GOOD) {
logger->log_warn("No node id in response for {}", path.c_str());
return UA_STATUSCODE_BADNODATAAVAILABLE;
}
bool found_data = 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) {
found_data = true;
UA_NodeId resultId;
UA_NodeId_copy(&res.targets[j].targetId.nodeId, &resultId);
found_node_ids.push_back(resultId);
}
}
if (found_data) {
logger->log_debug("Found {} nodes for path {}", found_node_ids.size(), path.c_str());
return UA_STATUSCODE_GOOD;
} else {
logger->log_warn("No node id found for path {}", path.c_str());
return UA_STATUSCODE_BADNODATAAVAILABLE;
}
}
template<typename T>
UA_StatusCode Client::add_node(const UA_NodeId parent_node_id, const UA_NodeId target_node_id, const UA_UInt32 ref_type_id, std::string_view browse_name, T value, UA_NodeId *received_node_id) {
UA_VariableAttributes attr = UA_VariableAttributes_default;
add_value_to_variant(&attr.value, value);
char local[6] = "en-US"; // NOLINT(cppcoreguidelines-avoid-c-arrays)
attr.displayName = UA_LOCALIZEDTEXT(local, const_cast<char*>(browse_name.data()));
UA_StatusCode sc = UA_Client_addVariableNode(client_,
target_node_id,
parent_node_id,
UA_NODEID_NUMERIC(0, ref_type_id),
UA_QUALIFIEDNAME(target_node_id.namespaceIndex, const_cast<char*>(browse_name.data())),
UA_NODEID_NULL,
attr, received_node_id);
UA_Variant_clear(&attr.value);
return sc;
}
template<typename T>
UA_StatusCode Client::update_node(const UA_NodeId node_id, T value) {
UA_Variant *variant = UA_Variant_new();
add_value_to_variant(variant, value);
UA_StatusCode sc = UA_Client_writeValueAttribute(client_, node_id, variant);
UA_Variant_delete(variant);
return sc;
}
std::unique_ptr<Client> Client::createClient(const std::shared_ptr<core::logging::Logger>& logger, const std::string& application_uri,
const std::vector<char>& cert_buffer, const std::vector<char>& key_buffer,
const std::vector<std::vector<char>>& trust_buffers) {
try {
return ClientPtr(new Client(logger, application_uri, cert_buffer, key_buffer, trust_buffers));
} catch (const std::exception& exception) {
logger->log_error("Failed to create client: {}", exception.what());
}
return nullptr;
}
template UA_StatusCode Client::update_node<int64_t>(const UA_NodeId node_id, int64_t value);
template UA_StatusCode Client::update_node<uint64_t>(const UA_NodeId node_id, uint64_t value);
template UA_StatusCode Client::update_node<int32_t>(const UA_NodeId node_id, int32_t value);
template UA_StatusCode Client::update_node<uint32_t>(const UA_NodeId node_id, uint32_t value);
template UA_StatusCode Client::update_node<float>(const UA_NodeId node_id, float value);
template UA_StatusCode Client::update_node<double>(const UA_NodeId node_id, double value);
template UA_StatusCode Client::update_node<bool>(const UA_NodeId node_id, bool value);
template UA_StatusCode Client::update_node<const char *>(const UA_NodeId node_id, const char * value);
template UA_StatusCode Client::update_node<std::string>(const UA_NodeId node_id, std::string value);
template UA_StatusCode Client::add_node<int64_t>(const UA_NodeId parent_node_id, const UA_NodeId target_node_id, const UA_UInt32 ref_type_id, std::string_view browse_name,
int64_t value, UA_NodeId *received_node_id);
template UA_StatusCode Client::add_node<uint64_t>(const UA_NodeId parent_node_id, const UA_NodeId target_node_id, const UA_UInt32 ref_type_id, std::string_view browse_name,
uint64_t value, UA_NodeId *received_node_id);
template UA_StatusCode Client::add_node<int32_t>(const UA_NodeId parent_node_id, const UA_NodeId target_node_id, const UA_UInt32 ref_type_id, std::string_view browse_name,
int32_t value, UA_NodeId *received_node_id);
template UA_StatusCode Client::add_node<uint32_t>(const UA_NodeId parent_node_id, const UA_NodeId target_node_id, const UA_UInt32 ref_type_id, std::string_view browse_name,
uint32_t value, UA_NodeId *received_node_id);
template UA_StatusCode Client::add_node<float>(const UA_NodeId parent_node_id, const UA_NodeId target_node_id, const UA_UInt32 ref_type_id, std::string_view browse_name,
float value, UA_NodeId *received_node_id);
template UA_StatusCode Client::add_node<double>(const UA_NodeId parent_node_id, const UA_NodeId target_node_id, const UA_UInt32 ref_type_id, std::string_view browse_name,
double value, UA_NodeId *received_node_id);
template UA_StatusCode Client::add_node<bool>(const UA_NodeId parent_node_id, const UA_NodeId target_node_id, const UA_UInt32 ref_type_id, std::string_view browse_name,
bool value, UA_NodeId *received_node_id);
template UA_StatusCode Client::add_node<const char *>(const UA_NodeId parent_node_id, const UA_NodeId target_node_id, const UA_UInt32 ref_type_id, std::string_view browse_name,
const char * value, UA_NodeId *received_node_id);
template UA_StatusCode Client::add_node<std::string>(const UA_NodeId parent_node_id, const UA_NodeId target_node_id, const UA_UInt32 ref_type_id, std::string_view browse_name,
std::string value, UA_NodeId *received_node_id);
std::string nodeValue2String(const NodeData& nd) {
std::string ret_val;
switch (nd.data_type_id) {
case UA_DATATYPEKIND_STRING:
case UA_DATATYPEKIND_LOCALIZEDTEXT:
case UA_DATATYPEKIND_BYTESTRING: {
UA_String value = *reinterpret_cast<UA_String *>(nd.var_->data);
ret_val = std::string(reinterpret_cast<const char *>(value.data), value.length);
break;
}
case UA_DATATYPEKIND_BOOLEAN: {
bool b = false;
memcpy(&b, nd.data.data(), sizeof(bool));
ret_val = b ? "True" : "False";
break;
}
case UA_DATATYPEKIND_SBYTE: {
int8_t i8t = 0;
memcpy(&i8t, nd.data.data(), sizeof(i8t));
ret_val = std::to_string(i8t);
break;
}
case UA_DATATYPEKIND_BYTE: {
uint8_t ui8t = 0;
memcpy(&ui8t, nd.data.data(), sizeof(ui8t));
ret_val = std::to_string(ui8t);
break;
}
case UA_DATATYPEKIND_INT16: {
int16_t i16t = 0;
memcpy(&i16t, nd.data.data(), sizeof(i16t));
ret_val = std::to_string(i16t);
break;
}
case UA_DATATYPEKIND_UINT16: {
uint16_t ui16t = 0;
memcpy(&ui16t, nd.data.data(), sizeof(ui16t));
ret_val = std::to_string(ui16t);
break;
}
case UA_DATATYPEKIND_INT32: {
int32_t i32t = 0;
memcpy(&i32t, nd.data.data(), sizeof(i32t));
ret_val = std::to_string(i32t);
break;
}
case UA_DATATYPEKIND_UINT32: {
uint32_t ui32t = 0;
memcpy(&ui32t, nd.data.data(), sizeof(ui32t));
ret_val = std::to_string(ui32t);
break;
}
case UA_DATATYPEKIND_INT64: {
int64_t i64t = 0;
memcpy(&i64t, nd.data.data(), sizeof(i64t));
ret_val = std::to_string(i64t);
break;
}
case UA_DATATYPEKIND_UINT64: {
uint64_t ui64t = 0;
memcpy(&ui64t, nd.data.data(), sizeof(ui64t));
ret_val = std::to_string(ui64t);
break;
}
case UA_DATATYPEKIND_FLOAT: {
if (sizeof(float) == 4 && std::numeric_limits<float>::is_iec559) {
float f = 0;
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_DATATYPEKIND_DOUBLE: {
if (sizeof(double) == 8 && std::numeric_limits<double>::is_iec559) {
double d = 0;
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_DATATYPEKIND_DATETIME: {
UA_DateTime dt = 0;
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 {charBuf.data(), gsl::narrow<std::size_t>(sz)};
}
void logFunc(void *context, UA_LogLevel level, UA_LogCategory /*category*/, const char *msg, va_list args) {
std::array<char, 1024> buffer{};
(void)vsnprintf(buffer.data(), buffer.size(), msg, args);
auto loggerPtr = reinterpret_cast<core::logging::Logger*>(context);
loggerPtr->log_string(MapOPCLogLevel(level), buffer.data());
}
std::optional<UA_UInt32> mapOpcReferenceType(const std::string& ref_type) {
if (ref_type == "Organizes") {
return UA_NS0ID_ORGANIZES;
} else if (ref_type == "HasComponent") {
return UA_NS0ID_HASCOMPONENT;
} else if (ref_type == "HasProperty") {
return UA_NS0ID_HASPROPERTY;
} else if (ref_type == "HasSubtype") {
return UA_NS0ID_HASSUBTYPE;
}
return std::nullopt;
}
} // namespace org::apache::nifi::minifi::opc