/* * Copyright (C) 2004 Steve Harris * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * $Id$ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #ifdef _MSC_VER #define _WINSOCKAPI_ #define snprintf _snprintf #else #include #endif #ifdef WIN32 #include #include #define EADDRINUSE WSAEADDRINUSE #else #include #include #ifdef HAVE_POLL #include #endif #include #include #endif #ifdef WIN32 #define geterror() WSAGetLastError() #else #define geterror() errno #endif #include "lo_types_internal.h" #include "lo_internal.h" #include "lo.h" #include "lo_throw.h" #define LO_HOST_SIZE 1024 typedef struct { lo_timetag ts; char *path; lo_message msg; void *next; } queued_msg_list; struct lo_cs lo_client_sockets = {-1, -1}; static int lo_can_coerce_spec(const char *a, const char *b); static int lo_can_coerce(char a, char b); static void dispatch_method(lo_server s, const char *path, lo_message msg); static int dispatch_queued(lo_server s); static void queue_data(lo_server s, lo_timetag ts, const char *path, lo_message msg); static lo_server lo_server_new_with_proto_internal(const char *group, const char *port, int proto, lo_err_handler err_h); static int lo_server_add_socket(lo_server s, int socket); static void lo_server_del_socket(lo_server s, int index, int socket); static int lo_server_join_multicast_group(lo_server s, const char *group); #ifdef WIN32 #ifndef gai_strerror // Copied from the Win32 SDK // WARNING: The gai_strerror inline functions below use static buffers, // and hence are not thread-safe. We'll use buffers long enough to hold // 1k characters. Any system error messages longer than this will be // returned as empty strings. However 1k should work for the error codes // used by getaddrinfo(). #define GAI_STRERROR_BUFFER_SIZE 1024 char *WSAAPI gai_strerrorA(int ecode) { DWORD dwMsgLen; static char buff[GAI_STRERROR_BUFFER_SIZE + 1]; dwMsgLen = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_IGNORE_INSERTS |FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, ecode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)buff, GAI_STRERROR_BUFFER_SIZE, NULL); return buff; } #endif static int stateWSock = -1; int initWSock() { WORD reqversion; WSADATA wsaData; if(stateWSock >= 0) return stateWSock; /* TODO - which version of Winsock do we actually need? */ reqversion = MAKEWORD( 2, 2 ); if(WSAStartup(reqversion,&wsaData) != 0) { /* Couldn't initialize Winsock */ stateWSock = 0; } else if ( LOBYTE( wsaData.wVersion ) != LOBYTE(reqversion) || HIBYTE( wsaData.wVersion ) != HIBYTE(reqversion) ) { /* wrong version */ WSACleanup(); stateWSock = 0; } else stateWSock = 1; return stateWSock; } #endif lo_server lo_server_new(const char *port, lo_err_handler err_h) { return lo_server_new_with_proto(port, LO_DEFAULT, err_h); } lo_server lo_server_new_multicast(const char *group, const char *port, lo_err_handler err_h) { return lo_server_new_with_proto_internal(group, port, LO_UDP, err_h); } lo_server lo_server_new_with_proto(const char *port, int proto, lo_err_handler err_h) { return lo_server_new_with_proto_internal(NULL, port, proto, err_h); } lo_server lo_server_new_with_proto_internal(const char *group, const char *port, int proto, lo_err_handler err_h) { lo_server s; struct addrinfo *ai = NULL, *it, *used; struct addrinfo hints; int ret = -1; int tries = 0; char pnum[16]; const char *service; char hostname[LO_HOST_SIZE]; // Set real protocol, if Default is requested if (proto==LO_DEFAULT) { #ifndef WIN32 if (port && *port == '/') proto = LO_UNIX; else #endif proto = LO_UDP; } #ifdef WIN32 if(!initWSock()) return NULL; #endif s = calloc(1, sizeof(struct _lo_server)); if (!s) return 0; s->err_h = err_h; s->first = NULL; s->ai = NULL; s->hostname = NULL; s->protocol = proto; s->port = 0; s->path = NULL; s->queued = NULL; s->sockets_len = 1; s->sockets_alloc = 2; s->sockets = calloc(2, sizeof(*(s->sockets))); if (!s->sockets) { free(s); return 0; } s->sockets[0].fd = -1; memset(&hints, 0, sizeof(hints)); if (proto == LO_UDP) { hints.ai_socktype = SOCK_DGRAM; } else if (proto == LO_TCP) { hints.ai_socktype = SOCK_STREAM; } #ifndef WIN32 else if (proto == LO_UNIX) { struct sockaddr_un sa; s->sockets[0].fd = socket(PF_UNIX, SOCK_DGRAM, 0); if (s->sockets[0].fd == -1) { int err = geterror(); used = NULL; lo_throw(s, err, strerror(err), "socket()"); lo_server_free(s); return NULL; } sa.sun_family = AF_UNIX; strncpy(sa.sun_path, port, sizeof(sa.sun_path)-1); if ((ret = bind(s->sockets[0].fd, (struct sockaddr *)&sa, sizeof(sa))) < 0) { int err = geterror(); lo_throw(s, err, strerror(err), "bind()"); lo_server_free(s); return NULL; } s->path = strdup(port); return s; } #endif else { lo_throw(s, LO_UNKNOWNPROTO, "Unknown protocol", NULL); lo_server_free(s); return NULL; } #ifdef ENABLE_IPV6 hints.ai_family = PF_UNSPEC; #else hints.ai_family = PF_INET; #endif hints.ai_flags = AI_PASSIVE; if (!port) { service = pnum; } else { service = port; } do { if (!port) { /* not a good way to get random numbers, but its not critical */ snprintf(pnum, 15, "%ld", 10000 + ((unsigned int)rand() + time(NULL)) % 10000); } if ((ret = getaddrinfo(NULL, service, &hints, &ai))) { lo_throw(s, ret, gai_strerror(ret), NULL); freeaddrinfo(ai); return NULL; } used = NULL; s->ai = ai; s->sockets[0].fd = -1; s->port = 0; for (it = ai; it && s->sockets[0].fd == -1; it = it->ai_next) { used = it; s->sockets[0].fd = socket(it->ai_family, hints.ai_socktype, 0); } if (s->sockets[0].fd == -1) { int err = geterror(); used = NULL; lo_throw(s, err, strerror(err), "socket()"); lo_server_free(s); return NULL; } /* Join multicast group if specified. */ /* This must be done before bind() on POSIX, but after bind() Windows. */ #ifndef WIN32 if (group != NULL) if (lo_server_join_multicast_group(s, group)) return NULL; #endif if ((ret = bind(s->sockets[0].fd, used->ai_addr, used->ai_addrlen)) < 0) { int err = geterror(); if (err == EINVAL || err == EADDRINUSE) { used = NULL; continue; } lo_throw(s, err, strerror(err), "bind()"); lo_server_free(s); return NULL; } } while (!used && tries++ < 16); /* Join multicast group if specified (see above). */ #ifdef WIN32 if (group != NULL) if (lo_server_join_multicast_group(s, group)) return NULL; #endif if (proto == LO_TCP) { listen(s->sockets[0].fd, 8); } if (!used) { lo_throw(s, LO_NOPORT, "cannot find free port", NULL); lo_server_free(s); return NULL; } if (proto == LO_UDP) { lo_client_sockets.udp = s->sockets[0].fd; } else if (proto == LO_TCP) { lo_client_sockets.tcp = s->sockets[0].fd; } /* Set hostname to empty string */ hostname[0] = '\0'; #ifdef ENABLE_IPV6 /* Try it the IPV6 friendly way first */ for (it = ai; it; it = it->ai_next) { if (getnameinfo(it->ai_addr, it->ai_addrlen, hostname, sizeof(hostname), NULL, 0, NI_NAMEREQD) == 0) { break; } } /* check to make sure getnameinfo() didn't just set the hostname to "::". Needed on Darwin. */ if (hostname[0] == ':') { hostname[0] = '\0'; } #endif /* Fallback to the oldschool (i.e. more reliable) way */ if (!hostname[0]) { struct hostent *he; gethostname(hostname, sizeof(hostname)); he = gethostbyname(hostname); if (he) { strncpy(hostname, he->h_name, sizeof(hostname)); } } /* soethings gone really wrong, just hope its local only */ if (!hostname[0]) { strcpy(hostname, "localhost"); } s->hostname = strdup(hostname); if (used->ai_family == PF_INET6) { struct sockaddr_in6 *addr = (struct sockaddr_in6 *)used->ai_addr; s->port = htons(addr->sin6_port); } else if (used->ai_family == PF_INET) { struct sockaddr_in *addr = (struct sockaddr_in *)used->ai_addr; s->port = htons(addr->sin_port); } else { lo_throw(s, LO_UNKNOWNPROTO, "unknown protocol family", NULL); s->port = atoi(port); } return s; } int lo_server_join_multicast_group(lo_server s, const char *group) { struct ip_mreq mreq; unsigned int yes = 1; memset(&mreq, 0, sizeof(mreq)); #ifdef HAVE_INET_ATON if (inet_aton(group, &mreq.imr_multiaddr)==0) { int err = geterror(); lo_throw(s, err, strerror(err), "inet_aton()"); lo_server_free(s); return err; } #else mreq.imr_multiaddr.s_addr = inet_addr(group); if (mreq.imr_multiaddr.s_addr == INADDR_ANY || mreq.imr_multiaddr.s_addr == INADDR_NONE) { int err = geterror(); lo_throw(s, err, strerror(err), "inet_addr()"); lo_server_free(s); return err; } #endif mreq.imr_interface.s_addr=htonl(INADDR_ANY); if (setsockopt(s->sockets[0].fd,IPPROTO_IP,IP_ADD_MEMBERSHIP, &mreq,sizeof(mreq)) < 0) { int err = geterror(); lo_throw(s, err, strerror(err), "setsockopt(IP_ADD_MEMBERSHIP)"); lo_server_free(s); return err; } if (setsockopt(s->sockets[0].fd,SOL_SOCKET,SO_REUSEADDR, &yes,sizeof(yes)) < 0) { int err = geterror(); lo_throw(s, err, strerror(err), "setsockopt(SO_REUSEADDR)"); lo_server_free(s); return err; } #ifdef SO_REUSEPORT if (setsockopt(s->sockets[0].fd,SOL_SOCKET,SO_REUSEPORT, &yes,sizeof(yes)) < 0) { int err = geterror(); lo_throw(s, err, strerror(err), "setsockopt(SO_REUSEPORT)"); lo_server_free(s); return err; } #endif return 0; } void lo_server_free(lo_server s) { if (s) { lo_method it; lo_method next; int i; for (i=s->sockets_len-1; i >= 0; i--) { if (s->sockets[i].fd != -1) { if (s->protocol == LO_UDP && s->sockets[i].fd == lo_client_sockets.udp) { lo_client_sockets.udp = -1; } else if (s->protocol == LO_TCP && s->sockets[0].fd == lo_client_sockets.tcp) { lo_client_sockets.tcp = -1; } close(s->sockets[i].fd); s->sockets[i].fd = -1; } } if (s->ai) { freeaddrinfo(s->ai); s->ai=NULL; } if (s->hostname) { free(s->hostname); s->hostname = NULL; } if (s->path) { if (s->protocol == LO_UNIX) unlink( s->path ); free(s->path); s->path = NULL; } for (it = s->first; it; it = next) { next = it->next; free((char *)it->path); free((char *)it->typespec); free(it); } free(s->sockets); free(s); } } void *lo_server_recv_raw(lo_server s, size_t *size) { char buffer[LO_MAX_MSG_SIZE]; int ret; void *data = NULL; #ifdef WIN32 if(!initWSock()) return NULL; #endif s->addr_len = sizeof(s->addr); ret = recvfrom(s->sockets[0].fd, buffer, LO_MAX_MSG_SIZE, 0, (struct sockaddr *)&s->addr, &s->addr_len); if (ret <= 0) { return NULL; } data = malloc(ret); memcpy(data, buffer, ret); if (size) *size = ret; return data; } void *lo_server_recv_raw_stream(lo_server s, size_t *size) { struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); char buffer[LO_MAX_MSG_SIZE]; int32_t read_size; int ret=0, i; void *data = NULL; int sock = -1; int repeat = 1; #ifdef HAVE_SELECT #ifndef HAVE_POLL fd_set ps; int nfds=0; #endif #endif /* check sockets in reverse order so that already-open sockets * have priority. this allows checking for closed sockets even * when new connections are being requested. it also allows to * continue looping through the list of sockets after closing and * deleting a socket, since deleting sockets doesn't affect the * order of the array to the left of the index. */ #ifdef HAVE_POLL for (i=0; i < s->sockets_len; i++) { s->sockets[i].events = POLLIN | POLLPRI; s->sockets[i].revents = 0; } poll(s->sockets, s->sockets_len, -1); for (i=(s->sockets_len-1); i >= 0; --i) { if (s->sockets[i].revents == POLLERR || s->sockets[i].revents == POLLHUP) { if (i>0) { close(s->sockets[i].fd); lo_server_del_socket(s, i, s->sockets[i].fd); continue; } else return NULL; } if (s->sockets[i].revents) { sock = s->sockets[i].fd; #else #ifdef HAVE_SELECT if(!initWSock()) return NULL; FD_ZERO(&ps); for (i=(s->sockets_len-1); i >= 0; --i) { FD_SET(s->sockets[i].fd, &ps); if (s->sockets[i].fd > nfds) nfds = s->sockets[i].fd; } if (select(nfds+1,&ps,NULL,NULL,NULL) == SOCKET_ERROR) return NULL; for (i=0; i < s->sockets_len; i++) { if (FD_ISSET(s->sockets[i].fd, &ps)) { sock = s->sockets[i].fd; #endif #endif if (sock == -1 || !repeat) return NULL; /* zeroeth socket is listening for new connections */ if (sock == s->sockets[0].fd) { sock = accept(sock, (struct sockaddr *)&addr, &addr_len); i = lo_server_add_socket(s, sock); /* only repeat this loop for sockets other than the listening * socket, (otherwise i will be wrong next time around) */ repeat = 0; } if (i<0) { close(sock); return NULL; } ret = recv(sock, &read_size, sizeof(read_size), 0); read_size = ntohl(read_size); if (read_size > LO_MAX_MSG_SIZE || ret <= 0) { close(sock); lo_server_del_socket(s, i, sock); if (ret > 0) lo_throw(s, LO_TOOBIG, "Message too large", "recv()"); continue; } ret = recv(sock, buffer, read_size, 0); if (ret <= 0) { close(sock); lo_server_del_socket(s, i, sock); continue; } /* end of loop over sockets: successfully read data */ break; } } data = malloc(ret); memcpy(data, buffer, ret); if (size) *size = ret; return data; } int lo_server_wait(lo_server s, int timeout) { int sched_timeout = lo_server_next_event_delay(s) * 1000; int i; #ifdef HAVE_SELECT #ifndef HAVE_POLL fd_set ps; struct timeval stimeout; #endif #endif #ifdef HAVE_POLL for (i=0; i < s->sockets_len; i++) { s->sockets[i].events = POLLIN | POLLPRI | POLLERR | POLLHUP; s->sockets[i].revents = 0; } poll(s->sockets, s->sockets_len, timeout > sched_timeout ? sched_timeout : timeout); if (lo_server_next_event_delay(s) < 0.01) return 1; for (i=0; i < s->sockets_len; i++) { if (s->sockets[i].revents == POLLERR || s->sockets[i].revents == POLLHUP) return 0; if (s->sockets[i].revents) return 1; } #else #ifdef HAVE_SELECT int res,to,nfds=0; if(!initWSock()) return 0; to = timeout > sched_timeout ? sched_timeout : timeout; stimeout.tv_sec = to/1000; stimeout.tv_usec = (to%1000)*1000; FD_ZERO(&ps); for (i=0; i < s->sockets_len; i++) { FD_SET(s->sockets[i].fd,&ps); if (s->sockets[i].fd > nfds) nfds = s->sockets[i].fd; } res = select(nfds+1,&ps,NULL,NULL,&stimeout); if(res == SOCKET_ERROR) return 0; if (res || lo_server_next_event_delay(s) < 0.01) return 1; #endif #endif return 0; } int lo_server_recv_noblock(lo_server s, int timeout) { int result = lo_server_wait(s,timeout); if (result>0) { return lo_server_recv(s); } else { return 0; } } int lo_server_recv(lo_server s) { void *data; size_t size; double sched_time = lo_server_next_event_delay(s); int i; #ifdef HAVE_SELECT #ifndef HAVE_POLL fd_set ps; struct timeval stimeout; int res,nfds=0; #endif #endif again: if (sched_time > 0.01) { if (sched_time > 10.0) { sched_time = 10.0; } #ifdef HAVE_POLL for (i=0; i < s->sockets_len; i++) { s->sockets[i].events = POLLIN | POLLPRI | POLLERR | POLLHUP; s->sockets[i].revents = 0; } poll(s->sockets, s->sockets_len, (int)(sched_time * 1000.0)); for (i=0; i < s->sockets_len; i++) { if ( s->sockets[i].revents == POLLERR || s->sockets[i].revents == POLLHUP) return 0; if (s->sockets[i].revents) break; } if (i >= s->sockets_len) { sched_time = lo_server_next_event_delay(s); if (sched_time > 0.01) goto again; return dispatch_queued(s); } #else #ifdef HAVE_SELECT if(!initWSock()) return 0; FD_ZERO(&ps); for (i=0; i < s->sockets_len; i++) { FD_SET(s->sockets[i].fd,&ps); if (s->sockets[i].fd > nfds) nfds = s->sockets[i].fd; } stimeout.tv_sec = sched_time; stimeout.tv_usec = (sched_time-stimeout.tv_sec)*1.e6; res = select(nfds+1,&ps,NULL,NULL,&stimeout); if(res == SOCKET_ERROR) { return 0; } if(!res) { sched_time = lo_server_next_event_delay(s); if (sched_time > 0.01) goto again; return dispatch_queued(s); } #endif #endif } else { return dispatch_queued(s); } if (s->protocol == LO_TCP) { data = lo_server_recv_raw_stream(s, &size); } else { data = lo_server_recv_raw(s, &size); } if (!data) { return 0; } if (lo_server_dispatch_data(s, data, size) < 0) { free(data); return -1; } free(data); return size; } /** \internal \brief Add a socket to this server's list of sockets. * \param s The lo_server * \param socket The socket number to add. * \return The index number of the added socket, or -1 on failure. */ int lo_server_add_socket(lo_server s, int socket) { if ((s->sockets_len+1) > s->sockets_alloc) { void *sp = realloc(s->sockets, sizeof(*(s->sockets))*(s->sockets_alloc*2)); if (!sp) return -1; s->sockets = sp; s->sockets_alloc *= 2; } s->sockets[s->sockets_len].fd = socket; s->sockets_len ++; return s->sockets_len-1; } /** \internal \brief Delete a socket from this server's list of sockets. * \param s The lo_server * \param index The index of the socket to delete, -1 if socket is provided. * \param socket The socket number to delete, -1 if index is provided. * \return The index number of the added socket. */ void lo_server_del_socket(lo_server s, int index, int socket) { int i; if (index < 0 && socket != -1) { for (index=0; index < s->sockets_len; index++) if (s->sockets[index].fd == socket) break; } if (index < 0 || index >= s->sockets_len) return; for (i=index+1; i < s->sockets_len; i++) s->sockets[i-1] = s->sockets[i]; s->sockets_len --; } int lo_server_dispatch_data(lo_server s, void *data, size_t size) { int result = 0; char *path = data; ssize_t len = lo_validate_string(data, size); if (len < 0) { lo_throw(s, -len, "Invalid message path", NULL); return len; } if (!strcmp(data, "#bundle")) { char *pos; int remain; uint32_t elem_len; lo_timetag ts, now; ssize_t bundle_result = lo_validate_bundle(data, size); if (bundle_result < 0) { lo_throw(s, -bundle_result, "Invalid bundle", NULL); return bundle_result; } pos = (char *)data + len; remain = size - len; lo_timetag_now(&now); ts.sec = lo_otoh32(*((uint32_t *)pos)); pos += 4; ts.frac = lo_otoh32(*((uint32_t *)pos)); pos += 4; remain -= 8; while (remain >= 4) { lo_message msg; elem_len = lo_otoh32(*((uint32_t *)pos)); pos += 4; remain -= 4; msg = lo_message_deserialise(pos, elem_len, &result); if (!msg) { lo_throw(s, result, "Invalid bundle element received", path); return -result; } // set timetag from bundle msg->ts = ts; // test for immediate dispatch if ((ts.sec == LO_TT_IMMEDIATE.sec && ts.frac == LO_TT_IMMEDIATE.frac) || lo_timetag_diff(ts, now) <= 0.0) { dispatch_method(s, pos, msg); lo_message_free(msg); } else { queue_data(s, ts, pos, msg); } pos += elem_len; remain -= elem_len; } } else { lo_message msg = lo_message_deserialise(data, size, &result); if (NULL == msg) { lo_throw(s, result, "Invalid message received", path); return -result; } dispatch_method(s, data, msg); lo_message_free(msg); } return size; } /* returns the time in seconds until the next scheduled event */ double lo_server_next_event_delay(lo_server s) { if (s->queued) { lo_timetag now; double delay; lo_timetag_now(&now); delay = lo_timetag_diff(((queued_msg_list *)s->queued)->ts, now); delay = delay > 100.0 ? 100.0 : delay; delay = delay < 0.0 ? 0.0 : delay; return delay; } return 100.0; } static void dispatch_method(lo_server s, const char *path, lo_message msg) { char *types = msg->types + 1; int argc = strlen(types); lo_arg **argv = msg->argv; lo_method it; int ret = 1; int err; int pattern = strpbrk(path, " #*,?[]{}") != NULL; lo_address src = lo_address_new(NULL, NULL); char hostname[LO_HOST_SIZE]; char portname[32]; const char *pptr; msg->source = src; //inet_ntop(s->addr.ss_family, &s->addr.padding, hostname, sizeof(hostname)); if (s->protocol == LO_UDP && s->addr_len>0) { err = getnameinfo((struct sockaddr *)&s->addr, sizeof(s->addr), hostname, sizeof(hostname), portname, sizeof(portname), NI_NUMERICHOST | NI_NUMERICSERV); if (err) { switch (err) { case EAI_AGAIN: lo_throw(s, err, "Try again", path); break; case EAI_BADFLAGS: lo_throw(s, err, "Bad flags", path); break; case EAI_FAIL: lo_throw(s, err, "Failed", path); break; case EAI_FAMILY: lo_throw(s, err, "Cannot resolve address family", path); break; case EAI_MEMORY: lo_throw(s, err, "Out of memory", path); break; case EAI_NONAME: lo_throw(s, err, "Cannot resolve", path); break; #ifndef WIN32 case EAI_SYSTEM: lo_throw(s, err, strerror(err), path); break; #endif default: lo_throw(s, err, "Unknown error", path); break; } return; } } else { hostname[0] = '\0'; portname[0] = '\0'; } // Store the source information in the lo_address if (src->host) free(src->host); if (src->host) free(src->port); src->host = strdup(hostname); src->port = strdup(portname); src->protocol = s->protocol; for (it = s->first; it; it = it->next) { /* If paths match or handler is wildcard */ if (!it->path || !strcmp(path, it->path) || (pattern && lo_pattern_match(it->path, path))) { /* If types match or handler is wildcard */ if (!it->typespec || !strcmp(types, it->typespec)) { /* Send wildcard path to generic handler, expanded path to others. */ pptr = path; if (it->path) pptr = it->path; ret = it->handler(pptr, types, argv, argc, msg, it->user_data); } else if (lo_can_coerce_spec(types, it->typespec)) { int i; int opsize = 0; char *ptr = msg->data; char *data_co, *data_co_ptr; argv = calloc(argc, sizeof(lo_arg *)); for (i=0; itypespec[i], ptr); ptr += lo_arg_size(types[i], ptr); } data_co = malloc(opsize); data_co_ptr = data_co; ptr = msg->data; for (i=0; itypespec[i], (lo_arg *)data_co_ptr, types[i], (lo_arg *)ptr); data_co_ptr += lo_arg_size(it->typespec[i], data_co_ptr); ptr += lo_arg_size(types[i], ptr); } /* Send wildcard path to generic handler, expanded path to others. */ pptr = path; if (it->path) pptr = it->path; ret = it->handler(pptr, it->typespec, argv, argc, msg, it->user_data); free(argv); free(data_co); argv = NULL; } if (ret == 0 && !pattern) { break; } } } /* If we find no matching methods, check for protocol level stuff */ if (ret == 1 && s->protocol == LO_UDP) { char *pos = strrchr(path, '/'); /* if its a method enumeration call */ if (pos && *(pos+1) == '\0') { lo_message reply = lo_message_new(); int len = strlen(path); lo_strlist *sl = NULL, *slit, *slnew, *slend; if (!strcmp(types, "i")) { lo_message_add_int32(reply, argv[0]->i); } lo_message_add_string(reply, path); for (it = s->first; it; it = it->next) { /* If paths match */ if (it->path && !strncmp(path, it->path, len)) { char *tmp; char *sec; tmp = malloc(strlen(it->path + len) + 1); strcpy(tmp, it->path + len); #ifdef WIN32 sec = strchr(tmp,'/'); #else sec = index(tmp, '/'); #endif if (sec) *sec = '\0'; slend = sl; for (slit = sl; slit; slend = slit, slit = slit->next) { if (!strcmp(slit->str, tmp)) { free(tmp); tmp = NULL; break; } } if (tmp) { slnew = calloc(1, sizeof(lo_strlist)); slnew->str = tmp; slnew->next = NULL; if (!slend) { sl = slnew; } else { slend->next = slnew; } } } } slit = sl; while(slit) { lo_message_add_string(reply, slit->str); slnew = slit; slit = slit->next; free(slnew->str); free(slnew); } lo_send_message(src, "#reply", reply); lo_message_free(reply); } } lo_address_free(src); msg->source = NULL; } int lo_server_events_pending(lo_server s) { return s->queued != 0; } static void queue_data(lo_server s, lo_timetag ts, const char *path, lo_message msg) { /* insert blob into future dispatch queue */ queued_msg_list *it = s->queued; queued_msg_list *prev = NULL; queued_msg_list *ins = calloc(1, sizeof(queued_msg_list)); ins->ts = ts; ins->path = strdup(path); ins->msg = msg; while (it) { if (lo_timetag_diff(it->ts, ts) > 0.0) { if (prev) { prev->next = ins; } else { s->queued = ins; ins->next = NULL; } ins->next = it; return; } prev = it; it = it->next; } /* fell through, so this event is last */ if (prev) { prev->next = ins; } else { s->queued = ins; } ins->next = NULL; } static int dispatch_queued(lo_server s) { queued_msg_list *head = s->queued; queued_msg_list *tailhead; lo_timetag disp_time; if (!head) { lo_throw(s, LO_INT_ERR, "attempted to dispatch with empty queue", "timeout"); return 1; } disp_time = head->ts; do { char *path; lo_message msg; tailhead = head->next; path = ((queued_msg_list *)s->queued)->path; msg = ((queued_msg_list *)s->queued)->msg; dispatch_method(s, path, msg); free(path); lo_message_free(msg); free((queued_msg_list *)s->queued); s->queued = tailhead; head = tailhead; } while (head && lo_timetag_diff(head->ts, disp_time) < FLT_EPSILON); return 0; } lo_method lo_server_add_method(lo_server s, const char *path, const char *typespec, lo_method_handler h, void *user_data) { lo_method m = calloc(1, sizeof(struct _lo_method)); lo_method it; if (path && strpbrk(path, " #*,?[]{}")) { return NULL; } if (path) { m->path = strdup(path); } else { m->path = NULL; } if (typespec) { m->typespec = strdup(typespec); } else { m->typespec = NULL; } m->handler = h; m->user_data = user_data; m->next = NULL; /* append the new method to the list */ if (!s->first) { s->first = m; } else { /* get to the last member of the list */ for (it=s->first; it->next; it=it->next); it->next = m; } return m; } void lo_server_del_method(lo_server s, const char *path, const char *typespec) { lo_method it, prev, next; int pattern = 0; if (!s->first) return; if (path) pattern = strpbrk(path, " #*,?[]{}") != NULL; it = s->first; prev = it; while (it) { /* incase we free it */ next = it->next; /* If paths match or handler is wildcard */ if ((it->path == path) || (path && it->path && !strcmp(path, it->path)) || (pattern && it->path && lo_pattern_match(it->path, path))) { /* If types match or handler is wildcard */ if ((it->typespec == typespec) || (typespec && it->typespec && !strcmp(typespec, it->typespec)) ) { /* Take care when removing the head. */ if (it == s->first) { s->first = it->next; } else { prev->next = it->next; } next = it->next; free((void *)it->path); free((void *)it->typespec); free(it); it = prev; } } prev = it; if (it) it = next; } } int lo_server_get_socket_fd(lo_server s) { if (s->protocol != LO_UDP && s->protocol != LO_TCP #ifndef WIN32 && s->protocol != LO_UNIX #endif ) { return -1; /* assume it is not supported */ } return s->sockets[0].fd; } int lo_server_get_port(lo_server s) { if (!s) { return 0; } return s->port; } int lo_server_get_protocol(lo_server s) { if (!s) { return -1; } return s->protocol; } char *lo_server_get_url(lo_server s) { int ret=0; char *buf; if (!s) { return NULL; } if (s->protocol == LO_UDP || s->protocol == LO_TCP) { char *proto = s->protocol == LO_UDP ? "udp" : "tcp"; #ifndef _MSC_VER ret = snprintf(NULL, 0, "osc.%s://%s:%d/", proto, s->hostname, s->port); #endif if (ret <= 0) { /* this libc is not C99 compliant, guess a size */ ret = 1023; } buf = malloc((ret + 2) * sizeof(char)); snprintf(buf, ret+1, "osc.%s://%s:%d/", proto, s->hostname, s->port); return buf; } #ifndef WIN32 else if (s->protocol == LO_UNIX) { ret = snprintf(NULL, 0, "osc.unix:///%s", s->path); if (ret <= 0) { /* this libc is not C99 compliant, guess a size */ ret = 1023; } buf = malloc((ret + 2) * sizeof(char)); snprintf(buf, ret+1, "osc.unix:///%s", s->path); return buf; } #endif return NULL; } void lo_server_pp(lo_server s) { lo_method it; printf("socket: %d\n\n", s->sockets[0].fd); printf("Methods\n"); for (it = s->first; it; it = it->next) { printf("\n"); lo_method_pp_prefix(it, " "); } } static int lo_can_coerce_spec(const char *a, const char *b) { unsigned int i; if (strlen(a) != strlen(b)) { return 0; } for (i=0; a[i]; i++) { if (!lo_can_coerce(a[i], b[i])) { return 0; } } return 1; } static int lo_can_coerce(char a, char b) { return ((a == b) || (lo_is_numerical_type(a) && lo_is_numerical_type(b)) || (lo_is_string_type(a) && lo_is_string_type (b))); } void lo_throw(lo_server s, int errnum, const char *message, const char *path) { if (s->err_h) { (*s->err_h)(errnum, message, path); } } /* vi:set ts=8 sts=4 sw=4: */