1// Copyright (C) 2019 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 "qopcuagdsclient_p.h"
5#include <QOpcUaProvider>
6#include <QOpcUaExtensionObject>
7#include <QOpcUaBinaryDataEncoding>
8#include <QOpcUaErrorState>
9#include <QOpcUaKeyPair>
10#include <QOpcUaX509ExtensionSubjectAlternativeName>
11#include <QOpcUaX509ExtensionBasicConstraints>
12#include <QOpcUaX509ExtensionKeyUsage>
13#include <QFile>
14#include <QTimer>
15#include <QTemporaryFile>
16#include <QSettings>
17#include <QSslCertificate>
18#include <QtCore/qloggingcategory.h>
19
20QT_BEGIN_NAMESPACE
21
22Q_LOGGING_CATEGORY(QT_OPCUA_GDSCLIENT, "qt.opcua.gdsclient")
23
24static const QStringList elementsToResolve {
25 QLatin1String("GetApplication"),
26 QLatin1String("FindApplications"),
27 QLatin1String("RegisterApplication"),
28 QLatin1String("UnregisterApplication"),
29 QLatin1String("GetCertificateGroups"),
30 QLatin1String("GetCertificateStatus"),
31 QLatin1String("StartSigningRequest"),
32 QLatin1String("FinishRequest"),
33 QLatin1String("GetTrustList"),
34};
35
36/*!
37 \class QOpcUaGdsClient
38 \inmodule QtOpcUa
39 \since 5.14
40
41 \brief Handles communication with the GDS Server.
42
43 This class is currently available as a Technology Preview, and therefore the API
44 and functionality provided by the class may be subject to change at any time without
45 prior notice.
46
47 This class handles all steps needed for communication with a GDS server. Provided with
48 information about the application it does registering with the server and managing
49 key/certificates.
50
51 Only few details need to be known in order to take part in a secured network.
52
53 First time registration requires administrative privileges using username and password
54 for authentication. All further authentications are application based, using the certificate
55 which was received first.
56
57 Expecting the whole process to succeed, you have to wait for the applicationRegistered signal.
58
59 Most of the setup structs have to be the same as for the connection with QOpcUaClient afterwards
60 and can be shared.
61
62 Setting up a GDS client:
63
64 \code
65 QOpcUaGdsClient c;
66
67 // In case the credentials are needed
68 QObject::connect(&c, &QOpcUaGdsClient::authenticationRequired, [&](QOpcUaAuthenticationInformation &authInfo) {
69 authInfo.setUsernameAuthentication("root", "secret");
70 });
71
72 // Await success
73 QObject::connect(&c, &QOpcUaGdsClient::applicationRegistered, [&]() {
74 qDebug() << "Application" << c.applicationId() << "registered";
75 });
76
77 c.setBackend(...);
78 c.setEndpoint(...);
79 c.setApplicationIdentity(...);
80 c.setPkiConfiguration(...);
81 c.setApplicationRecord(...);
82 c.setCertificateSigningRequestPresets(...);
83 c.start();
84 \endcode
85*/
86
87/*!
88 \enum QOpcUaGdsClient::Error
89
90 This enum is used to specify errors, that could happen during the registration
91 process.
92
93 \value NoError
94 Everying is fine
95 \value InvalidBackend
96 The backend could not be instantiated.
97 The backend string given, does not match any backend or loading the plugin failed.
98 \value InvalidEndpoint
99 The given endpoint is not valid.
100 \value ConnectionError
101 The connection to the server endpoint failed.
102 \value DirectoryNodeNotFound
103 The directory node on the server could not be resolved
104 \value FailedToRegisterApplication
105 The registration of the application was not successful.
106 \value FailedToUnregisterApplication
107 The unregistration of the application was not successful.
108 \value FailedToGetCertificateStatus
109 The status of the current certificate could not be retrieved.
110 \value FailedToGetCertificate
111 A certificate could not be retrieved from the server.
112*/
113
114/*!
115 \enum QOpcUaGdsClient::State
116
117 This enum is used to specify the current state of the registration of the GDS
118 client.
119
120 \value Idle
121 The client was not started yet.
122 \value BackendInstantiated
123 The backend was instantiated
124 \value Connecting
125 A connecting to the server is being made
126 \value Connected
127 The connection to the server endpoint was successful.
128 \value RegisteringApplication
129 The application is being registered with the server.
130 \value ApplicationRegistered
131 Registering the application with the server was successful.
132 \value Error
133 An error happened. See the return value of error() and the terminal output
134 for more details.
135*/
136
137/*!
138 \fn QOpcUaGdsClient::stateChanged(State state)
139
140 This signal is emitted when the internal state of the client changes.
141 The \a state indicates the new state.
142*/
143
144/*!
145 \fn QOpcUaGdsClient::errorChanged(Error error)
146
147 This signal is emitted when an \a error occurred.
148*/
149
150/*!
151 \fn QOpcUaGdsClient::applicationRegistered()
152
153 This signal is emitted when an application was registered successfully.
154*/
155
156/*!
157 \fn QOpcUaGdsClient::certificateGroupsReceived(QStringList certificateGroups)
158
159 This signal is emitted when the GDS client receives a new list of \a certificateGroups
160 for this application.
161*/
162
163/*!
164 \fn QOpcUaGdsClient::certificateUpdateRequired()
165
166 This signal is emitted when the GDS client detects that an update of the currently
167 used certificate is necessary.
168
169 This could be caused by the server, requesting the client to update the certificate,
170 when the certificate's due date is met or if the certificate is self-signed.
171
172 The certificate update is handled automatically. This signal is only for informational
173 purpose that an update is going to happen.
174*/
175
176/*!
177 \fn QOpcUaGdsClient::certificateUpdated()
178
179 This signal is emitted when the GDS client received a new certificate that was
180 stored on disk.
181*/
182
183/*!
184 \fn QOpcUaGdsClient::unregistered()
185
186 This signal is emitted when the GDS client has unregistered the application.
187*/
188
189/*!
190 \fn QOpcUaGdsClient::trustListUpdated()
191
192 This signal is emitted when the GDS client has received a new trust list from the
193 server and stored to disk.
194*/
195
196/*!
197 \fn QOpcUaGdsClient::authenticationRequired(QOpcUaAuthenticationInformation &authInfo)
198
199 This signal is emitted when the GDS client tries to do a first time authentication
200 with a server, that requires administrative privileges.
201
202 \a authInfo has to be filled with valid authentication information.
203 This slot must not be used crossing thread boundaries.
204*/
205
206/*
207 Step 1
208 Create a self-signed certificate for the application.
209 Previously created key and certificate are reused if present.
210
211 Step 2
212 Connect to the given endpoint and wait for the namespace array to be updated.
213
214 Step 3
215 Find the root node of the GDS API
216
217 Step 4
218 From the root node find the "Directory" node where the method nodes are located.
219 - resolveDirectoryNode()
220 - lambda
221
222 Step 5
223 Resolve the method nodes that are being used.
224 - resolveMethodNodes()
225 - _q_handleResolveBrowsePathFinished()
226
227 Step 6a
228 In case a previous appliation id is known, check if the application registration is still valid.
229 In case it is still valid, reuse it.
230 - getApplication()
231 - handleGetApplicationFinished()
232
233 Step 6b
234 In case there is no valid application id known, try to find a previous registration by the application URI.
235 - findRegisteredApplication()
236 - handleFindApplicationsFinished()
237
238 Step 6c
239 In case an application id was not found by the application URI, register the application.
240 - registerApplication()
241 - handleRegisterApplicationFinished()
242
243 Step 7
244 Get the certificate groups of the application
245 - getCertificateGroups()
246 - handleGetCertificateGroupsFinished()
247 - emit signal certificateGroupsReceived
248
249 Step 8
250 Resolve the certificate types from the certificate groups (CertificateGroup->CertificateTypes)
251 - resolveCertificateTypes()
252 - lambda
253
254 Step 9
255 Read certificate type value from the resolved nodes
256 - getCertificateTypes()
257 - lambda
258
259 Step 10
260 The application now is using a application id from 6a, 6b or 6c, resolved the certificates and is ready to interact with the GDS.
261 - registrationDone()
262 - emit signal applicationRegistered
263 -> Start a certificate check
264 -> Start a trust list update
265
266 Depending on the certificate state, the certificate needs to be updated by the client.
267 This may happen due to
268 - the server requires it (GetCertificateStatus)
269 - the expiry date is due
270 - the certificate is self-signed from the initial connection
271
272 Step 1
273 A timer will trigger the certificate check regularly by triggering
274 - _q_certificateCheckTimeout()
275 - emit signal certificateUpdateRequired
276
277 Step 2a
278 Check locally first, if a renew condition is met.
279 Otherwise ask the server.
280
281 Step 2b
282 Ask the server if the certificate needs renewal
283 - getCertificateStatus()
284 - handleGetCertificateStatusFinished()
285 - emit signal certificateUpdateRequired
286
287 Step 3
288 Start a certificate signing request
289 - startCertificateRequest()
290 - handleStartSigningRequestFinished()
291
292 Step 4
293 Regularly ask the server for the certificate signing result
294 - finishCertificateRequest()
295 - handleFinishRequestFinished()
296 - emit signal certificateUpdated
297 This will return a new certificate.
298*/
299
300/*!
301 Constructs a GDS client with \a parent as the parent object.
302*/
303QOpcUaGdsClient::QOpcUaGdsClient(QObject *parent)
304 : QObject(*(new QOpcUaGdsClientPrivate()), parent)
305{
306 Q_D(QOpcUaGdsClient);
307 d->initializePrivateConnections();
308}
309
310/*!
311 Destructs a GDS client.
312*/
313QOpcUaGdsClient::~QOpcUaGdsClient()
314{
315
316}
317
318/*!
319 Sets the backend to be used by the client to communicate with the server to \a backend.
320
321 This function has to be called before starting the GDS client.
322 Changing this setting afterwards has no effect.
323
324 \sa QOpcUaProvider::availableBackends() start()
325*/
326void QOpcUaGdsClient::setBackend(const QString &backend)
327{
328 Q_D(QOpcUaGdsClient);
329 d->setBackend(backend);
330}
331
332/*!
333 Returns the current backend setting.
334
335 If the backend was changed after starting the client, it will return
336 the changed setting, but not the actually used instance.
337*/
338const QString &QOpcUaGdsClient::backend() const
339{
340 Q_D(const QOpcUaGdsClient);
341 return d->backend();
342}
343
344/*!
345 Sets the endpoint to be used by the client to communicate with the server to \a endpoint.
346
347 This function has to be called before starting the GDS client.
348 Changing this setting afterwards has no effect.
349
350 Communication to a GDS server is only possible through an encrypted endpoint.
351 Using an unencrypted endpoint will fail.
352*/
353void QOpcUaGdsClient::setEndpoint(const QOpcUaEndpointDescription &endpoint)
354{
355 Q_D(QOpcUaGdsClient);
356 d->setEndpoint(endpoint);
357}
358
359/*!
360 Returns the current endpoint setting.
361
362 If the endpoint was changed after starting the client, it will return
363 the changed setting, but not the actually used endpoint.
364*/
365const QOpcUaEndpointDescription &QOpcUaGdsClient::endpoint() const
366{
367 Q_D(const QOpcUaGdsClient);
368 return d->endpoint();
369}
370
371/*!
372 Sets the PKI configuration \a pkiConfig to be used by the client.
373
374 All certificates, keys and trust lists will be used from or stored to
375 the locations given. In order to use the certificate received from
376 the GDS, the same configuration has to be used with QOpcUaClient.
377
378 This function has to be called before starting the GDS client.
379 Changing this setting afterwards has no effect.
380*/
381void QOpcUaGdsClient::setPkiConfiguration(const QOpcUaPkiConfiguration &pkiConfig)
382{
383 Q_D(QOpcUaGdsClient);
384 d->setPkiConfiguration(pkiConfig);
385}
386
387/*!
388 Returns the current pkiConfiguration.
389*/
390const QOpcUaPkiConfiguration &QOpcUaGdsClient::pkiConfiguration() const
391{
392 Q_D(const QOpcUaGdsClient);
393 return d->pkiConfiguration();
394}
395
396/*!
397 Sets the application identity \a appIdentity to be used by the client.
398
399 This identity is used to register with the GDS server.
400 This function has to be called before starting the GDS client.
401 Changing this setting afterwards has no effect.
402*/
403void QOpcUaGdsClient::setApplicationIdentity(const QOpcUaApplicationIdentity &appIdentity)
404{
405 Q_D(QOpcUaGdsClient);
406 d->setApplicationIdentity(appIdentity);
407}
408
409/*!
410 Returns the current applicationIdentity.
411*/
412const QOpcUaApplicationIdentity &QOpcUaGdsClient::applicationIdentity() const
413{
414 Q_D(const QOpcUaGdsClient);
415 return d->applicationIdentity();
416}
417
418/*!
419 Sets the application record data \a appRecord to be used by the client.
420
421 This data is used to register with the GDS server.
422 This function has to be called before starting the GDS client.
423
424 Most of the data is the same as in the application identity.
425 After registration the assigned application id can be retrieved.
426
427 \sa setApplicationIdentity
428*/
429void QOpcUaGdsClient::setApplicationRecord(const QOpcUaApplicationRecordDataType &appRecord)
430{
431 Q_D(QOpcUaGdsClient);
432 d->setApplicationRecord(appRecord);
433}
434
435/*!
436 Returns the application record data that is used by the client.
437*/
438const QOpcUaApplicationRecordDataType &QOpcUaGdsClient::applicationRecord() const
439{
440 Q_D(const QOpcUaGdsClient);
441 return d->applicationRecord();
442}
443
444/*!
445 Returns the application id assigned by the server.
446
447 Is is a shortcut to receive the application id from the application record data.
448
449 \sa applicationRecord()
450*/
451QString QOpcUaGdsClient::applicationId() const
452{
453 Q_D(const QOpcUaGdsClient);
454 return d->applicationId();
455}
456
457/*!
458 Sets the presets for certificate siging requests; the distinguished name \a dn and
459 the DNS string \a dns.
460
461 When creating a certificate signing request some additional information is needed,
462 that is not provided by the application identity.
463
464 This function has to be called before starting the GDS client.
465
466 \sa setApplicationIdentity()
467*/
468void QOpcUaGdsClient::setCertificateSigningRequestPresets(const QOpcUaX509DistinguishedName &dn, const QString &dns)
469{
470 Q_D(QOpcUaGdsClient);
471 d->setCertificateSigningRequestPresets(dn, dns);
472}
473
474/*!
475 Returns the distinguished name preset for certificate siging requests.
476*/
477const QOpcUaX509DistinguishedName &QOpcUaGdsClient::distinguishedNameCertificateSigningRequestPreset() const
478{
479 Q_D(const QOpcUaGdsClient);
480 return d->distinguishedNameCertificateSigningRequestPreset();
481}
482
483/*!
484 Returns the DNS preset for certificate siging requests.
485*/
486const QString &QOpcUaGdsClient::dnsCertificateSigningRequestPreset() const
487{
488 Q_D(const QOpcUaGdsClient);
489 return d->dnsCertificateSigningRequestPreset();
490}
491
492/*!
493 Sets the interval in milliseconds for checking the validity of the client certificate
494 to \a interval.
495*/
496void QOpcUaGdsClient::setCertificateCheckInterval(int interval)
497{
498 Q_D(QOpcUaGdsClient);
499 d->setCertificateCheckInterval(interval);
500}
501
502/*!
503 Returns the interval in milliseconds for checking the validity of the client certificate.
504*/
505int QOpcUaGdsClient::certificateCheckInterval() const
506{
507 Q_D(const QOpcUaGdsClient);
508 return d->certificateCheckInterval();
509}
510
511/*!
512 Sets the interval in milliseconds for updating the trust list from the server
513 to \a interval.
514*/
515void QOpcUaGdsClient::setTrustListUpdateInterval(int interval)
516{
517 Q_D(QOpcUaGdsClient);
518 d->setTrustListUpdateInterval(interval);
519}
520
521/*!
522 Returns the interval in milliseconds for updating the trust list from the server.
523*/
524int QOpcUaGdsClient::trustListUpdateInterval() const
525{
526 Q_D(const QOpcUaGdsClient);
527 return d->trustListUpdateInterval();
528}
529
530/*!
531 Returns the current error state.
532*/
533QOpcUaGdsClient::Error QOpcUaGdsClient::error() const
534{
535 Q_D(const QOpcUaGdsClient);
536 return d->error();
537}
538
539/*!
540 Returns the current client state.
541*/
542QOpcUaGdsClient::State QOpcUaGdsClient::state() const
543{
544 Q_D(const QOpcUaGdsClient);
545 return d->state();
546}
547
548/*!
549 Starts the client process.
550
551 After setting up all information,
552 the client can be started.
553
554 \list
555 \li setBackend
556 \li setEndpoing
557 \li setApplicationIdentity
558 \li setPkiConfiguration
559 \li setApplicationRecord
560 \li setCertificateSigingRequestPresets
561 \endlist
562*/
563void QOpcUaGdsClient::start()
564{
565 Q_D(QOpcUaGdsClient);
566 return d->start();
567}
568
569/*!
570 Unregisters an application from the server.
571
572 This function can be used when an application has to be removed permanently from
573 the network. It does not need to be called when rebooting or shutting down.
574*/
575void QOpcUaGdsClient::unregisterApplication()
576{
577 Q_D(QOpcUaGdsClient);
578 return d->unregisterApplication();
579}
580
581QOpcUaGdsClientPrivate::QOpcUaGdsClientPrivate()
582 : QObjectPrivate()
583 , m_certificateCheckTimer(new QTimer)
584 , m_trustListUpdateTimer(new QTimer)
585{
586 m_certificateCheckTimer->setInterval(60*60*1000);
587 m_trustListUpdateTimer->setInterval(60*60*1000);
588}
589
590QOpcUaGdsClientPrivate::~QOpcUaGdsClientPrivate()
591{
592 delete m_client;
593 delete m_directoryNode;
594 delete m_certificateGroupNode;
595 delete m_certificateTypesNode;
596 delete m_certificateFinishTimer;
597 delete m_certificateCheckTimer;
598 delete m_trustListUpdateTimer;
599 delete m_trustListNode;
600}
601
602void QOpcUaGdsClientPrivate::initializePrivateConnections()
603{
604 Q_Q(QOpcUaGdsClient);
605 QObject::connect(sender: m_certificateCheckTimer, SIGNAL(timeout()), receiver: q, SLOT(_q_certificateCheckTimeout()));
606 QObject::connect(sender: m_trustListUpdateTimer, SIGNAL(timeout()), receiver: q, SLOT(_q_updateTrustList()));
607}
608
609void QOpcUaGdsClientPrivate::setEndpoint(const QOpcUaEndpointDescription &endpoint)
610{
611 m_endpoint = endpoint;
612}
613
614const QOpcUaEndpointDescription &QOpcUaGdsClientPrivate::endpoint() const
615{
616 return m_endpoint;
617}
618
619void QOpcUaGdsClientPrivate::setBackend(const QString &backend)
620{
621 m_backend = backend;
622}
623
624const QString &QOpcUaGdsClientPrivate::backend() const
625{
626 return m_backend;
627}
628
629QString QOpcUaGdsClientPrivate::applicationId() const
630{
631 return m_appRecord.applicationId();
632}
633
634void QOpcUaGdsClientPrivate::setError(QOpcUaGdsClient::Error error)
635{
636 Q_Q(QOpcUaGdsClient);
637 m_error = error;
638 setState(QOpcUaGdsClient::State::Error);
639 emit q->errorChanged(error: m_error);
640}
641
642void QOpcUaGdsClientPrivate::setState(QOpcUaGdsClient::State state)
643{
644 Q_Q(QOpcUaGdsClient);
645 m_state = state;
646 emit q->stateChanged(state);
647}
648
649void QOpcUaGdsClientPrivate::start()
650{
651 Q_Q(QOpcUaGdsClient);
652
653 if (m_backend.isEmpty()) {
654 qCWarning(QT_OPCUA_GDSCLIENT) << "Backend name not set";
655 setError(QOpcUaGdsClient::Error::InvalidBackend);
656 return;
657 }
658
659 if (!m_client) {
660 QOpcUaProvider provider;
661 setState(QOpcUaGdsClient::State::BackendInstantiated);
662 m_client = provider.createClient(backend: m_backend);
663 if (!m_client) {
664 setError(QOpcUaGdsClient::Error::InvalidBackend);
665 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid backend";
666 return;
667 }
668
669 QObject::connect(sender: m_client, signal: &QOpcUaClient::namespaceArrayUpdated, context: q, slot: [this]() {
670 setState(QOpcUaGdsClient::State::Connected);
671 this->resolveDirectoryNode();
672 });
673
674 QObject::connect(sender: m_client, signal: &QOpcUaClient::errorChanged, context: q, slot: [this](QOpcUaClient::ClientError error) {
675 if (error == QOpcUaClient::InvalidUrl) {
676 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid URL";
677 setError(QOpcUaGdsClient::Error::InvalidEndpoint);
678 } else {
679 qCWarning(QT_OPCUA_GDSCLIENT) << "Connection error";
680 setError(QOpcUaGdsClient::Error::ConnectionError);
681 }
682 });
683
684 QObject::connect(sender: m_client, signal: &QOpcUaClient::disconnected, context: q, slot: [this]() {
685 delete m_directoryNode;
686 m_directoryNode = nullptr;
687 m_directoryNodes.clear();
688 setState(QOpcUaGdsClient::State::Idle);
689 if (m_restartRequired) {
690 m_restartRequired = false;
691 this->start();
692 }
693 });
694
695 QObject::connect(sender: m_client, signal: &QOpcUaClient::connectError, context: q, slot: [](QOpcUaErrorState *errorState) {
696 // Ignore all client side errors and continue
697 if (errorState->isClientSideError())
698 errorState->setIgnoreError();
699 });
700 }
701
702 setState(QOpcUaGdsClient::State::Connecting);
703 if (m_endpoint.endpointUrl().isEmpty()) {
704 setError(QOpcUaGdsClient::Error::InvalidEndpoint);
705 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid endpoint";
706 return;
707 }
708
709 QOpcUaKeyPair keyPair;
710 QFile keyFile(m_pkiConfig.privateKeyFile());
711
712 if (!keyFile.exists()) {
713 // File does not exist: Create a key
714 qCDebug(QT_OPCUA_GDSCLIENT) << "Creating a key";
715 keyPair.generateRsaKey(strength: QOpcUaKeyPair::RsaKeyStrength::Bits1024);
716
717 if (!keyFile.open(flags: QFile::WriteOnly | QFile::NewOnly)) {
718 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open private key file" << keyFile.fileName() << "for writing:" << keyFile.errorString();
719 setError(QOpcUaGdsClient::Error::ConnectionError);
720 return;
721 }
722
723 auto data = keyPair.privateKeyToByteArray(cipher: QOpcUaKeyPair::Cipher::Unencrypted, password: QString());
724
725 if (!keyFile.resize(sz: data.size())) {
726 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to set file size";
727 setError(QOpcUaGdsClient::Error::ConnectionError);
728 return;
729 }
730
731 if (!keyFile.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner)) {
732 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to set permissions";
733 setError(QOpcUaGdsClient::Error::ConnectionError);
734 return;
735 }
736
737 keyFile.write(data);
738 keyFile.close();
739 } else {
740 qCDebug(QT_OPCUA_GDSCLIENT) << "Using private key" << keyFile.fileName();
741
742 if (!keyFile.open(flags: QFile::ReadOnly)) {
743 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open private key file" << keyFile.fileName() << "for reading:" << keyFile.errorString();
744 setError(QOpcUaGdsClient::Error::ConnectionError);
745 return;
746 }
747
748 auto data = keyFile.readAll();
749 keyFile.close();
750
751 if (!keyPair.loadFromPemData(data)) {
752 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load private key";
753 setError(QOpcUaGdsClient::Error::ConnectionError);
754 return;
755 }
756
757 if (!keyPair.hasPrivateKey()) {
758 qCWarning(QT_OPCUA_GDSCLIENT) << "Private key does not contain a private key";
759 setError(QOpcUaGdsClient::Error::ConnectionError);
760 return;
761 }
762 }
763
764 QFile certFile(m_pkiConfig.clientCertificateFile());
765 if (!certFile.exists()) {
766 qCDebug(QT_OPCUA_GDSCLIENT) << "Creating self-signed certificate in" << certFile.fileName();
767
768 auto csr = createSigningRequest();
769 csr.setEncoding(QOpcUaX509CertificateSigningRequest::Encoding::DER);
770 auto selfSigned = csr.createSelfSignedCertificate(privateKey: keyPair);
771
772 if (!certFile.open(flags: QFile::WriteOnly | QFile::NewOnly)) {
773 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open certificate file" << certFile.fileName() << "for writing:" << certFile.errorString();
774 setError(QOpcUaGdsClient::Error::ConnectionError);
775 return;
776 }
777
778 if (!certFile.resize(sz: selfSigned.size())) {
779 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to set file size 2";
780 setError(QOpcUaGdsClient::Error::ConnectionError);
781 return;
782 }
783
784 certFile.write(data: selfSigned);
785 certFile.close();
786 }
787
788 // Load persistent data
789 QSettings settings;
790 qCDebug(QT_OPCUA_GDSCLIENT) << "Using settings from" << settings.fileName();
791 const auto applicationId = settings.value(key: QLatin1String("gds/applicationId"), defaultValue: QString()).toString();
792 if (applicationId.isEmpty())
793 qCInfo(QT_OPCUA_GDSCLIENT) << "No application ID in persistent storage";
794 else
795 m_appRecord.setApplicationId(applicationId);
796
797 m_client->setApplicationIdentity(m_appIdentitiy);
798 m_client->setPkiConfiguration(m_pkiConfig);
799 m_client->connectToEndpoint(endpoint: m_endpoint);
800}
801
802void QOpcUaGdsClientPrivate::resolveDirectoryNode()
803{
804 Q_Q(QOpcUaGdsClient);
805
806 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
807 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
808 return;
809 }
810
811 delete m_directoryNode;
812 m_directoryNode = m_client->node(nodeId: QOpcUa::namespace0Id(id: QOpcUa::NodeIds::Namespace0::ObjectsFolder)); // ns=0;i=85
813 if (!m_directoryNode) {
814 qCWarning(QT_OPCUA_GDSCLIENT) << "Root node not found";
815 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
816 return;
817 }
818
819 m_gdsNamespaceIndex = m_client->namespaceArray().indexOf(needle: QLatin1String("http://opcfoundation.org/UA/GDS/"));
820 if (m_gdsNamespaceIndex < 0) {
821 qCWarning(QT_OPCUA_GDSCLIENT) << "Namespace not found";
822 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
823 return;
824 }
825
826 QObject::connect(sender: m_directoryNode, signal: &QOpcUaNode::resolveBrowsePathFinished, context: q,
827 slot: [this, q](QList<QOpcUaBrowsePathTarget> targets,
828 QList<QOpcUaRelativePathElement> path,
829 QOpcUa::UaStatusCode statusCode) {
830 m_directoryNode->deleteLater();
831 m_directoryNode = nullptr;
832
833 if (path.size() != 1) {
834 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size";
835 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
836 return;
837 }
838
839 if (path[0].targetName().name() != QLatin1String("Directory")) {
840 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name" << path[0].targetName().name();
841 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
842 return;
843 }
844
845 if (statusCode != QOpcUa::Good) {
846 qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving directory failed" << statusCode;
847 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
848 return;
849 }
850
851 if (targets.size() != 1) {
852 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results";
853 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
854 return;
855 }
856
857 if (!targets[0].isFullyResolved()) {
858 qCWarning(QT_OPCUA_GDSCLIENT) << "Directory not fully resolved";
859 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
860 return;
861 }
862
863 m_directoryNode = m_client->node(expandedNodeId: targets[0].targetId());
864 if (!m_directoryNode) {
865 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid directory node";
866 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
867 return;
868 }
869
870 auto handleResolve = [this](QList<QOpcUaBrowsePathTarget> targets,
871 QList<QOpcUaRelativePathElement> path,
872 QOpcUa::UaStatusCode statusCode) {
873 _q_handleResolveBrowsePathFinished(targets, path, statusCode);
874 };
875 QObject::connect(sender: m_directoryNode, signal: &QOpcUaNode::resolveBrowsePathFinished,
876 context: q, slot: std::move(handleResolve));
877
878 auto handleDirNode = [this](QString methodNodeId, QVariant result,
879 QOpcUa::UaStatusCode statusCode) {
880 _q_handleDirectoryNodeMethodCallFinished(methodNodeId, result, statusCode);
881 };
882 QObject::connect(sender: m_directoryNode, signal: &QOpcUaNode::methodCallFinished,
883 context: q, slot: std::move(handleDirNode));
884
885 qCDebug(QT_OPCUA_GDSCLIENT) << "Directory node resolved:" << m_directoryNode->nodeId();
886 this->resolveMethodNodes();
887 });
888
889 QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(m_gdsNamespaceIndex, QLatin1String("Directory")),
890 QOpcUa::ReferenceTypeId::Organizes);
891 QList<QOpcUaRelativePathElement> browsePath { pathElement };
892
893 if (!m_directoryNode->resolveBrowsePath(path: browsePath)) {
894 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to resolve directory node";
895 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
896 delete m_directoryNode;
897 m_directoryNode = nullptr;
898 return;
899 }
900}
901
902void QOpcUaGdsClientPrivate::resolveMethodNodes()
903{
904 // See OPC UA Specification 1.05 part 12 6.6.2 "Directory"
905
906 QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(m_gdsNamespaceIndex, QLatin1String()),
907 QOpcUa::ReferenceTypeId::HasComponent);
908 QList<QOpcUaRelativePathElement> browsePath { pathElement };
909
910
911 // Resolve all needed nodes from the directory
912 for (const auto &key : std::as_const(t: elementsToResolve)) {
913 if (!m_directoryNodes.value(key).isEmpty())
914 continue; // Already resolved
915
916 auto target = browsePath[0].targetName();
917 target.setName(key);
918 browsePath[0].setTargetName(target);
919
920 if (!m_directoryNode->resolveBrowsePath(path: browsePath)) {
921 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not resolve Directory node";
922 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
923 return;
924 }
925 }
926}
927
928void QOpcUaGdsClientPrivate::_q_handleResolveBrowsePathFinished(QList<QOpcUaBrowsePathTarget> targets, QList<QOpcUaRelativePathElement> path, QOpcUa::UaStatusCode statusCode) {
929 if (path.size() != 1) {
930 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size";
931 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
932 return;
933 }
934
935 if (m_directoryNodes.contains(key: path[0].targetName().name()) || !elementsToResolve.contains(str: path[0].targetName().name())) {
936 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name" << path[0].targetName().name();
937 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
938 return;
939 }
940
941 if (statusCode != QOpcUa::Good) {
942 qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving directory failed" << statusCode;
943 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
944 return;
945 }
946
947 if (targets.size() != 1) {
948 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results";
949 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
950 return;
951 }
952
953 if (!targets[0].isFullyResolved()) {
954 qCWarning(QT_OPCUA_GDSCLIENT) << "Directory not fully resolved";
955 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
956 return;
957 }
958
959 m_directoryNodes[path[0].targetName().name()] = targets[0].targetId().nodeId();
960
961 if (elementsToResolve.size() == m_directoryNodes.size()) {
962 qCDebug(QT_OPCUA_GDSCLIENT) << "All symbols resolved";
963
964 if (m_appRecord.applicationId().isEmpty() || m_appRecord.applicationId() == QLatin1String("ns=0;i=0"))
965 this->findRegisteredApplication();
966 else
967 this->getApplication();
968 }
969}
970
971void QOpcUaGdsClientPrivate::getApplication()
972{
973 // See OPC UA Specification 1.05 part 12 6.6.9 "GetApplication"
974
975 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
976 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
977 return;
978 }
979
980 if (!m_directoryNode) {
981 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
982 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
983 return;
984 }
985
986 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetApplication")],
987 args: QList<QPair<QVariant, QOpcUa::Types>> { qMakePair(value1: QVariant(m_appRecord.applicationId()), value2: QOpcUa::Types::NodeId) })) {
988 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method GetApplication";
989 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
990 return;
991 }
992}
993
994void QOpcUaGdsClientPrivate::handleGetApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
995{
996 if (statusCode == QOpcUa::BadNotFound) {
997 qCWarning(QT_OPCUA_GDSCLIENT) << "No application with ID" << m_appRecord.applicationId() << "registered";
998 // Clear application ID and register
999 m_appRecord.setApplicationId(QString());
1000
1001 // Remove invalid id from settings
1002 QSettings settings;
1003 settings.remove(key: QLatin1String("gds/applicationId"));
1004 settings.sync();
1005
1006 restartWithCredentials();
1007 return;
1008 }
1009
1010 if (statusCode != QOpcUa::Good) {
1011 qCWarning(QT_OPCUA_GDSCLIENT) << "Get application failed" << statusCode;
1012 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1013 return;
1014 }
1015
1016 auto extensionObject = result.value<QOpcUaExtensionObject>();
1017 if (!extensionObject.encodingTypeId().isEmpty()) {
1018 if (extensionObject.encodingTypeId() != QOpcUa::nodeIdFromInteger(ns: m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary)) {
1019 qCWarning(QT_OPCUA_GDSCLIENT) << "Unexpected return type:" << extensionObject.encodingTypeId();
1020 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1021 return;
1022 }
1023
1024 auto buffer = extensionObject.encodedBody();
1025 QOpcUaBinaryDataEncoding decoder(&buffer);
1026 bool ok;
1027 QOpcUaApplicationRecordDataType appRecord = decoder.decode<QOpcUaApplicationRecordDataType>(success&: ok);
1028
1029 if (!ok) {
1030 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to decode data";
1031 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1032 return;
1033 }
1034
1035 if (m_appRecord.applicationId() != appRecord.applicationId())
1036 qCWarning(QT_OPCUA_GDSCLIENT) << "Returned application record contains a different application ID" << m_appRecord.applicationId() << appRecord.applicationId();
1037
1038 // FIXME: To be removed
1039 appRecord.setApplicationId(m_appRecord.applicationId());
1040
1041 m_appRecord = appRecord;
1042 qCInfo(QT_OPCUA_GDSCLIENT) << "Reusing application ID" << m_appRecord.applicationId() << "which is already registered at the server";
1043 }
1044 getCertificateGroups();
1045}
1046
1047void QOpcUaGdsClientPrivate::findRegisteredApplication()
1048{
1049 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1050 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1051 return;
1052 }
1053
1054 if (!m_directoryNode) {
1055 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
1056 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1057 return;
1058 }
1059
1060 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("FindApplications")],
1061 args: QList<QPair<QVariant, QOpcUa::Types>> { qMakePair(value1: QVariant(m_appRecord.applicationUri()), value2: QOpcUa::Types::String) })) {
1062 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method";
1063 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1064 return;
1065 }
1066}
1067
1068void QOpcUaGdsClientPrivate::handleFindApplicationsFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1069{
1070 if (statusCode != QOpcUa::Good) {
1071 qCWarning(QT_OPCUA_GDSCLIENT) << "Find application failed" << statusCode;
1072 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1073 return;
1074 }
1075
1076 auto extensionObject = result.value<QOpcUaExtensionObject>();
1077
1078 if (extensionObject.encodingTypeId().isEmpty()) {
1079 qCInfo(QT_OPCUA_GDSCLIENT) << "No application with URI" << m_appIdentitiy.applicationUri() << "registered";
1080 this->registerApplication();
1081 return;
1082 }
1083
1084 if (extensionObject.encodingTypeId() != QOpcUa::nodeIdFromInteger(ns: m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary)) {
1085 qCWarning(QT_OPCUA_GDSCLIENT) << "Unexpected return type:" << extensionObject.encodingTypeId();
1086 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1087 return;
1088 }
1089
1090 auto buffer = extensionObject.encodedBody();
1091 QOpcUaBinaryDataEncoding decoder(&buffer);
1092 bool ok;
1093 QOpcUaApplicationRecordDataType appRecord = decoder.decode<QOpcUaApplicationRecordDataType>(success&: ok);
1094
1095 if (!ok) {
1096 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to decode data";
1097 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1098 return;
1099 }
1100
1101 m_appRecord = appRecord;
1102 qCInfo(QT_OPCUA_GDSCLIENT) << "Reusing application ID" << appRecord.applicationId() << "registered for URI" << appRecord.applicationUri();
1103 getCertificateGroups();
1104}
1105
1106void QOpcUaGdsClientPrivate::registerApplication()
1107{
1108 if (!m_appRecord.applicationId().isEmpty() && m_appRecord.applicationId() != QLatin1String("ns=0;i=0"))
1109 return;
1110
1111 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1112 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1113 return;
1114 }
1115
1116 if (!m_directoryNode) {
1117 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
1118 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1119 return;
1120 }
1121
1122 QByteArray buffer;
1123 QOpcUaBinaryDataEncoding encoder(&buffer);
1124 if (!encoder.encode(src: m_appRecord)) {
1125 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to encode body";
1126 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1127 return;
1128 }
1129
1130 QOpcUaExtensionObject parameter;
1131 parameter.setBinaryEncodedBody(encodedBody: buffer, typeId: QOpcUa::nodeIdFromInteger(ns: m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary));
1132
1133 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("RegisterApplication")],
1134 args: QList<QOpcUa::TypedVariant> { QOpcUa::TypedVariant(parameter, QOpcUa::ExtensionObject) })) {
1135 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method RegisterApplication";
1136 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1137 }
1138}
1139
1140void QOpcUaGdsClientPrivate::handleRegisterApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1141{
1142 if (statusCode != QOpcUa::Good) {
1143 qCDebug(QT_OPCUA_GDSCLIENT) << "Requesting user credentials";
1144 if (statusCode == QOpcUa::BadUserAccessDenied && m_client->authenticationInformation().authenticationType() == QOpcUaUserTokenPolicy::Anonymous) {
1145 restartWithCredentials();
1146 return;
1147 }
1148 qCWarning(QT_OPCUA_GDSCLIENT) << "Register application failed with" << statusCode;
1149 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1150 return;
1151 }
1152
1153 m_appRecord.setApplicationId(result.toString());
1154
1155 QSettings settings;
1156 settings.setValue(key: QLatin1String("gds/applicationId"), value: m_appRecord.applicationId());
1157 settings.sync();
1158
1159 qCInfo(QT_OPCUA_GDSCLIENT) << "Registered application with id" << m_appRecord.applicationId();
1160 getCertificateGroups();
1161}
1162
1163void QOpcUaGdsClientPrivate::getCertificateGroups()
1164{
1165 // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.7 GetCertificateGroups
1166
1167 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1168 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1169 return;
1170 }
1171
1172 if (!m_directoryNode) {
1173 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
1174 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1175 return;
1176 }
1177
1178 QList<QOpcUa::TypedVariant> arguments;
1179 arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId));
1180
1181 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetCertificateGroups")], args: arguments)) {
1182 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetCertificateGroups";
1183 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1184 }
1185}
1186
1187void QOpcUaGdsClientPrivate::handleGetCertificateGroupsFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1188{
1189 Q_Q(QOpcUaGdsClient);
1190
1191 if (statusCode != QOpcUa::Good) {
1192 qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate groups failed" << statusCode;
1193 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1194 return;
1195 }
1196
1197 m_certificateGroups = result.value<QStringList>();
1198 qCDebug(QT_OPCUA_GDSCLIENT) << "Certificate groups:" << m_certificateGroups;
1199
1200 emit q->certificateGroupsReceived(certificateGroups: m_certificateGroups);
1201 resolveCertificateTypes();
1202}
1203
1204void QOpcUaGdsClientPrivate::resolveCertificateTypes()
1205{
1206 Q_Q(QOpcUaGdsClient);
1207
1208 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1209 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1210 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1211 return;
1212 }
1213
1214 if (m_certificateGroups.isEmpty()) {
1215 qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate groups is empty";
1216 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1217 return;
1218 }
1219
1220 delete m_certificateGroupNode;
1221 m_certificateGroupNode = m_client->node(nodeId: m_certificateGroups[0]);
1222
1223 if (!m_certificateGroupNode) {
1224 qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate node failed";
1225 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1226 return;
1227 }
1228
1229 QObject::connect(sender: m_certificateGroupNode, signal: &QOpcUaNode::resolveBrowsePathFinished,
1230 context: q, slot: [this](QList<QOpcUaBrowsePathTarget> targets,
1231 QList<QOpcUaRelativePathElement> path,
1232 QOpcUa::UaStatusCode statusCode) {
1233 if (path.size() != 1) {
1234 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size";
1235 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1236 return;
1237 }
1238
1239 if (path[0].targetName().name() != QLatin1String("CertificateTypes")) {
1240 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name" << path[0].targetName().name();
1241 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1242 return;
1243 }
1244
1245 if (statusCode != QOpcUa::Good) {
1246 qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving certificate types failed" << statusCode;
1247 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1248 return;
1249 }
1250
1251 if (targets.size() != 1) {
1252 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results";
1253 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1254 return;
1255 }
1256
1257 if (!targets[0].isFullyResolved()) {
1258 qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types not fully resolved";
1259 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1260 return;
1261 }
1262
1263 m_certificateTypesNodeId = targets[0].targetId().nodeId();
1264 this->getCertificateTypes();
1265 });
1266
1267 QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(0, QLatin1String("CertificateTypes")),
1268 QOpcUa::ReferenceTypeId::Unspecified);
1269 QList<QOpcUaRelativePathElement> browsePath { pathElement };
1270
1271
1272 if (!m_certificateGroupNode->resolveBrowsePath(path: browsePath)) {
1273 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not resolve certificate type";
1274 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1275 return;
1276 }
1277}
1278
1279void QOpcUaGdsClientPrivate::getCertificateTypes()
1280{
1281 Q_Q(QOpcUaGdsClient);
1282
1283 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1284 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1285 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1286 return;
1287 }
1288
1289 if (m_certificateTypesNodeId.isEmpty()) {
1290 qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types node id is empty";
1291 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1292 return;
1293 }
1294
1295 delete m_certificateTypesNode;
1296 m_certificateTypesNode = m_client->node(nodeId: m_certificateTypesNodeId);
1297
1298 if (!m_certificateTypesNode) {
1299 qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types node failed";
1300 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1301 return;
1302 }
1303
1304 QObject::connect(sender: m_certificateTypesNode, signal: &QOpcUaNode::attributeUpdated,
1305 context: q, slot: [this](QOpcUa::NodeAttribute attr, QVariant value) {
1306 if (attr == QOpcUa::NodeAttribute::Value)
1307 qCWarning(QT_OPCUA_GDSCLIENT) << "possible certificate types" << value;
1308 registrationDone();
1309 });
1310
1311 if (!m_certificateTypesNode->readValueAttribute()) {
1312 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not read certificate types";
1313 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1314 return;
1315 }
1316}
1317
1318void QOpcUaGdsClientPrivate::registrationDone()
1319{
1320 Q_Q(QOpcUaGdsClient);
1321
1322 setState(QOpcUaGdsClient::State::ApplicationRegistered);
1323 emit q->applicationRegistered();
1324
1325 m_certificateCheckTimer->start();
1326 _q_certificateCheckTimeout(); // Force a check now
1327
1328 m_trustListUpdateTimer->start();
1329 _q_updateTrustList(); // Force a check now
1330}
1331
1332void QOpcUaGdsClientPrivate::getCertificateStatus()
1333{
1334 // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.10 GetCertificateStatus
1335
1336 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1337 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1338 return;
1339 }
1340
1341 if (!m_directoryNode) {
1342 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
1343 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1344 return;
1345 }
1346
1347 if (m_certificateGroups.isEmpty()) {
1348 qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate groups received";
1349 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1350 return;
1351 }
1352
1353 QString certificateType = QLatin1String("ns=0;i=0"); // null node
1354
1355 QList<QOpcUa::TypedVariant> arguments;
1356 arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId));
1357 // Let the server choose the certificate group id
1358 arguments.push_back(t: QOpcUa::TypedVariant(m_certificateGroups[0], QOpcUa::NodeId));
1359 // Let the server choose the certificate type id
1360 arguments.push_back(t: QOpcUa::TypedVariant(certificateType, QOpcUa::NodeId));
1361
1362 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetCertificateStatus")], args: arguments)) {
1363 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetCertificateStatus";
1364 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1365 }
1366}
1367
1368void QOpcUaGdsClientPrivate::handleGetCertificateStatusFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1369{
1370 Q_Q(QOpcUaGdsClient);
1371
1372 if (statusCode != QOpcUa::Good) {
1373 qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate status failed" << statusCode;
1374 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1375 return;
1376 }
1377
1378 if (result.toBool()) {
1379 qInfo(catFunc: QT_OPCUA_GDSCLIENT) << "Certificate needs update";
1380 emit q->certificateUpdateRequired();
1381 startCertificateRequest();
1382 }
1383}
1384
1385void QOpcUaGdsClientPrivate::startCertificateRequest()
1386{
1387 // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.3 StartSigningRequest
1388
1389 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1390 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1391 return;
1392 }
1393
1394 if (!m_directoryNode) {
1395 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
1396 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1397 return;
1398 }
1399
1400 QOpcUaKeyPair keyPair;
1401 QFile keyFile(m_pkiConfig.privateKeyFile());
1402
1403 qCDebug(QT_OPCUA_GDSCLIENT) << "Using private key" << keyFile.fileName();
1404
1405 if (!keyFile.open(flags: QFile::ReadOnly)) {
1406 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open private key file" << keyFile.fileName() << "for reading:" << keyFile.errorString();
1407 setError(QOpcUaGdsClient::Error::ConnectionError);
1408 return;
1409 }
1410
1411 auto data = keyFile.readAll();
1412 keyFile.close();
1413
1414 if (!keyPair.loadFromPemData(data)) {
1415 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load private key";
1416 setError(QOpcUaGdsClient::Error::ConnectionError);
1417 return;
1418 }
1419
1420 if (!keyPair.hasPrivateKey()) {
1421 qCWarning(QT_OPCUA_GDSCLIENT) << "Private key does not contain a private key";
1422 setError(QOpcUaGdsClient::Error::ConnectionError);
1423 return;
1424 }
1425
1426 auto csr = createSigningRequest();
1427 csr.setEncoding(QOpcUaX509CertificateSigningRequest::Encoding::DER);
1428 const auto csrData = csr.createRequest(privateKey: keyPair);
1429
1430
1431 QList<QOpcUa::TypedVariant> arguments;
1432 arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId));
1433 // Let the server choose the certificate group id
1434 arguments.push_back(t: QOpcUa::TypedVariant(QLatin1String("ns=0;i=0"), QOpcUa::NodeId));
1435 // Let the server choose the certificate type id
1436 arguments.push_back(t: QOpcUa::TypedVariant(QLatin1String("ns=0;i=0"), QOpcUa::NodeId));
1437
1438 arguments.push_back(t: QOpcUa::TypedVariant(csrData, QOpcUa::ByteString));
1439
1440 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("StartSigningRequest")], args: arguments)) {
1441 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call StartSigningRequest";
1442 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1443 }
1444}
1445
1446void QOpcUaGdsClientPrivate::handleStartSigningRequestFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1447{
1448 Q_Q(QOpcUaGdsClient);
1449
1450 if (statusCode != QOpcUa::Good) {
1451 qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate failed" << statusCode;
1452 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1453 return;
1454 }
1455
1456 m_certificateRequestId = result.toString();
1457
1458 if (!m_certificateFinishTimer)
1459 m_certificateFinishTimer = new QTimer;
1460
1461 m_certificateFinishTimer->setInterval(2000);
1462 m_certificateFinishTimer->setSingleShot(true);
1463 QObject::connect(sender: m_certificateFinishTimer, signal: &QTimer::timeout, context: q, slot: [this]() {
1464 this->finishCertificateRequest();
1465 });
1466
1467 m_certificateFinishTimer->start();
1468}
1469
1470void QOpcUaGdsClientPrivate::finishCertificateRequest()
1471{
1472 // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.5 FinishRequest
1473
1474 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1475 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1476 return;
1477 }
1478
1479 if (m_certificateRequestId.isEmpty()) {
1480 qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate request id";
1481 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1482 return;
1483 }
1484
1485 QList<QOpcUa::TypedVariant> arguments;
1486 arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId));
1487 arguments.push_back(t: QOpcUa::TypedVariant(m_certificateRequestId, QOpcUa::NodeId));
1488
1489 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("FinishRequest")], args: arguments)) {
1490 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call FinishRequest";
1491 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1492 }
1493}
1494
1495void QOpcUaGdsClientPrivate::handleFinishRequestFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1496{
1497 Q_Q(QOpcUaGdsClient);
1498
1499 if (statusCode == QOpcUa::BadNothingToDo) {
1500 // Server not finished yet: Try again later
1501 m_certificateFinishTimer->setInterval(m_certificateFinishTimer->interval() * 2);
1502 m_certificateFinishTimer->start();
1503 qCWarning(QT_OPCUA_GDSCLIENT) << "Server not finished yet: Trying again in" << m_certificateFinishTimer->interval() / 1000 << "s";
1504 return;
1505 }
1506 if (statusCode != QOpcUa::Good) {
1507 qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate failed" << statusCode;
1508 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1509 return;
1510 }
1511
1512 m_certificateRequestId.clear();
1513 const auto resultList = result.toList();
1514
1515 if (resultList.size() != 3) {
1516 qCWarning(QT_OPCUA_GDSCLIENT) << "Expected list of 3 results but got" << resultList.size();
1517 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1518 return;
1519 }
1520
1521 const auto certificate = resultList[0].toByteArray();
1522 //const auto privateKey = resultList[1].toByteArray();
1523 const auto issuerList = resultList[2].toByteArray();
1524
1525 if (certificate.isEmpty() || issuerList.isEmpty()) {
1526 qCWarning(QT_OPCUA_GDSCLIENT) << "Certificates are empty";
1527 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1528 return;
1529 }
1530
1531 qCInfo(QT_OPCUA_GDSCLIENT) << "Received new certificate" << certificate;
1532
1533 QFile certificateFile(m_pkiConfig.clientCertificateFile());
1534 if (!certificateFile.open(flags: QFile::WriteOnly)) {
1535 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate";
1536 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1537 return;
1538 }
1539
1540 if (certificateFile.write(data: certificate) != certificate.size()) {
1541 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate data";
1542 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1543 return;
1544 }
1545
1546 certificateFile.close();
1547
1548 // FIMXE: How to store this?
1549 QTemporaryFile issuerFile(m_pkiConfig.issuerListDirectory() + QLatin1String("/XXXXXX.der"));
1550 if (!issuerFile.open()) {
1551 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store issuer certificates to" << m_pkiConfig.issuerListDirectory();
1552 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1553 return;
1554 }
1555
1556 if (issuerFile.write(data: issuerList) != issuerList.size()) {
1557 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate data";
1558 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1559 return;
1560 }
1561
1562 qCDebug(QT_OPCUA_GDSCLIENT) << "issuer list stored to" << issuerFile.fileName();
1563 issuerFile.close();
1564 issuerFile.setAutoRemove(false);
1565
1566 emit q->certificateUpdated();
1567}
1568
1569void QOpcUaGdsClientPrivate::unregisterApplication()
1570{
1571 Q_Q(QOpcUaGdsClient);
1572
1573 if (m_appRecord.applicationId().isEmpty() || m_appRecord.applicationId() == QLatin1String("ns=0;i=0")) {
1574 emit q->unregistered();
1575 return;
1576 }
1577
1578 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1579 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1580 setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication);
1581 return;
1582 }
1583
1584 if (!m_directoryNode) {
1585 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
1586 setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication);
1587 return;
1588 }
1589
1590 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("UnregisterApplication")],
1591 args: QList<QOpcUa::TypedVariant> { QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId) })) {
1592 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method UnregisterApplication";
1593 setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication);
1594 }
1595}
1596
1597void QOpcUaGdsClientPrivate::handleUnregisterApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1598{
1599 Q_Q(QOpcUaGdsClient);
1600 Q_UNUSED(result);
1601
1602 if (statusCode != QOpcUa::Good) {
1603 qCWarning(QT_OPCUA_GDSCLIENT) << "Unregister application failed" << statusCode;
1604 setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication);
1605 return;
1606 }
1607
1608 m_client->disconnectFromEndpoint();
1609 emit q->unregistered();
1610}
1611
1612void QOpcUaGdsClientPrivate::setPkiConfiguration(const QOpcUaPkiConfiguration &pkiConfig)
1613{
1614 m_pkiConfig = pkiConfig;
1615}
1616
1617const QOpcUaPkiConfiguration &QOpcUaGdsClientPrivate::pkiConfiguration() const
1618{
1619 return m_pkiConfig;
1620}
1621
1622void QOpcUaGdsClientPrivate::setApplicationIdentity(const QOpcUaApplicationIdentity &appIdentity)
1623{
1624 m_appIdentitiy = appIdentity;
1625}
1626
1627const QOpcUaApplicationIdentity &QOpcUaGdsClientPrivate::applicationIdentity() const
1628{
1629 return m_appIdentitiy;
1630}
1631
1632void QOpcUaGdsClientPrivate::setApplicationRecord(const QOpcUaApplicationRecordDataType &appRecord)
1633{
1634 m_appRecord = appRecord;
1635}
1636
1637const QOpcUaApplicationRecordDataType &QOpcUaGdsClientPrivate::applicationRecord() const
1638{
1639 return m_appRecord;
1640}
1641
1642void QOpcUaGdsClientPrivate::setCertificateSigningRequestPresets(const QOpcUaX509DistinguishedName &dn, const QString &dns)
1643{
1644 csrPresets.dn = dn;
1645 csrPresets.dns = dns;
1646}
1647
1648const QOpcUaX509DistinguishedName &QOpcUaGdsClientPrivate::distinguishedNameCertificateSigningRequestPreset() const
1649{
1650 return csrPresets.dn;
1651}
1652
1653const QString &QOpcUaGdsClientPrivate::dnsCertificateSigningRequestPreset() const
1654{
1655 return csrPresets.dns;
1656}
1657
1658void QOpcUaGdsClientPrivate::setCertificateCheckInterval(int interval)
1659{
1660 m_certificateCheckTimer->setInterval(interval);
1661}
1662
1663int QOpcUaGdsClientPrivate::certificateCheckInterval() const
1664{
1665 return m_certificateCheckTimer->interval();
1666}
1667
1668void QOpcUaGdsClientPrivate::setTrustListUpdateInterval(int interval)
1669{
1670 m_trustListUpdateTimer->setInterval(interval);
1671}
1672
1673int QOpcUaGdsClientPrivate::trustListUpdateInterval() const
1674{
1675 return m_trustListUpdateTimer->interval();
1676}
1677
1678QOpcUaGdsClient::Error QOpcUaGdsClientPrivate::error() const
1679{
1680 return m_error;
1681}
1682
1683QOpcUaGdsClient::State QOpcUaGdsClientPrivate::state() const
1684{
1685 return m_state;
1686}
1687
1688QOpcUaX509CertificateSigningRequest QOpcUaGdsClientPrivate::createSigningRequest() const
1689{
1690 QOpcUaX509CertificateSigningRequest csr;
1691 QOpcUaX509DistinguishedName dn = csrPresets.dn;
1692 // Overwrite the application name because it has to match the identity
1693 dn.setEntry(type: QOpcUaX509DistinguishedName::Type::CommonName, value: m_appIdentitiy.applicationName());
1694 csr.setSubject(dn);
1695
1696 QOpcUaX509ExtensionSubjectAlternativeName *san = new QOpcUaX509ExtensionSubjectAlternativeName;
1697 san->addEntry(type: QOpcUaX509ExtensionSubjectAlternativeName::Type::URI, value: m_appIdentitiy.applicationUri());
1698 if (!csrPresets.dns.isEmpty())
1699 san->addEntry(type: QOpcUaX509ExtensionSubjectAlternativeName::Type::DNS, value: csrPresets.dns);
1700 san->setCritical(true);
1701 csr.addExtension(extension: san);
1702
1703 QOpcUaX509ExtensionBasicConstraints *bc = new QOpcUaX509ExtensionBasicConstraints;
1704 bc->setCa(false);
1705 bc->setCritical(true);
1706 csr.addExtension(extension: bc);
1707
1708 QOpcUaX509ExtensionKeyUsage *ku = new QOpcUaX509ExtensionKeyUsage;
1709 ku->setCritical(true);
1710 ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::DigitalSignature);
1711 ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::NonRepudiation);
1712 ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::KeyEncipherment);
1713 ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::DataEncipherment);
1714 ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::CertificateSigning);
1715 csr.addExtension(extension: ku);
1716
1717 return csr;
1718}
1719
1720void QOpcUaGdsClientPrivate::_q_handleDirectoryNodeMethodCallFinished(QString methodNodeId, QVariant result, QOpcUa::UaStatusCode statusCode)
1721{
1722 if (methodNodeId == m_directoryNodes[QLatin1String("UnregisterApplication")]) {
1723 this->handleUnregisterApplicationFinished(result, statusCode);
1724 } else if ( methodNodeId == m_directoryNodes[QLatin1String("FinishRequest")]) {
1725 this->handleFinishRequestFinished(result, statusCode);
1726 } else if (methodNodeId == m_directoryNodes[QLatin1String("StartSigningRequest")]) {
1727 this->handleStartSigningRequestFinished(result, statusCode);
1728 } else if (methodNodeId == m_directoryNodes[QLatin1String("GetCertificateStatus")]) {
1729 this->handleGetCertificateStatusFinished(result, statusCode);
1730 } else if (methodNodeId == m_directoryNodes[QLatin1String("GetCertificateGroups")]) {
1731 this->handleGetCertificateGroupsFinished(result, statusCode);
1732 } else if (methodNodeId == m_directoryNodes[QLatin1String("RegisterApplication")]) {
1733 this->handleRegisterApplicationFinished(result, statusCode);
1734 } else if (methodNodeId == m_directoryNodes[QLatin1String("FindApplications")]) {
1735 this->handleFindApplicationsFinished(result, statusCode);
1736 } else if (methodNodeId == m_directoryNodes[QLatin1String("GetApplication")]) {
1737 this->handleGetApplicationFinished(result, statusCode);
1738 } else if (methodNodeId == m_directoryNodes[QLatin1String("GetTrustList")]) {
1739 this->handleGetTrustListFinished(result, statusCode);
1740 } else {
1741 qCWarning(QT_OPCUA_GDSCLIENT) << "Result unexpeced method call received" << methodNodeId;
1742 }
1743}
1744
1745void QOpcUaGdsClientPrivate::_q_certificateCheckTimeout()
1746{
1747 Q_Q(QOpcUaGdsClient);
1748
1749 QFile file(m_pkiConfig.clientCertificateFile());
1750 if (!file.open(flags: QFile::ReadOnly)) {
1751 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load certificate from file" << m_pkiConfig.clientCertificateFile();
1752 }
1753
1754 QSslCertificate cert(&file, QSsl::Der);
1755 if (cert.isNull()) {
1756 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load certificate from file" << m_pkiConfig.clientCertificateFile();
1757 return;
1758 }
1759
1760 bool needsUpdate = false;
1761
1762 if (cert.isSelfSigned()) {
1763 needsUpdate = true;
1764 qCInfo(QT_OPCUA_GDSCLIENT) << "Certificate is self-signed: requesting update";
1765 }
1766
1767 if (QDateTime::currentDateTime().addSecs(secs: 24*60*60) > cert.expiryDate()) {
1768 needsUpdate = true;
1769 qCInfo(QT_OPCUA_GDSCLIENT) << "Certificate expiry date is due: requesting update";
1770 }
1771
1772 if (!needsUpdate)
1773 getCertificateStatus();
1774
1775 if (needsUpdate) {
1776 emit q->certificateUpdateRequired();
1777 startCertificateRequest();
1778 }
1779}
1780
1781void QOpcUaGdsClientPrivate::_q_updateTrustList()
1782{
1783 // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.9 GetTrustList
1784
1785 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1786 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1787 return;
1788 }
1789
1790 if (!m_certificateGroupNode) {
1791 qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate group node";
1792 return;
1793 }
1794
1795 QList<QOpcUa::TypedVariant> arguments;
1796 arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId));
1797 arguments.push_back(t: QOpcUa::TypedVariant(m_certificateGroupNode->nodeId(), QOpcUa::NodeId));
1798
1799 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetTrustList")], args: arguments)) {
1800 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetTrustList";
1801 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1802 }
1803}
1804
1805void QOpcUaGdsClientPrivate::handleGetTrustListFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1806{
1807 Q_Q(QOpcUaGdsClient);
1808
1809 if (statusCode != QOpcUa::Good) {
1810 qCWarning(QT_OPCUA_GDSCLIENT) << "Getting trust list node failed" << statusCode;
1811 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1812 return;
1813 }
1814
1815 const auto trustListNodeId = result.toString();
1816
1817 if (trustListNodeId.isEmpty()) {
1818 qCWarning(QT_OPCUA_GDSCLIENT) << "Trust list node id is empty";
1819 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1820 return;
1821 }
1822
1823 delete m_trustListNode;
1824 m_trustListNode = m_client->node(nodeId: trustListNodeId);
1825
1826 if (!m_trustListNode) {
1827 qCWarning(QT_OPCUA_GDSCLIENT) << "Trust list node failed";
1828 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1829 return;
1830 }
1831
1832 QObject::connect(sender: m_trustListNode, signal: &QOpcUaNode::attributeUpdated,
1833 context: q, slot: [q](QOpcUa::NodeAttribute attr, QVariant value) {
1834 Q_UNUSED(value);
1835 if (attr == QOpcUa::NodeAttribute::Value)
1836 emit q->trustListUpdated();
1837 });
1838
1839 if (!m_trustListNode->readValueAttribute()) {
1840 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not read trust list";
1841 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1842 return;
1843 }
1844}
1845
1846// When it is detected that authentication credentials are required this function
1847// is used to re-connect to the server after requesting credentials
1848void QOpcUaGdsClientPrivate::restartWithCredentials()
1849{
1850 Q_Q(QOpcUaGdsClient);
1851
1852 QOpcUaAuthenticationInformation authInfo;
1853 emit q->authenticationRequired(authInfo);
1854 m_client->setAuthenticationInformation(authInfo);
1855
1856 qCInfo(QT_OPCUA_GDSCLIENT) << "Restarting connection with credentials";
1857 m_restartRequired = true;
1858 m_client->disconnectFromEndpoint();
1859}
1860
1861QT_END_NAMESPACE
1862
1863#include "moc_qopcuagdsclient.cpp"
1864

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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