sap.c 47.6 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
#include <ctype.h>
44 45 46 47 48 49 50

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

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

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

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

67 68
#define MAX_LINE_LENGTH 256

69
/* SAP is always on that port */
zorglub's avatar
zorglub committed
70
#define SAP_PORT 9875
71 72 73 74 75 76 77 78
/* 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"
79
#define ADD_SESSION 1
80 81 82 83

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

zorglub's avatar
zorglub committed
116 117 118 119 120
/* 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 * );
121

122
VLC_SD_PROBE_HELPER("sap", N_("Network streams (SAP)"))
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
123

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

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

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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
151 152
    VLC_SD_PROBE_SUBMODULE

153 154 155 156 157 158
    add_submodule ()
        set_description( N_("SDP Descriptions parser") )
        add_shortcut( "sdp" )
        set_capability( "demux", 51 )
        set_callbacks( OpenDemux, CloseDemux )
vlc_module_end ()
159

zorglub's avatar
zorglub committed
160

161
/*****************************************************************************
zorglub's avatar
zorglub committed
162
 * Local structures
163 164
 *****************************************************************************/

zorglub's avatar
zorglub committed
165 166 167
typedef struct sdp_t sdp_t;
typedef struct attribute_t attribute_t;
typedef struct sap_announce_t sap_announce_t;
168

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
169 170 171 172 173 174 175 176 177 178 179 180 181

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
182 183 184
/* The structure that contains sdp information */
struct  sdp_t
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
185
    const char *psz_sdp;
186

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
187 188 189 190 191 192 193
    /* 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
194 195
    /* s= field */
    char *psz_sessionname;
196

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

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

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

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

218 219
struct sap_announce_t
{
zorglub's avatar
zorglub committed
220
    mtime_t i_last;
221 222
    mtime_t i_period;
    uint8_t i_period_trust;
zorglub's avatar
zorglub committed
223 224 225 226 227 228 229

    uint16_t    i_hash;
    uint32_t    i_source[4];

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

230
    input_item_t * p_item;
231 232
};

zorglub's avatar
zorglub committed
233
struct services_discovery_sys_t
234
{
235 236
    vlc_thread_t thread;

zorglub's avatar
zorglub committed
237 238 239
    /* Socket descriptors */
    int i_fd;
    int *pi_fd;
240

241 242 243
    /* Table of announces */
    int i_announces;
    struct sap_announce_t **pp_announces;
244

zorglub's avatar
zorglub committed
245
    /* Modes */
246 247
    bool  b_strict;
    bool  b_parse;
zorglub's avatar
zorglub committed
248

249
    int i_timeout;
250
};
251

252 253 254 255 256
struct demux_sys_t
{
    sdp_t *p_sdp;
};

zorglub's avatar
zorglub committed
257 258 259
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
260

sigmunau's avatar
sigmunau committed
261

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

zorglub's avatar
zorglub committed
267 268
/* Main parsing functions */
    static int ParseConnection( vlc_object_t *p_obj, sdp_t *p_sdp );
269
    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
270
    static sdp_t *ParseSDP (vlc_object_t *p_sd, const char *psz_sdp);
zorglub's avatar
zorglub committed
271 272
    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
273

zorglub's avatar
zorglub committed
274
/* Helper functions */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
275 276 277
    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);
278 279
    static const char *FindAttribute (const sdp_t *sdp, unsigned media,
                                      const char *name);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
280

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

286
static inline int min_int( int a, int b )
287 288 289 290
{
    return a > b ? b : a;
}

291 292 293 294 295
static bool IsWellKnownPayload (int type)
{
    switch (type)
    {   /* Should be in sync with modules/demux/rtp.c */
        case  0: /* PCMU/8000 */
296
        case  3:
297 298 299
        case  8: /* PCMA/8000 */
        case 10: /* L16/44100/2 */
        case 11: /* L16/44100 */
300
        case 12:
301 302 303 304 305 306 307 308
        case 14: /* MPA/90000 */
        case 32: /* MPV/90000 */
        case 33: /* MP2/90000 */
            return true;
   }
   return false;
}

309 310 311 312 313
/*****************************************************************************
 * Open: initialize and create stuff
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
zorglub's avatar
zorglub committed
314
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
zorglub's avatar
zorglub committed
315 316
    services_discovery_sys_t *p_sys  = (services_discovery_sys_t *)
                                malloc( sizeof( services_discovery_sys_t ) );
317 318
    if( !p_sys )
        return VLC_ENOMEM;
319

320
    p_sys->i_timeout = var_CreateGetInteger( p_sd, "sap-timeout" );
321

zorglub's avatar
zorglub committed
322
    p_sd->p_sys  = p_sys;
zorglub's avatar
zorglub committed
323 324 325 326

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

327 328
    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
329

330
#if 0
331
    if( var_CreateGetInteger( p_sd, "sap-cache" ) )
zorglub's avatar
zorglub committed
332
    {
zorglub's avatar
zorglub committed
333
        CacheLoad( p_sd );
334
    }
335
#endif
336

337 338
    p_sys->i_announces = 0;
    p_sys->pp_announces = NULL;
339 340 341 342 343 344
    /* 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;
    }
345

zorglub's avatar
zorglub committed
346 347 348 349 350 351 352 353 354
    return VLC_SUCCESS;
}

/*****************************************************************************
 * OpenDemux: initialize and create stuff
 *****************************************************************************/
static int OpenDemux( vlc_object_t *p_this )
{
    demux_t *p_demux = (demux_t *)p_this;
355
    const uint8_t *p_peek;
356
    char *psz_sdp = NULL;
357
    sdp_t *p_sdp = NULL;
358
    int errval = VLC_EGENERIC;
Laurent Aimar's avatar
Laurent Aimar committed
359
    size_t i_len;
zorglub's avatar
zorglub committed
360

361 362 363 364 365 366
    if( !var_CreateGetInteger( p_demux, "sap-parse" ) )
    {
        /* We want livedotcom module to parse this SDP file */
        return VLC_EGENERIC;
    }

367
    assert( p_demux->s ); /* this is NOT an access_demux */
368

369 370 371
    /* Probe for SDP */
    if( stream_Peek( p_demux->s, &p_peek, 7 ) < 7 )
        return VLC_EGENERIC;
zorglub's avatar
zorglub committed
372

373 374
    if( memcmp( p_peek, "v=0\r\no=", 7 ) && memcmp( p_peek, "v=0\no=", 6 ) )
        return VLC_EGENERIC;
375

376
    /* Gather the complete sdp file */
Laurent Aimar's avatar
Laurent Aimar committed
377
    for( i_len = 0, psz_sdp = NULL; i_len < 65536; )
378
    {
Laurent Aimar's avatar
Laurent Aimar committed
379 380 381
        const int i_read_max = 1024;
        char *psz_sdp_new = realloc( psz_sdp, i_len + i_read_max );
        size_t i_read;
382
        if( psz_sdp_new == NULL )
383
        {
384
            errval = VLC_ENOMEM;
385 386
            goto error;
        }
387
        psz_sdp = psz_sdp_new;
388

Laurent Aimar's avatar
Laurent Aimar committed
389
        i_read = stream_Read( p_demux->s, &psz_sdp[i_len], i_read_max );
390
        if( (int)i_read < 0 )
391
        {
392 393
            msg_Err( p_demux, "cannot read SDP" );
            goto error;
394
        }
Laurent Aimar's avatar
Laurent Aimar committed
395
        i_len += i_read;
396

Laurent Aimar's avatar
Laurent Aimar committed
397
        psz_sdp[i_len] = '\0';
398

399
        if( (int)i_read < i_read_max )
400
            break; // EOF
401
    }
402

403
    p_sdp = ParseSDP( VLC_OBJECT(p_demux), psz_sdp );
404

405 406 407 408 409
    if( !p_sdp )
    {
        msg_Warn( p_demux, "invalid SDP");
        goto error;
    }
410

411 412 413 414
    if( ParseConnection( VLC_OBJECT( p_demux ), p_sdp ) )
    {
        p_sdp->psz_uri = NULL;
    }
415 416
    if (!IsWellKnownPayload (p_sdp->i_media_type))
        goto error;
417 418 419
    if( p_sdp->psz_uri == NULL ) goto error;

    p_demux->p_sys = (demux_sys_t *)malloc( sizeof(demux_sys_t) );
420 421
    if( unlikely( !p_demux->p_sys ) )
        goto error;
422
    p_demux->p_sys->p_sdp = p_sdp;
zorglub's avatar
zorglub committed
423 424
    p_demux->pf_control = Control;
    p_demux->pf_demux = Demux;
425

zorglub's avatar
zorglub committed
426
    FREENULL( psz_sdp );
427
    return VLC_SUCCESS;
428

429
error:
zorglub's avatar
zorglub committed
430
    FREENULL( psz_sdp );
431
    if( p_sdp ) FreeSDP( p_sdp ); p_sdp = NULL;
432
    stream_Seek( p_demux->s, 0 );
433
    return errval;
434 435
}

436 437 438 439
/*****************************************************************************
 * Close:
 *****************************************************************************/
static void Close( vlc_object_t *p_this )
440
{
zorglub's avatar
zorglub committed
441 442
    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
443
    int i;
444

445 446 447
    vlc_cancel (p_sys->thread);
    vlc_join (p_sys->thread, NULL);

zorglub's avatar
zorglub committed
448
    for( i = p_sys->i_fd-1 ; i >= 0 ; i-- )
449
    {
zorglub's avatar
zorglub committed
450
        net_Close( p_sys->pi_fd[i] );
451
    }
zorglub's avatar
zorglub committed
452
    FREENULL( p_sys->pi_fd );
zorglub's avatar
zorglub committed
453

454
#if 0
zorglub's avatar
zorglub committed
455
    if( config_GetInt( p_sd, "sap-cache" ) )
456
    {
zorglub's avatar
zorglub committed
457
        CacheSave( p_sd );
458
    }
459
#endif
460

zorglub's avatar
zorglub committed
461
    for( i = p_sys->i_announces  - 1;  i>= 0; i-- )
zorglub's avatar
free  
zorglub committed
462
    {
zorglub's avatar
zorglub committed
463
        RemoveAnnounce( p_sd, p_sys->pp_announces[i] );
zorglub's avatar
free  
zorglub committed
464
    }
zorglub's avatar
zorglub committed
465
    FREENULL( p_sys->pp_announces );
zorglub's avatar
free  
zorglub committed
466

467 468
    free( p_sys );
}
469

470
/*****************************************************************************
zorglub's avatar
zorglub committed
471 472 473 474
 * CloseDemux: Close the demuxer
 *****************************************************************************/
static void CloseDemux( vlc_object_t *p_this )
{
475
    demux_t *p_demux = (demux_t *)p_this;
476 477 478 479

    if( p_demux->p_sys->p_sdp )
        FreeSDP( p_demux->p_sys->p_sdp );
    free( p_demux->p_sys );
zorglub's avatar
zorglub committed
480 481 482 483
}

/*****************************************************************************
 * Run: main SAP thread
484 485 486
 *****************************************************************************
 * Listens to SAP packets, and sends them to packet_handle
 *****************************************************************************/
sigmunau's avatar
sigmunau committed
487
#define MAX_SAP_BUFFER 5000
488

489
static void *Run( void *data )
490
{
491
    services_discovery_t *p_sd = data;
492
    char *psz_addr;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
493
    int i;
494
    int timeout = -1;
495
    int canc = vlc_savecancel ();
496

497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
    /* 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" ) )
    {
512 513
        char psz_address[NI_MAXNUMERICHOST] = "ff02::2:7ffe%";

514
#ifndef WIN32
515 516 517 518 519 520 521 522 523 524 525
        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);
        }
526 527 528 529 530 531 532 533 534
#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
535
            size_t len = sizeof(ifaces);
536

537 538 539 540 541 542 543 544 545 546 547 548 549
            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
550
        *strchr (psz_address, '%') = '\0';
551

552 553
        static const char ipv6_scopes[] = "1456789ABCDE";
        for (const char *c_scope = ipv6_scopes; *c_scope; c_scope++)
554
        {
555
            psz_address[3] = *c_scope;
556 557 558 559
            InitSocket( p_sd, psz_address, SAP_PORT );
        }
    }

560 561 562
    psz_addr = var_CreateGetString( p_sd, "sap-addr" );
    if( psz_addr && *psz_addr )
        InitSocket( p_sd, psz_addr, SAP_PORT );
563
    free( psz_addr );
564

565 566 567
    if( p_sd->p_sys->i_fd == 0 )
    {
        msg_Err( p_sd, "unable to listen on any address" );
568
        return NULL;
569 570
    }

571
    /* read SAP packets */
572
    for (;;)
573
    {
574
        vlc_restorecancel (canc);
575
        unsigned n = p_sd->p_sys->i_fd;
576
        struct pollfd ufd[n];
zorglub's avatar
zorglub committed
577

578 579 580 581 582 583 584
        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;
        }

585 586 587
        int val = poll (ufd, n, timeout);
        canc = vlc_savecancel ();
        if (val > 0)
588 589 590 591 592 593 594 595 596
        {
            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,
597
                                       MAX_SAP_BUFFER, false);
598 599 600 601 602 603 604 605 606 607 608 609 610
                    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();
611

612 613 614 615
        /* 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
616
        /* Check for items that need deletion */
617
        for( i = 0; i < p_sd->p_sys->i_announces; i++ )
zorglub's avatar
zorglub committed
618
        {
619
            mtime_t i_timeout = ( mtime_t ) 1000000 * p_sd->p_sys->i_timeout;
620
            sap_announce_t * p_announce = p_sd->p_sys->pp_announces[i];
621 622
            mtime_t i_last_period = now - p_announce->i_last;

623 624 625 626 627
            /* 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 )
628
            {
629
                RemoveAnnounce( p_sd, p_announce );
630
            }
631 632 633 634
            else
            {
                /* Compute next timeout */
                if( p_announce->i_period_trust > 5 )
635 636
                    timeout = min_int((3 * p_announce->i_period - i_last_period) / 1000, timeout);
                timeout = min_int((i_timeout - i_last_period)/1000, timeout);
637
            }
zorglub's avatar
zorglub committed
638
        }
639 640 641 642 643

        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
644
    }
645
    assert (0);
zorglub's avatar
zorglub committed
646 647 648 649 650 651 652 653
}

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

658
    p_input = demux_GetParentInput( p_demux );
659
    assert( p_input );
660 661 662 663 664 665
    if( !p_input )
    {
        msg_Err( p_demux, "parent input could not be found" );
        return VLC_EGENERIC;
    }

666
    /* This item hasn't been held by input_GetItem
667
     * don't release it */
668 669 670 671
    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 );
672 673 674 675 676
    if( p_sdp->rtcp_port )
    {
        char *rtcp;
        if( asprintf( &rtcp, ":rtcp-port=%u", p_sdp->rtcp_port ) != -1 )
        {
677
            input_item_AddOption( p_parent_input, rtcp, VLC_INPUT_OPTION_TRUSTED );
678 679 680
            free( rtcp );
        }
    }
zorglub's avatar
zorglub committed
681

682
    vlc_mutex_lock( &p_parent_input->lock );
683

684
    p_parent_input->i_type = ITEM_TYPE_NET;
zorglub's avatar
zorglub committed
685

686
    vlc_mutex_unlock( &p_parent_input->lock );
687
    vlc_object_release( p_input );
688
    return VLC_SUCCESS;
689 690
}

zorglub's avatar
zorglub committed
691 692
static int Control( demux_t *p_demux, int i_query, va_list args )
{
693
    VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);
zorglub's avatar
zorglub committed
694 695 696 697 698 699
    return VLC_EGENERIC;
}

/**************************************************************
 * Local functions
 **************************************************************/
700

zorglub's avatar
zorglub committed
701
/* i_read is at least > 6 */
702 703
static int ParseSAP( services_discovery_t *p_sd, const uint8_t *buf,
                     size_t len )
704
{
705 706 707
    int i;
    const char          *psz_sdp;
    const uint8_t *end = buf + len;
zorglub's avatar
zorglub committed
708 709
    sdp_t               *p_sdp;

710
    assert (buf[len] == '\0');
711

712 713
    if (len < 4)
        return VLC_EGENERIC;
714

715 716 717 718
    uint8_t flags = buf[0];

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

721 722
    bool b_ipv6 = (flags & 0x10) != 0;
    bool b_need_delete = (flags & 0x04) != 0;
723

724
    if (flags & 0x02)
zorglub's avatar
zorglub committed
725
    {
zorglub's avatar
zorglub committed
726
        msg_Dbg( p_sd, "encrypted packet, unsupported" );
zorglub's avatar
zorglub committed
727 728
        return VLC_EGENERIC;
    }
729

730
    bool b_compressed = (flags & 0x01) != 0;
731

732
    uint16_t i_hash = U16_AT (buf + 2);
733

zorglub's avatar
zorglub committed
734
    if( p_sd->p_sys->b_strict && i_hash == 0 )
735
    {
zorglub's avatar
zorglub committed
736
        msg_Dbg( p_sd, "strict mode, discarding announce with null id hash");
zorglub's avatar
zorglub committed
737 738 739
        return VLC_EGENERIC;
    }

740 741 742 743
    // 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
744

745
    uint8_t *decomp = NULL;
746
    if( b_compressed )
zorglub's avatar
zorglub committed
747
    {
748 749
        int newsize = Decompress (buf, &decomp, end - buf);
        if (newsize < 0)
zorglub's avatar
zorglub committed
750
        {
751
            msg_Dbg( p_sd, "decompression of SAP packet failed" );
zorglub's avatar
zorglub committed
752 753
            return VLC_EGENERIC;
        }
zorglub's avatar
zorglub committed
754

755
        decomp = realloc (decomp, newsize + 1);
756
        decomp[newsize] = '\0';
757 758
        psz_sdp = (const char *)decomp;
        len = newsize;
759
    }
760
    else
zorglub's avatar
zorglub committed
761
    {
762 763
        psz_sdp = (const char *)buf;
        len = end - buf;
zorglub's avatar
zorglub committed
764
    }
765

766 767
    /* 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');
768

zorglub's avatar
zorglub committed
769
    /* Skip payload type */
770 771
    /* SAPv1 has implicit "application/sdp" payload type: first line is v=0 */
    if (strncmp (psz_sdp, "v=0", 3))
772
    {
773 774 775
        size_t clen = strlen (psz_sdp) + 1;

        if (strcmp (psz_sdp, "application/sdp"))
zorglub's avatar
zorglub committed
776
        {
777 778
            msg_Dbg (p_sd, "unsupported content type: %s", psz_