From 1baae638b5759ff092c7977ab17185975f7e6524 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20Paul=20K=C3=BChne?= <fkuehne@videolan.org>
Date: Tue, 9 Feb 2016 13:59:12 +0100
Subject: [PATCH] Add Bonjour service discovery module

---
 NEWS                                   |   1 +
 modules/MODULES_LIST                   |   1 +
 modules/services_discovery/Makefile.am |   6 +
 modules/services_discovery/bonjour.m   | 242 +++++++++++++++++++++++++
 po/POTFILES.in                         |   1 +
 5 files changed, 251 insertions(+)
 create mode 100644 modules/services_discovery/bonjour.m

diff --git a/NEWS b/NEWS
index 3e69c3ac073a..46b007dda22e 100644
--- a/NEWS
+++ b/NEWS
@@ -142,6 +142,7 @@ Muxers:
 Service Discovery:
  * New NetBios service discovery using libdsm
  * New mDNS services discovery using libmicrodns
+ * New mDNS services discovery using Bonjour (Mac OS X, tvOS, iOS)
  * Rewrite of the UPnP service discovery
 
 Mac OS X Interface
diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST
index 394b3db82b21..e3764127df25 100644
--- a/modules/MODULES_LIST
+++ b/modules/MODULES_LIST
@@ -62,6 +62,7 @@ $Id$
  * avio: Access and Stream output module using libavformat network
  * ball: Augmented reality ball video filter module
  * bandlimited_resampler: Bandlimited interpolation audio resampler
+ * bonjour: mDNS services discovery module based on Bonjour
  * blend: a picture filter that blends two pictures
  * blendbench: a picture filter that test performance of blending routines
  * bluescreen: Bluescreen (weather channel like) video filter
diff --git a/modules/services_discovery/Makefile.am b/modules/services_discovery/Makefile.am
index e72a55883c59..d2fcebb05a31 100644
--- a/modules/services_discovery/Makefile.am
+++ b/modules/services_discovery/Makefile.am
@@ -76,3 +76,9 @@ libmicrodns_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(sddir)'
 sd_LTLIBRARIES += $(LTLIBmicrodns)
 EXTRA_LTLIBRARIES += libmicrodns_plugin.la
 
+libbonjour_plugin_la_SOURCES = services_discovery/bonjour.m
+libbonjour_plugin_la_OBJCFLAGS = $(AM_CFLAGS) -fobjc-arc
+libbonjour_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(sddir)' -Wl,-framework,Foundation
+if HAVE_DARWIN
+sd_LTLIBRARIES += libbonjour_plugin.la
+endif
diff --git a/modules/services_discovery/bonjour.m b/modules/services_discovery/bonjour.m
new file mode 100644
index 000000000000..cadddee3cb9b
--- /dev/null
+++ b/modules/services_discovery/bonjour.m
@@ -0,0 +1,242 @@
+/*****************************************************************************
+ * bonjour.m: mDNS services discovery module based on Bonjour
+ *****************************************************************************
+ * Copyright (C) 2016 VLC authors, VideoLAN and VideoLabs
+ *
+ * Authors: Felix Paul Kühne <fkuehne@videolan.org>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_modules.h>
+#include <vlc_services_discovery.h>
+
+#import <Foundation/Foundation.h>
+
+static int Open( vlc_object_t * );
+static void Close( vlc_object_t * );
+
+VLC_SD_PROBE_HELPER( "Bonjour", "Bonjour Network Discovery", SD_CAT_LAN )
+
+/*
+ * Module descriptor
+ */
+vlc_module_begin()
+set_shortname( "bonjour" )
+set_description( N_( "Bonjour Network Discovery" ) )
+set_category( CAT_PLAYLIST )
+set_subcategory( SUBCAT_PLAYLIST_SD )
+set_capability( "services_discovery", 0 )
+set_callbacks( Open, Close )
+VLC_SD_PROBE_SUBMODULE
+vlc_module_end ()
+
+NSString *const VLCBonjourProtocolName = @"VLCBonjourProtocolName";
+NSString *const VLCBonjourProtocolServiceName = @"VLCBonjourProtocolServiceName";
+
+@interface VLCNetServiceDiscoveryController : NSObject <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
+{
+    NSArray<NSNetServiceBrowser *> *_serviceBrowsers;
+
+    NSMutableArray<NSNetService *> *_rawNetServices;
+    NSMutableArray<NSNetService *> *_resolvedNetServices;
+    NSMutableArray<NSValue *> *_inputItemsForNetServices;
+
+    NSArray *_activeProtocols;
+}
+
+@property (readwrite, nonatomic) services_discovery_t *p_sd;
+
+- (void)startDiscovery;
+- (void)stopDiscovery;
+
+@end
+
+struct services_discovery_sys_t
+{
+    CFTypeRef _Nullable discoveryController;
+};
+
+@implementation VLCNetServiceDiscoveryController
+
+- (void)startDiscovery
+{
+    _rawNetServices = [[NSMutableArray alloc] init];
+    _resolvedNetServices = [[NSMutableArray alloc] init];
+    _inputItemsForNetServices = [[NSMutableArray alloc] init];
+
+    NSDictionary *VLCFtpProtocol = @{ VLCBonjourProtocolName : @"ftp",
+                                      VLCBonjourProtocolServiceName : @"_ftp._tcp." };
+    NSDictionary *VLCSmbProtocol = @{ VLCBonjourProtocolName : @"smb",
+                                      VLCBonjourProtocolServiceName : @"_smb._tcp." };
+    NSDictionary *VLCNfsProtocol = @{ VLCBonjourProtocolName : @"nfs",
+                                      VLCBonjourProtocolServiceName : @"_nfs._tcp." };
+    NSDictionary *VLCSftpProtocol = @{ VLCBonjourProtocolName : @"sftp",
+                                       VLCBonjourProtocolServiceName : @"_sftp-ssh._tcp." };
+
+    NSArray *VLCSupportedProtocols = @[VLCFtpProtocol,
+                                       VLCSmbProtocol,
+                                       VLCNfsProtocol,
+                                       VLCSftpProtocol];
+
+    NSUInteger count = VLCSupportedProtocols.count;
+    NSMutableArray *discoverers = [[NSMutableArray alloc] init];
+    NSMutableArray *protocols = [[NSMutableArray alloc] init];
+
+    for (NSUInteger i = 0; i < count; i++) {
+        NSDictionary *protocol = VLCSupportedProtocols[i];
+
+        /* only discover hosts if we actually have a module that can handle those */
+        if (!module_exists([protocol[VLCBonjourProtocolName] UTF8String]))
+            continue;
+
+        NSNetServiceBrowser *serviceBrowser = [[NSNetServiceBrowser alloc] init];
+        serviceBrowser.delegate = self;
+        [serviceBrowser searchForServicesOfType:protocol[VLCBonjourProtocolServiceName] inDomain:@"local."];
+        [discoverers addObject:serviceBrowser];
+        [protocols addObject:protocol];
+    }
+
+    _serviceBrowsers = [discoverers copy];
+    _activeProtocols = [protocols copy];
+}
+
+- (void)stopDiscovery
+{
+    [_serviceBrowsers makeObjectsPerformSelector:@selector(stop)];
+
+    NSUInteger inputItemCount = _inputItemsForNetServices.count;
+    for (NSUInteger i = 0; i < inputItemCount; i++) {
+        input_item_t *p_input_item = [_inputItemsForNetServices[i] pointerValue];
+        if (p_input_item != NULL) {
+            services_discovery_RemoveItem(self.p_sd, p_input_item);
+            input_item_Release(p_input_item);
+        }
+    }
+
+    [_inputItemsForNetServices removeAllObjects];
+    [_resolvedNetServices removeAllObjects];
+}
+
+#pragma mark - functional delegation
+
+- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
+{
+    msg_Dbg(self.p_sd, "found bonjour service: %s (%s)", [aNetService.name UTF8String], [aNetService.type UTF8String]);
+    [_rawNetServices addObject:aNetService];
+    aNetService.delegate = self;
+    [aNetService resolveWithTimeout:5.];
+}
+
+- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
+{
+    msg_Dbg(self.p_sd, "bonjour service disappeared: %s", [aNetService.name UTF8String]);
+    if ([_rawNetServices containsObject:aNetService])
+        [_rawNetServices removeObject:aNetService];
+
+    if ([_resolvedNetServices containsObject:aNetService]) {
+        NSInteger index = [_resolvedNetServices indexOfObject:aNetService];
+        if (index == NSNotFound)
+            return;
+
+        [_resolvedNetServices removeObjectAtIndex:index];
+        input_item_t *p_input_item = [_inputItemsForNetServices[index] pointerValue];
+        if (p_input_item != NULL) {
+            services_discovery_RemoveItem(self.p_sd, p_input_item);
+            input_item_Release(p_input_item);
+        }
+    }
+}
+
+- (void)netServiceDidResolveAddress:(NSNetService *)aNetService
+{
+    if (![_resolvedNetServices containsObject:aNetService]) {
+        NSString *serviceType = aNetService.type;
+        NSUInteger count = _activeProtocols.count;
+        NSString *protocol = nil;
+        for (NSUInteger i = 0; i < count; i++) {
+            NSDictionary *protocolDefinition = _activeProtocols[i];
+            if ([serviceType isEqualToString:protocolDefinition[VLCBonjourProtocolServiceName]]) {
+                protocol = protocolDefinition[VLCBonjourProtocolName];
+            }
+        }
+
+        NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld",
+                         protocol,
+                         aNetService.hostName,
+                         aNetService.port];
+
+        input_item_t *p_input_item = input_item_NewWithTypeExt([uri UTF8String],
+                                                               [aNetService.name UTF8String],
+                                                               0, NULL, 0, -1,
+                                                               ITEM_TYPE_NODE, true );
+
+        if (p_input_item != NULL) {
+            services_discovery_AddItem(self.p_sd, p_input_item, NULL);
+            [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_input_item]];
+            [_resolvedNetServices addObject:aNetService];
+        }
+    }
+
+    [_rawNetServices removeObject:aNetService];
+}
+
+- (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict
+{
+    msg_Dbg(self.p_sd, "failed to resolve: %s", [aNetService.name UTF8String]);
+    [_rawNetServices removeObject:aNetService];
+}
+
+@end
+
+static int Open(vlc_object_t *p_this)
+{
+    services_discovery_t *p_sd = (services_discovery_t *)p_this;
+    services_discovery_sys_t *p_sys = NULL;
+
+    p_sd->p_sys = p_sys = calloc(1, sizeof(services_discovery_sys_t));
+    if (!p_sys) {
+        return VLC_ENOMEM;
+    }
+
+    VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] init];
+    discoveryController.p_sd = p_sd;
+
+    p_sys->discoveryController = CFBridgingRetain(discoveryController);
+
+    [discoveryController startDiscovery];
+
+    return VLC_SUCCESS;
+}
+
+static void Close(vlc_object_t *p_this)
+{
+    services_discovery_t *p_sd = (services_discovery_t *)p_this;
+    services_discovery_sys_t *p_sys = p_sd->p_sys;
+
+    VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
+    [discoveryController stopDiscovery];
+
+    CFBridgingRelease(p_sys->discoveryController);
+    discoveryController = nil;
+
+    free(p_sys);
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 68b7f49b0a89..a85ce89c1aee 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1014,6 +1014,7 @@ modules/packetizer/mpeg4video.c
 modules/packetizer/mpegvideo.c
 modules/packetizer/vc1.c
 modules/services_discovery/avahi.c
+modules/services_discovery/bonjour.m
 modules/services_discovery/mediadirs.c
 modules/services_discovery/mtp.c
 modules/services_discovery/os2drive.c
-- 
GitLab