DCP: fixed include-guards
[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     add_shortcut( "mdns", "bonjour" )
50
51     VLC_SD_PROBE_SUBMODULE
52 vlc_module_end ()
53
54 NSString *const VLCBonjourProtocolName = @"VLCBonjourProtocolName";
55 NSString *const VLCBonjourProtocolServiceName = @"VLCBonjourProtocolServiceName";
56
57 @interface VLCNetServiceDiscoveryController : NSObject <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
58 {
59     NSArray<NSNetServiceBrowser *> *_serviceBrowsers;
60
61     NSMutableArray<NSNetService *> *_rawNetServices;
62     NSMutableArray<NSNetService *> *_resolvedNetServices;
63     NSMutableArray<NSValue *> *_inputItemsForNetServices;
64
65     NSArray *_activeProtocols;
66 }
67
68 @property (readwrite, nonatomic) services_discovery_t *p_sd;
69
70 - (void)startDiscovery;
71 - (void)stopDiscovery;
72
73 @end
74
75 struct services_discovery_sys_t
76 {
77     CFTypeRef _Nullable discoveryController;
78 };
79
80 @implementation VLCNetServiceDiscoveryController
81
82 - (void)startDiscovery
83 {
84     _rawNetServices = [[NSMutableArray alloc] init];
85     _resolvedNetServices = [[NSMutableArray alloc] init];
86     _inputItemsForNetServices = [[NSMutableArray alloc] init];
87
88     NSDictionary *VLCFtpProtocol = @{ VLCBonjourProtocolName : @"ftp",
89                                       VLCBonjourProtocolServiceName : @"_ftp._tcp." };
90     NSDictionary *VLCSmbProtocol = @{ VLCBonjourProtocolName : @"smb",
91                                       VLCBonjourProtocolServiceName : @"_smb._tcp." };
92     NSDictionary *VLCNfsProtocol = @{ VLCBonjourProtocolName : @"nfs",
93                                       VLCBonjourProtocolServiceName : @"_nfs._tcp." };
94     NSDictionary *VLCSftpProtocol = @{ VLCBonjourProtocolName : @"sftp",
95                                        VLCBonjourProtocolServiceName : @"_sftp-ssh._tcp." };
96
97     NSArray *VLCSupportedProtocols = @[VLCFtpProtocol,
98                                        VLCSmbProtocol,
99                                        VLCNfsProtocol,
100                                        VLCSftpProtocol];
101
102     NSUInteger count = VLCSupportedProtocols.count;
103     NSMutableArray *discoverers = [[NSMutableArray alloc] init];
104     NSMutableArray *protocols = [[NSMutableArray alloc] init];
105
106     for (NSUInteger i = 0; i < count; i++) {
107         NSDictionary *protocol = VLCSupportedProtocols[i];
108
109         /* only discover hosts if we actually have a module that can handle those */
110         if (!module_exists([protocol[VLCBonjourProtocolName] UTF8String]))
111             continue;
112
113         NSNetServiceBrowser *serviceBrowser = [[NSNetServiceBrowser alloc] init];
114         serviceBrowser.delegate = self;
115         [serviceBrowser searchForServicesOfType:protocol[VLCBonjourProtocolServiceName] inDomain:@"local."];
116         [discoverers addObject:serviceBrowser];
117         [protocols addObject:protocol];
118     }
119
120     _serviceBrowsers = [discoverers copy];
121     _activeProtocols = [protocols copy];
122 }
123
124 - (void)stopDiscovery
125 {
126     [_serviceBrowsers makeObjectsPerformSelector:@selector(stop)];
127
128     NSUInteger inputItemCount = _inputItemsForNetServices.count;
129     for (NSUInteger i = 0; i < inputItemCount; i++) {
130         input_item_t *p_input_item = [_inputItemsForNetServices[i] pointerValue];
131         if (p_input_item != NULL) {
132             services_discovery_RemoveItem(self.p_sd, p_input_item);
133             input_item_Release(p_input_item);
134         }
135     }
136
137     [_inputItemsForNetServices removeAllObjects];
138     [_resolvedNetServices removeAllObjects];
139 }
140
141 #pragma mark - functional delegation
142
143 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
144 {
145     msg_Dbg(self.p_sd, "found bonjour service: %s (%s)", [aNetService.name UTF8String], [aNetService.type UTF8String]);
146     [_rawNetServices addObject:aNetService];
147     aNetService.delegate = self;
148     [aNetService resolveWithTimeout:5.];
149 }
150
151 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
152 {
153     msg_Dbg(self.p_sd, "bonjour service disappeared: %s", [aNetService.name UTF8String]);
154     if ([_rawNetServices containsObject:aNetService])
155         [_rawNetServices removeObject:aNetService];
156
157     if ([_resolvedNetServices containsObject:aNetService]) {
158         NSInteger index = [_resolvedNetServices indexOfObject:aNetService];
159         if (index == NSNotFound)
160             return;
161
162         [_resolvedNetServices removeObjectAtIndex:index];
163         input_item_t *p_input_item = [_inputItemsForNetServices[index] pointerValue];
164         if (p_input_item != NULL) {
165             services_discovery_RemoveItem(self.p_sd, p_input_item);
166             input_item_Release(p_input_item);
167         }
168     }
169 }
170
171 - (void)netServiceDidResolveAddress:(NSNetService *)aNetService
172 {
173     if (![_resolvedNetServices containsObject:aNetService]) {
174         NSString *serviceType = aNetService.type;
175         NSUInteger count = _activeProtocols.count;
176         NSString *protocol = nil;
177         for (NSUInteger i = 0; i < count; i++) {
178             NSDictionary *protocolDefinition = _activeProtocols[i];
179             if ([serviceType isEqualToString:protocolDefinition[VLCBonjourProtocolServiceName]]) {
180                 protocol = protocolDefinition[VLCBonjourProtocolName];
181             }
182         }
183
184         NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld",
185                          protocol,
186                          aNetService.hostName,
187                          aNetService.port];
188
189         input_item_t *p_input_item = input_item_NewWithTypeExt([uri UTF8String],
190                                                                [aNetService.name UTF8String],
191                                                                0, NULL, 0, -1,
192                                                                ITEM_TYPE_NODE, true );
193
194         if (p_input_item != NULL) {
195             services_discovery_AddItem(self.p_sd, p_input_item, NULL);
196             [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_input_item]];
197             [_resolvedNetServices addObject:aNetService];
198         }
199     }
200
201     [_rawNetServices removeObject:aNetService];
202 }
203
204 - (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict
205 {
206     msg_Dbg(self.p_sd, "failed to resolve: %s", [aNetService.name UTF8String]);
207     [_rawNetServices removeObject:aNetService];
208 }
209
210 @end
211
212 static int Open(vlc_object_t *p_this)
213 {
214     services_discovery_t *p_sd = (services_discovery_t *)p_this;
215     services_discovery_sys_t *p_sys = NULL;
216
217     p_sd->p_sys = p_sys = calloc(1, sizeof(services_discovery_sys_t));
218     if (!p_sys) {
219         return VLC_ENOMEM;
220     }
221
222     VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] init];
223     discoveryController.p_sd = p_sd;
224
225     p_sys->discoveryController = CFBridgingRetain(discoveryController);
226
227     [discoveryController startDiscovery];
228
229     return VLC_SUCCESS;
230 }
231
232 static void Close(vlc_object_t *p_this)
233 {
234     services_discovery_t *p_sd = (services_discovery_t *)p_this;
235     services_discovery_sys_t *p_sys = p_sd->p_sys;
236
237     VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
238     [discoveryController stopDiscovery];
239
240     CFBridgingRelease(p_sys->discoveryController);
241     discoveryController = nil;
242
243     free(p_sys);
244 }