summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/opcua/client/qopcuatype.cpp53
-rw-r--r--src/opcua/client/qopcuatype.h1
-rw-r--r--src/plugins/opcua/uacpp/quacpputils.cpp50
-rw-r--r--tests/auto/qopcuaclient/tst_client.cpp71
4 files changed, 115 insertions, 60 deletions
diff --git a/src/opcua/client/qopcuatype.cpp b/src/opcua/client/qopcuatype.cpp
index dc838a4..30d1841 100644
--- a/src/opcua/client/qopcuatype.cpp
+++ b/src/opcua/client/qopcuatype.cpp
@@ -37,6 +37,7 @@
#include "qopcuatype.h"
#include <QMetaEnum>
+#include <QRegularExpression>
#include <QUuid>
QT_BEGIN_NAMESPACE
@@ -812,42 +813,56 @@ QString QOpcUa::nodeIdFromReferenceType(QOpcUa::ReferenceTypeId referenceType)
Returns \c true if the node id could be split successfully.
For example, "ns=1;s=MyString" is split into 1, 's' and "MyString".
+ If no namespace index is given, ns=0 is assumed.
*/
bool QOpcUa::nodeIdStringSplit(const QString &nodeIdString, quint16 *nsIndex, QString *identifier, char *identifierType)
{
- QStringList components = nodeIdString.split(QLatin1String(";"));
- QStringList result;
+ quint16 namespaceIndex = 0;
- if (components.size() != 2)
- return false;
+ QStringList components = nodeIdString.split(QLatin1String(";"));
- if (components.at(0).contains(QRegExp(QLatin1String("/^ns=[0-9]+$/"))))
+ if (components.size() > 2)
return false;
- bool success = false;
- uint ns = components.at(0).midRef(3).toString().toUInt(&success);
- if (!success || ns > std::numeric_limits<quint16>::max())
- return false;
+ if (components.size() == 2 && components.at(0).contains(QRegularExpression(QLatin1String("^ns=[0-9]+")))) {
+ bool success = false;
+ uint ns = components.at(0).midRef(3).toString().toUInt(&success);
+ if (!success || ns > std::numeric_limits<quint16>::max())
+ return false;
+ namespaceIndex = ns;
+ }
- if (components.at(1).size() < 3)
+ if (components.last().size() < 3)
return false;
- if (!components.at(1).contains(QRegExp(QLatin1String("^[isgb]="))))
+ if (!components.last().contains(QRegularExpression(QLatin1String("^[isgb]="))))
return false;
if (nsIndex)
- *nsIndex = ns;
+ *nsIndex = namespaceIndex;
if (identifier)
- *identifier = components.at(1).midRef(2).toString();
+ *identifier = components.last().midRef(2).toString();
if (identifierType)
- *identifierType = components.at(1).at(0).toLatin1();
-
- result.append(components.at(1).midRef(2).toString());
+ *identifierType = components.last().at(0).toLatin1();
return true;
}
/*!
+ Returns \c true if the two node ids have the same namespace index and identifier.
+ A node id string without a namespace index is assumed to be in namespace 0.
+*/
+bool QOpcUa::nodeIdEquals(const QString &first, const QString &second)
+{
+ if (first.startsWith(QLatin1String("ns=0;")) && !second.startsWith(QLatin1String("ns=")))
+ return first.midRef(5) == second;
+ else if (second.startsWith(QLatin1String("ns=0;")) && !first.startsWith(QLatin1String("ns=")))
+ return second.midRef(5) == first;
+ else
+ return first == second;
+}
+
+/*!
Returns a node id string for the namespace 0 identifier \a id.
*/
QString QOpcUa::ns0ID(QOpcUa::NodeIds::NS0 id)
@@ -1716,7 +1731,7 @@ QOpcUa::QExpandedNodeId &QOpcUa::QExpandedNodeId::operator=(const QOpcUa::QExpan
bool QOpcUa::QExpandedNodeId::operator==(const QOpcUa::QExpandedNodeId &rhs) const
{
return data->namespaceUri == rhs.namespaceUri() &&
- data->nodeId == rhs.nodeId() &&
+ nodeIdEquals(data->nodeId, rhs.nodeId()) &&
data->serverIndex == rhs.serverIndex();
}
@@ -3495,7 +3510,7 @@ QOpcUa::QArgument &QOpcUa::QArgument::operator=(const QOpcUa::QArgument &rhs)
bool QOpcUa::QArgument::operator==(const QOpcUa::QArgument &other) const
{
return data->arrayDimensions == other.arrayDimensions() &&
- data->dataTypeId == other.dataTypeId() &&
+ nodeIdEquals(data->dataTypeId, other.dataTypeId()) &&
data->description == other.description() &&
data->name == other.name() &&
data->valueRank == other.valueRank();
@@ -3688,7 +3703,7 @@ QOpcUa::QExtensionObject &QOpcUa::QExtensionObject::operator=(const QOpcUa::QExt
bool QOpcUa::QExtensionObject::operator==(const QOpcUa::QExtensionObject &rhs) const
{
return data->encoding == rhs.encoding() &&
- data->encodingTypeId == rhs.encodingTypeId() &&
+ nodeIdEquals(data->encodingTypeId, rhs.encodingTypeId()) &&
data->encodedBody == rhs.encodedBody();
}
diff --git a/src/opcua/client/qopcuatype.h b/src/opcua/client/qopcuatype.h
index 3b372c2..bee0954 100644
--- a/src/opcua/client/qopcuatype.h
+++ b/src/opcua/client/qopcuatype.h
@@ -427,6 +427,7 @@ Q_OPCUA_EXPORT QString nodeIdFromInteger(quint16 ns, quint32 identifier);
Q_OPCUA_EXPORT QString nodeIdFromReferenceType(QOpcUa::ReferenceTypeId referenceType);
Q_OPCUA_EXPORT bool nodeIdStringSplit(const QString &nodeIdString, quint16 *nsIndex,
QString *identifier, char *identifierType);
+Q_OPCUA_EXPORT bool nodeIdEquals(const QString &first, const QString &second);
Q_OPCUA_EXPORT QString ns0ID(QOpcUa::NodeIds::NS0 id);
Q_OPCUA_EXPORT QOpcUa::NodeIds::NS0 ns0IDFromNodeId(const QString &nodeId);
Q_OPCUA_EXPORT QString ns0IDName(QOpcUa::NodeIds::NS0 id);
diff --git a/src/plugins/opcua/uacpp/quacpputils.cpp b/src/plugins/opcua/uacpp/quacpputils.cpp
index 940016d..4d5853e 100644
--- a/src/plugins/opcua/uacpp/quacpputils.cpp
+++ b/src/plugins/opcua/uacpp/quacpputils.cpp
@@ -21,6 +21,8 @@
#include "quacpputils.h"
+#include <QtOpcUa/qopcuatype.h>
+
#include <QtCore/QLoggingCategory>
#include <QtCore/QString>
#include <QtCore/QUuid>
@@ -36,50 +38,16 @@ namespace UACppUtils {
UaNodeId nodeIdFromQString(const QString &name)
{
- const int semicolonIndex = name.indexOf(';');
+ quint16 index = 0;
+ char identifierType = 0;
+ QString identifierString;
- if (semicolonIndex <= 0) {
+ bool success = QOpcUa::nodeIdStringSplit(name, &index, &identifierString, &identifierType);
+ if (!success) {
qCWarning(QT_OPCUA_PLUGINS_UACPP, "Unable to split node id string: %s", qUtf8Printable(name));
return UaNodeId();
}
- QStringRef namespaceString = name.leftRef(semicolonIndex);
- if (namespaceString.length() <= 3 || !namespaceString.startsWith(QLatin1String("ns="))) {
- qCWarning(QT_OPCUA_PLUGINS_UACPP, "Not a valid index string in node id string: %s", qUtf8Printable(name));
- return UaNodeId();
- }
- namespaceString = namespaceString.mid(3); // Remove "ns="
-
- QStringRef identifierString = name.midRef(semicolonIndex + 1);
-
- if (identifierString.length() <= 2) {
- qCWarning(QT_OPCUA_PLUGINS_UACPP, "There is no identifier in node id string: %s", qUtf8Printable(name));
- return UaNodeId();
- }
-
- char identifierType;
- if (identifierString.startsWith(QLatin1String("s=")))
- identifierType = 's';
- else if (identifierString.startsWith(QLatin1String("i=")))
- identifierType = 'i';
- else if (identifierString.startsWith(QLatin1String("g=")))
- identifierType = 'g';
- else if (identifierString.startsWith(QLatin1String("b=")))
- identifierType = 'b';
- else {
- qCWarning(QT_OPCUA_PLUGINS_UACPP, "There is no valid identifier type in node id string: %s", qUtf8Printable(name));
- return UaNodeId();
- }
- identifierString = identifierString.mid(2); // Remove identifier type
-
- bool ok = false;
- OpcUa_UInt16 index = static_cast<OpcUa_UInt16>(namespaceString.toUInt(&ok));
-
- if (!ok) {
- qCWarning(QT_OPCUA_PLUGINS_UACPP, "Not a valid namespace index in node id string: %s", qUtf8Printable(name));
- return UaNodeId();
- }
-
switch (identifierType) {
case 'i': {
bool isNumber;
@@ -98,7 +66,7 @@ UaNodeId nodeIdFromQString(const QString &name)
break;
}
case 'g': {
- QUuid uuid(identifierString.toString());
+ QUuid uuid(identifierString);
if (uuid.isNull()) {
qCWarning(QT_OPCUA_PLUGINS_UACPP, "%s does not contain a valid guid identifier", qUtf8Printable(name));
@@ -112,7 +80,7 @@ UaNodeId nodeIdFromQString(const QString &name)
return UaNodeId(guid, index);
}
case 'b': {
- QByteArray temp = QByteArray::fromBase64(identifierString.toLocal8Bit());
+ QByteArray temp = QByteArray::fromBase64(identifierString.toLatin1());
UaByteString bstr((OpcUa_Int32)temp.size(), reinterpret_cast<OpcUa_Byte *>(temp.data()));
if (temp.size() > 0) {
return UaNodeId(bstr, index);
diff --git a/tests/auto/qopcuaclient/tst_client.cpp b/tests/auto/qopcuaclient/tst_client.cpp
index 9ac1add..76409f7 100644
--- a/tests/auto/qopcuaclient/tst_client.cpp
+++ b/tests/auto/qopcuaclient/tst_client.cpp
@@ -346,6 +346,10 @@ private slots:
defineDataMethod(requestEndpoints_data)
void requestEndpoints();
+ defineDataMethod(compareNodeIds_data)
+ void compareNodeIds();
+ defineDataMethod(readNS0OmitNode_data)
+ void readNS0OmitNode();
defineDataMethod(readInvalidNode_data)
void readInvalidNode();
defineDataMethod(writeInvalidNode_data)
@@ -697,6 +701,73 @@ void Tst_QOpcUaClient::requestEndpoints()
QCOMPARE(desc[0].serverRef().productUri(), QStringLiteral("http://open62541.org"));
}
+void Tst_QOpcUaClient::compareNodeIds()
+{
+ const QString numericId = QStringLiteral("i=42");
+ const QString stringId = QStringLiteral ("s=TestString");
+ const QString guidId = QStringLiteral("g=72962b91-fa75-4ae6-8d28-b404dc7daf63");
+ const QString opaqueId = QStringLiteral("b=UXQgZnR3IQ==");
+
+ const QString prefix = QStringLiteral("ns=0;");
+
+ QVERIFY(QOpcUa::nodeIdEquals(numericId, prefix + numericId));
+ QVERIFY(QOpcUa::nodeIdEquals(stringId, prefix + stringId));
+ QVERIFY(QOpcUa::nodeIdEquals(guidId, prefix + guidId));
+ QVERIFY(QOpcUa::nodeIdEquals(opaqueId, prefix + opaqueId));
+
+ {
+ quint16 namespaceIndex = 1;
+ char identifierType = 0;
+ QString identifier;
+ QVERIFY(QOpcUa::nodeIdStringSplit(numericId, &namespaceIndex, &identifier, &identifierType));
+ QCOMPARE(namespaceIndex, 0);
+ QCOMPARE(identifierType, 'i');
+ QCOMPARE(identifier, QStringLiteral("42"));
+ }
+ {
+ quint16 namespaceIndex = 1;
+ char identifierType = 0;
+ QString identifier;
+ QVERIFY(QOpcUa::nodeIdStringSplit(stringId, &namespaceIndex, &identifier, &identifierType));
+ QCOMPARE(namespaceIndex, 0);
+ QCOMPARE(identifierType, 's');
+ QCOMPARE(identifier, QStringLiteral("TestString"));
+ }
+ {
+ quint16 namespaceIndex = 1;
+ char identifierType = 0;
+ QString identifier;
+ QVERIFY(QOpcUa::nodeIdStringSplit(guidId, &namespaceIndex, &identifier, &identifierType));
+ QCOMPARE(namespaceIndex, 0);
+ QCOMPARE(identifierType, 'g');
+ QCOMPARE(identifier, QStringLiteral("72962b91-fa75-4ae6-8d28-b404dc7daf63"));
+ }
+ {
+ quint16 namespaceIndex = 1;
+ char identifierType = 0;
+ QString identifier;
+ QVERIFY(QOpcUa::nodeIdStringSplit(opaqueId, &namespaceIndex, &identifier, &identifierType));
+ QCOMPARE(namespaceIndex, 0);
+ QCOMPARE(identifierType, 'b');
+ QCOMPARE(identifier, QStringLiteral("UXQgZnR3IQ=="));
+ }
+}
+
+void Tst_QOpcUaClient::readNS0OmitNode()
+{
+ QFETCH(QOpcUaClient*, opcuaClient);
+
+ OpcuaConnector connector(opcuaClient, m_endpoint);
+
+ QScopedPointer<QOpcUaNode> node(opcuaClient->node("i=84")); // Root node
+ QVERIFY(node != nullptr);
+
+ READ_MANDATORY_BASE_NODE(node);
+
+ QCOMPARE(node->attribute(QOpcUa::NodeAttribute::BrowseName).value<QOpcUa::QQualifiedName>(),
+ QOpcUa::QQualifiedName(0, QStringLiteral("Root")));
+}
+
void Tst_QOpcUaClient::readInvalidNode()
{
QFETCH(QOpcUaClient*, opcuaClient);