diff options
| author | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-19 05:13:42 +0100 |
|---|---|---|
| committer | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-19 05:38:24 +0100 |
| commit | a0c7f2a7ef04dbe2e7491eabf828e26423d1bd10 (patch) | |
| tree | 50491c80f28ad5f2789a3777bec74489327c4d7f /src/BobinkClient.cpp | |
| parent | 23916cbb98e952aab752a647ac96020aab709bb6 (diff) | |
| download | BobinkQtOpcUa-a0c7f2a7ef04dbe2e7491eabf828e26423d1bd10.tar.gz BobinkQtOpcUa-a0c7f2a7ef04dbe2e7491eabf828e26423d1bd10.zip | |
Rename targets, route messages to debug console, clean up
- Rename CMake project and library target to BobinkQtOpcUa (URI stays Bobink)
- Rename demo target to BobinkDemo, output binary to build/bin/
- Route all QML status/error messages to the in-app debug console
- Remove discoveryInterval QML property, default to 30s internally
- Add errorChanged handler, PKI file validation in applyPki()
Diffstat (limited to 'src/BobinkClient.cpp')
| -rw-r--r-- | src/BobinkClient.cpp | 685 |
1 files changed, 417 insertions, 268 deletions
diff --git a/src/BobinkClient.cpp b/src/BobinkClient.cpp index 565868c..e7f6d0d 100644 --- a/src/BobinkClient.cpp +++ b/src/BobinkClient.cpp @@ -11,433 +11,582 @@ BobinkClient *BobinkClient::s_instance = nullptr; -static QString defaultPkiDir() +static QString +defaultPkiDir () { - return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) - + QStringLiteral("/pki"); + return QStandardPaths::writableLocation (QStandardPaths::AppDataLocation) + + QStringLiteral ("/pki"); } /** @brief Create the standard OPC UA PKI directory tree. */ -static void ensurePkiDirs(const QString &base) +static void +ensurePkiDirs (const QString &base) { - for (const auto *sub : {"own/certs", "own/private", - "trusted/certs", "trusted/crl", - "issuers/certs", "issuers/crl"}) { - QDir().mkpath(base + QLatin1Char('/') + QLatin1String(sub)); + for (const auto *sub : { "own/certs", "own/private", "trusted/certs", + "trusted/crl", "issuers/certs", "issuers/crl" }) + { + QDir ().mkpath (base + QLatin1Char ('/') + QLatin1String (sub)); } } -BobinkClient::BobinkClient(QObject *parent) - : QObject(parent) - , m_provider(new QOpcUaProvider(this)) - , m_pkiDir(defaultPkiDir()) +BobinkClient::BobinkClient (QObject *parent) + : QObject (parent), m_provider (new QOpcUaProvider (this)), + m_pkiDir (defaultPkiDir ()) { - ensurePkiDirs(m_pkiDir); - setupClient(); - autoDetectPki(); - applyPki(); - connect(&m_discoveryTimer, &QTimer::timeout, this, &BobinkClient::doDiscovery); + ensurePkiDirs (m_pkiDir); + setupClient (); + autoDetectPki (); + applyPki (); + connect (&m_discoveryTimer, &QTimer::timeout, this, + &BobinkClient::doDiscovery); } -BobinkClient::~BobinkClient() +BobinkClient::~BobinkClient () { - if (s_instance == this) - s_instance = nullptr; + if (s_instance == this) + s_instance = nullptr; } -BobinkClient *BobinkClient::instance() +BobinkClient * +BobinkClient::instance () { - return s_instance; + return s_instance; } -BobinkClient *BobinkClient::create(QQmlEngine *, QJSEngine *) +BobinkClient * +BobinkClient::create (QQmlEngine *, QJSEngine *) { - if (!s_instance) { - s_instance = new BobinkClient; - QJSEngine::setObjectOwnership(s_instance, QJSEngine::CppOwnership); + if (!s_instance) + { + s_instance = new BobinkClient; + QJSEngine::setObjectOwnership (s_instance, QJSEngine::CppOwnership); } - return s_instance; + return s_instance; } -void BobinkClient::setupClient() +void +BobinkClient::setupClient () { - m_client = m_provider->createClient(QStringLiteral("open62541")); - if (!m_client) { - qWarning() << "BobinkClient: failed to create open62541 backend"; - return; + m_client = m_provider->createClient (QStringLiteral ("open62541")); + if (!m_client) + { + qWarning () << "BobinkClient: failed to create open62541 backend"; + return; } - connect(m_client, &QOpcUaClient::stateChanged, - this, &BobinkClient::handleStateChanged); - connect(m_client, &QOpcUaClient::endpointsRequestFinished, - this, &BobinkClient::handleEndpointsReceived); - connect(m_client, &QOpcUaClient::connectError, - this, &BobinkClient::handleConnectError); - connect(m_client, &QOpcUaClient::findServersFinished, - this, &BobinkClient::handleFindServersFinished); + connect (m_client, &QOpcUaClient::stateChanged, this, + &BobinkClient::handleStateChanged); + connect (m_client, &QOpcUaClient::endpointsRequestFinished, this, + &BobinkClient::handleEndpointsReceived); + connect (m_client, &QOpcUaClient::connectError, this, + &BobinkClient::handleConnectError); + connect (m_client, &QOpcUaClient::findServersFinished, this, + &BobinkClient::handleFindServersFinished); + connect (m_client, &QOpcUaClient::errorChanged, this, + [this] (QOpcUaClient::ClientError error) + { + static const char *names[] + = { "NoError", + "InvalidUrl", + "AccessDenied", + "ConnectionError", + "UnknownError", + "UnsupportedAuthenticationInformation", + "InvalidAuthenticationInformation", + "InvalidEndpointDescription", + "NoMatchingUserIdentityTokenFound", + "UnsupportedSecurityPolicy", + "InvalidPki" }; + int idx = static_cast<int> (error); + if (idx > 0 && idx <= 10) + emit connectionError ( + QStringLiteral ("Client error: %1").arg (names[idx])); + }); } /* ====================================== * Connection properties * ====================================== */ -bool BobinkClient::connected() const { return m_connected; } +bool +BobinkClient::connected () const +{ + return m_connected; +} -QString BobinkClient::serverUrl() const { return m_serverUrl; } +QString +BobinkClient::serverUrl () const +{ + return m_serverUrl; +} -void BobinkClient::setServerUrl(const QString &url) +void +BobinkClient::setServerUrl (const QString &url) { - if (m_serverUrl == url) - return; - m_serverUrl = url; - emit serverUrlChanged(); + if (m_serverUrl == url) + return; + m_serverUrl = url; + emit serverUrlChanged (); } -BobinkAuth *BobinkClient::auth() const { return m_auth; } +BobinkAuth * +BobinkClient::auth () const +{ + return m_auth; +} -void BobinkClient::setAuth(BobinkAuth *auth) +void +BobinkClient::setAuth (BobinkAuth *auth) { - if (m_auth == auth) - return; - m_auth = auth; - emit authChanged(); + if (m_auth == auth) + return; + m_auth = auth; + emit authChanged (); } -QOpcUaClient *BobinkClient::opcuaClient() const { return m_client; } +QOpcUaClient * +BobinkClient::opcuaClient () const +{ + return m_client; +} /* ====================================== * Connection methods * ====================================== */ -void BobinkClient::connectToServer() +void +BobinkClient::connectToServer () { - if (!m_client) { - emit connectionError(QStringLiteral("OPC UA backend not available")); - return; + if (!m_client) + { + emit connectionError (QStringLiteral ("OPC UA backend not available")); + return; } - if (m_serverUrl.isEmpty()) { - emit connectionError(QStringLiteral("No server URL set")); - return; + if (m_serverUrl.isEmpty ()) + { + emit connectionError (QStringLiteral ("No server URL set")); + return; } - if (m_client->state() != QOpcUaClient::Disconnected) { - emit connectionError(QStringLiteral("Already connected or connecting")); - return; + if (m_client->state () != QOpcUaClient::Disconnected) + { + emit connectionError ( + QStringLiteral ("Already connected or connecting")); + return; } - QUrl url(m_serverUrl); - if (!url.isValid()) { - emit connectionError(QStringLiteral("Invalid server URL: %1").arg(m_serverUrl)); - return; + QUrl url (m_serverUrl); + if (!url.isValid ()) + { + emit connectionError ( + QStringLiteral ("Invalid server URL: %1").arg (m_serverUrl)); + return; } - m_client->requestEndpoints(url); + m_client->requestEndpoints (url); } -static QString securityPolicyUri(BobinkClient::SecurityPolicy policy) +static QString +securityPolicyUri (BobinkClient::SecurityPolicy policy) { - switch (policy) { + switch (policy) + { case BobinkClient::Basic256Sha256: - return QStringLiteral( - "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"); + return QStringLiteral ( + "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"); case BobinkClient::Aes128_Sha256_RsaOaep: - return QStringLiteral( - "http://opcfoundation.org/UA/SecurityPolicy#Aes128_Sha256_RsaOaep"); + return QStringLiteral ( + "http://opcfoundation.org/UA/SecurityPolicy#Aes128_Sha256_RsaOaep"); case BobinkClient::Aes256_Sha256_RsaPss: - return QStringLiteral( - "http://opcfoundation.org/UA/SecurityPolicy#Aes256_Sha256_RsaPss"); + return QStringLiteral ( + "http://opcfoundation.org/UA/SecurityPolicy#Aes256_Sha256_RsaPss"); } - return {}; + return {}; } -void BobinkClient::connectDirect(SecurityPolicy policy, SecurityMode mode) +void +BobinkClient::connectDirect (SecurityPolicy policy, SecurityMode mode) { - if (!m_client) { - emit connectionError(QStringLiteral("OPC UA backend not available")); - return; + if (!m_client) + { + emit connectionError (QStringLiteral ("OPC UA backend not available")); + return; } - if (m_serverUrl.isEmpty()) { - emit connectionError(QStringLiteral("No server URL set")); - return; + if (m_serverUrl.isEmpty ()) + { + emit connectionError (QStringLiteral ("No server URL set")); + return; } - if (m_client->state() != QOpcUaClient::Disconnected) { - emit connectionError(QStringLiteral("Already connected or connecting")); - return; + if (m_client->state () != QOpcUaClient::Disconnected) + { + emit connectionError ( + QStringLiteral ("Already connected or connecting")); + return; } - QOpcUaEndpointDescription endpoint; - endpoint.setEndpointUrl(m_serverUrl); - endpoint.setSecurityPolicy(securityPolicyUri(policy)); - endpoint.setSecurityMode( - static_cast<QOpcUaEndpointDescription::MessageSecurityMode>(mode)); - - QOpcUaUserTokenPolicy tokenPolicy; - if (m_auth) { - switch (m_auth->mode()) { + QOpcUaEndpointDescription endpoint; + endpoint.setEndpointUrl (m_serverUrl); + endpoint.setSecurityPolicy (securityPolicyUri (policy)); + endpoint.setSecurityMode ( + static_cast<QOpcUaEndpointDescription::MessageSecurityMode> (mode)); + + QOpcUaUserTokenPolicy tokenPolicy; + if (m_auth) + { + switch (m_auth->mode ()) + { case BobinkAuth::Anonymous: - tokenPolicy.setTokenType(QOpcUaUserTokenPolicy::TokenType::Anonymous); - break; + tokenPolicy.setTokenType ( + QOpcUaUserTokenPolicy::TokenType::Anonymous); + break; case BobinkAuth::UserPass: - tokenPolicy.setTokenType(QOpcUaUserTokenPolicy::TokenType::Username); - break; + tokenPolicy.setTokenType ( + QOpcUaUserTokenPolicy::TokenType::Username); + break; case BobinkAuth::Certificate: - tokenPolicy.setTokenType(QOpcUaUserTokenPolicy::TokenType::Certificate); - break; + tokenPolicy.setTokenType ( + QOpcUaUserTokenPolicy::TokenType::Certificate); + break; } - m_client->setAuthenticationInformation(m_auth->toAuthenticationInformation()); - } else { - tokenPolicy.setTokenType(QOpcUaUserTokenPolicy::TokenType::Anonymous); + m_client->setAuthenticationInformation ( + m_auth->toAuthenticationInformation ()); + } + else + { + tokenPolicy.setTokenType (QOpcUaUserTokenPolicy::TokenType::Anonymous); } - endpoint.setUserIdentityTokens({tokenPolicy}); + endpoint.setUserIdentityTokens ({ tokenPolicy }); - m_client->connectToEndpoint(endpoint); + m_client->connectToEndpoint (endpoint); } -void BobinkClient::disconnectFromServer() +void +BobinkClient::disconnectFromServer () { - if (m_client) - m_client->disconnectFromEndpoint(); + if (m_client) + m_client->disconnectFromEndpoint (); } -void BobinkClient::acceptCertificate() +void +BobinkClient::acceptCertificate () { - m_certAccepted = true; - if (m_certLoop) - m_certLoop->quit(); + m_certAccepted = true; + if (m_certLoop) + m_certLoop->quit (); } -void BobinkClient::rejectCertificate() +void +BobinkClient::rejectCertificate () { - m_certAccepted = false; - if (m_certLoop) - m_certLoop->quit(); + m_certAccepted = false; + if (m_certLoop) + m_certLoop->quit (); } /* ====================================== * Discovery properties * ====================================== */ -QString BobinkClient::discoveryUrl() const { return m_discoveryUrl; } - -void BobinkClient::setDiscoveryUrl(const QString &url) +QString +BobinkClient::discoveryUrl () const { - if (m_discoveryUrl == url) - return; - m_discoveryUrl = url; - emit discoveryUrlChanged(); + return m_discoveryUrl; } -int BobinkClient::discoveryInterval() const { return m_discoveryInterval; } - -void BobinkClient::setDiscoveryInterval(int ms) +void +BobinkClient::setDiscoveryUrl (const QString &url) { - if (m_discoveryInterval == ms) - return; - m_discoveryInterval = ms; - emit discoveryIntervalChanged(); - - if (m_discoveryTimer.isActive()) - m_discoveryTimer.setInterval(ms); + if (m_discoveryUrl == url) + return; + m_discoveryUrl = url; + emit discoveryUrlChanged (); } -bool BobinkClient::discovering() const { return m_discovering; } +bool +BobinkClient::discovering () const +{ + return m_discovering; +} -const QList<QOpcUaApplicationDescription> &BobinkClient::discoveredServers() const +const QList<QOpcUaApplicationDescription> & +BobinkClient::discoveredServers () const { - return m_discoveredServers; + return m_discoveredServers; } -QVariantList BobinkClient::servers() const +QVariantList +BobinkClient::servers () const { - return m_serversCache; + return m_serversCache; } /* ====================================== * PKI * ====================================== */ -QString BobinkClient::pkiDir() const { return m_pkiDir; } - -void BobinkClient::setPkiDir(const QString &path) +QString +BobinkClient::pkiDir () const { - if (m_pkiDir == path) - return; - m_pkiDir = path; - ensurePkiDirs(m_pkiDir); - emit pkiDirChanged(); + return m_pkiDir; } -QString BobinkClient::certFile() const { return m_certFile; } - -void BobinkClient::setCertFile(const QString &path) +void +BobinkClient::setPkiDir (const QString &path) { - if (m_certFile == path) - return; - m_certFile = path; - emit certFileChanged(); + if (m_pkiDir == path) + return; + m_pkiDir = path; + ensurePkiDirs (m_pkiDir); + emit pkiDirChanged (); } -QString BobinkClient::keyFile() const { return m_keyFile; } - -void BobinkClient::setKeyFile(const QString &path) +QString +BobinkClient::certFile () const { - if (m_keyFile == path) - return; - m_keyFile = path; - emit keyFileChanged(); + return m_certFile; } -void BobinkClient::autoDetectPki() +void +BobinkClient::setCertFile (const QString &path) { - if (m_pkiDir.isEmpty()) - return; - - QDir certDir(m_pkiDir + QStringLiteral("/own/certs")); - QStringList certs = certDir.entryList({QStringLiteral("*.der")}, QDir::Files); - if (!certs.isEmpty()) - setCertFile(certDir.filePath(certs.first())); + if (m_certFile == path) + return; + m_certFile = path; + emit certFileChanged (); +} - QDir keyDir(m_pkiDir + QStringLiteral("/own/private")); - QStringList keys = keyDir.entryList( - {QStringLiteral("*.pem"), QStringLiteral("*.crt")}, QDir::Files); - if (!keys.isEmpty()) - setKeyFile(keyDir.filePath(keys.first())); +QString +BobinkClient::keyFile () const +{ + return m_keyFile; } -void BobinkClient::applyPki() +void +BobinkClient::setKeyFile (const QString &path) { - if (!m_client || m_pkiDir.isEmpty()) - return; + if (m_keyFile == path) + return; + m_keyFile = path; + emit keyFileChanged (); +} - QOpcUaPkiConfiguration pki; - if (!m_certFile.isEmpty()) - pki.setClientCertificateFile(m_certFile); - if (!m_keyFile.isEmpty()) - pki.setPrivateKeyFile(m_keyFile); - pki.setTrustListDirectory(m_pkiDir + QStringLiteral("/trusted/certs")); - pki.setRevocationListDirectory(m_pkiDir + QStringLiteral("/trusted/crl")); - pki.setIssuerListDirectory(m_pkiDir + QStringLiteral("/issuers/certs")); - pki.setIssuerRevocationListDirectory(m_pkiDir + QStringLiteral("/issuers/crl")); +void +BobinkClient::autoDetectPki () +{ + if (m_pkiDir.isEmpty ()) + return; + + QDir certDir (m_pkiDir + QStringLiteral ("/own/certs")); + QStringList certs + = certDir.entryList ({ QStringLiteral ("*.der") }, QDir::Files); + if (!certs.isEmpty ()) + setCertFile (certDir.filePath (certs.first ())); + + QDir keyDir (m_pkiDir + QStringLiteral ("/own/private")); + QStringList keys = keyDir.entryList ( + { QStringLiteral ("*.pem"), QStringLiteral ("*.crt") }, QDir::Files); + if (!keys.isEmpty ()) + setKeyFile (keyDir.filePath (keys.first ())); +} - m_client->setPkiConfiguration(pki); +void +BobinkClient::applyPki () +{ + if (!m_client || m_pkiDir.isEmpty ()) + return; + + if (!m_certFile.isEmpty () && !QFile::exists (m_certFile)) + { + emit statusMessage ( + QStringLiteral ("PKI error: certificate not found: %1") + .arg (m_certFile)); + return; + } + if (!m_keyFile.isEmpty () && !QFile::exists (m_keyFile)) + { + emit statusMessage ( + QStringLiteral ("PKI error: private key not found: %1") + .arg (m_keyFile)); + return; + } + if (!m_certFile.isEmpty () && m_keyFile.isEmpty ()) + { + emit statusMessage ( + QStringLiteral ("PKI error: certificate set but no private key")); + return; + } + if (m_certFile.isEmpty () && !m_keyFile.isEmpty ()) + { + emit statusMessage ( + QStringLiteral ("PKI error: private key set but no certificate")); + return; + } - if (pki.isKeyAndCertificateFileSet()) - m_client->setApplicationIdentity(pki.applicationIdentity()); + QOpcUaPkiConfiguration pki; + if (!m_certFile.isEmpty ()) + pki.setClientCertificateFile (m_certFile); + if (!m_keyFile.isEmpty ()) + pki.setPrivateKeyFile (m_keyFile); + pki.setTrustListDirectory (m_pkiDir + QStringLiteral ("/trusted/certs")); + pki.setRevocationListDirectory (m_pkiDir + QStringLiteral ("/trusted/crl")); + pki.setIssuerListDirectory (m_pkiDir + QStringLiteral ("/issuers/certs")); + pki.setIssuerRevocationListDirectory (m_pkiDir + + QStringLiteral ("/issuers/crl")); + + m_client->setPkiConfiguration (pki); + + if (pki.isKeyAndCertificateFileSet ()) + { + auto identity = pki.applicationIdentity (); + if (!identity.isValid ()) + { + emit statusMessage (QStringLiteral ( + "PKI error: certificate could not be parsed (invalid DER?)")); + return; + } + m_client->setApplicationIdentity (identity); + emit statusMessage (QStringLiteral ("PKI applied: %1") + .arg (m_certFile.split ('/').last ())); + } + else + { + emit statusMessage ( + QStringLiteral ("PKI applied (no client certificate)")); + } } /* ====================================== * Discovery methods * ====================================== */ -void BobinkClient::startDiscovery() +void +BobinkClient::startDiscovery () { - if (m_discoveryUrl.isEmpty() || !m_client) - return; + if (m_discoveryUrl.isEmpty () || !m_client) + return; - doDiscovery(); - m_discoveryTimer.start(m_discoveryInterval); + doDiscovery (); + m_discoveryTimer.start (m_discoveryInterval); - if (!m_discovering) { - m_discovering = true; - emit discoveringChanged(); + if (!m_discovering) + { + m_discovering = true; + emit discoveringChanged (); } } -void BobinkClient::stopDiscovery() +void +BobinkClient::stopDiscovery () { - m_discoveryTimer.stop(); + m_discoveryTimer.stop (); - if (m_discovering) { - m_discovering = false; - emit discoveringChanged(); + if (m_discovering) + { + m_discovering = false; + emit discoveringChanged (); } } -void BobinkClient::doDiscovery() +void +BobinkClient::doDiscovery () { - if (!m_client || m_discoveryUrl.isEmpty()) - return; - QUrl url(m_discoveryUrl); - if (!url.isValid()) - return; - m_client->findServers(url); + if (!m_client || m_discoveryUrl.isEmpty ()) + return; + QUrl url (m_discoveryUrl); + if (!url.isValid ()) + return; + m_client->findServers (url); } /* ====================================== * Private slots * ====================================== */ -void BobinkClient::handleStateChanged(QOpcUaClient::ClientState state) +void +BobinkClient::handleStateChanged (QOpcUaClient::ClientState state) { - bool nowConnected = (state == QOpcUaClient::Connected); - if (m_connected != nowConnected) { - m_connected = nowConnected; - emit connectedChanged(); + bool nowConnected = (state == QOpcUaClient::Connected); + if (m_connected != nowConnected) + { + m_connected = nowConnected; + emit connectedChanged (); } } -void BobinkClient::handleEndpointsReceived( +void +BobinkClient::handleEndpointsReceived ( const QList<QOpcUaEndpointDescription> &endpoints, QOpcUa::UaStatusCode statusCode, const QUrl &) { - if (statusCode != QOpcUa::Good || endpoints.isEmpty()) { - emit connectionError(QStringLiteral("Failed to retrieve endpoints")); - return; + if (statusCode != QOpcUa::Good || endpoints.isEmpty ()) + { + emit connectionError (QStringLiteral ("Failed to retrieve endpoints")); + return; } - QOpcUaEndpointDescription best = endpoints.first(); - for (const auto &ep : endpoints) { - if (ep.securityLevel() > best.securityLevel()) - best = ep; + QOpcUaEndpointDescription best = endpoints.first (); + for (const auto &ep : endpoints) + { + if (ep.securityLevel () > best.securityLevel ()) + best = ep; } - if (m_auth) - m_client->setAuthenticationInformation(m_auth->toAuthenticationInformation()); - - m_client->connectToEndpoint(best); -} - -void BobinkClient::handleConnectError(QOpcUaErrorState *errorState) -{ - if (errorState->connectionStep() == - QOpcUaErrorState::ConnectionStep::CertificateValidation) { - // connectError uses BlockingQueuedConnection — the backend thread is - // blocked waiting for us to return. The errorState pointer is stack- - // allocated in the backend, so it is only valid during this call. - // Spin a local event loop so QML can show a dialog and call - // acceptCertificate() / rejectCertificate() while we stay in scope. - m_certAccepted = false; - emit certificateTrustRequested( - QStringLiteral("The server certificate is not trusted. Accept?")); - - QEventLoop loop; - m_certLoop = &loop; - QTimer::singleShot(30000, &loop, &QEventLoop::quit); - loop.exec(); - m_certLoop = nullptr; - - errorState->setIgnoreError(m_certAccepted); - } else { - emit connectionError( - QStringLiteral("Connection error at step %1, code 0x%2") - .arg(static_cast<int>(errorState->connectionStep())) - .arg(static_cast<uint>(errorState->errorCode()), 8, 16, QLatin1Char('0'))); + if (m_auth) + m_client->setAuthenticationInformation ( + m_auth->toAuthenticationInformation ()); + + m_client->connectToEndpoint (best); +} + +void +BobinkClient::handleConnectError (QOpcUaErrorState *errorState) +{ + if (errorState->connectionStep () + == QOpcUaErrorState::ConnectionStep::CertificateValidation) + { + // connectError uses BlockingQueuedConnection — the backend thread is + // blocked waiting for us to return. The errorState pointer is stack- + // allocated in the backend, so it is only valid during this call. + // Spin a local event loop so QML can show a dialog and call + // acceptCertificate() / rejectCertificate() while we stay in scope. + m_certAccepted = false; + emit certificateTrustRequested ( + QStringLiteral ("The server certificate is not trusted. Accept?")); + + QEventLoop loop; + m_certLoop = &loop; + QTimer::singleShot (30000, &loop, &QEventLoop::quit); + loop.exec (); + m_certLoop = nullptr; + + errorState->setIgnoreError (m_certAccepted); + } + else + { + emit connectionError ( + QStringLiteral ("Connection error at step %1, code 0x%2") + .arg (static_cast<int> (errorState->connectionStep ())) + .arg (static_cast<uint> (errorState->errorCode ()), 8, 16, + QLatin1Char ('0'))); } } -void BobinkClient::handleFindServersFinished( +void +BobinkClient::handleFindServersFinished ( const QList<QOpcUaApplicationDescription> &servers, QOpcUa::UaStatusCode statusCode, const QUrl &) { - if (statusCode != QOpcUa::Good) - return; - - m_discoveredServers = servers; - m_serversCache.clear(); - for (const auto &s : m_discoveredServers) { - QVariantMap entry; - entry[QStringLiteral("serverName")] = s.applicationName().text(); - entry[QStringLiteral("applicationUri")] = s.applicationUri(); - entry[QStringLiteral("discoveryUrls")] = QVariant::fromValue(s.discoveryUrls()); - m_serversCache.append(entry); + if (statusCode != QOpcUa::Good) + return; + + m_discoveredServers = servers; + m_serversCache.clear (); + for (const auto &s : m_discoveredServers) + { + QVariantMap entry; + entry[QStringLiteral ("serverName")] = s.applicationName ().text (); + entry[QStringLiteral ("applicationUri")] = s.applicationUri (); + entry[QStringLiteral ("discoveryUrls")] + = QVariant::fromValue (s.discoveryUrls ()); + m_serversCache.append (entry); } - emit serversChanged(); + emit serversChanged (); } |
