/* unpackOSC is like dumpOSC but outputs two lists: a list of symbols for the path */ /* and a list of floats and/or symbols for the data */ /* This allows for the separation of the protocol and its transport. */ /* Started by Martin Peach 20060420 */ /* This version tries to be standalone from LIBOSC MP 20060425 */ /* MP 20060505 fixed a bug (line 209) where bytes are wrongly interpreted as negative */ /* dumpOSC.c header follows: */ /* Written by Matt Wright and Adrian Freed, The Center for New Music and Audio Technologies, University of California, Berkeley. Copyright (c) 1992,93,94,95,96,97,98,99,2000,01,02,03,04 The Regents of the University of California (Regents). Permission to use, copy, modify, distribute, and distribute modified versions of this software and its documentation without fee and without a signed licensing agreement, is hereby granted, provided that the above copyright notice, this paragraph and the following two paragraphs appear in all copies, modifications, and distributions. IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. The OSC webpage is http://cnmat.cnmat.berkeley.edu/OpenSoundControl */ /* dumpOSC.c server that displays OpenSoundControl messages sent to it for debugging client udp and UNIX protocol by Matt Wright, 6/3/97 modified from dumpSC.c, by Matt Wright and Adrian Freed version 0.2: Added "-silent" option a.k.a. "-quiet" version 0.3: Incorporated patches from Nicola Bernardini to make things Linux-friendly. Also added ntohl() in the right places to support little-endian architectures. compile: cc -o dumpOSC dumpOSC.c to-do: More robustness in saying exactly what's wrong with ill-formed messages. (If they don't make sense, show exactly what was received.) Time-based features: print time-received for each packet Clean up to separate OSC parsing code from socket/select stuff pd: branched from http://www.cnmat.berkeley.edu/OpenSoundControl/src/dumpOSC/dumpOSC.c ------------- -- added pd functions -- socket is made differently than original via pd mechanisms -- tweaks for Win32 www.zeggz.com/raf 13-April-2002 -- the OSX changes from cnmat didnt make it here yet but this compiles on OSX anyway. */ #if HAVE_CONFIG_H #include #endif #include "m_pd.h" /* declarations */ #ifdef _WIN32 #ifdef _MSC_VER // #include "OSC-common.h" #endif /* _MSC_VER */ #include #include #include #include #else #include #include #include // #include // #include #include // #include #include // #include // #include // #include // #include // #include // #include // #include #include // #include // #include // #include // #include // #include // #include //#include // #ifdef NEED_SCHEDCTL_AND_LOCK // #include // #include // #endif #endif /* _WIN32 */ /* Declarations */ #ifdef WIN32 typedef unsigned __int64 osc_time_t; #else typedef unsigned long long osc_time_t; #endif #define MAX_MESG 65536 // was 32768 MP: make same as MAX_UDP_PACKET /* ----------------------------- was dumpOSC ------------------------- */ #define MAX_PATH_AT 50 // maximum nuber of elements in OSC path static t_class *unpackOSC_class; typedef struct _unpackOSC { t_object x_obj; t_outlet *x_data_out; t_atom x_data_at[MAX_MESG];// symbols making up the path + payload int x_data_atc;// number of symbols to be output char x_raw[MAX_MESG];// bytes making up the entire OSC message int x_raw_c;// number of bytes in OSC message } t_unpackOSC; #ifdef MSW __declspec(dllexport) #endif void unpackOSC_setup(void); static void *unpackOSC_new(void); static void unpackOSC_free(t_unpackOSC *x); void unpackOSC_setup(void); static void unpackOSC_list(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv); static int unpackOSC_path(t_unpackOSC *x, char *path); static void unpackOSC_Smessage(t_unpackOSC *x, void *v, int n); static void unpackOSC_PrintTypeTaggedArgs(t_unpackOSC *x, void *v, int n); static void unpackOSC_PrintHeuristicallyTypeGuessedArgs(t_unpackOSC *x, void *v, int n, int skipComma); static char *unpackOSC_DataAfterAlignedString(char *string, char *boundary); static int unpackOSC_IsNiceString(char *string, char *boundary); static void *unpackOSC_new(void) { t_unpackOSC *x; x = (t_unpackOSC *)pd_new(unpackOSC_class); x->x_data_out = outlet_new(&x->x_obj, &s_list); x->x_raw_c = x->x_data_atc = 0; return (x); } static void unpackOSC_free(t_unpackOSC *x) { } #ifdef MSW __declspec(dllexport) #endif void unpackOSC_setup(void) { unpackOSC_class = class_new(gensym("unpackOSC"), (t_newmethod)unpackOSC_new, (t_method)unpackOSC_free, sizeof(t_unpackOSC), 0, 0); class_addlist(unpackOSC_class, (t_method)unpackOSC_list); } /* unpackOSC_list expects an OSC packet in the form of a list of floats on [0..255] */ static void unpackOSC_list(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv) { int size, messageLen, i, j; char *messageName, *args, *buf; if ((argc%4) != 0) { post("unpackOSC: packet size (%d) not a multiple of 4 bytes: dropping packet", argc); return; } /* copy the list to a byte buffer, checking for bytes only */ for (i = 0; i < argc; ++i) { if (argv[i].a_type == A_FLOAT) { j = (int)argv[i].a_w.w_float; // if ((j == argv[i].a_w.w_float) && (j >= 0) && (j <= 255)) // this can miss bytes between 128 and 255 because they are interpreted somewhere as negative // , so change to this: if ((j == argv[i].a_w.w_float) && (j >= -128) && (j <= 255)) { x->x_raw[i] = (char)j; } else { post("unpackOSC: data out of range (%d), dropping packet", argv[i].a_w.w_float); return; } } else { post("unpackOSC: data not float, dropping packet"); return; } } x->x_raw_c = argc; buf = x->x_raw; if ((argc >= 8) && (strncmp(buf, "#bundle", 8) == 0)) { /* This is a bundle message. */ #ifdef DEBUG post("unpackOSC: bundle msg: bundles not yet supported\n"); #endif if (argc < 16) { post("unpackOSC: Bundle message too small (%d bytes) for time tag", argc); return; } /* Print the time tag */ #ifdef DEBUG printf("unpackOSC: [ %lx%08lx\n", ntohl(*((unsigned long *)(buf+8))), ntohl(*((unsigned long *)(buf+12)))); #endif /* Note: if we wanted to actually use the time tag as a little-endian 64-bit int, we'd have to word-swap the two 32-bit halves of it */ i = 16; /* Skip "#group\0" and time tag */ while(i < argc) { size = ntohl(*((int *) (buf + i))); if ((size % 4) != 0) { post("unpackOSC: Bad size count %d in bundle (not a multiple of 4)", size); return; } if ((size + i + 4) > argc) { post("unpackOSC: Bad size count %d in bundle (only %d bytes left in entire bundle)", size, argc-i-4); return; } /* Recursively handle element of bundle */ unpackOSC_list(x, s, size, &argv[i+4]); i += 4 + size; } if (i != argc) { post("unpackOSC: This can't happen"); } #ifdef DEBUG printf("]\n"); #endif } else if ((argc == 24) && (strcmp(buf, "#time") == 0)) { post("unpackOSC: Time message: %s\n :).\n", buf); return; } else { /* This is not a bundle message or a time message */ messageName = buf; args = unpackOSC_DataAfterAlignedString(messageName, buf+x->x_raw_c); if (args == 0) { post("unpackOSC: Bad message name string: (%s) Dropping entire message.", messageName); return; } #ifdef DEBUG post("unpackOSC: message name string: %s", messageName); #endif messageLen = args-messageName; /* put the OSC path into a single symbol */ x->x_data_atc = unpackOSC_path(x, messageName); if (x->x_data_atc == 1) unpackOSC_Smessage(x, (void *)args, x->x_raw_c-messageLen); } if (x->x_data_atc >= 1) outlet_list(x->x_data_out, &s_list, x->x_data_atc, x->x_data_at); x->x_data_atc = 0; } static int unpackOSC_path(t_unpackOSC *x, char *path) { int i; if (path[0] != '/') { post("unpackOSC: bad path (%s)", path); return 0; } for (i = 1; i < MAX_MESG; ++i) { if (path[i] == '\0') { /* the end of the path: turn path into a symbol */ SETSYMBOL(&x->x_data_at[0],gensym(path)); return 1; } } post("unpackOSC: path too long"); return 0; } #define SMALLEST_POSITIVE_FLOAT 0.000001f static void unpackOSC_Smessage(t_unpackOSC *x, void *v, int n) { char *chars = v; if (n != 0) { if (chars[0] == ',') { if (chars[1] != ',') { /* This message begins with a type-tag string */ unpackOSC_PrintTypeTaggedArgs(x, v, n); } else { /* Double comma means an escaped real comma, not a type string */ unpackOSC_PrintHeuristicallyTypeGuessedArgs(x, v, n, 1); } } else { unpackOSC_PrintHeuristicallyTypeGuessedArgs(x, v, n, 0); } } } static void unpackOSC_PrintTypeTaggedArgs(t_unpackOSC *x, void *v, int n) { char *typeTags, *thisType, *p; int myargc = x->x_data_atc; t_atom *mya = x->x_data_at; typeTags = v; if (!unpackOSC_IsNiceString(typeTags, typeTags+n)) { /* No null-termination, so maybe it wasn't a type tag string after all */ unpackOSC_PrintHeuristicallyTypeGuessedArgs(x, v, n, 0); return; } p = unpackOSC_DataAfterAlignedString(typeTags, typeTags+n); for (thisType = typeTags + 1; *thisType != 0; ++thisType) { switch (*thisType) { case 'i': case 'r': case 'm': case 'c': #ifdef DEBUG post("integer: %d", ntohl(*((int *) p))); #endif SETFLOAT(mya+myargc,(signed)ntohl(*((int *) p))); myargc++; p += 4; break; case 'f': { int i = ntohl(*((int *) p)); float *floatp = ((float *) (&i)); #ifdef DEBUG post("float: %f", *floatp); #endif SETFLOAT(mya+myargc,*floatp); myargc++; p += 4; break; } case 'h': case 't': #ifdef DEBUG printf("[A 64-bit int] "); #endif post("[A 64-bit int] not implemented"); p += 8; break; case 'd': #ifdef DEBUG printf("[A 64-bit float] "); #endif post("[A 64-bit float] not implemented"); p += 8; break; case 's': case 'S': if (!unpackOSC_IsNiceString(p, typeTags+n)) { post("Type tag said this arg is a string but it's not!\n"); return; } else { #ifdef DEBUG post("string: \"%s\"", p); #endif SETSYMBOL(mya+myargc,gensym(p)); myargc++; p = unpackOSC_DataAfterAlignedString(p, typeTags+n); } break; case 'T': #ifdef DEBUG printf("[True] "); #endif SETFLOAT(mya+myargc,1.); myargc++; break; case 'F': #ifdef DEBUG printf("[False] "); #endif SETFLOAT(mya+myargc,0.); myargc++; break; case 'N': #ifdef DEBUG printf("[Nil]"); #endif post("sendOSC: [Nil] not implemented"); break; case 'I': #ifdef DEBUG printf("[Infinitum]"); #endif post("sendOSC: [Infinitum] not implemented"); break; default: post("sendOSC: [Unrecognized type tag %c]", *thisType); } } x->x_data_atc = myargc; } static void unpackOSC_PrintHeuristicallyTypeGuessedArgs(t_unpackOSC *x, void *v, int n, int skipComma) { int i, thisi; int *ints; float thisf; char *chars, *string, *nextString; int myargc= x->x_data_atc; t_atom* mya = x->x_data_at; /* Go through the arguments 32 bits at a time */ ints = v; chars = v; for (i = 0; i < n/4; ) { string = &chars[i*4]; thisi = ntohl(ints[i]); /* Reinterpret the (potentially byte-reversed) thisi as a float */ thisf = *(((float *) (&thisi))); if (thisi >= -1000 && thisi <= 1000000) { #ifdef DEBUG printf("%d ", thisi); #endif SETFLOAT(mya+myargc,(t_float) (thisi)); myargc++; i++; } else if (thisf >= -1000.f && thisf <= 1000000.f && (thisf <=0.0f || thisf >= SMALLEST_POSITIVE_FLOAT)) { #ifdef DEBUG printf("%f ", thisf); #endif SETFLOAT(mya+myargc,thisf); myargc++; i++; } else if (unpackOSC_IsNiceString(string, chars+n)) { nextString = unpackOSC_DataAfterAlignedString(string, chars+n); #ifdef DEBUG printf("\"%s\" ", (i == 0 && skipComma) ? string +1 : string); #endif SETSYMBOL(mya+myargc,gensym(string)); myargc++; i += (nextString-string) / 4; } else { // unhandled .. ;) #ifdef DEBUG post("unpackOSC: indeterminate type: 0x%x xx", ints[i]); #endif i++; } x->x_data_atc = myargc; } } #define STRING_ALIGN_PAD 4 static char *unpackOSC_DataAfterAlignedString(char *string, char *boundary) { /* The argument is a block of data beginning with a string. The string has (presumably) been padded with extra null characters so that the overall length is a multiple of STRING_ALIGN_PAD bytes. Return a pointer to the next byte after the null byte(s). The boundary argument points to the character after the last valid character in the buffer---if the string hasn't ended by there, something's wrong. If the data looks wrong, return 0, and set htm_error_string */ int i; if ((boundary - string) %4 != 0) { post("unpackOSC: DataAfterAlignedString: bad boundary"); return 0; } for (i = 0; string[i] != '\0'; i++) { if (string + i >= boundary) { post("unpackOSC: DataAfterAlignedString: Unreasonably long string"); return 0; } } /* Now string[i] is the first null character */ i++; for (; (i % STRING_ALIGN_PAD) != 0; i++) { if (string + i >= boundary) { post("unpackOSC: DataAfterAlignedString: Unreasonably long string"); return 0; } if (string[i] != '\0') { post("unpackOSC:DataAfterAlignedString: Incorrectly padded string"); return 0; } } return string+i; } static int unpackOSC_IsNiceString(char *string, char *boundary) { /* Arguments same as DataAfterAlignedString(). Is the given "string" really a string? I.e., is it a sequence of isprint() characters terminated with 1-4 null characters to align on a 4-byte boundary? Returns 1 if true, else 0. */ int i; if ((boundary - string) %4 != 0) { fprintf(stderr, "Internal error: IsNiceString: bad boundary\n"); return 0; } for (i = 0; string[i] != '\0'; i++) if ((!isprint(string[i])) || (string + i >= boundary)) return 0; /* If we made it this far, it's a null-terminated sequence of printing characters in the given boundary. Now we just make sure it's null padded... */ /* Now string[i] is the first null character */ i++; for (; (i % STRING_ALIGN_PAD) != 0; i++) if (string[i] != '\0') return 0; return 1; } /* end of unpackOSC.c */