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