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

source code of qtopcua/src/plugins/opcua/open62541/qopen62541backend.cpp