diff options
| author | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-20 10:41:09 +0100 |
|---|---|---|
| committer | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-20 10:41:09 +0100 |
| commit | 0012cb312e92c33f5263478d318eb82da22ee879 (patch) | |
| tree | caac374dd3716b42d13cb85b85a7f90c7d5aac45 /src/BobinkClient.cpp | |
| parent | 11b99fda8727f2225961c0b83ecdb18674a9670a (diff) | |
| download | BobinkQtOpcUa-0012cb312e92c33f5263478d318eb82da22ee879.tar.gz BobinkQtOpcUa-0012cb312e92c33f5263478d318eb82da22ee879.zip | |
Rename classes to OpcUa* prefix, replace BobinkNode with OpcUaMonitoredNode boilerplate
Rename BobinkAuth → OpcUaAuth, BobinkClient → OpcUaClient (C++ class
names only; QML module URI and singleton name stay as Bobink).
Remove BobinkNode (QQuickItem-based) and add OpcUaMonitoredNode
skeleton using QObject + QQmlParserStatus, following Qt convention
for non-visual QML types.
Diffstat (limited to 'src/BobinkClient.cpp')
| -rw-r--r-- | src/BobinkClient.cpp | 569 |
1 files changed, 0 insertions, 569 deletions
diff --git a/src/BobinkClient.cpp b/src/BobinkClient.cpp deleted file mode 100644 index 41b7dbf..0000000 --- a/src/BobinkClient.cpp +++ /dev/null @@ -1,569 +0,0 @@ -/** - * @file BobinkClient.cpp - * @brief BobinkClient implementation. - */ -#include "BobinkClient.h" -#include "BobinkAuth.h" - -#include <QDir> -#include <QMetaEnum> -#include <QOpcUaPkiConfiguration> -#include <QOpcUaUserTokenPolicy> -#include <QStandardPaths> - -using namespace Qt::Literals::StringLiterals; - -static constexpr qint32 DISCOVERY_INTERVAL = 30000; // ms - -static QString -defaultPkiDir () -{ - return QStandardPaths::writableLocation (QStandardPaths::AppDataLocation) - + QStringLiteral ("/pki"); -} - -/** @brief Create the standard OPC UA PKI directory tree. */ -static void -ensurePkiDirs (const QString &base) -{ - for (const auto sub : - { u"own/certs"_s, u"own/private"_s, u"trusted/certs"_s, - u"trusted/crl"_s, u"issuers/certs"_s, u"issuers/crl"_s }) - { - QDir ().mkpath (base + QLatin1Char ('/') + sub); - } -} - -static QString -securityPolicyUri (BobinkClient::SecurityPolicy policy) -{ - switch (policy) - { - case BobinkClient::Basic256Sha256: - return QStringLiteral ( - "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"); - case BobinkClient::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 {}; -} - -/* ====================================== - * Construction - * ====================================== */ - -BobinkClient::BobinkClient (QObject *parent) - : QObject (parent), m_provider (new QOpcUaProvider (this)), - m_pkiDir (defaultPkiDir ()) -{ - Q_ASSERT (!s_instance); - ensurePkiDirs (m_pkiDir); - setupClient (); - autoDetectPki (); - applyPki (); - connect (&m_discoveryTimer, &QTimer::timeout, this, - &BobinkClient::doDiscovery); - s_instance = this; -} - -void -BobinkClient::setupClient () -{ - 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::errorChanged, this, - &BobinkClient::handleClientError); -} - -/* ====================================== - * Connection - * ====================================== */ - -BobinkClient * -BobinkClient::instance () -{ - return s_instance; -} - -bool -BobinkClient::connected () const -{ - return m_connected; -} - -QString -BobinkClient::serverUrl () const -{ - return m_serverUrl; -} - -void -BobinkClient::setServerUrl (const QString &url) -{ - if (m_serverUrl == url) - return; - m_serverUrl = url; - emit serverUrlChanged (); -} - -BobinkAuth * -BobinkClient::auth () const -{ - return m_auth; -} - -void -BobinkClient::setAuth (BobinkAuth *auth) -{ - if (m_auth == auth) - return; - m_auth = auth; - emit authChanged (); -} - -QOpcUaClient * -BobinkClient::opcuaClient () const -{ - return m_client; -} - -void -BobinkClient::connectToServer () -{ - 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_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; - } - m_client->requestEndpoints (url); -} - -void -BobinkClient::connectDirect (SecurityPolicy policy, SecurityMode mode) -{ - 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_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 ()) - { - case BobinkAuth::Anonymous: - tokenPolicy.setTokenType ( - QOpcUaUserTokenPolicy::TokenType::Anonymous); - break; - case BobinkAuth::UserPass: - tokenPolicy.setTokenType ( - QOpcUaUserTokenPolicy::TokenType::Username); - break; - case BobinkAuth::Certificate: - tokenPolicy.setTokenType ( - QOpcUaUserTokenPolicy::TokenType::Certificate); - break; - } - m_client->setAuthenticationInformation ( - m_auth->toAuthenticationInformation ()); - } - else - { - tokenPolicy.setTokenType (QOpcUaUserTokenPolicy::TokenType::Anonymous); - } - endpoint.setUserIdentityTokens ({ tokenPolicy }); - - m_client->connectToEndpoint (endpoint); -} - -void -BobinkClient::disconnectFromServer () -{ - if (m_client) - m_client->disconnectFromEndpoint (); -} - -void -BobinkClient::acceptCertificate () -{ - m_certAccepted = true; - if (m_certLoop) - m_certLoop->quit (); -} - -void -BobinkClient::rejectCertificate () -{ - m_certAccepted = false; - if (m_certLoop) - m_certLoop->quit (); -} - -void -BobinkClient::handleStateChanged (QOpcUaClient::ClientState state) -{ - bool nowConnected = (state == QOpcUaClient::Connected); - if (m_connected != nowConnected) - { - m_connected = nowConnected; - emit connectedChanged (); - } -} - -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; - } - - // Pick the endpoint with the highest securityLevel (server-assigned score). - 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<quint32> (errorState->errorCode ()), 8, 16, - QLatin1Char ('0'))); - } -} - -void -BobinkClient::handleClientError (QOpcUaClient::ClientError error) -{ - if (error == QOpcUaClient::NoError) - return; - auto me = QMetaEnum::fromType<QOpcUaClient::ClientError> (); - const char *key = me.valueToKey (static_cast<int> (error)); - emit connectionError ( - QStringLiteral ("Client error: %1") - .arg (key ? QLatin1StringView (key) : "Unknown"_L1)); -} - -/* ====================================== - * Discovery - * ====================================== */ - -QString -BobinkClient::discoveryUrl () const -{ - return m_discoveryUrl; -} - -void -BobinkClient::setDiscoveryUrl (const QString &url) -{ - if (m_discoveryUrl == url) - return; - m_discoveryUrl = url; - emit discoveryUrlChanged (); -} - -bool -BobinkClient::discovering () const -{ - return m_discovering; -} - -const QList<QOpcUaApplicationDescription> & -BobinkClient::discoveredServers () const -{ - return m_discoveredServers; -} - -QVariantList -BobinkClient::servers () const -{ - return m_serversCache; -} - -void -BobinkClient::startDiscovery () -{ - if (m_discoveryUrl.isEmpty () || !m_client) - return; - - doDiscovery (); - m_discoveryTimer.start (DISCOVERY_INTERVAL); - - if (!m_discovering) - { - m_discovering = true; - emit discoveringChanged (); - } -} - -void -BobinkClient::stopDiscovery () -{ - m_discoveryTimer.stop (); - - if (m_discovering) - { - m_discovering = false; - emit discoveringChanged (); - } -} - -void -BobinkClient::doDiscovery () -{ - if (!m_client || m_discoveryUrl.isEmpty ()) - return; - QUrl url (m_discoveryUrl); - if (!url.isValid ()) - return; - m_client->findServers (url); -} - -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); - } - emit serversChanged (); -} - -/* ====================================== - * PKI - * ====================================== */ - -QString -BobinkClient::pkiDir () const -{ - return m_pkiDir; -} - -void -BobinkClient::setPkiDir (const QString &path) -{ - if (m_pkiDir == path) - return; - m_pkiDir = path; - ensurePkiDirs (m_pkiDir); - emit pkiDirChanged (); -} - -QString -BobinkClient::certFile () const -{ - return m_certFile; -} - -void -BobinkClient::setCertFile (const QString &path) -{ - if (m_certFile == path) - return; - m_certFile = path; - emit certFileChanged (); -} - -QString -BobinkClient::keyFile () const -{ - return m_keyFile; -} - -void -BobinkClient::setKeyFile (const QString &path) -{ - if (m_keyFile == path) - return; - m_keyFile = path; - emit keyFileChanged (); -} - -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 ())); -} - -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; - } - - 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)")); - } -} |
