| 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 |  | 
| 20 | QT_BEGIN_NAMESPACE | 
| 21 |  | 
| 22 | Q_LOGGING_CATEGORY(QT_OPCUA_GDSCLIENT, "qt.opcua.gdsclient" ) | 
| 23 |  | 
| 24 | static 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 | */ | 
| 303 | QOpcUaGdsClient::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 | */ | 
| 313 | QOpcUaGdsClient::~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 | */ | 
| 326 | void 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 | */ | 
| 338 | const 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 | */ | 
| 353 | void 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 | */ | 
| 365 | const 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 | */ | 
| 381 | void QOpcUaGdsClient::setPkiConfiguration(const QOpcUaPkiConfiguration &pkiConfig) | 
| 382 | { | 
| 383 |     Q_D(QOpcUaGdsClient); | 
| 384 |     d->setPkiConfiguration(pkiConfig); | 
| 385 | } | 
| 386 |  | 
| 387 | /*! | 
| 388 |     Returns the current pkiConfiguration. | 
| 389 | */ | 
| 390 | const 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 | */ | 
| 403 | void QOpcUaGdsClient::setApplicationIdentity(const QOpcUaApplicationIdentity &appIdentity) | 
| 404 | { | 
| 405 |     Q_D(QOpcUaGdsClient); | 
| 406 |     d->setApplicationIdentity(appIdentity); | 
| 407 | } | 
| 408 |  | 
| 409 | /*! | 
| 410 |     Returns the current applicationIdentity. | 
| 411 | */ | 
| 412 | const 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 | */ | 
| 429 | void 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 | */ | 
| 438 | const 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 | */ | 
| 451 | QString 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 | */ | 
| 468 | void 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 | */ | 
| 477 | const 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 | */ | 
| 486 | const 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 | */ | 
| 496 | void 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 | */ | 
| 505 | int 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 | */ | 
| 515 | void 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 | */ | 
| 524 | int QOpcUaGdsClient::trustListUpdateInterval() const | 
| 525 | { | 
| 526 |     Q_D(const QOpcUaGdsClient); | 
| 527 |     return d->trustListUpdateInterval(); | 
| 528 | } | 
| 529 |  | 
| 530 | /*! | 
| 531 |     Returns the current error state. | 
| 532 | */ | 
| 533 | QOpcUaGdsClient::Error QOpcUaGdsClient::error() const | 
| 534 | { | 
| 535 |     Q_D(const QOpcUaGdsClient); | 
| 536 |     return d->error(); | 
| 537 | } | 
| 538 |  | 
| 539 | /*! | 
| 540 |     Returns the current client state. | 
| 541 | */ | 
| 542 | QOpcUaGdsClient::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 | */ | 
| 563 | void 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 | */ | 
| 575 | void QOpcUaGdsClient::unregisterApplication() | 
| 576 | { | 
| 577 |     Q_D(QOpcUaGdsClient); | 
| 578 |     return d->unregisterApplication(); | 
| 579 | } | 
| 580 |  | 
| 581 | QOpcUaGdsClientPrivate::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 |  | 
| 590 | QOpcUaGdsClientPrivate::~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 |  | 
| 602 | void 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 |  | 
| 609 | void QOpcUaGdsClientPrivate::setEndpoint(const QOpcUaEndpointDescription &endpoint) | 
| 610 | { | 
| 611 |     m_endpoint = endpoint; | 
| 612 | } | 
| 613 |  | 
| 614 | const QOpcUaEndpointDescription &QOpcUaGdsClientPrivate::endpoint() const | 
| 615 | { | 
| 616 |     return m_endpoint; | 
| 617 | } | 
| 618 |  | 
| 619 | void QOpcUaGdsClientPrivate::setBackend(const QString &backend) | 
| 620 | { | 
| 621 |     m_backend = backend; | 
| 622 | } | 
| 623 |  | 
| 624 | const QString &QOpcUaGdsClientPrivate::backend() const | 
| 625 | { | 
| 626 |     return m_backend; | 
| 627 | } | 
| 628 |  | 
| 629 | QString QOpcUaGdsClientPrivate::applicationId() const | 
| 630 | { | 
| 631 |     return m_appRecord.applicationId(); | 
| 632 | } | 
| 633 |  | 
| 634 | void 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 |  | 
| 642 | void QOpcUaGdsClientPrivate::setState(QOpcUaGdsClient::State state) | 
| 643 | { | 
| 644 |     Q_Q(QOpcUaGdsClient); | 
| 645 |     m_state = state; | 
| 646 |     emit q->stateChanged(state); | 
| 647 | } | 
| 648 |  | 
| 649 | void QOpcUaGdsClientPrivate::start() | 
| 650 | { | 
| 651 |     Q_Q(QOpcUaGdsClient); | 
| 652 |  | 
| 653 |     if (m_backend.isEmpty()) { | 
| 654 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Backend name not set" ; | 
| 655 |         setError(QOpcUaGdsClient::Error::InvalidBackend); | 
| 656 |         return; | 
| 657 |     } | 
| 658 |  | 
| 659 |     if (!m_client) { | 
| 660 |         QOpcUaProvider provider; | 
| 661 |         setState(QOpcUaGdsClient::State::BackendInstantiated); | 
| 662 |         m_client = provider.createClient(backend: m_backend); | 
| 663 |         if (!m_client) { | 
| 664 |             setError(QOpcUaGdsClient::Error::InvalidBackend); | 
| 665 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid backend" ; | 
| 666 |             return; | 
| 667 |         } | 
| 668 |  | 
| 669 |         QObject::connect(sender: m_client, signal: &QOpcUaClient::namespaceArrayUpdated, context: q, slot: [this]() { | 
| 670 |             setState(QOpcUaGdsClient::State::Connected); | 
| 671 |             this->resolveDirectoryNode(); | 
| 672 |         }); | 
| 673 |  | 
| 674 |         QObject::connect(sender: m_client, signal: &QOpcUaClient::errorChanged, context: q, slot: [this](QOpcUaClient::ClientError error) { | 
| 675 |             if (error == QOpcUaClient::InvalidUrl) { | 
| 676 |                 qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid URL" ; | 
| 677 |                 setError(QOpcUaGdsClient::Error::InvalidEndpoint); | 
| 678 |             } else { | 
| 679 |                 qCWarning(QT_OPCUA_GDSCLIENT) << "Connection error" ; | 
| 680 |                 setError(QOpcUaGdsClient::Error::ConnectionError); | 
| 681 |             } | 
| 682 |         }); | 
| 683 |  | 
| 684 |         QObject::connect(sender: m_client, signal: &QOpcUaClient::disconnected, context: q, slot: [this]() { | 
| 685 |             delete m_directoryNode; | 
| 686 |             m_directoryNode = nullptr; | 
| 687 |             m_directoryNodes.clear(); | 
| 688 |             setState(QOpcUaGdsClient::State::Idle); | 
| 689 |             if (m_restartRequired) { | 
| 690 |                 m_restartRequired = false; | 
| 691 |                 this->start(); | 
| 692 |             } | 
| 693 |         }); | 
| 694 |  | 
| 695 |         QObject::connect(sender: m_client, signal: &QOpcUaClient::connectError, context: q, slot: [](QOpcUaErrorState *errorState) { | 
| 696 |             // Ignore all client side errors and continue | 
| 697 |             if (errorState->isClientSideError()) | 
| 698 |                 errorState->setIgnoreError(); | 
| 699 |         }); | 
| 700 |     } | 
| 701 |  | 
| 702 |     setState(QOpcUaGdsClient::State::Connecting); | 
| 703 |     if (m_endpoint.endpointUrl().isEmpty()) { | 
| 704 |         setError(QOpcUaGdsClient::Error::InvalidEndpoint); | 
| 705 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid endpoint" ; | 
| 706 |         return; | 
| 707 |     } | 
| 708 |  | 
| 709 |     QOpcUaKeyPair keyPair; | 
| 710 |     QFile keyFile(m_pkiConfig.privateKeyFile()); | 
| 711 |  | 
| 712 |     if (!keyFile.exists()) { | 
| 713 |         // File does not exist: Create a key | 
| 714 |         qCDebug(QT_OPCUA_GDSCLIENT) << "Creating a key" ; | 
| 715 |         keyPair.generateRsaKey(strength: QOpcUaKeyPair::RsaKeyStrength::Bits1024); | 
| 716 |  | 
| 717 |         if (!keyFile.open(flags: QFile::WriteOnly | QFile::NewOnly)) { | 
| 718 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open private key file"  << keyFile.fileName() << "for writing:"  << keyFile.errorString(); | 
| 719 |             setError(QOpcUaGdsClient::Error::ConnectionError); | 
| 720 |             return; | 
| 721 |         } | 
| 722 |  | 
| 723 |         auto data = keyPair.privateKeyToByteArray(cipher: QOpcUaKeyPair::Cipher::Unencrypted, password: QString()); | 
| 724 |  | 
| 725 |         if (!keyFile.resize(sz: data.size())) { | 
| 726 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to set file size" ; | 
| 727 |             setError(QOpcUaGdsClient::Error::ConnectionError); | 
| 728 |             return; | 
| 729 |         } | 
| 730 |  | 
| 731 |         if (!keyFile.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner)) { | 
| 732 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to set permissions" ; | 
| 733 |             setError(QOpcUaGdsClient::Error::ConnectionError); | 
| 734 |             return; | 
| 735 |         } | 
| 736 |  | 
| 737 |         keyFile.write(data); | 
| 738 |         keyFile.close(); | 
| 739 |     } else { | 
| 740 |         qCDebug(QT_OPCUA_GDSCLIENT) << "Using private key"  << keyFile.fileName(); | 
| 741 |  | 
| 742 |         if (!keyFile.open(flags: QFile::ReadOnly)) { | 
| 743 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open private key file"  << keyFile.fileName() << "for reading:"  << keyFile.errorString(); | 
| 744 |             setError(QOpcUaGdsClient::Error::ConnectionError); | 
| 745 |             return; | 
| 746 |         } | 
| 747 |  | 
| 748 |         auto data = keyFile.readAll(); | 
| 749 |         keyFile.close(); | 
| 750 |  | 
| 751 |         if (!keyPair.loadFromPemData(data)) { | 
| 752 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load private key" ; | 
| 753 |             setError(QOpcUaGdsClient::Error::ConnectionError); | 
| 754 |             return; | 
| 755 |         } | 
| 756 |  | 
| 757 |         if (!keyPair.hasPrivateKey()) { | 
| 758 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Private key does not contain a private key" ; | 
| 759 |             setError(QOpcUaGdsClient::Error::ConnectionError); | 
| 760 |             return; | 
| 761 |         } | 
| 762 |     } | 
| 763 |  | 
| 764 |     QFile certFile(m_pkiConfig.clientCertificateFile()); | 
| 765 |     if (!certFile.exists()) { | 
| 766 |         qCDebug(QT_OPCUA_GDSCLIENT) << "Creating self-signed certificate in"  << certFile.fileName(); | 
| 767 |  | 
| 768 |         auto csr = createSigningRequest(); | 
| 769 |         csr.setEncoding(QOpcUaX509CertificateSigningRequest::Encoding::DER); | 
| 770 |         auto selfSigned = csr.createSelfSignedCertificate(privateKey: keyPair); | 
| 771 |  | 
| 772 |         if (!certFile.open(flags: QFile::WriteOnly | QFile::NewOnly)) { | 
| 773 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open certificate file"  << certFile.fileName() << "for writing:"  << certFile.errorString(); | 
| 774 |             setError(QOpcUaGdsClient::Error::ConnectionError); | 
| 775 |             return; | 
| 776 |         } | 
| 777 |  | 
| 778 |         if (!certFile.resize(sz: selfSigned.size())) { | 
| 779 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to set file size 2" ; | 
| 780 |             setError(QOpcUaGdsClient::Error::ConnectionError); | 
| 781 |             return; | 
| 782 |         } | 
| 783 |  | 
| 784 |         certFile.write(data: selfSigned); | 
| 785 |         certFile.close(); | 
| 786 |     } | 
| 787 |  | 
| 788 |     // Load persistent data | 
| 789 |     QSettings settings; | 
| 790 |     qCDebug(QT_OPCUA_GDSCLIENT) << "Using settings from"  << settings.fileName(); | 
| 791 |     const auto applicationId = settings.value(key: QLatin1String("gds/applicationId" ), defaultValue: QString()).toString(); | 
| 792 |     if (applicationId.isEmpty()) | 
| 793 |         qCInfo(QT_OPCUA_GDSCLIENT) << "No application ID in persistent storage" ; | 
| 794 |     else | 
| 795 |         m_appRecord.setApplicationId(applicationId); | 
| 796 |  | 
| 797 |     m_client->setApplicationIdentity(m_appIdentitiy); | 
| 798 |     m_client->setPkiConfiguration(m_pkiConfig); | 
| 799 |     m_client->connectToEndpoint(endpoint: m_endpoint); | 
| 800 | } | 
| 801 |  | 
| 802 | void QOpcUaGdsClientPrivate::resolveDirectoryNode() | 
| 803 | { | 
| 804 |     Q_Q(QOpcUaGdsClient); | 
| 805 |  | 
| 806 |     if (!m_client || m_client->state() != QOpcUaClient::Connected) { | 
| 807 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No connection" ; | 
| 808 |         return; | 
| 809 |     } | 
| 810 |  | 
| 811 |     delete m_directoryNode; | 
| 812 |     m_directoryNode = m_client->node(nodeId: QOpcUa::namespace0Id(id: QOpcUa::NodeIds::Namespace0::ObjectsFolder)); // ns=0;i=85 | 
| 813 |     if (!m_directoryNode) { | 
| 814 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Root node not found" ; | 
| 815 |         setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 816 |         return; | 
| 817 |     } | 
| 818 |  | 
| 819 |     m_gdsNamespaceIndex = m_client->namespaceArray().indexOf(needle: QLatin1String("http://opcfoundation.org/UA/GDS/" )); | 
| 820 |     if (m_gdsNamespaceIndex < 0) { | 
| 821 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Namespace not found" ; | 
| 822 |         setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 823 |         return; | 
| 824 |     } | 
| 825 |  | 
| 826 |     QObject::connect(sender: m_directoryNode, signal: &QOpcUaNode::resolveBrowsePathFinished, context: q, | 
| 827 |                      slot: [this, q](QList<QOpcUaBrowsePathTarget> targets, | 
| 828 |                                QList<QOpcUaRelativePathElement> path, | 
| 829 |                                QOpcUa::UaStatusCode statusCode) { | 
| 830 |         m_directoryNode->deleteLater(); | 
| 831 |         m_directoryNode = nullptr; | 
| 832 |  | 
| 833 |         if (path.size() != 1) { | 
| 834 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size" ; | 
| 835 |             setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 836 |             return; | 
| 837 |         } | 
| 838 |  | 
| 839 |         if (path[0].targetName().name() != QLatin1String("Directory" )) { | 
| 840 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name"  << path[0].targetName().name(); | 
| 841 |             setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 842 |             return; | 
| 843 |         } | 
| 844 |  | 
| 845 |         if (statusCode != QOpcUa::Good) { | 
| 846 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving directory failed"  << statusCode; | 
| 847 |             setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 848 |             return; | 
| 849 |         } | 
| 850 |  | 
| 851 |         if (targets.size() != 1) { | 
| 852 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results" ; | 
| 853 |             setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 854 |             return; | 
| 855 |         } | 
| 856 |  | 
| 857 |         if (!targets[0].isFullyResolved()) { | 
| 858 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Directory not fully resolved" ; | 
| 859 |             setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 860 |             return; | 
| 861 |         } | 
| 862 |  | 
| 863 |         m_directoryNode = m_client->node(expandedNodeId: targets[0].targetId()); | 
| 864 |         if (!m_directoryNode) { | 
| 865 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid directory node" ; | 
| 866 |             setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 867 |             return; | 
| 868 |         } | 
| 869 |  | 
| 870 |         auto handleResolve = [this](QList<QOpcUaBrowsePathTarget> targets, | 
| 871 |                                     QList<QOpcUaRelativePathElement> path, | 
| 872 |                                     QOpcUa::UaStatusCode statusCode) { | 
| 873 |             _q_handleResolveBrowsePathFinished(targets, path, statusCode); | 
| 874 |         }; | 
| 875 |         QObject::connect(sender: m_directoryNode, signal: &QOpcUaNode::resolveBrowsePathFinished, | 
| 876 |                          context: q, slot: std::move(handleResolve)); | 
| 877 |  | 
| 878 |         auto handleDirNode = [this](QString methodNodeId, QVariant result, | 
| 879 |                                     QOpcUa::UaStatusCode statusCode) { | 
| 880 |             _q_handleDirectoryNodeMethodCallFinished(methodNodeId, result, statusCode); | 
| 881 |         }; | 
| 882 |         QObject::connect(sender: m_directoryNode, signal: &QOpcUaNode::methodCallFinished, | 
| 883 |                          context: q, slot: std::move(handleDirNode)); | 
| 884 |  | 
| 885 |         qCDebug(QT_OPCUA_GDSCLIENT) << "Directory node resolved:"  << m_directoryNode->nodeId(); | 
| 886 |         this->resolveMethodNodes(); | 
| 887 |     }); | 
| 888 |  | 
| 889 |     QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(m_gdsNamespaceIndex, QLatin1String("Directory" )), | 
| 890 |                                            QOpcUa::ReferenceTypeId::Organizes); | 
| 891 |     QList<QOpcUaRelativePathElement> browsePath { pathElement }; | 
| 892 |  | 
| 893 |     if (!m_directoryNode->resolveBrowsePath(path: browsePath)) { | 
| 894 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to resolve directory node" ; | 
| 895 |         setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 896 |         delete m_directoryNode; | 
| 897 |         m_directoryNode = nullptr; | 
| 898 |         return; | 
| 899 |     } | 
| 900 | } | 
| 901 |  | 
| 902 | void QOpcUaGdsClientPrivate::resolveMethodNodes() | 
| 903 | { | 
| 904 |     // See OPC UA Specification 1.05 part 12 6.6.2 "Directory" | 
| 905 |  | 
| 906 |     QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(m_gdsNamespaceIndex, QLatin1String()), | 
| 907 |                                            QOpcUa::ReferenceTypeId::HasComponent); | 
| 908 |     QList<QOpcUaRelativePathElement> browsePath { pathElement }; | 
| 909 |  | 
| 910 |  | 
| 911 |     // Resolve all needed nodes from the directory | 
| 912 |     for (const auto &key : std::as_const(t: elementsToResolve)) { | 
| 913 |         if (!m_directoryNodes.value(key).isEmpty()) | 
| 914 |             continue; // Already resolved | 
| 915 |  | 
| 916 |         auto target = browsePath[0].targetName(); | 
| 917 |         target.setName(key); | 
| 918 |         browsePath[0].setTargetName(target); | 
| 919 |  | 
| 920 |         if (!m_directoryNode->resolveBrowsePath(path: browsePath)) { | 
| 921 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Could not resolve Directory node" ; | 
| 922 |             setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 923 |             return; | 
| 924 |         } | 
| 925 |     } | 
| 926 | } | 
| 927 |  | 
| 928 | void QOpcUaGdsClientPrivate::_q_handleResolveBrowsePathFinished(QList<QOpcUaBrowsePathTarget> targets, QList<QOpcUaRelativePathElement> path, QOpcUa::UaStatusCode statusCode) { | 
| 929 |     if (path.size() != 1) { | 
| 930 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size" ; | 
| 931 |         setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 932 |         return; | 
| 933 |     } | 
| 934 |  | 
| 935 |     if (m_directoryNodes.contains(key: path[0].targetName().name()) || !elementsToResolve.contains(str: path[0].targetName().name())) { | 
| 936 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name"  << path[0].targetName().name(); | 
| 937 |         setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 938 |         return; | 
| 939 |     } | 
| 940 |  | 
| 941 |     if (statusCode != QOpcUa::Good) { | 
| 942 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving directory failed"  << statusCode; | 
| 943 |         setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 944 |         return; | 
| 945 |     } | 
| 946 |  | 
| 947 |     if (targets.size() != 1) { | 
| 948 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results" ; | 
| 949 |         setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 950 |         return; | 
| 951 |     } | 
| 952 |  | 
| 953 |     if (!targets[0].isFullyResolved()) { | 
| 954 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Directory not fully resolved" ; | 
| 955 |         setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); | 
| 956 |         return; | 
| 957 |     } | 
| 958 |  | 
| 959 |     m_directoryNodes[path[0].targetName().name()] = targets[0].targetId().nodeId(); | 
| 960 |  | 
| 961 |     if (elementsToResolve.size() == m_directoryNodes.size()) { | 
| 962 |         qCDebug(QT_OPCUA_GDSCLIENT) << "All symbols resolved" ; | 
| 963 |  | 
| 964 |         if (m_appRecord.applicationId().isEmpty() || m_appRecord.applicationId() == QLatin1String("ns=0;i=0" )) | 
| 965 |             this->findRegisteredApplication(); | 
| 966 |         else | 
| 967 |             this->getApplication(); | 
| 968 |     } | 
| 969 | } | 
| 970 |  | 
| 971 | void QOpcUaGdsClientPrivate::getApplication() | 
| 972 | { | 
| 973 |     // See OPC UA Specification 1.05 part 12 6.6.9 "GetApplication" | 
| 974 |  | 
| 975 |     if (!m_client || m_client->state() != QOpcUaClient::Connected) { | 
| 976 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No connection" ; | 
| 977 |         return; | 
| 978 |     } | 
| 979 |  | 
| 980 |     if (!m_directoryNode) { | 
| 981 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node" ; | 
| 982 |         setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 983 |         return; | 
| 984 |     } | 
| 985 |  | 
| 986 |     if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetApplication" )], | 
| 987 |                                      args: QList<QPair<QVariant, QOpcUa::Types>> { qMakePair(value1: QVariant(m_appRecord.applicationId()), value2: QOpcUa::Types::NodeId) })) { | 
| 988 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method GetApplication" ; | 
| 989 |         setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 990 |         return; | 
| 991 |     } | 
| 992 | } | 
| 993 |  | 
| 994 | void QOpcUaGdsClientPrivate::handleGetApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) | 
| 995 | { | 
| 996 |     if (statusCode == QOpcUa::BadNotFound) { | 
| 997 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No application with ID"  << m_appRecord.applicationId() << "registered" ; | 
| 998 |         // Clear application ID and register | 
| 999 |         m_appRecord.setApplicationId(QString()); | 
| 1000 |  | 
| 1001 |         // Remove invalid id from settings | 
| 1002 |         QSettings settings; | 
| 1003 |         settings.remove(key: QLatin1String("gds/applicationId" )); | 
| 1004 |         settings.sync(); | 
| 1005 |  | 
| 1006 |         restartWithCredentials(); | 
| 1007 |         return; | 
| 1008 |     } | 
| 1009 |  | 
| 1010 |     if (statusCode != QOpcUa::Good) { | 
| 1011 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Get application failed"  << statusCode; | 
| 1012 |         setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 1013 |         return; | 
| 1014 |     } | 
| 1015 |  | 
| 1016 |     auto extensionObject = result.value<QOpcUaExtensionObject>(); | 
| 1017 |     if (!extensionObject.encodingTypeId().isEmpty()) { | 
| 1018 |         if (extensionObject.encodingTypeId() != QOpcUa::nodeIdFromInteger(ns: m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary)) { | 
| 1019 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Unexpected return type:"  << extensionObject.encodingTypeId(); | 
| 1020 |             setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 1021 |             return; | 
| 1022 |         } | 
| 1023 |  | 
| 1024 |         auto buffer = extensionObject.encodedBody(); | 
| 1025 |         QOpcUaBinaryDataEncoding decoder(&buffer); | 
| 1026 |         bool ok; | 
| 1027 |         QOpcUaApplicationRecordDataType appRecord = decoder.decode<QOpcUaApplicationRecordDataType>(success&: ok); | 
| 1028 |  | 
| 1029 |         if (!ok) { | 
| 1030 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to decode data" ; | 
| 1031 |             setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 1032 |             return; | 
| 1033 |         } | 
| 1034 |  | 
| 1035 |         if (m_appRecord.applicationId() != appRecord.applicationId()) | 
| 1036 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Returned application record contains a different application ID"  << m_appRecord.applicationId() << appRecord.applicationId(); | 
| 1037 |  | 
| 1038 |         // FIXME: To be removed | 
| 1039 |         appRecord.setApplicationId(m_appRecord.applicationId()); | 
| 1040 |  | 
| 1041 |         m_appRecord = appRecord; | 
| 1042 |         qCInfo(QT_OPCUA_GDSCLIENT) << "Reusing application ID"  << m_appRecord.applicationId() << "which is already registered at the server" ; | 
| 1043 |     } | 
| 1044 |     getCertificateGroups(); | 
| 1045 | } | 
| 1046 |  | 
| 1047 | void QOpcUaGdsClientPrivate::findRegisteredApplication() | 
| 1048 | { | 
| 1049 |     if (!m_client || m_client->state() != QOpcUaClient::Connected) { | 
| 1050 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No connection" ; | 
| 1051 |         return; | 
| 1052 |     } | 
| 1053 |  | 
| 1054 |     if (!m_directoryNode) { | 
| 1055 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node" ; | 
| 1056 |         setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 1057 |         return; | 
| 1058 |     } | 
| 1059 |  | 
| 1060 |     if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("FindApplications" )], | 
| 1061 |                                      args: QList<QPair<QVariant, QOpcUa::Types>> { qMakePair(value1: QVariant(m_appRecord.applicationUri()), value2: QOpcUa::Types::String) })) { | 
| 1062 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method" ; | 
| 1063 |         setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 1064 |         return; | 
| 1065 |     } | 
| 1066 | } | 
| 1067 |  | 
| 1068 | void QOpcUaGdsClientPrivate::handleFindApplicationsFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) | 
| 1069 | { | 
| 1070 |     if (statusCode != QOpcUa::Good) { | 
| 1071 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Find application failed"  << statusCode; | 
| 1072 |         setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 1073 |         return; | 
| 1074 |     } | 
| 1075 |  | 
| 1076 |     auto extensionObject = result.value<QOpcUaExtensionObject>(); | 
| 1077 |  | 
| 1078 |     if (extensionObject.encodingTypeId().isEmpty()) { | 
| 1079 |         qCInfo(QT_OPCUA_GDSCLIENT) << "No application with URI"  << m_appIdentitiy.applicationUri() << "registered" ; | 
| 1080 |         this->registerApplication(); | 
| 1081 |         return; | 
| 1082 |     } | 
| 1083 |  | 
| 1084 |     if (extensionObject.encodingTypeId() != QOpcUa::nodeIdFromInteger(ns: m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary)) { | 
| 1085 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Unexpected return type:"  << extensionObject.encodingTypeId(); | 
| 1086 |         setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 1087 |         return; | 
| 1088 |     } | 
| 1089 |  | 
| 1090 |     auto buffer = extensionObject.encodedBody(); | 
| 1091 |     QOpcUaBinaryDataEncoding decoder(&buffer); | 
| 1092 |     bool ok; | 
| 1093 |     QOpcUaApplicationRecordDataType appRecord = decoder.decode<QOpcUaApplicationRecordDataType>(success&: ok); | 
| 1094 |  | 
| 1095 |     if (!ok) { | 
| 1096 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to decode data" ; | 
| 1097 |         setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 1098 |         return; | 
| 1099 |     } | 
| 1100 |  | 
| 1101 |     m_appRecord = appRecord; | 
| 1102 |     qCInfo(QT_OPCUA_GDSCLIENT) << "Reusing application ID"  << appRecord.applicationId() << "registered for URI"  << appRecord.applicationUri(); | 
| 1103 |     getCertificateGroups(); | 
| 1104 | } | 
| 1105 |  | 
| 1106 | void QOpcUaGdsClientPrivate::registerApplication() | 
| 1107 | { | 
| 1108 |     if (!m_appRecord.applicationId().isEmpty() && m_appRecord.applicationId() != QLatin1String("ns=0;i=0" )) | 
| 1109 |         return; | 
| 1110 |  | 
| 1111 |     if (!m_client || m_client->state() != QOpcUaClient::Connected) { | 
| 1112 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No connection" ; | 
| 1113 |         return; | 
| 1114 |     } | 
| 1115 |  | 
| 1116 |     if (!m_directoryNode) { | 
| 1117 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node" ; | 
| 1118 |         setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 1119 |         return; | 
| 1120 |     } | 
| 1121 |  | 
| 1122 |     QByteArray buffer; | 
| 1123 |     QOpcUaBinaryDataEncoding encoder(&buffer); | 
| 1124 |     if (!encoder.encode(src: m_appRecord)) { | 
| 1125 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to encode body" ; | 
| 1126 |         setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 1127 |         return; | 
| 1128 |     } | 
| 1129 |  | 
| 1130 |     QOpcUaExtensionObject parameter; | 
| 1131 |     parameter.setBinaryEncodedBody(encodedBody: buffer, typeId: QOpcUa::nodeIdFromInteger(ns: m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary)); | 
| 1132 |  | 
| 1133 |     if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("RegisterApplication" )], | 
| 1134 |                                      args: QList<QOpcUa::TypedVariant> { QOpcUa::TypedVariant(parameter, QOpcUa::ExtensionObject) })) { | 
| 1135 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method RegisterApplication" ; | 
| 1136 |         setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 1137 |     } | 
| 1138 | } | 
| 1139 |  | 
| 1140 | void QOpcUaGdsClientPrivate::handleRegisterApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) | 
| 1141 | { | 
| 1142 |     if (statusCode != QOpcUa::Good) { | 
| 1143 |         qCDebug(QT_OPCUA_GDSCLIENT) << "Requesting user credentials" ; | 
| 1144 |         if (statusCode == QOpcUa::BadUserAccessDenied && m_client->authenticationInformation().authenticationType() == QOpcUaUserTokenPolicy::Anonymous) { | 
| 1145 |             restartWithCredentials(); | 
| 1146 |             return; | 
| 1147 |         } | 
| 1148 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Register application failed with"  << statusCode; | 
| 1149 |         setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); | 
| 1150 |         return; | 
| 1151 |     } | 
| 1152 |  | 
| 1153 |     m_appRecord.setApplicationId(result.toString()); | 
| 1154 |  | 
| 1155 |     QSettings settings; | 
| 1156 |     settings.setValue(key: QLatin1String("gds/applicationId" ), value: m_appRecord.applicationId()); | 
| 1157 |     settings.sync(); | 
| 1158 |  | 
| 1159 |     qCInfo(QT_OPCUA_GDSCLIENT) << "Registered application with id"  << m_appRecord.applicationId(); | 
| 1160 |     getCertificateGroups(); | 
| 1161 | } | 
| 1162 |  | 
| 1163 | void QOpcUaGdsClientPrivate::getCertificateGroups() | 
| 1164 | { | 
| 1165 |     // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.7 GetCertificateGroups | 
| 1166 |  | 
| 1167 |     if (!m_client || m_client->state() != QOpcUaClient::Connected) { | 
| 1168 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No connection" ; | 
| 1169 |         return; | 
| 1170 |     } | 
| 1171 |  | 
| 1172 |     if (!m_directoryNode) { | 
| 1173 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node" ; | 
| 1174 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1175 |         return; | 
| 1176 |     } | 
| 1177 |  | 
| 1178 |     QList<QOpcUa::TypedVariant> arguments; | 
| 1179 |     arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); | 
| 1180 |  | 
| 1181 |     if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetCertificateGroups" )], args: arguments)) { | 
| 1182 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetCertificateGroups" ; | 
| 1183 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1184 |     } | 
| 1185 | } | 
| 1186 |  | 
| 1187 | void QOpcUaGdsClientPrivate::handleGetCertificateGroupsFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) | 
| 1188 | { | 
| 1189 |     Q_Q(QOpcUaGdsClient); | 
| 1190 |  | 
| 1191 |     if (statusCode != QOpcUa::Good) { | 
| 1192 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate groups failed"  << statusCode; | 
| 1193 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1194 |         return; | 
| 1195 |     } | 
| 1196 |  | 
| 1197 |     m_certificateGroups = result.value<QStringList>(); | 
| 1198 |     qCDebug(QT_OPCUA_GDSCLIENT) << "Certificate groups:"  << m_certificateGroups; | 
| 1199 |  | 
| 1200 |     emit q->certificateGroupsReceived(certificateGroups: m_certificateGroups); | 
| 1201 |     resolveCertificateTypes(); | 
| 1202 | } | 
| 1203 |  | 
| 1204 | void QOpcUaGdsClientPrivate::resolveCertificateTypes() | 
| 1205 | { | 
| 1206 |     Q_Q(QOpcUaGdsClient); | 
| 1207 |  | 
| 1208 |     if (!m_client || m_client->state() != QOpcUaClient::Connected) { | 
| 1209 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No connection" ; | 
| 1210 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1211 |         return; | 
| 1212 |     } | 
| 1213 |  | 
| 1214 |     if (m_certificateGroups.isEmpty()) { | 
| 1215 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate groups is empty" ; | 
| 1216 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1217 |         return; | 
| 1218 |     } | 
| 1219 |  | 
| 1220 |     delete m_certificateGroupNode; | 
| 1221 |     m_certificateGroupNode = m_client->node(nodeId: m_certificateGroups[0]); | 
| 1222 |  | 
| 1223 |     if (!m_certificateGroupNode) { | 
| 1224 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate node failed" ; | 
| 1225 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1226 |         return; | 
| 1227 |     } | 
| 1228 |  | 
| 1229 |     QObject::connect(sender: m_certificateGroupNode, signal: &QOpcUaNode::resolveBrowsePathFinished, | 
| 1230 |                      context: q, slot: [this](QList<QOpcUaBrowsePathTarget> targets, | 
| 1231 |                                QList<QOpcUaRelativePathElement> path, | 
| 1232 |                                QOpcUa::UaStatusCode statusCode) { | 
| 1233 |         if (path.size() != 1) { | 
| 1234 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size" ; | 
| 1235 |             setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1236 |             return; | 
| 1237 |         } | 
| 1238 |  | 
| 1239 |         if (path[0].targetName().name() != QLatin1String("CertificateTypes" )) { | 
| 1240 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name"  << path[0].targetName().name(); | 
| 1241 |             setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1242 |             return; | 
| 1243 |         } | 
| 1244 |  | 
| 1245 |         if (statusCode != QOpcUa::Good) { | 
| 1246 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving certificate types failed"  << statusCode; | 
| 1247 |             setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1248 |             return; | 
| 1249 |         } | 
| 1250 |  | 
| 1251 |         if (targets.size() != 1) { | 
| 1252 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results" ; | 
| 1253 |             setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1254 |             return; | 
| 1255 |         } | 
| 1256 |  | 
| 1257 |         if (!targets[0].isFullyResolved()) { | 
| 1258 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types not fully resolved" ; | 
| 1259 |             setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1260 |             return; | 
| 1261 |         } | 
| 1262 |  | 
| 1263 |         m_certificateTypesNodeId = targets[0].targetId().nodeId(); | 
| 1264 |         this->getCertificateTypes(); | 
| 1265 |     }); | 
| 1266 |  | 
| 1267 |     QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(0, QLatin1String("CertificateTypes" )), | 
| 1268 |                                            QOpcUa::ReferenceTypeId::Unspecified); | 
| 1269 |     QList<QOpcUaRelativePathElement> browsePath { pathElement }; | 
| 1270 |  | 
| 1271 |  | 
| 1272 |     if (!m_certificateGroupNode->resolveBrowsePath(path: browsePath)) { | 
| 1273 |             qCWarning(QT_OPCUA_GDSCLIENT) << "Could not resolve certificate type" ; | 
| 1274 |             setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1275 |             return; | 
| 1276 |     } | 
| 1277 | } | 
| 1278 |  | 
| 1279 | void QOpcUaGdsClientPrivate::getCertificateTypes() | 
| 1280 | { | 
| 1281 |     Q_Q(QOpcUaGdsClient); | 
| 1282 |  | 
| 1283 |     if (!m_client || m_client->state() != QOpcUaClient::Connected) { | 
| 1284 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No connection" ; | 
| 1285 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1286 |         return; | 
| 1287 |     } | 
| 1288 |  | 
| 1289 |     if (m_certificateTypesNodeId.isEmpty()) { | 
| 1290 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types node id is empty" ; | 
| 1291 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1292 |         return; | 
| 1293 |     } | 
| 1294 |  | 
| 1295 |     delete m_certificateTypesNode; | 
| 1296 |     m_certificateTypesNode = m_client->node(nodeId: m_certificateTypesNodeId); | 
| 1297 |  | 
| 1298 |     if (!m_certificateTypesNode) { | 
| 1299 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types node failed" ; | 
| 1300 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1301 |         return; | 
| 1302 |     } | 
| 1303 |  | 
| 1304 |     QObject::connect(sender: m_certificateTypesNode, signal: &QOpcUaNode::attributeUpdated, | 
| 1305 |                      context: q, slot: [this](QOpcUa::NodeAttribute attr, QVariant value) { | 
| 1306 |         if (attr == QOpcUa::NodeAttribute::Value) | 
| 1307 |             qCWarning(QT_OPCUA_GDSCLIENT) << "possible certificate types"  << value; | 
| 1308 |         registrationDone(); | 
| 1309 |     }); | 
| 1310 |  | 
| 1311 |     if (!m_certificateTypesNode->readValueAttribute()) { | 
| 1312 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Could not read certificate types" ; | 
| 1313 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1314 |         return; | 
| 1315 |     } | 
| 1316 | } | 
| 1317 |  | 
| 1318 | void QOpcUaGdsClientPrivate::registrationDone() | 
| 1319 | { | 
| 1320 |     Q_Q(QOpcUaGdsClient); | 
| 1321 |  | 
| 1322 |     setState(QOpcUaGdsClient::State::ApplicationRegistered); | 
| 1323 |     emit q->applicationRegistered(); | 
| 1324 |  | 
| 1325 |     m_certificateCheckTimer->start(); | 
| 1326 |     _q_certificateCheckTimeout(); // Force a check now | 
| 1327 |  | 
| 1328 |     m_trustListUpdateTimer->start(); | 
| 1329 |     _q_updateTrustList(); // Force a check now | 
| 1330 | } | 
| 1331 |  | 
| 1332 | void QOpcUaGdsClientPrivate::getCertificateStatus() | 
| 1333 | { | 
| 1334 |     // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.10 GetCertificateStatus | 
| 1335 |  | 
| 1336 |     if (!m_client || m_client->state() != QOpcUaClient::Connected) { | 
| 1337 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No connection" ; | 
| 1338 |         return; | 
| 1339 |     } | 
| 1340 |  | 
| 1341 |     if (!m_directoryNode) { | 
| 1342 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node" ; | 
| 1343 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1344 |         return; | 
| 1345 |     } | 
| 1346 |  | 
| 1347 |     if (m_certificateGroups.isEmpty()) { | 
| 1348 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate groups received" ; | 
| 1349 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1350 |         return; | 
| 1351 |     } | 
| 1352 |  | 
| 1353 |     QString certificateType = QLatin1String("ns=0;i=0" ); // null node | 
| 1354 |  | 
| 1355 |     QList<QOpcUa::TypedVariant> arguments; | 
| 1356 |     arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); | 
| 1357 |     // Let the server choose the certificate group id | 
| 1358 |     arguments.push_back(t: QOpcUa::TypedVariant(m_certificateGroups[0], QOpcUa::NodeId)); | 
| 1359 |     // Let the server choose the certificate type id | 
| 1360 |     arguments.push_back(t: QOpcUa::TypedVariant(certificateType, QOpcUa::NodeId)); | 
| 1361 |  | 
| 1362 |     if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetCertificateStatus" )], args: arguments)) { | 
| 1363 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetCertificateStatus" ; | 
| 1364 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1365 |     } | 
| 1366 | } | 
| 1367 |  | 
| 1368 | void QOpcUaGdsClientPrivate::handleGetCertificateStatusFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) | 
| 1369 | { | 
| 1370 |     Q_Q(QOpcUaGdsClient); | 
| 1371 |  | 
| 1372 |     if (statusCode != QOpcUa::Good) { | 
| 1373 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate status failed"  << statusCode; | 
| 1374 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1375 |         return; | 
| 1376 |     } | 
| 1377 |  | 
| 1378 |     if (result.toBool()) { | 
| 1379 |         qInfo(catFunc: QT_OPCUA_GDSCLIENT) << "Certificate needs update" ; | 
| 1380 |         emit q->certificateUpdateRequired(); | 
| 1381 |         startCertificateRequest(); | 
| 1382 |     } | 
| 1383 | } | 
| 1384 |  | 
| 1385 | void QOpcUaGdsClientPrivate::startCertificateRequest() | 
| 1386 | { | 
| 1387 |     // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.3 StartSigningRequest | 
| 1388 |  | 
| 1389 |     if (!m_client || m_client->state() != QOpcUaClient::Connected) { | 
| 1390 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No connection" ; | 
| 1391 |         return; | 
| 1392 |     } | 
| 1393 |  | 
| 1394 |     if (!m_directoryNode) { | 
| 1395 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node" ; | 
| 1396 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1397 |         return; | 
| 1398 |     } | 
| 1399 |  | 
| 1400 |     QOpcUaKeyPair keyPair; | 
| 1401 |     QFile keyFile(m_pkiConfig.privateKeyFile()); | 
| 1402 |  | 
| 1403 |     qCDebug(QT_OPCUA_GDSCLIENT) << "Using private key"  << keyFile.fileName(); | 
| 1404 |  | 
| 1405 |     if (!keyFile.open(flags: QFile::ReadOnly)) { | 
| 1406 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open private key file"  << keyFile.fileName() << "for reading:"  << keyFile.errorString(); | 
| 1407 |         setError(QOpcUaGdsClient::Error::ConnectionError); | 
| 1408 |         return; | 
| 1409 |     } | 
| 1410 |  | 
| 1411 |     auto data = keyFile.readAll(); | 
| 1412 |     keyFile.close(); | 
| 1413 |  | 
| 1414 |     if (!keyPair.loadFromPemData(data)) { | 
| 1415 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load private key" ; | 
| 1416 |         setError(QOpcUaGdsClient::Error::ConnectionError); | 
| 1417 |         return; | 
| 1418 |     } | 
| 1419 |  | 
| 1420 |     if (!keyPair.hasPrivateKey()) { | 
| 1421 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Private key does not contain a private key" ; | 
| 1422 |         setError(QOpcUaGdsClient::Error::ConnectionError); | 
| 1423 |         return; | 
| 1424 |     } | 
| 1425 |  | 
| 1426 |     auto csr = createSigningRequest(); | 
| 1427 |     csr.setEncoding(QOpcUaX509CertificateSigningRequest::Encoding::DER); | 
| 1428 |     const auto csrData = csr.createRequest(privateKey: keyPair); | 
| 1429 |  | 
| 1430 |  | 
| 1431 |     QList<QOpcUa::TypedVariant> arguments; | 
| 1432 |     arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); | 
| 1433 |     // Let the server choose the certificate group id | 
| 1434 |     arguments.push_back(t: QOpcUa::TypedVariant(QLatin1String("ns=0;i=0" ), QOpcUa::NodeId)); | 
| 1435 |     // Let the server choose the certificate type id | 
| 1436 |     arguments.push_back(t: QOpcUa::TypedVariant(QLatin1String("ns=0;i=0" ), QOpcUa::NodeId)); | 
| 1437 |  | 
| 1438 |     arguments.push_back(t: QOpcUa::TypedVariant(csrData, QOpcUa::ByteString)); | 
| 1439 |  | 
| 1440 |     if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("StartSigningRequest" )], args: arguments)) { | 
| 1441 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call StartSigningRequest" ; | 
| 1442 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificate); | 
| 1443 |     } | 
| 1444 | } | 
| 1445 |  | 
| 1446 | void QOpcUaGdsClientPrivate::handleStartSigningRequestFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) | 
| 1447 | { | 
| 1448 |     Q_Q(QOpcUaGdsClient); | 
| 1449 |  | 
| 1450 |     if (statusCode != QOpcUa::Good) { | 
| 1451 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate failed"  << statusCode; | 
| 1452 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificate); | 
| 1453 |         return; | 
| 1454 |     } | 
| 1455 |  | 
| 1456 |     m_certificateRequestId = result.toString(); | 
| 1457 |  | 
| 1458 |     if (!m_certificateFinishTimer) | 
| 1459 |         m_certificateFinishTimer = new QTimer; | 
| 1460 |  | 
| 1461 |     m_certificateFinishTimer->setInterval(2000); | 
| 1462 |     m_certificateFinishTimer->setSingleShot(true); | 
| 1463 |     QObject::connect(sender: m_certificateFinishTimer, signal: &QTimer::timeout, context: q, slot: [this]() { | 
| 1464 |         this->finishCertificateRequest(); | 
| 1465 |     }); | 
| 1466 |  | 
| 1467 |     m_certificateFinishTimer->start(); | 
| 1468 | } | 
| 1469 |  | 
| 1470 | void QOpcUaGdsClientPrivate::finishCertificateRequest() | 
| 1471 | { | 
| 1472 |     // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.5 FinishRequest | 
| 1473 |  | 
| 1474 |     if (!m_client || m_client->state() != QOpcUaClient::Connected) { | 
| 1475 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No connection" ; | 
| 1476 |         return; | 
| 1477 |     } | 
| 1478 |  | 
| 1479 |     if (m_certificateRequestId.isEmpty()) { | 
| 1480 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate request id" ; | 
| 1481 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1482 |         return; | 
| 1483 |     } | 
| 1484 |  | 
| 1485 |     QList<QOpcUa::TypedVariant> arguments; | 
| 1486 |     arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); | 
| 1487 |     arguments.push_back(t: QOpcUa::TypedVariant(m_certificateRequestId, QOpcUa::NodeId)); | 
| 1488 |  | 
| 1489 |     if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("FinishRequest" )], args: arguments)) { | 
| 1490 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call FinishRequest" ; | 
| 1491 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificate); | 
| 1492 |     } | 
| 1493 | } | 
| 1494 |  | 
| 1495 | void QOpcUaGdsClientPrivate::handleFinishRequestFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) | 
| 1496 | { | 
| 1497 |     Q_Q(QOpcUaGdsClient); | 
| 1498 |  | 
| 1499 |     if (statusCode == QOpcUa::BadNothingToDo) { | 
| 1500 |         // Server not finished yet: Try again later | 
| 1501 |         m_certificateFinishTimer->setInterval(m_certificateFinishTimer->interval() * 2); | 
| 1502 |         m_certificateFinishTimer->start(); | 
| 1503 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Server not finished yet: Trying again in"  << m_certificateFinishTimer->interval() / 1000 << "s" ; | 
| 1504 |         return; | 
| 1505 |     } | 
| 1506 |     if (statusCode != QOpcUa::Good) { | 
| 1507 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate failed"  << statusCode; | 
| 1508 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificate); | 
| 1509 |         return; | 
| 1510 |     } | 
| 1511 |  | 
| 1512 |     m_certificateRequestId.clear(); | 
| 1513 |     const auto resultList = result.toList(); | 
| 1514 |  | 
| 1515 |     if (resultList.size() != 3) { | 
| 1516 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Expected list of 3 results but got"  << resultList.size(); | 
| 1517 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificate); | 
| 1518 |         return; | 
| 1519 |     } | 
| 1520 |  | 
| 1521 |     const auto certificate = resultList[0].toByteArray(); | 
| 1522 |     //const auto privateKey = resultList[1].toByteArray(); | 
| 1523 |     const auto issuerList = resultList[2].toByteArray(); | 
| 1524 |  | 
| 1525 |     if (certificate.isEmpty() || issuerList.isEmpty()) { | 
| 1526 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Certificates are empty" ; | 
| 1527 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificate); | 
| 1528 |         return; | 
| 1529 |     } | 
| 1530 |  | 
| 1531 |     qCInfo(QT_OPCUA_GDSCLIENT) << "Received new certificate"  << certificate; | 
| 1532 |  | 
| 1533 |     QFile certificateFile(m_pkiConfig.clientCertificateFile()); | 
| 1534 |     if (!certificateFile.open(flags: QFile::WriteOnly)) { | 
| 1535 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate" ; | 
| 1536 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificate); | 
| 1537 |         return; | 
| 1538 |     } | 
| 1539 |  | 
| 1540 |     if (certificateFile.write(data: certificate) != certificate.size()) { | 
| 1541 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate data" ; | 
| 1542 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificate); | 
| 1543 |         return; | 
| 1544 |     } | 
| 1545 |  | 
| 1546 |     certificateFile.close(); | 
| 1547 |  | 
| 1548 |     // FIMXE: How to store this? | 
| 1549 |     QTemporaryFile issuerFile(m_pkiConfig.issuerListDirectory() + QLatin1String("/XXXXXX.der" )); | 
| 1550 |     if (!issuerFile.open()) { | 
| 1551 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store issuer certificates to"  << m_pkiConfig.issuerListDirectory(); | 
| 1552 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificate); | 
| 1553 |         return; | 
| 1554 |     } | 
| 1555 |  | 
| 1556 |     if (issuerFile.write(data: issuerList) != issuerList.size()) { | 
| 1557 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate data" ; | 
| 1558 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificate); | 
| 1559 |         return; | 
| 1560 |     } | 
| 1561 |  | 
| 1562 |     qCDebug(QT_OPCUA_GDSCLIENT) << "issuer list stored to"  << issuerFile.fileName(); | 
| 1563 |     issuerFile.close(); | 
| 1564 |     issuerFile.setAutoRemove(false); | 
| 1565 |  | 
| 1566 |     emit q->certificateUpdated(); | 
| 1567 | } | 
| 1568 |  | 
| 1569 | void QOpcUaGdsClientPrivate::unregisterApplication() | 
| 1570 | { | 
| 1571 |     Q_Q(QOpcUaGdsClient); | 
| 1572 |  | 
| 1573 |     if (m_appRecord.applicationId().isEmpty() || m_appRecord.applicationId() == QLatin1String("ns=0;i=0" )) { | 
| 1574 |         emit q->unregistered(); | 
| 1575 |         return; | 
| 1576 |     } | 
| 1577 |  | 
| 1578 |     if (!m_client || m_client->state() != QOpcUaClient::Connected) { | 
| 1579 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No connection" ; | 
| 1580 |         setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication); | 
| 1581 |         return; | 
| 1582 |     } | 
| 1583 |  | 
| 1584 |     if (!m_directoryNode) { | 
| 1585 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node" ; | 
| 1586 |         setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication); | 
| 1587 |         return; | 
| 1588 |     } | 
| 1589 |  | 
| 1590 |     if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("UnregisterApplication" )], | 
| 1591 |                                      args: QList<QOpcUa::TypedVariant> { QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId) })) { | 
| 1592 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method UnregisterApplication" ; | 
| 1593 |         setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication); | 
| 1594 |     } | 
| 1595 | } | 
| 1596 |  | 
| 1597 | void QOpcUaGdsClientPrivate::handleUnregisterApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) | 
| 1598 | { | 
| 1599 |     Q_Q(QOpcUaGdsClient); | 
| 1600 |     Q_UNUSED(result); | 
| 1601 |  | 
| 1602 |     if (statusCode != QOpcUa::Good) { | 
| 1603 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Unregister application failed"  << statusCode; | 
| 1604 |         setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication); | 
| 1605 |         return; | 
| 1606 |     } | 
| 1607 |  | 
| 1608 |     m_client->disconnectFromEndpoint(); | 
| 1609 |     emit q->unregistered(); | 
| 1610 | } | 
| 1611 |  | 
| 1612 | void QOpcUaGdsClientPrivate::setPkiConfiguration(const QOpcUaPkiConfiguration &pkiConfig) | 
| 1613 | { | 
| 1614 |     m_pkiConfig = pkiConfig; | 
| 1615 | } | 
| 1616 |  | 
| 1617 | const QOpcUaPkiConfiguration &QOpcUaGdsClientPrivate::pkiConfiguration() const | 
| 1618 | { | 
| 1619 |     return m_pkiConfig; | 
| 1620 | } | 
| 1621 |  | 
| 1622 | void QOpcUaGdsClientPrivate::setApplicationIdentity(const QOpcUaApplicationIdentity &appIdentity) | 
| 1623 | { | 
| 1624 |     m_appIdentitiy = appIdentity; | 
| 1625 | } | 
| 1626 |  | 
| 1627 | const QOpcUaApplicationIdentity &QOpcUaGdsClientPrivate::applicationIdentity() const | 
| 1628 | { | 
| 1629 |     return m_appIdentitiy; | 
| 1630 | } | 
| 1631 |  | 
| 1632 | void QOpcUaGdsClientPrivate::setApplicationRecord(const QOpcUaApplicationRecordDataType &appRecord) | 
| 1633 | { | 
| 1634 |     m_appRecord = appRecord; | 
| 1635 | } | 
| 1636 |  | 
| 1637 | const QOpcUaApplicationRecordDataType &QOpcUaGdsClientPrivate::applicationRecord() const | 
| 1638 | { | 
| 1639 |     return m_appRecord; | 
| 1640 | } | 
| 1641 |  | 
| 1642 | void QOpcUaGdsClientPrivate::setCertificateSigningRequestPresets(const QOpcUaX509DistinguishedName &dn, const QString &dns) | 
| 1643 | { | 
| 1644 |     csrPresets.dn = dn; | 
| 1645 |     csrPresets.dns = dns; | 
| 1646 | } | 
| 1647 |  | 
| 1648 | const QOpcUaX509DistinguishedName &QOpcUaGdsClientPrivate::distinguishedNameCertificateSigningRequestPreset() const | 
| 1649 | { | 
| 1650 |     return csrPresets.dn; | 
| 1651 | } | 
| 1652 |  | 
| 1653 | const QString &QOpcUaGdsClientPrivate::dnsCertificateSigningRequestPreset() const | 
| 1654 | { | 
| 1655 |     return csrPresets.dns; | 
| 1656 | } | 
| 1657 |  | 
| 1658 | void QOpcUaGdsClientPrivate::setCertificateCheckInterval(int interval) | 
| 1659 | { | 
| 1660 |     m_certificateCheckTimer->setInterval(interval); | 
| 1661 | } | 
| 1662 |  | 
| 1663 | int QOpcUaGdsClientPrivate::certificateCheckInterval() const | 
| 1664 | { | 
| 1665 |     return m_certificateCheckTimer->interval(); | 
| 1666 | } | 
| 1667 |  | 
| 1668 | void QOpcUaGdsClientPrivate::setTrustListUpdateInterval(int interval) | 
| 1669 | { | 
| 1670 |     m_trustListUpdateTimer->setInterval(interval); | 
| 1671 | } | 
| 1672 |  | 
| 1673 | int QOpcUaGdsClientPrivate::trustListUpdateInterval() const | 
| 1674 | { | 
| 1675 |     return m_trustListUpdateTimer->interval(); | 
| 1676 | } | 
| 1677 |  | 
| 1678 | QOpcUaGdsClient::Error QOpcUaGdsClientPrivate::error() const | 
| 1679 | { | 
| 1680 |     return m_error; | 
| 1681 | } | 
| 1682 |  | 
| 1683 | QOpcUaGdsClient::State QOpcUaGdsClientPrivate::state() const | 
| 1684 | { | 
| 1685 |     return m_state; | 
| 1686 | } | 
| 1687 |  | 
| 1688 | QOpcUaX509CertificateSigningRequest QOpcUaGdsClientPrivate::createSigningRequest() const | 
| 1689 | { | 
| 1690 |     QOpcUaX509CertificateSigningRequest csr; | 
| 1691 |     QOpcUaX509DistinguishedName dn = csrPresets.dn; | 
| 1692 |     // Overwrite the application name because it has to match the identity | 
| 1693 |     dn.setEntry(type: QOpcUaX509DistinguishedName::Type::CommonName, value: m_appIdentitiy.applicationName()); | 
| 1694 |     csr.setSubject(dn); | 
| 1695 |  | 
| 1696 |     QOpcUaX509ExtensionSubjectAlternativeName *san = new QOpcUaX509ExtensionSubjectAlternativeName; | 
| 1697 |     san->addEntry(type: QOpcUaX509ExtensionSubjectAlternativeName::Type::URI, value: m_appIdentitiy.applicationUri()); | 
| 1698 |     if (!csrPresets.dns.isEmpty()) | 
| 1699 |         san->addEntry(type: QOpcUaX509ExtensionSubjectAlternativeName::Type::DNS, value: csrPresets.dns); | 
| 1700 |     san->setCritical(true); | 
| 1701 |     csr.addExtension(extension: san); | 
| 1702 |  | 
| 1703 |     QOpcUaX509ExtensionBasicConstraints *bc = new QOpcUaX509ExtensionBasicConstraints; | 
| 1704 |     bc->setCa(false); | 
| 1705 |     bc->setCritical(true); | 
| 1706 |     csr.addExtension(extension: bc); | 
| 1707 |  | 
| 1708 |     QOpcUaX509ExtensionKeyUsage *ku = new QOpcUaX509ExtensionKeyUsage; | 
| 1709 |     ku->setCritical(true); | 
| 1710 |     ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::DigitalSignature); | 
| 1711 |     ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::NonRepudiation); | 
| 1712 |     ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::KeyEncipherment); | 
| 1713 |     ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::DataEncipherment); | 
| 1714 |     ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::CertificateSigning); | 
| 1715 |     csr.addExtension(extension: ku); | 
| 1716 |  | 
| 1717 |     return csr; | 
| 1718 | } | 
| 1719 |  | 
| 1720 | void QOpcUaGdsClientPrivate::_q_handleDirectoryNodeMethodCallFinished(QString methodNodeId, QVariant result, QOpcUa::UaStatusCode statusCode) | 
| 1721 | { | 
| 1722 |     if (methodNodeId == m_directoryNodes[QLatin1String("UnregisterApplication" )]) { | 
| 1723 |         this->handleUnregisterApplicationFinished(result, statusCode); | 
| 1724 |     } else if ( methodNodeId == m_directoryNodes[QLatin1String("FinishRequest" )]) { | 
| 1725 |         this->handleFinishRequestFinished(result, statusCode); | 
| 1726 |     } else if (methodNodeId == m_directoryNodes[QLatin1String("StartSigningRequest" )]) { | 
| 1727 |         this->handleStartSigningRequestFinished(result, statusCode); | 
| 1728 |     } else if (methodNodeId == m_directoryNodes[QLatin1String("GetCertificateStatus" )]) { | 
| 1729 |         this->handleGetCertificateStatusFinished(result, statusCode); | 
| 1730 |     } else if (methodNodeId == m_directoryNodes[QLatin1String("GetCertificateGroups" )]) { | 
| 1731 |         this->handleGetCertificateGroupsFinished(result, statusCode); | 
| 1732 |     } else if (methodNodeId == m_directoryNodes[QLatin1String("RegisterApplication" )]) { | 
| 1733 |         this->handleRegisterApplicationFinished(result, statusCode); | 
| 1734 |     } else if (methodNodeId == m_directoryNodes[QLatin1String("FindApplications" )]) { | 
| 1735 |         this->handleFindApplicationsFinished(result, statusCode); | 
| 1736 |     } else if (methodNodeId == m_directoryNodes[QLatin1String("GetApplication" )]) { | 
| 1737 |         this->handleGetApplicationFinished(result, statusCode); | 
| 1738 |     } else if (methodNodeId == m_directoryNodes[QLatin1String("GetTrustList" )]) { | 
| 1739 |         this->handleGetTrustListFinished(result, statusCode); | 
| 1740 |     } else { | 
| 1741 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Result unexpeced method call received"  << methodNodeId; | 
| 1742 |     } | 
| 1743 | } | 
| 1744 |  | 
| 1745 | void QOpcUaGdsClientPrivate::_q_certificateCheckTimeout() | 
| 1746 | { | 
| 1747 |     Q_Q(QOpcUaGdsClient); | 
| 1748 |  | 
| 1749 |     QFile file(m_pkiConfig.clientCertificateFile()); | 
| 1750 |     if (!file.open(flags: QFile::ReadOnly)) { | 
| 1751 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load certificate from file"  << m_pkiConfig.clientCertificateFile(); | 
| 1752 |     } | 
| 1753 |  | 
| 1754 |     QSslCertificate cert(&file, QSsl::Der); | 
| 1755 |     if (cert.isNull()) { | 
| 1756 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load certificate from file"  << m_pkiConfig.clientCertificateFile(); | 
| 1757 |         return; | 
| 1758 |     } | 
| 1759 |  | 
| 1760 |     bool needsUpdate = false; | 
| 1761 |  | 
| 1762 |     if (cert.isSelfSigned()) { | 
| 1763 |         needsUpdate = true; | 
| 1764 |         qCInfo(QT_OPCUA_GDSCLIENT) << "Certificate is self-signed: requesting update" ; | 
| 1765 |     } | 
| 1766 |  | 
| 1767 |     if (QDateTime::currentDateTime().addSecs(secs: 24*60*60) > cert.expiryDate()) { | 
| 1768 |         needsUpdate = true; | 
| 1769 |         qCInfo(QT_OPCUA_GDSCLIENT) << "Certificate expiry date is due: requesting update" ; | 
| 1770 |     } | 
| 1771 |  | 
| 1772 |     if (!needsUpdate) | 
| 1773 |         getCertificateStatus(); | 
| 1774 |  | 
| 1775 |     if (needsUpdate) { | 
| 1776 |         emit q->certificateUpdateRequired(); | 
| 1777 |         startCertificateRequest(); | 
| 1778 |     } | 
| 1779 | } | 
| 1780 |  | 
| 1781 | void QOpcUaGdsClientPrivate::_q_updateTrustList() | 
| 1782 | { | 
| 1783 |     // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.9 GetTrustList | 
| 1784 |  | 
| 1785 |     if (!m_client || m_client->state() != QOpcUaClient::Connected) { | 
| 1786 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No connection" ; | 
| 1787 |         return; | 
| 1788 |     } | 
| 1789 |  | 
| 1790 |     if (!m_certificateGroupNode) { | 
| 1791 |         qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate group node" ; | 
| 1792 |         return; | 
| 1793 |     } | 
| 1794 |  | 
| 1795 |     QList<QOpcUa::TypedVariant> arguments; | 
| 1796 |     arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); | 
| 1797 |     arguments.push_back(t: QOpcUa::TypedVariant(m_certificateGroupNode->nodeId(), QOpcUa::NodeId)); | 
| 1798 |  | 
| 1799 |     if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetTrustList" )], args: arguments)) { | 
| 1800 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetTrustList" ; | 
| 1801 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificate); | 
| 1802 |     } | 
| 1803 | } | 
| 1804 |  | 
| 1805 | void QOpcUaGdsClientPrivate::handleGetTrustListFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) | 
| 1806 | { | 
| 1807 |     Q_Q(QOpcUaGdsClient); | 
| 1808 |  | 
| 1809 |     if (statusCode != QOpcUa::Good) { | 
| 1810 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Getting trust list node failed"  << statusCode; | 
| 1811 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificate); | 
| 1812 |         return; | 
| 1813 |     } | 
| 1814 |  | 
| 1815 |     const auto trustListNodeId = result.toString(); | 
| 1816 |  | 
| 1817 |     if (trustListNodeId.isEmpty()) { | 
| 1818 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Trust list node id is empty" ; | 
| 1819 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificate); | 
| 1820 |         return; | 
| 1821 |     } | 
| 1822 |  | 
| 1823 |     delete m_trustListNode; | 
| 1824 |     m_trustListNode = m_client->node(nodeId: trustListNodeId); | 
| 1825 |  | 
| 1826 |     if (!m_trustListNode) { | 
| 1827 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Trust list node failed" ; | 
| 1828 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1829 |         return; | 
| 1830 |     } | 
| 1831 |  | 
| 1832 |     QObject::connect(sender: m_trustListNode, signal: &QOpcUaNode::attributeUpdated, | 
| 1833 |                      context: q, slot: [q](QOpcUa::NodeAttribute attr, QVariant value) { | 
| 1834 |         Q_UNUSED(value); | 
| 1835 |         if (attr == QOpcUa::NodeAttribute::Value) | 
| 1836 |             emit q->trustListUpdated(); | 
| 1837 |     }); | 
| 1838 |  | 
| 1839 |     if (!m_trustListNode->readValueAttribute()) { | 
| 1840 |         qCWarning(QT_OPCUA_GDSCLIENT) << "Could not read trust list" ; | 
| 1841 |         setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); | 
| 1842 |         return; | 
| 1843 |     } | 
| 1844 | } | 
| 1845 |  | 
| 1846 | // When it is detected that authentication credentials are required this function | 
| 1847 | // is used to re-connect to the server after requesting credentials | 
| 1848 | void QOpcUaGdsClientPrivate::restartWithCredentials() | 
| 1849 | { | 
| 1850 |     Q_Q(QOpcUaGdsClient); | 
| 1851 |  | 
| 1852 |     QOpcUaAuthenticationInformation authInfo; | 
| 1853 |     emit q->authenticationRequired(authInfo); | 
| 1854 |     m_client->setAuthenticationInformation(authInfo); | 
| 1855 |  | 
| 1856 |     qCInfo(QT_OPCUA_GDSCLIENT) << "Restarting connection with credentials" ; | 
| 1857 |     m_restartRequired = true; | 
| 1858 |     m_client->disconnectFromEndpoint(); | 
| 1859 | } | 
| 1860 |  | 
| 1861 | QT_END_NAMESPACE | 
| 1862 |  | 
| 1863 | #include "moc_qopcuagdsclient.cpp" | 
| 1864 |  |