diff options
author | Hanspeter Portner <dev@open-music-kontrollers.ch> | 2020-10-02 13:44:49 +0200 |
---|---|---|
committer | Hanspeter Portner <dev@open-music-kontrollers.ch> | 2020-10-02 13:44:49 +0200 |
commit | 16632b7783805a05690821cf416577cc3f80e214 (patch) | |
tree | 8cc1a23557feb4a790a2ee08e8e2d3cdf29689a4 | |
parent | 887ea4943e837add262c212f57c7a668fd91f6ed (diff) | |
download | sherlock.lv2-16632b7783805a05690821cf416577cc3f80e214.tar.xz |
Squashed 'osc.lv2/' changes from 3f2cb5db..cca99c96
cca99c96 fix last commit.
7337b70b stream: add method to get file descriptors.
75dfaf7f gitlab-ci: add aarch64 compile target.
ef9caebc redesign reader pattern matching infrastructure.
8eee956e add unit test for lv2_osc_reader_match.
343a802c fix unused function on mingw.
a7ad4082 fix last commit.
8b356228 ignore missing fnmatch.h on mingw.
8d93e692 ignore missing FNM_EXTMATCH definition on darwin.
4c14d725 meson: compile with -std=gnu11.
c04f3bcd prototype reader message path matching.
c9bb19d7 util: implement pattern matching via fnmatch.
c471d994 add missing stdlib header.
6885c8f1 fix last commit for mingw.
4e5a72cd prototype pollin.
d99b20c1 prototype '*' pattern matching.
52216f48 change hooks infra into recursive function.
3a634d97 fix compile warning.
1d0d2e31 prototype unrolling into hooks.
git-subtree-dir: osc.lv2
git-subtree-split: cca99c96f82b6b1142899a23ec1ac370a7652f8e
-rw-r--r-- | .gitlab-ci.yml | 3 | ||||
-rw-r--r-- | VERSION | 2 | ||||
-rw-r--r-- | osc.lv2/reader.h | 58 | ||||
-rw-r--r-- | osc.lv2/stream.h | 56 | ||||
-rw-r--r-- | osc.lv2/util.h | 126 | ||||
-rw-r--r-- | test/osc_test.c | 411 |
6 files changed, 651 insertions, 5 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ebc4676..f0e54dd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -52,6 +52,9 @@ i686-linux-gnu: arm-linux-gnueabihf: <<: *arm_linux_definition +aarch64-linux-gnu: + <<: *arm_linux_definition + x86_64-w64-mingw32: <<: *universal_w64_definition @@ -1 +1 @@ -0.1.105 +0.1.143 diff --git a/osc.lv2/reader.h b/osc.lv2/reader.h index 8e0ae45..ae46dfa 100644 --- a/osc.lv2/reader.h +++ b/osc.lv2/reader.h @@ -24,14 +24,25 @@ #include <osc.lv2/osc.h> #include <osc.lv2/endian.h> +#include <osc.lv2/util.h> #ifdef __cplusplus extern "C" { #endif + +typedef struct _LV2_OSC_Tree LV2_OSC_Tree; typedef struct _LV2_OSC_Reader LV2_OSC_Reader; typedef struct _LV2_OSC_Item LV2_OSC_Item; typedef struct _LV2_OSC_Arg LV2_OSC_Arg; +typedef void (*LV2_OSC_Branch)(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg, + const LV2_OSC_Tree *tree, void *data); + +struct _LV2_OSC_Tree { + const char *name; + const LV2_OSC_Tree *trees; + LV2_OSC_Branch branch; +}; struct _LV2_OSC_Reader { const uint8_t *buf; @@ -564,6 +575,53 @@ lv2_osc_reader_is_message(LV2_OSC_Reader *reader) return reader->ptr[0] == '/'; //FIXME check path } +static inline void +_lv2_osc_trees_internal(LV2_OSC_Reader *reader, const char *path, const char *from, + LV2_OSC_Arg *arg, const LV2_OSC_Tree *trees, void *data) +{ + const char *ptr = strchr(from, '/'); + const char *pattern = strpbrk(from, "*?[]{}/"); + const bool has_pattern = pattern && (pattern[0] != '/'); + (void)has_pattern; //FIXME + + const size_t len = ptr + ? (size_t)(ptr - from) + : strlen(from); + + for(const LV2_OSC_Tree *tree = trees; tree && tree->name; tree++) + { + if(lv2_osc_pattern_match(from, tree->name, len)) + { + if(tree->trees && ptr) + { + if(tree->branch) + { + LV2_OSC_Reader reader_clone = *reader; + tree->branch(&reader_clone, arg, tree, data); + } + + _lv2_osc_trees_internal(reader, path, &ptr[1], arg, tree->trees, data); + } + else if(tree->branch && !ptr) + { + LV2_OSC_Reader reader_clone = *reader; + tree->branch(&reader_clone, arg, tree, data); + } + } + } +} + +static inline void +lv2_osc_reader_match(LV2_OSC_Reader *reader, size_t len, + const LV2_OSC_Tree *trees, void *data) +{ + LV2_OSC_Arg *arg = OSC_READER_MESSAGE_BEGIN(reader, len); + const char *path = arg->path; + const char *from = &path[1]; + + _lv2_osc_trees_internal(reader, path, from, arg, trees, data); +} + #ifdef __cplusplus } // extern "C" #endif diff --git a/osc.lv2/stream.h b/osc.lv2/stream.h index a86c230..47d338a 100644 --- a/osc.lv2/stream.h +++ b/osc.lv2/stream.h @@ -19,6 +19,7 @@ #define LV2_OSC_STREAM_H #include <stdbool.h> +#include <stdlib.h> #include <string.h> #if !defined(_WIN32) # include <arpa/inet.h> @@ -34,6 +35,7 @@ #include <fcntl.h> #include <errno.h> #include <unistd.h> +#include <poll.h> #include <osc.lv2/osc.h> @@ -665,7 +667,7 @@ lv2_osc_stream_init(LV2_OSC_Stream *stream, const char *url, { memset(stream, 0x0, sizeof(LV2_OSC_Stream)); - strncpy(stream->url, url, sizeof(stream->url)); + strncpy(stream->url, url, sizeof(stream->url) - 1); stream->driv = driv; stream->data = data; stream->sock = -1; @@ -1372,6 +1374,58 @@ lv2_osc_stream_run(LV2_OSC_Stream *stream) return ev; } +static int +lv2_osc_stream_get_file_descriptors(LV2_OSC_Stream *stream, int fds [2]) +{ + if(!fds) + { + return 1; + } + + fds[0] = stream->sock; + fds[1] = stream->fd; + + return 0; +} + +static LV2_OSC_Enum +lv2_osc_stream_pollin(LV2_OSC_Stream *stream, int timeout_ms) +{ + int fd [2]; + + if(lv2_osc_stream_get_file_descriptors(stream, fd) != 0) + { + return LV2_OSC_STREAM_ERRNO(LV2_OSC_NONE, EBADF); + } + + struct pollfd fds [2] = { + [0] = { + .fd = fd[0], + .events = POLLIN, + .revents = 0 + }, + [1] = { + .fd = fd[1], + .events = POLLIN, + .revents = 0 + } + }; + + const int res = poll(fds, 2, timeout_ms); + if(res < 0) + { + return LV2_OSC_STREAM_ERRNO(LV2_OSC_NONE, errno); + } + +#if 0 + fprintf(stderr, "++ %i: %i %i %i %i\n", res, + fds[0].fd, (int)fds[0].revents, + fds[1].fd, (int)fds[1].revents); +#endif + + return lv2_osc_stream_run(stream); +} + #ifdef __cplusplus } // extern "C" #endif diff --git a/osc.lv2/util.h b/osc.lv2/util.h index 195bb86..3517618 100644 --- a/osc.lv2/util.h +++ b/osc.lv2/util.h @@ -23,6 +23,9 @@ #include <inttypes.h> #include <stdio.h> #include <stdlib.h> +#if !defined(_WIN32) +# include <fnmatch.h> +#endif #include <osc.lv2/osc.h> @@ -45,6 +48,15 @@ extern "C" { typedef void (*LV2_OSC_Method)(const char *path, const LV2_Atom_Tuple *arguments, void *data); +typedef struct _LV2_OSC_Hook LV2_OSC_Hook; + +struct _LV2_OSC_Hook { + const char *name; + const LV2_OSC_Hook *hooks; + LV2_OSC_Method method; + void *data; +}; + // characters not allowed in OSC path string static const char invalid_path_chars [] = { ' ', '#', @@ -60,6 +72,120 @@ static const char valid_format_chars [] = { '\0' }; +static bool +lv2_osc_pattern_match(const char *from, const char *name, size_t len) +{ +#if !defined(_WIN32) + size_t nbrace = 0; + +# if defined(FNM_EXTMATCH) + // count opening curly braces + for(size_t i = 0; i < len; i++) + { + if(from[i] == '{') + { + nbrace++; + } + } +# endif + + // allocate temporary pattern buffer + char *pattern = alloca(len + nbrace + 1); + + if(!pattern) + { + return false; + } + +# if defined(FNM_EXTMATCH) + // convert {x,y} to @(x|y) for extended fnmatch + if(nbrace) + { + char *ptr = pattern; + + for(size_t i = 0; i < len; i++) + { + switch(from[i]) + { + case '{': + { + *ptr++ = '@'; + *ptr++ = '('; + } break; + case ',': + { + *ptr++ = '|'; + } break; + case '}': + { + *ptr++ = ')'; + } break; + default: + { + *ptr++ = from[i]; + } break; + } + } + } + else +# endif + { + memcpy(pattern, from, len); + } + + // terminate pattern string with null terminator + pattern[len + nbrace] = '\0'; + +# if defined(FNM_EXTMATCH) + return fnmatch(pattern, name, FNM_NOESCAPE | FNM_EXTMATCH) == 0 ? true : false; +# else + return fnmatch(pattern, name, FNM_NOESCAPE) == 0 ? true : false; +# endif +#else + return strncmp(from, name, len) == 0 ? true : false; +#endif +} + +static void +_lv2_osc_hooks_internal(const char *path, const char *from, + const LV2_Atom_Tuple *arguments, const LV2_OSC_Hook *hooks) +{ + const char *ptr = strchr(from, '/'); + + const size_t len = ptr + ? (size_t)(ptr - from) + : strlen(from); + + for(const LV2_OSC_Hook *hook = hooks; hook && hook->name; hook++) + { + if(lv2_osc_pattern_match(from, hook->name, len)) + { + if(hook->hooks && ptr) + { + from = &ptr[1]; + + _lv2_osc_hooks_internal(path, from, arguments, hook->hooks); + } + else if(hook->method && !ptr) + { + hook->method(path, arguments, hook->data); + } + } + } +} + +/** + TODO +*/ +static void +lv2_osc_hooks(const char *path, const LV2_Atom_Tuple *arguments, void *data) +{ + const LV2_OSC_Hook *hooks = data; + const char *from = &path[1]; + + _lv2_osc_hooks_internal(path, from, arguments, hooks); +} + /** TODO */ diff --git a/test/osc_test.c b/test/osc_test.c index a535277..2c6a710 100644 --- a/test/osc_test.c +++ b/test/osc_test.c @@ -931,16 +931,421 @@ static const pair_t pairs [] = { }; #endif +#if !defined(_WIN32) +static unsigned foo_sub_one = 0; +static unsigned foo_sub_two [2] = { 0, 0 }; +static unsigned foo = 0; +static unsigned bar = 0; + +static void +_one(const char *path, unsigned *flag) +{ + *flag += 1; + + if(!path) + { + return; + } + + assert(!strcmp(path, "/sub/one") + || !strcmp(path, "/*/one") + || !strcmp(path, "/s*/one") + || !strcmp(path, "/su*/one") + || !strcmp(path, "/sub*/one") + || !strcmp(path, "/sub/*") + || !strcmp(path, "/*sub/one") + || !strcmp(path, "/*s*u*b*/one") + || !strcmp(path, "/su[ab]/one") + || !strcmp(path, "/su[a-b]/[!a-np-z]ne") + || !strcmp(path, "/su[a-b]/one") + || !strcmp(path, "/s?b/?ne") + || !strcmp(path, "/s?*/?ne") + || !strcmp(path, "/s?*/*?e") + || !strcmp(path, "/sub/{one,two}")); +} + +static void +_two(const char *path, unsigned *flag) +{ + *flag += 1; + + if(!path) + { + return; + } + + assert(!strcmp(path, "/sub/two") + || !strcmp(path, "/sub/*") + || !strcmp(path, "/sub/{one,two}")); +} + +static void +_foo(const char *path, unsigned *flag) +{ + *flag += 1; + + if(!path) + { + return; + } + + assert(!strcmp(path, "/foo") + || !strcmp(path, "/{foo,bar}")); +} + +static void +_bar(const char *path, unsigned *flag) +{ + *flag += 1; + + if(!path) + { + return; + } + + assert(!strcmp(path, "/bar") + || !strcmp(path, "/{foo,bar}")); +} + +static void +_hook_one(const char *path, const LV2_Atom_Tuple *arguments __attribute__((unused)), + void *data) +{ + _one(path, data); +} + +static void +_hook_two(const char *path, const LV2_Atom_Tuple *arguments __attribute__((unused)), + void *data) +{ + _two(path, data); +} + +static void +_hook_foo(const char *path, const LV2_Atom_Tuple *arguments __attribute__((unused)), + void *data) +{ + _foo(path, data); +} + +static void +_hook_bar(const char *path, const LV2_Atom_Tuple *arguments __attribute__((unused)), + void *data) +{ + _bar(path, data); +} + +static LV2_OSC_Hook hook_sub [] = { + { .name = "one", .method = _hook_one, .data = &foo_sub_one }, + { .name = "two", .method = _hook_two, .data = &foo_sub_two[0] }, + { .name = "two", .method = _hook_two, .data = &foo_sub_two[1] }, + { .name = NULL } +}; + +static LV2_OSC_Hook hook_root [] = { + { .name = "foo", .method = _hook_foo, .data = &foo }, + { .name = "bar", .method = _hook_bar, .data = &bar }, + { .name = "sub", .hooks = hook_sub }, + { .name = NULL } +}; + +static LV2_OSC_Tree tree_sub [4]; + +static void +_branch_one(LV2_OSC_Reader *reader __attribute__((unused)), + LV2_OSC_Arg *arg __attribute__((unused)), + const LV2_OSC_Tree *tree __attribute__((unused)), + void *data __attribute__((unused))) +{ + _one(NULL, &foo_sub_one); +} + +static void +_branch_two(LV2_OSC_Reader *reader __attribute__((unused)), + LV2_OSC_Arg *arg __attribute__((unused)), + const LV2_OSC_Tree *tree __attribute__((unused)), + void *data __attribute__((unused))) +{ + const size_t idx = tree - &tree_sub[1]; + + _two(NULL, &foo_sub_two[idx]); +} + +static void +_branch_foo(LV2_OSC_Reader *reader __attribute__((unused)), + LV2_OSC_Arg *arg __attribute__((unused)), + const LV2_OSC_Tree *tree __attribute__((unused)), + void *data __attribute__((unused))) +{ + _foo(NULL, &foo); +} + +static void +_branch_bar(LV2_OSC_Reader *reader __attribute__((unused)), + LV2_OSC_Arg *arg __attribute__((unused)), + const LV2_OSC_Tree *tree __attribute__((unused)), + void *data __attribute__((unused))) +{ + _bar(NULL, &bar); +} + +static LV2_OSC_Tree tree_sub [] = { + { .name = "one", .branch = _branch_one }, + { .name = "two", .branch = _branch_two }, + { .name = "two", .branch = _branch_two }, + { .name = NULL } +}; + +static LV2_OSC_Tree tree_root [] = { + { .name = "foo", .branch = _branch_foo }, + { .name = "bar", .branch = _branch_bar }, + { .name = "sub", .trees = tree_sub }, + { .name = NULL } +}; + +static bool +_run_test_hooks_internal(const char *path) +{ + foo_sub_one = foo_sub_two[0] = foo_sub_two[1] = foo = bar = false; + + { + LV2_OSC_URID osc_urid; + LV2_Atom_Forge forge; + + lv2_osc_urid_init(&osc_urid, &map); + lv2_atom_forge_init(&forge, &map); + + lv2_atom_forge_set_buffer(&forge, buf0, BUF_SIZE); + assert(lv2_osc_forge_message_vararg(&forge, &osc_urid, path, "")); + + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)buf0;; + assert(lv2_osc_unroll(&osc_urid, obj, lv2_osc_hooks, hook_root) == true); + } + + { + LV2_OSC_Writer writer; + LV2_OSC_Reader reader; + + lv2_osc_writer_initialize(&writer, buf1, BUF_SIZE); + assert(lv2_osc_writer_message_vararg(&writer, path, "") == true); + + size_t len; + const uint8_t *buf = lv2_osc_writer_finalize(&writer, &len); + assert(buf); + assert(len); + + lv2_osc_reader_initialize(&reader, buf, len); + lv2_osc_reader_match(&reader, len, tree_root, NULL); + } + + return true; +} + +static int +_run_test_hooks() +{ + { + assert(_run_test_hooks_internal("/nil") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 0); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/foo") == true); + assert(foo == 2); + assert(bar == 0); + assert(foo_sub_one == 0); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/bar") == true); + assert(foo == 0); + assert(bar == 2); + assert(foo_sub_one == 0); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/sub/nil") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 0); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/sub/one") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 2); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/sub/two") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 0); + assert(foo_sub_two[0] == 2); + assert(foo_sub_two[1] == 2); + } + + { + assert(_run_test_hooks_internal("/sub/*") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 2); + assert(foo_sub_two[0] == 2); + assert(foo_sub_two[1] == 2); + } + + { + assert(_run_test_hooks_internal("/*/one") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 2); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/s*/one") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 2); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/su*/one") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 2); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/sub*/one") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 2); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/*sub/one") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 2); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/*s*u*b*/one") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 2); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/su[ab]/one") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 2); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/su[a-b]/[!a-np-z]ne") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 2); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/su[!a-b]/one") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 0); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/s?b/?ne") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 2); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/s?*/*?e") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 2); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/{foo,bar}") == true); + assert(foo == 2); + assert(bar == 2); + assert(foo_sub_one == 0); + assert(foo_sub_two[0] == 0); + assert(foo_sub_two[1] == 0); + } + + { + assert(_run_test_hooks_internal("/sub/{one,two}") == true); + assert(foo == 0); + assert(bar == 0); + assert(foo_sub_one == 2); + assert(foo_sub_two[0] == 2); + assert(foo_sub_two[1] == 2); + } + + return 0; +} +#endif + int -main(int argc, char **argv) +main(int argc __attribute__((unused)), char **argv __attribute__((unused))) { - (void)argc; - (void)argv; +#if !defined(_WIN32) + (void)lv2_osc_stream_pollin; //FIXME +#endif fprintf(stdout, "running main tests:\n"); assert(_run_tests() == 0); #if !defined(_WIN32) + fprintf(stdout, "running hook tests:\n"); + assert(_run_test_hooks() == 0); +#else + (void)lv2_osc_hooks; //FIXME +#endif + +#if !defined(_WIN32) for(const pair_t *pair = pairs; pair->server; pair++) { pthread_t thread_1; |