access: srt: add support stream encryption
[vlc.git] / modules / services_discovery / bonjour.m
blobf8f4388689bec88cfafce3d5b9b00b0da2bff1c7
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  *****************************************************************************/
24 #ifdef HAVE_CONFIG_H
25 # include <config.h>
26 #endif
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>
34 #import <Foundation/Foundation.h>
36 #pragma mark Function declarations
38 static int OpenSD( vlc_object_t * );
39 static void CloseSD( vlc_object_t * );
41 static int OpenRD( vlc_object_t * );
42 static void CloseRD( vlc_object_t * );
44 VLC_SD_PROBE_HELPER( "Bonjour", N_("Bonjour Network Discovery"), SD_CAT_LAN )
45 VLC_RD_PROBE_HELPER( "Bonjour_renderer", "Bonjour Renderer Discovery" )
47 struct services_discovery_sys_t
49     CFTypeRef _Nullable discoveryController;
52 struct vlc_renderer_discovery_sys
54     CFTypeRef _Nullable discoveryController;
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()
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";
85 #pragma mark -
86 #pragma mark Interface definition
87 @interface VLCNetServiceDiscoveryController : NSObject <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
89     /* Stores all used service browsers, one for each protocol, usually */
90     NSArray *_serviceBrowsers;
92     /* Holds a required reference to all NSNetServices */
93     NSMutableArray *_rawNetServices;
95     /* Holds all successfully resolved NSNetServices */
96     NSMutableArray *_resolvedNetServices;
98     /* Holds the respective pointers to a vlc_object for each resolved and added NSNetService */
99     NSMutableArray *_inputItemsForNetServices;
101     /* Stores all protocols that are currently discovered */
102     NSArray *_activeProtocols;
105 @property (readonly) BOOL isRendererDiscovery;
106 @property (readonly, nonatomic) vlc_object_t *p_this;
108 - (instancetype)initWithRendererDiscoveryObject:(vlc_renderer_discovery_t *)p_rd;
109 - (instancetype)initWithServicesDiscoveryObject:(services_discovery_t *)p_sd;
111 - (void)startDiscovery;
112 - (void)stopDiscovery;
114 @end
116 @implementation VLCNetServiceDiscoveryController
118 - (instancetype)initWithRendererDiscoveryObject:(vlc_renderer_discovery_t *)p_rd
120     self = [super init];
121     if (self) {
122         _p_this = VLC_OBJECT( p_rd );
123         _isRendererDiscovery = YES;
124     }
126     return self;
129 - (instancetype)initWithServicesDiscoveryObject:(services_discovery_t *)p_sd
131     self = [super init];
132     if (self) {
133         _p_this = VLC_OBJECT( p_sd );
134         _isRendererDiscovery = NO;
135     }
137     return self;
140 - (void)startDiscovery
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                                        };
165     NSArray *VLCSupportedProtocols = @[VLCFtpProtocol,
166                                       VLCSmbProtocol,
167                                       VLCNfsProtocol,
168                                       VLCSftpProtocol,
169                                       VLCCastProtocol];
171     _rawNetServices = [[NSMutableArray alloc] init];
172     _resolvedNetServices = [[NSMutableArray alloc] init];
173     _inputItemsForNetServices = [[NSMutableArray alloc] init];
175     NSMutableArray *discoverers = [[NSMutableArray alloc] init];
176     NSMutableArray *protocols = [[NSMutableArray alloc] init];
178     msg_Info(_p_this, "starting discovery");
179     for (NSDictionary *protocol in VLCSupportedProtocols) {
180         /* Only discover services if we actually have a module that can handle those */
181         if (!module_exists([[protocol objectForKey: VLCBonjourProtocolName] UTF8String]) && !_isRendererDiscovery) {
182             msg_Dbg(_p_this, "no module for %s, skipping", [[protocol objectForKey: VLCBonjourProtocolName] UTF8String]);
183             continue;
184         }
186         /* Only discover hosts it they match the current mode (renderer or service) */
187         if ([[protocol objectForKey: VLCBonjourIsRenderer] boolValue] != _isRendererDiscovery) {
188             msg_Dbg(_p_this, "%s does not match current discovery mode, skipping", [[protocol objectForKey: VLCBonjourProtocolName] UTF8String]);
189             continue;
190         }
192         NSNetServiceBrowser *serviceBrowser = [[NSNetServiceBrowser alloc] init];
193         [serviceBrowser setDelegate:self];
194         msg_Dbg(_p_this, "starting discovery for type %s", [[protocol objectForKey: VLCBonjourProtocolServiceName] UTF8String]);
195         [serviceBrowser searchForServicesOfType:[protocol objectForKey: VLCBonjourProtocolServiceName] inDomain:@"local."];
196         [discoverers addObject:serviceBrowser];
197         [protocols addObject:protocol];
198     }
200     _serviceBrowsers = [discoverers copy];
201     _activeProtocols = [protocols copy];
204 - (void)stopDiscovery
206     [_serviceBrowsers makeObjectsPerformSelector:@selector(stop)];
208     /* Work around a macOS 10.12 bug, see https://openradar.appspot.com/28943305 */
209     [_serviceBrowsers makeObjectsPerformSelector:@selector(setDelegate:) withObject:nil];
210     [_resolvedNetServices makeObjectsPerformSelector:@selector(setDelegate:) withObject:nil];
212     for (NSValue *item in _inputItemsForNetServices) {
213         if (_isRendererDiscovery) {
214             [self removeRawRendererItem:item];
215         } else {
216             [self removeRawInputItem:item];
217         }
218     }
220     [_inputItemsForNetServices removeAllObjects];
221     [_resolvedNetServices removeAllObjects];
222     msg_Info(_p_this, "stopped discovery");
225 #pragma mark - 
226 #pragma mark Delegate methods
228 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
230     msg_Dbg(_p_this, "service found: %s (%s), resolving", [aNetService.name UTF8String], [aNetService.type UTF8String]);
231     [_rawNetServices addObject:aNetService];
232     aNetService.delegate = self;
233     [aNetService resolveWithTimeout:5.];
236 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
238     msg_Dbg(self.p_this, "service disappeared: %s (%s), removing", [aNetService.name UTF8String], [aNetService.type UTF8String]);
240     /* If the item was not looked-up yet, just remove it */
241     if ([_rawNetServices containsObject:aNetService])
242         [_rawNetServices removeObject:aNetService];
244     /* If the item was already resolved, the associated input or renderer items needs to be removed as well */
245     if ([_resolvedNetServices containsObject:aNetService]) {
246         NSInteger index = [_resolvedNetServices indexOfObject:aNetService];
247         if (index == NSNotFound) {
248             return;
249         }
251         [_resolvedNetServices removeObjectAtIndex:index];
253         if (_isRendererDiscovery) {
254             [self removeRawRendererItem:[_inputItemsForNetServices objectAtIndex:index]];
255         } else {
256             [self removeRawInputItem:[_inputItemsForNetServices objectAtIndex:index]];
257         }
259         /* Remove item pointer from our lookup array */
260         [_inputItemsForNetServices removeObjectAtIndex:index];
261     }
264 - (void)netServiceDidResolveAddress:(NSNetService *)aNetService
266     msg_Dbg(_p_this, "service resolved: %s", [aNetService.name UTF8String]);
267     if (![_resolvedNetServices containsObject:aNetService]) {
268         NSString *serviceType = aNetService.type;
269         NSString *protocol = nil;
270         for (NSDictionary *protocolDefinition in _activeProtocols) {
271             if ([serviceType isEqualToString:[protocolDefinition objectForKey:VLCBonjourProtocolServiceName]]) {
272                 protocol = [protocolDefinition objectForKey:VLCBonjourProtocolName];
273             }
274         }
276         if (_isRendererDiscovery) {
277             [self addResolvedRendererItem:aNetService withProtocol:protocol];
278         } else {
279             [self addResolvedInputItem:aNetService withProtocol:protocol];
280         }
281     }
283     [_rawNetServices removeObject:aNetService];
286 - (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict
288     msg_Warn(_p_this, "service resolution failed: %s, removing", [aNetService.name UTF8String]);
289     [_rawNetServices removeObject:aNetService];
292 #pragma mark -
293 #pragma mark Helper methods
295 - (void)addResolvedRendererItem:(NSNetService *)netService withProtocol:(NSString *)protocol
297     vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)_p_this;
299     NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld", protocol, netService.hostName, netService.port];
300     NSDictionary *txtDict = [NSNetService dictionaryFromTXTRecordData:[netService TXTRecordData]];
301     NSString *displayName = netService.name;
303     if ([netService.type isEqualToString:@"_googlecast._tcp."]) {
304         NSData *modelData = [txtDict objectForKey:@"md"];
305         NSData *nameData = [txtDict objectForKey:@"fn"];
306         if (modelData && nameData) {
307             NSString *model = [[NSString alloc] initWithData:modelData encoding:NSUTF8StringEncoding];
308             NSString *name = [[NSString alloc] initWithData:nameData encoding:NSUTF8StringEncoding];
309             displayName = [NSString stringWithFormat:@"%@ (%@)", name, model];
310         }
311     }
312     // TODO: Detect rendered capabilities and adapt to work with not just chromecast
313     vlc_renderer_item_t *p_renderer_item = vlc_renderer_item_new( "chromecast", [displayName UTF8String],
314                                                                  [uri UTF8String], NULL, "cc_demux",
315                                                                  "", VLC_RENDERER_CAN_VIDEO );
316     if (p_renderer_item != NULL) {
317         vlc_rd_add_item( p_rd, p_renderer_item );
318         [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_renderer_item]];
319         [_resolvedNetServices addObject:netService];
320     }
323 - (void)removeRawRendererItem:(NSValue *)item
325     vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)_p_this;
326     vlc_renderer_item_t *input_item = [item pointerValue];
328     if (input_item != NULL) {
329         vlc_rd_remove_item( p_rd, input_item );
330         vlc_renderer_item_release( input_item );
331     }
334 - (void)addResolvedInputItem:(NSNetService *)netService withProtocol:(NSString *)protocol
336     services_discovery_t *p_sd = (services_discovery_t *)_p_this;
338     NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld", protocol, netService.hostName, netService.port];
339     input_item_t *p_input_item = input_item_NewDirectory([uri UTF8String], [netService.name UTF8String], ITEM_NET );
340     if (p_input_item != NULL) {
341         services_discovery_AddItem(p_sd, p_input_item);
342         [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_input_item]];
343         [_resolvedNetServices addObject:netService];
344     }
347 - (void)removeRawInputItem:(NSValue *)item
349     services_discovery_t *p_sd = (services_discovery_t *)_p_this;
350     input_item_t *input_item = [item pointerValue];
352     if (input_item != NULL) {
353         services_discovery_RemoveItem( p_sd, input_item );
354         input_item_Release( input_item );
355     }
358 @end
360 static int OpenSD(vlc_object_t *p_this)
362     services_discovery_t *p_sd = (services_discovery_t *)p_this;
363     services_discovery_sys_t *p_sys = NULL;
365     p_sd->p_sys = p_sys = calloc(1, sizeof(services_discovery_sys_t));
366     if (!p_sys) {
367         return VLC_ENOMEM;
368     }
370     p_sd->description = _("Bonjour Network Discovery");
372     VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] initWithServicesDiscoveryObject:p_sd];
374     p_sys->discoveryController = CFBridgingRetain(discoveryController);
376     [discoveryController startDiscovery];
378     return VLC_SUCCESS;
381 static void CloseSD(vlc_object_t *p_this)
383     services_discovery_t *p_sd = (services_discovery_t *)p_this;
384     services_discovery_sys_t *p_sys = p_sd->p_sys;
386     VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
387     [discoveryController stopDiscovery];
389     CFBridgingRelease(p_sys->discoveryController);
390     discoveryController = nil;
392     free(p_sys);
395 static int OpenRD(vlc_object_t *p_this)
397     vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
398     vlc_renderer_discovery_sys *p_sys = NULL;
400     p_rd->p_sys = p_sys = calloc(1, sizeof(vlc_renderer_discovery_sys));
401     if (!p_sys) {
402         return VLC_ENOMEM;
403     }
405     VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] initWithRendererDiscoveryObject:p_rd];
407     p_sys->discoveryController = CFBridgingRetain(discoveryController);
409     [discoveryController startDiscovery];
411     return VLC_SUCCESS;
414 static void CloseRD(vlc_object_t *p_this)
416     vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
417     vlc_renderer_discovery_sys *p_sys = p_rd->p_sys;
419     VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
420     [discoveryController stopDiscovery];
422     CFBridgingRelease(p_sys->discoveryController);
423     discoveryController = nil;
425     free(p_sys);