sap.c 47.5 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
Clément Stenac's avatar
Clément Stenac 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
Antoine Cellerier's avatar
Antoine Cellerier committed
23
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 25 26
 *****************************************************************************/

/*****************************************************************************
Clément Stenac's avatar
Clément Stenac 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

Clément Stenac's avatar
Clément Stenac committed
37
#include <vlc_demux.h>
38
#include <vlc_services_discovery.h>
39

Clément Stenac's avatar
Clément Stenac committed
40 41
#include <vlc_network.h>
#include <vlc_charset.h>
Clément Stenac's avatar
Clément Stenac 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

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

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

Clément Stenac's avatar
Clément Stenac committed
64 65 66 67
/************************************************************************
 * Macros and definitions
 ************************************************************************/

68 69
#define MAX_LINE_LENGTH 256

70
/* SAP is always on that port */
Clément Stenac's avatar
Clément Stenac 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 " \
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_( \
97
       "Scope for IPv6 announcements (default is 8)." )
98
#define SAP_TIMEOUT_TEXT N_( "SAP timeout (seconds)" )
99
#define SAP_TIMEOUT_LONGTEXT N_( \
100
       "Delay after which SAP items get deleted if no new announcement " \
101
       "is received." )
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." )
Clément Stenac's avatar
Clément Stenac committed
111 112
#define SAP_CACHE_TEXT N_("Use SAP cache")
#define SAP_CACHE_LONGTEXT N_( \
113
       "This enables a SAP caching mechanism. " \
Clément Stenac's avatar
Clément Stenac committed
114
       "This will result in lower SAP startup time, but you could end up " \
115
       "with items corresponding to legacy streams." )
116

Clément Stenac's avatar
Clément Stenac 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 );
Gildas Bazin's avatar
 
Gildas Bazin 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 );
Gildas Bazin's avatar
 
Gildas Bazin 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
Clément Stenac's avatar
Clément Stenac 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 )
Clément Stenac's avatar
Clément Stenac 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

Clément Stenac's avatar
Clément Stenac committed
157

158
/*****************************************************************************
Clément Stenac's avatar
Clément Stenac committed
159
 * Local structures
160 161
 *****************************************************************************/

Clément Stenac's avatar
Clément Stenac 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;
};


Clément Stenac's avatar
Clément Stenac 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];

Clément Stenac's avatar
Clément Stenac committed
191 192
    /* s= field */
    char *psz_sessionname;
193

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
194
    /* old cruft */
Clément Stenac's avatar
Clément Stenac 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 */
Clément Stenac's avatar
Clément Stenac 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
};
208

Clément Stenac's avatar
Clément Stenac 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
};
214

215 216
struct sap_announce_t
{
Clément Stenac's avatar
Clément Stenac committed
217
    mtime_t i_last;
218 219
    mtime_t i_period;
    uint8_t i_period_trust;
Clément Stenac's avatar
Clément Stenac 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
};

230
struct services_discovery_sys_t
231
{
232 233
    vlc_thread_t thread;

Clément Stenac's avatar
Clément Stenac 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

Clément Stenac's avatar
Clément Stenac committed
242
    /* Modes */
243 244
    bool  b_strict;
    bool  b_parse;
Clément Stenac's avatar
Clément Stenac committed
245

246
    int i_timeout;
247
};
248

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

Clément Stenac's avatar
Clément Stenac committed
254 255 256
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
257

Sigmund Augdal Helberg's avatar
Sigmund Augdal Helberg committed
258

Clément Stenac's avatar
Clément Stenac 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 );
Sigmund Augdal Helberg's avatar
Sigmund Augdal Helberg committed
263

Clément Stenac's avatar
Clément Stenac 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);
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 );
Sigmund Augdal Helberg's avatar
Sigmund Augdal Helberg committed
270

Clément Stenac's avatar
Clément Stenac 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 );
Sigmund Augdal Helberg's avatar
Sigmund Augdal Helberg 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 )
{
293
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
Clément Stenac's avatar
Clément Stenac 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

301
    p_sd->p_sys  = p_sys;
Clément Stenac's avatar
Clément Stenac 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" );
Clément Stenac's avatar
Clément Stenac committed
308

309
#if 0
310
    if( var_CreateGetInteger( p_sd, "sap-cache" ) )
Clément Stenac's avatar
Clément Stenac committed
311
    {
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

Clément Stenac's avatar
Clément Stenac 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;
Clément Stenac's avatar
Clément Stenac 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;
Clément Stenac's avatar
Clément Stenac 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;
Clément Stenac's avatar
Clément Stenac committed
411 412
    p_demux->pf_control = Control;
    p_demux->pf_demux = Demux;
413

414
    FREENULL( psz_sdp );
415
    return VLC_SUCCESS;
416

417
error:
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
{
429 430
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
    services_discovery_sys_t    *p_sys  = p_sd->p_sys;
Clément Stenac's avatar
free  
Clément Stenac committed
431
    int i;
432

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

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

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

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

455 456
    free( p_sys );
}
457

458
/*****************************************************************************
Clément Stenac's avatar
Clément Stenac 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 );
    }
Clément Stenac's avatar
Clément Stenac committed
469 470 471 472
}

/*****************************************************************************
 * Run: main SAP thread
473 474 475
 *****************************************************************************
 * Listens to SAP packets, and sends them to packet_handle
 *****************************************************************************/
Sigmund Augdal Helberg's avatar
Sigmund Augdal Helberg 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];
Clément Stenac's avatar
Clément Stenac 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;

Clément Stenac's avatar
Clément Stenac committed
605
        /* Check for items that need deletion */
606
        for( i = 0; i < p_sd->p_sys->i_announces; i++ )
Clément Stenac's avatar
Clément Stenac 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
            }
Clément Stenac's avatar
Clément Stenac 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. */
Clément Stenac's avatar
Clément Stenac committed
633
    }
634
    assert (0);
Clément Stenac's avatar
Clément Stenac 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;
Clément Stenac's avatar
Clément Stenac 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
    if( p_sdp->rtcp_port )
    {
        char *rtcp;
        if( asprintf( &rtcp, ":rtcp-port=%u", p_sdp->rtcp_port ) != -1 )
        {
667
            input_item_AddOption( p_parent_input, rtcp, VLC_INPUT_OPTION_TRUSTED );
668 669 670
            free( rtcp );
        }
    }
Clément Stenac's avatar
Clément Stenac committed
671

672
    vlc_mutex_lock( &p_parent_input->lock );
673

674
    p_parent_input->i_type = ITEM_TYPE_NET;
Clément Stenac's avatar
Clément Stenac committed
675

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

Clément Stenac's avatar
Clément Stenac 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);
Clément Stenac's avatar
Clément Stenac committed
683 684 685 686 687 688
    return VLC_EGENERIC;
}

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

Clément Stenac's avatar
Clément Stenac 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;
Clément Stenac's avatar
Clément Stenac 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)
Clément Stenac's avatar
Clément Stenac 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)
Clément Stenac's avatar
Clément Stenac committed
714
    {
715
        msg_Dbg( p_sd, "encrypted packet, unsupported" );
Clément Stenac's avatar
Clément Stenac 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

723
    if( p_sd->p_sys->b_strict && i_hash == 0 )
724
    {
725
        msg_Dbg( p_sd, "strict mode, discarding announce with null id hash");
Clément Stenac's avatar
Clément Stenac 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;
Clément Stenac's avatar
Clément Stenac committed
733

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

744
        decomp = realloc (decomp, newsize + 1);
745
        decomp[newsize] = '\0';
746 747
        psz_sdp = (const char *)decomp;
        len = newsize;
748
    }
749
    else
Clément Stenac's avatar
Clément Stenac committed
750
    {
751 752
        psz_sdp = (const char *)buf;
        len = end - buf;
Clément Stenac's avatar
Clément Stenac 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

Clément Stenac's avatar
Clément Stenac 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"))
Clément Stenac's avatar
Clément Stenac committed
765
        {
766 767
            msg_Dbg (p_sd, "unsupported content type: %s", psz_sdp);
            return VLC_EGENERIC;
Clément Stenac's avatar
Clément Stenac 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

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

Clément Stenac's avatar
Clément Stenac 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;

Clément Stenac's avatar
Clément Stenac committed
786
    /* Decide whether we should add a playlist item for this SDP */
787 788 789
    /* Parse connection information (c= & m= ) */
    if( ParseConnection( VLC_OBJECT(p_sd), p_sdp ) )
        p_sdp->psz_uri = NULL;
Clément Stenac's avatar
Clément Stenac 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 )
Clément Stenac's avatar
Clément Stenac 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;
Clément Stenac's avatar
Clément Stenac committed
800 801
    }

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

808
    for( i = 0 ; i< p_sd->p_sys->i_announces ; i++ )
Clément Stenac's avatar
Clément Stenac committed
809
    {
810
        sap_announce_t * p_announce = p_sd->p_sys->pp_announces[i];
Clément Stenac's avatar
Clément Stenac 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