bonjour rd: Add bonjour renderer discovery submodule
[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  *          Marvin Scholz <epirat07@gmail.com>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 # include <config.h>
26 #endif
27
28 #include <vlc_common.h>
29 #include <vlc_plugin.h>
30 #include <vlc_modules.h>
31 #include <vlc_services_discovery.h>
32 #include <vlc_renderer_discovery.h>
33
34 #import <Foundation/Foundation.h>
35
36 #pragma mark Function declarations
37
38 static int OpenSD( vlc_object_t * );
39 static void CloseSD( vlc_object_t * );
40
41 static int OpenRD( vlc_object_t * );
42 static void CloseRD( vlc_object_t * );
43
44 VLC_SD_PROBE_HELPER( "Bonjour", N_("Bonjour Network Discovery"), SD_CAT_LAN )
45 VLC_RD_PROBE_HELPER( "Bonjour_renderer", "Bonjour Renderer Discovery" )
46
47 struct services_discovery_sys_t
48 {
49     CFTypeRef _Nullable discoveryController;
50 };
51
52 struct vlc_renderer_discovery_sys
53 {
54     CFTypeRef _Nullable discoveryController;
55 };
56
57 /*
58  * Module descriptor
59  */
60 vlc_module_begin()
61     set_shortname( "Bonjour" )
62     set_description( N_( "Bonjour Network Discovery" ) )
63     set_category( CAT_PLAYLIST )
64     set_subcategory( SUBCAT_PLAYLIST_SD )
65     set_capability( "services_discovery", 0 )
66     set_callbacks( OpenSD, CloseSD )
67     add_shortcut( "mdns", "bonjour" )
68     VLC_SD_PROBE_SUBMODULE
69     add_submodule() \
70         set_description( N_( "Bonjour Renderer Discovery" ) )
71         set_category( CAT_SOUT )
72         set_subcategory( SUBCAT_SOUT_RENDERER )
73         set_capability( "renderer_discovery", 0 )
74         set_callbacks( OpenRD, CloseRD )
75         add_shortcut( "mdns_renderer", "bonjour_renderer" )
76         VLC_RD_PROBE_SUBMODULE
77 vlc_module_end()
78
79 NSString *const VLCBonjourProtocolName          = @"VLCBonjourProtocolName";
80 NSString *const VLCBonjourProtocolServiceName   = @"VLCBonjourProtocolServiceName";
81 NSString *const VLCBonjourIsRenderer            = @"VLCBonjourIsRenderer";
82 NSString *const VLCBonjourRendererFlags         = @"VLCBonjourRendererFlags";
83 NSString *const VLCBonjourRendererDemux         = @"VLCBonjourRendererDemux";
84
85 #pragma mark -
86 #pragma mark Interface definition
87 @interface VLCNetServiceDiscoveryController : NSObject <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
88 {
89     /* Stores all used service browsers, one for each protocol, usually */
90     NSArray *_serviceBrowsers;
91
92     /* Holds a required reference to all NSNetServices */
93     NSMutableArray *_rawNetServices;
94
95     /* Holds all successfully resolved NSNetServices */
96     NSMutableArray *_resolvedNetServices;
97
98     /* Holds the respective pointers to a vlc_object for each resolved and added NSNetService */
99     NSMutableArray *_inputItemsForNetServices;
100
101     /* Stores all protocols that are currently discovered */
102     NSArray *_activeProtocols;
103 }
104
105 @property (readonly) BOOL isRendererDiscovery;
106 @property (readonly, nonatomic) vlc_object_t *p_this;
107
108 - (instancetype)initWithRendererDiscoveryObject:(vlc_renderer_discovery_t *)p_rd;
109 - (instancetype)initWithServicesDiscoveryObject:(services_discovery_t *)p_sd;
110
111 - (void)startDiscovery;
112 - (void)stopDiscovery;
113
114 @end
115
116 @implementation VLCNetServiceDiscoveryController
117
118 - (instancetype)initWithRendererDiscoveryObject:(vlc_renderer_discovery_t *)p_rd
119 {
120     self = [super init];
121     if (self) {
122         _p_this = VLC_OBJECT( p_rd );
123         _isRendererDiscovery = YES;
124     }
125
126     return self;
127 }
128
129 - (instancetype)initWithServicesDiscoveryObject:(services_discovery_t *)p_sd
130 {
131     self = [super init];
132     if (self) {
133         _p_this = VLC_OBJECT( p_sd );
134         _isRendererDiscovery = NO;
135     }
136
137     return self;
138 }
139
140 - (void)startDiscovery
141 {
142     NSDictionary *VLCFtpProtocol = @{ VLCBonjourProtocolName        : @"ftp",
143                                       VLCBonjourProtocolServiceName : @"_ftp._tcp.",
144                                       VLCBonjourIsRenderer          : @(NO)
145                                       };
146     NSDictionary *VLCSmbProtocol = @{ VLCBonjourProtocolName        : @"smb",
147                                       VLCBonjourProtocolServiceName : @"_smb._tcp.",
148                                       VLCBonjourIsRenderer          : @(NO)
149                                       };
150     NSDictionary *VLCNfsProtocol = @{ VLCBonjourProtocolName        : @"nfs",
151                                       VLCBonjourProtocolServiceName : @"_nfs._tcp.",
152                                       VLCBonjourIsRenderer          : @(NO)
153                                       };
154     NSDictionary *VLCSftpProtocol = @{ VLCBonjourProtocolName       : @"sftp",
155                                        VLCBonjourProtocolServiceName: @"_sftp-ssh._tcp.",
156                                        VLCBonjourIsRenderer         : @(NO)
157                                        };
158     NSDictionary *VLCCastProtocol = @{ VLCBonjourProtocolName       : @"chromecast",
159                                        VLCBonjourProtocolServiceName: @"_googlecast._tcp.",
160                                        VLCBonjourIsRenderer         : @(YES),
161                                        VLCBonjourRendererFlags      : @(VLC_RENDERER_CAN_AUDIO),
162                                        VLCBonjourRendererDemux      : @"cc_demux"
163                                        };
164
165     NSArray *VLCSupportedProtocols = @[VLCFtpProtocol,
166                                       VLCSmbProtocol,
167                                       VLCNfsProtocol,
168                                       VLCSftpProtocol,
169                                       VLCCastProtocol];
170
171     _rawNetServices = [[NSMutableArray alloc] init];
172     _resolvedNetServices = [[NSMutableArray alloc] init];
173     _inputItemsForNetServices = [[NSMutableArray alloc] init];
174
175     NSMutableArray *discoverers = [[NSMutableArray alloc] init];
176     NSMutableArray *protocols = [[NSMutableArray alloc] init];
177
178     msg_Info(_p_this, "starting discovery");
179     for (NSDictionary *protocol in VLCSupportedProtocols) {
180         msg_Info(_p_this, "looking up %s", [protocol[VLCBonjourProtocolName] UTF8String]);
181
182         /* Only discover services if we actually have a module that can handle those */
183         if (!module_exists([protocol[VLCBonjourProtocolName] UTF8String]) && !_isRendererDiscovery) {
184             msg_Info(_p_this, "no module for %s, skipping", [protocol[VLCBonjourProtocolName] UTF8String]);
185             continue;
186         }
187
188         /* Only discover hosts it they match the current mode (renderer or service) */
189         if ([protocol[VLCBonjourIsRenderer] boolValue] != _isRendererDiscovery) {
190             msg_Info(_p_this, "%s does not match current discovery mode, skipping", [protocol[VLCBonjourProtocolName] UTF8String]);
191             continue;
192         }
193
194         NSNetServiceBrowser *serviceBrowser = [[NSNetServiceBrowser alloc] init];
195         [serviceBrowser setDelegate:self];
196         msg_Info(_p_this, "starting discovery for type %s", [protocol[VLCBonjourProtocolServiceName] UTF8String]);
197         [serviceBrowser searchForServicesOfType:protocol[VLCBonjourProtocolServiceName] inDomain:@"local."];
198         [discoverers addObject:serviceBrowser];
199         [protocols addObject:protocol];
200     }
201
202     _serviceBrowsers = [discoverers copy];
203     _activeProtocols = [protocols copy];
204 }
205
206 - (void)stopDiscovery
207 {
208     [_serviceBrowsers makeObjectsPerformSelector:@selector(stop)];
209
210     for (NSValue *item in _inputItemsForNetServices) {
211         if (_isRendererDiscovery) {
212             [self removeRawRendererItem:item];
213         } else {
214             [self removeRawInputItem:item];
215         }
216     }
217
218     [_inputItemsForNetServices removeAllObjects];
219     [_resolvedNetServices removeAllObjects];
220     msg_Info(_p_this, "stopped discovery");
221 }
222
223 #pragma mark - 
224 #pragma mark Delegate methods
225
226 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
227 {
228     msg_Info(_p_this, "found something, looking up");
229     msg_Dbg(self.p_this, "found bonjour service: %s (%s)", [aNetService.name UTF8String], [aNetService.type UTF8String]);
230     [_rawNetServices addObject:aNetService];
231     aNetService.delegate = self;
232     [aNetService resolveWithTimeout:5.];
233 }
234
235 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
236 {
237     msg_Dbg(self.p_this, "bonjour service disappeared: %s", [aNetService.name UTF8String]);
238
239     /* If the item was not looked-up yet, just remove it */
240     if ([_rawNetServices containsObject:aNetService])
241         [_rawNetServices removeObject:aNetService];
242
243     /* If the item was already resolved, the associated input or renderer items needs to be removed as well */
244     if ([_resolvedNetServices containsObject:aNetService]) {
245         NSInteger index = [_resolvedNetServices indexOfObject:aNetService];
246         if (index == NSNotFound) {
247             return;
248         }
249
250         [_resolvedNetServices removeObjectAtIndex:index];
251
252         if (_isRendererDiscovery) {
253             [self removeRawRendererItem:_inputItemsForNetServices[index]];
254         } else {
255             [self removeRawInputItem:_inputItemsForNetServices[index]];
256         }
257
258         /* Remove item pointer from our lookup array */
259         [_inputItemsForNetServices removeObjectAtIndex:index];
260     }
261 }
262
263 - (void)netServiceDidResolveAddress:(NSNetService *)aNetService
264 {
265     msg_Info(_p_this, "resolved something");
266     if (![_resolvedNetServices containsObject:aNetService]) {
267         NSString *serviceType = aNetService.type;
268         NSString *protocol = nil;
269         for (NSDictionary *protocolDefinition in _activeProtocols) {
270             if ([serviceType isEqualToString:protocolDefinition[VLCBonjourProtocolServiceName]]) {
271                 protocol = protocolDefinition[VLCBonjourProtocolName];
272             }
273         }
274
275         if (_isRendererDiscovery) {
276             [self addResolvedRendererItem:aNetService withProtocol:protocol];
277         } else {
278             [self addResolvedInputItem:aNetService withProtocol:protocol];
279         }
280     }
281
282     [_rawNetServices removeObject:aNetService];
283 }
284
285 - (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict
286 {
287     msg_Dbg(_p_this, "failed to resolve: %s", [aNetService.name UTF8String]);
288     [_rawNetServices removeObject:aNetService];
289 }
290
291 #pragma mark -
292 #pragma mark Helper methods
293
294 - (void)addResolvedRendererItem:(NSNetService *)netService withProtocol:(NSString *)protocol
295 {
296     vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)_p_this;
297
298     NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld", protocol, netService.hostName, netService.port];
299     NSDictionary *txtDict = [NSNetService dictionaryFromTXTRecordData:[netService TXTRecordData]];
300
301     // TODO: Detect rendered capabilities and adapt to work with not just chromecast
302     vlc_renderer_item_t *p_renderer_item = vlc_renderer_item_new( "chromecast", [netService.name UTF8String],
303                                                                  [uri UTF8String], NULL, "cc_demux",
304                                                                  "", VLC_RENDERER_CAN_VIDEO );
305     if (p_renderer_item != NULL) {
306         vlc_rd_add_item( p_rd, p_renderer_item );
307         [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_renderer_item]];
308         [_resolvedNetServices addObject:netService];
309     }
310 }
311
312 - (void)removeRawRendererItem:(NSValue *)item
313 {
314     vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)_p_this;
315     vlc_renderer_item_t *input_item = [item pointerValue];
316
317     if (input_item != NULL) {
318         vlc_rd_remove_item( p_rd, input_item );
319         vlc_renderer_item_release( input_item );
320     }
321 }
322
323 - (void)addResolvedInputItem:(NSNetService *)netService withProtocol:(NSString *)protocol
324 {
325     services_discovery_t *p_sd = (services_discovery_t *)_p_this;
326
327     NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld", protocol, netService.hostName, netService.port];
328     input_item_t *p_input_item = input_item_NewDirectory([uri UTF8String], [netService.name UTF8String], ITEM_NET );
329     if (p_input_item != NULL) {
330         services_discovery_AddItem( p_sd, p_input_item, NULL);
331         [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_input_item]];
332         [_resolvedNetServices addObject:netService];
333     }
334 }
335
336 - (void)removeRawInputItem:(NSValue *)item
337 {
338     services_discovery_t *p_sd = (services_discovery_t *)_p_this;
339     input_item_t *input_item = [item pointerValue];
340
341     if (input_item != NULL) {
342         services_discovery_RemoveItem( p_sd, input_item );
343         input_item_Release( input_item );
344     }
345 }
346
347 @end
348
349 static int OpenSD(vlc_object_t *p_this)
350 {
351     services_discovery_t *p_sd = (services_discovery_t *)p_this;
352     services_discovery_sys_t *p_sys = NULL;
353
354     p_sd->p_sys = p_sys = calloc(1, sizeof(services_discovery_sys_t));
355     if (!p_sys) {
356         return VLC_ENOMEM;
357     }
358
359     p_sd->description = _("Bonjour Network Discovery");
360
361     VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] initWithServicesDiscoveryObject:p_sd];
362
363     p_sys->discoveryController = CFBridgingRetain(discoveryController);
364
365     [discoveryController startDiscovery];
366
367     return VLC_SUCCESS;
368 }
369
370 static void CloseSD(vlc_object_t *p_this)
371 {
372     services_discovery_t *p_sd = (services_discovery_t *)p_this;
373     services_discovery_sys_t *p_sys = p_sd->p_sys;
374
375     VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
376     [discoveryController stopDiscovery];
377
378     CFBridgingRelease(p_sys->discoveryController);
379     discoveryController = nil;
380
381     free(p_sys);
382 }
383
384 static int OpenRD(vlc_object_t *p_this)
385 {
386     vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
387     vlc_renderer_discovery_sys *p_sys = NULL;
388
389     p_rd->p_sys = p_sys = calloc(1, sizeof(vlc_renderer_discovery_sys));
390     if (!p_sys) {
391         return VLC_ENOMEM;
392     }
393
394     VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] initWithRendererDiscoveryObject:p_rd];
395
396     p_sys->discoveryController = CFBridgingRetain(discoveryController);
397
398     [discoveryController startDiscovery];
399
400     return VLC_SUCCESS;
401 }
402
403 static void CloseRD(vlc_object_t *p_this)
404 {
405     vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
406     vlc_renderer_discovery_sys *p_sys = p_rd->p_sys;
407
408     VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
409     [discoveryController stopDiscovery];
410
411     CFBridgingRelease(p_sys->discoveryController);
412     discoveryController = nil;
413
414     free(p_sys);
415 }