1 /*****************************************************************************
2 * microdns.c: mDNS services discovery module
3 *****************************************************************************
4 * Copyright © 2016 VLC authors, VideoLAN and VideoLabs
5 *
6 * Authors: Steve Lhomme <robux4@videolabs.io>
7 * Thomas Guillem <thomas@gllm.fr>
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 <assert.h>
29
30 #include <vlc_common.h>
31 #include <vlc_atomic.h>
32 #include <vlc_plugin.h>
33 #include <vlc_modules.h>
34 #include <vlc_services_discovery.h>
35 #include <vlc_renderer_discovery.h>
36
37 #include <microdns/microdns.h>
38
39 static int OpenSD( vlc_object_t * );
40 static void CloseSD( vlc_object_t * );
41 static int OpenRD( vlc_object_t * );
42 static void CloseRD( vlc_object_t * );
43
44 VLC_SD_PROBE_HELPER( "microdns", N_("mDNS Network Discovery"), SD_CAT_LAN )
45 VLC_RD_PROBE_HELPER( "microdns_renderer", "mDNS renderer Discovery" )
46
47 #define CFG_PREFIX "sd-microdns-"
48
49 #define LISTEN_INTERVAL INT64_C(15000000) /* 15 seconds */
50 #define TIMEOUT (3 * LISTEN_INTERVAL + INT64_C(5000000)) /* 3 * interval + 5 seconds */
51
52 /*
53 * Module descriptor
54 */
55 vlc_module_begin()
56 set_shortname( "mDNS" )
57 set_description( N_( "mDNS Network Discovery" ) )
58 set_category( CAT_PLAYLIST )
59 set_subcategory( SUBCAT_PLAYLIST_SD )
60 set_capability( "services_discovery", 0 )
61 set_callbacks( OpenSD, CloseSD )
62 add_shortcut( "mdns", "microdns" )
63 VLC_SD_PROBE_SUBMODULE
64 add_submodule() \
65 set_description( N_( "mDNS Renderer Discovery" ) )
66 set_category( CAT_SOUT )
67 set_subcategory( SUBCAT_SOUT_RENDERER )
68 set_capability( "renderer_discovery", 0 )
69 set_callbacks( OpenRD, CloseRD )
70 add_shortcut( "mdns_renderer", "microdns_renderer" )
71 VLC_RD_PROBE_SUBMODULE
72 vlc_module_end ()
73
74 static const struct
75 {
76 const char *psz_protocol;
77 const char *psz_service_name;
78 bool b_renderer;
79 int i_renderer_flags;
80 } protocols[] = {
81 { "ftp", "_ftp._tcp.local", false, 0 },
82 { "smb", "_smb._tcp.local", false, 0 },
83 { "nfs", "_nfs._tcp.local", false, 0 },
84 { "sftp", "_sftp-ssh._tcp.local", false, 0 },
85 { "rtsp", "_rtsp._tcp.local", false, 0 },
86 { "chromecast", "_googlecast._tcp.local", true, VLC_RENDERER_CAN_AUDIO },
87 };
88 #define NB_PROTOCOLS (sizeof(protocols) / sizeof(*protocols))
89
90 struct discovery_sys
91 {
92 vlc_thread_t thread;
93 atomic_bool stop;
94 struct mdns_ctx * p_microdns;
95 const char * ppsz_service_names[NB_PROTOCOLS];
96 unsigned int i_nb_service_names;
97 vlc_array_t items;
98 };
99
100 struct services_discovery_sys_t
101 {
102 struct discovery_sys s;
103 };
104
105 struct vlc_renderer_discovery_sys
106 {
107 struct discovery_sys s;
108 };
109
110 struct item
111 {
112 char * psz_uri;
113 input_item_t * p_input_item;
114 vlc_renderer_item_t*p_renderer_item;
115 mtime_t i_last_seen;
116 };
117
118 struct srv
119 {
120 const char *psz_protocol;
121 char * psz_device_name;
122 uint16_t i_port;
123 int i_renderer_flags;
124 };
125
126 static const char *const ppsz_options[] = {
127 NULL
128 };
129
130 static void
print_error(vlc_object_t * p_obj,const char * psz_what,int i_status)131 print_error( vlc_object_t *p_obj, const char *psz_what, int i_status )
132 {
133 char psz_err_str[128];
134
135 if( mdns_strerror( i_status, psz_err_str, sizeof(psz_err_str) ) == 0)
136 msg_Err( p_obj, "mDNS %s error: %s", psz_what, psz_err_str);
137 else
138 msg_Err( p_obj, "mDNS %s error: unknown: %d", psz_what, i_status);
139 }
140
141
142 static int
strrcmp(const char * s1,const char * s2)143 strrcmp(const char *s1, const char *s2)
144 {
145 size_t m, n;
146 m = strlen(s1);
147 n = strlen(s2);
148 if (n > m)
149 return 1;
150 return strncmp(s1 + m - n, s2, n);
151 }
152
153 static int
items_add_input(struct discovery_sys * p_sys,services_discovery_t * p_sd,char * psz_uri,const char * psz_name)154 items_add_input( struct discovery_sys *p_sys, services_discovery_t *p_sd,
155 char *psz_uri, const char *psz_name )
156 {
157 struct item *p_item = malloc( sizeof(struct item) );
158 if( p_item == NULL )
159 {
160 free( psz_uri );
161 return VLC_ENOMEM;
162 }
163
164 input_item_t *p_input_item =
165 input_item_NewDirectory( psz_uri, psz_name, ITEM_NET );
166 if( p_input_item == NULL )
167 {
168 free( psz_uri );
169 free( p_item );
170 return VLC_ENOMEM;
171 }
172
173 p_item->psz_uri = psz_uri;
174 p_item->p_input_item = p_input_item;
175 p_item->p_renderer_item = NULL;
176 p_item->i_last_seen = mdate();
177 vlc_array_append_or_abort( &p_sys->items, p_item );
178 services_discovery_AddItem( p_sd, p_input_item );
179
180 return VLC_SUCCESS;
181 }
182
183 static int
items_add_renderer(struct discovery_sys * p_sys,vlc_renderer_discovery_t * p_rd,const char * psz_name,char * psz_uri,const char * psz_demux_filter,const char * psz_icon_uri,int i_flags)184 items_add_renderer( struct discovery_sys *p_sys, vlc_renderer_discovery_t *p_rd,
185 const char *psz_name, char *psz_uri,
186 const char *psz_demux_filter, const char *psz_icon_uri,
187 int i_flags )
188 {
189 struct item *p_item = malloc( sizeof(struct item) );
190 if( p_item == NULL )
191 return VLC_ENOMEM;
192
193 const char *psz_extra_uri = i_flags & VLC_RENDERER_CAN_VIDEO ? NULL : "no-video";
194
195 vlc_renderer_item_t *p_renderer_item =
196 vlc_renderer_item_new( "chromecast", psz_name, psz_uri, psz_extra_uri,
197 psz_demux_filter, psz_icon_uri, i_flags );
198 if( p_renderer_item == NULL )
199 {
200 free( psz_uri );
201 free( p_item );
202 return VLC_ENOMEM;
203 }
204
205 p_item->psz_uri = psz_uri;
206 p_item->p_input_item = NULL;
207 p_item->p_renderer_item = p_renderer_item;
208 p_item->i_last_seen = mdate();
209 vlc_array_append_or_abort( &p_sys->items, p_item );
210 vlc_rd_add_item( p_rd, p_renderer_item );
211
212 return VLC_SUCCESS;
213 }
214
215 static void
items_release(struct discovery_sys * p_sys,struct item * p_item)216 items_release( struct discovery_sys *p_sys, struct item *p_item )
217 {
218 (void) p_sys;
219 if( p_item->p_input_item != NULL )
220 {
221 input_item_Release( p_item->p_input_item );
222 }
223 else
224 {
225 assert( p_item->p_renderer_item != NULL );
226 vlc_renderer_item_release( p_item->p_renderer_item );
227 }
228
229 free( p_item->psz_uri );
230 free( p_item );
231 }
232
233 static bool
items_exists(struct discovery_sys * p_sys,const char * psz_uri)234 items_exists( struct discovery_sys *p_sys, const char *psz_uri )
235 {
236 for( size_t i = 0; i < vlc_array_count( &p_sys->items ); ++i )
237 {
238 struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
239 if( strcmp( p_item->psz_uri, psz_uri ) == 0 )
240 {
241 p_item->i_last_seen = mdate();
242 return true;
243 }
244 }
245 return false;
246 }
247
248 static void
items_timeout(struct discovery_sys * p_sys,services_discovery_t * p_sd,vlc_renderer_discovery_t * p_rd)249 items_timeout( struct discovery_sys *p_sys, services_discovery_t *p_sd,
250 vlc_renderer_discovery_t *p_rd )
251 {
252 assert( p_rd != NULL || p_sd != NULL );
253 mtime_t i_now = mdate();
254
255 /* Remove items that are not seen since TIMEOUT */
256 for( size_t i = 0; i < vlc_array_count( &p_sys->items ); ++i )
257 {
258 struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
259 if( i_now - p_item->i_last_seen > TIMEOUT )
260 {
261 if( p_sd != NULL )
262 services_discovery_RemoveItem( p_sd, p_item->p_input_item );
263 else
264 vlc_rd_remove_item( p_rd, p_item->p_renderer_item );
265 items_release( p_sys, p_item );
266 vlc_array_remove( &p_sys->items, i-- );
267 }
268 }
269 }
270
271 static void
items_clear(struct discovery_sys * p_sys)272 items_clear( struct discovery_sys *p_sys )
273 {
274 for( size_t i = 0; i < vlc_array_count( &p_sys->items ); ++i )
275 {
276 struct item *p_item = vlc_array_item_at_index( &p_sys->items, i );
277 items_release( p_sys, p_item );
278 }
279 vlc_array_clear( &p_sys->items );
280 }
281
282 static int
parse_entries(const struct rr_entry * p_entries,bool b_renderer,struct srv ** pp_srvs,unsigned int * p_nb_srv,const char ** ppsz_ip,bool * p_ipv6)283 parse_entries( const struct rr_entry *p_entries, bool b_renderer,
284 struct srv **pp_srvs, unsigned int *p_nb_srv,
285 const char **ppsz_ip, bool *p_ipv6 )
286 {
287 /* Count the number of servers */
288 unsigned int i_nb_srv = 0;
289 for( const struct rr_entry *p_entry = p_entries;
290 p_entry != NULL; p_entry = p_entry->next )
291 {
292 if( p_entry->type == RR_SRV )
293 i_nb_srv++;
294 }
295 if( i_nb_srv == 0 )
296 return VLC_EGENERIC;
297
298 struct srv *p_srvs = calloc(i_nb_srv, sizeof(struct srv));
299 if( p_srvs == NULL )
300 return VLC_EGENERIC;
301
302 /* There is one ip for several srvs, fetch them */
303 const char *psz_ip = NULL;
304 struct srv *p_srv = NULL;
305 i_nb_srv = 0;
306 for( const struct rr_entry *p_entry = p_entries;
307 p_entry != NULL; p_entry = p_entry->next )
308 {
309 if( p_entry->type == RR_SRV )
310 {
311 for( unsigned i = 0; i < NB_PROTOCOLS; ++i )
312 {
313 if( !strrcmp( p_entry->name, protocols[i].psz_service_name ) &&
314 protocols[i].b_renderer == b_renderer )
315 {
316 p_srv = &p_srvs[i_nb_srv];
317
318 p_srv->psz_device_name =
319 strndup( p_entry->name, strlen( p_entry->name )
320 - strlen( protocols[i].psz_service_name ) - 1);
321 if( p_srv->psz_device_name == NULL )
322 break;
323 p_srv->psz_protocol = protocols[i].psz_protocol;
324 p_srv->i_port = p_entry->data.SRV.port;
325 p_srv->i_renderer_flags = protocols[i].i_renderer_flags;
326 ++i_nb_srv;
327 break;
328 }
329 }
330 }
331 else if( p_entry->type == RR_A && psz_ip == NULL )
332 psz_ip = p_entry->data.A.addr_str;
333 else if( p_entry->type == RR_AAAA && psz_ip == NULL )
334 {
335 psz_ip = p_entry->data.AAAA.addr_str;
336 *p_ipv6 = true;
337 }
338 else if( p_entry->type == RR_TXT && p_srv != NULL )
339 {
340 for ( struct rr_data_txt *p_txt = p_entry->data.TXT;
341 p_txt != NULL ; p_txt = p_txt->next )
342 {
343 if( !strcmp( p_srv->psz_protocol, "chromecast" ) )
344 {
345 if ( !strncmp( "fn=", p_txt->txt, 3 ) )
346 {
347 free( p_srv->psz_device_name );
348 p_srv->psz_device_name = strdup( p_txt->txt + 3 );
349 }
350 else if( !strncmp( "ca=", p_txt->txt, 3 ) )
351 {
352 int ca = atoi( p_txt->txt + 3);
353 /*
354 * For chromecast, the `ca=` is composed from (at least)
355 * 0x01 to indicate video support
356 * 0x04 to indivate audio support
357 */
358 if ( ( ca & 0x01 ) != 0 )
359 p_srv->i_renderer_flags |= VLC_RENDERER_CAN_VIDEO;
360 if ( ( ca & 0x04 ) != 0 )
361 p_srv->i_renderer_flags |= VLC_RENDERER_CAN_AUDIO;
362 }
363 }
364 }
365 }
366 }
367 if( psz_ip == NULL || i_nb_srv == 0 )
368 {
369 for( unsigned int i = 0; i < i_nb_srv; ++i )
370 free( p_srvs[i].psz_device_name );
371 free( p_srvs );
372 return VLC_EGENERIC;
373 }
374
375 *pp_srvs = p_srvs;
376 *p_nb_srv = i_nb_srv;
377 *ppsz_ip = psz_ip;
378 return VLC_SUCCESS;
379 }
380
381 static char *
create_uri(const char * psz_protocol,const char * psz_ip,bool b_ipv6,uint16_t i_port)382 create_uri( const char *psz_protocol, const char *psz_ip, bool b_ipv6,
383 uint16_t i_port )
384 {
385 char *psz_uri;
386
387 return asprintf( &psz_uri, "%s://%s%s%s:%u", psz_protocol,
388 b_ipv6 ? "[" : "", psz_ip, b_ipv6 ? "]" : "",
389 i_port ) < 0 ? NULL : psz_uri;
390 }
391
392 static void
new_entries_sd_cb(void * p_this,int i_status,const struct rr_entry * p_entries)393 new_entries_sd_cb( void *p_this, int i_status, const struct rr_entry *p_entries )
394 {
395 services_discovery_t *p_sd = (services_discovery_t *)p_this;
396 struct discovery_sys *p_sys = &p_sd->p_sys->s;
397 if( i_status < 0 )
398 {
399 print_error( VLC_OBJECT( p_sd ), "entry callback", i_status );
400 return;
401 }
402
403 struct srv *p_srvs;
404 unsigned i_nb_srv;
405 const char *psz_ip;
406 bool b_ipv6 = false;
407 if( parse_entries( p_entries, false, &p_srvs, &i_nb_srv,
408 &psz_ip, &b_ipv6 ) != VLC_SUCCESS )
409 return;
410
411 /* send new input items (if they don't already exist) */
412 for( unsigned int i = 0; i < i_nb_srv; ++i )
413 {
414 struct srv *p_srv = &p_srvs[i];
415 char *psz_uri = create_uri( p_srv->psz_protocol, psz_ip, b_ipv6,
416 p_srv->i_port );
417
418 if( psz_uri == NULL )
419 break;
420
421 if( items_exists( p_sys, psz_uri ) )
422 {
423 free( psz_uri );
424 continue;
425 }
426 items_add_input( p_sys, p_sd, psz_uri, p_srv->psz_device_name );
427 }
428
429 for( unsigned int i = 0; i < i_nb_srv; ++i )
430 free( p_srvs[i].psz_device_name );
431 free( p_srvs );
432 }
433
434
435 static bool
stop_sd_cb(void * p_this)436 stop_sd_cb( void *p_this )
437 {
438 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
439 struct discovery_sys *p_sys = &p_sd->p_sys->s;
440
441 if( atomic_load( &p_sys->stop ) )
442 return true;
443 else
444 {
445 items_timeout( p_sys, p_sd, NULL );
446 return false;
447 }
448 }
449
450 static void *
RunSD(void * p_this)451 RunSD( void *p_this )
452 {
453 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
454 struct discovery_sys *p_sys = &p_sd->p_sys->s;
455
456 int i_status = mdns_listen( p_sys->p_microdns,
457 p_sys->ppsz_service_names,
458 p_sys->i_nb_service_names,
459 RR_PTR, LISTEN_INTERVAL / INT64_C(1000000),
460 stop_sd_cb, new_entries_sd_cb, p_sd );
461
462 if( i_status < 0 )
463 print_error( VLC_OBJECT( p_sd ), "listen", i_status );
464
465 return NULL;
466 }
467
468 static void
new_entries_rd_cb(void * p_this,int i_status,const struct rr_entry * p_entries)469 new_entries_rd_cb( void *p_this, int i_status, const struct rr_entry *p_entries )
470 {
471 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
472 struct discovery_sys *p_sys = &p_rd->p_sys->s;
473 if( i_status < 0 )
474 {
475 print_error( VLC_OBJECT( p_rd ), "entry callback", i_status );
476 return;
477 }
478
479 struct srv *p_srvs;
480 unsigned i_nb_srv;
481 const char *psz_ip;
482 bool b_ipv6 = false;
483 if( parse_entries( p_entries, true, &p_srvs, &i_nb_srv,
484 &psz_ip, &b_ipv6 ) != VLC_SUCCESS )
485 return;
486
487 const char *psz_model = NULL;
488 const char *psz_icon = NULL;
489 for( const struct rr_entry *p_entry = p_entries;
490 p_entry != NULL && ( psz_model == NULL || psz_icon == NULL );
491 p_entry = p_entry->next )
492 {
493 if( p_entry->type == RR_TXT )
494 {
495 const struct rr_data_txt *p_txt = p_entry->data.TXT;
496 while( p_txt && ( psz_model == NULL || psz_icon == NULL ) )
497 {
498 if( !strncmp("md=", p_txt->txt, 3) )
499 psz_model = p_txt->txt + 3;
500 else if( !strncmp("ic=", p_txt->txt, 3) )
501 psz_icon = p_txt->txt + 3;
502 p_txt = p_txt->next;
503 }
504 }
505 }
506
507 /* send new input items (if they don't already exist) */
508 for( unsigned int i = 0; i < i_nb_srv; ++i )
509 {
510 struct srv *p_srv = &p_srvs[i];
511 char *psz_icon_uri = NULL;
512 char *psz_uri = create_uri( p_srv->psz_protocol, psz_ip, b_ipv6,
513 p_srv->i_port );
514 const char *psz_demux_filter = NULL;
515
516 if( psz_uri == NULL )
517 break;
518
519 if( items_exists( p_sys, psz_uri ) )
520 {
521 free( psz_uri );
522 continue;
523 }
524
525 if( psz_icon != NULL
526 && asprintf( &psz_icon_uri, "http://%s:8008%s", psz_ip, psz_icon )
527 == -1 )
528 {
529 free( psz_uri );
530 break;
531 }
532
533 if( strcmp( p_srv->psz_protocol, "chromecast" ) == 0)
534 psz_demux_filter = "cc_demux";
535
536 items_add_renderer( p_sys, p_rd, p_srv->psz_device_name, psz_uri,
537 psz_demux_filter, psz_icon_uri,
538 p_srv->i_renderer_flags );
539 free(psz_icon_uri);
540 }
541
542 for( unsigned int i = 0; i < i_nb_srv; ++i )
543 free( p_srvs[i].psz_device_name );
544 free( p_srvs );
545 }
546
547 static bool
stop_rd_cb(void * p_this)548 stop_rd_cb( void *p_this )
549 {
550 vlc_renderer_discovery_t *p_rd = p_this;
551 struct discovery_sys *p_sys = &p_rd->p_sys->s;
552
553 if( atomic_load( &p_sys->stop ) )
554 return true;
555 else
556 {
557 items_timeout( p_sys, NULL, p_rd );
558 return false;
559 }
560 }
561
562 static void *
RunRD(void * p_this)563 RunRD( void *p_this )
564 {
565 vlc_renderer_discovery_t *p_rd = p_this;
566 struct discovery_sys *p_sys = &p_rd->p_sys->s;
567
568 int i_status = mdns_listen( p_sys->p_microdns,
569 p_sys->ppsz_service_names,
570 p_sys->i_nb_service_names,
571 RR_PTR, LISTEN_INTERVAL / INT64_C(1000000),
572 stop_rd_cb, new_entries_rd_cb, p_rd );
573
574 if( i_status < 0 )
575 print_error( VLC_OBJECT( p_rd ), "listen", i_status );
576
577 return NULL;
578 }
579
580 static int
OpenCommon(vlc_object_t * p_obj,struct discovery_sys * p_sys,bool b_renderer)581 OpenCommon( vlc_object_t *p_obj, struct discovery_sys *p_sys, bool b_renderer )
582 {
583 int i_ret = VLC_EGENERIC;
584 atomic_init( &p_sys->stop, false );
585 vlc_array_init( &p_sys->items );
586
587 /* Listen to protocols that are handled by VLC */
588 for( unsigned int i = 0; i < NB_PROTOCOLS; ++i )
589 {
590 if( protocols[i].b_renderer == b_renderer )
591 p_sys->ppsz_service_names[p_sys->i_nb_service_names++] =
592 protocols[i].psz_service_name;
593 }
594
595 if( p_sys->i_nb_service_names == 0 )
596 {
597 msg_Err( p_obj, "no services found" );
598 goto error;
599 }
600 for( unsigned int i = 0; i < p_sys->i_nb_service_names; ++i )
601 msg_Dbg( p_obj, "mDNS: listening to %s %s", p_sys->ppsz_service_names[i],
602 b_renderer ? "renderer" : "service" );
603
604 int i_status;
605 if( ( i_status = mdns_init( &p_sys->p_microdns, MDNS_ADDR_IPV4,
606 MDNS_PORT ) ) < 0 )
607 {
608 print_error( p_obj, "init", i_status );
609 goto error;
610 }
611
612 if( vlc_clone( &p_sys->thread, b_renderer ? RunRD : RunSD, p_obj,
613 VLC_THREAD_PRIORITY_LOW) )
614 {
615 msg_Err( p_obj, "Can't run the lookup thread" );
616 goto error;
617 }
618
619 return VLC_SUCCESS;
620 error:
621 if( p_sys->p_microdns != NULL )
622 mdns_destroy( p_sys->p_microdns );
623 free( p_sys );
624 return i_ret;
625 }
626
627 static void
CleanCommon(struct discovery_sys * p_sys)628 CleanCommon( struct discovery_sys *p_sys )
629 {
630 atomic_store( &p_sys->stop, true );
631 vlc_join( p_sys->thread, NULL );
632
633 items_clear( p_sys );
634 mdns_destroy( p_sys->p_microdns );
635 }
636
637 static int
OpenSD(vlc_object_t * p_obj)638 OpenSD( vlc_object_t *p_obj )
639 {
640 services_discovery_t *p_sd = (services_discovery_t *)p_obj;
641
642 p_sd->p_sys = calloc( 1, sizeof(services_discovery_sys_t) );
643 if( !p_sd->p_sys )
644 return VLC_ENOMEM;
645
646 p_sd->description = _("mDNS Network Discovery");
647 config_ChainParse( p_sd, CFG_PREFIX, ppsz_options, p_sd->p_cfg );
648
649 return OpenCommon( p_obj, &p_sd->p_sys->s, false );
650 }
651
652 static void
CloseSD(vlc_object_t * p_this)653 CloseSD( vlc_object_t *p_this )
654 {
655 services_discovery_t *p_sd = (services_discovery_t *) p_this;
656
657 CleanCommon( &p_sd->p_sys->s );
658 free( p_sd->p_sys );
659 }
660
661 static int
OpenRD(vlc_object_t * p_obj)662 OpenRD( vlc_object_t *p_obj )
663 {
664 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_obj;
665
666 p_rd->p_sys = calloc( 1, sizeof(vlc_renderer_discovery_sys) );
667 if( !p_rd->p_sys )
668 return VLC_ENOMEM;
669
670 config_ChainParse( p_rd, CFG_PREFIX, ppsz_options, p_rd->p_cfg );
671
672 return OpenCommon( p_obj, &p_rd->p_sys->s, true );
673 }
674
675 static void
CloseRD(vlc_object_t * p_this)676 CloseRD( vlc_object_t *p_this )
677 {
678 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *) p_this;
679
680 CleanCommon( &p_rd->p_sys->s );
681 free( p_rd->p_sys );
682 }
683