1// Copyright (C) 2018 basysKom GmbH, opensource@basyskom.com
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QOPCUABINARYDATAENCODING_H
5#define QOPCUABINARYDATAENCODING_H
6
7#include <QtOpcUa/qopcuaapplicationrecorddatatype.h>
8#include <QtOpcUa/qopcuaargument.h>
9#include <QtOpcUa/qopcuaaxisinformation.h>
10#include <QtOpcUa/qopcuacomplexnumber.h>
11#include <QtOpcUa/qopcuadoublecomplexnumber.h>
12#include <QtOpcUa/qopcuaeuinformation.h>
13#include <QtOpcUa/qopcuaexpandednodeid.h>
14#include <QtOpcUa/qopcuaextensionobject.h>
15#include <QtOpcUa/qopcualocalizedtext.h>
16#include <QtOpcUa/qopcuaqualifiedname.h>
17#include <QtOpcUa/qopcuarange.h>
18#include <QtOpcUa/qopcuatype.h>
19#include <QtOpcUa/qopcuaxvalue.h>
20
21#include <QtCore/qdatetime.h>
22#include <QtCore/qendian.h>
23#include <QtCore/qlist.h>
24#include <QtCore/qmetatype.h>
25#include <QtCore/qtimezone.h>
26#include <QtCore/quuid.h>
27
28#include <limits>
29
30QT_BEGIN_NAMESPACE
31
32// This class implements a subset of the OPC UA Binary DataEncoding defined in OPC-UA part 6, 5.2.
33class Q_OPCUA_EXPORT QOpcUaBinaryDataEncoding
34{
35public:
36
37 QOpcUaBinaryDataEncoding(QByteArray *buffer);
38 QOpcUaBinaryDataEncoding(QOpcUaExtensionObject &object);
39
40 template <typename T, QOpcUa::Types OVERLAY = QOpcUa::Types::Undefined>
41 T decode(bool &success);
42 template <typename T, QOpcUa::Types OVERLAY = QOpcUa::Types::Undefined>
43 QList<T> decodeArray(bool &success);
44
45 template <typename T, QOpcUa::Types OVERLAY = QOpcUa::Types::Undefined>
46 bool encode(const T &src);
47 template <typename T, QOpcUa::Types OVERLAY = QOpcUa::Types::Undefined>
48 bool encodeArray(const QList<T> &src);
49
50
51 int offset() const;
52 void setOffset(int offset);
53 void truncateBufferToOffset();
54
55private:
56 bool enoughData(int requiredSize);
57 template <typename T>
58 T upperBound();
59
60 QByteArray *m_data{nullptr};
61 int m_offset{0};
62};
63
64template<typename T>
65T QOpcUaBinaryDataEncoding::upperBound()
66{
67 // Use extra parentheses to prevent macro substitution for max() on windows
68 return (std::numeric_limits<T>::max)();
69}
70
71template<typename T, QOpcUa::Types OVERLAY>
72inline T QOpcUaBinaryDataEncoding::decode(bool &success)
73{
74 static_assert(OVERLAY == QOpcUa::Types::Undefined, "Ambiguous types are only permitted for template specializations");
75 static_assert(std::is_arithmetic<T>::value == true, "Non-numeric types are only permitted for template specializations");
76
77 if (!m_data) {
78 success = false;
79 return T(0);
80 }
81
82 if (enoughData(requiredSize: sizeof(T))) {
83 T temp;
84 memcpy(&temp, m_data->constData() + m_offset, sizeof(T));
85 m_offset += sizeof(T);
86 success = true;
87 return qFromLittleEndian<T>(temp);
88 } else {
89 success = false;
90 return T(0);
91 }
92}
93
94template<>
95inline bool QOpcUaBinaryDataEncoding::decode<bool>(bool &success)
96{
97 if (!m_data) {
98 success = false;
99 return success;
100 }
101
102 if (enoughData(requiredSize: sizeof(quint8))) {
103 auto temp = *reinterpret_cast<const quint8 *>(m_data->constData() + m_offset);
104 m_offset += sizeof(temp);
105 success = true;
106 return temp != 0;
107 } else {
108 success = false;
109 return false;
110 }
111}
112
113template<>
114inline QString QOpcUaBinaryDataEncoding::decode<QString>(bool &success)
115{
116 if (!m_data) {
117 success = false;
118 return QString();
119 }
120
121 const auto length = decode<qint32>(success);
122
123 if (length > 0 && !enoughData(requiredSize: length)) {
124 success = false;
125 return QString();
126 }
127
128 if (length > 0) {
129 QString temp = QString::fromUtf8(utf8: reinterpret_cast<const char *>(m_data->constData() + m_offset), size: length);
130 m_offset += length;
131 success = true;
132 return temp;
133 } else if (length == 0) { // Empty string
134 success = true;
135 return QString::fromUtf8(utf8: "");
136 } else if (length == -1) { // Null string
137 success = true;
138 return QString();
139 }
140
141 success = false;
142 return QString();
143}
144
145template <>
146inline QOpcUaQualifiedName QOpcUaBinaryDataEncoding::decode<QOpcUaQualifiedName>(bool &success)
147{
148 QOpcUaQualifiedName temp;
149 temp.setNamespaceIndex(decode<quint16>(success));
150 if (!success)
151 return QOpcUaQualifiedName();
152
153 temp.setName(decode<QString>(success));
154 if (!success)
155 return QOpcUaQualifiedName();
156
157 return temp;
158}
159
160template <>
161inline QOpcUaLocalizedText QOpcUaBinaryDataEncoding::decode<QOpcUaLocalizedText>(bool &success)
162{
163 QOpcUaLocalizedText temp;
164 quint8 encodingMask = decode<quint8>(success);
165 if (!success)
166 return QOpcUaLocalizedText();
167
168 if (encodingMask & 0x01) {
169 temp.setLocale(decode<QString>(success));
170 if (!success)
171 return QOpcUaLocalizedText();
172 }
173 if (encodingMask & 0x02) {
174 temp.setText(decode<QString>(success));
175 if (!success)
176 return QOpcUaLocalizedText();
177 }
178 return temp;
179}
180
181template <>
182inline QOpcUaEUInformation QOpcUaBinaryDataEncoding::decode<QOpcUaEUInformation>(bool &success)
183{
184 QOpcUaEUInformation temp;
185
186 temp.setNamespaceUri(decode<QString>(success));
187 if (!success)
188 return QOpcUaEUInformation();
189
190 temp.setUnitId(decode<qint32>(success));
191 if (!success)
192 return QOpcUaEUInformation();
193
194 temp.setDisplayName(decode<QOpcUaLocalizedText>(success));
195 if (!success)
196 return QOpcUaEUInformation();
197
198 temp.setDescription(decode<QOpcUaLocalizedText>(success));
199 if (!success)
200 return QOpcUaEUInformation();
201
202 return temp;
203}
204
205template <>
206inline QOpcUaRange QOpcUaBinaryDataEncoding::decode<QOpcUaRange>(bool &success)
207{
208 QOpcUaRange temp;
209
210 temp.setLow(decode<double>(success));
211 if (!success)
212 return QOpcUaRange();
213
214 temp.setHigh(decode<double>(success));
215 if (!success)
216 return QOpcUaRange();
217
218 return temp;
219}
220
221template <>
222inline QOpcUaComplexNumber QOpcUaBinaryDataEncoding::decode<QOpcUaComplexNumber>(bool &success)
223{
224 QOpcUaComplexNumber temp;
225
226 temp.setReal(decode<float>(success));
227 if (!success)
228 return QOpcUaComplexNumber();
229
230 temp.setImaginary(decode<float>(success));
231 if (!success)
232 return QOpcUaComplexNumber();
233
234 return temp;
235}
236
237template <>
238inline QOpcUaDoubleComplexNumber QOpcUaBinaryDataEncoding::decode<QOpcUaDoubleComplexNumber>(bool &success)
239{
240 QOpcUaDoubleComplexNumber temp;
241
242 temp.setReal(decode<double>(success));
243 if (!success)
244 return QOpcUaDoubleComplexNumber();
245
246 temp.setImaginary(decode<double>(success));
247 if (!success)
248 return QOpcUaDoubleComplexNumber();
249
250 return temp;
251}
252
253template <>
254inline QOpcUaAxisInformation QOpcUaBinaryDataEncoding::decode<QOpcUaAxisInformation>(bool &success)
255{
256 QOpcUaAxisInformation temp;
257
258 temp.setEngineeringUnits(decode<QOpcUaEUInformation>(success));
259 if (!success)
260 return QOpcUaAxisInformation();
261
262 temp.setEURange(decode<QOpcUaRange>(success));
263 if (!success)
264 return QOpcUaAxisInformation();
265
266 temp.setTitle(decode<QOpcUaLocalizedText>(success));
267 if (!success)
268 return QOpcUaAxisInformation();
269
270 temp.setAxisScaleType(static_cast<QOpcUa::AxisScale>(decode<quint32>(success)));
271 if (!success)
272 return QOpcUaAxisInformation();
273
274 temp.setAxisSteps(decodeArray<double>(success));
275 if (!success)
276 return QOpcUaAxisInformation();
277
278 return temp;
279}
280
281template <>
282inline QOpcUaXValue QOpcUaBinaryDataEncoding::decode<QOpcUaXValue>(bool &success)
283{
284 QOpcUaXValue temp;
285
286 temp.setX(decode<double>(success));
287 if (!success)
288 return QOpcUaXValue();
289
290 temp.setValue(decode<float>(success));
291 if (!success)
292 return QOpcUaXValue();
293
294 return temp;
295}
296
297template <>
298inline QUuid QOpcUaBinaryDataEncoding::decode<QUuid>(bool &success)
299{
300 if (!m_data) {
301 success = false;
302 return QUuid();
303 }
304
305 // An UUID is 16 bytes long
306 const size_t uuidSize = 16;
307 if (!enoughData(requiredSize: uuidSize)) {
308 success = false;
309 return QUuid();
310 }
311
312 const auto data1 = decode<quint32>(success);
313 if (!success)
314 return QUuid();
315
316 const auto data2 = decode<quint16>(success);
317 if (!success)
318 return QUuid();
319
320 const auto data3 = decode<quint16>(success);
321 if (!success)
322 return QUuid();
323
324 const auto data4 = QByteArray::fromRawData(data: m_data->constData() + m_offset, size: 8);
325 if (!success)
326 return QUuid();
327
328 m_offset += 8;
329
330 const QUuid temp = QUuid(data1, data2, data3, data4[0], data4[1], data4[2],
331 data4[3], data4[4], data4[5], data4[6], data4[7]);
332
333 success = true;
334 return temp;
335}
336
337template <>
338inline QByteArray QOpcUaBinaryDataEncoding::decode<QByteArray>(bool &success)
339{
340 if (!m_data) {
341 success = false;
342 return QByteArray();
343 }
344
345 qint32 size = decode<qint32>(success);
346 if (!success)
347 return QByteArray();
348
349 if (size > 0 && enoughData(requiredSize: size)) {
350 const QByteArray temp(m_data->constData() + m_offset, size);
351 m_offset += size;
352 return temp;
353 } else if (size == 0) {
354 return QByteArray("");
355 } else if (size == -1) {
356 return QByteArray();
357 }
358
359 success = false;
360 return QByteArray();
361}
362
363template <>
364inline QString QOpcUaBinaryDataEncoding::decode<QString, QOpcUa::Types::NodeId>(bool &success)
365{
366 quint8 identifierType = decode<quint8>(success);
367 if (!success)
368 return QString();
369
370 identifierType &= ~(0x40 | 0x80); // Remove expanded node id flags
371
372 quint16 namespaceIndex;
373
374 if (identifierType == 0x00) {
375 // encodingType 0x00 does not transfer the namespace index, it has to be zero
376 // Part 6, Chapter 5.2.2.9, Section "Two Byte NodeId Binary DataEncoding"
377 namespaceIndex = 0;
378 } else if (identifierType == 0x01){
379 // encodingType 0x01 transfers only one byte namespace index, has to be in range 0-255
380 // Part 6, Chapter 5.2.2.9, Section "Four Byte NodeId Binary DataEncoding"
381 namespaceIndex = decode<quint8>(success);
382 } else {
383 namespaceIndex = decode<quint16>(success);
384 }
385
386 if (!success)
387 return QString();
388
389 switch (identifierType) {
390 case 0x00: {
391 quint8 identifier = decode<quint8>(success);
392 if (!success)
393 return QString();
394 return QStringLiteral("ns=%1;i=%2").arg(a: namespaceIndex).arg(a: identifier);
395 }
396 case 0x01: {
397 quint16 identifier = decode<quint16>(success);
398 if (!success)
399 return QString();
400 return QStringLiteral("ns=%1;i=%2").arg(a: namespaceIndex).arg(a: identifier);
401 }
402 case 0x02: {
403 quint32 identifier = decode<quint32>(success);
404 if (!success)
405 return QString();
406 return QStringLiteral("ns=%1;i=%2").arg(a: namespaceIndex).arg(a: identifier);
407 }
408 case 0x03: {
409 QString identifier = decode<QString>(success);
410 if (!success)
411 return QString();
412 return QStringLiteral("ns=%1;s=%2").arg(a: namespaceIndex).arg(a: identifier);
413 }
414 case 0x04: {
415 QUuid identifier = decode<QUuid>(success);
416 if (!success)
417 return QString();
418 return QStringLiteral("ns=%1;g=%2").arg(a: namespaceIndex).arg(a: QStringView(identifier.toString()).mid(pos: 1, n: 36)); // Remove enclosing {...}
419 }
420 case 0x05: {
421 QByteArray identifier = decode<QByteArray>(success);
422 if (!success)
423 return QString();
424 return QStringLiteral("ns=%1;b=%2").arg(a: namespaceIndex).arg(a: QString::fromLatin1(ba: identifier.toBase64().constData()));
425 }
426 }
427
428 success = false;
429 return QString();
430}
431
432template <>
433inline QOpcUaExpandedNodeId QOpcUaBinaryDataEncoding::decode<QOpcUaExpandedNodeId>(bool &success)
434{
435 if (!m_data) {
436 success = false;
437 return QOpcUaExpandedNodeId();
438 }
439
440 // Don't decode the first byte, it is required for decode<QString, QOpcUa::Types::NodeId>()
441 if (!enoughData(requiredSize: sizeof(quint8))) {
442 success = false;
443 return QOpcUaExpandedNodeId();
444 }
445 bool hasNamespaceUri = *(reinterpret_cast<const quint8 *>(m_data->constData() + m_offset)) & 0x80;
446 bool hasServerIndex = *(reinterpret_cast<const quint8 *>(m_data->constData() + m_offset)) & 0x40;
447
448 QString nodeId = decode<QString, QOpcUa::Types::NodeId>(success);
449 if (!success)
450 return QOpcUaExpandedNodeId();
451
452 QString namespaceUri;
453 if (hasNamespaceUri) {
454 namespaceUri = decode<QString>(success);
455 if (!success)
456 return QOpcUaExpandedNodeId();
457 }
458
459 quint32 serverIndex = 0;
460 if (hasServerIndex) {
461 serverIndex = decode<quint32>(success);
462 if (!success)
463 return QOpcUaExpandedNodeId();
464 }
465
466 return QOpcUaExpandedNodeId(namespaceUri, nodeId, serverIndex);
467}
468
469template <>
470inline QDateTime QOpcUaBinaryDataEncoding::decode<QDateTime>(bool &success)
471{
472 qint64 timestamp = decode<qint64>(success);
473 if (!success)
474 return QDateTime();
475
476 if (timestamp == 0 || timestamp == upperBound<qint64>())
477 return QDateTime();
478
479 // OPC-UA part 6, 5.2.2.5
480 const QDateTime epochStart(QDate(1601, 1, 1), QTime(0, 0), QTimeZone::UTC);
481 return epochStart.addMSecs(msecs: timestamp / 10000);
482}
483
484template <>
485inline QOpcUa::UaStatusCode QOpcUaBinaryDataEncoding::decode<QOpcUa::UaStatusCode>(bool &success)
486{
487 quint32 value = decode<quint32>(success);
488 if (!success)
489 return QOpcUa::UaStatusCode(0);
490
491 return QOpcUa::UaStatusCode(value);
492}
493
494template <>
495inline QOpcUaExtensionObject QOpcUaBinaryDataEncoding::decode<QOpcUaExtensionObject>(bool &success)
496{
497 QOpcUaExtensionObject temp;
498
499 QString typeId = decode<QString, QOpcUa::Types::NodeId>(success);
500 if (!success)
501 return QOpcUaExtensionObject();
502
503 temp.setEncodingTypeId(typeId);
504 quint8 encoding = decode<quint8>(success);
505 if (!success || encoding > 2) {
506 success = false;
507 return QOpcUaExtensionObject();
508 }
509 temp.setEncoding(QOpcUaExtensionObject::Encoding(encoding));
510 if (encoding == 0)
511 return temp;
512
513 QByteArray body = decode<QByteArray>(success);
514 if (!success)
515 return QOpcUaExtensionObject();
516
517 temp.setEncodedBody(body);
518 return temp;
519}
520
521template <>
522inline QOpcUaArgument QOpcUaBinaryDataEncoding::decode<QOpcUaArgument>(bool &success)
523{
524 QOpcUaArgument temp;
525
526 temp.setName(decode<QString>(success));
527 if (!success)
528 return QOpcUaArgument();
529
530 temp.setDataTypeId(decode<QString, QOpcUa::Types::NodeId>(success));
531 if (!success)
532 return QOpcUaArgument();
533
534 temp.setValueRank(decode<qint32>(success));
535 if (!success)
536 return QOpcUaArgument();
537
538 temp.setArrayDimensions(decodeArray<quint32>(success));
539 if (!success)
540 return QOpcUaArgument();
541
542 temp.setDescription(decode<QOpcUaLocalizedText>(success));
543 if (!success)
544 return QOpcUaArgument();
545
546 return temp;
547}
548
549template<typename T, QOpcUa::Types OVERLAY>
550inline bool QOpcUaBinaryDataEncoding::encode(const T &src)
551{
552 static_assert(OVERLAY == QOpcUa::Types::Undefined, "Ambiguous types are only permitted for template specializations");
553 static_assert(std::is_arithmetic<T>::value == true, "Non-numeric types are only permitted for template specializations");
554
555 if (!m_data)
556 return false;
557
558 T temp = qToLittleEndian<T>(src);
559 m_data->append(s: reinterpret_cast<const char *>(&temp), len: sizeof(T));
560 return true;
561}
562
563template<>
564inline bool QOpcUaBinaryDataEncoding::encode<bool>(const bool &src)
565{
566 if (!m_data)
567 return false;
568
569 const quint8 value = src ? 1 : 0;
570 m_data->append(s: reinterpret_cast<const char *>(&value), len: sizeof(value));
571 return true;
572}
573
574template<>
575inline bool QOpcUaBinaryDataEncoding::encode<QString>(const QString &src)
576{
577 if (!m_data)
578 return false;
579
580 if (src.size() > upperBound<qint32>())
581 return false;
582
583 QByteArray arr = src.toUtf8();
584 if (!encode<qint32>(src: arr.isNull() ? -1 : int(arr.size())))
585 return false;
586 m_data->append(a: arr);
587 return true;
588}
589
590template<>
591inline bool QOpcUaBinaryDataEncoding::encode<QOpcUaQualifiedName>(const QOpcUaQualifiedName &src)
592{
593 if (!encode<quint16>(src: src.namespaceIndex()))
594 return false;
595 if (!encode<QString>(src: src.name()))
596 return false;
597 return true;
598}
599
600template<>
601inline bool QOpcUaBinaryDataEncoding::encode<QOpcUaLocalizedText>(const QOpcUaLocalizedText &src)
602{
603 quint8 mask = 0;
604 if (src.locale().size() != 0)
605 mask |= 0x01;
606 if (src.text().size() != 0)
607 mask |= 0x02;
608 if (!encode<quint8>(src: mask))
609 return false;
610 if (src.locale().size())
611 if (!encode<QString>(src: src.locale()))
612 return false;
613 if (src.text().size())
614 if (!encode<QString>(src: src.text()))
615 return false;
616 return true;
617}
618
619template <>
620inline bool QOpcUaBinaryDataEncoding::encode<QOpcUaRange>(const QOpcUaRange &src)
621{
622 if (!encode<double>(src: src.low()))
623 return false;
624 if (!encode<double>(src: src.high()))
625 return false;
626 return true;
627}
628
629template <>
630inline bool QOpcUaBinaryDataEncoding::encode<QOpcUaEUInformation>(const QOpcUaEUInformation &src)
631{
632 if (!encode<QString>(src: src.namespaceUri()))
633 return false;
634 if (!encode<qint32>(src: src.unitId()))
635 return false;
636 if (!encode<QOpcUaLocalizedText>(src: src.displayName()))
637 return false;
638 if (!encode<QOpcUaLocalizedText>(src: src.description()))
639 return false;
640 return true;
641}
642
643template <>
644inline bool QOpcUaBinaryDataEncoding::encode<QOpcUaComplexNumber>(const QOpcUaComplexNumber &src)
645{
646 if (!encode<float>(src: src.real()))
647 return false;
648 if (!encode<float>(src: src.imaginary()))
649 return false;
650 return true;
651}
652
653template <>
654inline bool QOpcUaBinaryDataEncoding::encode<QOpcUaDoubleComplexNumber>(const QOpcUaDoubleComplexNumber &src)
655{
656 if (!encode<double>(src: src.real()))
657 return false;
658 if (!encode<double>(src: src.imaginary()))
659 return false;
660 return true;
661}
662
663template <>
664inline bool QOpcUaBinaryDataEncoding::encode<QOpcUaAxisInformation>(const QOpcUaAxisInformation &src)
665{
666 if (!encode<QOpcUaEUInformation>(src: src.engineeringUnits()))
667 return false;
668 if (!encode<QOpcUaRange>(src: src.eURange()))
669 return false;
670 if (!encode<QOpcUaLocalizedText>(src: src.title()))
671 return false;
672 if (!encode<quint32>(src: static_cast<quint32>(src.axisScaleType())))
673 return false;
674 if (!encodeArray<double>(src: src.axisSteps()))
675 return false;
676 return true;
677}
678
679template <>
680inline bool QOpcUaBinaryDataEncoding::encode<QOpcUaXValue>(const QOpcUaXValue &src)
681{
682 if (!encode<double>(src: src.x()))
683 return false;
684 if (!encode<float>(src: src.value()))
685 return false;
686 return true;
687}
688
689template <>
690inline bool QOpcUaBinaryDataEncoding::encode<QUuid>(const QUuid &src)
691{
692 if (!encode<quint32>(src: src.data1))
693 return false;
694 if (!encode<quint16>(src: src.data2))
695 return false;
696 if (!encode<quint16>(src: src.data3))
697 return false;
698
699 auto data = QByteArray::fromRawData(data: reinterpret_cast<const char *>(src.data4), size: sizeof(src.data4));
700 m_data->append(a: data);
701
702 return true;
703}
704
705template <>
706inline bool QOpcUaBinaryDataEncoding::encode<QByteArray>(const QByteArray &src)
707{
708 if (!m_data)
709 return false;
710
711 if (src.size() > upperBound<qint32>())
712 return false;
713
714 if (!encode<qint32>(src: src.isNull() ? -1 : int(src.size())))
715 return false;
716 if (src.size() > 1)
717 m_data->append(a: src);
718 return true;
719}
720
721template <>
722inline bool QOpcUaBinaryDataEncoding::encode<QString, QOpcUa::Types::NodeId>(const QString &src)
723{
724 if (!m_data)
725 return false;
726
727 quint16 index;
728 QString identifier;
729 char type;
730 if (!QOpcUa::nodeIdStringSplit(nodeIdString: src, nsIndex: &index, identifier: &identifier, identifierType: &type))
731 return false;
732
733 qint32 identifierType;
734 switch (type) {
735 case 'i':
736 identifierType = 0;
737 break;
738 case 's':
739 identifierType = 1;
740 break;
741 case 'g':
742 identifierType = 2;
743 break;
744 case 'b':
745 identifierType = 3;
746 break;
747 default:
748 return false;
749 }
750
751 QByteArray encodedIdentifier;
752 QOpcUaBinaryDataEncoding encoder(&encodedIdentifier);
753 quint8 encodingType = 0;
754
755 switch (identifierType) {
756 case 0: {
757 bool isNumber;
758 uint integerIdentifier = identifier.toUInt(ok: &isNumber);
759 if (!isNumber || integerIdentifier > upperBound<quint32>())
760 return false;
761
762 if (integerIdentifier <= 255 && index == 0) {
763 // encodingType 0x00 does not transfer the namespace index, it has to be zero
764 // Part 6, Chapter 5.2.2.9, Section "Two Byte NodeId Binary DataEncoding"
765 if (!encoder.encode<quint8>(src: integerIdentifier))
766 return false;
767 encodingType = 0x00; // 8 bit numeric
768 break;
769 } else if (integerIdentifier <= 65535 && index <= 255) {
770 // encodingType 0x01 transfers only one byte namespace index, has to be in range 0-255
771 // Part 6, Chapter 5.2.2.9, Section "Four Byte NodeId Binary DataEncoding"
772 if (!encoder.encode<quint16>(src: integerIdentifier))
773 return false;
774 encodingType = 0x01; // 16 bit numeric
775 break;
776 } else {
777 if (!encoder.encode<quint32>(src: integerIdentifier))
778 return false;
779 encodingType = 0x02; // 32 bit numeric
780 break;
781 }
782 }
783 case 1: {
784 if (identifier.isEmpty())
785 return false;
786 if (!encoder.encode<QString>(src: identifier))
787 return false;
788 encodingType = 0x03; // String
789 break;
790 }
791 case 2: {
792 QUuid uuid(identifier);
793 if (uuid.isNull())
794 return false;
795 if (!encoder.encode<QUuid>(src: uuid))
796 return false;
797 encodingType = 0x04; // GUID
798 break;
799 }
800 case 3: {
801 const QByteArray temp = QByteArray::fromBase64(base64: identifier.toLatin1());
802 if (temp.isEmpty())
803 return false;
804 if (!encoder.encode<QByteArray>(src: temp))
805 return false;
806 encodingType = 0x05; // ByteString
807 break;
808 }
809 default:
810 return false;
811 }
812
813 if (!encode<quint8>(src: encodingType))
814 return false;
815
816 if (encodingType == 0x00) {
817 // encodingType == 0x00 skips namespace completely, defaults to zero
818 // Part 6, Chapter 5.2.2.9, Section "Two Byte NodeId Binary DataEncoding"
819 } else if (encodingType == 0x01) {
820 if (!encode<quint8>(src: index))
821 return false;
822 } else {
823 if (!encode<quint16>(src: index))
824 return false;
825 }
826
827 m_data->append(a: encodedIdentifier);
828 return true;
829}
830
831template <>
832inline bool QOpcUaBinaryDataEncoding::encode<QOpcUaExpandedNodeId>(const QOpcUaExpandedNodeId &src)
833{
834 if (!m_data)
835 return false;
836
837 QByteArray temp;
838 QOpcUaBinaryDataEncoding encoder(&temp);
839 if (!encoder.encode<QString, QOpcUa::Types::NodeId>(src: src.nodeId()))
840 return false;
841
842 quint8 mask = temp.at(i: 0);
843
844 if (!src.namespaceUri().isEmpty()) {
845 mask |= 0x80;
846 if (!encoder.encode<QString>(src: src.namespaceUri()))
847 return false;
848 }
849
850 if (src.serverIndex() != 0) {
851 mask |= 0x40;
852 if (!encoder.encode<quint32>(src: src.serverIndex()))
853 return false;
854 }
855
856 temp[0] = mask;
857
858 m_data->append(a: temp);
859 return true;
860}
861
862template <>
863inline bool QOpcUaBinaryDataEncoding::encode<QDateTime>(const QDateTime &src)
864{
865 // OPC-UA part 6, 5.2.2.5
866 if (src >= QDateTime(QDate(9999, 12, 31), QTime(11, 59, 59))) {
867 if (!encode<qint64>(src: upperBound<qint64>()))
868 return false;
869 return true;
870 }
871
872 const QDateTime uaEpochStart(QDate(1601, 1, 1), QTime(0, 0), QTimeZone::UTC);
873
874 if (src <= uaEpochStart) {
875 if (!encode<qint64>(src: 0))
876 return false;
877 return true;
878 }
879
880 qint64 timestamp = 10000 * (src.toMSecsSinceEpoch() - uaEpochStart.toMSecsSinceEpoch());
881 if (!encode<qint64>(src: timestamp))
882 return false;
883 return true;
884}
885
886template <>
887inline bool QOpcUaBinaryDataEncoding::encode<QOpcUa::UaStatusCode>(const QOpcUa::UaStatusCode &src)
888{
889 if (!encode<quint32>(src))
890 return false;
891 return true;
892}
893
894template <>
895inline bool QOpcUaBinaryDataEncoding::encode<QOpcUaExtensionObject>(const QOpcUaExtensionObject &src)
896{
897 if (!encode<QString, QOpcUa::Types::NodeId>(src: src.encodingTypeId()))
898 return false;
899
900 if (!encode<quint8>(src: quint8(src.encoding())))
901 return false;
902 if (src.encoding() != QOpcUaExtensionObject::Encoding::NoBody)
903 if (!encode<QByteArray>(src: src.encodedBody()))
904 return false;
905
906 return true;
907}
908
909template <>
910inline bool QOpcUaBinaryDataEncoding::encode<QOpcUaArgument>(const QOpcUaArgument &src)
911{
912 if (!m_data)
913 return false;
914
915 QByteArray temp;
916 QOpcUaBinaryDataEncoding encoder(&temp);
917 if (!encoder.encode<QString>(src: src.name()))
918 return false;
919 if (!encoder.encode<QString, QOpcUa::Types::NodeId>(src: src.dataTypeId()))
920 return false;
921
922 if (!encoder.encode<qint32>(src: src.valueRank()))
923 return false;
924 if (!encoder.encodeArray<quint32>(src: src.arrayDimensions()))
925 return false;
926 if (!encoder.encode<QOpcUaLocalizedText>(src: src.description()))
927 return false;
928 m_data->append(a: temp);
929
930 return true;
931}
932
933template<typename T, QOpcUa::Types OVERLAY>
934inline QList<T> QOpcUaBinaryDataEncoding::decodeArray(bool &success)
935{
936 QList<T> temp;
937
938 qint32 size = decode<qint32>(success);
939 if (!success)
940 return temp;
941
942 for (int i = 0; i < size; ++i) {
943 temp.push_back(decode<T, OVERLAY>(success));
944 if (!success)
945 return QList<T>();
946 }
947
948 return temp;
949}
950
951template<typename T, QOpcUa::Types OVERLAY>
952inline bool QOpcUaBinaryDataEncoding::encodeArray(const QList<T> &src)
953{
954 if (src.size() > upperBound<qint32>())
955 return false;
956
957 if (!encode<qint32>(src: int(src.size())))
958 return false;
959 for (const auto &element : src) {
960 if (!encode<T, OVERLAY>(element))
961 return false;
962 }
963 return true;
964}
965
966template <>
967inline QOpcUaApplicationRecordDataType QOpcUaBinaryDataEncoding::decode<QOpcUaApplicationRecordDataType>(bool &success)
968{
969 QOpcUaApplicationRecordDataType temp;
970
971 temp.setApplicationId(decode<QString, QOpcUa::Types::NodeId>(success));
972 if (!success)
973 return QOpcUaApplicationRecordDataType();
974
975 temp.setApplicationUri(decode<QString>(success));
976 if (!success)
977 return QOpcUaApplicationRecordDataType();
978
979 temp.setApplicationType(static_cast<QOpcUaApplicationDescription::ApplicationType>(decode<uint32_t>(success)));
980 if (!success)
981 return QOpcUaApplicationRecordDataType();
982
983 temp.setApplicationNames(decodeArray<QOpcUaLocalizedText>(success));
984 if (!success)
985 return QOpcUaApplicationRecordDataType();
986
987 temp.setProductUri(decode<QString>(success));
988 if (!success)
989 return QOpcUaApplicationRecordDataType();
990
991 temp.setDiscoveryUrls(decodeArray<QString>(success));
992 if (!success)
993 return QOpcUaApplicationRecordDataType();
994
995 temp.setServerCapabilityIdentifiers(decodeArray<QString>(success));
996 if (!success)
997 return QOpcUaApplicationRecordDataType();
998
999 return temp;
1000}
1001
1002template <>
1003inline bool QOpcUaBinaryDataEncoding::encode<QOpcUaApplicationRecordDataType>(const QOpcUaApplicationRecordDataType &src)
1004{
1005 if (!encode<QString, QOpcUa::NodeId>(src: src.applicationId()))
1006 return false;
1007 if (!encode<QString>(src: src.applicationUri()))
1008 return false;
1009 if (!encode<uint32_t>(src: src.applicationType()))
1010 return false;
1011 if (!encodeArray<QOpcUaLocalizedText>(src: src.applicationNames()))
1012 return false;
1013 if (!encode<QString>(src: src.productUri()))
1014 return false;
1015 if (!encodeArray<QString>(src: src.discoveryUrls()))
1016 return false;
1017 if (!encodeArray<QString>(src: src.serverCapabilityIdentifiers()))
1018 return false;
1019 return true;
1020}
1021
1022QT_END_NAMESPACE
1023
1024#endif // QOPCUABINARYDATAENCODING_H
1025

source code of qtopcua/src/opcua/client/qopcuabinarydataencoding.h