1// Copyright (C) 2016 basysKom GmbH, opensource@basyskom.com
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 <private/qopcuaclient_p.h>
5
6#include <QtCore/qloggingcategory.h>
7#include <QtOpcUa/qopcuaendpointdescription.h>
8
9#include "qopcuaerrorstate.h"
10
11QT_BEGIN_NAMESPACE
12
13Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA)
14
15QOpcUaClientPrivate::QOpcUaClientPrivate(QOpcUaClientImpl *impl)
16 : QObjectPrivate()
17 , m_impl(impl)
18 , m_state(QOpcUaClient::Disconnected)
19 , m_error(QOpcUaClient::NoError)
20 , m_enableNamespaceArrayAutoupdate(false)
21 , m_authenticationInformation(QOpcUaAuthenticationInformation())
22 , m_namespaceArrayAutoupdateEnabled(false)
23 , m_namespaceArrayUpdateInterval(1000)
24{
25 // callback from client implementation
26 QObject::connect(sender: m_impl.data(), signal: &QOpcUaClientImpl::stateAndOrErrorChanged,
27 slot: [this](QOpcUaClient::ClientState state, QOpcUaClient::ClientError error) {
28 setStateAndError(state, error);
29 if (state == QOpcUaClient::ClientState::Connected) {
30 updateNamespaceArray();
31 setupNamespaceArrayMonitoring();
32 }
33 });
34
35 QObject::connect(sender: m_impl.data(), signal: &QOpcUaClientImpl::endpointsRequestFinished, context: m_impl.data(),
36 slot: [this](const QList<QOpcUaEndpointDescription> &e, QOpcUa::UaStatusCode s, const QUrl &requestUrl) {
37 Q_Q(QOpcUaClient);
38 emit q->endpointsRequestFinished(endpoints: e, statusCode: s, requestUrl);
39 });
40
41 QObject::connect(sender: m_impl.data(), signal: &QOpcUaClientImpl::findServersFinished, slot: [this](const QList<QOpcUaApplicationDescription> &a, QOpcUa::UaStatusCode s, const QUrl &requestUrl) {
42 Q_Q(QOpcUaClient);
43 emit q->findServersFinished(servers: a, statusCode: s, requestUrl);
44 });
45
46 QObject::connect(sender: m_impl.data(), signal: &QOpcUaClientImpl::readNodeAttributesFinished, slot: [this](const QList<QOpcUaReadResult> &results, QOpcUa::UaStatusCode serviceResult) {
47 Q_Q(QOpcUaClient);
48 emit q->readNodeAttributesFinished(results, serviceResult);
49 });
50
51 QObject::connect(sender: m_impl.data(), signal: &QOpcUaClientImpl::writeNodeAttributesFinished, slot: [this](const QList<QOpcUaWriteResult> &results, QOpcUa::UaStatusCode serviceResult) {
52 Q_Q(QOpcUaClient);
53 emit q->writeNodeAttributesFinished(results, serviceResult);
54 });
55
56 QObject::connect(sender: m_impl.data(), signal: &QOpcUaClientImpl::addNodeFinished, slot: [this](const QOpcUaExpandedNodeId &requestedNodeId, const QString &assignedNodeId, QOpcUa::UaStatusCode statusCode) {
57 Q_Q(QOpcUaClient);
58 emit q->addNodeFinished(requestedNodeId, assignedNodeId, statusCode);
59 });
60
61 QObject::connect(sender: m_impl.data(), signal: &QOpcUaClientImpl::deleteNodeFinished, slot: [this](const QString &nodeId, QOpcUa::UaStatusCode statusCode) {
62 Q_Q(QOpcUaClient);
63 emit q->deleteNodeFinished(nodeId, statusCode);
64 });
65
66 QObject::connect(sender: m_impl.data(), signal: &QOpcUaClientImpl::addReferenceFinished, slot: [this](const QString &sourceNodeId, const QString &referenceTypeId,
67 const QOpcUaExpandedNodeId &targetNodeId, bool isForwardReference, QOpcUa::UaStatusCode statusCode) {
68 Q_Q(QOpcUaClient);
69 emit q->addReferenceFinished(sourceNodeId, referenceTypeId, targetNodeId, isForwardReference, statusCode);
70 });
71
72 QObject::connect(sender: m_impl.data(), signal: &QOpcUaClientImpl::deleteReferenceFinished, slot: [this](const QString &sourceNodeId, const QString &referenceTypeId,
73 const QOpcUaExpandedNodeId &targetNodeId, bool isForwardReference, QOpcUa::UaStatusCode statusCode) {
74 Q_Q(QOpcUaClient);
75 emit q->deleteReferenceFinished(sourceNodeId, referenceTypeId, targetNodeId, isForwardReference, statusCode);
76 });
77
78 QObject::connect(sender: m_impl.data(), signal: &QOpcUaClientImpl::connectError, slot: [this](QOpcUaErrorState *errorState) {
79 Q_Q(QOpcUaClient);
80 emit q->connectError(errorState);
81 });
82
83 QObject::connect(sender: m_impl.data(), signal: &QOpcUaClientImpl::passwordForPrivateKeyRequired, slot: [this](QString privateKeyFilePath, QString *password, bool previousTryWasInvalid) {
84 Q_Q(QOpcUaClient);
85 emit q->passwordForPrivateKeyRequired(keyFilePath: privateKeyFilePath, password, previousTryWasInvalid);
86 });
87}
88
89QOpcUaClientPrivate::~QOpcUaClientPrivate()
90{
91}
92
93void QOpcUaClientPrivate::connectToEndpoint(const QOpcUaEndpointDescription &endpoint)
94{
95 // Some pre-connection checks
96 if (QOpcUa::isSecurePolicy(securityPolicy: endpoint.securityPolicy())) {
97 // We are going to connect to a secure endpoint
98
99 if (!m_pkiConfig.isPkiValid()) {
100 qCWarning(QT_OPCUA) << "Can not connect to a secure endpoint without a valid PKI setup.";
101 setStateAndError(state: m_state, error: QOpcUaClient::AccessDenied);
102 return;
103 }
104
105 if (!m_pkiConfig.isKeyAndCertificateFileSet()) {
106 qCWarning(QT_OPCUA) << "Can not connect to a secure endpoint without a client certificate.";
107 setStateAndError(state: m_state, error: QOpcUaClient::AccessDenied);
108 return;
109 }
110 }
111
112 m_endpoint = endpoint;
113 m_impl->connectToEndpoint(endpoint);
114}
115
116void QOpcUaClientPrivate::disconnectFromEndpoint()
117{
118 if (m_state != QOpcUaClient::Connected) {
119 qCWarning(QT_OPCUA) << "Closing a connection without being connected";
120 return;
121 }
122
123 setStateAndError(state: QOpcUaClient::Closing);
124 m_impl->disconnectFromEndpoint();
125}
126
127void QOpcUaClientPrivate::setStateAndError(QOpcUaClient::ClientState state,
128 QOpcUaClient::ClientError error)
129{
130 Q_Q(QOpcUaClient);
131
132 // ensure that state and error transition are atomic before signal emission
133 bool stateChanged = false;
134 bool errorOccurred = false;
135
136 if (m_state != state) {
137 m_state = state;
138 stateChanged = true;
139 }
140 if (error != QOpcUaClient::NoError && m_error != error) {
141 errorOccurred = true;
142 }
143 m_error = error;
144
145 if (errorOccurred)
146 emit q->errorChanged(error: m_error);
147 if (stateChanged) {
148 emit q->stateChanged(state: m_state);
149
150 if (m_state == QOpcUaClient::Connected)
151 emit q->connected();
152 else if (m_state == QOpcUaClient::Disconnected)
153 emit q->disconnected();
154 }
155
156 // According to UPC-UA part 5, page 23, the server is allowed to change entries of the namespace
157 // array if there is no active session. This could invalidate the cached namespaces table.
158 if (state == QOpcUaClient::Disconnected) {
159 m_namespaceArray.clear();
160 }
161}
162
163bool QOpcUaClientPrivate::updateNamespaceArray()
164{
165 if (m_state != QOpcUaClient::ClientState::Connected)
166 return false;
167
168 if (!m_namespaceArrayNode) {
169 m_namespaceArrayNode.reset(other: m_impl->node(QStringLiteral("ns=0;i=2255")));
170 if (!m_namespaceArrayNode)
171 return false;
172 QObjectPrivate::connect(sender: m_namespaceArrayNode.data(), signal: &QOpcUaNode::attributeRead, receiverPrivate: this, slot: &QOpcUaClientPrivate::namespaceArrayUpdated);
173 }
174
175 return m_namespaceArrayNode->readAttributes(attributes: QOpcUa::NodeAttribute::Value);
176}
177
178QStringList QOpcUaClientPrivate::namespaceArray() const
179{
180 return m_namespaceArray;
181}
182
183void QOpcUaClientPrivate::namespaceArrayUpdated(QOpcUa::NodeAttributes attr)
184{
185 Q_Q(QOpcUaClient);
186
187 const QVariant value = m_namespaceArrayNode->attribute(attribute: QOpcUa::NodeAttribute::Value);
188
189 if (!(attr & QOpcUa::NodeAttribute::Value) || value.metaType().id() != QMetaType::QVariantList) {
190 m_namespaceArray.clear();
191 emit q->namespaceArrayUpdated(namespaces: QStringList());
192 return;
193 }
194
195 QStringList updatedNamespaceArray;
196 for (const auto &it : value.toList())
197 updatedNamespaceArray.append(t: it.toString());
198
199 if (updatedNamespaceArray != m_namespaceArray) {
200 m_namespaceArray = updatedNamespaceArray;
201 emit q->namespaceArrayChanged(namespaces: m_namespaceArray);
202 }
203 emit q->namespaceArrayUpdated(namespaces: m_namespaceArray);
204}
205
206void QOpcUaClientPrivate::setupNamespaceArrayMonitoring()
207{
208 Q_Q(QOpcUaClient);
209
210 if (!m_namespaceArrayNode || m_state != QOpcUaClient::ClientState::Connected)
211 return;
212
213 if (!m_enableNamespaceArrayAutoupdate && m_namespaceArrayAutoupdateEnabled) {
214 m_namespaceArrayNode->disableMonitoring(attr: QOpcUa::NodeAttribute::Value);
215 m_namespaceArrayAutoupdateEnabled = false;
216 return;
217 }
218
219 if (m_enableNamespaceArrayAutoupdate && !m_namespaceArrayAutoupdateEnabled) {
220 QOpcUaMonitoringParameters options;
221 options.setSubscriptionType(QOpcUaMonitoringParameters::SubscriptionType::Exclusive);
222 options.setMaxKeepAliveCount((std::numeric_limits<quint32>::max)() - 1);
223 options.setPublishingInterval(m_namespaceArrayUpdateInterval);
224 m_namespaceArrayAutoupdateEnabled = true;
225
226 QObject::connect(sender: m_namespaceArrayNode.data(), signal: &QOpcUaNode::enableMonitoringFinished, context: q,
227 slot: [&] (QOpcUa::NodeAttribute, QOpcUa::UaStatusCode statusCode) {
228 if (statusCode == QOpcUa::Good) {
229 // Update interval member to the revised value from the server
230 m_namespaceArrayUpdateInterval = m_namespaceArrayNode->monitoringStatus(attr: QOpcUa::NodeAttribute::Value).publishingInterval();
231 } else {
232 m_namespaceArrayAutoupdateEnabled = m_enableNamespaceArrayAutoupdate = false;
233 }
234 }
235 );
236 QObject::connect(sender: m_namespaceArrayNode.data(), signal: &QOpcUaNode::attributeUpdated, context: q,
237 slot: [&] (QOpcUa::NodeAttribute attr, QVariant /*value*/) {
238 namespaceArrayUpdated(attr);
239 }
240 );
241
242 m_namespaceArrayNode->enableMonitoring(attr: QOpcUa::NodeAttribute::Value, settings: options);
243 }
244}
245
246void QOpcUaClientPrivate::setApplicationIdentity(const QOpcUaApplicationIdentity &identity)
247{
248 m_applicationIdentity = identity;
249}
250
251QOpcUaApplicationIdentity QOpcUaClientPrivate::applicationIdentity() const
252{
253 return m_applicationIdentity;
254}
255
256void QOpcUaClientPrivate::setPkiConfiguration(const QOpcUaPkiConfiguration &config)
257{
258 m_pkiConfig = config;
259}
260
261QOpcUaPkiConfiguration QOpcUaClientPrivate::pkiConfiguration() const
262{
263 return m_pkiConfig;
264}
265
266QT_END_NAMESPACE
267

source code of qtopcua/src/opcua/client/qopcuaclientprivate.cpp