From bef4dd78ca2a610b4c669ea2c7a477283769997d Mon Sep 17 00:00:00 2001
From: cbdev <cb@cbcdn.com>
Date: Thu, 12 Dec 2019 20:29:03 +0100
Subject: Extend hostspec parsing and implement local mode for sACN (#39)

---
 backends/artnet.c       |  6 +++---
 backends/libmmbackend.c | 15 ++++++++++++++-
 backends/libmmbackend.h |  6 ++++--
 backends/maweb.c        |  4 ++--
 backends/osc.c          |  6 +++---
 backends/sacn.c         | 26 +++++++++++++++++++++-----
 backends/sacn.h         |  1 -
 backends/sacn.md        |  6 +++++-
 8 files changed, 52 insertions(+), 18 deletions(-)

diff --git a/backends/artnet.c b/backends/artnet.c
index b181296..aa7c48e 100644
--- a/backends/artnet.c
+++ b/backends/artnet.c
@@ -68,14 +68,14 @@ MM_PLUGIN_API int init(){
 }
 
 static int artnet_configure(char* option, char* value){
-	char* host = NULL, *port = NULL;
+	char* host = NULL, *port = NULL, *fd_opts = NULL;
 	if(!strcmp(option, "net")){
 		//configure default net
 		default_net = strtoul(value, NULL, 0);
 		return 0;
 	}
 	else if(!strcmp(option, "bind")){
-		mmbackend_parse_hostspec(value, &host, &port);
+		mmbackend_parse_hostspec(value, &host, &port, &fd_opts);
 
 		if(!host){
 			fprintf(stderr, "Not valid ArtNet bind address given\n");
@@ -134,7 +134,7 @@ static int artnet_configure_instance(instance* inst, char* option, char* value){
 		return 0;
 	}
 	else if(!strcmp(option, "dest") || !strcmp(option, "destination")){
-		mmbackend_parse_hostspec(value, &host, &port);
+		mmbackend_parse_hostspec(value, &host, &port, NULL);
 
 		if(!host){
 			fprintf(stderr, "Not a valid ArtNet destination for instance %s\n", inst->name);
diff --git a/backends/libmmbackend.c b/backends/libmmbackend.c
index ccbeb52..7dbb4ae 100644
--- a/backends/libmmbackend.c
+++ b/backends/libmmbackend.c
@@ -1,6 +1,6 @@
 #include "libmmbackend.h"
 
-void mmbackend_parse_hostspec(char* spec, char** host, char** port){
+void mmbackend_parse_hostspec(char* spec, char** host, char** port, char** options){
 	size_t u = 0;
 
 	if(!spec || !host || !port){
@@ -29,6 +29,19 @@ void mmbackend_parse_hostspec(char* spec, char** host, char** port){
 		spec[u] = 0;
 		*port = spec + u + 1;
 	}
+
+	if(options){
+		*options = NULL;
+		if(*port){
+			//scan for space after port
+			for(u = 0; (*port)[u] && !isspace((*port)[u]); u++){
+			}
+			if(isspace((*port)[u])){
+				(*port)[u] = 0;
+				*options = (*port) + u + 1;
+			}
+		}
+	}
 }
 
 int mmbackend_parse_sockaddr(char* host, char* port, struct sockaddr_storage* addr, socklen_t* len){
diff --git a/backends/libmmbackend.h b/backends/libmmbackend.h
index 5749119..76e588f 100644
--- a/backends/libmmbackend.h
+++ b/backends/libmmbackend.h
@@ -22,13 +22,15 @@
 
 /* 
  * Parse spec as host specification in the form
- *	host port
+ *	host port [options]
  * into its constituent parts.
  * Returns offsets into the original string and modifies it.
  * Returns NULL in *port if none given.
  * Returns NULL in both *port and *host if spec was an empty string.
+ * Returns a pointer after the port in *options if options is non-NULL
+ * and the port was not followed by \0
  */
-void mmbackend_parse_hostspec(char* spec, char** host, char** port);
+void mmbackend_parse_hostspec(char* spec, char** host, char** port, char** options);
 
 /* 
  * Parse a given host / port combination into a sockaddr_storage
diff --git a/backends/maweb.c b/backends/maweb.c
index 4d41f0e..18d0351 100644
--- a/backends/maweb.c
+++ b/backends/maweb.c
@@ -145,10 +145,10 @@ static int maweb_configure(char* option, char* value){
 
 static int maweb_configure_instance(instance* inst, char* option, char* value){
 	maweb_instance_data* data = (maweb_instance_data*) inst->impl;
-	char* host = NULL, *port = NULL;
+	char* host = NULL, *port = NULL, *fd_opts = NULL;
 
 	if(!strcmp(option, "host")){
-		mmbackend_parse_hostspec(value, &host, &port);
+		mmbackend_parse_hostspec(value, &host, &port, &fd_opts);
 		if(!host){
 			fprintf(stderr, "Invalid host specified for maweb instance %s\n", inst->name);
 			return 1;
diff --git a/backends/osc.c b/backends/osc.c
index 2c65ecb..15b5c24 100644
--- a/backends/osc.c
+++ b/backends/osc.c
@@ -496,7 +496,7 @@ static int osc_register_pattern(osc_instance_data* data, char* pattern_path, cha
 
 static int osc_configure_instance(instance* inst, char* option, char* value){
 	osc_instance_data* data = (osc_instance_data*) inst->impl;
-	char* host = NULL, *port = NULL;
+	char* host = NULL, *port = NULL, *fd_opts = NULL;
 
 	if(!strcmp(option, "root")){
 		if(osc_path_validate(value, 0)){
@@ -516,7 +516,7 @@ static int osc_configure_instance(instance* inst, char* option, char* value){
 		return 0;
 	}
 	else if(!strcmp(option, "bind")){
-		mmbackend_parse_hostspec(value, &host, &port);
+		mmbackend_parse_hostspec(value, &host, &port, &fd_opts);
 		if(!host || !port){
 			fprintf(stderr, "Invalid bind address for instance %s\n", inst->name);
 			return 1;
@@ -541,7 +541,7 @@ static int osc_configure_instance(instance* inst, char* option, char* value){
 			return 0;
 		}
 
-		mmbackend_parse_hostspec(value, &host, &port);
+		mmbackend_parse_hostspec(value, &host, &port, NULL);
 		if(!host || !port){
 			fprintf(stderr, "Invalid destination address for instance %s\n", inst->name);
 			return 1;
diff --git a/backends/sacn.c b/backends/sacn.c
index ef422e8..2a04245 100644
--- a/backends/sacn.c
+++ b/backends/sacn.c
@@ -17,6 +17,10 @@
 #define MAX_FDS 4096
 #define BACKEND_NAME "sacn"
 
+enum /*_sacn_fd_flags*/ {
+	mcast_loop = 1
+};
+
 static struct /*_sacn_global_config*/ {
 	uint8_t source_name[64];
 	uint8_t cid[16];
@@ -58,8 +62,8 @@ MM_PLUGIN_API int init(){
 	return 0;
 }
 
-static int sacn_listener(char* host, char* port, uint8_t fd_flags){
-	int fd = -1;
+static int sacn_listener(char* host, char* port, uint8_t flags){
+	int fd = -1, yes = 1;
 	if(global_cfg.fds >= MAX_FDS){
 		fprintf(stderr, "sACN backend descriptor limit reached\n");
 		return -1;
@@ -80,10 +84,17 @@ static int sacn_listener(char* host, char* port, uint8_t fd_flags){
 
 	fprintf(stderr, "sACN backend interface %" PRIsize_t " bound to %s port %s\n", global_cfg.fds, host, port);
 	global_cfg.fd[global_cfg.fds].fd = fd;
-	global_cfg.fd[global_cfg.fds].flags = fd_flags;
 	global_cfg.fd[global_cfg.fds].universes = 0;
 	global_cfg.fd[global_cfg.fds].universe = NULL;
 	global_cfg.fd[global_cfg.fds].last_frame = NULL;
+
+	if(flags & mcast_loop){
+		//set IP_MCAST_LOOP to allow local applications to receive output
+		if(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void*)&yes, sizeof(yes)) < 0){
+			fprintf(stderr, "Failed to disable IP_MULTICAST_LOOP on socket: %s\n", strerror(errno));
+		}
+	}
+
 	global_cfg.fds++;
 	return 0;
 }
@@ -110,17 +121,22 @@ static int sacn_configure(char* option, char* value){
 		}
 	}
 	else if(!strcmp(option, "bind")){
-		mmbackend_parse_hostspec(value, &host, &port);
+		mmbackend_parse_hostspec(value, &host, &port, &next);
 
 		if(!host){
 			fprintf(stderr, "No valid sACN bind address provided\n");
 			return 1;
 		}
 
+		if(next && !strncmp(next, "local", 5)){
+			flags = mcast_loop;
+		}
+
 		if(sacn_listener(host, port ? port : SACN_PORT, flags)){
 			fprintf(stderr, "Failed to bind sACN descriptor: %s\n", value);
 			return 1;
 		}
+
 		return 0;
 	}
 
@@ -151,7 +167,7 @@ static int sacn_configure_instance(instance* inst, char* option, char* value){
 		return 0;
 	}
 	else if(!strcmp(option, "destination")){
-		mmbackend_parse_hostspec(value, &host, &port);
+		mmbackend_parse_hostspec(value, &host, &port, NULL);
 
 		if(!host){
 			fprintf(stderr, "No valid sACN destination for instance %s\n", inst->name);
diff --git a/backends/sacn.h b/backends/sacn.h
index 726fa68..c8d11e9 100644
--- a/backends/sacn.h
+++ b/backends/sacn.h
@@ -56,7 +56,6 @@ typedef union /*_sacn_instance_id*/ {
 
 typedef struct /*_sacn_socket*/ {
 	int fd;
-	uint8_t flags;
 	size_t universes;
 	uint16_t* universe;
 	uint64_t* last_frame;
diff --git a/backends/sacn.md b/backends/sacn.md
index 434beeb..46f1197 100644
--- a/backends/sacn.md
+++ b/backends/sacn.md
@@ -10,7 +10,11 @@ containing all write-enabled universes.
 |---------------|-----------------------|-----------------------|-----------------------|
 | `name`	| `sACN source`		| `MIDIMonster`		| sACN source name	|
 | `cid`		| `0xAA 0xBB 0xCC` ...	| `MIDIMonster`		| Source CID (16 bytes)	|
-| `bind`	| `0.0.0.0 5568`	| none			| Binds a network address to listen for data. This option may be set multiple times, with each descriptor being assigned an index starting from 0 to be used with the `interface` instance configuration option. At least one descriptor is required for transmission. |
+| `bind`	| `0.0.0.0 5568`	| none			| Binds a network address to listen for data. This option may be set multiple times, with each descriptor being assigned an index starting from 0 to be used with the `interface` instance configuration option. At least one descriptor is required for operation. |
+
+The `bind` configuration value can be extended by the keyword `local` to allow software on the
+local host to process the sACN output frames from the MIDIMonster (e.g. `bind = 0.0.0.0 5568 local`).
+This has the side effect of generating mirroring the output of instances on this descriptors to their input.
 
 #### Instance configuration
 
-- 
cgit v1.2.3