Coverage Report

Created: 2023-06-07 06:30

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