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, 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, 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(t: 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, slot: [this, q](QList<QOpcUaBrowsePathTarget> targets,
827 QList<QOpcUaRelativePathElement> path,
828 QOpcUa::UaStatusCode statusCode) {
829 m_directoryNode->deleteLater();
830 m_directoryNode = nullptr;
831
832 if (path.size() != 1) {
833 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size";
834 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
835 return;
836 }
837
838 if (path[0].targetName().name() != QLatin1String("Directory")) {
839 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name" << path[0].targetName().name();
840 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
841 return;
842 }
843
844 if (statusCode != QOpcUa::Good) {
845 qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving directory failed" << statusCode;
846 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
847 return;
848 }
849
850 if (targets.size() != 1) {
851 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results";
852 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
853 return;
854 }
855
856 if (!targets[0].isFullyResolved()) {
857 qCWarning(QT_OPCUA_GDSCLIENT) << "Directory not fully resolved";
858 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
859 return;
860 }
861
862 m_directoryNode = m_client->node(expandedNodeId: targets[0].targetId());
863 if (!m_directoryNode) {
864 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid directory node";
865 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
866 return;
867 }
868
869 QObject::connect(sender: m_directoryNode, SIGNAL(resolveBrowsePathFinished(QList<QOpcUaBrowsePathTarget>, QList<QOpcUaRelativePathElement>, QOpcUa::UaStatusCode)),
870 receiver: q, SLOT(_q_handleResolveBrowsePathFinished(QList<QOpcUaBrowsePathTarget>, QList<QOpcUaRelativePathElement>, QOpcUa::UaStatusCode)));
871 QObject::connect(sender: m_directoryNode, SIGNAL(methodCallFinished(QString, QVariant, QOpcUa::UaStatusCode)),
872 receiver: q, SLOT(_q_handleDirectoryNodeMethodCallFinished(QString, QVariant, QOpcUa::UaStatusCode)));
873
874 qCDebug(QT_OPCUA_GDSCLIENT) << "Directory node resolved:" << m_directoryNode->nodeId();
875 this->resolveMethodNodes();
876 });
877
878 QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(m_gdsNamespaceIndex, QLatin1String("Directory")),
879 QOpcUa::ReferenceTypeId::Organizes);
880 QList<QOpcUaRelativePathElement> browsePath { pathElement };
881
882 if (!m_directoryNode->resolveBrowsePath(path: browsePath)) {
883 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to resolve directory node";
884 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
885 delete m_directoryNode;
886 m_directoryNode = nullptr;
887 return;
888 }
889}
890
891void QOpcUaGdsClientPrivate::resolveMethodNodes()
892{
893 // See OPC UA Specification 1.04 part 12 6.3.2 "Directory"
894
895 QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(m_gdsNamespaceIndex, QLatin1String()),
896 QOpcUa::ReferenceTypeId::HasComponent);
897 QList<QOpcUaRelativePathElement> browsePath { pathElement };
898
899
900 // Resolve all needed nodes from the directory
901 for (const auto &key : std::as_const(t: elementsToResolve)) {
902 if (!m_directoryNodes.value(key).isEmpty())
903 continue; // Already resolved
904
905 auto target = browsePath[0].targetName();
906 target.setName(key);
907 browsePath[0].setTargetName(target);
908
909 if (!m_directoryNode->resolveBrowsePath(path: browsePath)) {
910 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not resolve Directory node";
911 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
912 return;
913 }
914 }
915}
916
917void QOpcUaGdsClientPrivate::_q_handleResolveBrowsePathFinished(QList<QOpcUaBrowsePathTarget> targets, QList<QOpcUaRelativePathElement> path, QOpcUa::UaStatusCode statusCode) {
918 if (path.size() != 1) {
919 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size";
920 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
921 return;
922 }
923
924 if (m_directoryNodes.contains(key: path[0].targetName().name()) || !elementsToResolve.contains(str: path[0].targetName().name())) {
925 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name" << path[0].targetName().name();
926 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
927 return;
928 }
929
930 if (statusCode != QOpcUa::Good) {
931 qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving directory failed" << statusCode;
932 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
933 return;
934 }
935
936 if (targets.size() != 1) {
937 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results";
938 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
939 return;
940 }
941
942 if (!targets[0].isFullyResolved()) {
943 qCWarning(QT_OPCUA_GDSCLIENT) << "Directory not fully resolved";
944 setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound);
945 return;
946 }
947
948 m_directoryNodes[path[0].targetName().name()] = targets[0].targetId().nodeId();
949
950 if (elementsToResolve.size() == m_directoryNodes.size()) {
951 qCDebug(QT_OPCUA_GDSCLIENT) << "All symbols resolved";
952
953 if (m_appRecord.applicationId().isEmpty() || m_appRecord.applicationId() == QLatin1String("ns=0;i=0"))
954 this->findRegisteredApplication();
955 else
956 this->getApplication();
957 }
958}
959
960void QOpcUaGdsClientPrivate::getApplication()
961{
962 // See OPC UA Specification 1.04 part 12 6.3.9 "GetApplication"
963
964 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
965 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
966 return;
967 }
968
969 if (!m_directoryNode) {
970 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
971 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
972 return;
973 }
974
975 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetApplication")],
976 args: QList<QPair<QVariant, QOpcUa::Types>> { qMakePair(value1: QVariant(m_appRecord.applicationId()), value2: QOpcUa::Types::NodeId) })) {
977 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method GetApplication";
978 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
979 return;
980 }
981}
982
983void QOpcUaGdsClientPrivate::handleGetApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
984{
985 if (statusCode == QOpcUa::BadNotFound) {
986 qCWarning(QT_OPCUA_GDSCLIENT) << "No application with ID" << m_appRecord.applicationId() << "registered";
987 // Clear application ID and register
988 m_appRecord.setApplicationId(QString());
989
990 // Remove invalid id from settings
991 QSettings settings;
992 settings.remove(key: QLatin1String("gds/applicationId"));
993 settings.sync();
994
995 restartWithCredentials();
996 return;
997 }
998
999 if (statusCode != QOpcUa::Good) {
1000 qCWarning(QT_OPCUA_GDSCLIENT) << "Get application failed" << statusCode;
1001 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1002 return;
1003 }
1004
1005 auto extensionObject = result.value<QOpcUaExtensionObject>();
1006 if (!extensionObject.encodingTypeId().isEmpty()) {
1007 if (extensionObject.encodingTypeId() != QOpcUa::nodeIdFromInteger(ns: m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary)) {
1008 qCWarning(QT_OPCUA_GDSCLIENT) << "Unexpected return type:" << extensionObject.encodingTypeId();
1009 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1010 return;
1011 }
1012
1013 auto buffer = extensionObject.encodedBody();
1014 QOpcUaBinaryDataEncoding decoder(&buffer);
1015 bool ok;
1016 QOpcUaApplicationRecordDataType appRecord = decoder.decode<QOpcUaApplicationRecordDataType>(success&: ok);
1017
1018 if (!ok) {
1019 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to decode data";
1020 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1021 return;
1022 }
1023
1024 if (m_appRecord.applicationId() != appRecord.applicationId())
1025 qCWarning(QT_OPCUA_GDSCLIENT) << "Returned application record contains a different application ID" << m_appRecord.applicationId() << appRecord.applicationId();
1026
1027 // FIXME: To be removed
1028 appRecord.setApplicationId(m_appRecord.applicationId());
1029
1030 m_appRecord = appRecord;
1031 qCInfo(QT_OPCUA_GDSCLIENT) << "Reusing application ID" << m_appRecord.applicationId() << "which is already registered at the server";
1032 }
1033 getCertificateGroups();
1034}
1035
1036void QOpcUaGdsClientPrivate::findRegisteredApplication()
1037{
1038 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1039 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1040 return;
1041 }
1042
1043 if (!m_directoryNode) {
1044 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
1045 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1046 return;
1047 }
1048
1049 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("FindApplications")],
1050 args: QList<QPair<QVariant, QOpcUa::Types>> { qMakePair(value1: QVariant(m_appRecord.applicationUri()), value2: QOpcUa::Types::String) })) {
1051 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method";
1052 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1053 return;
1054 }
1055}
1056
1057void QOpcUaGdsClientPrivate::handleFindApplicationsFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1058{
1059 if (statusCode != QOpcUa::Good) {
1060 qCWarning(QT_OPCUA_GDSCLIENT) << "Find application failed" << statusCode;
1061 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1062 return;
1063 }
1064
1065 auto extensionObject = result.value<QOpcUaExtensionObject>();
1066
1067 if (extensionObject.encodingTypeId().isEmpty()) {
1068 qCInfo(QT_OPCUA_GDSCLIENT) << "No application with URI" << m_appIdentitiy.applicationUri() << "registered";
1069 this->registerApplication();
1070 return;
1071 }
1072
1073 if (extensionObject.encodingTypeId() != QOpcUa::nodeIdFromInteger(ns: m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary)) {
1074 qCWarning(QT_OPCUA_GDSCLIENT) << "Unexpected return type:" << extensionObject.encodingTypeId();
1075 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1076 return;
1077 }
1078
1079 auto buffer = extensionObject.encodedBody();
1080 QOpcUaBinaryDataEncoding decoder(&buffer);
1081 bool ok;
1082 QOpcUaApplicationRecordDataType appRecord = decoder.decode<QOpcUaApplicationRecordDataType>(success&: ok);
1083
1084 if (!ok) {
1085 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to decode data";
1086 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1087 return;
1088 }
1089
1090 m_appRecord = appRecord;
1091 qCInfo(QT_OPCUA_GDSCLIENT) << "Reusing application ID" << appRecord.applicationId() << "registered for URI" << appRecord.applicationUri();
1092 getCertificateGroups();
1093}
1094
1095void QOpcUaGdsClientPrivate::registerApplication()
1096{
1097 if (!m_appRecord.applicationId().isEmpty() && m_appRecord.applicationId() != QLatin1String("ns=0;i=0"))
1098 return;
1099
1100 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1101 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1102 return;
1103 }
1104
1105 if (!m_directoryNode) {
1106 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
1107 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1108 return;
1109 }
1110
1111 QByteArray buffer;
1112 QOpcUaBinaryDataEncoding encoder(&buffer);
1113 if (!encoder.encode(src: m_appRecord)) {
1114 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to encode body";
1115 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1116 return;
1117 }
1118
1119 QOpcUaExtensionObject parameter;
1120 parameter.setBinaryEncodedBody(encodedBody: buffer, typeId: QOpcUa::nodeIdFromInteger(ns: m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary));
1121
1122 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("RegisterApplication")],
1123 args: QList<QOpcUa::TypedVariant> { QOpcUa::TypedVariant(parameter, QOpcUa::ExtensionObject) })) {
1124 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method RegisterApplication";
1125 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1126 }
1127}
1128
1129void QOpcUaGdsClientPrivate::handleRegisterApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1130{
1131 if (statusCode != QOpcUa::Good) {
1132 qCDebug(QT_OPCUA_GDSCLIENT) << "Requesting user credentials";
1133 if (statusCode == QOpcUa::BadUserAccessDenied && m_client->authenticationInformation().authenticationType() == QOpcUaUserTokenPolicy::Anonymous) {
1134 restartWithCredentials();
1135 return;
1136 }
1137 qCWarning(QT_OPCUA_GDSCLIENT) << "Register application failed with" << statusCode;
1138 setError(QOpcUaGdsClient::Error::FailedToRegisterApplication);
1139 return;
1140 }
1141
1142 m_appRecord.setApplicationId(result.toString());
1143
1144 QSettings settings;
1145 settings.setValue(key: QLatin1String("gds/applicationId"), value: m_appRecord.applicationId());
1146 settings.sync();
1147
1148 qCInfo(QT_OPCUA_GDSCLIENT) << "Registered application with id" << m_appRecord.applicationId();
1149 getCertificateGroups();
1150}
1151
1152void QOpcUaGdsClientPrivate::getCertificateGroups()
1153{
1154 // OPC UA Specification Version 1.04 Part 4 Chapter 7.6.6 GetCertificateGroups
1155
1156 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1157 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1158 return;
1159 }
1160
1161 if (!m_directoryNode) {
1162 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
1163 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1164 return;
1165 }
1166
1167 QList<QOpcUa::TypedVariant> arguments;
1168 arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId));
1169
1170 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetCertificateGroups")], args: arguments)) {
1171 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetCertificateGroups";
1172 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1173 }
1174}
1175
1176void QOpcUaGdsClientPrivate::handleGetCertificateGroupsFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1177{
1178 Q_Q(QOpcUaGdsClient);
1179
1180 if (statusCode != QOpcUa::Good) {
1181 qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate groups failed" << statusCode;
1182 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1183 return;
1184 }
1185
1186 m_certificateGroups = result.value<QStringList>();
1187 qCDebug(QT_OPCUA_GDSCLIENT) << "Certificate groups:" << m_certificateGroups;
1188
1189 emit q->certificateGroupsReceived(certificateGroups: m_certificateGroups);
1190 resolveCertificateTypes();
1191}
1192
1193void QOpcUaGdsClientPrivate::resolveCertificateTypes()
1194{
1195 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1196 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1197 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1198 return;
1199 }
1200
1201 if (m_certificateGroups.isEmpty()) {
1202 qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate groups is empty";
1203 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1204 return;
1205 }
1206
1207 delete m_certificateGroupNode;
1208 m_certificateGroupNode = m_client->node(nodeId: m_certificateGroups[0]);
1209
1210 if (!m_certificateGroupNode) {
1211 qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate node failed";
1212 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1213 return;
1214 }
1215
1216 QObject::connect(sender: m_certificateGroupNode, signal: &QOpcUaNode::resolveBrowsePathFinished, slot: [this](QList<QOpcUaBrowsePathTarget> targets,
1217 QList<QOpcUaRelativePathElement> path,
1218 QOpcUa::UaStatusCode statusCode) {
1219 if (path.size() != 1) {
1220 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size";
1221 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1222 return;
1223 }
1224
1225 if (path[0].targetName().name() != QLatin1String("CertificateTypes")) {
1226 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name" << path[0].targetName().name();
1227 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1228 return;
1229 }
1230
1231 if (statusCode != QOpcUa::Good) {
1232 qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving certificate types failed" << statusCode;
1233 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1234 return;
1235 }
1236
1237 if (targets.size() != 1) {
1238 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results";
1239 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1240 return;
1241 }
1242
1243 if (!targets[0].isFullyResolved()) {
1244 qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types not fully resolved";
1245 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1246 return;
1247 }
1248
1249 m_certificateTypesNodeId = targets[0].targetId().nodeId();
1250 this->getCertificateTypes();
1251 });
1252
1253 QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(0, QLatin1String("CertificateTypes")),
1254 QOpcUa::ReferenceTypeId::Unspecified);
1255 QList<QOpcUaRelativePathElement> browsePath { pathElement };
1256
1257
1258 if (!m_certificateGroupNode->resolveBrowsePath(path: browsePath)) {
1259 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not resolve certificate type";
1260 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1261 return;
1262 }
1263}
1264
1265void QOpcUaGdsClientPrivate::getCertificateTypes()
1266{
1267 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1268 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1269 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1270 return;
1271 }
1272
1273 if (m_certificateTypesNodeId.isEmpty()) {
1274 qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types node id is empty";
1275 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1276 return;
1277 }
1278
1279 delete m_certificateTypesNode;
1280 m_certificateTypesNode = m_client->node(nodeId: m_certificateTypesNodeId);
1281
1282 if (!m_certificateTypesNode) {
1283 qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types node failed";
1284 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1285 return;
1286 }
1287
1288 QObject::connect(sender: m_certificateTypesNode, signal: &QOpcUaNode::attributeUpdated, slot: [this](QOpcUa::NodeAttribute attr, QVariant value) {
1289 if (attr == QOpcUa::NodeAttribute::Value)
1290 qCWarning(QT_OPCUA_GDSCLIENT) << "possible certificate types" << value;
1291 registrationDone();
1292 });
1293
1294 if (!m_certificateTypesNode->readValueAttribute()) {
1295 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not read certificate types";
1296 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1297 return;
1298 }
1299}
1300
1301void QOpcUaGdsClientPrivate::registrationDone()
1302{
1303 Q_Q(QOpcUaGdsClient);
1304
1305 setState(QOpcUaGdsClient::State::ApplicationRegistered);
1306 emit q->applicationRegistered();
1307
1308 m_certificateCheckTimer->start();
1309 _q_certificateCheckTimeout(); // Force a check now
1310
1311 m_trustListUpdateTimer->start();
1312 _q_updateTrustList(); // Force a check now
1313}
1314
1315void QOpcUaGdsClientPrivate::getCertificateStatus()
1316{
1317 // OPC UA Specification Version 1.04 Part 4 Chapter 7.6.8 GetCertificateStatus
1318
1319 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1320 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1321 return;
1322 }
1323
1324 if (!m_directoryNode) {
1325 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
1326 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1327 return;
1328 }
1329
1330 if (m_certificateGroups.isEmpty()) {
1331 qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate groups received";
1332 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1333 return;
1334 }
1335
1336 QString certificateType = QLatin1String("ns=0;i=0"); // null node
1337
1338 QList<QOpcUa::TypedVariant> arguments;
1339 arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId));
1340 // Let the server choose the certificate group id
1341 arguments.push_back(t: QOpcUa::TypedVariant(m_certificateGroups[0], QOpcUa::NodeId));
1342 // Let the server choose the certificate type id
1343 arguments.push_back(t: QOpcUa::TypedVariant(certificateType, QOpcUa::NodeId));
1344
1345 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetCertificateStatus")], args: arguments)) {
1346 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetCertificateStatus";
1347 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1348 }
1349}
1350
1351void QOpcUaGdsClientPrivate::handleGetCertificateStatusFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1352{
1353 Q_Q(QOpcUaGdsClient);
1354
1355 if (statusCode != QOpcUa::Good) {
1356 qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate status failed" << statusCode;
1357 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1358 return;
1359 }
1360
1361 if (result.toBool()) {
1362 qInfo(catFunc: QT_OPCUA_GDSCLIENT) << "Certificate needs update";
1363 emit q->certificateUpdateRequired();
1364 startCertificateRequest();
1365 }
1366}
1367
1368void QOpcUaGdsClientPrivate::startCertificateRequest()
1369{
1370 // OPC UA Specification Version 1.04 Part 4 Chapter 7.6.3 StartSigningRequest
1371
1372 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1373 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1374 return;
1375 }
1376
1377 if (!m_directoryNode) {
1378 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
1379 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1380 return;
1381 }
1382
1383 QOpcUaKeyPair keyPair;
1384 QFile keyFile(m_pkiConfig.privateKeyFile());
1385
1386 qCDebug(QT_OPCUA_GDSCLIENT) << "Using private key" << keyFile.fileName();
1387
1388 if (!keyFile.open(flags: QFile::ReadOnly)) {
1389 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open private key file" << keyFile.fileName() << "for reading:" << keyFile.errorString();
1390 setError(QOpcUaGdsClient::Error::ConnectionError);
1391 return;
1392 }
1393
1394 auto data = keyFile.readAll();
1395 keyFile.close();
1396
1397 if (!keyPair.loadFromPemData(data)) {
1398 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load private key";
1399 setError(QOpcUaGdsClient::Error::ConnectionError);
1400 return;
1401 }
1402
1403 if (!keyPair.hasPrivateKey()) {
1404 qCWarning(QT_OPCUA_GDSCLIENT) << "Private key does not contain a private key";
1405 setError(QOpcUaGdsClient::Error::ConnectionError);
1406 return;
1407 }
1408
1409 auto csr = createSigningRequest();
1410 csr.setEncoding(QOpcUaX509CertificateSigningRequest::Encoding::DER);
1411 const auto csrData = csr.createRequest(privateKey: keyPair);
1412
1413
1414 QList<QOpcUa::TypedVariant> arguments;
1415 arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId));
1416 // Let the server choose the certificate group id
1417 arguments.push_back(t: QOpcUa::TypedVariant(QLatin1String("ns=0;i=0"), QOpcUa::NodeId));
1418 // Let the server choose the certificate type id
1419 arguments.push_back(t: QOpcUa::TypedVariant(QLatin1String("ns=0;i=0"), QOpcUa::NodeId));
1420
1421 arguments.push_back(t: QOpcUa::TypedVariant(csrData, QOpcUa::ByteString));
1422
1423 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("StartSigningRequest")], args: arguments)) {
1424 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call StartSigningRequest";
1425 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1426 }
1427}
1428
1429void QOpcUaGdsClientPrivate::handleStartSigningRequestFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1430{
1431 if (statusCode != QOpcUa::Good) {
1432 qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate failed" << statusCode;
1433 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1434 return;
1435 }
1436
1437 m_certificateRequestId = result.toString();
1438
1439 if (!m_certificateFinishTimer)
1440 m_certificateFinishTimer = new QTimer;
1441
1442 m_certificateFinishTimer->setInterval(2000);
1443 m_certificateFinishTimer->setSingleShot(true);
1444 QObject::connect(sender: m_certificateFinishTimer, signal: &QTimer::timeout, slot: [this]() {
1445 this->finishCertificateRequest();
1446 });
1447
1448 m_certificateFinishTimer->start();
1449}
1450
1451void QOpcUaGdsClientPrivate::finishCertificateRequest()
1452{
1453 // OPC UA Specification Version 1.04 Part 4 Chapter 7.6.5 FinishRequest
1454
1455 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1456 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1457 return;
1458 }
1459
1460 if (m_certificateRequestId.isEmpty()) {
1461 qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate request id";
1462 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1463 return;
1464 }
1465
1466 QList<QOpcUa::TypedVariant> arguments;
1467 arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId));
1468 arguments.push_back(t: QOpcUa::TypedVariant(m_certificateRequestId, QOpcUa::NodeId));
1469
1470 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("FinishRequest")], args: arguments)) {
1471 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call FinishRequest";
1472 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1473 }
1474}
1475
1476void QOpcUaGdsClientPrivate::handleFinishRequestFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1477{
1478 Q_Q(QOpcUaGdsClient);
1479
1480 if (statusCode == QOpcUa::BadNothingToDo) {
1481 // Server not finished yet: Try again later
1482 m_certificateFinishTimer->setInterval(m_certificateFinishTimer->interval() * 2);
1483 m_certificateFinishTimer->start();
1484 qCWarning(QT_OPCUA_GDSCLIENT) << "Server not finished yet: Trying again in" << m_certificateFinishTimer->interval() / 1000 << "s";
1485 return;
1486 }
1487 if (statusCode != QOpcUa::Good) {
1488 qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate failed" << statusCode;
1489 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1490 return;
1491 }
1492
1493 m_certificateRequestId.clear();
1494 const auto resultList = result.toList();
1495
1496 if (resultList.size() != 3) {
1497 qCWarning(QT_OPCUA_GDSCLIENT) << "Expected list of 3 results but got" << resultList.size();
1498 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1499 return;
1500 }
1501
1502 const auto certificate = resultList[0].toByteArray();
1503 //const auto privateKey = resultList[1].toByteArray();
1504 const auto issuerList = resultList[2].toByteArray();
1505
1506 if (certificate.isEmpty() || issuerList.isEmpty()) {
1507 qCWarning(QT_OPCUA_GDSCLIENT) << "Certificates are empty";
1508 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1509 return;
1510 }
1511
1512 qCInfo(QT_OPCUA_GDSCLIENT) << "Received new certificate" << certificate;
1513
1514 QFile certificateFile(m_pkiConfig.clientCertificateFile());
1515 if (!certificateFile.open(flags: QFile::WriteOnly)) {
1516 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate";
1517 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1518 return;
1519 }
1520
1521 if (certificateFile.write(data: certificate) != certificate.size()) {
1522 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate data";
1523 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1524 return;
1525 }
1526
1527 certificateFile.close();
1528
1529 // FIMXE: How to store this?
1530 QTemporaryFile issuerFile(m_pkiConfig.issuerListDirectory() + QLatin1String("/XXXXXX.der"));
1531 if (!issuerFile.open()) {
1532 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store issuer certificates to" << m_pkiConfig.issuerListDirectory();
1533 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1534 return;
1535 }
1536
1537 if (issuerFile.write(data: issuerList) != issuerList.size()) {
1538 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate data";
1539 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1540 return;
1541 }
1542
1543 qCDebug(QT_OPCUA_GDSCLIENT) << "issuer list stored to" << issuerFile.fileName();
1544 issuerFile.close();
1545 issuerFile.setAutoRemove(false);
1546
1547 emit q->certificateUpdated();
1548}
1549
1550void QOpcUaGdsClientPrivate::unregisterApplication()
1551{
1552 Q_Q(QOpcUaGdsClient);
1553
1554 if (m_appRecord.applicationId().isEmpty() || m_appRecord.applicationId() == QLatin1String("ns=0;i=0")) {
1555 emit q->unregistered();
1556 return;
1557 }
1558
1559 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1560 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1561 setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication);
1562 return;
1563 }
1564
1565 if (!m_directoryNode) {
1566 qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node";
1567 setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication);
1568 return;
1569 }
1570
1571 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("UnregisterApplication")],
1572 args: QList<QOpcUa::TypedVariant> { QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId) })) {
1573 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method UnregisterApplication";
1574 setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication);
1575 }
1576}
1577
1578void QOpcUaGdsClientPrivate::handleUnregisterApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1579{
1580 Q_Q(QOpcUaGdsClient);
1581 Q_UNUSED(result);
1582
1583 if (statusCode != QOpcUa::Good) {
1584 qCWarning(QT_OPCUA_GDSCLIENT) << "Unregister application failed" << statusCode;
1585 setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication);
1586 return;
1587 }
1588
1589 m_client->disconnectFromEndpoint();
1590 emit q->unregistered();
1591}
1592
1593void QOpcUaGdsClientPrivate::setPkiConfiguration(const QOpcUaPkiConfiguration &pkiConfig)
1594{
1595 m_pkiConfig = pkiConfig;
1596}
1597
1598const QOpcUaPkiConfiguration &QOpcUaGdsClientPrivate::pkiConfiguration() const
1599{
1600 return m_pkiConfig;
1601}
1602
1603void QOpcUaGdsClientPrivate::setApplicationIdentity(const QOpcUaApplicationIdentity &appIdentity)
1604{
1605 m_appIdentitiy = appIdentity;
1606}
1607
1608const QOpcUaApplicationIdentity &QOpcUaGdsClientPrivate::applicationIdentity() const
1609{
1610 return m_appIdentitiy;
1611}
1612
1613void QOpcUaGdsClientPrivate::setApplicationRecord(const QOpcUaApplicationRecordDataType &appRecord)
1614{
1615 m_appRecord = appRecord;
1616}
1617
1618const QOpcUaApplicationRecordDataType &QOpcUaGdsClientPrivate::applicationRecord() const
1619{
1620 return m_appRecord;
1621}
1622
1623void QOpcUaGdsClientPrivate::setCertificateSigningRequestPresets(const QOpcUaX509DistinguishedName &dn, const QString &dns)
1624{
1625 csrPresets.dn = dn;
1626 csrPresets.dns = dns;
1627}
1628
1629const QOpcUaX509DistinguishedName &QOpcUaGdsClientPrivate::distinguishedNameCertificateSigningRequestPreset() const
1630{
1631 return csrPresets.dn;
1632}
1633
1634const QString &QOpcUaGdsClientPrivate::dnsCertificateSigningRequestPreset() const
1635{
1636 return csrPresets.dns;
1637}
1638
1639void QOpcUaGdsClientPrivate::setCertificateCheckInterval(int interval)
1640{
1641 m_certificateCheckTimer->setInterval(interval);
1642}
1643
1644int QOpcUaGdsClientPrivate::certificateCheckInterval() const
1645{
1646 return m_certificateCheckTimer->interval();
1647}
1648
1649void QOpcUaGdsClientPrivate::setTrustListUpdateInterval(int interval)
1650{
1651 m_trustListUpdateTimer->setInterval(interval);
1652}
1653
1654int QOpcUaGdsClientPrivate::trustListUpdateInterval() const
1655{
1656 return m_trustListUpdateTimer->interval();
1657}
1658
1659QOpcUaGdsClient::Error QOpcUaGdsClientPrivate::error() const
1660{
1661 return m_error;
1662}
1663
1664QOpcUaGdsClient::State QOpcUaGdsClientPrivate::state() const
1665{
1666 return m_state;
1667}
1668
1669QOpcUaX509CertificateSigningRequest QOpcUaGdsClientPrivate::createSigningRequest() const
1670{
1671 QOpcUaX509CertificateSigningRequest csr;
1672 QOpcUaX509DistinguishedName dn = csrPresets.dn;
1673 // Overwrite the application name because it has to match the identity
1674 dn.setEntry(type: QOpcUaX509DistinguishedName::Type::CommonName, value: m_appIdentitiy.applicationName());
1675 csr.setSubject(dn);
1676
1677 QOpcUaX509ExtensionSubjectAlternativeName *san = new QOpcUaX509ExtensionSubjectAlternativeName;
1678 san->addEntry(type: QOpcUaX509ExtensionSubjectAlternativeName::Type::URI, value: m_appIdentitiy.applicationUri());
1679 if (!csrPresets.dns.isEmpty())
1680 san->addEntry(type: QOpcUaX509ExtensionSubjectAlternativeName::Type::DNS, value: csrPresets.dns);
1681 san->setCritical(true);
1682 csr.addExtension(extension: san);
1683
1684 QOpcUaX509ExtensionBasicConstraints *bc = new QOpcUaX509ExtensionBasicConstraints;
1685 bc->setCa(false);
1686 bc->setCritical(true);
1687 csr.addExtension(extension: bc);
1688
1689 QOpcUaX509ExtensionKeyUsage *ku = new QOpcUaX509ExtensionKeyUsage;
1690 ku->setCritical(true);
1691 ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::DigitalSignature);
1692 ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::NonRepudiation);
1693 ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::KeyEncipherment);
1694 ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::DataEncipherment);
1695 ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::CertificateSigning);
1696 csr.addExtension(extension: ku);
1697
1698 return csr;
1699}
1700
1701void QOpcUaGdsClientPrivate::_q_handleDirectoryNodeMethodCallFinished(QString methodNodeId, QVariant result, QOpcUa::UaStatusCode statusCode)
1702{
1703 if (methodNodeId == m_directoryNodes[QLatin1String("UnregisterApplication")]) {
1704 this->handleUnregisterApplicationFinished(result, statusCode);
1705 } else if ( methodNodeId == m_directoryNodes[QLatin1String("FinishRequest")]) {
1706 this->handleFinishRequestFinished(result, statusCode);
1707 } else if (methodNodeId == m_directoryNodes[QLatin1String("StartSigningRequest")]) {
1708 this->handleStartSigningRequestFinished(result, statusCode);
1709 } else if (methodNodeId == m_directoryNodes[QLatin1String("GetCertificateStatus")]) {
1710 this->handleGetCertificateStatusFinished(result, statusCode);
1711 } else if (methodNodeId == m_directoryNodes[QLatin1String("GetCertificateGroups")]) {
1712 this->handleGetCertificateGroupsFinished(result, statusCode);
1713 } else if (methodNodeId == m_directoryNodes[QLatin1String("RegisterApplication")]) {
1714 this->handleRegisterApplicationFinished(result, statusCode);
1715 } else if (methodNodeId == m_directoryNodes[QLatin1String("FindApplications")]) {
1716 this->handleFindApplicationsFinished(result, statusCode);
1717 } else if (methodNodeId == m_directoryNodes[QLatin1String("GetApplication")]) {
1718 this->handleGetApplicationFinished(result, statusCode);
1719 } else if (methodNodeId == m_directoryNodes[QLatin1String("GetTrustList")]) {
1720 this->handleGetTrustListFinished(result, statusCode);
1721 } else {
1722 qCWarning(QT_OPCUA_GDSCLIENT) << "Result unexpeced method call received" << methodNodeId;
1723 }
1724}
1725
1726void QOpcUaGdsClientPrivate::_q_certificateCheckTimeout()
1727{
1728 Q_Q(QOpcUaGdsClient);
1729
1730 QFile file(m_pkiConfig.clientCertificateFile());
1731 if (!file.open(flags: QFile::ReadOnly)) {
1732 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load certificate from file" << m_pkiConfig.clientCertificateFile();
1733 }
1734
1735 QSslCertificate cert(&file, QSsl::Der);
1736 if (cert.isNull()) {
1737 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load certificate from file" << m_pkiConfig.clientCertificateFile();
1738 return;
1739 }
1740
1741 bool needsUpdate = false;
1742
1743 if (cert.isSelfSigned()) {
1744 needsUpdate = true;
1745 qCInfo(QT_OPCUA_GDSCLIENT) << "Certificate is self-signed: requesting update";
1746 }
1747
1748 if (QDateTime::currentDateTime().addSecs(secs: 24*60*60) > cert.expiryDate()) {
1749 needsUpdate = true;
1750 qCInfo(QT_OPCUA_GDSCLIENT) << "Certificate expiry date is due: requesting update";
1751 }
1752
1753 if (!needsUpdate)
1754 getCertificateStatus();
1755
1756 if (needsUpdate) {
1757 emit q->certificateUpdateRequired();
1758 startCertificateRequest();
1759 }
1760}
1761
1762void QOpcUaGdsClientPrivate::_q_updateTrustList()
1763{
1764 // OPC UA Specification Version 1.04 Part 4 Chapter 7.6.7 GetTrustList
1765
1766 if (!m_client || m_client->state() != QOpcUaClient::Connected) {
1767 qCWarning(QT_OPCUA_GDSCLIENT) << "No connection";
1768 return;
1769 }
1770
1771 if (!m_certificateGroupNode) {
1772 qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate group node";
1773 return;
1774 }
1775
1776 QList<QOpcUa::TypedVariant> arguments;
1777 arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId));
1778 arguments.push_back(t: QOpcUa::TypedVariant(m_certificateGroupNode->nodeId(), QOpcUa::NodeId));
1779
1780 if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetTrustList")], args: arguments)) {
1781 qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetTrustList";
1782 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1783 }
1784}
1785
1786void QOpcUaGdsClientPrivate::handleGetTrustListFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode)
1787{
1788 Q_Q(QOpcUaGdsClient);
1789
1790 if (statusCode != QOpcUa::Good) {
1791 qCWarning(QT_OPCUA_GDSCLIENT) << "Getting trust list node failed" << statusCode;
1792 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1793 return;
1794 }
1795
1796 const auto trustListNodeId = result.toString();
1797
1798 if (trustListNodeId.isEmpty()) {
1799 qCWarning(QT_OPCUA_GDSCLIENT) << "Trust list node id is empty";
1800 setError(QOpcUaGdsClient::Error::FailedToGetCertificate);
1801 return;
1802 }
1803
1804 delete m_trustListNode;
1805 m_trustListNode = m_client->node(nodeId: trustListNodeId);
1806
1807 if (!m_trustListNode) {
1808 qCWarning(QT_OPCUA_GDSCLIENT) << "Trust list node failed";
1809 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1810 return;
1811 }
1812
1813 QObject::connect(sender: m_trustListNode, signal: &QOpcUaNode::attributeUpdated, slot: [q](QOpcUa::NodeAttribute attr, QVariant value) {
1814 Q_UNUSED(value);
1815 if (attr == QOpcUa::NodeAttribute::Value)
1816 emit q->trustListUpdated();
1817 });
1818
1819 if (!m_trustListNode->readValueAttribute()) {
1820 qCWarning(QT_OPCUA_GDSCLIENT) << "Could not read trust list";
1821 setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus);
1822 return;
1823 }
1824}
1825
1826// When it is detected that authentication credentials are required this function
1827// is used to re-connect to the server after requesting credentials
1828void QOpcUaGdsClientPrivate::restartWithCredentials()
1829{
1830 Q_Q(QOpcUaGdsClient);
1831
1832 QOpcUaAuthenticationInformation authInfo;
1833 emit q->authenticationRequired(authInfo);
1834 m_client->setAuthenticationInformation(authInfo);
1835
1836 qCInfo(QT_OPCUA_GDSCLIENT) << "Restarting connection with credentials";
1837 m_restartRequired = true;
1838 m_client->disconnectFromEndpoint();
1839}
1840
1841QT_END_NAMESPACE
1842
1843#include "moc_qopcuagdsclient.cpp"
1844

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