Revision control

++ // Copyright (C) 2017 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qopen62541backend.h"
++ #include "qopen62541client.h"
++ #include "qopen62541node.h"
++ #include "qopen62541subscription.h"
++ #include "qopen62541utils.h"
++ #include "qopen62541valueconverter.h"
++ #include "qopen62541utils.h"
++ #include <private/qopcuanode_p.h>
++
++ #include "qopcuacontentfilterelementresult.h"
++
++ #include <QtCore/qloggingcategory.h>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_OPEN62541)
++
++ static void monitoredValueHandler(UA_Client *client, UA_UInt32 subId, void *subContext, UA_UInt32 monId, void *monContext, UA_DataValue *value)
++ {
++ Q_UNUSED(client)
++ Q_UNUSED(subId)
++ Q_UNUSED(subContext)
++ QOpen62541Subscription *subscription = static_cast<QOpen62541Subscription *>(monContext);
++ subscription->monitoredValueUpdated(monId, value);
++ }
++
++ static void stateChangeHandler(UA_Client *client, UA_UInt32 subId, void *subContext, UA_StatusChangeNotification *notification)
++ {
++ Q_UNUSED(client);
++ Q_UNUSED(subId);
++
++ if (notification->status != UA_STATUSCODE_BADTIMEOUT)
++ return;
++
++ QOpen62541Subscription *sub = static_cast<QOpen62541Subscription *>(subContext);
++ sub->sendTimeoutNotification();
++ }
++
++ static void eventHandler(UA_Client *client, UA_UInt32 subId, void *subContext, UA_UInt32 monId, void *monContext,
++ UA_KeyValueMap eventFields)
++ {
++ Q_UNUSED(client);
++ Q_UNUSED(subId);
++ Q_UNUSED(subContext);
++
++ QOpen62541Subscription *subscription = static_cast<QOpen62541Subscription *>(monContext);
++
++ QVariantList list;
++ for (size_t i = 0; i < eventFields.mapSize; ++i)
++ list.append(QOpen62541ValueConverter::toQVariant(eventFields.map[i].value));
++ subscription->eventReceived(monId, list);
++ }
++
++ QOpen62541Subscription::QOpen62541Subscription(Open62541AsyncBackend *backend, const QOpcUaMonitoringParameters &settings)
++ : m_backend(backend)
++ , m_interval(settings.publishingInterval())
++ , m_subscriptionId(0)
++ , m_lifetimeCount(settings.lifetimeCount() ? settings.lifetimeCount() : UA_CreateSubscriptionRequest_default().requestedLifetimeCount)
++ , m_maxKeepaliveCount(settings.maxKeepAliveCount() ? settings.maxKeepAliveCount() : UA_CreateSubscriptionRequest_default().requestedMaxKeepAliveCount)
++ , m_shared(settings.subscriptionType())
++ , m_priority(settings.priority())
++ , m_maxNotificationsPerPublish(settings.maxNotificationsPerPublish())
++ , m_clientHandle(0)
++ , m_timeout(false)
++ {
++ }
++
++ QOpen62541Subscription::~QOpen62541Subscription()
++ {
++ removeOnServer();
++ }
++
++ UA_UInt32 QOpen62541Subscription::createOnServer()
++ {
++ UA_CreateSubscriptionRequest req = UA_CreateSubscriptionRequest_default();
++ req.requestedPublishingInterval = m_interval;
++ req.requestedLifetimeCount = m_lifetimeCount;
++ req.requestedMaxKeepAliveCount = m_maxKeepaliveCount;
++ req.priority = m_priority;
++ req.maxNotificationsPerPublish = m_maxNotificationsPerPublish;
++ UA_CreateSubscriptionResponse res = UA_Client_Subscriptions_create(m_backend->m_uaclient, req, this, stateChangeHandler, nullptr);
++
++ if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not create subscription with interval" << m_interval << UA_StatusCode_name(res.responseHeader.serviceResult);
++ return 0;
++ }
++
++ m_subscriptionId = res.subscriptionId;
++ m_maxKeepaliveCount = res.revisedMaxKeepAliveCount;
++ m_lifetimeCount = res.revisedLifetimeCount;
++ m_interval = res.revisedPublishingInterval;
++ return m_subscriptionId;
++ }
++
++ bool QOpen62541Subscription::removeOnServer()
++ {
++ UA_StatusCode res = UA_STATUSCODE_GOOD;
++ if (m_subscriptionId) {
++ res = UA_Client_Subscriptions_deleteSingle(m_backend->m_uaclient, m_subscriptionId);
++ m_subscriptionId = 0;
++ }
++
++ for (auto it : std::as_const(m_itemIdToItemMapping)) {
++ QOpcUaMonitoringParameters s;
++ s.setStatusCode(m_timeout ? QOpcUa::UaStatusCode::BadTimeout : QOpcUa::UaStatusCode::BadDisconnect);
++ emit m_backend->monitoringEnableDisable(it->handle, it->attr, false, s);
++ }
++
++ qDeleteAll(m_itemIdToItemMapping);
++
++ m_itemIdToItemMapping.clear();
++ m_nodeHandleToItemMapping.clear();
++
++ return (res == UA_STATUSCODE_GOOD) ? true : false;
++ }
++
++ void QOpen62541Subscription::modifyMonitoring(quint64 handle, QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value)
++ {
++ MonitoredItem *monItem = getItemForAttribute(handle, attr);
++ if (!monItem) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify parameter" << item << "there are no monitored items";
++ QOpcUaMonitoringParameters p;
++ p.setStatusCode(QOpcUa::UaStatusCode::BadAttributeIdInvalid);
++ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
++ return;
++ }
++
++ QOpcUaMonitoringParameters p = monItem->parameters;
++ p.setStatusCode(QOpcUa::UaStatusCode::BadNotImplemented);
++
++ // SetPublishingMode service
++ if (item == QOpcUaMonitoringParameters::Parameter::PublishingEnabled) {
++ if (value.metaType().id() != QMetaType::Bool) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "New value for PublishingEnabled is not a boolean";
++ p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
++ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
++ return;
++ }
++
++ UA_SetPublishingModeRequest req;
++ UA_SetPublishingModeRequest_init(&req);
++ UaDeleter<UA_SetPublishingModeRequest> requestDeleter(&req, UA_SetPublishingModeRequest_clear);
++ req.publishingEnabled = value.toBool();
++ req.subscriptionIdsSize = 1;
++ req.subscriptionIds = UA_UInt32_new();
++ *req.subscriptionIds = m_subscriptionId;
++ UA_SetPublishingModeResponse res = UA_Client_Subscriptions_setPublishingMode(m_backend->m_uaclient, req);
++ UaDeleter<UA_SetPublishingModeResponse> responseDeleter(&res, UA_SetPublishingModeResponse_clear);
++
++ if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to set publishing mode:" << res.responseHeader.serviceResult;
++ p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult));
++ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
++ return;
++ }
++
++ if (res.resultsSize && res.results[0] == UA_STATUSCODE_GOOD)
++ p.setPublishingEnabled(value.toBool());
++
++ p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.results[0]));
++ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
++
++ return;
++ }
++
++ // SetMonitoringMode service
++ if (item == QOpcUaMonitoringParameters::Parameter::MonitoringMode) {
++ if (value.userType() != QMetaType::fromType<QOpcUaMonitoringParameters::MonitoringMode>().id()) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "New value for MonitoringMode is not a monitoring mode";
++ p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
++ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
++ return;
++ }
++
++ UA_SetMonitoringModeRequest req;
++ UA_SetMonitoringModeRequest_init(&req);
++ UaDeleter<UA_SetMonitoringModeRequest> requestDeleter(&req, UA_SetMonitoringModeRequest_clear);
++ req.monitoringMode = static_cast<UA_MonitoringMode>(value.value<QOpcUaMonitoringParameters::MonitoringMode>());
++ req.monitoredItemIdsSize = 1;
++ req.monitoredItemIds = UA_UInt32_new();
++ *req.monitoredItemIds = monItem->monitoredItemId;
++ req.subscriptionId = m_subscriptionId;
++ UA_SetMonitoringModeResponse res = UA_Client_MonitoredItems_setMonitoringMode(m_backend->m_uaclient, req);
++ UaDeleter<UA_SetMonitoringModeResponse> responseDeleter(&res, UA_SetMonitoringModeResponse_clear);
++
++ if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to set monitoring mode:" << res.responseHeader.serviceResult;
++ p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult));
++ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
++ return;
++ }
++
++ if (res.resultsSize && res.results[0] == UA_STATUSCODE_GOOD)
++ p.setMonitoringMode(value.value<QOpcUaMonitoringParameters::MonitoringMode>());
++
++ p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.results[0]));
++ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
++ return;
++ }
++
++ // SetTriggering service
++ if (item == QOpcUaMonitoringParameters::Parameter::TriggeredItemIds) {
++ if (!value.canConvert<QSet<quint32>>()) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify triggering item id, value is not a set of quint32";
++ p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
++ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
++ return;
++ }
++
++ auto triggeredItemIds = value.value<QSet<quint32>>();
++
++ UA_SetTriggeringRequest triggeringReq;
++ UA_SetTriggeringRequest_init(&triggeringReq);
++ triggeringReq.subscriptionId = m_subscriptionId;
++ triggeringReq.triggeringItemId = monItem->monitoredItemId;
++
++ QList<quint32> itemsToRemove;
++ QList<quint32> itemsToAdd;
++
++ if (triggeredItemIds.isEmpty() && !monItem->parameters.triggeredItemIds().isEmpty()) {
++ itemsToRemove = monItem->parameters.triggeredItemIds().values();
++ } else if (!triggeredItemIds.isEmpty()) {
++ itemsToAdd = triggeredItemIds.values();
++ itemsToRemove = monItem->parameters.triggeredItemIds().subtract(triggeredItemIds).values();
++ }
++
++ if (itemsToAdd.isEmpty() && itemsToRemove.isEmpty()) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Nothing to do for TriggeredItemIds";
++ p.setStatusCode(QOpcUa::UaStatusCode::Good);
++ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
++ return;
++ }
++
++ if (!itemsToAdd.isEmpty()) {
++ triggeringReq.linksToAddSize = itemsToAdd.size();
++ triggeringReq.linksToAdd = itemsToAdd.data();
++ }
++
++ if (!itemsToRemove.isEmpty()) {
++ triggeringReq.linksToRemoveSize = itemsToRemove.size();
++ triggeringReq.linksToRemove = itemsToRemove.data();
++ }
++
++ auto triggeringRes = UA_Client_MonitoredItems_setTriggering(m_backend->m_uaclient, triggeringReq);
++
++ QHash<quint32, QOpcUa::UaStatusCode> failedItems;
++
++ if (triggeringRes.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Modifying TriggeredItemIds failed with"
++ << UA_StatusCode_name(triggeringRes.responseHeader.serviceResult);
++
++ for (const auto &entry : std::as_const(itemsToAdd))
++ failedItems[entry] = QOpcUa::UaStatusCode(triggeringRes.responseHeader.serviceResult);
++ p.setFailedTriggeredItemsStatus(failedItems);
++ p.setStatusCode(QOpcUa::UaStatusCode(triggeringRes.responseHeader.serviceResult));
++ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
++ UA_SetTriggeringResponse_clear(&triggeringRes);
++ return;
++ }
++
++ for (size_t i = 0; i < triggeringRes.addResultsSize; ++i) {
++ if (triggeringRes.addResults[i] != UA_STATUSCODE_GOOD) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add trigger link" << triggeringReq.triggeringItemId
++ << "->" << itemsToAdd.at(i) << "on subscription" << m_subscriptionId
++ << "with status"
++ << UA_StatusCode_name(triggeringRes.addResults[i]);
++ failedItems.insert(itemsToAdd.at(i), QOpcUa::UaStatusCode(triggeringRes.addResults[i]));
++ triggeredItemIds.remove(itemsToAdd.at(i));
++ }
++ }
++
++ UA_SetTriggeringResponse_clear(&triggeringRes);
++
++ monItem->parameters.setTriggeredItemIds(triggeredItemIds);
++ p.setStatusCode(QOpcUa::UaStatusCode::Good);
++ p.setTriggeredItemIds(triggeredItemIds);
++ p.setFailedTriggeredItemsStatus(failedItems);
++ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
++
++ return;
++ }
++
++ if (modifySubscriptionParameters(handle, attr, item, value))
++ return;
++ if (modifyMonitoredItemParameters(handle, attr, item, value))
++ return;
++
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Modifying" << item << "is not implemented";
++ p.setStatusCode(QOpcUa::UaStatusCode::BadNotImplemented);
++ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
++ }
++
++ bool QOpen62541Subscription::addAttributeMonitoredItem(quint64 handle, QOpcUa::NodeAttribute attr, const UA_NodeId &id, QOpcUaMonitoringParameters settings)
++ {
++ UA_MonitoredItemCreateRequest req;
++ UA_MonitoredItemCreateRequest_init(&req);
++ UaDeleter<UA_MonitoredItemCreateRequest> requestDeleter(&req, UA_MonitoredItemCreateRequest_clear);
++ req.itemToMonitor.attributeId = QOpen62541ValueConverter::toUaAttributeId(attr);
++ UA_NodeId_copy(&id, &(req.itemToMonitor.nodeId));
++ if (settings.indexRange().size())
++ QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(settings.indexRange(), &req.itemToMonitor.indexRange);
++ req.monitoringMode = static_cast<UA_MonitoringMode>(settings.monitoringMode());
++ req.requestedParameters.samplingInterval = qFuzzyIsNull(settings.samplingInterval()) ? m_interval : settings.samplingInterval();
++ req.requestedParameters.queueSize = settings.queueSize() == 0 ? 1 : settings.queueSize();
++ req.requestedParameters.discardOldest = settings.discardOldest();
++ req.requestedParameters.clientHandle = ++m_clientHandle;
++
++ if (settings.filter().isValid()) {
++ UA_ExtensionObject filter = createFilter(settings.filter());
++ if (filter.content.decoded.data)
++ req.requestedParameters.filter = filter;
++ else {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not create monitored item, filter creation failed";
++ QOpcUaMonitoringParameters s;
++ s.setStatusCode(QOpcUa::UaStatusCode::BadInternalError);
++ emit m_backend->monitoringEnableDisable(handle, attr, true, s);
++ return false;
++ }
++ }
++
++ UA_MonitoredItemCreateResult res;
++ UaDeleter<UA_MonitoredItemCreateResult> resultDeleter(&res, UA_MonitoredItemCreateResult_clear);
++
++ if (attr == QOpcUa::NodeAttribute::EventNotifier && settings.filter().canConvert<QOpcUaMonitoringParameters::EventFilter>())
++ res = UA_Client_MonitoredItems_createEvent(m_backend->m_uaclient, m_subscriptionId,
++ UA_TIMESTAMPSTORETURN_BOTH, req, this, eventHandler, nullptr);
++ else
++ res = UA_Client_MonitoredItems_createDataChange(m_backend->m_uaclient, m_subscriptionId, UA_TIMESTAMPSTORETURN_BOTH, req, this, monitoredValueHandler, nullptr);
++
++ if (res.statusCode != UA_STATUSCODE_GOOD) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not add monitored item for" << attr << "of node" << Open62541Utils::nodeIdToQString(id) << ":" << UA_StatusCode_name(res.statusCode);
++ QOpcUaMonitoringParameters s;
++ s.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.statusCode));
++ emit m_backend->monitoringEnableDisable(handle, attr, true, s);
++ return false;
++ }
++
++ QSet<quint32> successfulTriggerLinks;
++ QHash<quint32, QOpcUa::UaStatusCode> failedTriggerLinks;
++ if (!settings.triggeredItemIds().isEmpty()) {
++ auto triggeredItems = settings.triggeredItemIds().values();
++
++ UA_SetTriggeringRequest req;
++ UA_SetTriggeringRequest_init(&req);
++ req.subscriptionId = m_subscriptionId;
++ req.triggeringItemId = res.monitoredItemId;
++ req.linksToAddSize = triggeredItems.size();
++ req.linksToAdd = triggeredItems.data();
++
++ auto triggeringRes = UA_Client_MonitoredItems_setTriggering(m_backend->m_uaclient, req);
++
++ if (triggeringRes.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not set triggering item it for" << attr << "of node" << Open62541Utils::nodeIdToQString(id) << ":"
++ << UA_StatusCode_name(triggeringRes.responseHeader.serviceResult);
++
++ // Remove the new monitored item
++ UA_DeleteMonitoredItemsRequest deleteRequest;
++ UA_DeleteMonitoredItemsRequest_init(&deleteRequest);
++ deleteRequest.subscriptionId = m_subscriptionId;
++ deleteRequest.monitoredItemIdsSize = 1;
++ deleteRequest.monitoredItemIds = &res.monitoredItemId;
++ UA_Client_MonitoredItems_delete(m_backend->m_uaclient, deleteRequest);
++
++ for (const auto &entry : std::as_const(triggeredItems))
++ failedTriggerLinks.insert(entry, QOpcUa::UaStatusCode(triggeringRes.responseHeader.serviceResult));
++
++ QOpcUaMonitoringParameters s;
++ s.setStatusCode(static_cast<QOpcUa::UaStatusCode>(triggeringRes.responseHeader.serviceResult));
++ s.setFailedTriggeredItemsStatus(failedTriggerLinks);
++ emit m_backend->monitoringEnableDisable(handle, attr, true, s);
++
++ UA_SetTriggeringResponse_clear(&triggeringRes);
++
++ return false;
++ }
++
++ for (size_t i = 0; i < triggeringRes.addResultsSize; ++i) {
++ if (triggeringRes.addResults[i] == UA_STATUSCODE_GOOD) {
++ successfulTriggerLinks.insert(triggeredItems.at(i));
++ } else {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add trigger link" << res.monitoredItemId
++ << "->" << triggeredItems.at(i) << "on subscription" << m_subscriptionId
++ << "with status"
++ << UA_StatusCode_name(triggeringRes.addResults[i]);
++ failedTriggerLinks.insert(triggeredItems.at(i), QOpcUa::UaStatusCode(triggeringRes.addResults[i]));
++ }
++ }
++
++ UA_SetTriggeringResponse_clear(&triggeringRes);
++ }
++
++ MonitoredItem *temp = new MonitoredItem(handle, attr, res.monitoredItemId);
++ m_nodeHandleToItemMapping[handle][attr] = temp;
++ m_itemIdToItemMapping[res.monitoredItemId] = temp;
++
++ QOpcUaMonitoringParameters s = settings;
++ s.setSubscriptionId(m_subscriptionId);
++ s.setPublishingInterval(m_interval);
++ s.setMaxKeepAliveCount(m_maxKeepaliveCount);
++ s.setLifetimeCount(m_lifetimeCount);
++ s.setStatusCode(QOpcUa::UaStatusCode::Good);
++ s.setSamplingInterval(res.revisedSamplingInterval);
++ s.setQueueSize(res.revisedQueueSize);
++ s.setMonitoredItemId(res.monitoredItemId);
++ s.setTriggeredItemIds(successfulTriggerLinks);
++ s.setFailedTriggeredItemsStatus(failedTriggerLinks);
++ temp->parameters = s;
++ temp->clientHandle = m_clientHandle;
++
++ if (res.filterResult.encoding >= UA_EXTENSIONOBJECT_DECODED &&
++ res.filterResult.content.decoded.type == &UA_TYPES[UA_TYPES_EVENTFILTERRESULT])
++ s.setFilterResult(convertEventFilterResult(&res.filterResult));
++ else
++ s.clearFilterResult();
++
++ emit m_backend->monitoringEnableDisable(handle, attr, true, s);
++
++ return true;
++ }
++
++ bool QOpen62541Subscription::removeAttributeMonitoredItem(quint64 handle, QOpcUa::NodeAttribute attr)
++ {
++ MonitoredItem *item = getItemForAttribute(handle, attr);
++ if (!item) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "There is no monitored item for this attribute";
++ QOpcUaMonitoringParameters s;
++ s.setStatusCode(QOpcUa::UaStatusCode::BadMonitoredItemIdInvalid);
++ emit m_backend->monitoringEnableDisable(handle, attr, false, s);
++ return false;
++ }
++
++ UA_StatusCode res = UA_Client_MonitoredItems_deleteSingle(m_backend->m_uaclient, m_subscriptionId, item->monitoredItemId);
++ if (res != UA_STATUSCODE_GOOD)
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not remove monitored item" << item->monitoredItemId << "from subscription" << m_subscriptionId << ":" << UA_StatusCode_name(res);
++
++ m_itemIdToItemMapping.remove(item->monitoredItemId);
++ const auto it = m_nodeHandleToItemMapping.find(handle);
++ it->remove(attr);
++ if (it->empty())
++ m_nodeHandleToItemMapping.remove(it.key());
++
++ delete item;
++
++ QOpcUaMonitoringParameters s;
++ s.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res));
++ emit m_backend->monitoringEnableDisable(handle, attr, false, s);
++
++ return true;
++ }
++
++ void QOpen62541Subscription::monitoredValueUpdated(UA_UInt32 monId, UA_DataValue *value)
++ {
++ auto item = m_itemIdToItemMapping.constFind(monId);
++ if (item == m_itemIdToItemMapping.constEnd())
++ return;
++ QOpcUaReadResult res;
++
++ if (!value || value == UA_EMPTY_ARRAY_SENTINEL) {
++ res.setStatusCode(QOpcUa::UaStatusCode::Good);
++ emit m_backend->dataChangeOccurred(item.value()->handle, res);
++ return;
++ }
++
++ res.setValue(QOpen62541ValueConverter::toQVariant(value->value));
++ res.setAttribute(item.value()->attr);
++ if (value->hasServerTimestamp)
++ res.setServerTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(&value->serverTimestamp));
++ if (value->hasSourceTimestamp)
++ res.setSourceTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(&value->sourceTimestamp));
++ res.setStatusCode(value->hasStatus ? QOpcUa::UaStatusCode(value->status) : QOpcUa::UaStatusCode::Good);
++ emit m_backend->dataChangeOccurred(item.value()->handle, res);
++ }
++
++ void QOpen62541Subscription::sendTimeoutNotification()
++ {
++ QList<std::pair<quint64, QOpcUa::NodeAttribute>> items;
++ for (const auto &it : std::as_const(m_nodeHandleToItemMapping)) {
++ for (auto item : it) {
++ items.push_back({item->handle, item->attr});
++ }
++ }
++ emit timeout(this, items);
++ m_timeout = true;
++ }
++
++ void QOpen62541Subscription::eventReceived(UA_UInt32 monId, QVariantList list)
++ {
++ auto item = m_itemIdToItemMapping.constFind(monId);
++ if (item == m_itemIdToItemMapping.constEnd())
++ return;
++ emit m_backend->eventOccurred(item.value()->handle, list);
++ }
++
++ double QOpen62541Subscription::interval() const
++ {
++ return m_interval;
++ }
++
++ UA_UInt32 QOpen62541Subscription::subscriptionId() const
++ {
++ return m_subscriptionId;
++ }
++
++ int QOpen62541Subscription::monitoredItemsCount() const
++ {
++ return m_itemIdToItemMapping.size();
++ }
++
++ QOpcUaMonitoringParameters::SubscriptionType QOpen62541Subscription::shared() const
++ {
++ return m_shared;
++ }
++
++ QOpen62541Subscription::MonitoredItem *QOpen62541Subscription::getItemForAttribute(quint64 nodeHandle, QOpcUa::NodeAttribute attr)
++ {
++ auto nodeEntry = m_nodeHandleToItemMapping.constFind(nodeHandle);
++
++ if (nodeEntry == m_nodeHandleToItemMapping.constEnd())
++ return nullptr;
++
++ auto item = nodeEntry->constFind(attr);
++ if (item == nodeEntry->constEnd())
++ return nullptr;
++
++ return item.value();
++ }
++
++ UA_ExtensionObject QOpen62541Subscription::createFilter(const QVariant &filterData)
++ {
++ UA_ExtensionObject obj;
++ UA_ExtensionObject_init(&obj);
++
++ if (filterData.canConvert<QOpcUaMonitoringParameters::DataChangeFilter>()) {
++ createDataChangeFilter(filterData.value<QOpcUaMonitoringParameters::DataChangeFilter>(), &obj);
++ return obj;
++ }
++
++ if (filterData.canConvert<QOpcUaMonitoringParameters::EventFilter>()) {
++ QOpen62541ValueConverter::eventFilterToUa(filterData.value<QOpcUaMonitoringParameters::EventFilter>(), &obj);
++ return obj;
++ }
++
++ if (filterData.isValid())
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not create filter, invalid input.";
++
++ return obj;
++ }
++
++ void QOpen62541Subscription::createDataChangeFilter(const QOpcUaMonitoringParameters::DataChangeFilter &filter, UA_ExtensionObject *out)
++ {
++ UA_DataChangeFilter *uaFilter = UA_DataChangeFilter_new();
++ uaFilter->deadbandType = static_cast<UA_UInt32>(filter.deadbandType());
++ uaFilter->deadbandValue = filter.deadbandValue();
++ uaFilter->trigger = static_cast<UA_DataChangeTrigger>(filter.trigger());
++ out->encoding = UA_EXTENSIONOBJECT_DECODED;
++ out->content.decoded.type = &UA_TYPES[UA_TYPES_DATACHANGEFILTER];
++ out->content.decoded.data = uaFilter;
++ }
++
++ QOpcUaEventFilterResult QOpen62541Subscription::convertEventFilterResult(UA_ExtensionObject *obj)
++ {
++ QOpcUaEventFilterResult result;
++
++ if (!obj)
++ return result;
++
++ if (obj->encoding == UA_EXTENSIONOBJECT_DECODED && obj->content.decoded.type == &UA_TYPES[UA_TYPES_EVENTFILTERRESULT]) {
++ UA_EventFilterResult *filterResult = static_cast<UA_EventFilterResult *>(obj->content.decoded.data);
++
++ for (size_t i = 0; i < filterResult->selectClauseResultsSize; ++i)
++ result.selectClauseResultsRef().append(static_cast<QOpcUa::UaStatusCode>(filterResult->selectClauseResults[i]));
++
++ for (size_t i = 0; i < filterResult->whereClauseResult.elementResultsSize; ++i) {
++ QOpcUaContentFilterElementResult temp;
++ temp.setStatusCode(static_cast<QOpcUa::UaStatusCode>(filterResult->whereClauseResult.elementResults[i].statusCode));
++ for (size_t j = 0; j < filterResult->whereClauseResult.elementResults[i].operandStatusCodesSize; ++j)
++ temp.operandStatusCodesRef().append(static_cast<QOpcUa::UaStatusCode>(
++ filterResult->whereClauseResult.elementResults[i].operandStatusCodes[j]));
++ result.whereClauseResultsRef().append(temp);
++ }
++ }
++
++ return result;
++ }
++
++ bool QOpen62541Subscription::modifySubscriptionParameters(quint64 nodeHandle, QOpcUa::NodeAttribute attr, const QOpcUaMonitoringParameters::Parameter &item, const QVariant &value)
++ {
++ UA_ModifySubscriptionRequest req;
++ UA_ModifySubscriptionRequest_init(&req);
++ req.subscriptionId = m_subscriptionId;
++ req.requestedPublishingInterval = m_interval;
++ req.requestedLifetimeCount = m_lifetimeCount;
++ req.requestedMaxKeepAliveCount = m_maxKeepaliveCount;
++ req.maxNotificationsPerPublish = m_maxNotificationsPerPublish;
++
++ bool match = true;
++
++ switch (item) {
++ case QOpcUaMonitoringParameters::Parameter::PublishingInterval: {
++ bool ok;
++ req.requestedPublishingInterval = value.toDouble(&ok);
++ if (!ok) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify PublishingInterval, value is not a double";
++ QOpcUaMonitoringParameters p;
++ p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
++ emit m_backend->monitoringStatusChanged(nodeHandle, attr, item, p);
++ return true;
++ }
++ break;
++ }
++ case QOpcUaMonitoringParameters::Parameter::LifetimeCount: {
++ bool ok;
++ req.requestedLifetimeCount = value.toUInt(&ok);
++ if (!ok) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify LifetimeCount, value is not an integer";
++ QOpcUaMonitoringParameters p;
++ p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
++ emit m_backend->monitoringStatusChanged(nodeHandle, attr, item, p);
++ return true;
++ }
++ break;
++ }
++ case QOpcUaMonitoringParameters::Parameter::MaxKeepAliveCount: {
++ bool ok;
++ req.requestedMaxKeepAliveCount = value.toUInt(&ok);
++ if (!ok) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify MaxKeepAliveCount, value is not an integer";
++ QOpcUaMonitoringParameters p;
++ p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
++ emit m_backend->monitoringStatusChanged(nodeHandle, attr, item, p);
++ return true;
++ }
++ break;
++ }
++ case QOpcUaMonitoringParameters::Parameter::Priority: {
++ bool ok;
++ req.priority = value.toUInt(&ok);
++ if (!ok) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify Priority, value is not an integer";
++ QOpcUaMonitoringParameters p;
++ p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
++ emit m_backend->monitoringStatusChanged(nodeHandle, attr, item, p);
++ return true;
++ }
++ break;
++ }
++ case QOpcUaMonitoringParameters::Parameter::MaxNotificationsPerPublish: {
++ bool ok;
++ req.maxNotificationsPerPublish = value.toUInt(&ok);
++ if (!ok) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify MaxNotificationsPerPublish, value is not an integer";
++ QOpcUaMonitoringParameters p;
++ p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
++ emit m_backend->monitoringStatusChanged(nodeHandle, attr, item, p);
++ return true;
++ }
++ break;
++ }
++ default:
++ match = false;
++ break;
++ }
++
++ if (match) {
++ UA_ModifySubscriptionResponse res = UA_Client_Subscriptions_modify(m_backend->m_uaclient, req);
++
++ if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
++ QOpcUaMonitoringParameters p;
++ p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult));
++ emit m_backend->monitoringStatusChanged(nodeHandle, attr, item, p);
++ } else {
++ QOpcUaMonitoringParameters::Parameters changed = item;
++ if (!QtPrivate::fuzzyCompare(res.revisedPublishingInterval, m_interval))
++ changed |= QOpcUaMonitoringParameters::Parameter::PublishingInterval;
++ if (res.revisedLifetimeCount != m_lifetimeCount)
++ changed |= QOpcUaMonitoringParameters::Parameter::LifetimeCount;
++ if (res.revisedMaxKeepAliveCount != m_maxKeepaliveCount)
++ changed |= QOpcUaMonitoringParameters::Parameter::MaxKeepAliveCount;
++
++ m_lifetimeCount = res.revisedLifetimeCount;
++ m_maxKeepaliveCount = res.revisedMaxKeepAliveCount;
++ m_interval = res.revisedPublishingInterval;
++ if (item == QOpcUaMonitoringParameters::Parameter::Priority)
++ m_priority = value.toUInt();
++ if (item == QOpcUaMonitoringParameters::Parameter::MaxNotificationsPerPublish)
++ m_maxNotificationsPerPublish = value.toUInt();
++
++ QOpcUaMonitoringParameters p;
++ p.setStatusCode(QOpcUa::UaStatusCode::Good);
++ p.setPublishingInterval(m_interval);
++ p.setLifetimeCount(m_lifetimeCount);
++ p.setMaxKeepAliveCount(m_maxKeepaliveCount);
++ p.setPriority(m_priority);
++ p.setMaxNotificationsPerPublish(m_maxNotificationsPerPublish);
++
++ for (auto it : std::as_const(m_itemIdToItemMapping))
++ emit m_backend->monitoringStatusChanged(it->handle, it->attr, changed, p);
++ }
++ return true;
++ }
++ return false;
++ }
++
++ bool QOpen62541Subscription::modifyMonitoredItemParameters(quint64 nodeHandle, QOpcUa::NodeAttribute attr, const QOpcUaMonitoringParameters::Parameter &item, const QVariant &value)
++ {
++ MonitoredItem *monItem = getItemForAttribute(nodeHandle, attr);
++ QOpcUaMonitoringParameters p = monItem->parameters;
++
++ UA_ModifyMonitoredItemsRequest req;
++ UA_ModifyMonitoredItemsRequest_init(&req);
++ UaDeleter<UA_ModifyMonitoredItemsRequest> requestDeleter(&req, UA_ModifyMonitoredItemsRequest_clear);
++ req.subscriptionId = m_subscriptionId;
++ req.itemsToModifySize = 1;
++ req.itemsToModify = UA_MonitoredItemModifyRequest_new();
++ UA_MonitoredItemModifyRequest_init(req.itemsToModify);
++ req.itemsToModify->monitoredItemId = monItem->monitoredItemId;
++ req.itemsToModify->requestedParameters.discardOldest = monItem->parameters.discardOldest();
++ req.itemsToModify->requestedParameters.queueSize = monItem->parameters.queueSize();
++ req.itemsToModify->requestedParameters.samplingInterval = monItem->parameters.samplingInterval();
++ req.itemsToModify->monitoredItemId = monItem->monitoredItemId;
++ req.itemsToModify->requestedParameters.clientHandle = monItem->clientHandle;
++
++ bool match = true;
++
++ switch (item) {
++ case QOpcUaMonitoringParameters::Parameter::DiscardOldest: {
++ if (value.metaType().id() != QMetaType::Bool) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify DiscardOldest, value is not a bool";
++ p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
++ emit m_backend->monitoringStatusChanged(nodeHandle, attr, item, p);
++ return true;
++ }
++ req.itemsToModify->requestedParameters.discardOldest = value.toBool();
++ break;
++ }
++ case QOpcUaMonitoringParameters::Parameter::QueueSize: {
++ if (value.metaType().id() != QMetaType::UInt) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify QueueSize, value is not an integer";
++ p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
++ emit m_backend->monitoringStatusChanged(nodeHandle, attr, item, p);
++ return true;
++ }
++ req.itemsToModify->requestedParameters.queueSize = value.toUInt();
++ break;
++ }
++ case QOpcUaMonitoringParameters::Parameter::SamplingInterval: {
++ if (value.metaType().id() != QMetaType::Double) {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify SamplingInterval, value is not a double";
++ p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
++ emit m_backend->monitoringStatusChanged(nodeHandle, attr, item, p);
++ return true;
++ }
++ req.itemsToModify->requestedParameters.samplingInterval = value.toDouble();
++ break;
++ }
++ case QOpcUaMonitoringParameters::Parameter::Filter: {
++ UA_ExtensionObject filter = createFilter(value);
++ if (filter.content.decoded.data)
++ req.itemsToModify->requestedParameters.filter = filter;
++ else {
++ qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Unable to modify filter, filter creation failed";
++ p.setStatusCode(QOpcUa::UaStatusCode::BadInternalError);
++ emit m_backend->monitoringStatusChanged(nodeHandle, attr, item, p);
++ return true;
++ }
++ break;
++ }
++ default:
++ match = false;
++ break;
++ }
++
++ if (match) {
++ if (item != QOpcUaMonitoringParameters::Parameter::Filter && p.filter().isValid()) {
++ UA_ExtensionObject filter = createFilter(monItem->parameters.filter());
++ if (filter.content.decoded.data)
++ req.itemsToModify->requestedParameters.filter = filter;
++ else {
++ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify monitored item, filter creation failed";
++ p.setStatusCode(QOpcUa::UaStatusCode::BadInternalError);
++ emit m_backend->monitoringStatusChanged(nodeHandle, attr, item, p);
++ return true;
++ }
++ }
++
++ UA_ModifyMonitoredItemsResponse res = UA_Client_MonitoredItems_modify(m_backend->m_uaclient, req);
++ UaDeleter<UA_ModifyMonitoredItemsResponse> responseDeleter(
++ &res, UA_ModifyMonitoredItemsResponse_clear);
++
++ if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD || res.results[0].statusCode != UA_STATUSCODE_GOOD) {
++ p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult == UA_STATUSCODE_GOOD ? res.results[0].statusCode : res.responseHeader.serviceResult));
++ emit m_backend->monitoringStatusChanged(nodeHandle, attr, item, p);
++ return true;
++ } else {
++ p.setStatusCode(QOpcUa::UaStatusCode::Good);
++ QOpcUaMonitoringParameters::Parameters changed = item;
++ if (!QtPrivate::fuzzyCompare(p.samplingInterval(), res.results[0].revisedSamplingInterval)) {
++ p.setSamplingInterval(res.results[0].revisedSamplingInterval);
++ changed |= QOpcUaMonitoringParameters::Parameter::SamplingInterval;
++ }
++ if (p.queueSize() != res.results[0].revisedQueueSize) {
++ p.setQueueSize(res.results[0].revisedQueueSize);
++ changed |= QOpcUaMonitoringParameters::Parameter::QueueSize;
++ }
++
++ if (item == QOpcUaMonitoringParameters::Parameter::DiscardOldest) {
++ p.setDiscardOldest(value.toBool());
++ changed |= QOpcUaMonitoringParameters::Parameter::DiscardOldest;
++ }
++
++ if (item == QOpcUaMonitoringParameters::Parameter::Filter) {
++ changed |= QOpcUaMonitoringParameters::Parameter::Filter;
++ if (value.canConvert<QOpcUaMonitoringParameters::DataChangeFilter>())
++ p.setFilter(value.value<QOpcUaMonitoringParameters::DataChangeFilter>());
++ else if (value.canConvert<QOpcUaMonitoringParameters::EventFilter>())
++ p.setFilter(value.value<QOpcUaMonitoringParameters::EventFilter>());
++ if (res.results[0].filterResult.content.decoded.type == &UA_TYPES[UA_TYPES_EVENTFILTERRESULT])
++ p.setFilterResult(convertEventFilterResult(&res.results[0].filterResult));
++ }
++
++ emit m_backend->monitoringStatusChanged(nodeHandle, attr, changed, p);
++
++ monItem->parameters = p;
++ }
++ return true;
++ }
++ return false;
++ }
++
++ QT_END_NAMESPACE