From 60adf2c4fe53e935e6de359ef1c01d0a91ab7480 Mon Sep 17 00:00:00 2001
From: cbdev <cb@cbcdn.com>
Date: Wed, 11 Dec 2019 23:49:11 +0100
Subject: Implement rtpmidi peer handling

---
 backends/rtpmidi.c  | 76 +++++++++++++++++++++++++++++++++++++++--------------
 backends/rtpmidi.h  |  3 ++-
 backends/rtpmidi.md |  1 +
 3 files changed, 60 insertions(+), 20 deletions(-)

diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c
index 77819ee..612ac6f 100644
--- a/backends/rtpmidi.c
+++ b/backends/rtpmidi.c
@@ -19,7 +19,7 @@ static struct /*_rtpmidi_global*/ {
 	.detect = 0
 };
 
-int init(){
+MM_PLUGIN_API int init(){
 	backend rtpmidi = {
 		.name = BACKEND_NAME,
 		.conf = rtpmidi_configure,
@@ -124,9 +124,34 @@ static int rtpmidi_bind_instance(rtpmidi_instance_data* data, char* host, char*
 	return 0;
 }
 
+static int rtpmidi_push_peer(rtpmidi_instance_data* data, struct sockaddr_storage sock_addr, socklen_t sock_len){
+	size_t u;
+
+	for(u = 0; u < data->peers; u++){
+		//check whether the peer is already in the list
+		if(sock_len == data->peer[u].dest_len && !memcmp(&data->peer[u].dest, &sock_addr, sock_len)){
+			return 0;
+		}
+	}
+
+	data->peer = realloc(data->peer, (data->peers + 1) * sizeof(rtpmidi_peer));
+	if(!data->peer){
+		fprintf(stderr, "Failed to allocate memory\n");
+		return 1;
+	}
+
+	data->peer[data->peers].dest = sock_addr;
+	data->peer[data->peers].dest_len = sock_len;
+
+	data->peers++;
+	return 0;
+}
+
 static int rtpmidi_configure_instance(instance* inst, char* option, char* value){
 	rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl;
 	char* host = NULL, *port = NULL;
+	struct sockaddr_storage sock_addr;
+	socklen_t sock_len = sizeof(sock_addr);
 
 	if(!strcmp(option, "mode")){
 		if(!strcmp(value, "direct")){
@@ -174,13 +199,18 @@ static int rtpmidi_configure_instance(instance* inst, char* option, char* value)
 		return 0;
 	}
 	else if(!strcmp(option, "peer")){
-		if(data->mode != direct){
-			fprintf(stderr, "The rtpmidi 'peer' option is only valid for direct mode instances\n");
+		mmbackend_parse_hostspec(value, &host, &port);
+		if(!host || !port){
+			fprintf(stderr, "Invalid peer %s configured on rtpmidi instance %s\n", value, inst->name);
 			return 1;
 		}
 
-		//TODO add peer
-		return 0;
+		if(mmbackend_parse_sockaddr(host, port, &sock_addr, &sock_len)){
+			fprintf(stderr, "Failed to resolve peer %s configured on rtpmidi instance %s\n", value, inst->name);
+			return 1;
+		}
+
+		return rtpmidi_push_peer(data, sock_addr, sock_len);
 	}
 	else if(!strcmp(option, "session")){
 		if(data->mode != apple){
@@ -313,24 +343,34 @@ static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v
 }
 
 static int rtpmidi_handle(size_t num, managed_fd* fds){
-	//TODO handle discovery
+	size_t u;
+	int rv = 0;
+
+	//TODO handle mDNS discovery frames
 
 	if(!num){
 		return 0;
 	}
 
-	//TODO
-	return 1;
+	for(u = 0; u < num; u++){
+		if(!fds[u].impl){
+			//TODO handle mDNS discovery input
+		}
+		else{
+			//TODO handle rtp/control input
+		}
+	}
+
+	return rv;
 }
 
 static int rtpmidi_start(size_t n, instance** inst){
 	size_t u, fds = 0;
-	int rv = 1;
 	rtpmidi_instance_data* data = NULL;
 
 	//if mdns name defined and no socket, bind default values
 	if(cfg.mdns_name && cfg.mdns_fd < 0){
-		cfg.mdns_fd = mmbackend_socket("::", RTPMIDI_MDNS_PORT, SOCK_DGRAM, 1, 1);
+		cfg.mdns_fd = mmbackend_socket(RTPMIDI_DEFAULT_HOST, RTPMIDI_MDNS_PORT, SOCK_DGRAM, 1, 1);
 		if(cfg.mdns_fd < 0){
 			return 1;
 		}
@@ -340,7 +380,7 @@ static int rtpmidi_start(size_t n, instance** inst){
 	if(cfg.mdns_fd >= 0){
 		if(mm_manage_fd(cfg.mdns_fd, BACKEND_NAME, 1, NULL)){
 			fprintf(stderr, "rtpmidi failed to register mDNS socket with core\n");
-			goto bail;
+			return 1;
 		}
 		fds++;
 	}
@@ -353,7 +393,7 @@ static int rtpmidi_start(size_t n, instance** inst){
 		//check whether instances are explicitly configured to a mode
 		if(data->mode == unconfigured){
 			fprintf(stderr, "rtpmidi instance %s is missing a mode configuration\n", inst[u]->name);
-			goto bail;
+			return 1;
 		}
 
 		//generate random ssrc's
@@ -362,23 +402,21 @@ static int rtpmidi_start(size_t n, instance** inst){
 		}
 
 		//if not bound, bind to default
-		if(data->fd < 0 && rtpmidi_bind_instance(data, "::", NULL)){
+		if(data->fd < 0 && rtpmidi_bind_instance(data, RTPMIDI_DEFAULT_HOST, NULL)){
 			fprintf(stderr, "Failed to bind default sockets for rtpmidi instance %s\n", inst[u]->name);
-			goto bail;
+			return 1;
 		}
 
 		//register fds to core
-		if(mm_manage_fd(data->fd, BACKEND_NAME, 1, NULL) || (data->control_fd >= 0 && mm_manage_fd(data->control_fd, BACKEND_NAME, 1, NULL))){
+		if(mm_manage_fd(data->fd, BACKEND_NAME, 1, inst[u]) || (data->control_fd >= 0 && mm_manage_fd(data->control_fd, BACKEND_NAME, 1, inst[u]))){
 			fprintf(stderr, "rtpmidi failed to register instance socket with core\n");
-			goto bail;
+			return 1;
 		}
 		fds += (data->control_fd >= 0) ? 2 : 1;
 	}
 
 	fprintf(stderr, "rtpmidi backend registered %" PRIsize_t " descriptors to core\n", fds);
-	rv = 0;
-bail:
-	return rv;
+	return 0;
 }
 
 static int rtpmidi_shutdown(size_t n, instance** inst){
diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h
index 78675bc..ddb7bed 100644
--- a/backends/rtpmidi.h
+++ b/backends/rtpmidi.h
@@ -3,7 +3,7 @@
 #endif
 #include "midimonster.h"
 
-int init();
+MM_PLUGIN_API int init();
 static int rtpmidi_configure(char* option, char* value);
 static int rtpmidi_configure_instance(instance* instance, char* option, char* value);
 static instance* rtpmidi_instance();
@@ -14,6 +14,7 @@ static int rtpmidi_start(size_t n, instance** inst);
 static int rtpmidi_shutdown(size_t n, instance** inst);
 
 #define RTPMIDI_RECV_BUF 4096
+#define RTPMIDI_DEFAULT_HOST "::"
 #define RTPMIDI_MDNS_PORT "5353"
 #define RTPMIDI_HEADER_MAGIC htobe16(0x80E1)
 
diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md
index c208bf7..d8e3b63 100644
--- a/backends/rtpmidi.md
+++ b/backends/rtpmidi.md
@@ -51,6 +51,7 @@ Common instance configuration parameters
 | `session`	| `Just Jamming`	| `MIDIMonster`		| Session name to announce via mDNS |
 | `invite`	| `pad,piano`		| none			| Devices to send invitations to when discovered (the special value `*` invites all discovered peers). Setting this option makes the instance a session initiator |
 | `join`	| `Just Jamming`	| none			| Sessions for which to accept invitations (the special value `*` accepts all invitations). Setting this option makes the instance a session participant |
+| `peer`	| `10.1.2.3 9001`	| none			| Configure a direct session peer, bypassing AppleMIDI discovery. May be specified multiple times |
 
 Note that AppleMIDI session discovery requires mDNS functionality, thus the `mdns-name` global parameter
 (and, depending on your setup, the `mdns-bind` parameter) need to be configured properly.
-- 
cgit v1.2.3