Add Bonjour service discovery module
[vlc.git] / modules / services_discovery / bonjour.m
1 /*****************************************************************************
2  * bonjour.m: mDNS services discovery module based on Bonjour
3  *****************************************************************************
4  * Copyright (C) 2016 VLC authors, VideoLAN and VideoLabs
5  *
6  * Authors: Felix Paul K├╝hne <fkuehne@videolan.org>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2.1 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21  *****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26
27 #include <vlc_common.h>
28 #include <vlc_plugin.h>
29 #include <vlc_modules.h>
30 #include <vlc_services_discovery.h>
31
32 #import <Foundation/Foundation.h>
33
34 static int Open( vlc_object_t * );
35 static void Close( vlc_object_t * );
36
37 VLC_SD_PROBE_HELPER( "Bonjour", "Bonjour Network Discovery", SD_CAT_LAN )
38
39 /*
40  * Module descriptor
41  */
42 vlc_module_begin()
43 set_shortname( "bonjour" )
44 set_description( N_( "Bonjour Network Discovery" ) )
45 set_category( CAT_PLAYLIST )
46 set_subcategory( SUBCAT_PLAYLIST_SD )
47 set_capability( "services_discovery", 0 )
48 set_callbacks( Open, Close )
49 VLC_SD_PROBE_SUBMODULE
50 vlc_module_end ()
51
52 NSString *const VLCBonjourProtocolName = @"VLCBonjourProtocolName";
53 NSString *const VLCBonjourProtocolServiceName = @"VLCBonjourProtocolServiceName";
54
55 @interface VLCNetServiceDiscoveryController : NSObject <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
56 {
57     NSArray<NSNetServiceBrowser *> *_serviceBrowsers;
58
59     NSMutableArray<NSNetService *> *_rawNetServices;
60     NSMutableArray<NSNetService *> *_resolvedNetServices;
61     NSMutableArray<NSValue *> *_inputItemsForNetServices;
62
63     NSArray *_activeProtocols;
64 }
65
66 @property (readwrite, nonatomic) services_discovery_t *p_sd;
67
68 - (void)startDiscovery;
69 - (void)stopDiscovery;
70
71 @end
72
73 struct services_discovery_sys_t
74 {
75     CFTypeRef _Nullable discoveryController;
76 };
77
78 @implementation VLCNetServiceDiscoveryController
79
80 - (void)startDiscovery
81 {
82     _rawNetServices = [[NSMutableArray alloc] init];
83     _resolvedNetServices = [[NSMutableArray alloc] init];
84     _inputItemsForNetServices = [[NSMutableArray alloc] init];
85
86     NSDictionary *VLCFtpProtocol = @{ VLCBonjourProtocolName : @"ftp",
87                                       VLCBonjourProtocolServiceName : @"_ftp._tcp." };
88     NSDictionary *VLCSmbProtocol = @{ VLCBonjourProtocolName : @"smb",
89                                       VLCBonjourProtocolServiceName : @"_smb._tcp." };
90     NSDictionary *VLCNfsProtocol = @{ VLCBonjourProtocolName : @"nfs",
91                                       VLCBonjourProtocolServiceName : @"_nfs._tcp." };
92     NSDictionary *VLCSftpProtocol = @{ VLCBonjourProtocolName : @"sftp",
93                                        VLCBonjourProtocolServiceName : @"_sftp-ssh._tcp." };
94
95     NSArray *VLCSupportedProtocols = @[VLCFtpProtocol,
96                                        VLCSmbProtocol,
97                                        VLCNfsProtocol,
98                                        VLCSftpProtocol];
99
100     NSUInteger count = VLCSupportedProtocols.count;
101     NSMutableArray *discoverers = [[NSMutableArray alloc] init];
102     NSMutableArray *protocols = [[NSMutableArray alloc] init];
103
104     for (NSUInteger i = 0; i < count; i++) {
105         NSDictionary *protocol = VLCSupportedProtocols[i];
106
107         /* only discover hosts if we actually have a module that can handle those */
108         if (!module_exists([protocol[VLCBonjourProtocolName] UTF8String]))
109             continue;
110
111         NSNetServiceBrowser *serviceBrowser = [[NSNetServiceBrowser alloc] init];
112         serviceBrowser.delegate = self;
113         [serviceBrowser searchForServicesOfType:protocol[VLCBonjourProtocolServiceName] inDomain:@"local."];
114         [discoverers addObject:serviceBrowser];
115         [protocols addObject:protocol];
116     }
117
118     _serviceBrowsers = [discoverers copy];
119     _activeProtocols = [protocols copy];
120 }
121
122 - (void)stopDiscovery
123 {
124     [_serviceBrowsers makeObjectsPerformSelector:@selector(stop)];
125
126     NSUInteger inputItemCount = _inputItemsForNetServices.count;
127     for (NSUInteger i = 0; i < inputItemCount; i++) {
128         input_item_t *p_input_item = [_inputItemsForNetServices[i] pointerValue];
129         if (p_input_item != NULL) {
130             services_discovery_RemoveItem(self.p_sd, p_input_item);
131             input_item_Release(p_input_item);
132         }
133     }
134
135     [_inputItemsForNetServices removeAllObjects];
136     [_resolvedNetServices removeAllObjects];
137 }
138
139 #pragma mark - functional delegation
140
141 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
142 {
143     msg_Dbg(self.p_sd, "found bonjour service: %s (%s)", [aNetService.name UTF8String], [aNetService.type UTF8String]);
144     [_rawNetServices addObject:aNetService];
145     aNetService.delegate = self;
146     [aNetService resolveWithTimeout:5.];
147 }
148
149 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
150 {
151     msg_Dbg(self.p_sd, "bonjour service disappeared: %s", [aNetService.name UTF8String]);
152     if ([_rawNetServices containsObject:aNetService])
153         [_rawNetServices removeObject:aNetService];
154
155     if ([_resolvedNetServices containsObject:aNetService]) {
156         NSInteger index = [_resolvedNetServices indexOfObject:aNetService];
157         if (index == NSNotFound)
158             return;
159
160         [_resolvedNetServices removeObjectAtIndex:index];
161         input_item_t *p_input_item = [_inputItemsForNetServices[index] pointerValue];
162         if (p_input_item != NULL) {
163             services_discovery_RemoveItem(self.p_sd, p_input_item);
164             input_item_Release(p_input_item);
165         }
166     }
167 }
168
169 - (void)netServiceDidResolveAddress:(NSNetService *)aNetService
170 {
171     if (![_resolvedNetServices containsObject:aNetService]) {
172         NSString *serviceType = aNetService.type;
173         NSUInteger count = _activeProtocols.count;
174         NSString *protocol = nil;
175         for (NSUInteger i = 0; i < count; i++) {
176             NSDictionary *protocolDefinition = _activeProtocols[i];
177             if ([serviceType isEqualToString:protocolDefinition[VLCBonjourProtocolServiceName]]) {
178                 protocol = protocolDefinition[VLCBonjourProtocolName];
179             }
180         }
181
182         NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld",
183                          protocol,
184                          aNetService.hostName,
185                          aNetService.port];
186
187         input_item_t *p_input_item = input_item_NewWithTypeExt([uri UTF8String],
188                                                                [aNetService.name UTF8String],
189                                                                0, NULL, 0, -1,
190                                                                ITEM_TYPE_NODE, true );
191
192         if (p_input_item != NULL) {
193             services_discovery_AddItem(self.p_sd, p_input_item, NULL);
194             [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_input_item]];
195             [_resolvedNetServices addObject:aNetService];
196         }
197     }
198
199     [_rawNetServices removeObject:aNetService];
200 }
201
202 - (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict
203 {
204     msg_Dbg(self.p_sd, "failed to resolve: %s", [aNetService.name UTF8String]);
205     [_rawNetServices removeObject:aNetService];
206 }
207
208 @end
209
210 static int Open(vlc_object_t *p_this)
211 {
212     services_discovery_t *p_sd = (services_discovery_t *)p_this;
213     services_discovery_sys_t *p_sys = NULL;
214
215     p_sd->p_sys = p_sys = calloc(1, sizeof(services_discovery_sys_t));
216     if (!p_sys) {
217         return VLC_ENOMEM;
218     }
219
220     VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] init];
221     discoveryController.p_sd = p_sd;
222
223     p_sys->discoveryController = CFBridgingRetain(discoveryController);
224
225     [discoveryController startDiscovery];
226
227     return VLC_SUCCESS;
228 }
229
230 static void Close(vlc_object_t *p_this)
231 {
232     services_discovery_t *p_sd = (services_discovery_t *)p_this;
233     services_discovery_sys_t *p_sys = p_sd->p_sys;
234
235     VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
236     [discoveryController stopDiscovery];
237
238     CFBridgingRelease(p_sys->discoveryController);
239     discoveryController = nil;
240
241     free(p_sys);
242 }