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 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 &current = req.nodesToRead[index++];
90
91 current.attributeId = QOpen62541ValueConverter::toUaAttributeId(attr: attribute);
92 UA_NodeId_copy(src: &id, dst: &current.nodeId);
93 if (indexRange.size())
94 QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: indexRange, ptr: &current.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
118void 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
156void 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
200void 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
260void 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
287void 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
308QOpen62541Subscription *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
333bool 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
345void 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
386void 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
430void 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
478void 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 serversSize = 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
524void 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
566void 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 &currentItem = nodesToWrite.at(i);
587 auto &currentUaItem = 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: &currentUaItem.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: &currentUaItem.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: &currentUaItem.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
625void 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
678void 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
735void 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
784void 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
818void 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
864void 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
909static 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
927void 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
960void 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
980void 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
987void 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
1191void Open62541AsyncBackend::disconnectFromEndpoint()
1192{
1193 disconnectInternal();
1194}
1195
1196void 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
1257void 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
1270void 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
1282QOpen62541Subscription *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
1296QOpcUaApplicationDescription 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
1314void Open62541AsyncBackend::cleanupSubscriptions()
1315{
1316 qDeleteAll(c: m_subscriptions);
1317 m_subscriptions.clear();
1318 m_attributeMapping.clear();
1319 m_minPublishingInterval = 0;
1320}
1321
1322void 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
1352void 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
1382void 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
1409void 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
1438void 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
1464void 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
1478void 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
1493void 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
1509void 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
1541void 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
1559void 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
1612void 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
1654void 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
1686void 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
1729void 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
1752void 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
1769void 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
1814bool 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
1853bool 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
1901void 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
1915UA_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
2111UA_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
2125QT_END_NAMESPACE
2126

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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