/**************************************************************************** ** ** Copyright (C) 2018 basysKom GmbH, opensource@basyskom.com ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtOpcUa module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL3$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPLv3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or later as published by the Free ** Software Foundation and appearing in the file LICENSE.GPL included in ** the packaging of this file. Please review the following information to ** ensure the GNU General Public License version 2.0 requirements will be ** met: http://www.gnu.org/licenses/gpl-2.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef QOPCUABINARYDATAENCODING_H #define QOPCUABINARYDATAENCODING_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE // This class implements a subset of the OPC UA Binary DataEncoding defined in OPC-UA part 6, 5.2. class Q_OPCUA_EXPORT QOpcUaBinaryDataEncoding { public: QOpcUaBinaryDataEncoding(QByteArray *buffer); QOpcUaBinaryDataEncoding(QOpcUaExtensionObject &object); template T decode(bool &success); template QVector decodeArray(bool &success); template bool encode(const T &src); template bool encodeArray(const QVector &src); int offset() const; void setOffset(int offset); void truncateBufferToOffset(); private: bool enoughData(int requiredSize); template T upperBound(); QByteArray *m_data{nullptr}; int m_offset{0}; }; template T QOpcUaBinaryDataEncoding::upperBound() { // Use extra parentheses to prevent macro substitution for max() on windows return (std::numeric_limits::max)(); } template inline T QOpcUaBinaryDataEncoding::decode(bool &success) { static_assert(OVERLAY == QOpcUa::Types::Undefined, "Ambiguous types are only permitted for template specializations"); static_assert(std::is_arithmetic::value == true, "Non-numeric types are only permitted for template specializations"); if (!m_data) { success = false; return T(0); } if (enoughData(sizeof(T))) { T temp = *reinterpret_cast(m_data->constData() + m_offset); m_offset += sizeof(T); success = true; return qFromLittleEndian(temp); } else { success = false; return T(0); } } template<> inline bool QOpcUaBinaryDataEncoding::decode(bool &success) { if (!m_data) { success = false; return success; } if (enoughData(sizeof(quint8))) { auto temp = *reinterpret_cast(m_data->constData() + m_offset); m_offset += sizeof(temp); success = true; return temp != 0; } else { success = false; return false; } } template<> inline QString QOpcUaBinaryDataEncoding::decode(bool &success) { if (!m_data) { success = false; return QString(); } const auto length = decode(success); if (length > 0 && !enoughData(static_cast(length))) { success = false; return QString(); } if (length > 0) { QString temp = QString::fromUtf8(reinterpret_cast(m_data->constData() + m_offset), length); m_offset += length; success = true; return temp; } else if (length == 0) { // Empty string success = true; return QString::fromUtf8(""); } else if (length == -1) { // Null string success = true; return QString(); } success = false; return QString(); } template <> inline QOpcUaQualifiedName QOpcUaBinaryDataEncoding::decode(bool &success) { QOpcUaQualifiedName temp; temp.setNamespaceIndex(decode(success)); if (!success) return QOpcUaQualifiedName(); temp.setName(decode(success)); if (!success) return QOpcUaQualifiedName(); return temp; } template <> inline QOpcUaLocalizedText QOpcUaBinaryDataEncoding::decode(bool &success) { QOpcUaLocalizedText temp; quint8 encodingMask = decode(success); if (!success) return QOpcUaLocalizedText(); if (encodingMask & 0x01) { temp.setLocale(decode(success)); if (!success) return QOpcUaLocalizedText(); } if (encodingMask & 0x02) { temp.setText(decode(success)); if (!success) return QOpcUaLocalizedText(); } return temp; } template <> inline QOpcUaEUInformation QOpcUaBinaryDataEncoding::decode(bool &success) { QOpcUaEUInformation temp; temp.setNamespaceUri(decode(success)); if (!success) return QOpcUaEUInformation(); temp.setUnitId(decode(success)); if (!success) return QOpcUaEUInformation(); temp.setDisplayName(decode(success)); if (!success) return QOpcUaEUInformation(); temp.setDescription(decode(success)); if (!success) return QOpcUaEUInformation(); return temp; } template <> inline QOpcUaRange QOpcUaBinaryDataEncoding::decode(bool &success) { QOpcUaRange temp; temp.setLow(decode(success)); if (!success) return QOpcUaRange(); temp.setHigh(decode(success)); if (!success) return QOpcUaRange(); return temp; } template <> inline QOpcUaComplexNumber QOpcUaBinaryDataEncoding::decode(bool &success) { QOpcUaComplexNumber temp; temp.setReal(decode(success)); if (!success) return QOpcUaComplexNumber(); temp.setImaginary(decode(success)); if (!success) return QOpcUaComplexNumber(); return temp; } template <> inline QOpcUaDoubleComplexNumber QOpcUaBinaryDataEncoding::decode(bool &success) { QOpcUaDoubleComplexNumber temp; temp.setReal(decode(success)); if (!success) return QOpcUaDoubleComplexNumber(); temp.setImaginary(decode(success)); if (!success) return QOpcUaDoubleComplexNumber(); return temp; } template <> inline QOpcUaAxisInformation QOpcUaBinaryDataEncoding::decode(bool &success) { QOpcUaAxisInformation temp; temp.setEngineeringUnits(decode(success)); if (!success) return QOpcUaAxisInformation(); temp.setEURange(decode(success)); if (!success) return QOpcUaAxisInformation(); temp.setTitle(decode(success)); if (!success) return QOpcUaAxisInformation(); temp.setAxisScaleType(static_cast(decode(success))); if (!success) return QOpcUaAxisInformation(); temp.setAxisSteps(decodeArray(success)); if (!success) return QOpcUaAxisInformation(); return temp; } template <> inline QOpcUaXValue QOpcUaBinaryDataEncoding::decode(bool &success) { QOpcUaXValue temp; temp.setX(decode(success)); if (!success) return QOpcUaXValue(); temp.setValue(decode(success)); if (!success) return QOpcUaXValue(); return temp; } template <> inline QUuid QOpcUaBinaryDataEncoding::decode(bool &success) { if (!m_data) { success = false; return QUuid(); } // An UUID is 16 bytes long const size_t uuidSize = 16; if (!enoughData(uuidSize)) { success = false; return QUuid(); } const auto data1 = decode(success); if (!success) return QUuid(); const auto data2 = decode(success); if (!success) return QUuid(); const auto data3 = decode(success); if (!success) return QUuid(); const auto data4 = QByteArray::fromRawData(m_data->constData() + m_offset, 8); if (!success) return QUuid(); m_offset += 8; const QUuid temp = QUuid(data1, data2, data3, data4[0], data4[1], data4[2], data4[3], data4[4], data4[5], data4[6], data4[7]); success = true; return temp; } template <> inline QByteArray QOpcUaBinaryDataEncoding::decode(bool &success) { if (!m_data) { success = false; return QByteArray(); } qint32 size = decode(success); if (!success) return QByteArray(); if (size > 0 && enoughData(size)) { const QByteArray temp(m_data->constData() + m_offset, size); m_offset += size; return temp; } else if (size == 0) { return QByteArray(""); } else if (size == -1) { return QByteArray(); } success = false; return QByteArray(); } template <> inline QString QOpcUaBinaryDataEncoding::decode(bool &success) { quint8 identifierType = decode(success); if (!success) return QString(); identifierType &= ~(0x40 | 0x80); // Remove expanded node id flags quint16 namespaceIndex; if (identifierType == 0x00) { // encodingType 0x00 does not transfer the namespace index, it has to be zero // Part 6, Chapter 5.2.2.9, Section "Two Byte NodeId Binary DataEncoding" namespaceIndex = 0; } else if (identifierType == 0x01){ // encodingType 0x01 transfers only one byte namespace index, has to be in range 0-255 // Part 6, Chapter 5.2.2.9, Section "Four Byte NodeId Binary DataEncoding" namespaceIndex = decode(success); } else { namespaceIndex = decode(success); } if (!success) return QString(); switch (identifierType) { case 0x00: { quint8 identifier = decode(success); if (!success) return QString(); return QStringLiteral("ns=%1;i=%2").arg(namespaceIndex).arg(identifier); } case 0x01: { quint16 identifier = decode(success); if (!success) return QString(); return QStringLiteral("ns=%1;i=%2").arg(namespaceIndex).arg(identifier); } case 0x02: { quint32 identifier = decode(success); if (!success) return QString(); return QStringLiteral("ns=%1;i=%2").arg(namespaceIndex).arg(identifier); } case 0x03: { QString identifier = decode(success); if (!success) return QString(); return QStringLiteral("ns=%1;s=%2").arg(namespaceIndex).arg(identifier); } case 0x04: { QUuid identifier = decode(success); if (!success) return QString(); return QStringLiteral("ns=%1;g=%2").arg(namespaceIndex).arg(identifier.toString().midRef(1, 36)); // Remove enclosing {...} } case 0x05: { QByteArray identifier = decode(success); if (!success) return QString(); return QStringLiteral("ns=%1;b=%2").arg(namespaceIndex).arg(QString::fromLatin1(identifier.toBase64().constData())); } } success = false; return QString(); } template <> inline QOpcUaExpandedNodeId QOpcUaBinaryDataEncoding::decode(bool &success) { if (!m_data) { success = false; return QOpcUaExpandedNodeId(); } // Don't decode the first byte, it is required for decode() if (!enoughData(sizeof(quint8))) { success = false; return QOpcUaExpandedNodeId(); } bool hasNamespaceUri = *(reinterpret_cast(m_data->constData() + m_offset)) & 0x80; bool hasServerIndex = *(reinterpret_cast(m_data->constData() + m_offset)) & 0x40; QString nodeId = decode(success); if (!success) return QOpcUaExpandedNodeId(); QString namespaceUri; if (hasNamespaceUri) { namespaceUri = decode(success); if (!success) return QOpcUaExpandedNodeId(); } quint32 serverIndex = 0; if (hasServerIndex) { serverIndex = decode(success); if (!success) return QOpcUaExpandedNodeId(); } return QOpcUaExpandedNodeId(namespaceUri, nodeId, serverIndex); } template <> inline QDateTime QOpcUaBinaryDataEncoding::decode(bool &success) { qint64 timestamp = decode(success); if (!success) return QDateTime(); if (timestamp == 0 || timestamp == upperBound()) return QDateTime(); // OPC-UA part 6, 5.2.2.5 const QDateTime epochStart(QDate(1601, 1, 1), QTime(0, 0), Qt::UTC); return epochStart.addMSecs(timestamp / 10000); } template <> inline QOpcUa::UaStatusCode QOpcUaBinaryDataEncoding::decode(bool &success) { quint32 value = decode(success); if (!success) return QOpcUa::UaStatusCode(0); return QOpcUa::UaStatusCode(value); } template <> inline QOpcUaExtensionObject QOpcUaBinaryDataEncoding::decode(bool &success) { QOpcUaExtensionObject temp; QString typeId = decode(success); if (!success) return QOpcUaExtensionObject(); temp.setEncodingTypeId(typeId); quint8 encoding = decode(success); if (!success || encoding > 2) { success = false; return QOpcUaExtensionObject(); } temp.setEncoding(QOpcUaExtensionObject::Encoding(encoding)); if (encoding == 0) return temp; QByteArray body = decode(success); if (!success) return QOpcUaExtensionObject(); temp.setEncodedBody(body); return temp; } template <> inline QOpcUaArgument QOpcUaBinaryDataEncoding::decode(bool &success) { QOpcUaArgument temp; temp.setName(decode(success)); if (!success) return QOpcUaArgument(); temp.setDataTypeId(decode(success)); if (!success) return QOpcUaArgument(); temp.setValueRank(decode(success)); if (!success) return QOpcUaArgument(); temp.setArrayDimensions(decodeArray(success)); if (!success) return QOpcUaArgument(); temp.setDescription(decode(success)); if (!success) return QOpcUaArgument(); return temp; } template inline bool QOpcUaBinaryDataEncoding::encode(const T &src) { static_assert(OVERLAY == QOpcUa::Types::Undefined, "Ambiguous types are only permitted for template specializations"); static_assert(std::is_arithmetic::value == true, "Non-numeric types are only permitted for template specializations"); if (!m_data) return false; T temp = qToLittleEndian(src); m_data->append(reinterpret_cast(&temp), sizeof(T)); return true; } template<> inline bool QOpcUaBinaryDataEncoding::encode(const bool &src) { if (!m_data) return false; const quint8 value = src ? 1 : 0; m_data->append(reinterpret_cast(&value), sizeof(value)); return true; } template<> inline bool QOpcUaBinaryDataEncoding::encode(const QString &src) { if (!m_data) return false; if (src.size() > upperBound()) return false; QByteArray arr = src.toUtf8(); if (!encode(arr.isNull() ? -1 : arr.length())) return false; m_data->append(arr); return true; } template<> inline bool QOpcUaBinaryDataEncoding::encode(const QOpcUaQualifiedName &src) { if (!encode(src.namespaceIndex())) return false; if (!encode(src.name())) return false; return true; } template<> inline bool QOpcUaBinaryDataEncoding::encode(const QOpcUaLocalizedText &src) { quint8 mask = 0; if (src.locale().length() != 0) mask |= 0x01; if (src.text().length() != 0) mask |= 0x02; if (!encode(mask)) return false; if (src.locale().length()) if (!encode(src.locale())) return false; if (src.text().length()) if (!encode(src.text())) return false; return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QOpcUaRange &src) { if (!encode(src.low())) return false; if (!encode(src.high())) return false; return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QOpcUaEUInformation &src) { if (!encode(src.namespaceUri())) return false; if (!encode(src.unitId())) return false; if (!encode(src.displayName())) return false; if (!encode(src.description())) return false; return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QOpcUaComplexNumber &src) { if (!encode(src.real())) return false; if (!encode(src.imaginary())) return false; return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QOpcUaDoubleComplexNumber &src) { if (!encode(src.real())) return false; if (!encode(src.imaginary())) return false; return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QOpcUaAxisInformation &src) { if (!encode(src.engineeringUnits())) return false; if (!encode(src.eURange())) return false; if (!encode(src.title())) return false; if (!encode(static_cast(src.axisScaleType()))) return false; if (!encodeArray(src.axisSteps())) return false; return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QOpcUaXValue &src) { if (!encode(src.x())) return false; if (!encode(src.value())) return false; return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QUuid &src) { if (!encode(src.data1)) return false; if (!encode(src.data2)) return false; if (!encode(src.data3)) return false; auto data = QByteArray::fromRawData(reinterpret_cast(src.data4), sizeof(src.data4)); m_data->append(data); return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QByteArray &src) { if (!m_data) return false; if (src.size() > upperBound()) return false; if (!encode(src.isNull() ? -1 : src.size())) return false; if (src.size() > 1) m_data->append(src); return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QString &src) { if (!m_data) return false; quint16 index; QString identifier; char type; if (!QOpcUa::nodeIdStringSplit(src, &index, &identifier, &type)) return false; qint32 identifierType; switch (type) { case 'i': identifierType = 0; break; case 's': identifierType = 1; break; case 'g': identifierType = 2; break; case 'b': identifierType = 3; break; default: return false; } QByteArray encodedIdentifier; QOpcUaBinaryDataEncoding encoder(&encodedIdentifier); quint8 encodingType = 0; switch (identifierType) { case 0: { bool isNumber; uint integerIdentifier = identifier.toUInt(&isNumber); if (!isNumber || integerIdentifier > upperBound()) return false; if (integerIdentifier <= 255 && index == 0) { // encodingType 0x00 does not transfer the namespace index, it has to be zero // Part 6, Chapter 5.2.2.9, Section "Two Byte NodeId Binary DataEncoding" if (!encoder.encode(integerIdentifier)) return false; encodingType = 0x00; // 8 bit numeric break; } else if (integerIdentifier <= 65535 && index <= 255) { // encodingType 0x01 transfers only one byte namespace index, has to be in range 0-255 // Part 6, Chapter 5.2.2.9, Section "Four Byte NodeId Binary DataEncoding" if (!encoder.encode(integerIdentifier)) return false; encodingType = 0x01; // 16 bit numeric break; } else { if (!encoder.encode(integerIdentifier)) return false; encodingType = 0x02; // 32 bit numeric break; } } case 1: { if (identifier.isEmpty()) return false; if (!encoder.encode(identifier)) return false; encodingType = 0x03; // String break; } case 2: { QUuid uuid(identifier); if (uuid.isNull()) return false; if (!encoder.encode(uuid)) return false; encodingType = 0x04; // GUID break; } case 3: { const QByteArray temp = QByteArray::fromBase64(identifier.toLatin1()); if (temp.isEmpty()) return false; if (!encoder.encode(temp)) return false; encodingType = 0x05; // ByteString break; } default: return false; } if (!encode(encodingType)) return false; if (encodingType == 0x00) { // encodingType == 0x00 skips namespace completely, defaults to zero // Part 6, Chapter 5.2.2.9, Section "Two Byte NodeId Binary DataEncoding" } else if (encodingType == 0x01) { if (!encode(index)) return false; } else { if (!encode(index)) return false; } m_data->append(encodedIdentifier); return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QOpcUaExpandedNodeId &src) { if (!m_data) return false; QByteArray temp; QOpcUaBinaryDataEncoding encoder(&temp); if (!encoder.encode(src.nodeId())) return false; quint8 mask = temp.at(0); if (!src.namespaceUri().isEmpty()) { mask |= 0x80; if (!encoder.encode(src.namespaceUri())) return false; } if (src.serverIndex() != 0) { mask |= 0x40; if (!encoder.encode(src.serverIndex())) return false; } temp[0] = mask; m_data->append(temp); return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QDateTime &src) { // OPC-UA part 6, 5.2.2.5 if (src >= QDateTime(QDate(9999, 12, 31), QTime(11, 59, 59))) { if (!encode(upperBound())) return false; return true; } const QDateTime uaEpochStart(QDate(1601, 1, 1), QTime(0, 0), Qt::UTC); if (src <= uaEpochStart) { if (!encode(0)) return false; return true; } qint64 timestamp = 10000 * (src.toMSecsSinceEpoch() - uaEpochStart.toMSecsSinceEpoch()); if (!encode(timestamp)) return false; return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QOpcUa::UaStatusCode &src) { if (!encode(src)) return false; return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QOpcUaExtensionObject &src) { if (!encode(src.encodingTypeId())) return false; if (!encode(quint8(src.encoding()))) return false; if (src.encoding() != QOpcUaExtensionObject::Encoding::NoBody) if (!encode(src.encodedBody())) return false; return true; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QOpcUaArgument &src) { if (!m_data) return false; QByteArray temp; QOpcUaBinaryDataEncoding encoder(&temp); if (!encoder.encode(src.name())) return false; if (!encoder.encode(src.dataTypeId())) return false; if (!encoder.encode(src.valueRank())) return false; if (!encoder.encodeArray(src.arrayDimensions())) return false; if (!encoder.encode(src.description())) return false; m_data->append(temp); return true; } template inline QVector QOpcUaBinaryDataEncoding::decodeArray(bool &success) { QVector temp; qint32 size = decode(success); if (!success) return temp; for (int i = 0; i < size; ++i) { temp.push_back(decode(success)); if (!success) return QVector(); } return temp; } template inline bool QOpcUaBinaryDataEncoding::encodeArray(const QVector &src) { if (src.size() > upperBound()) return false; if (!encode(src.size())) return false; for (const auto &element : src) { if (!encode(element)) return false; } return true; } template <> inline QOpcUaApplicationRecordDataType QOpcUaBinaryDataEncoding::decode(bool &success) { QOpcUaApplicationRecordDataType temp; temp.setApplicationId(decode(success)); if (!success) return QOpcUaApplicationRecordDataType(); temp.setApplicationUri(decode(success)); if (!success) return QOpcUaApplicationRecordDataType(); temp.setApplicationType(static_cast(decode(success))); if (!success) return QOpcUaApplicationRecordDataType(); temp.setApplicationNames(decodeArray(success)); if (!success) return QOpcUaApplicationRecordDataType(); temp.setProductUri(decode(success)); if (!success) return QOpcUaApplicationRecordDataType(); temp.setDiscoveryUrls(decodeArray(success)); if (!success) return QOpcUaApplicationRecordDataType(); temp.setServerCapabilityIdentifiers(decodeArray(success)); if (!success) return QOpcUaApplicationRecordDataType(); return temp; } template <> inline bool QOpcUaBinaryDataEncoding::encode(const QOpcUaApplicationRecordDataType &src) { if (!encode(src.applicationId())) return false; if (!encode(src.applicationUri())) return false; if (!encode(src.applicationType())) return false; if (!encodeArray(src.applicationNames())) return false; if (!encode(src.productUri())) return false; if (!encodeArray(src.discoveryUrls())) return false; if (!encodeArray(src.serverCapabilityIdentifiers())) return false; return true; } QT_END_NAMESPACE #endif // QOPCUABINARYDATAENCODING_H