diff options
-rw-r--r-- | src/opcua/client/qopcuatype.cpp | 53 | ||||
-rw-r--r-- | src/opcua/client/qopcuatype.h | 1 | ||||
-rw-r--r-- | src/plugins/opcua/uacpp/quacpputils.cpp | 50 | ||||
-rw-r--r-- | tests/auto/qopcuaclient/tst_client.cpp | 71 |
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); |