sap.c 47.4 KB
Newer Older
1
2
3
/*****************************************************************************
 * sap.c :  SAP interface module
 *****************************************************************************
4
 * Copyright (C) 2004-2005 the VideoLAN team
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
5
 * Copyright © 2007 Rémi Denis-Courmont
Carlo Calabrò's avatar
Carlo Calabrò committed
6
 * $Id$
7
 *
8
 * Authors: Clément Stenac <zorglub@videolan.org>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
9
 *          Rémi Denis-Courmont
zorglub's avatar
zorglub committed
10
 *
11
12
13
14
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
15
 *
16
17
18
19
20
21
22
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
dionoea's avatar
dionoea committed
23
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24
25
26
 *****************************************************************************/

/*****************************************************************************
zorglub's avatar
zorglub committed
27
 * Includes
28
 *****************************************************************************/
29
30
31
32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33
#include <vlc_common.h>
34
#include <vlc_plugin.h>
35
#include <assert.h>
36

zorglub's avatar
zorglub committed
37
#include <vlc_demux.h>
38
#include <vlc_services_discovery.h>
39

zorglub's avatar
zorglub committed
40
41
#include <vlc_network.h>
#include <vlc_charset.h>
zorglub's avatar
zorglub committed
42

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
43
44
#include <ctype.h>
#include <errno.h>
45
46
47
48
49
50
51

#ifdef HAVE_UNISTD_H
#    include <unistd.h>
#endif
#ifdef HAVE_SYS_TIME_H
#    include <sys/time.h>
#endif
52
53
54
#ifdef HAVE_POLL
# include <poll.h>
#endif
55

sigmunau's avatar
sigmunau committed
56
57
58
#ifdef HAVE_ZLIB_H
#   include <zlib.h>
#endif
59
60
61
62

#ifndef WIN32
#   include <net/if.h>
#endif
sigmunau's avatar
sigmunau committed
63

zorglub's avatar
zorglub committed
64
65
66
67
/************************************************************************
 * Macros and definitions
 ************************************************************************/

68
69
#define MAX_LINE_LENGTH 256

70
/* SAP is always on that port */
zorglub's avatar
zorglub committed
71
#define SAP_PORT 9875
72
73
74
75
76
77
78
79
/* Global-scope SAP address */
#define SAP_V4_GLOBAL_ADDRESS   "224.2.127.254"
/* Organization-local SAP address */
#define SAP_V4_ORG_ADDRESS      "239.195.255.255"
/* Local (smallest non-link-local scope) SAP address */
#define SAP_V4_LOCAL_ADDRESS    "239.255.255.255"
/* Link-local SAP address */
#define SAP_V4_LINK_ADDRESS     "224.0.0.255"
80
#define ADD_SESSION 1
81
82
83
84

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
85
86
#define SAP_ADDR_TEXT N_( "SAP multicast address" )
#define SAP_ADDR_LONGTEXT N_( "The SAP module normally chooses itself the " \
87
                              "right addresses to listen to. However, you " \
zorglub's avatar
zorglub committed
88
                              "can specify a specific address." )
89
#define SAP_IPV4_TEXT N_( "IPv4 SAP" )
90
#define SAP_IPV4_LONGTEXT N_( \
91
      "Listen to IPv4 announcements on the standard addresses." )
92
#define SAP_IPV6_TEXT N_( "IPv6 SAP" )
93
#define SAP_IPV6_LONGTEXT N_( \
94
      "Listen to IPv6 announcements on the standard addresses." )
95
#define SAP_SCOPE_TEXT N_( "IPv6 SAP scope" )
96
#define SAP_SCOPE_LONGTEXT N_( \
zorglub's avatar
zorglub committed
97
       "Scope for IPv6 announcements (default is 8)." )
98
#define SAP_TIMEOUT_TEXT N_( "SAP timeout (seconds)" )
99
#define SAP_TIMEOUT_LONGTEXT N_( \
zorglub's avatar
zorglub committed
100
       "Delay after which SAP items get deleted if no new announcement " \
101
       "is received." )
zorglub's avatar
zorglub committed
102
#define SAP_PARSE_TEXT N_( "Try to parse the announce" )
103
#define SAP_PARSE_LONGTEXT N_( \
104
       "This enables actual parsing of the announces by the SAP module. " \
Christophe Mutricy's avatar
Christophe Mutricy committed
105
       "Otherwise, all announcements are parsed by the \"live555\" " \
106
       "(RTP/RTSP) module." )
107
108
109
#define SAP_STRICT_TEXT N_( "SAP Strict mode" )
#define SAP_STRICT_LONGTEXT N_( \
       "When this is set, the SAP parser will discard some non-compliant " \
110
       "announcements." )
zorglub's avatar
zorglub committed
111
112
#define SAP_CACHE_TEXT N_("Use SAP cache")
#define SAP_CACHE_LONGTEXT N_( \
113
       "This enables a SAP caching mechanism. " \
zorglub's avatar
zorglub committed
114
       "This will result in lower SAP startup time, but you could end up " \
115
       "with items corresponding to legacy streams." )
zorglub's avatar
zorglub committed
116

zorglub's avatar
zorglub committed
117
118
119
120
121
/* Callbacks */
    static int  Open ( vlc_object_t * );
    static void Close( vlc_object_t * );
    static int  OpenDemux ( vlc_object_t * );
    static void CloseDemux ( vlc_object_t * );
122

123
124
125
126
127
vlc_module_begin ()
    set_shortname( N_("SAP"))
    set_description( N_("SAP Announcements") )
    set_category( CAT_PLAYLIST )
    set_subcategory( SUBCAT_PLAYLIST_SD )
128

129
    add_string( "sap-addr", NULL, NULL,
130
                SAP_ADDR_TEXT, SAP_ADDR_LONGTEXT, true );
gbazin's avatar
   
gbazin committed
131
    add_bool( "sap-ipv4", 1 , NULL,
132
               SAP_IPV4_TEXT,SAP_IPV4_LONGTEXT, true );
133
    add_bool( "sap-ipv6", 1 , NULL,
134
              SAP_IPV6_TEXT, SAP_IPV6_LONGTEXT, true );
gbazin's avatar
   
gbazin committed
135
    add_integer( "sap-timeout", 1800, NULL,
136
                 SAP_TIMEOUT_TEXT, SAP_TIMEOUT_LONGTEXT, true );
137
    add_bool( "sap-parse", 1 , NULL,
138
               SAP_PARSE_TEXT,SAP_PARSE_LONGTEXT, true );
139
    add_bool( "sap-strict", 0 , NULL,
140
               SAP_STRICT_TEXT,SAP_STRICT_LONGTEXT, true );
141
#if 0
zorglub's avatar
zorglub committed
142
    add_bool( "sap-cache", 0 , NULL,
143
               SAP_CACHE_TEXT,SAP_CACHE_LONGTEXT, true );
144
#endif
145
    add_obsolete_bool( "sap-timeshift" ) /* Redumdant since 1.0.0 */
146

147
148
    set_capability( "services_discovery", 0 )
    set_callbacks( Open, Close )
zorglub's avatar
zorglub committed
149

150
151
152
153
154
155
    add_submodule ()
        set_description( N_("SDP Descriptions parser") )
        add_shortcut( "sdp" )
        set_capability( "demux", 51 )
        set_callbacks( OpenDemux, CloseDemux )
vlc_module_end ()
156

zorglub's avatar
zorglub committed
157

158
/*****************************************************************************
zorglub's avatar
zorglub committed
159
 * Local structures
160
161
 *****************************************************************************/

zorglub's avatar
zorglub committed
162
163
164
typedef struct sdp_t sdp_t;
typedef struct attribute_t attribute_t;
typedef struct sap_announce_t sap_announce_t;
165

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
166
167
168
169
170
171
172
173
174
175
176
177
178

struct sdp_media_t
{
    struct sdp_t           *parent;
    char                   *fmt;
    struct sockaddr_storage addr;
    socklen_t               addrlen;
    unsigned                n_addr;
    int           i_attributes;
    attribute_t  **pp_attributes;
};


zorglub's avatar
zorglub committed
179
180
181
/* The structure that contains sdp information */
struct  sdp_t
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
182
    const char *psz_sdp;
183

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
184
185
186
187
188
189
190
    /* o field */
    char     username[64];
    uint64_t session_id;
    uint64_t session_version;
    unsigned orig_ip_version;
    char     orig_host[1024];

zorglub's avatar
zorglub committed
191
192
    /* s= field */
    char *psz_sessionname;
193

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
194
    /* old cruft */
zorglub's avatar
zorglub committed
195
196
    /* "computed" URI */
    char *psz_uri;
197
    int           i_media_type;
198
    unsigned rtcp_port;
199

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
200
    /* a= global attributes */
zorglub's avatar
zorglub committed
201
202
    int           i_attributes;
    attribute_t  **pp_attributes;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
203
204
205

    /* medias (well, we only support one atm) */
    unsigned            mediac;
206
    struct sdp_media_t *mediav;
207
};
zorglub's avatar
zorglub committed
208

zorglub's avatar
zorglub committed
209
struct attribute_t
210
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
211
    const char *value;
212
    char name[];
213
};
zorglub's avatar
zorglub committed
214

215
216
struct sap_announce_t
{
zorglub's avatar
zorglub committed
217
    mtime_t i_last;
218
219
    mtime_t i_period;
    uint8_t i_period_trust;
zorglub's avatar
zorglub committed
220
221
222
223
224
225
226

    uint16_t    i_hash;
    uint32_t    i_source[4];

    /* SAP annnounces must only contain one SDP */
    sdp_t       *p_sdp;

227
    input_item_t * p_item;
228
229
};

zorglub's avatar
zorglub committed
230
struct services_discovery_sys_t
231
{
232
233
    vlc_thread_t thread;

zorglub's avatar
zorglub committed
234
235
236
    /* Socket descriptors */
    int i_fd;
    int *pi_fd;
237

238
239
240
    /* Table of announces */
    int i_announces;
    struct sap_announce_t **pp_announces;
241

zorglub's avatar
zorglub committed
242
    /* Modes */
243
244
    bool  b_strict;
    bool  b_parse;
zorglub's avatar
zorglub committed
245

246
    int i_timeout;
247
};
248

249
250
251
252
253
struct demux_sys_t
{
    sdp_t *p_sdp;
};

zorglub's avatar
zorglub committed
254
255
256
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
257

sigmunau's avatar
sigmunau committed
258

zorglub's avatar
zorglub committed
259
260
261
/* Main functions */
    static int Demux( demux_t *p_demux );
    static int Control( demux_t *, int, va_list );
262
    static void *Run  ( void *p_sd );
sigmunau's avatar
sigmunau committed
263

zorglub's avatar
zorglub committed
264
265
/* Main parsing functions */
    static int ParseConnection( vlc_object_t *p_obj, sdp_t *p_sdp );
266
    static int ParseSAP( services_discovery_t *p_sd, const uint8_t *p_buffer, size_t i_read );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
267
    static sdp_t *ParseSDP (vlc_object_t *p_sd, const char *psz_sdp);
zorglub's avatar
zorglub committed
268
269
    static sap_announce_t *CreateAnnounce( services_discovery_t *, uint16_t, sdp_t * );
    static int RemoveAnnounce( services_discovery_t *p_sd, sap_announce_t *p_announce );
sigmunau's avatar
sigmunau committed
270

zorglub's avatar
zorglub committed
271
/* Helper functions */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
272
273
274
    static inline attribute_t *MakeAttribute (const char *str);
    static const char *GetAttribute (attribute_t **tab, unsigned n, const char *name);
    static inline void FreeAttribute (attribute_t *a);
275
276
    static const char *FindAttribute (const sdp_t *sdp, unsigned media,
                                      const char *name);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
277

278
    static bool IsSameSession( sdp_t *p_sdp1, sdp_t *p_sdp2 );
279
    static int InitSocket( services_discovery_t *p_sd, const char *psz_address, int i_port );
280
    static int Decompress( const unsigned char *psz_src, unsigned char **_dst, int i_len );
Christophe Massiot's avatar
Christophe Massiot committed
281
    static void FreeSDP( sdp_t *p_sdp );
sigmunau's avatar
sigmunau committed
282

283
static inline int min_int( int a, int b )
284
285
286
287
{
    return a > b ? b : a;
}

288
289
290
291
292
/*****************************************************************************
 * Open: initialize and create stuff
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
zorglub's avatar
zorglub committed
293
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
zorglub's avatar
zorglub committed
294
295
    services_discovery_sys_t *p_sys  = (services_discovery_sys_t *)
                                malloc( sizeof( services_discovery_sys_t ) );
296
297
    if( !p_sys )
        return VLC_ENOMEM;
298

299
    p_sys->i_timeout = var_CreateGetInteger( p_sd, "sap-timeout" );
300

zorglub's avatar
zorglub committed
301
    p_sd->p_sys  = p_sys;
zorglub's avatar
zorglub committed
302
303
304
305

    p_sys->pi_fd = NULL;
    p_sys->i_fd = 0;

306
307
    p_sys->b_strict = var_CreateGetInteger( p_sd, "sap-strict");
    p_sys->b_parse = var_CreateGetInteger( p_sd, "sap-parse" );
zorglub's avatar
zorglub committed
308

309
#if 0
310
    if( var_CreateGetInteger( p_sd, "sap-cache" ) )
zorglub's avatar
zorglub committed
311
    {
zorglub's avatar
zorglub committed
312
        CacheLoad( p_sd );
313
    }
314
#endif
315

316
317
    p_sys->i_announces = 0;
    p_sys->pp_announces = NULL;
318
319
320
321
322
323
    /* TODO: create sockets here, and fix racy sockets table */
    if (vlc_clone (&p_sys->thread, Run, p_sd, VLC_THREAD_PRIORITY_LOW))
    {
        free (p_sys);
        return VLC_EGENERIC;
    }
324

zorglub's avatar
zorglub committed
325
326
327
328
329
330
331
332
333
    return VLC_SUCCESS;
}

/*****************************************************************************
 * OpenDemux: initialize and create stuff
 *****************************************************************************/
static int OpenDemux( vlc_object_t *p_this )
{
    demux_t *p_demux = (demux_t *)p_this;
334
    const uint8_t *p_peek;
335
    char *psz_sdp = NULL;
336
    sdp_t *p_sdp = NULL;
337
    int errval = VLC_EGENERIC;
Laurent Aimar's avatar
Laurent Aimar committed
338
    size_t i_len;
zorglub's avatar
zorglub committed
339

340
341
342
343
344
345
    if( !var_CreateGetInteger( p_demux, "sap-parse" ) )
    {
        /* We want livedotcom module to parse this SDP file */
        return VLC_EGENERIC;
    }

346
    assert( p_demux->s ); /* this is NOT an access_demux */
347

348
349
350
    /* Probe for SDP */
    if( stream_Peek( p_demux->s, &p_peek, 7 ) < 7 )
        return VLC_EGENERIC;
zorglub's avatar
zorglub committed
351

352
353
    if( memcmp( p_peek, "v=0\r\no=", 7 ) && memcmp( p_peek, "v=0\no=", 6 ) )
        return VLC_EGENERIC;
354

355
    /* Gather the complete sdp file */
Laurent Aimar's avatar
Laurent Aimar committed
356
    for( i_len = 0, psz_sdp = NULL; i_len < 65536; )
357
    {
Laurent Aimar's avatar
Laurent Aimar committed
358
359
360
        const int i_read_max = 1024;
        char *psz_sdp_new = realloc( psz_sdp, i_len + i_read_max );
        size_t i_read;
361
        if( psz_sdp_new == NULL )
362
        {
363
            errval = VLC_ENOMEM;
364
365
            goto error;
        }
366
        psz_sdp = psz_sdp_new;
367

Laurent Aimar's avatar
Laurent Aimar committed
368
        i_read = stream_Read( p_demux->s, &psz_sdp[i_len], i_read_max );
369
        if( (int)i_read < 0 )
370
        {
371
372
            msg_Err( p_demux, "cannot read SDP" );
            goto error;
373
        }
Laurent Aimar's avatar
Laurent Aimar committed
374
        i_len += i_read;
375

Laurent Aimar's avatar
Laurent Aimar committed
376
        psz_sdp[i_len] = '\0';
377

378
        if( (int)i_read < i_read_max )
379
            break; // EOF
380
    }
381

382
    p_sdp = ParseSDP( VLC_OBJECT(p_demux), psz_sdp );
383

384
385
386
387
388
    if( !p_sdp )
    {
        msg_Warn( p_demux, "invalid SDP");
        goto error;
    }
389

390
391
392
393
    if( ParseConnection( VLC_OBJECT( p_demux ), p_sdp ) )
    {
        p_sdp->psz_uri = NULL;
    }
394
395
396
397
398
399
400
401
402
403
404
405
406
    switch (p_sdp->i_media_type)
    {   /* Should be in sync with modules/demux/rtp.c */
        case  0: /* PCMU/8000 */
        case  8: /* PCMA/8000 */
        case 10: /* L16/44100/2 */
        case 11: /* L16/44100 */
        case 14: /* MPA/90000 */
        case 32: /* MPV/90000 */
        case 33: /* MP2/90000 */
            break;
        default:
            goto error;
    }
407
408
409
410
    if( p_sdp->psz_uri == NULL ) goto error;

    p_demux->p_sys = (demux_sys_t *)malloc( sizeof(demux_sys_t) );
    p_demux->p_sys->p_sdp = p_sdp;
zorglub's avatar
zorglub committed
411
412
    p_demux->pf_control = Control;
    p_demux->pf_demux = Demux;
413

zorglub's avatar
zorglub committed
414
    FREENULL( psz_sdp );
415
    return VLC_SUCCESS;
416

417
error:
zorglub's avatar
zorglub committed
418
    FREENULL( psz_sdp );
419
    if( p_sdp ) FreeSDP( p_sdp ); p_sdp = NULL;
420
    stream_Seek( p_demux->s, 0 );
421
    return errval;
422
423
}

424
425
426
427
/*****************************************************************************
 * Close:
 *****************************************************************************/
static void Close( vlc_object_t *p_this )
428
{
zorglub's avatar
zorglub committed
429
430
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
    services_discovery_sys_t    *p_sys  = p_sd->p_sys;
zorglub's avatar
free    
zorglub committed
431
    int i;
432

433
434
435
    vlc_cancel (p_sys->thread);
    vlc_join (p_sys->thread, NULL);

zorglub's avatar
zorglub committed
436
    for( i = p_sys->i_fd-1 ; i >= 0 ; i-- )
437
    {
zorglub's avatar
zorglub committed
438
        net_Close( p_sys->pi_fd[i] );
439
    }
zorglub's avatar
zorglub committed
440
    FREENULL( p_sys->pi_fd );
zorglub's avatar
zorglub committed
441

442
#if 0
zorglub's avatar
zorglub committed
443
    if( config_GetInt( p_sd, "sap-cache" ) )
444
    {
zorglub's avatar
zorglub committed
445
        CacheSave( p_sd );
446
    }
447
#endif
448

zorglub's avatar
zorglub committed
449
    for( i = p_sys->i_announces  - 1;  i>= 0; i-- )
zorglub's avatar
free    
zorglub committed
450
    {
zorglub's avatar
zorglub committed
451
        RemoveAnnounce( p_sd, p_sys->pp_announces[i] );
zorglub's avatar
free    
zorglub committed
452
    }
zorglub's avatar
zorglub committed
453
    FREENULL( p_sys->pp_announces );
zorglub's avatar
free    
zorglub committed
454

455
456
    free( p_sys );
}
457

458
/*****************************************************************************
zorglub's avatar
zorglub committed
459
460
461
462
 * CloseDemux: Close the demuxer
 *****************************************************************************/
static void CloseDemux( vlc_object_t *p_this )
{
463
464
465
    demux_t *p_demux = (demux_t *)p_this;
    if( p_demux->p_sys )
    {
466
        if( p_demux->p_sys->p_sdp ) { FreeSDP( p_demux->p_sys->p_sdp ); p_demux->p_sys->p_sdp = NULL; }
467
468
        free( p_demux->p_sys );
    }
zorglub's avatar
zorglub committed
469
470
471
472
}

/*****************************************************************************
 * Run: main SAP thread
473
474
475
 *****************************************************************************
 * Listens to SAP packets, and sends them to packet_handle
 *****************************************************************************/
sigmunau's avatar
sigmunau committed
476
#define MAX_SAP_BUFFER 5000
477

478
static void *Run( void *data )
479
{
480
    services_discovery_t *p_sd = data;
481
    char *psz_addr;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
482
    int i;
483
    int timeout = -1;
484
    int canc = vlc_savecancel ();
485

486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
    /* Braindead Winsock DNS resolver will get stuck over 2 seconds per failed
     * DNS queries, even if the DNS server returns an error with milliseconds.
     * You don't want to know why the bug (as of XP SP2) wasn't fixed since
     * Winsock 1.1 from Windows 95, if not Windows 3.1.
     * Anyway, to avoid a 30 seconds delay for failed IPv6 socket creation,
     * we have to open sockets in Run() rather than Open(). */
    if( var_CreateGetInteger( p_sd, "sap-ipv4" ) )
    {
        InitSocket( p_sd, SAP_V4_GLOBAL_ADDRESS, SAP_PORT );
        InitSocket( p_sd, SAP_V4_ORG_ADDRESS, SAP_PORT );
        InitSocket( p_sd, SAP_V4_LOCAL_ADDRESS, SAP_PORT );
        InitSocket( p_sd, SAP_V4_LINK_ADDRESS, SAP_PORT );
    }
    if( var_CreateGetInteger( p_sd, "sap-ipv6" ) )
    {
501
502
        char psz_address[NI_MAXNUMERICHOST] = "ff02::2:7ffe%";

503
#ifndef WIN32
504
505
506
507
508
509
510
511
512
513
514
        struct if_nameindex *l = if_nameindex ();
        if (l != NULL)
        {
            char *ptr = strchr (psz_address, '%') + 1;
            for (unsigned i = 0; l[i].if_index; i++)
            {
                strcpy (ptr, l[i].if_name);
                InitSocket (p_sd, psz_address, SAP_PORT);
            }
            if_freenameindex (l);
        }
515
516
517
518
519
520
521
522
523
#else
        /* this is the Winsock2 equivalant of SIOCGIFCONF on BSD stacks,
           which if_nameindex uses internally anyway */

        // first create a dummy socket to pin down the protocol family
        SOCKET s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
        if( s != INVALID_SOCKET )
        {
            INTERFACE_INFO ifaces[10]; // Assume there will be no more than 10 IP interfaces
524
            size_t len = sizeof(ifaces);
525

526
527
528
529
530
531
532
533
534
535
536
537
538
            if( SOCKET_ERROR != WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0, &ifaces, len, &len, NULL, NULL) )
            {
                unsigned ifcount = len/sizeof(INTERFACE_INFO);
                char *ptr = strchr (psz_address, '%') + 1;
                for(unsigned i = 1; i<=ifcount; ++i )
                {
                    // append link-local zone identifier
                    sprintf(ptr, "%d", i);
                }
            }
            closesocket(s);
        }
#endif
539
        *strchr (psz_address, '%') = '\0';
540

541
542
        static const char ipv6_scopes[] = "1456789ABCDE";
        for (const char *c_scope = ipv6_scopes; *c_scope; c_scope++)
543
        {
544
            psz_address[3] = *c_scope;
545
546
547
548
            InitSocket( p_sd, psz_address, SAP_PORT );
        }
    }

549
550
551
    psz_addr = var_CreateGetString( p_sd, "sap-addr" );
    if( psz_addr && *psz_addr )
        InitSocket( p_sd, psz_addr, SAP_PORT );
552
    free( psz_addr );
553

554
555
556
    if( p_sd->p_sys->i_fd == 0 )
    {
        msg_Err( p_sd, "unable to listen on any address" );
557
        return NULL;
558
559
    }

560
    /* read SAP packets */
561
    for (;;)
562
    {
563
        vlc_restorecancel (canc);
564
        unsigned n = p_sd->p_sys->i_fd;
565
        struct pollfd ufd[n];
zorglub's avatar
zorglub committed
566

567
568
569
570
571
572
573
        for (unsigned i = 0; i < n; i++)
        {
            ufd[i].fd = p_sd->p_sys->pi_fd[i];
            ufd[i].events = POLLIN;
            ufd[i].revents = 0;
        }

574
575
576
        int val = poll (ufd, n, timeout);
        canc = vlc_savecancel ();
        if (val > 0)
577
578
579
580
581
582
583
584
585
        {
            for (unsigned i = 0; i < n; i++)
            {
                if (ufd[i].revents)
                {
                    uint8_t p_buffer[MAX_SAP_BUFFER+1];
                    ssize_t i_read;

                    i_read = net_Read (p_sd, ufd[i].fd, NULL, p_buffer,
586
                                       MAX_SAP_BUFFER, false);
587
588
589
590
591
592
593
594
595
596
597
598
599
                    if (i_read < 0)
                        msg_Warn (p_sd, "receive error: %m");
                    if (i_read > 6)
                    {
                        /* Parse the packet */
                        p_buffer[i_read] = '\0';
                        ParseSAP (p_sd, p_buffer, i_read);
                    }
                }
            }
        }

        mtime_t now = mdate();
600

601
602
603
604
        /* A 1 hour timeout correspong to the RFC Implicit timeout.
         * This timeout is tuned in the following loop. */
        timeout = 1000 * 60 * 60;

zorglub's avatar
zorglub committed
605
        /* Check for items that need deletion */
606
        for( i = 0; i < p_sd->p_sys->i_announces; i++ )
zorglub's avatar
zorglub committed
607
        {
608
            mtime_t i_timeout = ( mtime_t ) 1000000 * p_sd->p_sys->i_timeout;
609
            sap_announce_t * p_announce = p_sd->p_sys->pp_announces[i];
610
611
            mtime_t i_last_period = now - p_announce->i_last;

612
613
614
615
616
            /* Remove the annoucement, if the last announcement was 1 hour ago
             * or if the last packet emitted was 3 times the average time
             * between two packets */
            if( ( p_announce->i_period_trust > 5 && i_last_period > 3 * p_announce->i_period ) ||
                i_last_period > i_timeout )
617
            {
618
                RemoveAnnounce( p_sd, p_announce );
619
            }
620
621
622
623
            else
            {
                /* Compute next timeout */
                if( p_announce->i_period_trust > 5 )
624
625
                    timeout = min_int((3 * p_announce->i_period - i_last_period) / 1000, timeout);
                timeout = min_int((i_timeout - i_last_period)/1000, timeout);
626
            }
zorglub's avatar
zorglub committed
627
        }
628
629
630
631
632

        if( !p_sd->p_sys->i_announces )
            timeout = -1; /* We can safely poll indefinitly. */
        else if( timeout < 200 )
            timeout = 200; /* Don't wakeup too fast. */
zorglub's avatar
zorglub committed
633
    }
634
    assert (0);
zorglub's avatar
zorglub committed
635
636
637
638
639
640
641
642
}

/**********************************************************************
 * Demux: reads and demuxes data packets
 * Return -1 if error, 0 if EOF, 1 else
 **********************************************************************/
static int Demux( demux_t *p_demux )
{
643
    sdp_t *p_sdp = p_demux->p_sys->p_sdp;
644
645
    input_thread_t *p_input;
    input_item_t *p_parent_input;
zorglub's avatar
zorglub committed
646

647
    p_input = (input_thread_t *)vlc_object_find( p_demux, VLC_OBJECT_INPUT,
648
649
                                                 FIND_PARENT );
    assert( p_input );
650
651
652
653
654
655
    if( !p_input )
    {
        msg_Err( p_demux, "parent input could not be found" );
        return VLC_EGENERIC;
    }

656
    /* This item hasn't been held by input_GetItem
657
     * don't release it */
658
659
660
661
    p_parent_input = input_GetItem( p_input );

    input_item_SetURI( p_parent_input, p_sdp->psz_uri );
    input_item_SetName( p_parent_input, p_sdp->psz_sessionname );
662
663
664
665
666
667
668
669
670
    if( p_sdp->rtcp_port )
    {
        char *rtcp;
        if( asprintf( &rtcp, ":rtcp-port=%u", p_sdp->rtcp_port ) != -1 )
        {
            input_item_AddOption( p_parent_input, rtcp );
            free( rtcp );
        }
    }
zorglub's avatar
zorglub committed
671

672
    vlc_mutex_lock( &p_parent_input->lock );
673

674
    p_parent_input->i_type = ITEM_TYPE_NET;
zorglub's avatar
zorglub committed
675

676
    vlc_mutex_unlock( &p_parent_input->lock );
677
    return VLC_SUCCESS;
678
679
}

zorglub's avatar
zorglub committed
680
681
static int Control( demux_t *p_demux, int i_query, va_list args )
{
682
    VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);
zorglub's avatar
zorglub committed
683
684
685
686
687
688
    return VLC_EGENERIC;
}

/**************************************************************
 * Local functions
 **************************************************************/
689

zorglub's avatar
zorglub committed
690
/* i_read is at least > 6 */
691
692
static int ParseSAP( services_discovery_t *p_sd, const uint8_t *buf,
                     size_t len )
693
{
694
695
696
    int i;
    const char          *psz_sdp;
    const uint8_t *end = buf + len;
zorglub's avatar
zorglub committed
697
698
    sdp_t               *p_sdp;

699
    assert (buf[len] == '\0');
700

701
702
    if (len < 4)
        return VLC_EGENERIC;
703

704
705
706
707
    uint8_t flags = buf[0];

    /* First, check the sap announce is correct */
    if ((flags >> 5) != 1)
zorglub's avatar
zorglub committed
708
709
        return VLC_EGENERIC;

710
711
    bool b_ipv6 = (flags & 0x10) != 0;
    bool b_need_delete = (flags & 0x04) != 0;
712

713
    if (flags & 0x02)
zorglub's avatar
zorglub committed
714
    {
zorglub's avatar
zorglub committed
715
        msg_Dbg( p_sd, "encrypted packet, unsupported" );
zorglub's avatar
zorglub committed
716
717
        return VLC_EGENERIC;
    }
718

719
    bool b_compressed = (flags & 0x01) != 0;
720

721
    uint16_t i_hash = U16_AT (buf + 2);
722

zorglub's avatar
zorglub committed
723
    if( p_sd->p_sys->b_strict && i_hash == 0 )
724
    {
zorglub's avatar
zorglub committed
725
        msg_Dbg( p_sd, "strict mode, discarding announce with null id hash");
zorglub's avatar
zorglub committed
726
727
728
        return VLC_EGENERIC;
    }

729
730
731
732
    // Skips source address and auth data
    buf += 4 + (b_ipv6 ? 16 : 4) + buf[1];
    if (buf > end)
        return VLC_EGENERIC;
zorglub's avatar
zorglub committed
733

734
    uint8_t *decomp = NULL;
735
    if( b_compressed )
zorglub's avatar
zorglub committed
736
    {
737
738
        int newsize = Decompress (buf, &decomp, end - buf);
        if (newsize < 0)
zorglub's avatar
zorglub committed
739
        {
740
            msg_Dbg( p_sd, "decompression of SAP packet failed" );
zorglub's avatar
zorglub committed
741
742
            return VLC_EGENERIC;
        }
zorglub's avatar
zorglub committed
743

744
        decomp = realloc (decomp, newsize + 1);
745
        decomp[newsize] = '\0';
746
747
        psz_sdp = (const char *)decomp;
        len = newsize;
748
    }
749
    else
zorglub's avatar
zorglub committed
750
    {
751
752
        psz_sdp = (const char *)buf;
        len = end - buf;
zorglub's avatar
zorglub committed
753
    }
754

755
756
    /* len is a strlen here here. both buf and decomp are len+1 where the 1 should be a \0 */
    assert( psz_sdp[len] == '\0');
757

zorglub's avatar
zorglub committed
758
    /* Skip payload type */
759
760
    /* SAPv1 has implicit "application/sdp" payload type: first line is v=0 */
    if (strncmp (psz_sdp, "v=0", 3))
761
    {
762
763
764
        size_t clen = strlen (psz_sdp) + 1;

        if (strcmp (psz_sdp, "application/sdp"))
zorglub's avatar
zorglub committed
765
        {
766
767
            msg_Dbg (p_sd, "unsupported content type: %s", psz_sdp);
            return VLC_EGENERIC;
zorglub's avatar
zorglub committed
768
        }
769

770
771
772
773
774
775
        // skips content type
        if (len <= clen)
            return VLC_EGENERIC;

        len -= clen;
        psz_sdp += clen;
776
    }
777

zorglub's avatar
zorglub committed
778
    /* Parse SDP info */
zorglub's avatar
zorglub committed
779
    p_sdp = ParseSDP( VLC_OBJECT(p_sd), psz_sdp );
780

zorglub's avatar
zorglub committed
781
782
783
    if( p_sdp == NULL )
        return VLC_EGENERIC;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
784
785
    p_sdp->psz_sdp = psz_sdp;

zorglub's avatar
zorglub committed
786
    /* Decide whether we should add a playlist item for this SDP */
zorglub's avatar
zorglub committed
787
788
789
    /* Parse connection information (c= & m= ) */
    if( ParseConnection( VLC_OBJECT(p_sd), p_sdp ) )
        p_sdp->psz_uri = NULL;
zorglub's avatar
zorglub committed
790
791

    /* Multi-media or no-parse -> pass to LIVE.COM */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
792
793
794
    if( ( p_sdp->i_media_type != 14
       && p_sdp->i_media_type != 32
       && p_sdp->i_media_type != 33)
795
     || p_sd->p_sys->b_parse == false )
zorglub's avatar
zorglub committed
796
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
797
798
799
        free( p_sdp->psz_uri );
        if (asprintf( &p_sdp->psz_uri, "sdp://%s", p_sdp->psz_sdp ) == -1)
            p_sdp->psz_uri = NULL;
zorglub's avatar
zorglub committed
800
801
    }

802
803
804
805
806
    if( p_sdp->psz_uri == NULL )
    {
        FreeSDP( p_sdp );
        return VLC_EGENERIC;
    }
zorglub's avatar
zorglub committed
807

zorglub's avatar
zorglub committed
808
    for( i = 0 ; i< p_sd->p_sys->i_announces ; i++ )
zorglub's avatar
zorglub committed
809
    {
810
        sap_announce_t * p_announce = p_sd->p_sys->pp_announces[i];
zorglub's avatar
zorglub committed
811
812
        /* FIXME: slow */
        /* FIXME: we create a new announce each time the sdp changes */
813
        if( IsSameSession( p_announce->p_sdp, p_sdp ) )
814
        {
815
816
817
818
819
820
821
822
823
824
            /* We don't support delete announcement as they can easily
             * Be used to highjack an announcement by a third party.
             * Intead we cleverly implement Implicit Announcement removal.
             *
             * if( b_need_delete )
             *    RemoveAnnounce( p_sd, p_sd->p_sys->pp_announces[i]);
             * else
             */

            if( !b_need_delete )
825
            {
826
827
828
829
830
831
                /* No need to go after six, as we start to trust the
                 * average period at six */
                if( p_announce->i_period_trust <= 5 )
                    p_announce->i_period_trust++;

                /* Compute the average period */
ivoire's avatar
ivoire committed
832
833
834
                mtime_t now = mdate();
                p_announce->i_period = (p_announce->i_period + (now - p_announce->i_last)) / 2;
                p_announce->i_last = now;
835
            }
836
            FreeSDP( p_sdp ); p_sdp = NULL;
837
            return VLC_SUCCESS;
838
        }
zorglub's avatar
zorglub committed
839
    }
zorglub's avatar
zorglub committed
840

zorglub's avatar
zorglub committed
841
    CreateAnnounce( p_sd, i_hash, p_sdp );
zorglub's avatar
zorglub committed
842

843
    FREENULL (decomp);
zorglub's avatar
zorglub committed
844
845
846
    return VLC_SUCCESS;
}

zorglub's avatar
zorglub committed
847
sap_announce_t *CreateAnnounce( services_discovery_t *p_sd, uint16_t i_hash,
zorglub's avatar
zorglub committed
848
849
                                sdp_t *p_sdp )
{
850
    input_item_t *p_input;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
851
    const char *psz_value;
zorglub's avatar
zorglub committed
852
853
    sap_announce_t *p_sap = (sap_announce_t *)malloc(
                                        sizeof(sap_announce_t ) );
854
    services_discovery_sys_t *p_sys;
855
    if( p_sap == NULL )
zorglub's avatar
zorglub committed
856
        return NULL;
857

858
859
    p_sys = p_sd->p_sys;

zorglub's avatar
zorglub committed
860
    p_sap->i_last = mdate();
861
862
    p_sap->i_period = 0;
    p_sap->i_period_trust = 0;
zorglub's avatar
zorglub committed
863
864
    p_sap->i_hash = i_hash;
    p_sap->p_sdp = p_sdp;
865

866
    /* Released in RemoveAnnounce */
867
    p_input = input_item_NewWithType( VLC_OBJECT(p_sd),
868
869
870
                                     p_sap->p_sdp->psz_uri,
                                     p_sdp->psz_sessionname,
                                     0, NULL, -1, ITEM_TYPE_NET );
871
    p_sap->p_item = p_input;
872
    if( !p_input )
zorglub's avatar
zorglub committed
873
    {
874
        free( p_sap );
zorglub's avatar
zorglub committed
875
        return NULL;
876
877
    }

878
879
880
881
882
883
884
885
886
887
    if( p_sdp->rtcp_port )
    {
        char *rtcp;
        if( asprintf( &rtcp, ":rtcp-port=%u", p_sdp->rtcp_port ) != -1 )
        {
            input_item_AddOption( p_input, rtcp );
            free( rtcp );
        }
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
888
    psz_value = GetAttribute( p_sap->p_sdp->pp_attributes, p_sap->p_sdp->i_attributes, "tool" );
zorglub's avatar
zorglub committed
889
890
    if( psz_value != NULL )
    {
891
        input_item_AddInfo( p_input, _("Session"), _("Tool"), "%s", psz_value );
zorglub's avatar
zorglub committed
892
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
893
    if( strcmp( p_sdp->username, "-" ) )
zorglub's avatar
zorglub committed
894
    {
895
        input_item_AddInfo( p_input, _("Session"), _("User"), "%s",
896
                           p_sdp->username );
zorglub's avatar
zorglub committed
897
898
    }

899
    /* Handle group */
900
901
902
903
    if (p_sap->p_sdp->mediac >= 1)
        psz_value = FindAttribute (p_sap->p_sdp, 0, "x-plgroup");
    else
        psz_value = GetAttribute( p_sap->p_sdp->pp_attributes, p_sap->p_sdp->i_attributes, "x-plgroup" );
Arnaud Schauly's avatar