Coverage Report

Created: 2025-07-11 06:16

/src/vlc/modules/services_discovery/sap.c
Line
Count
Source (jump to first uncovered line)
1
/*****************************************************************************
2
 * sap.c :  SAP interface module
3
 *****************************************************************************
4
 * Copyright (C) 2004-2005 the VideoLAN team
5
 * Copyright © 2007 Rémi Denis-Courmont
6
 *
7
 * Authors: Clément Stenac <zorglub@videolan.org>
8
 *          Rémi Denis-Courmont
9
 *
10
 * This program is free software; you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation; either version 2 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program; if not, write to the Free Software
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23
 *****************************************************************************/
24
25
/*****************************************************************************
26
 * Includes
27
 *****************************************************************************/
28
#ifdef HAVE_CONFIG_H
29
# include "config.h"
30
#endif
31
32
#define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
33
#include <vlc_common.h>
34
#include <vlc_arrays.h>
35
#include <vlc_threads.h>
36
#include <vlc_poll.h>
37
#include <vlc_plugin.h>
38
#include <assert.h>
39
40
#include <vlc_demux.h>
41
#include <vlc_services_discovery.h>
42
43
#include <vlc_network.h>
44
#include <vlc_charset.h>
45
46
#include <errno.h>
47
#include <unistd.h>
48
#ifdef HAVE_ARPA_INET_H
49
# include <arpa/inet.h>
50
#endif
51
#ifdef HAVE_POLL_H
52
# include <poll.h>
53
#endif
54
55
#ifdef HAVE_ZLIB_H
56
#   include <zlib.h>
57
#endif
58
59
#ifdef HAVE_NET_IF_H
60
#   include <net/if.h>
61
#endif
62
63
#ifdef HAVE_SEARCH_H
64
#   include <search.h>
65
#endif
66
67
#include "access/rtp/sdp.h"
68
69
/************************************************************************
70
 * Macros and definitions
71
 ************************************************************************/
72
73
#define MAX_LINE_LENGTH 256
74
75
/* SAP is always on that port */
76
0
#define SAP_PORT 9875
77
/* Global-scope SAP address */
78
0
#define SAP_V4_GLOBAL_ADDRESS   "224.2.127.254"
79
/* Organization-local SAP address */
80
0
#define SAP_V4_ORG_ADDRESS      "239.195.255.255"
81
/* Local (smallest non-link-local scope) SAP address */
82
0
#define SAP_V4_LOCAL_ADDRESS    "239.255.255.255"
83
/* Link-local SAP address */
84
0
#define SAP_V4_LINK_ADDRESS     "224.0.0.255"
85
#define ADD_SESSION 1
86
87
typedef struct
88
{
89
    vlc_thread_t thread;
90
91
    /* Socket descriptors */
92
    int i_fd;
93
    int *pi_fd;
94
95
    /* Table of announces */
96
    int i_announces;
97
    struct sap_announce_t **pp_announces;
98
99
    vlc_tick_t i_timeout;
100
101
    void *root_cat;
102
} services_discovery_sys_t;
103
104
static int Decompress( const unsigned char *psz_src, unsigned char **_dst, int i_len )
105
0
{
106
#ifdef HAVE_ZLIB
107
    int i_result, i_dstsize, n = 0;
108
    unsigned char *psz_dst = NULL;
109
    z_stream d_stream;
110
111
    memset (&d_stream, 0, sizeof (d_stream));
112
113
    i_result = inflateInit(&d_stream);
114
    if( i_result != Z_OK )
115
        return( -1 );
116
117
    d_stream.next_in = (Bytef *)psz_src;
118
    d_stream.avail_in = i_len;
119
120
    do
121
    {
122
        n++;
123
        psz_dst = xrealloc( psz_dst, n * 1000 );
124
        d_stream.next_out = (Bytef *)&psz_dst[(n - 1) * 1000];
125
        d_stream.avail_out = 1000;
126
127
        i_result = inflate(&d_stream, Z_NO_FLUSH);
128
        if( ( i_result != Z_OK ) && ( i_result != Z_STREAM_END ) )
129
        {
130
            inflateEnd( &d_stream );
131
            free( psz_dst );
132
            return( -1 );
133
        }
134
    }
135
    while( ( d_stream.avail_out == 0 ) && ( d_stream.avail_in != 0 ) &&
136
           ( i_result != Z_STREAM_END ) );
137
138
    i_dstsize = d_stream.total_out;
139
    inflateEnd( &d_stream );
140
141
    *_dst = xrealloc( psz_dst, i_dstsize );
142
143
    return i_dstsize;
144
#else
145
0
    (void)psz_src;
146
0
    (void)_dst;
147
0
    (void)i_len;
148
0
    return -1;
149
0
#endif
150
0
}
151
152
struct category
153
{
154
    input_item_t *item;
155
    services_discovery_t *sd;
156
    char name[];
157
};
158
159
static void DestroyCategory(void *data)
160
0
{
161
0
    struct category *c = data;
162
163
0
    services_discovery_RemoveItem(c->sd, c->item);
164
0
    input_item_Release(c->item);
165
0
    free(c);
166
0
}
167
168
static int cmpcat (const void *a, const void *b)
169
0
{
170
0
    const struct category *ca = a, *cb = b;
171
0
    return strcmp(ca->name, cb->name);
172
0
}
173
174
static input_item_t *AddCategory (services_discovery_t *sd, const char *name)
175
0
{
176
0
    services_discovery_sys_t *sys = sd->p_sys;
177
178
0
    struct category *c = malloc(sizeof(*c) + strlen(name) + 1);
179
0
    if (unlikely(c == NULL))
180
0
        return NULL;
181
0
    strcpy(c->name, name);
182
183
0
    void **cp = tsearch(c, &sys->root_cat, cmpcat);
184
0
    if (cp == NULL) /* Out-of-memory */
185
0
    {
186
0
        free(c);
187
0
        return NULL;
188
0
    }
189
0
    if (*cp != c)
190
0
    {
191
0
        free(c);
192
0
        c = *cp;
193
0
        assert(c->item != NULL);
194
0
        return c->item;
195
0
    }
196
197
0
    c->item = input_item_NewExt("vlc://nop", c->name,
198
0
                                INPUT_DURATION_INDEFINITE,
199
0
                                ITEM_TYPE_NODE, ITEM_LOCAL);
200
201
0
    if (unlikely(c->item == NULL))
202
0
    {
203
0
        tdelete(c, &sys->root_cat, cmpcat);
204
0
        return NULL;
205
0
    }
206
0
    services_discovery_AddItem(sd, c->item);
207
0
    c->sd = sd;
208
209
0
    return c->item;
210
0
}
211
212
typedef struct sap_announce_t
213
{
214
    vlc_tick_t i_last;
215
    vlc_tick_t i_period;
216
    uint8_t i_period_trust;
217
218
    uint16_t    i_hash;
219
    uint32_t    i_source[4];
220
221
    input_item_t * p_item;
222
} sap_announce_t;
223
224
static sap_announce_t *CreateAnnounce(services_discovery_t *p_sd,
225
                                      const uint32_t *i_source,
226
                                      uint16_t i_hash, const char *psz_sdp)
227
0
{
228
    /* Parse SDP info */
229
0
    struct vlc_sdp *p_sdp = vlc_sdp_parse(psz_sdp, strlen(psz_sdp));
230
0
    if (p_sdp == NULL)
231
0
        return NULL;
232
233
0
    sap_announce_t *p_sap = malloc(sizeof (*p_sap));
234
0
    if( p_sap == NULL )
235
0
        goto error;
236
237
0
    char *uri = NULL;
238
239
0
    if (asprintf(&uri, "sdp://%s", psz_sdp) == -1)
240
0
        goto error;
241
242
0
    input_item_t *p_input;
243
0
    const char *psz_value;
244
245
0
    p_sap->i_last = vlc_tick_now();
246
0
    p_sap->i_period = 0;
247
0
    p_sap->i_period_trust = 0;
248
0
    p_sap->i_hash = i_hash;
249
0
    memcpy (p_sap->i_source, i_source, sizeof(p_sap->i_source));
250
251
    /* Released in RemoveAnnounce */
252
0
    p_input = input_item_NewStream(uri, p_sdp->name,
253
0
                                   INPUT_DURATION_INDEFINITE);
254
0
    free(uri);
255
0
    if( unlikely(p_input == NULL) )
256
0
        goto error;
257
258
0
    p_sap->p_item = p_input;
259
260
0
    input_item_SetMeta(p_input, vlc_meta_Description, p_sdp->info);
261
262
0
    psz_value = vlc_sdp_attr_value(p_sdp, "tool");
263
0
    if( psz_value != NULL )
264
0
    {
265
0
        input_item_AddInfo( p_input, _("Session"), _("Tool"), "%s", psz_value );
266
0
    }
267
268
    /* Handle category */
269
0
    psz_value = vlc_sdp_attr_value(p_sdp, "cat");
270
0
    char *str = NULL;
271
0
    if (psz_value != NULL)
272
0
    {
273
        /* a=cat provides a dot-separated hierarchy.
274
         * For the time being only replace dots with pipe. TODO: FIXME */
275
0
        str = strdup(psz_value);
276
0
        if (likely(str != NULL))
277
0
        {
278
0
            for (char *p = strchr(str, '.'); p != NULL; p = strchr(p, '.'))
279
0
                *(p++) = '|';
280
0
            psz_value = str;
281
0
        }
282
0
    }
283
0
    else
284
0
    {
285
        /* backward compatibility with VLC 0.7.3-2.0.0 senders */
286
0
        psz_value = vlc_sdp_attr_value(p_sdp, "x-plgroup");
287
0
    }
288
0
    input_item_t *cat = psz_value == NULL ? NULL : AddCategory(p_sd, psz_value);
289
0
    free(str);
290
0
    services_discovery_AddSubItem(p_sd, cat, p_input);
291
292
0
    vlc_sdp_free(p_sdp);
293
0
    return p_sap;
294
0
error:
295
0
    free(p_sap);
296
0
    vlc_sdp_free(p_sdp);
297
0
    return NULL;
298
0
}
299
300
static int RemoveAnnounce( services_discovery_t *p_sd,
301
                           sap_announce_t *p_announce )
302
0
{
303
0
    if( p_announce->p_item )
304
0
    {
305
0
        services_discovery_RemoveItem( p_sd, p_announce->p_item );
306
0
        input_item_Release( p_announce->p_item );
307
0
        p_announce->p_item = NULL;
308
0
    }
309
310
0
    services_discovery_sys_t *p_sys = p_sd->p_sys;
311
0
    TAB_REMOVE(p_sys->i_announces, p_sys->pp_announces, p_announce);
312
0
    free( p_announce );
313
314
0
    return VLC_SUCCESS;
315
0
}
316
317
static int InitSocket( services_discovery_t *p_sd, const char *psz_address,
318
                       int i_port )
319
0
{
320
0
    int i_fd = net_ListenUDP1 (VLC_OBJECT(p_sd), psz_address, i_port);
321
0
    if (i_fd == -1)
322
0
        return VLC_EGENERIC;
323
324
0
    shutdown( i_fd, SHUT_WR );
325
0
    services_discovery_sys_t *p_sys = p_sd->p_sys;
326
0
    TAB_APPEND(p_sys->i_fd, p_sys->pi_fd, i_fd);
327
0
    return VLC_SUCCESS;
328
0
}
329
330
/* i_read is at least > 6 */
331
static int ParseSAP( services_discovery_t *p_sd, const uint8_t *buf,
332
                     size_t len )
333
0
{
334
0
    services_discovery_sys_t *p_sys = p_sd->p_sys;
335
0
    const char          *psz_sdp;
336
0
    const uint8_t *end = buf + len;
337
0
    uint32_t            i_source[4];
338
339
0
    assert (buf[len] == '\0');
340
341
0
    if (len < 4)
342
0
        return VLC_EGENERIC;
343
344
0
    uint8_t flags = buf[0];
345
0
    uint8_t auth_len = buf[1];
346
347
    /* First, check the sap announce is correct */
348
0
    if ((flags >> 5) != 1)
349
0
        return VLC_EGENERIC;
350
351
0
    bool b_ipv6 = (flags & 0x10) != 0;
352
0
    bool b_need_delete = (flags & 0x04) != 0;
353
354
0
    if (flags & 0x02)
355
0
    {
356
0
        msg_Dbg( p_sd, "encrypted packet, unsupported" );
357
0
        return VLC_EGENERIC;
358
0
    }
359
360
0
    bool b_compressed = (flags & 0x01) != 0;
361
362
0
    uint16_t i_hash = U16_AT (buf + 2);
363
364
0
    if( i_hash == 0 )
365
0
        return VLC_EGENERIC;
366
367
0
    buf += 4;
368
0
    if( b_ipv6 )
369
0
    {
370
0
        for( int i = 0; i < 4; i++,buf+=4)
371
0
            i_source[i] = U32_AT(buf);
372
0
    }
373
0
    else
374
0
    {
375
0
        memset(i_source, 0, sizeof(i_source));
376
0
        i_source[3] = U32_AT(buf);
377
0
        buf+=4;
378
0
    }
379
    // Skips auth data
380
0
    buf += auth_len;
381
0
    if (buf > end)
382
0
        return VLC_EGENERIC;
383
384
0
    uint8_t *decomp = NULL;
385
0
    if( b_compressed )
386
0
    {
387
0
        int newsize = Decompress (buf, &decomp, end - buf);
388
0
        if (newsize < 0)
389
0
        {
390
0
            msg_Dbg( p_sd, "decompression of SAP packet failed" );
391
0
            return VLC_EGENERIC;
392
0
        }
393
394
0
        decomp = xrealloc (decomp, newsize + 1);
395
0
        decomp[newsize] = '\0';
396
0
        psz_sdp = (const char *)decomp;
397
0
        len = newsize;
398
0
    }
399
0
    else
400
0
    {
401
0
        psz_sdp = (const char *)buf;
402
0
        len = end - buf;
403
0
    }
404
405
    /* len is a strlen here here. both buf and decomp are len+1 where the 1 should be a \0 */
406
0
    assert( psz_sdp[len] == '\0');
407
408
    /* Skip payload type */
409
    /* SAPv1 has implicit "application/sdp" payload type: first line is v=0 */
410
0
    if (strncmp (psz_sdp, "v=0", 3))
411
0
    {
412
0
        size_t clen = strlen (psz_sdp) + 1;
413
414
0
        if (strcmp (psz_sdp, "application/sdp"))
415
0
        {
416
0
            msg_Dbg (p_sd, "unsupported content type: %s", psz_sdp);
417
0
            goto error;
418
0
        }
419
420
        // skips content type
421
0
        if (len <= clen)
422
0
            goto error;
423
424
0
        len -= clen;
425
0
        psz_sdp += clen;
426
0
    }
427
428
0
    for( int i = 0 ; i < p_sys->i_announces ; i++ )
429
0
    {
430
0
        sap_announce_t * p_announce = p_sys->pp_announces[i];
431
        /* FIXME: slow */
432
0
        if (p_announce->i_hash == i_hash
433
0
         && memcmp(p_announce->i_source, i_source, sizeof (i_source)) == 0)
434
0
        {
435
            /* We don't support delete announcement as they can easily
436
             * Be used to hijack an announcement by a third party.
437
             * Instead we cleverly implement Implicit Announcement removal.
438
             *
439
             * if( b_need_delete )
440
             *    RemoveAnnounce( p_sd, p_sys->pp_announces[i]);
441
             * else
442
             */
443
444
0
            if( !b_need_delete )
445
0
            {
446
                /* No need to go after six, as we start to trust the
447
                 * average period at six */
448
0
                if( p_announce->i_period_trust <= 5 )
449
0
                    p_announce->i_period_trust++;
450
451
                /* Compute the average period */
452
0
                vlc_tick_t now = vlc_tick_now();
453
0
                p_announce->i_period = ( p_announce->i_period * (p_announce->i_period_trust-1) + (now - p_announce->i_last) ) / p_announce->i_period_trust;
454
0
                p_announce->i_last = now;
455
0
            }
456
0
            free (decomp);
457
0
            return VLC_SUCCESS;
458
0
        }
459
0
    }
460
461
0
    sap_announce_t *sap = CreateAnnounce(p_sd, i_source, i_hash, psz_sdp);
462
0
    if (sap != NULL)
463
0
        TAB_APPEND(p_sys->i_announces, p_sys->pp_announces, sap);
464
465
0
    free (decomp);
466
0
    return VLC_SUCCESS;
467
0
error:
468
0
    free (decomp);
469
0
    return VLC_EGENERIC;
470
0
}
471
472
/*****************************************************************************
473
 * Run: main SAP thread
474
 *****************************************************************************
475
 * Listens to SAP packets, and sends them to packet_handle
476
 *****************************************************************************/
477
0
#define MAX_SAP_BUFFER 5000
478
479
static void *Run( void *data )
480
0
{
481
0
    vlc_thread_set_name("vlc-sap");
482
483
0
    services_discovery_t *p_sd = data;
484
0
    services_discovery_sys_t *p_sys = p_sd->p_sys;
485
0
    char *psz_addr;
486
0
    int timeout = -1;
487
0
    int canc = vlc_savecancel ();
488
489
    /* Braindead Winsock DNS resolver will get stuck over 2 seconds per failed
490
     * DNS queries, even if the DNS server returns an error with milliseconds.
491
     * You don't want to know why the bug (as of XP SP2) wasn't fixed since
492
     * Winsock 1.1 from Windows 95, if not Windows 3.1.
493
     * Anyway, to avoid a 30 seconds delay for failed IPv6 socket creation,
494
     * we have to open sockets in Run() rather than Open(). */
495
0
    InitSocket( p_sd, SAP_V4_GLOBAL_ADDRESS, SAP_PORT );
496
0
    InitSocket( p_sd, SAP_V4_ORG_ADDRESS, SAP_PORT );
497
0
    InitSocket( p_sd, SAP_V4_LOCAL_ADDRESS, SAP_PORT );
498
0
    InitSocket( p_sd, SAP_V4_LINK_ADDRESS, SAP_PORT );
499
500
0
    char psz_address[NI_MAXNUMERICHOST] = "ff02::2:7ffe%";
501
0
#ifndef _WIN32
502
0
    struct if_nameindex *l = if_nameindex ();
503
0
    if (l != NULL)
504
0
    {
505
0
        char *ptr = strchr (psz_address, '%') + 1;
506
0
        for (unsigned i = 0; l[i].if_index; i++)
507
0
        {
508
0
            strcpy (ptr, l[i].if_name);
509
0
            InitSocket (p_sd, psz_address, SAP_PORT);
510
0
        }
511
0
        if_freenameindex (l);
512
0
    }
513
#else
514
        /* this is the Winsock2 equivalent of SIOCGIFCONF on BSD stacks,
515
           which if_nameindex uses internally anyway */
516
517
        // first create a dummy socket to pin down the protocol family
518
        SOCKET s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
519
        if( s != INVALID_SOCKET )
520
        {
521
            INTERFACE_INFO ifaces[10]; // Assume there will be no more than 10 IP interfaces
522
            DWORD len = sizeof(ifaces);
523
524
            if( SOCKET_ERROR != WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0, &ifaces, len, &len, NULL, NULL) )
525
            {
526
                unsigned ifcount = len/sizeof(INTERFACE_INFO);
527
                char *ptr = strchr (psz_address, '%') + 1;
528
                for(unsigned i = 1; i<=ifcount; ++i )
529
                {
530
                    // append link-local zone identifier
531
                    sprintf(ptr, "%d", i);
532
                }
533
            }
534
            closesocket(s);
535
        }
536
#endif
537
0
    *strchr (psz_address, '%') = '\0';
538
539
0
    static const char ipv6_scopes[] = "1456789ABCDE";
540
0
    for (const char *c_scope = ipv6_scopes; *c_scope; c_scope++)
541
0
    {
542
0
        psz_address[3] = *c_scope;
543
0
        InitSocket( p_sd, psz_address, SAP_PORT );
544
0
    }
545
546
0
    psz_addr = var_CreateGetString( p_sd, "sap-addr" );
547
0
    if( psz_addr && *psz_addr )
548
0
        InitSocket( p_sd, psz_addr, SAP_PORT );
549
0
    free( psz_addr );
550
551
0
    if( p_sys->i_fd == 0 )
552
0
    {
553
0
        msg_Err( p_sd, "unable to listen on any address" );
554
0
        return NULL;
555
0
    }
556
557
    /* read SAP packets */
558
0
    for (;;)
559
0
    {
560
0
        vlc_restorecancel (canc);
561
0
        unsigned n = p_sys->i_fd;
562
0
        struct pollfd ufd[n];
563
564
0
        for (unsigned i = 0; i < n; i++)
565
0
        {
566
0
            ufd[i].fd = p_sys->pi_fd[i];
567
0
            ufd[i].events = POLLIN;
568
0
            ufd[i].revents = 0;
569
0
        }
570
571
0
        int val = poll (ufd, n, timeout);
572
0
        canc = vlc_savecancel ();
573
0
        if (val > 0)
574
0
        {
575
0
            for (unsigned i = 0; i < n; i++)
576
0
            {
577
0
                if (ufd[i].revents)
578
0
                {
579
0
                    uint8_t p_buffer[MAX_SAP_BUFFER+1];
580
0
                    ssize_t i_read;
581
582
0
                    i_read = recv (ufd[i].fd, p_buffer, MAX_SAP_BUFFER, 0);
583
0
                    if (i_read < 0)
584
0
                        msg_Warn (p_sd, "receive error: %s",
585
0
                                  vlc_strerror_c(errno));
586
0
                    if (i_read > 6)
587
0
                    {
588
                        /* Parse the packet */
589
0
                        p_buffer[i_read] = '\0';
590
0
                        ParseSAP (p_sd, p_buffer, i_read);
591
0
                    }
592
0
                }
593
0
            }
594
0
        }
595
596
0
        vlc_tick_t now = vlc_tick_now();
597
598
        /* A 1 hour timeout correspond to the RFC Implicit timeout.
599
         * This timeout is tuned in the following loop. */
600
0
        timeout = 1000 * 60 * 60;
601
602
        /* Check for items that need deletion */
603
0
        for( int i = 0; i < p_sys->i_announces; i++ )
604
0
        {
605
0
            sap_announce_t * p_announce = p_sys->pp_announces[i];
606
0
            vlc_tick_t i_last_period = now - p_announce->i_last;
607
608
            /* Remove the announcement, if the last announcement was 1 hour ago
609
             * or if the last packet emitted was 10 times the average time
610
             * between two packets */
611
0
            if( ( p_announce->i_period_trust > 5 && i_last_period > 10 * p_announce->i_period ) ||
612
0
                i_last_period > p_sys->i_timeout )
613
0
            {
614
0
                RemoveAnnounce( p_sd, p_announce );
615
0
            }
616
0
            else
617
0
            {
618
                /* Compute next timeout */
619
0
                if( p_announce->i_period_trust > 5 )
620
0
                    timeout = __MIN(MS_FROM_VLC_TICK(10 * p_announce->i_period - i_last_period), timeout);
621
0
                timeout = __MIN(MS_FROM_VLC_TICK(p_sys->i_timeout - i_last_period), timeout);
622
0
            }
623
0
        }
624
625
0
        if( !p_sys->i_announces )
626
0
            timeout = -1; /* We can safely poll indefinitely. */
627
0
        else if( timeout < 200 )
628
0
            timeout = 200; /* Don't wakeup too fast. */
629
0
    }
630
0
    vlc_assert_unreachable ();
631
0
}
632
633
/*****************************************************************************
634
 * Open: initialize and create stuff
635
 *****************************************************************************/
636
static int Open( vlc_object_t *p_this )
637
0
{
638
0
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
639
0
    services_discovery_sys_t *p_sys  = (services_discovery_sys_t *)
640
0
                                malloc( sizeof( services_discovery_sys_t ) );
641
0
    if( !p_sys )
642
0
        return VLC_ENOMEM;
643
644
0
    p_sys->i_timeout = vlc_tick_from_sec(var_CreateGetInteger( p_sd, "sap-timeout" ));
645
646
0
    p_sd->p_sys  = p_sys;
647
0
    p_sd->description = _("Network streams (SAP)");
648
649
0
    p_sys->pi_fd = NULL;
650
0
    p_sys->i_fd = 0;
651
652
0
    p_sys->i_announces = 0;
653
0
    p_sys->pp_announces = NULL;
654
0
    p_sys->root_cat = NULL;
655
    /* TODO: create sockets here, and fix racy sockets table */
656
0
    if (vlc_clone (&p_sys->thread, Run, p_sd))
657
0
    {
658
0
        free (p_sys);
659
0
        return VLC_EGENERIC;
660
0
    }
661
662
0
    return VLC_SUCCESS;
663
0
}
664
665
/*****************************************************************************
666
 * Close:
667
 *****************************************************************************/
668
static void Close( vlc_object_t *p_this )
669
0
{
670
0
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
671
0
    services_discovery_sys_t    *p_sys  = p_sd->p_sys;
672
0
    int i;
673
674
0
    vlc_cancel (p_sys->thread);
675
0
    vlc_join (p_sys->thread, NULL);
676
677
0
    for( i = p_sys->i_fd-1 ; i >= 0 ; i-- )
678
0
    {
679
0
        net_Close( p_sys->pi_fd[i] );
680
0
    }
681
0
    FREENULL( p_sys->pi_fd );
682
683
0
    for( i = p_sys->i_announces  - 1;  i>= 0; i-- )
684
0
    {
685
0
        RemoveAnnounce( p_sd, p_sys->pp_announces[i] );
686
0
    }
687
0
    FREENULL( p_sys->pp_announces );
688
0
    tdestroy(p_sys->root_cat, DestroyCategory);
689
690
0
    free( p_sys );
691
0
}
692
693
/*****************************************************************************
694
 * Module descriptor
695
 *****************************************************************************/
696
#define SAP_ADDR_TEXT N_( "SAP multicast address" )
697
#define SAP_ADDR_LONGTEXT N_( "The SAP module normally chooses itself the " \
698
                              "right addresses to listen to. However, you " \
699
                              "can specify a specific address." )
700
#define SAP_TIMEOUT_TEXT N_( "SAP timeout (seconds)" )
701
#define SAP_TIMEOUT_LONGTEXT N_( \
702
       "Delay after which SAP items get deleted if no new announcement " \
703
       "is received." )
704
#define SAP_PARSE_TEXT N_( "Try to parse the announce" )
705
#define SAP_PARSE_LONGTEXT N_( \
706
       "This enables actual parsing of the announces by the SAP module. " \
707
       "Otherwise, all announcements are parsed by the \"live555\" " \
708
       "(RTP/RTSP) module." )
709
710
VLC_SD_PROBE_HELPER("sap", N_("Network streams (SAP)"), SD_CAT_LAN)
711
712
4
vlc_module_begin()
713
2
    set_shortname(N_("SAP"))
714
2
    set_description(N_("Network streams (SAP)") )
715
2
    set_subcategory(SUBCAT_PLAYLIST_SD)
716
717
2
    add_string("sap-addr", NULL, SAP_ADDR_TEXT, SAP_ADDR_LONGTEXT)
718
2
    add_integer("sap-timeout", 1800,
719
2
                SAP_TIMEOUT_TEXT, SAP_TIMEOUT_LONGTEXT)
720
2
    add_obsolete_bool("sap-parse") /* since 4.0.0 */
721
2
    add_obsolete_bool("sap-strict") /* since 4.0.0 */
722
723
2
    set_capability("services_discovery", 0)
724
4
    set_callbacks(Open, Close)
725
726
2
    VLC_SD_PROBE_SUBMODULE
727
2
vlc_module_end()