aboutsummaryrefslogtreecommitdiff
path: root/osc.lv2
diff options
context:
space:
mode:
authorHanspeter Portner <dev@open-music-kontrollers.ch>2018-02-10 22:05:27 +0100
committerHanspeter Portner <dev@open-music-kontrollers.ch>2018-02-10 22:05:27 +0100
commit6c9fb705e6663f6127b0d960e35a8e651a722e40 (patch)
treec781279c424725a4157b292bd91c704a0428a675 /osc.lv2
parent56f6f670a043e980f9a0aeb677e233616c3d5f73 (diff)
parente4ddd64de6de2989f905488b0a27b6694649079f (diff)
downloadsynthpod-6c9fb705e6663f6127b0d960e35a8e651a722e40.tar.xz
Merge commit 'e4ddd64de6de2989f905488b0a27b6694649079f'
Diffstat (limited to 'osc.lv2')
-rw-r--r--osc.lv2/CMakeLists.txt16
-rw-r--r--osc.lv2/osc.lv2/reader.h1
-rw-r--r--osc.lv2/osc.lv2/stream.h1031
-rw-r--r--osc.lv2/osc.lv2/writer.h50
-rw-r--r--osc.lv2/test/Makefile15
-rw-r--r--osc.lv2/test/osc_test.c (renamed from osc.lv2/osc_test.c)480
6 files changed, 1552 insertions, 41 deletions
diff --git a/osc.lv2/CMakeLists.txt b/osc.lv2/CMakeLists.txt
deleted file mode 100644
index 64ce73bb..00000000
--- a/osc.lv2/CMakeLists.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-cmake_minimum_required(VERSION 2.8)
-
-project(osc.lv2)
-
-include_directories(${PROJECT_SOURCE_DIR})
-
-set(CMAKE_C_FLAGS "-std=gnu11 -Wextra -Wno-unused-parameter -ffast-math -fvisibility=hidden ${CMAKE_C_FLAGS}")
-set(CMAKE_C_FLAGS "-Wshadow -Wimplicit-function-declaration -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes ${CMAKE_C_FLAGS}")
-
-include(CTest)
-
-if(${BUILD_TESTING})
- add_executable(osc_test
- osc_test.c)
- add_test(NAME API-Test COMMAND osc_test)
-endif()
diff --git a/osc.lv2/osc.lv2/reader.h b/osc.lv2/osc.lv2/reader.h
index 9dda227e..8e0ae459 100644
--- a/osc.lv2/osc.lv2/reader.h
+++ b/osc.lv2/osc.lv2/reader.h
@@ -20,6 +20,7 @@
#include <stdbool.h>
#include <string.h>
+#include <stdarg.h>
#include <osc.lv2/osc.h>
#include <osc.lv2/endian.h>
diff --git a/osc.lv2/osc.lv2/stream.h b/osc.lv2/osc.lv2/stream.h
new file mode 100644
index 00000000..1717dfca
--- /dev/null
+++ b/osc.lv2/osc.lv2/stream.h
@@ -0,0 +1,1031 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source 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
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_STREAM_H
+#define LV2_OSC_STREAM_H
+
+#include <stdbool.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <osc.lv2/osc.h>
+
+#if !defined(LV2_OSC_STREAM_SNDBUF)
+# define LV2_OSC_STREAM_SNDBUF 0x100000 // 1 M
+#endif
+
+#if !defined(LV2_OSC_STREAM_RCVBUF)
+# define LV2_OSC_STREAM_RCVBUF 0x100000 // 1 M
+#endif
+
+#if !defined(LV2_OSC_STREAM_REQBUF)
+# define LV2_OSC_STREAM_REQBUF 1024
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void *
+(*LV2_OSC_Stream_Write_Request)(void *data, size_t minimum, size_t *maximum);
+
+typedef void
+(*LV2_OSC_Stream_Write_Advance)(void *data, size_t written);
+
+typedef const void *
+(*LV2_OSC_Stream_Read_Request)(void *data, size_t *toread);
+
+typedef void
+(*LV2_OSC_Stream_Read_Advance)(void *data);
+
+typedef struct _LV2_OSC_Address LV2_OSC_Address;
+typedef struct _LV2_OSC_Driver LV2_OSC_Driver;
+typedef struct _LV2_OSC_Stream LV2_OSC_Stream;
+
+struct _LV2_OSC_Address {
+ socklen_t len;
+ union {
+ struct sockaddr in;
+ struct sockaddr_in in4;
+ struct sockaddr_in6 in6;
+ };
+};
+
+struct _LV2_OSC_Driver {
+ LV2_OSC_Stream_Write_Request write_req;
+ LV2_OSC_Stream_Write_Advance write_adv;
+ LV2_OSC_Stream_Read_Request read_req;
+ LV2_OSC_Stream_Read_Advance read_adv;
+};
+
+struct _LV2_OSC_Stream {
+ int socket_family;
+ int socket_type;
+ int protocol;
+ bool server;
+ bool slip;
+ int sock;
+ int fd;
+ LV2_OSC_Address self;
+ LV2_OSC_Address peer;
+ const LV2_OSC_Driver *driv;
+ void *data;
+ uint8_t tx_buf [8092];
+ uint8_t rx_buf [8092];
+ size_t rx_off;
+};
+
+typedef enum _LV2_OSC_Enum {
+ LV2_OSC_NONE = (0 << 0),
+ LV2_OSC_SEND = (1 << 0),
+ LV2_OSC_RECV = (1 << 1)
+} LV2_OSC_Enum;
+
+static const char *udp_prefix = "osc.udp://";
+static const char *tcp_prefix = "osc.tcp://";
+static const char *tcp_slip_prefix = "osc.slip.tcp://";
+static const char *tcp_prefix_prefix = "osc.prefix.tcp://";
+//FIXME serial
+
+static int
+lv2_osc_stream_init(LV2_OSC_Stream *stream, const char *url,
+ const LV2_OSC_Driver *driv, void *data)
+{
+ memset(stream, 0x0, sizeof(LV2_OSC_Stream));
+
+ char *dup = strdup(url);
+ if(!dup)
+ {
+ fprintf(stderr, "%s: out-of-memory\n", __func__);
+ goto fail;
+ }
+
+ char *ptr = dup;
+ char *tmp;
+
+ if(strncmp(ptr, udp_prefix, strlen(udp_prefix)) == 0)
+ {
+ stream->slip = false;
+ stream->socket_family = AF_INET;
+ stream->socket_type = SOCK_DGRAM;
+ stream->protocol = IPPROTO_UDP;
+ ptr += strlen(udp_prefix);
+ }
+ else if(strncmp(ptr, tcp_prefix, strlen(tcp_prefix)) == 0)
+ {
+ stream->slip = true;
+ stream->socket_family = AF_INET;
+ stream->socket_type = SOCK_STREAM;
+ stream->protocol = IPPROTO_TCP;
+ ptr += strlen(tcp_prefix);
+ }
+ else if(strncmp(ptr, tcp_slip_prefix, strlen(tcp_slip_prefix)) == 0)
+ {
+ stream->slip = true;
+ stream->socket_family = AF_INET;
+ stream->socket_type = SOCK_STREAM;
+ stream->protocol = IPPROTO_TCP;
+ ptr += strlen(tcp_slip_prefix);
+ }
+ else if(strncmp(ptr, tcp_prefix_prefix, strlen(tcp_prefix_prefix)) == 0)
+ {
+ stream->slip = false;
+ stream->socket_family = AF_INET;
+ stream->socket_type = SOCK_STREAM;
+ stream->protocol = IPPROTO_TCP;
+ ptr += strlen(tcp_prefix_prefix);
+ }
+ else
+ {
+ fprintf(stderr, "%s: invalid protocol\n", __func__);
+ goto fail;
+ }
+
+ if(ptr[0] == '\0')
+ {
+ fprintf(stderr, "%s: URI has no colon\n", __func__);
+ goto fail;
+ }
+
+ const char *node = NULL;
+ const char *iface = NULL;
+ const char *service = NULL;
+
+ char *colon = strrchr(ptr, ':');
+
+ // optional IPv6
+ if(ptr[0] == '[')
+ {
+ stream->socket_family = AF_INET6;
+ ++ptr;
+ }
+
+ node = ptr;
+
+ // optional IPv6
+ if( (tmp = strchr(ptr, '%')) )
+ {
+ if(stream->socket_family != AF_INET6)
+ {
+ fprintf(stderr, "%s: no IPv6 interface delimiter expected here\n", __func__);
+ goto fail;
+ }
+
+ ptr = tmp;
+ ptr[0] = '\0';
+ iface = ++ptr;
+ }
+
+ // optional IPv6
+ if( (tmp = strchr(ptr, ']')) )
+ if(ptr)
+ {
+ if(stream->socket_family != AF_INET6)
+ {
+ fprintf(stderr, "%s: no closing IPv6 bracket expected here\n", __func__);
+ goto fail;
+ }
+
+ ptr = tmp;
+ ptr[0] = '\0';
+ ++ptr;
+ }
+
+ // mandatory IPv4/6
+ ptr = strchr(ptr, ':');
+ if(!ptr)
+ {
+ fprintf(stderr, "%s: pre-service colon expected\n", __func__);
+ goto fail;
+ }
+
+ ptr[0] = '\0';
+
+ service = ++ptr;
+
+ if(strlen(node) == 0)
+ {
+ node = NULL;
+ stream->server = true;
+ }
+
+ stream->sock = socket(stream->socket_family, stream->socket_type,
+ stream->protocol);
+
+ if(stream->sock < 0)
+ {
+ fprintf(stderr, "%s: socket failed\n", __func__);
+ goto fail;
+ }
+
+ if(fcntl(stream->sock, F_SETFL, O_NONBLOCK) == -1)
+ {
+ fprintf(stderr, "%s: fcntl failed\n", __func__);
+ goto fail;
+ }
+
+ const int sendbuff = LV2_OSC_STREAM_SNDBUF;
+ const int recvbuff = LV2_OSC_STREAM_RCVBUF;
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_SNDBUF, &sendbuff, sizeof(int))== -1)
+ {
+ fprintf(stderr, "%s: setsockopt failed\n", __func__);
+ goto fail;
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_RCVBUF, &recvbuff, sizeof(int))== -1)
+ {
+ fprintf(stderr, "%s: setsockopt failed\n", __func__);
+ goto fail;
+ }
+
+ stream->driv = driv;
+ stream->data = data;
+
+ if(stream->socket_family == AF_INET) // IPv4
+ {
+ if(stream->server)
+ {
+ // resolve self address
+ struct addrinfo hints;
+ memset(&hints, 0x0, sizeof(struct addrinfo));
+ hints.ai_family = stream->socket_family;
+ hints.ai_socktype = stream->socket_type;
+ hints.ai_protocol = stream->protocol;
+
+ struct addrinfo *res;
+ if(getaddrinfo(node, service, &hints, &res) != 0)
+ {
+ fprintf(stderr, "%s: getaddrinfo failed\n", __func__);
+ goto fail;
+ }
+ if(res->ai_addrlen != sizeof(stream->peer.in4))
+ {
+ fprintf(stderr, "%s: IPv4 address expected\n", __func__);
+ goto fail;
+ }
+
+ stream->self.len = res->ai_addrlen;
+ stream->self.in = *res->ai_addr;
+ stream->self.in4.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ freeaddrinfo(res);
+
+ if(bind(stream->sock, &stream->self.in, stream->self.len) != 0)
+ {
+ fprintf(stderr, "%s: bind failed\n", __func__);
+ goto fail;
+ }
+ }
+ else // client
+ {
+ stream->self.len = sizeof(stream->self.in4);
+ stream->self.in4.sin_family = stream->socket_family;
+ stream->self.in4.sin_port = htons(0);
+ stream->self.in4.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ if(bind(stream->sock, &stream->self.in, stream->self.len) != 0)
+ {
+ fprintf(stderr, "%s: bind failed\n", __func__);
+ goto fail;
+ }
+
+ // resolve peer address
+ struct addrinfo hints;
+ memset(&hints, 0x0, sizeof(struct addrinfo));
+ hints.ai_family = stream->socket_family;
+ hints.ai_socktype = stream->socket_type;
+ hints.ai_protocol = stream->protocol;
+
+ struct addrinfo *res;
+ if(getaddrinfo(node, service, &hints, &res) != 0)
+ {
+ fprintf(stderr, "%s: getaddrinfo failed\n", __func__);
+ goto fail;
+ }
+ if(res->ai_addrlen != sizeof(stream->peer.in4))
+ {
+ fprintf(stderr, "%s: IPv4 address failed\n", __func__);
+ goto fail;
+ }
+
+ stream->peer.len = res->ai_addrlen;
+ stream->peer.in = *res->ai_addr;
+
+ freeaddrinfo(res);
+ }
+
+ if(stream->socket_type == SOCK_DGRAM)
+ {
+ const int broadcast = 1;
+
+ if(setsockopt(stream->sock, SOL_SOCKET, SO_BROADCAST,
+ &broadcast, sizeof(broadcast)) != 0)
+ {
+ fprintf(stderr, "%s: setsockopt failed\n", __func__);
+ goto fail;
+ }
+
+ //FIXME handle multicast
+ }
+ else if(stream->socket_type == SOCK_STREAM)
+ {
+ const int flag = 1;
+
+ if(setsockopt(stream->sock, stream->protocol,
+ TCP_NODELAY, &flag, sizeof(int)) != 0)
+ {
+ fprintf(stderr, "%s: setsockopt failed\n", __func__);
+ goto fail;
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_KEEPALIVE, &flag, sizeof(int)) != 0)
+ {
+ fprintf(stderr, "%s: setsockopt failed\n", __func__);
+ goto fail;
+ }
+
+ if(stream->server)
+ {
+ if(listen(stream->sock, 1) != 0)
+ {
+ fprintf(stderr, "%s: listen failed\n", __func__);
+ goto fail;
+ }
+ }
+ else // client
+ {
+ connect(stream->sock, &stream->peer.in, stream->peer.len);
+ }
+ }
+ else
+ {
+ fprintf(stderr, "%s: invalid socket type\n", __func__);
+ goto fail;
+ }
+ }
+ else if(stream->socket_family == AF_INET6) // IPv6
+ {
+ if(stream->server)
+ {
+ // resolve self address
+ struct addrinfo hints;
+ memset(&hints, 0x0, sizeof(struct addrinfo));
+ hints.ai_family = stream->socket_family;
+ hints.ai_socktype = stream->socket_type;
+ hints.ai_protocol = stream->protocol;
+
+ struct addrinfo *res;
+ if(getaddrinfo(node, service, &hints, &res) != 0)
+ {
+ fprintf(stderr, "%s: getaddrinfo failed\n", __func__);
+ goto fail;
+ }
+ if(res->ai_addrlen != sizeof(stream->peer.in6))
+ {
+ fprintf(stderr, "%s: IPv6 address expected\n", __func__);
+ goto fail;
+ }
+
+ stream->self.len = res->ai_addrlen;
+ stream->self.in = *res->ai_addr;
+ stream->self.in6.sin6_addr = in6addr_any;
+ if(iface)
+ {
+ stream->self.in6.sin6_scope_id = if_nametoindex(iface);
+ }
+
+ freeaddrinfo(res);
+
+ if(bind(stream->sock, &stream->self.in, stream->self.len) != 0)
+ {
+ fprintf(stderr, "%s: bind failed\n", __func__);
+ goto fail;
+ }
+ }
+ else // client
+ {
+ stream->self.len = sizeof(stream->self.in6);
+ stream->self.in6.sin6_family = stream->socket_family;
+ stream->self.in6.sin6_port = htons(0);
+ stream->self.in6.sin6_addr = in6addr_any;
+ if(iface)
+ {
+ stream->self.in6.sin6_scope_id = if_nametoindex(iface);
+ }
+
+ if(bind(stream->sock, &stream->self.in, stream->self.len) != 0)
+ {
+ fprintf(stderr, "%s: bind failed\n", __func__);
+ goto fail;
+ }
+
+ // resolve peer address
+ struct addrinfo hints;
+ memset(&hints, 0x0, sizeof(struct addrinfo));
+ hints.ai_family = stream->socket_family;
+ hints.ai_socktype = stream->socket_type;
+ hints.ai_protocol = stream->protocol;
+
+ struct addrinfo *res;
+ if(getaddrinfo(node, service, &hints, &res) != 0)
+ {
+ fprintf(stderr, "%s: getaddrinfo failed\n", __func__);
+ goto fail;
+ }
+ if(res->ai_addrlen != sizeof(stream->peer.in6))
+ {
+ fprintf(stderr, "%s: IPv6 address expected\n", __func__);
+ goto fail;
+ }
+ stream->peer.len = res->ai_addrlen;
+ stream->peer.in = *res->ai_addr;
+ if(iface)
+ {
+ stream->peer.in6.sin6_scope_id = if_nametoindex(iface);
+ }
+
+ freeaddrinfo(res);
+ }
+
+ if(stream->socket_type == SOCK_DGRAM)
+ {
+ // nothing to do
+ }
+ else if(stream->socket_type == SOCK_STREAM)
+ {
+ const int flag = 1;
+
+ if(setsockopt(stream->sock, stream->protocol,
+ TCP_NODELAY, &flag, sizeof(int)) != 0)
+ {
+ fprintf(stderr, "%s: setsockopt failed\n", __func__);
+ goto fail;
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_KEEPALIVE, &flag, sizeof(int)) != 0)
+ {
+ fprintf(stderr, "%s: setsockopt failed\n", __func__);
+ goto fail;
+ }
+
+ if(stream->server)
+ {
+ if(listen(stream->sock, 1) != 0)
+ {
+ fprintf(stderr, "%s: listen failed\n", __func__);
+ goto fail;
+ }
+ }
+ else // client
+ {
+ connect(stream->sock, &stream->peer.in, stream->peer.len);
+ }
+ }
+ else
+ {
+ fprintf(stderr, "%s: invalid socket type\n", __func__);
+ goto fail;
+ }
+ }
+ else
+ {
+ fprintf(stderr, "%s: invalid socket family\n", __func__);
+ goto fail;
+ }
+
+ free(dup);
+
+ return 0;
+
+fail:
+ if(dup)
+ {
+ free(dup);
+ }
+
+ if(stream->sock >= 0)
+ {
+ close(stream->sock);
+ stream->sock = 0;
+ }
+
+ return -1;
+}
+
+#define SLIP_END 0300 // 0xC0, 192, indicates end of packet
+#define SLIP_ESC 0333 // 0xDB, 219, indicates byte stuffing
+#define SLIP_END_REPLACE 0334 // 0xDC, 220, ESC ESC_END means END data byte
+#define SLIP_ESC_REPLACE 0335 // 0xDD, 221, ESC ESC_ESC means ESC data byte
+
+// SLIP encoding
+static size_t
+lv2_osc_slip_encode_inline(uint8_t *dst, size_t len)
+{
+ if(len == 0)
+ return 0;
+
+ const uint8_t *end = dst + len;
+
+ // estimate new size
+ size_t size = 2; // double ended SLIP
+ for(const uint8_t *from=dst; from<end; from++, size++)
+ {
+ if( (*from == SLIP_END) || (*from == SLIP_ESC))
+ size ++;
+ }
+
+ // fast track if no escaping needed
+ if(size == len + 2)
+ {
+ memmove(dst+1, dst, len);
+ dst[0] = SLIP_END;
+ dst[size-1] = SLIP_END;
+
+ return size;
+ }
+
+ // slow track if some escaping needed
+ uint8_t *to = dst + size - 1;
+ *to-- = SLIP_END;
+ for(const uint8_t *from=end-1; from>=dst; from--)
+ {
+ if(*from == SLIP_END)
+ {
+ *to-- = SLIP_END_REPLACE;
+ *to-- = SLIP_ESC;
+ }
+ else if(*from == SLIP_ESC)
+ {
+ *to-- = SLIP_ESC_REPLACE;
+ *to-- = SLIP_ESC;
+ }
+ else
+ *to-- = *from;
+ }
+ *to-- = SLIP_END;
+
+ return size;
+}
+
+// SLIP decoding
+static size_t
+lv2_osc_slip_decode_inline(uint8_t *dst, size_t len, size_t *size)
+{
+ const uint8_t *src = dst;
+ const uint8_t *end = dst + len;
+ uint8_t *ptr = dst;
+
+ bool whole = false;
+
+ if( (src < end) && (*src == SLIP_END) )
+ {
+ whole = true;
+ src++;
+ }
+
+ while(src < end)
+ {
+ if(*src == SLIP_ESC)
+ {
+ if(src == end-1)
+ break;
+
+ src++;
+ if(*src == SLIP_END_REPLACE)
+ *ptr++ = SLIP_END;
+ else if(*src == SLIP_ESC_REPLACE)
+ *ptr++ = SLIP_ESC;
+ src++;
+ }
+ else if(*src == SLIP_END)
+ {
+ src++;
+
+ *size = whole ? ptr - dst : 0;
+ return src - dst;
+ }
+ else
+ {
+ *ptr++ = *src++;
+ }
+ }
+
+ *size = 0;
+ return 0;
+}
+
+static LV2_OSC_Enum
+lv2_osc_stream_run(LV2_OSC_Stream *stream)
+{
+ LV2_OSC_Enum ev = LV2_OSC_NONE;
+
+ // handle connections
+ if( (stream->socket_type == SOCK_STREAM)
+ && (stream->server)
+ && (stream->fd <= 0)) // no peer
+ {
+ stream->peer.len = sizeof(stream->peer.in);
+ stream->fd = accept(stream->sock, &stream->peer.in, &stream->peer.len);
+
+ if(stream->fd > 0)
+ {
+ const int flag = 1;
+ const int sendbuff = LV2_OSC_STREAM_SNDBUF;
+ const int recvbuff = LV2_OSC_STREAM_RCVBUF;
+
+ if(fcntl(stream->fd, F_SETFL, O_NONBLOCK) == -1)
+ {
+ fprintf(stderr, "%s: fcntl failed\n", __func__);
+ }
+
+ if(setsockopt(stream->fd, stream->protocol,
+ TCP_NODELAY, &flag, sizeof(int)) != 0)
+ {
+ fprintf(stderr, "%s: setsockopt failed\n", __func__);
+ }
+
+ if(setsockopt(stream->sock, SOL_SOCKET,
+ SO_KEEPALIVE, &flag, sizeof(int)) != 0)
+ {
+ fprintf(stderr, "%s: setsockopt failed\n", __func__);
+ }
+
+ if(setsockopt(stream->fd, SOL_SOCKET,
+ SO_SNDBUF, &sendbuff, sizeof(int))== -1)
+ {
+ fprintf(stderr, "%s: setsockopt failed\n", __func__);
+ }
+
+ if(setsockopt(stream->fd, SOL_SOCKET,
+ SO_RCVBUF, &recvbuff, sizeof(int))== -1)
+ {
+ fprintf(stderr, "%s: setsockopt failed\n", __func__);
+ }
+
+ //FIXME ev |=
+ }
+ }
+
+ // send everything
+ if(stream->socket_type == SOCK_DGRAM)
+ {
+ if(stream->peer.len) // has a peer
+ {
+ const uint8_t *buf;
+ size_t tosend;
+
+ while( (buf = stream->driv->read_req(stream->data, &tosend)) )
+ {
+ const ssize_t sent = sendto(stream->sock, buf, tosend, 0,
+ &stream->peer.in, stream->peer.len);
+
+ if(sent == -1)
+ {
+ if( (errno = EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // full queue
+ break;
+ }
+
+ fprintf(stderr, "%s: sendto: %s\n", __func__, strerror(errno));
+ break;
+ }
+ else if(sent != (ssize_t)tosend)
+ {
+ fprintf(stderr, "%s: only sent %zi of %zu bytes", __func__, sent, tosend);
+ break;
+ }
+
+ stream->driv->read_adv(stream->data);
+ ev |= LV2_OSC_SEND;
+ }
+ }
+ }
+ else if(stream->socket_type == SOCK_STREAM)
+ {
+ const int fd = stream->server
+ ? stream->fd
+ : stream->sock;
+
+ if(fd > 0)
+ {
+ const uint8_t *buf;
+ size_t tosend;
+
+ while( (buf = stream->driv->read_req(stream->data, &tosend)) )
+ {
+ if(stream->slip) // SLIP framed
+ {
+ if(tosend <= sizeof(stream->tx_buf)) // check if there is enough memory
+ {
+ memcpy(stream->tx_buf, buf, tosend);
+ tosend = lv2_osc_slip_encode_inline(stream->tx_buf, tosend);
+ }
+ else
+ {
+ tosend = 0;
+ }
+ }
+ else // uint32_t prefix frames
+ {
+ const size_t nsize = tosend + sizeof(uint32_t);
+
+ if(nsize <= sizeof(stream->tx_buf)) // check if there is enough memory
+ {
+ const uint32_t prefix = htonl(tosend);
+
+ memcpy(stream->tx_buf, &prefix, sizeof(uint32_t));
+ memcpy(stream->tx_buf + sizeof(uint32_t), buf, tosend);
+ tosend = nsize;
+ }
+ else
+ {
+ tosend = 0;
+ }
+ }
+
+ const ssize_t sent = tosend
+ ? send(fd, stream->tx_buf, tosend, 0)
+ : 0;
+
+ if(sent == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ break;
+ }
+ else if(errno == ECONNRESET)
+ {
+ if(stream->server)
+ {
+ // peer has shut down
+ close(stream->fd);
+ stream->fd = 0;
+ break;
+ }
+ else
+ {
+ assert(false); //FIXME reconnect
+ }
+ }
+
+ fprintf(stderr, "%s: send: %s\n", __func__, strerror(errno));
+ break;
+ }
+ else if(sent != (ssize_t)tosend)
+ {
+ fprintf(stderr, "%s: only sent %zi of %zu bytes", __func__, sent, tosend);
+ break;
+ }
+
+ stream->driv->read_adv(stream->data);
+ ev |= LV2_OSC_SEND;
+ }
+ }
+ }
+
+ // recv everything
+ if(stream->socket_type == SOCK_DGRAM)
+ {
+ uint8_t *buf;
+ size_t max_len;
+
+ while( (buf = stream->driv->write_req(stream->data,
+ LV2_OSC_STREAM_REQBUF, &max_len)) )
+ {
+ struct sockaddr in;
+ socklen_t in_len = sizeof(in);
+
+ const ssize_t recvd = recvfrom(stream->sock, buf, max_len, 0,
+ &in, &in_len);
+
+ if(recvd == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+
+ fprintf(stderr, "%s: recv: %s\n", __func__, strerror(errno));
+ break;
+ }
+ else if(recvd == 0)
+ {
+ // peer has shut down
+ break;
+ }
+
+ stream->peer.len = in_len;
+ stream->peer.in = in;
+
+ stream->driv->write_adv(stream->data, recvd);
+ ev |= LV2_OSC_RECV;
+ }
+ }
+ else if(stream->socket_type == SOCK_STREAM)
+ {
+ const int fd = stream->server
+ ? stream->fd
+ : stream->sock;
+
+ if(fd > 0)
+ {
+ if(stream->slip) // SLIP framed
+ {
+ while(true)
+ {
+ ssize_t recvd = recv(fd, stream->rx_buf + stream->rx_off,
+ sizeof(stream->rx_buf) - stream->rx_off, 0);
+
+ if(recvd == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+ else if(errno == ECONNRESET)
+ {
+ if(stream->server)
+ {
+ // peer has shut down
+ close(stream->fd);
+ stream->fd = 0;
+ break;
+ }
+ else
+ {
+ assert(false); //FIXME reconnect
+ }
+ }
+
+ fprintf(stderr, "%s: recv: %s\n", __func__, strerror(errno));
+ break;
+ }
+ else if( (recvd == 0) && stream->server)
+ {
+ // peer has shut down
+ close(stream->fd);
+ stream->fd = 0;
+ break;
+ }
+
+ uint8_t *ptr = stream->rx_buf;
+ recvd += stream->rx_off;
+
+ while(recvd > 0)
+ {
+ size_t size;
+ const size_t parsed = lv2_osc_slip_decode_inline(ptr, recvd, &size);
+
+ if(size) // dispatch
+ {
+ uint8_t *buf ;
+
+ if( (buf = stream->driv->write_req(stream->data, size, NULL)) )
+ {
+ memcpy(buf, ptr, size);
+
+ stream->driv->write_adv(stream->data, size);
+ ev |= LV2_OSC_RECV;
+ }
+ else
+ {
+ fprintf(stderr, "%s: write buffer overflow", __func__);
+ }
+ }
+
+ if(parsed)
+ {
+ ptr += parsed;
+ recvd -= parsed;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if(recvd > 0) // is there remaining chunk for next call?
+ {
+ memmove(stream->rx_buf, ptr, recvd);
+ stream->rx_off = recvd;
+ }
+ else
+ {
+ stream->rx_off = 0;
+ }
+
+ break;
+ }
+ }
+ else // uint32_t prefix frames
+ {
+ uint8_t *buf;
+
+ while( (buf = stream->driv->write_req(stream->data,
+ LV2_OSC_STREAM_REQBUF, NULL)) )
+ {
+ uint32_t prefix;
+
+ ssize_t recvd = recv(fd, &prefix, sizeof(uint32_t), 0);
+ if(recvd == sizeof(uint32_t))
+ {
+ prefix = ntohl(prefix); //FIXME check prefix <= max_len
+ recvd = recv(fd, buf, prefix, 0);
+ }
+
+ if(recvd == -1)
+ {
+ if( (errno == EAGAIN) || (errno == EWOULDBLOCK) )
+ {
+ // empty queue
+ break;
+ }
+ else if(errno == ECONNRESET)
+ {
+ if(stream->server)
+ {
+ // peer has shut down
+ close(stream->fd);
+ stream->fd = 0;
+ break;
+ }
+ else
+ {
+ assert(false); //FIXME reconnect
+ }
+ }
+
+ fprintf(stderr, "%s: recv: %s\n", __func__, strerror(errno));
+ break;
+ }
+ else if( (recvd == 0) && stream->server)
+ {
+ // peer has shut down
+ close(stream->fd);
+ stream->fd = 0;
+ break;
+ }
+
+ stream->driv->write_adv(stream->data, recvd);
+ ev |= LV2_OSC_RECV;
+ }
+ }
+ }
+ }
+
+ return ev;
+}
+
+static int
+lv2_osc_stream_deinit(LV2_OSC_Stream *stream)
+{
+ if(stream->fd >= 0)
+ {
+ close(stream->fd);
+ stream->fd = 0;
+ }
+
+ if(stream->sock >= 0)
+ {
+ close(stream->sock);
+ stream->sock = 0;
+ }
+
+ return 0;
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_STREAM_H
diff --git a/osc.lv2/osc.lv2/writer.h b/osc.lv2/osc.lv2/writer.h
index c081cad0..97957a97 100644
--- a/osc.lv2/osc.lv2/writer.h
+++ b/osc.lv2/osc.lv2/writer.h
@@ -332,9 +332,11 @@ lv2_osc_writer_arg_varlist(LV2_OSC_Writer *writer, const char *fmt, va_list args
return false;
break;
case LV2_OSC_BLOB:
- if(!lv2_osc_writer_add_blob(writer, va_arg(args, int32_t), va_arg(args, const uint8_t *)))
+ {
+ const int32_t len = va_arg(args, int32_t);
+ if(!lv2_osc_writer_add_blob(writer, len, va_arg(args, const uint8_t *)))
return false;
- break;
+ } break;
case LV2_OSC_TRUE:
case LV2_OSC_FALSE:
@@ -356,9 +358,11 @@ lv2_osc_writer_arg_varlist(LV2_OSC_Writer *writer, const char *fmt, va_list args
break;
case LV2_OSC_MIDI:
- if(!lv2_osc_writer_add_midi(writer, va_arg(args, int32_t), va_arg(args, const uint8_t *)))
+ {
+ const int32_t len = va_arg(args, int32_t);
+ if(!lv2_osc_writer_add_midi(writer, len, va_arg(args, const uint8_t *)))
return false;
- break;
+ } break;
case LV2_OSC_SYMBOL:
if(!lv2_osc_writer_add_symbol(writer, va_arg(args, const char *)))
return false;
@@ -368,10 +372,14 @@ lv2_osc_writer_arg_varlist(LV2_OSC_Writer *writer, const char *fmt, va_list args
return false;
break;
case LV2_OSC_RGBA:
- if(!lv2_osc_writer_add_rgba(writer, va_arg(args, unsigned), va_arg(args, unsigned),
- va_arg(args, unsigned), va_arg(args, unsigned)))
+ {
+ const uint8_t r = va_arg(args, unsigned);
+ const uint8_t g = va_arg(args, unsigned);
+ const uint8_t b = va_arg(args, unsigned);
+ const uint8_t a = va_arg(args, unsigned);
+ if(!lv2_osc_writer_add_rgba(writer, r, g, b, a))
return false;
- break;
+ } break;
}
}
@@ -529,17 +537,25 @@ lv2_osc_writer_packet(LV2_OSC_Writer *writer, LV2_OSC_URID *osc_urid,
m[0] = 0x0; // port
memcpy(&m[1], LV2_ATOM_BODY_CONST(atom), atom->size);
}
- else if(atom->type == osc_urid->OSC_Char)
+ else if(atom->type == osc_urid->ATOM_Literal)
{
- const char c = *(const char *)LV2_ATOM_BODY_CONST(atom);
- if(!lv2_osc_writer_add_char(writer, c))
- return false;
- }
- else if(atom->type == osc_urid->OSC_RGBA)
- {
- const uint8_t *rgba = LV2_ATOM_BODY_CONST(atom);
- if(!lv2_osc_writer_add_rgba(writer, rgba[0], rgba[1], rgba[2], rgba[3]))
- return false;
+ const LV2_Atom_Literal *lit = (LV2_Atom_Literal *)atom;
+
+ if(lit->body.datatype == osc_urid->OSC_Char)
+ {
+ const char c = *(const char *)LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, lit);
+ if(!lv2_osc_writer_add_char(writer, c))
+ return false;
+ }
+ else if(lit->body.datatype == osc_urid->OSC_RGBA)
+ {
+ const uint8_t *rgba = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, atom);
+ uint8_t r, g, b, a;
+ if(sscanf(rgba, "%02"SCNx8"%02"SCNx8"%02"SCNx8"%02"SCNx8, &r, &g, &b, &a) != 4)
+ return false;
+ if(!lv2_osc_writer_add_rgba(writer, r, g, b, a))
+ return false;
+ }
}
}
}
diff --git a/osc.lv2/test/Makefile b/osc.lv2/test/Makefile
new file mode 100644
index 00000000..a9ebca32
--- /dev/null
+++ b/osc.lv2/test/Makefile
@@ -0,0 +1,15 @@
+CC ?= gcc
+#C_FLAGS ?= -I../ -Wall -Wextra -Wpedantic $(shell pkg-config --cflags lv2) \
+# -fprofile-arcs -ftest-coverage
+C_FLAGS ?= -I../ $(shell pkg-config --cflags lv2) \
+ -fprofile-arcs -ftest-coverage
+
+all: osc_test
+
+osc_test: osc_test.c ../osc.lv2/*.h
+ $(CC) -std=gnu11 -g -o $@ $< $(C_FLAGS) -lpthread
+ ./$@
+ gcov $<
+
+clean:
+ rm -f osc_test *.gcov *.gc* vgcore.*
diff --git a/osc.lv2/osc_test.c b/osc.lv2/test/osc_test.c
index 1280fc07..77847ac4 100644
--- a/osc.lv2/osc_test.c
+++ b/osc.lv2/test/osc_test.c
@@ -1,11 +1,13 @@
#include <assert.h>
#include <string.h>
#include <stdio.h>
+#include <pthread.h>
#include <osc.lv2/osc.h>
#include <osc.lv2/reader.h>
#include <osc.lv2/writer.h>
#include <osc.lv2/forge.h>
+#include <osc.lv2/stream.h>
#define BUF_SIZE 8192
#define MAX_URIDS 512
@@ -117,6 +119,17 @@ const uint8_t raw_7 [] = {
',', 0x0, 0x0, 0x0
};
+const uint8_t raw_8 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 't', 'c', 'r',
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+ 0x0, 0x0, 0x0, 'o',
+ 0x1, 0x2, 0x3, 0x4
+};
+
static LV2_URID
_map(LV2_URID_Map_Handle instance, const char *uri)
{
@@ -164,6 +177,8 @@ static LV2_URID_Unmap unmap = {
.unmap = _unmap
};
+//#define DUMP
+#if defined(DUMP)
static void
_dump(const uint8_t *src, const uint8_t *dst, size_t size)
{
@@ -171,6 +186,7 @@ _dump(const uint8_t *src, const uint8_t *dst, size_t size)
printf("%zu %02x %02x\n", i, src[i], dst[i]);
printf("\n");
}
+#endif
static void
_clone(LV2_OSC_Reader *reader, LV2_OSC_Writer *writer, size_t size)
@@ -264,6 +280,10 @@ _test_a(LV2_OSC_Writer *writer, const uint8_t *raw, size_t size)
size_t len;
assert(lv2_osc_writer_finalize(writer, &len) == buf0);
assert(len == size);
+#if defined(DUMP)
+ if(memcmp(raw, buf0, size) != 0)
+ _dump(raw, buf0, size);
+#endif
assert(memcmp(raw, buf0, size) == 0);
// check reader & writer
@@ -275,6 +295,10 @@ _test_a(LV2_OSC_Writer *writer, const uint8_t *raw, size_t size)
// check cloned against raw bytes
assert(lv2_osc_writer_finalize(writer, &len) == buf1);
assert(len == size);
+#if defined(DUMP)
+ if(memcmp(raw, buf1, size) != 0)
+ _dump(raw, buf1, size);
+#endif
assert(memcmp(raw, buf1, size) == 0);
// check forge
@@ -290,6 +314,10 @@ _test_a(LV2_OSC_Writer *writer, const uint8_t *raw, size_t size)
// check deforged against raw bytes
assert(lv2_osc_writer_finalize(writer, &len) == buf1);
assert(len == size);
+#if defined(DUMP)
+ if(memcmp(raw, buf1, size) != 0)
+ _dump(raw, buf1, size);
+#endif
assert(memcmp(raw, buf1, size) == 0);
}
@@ -326,16 +354,18 @@ test_3_a(LV2_OSC_Writer *writer)
static void
test_4_a(LV2_OSC_Writer *writer)
{
- uint8_t m [] = {0x00, 0x90, 24, 0x7f};
- assert(lv2_osc_writer_message_vararg(writer, "/midi", "m", 4, m));
+ const uint8_t m [] = {0x00, 0x90, 24, 0x7f};
+ const int32_t len = sizeof(m);
+ assert(lv2_osc_writer_message_vararg(writer, "/midi", "m", len, m));
_test_a(writer, raw_4, sizeof(raw_4));
}
static void
test_5_a(LV2_OSC_Writer *writer)
{
- uint8_t b [] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6};
- assert(lv2_osc_writer_message_vararg(writer, "/blob", "b", 6, b));
+ const uint8_t b [] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6};
+ const int32_t len = sizeof(b);
+ assert(lv2_osc_writer_message_vararg(writer, "/blob", "b", len, b));
_test_a(writer, raw_5, sizeof(raw_5));
}
@@ -389,6 +419,16 @@ test_7_a(LV2_OSC_Writer *writer)
_test_a(writer, raw_7, sizeof(raw_7));
}
+static void
+test_8_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/ping", "tcr",
+ 1ULL,
+ 'o',
+ 0x1, 0x2, 0x3, 0x4));
+ _test_a(writer, raw_8, sizeof(raw_8));
+}
+
static test_t tests [] = {
test_0_a,
test_1_a,
@@ -398,12 +438,13 @@ static test_t tests [] = {
test_5_a,
test_6_a,
test_7_a,
+ test_8_a,
NULL
-};
-
-int
-main(int argc, char **argv)
+}
+;
+static int
+_run_tests()
{
LV2_OSC_Writer writer;
@@ -419,5 +460,428 @@ main(int argc, char **argv)
cb(&writer);
}
+ assert(unmap.unmap(unmap.handle, 0)== NULL);
+
+ return 0;
+}
+
+typedef struct _item_t item_t;
+typedef struct _stash_t stash_t;
+
+struct _item_t {
+ size_t size;
+ uint8_t buf [];
+};
+
+struct _stash_t {
+ size_t size;
+ item_t **items;
+ item_t *rsvd;
+};
+
+static uint8_t *
+_stash_write_req(stash_t *stash, size_t minimum, size_t *maximum)
+{
+ if(!stash->rsvd || (stash->rsvd->size < minimum))
+ {
+ const size_t sz = sizeof(item_t) + minimum;
+ stash->rsvd = realloc(stash->rsvd, sz);
+ assert(stash->rsvd);
+ stash->rsvd->size = minimum;
+ }
+
+ if(maximum)
+ {
+ *maximum = stash->rsvd->size;
+ }
+
+ return stash->rsvd->buf;
+}
+
+static void
+_stash_write_adv(stash_t *stash, size_t written)
+{
+ assert(stash->rsvd);
+ assert(stash->rsvd->size >= written);
+ stash->rsvd->size = written;
+ stash->size += 1;
+ stash->items = realloc(stash->items, sizeof(item_t *) * stash->size);
+ stash->items[stash->size - 1] = stash->rsvd;
+ stash->rsvd = NULL;
+}
+
+static const uint8_t *
+_stash_read_req(stash_t *stash, size_t *size)
+{
+ if(stash->size == 0)
+ {
+ if(size)
+ {
+ *size = 0;
+ }
+
+ return NULL;
+ }
+
+ item_t *item = stash->items[0];
+
+ if(size)
+ {
+ *size = item->size;
+ }
+
+ return item->buf;
+}
+
+static void
+_stash_read_adv(stash_t *stash)
+{
+ assert(stash->size);
+
+ free(stash->items[0]);
+ stash->size -= 1;
+
+ for(unsigned i = 0; i < stash->size; i++)
+ {
+ stash->items[i] = stash->items[i+1];
+ }
+
+ stash->items = realloc(stash->items, sizeof(item_t *) * stash->size);
+}
+
+static void *
+_write_req(void *data, size_t minimum, size_t *maximum)
+{
+ stash_t *stash = data;
+
+ return _stash_write_req(&stash[0], minimum, maximum);
+}
+
+static void
+_write_adv(void *data, size_t written)
+{
+ stash_t *stash = data;
+
+ _stash_write_adv(&stash[0], written);
+}
+
+static const void *
+_read_req(void *data, size_t *toread)
+{
+ stash_t *stash = data;
+
+ return _stash_read_req(&stash[1], toread);
+}
+
+static void
+_read_adv(void *data)
+{
+ stash_t *stash = data;
+
+ _stash_read_adv(&stash[1]);
+}
+
+static const LV2_OSC_Driver driv = {
+ .write_req = _write_req,
+ .write_adv = _write_adv,
+ .read_req = _read_req,
+ .read_adv = _read_adv
+};
+
+#define COUNT 1024
+
+static void *
+_thread_1(void *data)
+{
+ const char *uri = data;
+
+ LV2_OSC_Stream stream;
+ stash_t stash [2];
+ uint8_t check [COUNT];
+
+ memset(&stream, 0x0, sizeof(stream));
+ memset(stash, 0x0, sizeof(stash));
+ memset(check, 0x0, sizeof(check));
+
+ assert(lv2_osc_stream_init(&stream, uri, &driv, stash) == 0);
+
+ unsigned count = 0;
+ while(true)
+ {
+ const LV2_OSC_Enum ev = lv2_osc_stream_run(&stream);
+
+ if(ev & LV2_OSC_RECV)
+ {
+ const uint8_t *buf_rx;
+ size_t reat;
+
+ while( (buf_rx = _stash_read_req(&stash[0], &reat)) )
+ {
+ LV2_OSC_Reader reader;
+
+ lv2_osc_reader_initialize(&reader, buf_rx, reat);
+ assert(lv2_osc_reader_is_message(&reader));
+
+ OSC_READER_MESSAGE_FOREACH(&reader, arg, reat)
+ {
+ assert(strcmp(arg->path, "/trip") == 0);
+ assert(*arg->type == 'i');
+ assert(arg->size == sizeof(int32_t));
+ assert(check[arg->i] == 0);
+ check[arg->i] = 1;
+ }
+
+ count++;
+
+ while(true)
+ {
+ // send back
+ uint8_t *buf_tx;
+ if( (buf_tx = _stash_write_req(&stash[1], reat, NULL)) )
+ {
+ memcpy(buf_tx, buf_rx, reat);
+
+ _stash_write_adv(&stash[1], reat);
+ break;
+ }
+ }
+
+ _stash_read_adv(&stash[0]);
+ }
+ }
+
+ if(count >= COUNT)
+ {
+ break;
+ }
+ }
+
+ LV2_OSC_Enum ev;
+ do
+ {
+ ev = lv2_osc_stream_run(&stream);
+ } while( (ev & LV2_OSC_SEND) || (stream.fd > 0) );
+
+ sleep(1);
+
+ assert(lv2_osc_stream_deinit(&stream) == 0);
+
+ if(stash[0].rsvd)
+ {
+ free(stash[0].rsvd);
+ stash[0].rsvd = NULL;
+ }
+
+ assert(stash[1].rsvd == 0);
+
+ return NULL;
+}
+
+static void *
+_thread_2(void *data)
+{
+ const char *uri = data;
+
+ LV2_OSC_Stream stream;
+ stash_t stash [2];
+ uint8_t check [COUNT];
+
+ memset(&stream, 0x0, sizeof(stream));
+ memset(stash, 0x0, sizeof(stash));
+ memset(check, 0x0, sizeof(check));
+
+ assert(lv2_osc_stream_init(&stream, uri, &driv, stash) == 0);
+
+ unsigned count = 0;
+ for(int32_t i = 0; i < COUNT; i++)
+ {
+ LV2_OSC_Writer writer;
+
+ while(true)
+ {
+ uint8_t *buf_tx;
+ size_t max;
+ if( (buf_tx = _stash_write_req(&stash[1], 1024, &max)) )
+ {
+ size_t writ;
+ lv2_osc_writer_initialize(&writer, buf_tx, max);
+ assert(lv2_osc_writer_message_vararg(&writer, "/trip", "i", i));
+ assert(lv2_osc_writer_finalize(&writer, &writ) == buf_tx);
+ assert(writ == 16);
+ assert(check[i] == 0);
+ check[i] = 1;
+
+ _stash_write_adv(&stash[1], writ);
+ break;
+ }
+ }
+
+ const LV2_OSC_Enum ev = lv2_osc_stream_run(&stream);
+
+ if(ev & LV2_OSC_RECV)
+ {
+ const uint8_t *buf_rx;
+ size_t reat;
+
+ while( (buf_rx = _stash_read_req(&stash[0], &reat)) )
+ {
+ LV2_OSC_Reader reader;
+
+ lv2_osc_reader_initialize(&reader, buf_rx, reat);
+ assert(lv2_osc_reader_is_message(&reader));
+
+ OSC_READER_MESSAGE_FOREACH(&reader, arg, reat)
+ {
+ assert(strcmp(arg->path, "/trip") == 0);
+ assert(*arg->type == 'i');
+ assert(arg->size == sizeof(int32_t));
+ assert(check[arg->i] == 1);
+ check[arg->i] = 2;
+ }
+
+ count++;
+
+ _stash_read_adv(&stash[0]);
+ }
+ }
+ }
+
+ while(count <= (COUNT - 1))
+ {
+ const LV2_OSC_Enum ev = lv2_osc_stream_run(&stream);
+
+ if(ev & LV2_OSC_RECV)
+ {
+ const uint8_t *buf_rx;
+ size_t reat;
+
+ while( (buf_rx = _stash_read_req(&stash[0], &reat)) )
+ {
+ LV2_OSC_Reader reader;
+
+ lv2_osc_reader_initialize(&reader, buf_rx, reat);
+ assert(lv2_osc_reader_is_message(&reader));
+
+ OSC_READER_MESSAGE_FOREACH(&reader, arg, reat)
+ {
+ assert(strcmp(arg->path, "/trip") == 0);
+ assert(*arg->type == 'i');
+ assert(arg->size == sizeof(int32_t));
+ assert(check[arg->i] == 1);
+ check[arg->i] = 2;
+ }
+
+ count++;
+
+ _stash_read_adv(&stash[0]);
+ }
+ }
+ }
+
+ assert(count == COUNT);
+
+ sleep(1);
+
+ assert(lv2_osc_stream_deinit(&stream) == 0);
+
+ if(stash[0].rsvd)
+ {
+ free(stash[0].rsvd);
+ stash[0].rsvd = NULL;
+ }
+
+ assert(stash[1].rsvd == NULL);
+
+ return NULL;
+}
+
+typedef struct _pair_t pair_t;
+
+struct _pair_t {
+ const char *server;
+ const char *client;
+};
+
+static const pair_t pairs [] = {
+ {
+ .server = "osc.udp://:2222",
+ .client = "osc.udp://localhost:2222"
+ },
+ {
+ .server = "osc.udp://[]:3333",
+ .client = "osc.udp://[::1]:3333"
+ },
+
+ {
+ .server = "osc.udp://:3344",
+ .client = "osc.udp://255.255.255.255:3344"
+ },
+
+ {
+ .server = "osc.tcp://:4444",
+ .client = "osc.tcp://localhost:4444"
+ },
+ {
+ .server = "osc.tcp://[]:5555",
+ .client = "osc.tcp://[::1]:5555"
+ },
+
+ {
+ .server = "osc.slip.tcp://:6666",
+ .client = "osc.slip.tcp://localhost:6666"
+ },
+ {
+ .server = "osc.slip.tcp://[]:7777",
+ .client = "osc.slip.tcp://[::1]:7777"
+ },
+
+ {
+ .server = "osc.prefix.tcp://:8888",
+ .client = "osc.prefix.tcp://localhost:8888"
+ },
+ {
+ .server = "osc.prefix.tcp://[%lo]:9999",
+ .client = "osc.prefix.tcp://[::1%lo]:9999"
+ },
+
+ {
+ .server = NULL,
+ .client = NULL
+ }
+};
+
+int
+main(int argc, char **argv)
+{
+ (void)argc;
+ (void)argv;
+
+ fprintf(stdout, "running main tests:\n");
+ assert(_run_tests() == 0);
+
+ for(const pair_t *pair = pairs; pair->server; pair++)
+ {
+ pthread_t thread_1;
+ pthread_t thread_2;
+
+ const char *uri_1 = pair->server;
+ const char *uri_2 = pair->client;
+
+ fprintf(stdout, "running stream test: <%s> <%s>\n", uri_1, uri_2);
+
+ assert(pthread_create(&thread_1, NULL, _thread_1, (void *)uri_1) == 0);
+ assert(pthread_create(&thread_2, NULL, _thread_2, (void *)uri_2) == 0);
+
+ assert(pthread_join(thread_1, NULL) == 0);
+ assert(pthread_join(thread_2, NULL) == 0);
+ }
+
+ for(unsigned i=0; i<__handle.urid; i++)
+ {
+ urid_t *itm = &__handle.urids[i];
+
+ free(itm->uri);
+ }
+
return 0;
}