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