| 1 | // Copyright (C) 2017 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only | 
| 3 |  | 
| 4 | #include "qopen62541backend.h" | 
| 5 | #include "qopen62541node.h" | 
| 6 | #include "qopen62541utils.h" | 
| 7 | #include "qopen62541valueconverter.h" | 
| 8 | #include <private/qopcuaclient_p.h> | 
| 9 |  | 
| 10 | #include "qopcuaauthenticationinformation.h" | 
| 11 | #include <qopcuaerrorstate.h> | 
| 12 |  | 
| 13 | #include <QtCore/QDir> | 
| 14 | #include <QtCore/QFile> | 
| 15 | #include <QtCore/qloggingcategory.h> | 
| 16 | #include <QtCore/qstringlist.h> | 
| 17 | #include <QtCore/qurl.h> | 
| 18 | #include <QtCore/private/qnumeric_p.h> // for qt_saturate | 
| 19 |  | 
| 20 | #include <algorithm> | 
| 21 | #include <limits> | 
| 22 |  | 
| 23 | QT_BEGIN_NAMESPACE | 
| 24 |  | 
| 25 | Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_OPEN62541) | 
| 26 |  | 
| 27 | Open62541AsyncBackend::Open62541AsyncBackend(QOpen62541Client *parent) | 
| 28 |     : QOpcUaBackend() | 
| 29 |     , m_uaclient(nullptr) | 
| 30 |     , m_clientImpl(parent) | 
| 31 |     , m_useStateCallback(false) | 
| 32 |     , m_clientIterateInterval(50) | 
| 33 |     , m_asyncRequestTimeout(15000) | 
| 34 |     , m_clientIterateTimer(this) | 
| 35 |     , m_disconnectAfterStateChangeTimer(this) | 
| 36 |     , m_minPublishingInterval(0) | 
| 37 | { | 
| 38 |     QObject::connect(sender: &m_clientIterateTimer, signal: &QTimer::timeout, | 
| 39 |                      context: this, slot: &Open62541AsyncBackend::iterateClient); | 
| 40 |  | 
| 41 |     m_disconnectAfterStateChangeTimer.setSingleShot(true); | 
| 42 |     m_disconnectAfterStateChangeTimer.setInterval(0); | 
| 43 |  | 
| 44 |     QObject::connect(sender: &m_disconnectAfterStateChangeTimer, signal: &QTimer::timeout, | 
| 45 |                      context: this, slot: [this]() { | 
| 46 |         disconnectInternal(error: QOpcUaClient::ConnectionError); | 
| 47 |     }); | 
| 48 | } | 
| 49 |  | 
| 50 | Open62541AsyncBackend::~Open62541AsyncBackend() | 
| 51 | { | 
| 52 |     cleanupSubscriptions(); | 
| 53 |     if (m_uaclient) | 
| 54 |         UA_Client_delete(client: m_uaclient); | 
| 55 | } | 
| 56 |  | 
| 57 | void Open62541AsyncBackend::readAttributes(quint64 handle, UA_NodeId id, QOpcUa::NodeAttributes attr, QString indexRange) | 
| 58 | { | 
| 59 |     UaDeleter<UA_NodeId> nodeIdDeleter(&id, UA_NodeId_clear); | 
| 60 |  | 
| 61 |     if (!m_uaclient) { | 
| 62 |         QList<QOpcUaReadResult> resultMetadata; | 
| 63 |  | 
| 64 |         qt_forEachAttribute(attributes: attr, f: [&](QOpcUa::NodeAttribute attribute){ | 
| 65 |             QOpcUaReadResult temp; | 
| 66 |             temp.setAttribute(attribute); | 
| 67 |             resultMetadata.push_back(t: temp); | 
| 68 |         }); | 
| 69 |         emit attributesRead(handle, attributes: resultMetadata, serviceResult: QOpcUa::UaStatusCode::BadDisconnect); | 
| 70 |         return; | 
| 71 |     } | 
| 72 |  | 
| 73 |     UA_ReadRequest req; | 
| 74 |     UA_ReadRequest_init(p: &req); | 
| 75 |     UaDeleter<UA_ReadRequest> requestDeleter(&req, UA_ReadRequest_clear); | 
| 76 |     req.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; | 
| 77 |  | 
| 78 |     qt_forEachAttribute(attributes: attr, f: [&req](QOpcUa::NodeAttribute attr) { | 
| 79 |        Q_UNUSED(attr); | 
| 80 |         ++req.nodesToReadSize; | 
| 81 |     }); | 
| 82 |  | 
| 83 |     if (req.nodesToReadSize) | 
| 84 |         req.nodesToRead = static_cast<UA_ReadValueId *>(UA_Array_new(size: req.nodesToReadSize, type: &UA_TYPES[UA_TYPES_READVALUEID])); | 
| 85 |  | 
| 86 |     QList<QOpcUaReadResult> resultMetadata; | 
| 87 |     size_t index = 0; | 
| 88 |     qt_forEachAttribute(attributes: attr, f: [&](QOpcUa::NodeAttribute attribute){ | 
| 89 |         auto ¤t = req.nodesToRead[index++]; | 
| 90 |  | 
| 91 |         current.attributeId = QOpen62541ValueConverter::toUaAttributeId(attr: attribute); | 
| 92 |         UA_NodeId_copy(src: &id, dst: ¤t.nodeId); | 
| 93 |         if (indexRange.size()) | 
| 94 |             QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: indexRange, ptr: ¤t.indexRange); | 
| 95 |  | 
| 96 |         QOpcUaReadResult temp; | 
| 97 |         temp.setAttribute(attribute); | 
| 98 |         resultMetadata.push_back(t: temp); | 
| 99 |     }); | 
| 100 |  | 
| 101 |     quint32 requestId = 0; | 
| 102 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_READREQUEST], | 
| 103 |                                                       callback: &asyncReadCallback, responseType: &UA_TYPES[UA_TYPES_READRESPONSE], userdata: this, | 
| 104 |                                                       requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 105 |  | 
| 106 |     if (result != UA_STATUSCODE_GOOD) { | 
| 107 |         const auto statusCode = static_cast<QOpcUa::UaStatusCode>(result); | 
| 108 |         for (auto &entry : resultMetadata) { | 
| 109 |             entry.setStatusCode(statusCode); | 
| 110 |         } | 
| 111 |         emit attributesRead(handle, attributes: resultMetadata, serviceResult: statusCode); | 
| 112 |         return; | 
| 113 |     } | 
| 114 |  | 
| 115 |     m_asyncReadContext[requestId] = { .handle: handle, .results: resultMetadata }; | 
| 116 | } | 
| 117 |  | 
| 118 | void Open62541AsyncBackend::writeAttribute(quint64 handle, UA_NodeId id, QOpcUa::NodeAttribute attrId, QVariant value, QOpcUa::Types type, QString indexRange) | 
| 119 | { | 
| 120 |     if (!m_uaclient) { | 
| 121 |         UA_NodeId_clear(p: &id); | 
| 122 |         emit attributeWritten(handle, attribute: attrId, value, statusCode: QOpcUa::UaStatusCode::BadDisconnect); | 
| 123 |         return; | 
| 124 |     } | 
| 125 |  | 
| 126 |     if (type == QOpcUa::Types::Undefined && attrId != QOpcUa::NodeAttribute::Value) | 
| 127 |         type = attributeIdToTypeId(attr: attrId); | 
| 128 |  | 
| 129 |     UA_WriteRequest req; | 
| 130 |     UA_WriteRequest_init(p: &req); | 
| 131 |     UaDeleter<UA_WriteRequest> requestDeleter(&req, UA_WriteRequest_clear); | 
| 132 |     req.nodesToWriteSize = 1; | 
| 133 |     req.nodesToWrite = UA_WriteValue_new(); | 
| 134 |  | 
| 135 |     UA_WriteValue_init(p: req.nodesToWrite); | 
| 136 |     req.nodesToWrite->attributeId = QOpen62541ValueConverter::toUaAttributeId(attr: attrId); | 
| 137 |     req.nodesToWrite->nodeId = id; | 
| 138 |     req.nodesToWrite->value.value = QOpen62541ValueConverter::toOpen62541Variant(value, type); | 
| 139 |     req.nodesToWrite->value.hasValue = true; | 
| 140 |     if (indexRange.size()) | 
| 141 |         QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: indexRange, ptr: &req.nodesToWrite->indexRange); | 
| 142 |  | 
| 143 |     quint32 requestId = 0; | 
| 144 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_WRITEREQUEST], | 
| 145 |                                                       callback: &asyncWriteAttributesCallback, responseType: &UA_TYPES[UA_TYPES_WRITERESPONSE], userdata: this, | 
| 146 |                                                       requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 147 |  | 
| 148 |     if (result != UA_STATUSCODE_GOOD) { | 
| 149 |         emit attributeWritten(handle, attribute: attrId, value, statusCode: static_cast<QOpcUa::UaStatusCode>(result)); | 
| 150 |         return; | 
| 151 |     } | 
| 152 |  | 
| 153 |     m_asyncWriteAttributesContext[requestId] = { .handle: handle, .toWrite: {{attrId, value}} }; | 
| 154 | } | 
| 155 |  | 
| 156 | void Open62541AsyncBackend::writeAttributes(quint64 handle, UA_NodeId id, QOpcUaNode::AttributeMap toWrite, QOpcUa::Types valueAttributeType) | 
| 157 | { | 
| 158 |     UaDeleter<UA_NodeId> nodeIdDeleter(&id, UA_NodeId_clear); | 
| 159 |  | 
| 160 |     if (!m_uaclient) { | 
| 161 |         emit attributeWritten(handle, attribute: QOpcUa::NodeAttribute::None, value: QVariant(), statusCode: QOpcUa::UaStatusCode::BadDisconnect); | 
| 162 |         return; | 
| 163 |     } | 
| 164 |  | 
| 165 |     if (toWrite.size() == 0) { | 
| 166 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "No values to be written" ; | 
| 167 |         emit attributeWritten(handle, attribute: QOpcUa::NodeAttribute::None, value: QVariant(), statusCode: QOpcUa::UaStatusCode::BadNothingToDo); | 
| 168 |         return; | 
| 169 |     } | 
| 170 |  | 
| 171 |     UA_WriteRequest req; | 
| 172 |     UA_WriteRequest_init(p: &req); | 
| 173 |     UaDeleter<UA_WriteRequest> requestDeleter(&req, UA_WriteRequest_clear); | 
| 174 |     req.nodesToWriteSize = toWrite.size(); | 
| 175 |     req.nodesToWrite = static_cast<UA_WriteValue *>(UA_Array_new(size: req.nodesToWriteSize, type: &UA_TYPES[UA_TYPES_WRITEVALUE])); | 
| 176 |     size_t index = 0; | 
| 177 |     for (auto it = toWrite.begin(); it != toWrite.end(); ++it, ++index) { | 
| 178 |         UA_WriteValue_init(p: &(req.nodesToWrite[index])); | 
| 179 |         req.nodesToWrite[index].attributeId = QOpen62541ValueConverter::toUaAttributeId(attr: it.key()); | 
| 180 |         UA_NodeId_copy(src: &id, dst: &(req.nodesToWrite[index].nodeId)); | 
| 181 |         QOpcUa::Types type = it.key() == QOpcUa::NodeAttribute::Value ? valueAttributeType : attributeIdToTypeId(attr: it.key()); | 
| 182 |         req.nodesToWrite[index].value.value = QOpen62541ValueConverter::toOpen62541Variant(it.value(), type); | 
| 183 |     } | 
| 184 |  | 
| 185 |     quint32 requestId = 0; | 
| 186 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_WRITEREQUEST], | 
| 187 |                                                       callback: &asyncWriteAttributesCallback, responseType: &UA_TYPES[UA_TYPES_WRITERESPONSE], userdata: this, | 
| 188 |                                                       requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 189 |  | 
| 190 |     if (result != UA_STATUSCODE_GOOD) { | 
| 191 |         for (auto it = toWrite.begin(); it != toWrite.end(); ++it) { | 
| 192 |             emit attributeWritten(handle, attribute: it.key(), value: it.value(), statusCode: static_cast<QOpcUa::UaStatusCode>(result)); | 
| 193 |         } | 
| 194 |         return; | 
| 195 |     } | 
| 196 |  | 
| 197 |     m_asyncWriteAttributesContext[requestId] = { .handle: handle, .toWrite: toWrite }; | 
| 198 | } | 
| 199 |  | 
| 200 | void Open62541AsyncBackend::enableMonitoring(quint64 handle, UA_NodeId id, QOpcUa::NodeAttributes attr, const QOpcUaMonitoringParameters &settings) | 
| 201 | { | 
| 202 |     UaDeleter<UA_NodeId> nodeIdDeleter(&id, UA_NodeId_clear); | 
| 203 |  | 
| 204 |     if (!m_uaclient) { | 
| 205 |         qt_forEachAttribute(attributes: attr, f: [&](QOpcUa::NodeAttribute attribute){ | 
| 206 |             QOpcUaMonitoringParameters s; | 
| 207 |             s.setStatusCode(QOpcUa::UaStatusCode::BadDisconnect); | 
| 208 |             emit monitoringEnableDisable(handle, attr: attribute, subscribe: true, status: s); | 
| 209 |         }); | 
| 210 |         return; | 
| 211 |     } | 
| 212 |  | 
| 213 |     QOpen62541Subscription *usedSubscription = nullptr; | 
| 214 |  | 
| 215 |     // Create a new subscription if necessary | 
| 216 |     if (settings.subscriptionId()) { | 
| 217 |         auto sub = m_subscriptions.find(key: settings.subscriptionId()); | 
| 218 |         if (sub == m_subscriptions.end()) { | 
| 219 |             qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "There is no subscription with id"  << settings.subscriptionId(); | 
| 220 |  | 
| 221 |             qt_forEachAttribute(attributes: attr, f: [&](QOpcUa::NodeAttribute attribute){ | 
| 222 |                 QOpcUaMonitoringParameters s; | 
| 223 |                 s.setStatusCode(QOpcUa::UaStatusCode::BadSubscriptionIdInvalid); | 
| 224 |                 emit monitoringEnableDisable(handle, attr: attribute, subscribe: true, status: s); | 
| 225 |             }); | 
| 226 |             return; | 
| 227 |         } | 
| 228 |         usedSubscription = sub.value(); // Ignore interval != subscription.interval | 
| 229 |     } else { | 
| 230 |         usedSubscription = getSubscription(settings); | 
| 231 |     } | 
| 232 |  | 
| 233 |     if (!usedSubscription) { | 
| 234 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not create subscription with interval"  << settings.publishingInterval(); | 
| 235 |         qt_forEachAttribute(attributes: attr, f: [&](QOpcUa::NodeAttribute attribute){ | 
| 236 |             QOpcUaMonitoringParameters s; | 
| 237 |             s.setStatusCode(QOpcUa::UaStatusCode::BadSubscriptionIdInvalid); | 
| 238 |             emit monitoringEnableDisable(handle, attr: attribute, subscribe: true, status: s); | 
| 239 |         }); | 
| 240 |         return; | 
| 241 |     } | 
| 242 |  | 
| 243 |     qt_forEachAttribute(attributes: attr, f: [&](QOpcUa::NodeAttribute attribute){ | 
| 244 |         if (getSubscriptionForItem(handle, attr: attribute)) { | 
| 245 |             qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Monitored item for"  << attribute << "has already been created" ; | 
| 246 |             QOpcUaMonitoringParameters s; | 
| 247 |             s.setStatusCode(QOpcUa::UaStatusCode::BadEntryExists); | 
| 248 |             emit monitoringEnableDisable(handle, attr: attribute, subscribe: true, status: s); | 
| 249 |         } else { | 
| 250 |             bool success = usedSubscription->addAttributeMonitoredItem(handle, attr: attribute, id, settings); | 
| 251 |             if (success) | 
| 252 |                 m_attributeMapping[handle][attribute] = usedSubscription; | 
| 253 |         } | 
| 254 |     }); | 
| 255 |  | 
| 256 |     if (usedSubscription->monitoredItemsCount() == 0) | 
| 257 |         removeSubscription(subscriptionId: usedSubscription->subscriptionId()); // No items were added | 
| 258 | } | 
| 259 |  | 
| 260 | void Open62541AsyncBackend::disableMonitoring(quint64 handle, QOpcUa::NodeAttributes attr) | 
| 261 | { | 
| 262 |     if (!m_uaclient) { | 
| 263 |         qt_forEachAttribute(attributes: attr, f: [&](QOpcUa::NodeAttribute attribute){ | 
| 264 |             QOpcUaMonitoringParameters s; | 
| 265 |             s.setStatusCode(QOpcUa::UaStatusCode::BadDisconnect); | 
| 266 |             emit monitoringEnableDisable(handle, attr: attribute, subscribe: false, status: s); | 
| 267 |         }); | 
| 268 |         return; | 
| 269 |     } | 
| 270 |  | 
| 271 |     qt_forEachAttribute(attributes: attr, f: [&](QOpcUa::NodeAttribute attribute){ | 
| 272 |         QOpen62541Subscription *sub = getSubscriptionForItem(handle, attr: attribute); | 
| 273 |         if (sub) { | 
| 274 |             sub->removeAttributeMonitoredItem(handle, attr: attribute); | 
| 275 |             m_attributeMapping[handle].remove(key: attribute); | 
| 276 |             if (sub->monitoredItemsCount() == 0) | 
| 277 |                 removeSubscription(subscriptionId: sub->subscriptionId()); | 
| 278 |         } else { | 
| 279 |             qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "There is no monitored item for this attribute" ; | 
| 280 |             QOpcUaMonitoringParameters s; | 
| 281 |             s.setStatusCode(QOpcUa::UaStatusCode::BadMonitoredItemIdInvalid); | 
| 282 |             emit monitoringEnableDisable(handle, attr: attribute, subscribe: false, status: s); | 
| 283 |         } | 
| 284 |     }); | 
| 285 | } | 
| 286 |  | 
| 287 | void Open62541AsyncBackend::modifyMonitoring(quint64 handle, QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value) | 
| 288 | { | 
| 289 |     if (!m_uaclient) { | 
| 290 |         QOpcUaMonitoringParameters p; | 
| 291 |         p.setStatusCode(QOpcUa::UaStatusCode::BadDisconnect); | 
| 292 |         emit monitoringStatusChanged(handle, attr, items: item, param: p); | 
| 293 |         return; | 
| 294 |     } | 
| 295 |  | 
| 296 |     QOpen62541Subscription *subscription = getSubscriptionForItem(handle, attr); | 
| 297 |     if (!subscription) { | 
| 298 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify"  << item << ", the monitored item does not exist" ; | 
| 299 |         QOpcUaMonitoringParameters p; | 
| 300 |         p.setStatusCode(QOpcUa::UaStatusCode::BadMonitoredItemIdInvalid); | 
| 301 |         emit monitoringStatusChanged(handle, attr, items: item, param: p); | 
| 302 |         return; | 
| 303 |     } | 
| 304 |  | 
| 305 |     subscription->modifyMonitoring(handle, attr, item, value); | 
| 306 | } | 
| 307 |  | 
| 308 | QOpen62541Subscription *Open62541AsyncBackend::getSubscription(const QOpcUaMonitoringParameters &settings) | 
| 309 | { | 
| 310 |     if (settings.subscriptionType() == QOpcUaMonitoringParameters::SubscriptionType::Shared) { | 
| 311 |         // Requesting multiple subscriptions with publishing interval < minimum publishing interval breaks subscription sharing | 
| 312 |         double interval = revisePublishingInterval(requestedValue: settings.publishingInterval(), minimumValue: m_minPublishingInterval); | 
| 313 |         for (auto entry : std::as_const(t&: m_subscriptions)) { | 
| 314 |             if (qFuzzyCompare(p1: entry->interval(), p2: interval) && entry->shared() == QOpcUaMonitoringParameters::SubscriptionType::Shared) | 
| 315 |                 return entry; | 
| 316 |         } | 
| 317 |     } | 
| 318 |  | 
| 319 |     QOpen62541Subscription *sub = new QOpen62541Subscription(this, settings); | 
| 320 |     UA_UInt32 id = sub->createOnServer(); | 
| 321 |     if (!id) { | 
| 322 |         delete sub; | 
| 323 |         return nullptr; | 
| 324 |     } | 
| 325 |     m_subscriptions[id] = sub; | 
| 326 |     if (sub->interval() > settings.publishingInterval()) // The publishing interval has been revised by the server. | 
| 327 |         m_minPublishingInterval = sub->interval(); | 
| 328 |     // This must be a queued connection to prevent the slot from being called while the client is inside UA_Client_run_iterate(). | 
| 329 |     QObject::connect(sender: sub, signal: &QOpen62541Subscription::timeout, context: this, slot: &Open62541AsyncBackend::handleSubscriptionTimeout, type: Qt::QueuedConnection); | 
| 330 |     return sub; | 
| 331 | } | 
| 332 |  | 
| 333 | bool Open62541AsyncBackend::removeSubscription(UA_UInt32 subscriptionId) | 
| 334 | { | 
| 335 |     auto sub = m_subscriptions.find(key: subscriptionId); | 
| 336 |     if (sub != m_subscriptions.end()) { | 
| 337 |         sub.value()->removeOnServer(); | 
| 338 |         delete sub.value(); | 
| 339 |         m_subscriptions.remove(key: subscriptionId); | 
| 340 |         return true; | 
| 341 |     } | 
| 342 |     return false; | 
| 343 | } | 
| 344 |  | 
| 345 | void Open62541AsyncBackend::callMethod(quint64 handle, UA_NodeId objectId, UA_NodeId methodId, QList<QOpcUa::TypedVariant> args) | 
| 346 | { | 
| 347 |     if (!m_uaclient) { | 
| 348 |         emit methodCallFinished(handle, methodNodeId: Open62541Utils::nodeIdToQString(id: methodId), result: QVariant(), statusCode: QOpcUa::UaStatusCode::BadDisconnect); | 
| 349 |         UA_NodeId_clear(p: &objectId); | 
| 350 |         UA_NodeId_clear(p: &methodId); | 
| 351 |         return; | 
| 352 |     } | 
| 353 |  | 
| 354 |     UA_Variant *inputArgs = nullptr; | 
| 355 |  | 
| 356 |     if (args.size()) { | 
| 357 |         inputArgs = static_cast<UA_Variant *>(UA_Array_new(size: args.size(), type: &UA_TYPES[UA_TYPES_VARIANT])); | 
| 358 |         for (qsizetype i = 0; i < args.size(); ++i) | 
| 359 |             inputArgs[i] = QOpen62541ValueConverter::toOpen62541Variant(args[i].first, args[i].second); | 
| 360 |     } | 
| 361 |  | 
| 362 |     quint32 requestId = 0; | 
| 363 |  | 
| 364 |     UA_CallRequest request; | 
| 365 |     UA_CallRequest_init(p: &request); | 
| 366 |     UaDeleter<UA_CallRequest> requestDeleter(&request, UA_CallRequest_clear); | 
| 367 |  | 
| 368 |     request.methodsToCallSize = 1; | 
| 369 |     request.methodsToCall = UA_CallMethodRequest_new(); | 
| 370 |     request.methodsToCall->objectId = objectId; | 
| 371 |     request.methodsToCall->methodId = methodId; | 
| 372 |     request.methodsToCall->inputArguments = inputArgs; | 
| 373 |     request.methodsToCall->inputArgumentsSize = args.size(); | 
| 374 |  | 
| 375 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &request, requestType: &UA_TYPES[UA_TYPES_CALLREQUEST], | 
| 376 |                                                       callback: &asyncMethodCallback, | 
| 377 |                                                       responseType: &UA_TYPES[UA_TYPES_CALLRESPONSE], | 
| 378 |                                                       userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 379 |     if (result != UA_STATUSCODE_GOOD) | 
| 380 |         emit methodCallFinished(handle, methodNodeId: Open62541Utils::nodeIdToQString(id: methodId), result: QVariant(), | 
| 381 |                                 statusCode: static_cast<QOpcUa::UaStatusCode>(result)); | 
| 382 |  | 
| 383 |     m_asyncCallContext[requestId] = { .handle: handle, .methodNodeId: Open62541Utils::nodeIdToQString(id: methodId) }; | 
| 384 | } | 
| 385 |  | 
| 386 | void Open62541AsyncBackend::resolveBrowsePath(quint64 handle, UA_NodeId startNode, const QList<QOpcUaRelativePathElement> &path) | 
| 387 | { | 
| 388 |     if (!m_uaclient) { | 
| 389 |         UA_NodeId_clear(p: &startNode); | 
| 390 |         emit resolveBrowsePathFinished(handle, targets: {}, path, statusCode: QOpcUa::UaStatusCode::BadDisconnect); | 
| 391 |         return; | 
| 392 |     } | 
| 393 |  | 
| 394 |     UA_TranslateBrowsePathsToNodeIdsRequest req; | 
| 395 |     UA_TranslateBrowsePathsToNodeIdsRequest_init(p: &req); | 
| 396 |     UaDeleter<UA_TranslateBrowsePathsToNodeIdsRequest> requestDeleter( | 
| 397 |                 &req,UA_TranslateBrowsePathsToNodeIdsRequest_clear); | 
| 398 |  | 
| 399 |     req.browsePathsSize = 1; | 
| 400 |     req.browsePaths = UA_BrowsePath_new(); | 
| 401 |     UA_BrowsePath_init(p: req.browsePaths); | 
| 402 |     req.browsePaths->startingNode = startNode; | 
| 403 |     req.browsePaths->relativePath.elementsSize = path.size(); | 
| 404 |     req.browsePaths->relativePath.elements = static_cast<UA_RelativePathElement *>(UA_Array_new(size: path.size(), type: &UA_TYPES[UA_TYPES_RELATIVEPATHELEMENT])); | 
| 405 |  | 
| 406 |     for (qsizetype i = 0 ; i < path.size(); ++i) { | 
| 407 |         req.browsePaths->relativePath.elements[i].includeSubtypes = path[i].includeSubtypes(); | 
| 408 |         req.browsePaths->relativePath.elements[i].isInverse = path[i].isInverse(); | 
| 409 |         req.browsePaths->relativePath.elements[i].referenceTypeId = Open62541Utils::nodeIdFromQString(name: path[i].referenceTypeId()); | 
| 410 |         req.browsePaths->relativePath.elements[i].targetName = UA_QUALIFIEDNAME_ALLOC(nsIndex: path[i].targetName().namespaceIndex(), | 
| 411 |                                                                                       chars: path[i].targetName().name().toUtf8().constData()); | 
| 412 |     } | 
| 413 |  | 
| 414 |     quint32 requestId = 0; | 
| 415 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSREQUEST], | 
| 416 |                                                       callback: &asyncTranslateBrowsePathCallback, | 
| 417 |                                                       responseType: &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSRESPONSE], | 
| 418 |                                                       userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 419 |  | 
| 420 |     if (result != UA_STATUSCODE_GOOD) { | 
| 421 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Translate browse path failed:"  << UA_StatusCode_name(code: result); | 
| 422 |         emit resolveBrowsePathFinished(handle, targets: QList<QOpcUaBrowsePathTarget>(), path, | 
| 423 |                                          statusCode: static_cast<QOpcUa::UaStatusCode>(result)); | 
| 424 |         return; | 
| 425 |     } | 
| 426 |  | 
| 427 |     m_asyncTranslateContext[requestId] = { .handle: handle, .path: path }; | 
| 428 | } | 
| 429 |  | 
| 430 | void Open62541AsyncBackend::open62541LogHandler (void *logContext, UA_LogLevel level, UA_LogCategory category, | 
| 431 |                                                  const char *msg, va_list args) { | 
| 432 |  | 
| 433 |     Q_UNUSED(logContext) | 
| 434 |  | 
| 435 |     Q_STATIC_ASSERT(UA_LOGCATEGORY_NETWORK == 0); | 
| 436 |     Q_STATIC_ASSERT(UA_LOGCATEGORY_SECURECHANNEL == 1); | 
| 437 |     Q_STATIC_ASSERT(UA_LOGCATEGORY_SESSION == 2); | 
| 438 |     Q_STATIC_ASSERT(UA_LOGCATEGORY_SERVER == 3); | 
| 439 |     Q_STATIC_ASSERT(UA_LOGCATEGORY_CLIENT == 4); | 
| 440 |     Q_STATIC_ASSERT(UA_LOGCATEGORY_USERLAND == 5); | 
| 441 |     Q_STATIC_ASSERT(UA_LOGCATEGORY_SECURITYPOLICY == 6); | 
| 442 |  | 
| 443 |     Q_ASSERT(category <= UA_LOGCATEGORY_SECURITYPOLICY); | 
| 444 |  | 
| 445 |     const auto logMessage = QString::vasprintf(format: msg, ap: args); | 
| 446 |  | 
| 447 |     static const QLoggingCategory loggingCategories[] { | 
| 448 |         QLoggingCategory("qt.opcua.plugins.open62541.sdk.network" ), | 
| 449 |         QLoggingCategory("qt.opcua.plugins.open62541.sdk.securechannel" ), | 
| 450 |         QLoggingCategory("qt.opcua.plugins.open62541.sdk.session" ), | 
| 451 |         QLoggingCategory("qt.opcua.plugins.open62541.sdk.server" ), | 
| 452 |         QLoggingCategory("qt.opcua.plugins.open62541.sdk.client" ), | 
| 453 |         QLoggingCategory("qt.opcua.plugins.open62541.sdk.userland" ), | 
| 454 |         QLoggingCategory("qt.opcua.plugins.open62541.sdk.securitypolicy" ) | 
| 455 |     }; | 
| 456 |  | 
| 457 |     switch (level) { | 
| 458 |     case UA_LOGLEVEL_TRACE: | 
| 459 |     case UA_LOGLEVEL_DEBUG: | 
| 460 |         qCDebug(loggingCategories[category]) << logMessage; | 
| 461 |         break; | 
| 462 |     case UA_LOGLEVEL_INFO: | 
| 463 |         qCInfo(loggingCategories[category]) << logMessage; | 
| 464 |         break; | 
| 465 |     case UA_LOGLEVEL_WARNING: | 
| 466 |         qCWarning(loggingCategories[category]) << logMessage; | 
| 467 |         break; | 
| 468 |     case UA_LOGLEVEL_ERROR: | 
| 469 |     case UA_LOGLEVEL_FATAL: | 
| 470 |         qCCritical(loggingCategories[category]) << logMessage; | 
| 471 |         break; | 
| 472 |     default: | 
| 473 |         qCCritical(loggingCategories[category]) << "Unknown UA_LOGLEVEL"  << logMessage; | 
| 474 |         break; | 
| 475 |     } | 
| 476 | } | 
| 477 |  | 
| 478 | void Open62541AsyncBackend::findServers(const QUrl &url, const QStringList &localeIds, const QStringList &serverUris) | 
| 479 | { | 
| 480 |     UA_ClientConfig initialConfig {}; | 
| 481 |     initialConfig.logger = m_open62541Logger; | 
| 482 |     UA_ClientConfig_setDefault(config: &initialConfig); | 
| 483 |     UA_Client *tmpClient = UA_Client_newWithConfig(config: &initialConfig); | 
| 484 |  | 
| 485 |     UaDeleter<UA_Client> clientDeleter(tmpClient, UA_Client_delete); | 
| 486 |  | 
| 487 |     UA_String *uaServerUris = nullptr; | 
| 488 |     if (!serverUris.isEmpty()) { | 
| 489 |         uaServerUris = static_cast<UA_String *>(UA_Array_new(size: serverUris.size(), type: &UA_TYPES[UA_TYPES_STRING])); | 
| 490 |         for (qsizetype i = 0; i < serverUris.size(); ++i) | 
| 491 |             QOpen62541ValueConverter::scalarFromQt(var: serverUris.at(i), ptr: &uaServerUris[i]); | 
| 492 |     } | 
| 493 |     UaArrayDeleter<UA_TYPES_STRING> serverUrisDeleter(uaServerUris, serverUris.size()); | 
| 494 |  | 
| 495 |     UA_String *uaLocaleIds = nullptr; | 
| 496 |     if (!localeIds.isEmpty()) { | 
| 497 |         uaLocaleIds = static_cast<UA_String *>(UA_Array_new(size: localeIds.size(), type: &UA_TYPES[UA_TYPES_STRING])); | 
| 498 |         for (qsizetype i = 0; i < localeIds.size(); ++i) | 
| 499 |             QOpen62541ValueConverter::scalarFromQt(var: localeIds.at(i), ptr: &uaLocaleIds[i]); | 
| 500 |     } | 
| 501 |     UaArrayDeleter<UA_TYPES_STRING> localeIdsDeleter(uaLocaleIds, localeIds.size()); | 
| 502 |  | 
| 503 |     size_t  = 0; | 
| 504 |     UA_ApplicationDescription *servers = nullptr; | 
| 505 |  | 
| 506 |     UA_StatusCode result = UA_Client_findServers(client: tmpClient, serverUrl: url.toString(options: QUrl::RemoveUserInfo).toUtf8().constData(), | 
| 507 |                                                  serverUrisSize: serverUris.size(), serverUris: uaServerUris, localeIdsSize: localeIds.size(), localeIds: uaLocaleIds, | 
| 508 |                                                  registeredServersSize: &serversSize, registeredServers: &servers); | 
| 509 |  | 
| 510 |     UaArrayDeleter<UA_TYPES_APPLICATIONDESCRIPTION> serversDeleter(servers, serversSize); | 
| 511 |  | 
| 512 |     QList<QOpcUaApplicationDescription> ret; | 
| 513 |  | 
| 514 |     for (size_t i = 0; i < serversSize; ++i) | 
| 515 |         ret.append(t: convertApplicationDescription(desc&: servers[i])); | 
| 516 |  | 
| 517 |     if (result != UA_STATUSCODE_GOOD) { | 
| 518 |         qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to get servers:"  << static_cast<QOpcUa::UaStatusCode>(result); | 
| 519 |     } | 
| 520 |  | 
| 521 |     emit findServersFinished(servers: ret, statusCode: static_cast<QOpcUa::UaStatusCode>(result), requestUrl: url); | 
| 522 | } | 
| 523 |  | 
| 524 | void Open62541AsyncBackend::readNodeAttributes(const QList<QOpcUaReadItem> &nodesToRead) | 
| 525 | { | 
| 526 |     if (!m_uaclient) { | 
| 527 |         emit readNodeAttributesFinished(results: {}, serviceResult: QOpcUa::UaStatusCode::BadDisconnect); | 
| 528 |         return; | 
| 529 |     } | 
| 530 |  | 
| 531 |     if (nodesToRead.size() == 0) { | 
| 532 |         emit readNodeAttributesFinished(results: QList<QOpcUaReadResult>(), serviceResult: QOpcUa::UaStatusCode::BadNothingToDo); | 
| 533 |         return; | 
| 534 |     } | 
| 535 |  | 
| 536 |     UA_ReadRequest req; | 
| 537 |     UA_ReadRequest_init(p: &req); | 
| 538 |     UaDeleter<UA_ReadRequest> requestDeleter(&req, UA_ReadRequest_clear); | 
| 539 |  | 
| 540 |     req.nodesToReadSize = nodesToRead.size(); | 
| 541 |     req.nodesToRead = static_cast<UA_ReadValueId *>(UA_Array_new(size: nodesToRead.size(), type: &UA_TYPES[UA_TYPES_READVALUEID])); | 
| 542 |     req.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; | 
| 543 |  | 
| 544 |     for (qsizetype i = 0; i < nodesToRead.size(); ++i) { | 
| 545 |         UA_ReadValueId_init(p: &req.nodesToRead[i]); | 
| 546 |         req.nodesToRead[i].attributeId = QOpen62541ValueConverter::toUaAttributeId(attr: nodesToRead.at(i).attribute()); | 
| 547 |         req.nodesToRead[i].nodeId = Open62541Utils::nodeIdFromQString(name: nodesToRead.at(i).nodeId()); | 
| 548 |         if (!nodesToRead[i].indexRange().isEmpty()) | 
| 549 |             QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: nodesToRead.at(i).indexRange(), | 
| 550 |                                                                        ptr: &req.nodesToRead[i].indexRange); | 
| 551 |     } | 
| 552 |  | 
| 553 |     quint32 requestId = 0; | 
| 554 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_READREQUEST], callback: &asyncBatchReadCallback, | 
| 555 |                                                       responseType: &UA_TYPES[UA_TYPES_READRESPONSE], userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 556 |  | 
| 557 |     if (result != UA_STATUSCODE_GOOD) { | 
| 558 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch read failed:"  << result; | 
| 559 |         emit readNodeAttributesFinished(results: QList<QOpcUaReadResult>(), serviceResult: static_cast<QOpcUa::UaStatusCode>(result)); | 
| 560 |         return; | 
| 561 |     } | 
| 562 |  | 
| 563 |     m_asyncBatchReadContext[requestId] = { .nodesToRead: nodesToRead }; | 
| 564 | } | 
| 565 |  | 
| 566 | void Open62541AsyncBackend::writeNodeAttributes(const QList<QOpcUaWriteItem> &nodesToWrite) | 
| 567 | { | 
| 568 |     if (!m_uaclient) { | 
| 569 |         emit writeNodeAttributesFinished(results: {}, serviceResult: QOpcUa::UaStatusCode::BadDisconnect); | 
| 570 |         return; | 
| 571 |     } | 
| 572 |  | 
| 573 |     if (nodesToWrite.isEmpty()) { | 
| 574 |         emit writeNodeAttributesFinished(results: QList<QOpcUaWriteResult>(), serviceResult: QOpcUa::UaStatusCode::BadNothingToDo); | 
| 575 |         return; | 
| 576 |     } | 
| 577 |  | 
| 578 |     UA_WriteRequest req; | 
| 579 |     UA_WriteRequest_init(p: &req); | 
| 580 |     UaDeleter<UA_WriteRequest> requestDeleter(&req, UA_WriteRequest_clear); | 
| 581 |  | 
| 582 |     req.nodesToWriteSize = nodesToWrite.size(); | 
| 583 |     req.nodesToWrite = static_cast<UA_WriteValue *>(UA_Array_new(size: nodesToWrite.size(), type: &UA_TYPES[UA_TYPES_WRITEVALUE])); | 
| 584 |  | 
| 585 |     for (qsizetype i = 0; i < nodesToWrite.size(); ++i) { | 
| 586 |         const auto ¤tItem = nodesToWrite.at(i); | 
| 587 |         auto ¤tUaItem = req.nodesToWrite[i]; | 
| 588 |         currentUaItem.attributeId = QOpen62541ValueConverter::toUaAttributeId(attr: currentItem.attribute()); | 
| 589 |         currentUaItem.nodeId = Open62541Utils::nodeIdFromQString(name: currentItem.nodeId()); | 
| 590 |         if (currentItem.hasStatusCode()) { | 
| 591 |             currentUaItem.value.status = currentItem.statusCode(); | 
| 592 |             currentUaItem.value.hasStatus = UA_TRUE; | 
| 593 |         } | 
| 594 |         if (!currentItem.indexRange().isEmpty()) | 
| 595 |             QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: currentItem.indexRange(), ptr: ¤tUaItem.indexRange); | 
| 596 |         if (!currentItem.value().isNull()) { | 
| 597 |             currentUaItem.value.hasValue = true; | 
| 598 |             currentUaItem.value.value = QOpen62541ValueConverter::toOpen62541Variant(currentItem.value(), currentItem.type()); | 
| 599 |         } | 
| 600 |         if (currentItem.sourceTimestamp().isValid()) { | 
| 601 |             QOpen62541ValueConverter::scalarFromQt<UA_DateTime, QDateTime>(var: currentItem.sourceTimestamp(), | 
| 602 |                                                                            ptr: ¤tUaItem.value.sourceTimestamp); | 
| 603 |             currentUaItem.value.hasSourceTimestamp = UA_TRUE; | 
| 604 |         } | 
| 605 |         if (currentItem.serverTimestamp().isValid()) { | 
| 606 |             QOpen62541ValueConverter::scalarFromQt<UA_DateTime, QDateTime>(var: currentItem.serverTimestamp(), | 
| 607 |                                                                            ptr: ¤tUaItem.value.serverTimestamp); | 
| 608 |             currentUaItem.value.hasServerTimestamp = UA_TRUE; | 
| 609 |         } | 
| 610 |     } | 
| 611 |  | 
| 612 |     quint32 requestId = 0; | 
| 613 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_WRITEREQUEST], callback: &asyncBatchWriteCallback, | 
| 614 |                                                       responseType: &UA_TYPES[UA_TYPES_WRITERESPONSE], userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 615 |  | 
| 616 |     if (result != UA_STATUSCODE_GOOD) { | 
| 617 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch write failed:"  << result; | 
| 618 |         emit writeNodeAttributesFinished(results: QList<QOpcUaWriteResult>(), serviceResult: static_cast<QOpcUa::UaStatusCode>(result)); | 
| 619 |         return; | 
| 620 |     } | 
| 621 |  | 
| 622 |     m_asyncBatchWriteContext[requestId] = { .nodesToWrite: nodesToWrite }; | 
| 623 | } | 
| 624 |  | 
| 625 | void Open62541AsyncBackend::readHistoryRaw(QOpcUaHistoryReadRawRequest request, QList<QByteArray> continuationPoints, bool releaseContinuationPoints, quint64 handle) | 
| 626 | { | 
| 627 |     if (!m_uaclient) { | 
| 628 |         emit historyDataAvailable(data: {}, continuationPoints: {}, serviceResult: QOpcUa::UaStatusCode::BadDisconnect, handle); | 
| 629 |         return; | 
| 630 |     } | 
| 631 |  | 
| 632 |     if (!continuationPoints.empty() && continuationPoints.size() != request.nodesToRead().size()) { | 
| 633 |         emit historyDataAvailable(data: {}, continuationPoints: {}, serviceResult: QOpcUa::UaStatusCode::BadInternalError, handle); | 
| 634 |         return; | 
| 635 |     } | 
| 636 |  | 
| 637 |     UA_HistoryReadRequest uarequest; | 
| 638 |     UA_HistoryReadRequest_init(p: &uarequest); | 
| 639 |     uarequest.nodesToReadSize = request.nodesToRead().size(); | 
| 640 |     uarequest.nodesToRead = static_cast<UA_HistoryReadValueId*>(UA_Array_new(size: uarequest.nodesToReadSize, type: &UA_TYPES[UA_TYPES_HISTORYREADVALUEID])); | 
| 641 |     for (size_t i = 0; i < uarequest.nodesToReadSize; ++i) { | 
| 642 |         uarequest.nodesToRead[i].nodeId = Open62541Utils::nodeIdFromQString(name: request.nodesToRead().at(i).nodeId()); | 
| 643 |         QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: request.nodesToRead().at(i).indexRange(), ptr: &uarequest.nodesToRead[i].indexRange); | 
| 644 |         uarequest.nodesToRead[i].dataEncoding = UA_QUALIFIEDNAME_ALLOC(nsIndex: 0, chars: "Default Binary" ); | 
| 645 |         if (!continuationPoints.isEmpty()) | 
| 646 |             QOpen62541ValueConverter::scalarFromQt<UA_ByteString, QByteArray>(var: continuationPoints.at(i), ptr: &uarequest.nodesToRead[i].continuationPoint); | 
| 647 |     } | 
| 648 |     uarequest.timestampsToReturn = static_cast<UA_TimestampsToReturn>(request.timestampsToReturn()); | 
| 649 |  | 
| 650 |     if (releaseContinuationPoints) | 
| 651 |         uarequest.releaseContinuationPoints = releaseContinuationPoints; | 
| 652 |  | 
| 653 |     uarequest.historyReadDetails.encoding = UA_EXTENSIONOBJECT_DECODED; | 
| 654 |     uarequest.historyReadDetails.content.decoded.type = &UA_TYPES[UA_TYPES_READRAWMODIFIEDDETAILS]; | 
| 655 |     UA_ReadRawModifiedDetails *details = UA_ReadRawModifiedDetails_new(); | 
| 656 |     uarequest.historyReadDetails.content.decoded.data = details; | 
| 657 |     QOpen62541ValueConverter::scalarFromQt<UA_DateTime, QDateTime>(var: request.startTimestamp(), ptr: &details->startTime); | 
| 658 |     QOpen62541ValueConverter::scalarFromQt<UA_DateTime, QDateTime>(var: request.endTimestamp(), ptr: &details->endTime); | 
| 659 |     details->isReadModified = UA_FALSE; | 
| 660 |     details->returnBounds = request.returnBounds(); | 
| 661 |     details->numValuesPerNode = request.numValuesPerNode(); | 
| 662 |  | 
| 663 |     quint32 requestId = 0; | 
| 664 |     UA_StatusCode resultCode = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &uarequest, requestType: &UA_TYPES[UA_TYPES_HISTORYREADREQUEST], callback: &asyncReadHistoryDataCallBack, | 
| 665 |                                                       responseType: &UA_TYPES[UA_TYPES_HISTORYREADRESPONSE], userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 666 |  | 
| 667 |     UA_HistoryReadRequest_clear(p: &uarequest); | 
| 668 |  | 
| 669 |     if (resultCode != UA_STATUSCODE_GOOD) { | 
| 670 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Read history data failed:"  << resultCode; | 
| 671 |         emit historyDataAvailable(data: {}, continuationPoints: {}, serviceResult: QOpcUa::UaStatusCode(resultCode), handle); | 
| 672 |         return; | 
| 673 |     } | 
| 674 |  | 
| 675 |     m_asyncReadHistoryDataContext[requestId] = {.handle: handle, .historyReadRawRequest: request}; | 
| 676 | } | 
| 677 |  | 
| 678 | void Open62541AsyncBackend::readHistoryEvents(const QOpcUaHistoryReadEventRequest &request, const QList<QByteArray> &continuationPoints, | 
| 679 |                                               bool releaseContinuationPoints, quint64 handle) | 
| 680 | { | 
| 681 |     if (!m_uaclient) { | 
| 682 |         emit historyDataAvailable(data: {}, continuationPoints: {}, serviceResult: QOpcUa::UaStatusCode::BadDisconnect, handle); | 
| 683 |         return; | 
| 684 |     } | 
| 685 |  | 
| 686 |     if (!continuationPoints.empty() && continuationPoints.size() != request.nodesToRead().size()) { | 
| 687 |         emit historyDataAvailable(data: {}, continuationPoints: {}, serviceResult: QOpcUa::UaStatusCode::BadInternalError, handle); | 
| 688 |         return; | 
| 689 |     } | 
| 690 |  | 
| 691 |     UA_HistoryReadRequest uarequest; | 
| 692 |     UA_HistoryReadRequest_init(p: &uarequest); | 
| 693 |     uarequest.requestHeader.timeoutHint = m_asyncRequestTimeout; | 
| 694 |     uarequest.nodesToReadSize = request.nodesToRead().size(); | 
| 695 |     uarequest.nodesToRead = static_cast<UA_HistoryReadValueId*>(UA_Array_new(size: uarequest.nodesToReadSize, type: &UA_TYPES[UA_TYPES_HISTORYREADVALUEID])); | 
| 696 |  | 
| 697 |     for (size_t i = 0; i < uarequest.nodesToReadSize; ++i) { | 
| 698 |         uarequest.nodesToRead[i].nodeId = Open62541Utils::nodeIdFromQString(name: request.nodesToRead().at(i).nodeId()); | 
| 699 |         QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: request.nodesToRead().at(i).indexRange(), ptr: &uarequest.nodesToRead[i].indexRange); | 
| 700 |         uarequest.nodesToRead[i].dataEncoding = UA_QUALIFIEDNAME_ALLOC(nsIndex: 0, chars: "Default Binary" ); | 
| 701 |         if (!continuationPoints.isEmpty()) | 
| 702 |             QOpen62541ValueConverter::scalarFromQt<UA_ByteString, QByteArray>(var: continuationPoints.at(i), ptr: &uarequest.nodesToRead[i].continuationPoint); | 
| 703 |     } | 
| 704 |  | 
| 705 |     uarequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; | 
| 706 |  | 
| 707 |     if (releaseContinuationPoints) | 
| 708 |         uarequest.releaseContinuationPoints = releaseContinuationPoints; | 
| 709 |  | 
| 710 |     uarequest.historyReadDetails.encoding = UA_EXTENSIONOBJECT_DECODED; | 
| 711 |     uarequest.historyReadDetails.content.decoded.type = &UA_TYPES[UA_TYPES_READEVENTDETAILS]; | 
| 712 |     UA_ReadEventDetails *details = UA_ReadEventDetails_new(); | 
| 713 |     uarequest.historyReadDetails.content.decoded.data = details; | 
| 714 |     QOpen62541ValueConverter::scalarFromQt<UA_DateTime, QDateTime>(var: request.startTimestamp(), ptr: &details->startTime); | 
| 715 |     QOpen62541ValueConverter::scalarFromQt<UA_DateTime, QDateTime>(var: request.endTimestamp(), ptr: &details->endTime); | 
| 716 |     details->numValuesPerNode = request.numValuesPerNode(); | 
| 717 |  | 
| 718 |     QOpen62541ValueConverter::scalarFromQt<UA_EventFilter, QOpcUaMonitoringParameters::EventFilter>(var: request.filter(), ptr: &details->filter); | 
| 719 |  | 
| 720 |     quint32 requestId = 0; | 
| 721 |     UA_StatusCode resultCode = __UA_Client_AsyncService(client: m_uaclient, request: &uarequest, requestType: &UA_TYPES[UA_TYPES_HISTORYREADREQUEST], callback: &asyncReadHistoryEventsCallback, | 
| 722 |                                                         responseType: &UA_TYPES[UA_TYPES_HISTORYREADRESPONSE], userdata: this, requestId: &requestId); | 
| 723 |  | 
| 724 |     UA_HistoryReadRequest_clear(p: &uarequest); | 
| 725 |  | 
| 726 |     if (resultCode != UA_STATUSCODE_GOOD) { | 
| 727 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Read history data failed:"  << resultCode; | 
| 728 |         emit historyDataAvailable(data: {}, continuationPoints: {}, serviceResult: QOpcUa::UaStatusCode(resultCode), handle); | 
| 729 |         return; | 
| 730 |     } | 
| 731 |  | 
| 732 |     m_asyncReadHistoryEventsContext[requestId] = {.handle: handle, .historyReadEventRequest: request}; | 
| 733 | } | 
| 734 |  | 
| 735 | void Open62541AsyncBackend::addNode(const QOpcUaAddNodeItem &nodeToAdd) | 
| 736 | { | 
| 737 |     if (!m_uaclient) { | 
| 738 |         emit addNodeFinished(requestedNodeId: nodeToAdd.requestedNewNodeId(), assignedNodeId: {}, statusCode: QOpcUa::UaStatusCode::BadDisconnect); | 
| 739 |         return; | 
| 740 |     } | 
| 741 |  | 
| 742 |     UA_AddNodesRequest req; | 
| 743 |     UA_AddNodesRequest_init(p: &req); | 
| 744 |     UaDeleter<UA_AddNodesRequest> requestDeleter(&req, UA_AddNodesRequest_clear); | 
| 745 |     req.nodesToAddSize = 1; | 
| 746 |     req.nodesToAdd = UA_AddNodesItem_new(); | 
| 747 |     UA_AddNodesItem_init(p: req.nodesToAdd); | 
| 748 |  | 
| 749 |     QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>( | 
| 750 |                 var: nodeToAdd.parentNodeId(), ptr: &req.nodesToAdd->parentNodeId); | 
| 751 |  | 
| 752 |     req.nodesToAdd->referenceTypeId = Open62541Utils::nodeIdFromQString(name: nodeToAdd.referenceTypeId()); | 
| 753 |  | 
| 754 |     QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>( | 
| 755 |                 var: nodeToAdd.requestedNewNodeId(), ptr: &req.nodesToAdd->requestedNewNodeId); | 
| 756 |  | 
| 757 |     QOpen62541ValueConverter::scalarFromQt<UA_QualifiedName, QOpcUaQualifiedName>( | 
| 758 |                 var: nodeToAdd.browseName(), ptr: &req.nodesToAdd->browseName); | 
| 759 |  | 
| 760 |     req.nodesToAdd->nodeClass = static_cast<UA_NodeClass>(nodeToAdd.nodeClass()); | 
| 761 |  | 
| 762 |     req.nodesToAdd->nodeAttributes = assembleNodeAttributes(nodeAttributes: nodeToAdd.nodeAttributes(), | 
| 763 |                                                             nodeClass: nodeToAdd.nodeClass()); | 
| 764 |  | 
| 765 |     if (!nodeToAdd.typeDefinition().nodeId().isEmpty()) | 
| 766 |         QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>( | 
| 767 |                     var: nodeToAdd.typeDefinition(), ptr: &req.nodesToAdd->typeDefinition); | 
| 768 |  | 
| 769 |     quint32 requestId = 0; | 
| 770 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_ADDNODESREQUEST], | 
| 771 |                                                       callback: &asyncAddNodeCallback, | 
| 772 |                                                       responseType: &UA_TYPES[UA_TYPES_ADDNODESRESPONSE], | 
| 773 |                                                       userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 774 |  | 
| 775 |     if (result != UA_STATUSCODE_GOOD) { | 
| 776 |         qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add node:"  << result; | 
| 777 |         emit addNodeFinished(requestedNodeId: nodeToAdd.requestedNewNodeId(), assignedNodeId: QString(), statusCode: static_cast<QOpcUa::UaStatusCode>(result)); | 
| 778 |         return; | 
| 779 |     } | 
| 780 |  | 
| 781 |     m_asyncAddNodeContext[requestId] = { .requestedNodeId: nodeToAdd.requestedNewNodeId() }; | 
| 782 | } | 
| 783 |  | 
| 784 | void Open62541AsyncBackend::deleteNode(const QString &nodeId, bool deleteTargetReferences) | 
| 785 | { | 
| 786 |     if (!m_uaclient) { | 
| 787 |         emit deleteNodeFinished(nodeId, statusCode: QOpcUa::UaStatusCode::BadDisconnect); | 
| 788 |         return; | 
| 789 |     } | 
| 790 |  | 
| 791 |     UA_DeleteNodesRequest request; | 
| 792 |     UA_DeleteNodesRequest_init(p: &request); | 
| 793 |     UaDeleter<UA_DeleteNodesRequest> requestDeleter(&request, UA_DeleteNodesRequest_clear); | 
| 794 |  | 
| 795 |     request.nodesToDeleteSize = 1; | 
| 796 |     request.nodesToDelete = UA_DeleteNodesItem_new(); | 
| 797 |  | 
| 798 |     request.nodesToDelete->nodeId = Open62541Utils::nodeIdFromQString(name: nodeId); | 
| 799 |     request.nodesToDelete->deleteTargetReferences = deleteTargetReferences; | 
| 800 |  | 
| 801 |     quint32 requestId = 0; | 
| 802 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &request, requestType: &UA_TYPES[UA_TYPES_DELETENODESREQUEST], | 
| 803 |                                                       callback: &asyncDeleteNodeCallback, | 
| 804 |                                                       responseType: &UA_TYPES[UA_TYPES_DELETENODESRESPONSE], | 
| 805 |                                                       userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 806 |  | 
| 807 |     QOpcUa::UaStatusCode resultStatus = static_cast<QOpcUa::UaStatusCode>(result); | 
| 808 |  | 
| 809 |     if (result != QOpcUa::UaStatusCode::Good) { | 
| 810 |         qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to delete node"  << nodeId << "with status code"  << resultStatus; | 
| 811 |         emit deleteNodeFinished(nodeId, statusCode: resultStatus); | 
| 812 |         return; | 
| 813 |     } | 
| 814 |  | 
| 815 |     m_asyncDeleteNodeContext[requestId] = { .nodeId: nodeId }; | 
| 816 | } | 
| 817 |  | 
| 818 | void Open62541AsyncBackend::addReference(const QOpcUaAddReferenceItem &referenceToAdd) | 
| 819 | { | 
| 820 |     if (!m_uaclient) { | 
| 821 |         emit addReferenceFinished(sourceNodeId: referenceToAdd.sourceNodeId(), referenceTypeId: referenceToAdd.referenceTypeId(), | 
| 822 |                                   targetNodeId: referenceToAdd.targetNodeId(), isForwardReference: referenceToAdd.isForwardReference(), | 
| 823 |                                   statusCode: QOpcUa::UaStatusCode::BadDisconnect); | 
| 824 |         return; | 
| 825 |     } | 
| 826 |  | 
| 827 |     UA_AddReferencesRequest request; | 
| 828 |     UA_AddReferencesRequest_init(p: &request); | 
| 829 |     UaDeleter<UA_AddReferencesRequest> requestDeleter(&request, UA_AddReferencesRequest_clear); | 
| 830 |  | 
| 831 |     request.referencesToAddSize = 1; | 
| 832 |     request.referencesToAdd = UA_AddReferencesItem_new(); | 
| 833 |  | 
| 834 |     request.referencesToAdd->isForward = referenceToAdd.isForwardReference(); | 
| 835 |     QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(var: referenceToAdd.sourceNodeId(), | 
| 836 |                                                                ptr: &request.referencesToAdd->sourceNodeId); | 
| 837 |     QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>(var: referenceToAdd.targetNodeId(), | 
| 838 |                                                                                   ptr: &request.referencesToAdd->targetNodeId); | 
| 839 |     QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(var: referenceToAdd.referenceTypeId(), | 
| 840 |                                                                ptr: &request.referencesToAdd->referenceTypeId); | 
| 841 |     request.referencesToAdd->targetNodeClass = static_cast<UA_NodeClass>(referenceToAdd.targetNodeClass()); | 
| 842 |     QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: referenceToAdd.targetServerUri(), | 
| 843 |                                                                ptr: &request.referencesToAdd->targetServerUri); | 
| 844 |  | 
| 845 |     quint32 requestId = 0; | 
| 846 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &request, requestType: &UA_TYPES[UA_TYPES_ADDREFERENCESREQUEST], | 
| 847 |                                                       callback: &asyncAddReferenceCallback, | 
| 848 |                                                       responseType: &UA_TYPES[UA_TYPES_ADDREFERENCESRESPONSE], | 
| 849 |                                                       userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 850 |  | 
| 851 |     QOpcUa::UaStatusCode statusCode = static_cast<QOpcUa::UaStatusCode>(result); | 
| 852 |     if (result != UA_STATUSCODE_GOOD) { | 
| 853 |         qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add reference from"  << referenceToAdd.sourceNodeId() << "to"  | 
| 854 |                                             << referenceToAdd.targetNodeId().nodeId() << ":"  << statusCode; | 
| 855 |         emit addReferenceFinished(sourceNodeId: referenceToAdd.sourceNodeId(), referenceTypeId: referenceToAdd.referenceTypeId(), | 
| 856 |                                   targetNodeId: referenceToAdd.targetNodeId(), isForwardReference: referenceToAdd.isForwardReference(), statusCode); | 
| 857 |         return; | 
| 858 |     } | 
| 859 |  | 
| 860 |     m_asyncAddReferenceContext[requestId] = { .sourceNodeId: referenceToAdd.sourceNodeId(), .referenceTypeId: referenceToAdd.referenceTypeId(), | 
| 861 |                                               .targetNodeId: referenceToAdd.targetNodeId(), .isForwardReference: referenceToAdd.isForwardReference() }; | 
| 862 | } | 
| 863 |  | 
| 864 | void Open62541AsyncBackend::deleteReference(const QOpcUaDeleteReferenceItem &referenceToDelete) | 
| 865 | { | 
| 866 |     if (!m_uaclient) { | 
| 867 |         emit deleteReferenceFinished(sourceNodeId: referenceToDelete.sourceNodeId(), referenceTypeId: referenceToDelete.referenceTypeId(), | 
| 868 |                                      targetNodeId: referenceToDelete.targetNodeId(), isForwardReference: referenceToDelete.isForwardReference(), | 
| 869 |                                      statusCode: QOpcUa::UaStatusCode::BadDisconnect); | 
| 870 |         return; | 
| 871 |     } | 
| 872 |  | 
| 873 |     UA_DeleteReferencesRequest request; | 
| 874 |     UA_DeleteReferencesRequest_init(p: &request); | 
| 875 |     UaDeleter<UA_DeleteReferencesRequest> requestDeleter(&request, UA_DeleteReferencesRequest_clear); | 
| 876 |  | 
| 877 |     request.referencesToDeleteSize = 1; | 
| 878 |     request.referencesToDelete = UA_DeleteReferencesItem_new(); | 
| 879 |     request.referencesToDelete->isForward = referenceToDelete.isForwardReference(); | 
| 880 |     QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(var: referenceToDelete.sourceNodeId(), | 
| 881 |                                                                ptr: &request.referencesToDelete->sourceNodeId); | 
| 882 |     QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>(var: referenceToDelete.targetNodeId(), | 
| 883 |                                                                                     ptr: &request.referencesToDelete->targetNodeId); | 
| 884 |     QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(var: referenceToDelete.referenceTypeId(), | 
| 885 |                                                                ptr: &request.referencesToDelete->referenceTypeId); | 
| 886 |     request.referencesToDelete->deleteBidirectional = referenceToDelete.deleteBidirectional(); | 
| 887 |  | 
| 888 |     quint32 requestId = 0; | 
| 889 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &request, requestType: &UA_TYPES[UA_TYPES_DELETEREFERENCESREQUEST], | 
| 890 |                                                       callback: &asyncDeleteReferenceCallback, | 
| 891 |                                                       responseType: &UA_TYPES[UA_TYPES_DELETEREFERENCESRESPONSE], | 
| 892 |                                                       userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 893 |  | 
| 894 |     QOpcUa::UaStatusCode statusCode = static_cast<QOpcUa::UaStatusCode>(result); | 
| 895 |     if (result != UA_STATUSCODE_GOOD) { | 
| 896 |         qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to delete reference from"  << referenceToDelete.sourceNodeId() << "to"  | 
| 897 |                                             << referenceToDelete.targetNodeId().nodeId() << ":"  << statusCode; | 
| 898 |  | 
| 899 |         emit deleteReferenceFinished(sourceNodeId: referenceToDelete.sourceNodeId(), referenceTypeId: referenceToDelete.referenceTypeId(), | 
| 900 |                                      targetNodeId: referenceToDelete.targetNodeId(), | 
| 901 |                                      isForwardReference: referenceToDelete.isForwardReference(), statusCode); | 
| 902 |         return; | 
| 903 |     } | 
| 904 |  | 
| 905 |     m_asyncDeleteReferenceContext[requestId] = { .sourceNodeId: referenceToDelete.sourceNodeId(), .referenceTypeId: referenceToDelete.referenceTypeId(), | 
| 906 |                                                .targetNodeId: referenceToDelete.targetNodeId(), .isForwardReference: referenceToDelete.isForwardReference()}; | 
| 907 | } | 
| 908 |  | 
| 909 | static void convertBrowseResult(UA_BrowseResult *src, size_t referencesSize, QList<QOpcUaReferenceDescription> &dst) | 
| 910 | { | 
| 911 |     if (!src) | 
| 912 |         return; | 
| 913 |  | 
| 914 |     for (size_t i = 0; i < referencesSize; ++i) { | 
| 915 |         QOpcUaReferenceDescription temp; | 
| 916 |         temp.setTargetNodeId(QOpen62541ValueConverter::scalarToQt<QOpcUaExpandedNodeId>(data: &src->references[i].nodeId)); | 
| 917 |         temp.setTypeDefinition(QOpen62541ValueConverter::scalarToQt<QOpcUaExpandedNodeId>(data: &src->references[i].typeDefinition)); | 
| 918 |         temp.setRefTypeId(Open62541Utils::nodeIdToQString(id: src->references[i].referenceTypeId)); | 
| 919 |         temp.setNodeClass(static_cast<QOpcUa::NodeClass>(src->references[i].nodeClass)); | 
| 920 |         temp.setBrowseName(QOpen62541ValueConverter::scalarToQt<QOpcUaQualifiedName, UA_QualifiedName>(data: &src->references[i].browseName)); | 
| 921 |         temp.setDisplayName(QOpen62541ValueConverter::scalarToQt<QOpcUaLocalizedText, UA_LocalizedText>(data: &src->references[i].displayName)); | 
| 922 |         temp.setIsForwardReference(src->references[i].isForward); | 
| 923 |         dst.push_back(t: temp); | 
| 924 |     } | 
| 925 | } | 
| 926 |  | 
| 927 | void Open62541AsyncBackend::browse(quint64 handle, UA_NodeId id, const QOpcUaBrowseRequest &request) | 
| 928 | { | 
| 929 |     if (!m_uaclient) { | 
| 930 |         emit browseFinished(handle, children: {}, statusCode: QOpcUa::UaStatusCode::BadDisconnect); | 
| 931 |         return; | 
| 932 |     } | 
| 933 |  | 
| 934 |     UA_BrowseRequest uaRequest; | 
| 935 |     UA_BrowseRequest_init(p: &uaRequest); | 
| 936 |     UaDeleter<UA_BrowseRequest> requestDeleter(&uaRequest, UA_BrowseRequest_clear); | 
| 937 |  | 
| 938 |     uaRequest.nodesToBrowse = UA_BrowseDescription_new(); | 
| 939 |     uaRequest.nodesToBrowseSize = 1; | 
| 940 |     uaRequest.nodesToBrowse->browseDirection = static_cast<UA_BrowseDirection>(request.browseDirection()); | 
| 941 |     uaRequest.nodesToBrowse->includeSubtypes = request.includeSubtypes(); | 
| 942 |     uaRequest.nodesToBrowse->nodeClassMask = static_cast<quint32>(request.nodeClassMask()); | 
| 943 |     uaRequest.nodesToBrowse->nodeId = id; | 
| 944 |     uaRequest.nodesToBrowse->resultMask = UA_BROWSERESULTMASK_ALL; | 
| 945 |     uaRequest.nodesToBrowse->referenceTypeId = Open62541Utils::nodeIdFromQString(name: request.referenceTypeId()); | 
| 946 |     uaRequest.requestedMaxReferencesPerNode = 0; // Let the server choose a maximum value | 
| 947 |  | 
| 948 |     quint32 requestId = 0; | 
| 949 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &uaRequest, requestType: &UA_TYPES[UA_TYPES_BROWSEREQUEST], callback: &asyncBrowseCallback, | 
| 950 |                                                       responseType: &UA_TYPES[UA_TYPES_BROWSERESPONSE], userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 951 |  | 
| 952 |     if (result != UA_STATUSCODE_GOOD) { | 
| 953 |         emit browseFinished(handle, children: QList<QOpcUaReferenceDescription>(), statusCode: static_cast<QOpcUa::UaStatusCode>(result)); | 
| 954 |         return; | 
| 955 |     } | 
| 956 |  | 
| 957 |     m_asyncBrowseContext[requestId] = { .handle: handle, .isBrowseNext: false, .results: QList<QOpcUaReferenceDescription>() }; | 
| 958 | } | 
| 959 |  | 
| 960 | void Open62541AsyncBackend::clientStateCallback(UA_Client *client, | 
| 961 |                                                 UA_SecureChannelState channelState, | 
| 962 |                                                 UA_SessionState sessionState, | 
| 963 |                                                 UA_StatusCode connectStatus) | 
| 964 | { | 
| 965 |     Q_UNUSED(channelState) | 
| 966 |     Q_UNUSED(sessionState) | 
| 967 |     Q_UNUSED(connectStatus) | 
| 968 |  | 
| 969 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(UA_Client_getContext(client)); | 
| 970 |     if (!backend || !backend->m_useStateCallback) | 
| 971 |         return; | 
| 972 |  | 
| 973 |     backend->m_useStateCallback = false; | 
| 974 |     backend->m_clientIterateTimer.stop(); | 
| 975 |  | 
| 976 |     // UA_Client_disconnect() must be called from outside this callback or open62541 will crash | 
| 977 |     backend->m_disconnectAfterStateChangeTimer.start(); | 
| 978 | } | 
| 979 |  | 
| 980 | void Open62541AsyncBackend::inactivityCallback(UA_Client *client) | 
| 981 | { | 
| 982 |     // The client state callback is not called if the background check encounters a timeout. | 
| 983 |     // This call triggers the disconnect and the appropriate state change | 
| 984 |     clientStateCallback(client, channelState: UA_SECURECHANNELSTATE_CLOSED, sessionState: UA_SESSIONSTATE_CLOSED, UA_STATUSCODE_BADTIMEOUT); | 
| 985 | } | 
| 986 |  | 
| 987 | void Open62541AsyncBackend::connectToEndpoint(const QOpcUaEndpointDescription &endpoint) | 
| 988 | { | 
| 989 |     disconnectInternal(); | 
| 990 |  | 
| 991 |     QString errorMessage; | 
| 992 |     if (!verifyEndpointDescription(endpoint, message: &errorMessage)) { | 
| 993 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << errorMessage; | 
| 994 |         emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::ClientError::InvalidUrl); | 
| 995 |         return; | 
| 996 |     } | 
| 997 |  | 
| 998 |     if (!m_clientImpl->supportedSecurityPolicies().contains(str: endpoint.securityPolicy())) { | 
| 999 | #ifndef UA_ENABLE_ENCRYPTION | 
| 1000 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "The open62541 plugin has been built without encryption support" ; | 
| 1001 | #endif | 
| 1002 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Unsupported security policy:"  << endpoint.securityPolicy(); | 
| 1003 |         emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::ClientError::InvalidUrl); | 
| 1004 |         return; | 
| 1005 |     } | 
| 1006 |  | 
| 1007 |     emit stateAndOrErrorChanged(state: QOpcUaClient::Connecting, error: QOpcUaClient::NoError); | 
| 1008 |  | 
| 1009 |     UA_ClientConfig initialConfig {}; | 
| 1010 |     initialConfig.logger = m_open62541Logger; | 
| 1011 |     m_uaclient = UA_Client_newWithConfig(config: &initialConfig); | 
| 1012 |  | 
| 1013 |     auto conf = UA_Client_getConfig(client: m_uaclient); | 
| 1014 |  | 
| 1015 |     const auto identity = m_clientImpl->m_client->applicationIdentity(); | 
| 1016 |     const auto authInfo = m_clientImpl->m_client->authenticationInformation(); | 
| 1017 |     const auto connectionSettings = m_clientImpl->m_client->connectionSettings(); | 
| 1018 | #ifdef UA_ENABLE_ENCRYPTION | 
| 1019 |     const auto pkiConfig = m_clientImpl->m_client->pkiConfiguration(); | 
| 1020 | #endif | 
| 1021 |  | 
| 1022 | #ifdef UA_ENABLE_ENCRYPTION | 
| 1023 |     if (pkiConfig.isPkiValid()) { | 
| 1024 |         UA_ByteString localCertificate; | 
| 1025 |         UA_ByteString privateKey; | 
| 1026 |         UA_ByteString *trustList = nullptr; | 
| 1027 |         qsizetype trustListSize = 0; | 
| 1028 |         UA_ByteString *revocationList = nullptr; | 
| 1029 |         qsizetype revocationListSize = 0; | 
| 1030 |  | 
| 1031 |         bool success = loadFileToByteString(location: pkiConfig.clientCertificateFile(), target: &localCertificate); | 
| 1032 |  | 
| 1033 |         if (!success) { | 
| 1034 |             qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to load client certificate" ; | 
| 1035 |             emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::AccessDenied); | 
| 1036 |             UA_Client_delete(client: m_uaclient); | 
| 1037 |             m_uaclient = nullptr; | 
| 1038 |             return; | 
| 1039 |         } | 
| 1040 |  | 
| 1041 |         UaDeleter<UA_ByteString> clientCertDeleter(&localCertificate, &UA_ByteString_clear); | 
| 1042 |  | 
| 1043 |         success = loadFileToByteString(location: pkiConfig.privateKeyFile(), target: &privateKey); | 
| 1044 |  | 
| 1045 |         if (!success) { | 
| 1046 |             qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to load private key" ; | 
| 1047 |             emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::AccessDenied); | 
| 1048 |             UA_Client_delete(client: m_uaclient); | 
| 1049 |             m_uaclient = nullptr; | 
| 1050 |             return; | 
| 1051 |         } | 
| 1052 |  | 
| 1053 |         UaDeleter<UA_ByteString> privateKeyDeleter(&privateKey, &UA_ByteString_clear); | 
| 1054 |  | 
| 1055 |         success = loadAllFilesInDirectory(location: pkiConfig.trustListDirectory(), target: &trustList, size: &trustListSize); | 
| 1056 |  | 
| 1057 |         if (!success) { | 
| 1058 |             qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to load trust list" ; | 
| 1059 |             emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::AccessDenied); | 
| 1060 |             UA_Client_delete(client: m_uaclient); | 
| 1061 |             m_uaclient = nullptr; | 
| 1062 |             return; | 
| 1063 |         } | 
| 1064 |  | 
| 1065 |         UaArrayDeleter<UA_TYPES_BYTESTRING> trustListDeleter(trustList, trustListSize); | 
| 1066 |  | 
| 1067 |         success = loadAllFilesInDirectory(location: pkiConfig.revocationListDirectory(), target: &revocationList, size: &revocationListSize); | 
| 1068 |  | 
| 1069 |         if (!success) { | 
| 1070 |             qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to load revocation list" ; | 
| 1071 |             emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::AccessDenied); | 
| 1072 |             UA_Client_delete(client: m_uaclient); | 
| 1073 |             m_uaclient = nullptr; | 
| 1074 |             return; | 
| 1075 |         } | 
| 1076 |  | 
| 1077 |         UaArrayDeleter<UA_TYPES_BYTESTRING> revocationListDeleter(revocationList, revocationListSize); | 
| 1078 |  | 
| 1079 |         UA_StatusCode result = UA_ClientConfig_setDefaultEncryption(config: conf, localCertificate, privateKey, trustList, | 
| 1080 |                                                                     trustListSize, revocationList, revocationListSize); | 
| 1081 |  | 
| 1082 |         if (result != UA_STATUSCODE_GOOD) { | 
| 1083 |             qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to initialize PKI:"  << static_cast<QOpcUa::UaStatusCode>(result); | 
| 1084 |             emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::AccessDenied); | 
| 1085 |             UA_Client_delete(client: m_uaclient); | 
| 1086 |             m_uaclient = nullptr; | 
| 1087 |             return; | 
| 1088 |         } | 
| 1089 |     } else { | 
| 1090 | #else | 
| 1091 |     { | 
| 1092 | #endif | 
| 1093 |         UA_ClientConfig_setDefault(config: conf); | 
| 1094 |     } | 
| 1095 |  | 
| 1096 |     using Timeout_t = decltype(conf->timeout); | 
| 1097 |     conf->timeout = qt_saturate<Timeout_t>(x: connectionSettings.connectTimeout().count()); | 
| 1098 |     conf->secureChannelLifeTime = qt_saturate<Timeout_t>(x: connectionSettings.secureChannelLifeTime().count()); | 
| 1099 |     conf->requestedSessionTimeout = qt_saturate<Timeout_t>(x: connectionSettings.sessionTimeout().count()); | 
| 1100 |  | 
| 1101 |     const auto sessionLocaleIds = connectionSettings.sessionLocaleIds(); | 
| 1102 |     if (!sessionLocaleIds.isEmpty()) { | 
| 1103 |         conf->sessionLocaleIds = static_cast<UA_String *>(UA_Array_new(size: sessionLocaleIds.size(), type: &UA_TYPES[UA_TYPES_STRING])); | 
| 1104 |         for (qsizetype i = 0; i < sessionLocaleIds.size(); ++i) | 
| 1105 |             conf->sessionLocaleIds[i] = UA_STRING_ALLOC(sessionLocaleIds[i].toUtf8().constData()); | 
| 1106 |         conf->sessionLocaleIdsSize = sessionLocaleIds.size(); | 
| 1107 |     } | 
| 1108 |  | 
| 1109 |     UA_LocalizedText_clear(p: &conf->clientDescription.applicationName); | 
| 1110 |     UA_String_clear(p: &conf->clientDescription.applicationUri); | 
| 1111 |     UA_String_clear(p: &conf->clientDescription.productUri); | 
| 1112 |  | 
| 1113 |     conf->clientContext = this; | 
| 1114 |     conf->stateCallback = clientStateCallback; | 
| 1115 |  | 
| 1116 |     // Send periodic read requests as keepalive | 
| 1117 |     conf->connectivityCheckInterval = 60000; | 
| 1118 |     conf->inactivityCallback = inactivityCallback; | 
| 1119 |  | 
| 1120 |     conf->clientDescription.applicationName = UA_LOCALIZEDTEXT_ALLOC(locale: "" , text: identity.applicationName().toUtf8().constData()); | 
| 1121 |     conf->clientDescription.applicationUri  = UA_STRING_ALLOC(identity.applicationUri().toUtf8().constData()); | 
| 1122 |     conf->clientDescription.productUri      = UA_STRING_ALLOC(identity.productUri().toUtf8().constData()); | 
| 1123 |     conf->clientDescription.applicationType = UA_APPLICATIONTYPE_CLIENT; | 
| 1124 |  | 
| 1125 |     conf->securityPolicyUri = UA_STRING_ALLOC(endpoint.securityPolicy().toUtf8().constData()); | 
| 1126 |     conf->securityMode = static_cast<UA_MessageSecurityMode>(endpoint.securityMode()); | 
| 1127 |  | 
| 1128 |     UA_StatusCode ret; | 
| 1129 |  | 
| 1130 |     if (authInfo.authenticationType() == QOpcUaUserTokenPolicy::TokenType::Anonymous) { | 
| 1131 |         ret = UA_Client_connect(client: m_uaclient, endpointUrl: endpoint.endpointUrl().toUtf8().constData()); | 
| 1132 |     } else if (authInfo.authenticationType() == QOpcUaUserTokenPolicy::TokenType::Username) { | 
| 1133 |  | 
| 1134 |         bool suitableTokenFound = false; | 
| 1135 |         const auto userIdentityTokens = endpoint.userIdentityTokens(); | 
| 1136 |         for (const auto &token : userIdentityTokens) { | 
| 1137 |             if (token.tokenType() == QOpcUaUserTokenPolicy::Username && | 
| 1138 |                     m_clientImpl->supportedSecurityPolicies().contains(str: token.securityPolicy())) { | 
| 1139 |                 suitableTokenFound = true; | 
| 1140 |                 break; | 
| 1141 |             } | 
| 1142 |         } | 
| 1143 |  | 
| 1144 |         if (!suitableTokenFound) { | 
| 1145 |             qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "No suitable user token policy found" ; | 
| 1146 |             emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::ClientError::NoError); | 
| 1147 |             UA_Client_delete(client: m_uaclient); | 
| 1148 |             m_uaclient = nullptr; | 
| 1149 |             return; | 
| 1150 |         } | 
| 1151 |  | 
| 1152 |         const auto credentials = authInfo.authenticationData().value<QPair<QString, QString>>(); | 
| 1153 |         ret = UA_Client_connectUsername(client: m_uaclient, endpointUrl: endpoint.endpointUrl().toUtf8().constData(), | 
| 1154 |                                          username: credentials.first.toUtf8().constData(), password: credentials.second.toUtf8().constData()); | 
| 1155 |     } else { | 
| 1156 |         emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::UnsupportedAuthenticationInformation); | 
| 1157 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to connect: Selected authentication type"  | 
| 1158 |                                           << authInfo.authenticationType() << "is not supported." ; | 
| 1159 |         UA_Client_delete(client: m_uaclient); | 
| 1160 |         m_uaclient = nullptr; | 
| 1161 |         return; | 
| 1162 |     } | 
| 1163 |  | 
| 1164 |     if (ret != UA_STATUSCODE_GOOD) { | 
| 1165 |         UA_Client_delete(client: m_uaclient); | 
| 1166 |         m_uaclient = nullptr; | 
| 1167 |         QOpcUaClient::ClientError error = ret == UA_STATUSCODE_BADUSERACCESSDENIED ? QOpcUaClient::AccessDenied : QOpcUaClient::UnknownError; | 
| 1168 |  | 
| 1169 |         QOpcUaErrorState errorState; | 
| 1170 |         errorState.setConnectionStep(QOpcUaErrorState::ConnectionStep::Unknown); | 
| 1171 |         errorState.setErrorCode(static_cast<QOpcUa::UaStatusCode>(ret)); | 
| 1172 |         errorState.setClientSideError(false); | 
| 1173 |         errorState.setIgnoreError(false); | 
| 1174 |  | 
| 1175 |         // This signal is connected using Qt::BlockingQueuedConnection. It will place a metacall to a different thread and waits | 
| 1176 |         // until this metacall is fully handled before returning. | 
| 1177 |         emit QOpcUaBackend::connectError(errorState: &errorState); | 
| 1178 |  | 
| 1179 |         emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error); | 
| 1180 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Open62541: Failed to connect" ; | 
| 1181 |         return; | 
| 1182 |     } | 
| 1183 |  | 
| 1184 |     conf->timeout = qt_saturate<Timeout_t>(x: connectionSettings.requestTimeout().count()); | 
| 1185 |  | 
| 1186 |     m_useStateCallback = true; | 
| 1187 |     m_clientIterateTimer.start(msec: m_clientIterateInterval); | 
| 1188 |     emit stateAndOrErrorChanged(state: QOpcUaClient::Connected, error: QOpcUaClient::NoError); | 
| 1189 | } | 
| 1190 |  | 
| 1191 | void Open62541AsyncBackend::disconnectFromEndpoint() | 
| 1192 | { | 
| 1193 |     disconnectInternal(); | 
| 1194 | } | 
| 1195 |  | 
| 1196 | void Open62541AsyncBackend::requestEndpoints(const QUrl &url) | 
| 1197 | { | 
| 1198 |     UA_ClientConfig initialConfig {}; | 
| 1199 |     initialConfig.logger = m_open62541Logger; | 
| 1200 |     UA_ClientConfig_setDefault(config: &initialConfig); | 
| 1201 |     UA_Client *tmpClient = UA_Client_newWithConfig(config: &initialConfig); | 
| 1202 |  | 
| 1203 |     size_t numEndpoints = 0; | 
| 1204 |     UA_EndpointDescription *endpoints = nullptr; | 
| 1205 |     UA_StatusCode res = UA_Client_getEndpoints(client: tmpClient, serverUrl: url.toString(options: QUrl::RemoveUserInfo).toUtf8().constData(), endpointDescriptionsSize: &numEndpoints, endpointDescriptions: &endpoints); | 
| 1206 |     UaArrayDeleter<UA_TYPES_ENDPOINTDESCRIPTION> endpointDescriptionDeleter(endpoints, numEndpoints); | 
| 1207 |     QList<QOpcUaEndpointDescription> ret; | 
| 1208 |  | 
| 1209 |     namespace vc = QOpen62541ValueConverter; | 
| 1210 |     using namespace QOpcUa; | 
| 1211 |     if (res == UA_STATUSCODE_GOOD && numEndpoints) { | 
| 1212 |         for (size_t i = 0; i < numEndpoints ; ++i) { | 
| 1213 |             QOpcUaEndpointDescription epd; | 
| 1214 |             QOpcUaApplicationDescription &apd = epd.serverRef(); | 
| 1215 |  | 
| 1216 |             apd.setApplicationUri(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].server.applicationUri)); | 
| 1217 |             apd.setProductUri(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].server.productUri)); | 
| 1218 |             apd.setApplicationName(vc::scalarToQt<QOpcUaLocalizedText, UA_LocalizedText>(data: &endpoints[i].server.applicationName)); | 
| 1219 |             apd.setApplicationType(static_cast<QOpcUaApplicationDescription::ApplicationType>(endpoints[i].server.applicationType)); | 
| 1220 |             apd.setGatewayServerUri(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].server.gatewayServerUri)); | 
| 1221 |             apd.setDiscoveryProfileUri(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].server.discoveryProfileUri)); | 
| 1222 |             for (size_t j = 0; j < endpoints[i].server.discoveryUrlsSize; ++j) | 
| 1223 |                 apd.discoveryUrlsRef().append(t: vc::scalarToQt<QString, UA_String>(data: &endpoints[i].server.discoveryUrls[j])); | 
| 1224 |  | 
| 1225 |             epd.setEndpointUrl(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].endpointUrl)); | 
| 1226 |             epd.setServerCertificate(vc::scalarToQt<QByteArray, UA_ByteString>(data: &endpoints[i].serverCertificate)); | 
| 1227 |             epd.setSecurityMode(static_cast<QOpcUaEndpointDescription::MessageSecurityMode>(endpoints[i].securityMode)); | 
| 1228 |             epd.setSecurityPolicy(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].securityPolicyUri)); | 
| 1229 |             for (size_t j = 0; j < endpoints[i].userIdentityTokensSize; ++j) { | 
| 1230 |                 QOpcUaUserTokenPolicy policy; | 
| 1231 |                 UA_UserTokenPolicy *policySrc = &endpoints[i].userIdentityTokens[j]; | 
| 1232 |                 policy.setPolicyId(vc::scalarToQt<QString, UA_String>(data: &policySrc->policyId)); | 
| 1233 |                 policy.setTokenType(static_cast<QOpcUaUserTokenPolicy::TokenType>(endpoints[i].userIdentityTokens[j].tokenType)); | 
| 1234 |                 policy.setIssuedTokenType(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].userIdentityTokens[j].issuedTokenType)); | 
| 1235 |                 policy.setIssuerEndpointUrl(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].userIdentityTokens[j].issuerEndpointUrl)); | 
| 1236 |                 policy.setSecurityPolicy(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].userIdentityTokens[j].securityPolicyUri)); | 
| 1237 |                 epd.userIdentityTokensRef().append(t: policy); | 
| 1238 |             } | 
| 1239 |  | 
| 1240 |             epd.setTransportProfileUri(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].transportProfileUri)); | 
| 1241 |             epd.setSecurityLevel(endpoints[i].securityLevel); | 
| 1242 |             ret.append(t: epd); | 
| 1243 |         } | 
| 1244 |     } else { | 
| 1245 |         if (res == UA_STATUSCODE_GOOD) | 
| 1246 |             qWarning() << "Server returned an empty endpoint list" ; | 
| 1247 |         else | 
| 1248 |             qWarning() << "Failed to retrieve endpoints from "  << url.toString(options: QUrl::RemoveUserInfo).toUtf8().constData() | 
| 1249 |                        << "with status"  << UA_StatusCode_name(code: res); | 
| 1250 |     } | 
| 1251 |  | 
| 1252 |     emit endpointsRequestFinished(endpoints: ret, statusCode: static_cast<QOpcUa::UaStatusCode>(res), requestUrl: url); | 
| 1253 |  | 
| 1254 |     UA_Client_delete(client: tmpClient); | 
| 1255 | } | 
| 1256 |  | 
| 1257 | void Open62541AsyncBackend::iterateClient() | 
| 1258 | { | 
| 1259 |     if (!m_uaclient) | 
| 1260 |         return; | 
| 1261 |  | 
| 1262 |     // If BADSERVERNOTCONNECTED is returned, the subscriptions are gone and local information can be deleted. | 
| 1263 |     if (UA_Client_run_iterate(client: m_uaclient, | 
| 1264 |                               timeout: std::max<quint32>(a: 1, b: m_clientIterateInterval / 2)) == UA_STATUSCODE_BADSERVERNOTCONNECTED) { | 
| 1265 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Unable to send publish request" ; | 
| 1266 |         cleanupSubscriptions(); | 
| 1267 |     } | 
| 1268 | } | 
| 1269 |  | 
| 1270 | void Open62541AsyncBackend::handleSubscriptionTimeout(QOpen62541Subscription *sub, QList<QPair<quint64, QOpcUa::NodeAttribute>> items) | 
| 1271 | { | 
| 1272 |     for (auto it : std::as_const(t&: items)) { | 
| 1273 |         auto item = m_attributeMapping.find(key: it.first); | 
| 1274 |         if (item == m_attributeMapping.end()) | 
| 1275 |             continue; | 
| 1276 |         item->remove(key: it.second); | 
| 1277 |     } | 
| 1278 |     m_subscriptions.remove(key: sub->subscriptionId()); | 
| 1279 |     delete sub; | 
| 1280 | } | 
| 1281 |  | 
| 1282 | QOpen62541Subscription *Open62541AsyncBackend::getSubscriptionForItem(quint64 handle, QOpcUa::NodeAttribute attr) | 
| 1283 | { | 
| 1284 |     auto nodeEntry = m_attributeMapping.find(key: handle); | 
| 1285 |     if (nodeEntry == m_attributeMapping.end()) | 
| 1286 |         return nullptr; | 
| 1287 |  | 
| 1288 |     auto subscription = nodeEntry->find(key: attr); | 
| 1289 |     if (subscription == nodeEntry->end()) { | 
| 1290 |         return nullptr; | 
| 1291 |     } | 
| 1292 |  | 
| 1293 |     return subscription.value(); | 
| 1294 | } | 
| 1295 |  | 
| 1296 | QOpcUaApplicationDescription Open62541AsyncBackend::convertApplicationDescription(UA_ApplicationDescription &desc) | 
| 1297 | { | 
| 1298 |     QOpcUaApplicationDescription temp; | 
| 1299 |  | 
| 1300 |     temp.setApplicationUri(QOpen62541ValueConverter::scalarToQt<QString, UA_String>(data: &desc.applicationUri)); | 
| 1301 |     temp.setProductUri(QOpen62541ValueConverter::scalarToQt<QString, UA_String>(data: &desc.productUri)); | 
| 1302 |     temp.setApplicationName(QOpen62541ValueConverter::scalarToQt<QOpcUaLocalizedText, UA_LocalizedText>(data: &desc.applicationName)); | 
| 1303 |     temp.setApplicationType(static_cast<QOpcUaApplicationDescription::ApplicationType>(desc.applicationType)); | 
| 1304 |     temp.setGatewayServerUri(QOpen62541ValueConverter::scalarToQt<QString, UA_String>(data: &desc.gatewayServerUri)); | 
| 1305 |     temp.setDiscoveryProfileUri(QOpen62541ValueConverter::scalarToQt<QString, UA_String>(data: &desc.discoveryProfileUri)); | 
| 1306 |  | 
| 1307 |  | 
| 1308 |     for (size_t i = 0; i < desc.discoveryUrlsSize; ++i) | 
| 1309 |         temp.discoveryUrlsRef().append(t: QOpen62541ValueConverter::scalarToQt<QString, UA_String>(data: &desc.discoveryUrls[i])); | 
| 1310 |  | 
| 1311 |     return temp; | 
| 1312 | } | 
| 1313 |  | 
| 1314 | void Open62541AsyncBackend::cleanupSubscriptions() | 
| 1315 | { | 
| 1316 |     qDeleteAll(c: m_subscriptions); | 
| 1317 |     m_subscriptions.clear(); | 
| 1318 |     m_attributeMapping.clear(); | 
| 1319 |     m_minPublishingInterval = 0; | 
| 1320 | } | 
| 1321 |  | 
| 1322 | void Open62541AsyncBackend::registerNodes(const QStringList &nodesToRegister) | 
| 1323 | { | 
| 1324 |     if (!m_uaclient) { | 
| 1325 |         emit registerNodesFinished(nodesToRegister, registeredNodeIds: {}, statusCode: QOpcUa::UaStatusCode::BadDisconnect); | 
| 1326 |         return; | 
| 1327 |     } | 
| 1328 |  | 
| 1329 |     UA_RegisterNodesRequest req; | 
| 1330 |     UA_RegisterNodesRequest_init(p: &req); | 
| 1331 |  | 
| 1332 |     req.nodesToRegisterSize = nodesToRegister.size(); | 
| 1333 |     req.nodesToRegister = static_cast<UA_NodeId *>(UA_Array_new(size: nodesToRegister.size(), type: &UA_TYPES[UA_TYPES_NODEID])); | 
| 1334 |  | 
| 1335 |     for (int i = 0; i < nodesToRegister.size(); ++i) | 
| 1336 |         QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(var: nodesToRegister.at(i), ptr: &req.nodesToRegister[i]); | 
| 1337 |  | 
| 1338 |     quint32 requestId = 0; | 
| 1339 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_REGISTERNODESREQUEST], | 
| 1340 |                                                       callback: &asyncRegisterNodesCallback, | 
| 1341 |                                                       responseType: &UA_TYPES[UA_TYPES_REGISTERNODESRESPONSE], | 
| 1342 |                                                       userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 1343 |  | 
| 1344 |     UA_RegisterNodesRequest_clear(p: &req); | 
| 1345 |  | 
| 1346 |     if (result != UA_STATUSCODE_GOOD) | 
| 1347 |         emit registerNodesFinished(nodesToRegister, registeredNodeIds: {}, statusCode: QOpcUa::UaStatusCode(result)); | 
| 1348 |     else | 
| 1349 |         m_asyncRegisterUnregisterNodesContext[requestId] = { .nodeIds: nodesToRegister }; | 
| 1350 | } | 
| 1351 |  | 
| 1352 | void Open62541AsyncBackend::unregisterNodes(const QStringList &nodesToUnregister) | 
| 1353 | { | 
| 1354 |     if (!m_uaclient) { | 
| 1355 |         emit unregisterNodesFinished(nodesToUnregister, statusCode: QOpcUa::UaStatusCode::BadDisconnect); | 
| 1356 |         return; | 
| 1357 |     } | 
| 1358 |  | 
| 1359 |     UA_UnregisterNodesRequest req; | 
| 1360 |     UA_UnregisterNodesRequest_init(p: &req); | 
| 1361 |  | 
| 1362 |     req.nodesToUnregisterSize = nodesToUnregister.size(); | 
| 1363 |     req.nodesToUnregister = static_cast<UA_NodeId *>(UA_Array_new(size: nodesToUnregister.size(), type: &UA_TYPES[UA_TYPES_NODEID])); | 
| 1364 |  | 
| 1365 |     for (int i = 0; i < nodesToUnregister.size(); ++i) | 
| 1366 |         QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(var: nodesToUnregister.at(i), ptr: &req.nodesToUnregister[i]); | 
| 1367 |  | 
| 1368 |     quint32 requestId = 0; | 
| 1369 |     UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_UNREGISTERNODESREQUEST], | 
| 1370 |                                                       callback: &asyncUnregisterNodesCallback, | 
| 1371 |                                                       responseType: &UA_TYPES[UA_TYPES_UNREGISTERNODESRESPONSE], | 
| 1372 |                                                       userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); | 
| 1373 |  | 
| 1374 |     UA_UnregisterNodesRequest_clear(p: &req); | 
| 1375 |  | 
| 1376 |     if (result != UA_STATUSCODE_GOOD) | 
| 1377 |         emit unregisterNodesFinished(nodesToUnregister, statusCode: QOpcUa::UaStatusCode(result)); | 
| 1378 |     else | 
| 1379 |         m_asyncRegisterUnregisterNodesContext[requestId] = { .nodeIds: nodesToUnregister }; | 
| 1380 | } | 
| 1381 |  | 
| 1382 | void Open62541AsyncBackend::asyncMethodCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1383 | { | 
| 1384 |     Q_UNUSED(client) | 
| 1385 |  | 
| 1386 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1387 |     const auto context = backend->m_asyncCallContext.take(key: requestId); | 
| 1388 |  | 
| 1389 |     QVariant result; | 
| 1390 |  | 
| 1391 |     const auto cr = static_cast<UA_CallResponse *>(response); | 
| 1392 |  | 
| 1393 |     if (cr->resultsSize && cr->results->outputArgumentsSize > 1 && cr->results->statusCode == UA_STATUSCODE_GOOD) { | 
| 1394 |         QVariantList temp; | 
| 1395 |         for (size_t i = 0; i < cr->results->outputArgumentsSize; ++i) | 
| 1396 |             temp.append(t: QOpen62541ValueConverter::toQVariant(cr->results->outputArguments[i])); | 
| 1397 |  | 
| 1398 |         result = temp; | 
| 1399 |     } else if (cr->resultsSize && cr->results->outputArgumentsSize == 1 && cr->results->statusCode == UA_STATUSCODE_GOOD) { | 
| 1400 |         result = QOpen62541ValueConverter::toQVariant(cr->results->outputArguments[0]); | 
| 1401 |     } | 
| 1402 |  | 
| 1403 |     emit backend->methodCallFinished(handle: context.handle, methodNodeId: context.methodNodeId, result, | 
| 1404 |                                      statusCode: static_cast<QOpcUa::UaStatusCode>(cr->resultsSize ? | 
| 1405 |                                                                            cr->results->statusCode : | 
| 1406 |                                                                            cr->responseHeader.serviceResult)); | 
| 1407 | } | 
| 1408 |  | 
| 1409 | void Open62541AsyncBackend::asyncTranslateBrowsePathCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1410 | { | 
| 1411 |     Q_UNUSED(client) | 
| 1412 |  | 
| 1413 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1414 |     const auto context = backend->m_asyncTranslateContext.take(key: requestId); | 
| 1415 |  | 
| 1416 |     const auto res = static_cast<UA_TranslateBrowsePathsToNodeIdsResponse *>(response); | 
| 1417 |  | 
| 1418 |     if (res->responseHeader.serviceResult != UA_STATUSCODE_GOOD || res->resultsSize != 1) { | 
| 1419 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Translate browse path failed:"  << UA_StatusCode_name(code: res->responseHeader.serviceResult); | 
| 1420 |         emit backend->resolveBrowsePathFinished(handle: context.handle, targets: QList<QOpcUaBrowsePathTarget>(), path: context.path, | 
| 1421 |                                                 statusCode: static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult)); | 
| 1422 |         return; | 
| 1423 |     } | 
| 1424 |  | 
| 1425 |     QList<QOpcUaBrowsePathTarget> ret; | 
| 1426 |     for (size_t i = 0; i < res->results->targetsSize ; ++i) { | 
| 1427 |         QOpcUaBrowsePathTarget temp; | 
| 1428 |         temp.setRemainingPathIndex(res->results->targets[i].remainingPathIndex); | 
| 1429 |         temp.targetIdRef().setNamespaceUri(QString::fromUtf8(utf8: reinterpret_cast<char *>(res->results->targets[i].targetId.namespaceUri.data))); | 
| 1430 |         temp.targetIdRef().setServerIndex(res->results->targets[i].targetId.serverIndex); | 
| 1431 |         temp.targetIdRef().setNodeId(Open62541Utils::nodeIdToQString(id: res->results->targets[i].targetId.nodeId)); | 
| 1432 |         ret.append(t: temp); | 
| 1433 |     } | 
| 1434 |  | 
| 1435 |     emit backend->resolveBrowsePathFinished(handle: context.handle, targets: ret, path: context.path, statusCode: static_cast<QOpcUa::UaStatusCode>(res->results->statusCode)); | 
| 1436 | } | 
| 1437 |  | 
| 1438 | void Open62541AsyncBackend::asyncAddNodeCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1439 | { | 
| 1440 |     Q_UNUSED(client) | 
| 1441 |  | 
| 1442 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1443 |     const auto context = backend->m_asyncAddNodeContext.take(key: requestId); | 
| 1444 |  | 
| 1445 |     const auto res = static_cast<UA_AddNodesResponse *>(response); | 
| 1446 |  | 
| 1447 |     QOpcUa::UaStatusCode status = QOpcUa::UaStatusCode::Good; | 
| 1448 |     QString resultId; | 
| 1449 |     if (res->responseHeader.serviceResult == UA_STATUSCODE_GOOD) { | 
| 1450 |         if (res->results->statusCode == UA_STATUSCODE_GOOD) | 
| 1451 |             resultId = Open62541Utils::nodeIdToQString(id: res->results->addedNodeId); | 
| 1452 |         else { | 
| 1453 |             status = static_cast<QOpcUa::UaStatusCode>(res->results->statusCode); | 
| 1454 |             qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add node:"  << status; | 
| 1455 |         } | 
| 1456 |     } else { | 
| 1457 |         status = static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult); | 
| 1458 |         qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add node:"  << status; | 
| 1459 |     } | 
| 1460 |  | 
| 1461 |     emit backend->addNodeFinished(requestedNodeId: context.requestedNodeId, assignedNodeId: resultId, statusCode: status); | 
| 1462 | } | 
| 1463 |  | 
| 1464 | void Open62541AsyncBackend::asyncDeleteNodeCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1465 | { | 
| 1466 |     Q_UNUSED(client) | 
| 1467 |  | 
| 1468 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1469 |     const auto context = backend->m_asyncDeleteNodeContext.take(key: requestId); | 
| 1470 |  | 
| 1471 |     const auto res = static_cast<UA_DeleteNodesResponse *>(response); | 
| 1472 |  | 
| 1473 |     emit backend->deleteNodeFinished(nodeId: context.nodeId, | 
| 1474 |                                      statusCode: static_cast<QOpcUa::UaStatusCode>(res->resultsSize ? | 
| 1475 |                                                                            res->results[0] : res->responseHeader.serviceResult)); | 
| 1476 | } | 
| 1477 |  | 
| 1478 | void Open62541AsyncBackend::asyncAddReferenceCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1479 | { | 
| 1480 |     Q_UNUSED(client) | 
| 1481 |  | 
| 1482 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1483 |     const auto context = backend->m_asyncAddReferenceContext.take(key: requestId); | 
| 1484 |  | 
| 1485 |     const auto res = static_cast<UA_AddReferencesResponse *>(response); | 
| 1486 |  | 
| 1487 |     emit backend->addReferenceFinished(sourceNodeId: context.sourceNodeId, referenceTypeId: context.referenceTypeId, targetNodeId: context.targetNodeId, | 
| 1488 |                                        isForwardReference: context.isForwardReference, | 
| 1489 |                                        statusCode: static_cast<QOpcUa::UaStatusCode>(res->resultsSize ? | 
| 1490 |                                                                              res->results[0] : res->responseHeader.serviceResult)); | 
| 1491 | } | 
| 1492 |  | 
| 1493 | void Open62541AsyncBackend::asyncDeleteReferenceCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1494 | { | 
| 1495 |     Q_UNUSED(client) | 
| 1496 |  | 
| 1497 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1498 |     const auto context = backend->m_asyncDeleteReferenceContext.take(key: requestId); | 
| 1499 |  | 
| 1500 |     const auto res = static_cast<UA_DeleteReferencesResponse *>(response); | 
| 1501 |  | 
| 1502 |     emit backend->deleteReferenceFinished(sourceNodeId: context.sourceNodeId, referenceTypeId: context.referenceTypeId, | 
| 1503 |                                           targetNodeId: context.targetNodeId, isForwardReference: context.isForwardReference, | 
| 1504 |                                           statusCode: static_cast<QOpcUa::UaStatusCode>(res->resultsSize ? | 
| 1505 |                                                                                 res->results[0] : | 
| 1506 |                                                                             res->responseHeader.serviceResult)); | 
| 1507 | } | 
| 1508 |  | 
| 1509 | void Open62541AsyncBackend::asyncReadCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1510 | { | 
| 1511 |     Q_UNUSED(client) | 
| 1512 |  | 
| 1513 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1514 |     auto context = backend->m_asyncReadContext.take(key: requestId); | 
| 1515 |  | 
| 1516 |     const auto res = static_cast<UA_ReadResponse *>(response); | 
| 1517 |  | 
| 1518 |     for (qsizetype i = 0; i < context.results.size(); ++i) { | 
| 1519 |         // Use the service result as status code if there is no specific result for the current value. | 
| 1520 |         // This ensures a result for each attribute when UA_Client_Service_read is called for a disconnected client. | 
| 1521 |         if (static_cast<size_t>(i) >= res->resultsSize) { | 
| 1522 |             context.results[i].setStatusCode(static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult)); | 
| 1523 |             continue; | 
| 1524 |         } | 
| 1525 |         if (res->results[i].hasStatus) | 
| 1526 |             context.results[i].setStatusCode(static_cast<QOpcUa::UaStatusCode>(res->results[i].status)); | 
| 1527 |         else | 
| 1528 |             context.results[i].setStatusCode(QOpcUa::UaStatusCode::Good); | 
| 1529 |         if (res->results[i].hasValue && res->results[i].value.data) | 
| 1530 |                 context.results[i].setValue(QOpen62541ValueConverter::toQVariant(res->results[i].value)); | 
| 1531 |         if (res->results[i].hasSourceTimestamp) | 
| 1532 |             context.results[i].setSourceTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(data: &res->results[i].sourceTimestamp)); | 
| 1533 |         if (res->results[i].hasServerTimestamp) | 
| 1534 |             context.results[i].setServerTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(data: &res->results[i].serverTimestamp)); | 
| 1535 |     } | 
| 1536 |  | 
| 1537 |     emit backend->attributesRead(handle: context.handle, attributes: context.results, | 
| 1538 |                                  serviceResult: static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult)); | 
| 1539 | } | 
| 1540 |  | 
| 1541 | void Open62541AsyncBackend::asyncWriteAttributesCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1542 | { | 
| 1543 |     Q_UNUSED(client) | 
| 1544 |  | 
| 1545 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1546 |     auto context = backend->m_asyncWriteAttributesContext.take(key: requestId); | 
| 1547 |  | 
| 1548 |     const auto res = static_cast<UA_WriteResponse *>(response); | 
| 1549 |  | 
| 1550 |     size_t index = 0; | 
| 1551 |     for (auto it = context.toWrite.begin(); it != context.toWrite.end(); ++it, ++index) { | 
| 1552 |         QOpcUa::UaStatusCode status = index < res->resultsSize ? | 
| 1553 |                     static_cast<QOpcUa::UaStatusCode>(res->results[index]) : | 
| 1554 |                     static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult); | 
| 1555 |         emit backend->attributeWritten(handle: context.handle, attribute: it.key(), value: it.value(), statusCode: status); | 
| 1556 |     } | 
| 1557 | } | 
| 1558 |  | 
| 1559 | void Open62541AsyncBackend::asyncBrowseCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1560 | { | 
| 1561 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1562 |     auto context = backend->m_asyncBrowseContext.take(key: requestId); | 
| 1563 |  | 
| 1564 |     UA_StatusCode statusCode = UA_STATUSCODE_GOOD; | 
| 1565 |     size_t referencesSize = 0; | 
| 1566 |     UA_BrowseResult *references = nullptr; | 
| 1567 |     UA_ByteString *continuationPoint = nullptr; | 
| 1568 |  | 
| 1569 |     if (context.isBrowseNext) { | 
| 1570 |         const auto res = static_cast<UA_BrowseNextResponse *>(response); | 
| 1571 |         referencesSize = res->resultsSize ? res->results->referencesSize : 0; | 
| 1572 |         references = res->results; | 
| 1573 |         statusCode = referencesSize ? references->statusCode : res->responseHeader.serviceResult; | 
| 1574 |         continuationPoint = res->resultsSize ? &res->results->continuationPoint : nullptr; | 
| 1575 |     } else { | 
| 1576 |         const auto res = static_cast<UA_BrowseResponse *>(response); | 
| 1577 |         referencesSize = res->resultsSize ? res->results->referencesSize : 0; | 
| 1578 |         references = res->results; | 
| 1579 |         statusCode = referencesSize ? references->statusCode : res->responseHeader.serviceResult; | 
| 1580 |         continuationPoint = res->resultsSize ? &res->results->continuationPoint : nullptr; | 
| 1581 |     } | 
| 1582 |  | 
| 1583 |     convertBrowseResult(src: references, referencesSize, dst&: context.results); | 
| 1584 |  | 
| 1585 |     if (statusCode == UA_STATUSCODE_GOOD && continuationPoint && continuationPoint->length) { | 
| 1586 |         UA_BrowseNextRequest request; | 
| 1587 |         UA_BrowseNextRequest_init(p: &request); | 
| 1588 |         UaDeleter<UA_BrowseNextRequest> requestDeleter(&request, UA_BrowseNextRequest_clear); | 
| 1589 |  | 
| 1590 |         request.continuationPointsSize = 1; | 
| 1591 |         request.continuationPoints = UA_ByteString_new(); | 
| 1592 |         UA_ByteString_copy(src: continuationPoint, dst: request.continuationPoints); | 
| 1593 |  | 
| 1594 |         quint32 requestId = 0; | 
| 1595 |         statusCode =__UA_Client_AsyncServiceEx(client, request: &request, requestType: &UA_TYPES[UA_TYPES_BROWSENEXTREQUEST], callback: &asyncBrowseCallback, | 
| 1596 |                                    responseType: &UA_TYPES[UA_TYPES_BROWSENEXTRESPONSE], userdata: backend, requestId: &requestId, timeout: backend->m_asyncRequestTimeout); | 
| 1597 |  | 
| 1598 |         if (statusCode == UA_STATUSCODE_GOOD) { | 
| 1599 |             context.isBrowseNext = true; | 
| 1600 |             backend->m_asyncBrowseContext[requestId] = context; | 
| 1601 |             return; | 
| 1602 |         } | 
| 1603 |     } else if (statusCode != UA_STATUSCODE_GOOD) { | 
| 1604 |         emit backend->browseFinished(handle: context.handle, children: QList<QOpcUaReferenceDescription>(), | 
| 1605 |                                      statusCode: static_cast<QOpcUa::UaStatusCode>(statusCode)); | 
| 1606 |         return; | 
| 1607 |     } | 
| 1608 |  | 
| 1609 |     emit backend->browseFinished(handle: context.handle, children: context.results, statusCode: static_cast<QOpcUa::UaStatusCode>(statusCode)); | 
| 1610 | } | 
| 1611 |  | 
| 1612 | void Open62541AsyncBackend::asyncBatchReadCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1613 | { | 
| 1614 |     Q_UNUSED(client) | 
| 1615 |  | 
| 1616 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1617 |     auto context = backend->m_asyncBatchReadContext.take(key: requestId); | 
| 1618 |  | 
| 1619 |     const auto res = static_cast<UA_ReadResponse *>(response); | 
| 1620 |  | 
| 1621 |     QOpcUa::UaStatusCode serviceResult = static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult); | 
| 1622 |  | 
| 1623 |     if (serviceResult != QOpcUa::UaStatusCode::Good) { | 
| 1624 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch read failed:"  << serviceResult; | 
| 1625 |         emit backend->readNodeAttributesFinished(results: QList<QOpcUaReadResult>(), serviceResult); | 
| 1626 |     } else { | 
| 1627 |         QList<QOpcUaReadResult> ret; | 
| 1628 |  | 
| 1629 |         for (qsizetype i = 0; i < context.nodesToRead.size(); ++i) { | 
| 1630 |             QOpcUaReadResult item; | 
| 1631 |             item.setAttribute(context.nodesToRead.at(i).attribute()); | 
| 1632 |             item.setNodeId(context.nodesToRead.at(i).nodeId()); | 
| 1633 |             item.setIndexRange(context.nodesToRead.at(i).indexRange()); | 
| 1634 |             if (static_cast<size_t>(i) < res->resultsSize) { | 
| 1635 |                 if (res->results[i].hasServerTimestamp) | 
| 1636 |                     item.setServerTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime>(data: &res->results[i].serverTimestamp)); | 
| 1637 |                 if (res->results[i].hasSourceTimestamp) | 
| 1638 |                     item.setSourceTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime>(data: &res->results[i].sourceTimestamp)); | 
| 1639 |                 if (res->results[i].hasValue) | 
| 1640 |                     item.setValue(QOpen62541ValueConverter::toQVariant(res->results[i].value)); | 
| 1641 |                 if (res->results[i].hasStatus) | 
| 1642 |                     item.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res->results[i].status)); | 
| 1643 |                 else | 
| 1644 |                     item.setStatusCode(serviceResult); | 
| 1645 |             } else { | 
| 1646 |                 item.setStatusCode(serviceResult); | 
| 1647 |             } | 
| 1648 |             ret.push_back(t: item); | 
| 1649 |         } | 
| 1650 |         emit backend->readNodeAttributesFinished(results: ret, serviceResult); | 
| 1651 |     } | 
| 1652 | } | 
| 1653 |  | 
| 1654 | void Open62541AsyncBackend::asyncBatchWriteCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1655 | { | 
| 1656 |     Q_UNUSED(client) | 
| 1657 |  | 
| 1658 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1659 |     const auto context = backend->m_asyncBatchWriteContext.take(key: requestId); | 
| 1660 |  | 
| 1661 |     const auto res = static_cast<UA_WriteResponse *>(response); | 
| 1662 |  | 
| 1663 |     QOpcUa::UaStatusCode serviceResult = QOpcUa::UaStatusCode(res->responseHeader.serviceResult); | 
| 1664 |  | 
| 1665 |     if (serviceResult != QOpcUa::UaStatusCode::Good) { | 
| 1666 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch write failed:"  << serviceResult; | 
| 1667 |         emit backend->writeNodeAttributesFinished(results: QList<QOpcUaWriteResult>(), serviceResult); | 
| 1668 |     } else { | 
| 1669 |         QList<QOpcUaWriteResult> ret; | 
| 1670 |  | 
| 1671 |         for (qsizetype i = 0; i < context.nodesToWrite.size(); ++i) { | 
| 1672 |             QOpcUaWriteResult item; | 
| 1673 |             item.setAttribute(context.nodesToWrite.at(i).attribute()); | 
| 1674 |             item.setNodeId(context.nodesToWrite.at(i).nodeId()); | 
| 1675 |             item.setIndexRange(context.nodesToWrite.at(i).indexRange()); | 
| 1676 |             if (static_cast<size_t>(i) < res->resultsSize) | 
| 1677 |                 item.setStatusCode(QOpcUa::UaStatusCode(res->results[i])); | 
| 1678 |             else | 
| 1679 |                 item.setStatusCode(serviceResult); | 
| 1680 |             ret.push_back(t: item); | 
| 1681 |         } | 
| 1682 |         emit backend->writeNodeAttributesFinished(results: ret, serviceResult); | 
| 1683 |     } | 
| 1684 | } | 
| 1685 |  | 
| 1686 | void Open62541AsyncBackend::asyncReadHistoryDataCallBack(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1687 | { | 
| 1688 |     Q_UNUSED(client); | 
| 1689 |  | 
| 1690 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1691 |     AsyncReadHistoryDataContext context = backend->m_asyncReadHistoryDataContext.take(key: requestId); | 
| 1692 |  | 
| 1693 |     UA_HistoryReadResponse* res = static_cast<UA_HistoryReadResponse*>(response); | 
| 1694 |  | 
| 1695 |     QList<QByteArray> continuationPoints; | 
| 1696 |  | 
| 1697 |     QList<QOpcUaHistoryData> historyData; | 
| 1698 |  | 
| 1699 |     for (size_t i = 0; i < res->resultsSize; ++i) { | 
| 1700 |         if (res->results[i].historyData.encoding != UA_EXTENSIONOBJECT_DECODED) { | 
| 1701 |             emit backend->historyDataAvailable(data: {}, continuationPoints: {}, serviceResult: QOpcUa::UaStatusCode(res->responseHeader.serviceResult), handle: context.handle); | 
| 1702 |             return; | 
| 1703 |         } | 
| 1704 |  | 
| 1705 |         historyData.push_back(t: QOpcUaHistoryData(context.historyReadRawRequest.nodesToRead().at(i).nodeId())); | 
| 1706 |  | 
| 1707 |         historyData[i].setStatusCode(QOpcUa::UaStatusCode(res->results[i].statusCode)); | 
| 1708 |  | 
| 1709 |         if (res->results[i].statusCode != UA_STATUSCODE_GOOD) | 
| 1710 |             continue; | 
| 1711 |  | 
| 1712 |         if (res->results[i].historyData.content.decoded.type != &UA_TYPES[UA_TYPES_HISTORYDATA]) { | 
| 1713 |             historyData[i].setStatusCode(QOpcUa::UaStatusCode::BadInternalError); | 
| 1714 |             continue; | 
| 1715 |         } | 
| 1716 |  | 
| 1717 |         UA_HistoryData *data = static_cast<UA_HistoryData *>(res->results[i].historyData.content.decoded.data); | 
| 1718 |         for (size_t j = 0; j < data->dataValuesSize; ++j) { | 
| 1719 |             const QOpcUaDataValue value = QOpen62541ValueConverter::scalarToQt<QOpcUaDataValue, UA_DataValue>(data: &data->dataValues[j]); | 
| 1720 |             historyData[i].addValue(value); | 
| 1721 |         } | 
| 1722 |  | 
| 1723 |         continuationPoints.push_back(t: QOpen62541ValueConverter::scalarToQt<QByteArray, UA_ByteString>(data: &res->results[i].continuationPoint)); | 
| 1724 |     } | 
| 1725 |  | 
| 1726 |     emit backend->historyDataAvailable(data: historyData, continuationPoints, serviceResult: QOpcUa::UaStatusCode(res->responseHeader.serviceResult), handle: context.handle); | 
| 1727 | } | 
| 1728 |  | 
| 1729 | void Open62541AsyncBackend::asyncRegisterNodesCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1730 | { | 
| 1731 |     Q_UNUSED(client) | 
| 1732 |  | 
| 1733 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1734 |     const auto context = backend->m_asyncRegisterUnregisterNodesContext.take(key: requestId); | 
| 1735 |  | 
| 1736 |     const auto res = static_cast<UA_RegisterNodesResponse *>(response); | 
| 1737 |  | 
| 1738 |     const auto serviceResult = QOpcUa::UaStatusCode(res->responseHeader.serviceResult); | 
| 1739 |  | 
| 1740 |     if (serviceResult != QOpcUa::UaStatusCode::Good) { | 
| 1741 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Register nodes failed:"  << serviceResult; | 
| 1742 |         emit backend->registerNodesFinished(nodesToRegister: context.nodeIds, registeredNodeIds: {}, statusCode: serviceResult); | 
| 1743 |     } else { | 
| 1744 |         QStringList resultIds; | 
| 1745 |         for (size_t i = 0; i < res->registeredNodeIdsSize; ++i) | 
| 1746 |             resultIds.push_back(t: QOpen62541ValueConverter::scalarToQt<QString, UA_NodeId>(data: &res->registeredNodeIds[i])); | 
| 1747 |  | 
| 1748 |         emit backend->registerNodesFinished(nodesToRegister: context.nodeIds, registeredNodeIds: resultIds, statusCode: serviceResult); | 
| 1749 |     } | 
| 1750 | } | 
| 1751 |  | 
| 1752 | void Open62541AsyncBackend::asyncUnregisterNodesCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1753 | { | 
| 1754 |     Q_UNUSED(client) | 
| 1755 |  | 
| 1756 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1757 |     const auto context = backend->m_asyncRegisterUnregisterNodesContext.take(key: requestId); | 
| 1758 |  | 
| 1759 |     const auto res = static_cast<UA_UnregisterNodesResponse *>(response); | 
| 1760 |  | 
| 1761 |     const auto serviceResult = QOpcUa::UaStatusCode(res->responseHeader.serviceResult); | 
| 1762 |  | 
| 1763 |     if (serviceResult != QOpcUa::UaStatusCode::Good) | 
| 1764 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Unregister nodes failed:"  << serviceResult; | 
| 1765 |  | 
| 1766 |     emit backend->unregisterNodesFinished(nodesToUnregister: context.nodeIds, statusCode: serviceResult); | 
| 1767 | } | 
| 1768 |  | 
| 1769 | void Open62541AsyncBackend::asyncReadHistoryEventsCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) | 
| 1770 | { | 
| 1771 |     Q_UNUSED(client); | 
| 1772 |  | 
| 1773 |     Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); | 
| 1774 |     auto context = backend->m_asyncReadHistoryEventsContext.take(key: requestId); | 
| 1775 |  | 
| 1776 |     auto res = static_cast<UA_HistoryReadResponse*>(response); | 
| 1777 |  | 
| 1778 |     QList<QByteArray> continuationPoints; | 
| 1779 |  | 
| 1780 |     QList<QOpcUaHistoryEvent> historyEvents; | 
| 1781 |  | 
| 1782 |     for (size_t i = 0; i < res->resultsSize; ++i) { | 
| 1783 |         if (res->results[i].historyData.encoding != UA_EXTENSIONOBJECT_DECODED) { | 
| 1784 |             emit backend->historyEventsAvailable(data: {}, continuationPoints: {}, serviceResult: QOpcUa::UaStatusCode(res->responseHeader.serviceResult), handle: context.handle); | 
| 1785 |             return; | 
| 1786 |         } | 
| 1787 |  | 
| 1788 |         historyEvents.push_back(t: QOpcUaHistoryEvent(context.historyReadEventRequest.nodesToRead().at(i).nodeId())); | 
| 1789 |  | 
| 1790 |         historyEvents[i].setStatusCode(QOpcUa::UaStatusCode(res->results[i].statusCode)); | 
| 1791 |  | 
| 1792 |         if (res->results[i].statusCode != UA_STATUSCODE_GOOD) | 
| 1793 |             continue; | 
| 1794 |  | 
| 1795 |         if (res->results[i].historyData.content.decoded.type != &UA_TYPES[UA_TYPES_HISTORYEVENT]) { | 
| 1796 |             historyEvents[i].setStatusCode(QOpcUa::UaStatusCode::BadInternalError); | 
| 1797 |             continue; | 
| 1798 |         } | 
| 1799 |  | 
| 1800 |         auto events = static_cast<UA_HistoryEvent *>(res->results[i].historyData.content.decoded.data); | 
| 1801 |         for (size_t j = 0; j < events->eventsSize; ++j) { | 
| 1802 |             QVariantList eventFields; | 
| 1803 |             for (size_t k = 0; k < events->events[j].eventFieldsSize; ++k) | 
| 1804 |                 eventFields.push_back(t: QOpen62541ValueConverter::toQVariant(events->events[j].eventFields[k])); | 
| 1805 |             historyEvents.back().addEvent(value: eventFields); | 
| 1806 |         } | 
| 1807 |  | 
| 1808 |         continuationPoints.push_back(t: QOpen62541ValueConverter::scalarToQt<QByteArray, UA_ByteString>(data: &res->results[i].continuationPoint)); | 
| 1809 |     } | 
| 1810 |  | 
| 1811 |     emit backend->historyEventsAvailable(data: historyEvents, continuationPoints, serviceResult: QOpcUa::UaStatusCode(res->responseHeader.serviceResult), handle: context.handle); | 
| 1812 | } | 
| 1813 |  | 
| 1814 | bool Open62541AsyncBackend::loadFileToByteString(const QString &location, UA_ByteString *target) const | 
| 1815 | { | 
| 1816 |     if (location.isEmpty()) { | 
| 1817 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Unable to read from empty file path" ; | 
| 1818 |         return false; | 
| 1819 |     } | 
| 1820 |  | 
| 1821 |     if (!target) { | 
| 1822 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "No target ByteString given" ; | 
| 1823 |         return false; | 
| 1824 |     } | 
| 1825 |  | 
| 1826 |     UA_ByteString_init(p: target); | 
| 1827 |  | 
| 1828 |     QFile file(location); | 
| 1829 |  | 
| 1830 |     if (!file.open(flags: QFile::ReadOnly)) { | 
| 1831 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to open file"  << location << file.errorString(); | 
| 1832 |         return false; | 
| 1833 |     } | 
| 1834 |  | 
| 1835 |     QByteArray data = file.readAll(); | 
| 1836 |  | 
| 1837 |     UA_ByteString temp; | 
| 1838 |     temp.length = data.size(); | 
| 1839 |     if (data.isEmpty()) | 
| 1840 |         temp.data = nullptr; | 
| 1841 |     else { | 
| 1842 |         if (data.startsWith(c: '-')) { // PEM file | 
| 1843 |             // mbedTLS expects PEM encoded data to be null terminated | 
| 1844 |             data = data.append(c: '\0'); | 
| 1845 |             temp.length = data.size(); | 
| 1846 |         } | 
| 1847 |         temp.data = reinterpret_cast<unsigned char *>(data.data()); | 
| 1848 |     } | 
| 1849 |  | 
| 1850 |     return UA_ByteString_copy(src: &temp, dst: target) == UA_STATUSCODE_GOOD; | 
| 1851 | } | 
| 1852 |  | 
| 1853 | bool Open62541AsyncBackend::loadAllFilesInDirectory(const QString &location, UA_ByteString **target, qsizetype *size) const | 
| 1854 | { | 
| 1855 |     if (location.isEmpty()) { | 
| 1856 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Unable to read from empty file path" ; | 
| 1857 |         return false; | 
| 1858 |     } | 
| 1859 |  | 
| 1860 |     if (!target) { | 
| 1861 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "No target ByteString given" ; | 
| 1862 |         return false; | 
| 1863 |     } | 
| 1864 |  | 
| 1865 |     *target = nullptr; | 
| 1866 |     *size = 0; | 
| 1867 |  | 
| 1868 |     QDir dir(location); | 
| 1869 |  | 
| 1870 |     auto entries = dir.entryList(filters: QDir::Files); | 
| 1871 |  | 
| 1872 |     if (entries.isEmpty()) { | 
| 1873 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Directory is empty" ; | 
| 1874 |         return true; | 
| 1875 |     } | 
| 1876 |  | 
| 1877 |     const qsizetype tempSize = entries.size(); | 
| 1878 |     UA_ByteString *list = static_cast<UA_ByteString *>(UA_Array_new(size: tempSize, type: &UA_TYPES[UA_TYPES_BYTESTRING])); | 
| 1879 |  | 
| 1880 |     if (!list) { | 
| 1881 |         qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to allocate memory for loading files in"  << location; | 
| 1882 |         return false; | 
| 1883 |     } | 
| 1884 |  | 
| 1885 |     for (qsizetype i = 0; i < entries.size(); ++i) { | 
| 1886 |         if (!loadFileToByteString(location: dir.filePath(fileName: entries.at(i)), target: &list[i])) { | 
| 1887 |             qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to open file"  << entries.at(i); | 
| 1888 |             UA_Array_delete(p: list, size: tempSize, type: &UA_TYPES[UA_TYPES_BYTESTRING]); | 
| 1889 |             size = 0; | 
| 1890 |             *target = nullptr; | 
| 1891 |             return false; | 
| 1892 |         } | 
| 1893 |     } | 
| 1894 |  | 
| 1895 |     *target = list; | 
| 1896 |     *size = tempSize; | 
| 1897 |  | 
| 1898 |     return true; | 
| 1899 | } | 
| 1900 |  | 
| 1901 | void Open62541AsyncBackend::disconnectInternal(QOpcUaClient::ClientError error) | 
| 1902 | { | 
| 1903 |     m_useStateCallback = false; | 
| 1904 |     m_clientIterateTimer.stop(); | 
| 1905 |     cleanupSubscriptions(); | 
| 1906 |  | 
| 1907 |     if (m_uaclient) { | 
| 1908 |         UA_Client_disconnect(client: m_uaclient); | 
| 1909 |         UA_Client_delete(client: m_uaclient); | 
| 1910 |         m_uaclient = nullptr; | 
| 1911 |         emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error); | 
| 1912 |     } | 
| 1913 | } | 
| 1914 |  | 
| 1915 | UA_ExtensionObject Open62541AsyncBackend::assembleNodeAttributes(const QOpcUaNodeCreationAttributes &nodeAttributes, | 
| 1916 |                                                                  QOpcUa::NodeClass nodeClass) | 
| 1917 | { | 
| 1918 |     UA_ExtensionObject obj; | 
| 1919 |     UA_ExtensionObject_init(p: &obj); | 
| 1920 |     obj.encoding = UA_EXTENSIONOBJECT_DECODED; | 
| 1921 |  | 
| 1922 |     switch (nodeClass) { | 
| 1923 |     case QOpcUa::NodeClass::Object: { | 
| 1924 |         UA_ObjectAttributes *attr = UA_ObjectAttributes_new(); | 
| 1925 |         *attr = UA_ObjectAttributes_default; | 
| 1926 |         obj.content.decoded.data = attr; | 
| 1927 |         obj.content.decoded.type = &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES]; | 
| 1928 |  | 
| 1929 |         if (nodeAttributes.hasEventNotifier()) { | 
| 1930 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_EVENTNOTIFIER; | 
| 1931 |             attr->eventNotifier = nodeAttributes.eventNotifier(); | 
| 1932 |         } | 
| 1933 |         break; | 
| 1934 |     } | 
| 1935 |     case QOpcUa::NodeClass::Variable: { | 
| 1936 |         UA_VariableAttributes *attr = UA_VariableAttributes_new(); | 
| 1937 |         *attr = UA_VariableAttributes_default; | 
| 1938 |         obj.content.decoded.data = attr; | 
| 1939 |         obj.content.decoded.type = &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES]; | 
| 1940 |  | 
| 1941 |         if (nodeAttributes.hasValue()) { | 
| 1942 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_VALUE; | 
| 1943 |             attr->value = QOpen62541ValueConverter::toOpen62541Variant(nodeAttributes.value(), | 
| 1944 |                                                                        nodeAttributes.valueType()); | 
| 1945 |         } | 
| 1946 |         if (nodeAttributes.hasDataTypeId()) { | 
| 1947 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_DATATYPE; | 
| 1948 |             attr->dataType = Open62541Utils::nodeIdFromQString(name: nodeAttributes.dataTypeId()); | 
| 1949 |         } | 
| 1950 |         if (nodeAttributes.hasValueRank()) { | 
| 1951 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_VALUERANK; | 
| 1952 |             attr->valueRank = nodeAttributes.valueRank(); | 
| 1953 |         } | 
| 1954 |         if (nodeAttributes.hasArrayDimensions()) { | 
| 1955 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ARRAYDIMENSIONS; | 
| 1956 |             attr->arrayDimensions = copyArrayDimensions(arrayDimensions: nodeAttributes.arrayDimensions(), outputSize: &attr->arrayDimensionsSize); | 
| 1957 |         } | 
| 1958 |         if (nodeAttributes.hasAccessLevel()) { | 
| 1959 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ACCESSLEVEL; | 
| 1960 |             attr->accessLevel = nodeAttributes.accessLevel(); | 
| 1961 |         } | 
| 1962 |         if (nodeAttributes.hasUserAccessLevel()) { | 
| 1963 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_USERACCESSLEVEL; | 
| 1964 |             attr->userAccessLevel = nodeAttributes.userAccessLevel(); | 
| 1965 |         } | 
| 1966 |         if (nodeAttributes.hasMinimumSamplingInterval()) { | 
| 1967 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_MINIMUMSAMPLINGINTERVAL; | 
| 1968 |             attr->minimumSamplingInterval = nodeAttributes.minimumSamplingInterval(); | 
| 1969 |         } | 
| 1970 |         if (nodeAttributes.hasHistorizing()) { | 
| 1971 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_HISTORIZING; | 
| 1972 |             attr->historizing = nodeAttributes.historizing(); | 
| 1973 |         } | 
| 1974 |         break; | 
| 1975 |     } | 
| 1976 |     case QOpcUa::NodeClass::Method: { | 
| 1977 |         UA_MethodAttributes *attr = UA_MethodAttributes_new(); | 
| 1978 |         *attr = UA_MethodAttributes_default; | 
| 1979 |         obj.content.decoded.data = attr; | 
| 1980 |         obj.content.decoded.type = &UA_TYPES[UA_TYPES_METHODATTRIBUTES]; | 
| 1981 |  | 
| 1982 |         if (nodeAttributes.hasExecutable()) { | 
| 1983 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_EXECUTABLE; | 
| 1984 |             attr->executable = nodeAttributes.executable(); | 
| 1985 |         } | 
| 1986 |         if (nodeAttributes.hasUserExecutable()) { | 
| 1987 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_USEREXECUTABLE; | 
| 1988 |             attr->userExecutable = nodeAttributes.userExecutable(); | 
| 1989 |         } | 
| 1990 |         break; | 
| 1991 |     } | 
| 1992 |     case QOpcUa::NodeClass::ObjectType: { | 
| 1993 |         UA_ObjectTypeAttributes *attr = UA_ObjectTypeAttributes_new(); | 
| 1994 |         *attr = UA_ObjectTypeAttributes_default; | 
| 1995 |         obj.content.decoded.data = attr; | 
| 1996 |         obj.content.decoded.type = &UA_TYPES[UA_TYPES_OBJECTTYPEATTRIBUTES]; | 
| 1997 |  | 
| 1998 |         if (nodeAttributes.hasIsAbstract()) { | 
| 1999 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ISABSTRACT; | 
| 2000 |             attr->isAbstract = nodeAttributes.isAbstract(); | 
| 2001 |         } | 
| 2002 |         break; | 
| 2003 |     } | 
| 2004 |     case QOpcUa::NodeClass::VariableType: { | 
| 2005 |         UA_VariableTypeAttributes *attr = UA_VariableTypeAttributes_new(); | 
| 2006 |         *attr = UA_VariableTypeAttributes_default; | 
| 2007 |         obj.content.decoded.data = attr; | 
| 2008 |         obj.content.decoded.type = &UA_TYPES[UA_TYPES_VARIABLETYPEATTRIBUTES]; | 
| 2009 |  | 
| 2010 |         if (nodeAttributes.hasValue()) { | 
| 2011 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_VALUE; | 
| 2012 |             attr->value = QOpen62541ValueConverter::toOpen62541Variant(nodeAttributes.value(), | 
| 2013 |                                                                        nodeAttributes.valueType()); | 
| 2014 |         } | 
| 2015 |         if (nodeAttributes.hasDataTypeId()) { | 
| 2016 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_DATATYPE; | 
| 2017 |             attr->dataType = Open62541Utils::nodeIdFromQString(name: nodeAttributes.dataTypeId()); | 
| 2018 |         } | 
| 2019 |         if (nodeAttributes.hasValueRank()) { | 
| 2020 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_VALUERANK; | 
| 2021 |             attr->valueRank = nodeAttributes.valueRank(); | 
| 2022 |         } | 
| 2023 |         if (nodeAttributes.hasArrayDimensions()) { | 
| 2024 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ARRAYDIMENSIONS; | 
| 2025 |             attr->arrayDimensions = copyArrayDimensions(arrayDimensions: nodeAttributes.arrayDimensions(), outputSize: &attr->arrayDimensionsSize); | 
| 2026 |         } | 
| 2027 |         if (nodeAttributes.hasIsAbstract()) { | 
| 2028 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ISABSTRACT; | 
| 2029 |             attr->isAbstract = nodeAttributes.isAbstract(); | 
| 2030 |         } | 
| 2031 |         break; | 
| 2032 |     } | 
| 2033 |     case QOpcUa::NodeClass::ReferenceType: { | 
| 2034 |         UA_ReferenceTypeAttributes *attr = UA_ReferenceTypeAttributes_new(); | 
| 2035 |         *attr = UA_ReferenceTypeAttributes_default; | 
| 2036 |         obj.content.decoded.data = attr; | 
| 2037 |         obj.content.decoded.type = &UA_TYPES[UA_TYPES_REFERENCETYPEATTRIBUTES]; | 
| 2038 |  | 
| 2039 |         if (nodeAttributes.hasIsAbstract()) { | 
| 2040 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ISABSTRACT; | 
| 2041 |             attr->isAbstract = nodeAttributes.isAbstract(); | 
| 2042 |         } | 
| 2043 |         if (nodeAttributes.hasSymmetric()) { | 
| 2044 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_SYMMETRIC; | 
| 2045 |             attr->symmetric = nodeAttributes.symmetric(); | 
| 2046 |         } | 
| 2047 |         if (nodeAttributes.hasInverseName()) { | 
| 2048 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_INVERSENAME; | 
| 2049 |             QOpen62541ValueConverter::scalarFromQt<UA_LocalizedText, QOpcUaLocalizedText>( | 
| 2050 |                         var: nodeAttributes.inverseName(), ptr: &attr->inverseName); | 
| 2051 |         } | 
| 2052 |         break; | 
| 2053 |     } | 
| 2054 |     case QOpcUa::NodeClass::DataType: { | 
| 2055 |         UA_DataTypeAttributes *attr = UA_DataTypeAttributes_new(); | 
| 2056 |         *attr = UA_DataTypeAttributes_default; | 
| 2057 |         obj.content.decoded.data = attr; | 
| 2058 |         obj.content.decoded.type = &UA_TYPES[UA_TYPES_DATATYPEATTRIBUTES]; | 
| 2059 |  | 
| 2060 |         if (nodeAttributes.hasIsAbstract()) { | 
| 2061 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ISABSTRACT; | 
| 2062 |             attr->isAbstract = nodeAttributes.isAbstract(); | 
| 2063 |         } | 
| 2064 |         break; | 
| 2065 |     } | 
| 2066 |     case QOpcUa::NodeClass::View: { | 
| 2067 |         UA_ViewAttributes *attr = UA_ViewAttributes_new(); | 
| 2068 |         *attr = UA_ViewAttributes_default; | 
| 2069 |         obj.content.decoded.data = attr; | 
| 2070 |         obj.content.decoded.type = &UA_TYPES[UA_TYPES_VIEWATTRIBUTES]; | 
| 2071 |  | 
| 2072 |         if (nodeAttributes.hasContainsNoLoops()) { | 
| 2073 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_CONTAINSNOLOOPS; | 
| 2074 |             attr->containsNoLoops = nodeAttributes.containsNoLoops(); | 
| 2075 |         } | 
| 2076 |         if (nodeAttributes.hasEventNotifier()) { | 
| 2077 |             attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_EVENTNOTIFIER; | 
| 2078 |             attr->eventNotifier = nodeAttributes.eventNotifier(); | 
| 2079 |         } | 
| 2080 |         break; | 
| 2081 |     } | 
| 2082 |     default: | 
| 2083 |         qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Could not convert node attributes, unknown node class" ; | 
| 2084 |         UA_ExtensionObject_init(p: &obj); | 
| 2085 |         return obj; | 
| 2086 |     } | 
| 2087 |  | 
| 2088 |     UA_ObjectAttributes *attr = reinterpret_cast<UA_ObjectAttributes *>(obj.content.decoded.data); | 
| 2089 |     if (nodeAttributes.hasDisplayName()) { | 
| 2090 |         attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_DISPLAYNAME; | 
| 2091 |         QOpen62541ValueConverter::scalarFromQt<UA_LocalizedText, QOpcUaLocalizedText>( | 
| 2092 |                     var: nodeAttributes.displayName(), ptr: &attr->displayName); | 
| 2093 |     } | 
| 2094 |     if (nodeAttributes.hasDescription()) { | 
| 2095 |         attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_DESCRIPTION; | 
| 2096 |         QOpen62541ValueConverter::scalarFromQt<UA_LocalizedText, QOpcUaLocalizedText>( | 
| 2097 |                     var: nodeAttributes.description(), ptr: &attr->description); | 
| 2098 |     } | 
| 2099 |     if (nodeAttributes.hasWriteMask()) { | 
| 2100 |         attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_WRITEMASK; | 
| 2101 |         attr->writeMask = nodeAttributes.writeMask(); | 
| 2102 |     } | 
| 2103 |     if (nodeAttributes.hasUserWriteMask()) { | 
| 2104 |         attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_USERWRITEMASK; | 
| 2105 |         attr->userWriteMask = nodeAttributes.userWriteMask(); | 
| 2106 |     } | 
| 2107 |  | 
| 2108 |     return obj; | 
| 2109 | } | 
| 2110 |  | 
| 2111 | UA_UInt32 *Open62541AsyncBackend::copyArrayDimensions(const QList<quint32> &arrayDimensions, size_t *outputSize) | 
| 2112 | { | 
| 2113 |     if (outputSize) | 
| 2114 |         *outputSize = arrayDimensions.size(); | 
| 2115 |  | 
| 2116 |     if (!outputSize) | 
| 2117 |         return nullptr; | 
| 2118 |  | 
| 2119 |     UA_UInt32 *data = nullptr; | 
| 2120 |     UA_StatusCode res = UA_Array_copy(src: arrayDimensions.constData(), size: arrayDimensions.size(), | 
| 2121 |                                       dst: reinterpret_cast<void **>(&data), type: &UA_TYPES[UA_TYPES_UINT32]); | 
| 2122 |     return res == UA_STATUSCODE_GOOD ? data : nullptr; | 
| 2123 | } | 
| 2124 |  | 
| 2125 | QT_END_NAMESPACE | 
| 2126 |  |