1// Copyright (C) 2014 Robin Burchell <robin.burchell@viroteck.net>
2// Copyright (C) 2016 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qoscbundle_p.h"
6#include "qtuio_p.h"
7
8#include <QtEndian>
9#include <QDebug>
10#include <QLoggingCategory>
11
12
13QT_BEGIN_NAMESPACE
14
15Q_LOGGING_CATEGORY(lcTuioBundle, "qt.qpa.tuio.bundle")
16
17QOscBundle::QOscBundle() {}
18
19// TUIO packets are transmitted using the OSC protocol, located at:
20// http://opensoundcontrol.org/specification
21// Snippets of this specification have been pasted into the source as a means of
22// easily communicating requirements.
23
24QOscBundle::QOscBundle(const QByteArray &data)
25 : m_isValid(false)
26 , m_immediate(false)
27 , m_timeEpoch(0)
28 , m_timePico(0)
29{
30 // 8 16 24 32 40 48 56 64
31 // # b u n d l e \0
32 // 23 62 75 6e 64 6c 65 00 // OSC string bundle identifier
33 // 00 00 00 00 00 00 00 01 // osc time-tag, "immediately"
34 // 00 00 00 30 // element length
35 // => message or bundle(s), preceded by length each time
36 qCDebug(lcTuioBundle) << data.toHex();
37 quint32 parsedBytes = 0;
38
39 // "An OSC Bundle consists of the OSC-string "#bundle""
40 QByteArray identifier;
41 if (!qt_readOscString(source: data, dest&: identifier, pos&: parsedBytes) || identifier != "#bundle")
42 return;
43
44 // "followed by an OSC Time
45 // Tag, followed by zero or more OSC Bundle Elements. The OSC-timetag is a
46 // 64-bit fixed point time tag whose semantics are described below."
47 if (parsedBytes > (quint32)data.size() || data.size() - parsedBytes < qsizetype(sizeof(quint64)))
48 return;
49
50 // "Time tags are represented by a 64 bit fixed point number. The first 32
51 // bits specify the number of seconds since midnight on January 1, 1900,
52 // and the last 32 bits specify fractional parts of a second to a precision
53 // of about 200 picoseconds. This is the representation used by Internet NTP
54 // timestamps."
55 //
56 // (editor's note: one may wonder how a 64bit big-endian number can also be
57 // two 32bit numbers, without specifying in which order they occur or
58 // anything, and one may indeed continue to wonder.)
59 quint32 oscTimeEpoch = qFromBigEndian<quint32>(src: data.constData() + parsedBytes);
60 parsedBytes += sizeof(quint32);
61 quint32 oscTimePico = qFromBigEndian<quint32>(src: data.constData() + parsedBytes);
62 parsedBytes += sizeof(quint32);
63
64 bool isImmediate = false;
65
66 if (oscTimeEpoch == 0 && oscTimePico == 1) {
67 // "The time tag value consisting of 63 zero bits followed by a
68 // one in the least significant bit is a special case meaning
69 // "immediately.""
70 isImmediate = true;
71 }
72
73 while (parsedBytes < (quint32)data.size()) {
74 // "An OSC Bundle Element consists of its size and its contents. The size is an
75 // int32 representing the number of 8-bit bytes in the contents, and will
76 // always be a multiple of 4."
77 //
78 // in practice, a bundle can contain multiple bundles or messages,
79 // though, and each is prefixed by a size.
80 if (data.size() - parsedBytes < qsizetype(sizeof(quint32)))
81 return;
82
83 quint32 size = qFromBigEndian<quint32>(src: (const uchar*)data.constData() + parsedBytes);
84 parsedBytes += sizeof(quint32);
85
86 if (data.size() - parsedBytes < size)
87 return;
88
89 if (size == 0) {
90 // empty bundle; these are valid, but should they be allowed? the
91 // spec is unclear on this...
92 qCWarning(lcTuioBundle, "Empty bundle?");
93 m_isValid = true;
94 m_immediate = isImmediate;
95 m_timeEpoch = oscTimeEpoch;
96 m_timePico = oscTimePico;
97 return;
98 }
99
100 // "The contents are either an OSC Message or an OSC Bundle.
101 // Note this recursive definition: bundle may contain bundles."
102 QByteArray subdata = data.mid(index: parsedBytes, len: size);
103 parsedBytes += size;
104
105 // "The contents of an OSC packet must be either an OSC Message or an OSC Bundle.
106 // The first byte of the packet's contents unambiguously distinguishes between
107 // these two alternatives."
108 //
109 // we're not dealing with a packet here, but the same trick works just
110 // the same.
111 QByteArray bundleIdentifier = QByteArray("#bundle\0", 8);
112 if (subdata.startsWith(c: '/')) {
113 // starts with / => address pattern => start of a message
114 QOscMessage subMessage(subdata);
115 if (subMessage.isValid()) {
116 m_isValid = true;
117 m_immediate = isImmediate;
118 m_timeEpoch = oscTimeEpoch;
119 m_timePico = oscTimePico;
120 m_messages.append(t: subMessage);
121 } else {
122 qCWarning(lcTuioBundle, "Invalid sub-message");
123 return;
124 }
125 } else if (subdata.startsWith(bv: bundleIdentifier)) {
126 // bundle identifier start => bundle
127 QOscBundle subBundle(subdata);
128 if (subBundle.isValid()) {
129 m_isValid = true;
130 m_immediate = isImmediate;
131 m_timeEpoch = oscTimeEpoch;
132 m_timePico = oscTimePico;
133 m_bundles.append(t: subBundle);
134 }
135 } else {
136 qCWarning(lcTuioBundle, "Malformed sub-data!");
137 return;
138 }
139 }
140}
141
142QT_END_NAMESPACE
143
144

source code of qtbase/src/plugins/generic/tuiotouch/qoscbundle.cpp