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

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