summaryrefslogtreecommitdiffstats
path: root/src/BobinkClient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/BobinkClient.cpp')
-rw-r--r--src/BobinkClient.cpp569
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)"));
- }
-}