l@270
|
1 /** @mainpage OSCPKT : a minimalistic OSC ( http://opensoundcontrol.org ) c++ library
|
l@270
|
2
|
l@270
|
3 Before using this file please take the time to read the OSC spec, it
|
l@270
|
4 is short and not complicated: http://opensoundcontrol.org/spec-1_0
|
l@270
|
5
|
l@270
|
6 Features:
|
l@270
|
7 - handles basic OSC types: TFihfdsb
|
l@270
|
8 - handles bundles
|
l@270
|
9 - handles OSC pattern-matching rules (wildcards etc in message paths)
|
l@270
|
10 - portable on win / macos / linux
|
l@270
|
11 - robust wrt malformed packets
|
l@270
|
12 - optional udp transport for packets
|
l@270
|
13 - concise, all in a single .h file
|
l@270
|
14 - does not throw exceptions
|
l@270
|
15
|
l@270
|
16 does not:
|
l@270
|
17 - take into account timestamp values.
|
l@270
|
18 - provide a cpu-scalable message dispatching.
|
l@270
|
19 - not suitable for use inside a realtime thread as it allocates memory when
|
l@270
|
20 building or reading messages.
|
l@270
|
21
|
l@270
|
22
|
l@270
|
23 There are basically 3 classes of interest:
|
l@270
|
24 - oscpkt::Message : read/write the content of an OSC message
|
l@270
|
25 - oscpkt::PacketReader : read the bundles/messages embedded in an OSC packet
|
l@270
|
26 - oscpkt::PacketWriter : write bundles/messages into an OSC packet
|
l@270
|
27
|
l@270
|
28 And optionaly:
|
l@270
|
29 - oscpkt::UdpSocket : read/write OSC packets over UDP.
|
l@270
|
30
|
l@270
|
31 @example: oscpkt_demo.cc
|
l@270
|
32 @example: oscpkt_test.cc
|
l@270
|
33 */
|
l@270
|
34
|
l@270
|
35 /* Copyright (C) 2010 Julien Pommier
|
l@270
|
36
|
l@270
|
37 This software is provided 'as-is', without any express or implied
|
l@270
|
38 warranty. In no event will the authors be held liable for any damages
|
l@270
|
39 arising from the use of this software.
|
l@270
|
40
|
l@270
|
41 Permission is granted to anyone to use this software for any purpose,
|
l@270
|
42 including commercial applications, and to alter it and redistribute it
|
l@270
|
43 freely, subject to the following restrictions:
|
l@270
|
44
|
l@270
|
45 1. The origin of this software must not be misrepresented; you must not
|
l@270
|
46 claim that you wrote the original software. If you use this software
|
l@270
|
47 in a product, an acknowledgment in the product documentation would be
|
l@270
|
48 appreciated but is not required.
|
l@270
|
49 2. Altered source versions must be plainly marked as such, and must not be
|
l@270
|
50 misrepresented as being the original software.
|
l@270
|
51 3. This notice may not be removed or altered from any source distribution.
|
l@270
|
52
|
l@270
|
53 (this is the zlib license)
|
l@270
|
54 */
|
l@270
|
55
|
l@270
|
56 #ifndef OSCPKT_HH
|
l@270
|
57 #define OSCPKT_HH
|
l@270
|
58
|
l@270
|
59 #ifndef _MSC_VER
|
l@270
|
60 #include <stdint.h>
|
l@270
|
61 #else
|
l@270
|
62 namespace oscpkt {
|
l@270
|
63 typedef __int32 int32_t;
|
l@270
|
64 typedef unsigned __int32 uint32_t;
|
l@270
|
65 typedef __int64 int64_t;
|
l@270
|
66 typedef unsigned __int64 uint64_t;
|
l@270
|
67 }
|
l@270
|
68 #endif
|
l@270
|
69 #include <cstring>
|
l@270
|
70 #include <cassert>
|
l@270
|
71 #include <string>
|
l@270
|
72 #include <vector>
|
l@270
|
73 #include <list>
|
l@270
|
74
|
l@270
|
75 #if defined(OSCPKT_OSTREAM_OUTPUT) || defined(OSCPKT_TEST)
|
l@270
|
76 #include <iostream>
|
l@270
|
77 #endif
|
l@270
|
78
|
l@270
|
79 namespace oscpkt {
|
l@270
|
80
|
l@270
|
81 /**
|
l@270
|
82 OSC timetag stuff, the highest 32-bit are seconds, the lowest are fraction of a second.
|
l@270
|
83 */
|
l@270
|
84 class TimeTag {
|
l@270
|
85 uint64_t v;
|
l@270
|
86 public:
|
l@270
|
87 TimeTag() : v(1) {}
|
l@270
|
88 explicit TimeTag(uint64_t w): v(w) {}
|
l@270
|
89 operator uint64_t() const { return v; }
|
l@270
|
90 static TimeTag immediate() { return TimeTag(1); }
|
l@270
|
91 };
|
l@270
|
92
|
l@270
|
93 /* the various types that we handle (OSC 1.0 specifies that INT32/FLOAT/STRING/BLOB are the bare minimum) */
|
l@270
|
94 enum {
|
l@270
|
95 TYPE_TAG_TRUE = 'T',
|
l@270
|
96 TYPE_TAG_FALSE = 'F',
|
l@270
|
97 TYPE_TAG_INT32 = 'i',
|
l@270
|
98 TYPE_TAG_INT64 = 'h',
|
l@270
|
99 TYPE_TAG_FLOAT = 'f',
|
l@270
|
100 TYPE_TAG_DOUBLE = 'd',
|
l@270
|
101 TYPE_TAG_STRING = 's',
|
l@270
|
102 TYPE_TAG_BLOB = 'b'
|
l@270
|
103 };
|
l@270
|
104
|
l@270
|
105 /* a few utility functions follow.. */
|
l@270
|
106
|
l@270
|
107 // round to the next multiple of 4, works for size_t and pointer arguments
|
l@270
|
108 template <typename Type> Type ceil4(Type p) { return (Type)((size_t(p) + 3)&(~size_t(3))); }
|
l@270
|
109
|
l@270
|
110 // check that a memory area is zero padded until the next address which is a multiple of 4
|
l@270
|
111 inline bool isZeroPaddingCorrect(const char *p) {
|
l@270
|
112 const char *q = ceil4(p);
|
l@270
|
113 for (;p < q; ++p)
|
l@270
|
114 if (*p != 0) { return false; }
|
l@270
|
115 return true;
|
l@270
|
116 }
|
l@270
|
117
|
l@270
|
118 // stuff for reading / writing POD ("Plain Old Data") variables to unaligned bytes.
|
l@270
|
119 template <typename POD> union PodBytes {
|
l@270
|
120 char bytes[sizeof(POD)];
|
l@270
|
121 POD value;
|
l@270
|
122 };
|
l@270
|
123
|
l@270
|
124 inline bool isBigEndian() { // a compile-time constant would certainly improve performances..
|
l@270
|
125 PodBytes<int32_t> p; p.value = 0x12345678;
|
l@270
|
126 return p.bytes[0] == 0x12;
|
l@270
|
127 }
|
l@270
|
128
|
l@270
|
129 /** read unaligned bytes into a POD type, assuming the bytes are a little endian representation */
|
l@270
|
130 template <typename POD> POD bytes2pod(const char *bytes) {
|
l@270
|
131 PodBytes<POD> p;
|
l@270
|
132 for (size_t i=0; i < sizeof(POD); ++i) {
|
l@270
|
133 if (isBigEndian())
|
l@270
|
134 p.bytes[i] = bytes[i];
|
l@270
|
135 else
|
l@270
|
136 p.bytes[i] = bytes[sizeof(POD) - i - 1];
|
l@270
|
137 }
|
l@270
|
138 return p.value;
|
l@270
|
139 }
|
l@270
|
140
|
l@270
|
141 /** stored a POD type into an unaligned bytes array, using little endian representation */
|
l@270
|
142 template <typename POD> void pod2bytes(const POD value, char *bytes) {
|
l@270
|
143 PodBytes<POD> p; p.value = value;
|
l@270
|
144 for (size_t i=0; i < sizeof(POD); ++i) {
|
l@270
|
145 if (isBigEndian())
|
l@270
|
146 bytes[i] = p.bytes[i];
|
l@270
|
147 else
|
l@270
|
148 bytes[i] = p.bytes[sizeof(POD) - i - 1];
|
l@270
|
149 }
|
l@270
|
150 }
|
l@270
|
151
|
l@270
|
152 /** internal stuff, handles the dynamic storage with correct alignments to 4 bytes */
|
l@270
|
153 struct Storage {
|
l@270
|
154 std::vector<char> data;
|
l@270
|
155 Storage() { data.reserve(200); }
|
l@270
|
156 char *getBytes(size_t sz) {
|
l@270
|
157 assert((data.size() & 3) == 0);
|
l@270
|
158 if (data.size() + sz > data.capacity()) { data.reserve((data.size() + sz)*2); }
|
l@270
|
159 size_t sz4 = ceil4(sz);
|
l@270
|
160 size_t pos = data.size();
|
l@270
|
161 data.resize(pos + sz4); // resize will fill with zeros, so the zero padding is OK
|
l@270
|
162 return &(data[pos]);
|
l@270
|
163 }
|
l@270
|
164 char *begin() { return data.size() ? &data.front() : 0; }
|
l@270
|
165 char *end() { return begin() + size(); }
|
l@270
|
166 const char *begin() const { return data.size() ? &data.front() : 0; }
|
l@270
|
167 const char *end() const { return begin() + size(); }
|
l@270
|
168 size_t size() const { return data.size(); }
|
l@270
|
169 void assign(const char *beg, const char *end) { data.assign(beg, end); }
|
l@270
|
170 void clear() { data.resize(0); }
|
l@270
|
171 };
|
l@270
|
172
|
l@270
|
173 /** check if the path matches the supplied path pattern , according to the OSC spec pattern
|
l@270
|
174 rules ('*' and '//' wildcards, '{}' alternatives, brackets etc) */
|
l@270
|
175 bool fullPatternMatch(const std::string &pattern, const std::string &path);
|
l@270
|
176 /** check if the path matches the beginning of pattern */
|
l@270
|
177 bool partialPatternMatch(const std::string &pattern, const std::string &path);
|
l@270
|
178
|
l@270
|
179 #if defined(OSCPKT_DEBUG)
|
l@270
|
180 #define OSCPKT_SET_ERR(errcode) do { if (!err) { err = errcode; std::cerr << "set " #errcode << " at line " << __LINE__ << "\n"; } } while (0)
|
l@270
|
181 #else
|
l@270
|
182 #define OSCPKT_SET_ERR(errcode) do { if (!err) err = errcode; } while (0)
|
l@270
|
183 #endif
|
l@270
|
184
|
l@270
|
185 typedef enum { OK_NO_ERROR=0,
|
l@270
|
186 // errors raised by the Message class:
|
l@270
|
187 MALFORMED_ADDRESS_PATTERN, MALFORMED_TYPE_TAGS, MALFORMED_ARGUMENTS, UNHANDLED_TYPE_TAGS,
|
l@270
|
188 // errors raised by ArgReader
|
l@270
|
189 TYPE_MISMATCH, NOT_ENOUGH_ARG, PATTERN_MISMATCH,
|
l@270
|
190 // errors raised by PacketReader/PacketWriter
|
l@270
|
191 INVALID_BUNDLE, INVALID_PACKET_SIZE, BUNDLE_REQUIRED_FOR_MULTI_MESSAGES } ErrorCode;
|
l@270
|
192
|
l@270
|
193 /**
|
l@270
|
194 struct used to hold an OSC message that will be written or read.
|
l@270
|
195
|
l@270
|
196 The list of arguments is exposed as a sort of queue. You "pop"
|
l@270
|
197 arguments from the front of the queue when reading, you push
|
l@270
|
198 arguments at the back of the queue when writing.
|
l@270
|
199
|
l@270
|
200 Many functions return *this, so they can be chained: init("/foo").pushInt32(2).pushStr("kllk")...
|
l@270
|
201
|
l@270
|
202 Example of use:
|
l@270
|
203
|
l@270
|
204 creation of a message:
|
l@270
|
205 @code
|
l@270
|
206 msg.init("/foo").pushInt32(4).pushStr("bar");
|
l@270
|
207 @endcode
|
l@270
|
208 reading a message, with error detection:
|
l@270
|
209 @code
|
l@270
|
210 if (msg.match("/foo/b*ar/plop")) {
|
l@270
|
211 int i; std::string s; std::vector<char> b;
|
l@270
|
212 if (msg.arg().popInt32(i).popStr(s).popBlob(b).isOkNoMoreArgs()) {
|
l@270
|
213 process message...;
|
l@270
|
214 } else arguments mismatch;
|
l@270
|
215 }
|
l@270
|
216 @endcode
|
l@270
|
217 */
|
l@270
|
218 class Message {
|
l@270
|
219 TimeTag time_tag;
|
l@270
|
220 std::string address;
|
l@270
|
221 std::string type_tags;
|
l@270
|
222 std::vector<std::pair<size_t, size_t> > arguments; // array of pairs (pos,size), pos being an index into the 'storage' array.
|
l@270
|
223 Storage storage; // the arguments data is stored here
|
l@270
|
224 ErrorCode err;
|
l@270
|
225 public:
|
l@270
|
226 /** ArgReader is used for popping arguments from a Message, holds a
|
l@270
|
227 pointer to the original Message, and maintains a local error code */
|
l@270
|
228 class ArgReader {
|
l@270
|
229 const Message *msg;
|
l@270
|
230 ErrorCode err;
|
l@270
|
231 size_t arg_idx; // arg index of the next arg that will be popped out.
|
l@270
|
232 public:
|
l@270
|
233 ArgReader(const Message &m, ErrorCode e = OK_NO_ERROR) : msg(&m), err(msg->getErr()), arg_idx(0) {
|
l@270
|
234 if (e != OK_NO_ERROR && err == OK_NO_ERROR) err=e;
|
l@270
|
235 }
|
l@270
|
236 ArgReader(const ArgReader &other) : msg(other.msg), err(other.err), arg_idx(other.arg_idx) {}
|
l@270
|
237 bool isBool() { return currentTypeTag() == TYPE_TAG_TRUE || currentTypeTag() == TYPE_TAG_FALSE; }
|
l@270
|
238 bool isInt32() { return currentTypeTag() == TYPE_TAG_INT32; }
|
l@270
|
239 bool isInt64() { return currentTypeTag() == TYPE_TAG_INT64; }
|
l@270
|
240 bool isFloat() { return currentTypeTag() == TYPE_TAG_FLOAT; }
|
l@270
|
241 bool isDouble() { return currentTypeTag() == TYPE_TAG_DOUBLE; }
|
l@270
|
242 bool isStr() { return currentTypeTag() == TYPE_TAG_STRING; }
|
l@270
|
243 bool isBlob() { return currentTypeTag() == TYPE_TAG_BLOB; }
|
l@270
|
244
|
l@270
|
245 size_t nbArgRemaining() const { return msg->arguments.size() - arg_idx; }
|
l@270
|
246 bool isOk() const { return err == OK_NO_ERROR; }
|
l@270
|
247 operator bool() const { return isOk(); } // implicit bool conversion is handy here
|
l@270
|
248 /** call this at the end of the popXXX() chain to make sure everything is ok and
|
l@270
|
249 all arguments have been popped */
|
l@270
|
250 bool isOkNoMoreArgs() const { return err == OK_NO_ERROR && nbArgRemaining() == 0; }
|
l@270
|
251 ErrorCode getErr() const { return err; }
|
l@270
|
252
|
l@270
|
253 /** retrieve an int32 argument */
|
l@270
|
254 ArgReader &popInt32(int32_t &i) { return popPod<int32_t>(TYPE_TAG_INT32, i); }
|
l@270
|
255 /** retrieve an int64 argument */
|
l@270
|
256 ArgReader &popInt64(int64_t &i) { return popPod<int64_t>(TYPE_TAG_INT64, i); }
|
l@270
|
257 /** retrieve a single precision floating point argument */
|
l@270
|
258 ArgReader &popFloat(float &f) { return popPod<float>(TYPE_TAG_FLOAT, f); }
|
l@270
|
259 /** retrieve a double precision floating point argument */
|
l@270
|
260 ArgReader &popDouble(double &d) { return popPod<double>(TYPE_TAG_DOUBLE, d); }
|
l@270
|
261 /** retrieve a string argument (no check performed on its content, so it may contain any byte value except 0) */
|
l@270
|
262 ArgReader &popStr(std::string &s) {
|
l@270
|
263 if (precheck(TYPE_TAG_STRING)) {
|
l@270
|
264 s = argBeg(arg_idx++);
|
l@270
|
265 }
|
l@270
|
266 return *this;
|
l@270
|
267 }
|
l@270
|
268 /** retrieve a binary blob */
|
l@270
|
269 ArgReader &popBlob(std::vector<char> &b) {
|
l@270
|
270 if (precheck(TYPE_TAG_BLOB)) {
|
l@270
|
271 b.assign(argBeg(arg_idx)+4, argEnd(arg_idx));
|
l@270
|
272 ++arg_idx;
|
l@270
|
273 }
|
l@270
|
274 return *this;
|
l@270
|
275 }
|
l@270
|
276 /** retrieve a boolean argument */
|
l@270
|
277 ArgReader &popBool(bool &b) {
|
l@270
|
278 b = false;
|
l@270
|
279 if (arg_idx >= msg->arguments.size()) OSCPKT_SET_ERR(NOT_ENOUGH_ARG);
|
l@270
|
280 else if (currentTypeTag() == TYPE_TAG_TRUE) b = true;
|
l@270
|
281 else if (currentTypeTag() == TYPE_TAG_FALSE) b = false;
|
l@270
|
282 else OSCPKT_SET_ERR(TYPE_MISMATCH);
|
l@270
|
283 ++arg_idx;
|
l@270
|
284 return *this;
|
l@270
|
285 }
|
l@270
|
286 /** skip whatever comes next */
|
l@270
|
287 ArgReader &pop() {
|
l@270
|
288 if (arg_idx >= msg->arguments.size()) OSCPKT_SET_ERR(NOT_ENOUGH_ARG);
|
l@270
|
289 else ++arg_idx;
|
l@270
|
290 return *this;
|
l@270
|
291 }
|
l@270
|
292 private:
|
l@270
|
293 const char *argBeg(size_t idx) {
|
l@270
|
294 if (err || idx >= msg->arguments.size()) return 0;
|
l@270
|
295 else return msg->storage.begin() + msg->arguments[idx].first;
|
l@270
|
296 }
|
l@270
|
297 const char *argEnd(size_t idx) {
|
l@270
|
298 if (err || idx >= msg->arguments.size()) return 0;
|
l@270
|
299 else return msg->storage.begin() + msg->arguments[idx].first + msg->arguments[idx].second;
|
l@270
|
300 }
|
l@270
|
301 int currentTypeTag() {
|
l@270
|
302 if (!err && arg_idx < msg->type_tags.size()) return msg->type_tags[arg_idx];
|
l@270
|
303 else OSCPKT_SET_ERR(NOT_ENOUGH_ARG);
|
l@270
|
304 return -1;
|
l@270
|
305 }
|
l@270
|
306 template <typename POD> ArgReader &popPod(int tag, POD &v) {
|
l@270
|
307 if (precheck(tag)) {
|
l@270
|
308 v = bytes2pod<POD>(argBeg(arg_idx));
|
l@270
|
309 ++arg_idx;
|
l@270
|
310 } else v = POD(0);
|
l@270
|
311 return *this;
|
l@270
|
312 }
|
l@270
|
313 /* pre-check stuff before popping an argument from the message */
|
l@270
|
314 bool precheck(int tag) {
|
l@270
|
315 if (arg_idx >= msg->arguments.size()) OSCPKT_SET_ERR(NOT_ENOUGH_ARG);
|
l@270
|
316 else if (!err && currentTypeTag() != tag) OSCPKT_SET_ERR(TYPE_MISMATCH);
|
l@270
|
317 return err == OK_NO_ERROR;
|
l@270
|
318 }
|
l@270
|
319 };
|
l@270
|
320
|
l@270
|
321 Message() { clear(); }
|
l@270
|
322 Message(const std::string &s, TimeTag tt = TimeTag::immediate()) : time_tag(tt), address(s), err(OK_NO_ERROR) {}
|
l@270
|
323 Message(const void *ptr, size_t sz, TimeTag tt = TimeTag::immediate()) { buildFromRawData(ptr, sz); time_tag = tt; }
|
l@270
|
324
|
l@270
|
325 bool isOk() const { return err == OK_NO_ERROR; }
|
l@270
|
326 ErrorCode getErr() const { return err; }
|
l@270
|
327
|
l@270
|
328 /** return the type_tags string, with its initial ',' stripped. */
|
l@270
|
329 const std::string &typeTags() const { return type_tags; }
|
l@270
|
330 /** retrieve the address pattern. If you want to follow to the whole OSC spec, you
|
l@270
|
331 have to handle its matching rules for address specifications -- this file does
|
l@270
|
332 not provide this functionality */
|
l@270
|
333 const std::string &addressPattern() const { return address; }
|
l@270
|
334 TimeTag timeTag() const { return time_tag; }
|
l@270
|
335 /** clear the message and start a new message with the supplied address and time_tag. */
|
l@270
|
336 Message &init(const std::string &addr, TimeTag tt = TimeTag::immediate()) {
|
l@270
|
337 clear();
|
l@270
|
338 address = addr; time_tag = tt;
|
l@270
|
339 if (address.empty() || address[0] != '/') OSCPKT_SET_ERR(MALFORMED_ADDRESS_PATTERN);
|
l@270
|
340 return *this;
|
l@270
|
341 }
|
l@270
|
342
|
l@270
|
343 /** start a matching test. The typical use-case is to follow this by
|
l@270
|
344 a sequence of calls to popXXX() and a final call to
|
l@270
|
345 isOkNoMoreArgs() which will allow to check that everything went
|
l@270
|
346 fine. For example:
|
l@270
|
347 @code
|
l@270
|
348 if (msg.match("/foo").popInt32(i).isOkNoMoreArgs()) { blah(i); }
|
l@270
|
349 else if (msg.match("/bar").popStr(s).popInt32(i).isOkNoMoreArgs()) { plop(s,i); }
|
l@270
|
350 else cerr << "unhandled message: " << msg << "\n";
|
l@270
|
351 @endcode
|
l@270
|
352 */
|
l@270
|
353 ArgReader match(const std::string &test) const {
|
l@270
|
354 return ArgReader(*this, fullPatternMatch(address.c_str(), test.c_str()) ? OK_NO_ERROR : PATTERN_MISMATCH);
|
l@270
|
355 }
|
l@270
|
356 /** return true if the 'test' path matched by the first characters of addressPattern().
|
l@270
|
357 For ex. ("/foo/bar").partialMatch("/foo/") is true */
|
l@270
|
358 ArgReader partialMatch(const std::string &test) const {
|
l@270
|
359 return ArgReader(*this, partialPatternMatch(address.c_str(), test.c_str()) ? OK_NO_ERROR : PATTERN_MISMATCH);
|
l@270
|
360 }
|
l@270
|
361 ArgReader arg() const { return ArgReader(*this, OK_NO_ERROR); }
|
l@270
|
362
|
l@270
|
363 /** build the osc message for raw data (the message will keep a copy of that data) */
|
l@270
|
364 void buildFromRawData(const void *ptr, size_t sz) {
|
l@270
|
365 clear();
|
l@270
|
366 storage.assign((const char*)ptr, (const char*)ptr + sz);
|
l@270
|
367 const char *address_beg = storage.begin();
|
l@270
|
368 const char *address_end = (const char*)memchr(address_beg, 0, storage.end()-address_beg);
|
l@270
|
369 if (!address_end || !isZeroPaddingCorrect(address_end+1) || address_beg[0] != '/') {
|
l@270
|
370 OSCPKT_SET_ERR(MALFORMED_ADDRESS_PATTERN); return;
|
l@270
|
371 } else address.assign(address_beg, address_end);
|
l@270
|
372
|
l@270
|
373 const char *type_tags_beg = ceil4(address_end+1);
|
l@270
|
374 const char *type_tags_end = (const char*)memchr(type_tags_beg, 0, storage.end()-type_tags_beg);
|
l@270
|
375 if (!type_tags_end || !isZeroPaddingCorrect(type_tags_end+1) || type_tags_beg[0] != ',') {
|
l@270
|
376 OSCPKT_SET_ERR(MALFORMED_TYPE_TAGS); return;
|
l@270
|
377 } else type_tags.assign(type_tags_beg+1, type_tags_end); // we do not copy the initial ','
|
l@270
|
378
|
l@270
|
379 const char *arg = ceil4(type_tags_end+1); assert(arg <= storage.end());
|
l@270
|
380 size_t iarg = 0;
|
l@270
|
381 while (isOk() && iarg < type_tags.size()) {
|
l@270
|
382 assert(arg <= storage.end());
|
l@270
|
383 size_t len = getArgSize(type_tags[iarg], arg);
|
l@270
|
384 if (isOk()) arguments.push_back(std::make_pair(arg - storage.begin(), len));
|
l@270
|
385 arg += ceil4(len); ++iarg;
|
l@270
|
386 }
|
l@270
|
387 if (iarg < type_tags.size() || arg != storage.end()) {
|
l@270
|
388 OSCPKT_SET_ERR(MALFORMED_ARGUMENTS);
|
l@270
|
389 }
|
l@270
|
390 }
|
l@270
|
391
|
l@270
|
392 /* below are all the functions that serve when *writing* a message */
|
l@270
|
393 Message &pushBool(bool b) {
|
l@270
|
394 type_tags += (b ? TYPE_TAG_TRUE : TYPE_TAG_FALSE);
|
l@270
|
395 arguments.push_back(std::make_pair(storage.size(), storage.size()));
|
l@270
|
396 return *this;
|
l@270
|
397 }
|
l@270
|
398 Message &pushInt32(int32_t i) { return pushPod(TYPE_TAG_INT32, i); }
|
l@270
|
399 Message &pushInt64(int64_t h) { return pushPod(TYPE_TAG_INT64, h); }
|
l@270
|
400 Message &pushFloat(float f) { return pushPod(TYPE_TAG_FLOAT, f); }
|
l@270
|
401 Message &pushDouble(double d) { return pushPod(TYPE_TAG_DOUBLE, d); }
|
l@270
|
402 Message &pushStr(const std::string &s) {
|
l@270
|
403 assert(s.size() < 2147483647); // insane values are not welcome
|
l@270
|
404 type_tags += TYPE_TAG_STRING;
|
l@270
|
405 arguments.push_back(std::make_pair(storage.size(), s.size() + 1));
|
l@270
|
406 strcpy(storage.getBytes(s.size()+1), s.c_str());
|
l@270
|
407 return *this;
|
l@270
|
408 }
|
l@270
|
409 Message &pushBlob(void *ptr, size_t num_bytes) {
|
l@270
|
410 assert(num_bytes < 2147483647); // insane values are not welcome
|
l@270
|
411 type_tags += TYPE_TAG_BLOB;
|
l@270
|
412 arguments.push_back(std::make_pair(storage.size(), num_bytes+4));
|
l@270
|
413 pod2bytes<int32_t>((int32_t)num_bytes, storage.getBytes(4));
|
l@270
|
414 if (num_bytes)
|
l@270
|
415 memcpy(storage.getBytes(num_bytes), ptr, num_bytes);
|
l@270
|
416 return *this;
|
l@270
|
417 }
|
l@270
|
418
|
l@270
|
419 /** reset the message to a clean state */
|
l@270
|
420 void clear() {
|
l@270
|
421 address.clear(); type_tags.clear(); storage.clear(); arguments.clear();
|
l@270
|
422 err = OK_NO_ERROR; time_tag = TimeTag::immediate();
|
l@270
|
423 }
|
l@270
|
424
|
l@270
|
425 /** write the raw message data (used by PacketWriter) */
|
l@270
|
426 void packMessage(Storage &s, bool write_size) const {
|
l@270
|
427 if (!isOk()) return;
|
l@270
|
428 size_t l_addr = address.size()+1, l_type = type_tags.size()+2;
|
l@270
|
429 if (write_size)
|
l@270
|
430 pod2bytes<uint32_t>(uint32_t(ceil4(l_addr) + ceil4(l_type) + ceil4(storage.size())), s.getBytes(4));
|
l@270
|
431 strcpy(s.getBytes(l_addr), address.c_str());
|
l@270
|
432 strcpy(s.getBytes(l_type), ("," + type_tags).c_str());
|
l@270
|
433 if (storage.size())
|
l@270
|
434 memcpy(s.getBytes(storage.size()), const_cast<Storage&>(storage).begin(), storage.size());
|
l@270
|
435 }
|
l@270
|
436
|
l@270
|
437 private:
|
l@270
|
438
|
l@270
|
439 /* get the number of bytes occupied by the argument */
|
l@270
|
440 size_t getArgSize(int type, const char *p) {
|
l@270
|
441 if (err) return 0;
|
l@270
|
442 size_t sz = 0;
|
l@270
|
443 assert(p >= storage.begin() && p <= storage.end());
|
l@270
|
444 switch (type) {
|
l@270
|
445 case TYPE_TAG_TRUE:
|
l@270
|
446 case TYPE_TAG_FALSE: sz = 0; break;
|
l@270
|
447 case TYPE_TAG_INT32:
|
l@270
|
448 case TYPE_TAG_FLOAT: sz = 4; break;
|
l@270
|
449 case TYPE_TAG_INT64:
|
l@270
|
450 case TYPE_TAG_DOUBLE: sz = 8; break;
|
l@270
|
451 case TYPE_TAG_STRING: {
|
l@270
|
452 const char *q = (const char*)memchr(p, 0, storage.end()-p);
|
l@270
|
453 if (!q) OSCPKT_SET_ERR(MALFORMED_ARGUMENTS);
|
l@270
|
454 else sz = (q-p)+1;
|
l@270
|
455 } break;
|
l@270
|
456 case TYPE_TAG_BLOB: {
|
l@270
|
457 if (p == storage.end()) { OSCPKT_SET_ERR(MALFORMED_ARGUMENTS); return 0; }
|
l@270
|
458 sz = 4+bytes2pod<uint32_t>(p);
|
l@270
|
459 } break;
|
l@270
|
460 default: {
|
l@270
|
461 OSCPKT_SET_ERR(UNHANDLED_TYPE_TAGS); return 0;
|
l@270
|
462 } break;
|
l@270
|
463 }
|
l@270
|
464 if (p+sz > storage.end() || /* string or blob too large.. */
|
l@270
|
465 p+sz < p /* or even blob so large that it did overflow */) {
|
l@270
|
466 OSCPKT_SET_ERR(MALFORMED_ARGUMENTS); return 0;
|
l@270
|
467 }
|
l@270
|
468 if (!isZeroPaddingCorrect(p+sz)) { OSCPKT_SET_ERR(MALFORMED_ARGUMENTS); return 0; }
|
l@270
|
469 return sz;
|
l@270
|
470 }
|
l@270
|
471
|
l@270
|
472 template <typename POD> Message &pushPod(int tag, POD v) {
|
l@270
|
473 type_tags += (char)tag;
|
l@270
|
474 arguments.push_back(std::make_pair(storage.size(), sizeof(POD)));
|
l@270
|
475 pod2bytes(v, storage.getBytes(sizeof(POD)));
|
l@270
|
476 return *this;
|
l@270
|
477 }
|
l@270
|
478
|
l@270
|
479 #ifdef OSCPKT_OSTREAM_OUTPUT
|
l@270
|
480 friend std::ostream &operator<<(std::ostream &os, const Message &msg) {
|
l@270
|
481 os << "osc_address: '" << msg.address << "', types: '" << msg.type_tags << "', timetag=" << msg.time_tag << ", args=[";
|
l@270
|
482 Message::ArgReader arg(msg);
|
l@270
|
483 while (arg.nbArgRemaining() && arg.isOk()) {
|
l@270
|
484 if (arg.isBool()) { bool b; arg.popBool(b); os << (b?"True":"False"); }
|
l@270
|
485 else if (arg.isInt32()) { int32_t i; arg.popInt32(i); os << i; }
|
l@270
|
486 else if (arg.isInt64()) { int64_t h; arg.popInt64(h); os << h << "ll"; }
|
l@270
|
487 else if (arg.isFloat()) { float f; arg.popFloat(f); os << f << "f"; }
|
l@270
|
488 else if (arg.isDouble()) { double d; arg.popDouble(d); os << d; }
|
l@270
|
489 else if (arg.isStr()) { std::string s; arg.popStr(s); os << "'" << s << "'"; }
|
l@270
|
490 else if (arg.isBlob()) { std::vector<char> b; arg.popBlob(b); os << "Blob " << b.size() << " bytes"; }
|
l@270
|
491 else {
|
l@270
|
492 assert(0); // I forgot a case..
|
l@270
|
493 }
|
l@270
|
494 if (arg.nbArgRemaining()) os << ", ";
|
l@270
|
495 }
|
l@270
|
496 if (!arg.isOk()) { os << " ERROR#" << arg.getErr(); }
|
l@270
|
497 os << "]";
|
l@270
|
498 return os;
|
l@270
|
499 }
|
l@270
|
500 #endif
|
l@270
|
501 };
|
l@270
|
502
|
l@270
|
503 /**
|
l@270
|
504 parse an OSC packet and extracts the embedded OSC messages.
|
l@270
|
505 */
|
l@270
|
506 class PacketReader {
|
l@270
|
507 public:
|
l@270
|
508 PacketReader() { err = OK_NO_ERROR; }
|
l@270
|
509 /** pointer and size of the osc packet to be parsed. */
|
l@270
|
510 PacketReader(const void *ptr, size_t sz) { init(ptr, sz); }
|
l@270
|
511
|
l@270
|
512 void init(const void *ptr, size_t sz) {
|
l@270
|
513 err = OK_NO_ERROR; messages.clear();
|
l@270
|
514 if ((sz%4) == 0) {
|
l@270
|
515 parse((const char*)ptr, (const char *)ptr+sz, TimeTag::immediate());
|
l@270
|
516 } else OSCPKT_SET_ERR(INVALID_PACKET_SIZE);
|
l@270
|
517 it_messages = messages.begin();
|
l@270
|
518 }
|
l@270
|
519
|
l@270
|
520 /** extract the next osc message from the packet. return 0 when all messages have been read, or in case of error. */
|
l@270
|
521 Message *popMessage() {
|
l@270
|
522 if (!err && !messages.empty() && it_messages != messages.end()) return &*it_messages++;
|
l@270
|
523 else return 0;
|
l@270
|
524 }
|
l@270
|
525 bool isOk() const { return err == OK_NO_ERROR; }
|
l@270
|
526 ErrorCode getErr() const { return err; }
|
l@270
|
527
|
l@270
|
528 private:
|
l@270
|
529 std::list<Message> messages;
|
l@270
|
530 std::list<Message>::iterator it_messages;
|
l@270
|
531 ErrorCode err;
|
l@270
|
532
|
l@270
|
533 void parse(const char *beg, const char *end, TimeTag time_tag) {
|
l@270
|
534 assert(beg <= end && !err); assert(((end-beg)%4)==0);
|
l@270
|
535
|
l@270
|
536 if (beg == end) return;
|
l@270
|
537 if (*beg == '#') {
|
l@270
|
538 /* it's a bundle */
|
l@270
|
539 if (end - beg >= 20
|
l@270
|
540 && memcmp(beg, "#bundle\0", 8) == 0) {
|
l@270
|
541 TimeTag time_tag2(bytes2pod<uint64_t>(beg+8));
|
l@270
|
542 const char *pos = beg + 16;
|
l@270
|
543 do {
|
l@270
|
544 uint32_t sz = bytes2pod<uint32_t>(pos); pos += 4;
|
l@270
|
545 if ((sz&3) != 0 || pos + sz > end || pos+sz < pos) {
|
l@270
|
546 OSCPKT_SET_ERR(INVALID_BUNDLE);
|
l@270
|
547 } else {
|
l@270
|
548 parse(pos, pos+sz, time_tag2);
|
l@270
|
549 pos += sz;
|
l@270
|
550 }
|
l@270
|
551 } while (!err && pos != end);
|
l@270
|
552 } else {
|
l@270
|
553 OSCPKT_SET_ERR(INVALID_BUNDLE);
|
l@270
|
554 }
|
l@270
|
555 } else {
|
l@270
|
556 messages.push_back(Message(beg, end-beg, time_tag));
|
l@270
|
557 if (!messages.back().isOk()) OSCPKT_SET_ERR(messages.back().getErr());
|
l@270
|
558 }
|
l@270
|
559 }
|
l@270
|
560 };
|
l@270
|
561
|
l@270
|
562
|
l@270
|
563 /**
|
l@270
|
564 Assemble messages into an OSC packet. Example of use:
|
l@270
|
565 @code
|
l@270
|
566 PacketWriter pkt;
|
l@270
|
567 Message msg;
|
l@270
|
568 pkt.startBundle();
|
l@270
|
569 pkt.addMessage(msg.init("/foo").pushBool(true).pushStr("plop").pushFloat(3.14f));
|
l@270
|
570 pkt.addMessage(msg.init("/bar").pushBool(false));
|
l@270
|
571 pkt.endBundle();
|
l@270
|
572 if (pkt.isOk()) {
|
l@270
|
573 send(pkt.data(), pkt.size());
|
l@270
|
574 }
|
l@270
|
575 @endcode
|
l@270
|
576 */
|
l@270
|
577 class PacketWriter {
|
l@270
|
578 public:
|
l@270
|
579 PacketWriter() { init(); }
|
l@270
|
580 PacketWriter &init() { err = OK_NO_ERROR; storage.clear(); bundles.clear(); return *this; }
|
l@270
|
581
|
l@270
|
582 /** begin a new bundle. If you plan to pack more than one message in the Osc packet, you have to
|
l@270
|
583 put them in a bundle. Nested bundles inside bundles are also allowed. */
|
l@270
|
584 PacketWriter &startBundle(TimeTag ts = TimeTag::immediate()) {
|
l@270
|
585 char *p;
|
l@270
|
586 if (bundles.size()) storage.getBytes(4); // hold the bundle size
|
l@270
|
587 p = storage.getBytes(8); strcpy(p, "#bundle"); bundles.push_back(p - storage.begin());
|
l@270
|
588 p = storage.getBytes(8); pod2bytes<uint64_t>(ts, p);
|
l@270
|
589 return *this;
|
l@270
|
590 }
|
l@270
|
591 /** close the current bundle. */
|
l@270
|
592 PacketWriter &endBundle() {
|
l@270
|
593 if (bundles.size()) {
|
l@270
|
594 if (storage.size() - bundles.back() == 16) {
|
l@270
|
595 pod2bytes<uint32_t>(0, storage.getBytes(4)); // the 'empty bundle' case, not very elegant
|
l@270
|
596 }
|
l@270
|
597 if (bundles.size()>1) { // no size stored for the top-level bundle
|
l@270
|
598 pod2bytes<uint32_t>(uint32_t(storage.size() - bundles.back()), storage.begin() + bundles.back()-4);
|
l@270
|
599 }
|
l@270
|
600 bundles.pop_back();
|
l@270
|
601 } else OSCPKT_SET_ERR(INVALID_BUNDLE);
|
l@270
|
602 return *this;
|
l@270
|
603 }
|
l@270
|
604
|
l@270
|
605 /** insert an Osc message into the current bundle / packet.
|
l@270
|
606 */
|
l@270
|
607 PacketWriter &addMessage(const Message &msg) {
|
l@270
|
608 if (storage.size() != 0 && bundles.empty()) OSCPKT_SET_ERR(BUNDLE_REQUIRED_FOR_MULTI_MESSAGES);
|
l@270
|
609 else msg.packMessage(storage, bundles.size()>0);
|
l@270
|
610 if (!msg.isOk()) OSCPKT_SET_ERR(msg.getErr());
|
l@270
|
611 return *this;
|
l@270
|
612 }
|
l@270
|
613
|
l@270
|
614 /** the error flag will be raised if an opened bundle is not closed, or if more than one message is
|
l@270
|
615 inserted in the packet without a bundle */
|
l@270
|
616 bool isOk() { return err == OK_NO_ERROR; }
|
l@270
|
617 ErrorCode getErr() { return err; }
|
l@270
|
618
|
l@270
|
619 /** return the number of bytes of the osc packet -- will always be a
|
l@270
|
620 multiple of 4 -- returns 0 if the construction of the packet has
|
l@270
|
621 failed. */
|
l@270
|
622 uint32_t packetSize() { return err ? 0 : (uint32_t)storage.size(); }
|
l@270
|
623
|
l@270
|
624 /** return the bytes of the osc packet (NULL if the construction of the packet has failed) */
|
l@270
|
625 char *packetData() { return err ? 0 : storage.begin(); }
|
l@270
|
626 private:
|
l@270
|
627 std::vector<size_t> bundles; // hold the position in the storage array of the beginning marker of each bundle
|
l@270
|
628 Storage storage;
|
l@270
|
629 ErrorCode err;
|
l@270
|
630 };
|
l@270
|
631
|
l@270
|
632 // see the OSC spec for the precise pattern matching rules
|
l@270
|
633 inline const char *internalPatternMatch(const char *pattern, const char *path) {
|
l@270
|
634 while (*pattern) {
|
l@270
|
635 const char *p = pattern;
|
l@270
|
636 if (*p == '?' && *path) { ++p; ++path; }
|
l@270
|
637 else if (*p == '[' && *path) { // bracketted range, e.g. [a-zABC]
|
l@270
|
638 ++p;
|
l@270
|
639 bool reverse = false;
|
l@270
|
640 if (*p == '!') { reverse = true; ++p; }
|
l@270
|
641 bool match = reverse;
|
l@270
|
642 for (; *p && *p != ']'; ++p) {
|
l@270
|
643 char c0 = *p, c1 = c0;
|
l@270
|
644 if (p[1] == '-' && p[2] && p[2] != ']') { p += 2; c1 = *p; }
|
l@270
|
645 if (*path >= c0 && *path <= c1) { match = !reverse; }
|
l@270
|
646 }
|
l@270
|
647 if (!match || *p != ']') return pattern;
|
l@270
|
648 ++p; ++path;
|
l@270
|
649 } else if (*p == '*') { // wildcard '*'
|
l@270
|
650 while (*p == '*') ++p;
|
l@270
|
651 const char *best = 0;
|
l@270
|
652 while (true) {
|
l@270
|
653 const char *ret = internalPatternMatch(p, path);
|
l@270
|
654 if (ret && ret > best) best = ret;
|
l@270
|
655 if (*path == 0 || *path == '/') break;
|
l@270
|
656 else ++path;
|
l@270
|
657 }
|
l@270
|
658 return best;
|
l@270
|
659 } else if (*p == '/' && *(p+1) == '/') { // the super-wildcard '//'
|
l@270
|
660 while (*(p+1)=='/') ++p;
|
l@270
|
661 const char *best = 0;
|
l@270
|
662 while (true) {
|
l@270
|
663 const char *ret = internalPatternMatch(p, path);
|
l@270
|
664 if (ret && ret > best) best = ret;
|
l@270
|
665 if (*path == 0) break;
|
l@270
|
666 if (*path == 0 || (path = strchr(path+1, '/')) == 0) break;
|
l@270
|
667 }
|
l@270
|
668 return best;
|
l@270
|
669 } else if (*p == '{') { // braced list {foo,bar,baz}
|
l@270
|
670 const char *end = strchr(p, '}'), *q;
|
l@270
|
671 if (!end) return 0; // syntax error in brace list..
|
l@270
|
672 bool match = false;
|
l@270
|
673 do {
|
l@270
|
674 ++p;
|
l@270
|
675 q = strchr(p, ',');
|
l@270
|
676 if (q == 0 || q > end) q = end;
|
l@270
|
677 if (strncmp(p, path, q-p)==0) {
|
l@270
|
678 path += (q-p); p = end+1; match = true;
|
l@270
|
679 } else p=q;
|
l@270
|
680 } while (q != end && !match);
|
l@270
|
681 if (!match) return pattern;
|
l@270
|
682 } else if (*p == *path) { ++p; ++path; } // any other character
|
l@270
|
683 else break;
|
l@270
|
684 pattern = p;
|
l@270
|
685 }
|
l@270
|
686 return (*path == 0 ? pattern : 0);
|
l@270
|
687 }
|
l@270
|
688
|
l@270
|
689 inline bool partialPatternMatch(const std::string &pattern, const std::string &test) {
|
l@270
|
690 const char *q = internalPatternMatch(pattern.c_str(), test.c_str());
|
l@270
|
691 return q != 0;
|
l@270
|
692 }
|
l@270
|
693
|
l@270
|
694 inline bool fullPatternMatch(const std::string &pattern, const std::string &test) {
|
l@270
|
695 const char *q = internalPatternMatch(pattern.c_str(), test.c_str());
|
l@270
|
696 return q && *q == 0;
|
l@270
|
697 }
|
l@270
|
698
|
l@270
|
699 } // namespace oscpkt
|
l@270
|
700
|
l@270
|
701 #endif // OSCPKT_HH
|