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 | |
30 | QT_BEGIN_NAMESPACE |
31 | |
32 | // This class implements a subset of the OPC UA Binary DataEncoding defined in OPC-UA part 6, 5.2. |
33 | class Q_OPCUA_EXPORT QOpcUaBinaryDataEncoding |
34 | { |
35 | public: |
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 | |
55 | private: |
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 | |
64 | template<typename T> |
65 | T QOpcUaBinaryDataEncoding::upperBound() |
66 | { |
67 | // Use extra parentheses to prevent macro substitution for max() on windows |
68 | return (std::numeric_limits<T>::max)(); |
69 | } |
70 | |
71 | template<typename T, QOpcUa::Types OVERLAY> |
72 | inline 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 | |
94 | template<> |
95 | inline 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 | |
113 | template<> |
114 | inline 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 | |
145 | template <> |
146 | inline 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 | |
160 | template <> |
161 | inline 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 | |
181 | template <> |
182 | inline 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 | |
205 | template <> |
206 | inline 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 | |
221 | template <> |
222 | inline 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 | |
237 | template <> |
238 | inline 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 | |
253 | template <> |
254 | inline 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 | |
281 | template <> |
282 | inline 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 | |
297 | template <> |
298 | inline 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 | |
337 | template <> |
338 | inline 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 | |
363 | template <> |
364 | inline 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 | |
432 | template <> |
433 | inline 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 | |
469 | template <> |
470 | inline 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 | |
484 | template <> |
485 | inline 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 | |
494 | template <> |
495 | inline 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 | |
521 | template <> |
522 | inline 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 | |
549 | template<typename T, QOpcUa::Types OVERLAY> |
550 | inline 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 | |
563 | template<> |
564 | inline 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 | |
574 | template<> |
575 | inline 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 | |
590 | template<> |
591 | inline 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 | |
600 | template<> |
601 | inline 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 | |
619 | template <> |
620 | inline 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 | |
629 | template <> |
630 | inline 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 | |
643 | template <> |
644 | inline 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 | |
653 | template <> |
654 | inline 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 | |
663 | template <> |
664 | inline 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 | |
679 | template <> |
680 | inline 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 | |
689 | template <> |
690 | inline 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 | |
705 | template <> |
706 | inline 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 | |
721 | template <> |
722 | inline 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 | |
831 | template <> |
832 | inline 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 | |
862 | template <> |
863 | inline 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 | |
886 | template <> |
887 | inline bool QOpcUaBinaryDataEncoding::encode<QOpcUa::UaStatusCode>(const QOpcUa::UaStatusCode &src) |
888 | { |
889 | if (!encode<quint32>(src)) |
890 | return false; |
891 | return true; |
892 | } |
893 | |
894 | template <> |
895 | inline 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 | |
909 | template <> |
910 | inline 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 | |
933 | template<typename T, QOpcUa::Types OVERLAY> |
934 | inline 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 | |
951 | template<typename T, QOpcUa::Types OVERLAY> |
952 | inline 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 | |
966 | template <> |
967 | inline 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 | |
1002 | template <> |
1003 | inline 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 | |
1022 | QT_END_NAMESPACE |
1023 | |
1024 | #endif // QOPCUABINARYDATAENCODING_H |
1025 | |