sap.c 41.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
 *****************************************************************************/
dionoea's avatar
dionoea committed
29
#define _GNU_SOURCE
30
31
32
#include <stdlib.h>                                      /* malloc(), free() */

#include <vlc/vlc.h>
zorglub's avatar
zorglub committed
33
34
#include <vlc_playlist.h>
#include <vlc_demux.h>
35

zorglub's avatar
zorglub committed
36
37
#include <vlc_network.h>
#include <vlc_charset.h>
zorglub's avatar
zorglub committed
38

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
39
40
#include <ctype.h>
#include <errno.h>
41
42
43
44
45
46
47
48

#ifdef HAVE_UNISTD_H
#    include <unistd.h>
#endif
#ifdef HAVE_SYS_TIME_H
#    include <sys/time.h>
#endif

sigmunau's avatar
sigmunau committed
49
50
51
52
#ifdef HAVE_ZLIB_H
#   include <zlib.h>
#endif

zorglub's avatar
zorglub committed
53
54
55
56
/************************************************************************
 * Macros and definitions
 ************************************************************************/

57
58
#define MAX_LINE_LENGTH 256

59
/* SAP is always on that port */
zorglub's avatar
zorglub committed
60
#define SAP_PORT 9875
61
62
63
64
65
66
67
68
/* 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"
69
#define ADD_SESSION 1
70

71
72
73
74
#define SAP_V6_1 "FF0"
/* Scope is inserted between them */
#define SAP_V6_2 "::2:7FFE"
/* See RFC3513 for list of valid scopes */
75
76
/* FIXME: find a way to listen to link-local scope */
static const char ipv6_scopes[] = "1456789ABCDE";
77

78

79
80
81
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
82
83
#define SAP_ADDR_TEXT N_( "SAP multicast address" )
#define SAP_ADDR_LONGTEXT N_( "The SAP module normally chooses itself the " \
84
                              "right addresses to listen to. However, you " \
zorglub's avatar
zorglub committed
85
                              "can specify a specific address." )
86
#define SAP_IPV4_TEXT N_( "IPv4 SAP" )
87
#define SAP_IPV4_LONGTEXT N_( \
zorglub's avatar
zorglub committed
88
      "Listen to IPv4 announcements " \
89
      "on the standard address." )
90
#define SAP_IPV6_TEXT N_( "IPv6 SAP" )
91
#define SAP_IPV6_LONGTEXT N_( \
zorglub's avatar
zorglub committed
92
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
104
105
       "This enables actual parsing of the announces by the SAP module. " \
       "Otherwise, all announcements are parsed by the \"livedotcom\" " \
       "(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." )
115
#define SAP_TIMESHIFT_TEXT N_("Allow timeshifting")
116
117
#define SAP_TIMESHIFT_LONGTEXT N_( "This automatically enables timeshifting " \
        "for streams discovered through SAP announcements." )
zorglub's avatar
zorglub committed
118

zorglub's avatar
zorglub committed
119
120
121
122
123
/* 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 * );
124

125
vlc_module_begin();
126
    set_shortname( _("SAP"));
127
    set_description( _("SAP Announcements") );
zorglub's avatar
zorglub committed
128
129
    set_category( CAT_PLAYLIST );
    set_subcategory( SUBCAT_PLAYLIST_SD );
130

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

zorglub's avatar
zorglub committed
150
    set_capability( "services_discovery", 0 );
151
    set_callbacks( Open, Close );
zorglub's avatar
zorglub committed
152

153
    add_submodule();
zorglub's avatar
zorglub committed
154
        set_description( _("SDP file parser for UDP") );
155
156
        add_shortcut( "sdp" );
        set_capability( "demux2", 51 );
zorglub's avatar
zorglub committed
157
        set_callbacks( OpenDemux, CloseDemux );
158
159
vlc_module_end();

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
185
/* The structure that contains sdp information */
struct  sdp_t
{
    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

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

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

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

217
218
struct sap_announce_t
{
zorglub's avatar
zorglub committed
219
    mtime_t i_last;
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
228
229
    int i_input_id;
    int i_item_id_cat;
    int i_item_id_one;
230
231
};

zorglub's avatar
zorglub committed
232
struct services_discovery_sys_t
233
{
zorglub's avatar
zorglub committed
234
235
236
    /* Socket descriptors */
    int i_fd;
    int *pi_fd;
237

zorglub's avatar
zorglub committed
238
    /* playlist node */
239
240
    playlist_item_t *p_node_cat;
    playlist_item_t *p_node_one;
241
242
243
244

    /* Table of announces */
    int i_announces;
    struct sap_announce_t **pp_announces;
245

zorglub's avatar
zorglub committed
246
247
248
    /* Modes */
    vlc_bool_t  b_strict;
    vlc_bool_t  b_parse;
249
    vlc_bool_t  b_timeshift;
zorglub's avatar
zorglub committed
250

251
    int i_timeout;
252
};
253

254
255
256
257
258
struct demux_sys_t
{
    sdp_t *p_sdp;
};

zorglub's avatar
zorglub committed
259
260
261
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
262

sigmunau's avatar
sigmunau committed
263

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

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

zorglub's avatar
zorglub committed
276
/* Helper functions */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
277
278
279
280
    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);

281
    static vlc_bool_t 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
287
288
289
290
/*****************************************************************************
 * Open: initialize and create stuff
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
zorglub's avatar
zorglub committed
291
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
zorglub's avatar
zorglub committed
292
293
    services_discovery_sys_t *p_sys  = (services_discovery_sys_t *)
                                malloc( sizeof( services_discovery_sys_t ) );
294

295
    p_sys->i_timeout = var_CreateGetInteger( p_sd, "sap-timeout" );
296

zorglub's avatar
zorglub committed
297
298
    p_sd->pf_run = Run;
    p_sd->p_sys  = p_sys;
zorglub's avatar
zorglub committed
299
300
301
302

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

303
304
    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
305

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

313
314
315
316
    /* Cache sap_timeshift value */
    p_sys->b_timeshift = var_CreateGetInteger( p_sd, "sap-timeshift" )
            ? VLC_TRUE : VLC_FALSE;

zorglub's avatar
zorglub committed
317
    /* Create our playlist node */
318
    pl_Yield( p_sd );
319

320
    playlist_NodesPairCreate( pl_Get( p_sd ), _("SAP sessions"),
321
322
                              &p_sys->p_node_cat, &p_sys->p_node_one,
                              VLC_TRUE );
323
    p_sys->p_node_cat->p_input->b_prefers_tree = VLC_TRUE;
324
325
326
    p_sys->i_announces = 0;
    p_sys->pp_announces = NULL;

zorglub's avatar
zorglub committed
327
328
329
330
331
332
333
334
335
336
    return VLC_SUCCESS;
}

/*****************************************************************************
 * OpenDemux: initialize and create stuff
 *****************************************************************************/
static int OpenDemux( vlc_object_t *p_this )
{
    demux_t *p_demux = (demux_t *)p_this;
    uint8_t *p_peek;
337
338
    int i_max_sdp = 1024;
    int i_sdp = 0;
339
    char *psz_sdp = NULL;
340
    sdp_t *p_sdp = NULL;
zorglub's avatar
zorglub committed
341

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

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

zorglub's avatar
zorglub committed
353
354
355
356
357
358
359
360
        if( strncmp( (char*)p_peek, "v=0\r\n", 5 ) &&
            strncmp( (char*)p_peek, "v=0\n", 4 ) &&
            ( p_peek[0] < 'a' || p_peek[0] > 'z' || p_peek[1] != '=' ) )
        {
            return VLC_EGENERIC;
        }
    }

361
362
363
    psz_sdp = (char *)malloc( i_max_sdp );
    if( !psz_sdp ) return VLC_EGENERIC;

364
365
366
367
368
    /* Gather the complete sdp file */
    for( ;; )
    {
        int i_read = stream_Read( p_demux->s,
                                  &psz_sdp[i_sdp], i_max_sdp - i_sdp - 1 );
369

370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
        if( i_read < 0 )
        {
            msg_Err( p_demux, "failed to read SDP" );
            goto error;
        }

        i_sdp += i_read;

        if( i_read < i_max_sdp - i_sdp - 1 )
        {
            psz_sdp[i_sdp] = '\0';
            break;
        }

        i_max_sdp += 1000;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
385
        psz_sdp = (char *)realloc( psz_sdp, i_max_sdp );
386
    }
387

388
    p_sdp = ParseSDP( VLC_OBJECT(p_demux), psz_sdp );
389

390
391
392
393
394
    if( !p_sdp )
    {
        msg_Warn( p_demux, "invalid SDP");
        goto error;
    }
395

396
397
398
399
    if( ParseConnection( VLC_OBJECT( p_demux ), p_sdp ) )
    {
        p_sdp->psz_uri = NULL;
    }
400
401
    if( p_sdp->i_media_type != 33 && p_sdp->i_media_type != 32 &&
        p_sdp->i_media_type != 14 )
402
        goto error;
403

404
405
406
407
    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
408
409
    p_demux->pf_control = Control;
    p_demux->pf_demux = Demux;
410

zorglub's avatar
zorglub committed
411
    FREENULL( psz_sdp );
412
    return VLC_SUCCESS;
413

414
error:
zorglub's avatar
zorglub committed
415
    FREENULL( psz_sdp );
416
    if( p_sdp ) FreeSDP( p_sdp ); p_sdp = NULL;
417
    stream_Seek( p_demux->s, 0 );
418
    return VLC_EGENERIC;
419
420
}

421
422
423
424
/*****************************************************************************
 * Close:
 *****************************************************************************/
static void Close( vlc_object_t *p_this )
425
{
zorglub's avatar
zorglub committed
426
427
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
    services_discovery_sys_t    *p_sys  = p_sd->p_sys;
zorglub's avatar
zorglub committed
428

zorglub's avatar
free    
zorglub committed
429
    int i;
430

zorglub's avatar
zorglub committed
431
    for( i = p_sys->i_fd-1 ; i >= 0 ; i-- )
432
    {
zorglub's avatar
zorglub committed
433
        net_Close( p_sys->pi_fd[i] );
434
    }
zorglub's avatar
zorglub committed
435
    FREENULL( p_sys->pi_fd );
zorglub's avatar
zorglub committed
436

437
#if 0
zorglub's avatar
zorglub committed
438
    if( config_GetInt( p_sd, "sap-cache" ) )
439
    {
zorglub's avatar
zorglub committed
440
        CacheSave( p_sd );
441
    }
442
#endif
443

zorglub's avatar
zorglub committed
444
    for( i = p_sys->i_announces  - 1;  i>= 0; i-- )
zorglub's avatar
free    
zorglub committed
445
    {
zorglub's avatar
zorglub committed
446
        RemoveAnnounce( p_sd, p_sys->pp_announces[i] );
zorglub's avatar
free    
zorglub committed
447
    }
zorglub's avatar
zorglub committed
448
    FREENULL( p_sys->pp_announces );
zorglub's avatar
free    
zorglub committed
449

450
451
452
453
454
    playlist_NodeDelete( pl_Get(p_sd), p_sys->p_node_cat, VLC_TRUE,
                         VLC_TRUE );
    playlist_NodeDelete( pl_Get(p_sd), p_sys->p_node_one, VLC_TRUE,
                         VLC_TRUE );
    pl_Release( p_sd );
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

zorglub's avatar
zorglub committed
478
static void Run( services_discovery_t *p_sd )
479
{
480
    char *psz_addr;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
481
    int i;
482

483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
    /* 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" ) )
    {
        char psz_address[] = SAP_V6_1"0"SAP_V6_2;
        const char *c_scope;

        for( c_scope = ipv6_scopes; *c_scope; c_scope++ )
        {
            psz_address[sizeof(SAP_V6_1) - 1] = *c_scope;
            InitSocket( p_sd, psz_address, SAP_PORT );
        }
    }

508
509
510
511
    psz_addr = var_CreateGetString( p_sd, "sap-addr" );
    if( psz_addr && *psz_addr )
    {
        InitSocket( p_sd, psz_addr, SAP_PORT );
512
        free( psz_addr );
513
514
    }

515
516
517
518
519
520
    if( p_sd->p_sys->i_fd == 0 )
    {
        msg_Err( p_sd, "unable to listen on any address" );
        return;
    }

521
    /* read SAP packets */
zorglub's avatar
zorglub committed
522
    while( !p_sd->b_die )
523
    {
524
        int i_read;
zorglub's avatar
zorglub committed
525
        uint8_t p_buffer[MAX_SAP_BUFFER+1];
zorglub's avatar
zorglub committed
526

527
528
529
        i_read = net_Select( p_sd, p_sd->p_sys->pi_fd, NULL,
                             p_sd->p_sys->i_fd, p_buffer,
                             MAX_SAP_BUFFER, 500000 );
530

zorglub's avatar
zorglub committed
531
        /* Check for items that need deletion */
532
        for( i = 0; i < p_sd->p_sys->i_announces; i++ )
zorglub's avatar
zorglub committed
533
        {
534
535
536
537
            mtime_t i_timeout = ( mtime_t ) 1000000 * p_sd->p_sys->i_timeout;

            if( mdate() - p_sd->p_sys->pp_announces[i]->i_last > i_timeout )
            {
538
                RemoveAnnounce( p_sd, p_sd->p_sys->pp_announces[i] );
539
            }
zorglub's avatar
zorglub committed
540
541
        }

542
543
        /* Minimum length is > 6 */
        if( i_read <= 6 )
544
        {
545
546
            if( i_read < 0 )
            {
zorglub's avatar
zorglub committed
547
                msg_Warn( p_sd, "socket read error" );
548
549
            }
            continue;
550
551
        }

zorglub's avatar
zorglub committed
552
553
554
        p_buffer[i_read] = '\0';

        /* Parse the packet */
zorglub's avatar
zorglub committed
555
        ParseSAP( p_sd, p_buffer, i_read );
zorglub's avatar
zorglub committed
556
557
558
559
560
561
562
563
564
    }
}

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

569
    playlist_t *p_playlist = pl_Yield( p_demux );
570
    p_input = (input_thread_t *)vlc_object_find( p_demux, VLC_OBJECT_INPUT,
571
572
                                                 FIND_PARENT );
    assert( p_input );
573
574
575
576
577
578
    if( !p_input )
    {
        msg_Err( p_demux, "parent input could not be found" );
        return VLC_EGENERIC;
    }

zorglub's avatar
zorglub committed
579
    p_parent_input = input_GetItem(p_input);
zorglub's avatar
zorglub committed
580

581
    vlc_mutex_lock( &p_parent_input->lock );
zorglub's avatar
zorglub committed
582
    FREENULL( p_parent_input->psz_uri );
583
    p_parent_input->psz_uri = strdup( p_sdp->psz_uri );
zorglub's avatar
zorglub committed
584
    FREENULL( p_parent_input->psz_name );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
585
    p_parent_input->psz_name = strdup( p_sdp->psz_sessionname );
586
    p_parent_input->i_type = ITEM_TYPE_NET;
zorglub's avatar
zorglub committed
587

588
589
590
    if( p_playlist->status.p_item &&
             p_playlist->status.p_item->p_input == p_parent_input )
    {
591
        playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VLC_TRUE,
592
593
594
595
596
                          p_playlist->status.p_node, p_playlist->status.p_item );
    }

    vlc_mutex_unlock( &p_parent_input->lock );
    vlc_object_release( p_input );
597
    vlc_object_release( p_playlist );
zorglub's avatar
zorglub committed
598

599
    return VLC_SUCCESS;
600
601
}

zorglub's avatar
zorglub committed
602
603
604
605
606
607
608
609
static int Control( demux_t *p_demux, int i_query, va_list args )
{
    return VLC_EGENERIC;
}

/**************************************************************
 * Local functions
 **************************************************************/
610

zorglub's avatar
zorglub committed
611
/* i_read is at least > 6 */
612
613
static int ParseSAP( services_discovery_t *p_sd, const uint8_t *buf,
                     size_t len )
614
{
615
616
617
    int i;
    const char          *psz_sdp;
    const uint8_t *end = buf + len;
zorglub's avatar
zorglub committed
618
619
    sdp_t               *p_sdp;

620
    assert (buf[len] == '\0');
621

622
623
    if (len < 4)
        return VLC_EGENERIC;
624

625
626
627
628
    uint8_t flags = buf[0];

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

631
632
    vlc_bool_t b_ipv6 = (flags & 0x10) != 0;
    vlc_bool_t b_need_delete = (flags & 0x04) != 0;
633

634
    if (flags & 0x02)
zorglub's avatar
zorglub committed
635
    {
zorglub's avatar
zorglub committed
636
        msg_Dbg( p_sd, "encrypted packet, unsupported" );
zorglub's avatar
zorglub committed
637
638
        return VLC_EGENERIC;
    }
639

640
    vlc_bool_t b_compressed = (flags & 0x01) != 0;
641

642
    uint16_t i_hash = U16_AT (buf + 2);
643

zorglub's avatar
zorglub committed
644
    if( p_sd->p_sys->b_strict && i_hash == 0 )
645
    {
zorglub's avatar
zorglub committed
646
        msg_Dbg( p_sd, "strict mode, discarding announce with null id hash");
zorglub's avatar
zorglub committed
647
648
649
        return VLC_EGENERIC;
    }

650
651
652
653
    // 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
654

655
    uint8_t *decomp = NULL;
656
    if( b_compressed )
zorglub's avatar
zorglub committed
657
    {
658
659
        int newsize = Decompress (buf, &decomp, end - buf);
        if (newsize < 0)
zorglub's avatar
zorglub committed
660
        {
661
            msg_Warn( p_sd, "decompression of sap packet failed" );
zorglub's avatar
zorglub committed
662
663
            return VLC_EGENERIC;
        }
zorglub's avatar
zorglub committed
664

665
        decomp = realloc (decomp, newsize + 1);
666
        decomp[newsize] = '\0';
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
667

668
669
        psz_sdp = (const char *)decomp;
        len = newsize;
670
    }
671
    else
zorglub's avatar
zorglub committed
672
    {
673
674
        psz_sdp = (const char *)buf;
        len = end - buf;
zorglub's avatar
zorglub committed
675
    }
676

677
678
    /* 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');
679

zorglub's avatar
zorglub committed
680
    /* Skip payload type */
681
682
    /* SAPv1 has implicit "application/sdp" payload type: first line is v=0 */
    if (strncmp (psz_sdp, "v=0", 3))
683
    {
684
685
686
        size_t clen = strlen (psz_sdp) + 1;

        if (strcmp (psz_sdp, "application/sdp"))
zorglub's avatar
zorglub committed
687
        {
688
689
            msg_Dbg (p_sd, "unsupported content type: %s", psz_sdp);
            return VLC_EGENERIC;
zorglub's avatar
zorglub committed
690
        }
691

692
693
694
695
696
697
        // skips content type
        if (len <= clen)
            return VLC_EGENERIC;

        len -= clen;
        psz_sdp += clen;
698
    }
699

zorglub's avatar
zorglub committed
700
    /* Parse SDP info */
zorglub's avatar
zorglub committed
701
    p_sdp = ParseSDP( VLC_OBJECT(p_sd), psz_sdp );
702

zorglub's avatar
zorglub committed
703
704
705
    if( p_sdp == NULL )
        return VLC_EGENERIC;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
706
707
    p_sdp->psz_sdp = psz_sdp;

zorglub's avatar
zorglub committed
708
    /* Decide whether we should add a playlist item for this SDP */
zorglub's avatar
zorglub committed
709
710
711
    /* Parse connection information (c= & m= ) */
    if( ParseConnection( VLC_OBJECT(p_sd), p_sdp ) )
        p_sdp->psz_uri = NULL;
zorglub's avatar
zorglub committed
712
713

    /* Multi-media or no-parse -> pass to LIVE.COM */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
714
715
716
717
    if( ( p_sdp->i_media_type != 14
       && p_sdp->i_media_type != 32
       && p_sdp->i_media_type != 33)
     || p_sd->p_sys->b_parse == VLC_FALSE )
zorglub's avatar
zorglub committed
718
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
719
720
721
        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
722
723
724
725
    }

    if( p_sdp->psz_uri == NULL ) return VLC_EGENERIC;

zorglub's avatar
zorglub committed
726
    for( i = 0 ; i< p_sd->p_sys->i_announces ; i++ )
zorglub's avatar
zorglub committed
727
728
729
    {
        /* FIXME: slow */
        /* FIXME: we create a new announce each time the sdp changes */
730
        if( IsSameSession( p_sd->p_sys->pp_announces[i]->p_sdp, p_sdp ) )
731
        {
zorglub's avatar
zorglub committed
732
733
            if( b_need_delete )
            {
zorglub's avatar
zorglub committed
734
                RemoveAnnounce( p_sd, p_sd->p_sys->pp_announces[i]);
zorglub's avatar
zorglub committed
735
736
            }
            else
737
            {
zorglub's avatar
zorglub committed
738
                p_sd->p_sys->pp_announces[i]->i_last = mdate();
739
            }
740
            FreeSDP( p_sdp ); p_sdp = NULL;
741
            return VLC_SUCCESS;
742
        }
zorglub's avatar
zorglub committed
743
    }
zorglub's avatar
zorglub committed
744

zorglub's avatar
zorglub committed
745
    CreateAnnounce( p_sd, i_hash, p_sdp );
zorglub's avatar
zorglub committed
746

747
    FREENULL (decomp);
zorglub's avatar
zorglub committed
748
749
750
    return VLC_SUCCESS;
}

zorglub's avatar
zorglub committed
751
sap_announce_t *CreateAnnounce( services_discovery_t *p_sd, uint16_t i_hash,
zorglub's avatar
zorglub committed
752
753
                                sdp_t *p_sdp )
{
754
    input_item_t *p_input;
zorglub's avatar
zorglub committed
755
    playlist_item_t     *p_item, *p_child;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
756
    const char *psz_value;
zorglub's avatar
zorglub committed
757
758
    sap_announce_t *p_sap = (sap_announce_t *)malloc(
                                        sizeof(sap_announce_t ) );
759
    services_discovery_sys_t *p_sys;
760
    if( p_sap == NULL )
zorglub's avatar
zorglub committed
761
        return NULL;
762

763
764
    p_sys = p_sd->p_sys;

zorglub's avatar
zorglub committed
765
766
767
    p_sap->i_last = mdate();
    p_sap->i_hash = i_hash;
    p_sap->p_sdp = p_sdp;
768

769
    /* Create the actual playlist item here */
770
771
772
773
774
775
    p_input = input_ItemNewWithType( VLC_OBJECT(p_sd),
                                     p_sap->p_sdp->psz_uri,
                                     p_sdp->psz_sessionname,
                                     0, NULL, -1, ITEM_TYPE_NET );
    p_sap->i_input_id = p_input->i_id;
    if( !p_input )
zorglub's avatar
zorglub committed
776
    {
777
        free( p_sap );
zorglub's avatar
zorglub committed
778
        return NULL;
779
780
    }

781
    if( p_sys->b_timeshift )
782
        input_ItemAddOption( p_input, ":access-filter=timeshift" );
783

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
784
    psz_value = GetAttribute( p_sap->p_sdp->pp_attributes, p_sap->p_sdp->i_attributes, "tool" );
zorglub's avatar
zorglub committed
785
786
    if( psz_value != NULL )
    {
787
        input_ItemAddInfo( p_input, _("Session"),_("Tool"), psz_value );
zorglub's avatar
zorglub committed
788
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
789
    if( strcmp( p_sdp->username, "-" ) )
zorglub's avatar
zorglub committed
790
    {
791
        input_ItemAddInfo( p_input, _("Session"),
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
792
                                _("User"), p_sdp->username );
zorglub's avatar
zorglub committed
793
794
    }

795
    /* Handle group */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
796
    psz_value = GetAttribute( p_sap->p_sdp->pp_attributes, p_sap->p_sdp->i_attributes, "x-plgroup" );
zorglub's avatar
zorglub committed
797
    if( psz_value == NULL )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
798
        psz_value = GetAttribute( p_sap->p_sdp->pp_attributes, p_sap->p_sdp->i_attributes, "plgroup" );
799

zorglub's avatar
zorglub committed
800
801
    if( psz_value != NULL )
    {
802
        p_child = playlist_ChildSearchName( p_sys->p_node_cat, psz_value );
803

804
        if( p_child == NULL )
805
        {
806
            p_child = playlist_NodeCreate( pl_Get( p_sd ), psz_value,
807
                                           p_sys->p_node_cat, 0 );
808
            p_child->i_flags &= ~PLAYLIST_SKIP_FLAG;
809
        }
zorglub's avatar
zorglub committed
810
811
812
    }
    else
    {
813
        p_child = p_sys->p_node_cat;
zorglub's avatar
zorglub committed
814
815
    }

816
    p_item = playlist_NodeAddInput( pl_Get( p_sd ), p_input, p_child,
817
                                    PLAYLIST_APPEND, PLAYLIST_END );
zorglub's avatar
zorglub committed
818
    p_item->i_flags &= ~PLAYLIST_SKIP_FLAG;
zorglub's avatar
zorglub committed
819
    p_item->i_flags &= ~PLAYLIST_SAVE_FLAG;
820
    p_sap->i_item_id_cat = p_item->i_id;
zorglub's avatar
zorglub committed
821

822
    p_item = playlist_NodeAddInput( pl_Get( p_sd ), p_input,
823
824
825
826
                        p_sys->p_node_one, PLAYLIST_APPEND, PLAYLIST_END );
    p_item->i_flags &= ~PLAYLIST_SKIP_FLAG;
    p_item->i_flags &= ~PLAYLIST_SAVE_FLAG;
    p_sap->i_item_id_one = p_item->i_id;
zorglub's avatar
zorglub committed
827

828
    TAB_APPEND( p_sys->i_announces, p_sys->pp_announces, p_sap );
zorglub's avatar
zorglub committed
829
830
831

    return p_sap;
}
Laurent Aimar's avatar
Laurent Aimar committed
832

zorglub's avatar
zorglub committed
833
834
835
/* Fill p_sdp->psz_uri */
static int ParseConnection( vlc_object_t *p_obj, sdp_t *p_sdp )
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
836
837
    if (p_sdp->mediac != 1)
        return VLC_EGENERIC;
838

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
839
840
841
    char psz_uri[1026];
    const char *host;
    int port;