diff options
Diffstat (limited to 'osc.c')
-rw-r--r-- | osc.c | 813 |
1 files changed, 0 insertions, 813 deletions
@@ -1,813 +0,0 @@ -#include <string.h> -#include <unistd.h> -#include <ctype.h> -#include <netdb.h> -#include <errno.h> -#include <fcntl.h> -#include "osc.h" - -/* - * TODO - * ping method - */ - -#define osc_align(a) ((((a) / 4) + (((a) % 4) ? 1 : 0)) * 4) -#define BACKEND_NAME "osc" - -int init(){ - backend osc = { - .name = BACKEND_NAME, - .conf = backend_configure, - .create = backend_instance, - .conf_instance = backend_configure_instance, - .channel = backend_channel, - .handle = backend_set, - .process = backend_handle, - .start = backend_start, - .shutdown = backend_shutdown - }; - - //register backend - if(mm_backend_register(osc)){ - fprintf(stderr, "Failed to register OSC backend\n"); - return 1; - } - return 0; -} - -static size_t osc_data_length(osc_parameter_type t){ - switch(t){ - case int32: - case float32: - return 4; - case int64: - case double64: - return 8; - default: - fprintf(stderr, "Invalid OSC format specified %c\n", t); - return 0; - } -} - -static inline void osc_defaults(osc_parameter_type t, osc_parameter_value* max, osc_parameter_value* min){ - memset(max, 0, sizeof(osc_parameter_value)); - memset(min, 0, sizeof(osc_parameter_value)); - switch(t){ - case int32: - max->i32 = 255; - return; - case float32: - max->f = 1.0; - return; - case int64: - max->i64 = 1024; - return; - case double64: - max->d = 1.0; - return; - default: - fprintf(stderr, "Invalid OSC type, not setting any sane defaults\n"); - return; - } -} - -static inline osc_parameter_value osc_parse(osc_parameter_type t, uint8_t* data){ - osc_parameter_value v = {0}; - switch(t){ - case int32: - case float32: - v.i32 = be32toh(*((int32_t*) data)); - break; - case int64: - case double64: - v.i64 = be64toh(*((int64_t*) data)); - break; - default: - fprintf(stderr, "Invalid OSC type passed to parsing routine\n"); - } - return v; -} - -static inline int osc_deparse(osc_parameter_type t, osc_parameter_value v, uint8_t* data){ - uint64_t u64 = 0; - uint32_t u32 = 0; - switch(t){ - case int32: - case float32: - u32 = htobe32(v.i32); - memcpy(data, &u32, sizeof(u32)); - break; - case int64: - case double64: - u64 = htobe64(v.i64); - memcpy(data, &u64, sizeof(u64)); - break; - default: - fprintf(stderr, "Invalid OSC type passed to parsing routine\n"); - return 1; - } - return 0; -} - -static inline osc_parameter_value osc_parse_value_spec(osc_parameter_type t, char* value){ - osc_parameter_value v = {0}; - switch(t){ - case int32: - v.i32 = strtol(value, NULL, 0); - break; - case float32: - v.f = strtof(value, NULL); - break; - case int64: - v.i64 = strtoll(value, NULL, 0); - break; - case double64: - v.d = strtod(value, NULL); - break; - default: - fprintf(stderr, "Invalid OSC type passed to value parser\n"); - } - return v; -} - -static inline channel_value osc_parameter_normalise(osc_parameter_type t, osc_parameter_value min, osc_parameter_value max, osc_parameter_value cur){ - channel_value v = { - .raw = {0}, - .normalised = 0 - }; - - union { - uint32_t u32; - float f32; - uint64_t u64; - double d64; - } range; - - switch(t){ - case int32: - range.u32 = max.i32 - min.i32; - v.raw.u64 = cur.i32 - min.i32; - v.normalised = v.raw.u64 / range.u32; - break; - case float32: - range.f32 = max.f - min.f; - v.raw.dbl = cur.f - min.f; - v.normalised = v.raw.dbl / range.f32; - break; - case int64: - range.u64 = max.i64 - min.i64; - v.raw.u64 = cur.i64 - min.i64; - v.normalised = v.raw.u64 / range.u64; - break; - case double64: - range.d64 = max.d - min.d; - v.raw.dbl = cur.d - min.d; - v.normalised = v.raw.dbl / range.d64; - break; - default: - fprintf(stderr, "Invalid OSC type passed to interpolation routine\n"); - } - - //fix overshoot - if(v.normalised > 1.0){ - v.normalised = 1.0; - } - else if(v.normalised < 0.0){ - v.normalised = 0.0; - } - - return v; -} - -static inline osc_parameter_value osc_parameter_denormalise(osc_parameter_type t, osc_parameter_value min, osc_parameter_value max, channel_value cur){ - osc_parameter_value v = {0}; - - union { - uint32_t u32; - float f32; - uint64_t u64; - double d64; - } range; - - switch(t){ - case int32: - range.u32 = max.i32 - min.i32; - v.i32 = (range.u32 * cur.normalised) + min.i32; - break; - case float32: - range.f32 = max.f - min.f; - v.f = (range.f32 * cur.normalised) + min.f; - break; - case int64: - range.u64 = max.i64 - min.i64; - v.i64 = (range.u64 * cur.normalised) + min.i64; - break; - case double64: - range.d64 = max.d - min.d; - v.d = (range.d64 * cur.normalised) + min.d; - break; - default: - fprintf(stderr, "Invalid OSC type passed to interpolation routine\n"); - } - - return v; -} - -static int osc_generate_event(channel* c, osc_channel* info, char* fmt, uint8_t* data, size_t data_len){ - size_t p, off = 0; - if(!c || !info){ - return 0; - } - - osc_parameter_value min, max, cur; - channel_value evt; - - if(!fmt || !data || data_len % 4 || !*fmt){ - fprintf(stderr, "Invalid OSC packet, data length %zu\n", data_len); - return 1; - } - - //find offset for this parameter - for(p = 0; p < info->param_index; p++){ - off += osc_data_length(fmt[p]); - } - - if(info->type != not_set){ - max = info->max; - min = info->min; - } - else{ - osc_defaults(fmt[info->param_index], &max, &min); - } - - cur = osc_parse(fmt[info->param_index], data + off); - evt = osc_parameter_normalise(fmt[info->param_index], min, max, cur); - - return mm_channel_event(c, evt); -} - -static int osc_validate_path(char* path){ - if(path[0] != '/'){ - fprintf(stderr, "%s is not a valid OSC path: Missing root /\n", path); - return 1; - } - return 0; -} - -static int osc_separate_hostspec(char* in, char** host, char** port){ - size_t u; - - if(!in || !host || !port){ - return 1; - } - - for(u = 0; in[u] && !isspace(in[u]); u++){ - } - - //guess - *host = in; - - if(in[u]){ - in[u] = 0; - *port = in + u + 1; - } - else{ - //no port given - *port = NULL; - } - return 0; -} - -static int osc_listener(char* host, char* port){ - int fd = -1, status, yes = 1, flags; - struct addrinfo hints = { - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_DGRAM, - .ai_flags = AI_PASSIVE - }; - struct addrinfo* info; - struct addrinfo* addr_it; - - status = getaddrinfo(host, port, &hints, &info); - if(status){ - fprintf(stderr, "Failed to get socket info for %s port %s: %s\n", host, port, gai_strerror(status)); - return -1; - } - - for(addr_it = info; addr_it != NULL; addr_it = addr_it->ai_next){ - fd = socket(addr_it->ai_family, addr_it->ai_socktype, addr_it->ai_protocol); - if(fd < 0){ - continue; - } - - yes = 1; - if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(yes)) < 0){ - fprintf(stderr, "Failed to set SO_REUSEADDR on socket\n"); - } - - yes = 1; - if(setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (void*)&yes, sizeof(yes)) < 0){ - fprintf(stderr, "Failed to set SO_BROADCAST on socket\n"); - } - - yes = 0; - if(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void*)&yes, sizeof(yes)) < 0){ - fprintf(stderr, "Failed to unset IP_MULTICAST_LOOP option: %s\n", strerror(errno)); - } - - status = bind(fd, addr_it->ai_addr, addr_it->ai_addrlen); - if(status < 0){ - close(fd); - continue; - } - - break; - } - - freeaddrinfo(info); - - if(!addr_it){ - fprintf(stderr, "Failed to create listening socket for %s port %s\n", host, port); - return -1; - } - - //set nonblocking - flags = fcntl(fd, F_GETFL, 0); - if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0){ - fprintf(stderr, "Failed to set OSC descriptor nonblocking\n"); - return -1; - } - - return fd; -} - -static int osc_parse_addr(char* host, char* port, struct sockaddr_storage* addr, socklen_t* len){ - struct addrinfo* head; - struct addrinfo hints = { - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_DGRAM - }; - - int error = getaddrinfo(host, port, &hints, &head); - if(error || !head){ - fprintf(stderr, "Failed to parse address %s port %s: %s\n", host, port, gai_strerror(error)); - return 1; - } - - memcpy(addr, head->ai_addr, head->ai_addrlen); - *len = head->ai_addrlen; - - freeaddrinfo(head); - return 0; -} - -static int backend_configure(char* option, char* value){ - fprintf(stderr, "The OSC backend does not take any global configuration\n"); - return 1; -} - -static int backend_configure_instance(instance* inst, char* option, char* value){ - osc_instance* data = (osc_instance*) inst->impl; - char* host = NULL, *port = NULL, *token = NULL, *format = NULL; - size_t u, p; - - if(!strcmp(option, "root")){ - if(osc_validate_path(value)){ - fprintf(stderr, "Not a valid OSC root: %s\n", value); - return 1; - } - - if(data->root){ - free(data->root); - } - data->root = strdup(value); - - if(!data->root){ - fprintf(stderr, "Failed to allocate memory\n"); - return 1; - } - return 0; - } - else if(!strcmp(option, "bind")){ - if(osc_separate_hostspec(value, &host, &port)){ - fprintf(stderr, "Invalid bind address for instance %s\n", inst->name); - return 1; - } - - data->fd = osc_listener(host, port); - if(data->fd < 0){ - fprintf(stderr, "Failed to bind for instance %s\n", inst->name); - return 1; - } - return 0; - } - else if(!strcmp(option, "dest") || !strcmp(option, "destination")){ - if(!strncmp(value, "learn", 5)){ - data->learn = 1; - - //check if a forced port was provided - if(value[5] == '@'){ - data->forced_rport = strtoul(value + 6, NULL, 0); - } - return 0; - } - - if(osc_separate_hostspec(value, &host, &port)){ - fprintf(stderr, "Invalid destination address for instance %s\n", inst->name); - return 1; - } - - if(osc_parse_addr(host, port, &data->dest, &data->dest_len)){ - fprintf(stderr, "Failed to parse destination address for instance %s\n", inst->name); - return 1; - } - return 0; - } - else if(*option == '/'){ - //pre-configure channel - if(osc_validate_path(option)){ - fprintf(stderr, "Not a valid OSC path: %s\n", option); - return 1; - } - - for(u = 0; u < data->channels; u++){ - if(!strcmp(option, data->channel[u].path)){ - fprintf(stderr, "OSC channel %s already configured\n", option); - return 1; - } - } - - //tokenize configuration - format = strtok(value, " "); - if(!format || strlen(format) < 1){ - fprintf(stderr, "Not a valid format for OSC path %s\n", option); - return 1; - } - - //check format validity, create subchannels - for(p = 0; p < strlen(format); p++){ - if(!osc_data_length(format[p])){ - fprintf(stderr, "Invalid format specifier %c for path %s, ignoring\n", format[p], option); - continue; - } - - //register new sub-channel - data->channel = realloc(data->channel, (data->channels + 1) * sizeof(osc_channel)); - if(!data->channel){ - fprintf(stderr, "Failed to allocate memory\n"); - return 1; - } - - memset(data->channel + data->channels, 0, sizeof(osc_channel)); - data->channel[data->channels].params = strlen(format); - data->channel[data->channels].param_index = p; - data->channel[data->channels].type = format[p]; - data->channel[data->channels].path = strdup(option); - - if(!data->channel[data->channels].path){ - fprintf(stderr, "Failed to allocate memory\n"); - return 1; - } - - //parse min/max values - token = strtok(NULL, " "); - if(!token){ - fprintf(stderr, "Missing minimum specification for parameter %zu of %s\n", p, option); - return 1; - } - data->channel[data->channels].min = osc_parse_value_spec(format[p], token); - - token = strtok(NULL, " "); - if(!token){ - fprintf(stderr, "Missing maximum specification for parameter %zu of %s\n", p, option); - return 1; - } - data->channel[data->channels].max = osc_parse_value_spec(format[p], token); - - //allocate channel from core - if(!mm_channel(inst, data->channels, 1)){ - fprintf(stderr, "Failed to register core channel\n"); - return 1; - } - - //increase channel count - data->channels++; - } - return 0; - } - - fprintf(stderr, "Unknown configuration parameter %s for OSC backend\n", option); - return 1; -} - -static instance* backend_instance(){ - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - - osc_instance* data = calloc(1, sizeof(osc_instance)); - if(!data){ - fprintf(stderr, "Failed to allocate memory\n"); - return NULL; - } - - data->fd = -1; - inst->impl = data; - return inst; -} - -static channel* backend_channel(instance* inst, char* spec){ - size_t u; - osc_instance* data = (osc_instance*) inst->impl; - size_t param_index = 0; - - //check spec for correctness - if(osc_validate_path(spec)){ - return NULL; - } - - //parse parameter offset - if(strrchr(spec, ':')){ - param_index = strtoul(strrchr(spec, ':') + 1, NULL, 10); - *(strrchr(spec, ':')) = 0; - } - - //find matching channel - for(u = 0; u < data->channels; u++){ - if(!strcmp(spec, data->channel[u].path) && data->channel[u].param_index == param_index){ - //fprintf(stderr, "Reusing previously created channel %s parameter %zu\n", data->channel[u].path, data->channel[u].param_index); - break; - } - } - - //allocate new channel - if(u == data->channels){ - data->channel = realloc(data->channel, (u + 1) * sizeof(osc_channel)); - if(!data->channel){ - fprintf(stderr, "Failed to allocate memory\n"); - return NULL; - } - - memset(data->channel + u, 0, sizeof(osc_channel)); - data->channel[u].param_index = param_index; - data->channel[u].path = strdup(spec); - - if(!data->channel[u].path){ - fprintf(stderr, "Failed to allocate memory\n"); - return NULL; - } - data->channels++; - } - - return mm_channel(inst, u, 1); -} - -static int backend_set(instance* inst, size_t num, channel** c, channel_value* v){ - uint8_t xmit_buf[OSC_XMIT_BUF], *format = NULL; - size_t evt = 0, off, members, p; - if(!num){ - return 0; - } - - osc_instance* data = (osc_instance*) inst->impl; - if(!data->dest_len){ - fprintf(stderr, "OSC instance %s does not have a destination, output is disabled (%zu channels)\n", inst->name, num); - return 0; - } - - for(evt = 0; evt < num; evt++){ - off = c[evt]->ident; - - //sanity check - if(off >= data->channels){ - fprintf(stderr, "OSC channel identifier out of range\n"); - return 1; - } - - //if the format is unknown, don't output - if(data->channel[off].type == not_set || data->channel[off].params == 0){ - fprintf(stderr, "OSC channel %s.%s requires format specification for output\n", inst->name, data->channel[off].path); - continue; - } - - //update current value - data->channel[off].current = osc_parameter_denormalise(data->channel[off].type, data->channel[off].min, data->channel[off].max, v[evt]); - //mark channel - data->channel[off].mark = 1; - } - - //fix destination rport if required - if(data->forced_rport){ - //cheating a bit because both IPv4 and IPv6 have the port at the same offset - struct sockaddr_in* sockadd = (struct sockaddr_in*) &(data->dest); - sockadd->sin_port = htobe16(data->forced_rport); - } - - //find all marked channels - for(evt = 0; evt < data->channels; evt++){ - //zero output buffer - memset(xmit_buf, 0, sizeof(xmit_buf)); - if(data->channel[evt].mark){ - //determine minimum packet size - if(osc_align((data->root ? strlen(data->root) : 0) + strlen(data->channel[evt].path) + 1) + osc_align(data->channel[evt].params + 2) >= sizeof(xmit_buf)){ - fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s\n", inst->name, data->channel[evt].path); - return 1; - } - - off = 0; - //copy osc target path - if(data->root){ - memcpy(xmit_buf, data->root, strlen(data->root)); - off += strlen(data->root); - } - memcpy(xmit_buf + off, data->channel[evt].path, strlen(data->channel[evt].path)); - off += strlen(data->channel[evt].path) + 1; - off = osc_align(off); - - //get format string offset, initialize - format = xmit_buf + off; - off += osc_align(data->channel[evt].params + 2); - *format = ','; - format++; - - //gather subchannels, unmark - members = 0; - for(p = 0; p < data->channels && members < data->channel[evt].params; p++){ - if(!strcmp(data->channel[evt].path, data->channel[p].path)){ - //unmark channel - data->channel[p].mark = 0; - - //sanity check - if(data->channel[p].param_index >= data->channel[evt].params){ - fprintf(stderr, "OSC channel %s.%s has multiple parameter offset definitions\n", inst->name, data->channel[evt].path); - return 1; - } - - //write format specifier - format[data->channel[p].param_index] = data->channel[p].type; - - //write data - //FIXME this currently depends on all channels being registered in the correct order, since it just appends data - if(off + osc_data_length(data->channel[p].type) >= sizeof(xmit_buf)){ - fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s at parameter %zu\n", inst->name, data->channel[evt].path, members); - return 1; - } - - osc_deparse(data->channel[p].type, data->channel[p].current, xmit_buf + off); - off += osc_data_length(data->channel[p].type); - members++; - } - } - - //output packet - if(sendto(data->fd, xmit_buf, off, 0, (struct sockaddr*) &(data->dest), data->dest_len) < 0){ - fprintf(stderr, "Failed to transmit OSC packet: %s\n", strerror(errno)); - } - } - } - return 0; -} - -static int backend_handle(size_t num, managed_fd* fds){ - size_t fd; - char recv_buf[OSC_RECV_BUF]; - instance* inst = NULL; - osc_instance* data = NULL; - ssize_t bytes_read = 0; - size_t c; - char* osc_fmt = NULL; - char* osc_local = NULL; - uint8_t* osc_data = NULL; - - for(fd = 0; fd < num; fd++){ - inst = (instance*) fds[fd].impl; - if(!inst){ - fprintf(stderr, "OSC backend signaled for unknown fd\n"); - continue; - } - - data = (osc_instance*) inst->impl; - - do{ - if(data->learn){ - data->dest_len = sizeof(data->dest); - bytes_read = recvfrom(fds[fd].fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*) &(data->dest), &(data->dest_len)); - } - else{ - bytes_read = recv(fds[fd].fd, recv_buf, sizeof(recv_buf), 0); - } - if(data->root && strncmp(recv_buf, data->root, strlen(data->root))){ - //ignore packet for different root - continue; - } - osc_local = recv_buf + (data->root ? strlen(data->root) : 0); - - if(bytes_read < 0){ - break; - } - - osc_fmt = recv_buf + osc_align(strlen(recv_buf) + 1); - if(*osc_fmt != ','){ - //invalid format string - fprintf(stderr, "Invalid OSC format string in packet\n"); - continue; - } - osc_fmt++; - - osc_data = (uint8_t*) osc_fmt + (osc_align(strlen(osc_fmt) + 2) - 1); - //FIXME check supplied data length - - for(c = 0; c < data->channels; c++){ - //FIXME implement proper OSC path match - //prefix match - if(!strcmp(osc_local, data->channel[c].path)){ - if(strlen(osc_fmt) > data->channel[c].param_index){ - //fprintf(stderr, "Taking parameter %zu of %s (%s), %zd bytes, data offset %zu\n", data->channel[c].param_index, recv_buf, osc_fmt, bytes_read, (osc_data - (uint8_t*)recv_buf)); - if(osc_generate_event(mm_channel(inst, c, 0), data->channel + c, osc_fmt, osc_data, bytes_read - (osc_data - (uint8_t*) recv_buf))){ - fprintf(stderr, "Failed to generate OSC channel event\n"); - } - } - } - } - } while(bytes_read > 0); - - if(bytes_read < 0 && errno != EAGAIN){ - fprintf(stderr, "OSC failed to receive data for instance %s: %s\n", inst->name, strerror(errno)); - } - - if(bytes_read == 0){ - fprintf(stderr, "OSC descriptor for instance %s closed\n", inst->name); - return 1; - } - } - - return 0; -} - -static int backend_start(){ - size_t n, u, fds = 0; - instance** inst = NULL; - osc_instance* data = NULL; - - //fetch all instances - if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ - fprintf(stderr, "Failed to fetch instance list\n"); - return 1; - } - - if(!n){ - free(inst); - return 0; - } - - //update instance identifiers - for(u = 0; u < n; u++){ - data = (osc_instance*) inst[u]->impl; - - if(data->fd >= 0){ - inst[u]->ident = data->fd; - if(mm_manage_fd(data->fd, BACKEND_NAME, 1, inst[u])){ - fprintf(stderr, "Failed to register OSC descriptor for instance %s\n", inst[u]->name); - free(inst); - return 1; - } - fds++; - } - else{ - inst[u]->ident = -1; - } - } - - fprintf(stderr, "OSC backend registered %zu descriptors to core\n", fds); - - free(inst); - return 0; -} - -static int backend_shutdown(){ - size_t n, u, c; - instance** inst = NULL; - osc_instance* data = NULL; - - if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ - fprintf(stderr, "Failed to fetch instance list\n"); - return 1; - } - - for(u = 0; u < n; u++){ - data = (osc_instance*) inst[u]->impl; - for(c = 0; c < data->channels; c++){ - free(data->channel[c].path); - } - free(data->channel); - free(data->root); - close(data->fd); - data->fd = -1; - data->channels = 0; - free(inst[u]->impl); - } - - free(inst); - return 0; -} |