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