1 /*****************************************************************************
2 * bonjour.m: mDNS services discovery module based on Bonjour
3 *****************************************************************************
4 * Copyright (C) 2016 VLC authors, VideoLAN and VideoLabs
6 * Authors: Felix Paul Kühne <fkuehne@videolan.org>
7 * Marvin Scholz <epirat07@gmail.com>
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.
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.
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 *****************************************************************************/
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;
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
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
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";
86 * For chromecast, the `ca=` is composed from (at least)
87 * 0x01 to indicate video support
88 * 0x04 to indivate audio support
90 #define CHROMECAST_FLAG_VIDEO 0x01
91 #define CHROMECAST_FLAG_AUDIO 0x04
94 #pragma mark Interface definition
95 @interface VLCNetServiceDiscoveryController : NSObject <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
97 /* Stores all used service browsers, one for each protocol, usually */
98 NSArray *_serviceBrowsers;
100 /* Holds a required reference to all NSNetServices */
101 NSMutableArray *_rawNetServices;
103 /* Holds all successfully resolved NSNetServices */
104 NSMutableArray *_resolvedNetServices;
106 /* Holds the respective pointers to a vlc_object for each resolved and added NSNetService */
107 NSMutableArray *_inputItemsForNetServices;
109 /* Stores all protocols that are currently discovered */
110 NSArray *_activeProtocols;
113 @property (readonly) BOOL isRendererDiscovery;
114 @property (readonly, nonatomic) vlc_object_t *p_this;
116 - (instancetype)initWithRendererDiscoveryObject:(vlc_renderer_discovery_t *)p_rd;
117 - (instancetype)initWithServicesDiscoveryObject:(services_discovery_t *)p_sd;
119 - (void)startDiscovery;
120 - (void)stopDiscovery;
124 @implementation VLCNetServiceDiscoveryController
126 - (instancetype)initWithRendererDiscoveryObject:(vlc_renderer_discovery_t *)p_rd
130 _p_this = VLC_OBJECT( p_rd );
131 _isRendererDiscovery = YES;
137 - (instancetype)initWithServicesDiscoveryObject:(services_discovery_t *)p_sd
141 _p_this = VLC_OBJECT( p_sd );
142 _isRendererDiscovery = NO;
148 - (void)startDiscovery
150 NSDictionary *VLCFtpProtocol = @{ VLCBonjourProtocolName : @"ftp",
151 VLCBonjourProtocolServiceName : @"_ftp._tcp.",
152 VLCBonjourIsRenderer : @(NO)
154 NSDictionary *VLCSmbProtocol = @{ VLCBonjourProtocolName : @"smb",
155 VLCBonjourProtocolServiceName : @"_smb._tcp.",
156 VLCBonjourIsRenderer : @(NO)
158 NSDictionary *VLCNfsProtocol = @{ VLCBonjourProtocolName : @"nfs",
159 VLCBonjourProtocolServiceName : @"_nfs._tcp.",
160 VLCBonjourIsRenderer : @(NO)
162 NSDictionary *VLCSftpProtocol = @{ VLCBonjourProtocolName : @"sftp",
163 VLCBonjourProtocolServiceName: @"_sftp-ssh._tcp.",
164 VLCBonjourIsRenderer : @(NO)
166 NSDictionary *VLCCastProtocol = @{ VLCBonjourProtocolName : @"chromecast",
167 VLCBonjourProtocolServiceName: @"_googlecast._tcp.",
168 VLCBonjourIsRenderer : @(YES),
169 VLCBonjourRendererFlags : @(VLC_RENDERER_CAN_AUDIO),
170 VLCBonjourRendererDemux : @"cc_demux"
173 NSArray *VLCSupportedProtocols = @[VLCFtpProtocol,
179 _rawNetServices = [[NSMutableArray alloc] init];
180 _resolvedNetServices = [[NSMutableArray alloc] init];
181 _inputItemsForNetServices = [[NSMutableArray alloc] init];
183 NSMutableArray *discoverers = [[NSMutableArray alloc] init];
184 NSMutableArray *protocols = [[NSMutableArray alloc] init];
186 msg_Info(_p_this, "starting discovery");
187 for (NSDictionary *protocol in VLCSupportedProtocols) {
188 /* Only discover services if we actually have a module that can handle those */
189 if (!module_exists([[protocol objectForKey: VLCBonjourProtocolName] UTF8String]) && !_isRendererDiscovery) {
190 msg_Dbg(_p_this, "no module for %s, skipping", [[protocol objectForKey: VLCBonjourProtocolName] UTF8String]);
194 /* Only discover hosts it they match the current mode (renderer or service) */
195 if ([[protocol objectForKey: VLCBonjourIsRenderer] boolValue] != _isRendererDiscovery) {
196 msg_Dbg(_p_this, "%s does not match current discovery mode, skipping", [[protocol objectForKey: VLCBonjourProtocolName] UTF8String]);
200 NSNetServiceBrowser *serviceBrowser = [[NSNetServiceBrowser alloc] init];
201 [serviceBrowser setDelegate:self];
202 msg_Dbg(_p_this, "starting discovery for type %s", [[protocol objectForKey: VLCBonjourProtocolServiceName] UTF8String]);
203 [serviceBrowser searchForServicesOfType:[protocol objectForKey: VLCBonjourProtocolServiceName] inDomain:@"local."];
204 [discoverers addObject:serviceBrowser];
205 [protocols addObject:protocol];
208 _serviceBrowsers = [discoverers copy];
209 _activeProtocols = [protocols copy];
212 - (void)stopDiscovery
214 [_serviceBrowsers makeObjectsPerformSelector:@selector(stop)];
216 /* Work around a macOS 10.12 bug, see https://openradar.appspot.com/28943305 */
217 [_serviceBrowsers makeObjectsPerformSelector:@selector(setDelegate:) withObject:nil];
218 [_resolvedNetServices makeObjectsPerformSelector:@selector(setDelegate:) withObject:nil];
220 for (NSValue *item in _inputItemsForNetServices) {
221 if (_isRendererDiscovery) {
222 [self removeRawRendererItem:item];
224 [self removeRawInputItem:item];
228 [_inputItemsForNetServices removeAllObjects];
229 [_resolvedNetServices removeAllObjects];
230 msg_Info(_p_this, "stopped discovery");
234 #pragma mark Delegate methods
236 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
238 msg_Dbg(_p_this, "service found: %s (%s), resolving", [aNetService.name UTF8String], [aNetService.type UTF8String]);
239 [_rawNetServices addObject:aNetService];
240 aNetService.delegate = self;
241 [aNetService resolveWithTimeout:5.];
244 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
246 msg_Dbg(self.p_this, "service disappeared: %s (%s), removing", [aNetService.name UTF8String], [aNetService.type UTF8String]);
248 /* If the item was not looked-up yet, just remove it */
249 if ([_rawNetServices containsObject:aNetService])
250 [_rawNetServices removeObject:aNetService];
252 /* If the item was already resolved, the associated input or renderer items needs to be removed as well */
253 if ([_resolvedNetServices containsObject:aNetService]) {
254 NSInteger index = [_resolvedNetServices indexOfObject:aNetService];
255 if (index == NSNotFound) {
259 [_resolvedNetServices removeObjectAtIndex:index];
261 if (_isRendererDiscovery) {
262 [self removeRawRendererItem:[_inputItemsForNetServices objectAtIndex:index]];
264 [self removeRawInputItem:[_inputItemsForNetServices objectAtIndex:index]];
267 /* Remove item pointer from our lookup array */
268 [_inputItemsForNetServices removeObjectAtIndex:index];
272 - (void)netServiceDidResolveAddress:(NSNetService *)aNetService
274 msg_Dbg(_p_this, "service resolved: %s", [aNetService.name UTF8String]);
275 if (![_resolvedNetServices containsObject:aNetService]) {
276 NSString *serviceType = aNetService.type;
277 NSString *protocol = nil;
278 for (NSDictionary *protocolDefinition in _activeProtocols) {
279 if ([serviceType isEqualToString:[protocolDefinition objectForKey:VLCBonjourProtocolServiceName]]) {
280 protocol = [protocolDefinition objectForKey:VLCBonjourProtocolName];
284 if (_isRendererDiscovery) {
285 [self addResolvedRendererItem:aNetService withProtocol:protocol];
287 [self addResolvedInputItem:aNetService withProtocol:protocol];
291 [_rawNetServices removeObject:aNetService];
294 - (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict
296 msg_Warn(_p_this, "service resolution failed: %s, removing", [aNetService.name UTF8String]);
297 [_rawNetServices removeObject:aNetService];
301 #pragma mark Helper methods
303 - (void)addResolvedRendererItem:(NSNetService *)netService withProtocol:(NSString *)protocol
305 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)_p_this;
307 NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld", protocol, netService.hostName, netService.port];
308 NSDictionary *txtDict = [NSNetService dictionaryFromTXTRecordData:[netService TXTRecordData]];
309 NSString *displayName = netService.name;
310 int rendererFlags = 0;
312 if ([netService.type isEqualToString:@"_googlecast._tcp."]) {
313 NSData *modelData = [txtDict objectForKey:@"md"];
314 NSData *nameData = [txtDict objectForKey:@"fn"];
315 NSData *flagsData = [txtDict objectForKey:@"ca"];
317 // Get CC capability flags from TXT data
319 NSString *flagsString = [[NSString alloc] initWithData:flagsData encoding:NSUTF8StringEncoding];
320 NSInteger flags = [flagsString intValue];
322 if ((flags & CHROMECAST_FLAG_VIDEO) != 0) {
323 rendererFlags |= VLC_RENDERER_CAN_VIDEO;
325 if ((flags & CHROMECAST_FLAG_AUDIO) != 0) {
326 rendererFlags |= VLC_RENDERER_CAN_AUDIO;
330 // Get CC model and name from TXT data
331 if (modelData && nameData) {
332 NSString *model = [[NSString alloc] initWithData:modelData encoding:NSUTF8StringEncoding];
333 NSString *name = [[NSString alloc] initWithData:nameData encoding:NSUTF8StringEncoding];
334 displayName = [NSString stringWithFormat:@"%@ (%@)", name, model];
338 const char *extra_uri = rendererFlags & VLC_RENDERER_CAN_VIDEO ? NULL : "no-video";
340 // TODO: Adapt to work with not just chromecast!
341 vlc_renderer_item_t *p_renderer_item = vlc_renderer_item_new("chromecast", [displayName UTF8String],
342 [uri UTF8String], extra_uri, "cc_demux",
344 if (p_renderer_item != NULL) {
345 vlc_rd_add_item( p_rd, p_renderer_item );
346 [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_renderer_item]];
347 [_resolvedNetServices addObject:netService];
351 - (void)removeRawRendererItem:(NSValue *)item
353 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)_p_this;
354 vlc_renderer_item_t *input_item = [item pointerValue];
356 if (input_item != NULL) {
357 vlc_rd_remove_item( p_rd, input_item );
358 vlc_renderer_item_release( input_item );
362 - (void)addResolvedInputItem:(NSNetService *)netService withProtocol:(NSString *)protocol
364 services_discovery_t *p_sd = (services_discovery_t *)_p_this;
366 NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld", protocol, netService.hostName, netService.port];
367 input_item_t *p_input_item = input_item_NewDirectory([uri UTF8String], [netService.name UTF8String], ITEM_NET );
368 if (p_input_item != NULL) {
369 services_discovery_AddItem(p_sd, p_input_item);
370 [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_input_item]];
371 [_resolvedNetServices addObject:netService];
375 - (void)removeRawInputItem:(NSValue *)item
377 services_discovery_t *p_sd = (services_discovery_t *)_p_this;
378 input_item_t *input_item = [item pointerValue];
380 if (input_item != NULL) {
381 services_discovery_RemoveItem( p_sd, input_item );
382 input_item_Release( input_item );
388 static int OpenSD(vlc_object_t *p_this)
390 services_discovery_t *p_sd = (services_discovery_t *)p_this;
391 services_discovery_sys_t *p_sys = NULL;
393 p_sd->p_sys = p_sys = calloc(1, sizeof(services_discovery_sys_t));
398 p_sd->description = _("Bonjour Network Discovery");
400 VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] initWithServicesDiscoveryObject:p_sd];
402 p_sys->discoveryController = CFBridgingRetain(discoveryController);
404 [discoveryController startDiscovery];
409 static void CloseSD(vlc_object_t *p_this)
411 services_discovery_t *p_sd = (services_discovery_t *)p_this;
412 services_discovery_sys_t *p_sys = p_sd->p_sys;
414 VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
415 [discoveryController stopDiscovery];
417 CFBridgingRelease(p_sys->discoveryController);
418 discoveryController = nil;
423 static int OpenRD(vlc_object_t *p_this)
425 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
426 vlc_renderer_discovery_sys *p_sys = NULL;
428 p_rd->p_sys = p_sys = calloc(1, sizeof(vlc_renderer_discovery_sys));
433 VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] initWithRendererDiscoveryObject:p_rd];
435 p_sys->discoveryController = CFBridgingRetain(discoveryController);
437 [discoveryController startDiscovery];
442 static void CloseRD(vlc_object_t *p_this)
444 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
445 vlc_renderer_discovery_sys *p_sys = p_rd->p_sys;
447 VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
448 [discoveryController stopDiscovery];
450 CFBridgingRelease(p_sys->discoveryController);
451 discoveryController = nil;