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#ifdef USE_SYSTEM_OPEN62541
5#include <open62541/client.h>
6#include <open62541/client_config_default.h>
7#else
8#include "qopen62541.h"
9#endif
10
11#include "qopen62541backend.h"
12#include "qopen62541client.h"
13#include "qopen62541node.h"
14#include "qopen62541subscription.h"
15#include "qopen62541utils.h"
16#include "qopen62541valueconverter.h"
17#include <private/qopcuaclient_p.h>
18#include <private/qopcuahistoryreadresponseimpl_p.h>
19#include <private/qopcuasecuritypolicyuris_p.h>
20
21#include <QtCore/qloggingcategory.h>
22#include <QtCore/qstringlist.h>
23#include <QtCore/qthread.h>
24#include <QtCore/qurl.h>
25
26QT_BEGIN_NAMESPACE
27
28Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_OPEN62541)
29
30using namespace Qt::Literals::StringLiterals;
31
32QOpen62541Client::QOpen62541Client(const QVariantMap &backendProperties)
33 : QOpcUaClientImpl()
34 , m_backend(new Open62541AsyncBackend(this))
35{
36#ifdef UA_ENABLE_ENCRYPTION
37 m_hasSha1SignatureSupport = Open62541Utils::checkSha1SignatureSupport();
38
39 if (!m_hasSha1SignatureSupport)
40 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "SHA-1 signatures are not supported by OpenSSL"
41 << "The security policies Basic128Rsa15 and Basic256 will not be available";
42#endif
43
44 bool ok = false;
45 const quint32 clientIterateInterval = backendProperties.value(key: u"clientIterateIntervalMs"_s, defaultValue: 50)
46 .toUInt(ok: &ok);
47
48 if (ok)
49 m_backend->m_clientIterateInterval = clientIterateInterval;
50
51 const quint32 asyncRequestTimeout = backendProperties.value(key: u"asyncRequestTimeoutMs"_s, defaultValue: 15000)
52 .toUInt(ok: &ok);
53
54 if (ok)
55 m_backend->m_asyncRequestTimeout = asyncRequestTimeout;
56
57 m_thread = new QThread();
58 m_thread->setObjectName("QOpen62541Client");
59 connectBackendWithClient(backend: m_backend);
60 m_backend->moveToThread(thread: m_thread);
61 connect(sender: m_thread, signal: &QThread::finished, context: m_thread, slot: &QObject::deleteLater);
62 connect(sender: m_thread, signal: &QThread::finished, context: m_backend, slot: &QObject::deleteLater);
63 m_thread->start();
64}
65
66QOpen62541Client::~QOpen62541Client()
67{
68 // The connectError() and passwordForPrivateKeyRequired() signals use a blocking queued connection.
69 // They must be disconnected before waiting for the thread to avoid a deadlock.
70 QObject::disconnect(sender: m_backend, signal: &Open62541AsyncBackend::connectError, receiver: this, slot: &QOpcUaClientImpl::connectError);
71 QObject::disconnect(sender: m_backend, signal: &Open62541AsyncBackend::passwordForPrivateKeyRequired,
72 receiver: this, slot: &QOpcUaClientImpl::passwordForPrivateKeyRequired);
73
74 if (m_thread->isRunning())
75 m_thread->quit();
76
77 m_thread->wait();
78}
79
80void QOpen62541Client::connectToEndpoint(const QOpcUaEndpointDescription &endpoint)
81{
82 QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::connectToEndpoint, type: Qt::QueuedConnection, args: endpoint);
83}
84
85void QOpen62541Client::disconnectFromEndpoint()
86{
87 QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::disconnectFromEndpoint, type: Qt::QueuedConnection);
88}
89
90QOpcUaNode *QOpen62541Client::node(const QString &nodeId)
91{
92 UA_NodeId uaNodeId = Open62541Utils::nodeIdFromQString(name: nodeId);
93 if (UA_NodeId_isNull(p: &uaNodeId))
94 return nullptr;
95
96 auto tempNode = new QOpen62541Node(uaNodeId, this, nodeId);
97 if (!tempNode->registered()) {
98 qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to register node with backend, maximum number of nodes reached.";
99 delete tempNode;
100 return nullptr;
101 }
102 return new QOpcUaNode(tempNode, m_client);
103}
104
105QString QOpen62541Client::backend() const
106{
107 return u"open62541"_s;
108}
109
110bool QOpen62541Client::requestEndpoints(const QUrl &url)
111{
112 return QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::requestEndpoints,
113 type: Qt::QueuedConnection, args: url);
114}
115
116bool QOpen62541Client::findServers(const QUrl &url, const QStringList &localeIds, const QStringList &serverUris)
117{
118 return QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::findServers,
119 type: Qt::QueuedConnection, args: url, args: localeIds, args: serverUris);
120}
121
122bool QOpen62541Client::readNodeAttributes(const QList<QOpcUaReadItem> &nodesToRead)
123{
124 return QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::readNodeAttributes,
125 type: Qt::QueuedConnection, args: nodesToRead);
126}
127
128bool QOpen62541Client::writeNodeAttributes(const QList<QOpcUaWriteItem> &nodesToWrite)
129{
130 return QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::writeNodeAttributes,
131 type: Qt::QueuedConnection, args: nodesToWrite);
132}
133
134QOpcUaHistoryReadResponse *QOpen62541Client::readHistoryData(const QOpcUaHistoryReadRawRequest &request)
135{
136 if (!m_client)
137 return nullptr;
138
139 auto impl = new QOpcUaHistoryReadResponseImpl(request);
140 auto result = new QOpcUaHistoryReadResponse(impl);
141
142 // Connect signals
143 QObject::connect(sender: m_backend, signal: &QOpcUaBackend::historyDataAvailable, context: impl, slot: &QOpcUaHistoryReadResponseImpl::handleDataAvailable);
144 QObject::connect(sender: impl, signal: &QOpcUaHistoryReadResponseImpl::historyReadRawRequested, context: this, slot: &QOpen62541Client::handleHistoryReadRawRequested);
145 QObject::connect(sender: this, signal: &QOpen62541Client::historyReadRequestError, context: impl, slot: &QOpcUaHistoryReadResponseImpl::handleRequestError);
146
147 auto success = handleHistoryReadRawRequested(request, continuationPoints: {}, releaseContinuationPoints: false, handle: impl->handle());
148
149 if (!success) {
150 delete result;
151 return nullptr;
152 }
153
154 return result;
155}
156
157QOpcUaHistoryReadResponse *QOpen62541Client::readHistoryEvents(const QOpcUaHistoryReadEventRequest &request)
158{
159 if (!m_client)
160 return nullptr;
161
162 auto impl = new QOpcUaHistoryReadResponseImpl(request);
163 const auto result = new QOpcUaHistoryReadResponse(impl);
164
165 // Connect signals
166 QObject::connect(sender: m_backend, signal: &QOpcUaBackend::historyEventsAvailable, context: impl, slot: &QOpcUaHistoryReadResponseImpl::handleEventsAvailable);
167 QObject::connect(sender: impl, signal: &QOpcUaHistoryReadResponseImpl::historyReadEventsRequested, context: this, slot: &QOpen62541Client::handleHistoryReadEventsRequested);
168 QObject::connect(sender: this, signal: &QOpen62541Client::historyReadRequestError, context: impl, slot: &QOpcUaHistoryReadResponseImpl::handleRequestError);
169
170 const auto success = handleHistoryReadEventsRequested(request, continuationPoints: {}, releaseContinuationPoints: false, handle: impl->handle());
171
172 if (!success) {
173 delete result;
174 return nullptr;
175 }
176
177 return result;
178}
179
180bool QOpen62541Client::addNode(const QOpcUaAddNodeItem &nodeToAdd)
181{
182 return QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::addNode,
183 type: Qt::QueuedConnection, args: nodeToAdd);
184}
185
186bool QOpen62541Client::deleteNode(const QString &nodeId, bool deleteTargetReferences)
187{
188 return QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::deleteNode, type: Qt::QueuedConnection,
189 args: nodeId, args&: deleteTargetReferences);
190}
191
192bool QOpen62541Client::addReference(const QOpcUaAddReferenceItem &referenceToAdd)
193{
194 return QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::addReference,
195 type: Qt::QueuedConnection, args: referenceToAdd);
196}
197
198bool QOpen62541Client::deleteReference(const QOpcUaDeleteReferenceItem &referenceToDelete)
199{
200 return QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::deleteReference,
201 type: Qt::QueuedConnection, args: referenceToDelete);
202}
203
204QStringList QOpen62541Client::supportedSecurityPolicies() const
205{
206#ifdef UA_ENABLE_ENCRYPTION
207 size_t numPolicies = m_hasSha1SignatureSupport ? 6 : 4;
208#else
209 size_t numPolicies = 1;
210#endif
211
212 QStringList result;
213 result.reserve(asize: numPolicies);
214 result.append(t: QOpcUa::NonePolicy);
215
216#ifdef UA_ENABLE_ENCRYPTION
217 // Sort by strength
218 if (m_hasSha1SignatureSupport) {
219 result.append(t: QOpcUa::Basic128Rsa15Policy);
220 result.append(t: QOpcUa::Basic256Policy);
221 }
222 result.append(t: QOpcUa::Aes128Sha256RsaOaepPolicy);
223 result.append(t: QOpcUa::Basic256Sha256Policy);
224 result.append(t: QOpcUa::Aes256Sha256RsaPssPolicy);
225#endif
226
227 return result;
228}
229
230QList<QOpcUaUserTokenPolicy::TokenType> QOpen62541Client::supportedUserTokenTypes() const
231{
232 return QList<QOpcUaUserTokenPolicy::TokenType> {
233 QOpcUaUserTokenPolicy::TokenType::Anonymous,
234 QOpcUaUserTokenPolicy::TokenType::Username
235 };
236}
237
238bool QOpen62541Client::handleHistoryReadRawRequested(const QOpcUaHistoryReadRawRequest &request, const QList<QByteArray> &continuationPoints,
239 bool releaseContinuationPoints, quint64 handle)
240{
241 const auto success = QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::readHistoryRaw,
242 type: Qt::QueuedConnection, args: request, args: continuationPoints,
243 args&: releaseContinuationPoints, args&: handle);
244
245 if (!success)
246 emit historyReadRequestError(handle);
247
248 return success;
249}
250
251bool QOpen62541Client::registerNodes(const QStringList &nodesToRegister)
252{
253 return QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::registerNodes,
254 type: Qt::QueuedConnection, args: nodesToRegister);
255}
256
257bool QOpen62541Client::unregisterNodes(const QStringList &nodesToUnregister)
258{
259 return QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::unregisterNodes,
260 type: Qt::QueuedConnection, args: nodesToUnregister);
261}
262
263bool QOpen62541Client::handleHistoryReadEventsRequested(const QOpcUaHistoryReadEventRequest &request, const QList<QByteArray> &continuationPoints,
264 bool releaseContinuationPoints, quint64 handle)
265{
266 const auto success = QMetaObject::invokeMethod(object: m_backend, function: &Open62541AsyncBackend::readHistoryEvents,
267 type: Qt::QueuedConnection, args: request, args: continuationPoints,
268 args&: releaseContinuationPoints, args&: handle);
269
270 if (!success)
271 emit historyReadRequestError(handle);
272
273 return success;
274}
275
276QT_END_NAMESPACE
277

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