v4k-git-backup/tools/labs/oscrecv.h

300 lines
12 KiB
C

// simple osc server. designed for easy integration with a gameloop / immediate mode style.
// - rlyeh, public domain. forked from original code by @mmalex (public domain).
//
// @todo: add support for // wildcard
// @todo: add support for [ array parameters ]
API int osc_listen( const char *mask, const char *port ); // creates a listening socket
API int osc_update(int s, int timeout_ms); // call every frame. reads the udp port and parse all messages found there
API int osc_list(const struct osc_message **first); // returns number of received messages, also set arg pointer to first item
API int osc_debug( FILE *out, const char *inmsg, int len ); // debugs raw osc buffer to stream
API const struct osc_message *osc_find(const char *addr); // finds most recent message matching 'addr'
// OSC types from the spec.
enum {
OSC_INT = 'i',
OSC_FLOAT = 'f',
OSC_STRING = 's',
OSC_SYMBOL = 'S',
OSC_BLOB = 'b',
OSC_INT64 = 'h',
OSC_TIME = 't',
OSC_DOUBLE = 'd',
OSC_CHAR = 'c',
OSC_RGBA = 'r',
OSC_MIDI = 'm',
OSC_TRUE = 'T',
OSC_FALSE = 'F',
OSC_NIL = 'N',
OSC_INFINITY = 'I',
// OSC_ARRAY = '[', // @todo
};
// OSC message
typedef struct osc_message {
const char *pattern;// address in osc message
const char *types; // string of characters taken from the OSC types enum
const char *data; // pointer to raw osc data. for debugging purposes only (?)
#if 0
int64_t i[8]; // integer interpretation of first 8 params (for blobs & strings, is length)
const char *s[8]; // for blobs and strings
float f[8]; // floating point interpretation of first 8 params
#else
union /*variant*/ {
int64_t i;
const char *s;
double f;
uintptr_t up;
} v[8];
#endif
} osc_message;
#pragma once
enum { OSC_MAX_BUF = 8*1024*1024 }; // was: 65536
enum { OSC_MAX_MESSAGES = 65536 }; // was: 1024
static int osc_match_(const char *pat, const char *addr) {
for (int n=0;*pat;addr++,pat++) switch (*pat) {
default: if (*pat!=*addr) return 0; break;
case '?': break;
case '[': n=(*++pat=='!'); for (pat+=n; *pat!=']' && *pat;++pat) { if (pat[1]=='-') { if (*addr>=*pat && *addr<=pat[2]) { n=!n; break; } pat+=pat[2] ? 2 : 1; } else if (*pat==*addr) { n=!n; break; } }
if (!n) return 0; while (*pat && *pat!=']') pat++; break;
case '{': n=0; for (const char *p=++pat; *p && *p!='}' && *p!='/'; pat=++p) { while (*p && *p!='}' && *p!='/' && *p!=',') p++; if (!strncmp(pat,addr,p-pat)) { addr+=p-pat; n=1; break; } }
while (*pat && *pat!='}') pat++; if (!n) return 0; break;
case '*': while (pat[1]=='*') pat++; n=0; if (pat[1]=='/' || pat[1]==0) { while (*addr && *addr!='/') ++addr; n=1; } else for (;*addr!='/' && *addr; ++addr) if (osc_match_(pat + 1, addr)) { n=1; break; }
if (!n) return 0; addr--; break;
// @todo: add // wildcard support
}
return *addr== 0;
}
static float osc_asfloat_(uint32_t x) { union { float f; uint32_t i; } u; u.i=x; return u.f; }
static double osc_asdouble_(uint64_t x) { union { double f; uint64_t i; } u; u.i=x; return u.f; }
static int osc_parse_i32_(const char **s, const char *e) {
if (*s+4>e) { *s=e; return 0; }
int rv=htonl(*(uint32_t*)*s);
*s+=4;
return rv;
}
static int64_t osc_parse_i64_(const char **s, const char *e) {
if (*s+8>e) { *s=e; return 0; }
int64_t rv=htonll(*(uint64_t*)*s);
*s+=8;
return rv;
}
static const char *osc_parse_str_(const char **s, const char *e) {
int len=(int)strlen(*s);
const char *rv=*s;
*s=rv+((len+4)&~3);
if (*s>e) *s=e;
return rv;
}
static const char *osc_parse_bin_(const char **s, const char *e, int *len) {
*len=osc_parse_i32_(s,e);
int maxlen=(int)(e-*s);
if (*len>maxlen) *len=maxlen;
if (*len<0) *len=0;
const char *rv=*s;
*s=rv+((*len+3)&~3);
if (*s>e) *s=e;
return rv;
}
static int osc_parse_(osc_message *out, int maxmsg, const char *s, const char *e) {
if (maxmsg<=0 || s>=e) return 0;
if (*(uint64_t*)s==*(uint64_t*)"#bundle\0") { // bundle is #bundle, uint64_t time, uint32_t length, <osc packet>
osc_parse_i64_(&s,e); // @todo: skipped time for now
int msgcount=0;
while (s<e && msgcount<maxmsg) {
int len=osc_parse_i32_(&s,e);
int maxlen=(int)(e-s);
if (len>maxlen) len=maxlen;
if (len<0) len=0;
int n=osc_parse_(out+msgcount, maxmsg-msgcount, s, s+len);
msgcount+=n;
s+=((len+3)&~3);
}
return msgcount;
}
// single message
memset(out,0,sizeof(osc_message));
out->pattern=osc_parse_str_(&s,e);
if (!out->pattern)
return 0;
out->types=(*s==',')?osc_parse_str_(&s,e):",f";
if (!out->types)
return 0;
out->types++;
out->data=s;
for (int param=0;param<8;++param) {
switch (out->types[param]) {
default: return 1; // done!
case OSC_CHAR: case OSC_RGBA: // all int32...
case OSC_MIDI: case OSC_INT: out->v[param].i=osc_parse_i32_(&s,e); break;
case OSC_TIME: case OSC_INT64: out->v[param].i=osc_parse_i64_(&s,e); break;
case OSC_STRING: case OSC_SYMBOL: out->v[param].s=osc_parse_str_(&s,e); /*out->v[param].i=strlen(out->v[param].s);*/ break;
case OSC_FLOAT: out->v[param].f=osc_asfloat_(osc_parse_i32_(&s,e)); break;
case OSC_DOUBLE: out->v[param].f=osc_asdouble_(osc_parse_i32_(&s,e)); break;
case OSC_BLOB: {int len=0; out->v[param].s=osc_parse_bin_(&s,e,&len); /*out->v[param].i=len;*/ break; } // @todo: important to signal len in variant somewhere
case OSC_INFINITY: out->v[param].f=INFINITY; break;
case OSC_TRUE: out->v[param].i=1; break;
case OSC_FALSE: case OSC_NIL: out->v[param].i=0; break;
// case OSC_ARRAY: @todo
}
}
return 1;
}
static struct osc_message *msg = 0; //[OSC_MAX_MESSAGES];
static int msgpos;
int osc_listen( const char *mask, const char *port ) {
do_once udp_init();
int fd = swrapSocket(SWRAP_UDP,SWRAP_BIND,SWRAP_NOBLOCK,mask,port);
return fd;
}
int osc_update(int fd, int timeout_ms /*= -1*/) {
do_once udp_init();
static char *buf = 0; // [OSC_MAX_BUF];
static int bufpos;
if( !buf ) {
buf = CALLOC( 1, OSC_MAX_BUF );
msg = CALLOC( 1, OSC_MAX_MESSAGES * sizeof(struct osc_message) );
}
if(fd<0) return 0;
/* check if something is available */
msgpos=0;
if( timeout_ms >= 0 ) {
int ret = swrapSelect(fd, timeout_ms / 1000.0);
if (ret <= 0) { // error, or timeout
return 0;
}
}
for (msgpos=0,bufpos=0;msgpos<OSC_MAX_MESSAGES && bufpos<OSC_MAX_BUF-8;) {
int n = swrapReceive(fd, buf+bufpos, OSC_MAX_BUF-bufpos-1);
if( n <= 0 ) {
#ifdef _WIN32
//if (n == -1 && WSAGetLastError() == WSAEINTR) continue;
if( WSAGetLastError() != WSAEINTR && /*WSAGetLastError() != WSAEWOULDBLOCK &&*/
WSAGetLastError() != WSAECONNRESET && WSAGetLastError() != WSAECONNREFUSED ) {
// error: %d, WSAGetLastError();
return 0;
}
#else
//if (n == -1 && errno == EINTR) continue;
if( errno != EAGAIN && errno != EINTR && /*errno != EWOULDBLOCK &&*/
errno != ECONNRESET && errno != ECONNREFUSED ) {
// error: %s, strerror(errno));
return 0;
}
#endif
continue;
}
char *s=buf+bufpos;
s[n]=0; // null terminate packet always, for easier c handling
msgpos+=osc_parse_(msg+msgpos,OSC_MAX_MESSAGES-msgpos,s,s+n);
bufpos+=n+1;
}
return 1;
}
int osc_list(const osc_message **first) {
*first = msg;
return msgpos;
}
const osc_message *osc_find(const char *addr) { // search in reverse order, so newest wins
for (int i=msgpos;i-->0;) if (osc_match_(msg[i].pattern, addr)) return &msg[i];
return 0;
}
int osc_debug(FILE *fp, const char *buf, int len) {
osc_message m[16];
int nn = osc_parse_(m, 16, buf, buf+len);
for( int n = 0; n < nn; ++n ) {
fprintf(fp, "%s [%s]", m[n].pattern, m[n].types);
// @todo #bundle @%lld
for(int i = 0; m[n].types[i]; ++i) {
char f = m[n].types[i];
/**/ if (f == 'T' || f == 'F' || f == 'N') fprintf( fp, ",%s", (f=='T'?"True":f=='F'?"False":"Null"));
else if (f == 'h' || f == 't') fprintf( fp, ",%lld", m[n].v[i].i);
else if (f == 'f' || f == 'd') fprintf( fp, ",%f", m[n].v[i].f);
else if (f == 'i' || f == 'c' || f == 'r' || f == 'm' ) fprintf( fp, ",%d", (int)m[n].v[i].i);
else if (f == 's' || f == 'S' ) fprintf( fp, ",%s", m[n].v[i].s);
else if (f == 'b') fprintf( fp, ",%d bytes", (int)m[n].v[i].i);
else fprintf(fp, ",%s", "?");
}
fprintf(fp, "%s\n", "");
}
return 1;
}
#ifdef OSCRECV_DEMO
int main() {
// @mmalex's tests
assert( osc_match_("/[a-c]/?/[abc]/*/fish*/*food/f*/{foo,bar,baz}","/a/b/c//fishfood/monkeyfood/f/bar") );
assert( !osc_match_("/[a-c]/?/[abc]/*/fish*/*food/f*/{fog,bar,baz}","/a/b/c//fishfood/monkeyfood/f/foo") );
assert( !osc_match_("/[a-c]/?/[abc]/*/fith*/*food/f*/{foo,bar,baz}","/a/b/c//fishfood/monkeyfood/f/foo") );
assert( !osc_match_("/[a-c]/?/[abc]/*/fish*/*good/f*/{foo,bar,baz}","/a/b/c//fishfood/monkeyfood/f/foo") );
assert( !osc_match_("/[a-c]/?/[abc]/*/fish*/*food/g/{foo,bar,baz}","/a/b/c//fishfood/monkeyfood/f/foo") );
assert( osc_match_("/[fa-cd]/?/[abc]/*/fish*/*food/f*/{foo,bar,baz}","/d/b/c//fishfood/monkeyfood/f/bar") );
// Julien Pommier's oscpkt tests
assert( !osc_match_("//bar", "bar"));
// assert( osc_match_("//bar", "/bar"));
// assert( osc_match_("//bar", "/foo/plop/bar"));
// assert( osc_match_("/foo//", "/foo/plop/df/"));
// assert( osc_match_("/foo///////bar", "/foo/plop/baz/bar"));
assert( osc_match_("*", "bar"));
assert( osc_match_("/foo/*", "/foo/bar"));
// assert( osc_match_("/{bar,fo}/b[aA]r", "/fo/bar"));
// assert( !osc_match_("/{bar,fo}/b[aA]r", "/foo/bar"));
// assert( osc_match_("/fo{bar,}/ba[e-t]", "/fo/bar"));
assert( !osc_match_("/fo{bar,}/ba[t-z]", "/fo/bar"));
// assert( osc_match_("/f{,ioio,bar}o/?a[!a]", "/fo/bar"));
assert( osc_match_("/foo/bar", "/foo/bar"));
assert( osc_match_("/f*o/bar", "/foo/bar"));
assert( osc_match_("/fo*o/bar", "/foo/bar"));
// assert( osc_match_("/*//bar", "/foo/bar"));
assert( osc_match_("/*/bar", "/foo/bar"));
assert( osc_match_("/*o/bar", "/foo/bar"));
assert( osc_match_("/*/*/*/*a***/*/*/*/*/", "/foo/bar/foo/barrrr/foo/bar/foo/barrrr/"));
assert( !osc_match_("/*/*/*/**/*/*/*/*/q", "/foo/bar/foo/barrrr/foo/bar/foo/barrrr/p"));
assert( osc_match_("[-]", "-"));
// assert( osc_match_("[a-]", "a"));
// assert( osc_match_("[a-]", "-"));
int fd = osc_listen( "127.0.0.1", "9000" );
if( fd ) for(;;) {
static unsigned char counter = 0;
printf("\r127.0.0.1:9000 %c", "\\|/-"[ counter = (counter+1) & 3 ]);
Sleep(100);
osc_update(fd);
const osc_message *begin;
for( int it = 0, end = osc_list(&begin); it < end; ++it ) {
const osc_message *msg = begin + it;
printf("> %s [%s]\n", msg->pattern, msg->types);
}
}
}
#endif