1/****************************************************************************
2**
3** Copyright (C) 2014 Robin Burchell <robin.burchell@viroteck.net>
4** Copyright (C) 2016 The Qt Company Ltd.
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the QtCore module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU Lesser General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU Lesser
20** General Public License version 3 as published by the Free Software
21** Foundation and appearing in the file LICENSE.LGPL3 included in the
22** packaging of this file. Please review the following information to
23** ensure the GNU Lesser General Public License version 3 requirements
24** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25**
26** GNU General Public License Usage
27** Alternatively, this file may be used under the terms of the GNU
28** General Public License version 2.0 or (at your option) the GNU General
29** Public license version 3 or any later version approved by the KDE Free
30** Qt Foundation. The licenses are as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32** included in the packaging of this file. Please review the following
33** information to ensure the GNU General Public License requirements will
34** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35** https://www.gnu.org/licenses/gpl-3.0.html.
36**
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41#include "qoscbundle_p.h"
42#include "qtuio_p.h"
43
44#include <QtEndian>
45#include <QDebug>
46#include <QLoggingCategory>
47
48
49QT_BEGIN_NAMESPACE
50
51Q_LOGGING_CATEGORY(lcTuioBundle, "qt.qpa.tuio.bundle")
52
53QOscBundle::QOscBundle() {}
54
55// TUIO packets are transmitted using the OSC protocol, located at:
56// http://opensoundcontrol.org/specification
57// Snippets of this specification have been pasted into the source as a means of
58// easily communicating requirements.
59
60QOscBundle::QOscBundle(const QByteArray &data)
61 : m_isValid(false)
62 , m_immediate(false)
63 , m_timeEpoch(0)
64 , m_timePico(0)
65{
66 // 8 16 24 32 40 48 56 64
67 // # b u n d l e \0
68 // 23 62 75 6e 64 6c 65 00 // OSC string bundle identifier
69 // 00 00 00 00 00 00 00 01 // osc time-tag, "immediately"
70 // 00 00 00 30 // element length
71 // => message or bundle(s), preceded by length each time
72 qCDebug(lcTuioBundle) << data.toHex();
73 quint32 parsedBytes = 0;
74
75 // "An OSC Bundle consists of the OSC-string "#bundle""
76 QByteArray identifier;
77 if (!qt_readOscString(data, identifier, parsedBytes) || identifier != "#bundle")
78 return;
79
80 // "followed by an OSC Time
81 // Tag, followed by zero or more OSC Bundle Elements. The OSC-timetag is a
82 // 64-bit fixed point time tag whose semantics are described below."
83 if (parsedBytes > (quint32)data.size() || data.size() - parsedBytes < qsizetype(sizeof(quint64)))
84 return;
85
86 // "Time tags are represented by a 64 bit fixed point number. The first 32
87 // bits specify the number of seconds since midnight on January 1, 1900,
88 // and the last 32 bits specify fractional parts of a second to a precision
89 // of about 200 picoseconds. This is the representation used by Internet NTP
90 // timestamps."
91 //
92 // (editor's note: one may wonder how a 64bit big-endian number can also be
93 // two 32bit numbers, without specifying in which order they occur or
94 // anything, and one may indeed continue to wonder.)
95 quint32 oscTimeEpoch = qFromBigEndian<quint32>(data.constData() + parsedBytes);
96 parsedBytes += sizeof(quint32);
97 quint32 oscTimePico = qFromBigEndian<quint32>(data.constData() + parsedBytes);
98 parsedBytes += sizeof(quint32);
99
100 bool isImmediate = false;
101
102 if (oscTimeEpoch == 0 && oscTimePico == 1) {
103 // "The time tag value consisting of 63 zero bits followed by a
104 // one in the least signifigant bit is a special case meaning
105 // "immediately.""
106 isImmediate = true;
107 }
108
109 while (parsedBytes < (quint32)data.size()) {
110 // "An OSC Bundle Element consists of its size and its contents. The size is an
111 // int32 representing the number of 8-bit bytes in the contents, and will
112 // always be a multiple of 4."
113 //
114 // in practice, a bundle can contain multiple bundles or messages,
115 // though, and each is prefixed by a size.
116 if (data.size() - parsedBytes < qsizetype(sizeof(quint32)))
117 return;
118
119 quint32 size = qFromBigEndian<quint32>((const uchar*)data.constData() + parsedBytes);
120 parsedBytes += sizeof(quint32);
121
122 if (data.size() - parsedBytes < size)
123 return;
124
125 if (size == 0) {
126 // empty bundle; these are valid, but should they be allowed? the
127 // spec is unclear on this...
128 qCWarning(lcTuioBundle, "Empty bundle?");
129 m_isValid = true;
130 m_immediate = isImmediate;
131 m_timeEpoch = oscTimeEpoch;
132 m_timePico = oscTimePico;
133 return;
134 }
135
136 // "The contents are either an OSC Message or an OSC Bundle.
137 // Note this recursive definition: bundle may contain bundles."
138 QByteArray subdata = data.mid(parsedBytes, size);
139 parsedBytes += size;
140
141 // "The contents of an OSC packet must be either an OSC Message or an OSC Bundle.
142 // The first byte of the packet's contents unambiguously distinguishes between
143 // these two alternatives."
144 //
145 // we're not dealing with a packet here, but the same trick works just
146 // the same.
147 QByteArray bundleIdentifier = QByteArray("#bundle\0", 8);
148 if (subdata.startsWith('/')) {
149 // starts with / => address pattern => start of a message
150 QOscMessage subMessage(subdata);
151 if (subMessage.isValid()) {
152 m_isValid = true;
153 m_immediate = isImmediate;
154 m_timeEpoch = oscTimeEpoch;
155 m_timePico = oscTimePico;
156 m_messages.append(subMessage);
157 } else {
158 qCWarning(lcTuioBundle, "Invalid sub-message");
159 return;
160 }
161 } else if (subdata.startsWith(bundleIdentifier)) {
162 // bundle identifier start => bundle
163 QOscBundle subBundle(subdata);
164 if (subBundle.isValid()) {
165 m_isValid = true;
166 m_immediate = isImmediate;
167 m_timeEpoch = oscTimeEpoch;
168 m_timePico = oscTimePico;
169 m_bundles.append(subBundle);
170 }
171 } else {
172 qCWarning(lcTuioBundle, "Malformed sub-data!");
173 return;
174 }
175 }
176}
177
178QT_END_NAMESPACE
179
180