summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/BobinkClient.cpp325
-rw-r--r--src/BobinkClient.h98
-rw-r--r--src/BobinkNode.cpp34
3 files changed, 232 insertions, 225 deletions
diff --git a/src/BobinkClient.cpp b/src/BobinkClient.cpp
index 01f9912..6d1c608 100644
--- a/src/BobinkClient.cpp
+++ b/src/BobinkClient.cpp
@@ -30,12 +30,33 @@ ensurePkiDirs (const QString &base)
}
}
+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 ())
{
- // Singleton pattern: construct only once
- Q_ASSERT(!s_instance);
+ Q_ASSERT (!s_instance);
ensurePkiDirs (m_pkiDir);
setupClient ();
autoDetectPki ();
@@ -87,9 +108,15 @@ BobinkClient::setupClient ()
}
/* ======================================
- * Connection properties
+ * Connection
* ====================================== */
+BobinkClient *
+BobinkClient::instance ()
+{
+ return s_instance;
+}
+
bool
BobinkClient::connected () const
{
@@ -132,10 +159,6 @@ BobinkClient::opcuaClient () const
return m_client;
}
-/* ======================================
- * Connection methods
- * ====================================== */
-
void
BobinkClient::connectToServer ()
{
@@ -166,24 +189,6 @@ BobinkClient::connectToServer ()
m_client->requestEndpoints (url);
}
-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 {};
-}
-
void
BobinkClient::connectDirect (SecurityPolicy policy, SecurityMode mode)
{
@@ -263,8 +268,77 @@ BobinkClient::rejectCertificate ()
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;
+ }
+
+ 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')));
+ }
+}
+
/* ======================================
- * Discovery properties
+ * Discovery
* ====================================== */
QString
@@ -300,6 +374,67 @@ BobinkClient::servers () const
return m_serversCache;
}
+void
+BobinkClient::startDiscovery ()
+{
+ if (m_discoveryUrl.isEmpty () || !m_client)
+ return;
+
+ doDiscovery ();
+ m_discoveryTimer.start (m_discoveryInterval);
+
+ 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
* ====================================== */
@@ -434,141 +569,3 @@ BobinkClient::applyPki ()
QStringLiteral ("PKI applied (no client certificate)"));
}
}
-
-/* ======================================
- * Discovery methods
- * ====================================== */
-
-void
-BobinkClient::startDiscovery ()
-{
- if (m_discoveryUrl.isEmpty () || !m_client)
- return;
-
- doDiscovery ();
- m_discoveryTimer.start (m_discoveryInterval);
-
- 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);
-}
-
-/* ======================================
- * Private slots
- * ====================================== */
-
-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;
- }
-
- 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::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 ();
-}
diff --git a/src/BobinkClient.h b/src/BobinkClient.h
index 3f36e75..2b41e05 100644
--- a/src/BobinkClient.h
+++ b/src/BobinkClient.h
@@ -28,16 +28,19 @@ class BobinkClient : public QObject
QML_SINGLETON
QML_NAMED_ELEMENT (Bobink)
+ /* -- Connection -- */
Q_PROPERTY (bool connected READ connected NOTIFY connectedChanged)
Q_PROPERTY (QString serverUrl READ serverUrl WRITE setServerUrl NOTIFY
serverUrlChanged)
Q_PROPERTY (BobinkAuth *auth READ auth WRITE setAuth NOTIFY authChanged)
+ /* -- Discovery -- */
Q_PROPERTY (QString discoveryUrl READ discoveryUrl WRITE setDiscoveryUrl
NOTIFY discoveryUrlChanged)
Q_PROPERTY (bool discovering READ discovering NOTIFY discoveringChanged)
Q_PROPERTY (QVariantList servers READ servers NOTIFY serversChanged)
+ /* -- PKI -- */
Q_PROPERTY (QString pkiDir READ pkiDir WRITE setPkiDir NOTIFY pkiDirChanged)
Q_PROPERTY (
QString certFile READ certFile WRITE setCertFile NOTIFY certFileChanged)
@@ -45,46 +48,6 @@ class BobinkClient : public QObject
QString keyFile READ keyFile WRITE setKeyFile NOTIFY keyFileChanged)
public:
- BobinkClient (QObject *parent = nullptr);
- // ~BobinkClient () override;
-
- static inline BobinkClient *s_instance = nullptr;
-
- // static BobinkClient *instance ();
-
- /**
- * @brief QML singleton factory.
- * @note CppOwnership — lives for the process lifetime.
- */
- // static BobinkClient *create (QQmlEngine *qmlEngine, QJSEngine *jsEngine);
-
- bool connected () const;
-
- QString serverUrl () const;
- void setServerUrl (const QString &url);
-
- BobinkAuth *auth () const;
- void setAuth (BobinkAuth *auth);
-
- QOpcUaClient *opcuaClient () const;
-
- QString discoveryUrl () const;
- void setDiscoveryUrl (const QString &url);
-
- bool discovering () const;
-
- const QList<QOpcUaApplicationDescription> &discoveredServers () const;
- QVariantList servers () const;
-
- QString pkiDir () const;
- void setPkiDir (const QString &path);
-
- QString certFile () const;
- void setCertFile (const QString &path);
-
- QString keyFile () const;
- void setKeyFile (const QString &path);
-
enum SecurityMode
{
SignAndEncrypt = 3,
@@ -101,6 +64,18 @@ public:
};
Q_ENUM (SecurityPolicy)
+ BobinkClient (QObject *parent = nullptr);
+
+ /* -- Connection -- */
+
+ bool connected () const;
+
+ QString serverUrl () const;
+ void setServerUrl (const QString &url);
+
+ BobinkAuth *auth () const;
+ void setAuth (BobinkAuth *auth);
+
/** @brief Discover endpoints, pick the most secure, connect. */
Q_INVOKABLE void connectToServer ();
/** @brief Connect directly without endpoint discovery. */
@@ -112,19 +87,45 @@ public:
/** @brief Reject the pending server certificate. */
Q_INVOKABLE void rejectCertificate ();
+ /* -- Discovery -- */
+
+ QString discoveryUrl () const;
+ void setDiscoveryUrl (const QString &url);
+
+ bool discovering () const;
+
+ const QList<QOpcUaApplicationDescription> &discoveredServers () const;
+ QVariantList servers () const;
+
Q_INVOKABLE void startDiscovery ();
Q_INVOKABLE void stopDiscovery ();
+ /* -- PKI -- */
+
+ QString pkiDir () const;
+ void setPkiDir (const QString &path);
+
+ QString certFile () const;
+ void setCertFile (const QString &path);
+
+ QString keyFile () const;
+ void setKeyFile (const QString &path);
+
/** @brief Auto-detect cert/key from the PKI directory and apply. */
Q_INVOKABLE void autoDetectPki ();
/** @brief Apply PKI dirs and cert/key. Call before connecting. */
Q_INVOKABLE void applyPki ();
+ /* -- C++ only -- */
+
+ static BobinkClient *instance ();
+ QOpcUaClient *opcuaClient () const;
+
signals:
+ /* -- Connection -- */
void connectedChanged ();
void serverUrlChanged ();
void authChanged ();
-
/**
* @brief Emitted when the server presents an untrusted cert.
*
@@ -135,20 +136,26 @@ signals:
void connectionError (const QString &message);
void statusMessage (const QString &message);
+ /* -- Discovery -- */
void discoveryUrlChanged ();
void discoveringChanged ();
void serversChanged ();
+
+ /* -- PKI -- */
void pkiDirChanged ();
void certFileChanged ();
void keyFileChanged ();
private slots:
+ /* -- Connection -- */
void handleStateChanged (QOpcUaClient::ClientState state);
void
handleEndpointsReceived (const QList<QOpcUaEndpointDescription> &endpoints,
QOpcUa::UaStatusCode statusCode,
const QUrl &requestUrl);
void handleConnectError (QOpcUaErrorState *errorState);
+
+ /* -- Discovery -- */
void handleFindServersFinished (
const QList<QOpcUaApplicationDescription> &servers,
QOpcUa::UaStatusCode statusCode, const QUrl &requestUrl);
@@ -157,26 +164,29 @@ private slots:
private:
void setupClient ();
+ /* -- Connection -- */
QOpcUaProvider *m_provider = nullptr;
QOpcUaClient *m_client = nullptr;
BobinkAuth *m_auth = nullptr;
QString m_serverUrl;
bool m_connected = false;
-
- // Certificate trust event loop — see handleConnectError().
QEventLoop *m_certLoop = nullptr;
bool m_certAccepted = false;
+ /* -- Discovery -- */
QString m_discoveryUrl;
qint32 m_discoveryInterval = 30000; // ms
QTimer m_discoveryTimer;
bool m_discovering = false;
QList<QOpcUaApplicationDescription> m_discoveredServers;
+ QVariantList m_serversCache;
+ /* -- PKI -- */
QString m_pkiDir;
QString m_certFile;
QString m_keyFile;
- QVariantList m_serversCache;
+
+ static inline BobinkClient *s_instance = nullptr;
};
#endif // BOBINKCLIENT_H
diff --git a/src/BobinkNode.cpp b/src/BobinkNode.cpp
index fed87bc..55e3b75 100644
--- a/src/BobinkNode.cpp
+++ b/src/BobinkNode.cpp
@@ -104,7 +104,7 @@ BobinkNode::componentComplete ()
QQuickItem::componentComplete ();
m_componentComplete = true;
- auto *client = BobinkClient::s_instance;
+ auto *client = BobinkClient::instance ();
if (client)
connect (client, &BobinkClient::connectedChanged, this,
&BobinkNode::handleClientConnectedChanged);
@@ -137,7 +137,7 @@ BobinkNode::startMonitoring ()
if (m_opcuaNode || m_nodeId.isEmpty ())
return;
- auto *client = BobinkClient::s_instance;
+ auto *client = BobinkClient::instance ();
if (!client || !client->connected ())
return;
@@ -245,10 +245,10 @@ BobinkNode::handleAttributeWritten (QOpcUa::NodeAttribute attr,
void
BobinkNode::handleClientConnectedChanged ()
{
- if (!BobinkClient::s_instance)
+ if (!BobinkClient::instance ())
return;
- if (BobinkClient::s_instance->connected ())
+ if (BobinkClient::instance ()->connected ())
{
if (m_componentComplete && isVisible ())
startMonitoring ();
@@ -262,7 +262,7 @@ BobinkNode::handleClientConnectedChanged ()
void
BobinkNode::handleAttributeReadFinished (QOpcUa::NodeAttributes attrs)
{
- if (!BobinkClient::s_instance || !m_opcuaNode)
+ if (!BobinkClient::instance () || !m_opcuaNode)
return;
for (int bit = 0; bit < 27; ++bit)
@@ -274,12 +274,12 @@ BobinkNode::handleAttributeReadFinished (QOpcUa::NodeAttributes attrs)
auto sc = m_opcuaNode->attributeError (attr);
QLatin1StringView name = nameFromAttribute (attr);
if (sc == QOpcUa::UaStatusCode::Good)
- emit BobinkClient::s_instance->statusMessage (
+ emit BobinkClient::instance () -> statusMessage (
QStringLiteral ("Read %1.%2 = %3")
.arg (m_nodeId, name,
m_opcuaNode->attribute (attr).toString ()));
else
- emit BobinkClient::s_instance->statusMessage (
+ emit BobinkClient::instance () -> statusMessage (
QStringLiteral ("Read %1.%2 failed: 0x%3")
.arg (m_nodeId, name)
.arg (static_cast<quint32> (sc), 8, 16, QLatin1Char ('0')));
@@ -290,32 +290,32 @@ void
BobinkNode::handleEnableMonitoringFinished (QOpcUa::NodeAttribute,
QOpcUa::UaStatusCode statusCode)
{
- if (!BobinkClient::s_instance)
+ if (!BobinkClient::instance ())
return;
if (statusCode == QOpcUa::Good)
- emit BobinkClient::s_instance->statusMessage (
+ emit BobinkClient::instance () -> statusMessage (
QStringLiteral ("Monitoring started: %1").arg (m_nodeId));
else
- emit BobinkClient::s_instance->statusMessage (
- QStringLiteral ("Monitoring failed for %1: 0x%2")
- .arg (m_nodeId)
- .arg (static_cast<quint32> (statusCode), 8, 16,
- QLatin1Char ('0')));
+ emit BobinkClient::instance ()
+ -> statusMessage (QStringLiteral ("Monitoring failed for %1: 0x%2")
+ .arg (m_nodeId)
+ .arg (static_cast<quint32> (statusCode), 8, 16,
+ QLatin1Char ('0')));
}
void
BobinkNode::handleDisableMonitoringFinished (QOpcUa::NodeAttribute,
QOpcUa::UaStatusCode statusCode)
{
- if (!BobinkClient::s_instance)
+ if (!BobinkClient::instance ())
return;
if (statusCode == QOpcUa::Good)
- emit BobinkClient::s_instance->statusMessage (
+ emit BobinkClient::instance () -> statusMessage (
QStringLiteral ("Monitoring stopped: %1").arg (m_nodeId));
else
- emit BobinkClient::s_instance->statusMessage (
+ emit BobinkClient::instance () -> statusMessage (
QStringLiteral ("Stop monitoring failed for %1: 0x%2")
.arg (m_nodeId)
.arg (static_cast<quint32> (statusCode), 8, 16,