l@270: /** @mainpage OSCPKT : a minimalistic OSC ( http://opensoundcontrol.org ) c++ library l@270: l@270: Before using this file please take the time to read the OSC spec, it l@270: is short and not complicated: http://opensoundcontrol.org/spec-1_0 l@270: l@270: Features: l@270: - handles basic OSC types: TFihfdsb l@270: - handles bundles l@270: - handles OSC pattern-matching rules (wildcards etc in message paths) l@270: - portable on win / macos / linux l@270: - robust wrt malformed packets l@270: - optional udp transport for packets l@270: - concise, all in a single .h file l@270: - does not throw exceptions l@270: l@270: does not: l@270: - take into account timestamp values. l@270: - provide a cpu-scalable message dispatching. l@270: - not suitable for use inside a realtime thread as it allocates memory when l@270: building or reading messages. l@270: l@270: l@270: There are basically 3 classes of interest: l@270: - oscpkt::Message : read/write the content of an OSC message l@270: - oscpkt::PacketReader : read the bundles/messages embedded in an OSC packet l@270: - oscpkt::PacketWriter : write bundles/messages into an OSC packet l@270: l@270: And optionaly: l@270: - oscpkt::UdpSocket : read/write OSC packets over UDP. l@270: l@270: @example: oscpkt_demo.cc l@270: @example: oscpkt_test.cc l@270: */ l@270: l@270: /* Copyright (C) 2010 Julien Pommier l@270: l@270: This software is provided 'as-is', without any express or implied l@270: warranty. In no event will the authors be held liable for any damages l@270: arising from the use of this software. l@270: l@270: Permission is granted to anyone to use this software for any purpose, l@270: including commercial applications, and to alter it and redistribute it l@270: freely, subject to the following restrictions: l@270: l@270: 1. The origin of this software must not be misrepresented; you must not l@270: claim that you wrote the original software. If you use this software l@270: in a product, an acknowledgment in the product documentation would be l@270: appreciated but is not required. l@270: 2. Altered source versions must be plainly marked as such, and must not be l@270: misrepresented as being the original software. l@270: 3. This notice may not be removed or altered from any source distribution. l@270: l@270: (this is the zlib license) l@270: */ l@270: l@270: #ifndef OSCPKT_HH l@270: #define OSCPKT_HH l@270: l@270: #ifndef _MSC_VER l@270: #include l@270: #else l@270: namespace oscpkt { l@270: typedef __int32 int32_t; l@270: typedef unsigned __int32 uint32_t; l@270: typedef __int64 int64_t; l@270: typedef unsigned __int64 uint64_t; l@270: } l@270: #endif l@270: #include l@270: #include l@270: #include l@270: #include l@270: #include l@270: l@270: #if defined(OSCPKT_OSTREAM_OUTPUT) || defined(OSCPKT_TEST) l@270: #include l@270: #endif l@270: l@270: namespace oscpkt { l@270: l@270: /** l@270: OSC timetag stuff, the highest 32-bit are seconds, the lowest are fraction of a second. l@270: */ l@270: class TimeTag { l@270: uint64_t v; l@270: public: l@270: TimeTag() : v(1) {} l@270: explicit TimeTag(uint64_t w): v(w) {} l@270: operator uint64_t() const { return v; } l@270: static TimeTag immediate() { return TimeTag(1); } l@270: }; l@270: l@270: /* the various types that we handle (OSC 1.0 specifies that INT32/FLOAT/STRING/BLOB are the bare minimum) */ l@270: enum { l@270: TYPE_TAG_TRUE = 'T', l@270: TYPE_TAG_FALSE = 'F', l@270: TYPE_TAG_INT32 = 'i', l@270: TYPE_TAG_INT64 = 'h', l@270: TYPE_TAG_FLOAT = 'f', l@270: TYPE_TAG_DOUBLE = 'd', l@270: TYPE_TAG_STRING = 's', l@270: TYPE_TAG_BLOB = 'b' l@270: }; l@270: l@270: /* a few utility functions follow.. */ l@270: l@270: // round to the next multiple of 4, works for size_t and pointer arguments l@270: template Type ceil4(Type p) { return (Type)((size_t(p) + 3)&(~size_t(3))); } l@270: l@270: // check that a memory area is zero padded until the next address which is a multiple of 4 l@270: inline bool isZeroPaddingCorrect(const char *p) { l@270: const char *q = ceil4(p); l@270: for (;p < q; ++p) l@270: if (*p != 0) { return false; } l@270: return true; l@270: } l@270: l@270: // stuff for reading / writing POD ("Plain Old Data") variables to unaligned bytes. l@270: template union PodBytes { l@270: char bytes[sizeof(POD)]; l@270: POD value; l@270: }; l@270: l@270: inline bool isBigEndian() { // a compile-time constant would certainly improve performances.. l@270: PodBytes p; p.value = 0x12345678; l@270: return p.bytes[0] == 0x12; l@270: } l@270: l@270: /** read unaligned bytes into a POD type, assuming the bytes are a little endian representation */ l@270: template POD bytes2pod(const char *bytes) { l@270: PodBytes p; l@270: for (size_t i=0; i < sizeof(POD); ++i) { l@270: if (isBigEndian()) l@270: p.bytes[i] = bytes[i]; l@270: else l@270: p.bytes[i] = bytes[sizeof(POD) - i - 1]; l@270: } l@270: return p.value; l@270: } l@270: l@270: /** stored a POD type into an unaligned bytes array, using little endian representation */ l@270: template void pod2bytes(const POD value, char *bytes) { l@270: PodBytes p; p.value = value; l@270: for (size_t i=0; i < sizeof(POD); ++i) { l@270: if (isBigEndian()) l@270: bytes[i] = p.bytes[i]; l@270: else l@270: bytes[i] = p.bytes[sizeof(POD) - i - 1]; l@270: } l@270: } l@270: l@270: /** internal stuff, handles the dynamic storage with correct alignments to 4 bytes */ l@270: struct Storage { l@270: std::vector data; l@270: Storage() { data.reserve(200); } l@270: char *getBytes(size_t sz) { l@270: assert((data.size() & 3) == 0); l@270: if (data.size() + sz > data.capacity()) { data.reserve((data.size() + sz)*2); } l@270: size_t sz4 = ceil4(sz); l@270: size_t pos = data.size(); l@270: data.resize(pos + sz4); // resize will fill with zeros, so the zero padding is OK l@270: return &(data[pos]); l@270: } l@270: char *begin() { return data.size() ? &data.front() : 0; } l@270: char *end() { return begin() + size(); } l@270: const char *begin() const { return data.size() ? &data.front() : 0; } l@270: const char *end() const { return begin() + size(); } l@270: size_t size() const { return data.size(); } l@270: void assign(const char *beg, const char *end) { data.assign(beg, end); } l@270: void clear() { data.resize(0); } l@270: }; l@270: l@270: /** check if the path matches the supplied path pattern , according to the OSC spec pattern l@270: rules ('*' and '//' wildcards, '{}' alternatives, brackets etc) */ l@270: bool fullPatternMatch(const std::string &pattern, const std::string &path); l@270: /** check if the path matches the beginning of pattern */ l@270: bool partialPatternMatch(const std::string &pattern, const std::string &path); l@270: l@270: #if defined(OSCPKT_DEBUG) l@270: #define OSCPKT_SET_ERR(errcode) do { if (!err) { err = errcode; std::cerr << "set " #errcode << " at line " << __LINE__ << "\n"; } } while (0) l@270: #else l@270: #define OSCPKT_SET_ERR(errcode) do { if (!err) err = errcode; } while (0) l@270: #endif l@270: l@270: typedef enum { OK_NO_ERROR=0, l@270: // errors raised by the Message class: l@270: MALFORMED_ADDRESS_PATTERN, MALFORMED_TYPE_TAGS, MALFORMED_ARGUMENTS, UNHANDLED_TYPE_TAGS, l@270: // errors raised by ArgReader l@270: TYPE_MISMATCH, NOT_ENOUGH_ARG, PATTERN_MISMATCH, l@270: // errors raised by PacketReader/PacketWriter l@270: INVALID_BUNDLE, INVALID_PACKET_SIZE, BUNDLE_REQUIRED_FOR_MULTI_MESSAGES } ErrorCode; l@270: l@270: /** l@270: struct used to hold an OSC message that will be written or read. l@270: l@270: The list of arguments is exposed as a sort of queue. You "pop" l@270: arguments from the front of the queue when reading, you push l@270: arguments at the back of the queue when writing. l@270: l@270: Many functions return *this, so they can be chained: init("/foo").pushInt32(2).pushStr("kllk")... l@270: l@270: Example of use: l@270: l@270: creation of a message: l@270: @code l@270: msg.init("/foo").pushInt32(4).pushStr("bar"); l@270: @endcode l@270: reading a message, with error detection: l@270: @code l@270: if (msg.match("/foo/b*ar/plop")) { l@270: int i; std::string s; std::vector b; l@270: if (msg.arg().popInt32(i).popStr(s).popBlob(b).isOkNoMoreArgs()) { l@270: process message...; l@270: } else arguments mismatch; l@270: } l@270: @endcode l@270: */ l@270: class Message { l@270: TimeTag time_tag; l@270: std::string address; l@270: std::string type_tags; l@270: std::vector > arguments; // array of pairs (pos,size), pos being an index into the 'storage' array. l@270: Storage storage; // the arguments data is stored here l@270: ErrorCode err; l@270: public: l@270: /** ArgReader is used for popping arguments from a Message, holds a l@270: pointer to the original Message, and maintains a local error code */ l@270: class ArgReader { l@270: const Message *msg; l@270: ErrorCode err; l@270: size_t arg_idx; // arg index of the next arg that will be popped out. l@270: public: l@270: ArgReader(const Message &m, ErrorCode e = OK_NO_ERROR) : msg(&m), err(msg->getErr()), arg_idx(0) { l@270: if (e != OK_NO_ERROR && err == OK_NO_ERROR) err=e; l@270: } l@270: ArgReader(const ArgReader &other) : msg(other.msg), err(other.err), arg_idx(other.arg_idx) {} l@270: bool isBool() { return currentTypeTag() == TYPE_TAG_TRUE || currentTypeTag() == TYPE_TAG_FALSE; } l@270: bool isInt32() { return currentTypeTag() == TYPE_TAG_INT32; } l@270: bool isInt64() { return currentTypeTag() == TYPE_TAG_INT64; } l@270: bool isFloat() { return currentTypeTag() == TYPE_TAG_FLOAT; } l@270: bool isDouble() { return currentTypeTag() == TYPE_TAG_DOUBLE; } l@270: bool isStr() { return currentTypeTag() == TYPE_TAG_STRING; } l@270: bool isBlob() { return currentTypeTag() == TYPE_TAG_BLOB; } l@270: l@270: size_t nbArgRemaining() const { return msg->arguments.size() - arg_idx; } l@270: bool isOk() const { return err == OK_NO_ERROR; } l@270: operator bool() const { return isOk(); } // implicit bool conversion is handy here l@270: /** call this at the end of the popXXX() chain to make sure everything is ok and l@270: all arguments have been popped */ l@270: bool isOkNoMoreArgs() const { return err == OK_NO_ERROR && nbArgRemaining() == 0; } l@270: ErrorCode getErr() const { return err; } l@270: l@270: /** retrieve an int32 argument */ l@270: ArgReader &popInt32(int32_t &i) { return popPod(TYPE_TAG_INT32, i); } l@270: /** retrieve an int64 argument */ l@270: ArgReader &popInt64(int64_t &i) { return popPod(TYPE_TAG_INT64, i); } l@270: /** retrieve a single precision floating point argument */ l@270: ArgReader &popFloat(float &f) { return popPod(TYPE_TAG_FLOAT, f); } l@270: /** retrieve a double precision floating point argument */ l@270: ArgReader &popDouble(double &d) { return popPod(TYPE_TAG_DOUBLE, d); } l@270: /** retrieve a string argument (no check performed on its content, so it may contain any byte value except 0) */ l@270: ArgReader &popStr(std::string &s) { l@270: if (precheck(TYPE_TAG_STRING)) { l@270: s = argBeg(arg_idx++); l@270: } l@270: return *this; l@270: } l@270: /** retrieve a binary blob */ l@270: ArgReader &popBlob(std::vector &b) { l@270: if (precheck(TYPE_TAG_BLOB)) { l@270: b.assign(argBeg(arg_idx)+4, argEnd(arg_idx)); l@270: ++arg_idx; l@270: } l@270: return *this; l@270: } l@270: /** retrieve a boolean argument */ l@270: ArgReader &popBool(bool &b) { l@270: b = false; l@270: if (arg_idx >= msg->arguments.size()) OSCPKT_SET_ERR(NOT_ENOUGH_ARG); l@270: else if (currentTypeTag() == TYPE_TAG_TRUE) b = true; l@270: else if (currentTypeTag() == TYPE_TAG_FALSE) b = false; l@270: else OSCPKT_SET_ERR(TYPE_MISMATCH); l@270: ++arg_idx; l@270: return *this; l@270: } l@270: /** skip whatever comes next */ l@270: ArgReader &pop() { l@270: if (arg_idx >= msg->arguments.size()) OSCPKT_SET_ERR(NOT_ENOUGH_ARG); l@270: else ++arg_idx; l@270: return *this; l@270: } l@270: private: l@270: const char *argBeg(size_t idx) { l@270: if (err || idx >= msg->arguments.size()) return 0; l@270: else return msg->storage.begin() + msg->arguments[idx].first; l@270: } l@270: const char *argEnd(size_t idx) { l@270: if (err || idx >= msg->arguments.size()) return 0; l@270: else return msg->storage.begin() + msg->arguments[idx].first + msg->arguments[idx].second; l@270: } l@270: int currentTypeTag() { l@270: if (!err && arg_idx < msg->type_tags.size()) return msg->type_tags[arg_idx]; l@270: else OSCPKT_SET_ERR(NOT_ENOUGH_ARG); l@270: return -1; l@270: } l@270: template ArgReader &popPod(int tag, POD &v) { l@270: if (precheck(tag)) { l@270: v = bytes2pod(argBeg(arg_idx)); l@270: ++arg_idx; l@270: } else v = POD(0); l@270: return *this; l@270: } l@270: /* pre-check stuff before popping an argument from the message */ l@270: bool precheck(int tag) { l@270: if (arg_idx >= msg->arguments.size()) OSCPKT_SET_ERR(NOT_ENOUGH_ARG); l@270: else if (!err && currentTypeTag() != tag) OSCPKT_SET_ERR(TYPE_MISMATCH); l@270: return err == OK_NO_ERROR; l@270: } l@270: }; l@270: l@270: Message() { clear(); } l@270: Message(const std::string &s, TimeTag tt = TimeTag::immediate()) : time_tag(tt), address(s), err(OK_NO_ERROR) {} l@270: Message(const void *ptr, size_t sz, TimeTag tt = TimeTag::immediate()) { buildFromRawData(ptr, sz); time_tag = tt; } l@270: l@270: bool isOk() const { return err == OK_NO_ERROR; } l@270: ErrorCode getErr() const { return err; } l@270: l@270: /** return the type_tags string, with its initial ',' stripped. */ l@270: const std::string &typeTags() const { return type_tags; } l@270: /** retrieve the address pattern. If you want to follow to the whole OSC spec, you l@270: have to handle its matching rules for address specifications -- this file does l@270: not provide this functionality */ l@270: const std::string &addressPattern() const { return address; } l@270: TimeTag timeTag() const { return time_tag; } l@270: /** clear the message and start a new message with the supplied address and time_tag. */ l@270: Message &init(const std::string &addr, TimeTag tt = TimeTag::immediate()) { l@270: clear(); l@270: address = addr; time_tag = tt; l@270: if (address.empty() || address[0] != '/') OSCPKT_SET_ERR(MALFORMED_ADDRESS_PATTERN); l@270: return *this; l@270: } l@270: l@270: /** start a matching test. The typical use-case is to follow this by l@270: a sequence of calls to popXXX() and a final call to l@270: isOkNoMoreArgs() which will allow to check that everything went l@270: fine. For example: l@270: @code l@270: if (msg.match("/foo").popInt32(i).isOkNoMoreArgs()) { blah(i); } l@270: else if (msg.match("/bar").popStr(s).popInt32(i).isOkNoMoreArgs()) { plop(s,i); } l@270: else cerr << "unhandled message: " << msg << "\n"; l@270: @endcode l@270: */ l@270: ArgReader match(const std::string &test) const { l@270: return ArgReader(*this, fullPatternMatch(address.c_str(), test.c_str()) ? OK_NO_ERROR : PATTERN_MISMATCH); l@270: } l@270: /** return true if the 'test' path matched by the first characters of addressPattern(). l@270: For ex. ("/foo/bar").partialMatch("/foo/") is true */ l@270: ArgReader partialMatch(const std::string &test) const { l@270: return ArgReader(*this, partialPatternMatch(address.c_str(), test.c_str()) ? OK_NO_ERROR : PATTERN_MISMATCH); l@270: } l@270: ArgReader arg() const { return ArgReader(*this, OK_NO_ERROR); } l@270: l@270: /** build the osc message for raw data (the message will keep a copy of that data) */ l@270: void buildFromRawData(const void *ptr, size_t sz) { l@270: clear(); l@270: storage.assign((const char*)ptr, (const char*)ptr + sz); l@270: const char *address_beg = storage.begin(); l@270: const char *address_end = (const char*)memchr(address_beg, 0, storage.end()-address_beg); l@270: if (!address_end || !isZeroPaddingCorrect(address_end+1) || address_beg[0] != '/') { l@270: OSCPKT_SET_ERR(MALFORMED_ADDRESS_PATTERN); return; l@270: } else address.assign(address_beg, address_end); l@270: l@270: const char *type_tags_beg = ceil4(address_end+1); l@270: const char *type_tags_end = (const char*)memchr(type_tags_beg, 0, storage.end()-type_tags_beg); l@270: if (!type_tags_end || !isZeroPaddingCorrect(type_tags_end+1) || type_tags_beg[0] != ',') { l@270: OSCPKT_SET_ERR(MALFORMED_TYPE_TAGS); return; l@270: } else type_tags.assign(type_tags_beg+1, type_tags_end); // we do not copy the initial ',' l@270: l@270: const char *arg = ceil4(type_tags_end+1); assert(arg <= storage.end()); l@270: size_t iarg = 0; l@270: while (isOk() && iarg < type_tags.size()) { l@270: assert(arg <= storage.end()); l@270: size_t len = getArgSize(type_tags[iarg], arg); l@270: if (isOk()) arguments.push_back(std::make_pair(arg - storage.begin(), len)); l@270: arg += ceil4(len); ++iarg; l@270: } l@270: if (iarg < type_tags.size() || arg != storage.end()) { l@270: OSCPKT_SET_ERR(MALFORMED_ARGUMENTS); l@270: } l@270: } l@270: l@270: /* below are all the functions that serve when *writing* a message */ l@270: Message &pushBool(bool b) { l@270: type_tags += (b ? TYPE_TAG_TRUE : TYPE_TAG_FALSE); l@270: arguments.push_back(std::make_pair(storage.size(), storage.size())); l@270: return *this; l@270: } l@270: Message &pushInt32(int32_t i) { return pushPod(TYPE_TAG_INT32, i); } l@270: Message &pushInt64(int64_t h) { return pushPod(TYPE_TAG_INT64, h); } l@270: Message &pushFloat(float f) { return pushPod(TYPE_TAG_FLOAT, f); } l@270: Message &pushDouble(double d) { return pushPod(TYPE_TAG_DOUBLE, d); } l@270: Message &pushStr(const std::string &s) { l@270: assert(s.size() < 2147483647); // insane values are not welcome l@270: type_tags += TYPE_TAG_STRING; l@270: arguments.push_back(std::make_pair(storage.size(), s.size() + 1)); l@270: strcpy(storage.getBytes(s.size()+1), s.c_str()); l@270: return *this; l@270: } l@270: Message &pushBlob(void *ptr, size_t num_bytes) { l@270: assert(num_bytes < 2147483647); // insane values are not welcome l@270: type_tags += TYPE_TAG_BLOB; l@270: arguments.push_back(std::make_pair(storage.size(), num_bytes+4)); l@270: pod2bytes((int32_t)num_bytes, storage.getBytes(4)); l@270: if (num_bytes) l@270: memcpy(storage.getBytes(num_bytes), ptr, num_bytes); l@270: return *this; l@270: } l@270: l@270: /** reset the message to a clean state */ l@270: void clear() { l@270: address.clear(); type_tags.clear(); storage.clear(); arguments.clear(); l@270: err = OK_NO_ERROR; time_tag = TimeTag::immediate(); l@270: } l@270: l@270: /** write the raw message data (used by PacketWriter) */ l@270: void packMessage(Storage &s, bool write_size) const { l@270: if (!isOk()) return; l@270: size_t l_addr = address.size()+1, l_type = type_tags.size()+2; l@270: if (write_size) l@270: pod2bytes(uint32_t(ceil4(l_addr) + ceil4(l_type) + ceil4(storage.size())), s.getBytes(4)); l@270: strcpy(s.getBytes(l_addr), address.c_str()); l@270: strcpy(s.getBytes(l_type), ("," + type_tags).c_str()); l@270: if (storage.size()) l@270: memcpy(s.getBytes(storage.size()), const_cast(storage).begin(), storage.size()); l@270: } l@270: l@270: private: l@270: l@270: /* get the number of bytes occupied by the argument */ l@270: size_t getArgSize(int type, const char *p) { l@270: if (err) return 0; l@270: size_t sz = 0; l@270: assert(p >= storage.begin() && p <= storage.end()); l@270: switch (type) { l@270: case TYPE_TAG_TRUE: l@270: case TYPE_TAG_FALSE: sz = 0; break; l@270: case TYPE_TAG_INT32: l@270: case TYPE_TAG_FLOAT: sz = 4; break; l@270: case TYPE_TAG_INT64: l@270: case TYPE_TAG_DOUBLE: sz = 8; break; l@270: case TYPE_TAG_STRING: { l@270: const char *q = (const char*)memchr(p, 0, storage.end()-p); l@270: if (!q) OSCPKT_SET_ERR(MALFORMED_ARGUMENTS); l@270: else sz = (q-p)+1; l@270: } break; l@270: case TYPE_TAG_BLOB: { l@270: if (p == storage.end()) { OSCPKT_SET_ERR(MALFORMED_ARGUMENTS); return 0; } l@270: sz = 4+bytes2pod(p); l@270: } break; l@270: default: { l@270: OSCPKT_SET_ERR(UNHANDLED_TYPE_TAGS); return 0; l@270: } break; l@270: } l@270: if (p+sz > storage.end() || /* string or blob too large.. */ l@270: p+sz < p /* or even blob so large that it did overflow */) { l@270: OSCPKT_SET_ERR(MALFORMED_ARGUMENTS); return 0; l@270: } l@270: if (!isZeroPaddingCorrect(p+sz)) { OSCPKT_SET_ERR(MALFORMED_ARGUMENTS); return 0; } l@270: return sz; l@270: } l@270: l@270: template Message &pushPod(int tag, POD v) { l@270: type_tags += (char)tag; l@270: arguments.push_back(std::make_pair(storage.size(), sizeof(POD))); l@270: pod2bytes(v, storage.getBytes(sizeof(POD))); l@270: return *this; l@270: } l@270: l@270: #ifdef OSCPKT_OSTREAM_OUTPUT l@270: friend std::ostream &operator<<(std::ostream &os, const Message &msg) { l@270: os << "osc_address: '" << msg.address << "', types: '" << msg.type_tags << "', timetag=" << msg.time_tag << ", args=["; l@270: Message::ArgReader arg(msg); l@270: while (arg.nbArgRemaining() && arg.isOk()) { l@270: if (arg.isBool()) { bool b; arg.popBool(b); os << (b?"True":"False"); } l@270: else if (arg.isInt32()) { int32_t i; arg.popInt32(i); os << i; } l@270: else if (arg.isInt64()) { int64_t h; arg.popInt64(h); os << h << "ll"; } l@270: else if (arg.isFloat()) { float f; arg.popFloat(f); os << f << "f"; } l@270: else if (arg.isDouble()) { double d; arg.popDouble(d); os << d; } l@270: else if (arg.isStr()) { std::string s; arg.popStr(s); os << "'" << s << "'"; } l@270: else if (arg.isBlob()) { std::vector b; arg.popBlob(b); os << "Blob " << b.size() << " bytes"; } l@270: else { l@270: assert(0); // I forgot a case.. l@270: } l@270: if (arg.nbArgRemaining()) os << ", "; l@270: } l@270: if (!arg.isOk()) { os << " ERROR#" << arg.getErr(); } l@270: os << "]"; l@270: return os; l@270: } l@270: #endif l@270: }; l@270: l@270: /** l@270: parse an OSC packet and extracts the embedded OSC messages. l@270: */ l@270: class PacketReader { l@270: public: l@270: PacketReader() { err = OK_NO_ERROR; } l@270: /** pointer and size of the osc packet to be parsed. */ l@270: PacketReader(const void *ptr, size_t sz) { init(ptr, sz); } l@270: l@270: void init(const void *ptr, size_t sz) { l@270: err = OK_NO_ERROR; messages.clear(); l@270: if ((sz%4) == 0) { l@270: parse((const char*)ptr, (const char *)ptr+sz, TimeTag::immediate()); l@270: } else OSCPKT_SET_ERR(INVALID_PACKET_SIZE); l@270: it_messages = messages.begin(); l@270: } l@270: l@270: /** extract the next osc message from the packet. return 0 when all messages have been read, or in case of error. */ l@270: Message *popMessage() { l@270: if (!err && !messages.empty() && it_messages != messages.end()) return &*it_messages++; l@270: else return 0; l@270: } l@270: bool isOk() const { return err == OK_NO_ERROR; } l@270: ErrorCode getErr() const { return err; } l@270: l@270: private: l@270: std::list messages; l@270: std::list::iterator it_messages; l@270: ErrorCode err; l@270: l@270: void parse(const char *beg, const char *end, TimeTag time_tag) { l@270: assert(beg <= end && !err); assert(((end-beg)%4)==0); l@270: l@270: if (beg == end) return; l@270: if (*beg == '#') { l@270: /* it's a bundle */ l@270: if (end - beg >= 20 l@270: && memcmp(beg, "#bundle\0", 8) == 0) { l@270: TimeTag time_tag2(bytes2pod(beg+8)); l@270: const char *pos = beg + 16; l@270: do { l@270: uint32_t sz = bytes2pod(pos); pos += 4; l@270: if ((sz&3) != 0 || pos + sz > end || pos+sz < pos) { l@270: OSCPKT_SET_ERR(INVALID_BUNDLE); l@270: } else { l@270: parse(pos, pos+sz, time_tag2); l@270: pos += sz; l@270: } l@270: } while (!err && pos != end); l@270: } else { l@270: OSCPKT_SET_ERR(INVALID_BUNDLE); l@270: } l@270: } else { l@270: messages.push_back(Message(beg, end-beg, time_tag)); l@270: if (!messages.back().isOk()) OSCPKT_SET_ERR(messages.back().getErr()); l@270: } l@270: } l@270: }; l@270: l@270: l@270: /** l@270: Assemble messages into an OSC packet. Example of use: l@270: @code l@270: PacketWriter pkt; l@270: Message msg; l@270: pkt.startBundle(); l@270: pkt.addMessage(msg.init("/foo").pushBool(true).pushStr("plop").pushFloat(3.14f)); l@270: pkt.addMessage(msg.init("/bar").pushBool(false)); l@270: pkt.endBundle(); l@270: if (pkt.isOk()) { l@270: send(pkt.data(), pkt.size()); l@270: } l@270: @endcode l@270: */ l@270: class PacketWriter { l@270: public: l@270: PacketWriter() { init(); } l@270: PacketWriter &init() { err = OK_NO_ERROR; storage.clear(); bundles.clear(); return *this; } l@270: l@270: /** begin a new bundle. If you plan to pack more than one message in the Osc packet, you have to l@270: put them in a bundle. Nested bundles inside bundles are also allowed. */ l@270: PacketWriter &startBundle(TimeTag ts = TimeTag::immediate()) { l@270: char *p; l@270: if (bundles.size()) storage.getBytes(4); // hold the bundle size l@270: p = storage.getBytes(8); strcpy(p, "#bundle"); bundles.push_back(p - storage.begin()); l@270: p = storage.getBytes(8); pod2bytes(ts, p); l@270: return *this; l@270: } l@270: /** close the current bundle. */ l@270: PacketWriter &endBundle() { l@270: if (bundles.size()) { l@270: if (storage.size() - bundles.back() == 16) { l@270: pod2bytes(0, storage.getBytes(4)); // the 'empty bundle' case, not very elegant l@270: } l@270: if (bundles.size()>1) { // no size stored for the top-level bundle l@270: pod2bytes(uint32_t(storage.size() - bundles.back()), storage.begin() + bundles.back()-4); l@270: } l@270: bundles.pop_back(); l@270: } else OSCPKT_SET_ERR(INVALID_BUNDLE); l@270: return *this; l@270: } l@270: l@270: /** insert an Osc message into the current bundle / packet. l@270: */ l@270: PacketWriter &addMessage(const Message &msg) { l@270: if (storage.size() != 0 && bundles.empty()) OSCPKT_SET_ERR(BUNDLE_REQUIRED_FOR_MULTI_MESSAGES); l@270: else msg.packMessage(storage, bundles.size()>0); l@270: if (!msg.isOk()) OSCPKT_SET_ERR(msg.getErr()); l@270: return *this; l@270: } l@270: l@270: /** the error flag will be raised if an opened bundle is not closed, or if more than one message is l@270: inserted in the packet without a bundle */ l@270: bool isOk() { return err == OK_NO_ERROR; } l@270: ErrorCode getErr() { return err; } l@270: l@270: /** return the number of bytes of the osc packet -- will always be a l@270: multiple of 4 -- returns 0 if the construction of the packet has l@270: failed. */ l@270: uint32_t packetSize() { return err ? 0 : (uint32_t)storage.size(); } l@270: l@270: /** return the bytes of the osc packet (NULL if the construction of the packet has failed) */ l@270: char *packetData() { return err ? 0 : storage.begin(); } l@270: private: l@270: std::vector bundles; // hold the position in the storage array of the beginning marker of each bundle l@270: Storage storage; l@270: ErrorCode err; l@270: }; l@270: l@270: // see the OSC spec for the precise pattern matching rules l@270: inline const char *internalPatternMatch(const char *pattern, const char *path) { l@270: while (*pattern) { l@270: const char *p = pattern; l@270: if (*p == '?' && *path) { ++p; ++path; } l@270: else if (*p == '[' && *path) { // bracketted range, e.g. [a-zABC] l@270: ++p; l@270: bool reverse = false; l@270: if (*p == '!') { reverse = true; ++p; } l@270: bool match = reverse; l@270: for (; *p && *p != ']'; ++p) { l@270: char c0 = *p, c1 = c0; l@270: if (p[1] == '-' && p[2] && p[2] != ']') { p += 2; c1 = *p; } l@270: if (*path >= c0 && *path <= c1) { match = !reverse; } l@270: } l@270: if (!match || *p != ']') return pattern; l@270: ++p; ++path; l@270: } else if (*p == '*') { // wildcard '*' l@270: while (*p == '*') ++p; l@270: const char *best = 0; l@270: while (true) { l@270: const char *ret = internalPatternMatch(p, path); l@270: if (ret && ret > best) best = ret; l@270: if (*path == 0 || *path == '/') break; l@270: else ++path; l@270: } l@270: return best; l@270: } else if (*p == '/' && *(p+1) == '/') { // the super-wildcard '//' l@270: while (*(p+1)=='/') ++p; l@270: const char *best = 0; l@270: while (true) { l@270: const char *ret = internalPatternMatch(p, path); l@270: if (ret && ret > best) best = ret; l@270: if (*path == 0) break; l@270: if (*path == 0 || (path = strchr(path+1, '/')) == 0) break; l@270: } l@270: return best; l@270: } else if (*p == '{') { // braced list {foo,bar,baz} l@270: const char *end = strchr(p, '}'), *q; l@270: if (!end) return 0; // syntax error in brace list.. l@270: bool match = false; l@270: do { l@270: ++p; l@270: q = strchr(p, ','); l@270: if (q == 0 || q > end) q = end; l@270: if (strncmp(p, path, q-p)==0) { l@270: path += (q-p); p = end+1; match = true; l@270: } else p=q; l@270: } while (q != end && !match); l@270: if (!match) return pattern; l@270: } else if (*p == *path) { ++p; ++path; } // any other character l@270: else break; l@270: pattern = p; l@270: } l@270: return (*path == 0 ? pattern : 0); l@270: } l@270: l@270: inline bool partialPatternMatch(const std::string &pattern, const std::string &test) { l@270: const char *q = internalPatternMatch(pattern.c_str(), test.c_str()); l@270: return q != 0; l@270: } l@270: l@270: inline bool fullPatternMatch(const std::string &pattern, const std::string &test) { l@270: const char *q = internalPatternMatch(pattern.c_str(), test.c_str()); l@270: return q && *q == 0; l@270: } l@270: l@270: } // namespace oscpkt l@270: l@270: #endif // OSCPKT_HH