1 /*****************************************************************************
2 * sap.c : SAP interface module
3 *****************************************************************************
4 * Copyright (C) 2004-2005 the VideoLAN team
5 * Copyright © 2007 Rémi Denis-Courmont
8 * Authors: Clément Stenac <zorglub@videolan.org>
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
26 /*****************************************************************************
28 *****************************************************************************/
33 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
38 #include <vlc_demux.h>
39 #include <vlc_services_discovery.h>
41 #include <vlc_network.h>
42 #include <vlc_charset.h>
46 #ifdef HAVE_ARPA_INET_H
47 # include <arpa/inet.h>
61 /************************************************************************
62 * Macros and definitions
63 ************************************************************************/
65 #define MAX_LINE_LENGTH 256
67 /* SAP is always on that port */
69 /* Global-scope SAP address */
70 #define SAP_V4_GLOBAL_ADDRESS "224.2.127.254"
71 /* Organization-local SAP address */
72 #define SAP_V4_ORG_ADDRESS "239.195.255.255"
73 /* Local (smallest non-link-local scope) SAP address */
74 #define SAP_V4_LOCAL_ADDRESS "239.255.255.255"
75 /* Link-local SAP address */
76 #define SAP_V4_LINK_ADDRESS "224.0.0.255"
79 /*****************************************************************************
81 *****************************************************************************/
82 #define SAP_ADDR_TEXT N_( "SAP multicast address" )
83 #define SAP_ADDR_LONGTEXT N_( "The SAP module normally chooses itself the " \
84 "right addresses to listen to. However, you " \
85 "can specify a specific address." )
86 #define SAP_TIMEOUT_TEXT N_( "SAP timeout (seconds)" )
87 #define SAP_TIMEOUT_LONGTEXT N_( \
88 "Delay after which SAP items get deleted if no new announcement " \
90 #define SAP_PARSE_TEXT N_( "Try to parse the announce" )
91 #define SAP_PARSE_LONGTEXT N_( \
92 "This enables actual parsing of the announces by the SAP module. " \
93 "Otherwise, all announcements are parsed by the \"live555\" " \
94 "(RTP/RTSP) module." )
95 #define SAP_STRICT_TEXT N_( "SAP Strict mode" )
96 #define SAP_STRICT_LONGTEXT N_( \
97 "When this is set, the SAP parser will discard some non-compliant " \
101 static int Open ( vlc_object_t
* );
102 static void Close( vlc_object_t
* );
103 static int OpenDemux ( vlc_object_t
* );
104 static void CloseDemux ( vlc_object_t
* );
106 VLC_SD_PROBE_HELPER("sap", N_("Network streams (SAP)"), SD_CAT_LAN
)
109 set_shortname( N_("SAP"))
110 set_description( N_("Network streams (SAP)") )
111 set_category( CAT_PLAYLIST
)
112 set_subcategory( SUBCAT_PLAYLIST_SD
)
114 add_string( "sap-addr", NULL
,
115 SAP_ADDR_TEXT
, SAP_ADDR_LONGTEXT
, true )
116 add_obsolete_bool( "sap-ipv4" ) /* since 2.0.0 */
117 add_obsolete_bool( "sap-ipv6" ) /* since 2.0.0 */
118 add_integer( "sap-timeout", 1800,
119 SAP_TIMEOUT_TEXT
, SAP_TIMEOUT_LONGTEXT
, true )
120 add_bool( "sap-parse", true,
121 SAP_PARSE_TEXT
,SAP_PARSE_LONGTEXT
, true )
122 add_bool( "sap-strict", false,
123 SAP_STRICT_TEXT
,SAP_STRICT_LONGTEXT
, true )
124 add_obsolete_bool( "sap-timeshift" ) /* Redumdant since 1.0.0 */
126 set_capability( "services_discovery", 0 )
127 set_callbacks( Open
, Close
)
129 VLC_SD_PROBE_SUBMODULE
132 set_description( N_("SDP Descriptions parser") )
133 add_shortcut( "sdp" )
134 set_capability( "demux", 51 )
135 set_callbacks( OpenDemux
, CloseDemux
)
139 /*****************************************************************************
141 *****************************************************************************/
143 typedef struct sdp_t sdp_t
;
144 typedef struct attribute_t attribute_t
;
145 typedef struct sap_announce_t sap_announce_t
;
150 struct sdp_t
*parent
;
152 struct sockaddr_storage addr
;
156 attribute_t
**pp_attributes
;
160 /* The structure that contains sdp information */
168 uint64_t session_version
;
169 unsigned orig_ip_version
;
170 char orig_host
[1024];
173 char *psz_sessionname
;
176 char *psz_sessioninfo
;
184 /* a= global attributes */
186 attribute_t
**pp_attributes
;
188 /* medias (well, we only support one atm) */
190 struct sdp_media_t
*mediav
;
199 struct sap_announce_t
203 uint8_t i_period_trust
;
206 uint32_t i_source
[4];
208 /* SAP annnounces must only contain one SDP */
211 input_item_t
* p_item
;
214 struct services_discovery_sys_t
218 /* Socket descriptors */
222 /* Table of announces */
224 struct sap_announce_t
**pp_announces
;
238 /*****************************************************************************
240 *****************************************************************************/
244 static int Demux( demux_t
*p_demux
);
245 static int Control( demux_t
*, int, va_list );
246 static void *Run ( void *p_sd
);
248 /* Main parsing functions */
249 static int ParseConnection( vlc_object_t
*p_obj
, sdp_t
*p_sdp
);
250 static int ParseSAP( services_discovery_t
*p_sd
, const uint8_t *p_buffer
, size_t i_read
);
251 static sdp_t
*ParseSDP (vlc_object_t
*p_sd
, const char *psz_sdp
);
252 static sap_announce_t
*CreateAnnounce( services_discovery_t
*, uint32_t *, uint16_t, sdp_t
* );
253 static int RemoveAnnounce( services_discovery_t
*p_sd
, sap_announce_t
*p_announce
);
255 /* Helper functions */
256 static inline attribute_t
*MakeAttribute (const char *str
);
257 static const char *GetAttribute (attribute_t
**tab
, unsigned n
, const char *name
);
258 static inline void FreeAttribute (attribute_t
*a
);
259 static const char *FindAttribute (const sdp_t
*sdp
, unsigned media
,
262 static bool IsSameSession( sdp_t
*p_sdp1
, sdp_t
*p_sdp2
);
263 static int InitSocket( services_discovery_t
*p_sd
, const char *psz_address
, int i_port
);
264 static int Decompress( const unsigned char *psz_src
, unsigned char **_dst
, int i_len
);
265 static void FreeSDP( sdp_t
*p_sdp
);
267 static inline int min_int( int a
, int b
)
269 return a
> b
? b
: a
;
272 static bool IsWellKnownPayload (int type
)
275 { /* Should be in sync with modules/demux/rtp.c */
276 case 0: /* PCMU/8000 */
278 case 8: /* PCMA/8000 */
279 case 10: /* L16/44100/2 */
280 case 11: /* L16/44100 */
282 case 14: /* MPA/90000 */
283 case 32: /* MPV/90000 */
284 case 33: /* MP2/90000 */
290 /*****************************************************************************
291 * Open: initialize and create stuff
292 *****************************************************************************/
293 static int Open( vlc_object_t
*p_this
)
295 services_discovery_t
*p_sd
= ( services_discovery_t
* )p_this
;
296 services_discovery_sys_t
*p_sys
= (services_discovery_sys_t
*)
297 malloc( sizeof( services_discovery_sys_t
) );
301 p_sys
->i_timeout
= var_CreateGetInteger( p_sd
, "sap-timeout" );
304 p_sd
->description
= _("Network streams (SAP)");
309 p_sys
->b_strict
= var_CreateGetBool( p_sd
, "sap-strict");
310 p_sys
->b_parse
= var_CreateGetBool( p_sd
, "sap-parse" );
312 p_sys
->i_announces
= 0;
313 p_sys
->pp_announces
= NULL
;
314 /* TODO: create sockets here, and fix racy sockets table */
315 if (vlc_clone (&p_sys
->thread
, Run
, p_sd
, VLC_THREAD_PRIORITY_LOW
))
324 /*****************************************************************************
325 * OpenDemux: initialize and create stuff
326 *****************************************************************************/
327 static int OpenDemux( vlc_object_t
*p_this
)
329 demux_t
*p_demux
= (demux_t
*)p_this
;
330 const uint8_t *p_peek
;
331 char *psz_sdp
= NULL
;
333 int errval
= VLC_EGENERIC
;
336 if( !var_CreateGetBool( p_demux
, "sap-parse" ) )
338 /* We want livedotcom module to parse this SDP file */
342 assert( p_demux
->s
); /* this is NOT an access_demux */
345 if( vlc_stream_Peek( p_demux
->s
, &p_peek
, 7 ) < 7 )
348 if( memcmp( p_peek
, "v=0\r\no=", 7 ) && memcmp( p_peek
, "v=0\no=", 6 ) )
351 /* Gather the complete sdp file */
352 for( i_len
= 0, psz_sdp
= NULL
; i_len
< 65536; )
354 const int i_read_max
= 1024;
355 char *psz_sdp_new
= realloc( psz_sdp
, i_len
+ i_read_max
+ 1 );
357 if( psz_sdp_new
== NULL
)
362 psz_sdp
= psz_sdp_new
;
364 i_read
= vlc_stream_Read( p_demux
->s
, &psz_sdp
[i_len
], i_read_max
);
365 if( (int)i_read
< 0 )
367 msg_Err( p_demux
, "cannot read SDP" );
372 psz_sdp
[i_len
] = '\0';
374 if( (int)i_read
< i_read_max
)
378 p_sdp
= ParseSDP( VLC_OBJECT(p_demux
), psz_sdp
);
382 msg_Warn( p_demux
, "invalid SDP");
386 if( ParseConnection( VLC_OBJECT( p_demux
), p_sdp
) )
388 p_sdp
->psz_uri
= NULL
;
390 if (!IsWellKnownPayload (p_sdp
->i_media_type
))
392 if( p_sdp
->psz_uri
== NULL
) goto error
;
394 demux_sys_t
*p_sys
= malloc( sizeof(*p_sys
) );
395 if( unlikely(p_sys
== NULL
) )
397 p_sys
->p_sdp
= p_sdp
;
398 p_demux
->p_sys
= p_sys
;
399 p_demux
->pf_control
= Control
;
400 p_demux
->pf_demux
= Demux
;
407 if( p_sdp
) FreeSDP( p_sdp
);
411 /*****************************************************************************
413 *****************************************************************************/
414 static void Close( vlc_object_t
*p_this
)
416 services_discovery_t
*p_sd
= ( services_discovery_t
* )p_this
;
417 services_discovery_sys_t
*p_sys
= p_sd
->p_sys
;
420 vlc_cancel (p_sys
->thread
);
421 vlc_join (p_sys
->thread
, NULL
);
423 for( i
= p_sys
->i_fd
-1 ; i
>= 0 ; i
-- )
425 net_Close( p_sys
->pi_fd
[i
] );
427 FREENULL( p_sys
->pi_fd
);
429 for( i
= p_sys
->i_announces
- 1; i
>= 0; i
-- )
431 RemoveAnnounce( p_sd
, p_sys
->pp_announces
[i
] );
433 FREENULL( p_sys
->pp_announces
);
438 /*****************************************************************************
439 * CloseDemux: Close the demuxer
440 *****************************************************************************/
441 static void CloseDemux( vlc_object_t
*p_this
)
443 demux_t
*p_demux
= (demux_t
*)p_this
;
444 demux_sys_t
*sys
= p_demux
->p_sys
;
447 FreeSDP( sys
->p_sdp
);
451 /*****************************************************************************
452 * Run: main SAP thread
453 *****************************************************************************
454 * Listens to SAP packets, and sends them to packet_handle
455 *****************************************************************************/
456 #define MAX_SAP_BUFFER 5000
458 static void *Run( void *data
)
460 services_discovery_t
*p_sd
= data
;
463 int canc
= vlc_savecancel ();
465 /* Braindead Winsock DNS resolver will get stuck over 2 seconds per failed
466 * DNS queries, even if the DNS server returns an error with milliseconds.
467 * You don't want to know why the bug (as of XP SP2) wasn't fixed since
468 * Winsock 1.1 from Windows 95, if not Windows 3.1.
469 * Anyway, to avoid a 30 seconds delay for failed IPv6 socket creation,
470 * we have to open sockets in Run() rather than Open(). */
471 InitSocket( p_sd
, SAP_V4_GLOBAL_ADDRESS
, SAP_PORT
);
472 InitSocket( p_sd
, SAP_V4_ORG_ADDRESS
, SAP_PORT
);
473 InitSocket( p_sd
, SAP_V4_LOCAL_ADDRESS
, SAP_PORT
);
474 InitSocket( p_sd
, SAP_V4_LINK_ADDRESS
, SAP_PORT
);
476 char psz_address
[NI_MAXNUMERICHOST
] = "ff02::2:7ffe%";
478 struct if_nameindex
*l
= if_nameindex ();
481 char *ptr
= strchr (psz_address
, '%') + 1;
482 for (unsigned i
= 0; l
[i
].if_index
; i
++)
484 strcpy (ptr
, l
[i
].if_name
);
485 InitSocket (p_sd
, psz_address
, SAP_PORT
);
487 if_freenameindex (l
);
490 /* this is the Winsock2 equivalant of SIOCGIFCONF on BSD stacks,
491 which if_nameindex uses internally anyway */
493 // first create a dummy socket to pin down the protocol family
494 SOCKET s
= socket(PF_INET6
, SOCK_DGRAM
, IPPROTO_UDP
);
495 if( s
!= INVALID_SOCKET
)
497 INTERFACE_INFO ifaces
[10]; // Assume there will be no more than 10 IP interfaces
498 DWORD len
= sizeof(ifaces
);
500 if( SOCKET_ERROR
!= WSAIoctl(s
, SIO_GET_INTERFACE_LIST
, NULL
, 0, &ifaces
, len
, &len
, NULL
, NULL
) )
502 unsigned ifcount
= len
/sizeof(INTERFACE_INFO
);
503 char *ptr
= strchr (psz_address
, '%') + 1;
504 for(unsigned i
= 1; i
<=ifcount
; ++i
)
506 // append link-local zone identifier
507 sprintf(ptr
, "%d", i
);
513 *strchr (psz_address
, '%') = '\0';
515 static const char ipv6_scopes
[] = "1456789ABCDE";
516 for (const char *c_scope
= ipv6_scopes
; *c_scope
; c_scope
++)
518 psz_address
[3] = *c_scope
;
519 InitSocket( p_sd
, psz_address
, SAP_PORT
);
522 psz_addr
= var_CreateGetString( p_sd
, "sap-addr" );
523 if( psz_addr
&& *psz_addr
)
524 InitSocket( p_sd
, psz_addr
, SAP_PORT
);
527 if( p_sd
->p_sys
->i_fd
== 0 )
529 msg_Err( p_sd
, "unable to listen on any address" );
533 /* read SAP packets */
536 vlc_restorecancel (canc
);
537 unsigned n
= p_sd
->p_sys
->i_fd
;
538 struct pollfd ufd
[n
];
540 for (unsigned i
= 0; i
< n
; i
++)
542 ufd
[i
].fd
= p_sd
->p_sys
->pi_fd
[i
];
543 ufd
[i
].events
= POLLIN
;
547 int val
= poll (ufd
, n
, timeout
);
548 canc
= vlc_savecancel ();
551 for (unsigned i
= 0; i
< n
; i
++)
555 uint8_t p_buffer
[MAX_SAP_BUFFER
+1];
558 i_read
= recv (ufd
[i
].fd
, p_buffer
, MAX_SAP_BUFFER
, 0);
560 msg_Warn (p_sd
, "receive error: %s",
561 vlc_strerror_c(errno
));
564 /* Parse the packet */
565 p_buffer
[i_read
] = '\0';
566 ParseSAP (p_sd
, p_buffer
, i_read
);
572 mtime_t now
= mdate();
574 /* A 1 hour timeout correspond to the RFC Implicit timeout.
575 * This timeout is tuned in the following loop. */
576 timeout
= 1000 * 60 * 60;
578 /* Check for items that need deletion */
579 for( int i
= 0; i
< p_sd
->p_sys
->i_announces
; i
++ )
581 mtime_t i_timeout
= ( mtime_t
) 1000000 * p_sd
->p_sys
->i_timeout
;
582 sap_announce_t
* p_announce
= p_sd
->p_sys
->pp_announces
[i
];
583 mtime_t i_last_period
= now
- p_announce
->i_last
;
585 /* Remove the announcement, if the last announcement was 1 hour ago
586 * or if the last packet emitted was 10 times the average time
587 * between two packets */
588 if( ( p_announce
->i_period_trust
> 5 && i_last_period
> 10 * p_announce
->i_period
) ||
589 i_last_period
> i_timeout
)
591 RemoveAnnounce( p_sd
, p_announce
);
595 /* Compute next timeout */
596 if( p_announce
->i_period_trust
> 5 )
597 timeout
= min_int((10 * p_announce
->i_period
- i_last_period
) / 1000, timeout
);
598 timeout
= min_int((i_timeout
- i_last_period
)/1000, timeout
);
602 if( !p_sd
->p_sys
->i_announces
)
603 timeout
= -1; /* We can safely poll indefinitely. */
604 else if( timeout
< 200 )
605 timeout
= 200; /* Don't wakeup too fast. */
607 vlc_assert_unreachable ();
610 /**********************************************************************
611 * Demux: reads and demuxes data packets
612 * Return -1 if error, 0 if EOF, 1 else
613 **********************************************************************/
614 static int Demux( demux_t
*p_demux
)
616 demux_sys_t
*p_sys
= p_demux
->p_sys
;
617 sdp_t
*p_sdp
= p_sys
->p_sdp
;
618 input_thread_t
*p_input
= p_demux
->p_input
;
619 input_item_t
*p_parent_input
;
623 msg_Err( p_demux
, "parent input could not be found" );
627 /* This item hasn't been held by input_GetItem
628 * don't release it */
629 p_parent_input
= input_GetItem( p_input
);
631 input_item_SetURI( p_parent_input
, p_sdp
->psz_uri
);
632 input_item_SetName( p_parent_input
, p_sdp
->psz_sessionname
);
633 if( p_sdp
->rtcp_port
)
636 if( asprintf( &rtcp
, ":rtcp-port=%u", p_sdp
->rtcp_port
) != -1 )
638 input_item_AddOption( p_parent_input
, rtcp
, VLC_INPUT_OPTION_TRUSTED
);
643 vlc_mutex_lock( &p_parent_input
->lock
);
645 p_parent_input
->i_type
= ITEM_TYPE_STREAM
;
646 p_parent_input
->b_net
= true;
648 vlc_mutex_unlock( &p_parent_input
->lock
);
652 static int Control( demux_t
*p_demux
, int i_query
, va_list args
)
654 VLC_UNUSED(p_demux
); VLC_UNUSED(i_query
); VLC_UNUSED(args
);
658 /**************************************************************
660 **************************************************************/
662 /* i_read is at least > 6 */
663 static int ParseSAP( services_discovery_t
*p_sd
, const uint8_t *buf
,
667 const uint8_t *end
= buf
+ len
;
669 uint32_t i_source
[4];
671 assert (buf
[len
] == '\0');
676 uint8_t flags
= buf
[0];
677 uint8_t auth_len
= buf
[1];
679 /* First, check the sap announce is correct */
680 if ((flags
>> 5) != 1)
683 bool b_ipv6
= (flags
& 0x10) != 0;
684 bool b_need_delete
= (flags
& 0x04) != 0;
688 msg_Dbg( p_sd
, "encrypted packet, unsupported" );
692 bool b_compressed
= (flags
& 0x01) != 0;
694 uint16_t i_hash
= U16_AT (buf
+ 2);
696 if( p_sd
->p_sys
->b_strict
&& i_hash
== 0 )
698 msg_Dbg( p_sd
, "strict mode, discarding announce with null id hash");
705 for( int i
= 0; i
< 4; i
++,buf
+=4)
706 i_source
[i
] = U32_AT(buf
);
710 memset(i_source
, 0, sizeof(i_source
));
711 i_source
[3] = U32_AT(buf
);
719 uint8_t *decomp
= NULL
;
722 int newsize
= Decompress (buf
, &decomp
, end
- buf
);
725 msg_Dbg( p_sd
, "decompression of SAP packet failed" );
729 decomp
= xrealloc (decomp
, newsize
+ 1);
730 decomp
[newsize
] = '\0';
731 psz_sdp
= (const char *)decomp
;
736 psz_sdp
= (const char *)buf
;
740 /* len is a strlen here here. both buf and decomp are len+1 where the 1 should be a \0 */
741 assert( psz_sdp
[len
] == '\0');
743 /* Skip payload type */
744 /* SAPv1 has implicit "application/sdp" payload type: first line is v=0 */
745 if (strncmp (psz_sdp
, "v=0", 3))
747 size_t clen
= strlen (psz_sdp
) + 1;
749 if (strcmp (psz_sdp
, "application/sdp"))
751 msg_Dbg (p_sd
, "unsupported content type: %s", psz_sdp
);
755 // skips content type
764 p_sdp
= ParseSDP( VLC_OBJECT(p_sd
), psz_sdp
);
769 p_sdp
->psz_sdp
= psz_sdp
;
771 /* Decide whether we should add a playlist item for this SDP */
772 /* Parse connection information (c= & m= ) */
773 if( ParseConnection( VLC_OBJECT(p_sd
), p_sdp
) )
774 p_sdp
->psz_uri
= NULL
;
776 /* Multi-media or no-parse -> pass to LIVE.COM */
777 if( !IsWellKnownPayload( p_sdp
->i_media_type
) || !p_sd
->p_sys
->b_parse
)
779 free( p_sdp
->psz_uri
);
780 if (asprintf( &p_sdp
->psz_uri
, "sdp://%s", p_sdp
->psz_sdp
) == -1)
781 p_sdp
->psz_uri
= NULL
;
784 if( p_sdp
->psz_uri
== NULL
)
790 for( int i
= 0 ; i
< p_sd
->p_sys
->i_announces
; i
++ )
792 sap_announce_t
* p_announce
= p_sd
->p_sys
->pp_announces
[i
];
794 if( ( !i_hash
&& IsSameSession( p_announce
->p_sdp
, p_sdp
) )
795 || ( i_hash
&& p_announce
->i_hash
== i_hash
796 && !memcmp(p_announce
->i_source
, i_source
, sizeof(i_source
)) ) )
798 /* We don't support delete announcement as they can easily
799 * Be used to highjack an announcement by a third party.
800 * Instead we cleverly implement Implicit Announcement removal.
802 * if( b_need_delete )
803 * RemoveAnnounce( p_sd, p_sd->p_sys->pp_announces[i]);
809 /* No need to go after six, as we start to trust the
810 * average period at six */
811 if( p_announce
->i_period_trust
<= 5 )
812 p_announce
->i_period_trust
++;
814 /* Compute the average period */
815 mtime_t now
= mdate();
816 p_announce
->i_period
= ( p_announce
->i_period
* (p_announce
->i_period_trust
-1) + (now
- p_announce
->i_last
) ) / p_announce
->i_period_trust
;
817 p_announce
->i_last
= now
;
825 CreateAnnounce( p_sd
, i_source
, i_hash
, p_sdp
);
834 sap_announce_t
*CreateAnnounce( services_discovery_t
*p_sd
, uint32_t *i_source
, uint16_t i_hash
,
837 input_item_t
*p_input
;
838 const char *psz_value
;
839 sap_announce_t
*p_sap
= (sap_announce_t
*)malloc(
840 sizeof(sap_announce_t
) );
841 services_discovery_sys_t
*p_sys
;
847 p_sap
->i_last
= mdate();
849 p_sap
->i_period_trust
= 0;
850 p_sap
->i_hash
= i_hash
;
851 memcpy (p_sap
->i_source
, i_source
, sizeof(p_sap
->i_source
));
852 p_sap
->p_sdp
= p_sdp
;
854 /* Released in RemoveAnnounce */
855 p_input
= input_item_NewStream( p_sap
->p_sdp
->psz_uri
, p_sdp
->psz_sessionname
,
857 if( unlikely(p_input
== NULL
) )
862 p_sap
->p_item
= p_input
;
864 vlc_meta_t
*p_meta
= vlc_meta_New();
865 if( likely(p_meta
!= NULL
) )
867 vlc_meta_Set( p_meta
, vlc_meta_Description
, p_sdp
->psz_sessioninfo
);
868 p_input
->p_meta
= p_meta
;
871 if( p_sdp
->rtcp_port
)
874 if( asprintf( &rtcp
, ":rtcp-port=%u", p_sdp
->rtcp_port
) != -1 )
876 input_item_AddOption( p_input
, rtcp
, VLC_INPUT_OPTION_TRUSTED
);
881 psz_value
= GetAttribute( p_sap
->p_sdp
->pp_attributes
, p_sap
->p_sdp
->i_attributes
, "tool" );
882 if( psz_value
!= NULL
)
884 input_item_AddInfo( p_input
, _("Session"), _("Tool"), "%s", psz_value
);
886 if( strcmp( p_sdp
->username
, "-" ) )
888 input_item_AddInfo( p_input
, _("Session"), _("User"), "%s",
892 /* Handle category */
893 psz_value
= GetAttribute(p_sap
->p_sdp
->pp_attributes
,
894 p_sap
->p_sdp
->i_attributes
, "cat");
895 if (psz_value
!= NULL
)
897 /* a=cat provides a dot-separated hierarchy.
898 * For the time being only replace dots with pipe. TODO: FIXME */
899 char *str
= strdup(psz_value
);
900 if (likely(str
!= NULL
))
901 for (char *p
= strchr(str
, '.'); p
!= NULL
; p
= strchr(p
, '.'))
903 services_discovery_AddItemCat(p_sd
, p_input
, str
? str
: psz_value
);
908 /* backward compatibility with VLC 0.7.3-2.0.0 senders */
909 psz_value
= GetAttribute(p_sap
->p_sdp
->pp_attributes
,
910 p_sap
->p_sdp
->i_attributes
, "x-plgroup");
911 services_discovery_AddItemCat(p_sd
, p_input
, psz_value
);
914 TAB_APPEND( p_sys
->i_announces
, p_sys
->pp_announces
, p_sap
);
920 static const char *FindAttribute (const sdp_t
*sdp
, unsigned media
,
923 /* Look for media attribute, and fallback to session */
924 const char *attr
= GetAttribute (sdp
->mediav
[media
].pp_attributes
,
925 sdp
->mediav
[media
].i_attributes
, name
);
927 attr
= GetAttribute (sdp
->pp_attributes
, sdp
->i_attributes
, name
);
932 /* Fill p_sdp->psz_uri */
933 static int ParseConnection( vlc_object_t
*p_obj
, sdp_t
*p_sdp
)
935 if (p_sdp
->mediac
== 0)
937 msg_Dbg (p_obj
, "Ignoring SDP with no media");
941 for (unsigned i
= 1; i
< p_sdp
->mediac
; i
++)
943 if ((p_sdp
->mediav
[i
].n_addr
!= p_sdp
->mediav
->n_addr
)
944 || (p_sdp
->mediav
[i
].addrlen
!= p_sdp
->mediav
->addrlen
)
945 || memcmp (&p_sdp
->mediav
[i
].addr
, &p_sdp
->mediav
->addr
,
946 p_sdp
->mediav
->addrlen
))
948 msg_Dbg (p_obj
, "Multiple media ports not supported -> live555");
953 if (p_sdp
->mediav
->n_addr
!= 1)
955 msg_Dbg (p_obj
, "Layered encoding not supported -> live555");
964 if (vlc_getnameinfo ((struct sockaddr
*)&(p_sdp
->mediav
->addr
),
965 p_sdp
->mediav
->addrlen
, psz_uri
+ 1,
966 sizeof (psz_uri
) - 2, &port
, NI_NUMERICHOST
))
969 if (strchr (psz_uri
+ 1, ':'))
972 strcat (psz_uri
, "]");
978 char *sdp_proto
= strdup (p_sdp
->mediav
[0].fmt
);
979 if (sdp_proto
== NULL
)
982 char *subtype
= strchr (sdp_proto
, ' ');
985 msg_Dbg (p_obj
, "missing SDP media subtype: %s", sdp_proto
);
992 /* FIXME: check for multiple payload types in RTP/AVP case.
993 * FIXME: check for "mpeg" subtype in raw udp case. */
994 if (!strcasecmp (sdp_proto
, "udp"))
995 p_sdp
->i_media_type
= 33;
997 p_sdp
->i_media_type
= atoi (subtype
);
1000 /* RTP protocol, nul, VLC shortcut, nul, flags byte as follow:
1001 * 0x1: Connection-Oriented media. */
1002 static const char proto_match
[] =
1004 "RTP/AVP\0" "rtp\0\0"
1005 "UDPLite/RTP/AVP\0" "udplite\0\0"
1006 "DCCP/RTP/AVP\0" "dccp\0\1"
1007 "TCP/RTP/AVP\0" "rtptcp\0\1"
1010 const char *vlc_proto
= NULL
;
1012 for (const char *proto
= proto_match
; *proto
;)
1014 if (strcasecmp (proto
, sdp_proto
) == 0)
1016 vlc_proto
= proto
+ strlen (proto
) + 1;
1017 flags
= vlc_proto
[strlen (vlc_proto
) + 1];
1020 proto
+= strlen (proto
) + 1;
1021 proto
+= strlen (proto
) + 2;
1025 if (vlc_proto
== NULL
)
1027 msg_Dbg (p_obj
, "unknown SDP media protocol: %s",
1028 p_sdp
->mediav
[0].fmt
);
1029 return VLC_EGENERIC
;
1032 if (!strcmp (vlc_proto
, "udp") || FindAttribute (p_sdp
, 0, "rtcp-mux"))
1033 p_sdp
->rtcp_port
= 0;
1036 const char *rtcp
= FindAttribute (p_sdp
, 0, "rtcp");
1038 p_sdp
->rtcp_port
= atoi (rtcp
);
1040 if (port
& 1) /* odd port -> RTCP; next even port -> RTP */
1041 p_sdp
->rtcp_port
= port
++;
1042 else /* even port -> RTP; next odd port -> RTCP */
1043 p_sdp
->rtcp_port
= port
+ 1;
1048 /* Connection-oriented media */
1049 const char *setup
= FindAttribute (p_sdp
, 0, "setup");
1051 setup
= "active"; /* default value */
1053 if (strcmp (setup
, "actpass") && strcmp (setup
, "passive"))
1055 msg_Dbg (p_obj
, "unsupported COMEDIA mode: %s", setup
);
1056 return VLC_EGENERIC
;
1059 if (asprintf (&p_sdp
->psz_uri
, "%s://%s:%d", vlc_proto
,
1065 /* Non-connected (normally multicast) media */
1066 char psz_source
[258] = "";
1067 const char *sfilter
= FindAttribute (p_sdp
, 0, "source-filter");
1068 if (sfilter
!= NULL
)
1070 char psz_source_ip
[256];
1073 if (sscanf (sfilter
, " incl IN IP%u %*s %255s ", &ipv
,
1074 psz_source_ip
) == 2)
1076 /* According to RFC4570, FQDNs can be used for source-filters,
1077 * but -seriously- this is impractical */
1083 struct in6_addr addr
;
1084 if ((inet_pton (AF_INET6
, psz_source_ip
, &addr
) > 0)
1085 && (inet_ntop (AF_INET6
, &addr
, psz_source
+ 1,
1086 sizeof (psz_source
) - 2) != NULL
))
1088 psz_source
[0] = '[';
1089 psz_source
[strlen (psz_source
)] = ']';
1096 struct in_addr addr
;
1097 if ((inet_pton (AF_INET
, psz_source_ip
, &addr
) > 0)
1098 && (inet_ntop (AF_INET
, &addr
, psz_source
,
1099 sizeof (psz_source
)) == NULL
))
1107 if (asprintf (&p_sdp
->psz_uri
, "%s://%s@%s:%i", vlc_proto
, psz_source
,
1116 static int ParseSDPConnection (const char *str
, struct sockaddr_storage
*addr
,
1117 socklen_t
*addrlen
, unsigned *number
)
1120 unsigned fam
, n1
, n2
;
1122 int res
= sscanf (str
, "IN IP%u %59[^/]/%u/%u", &fam
, host
, &n1
, &n2
);
1130 addr
->ss_family
= AF_INET6
;
1134 *addrlen
= sizeof (struct sockaddr_in6
);
1136 if (inet_pton (AF_INET6
, host
,
1137 &((struct sockaddr_in6
*)addr
)->sin6_addr
) <= 0)
1140 *number
= (res
>= 3) ? n1
: 1;
1145 addr
->ss_family
= AF_INET
;
1149 *addrlen
= sizeof (struct sockaddr_in
);
1151 if (inet_pton (AF_INET
, host
,
1152 &((struct sockaddr_in
*)addr
)->sin_addr
) <= 0)
1155 *number
= (res
>= 4) ? n2
: 1;
1165 /***********************************************************************
1166 * ParseSDP : SDP parsing
1167 * *********************************************************************
1168 * Validate SDP and parse all fields
1169 ***********************************************************************/
1170 static sdp_t
*ParseSDP (vlc_object_t
*p_obj
, const char *psz_sdp
)
1172 if( psz_sdp
== NULL
)
1175 sdp_t
*p_sdp
= calloc (1, sizeof (*p_sdp
));
1180 struct sockaddr_storage glob_addr
;
1181 memset (&glob_addr
, 0, sizeof (glob_addr
));
1182 socklen_t glob_len
= 0;
1183 unsigned glob_count
= 1;
1186 /* TODO: use iconv and charset attribute instead of EnsureUTF8 */
1189 /* Extract one line */
1190 size_t linelen
= strcspn(psz_sdp
, "\n");
1191 if (psz_sdp
[linelen
] == '\0')
1194 char line
[linelen
+ 1];
1195 memcpy (line
, psz_sdp
, linelen
);
1196 line
[linelen
] = '\0';
1198 psz_sdp
+= linelen
+ 1;
1200 /* Remove carriage return if present */
1201 char *eol
= strchr (line
, '\r');
1204 linelen
= eol
- line
;
1205 line
[linelen
] = '\0';
1209 char cat
= line
[0], *data
= line
+ 2;
1210 if (!cat
|| (strchr ("vosiuepcbtrzkam", cat
) == NULL
))
1212 /* MUST ignore SDP with unknown line type */
1213 msg_Dbg (p_obj
, "unknown SDP line type: 0x%02x", (int)cat
);
1218 msg_Dbg (p_obj
, "invalid SDP line: %s", line
);
1222 assert (linelen
>= 2);
1224 /* SDP parsing state machine
1225 * We INTERNALLY use uppercase for session, lowercase for media
1229 /* Session description */
1234 msg_Dbg (p_obj
, "missing SDP version");
1237 if (strcmp (data
, "0"))
1239 msg_Dbg (p_obj
, "unknown SDP version: %s", data
);
1248 msg_Dbg (p_obj
, "missing SDP originator");
1252 if ((sscanf (data
, "%63s %"SCNu64
" %"SCNu64
" IN IP%u %1023s",
1253 p_sdp
->username
, &p_sdp
->session_id
,
1254 &p_sdp
->session_version
, &p_sdp
->orig_ip_version
,
1255 p_sdp
->orig_host
) != 5)
1256 || ((p_sdp
->orig_ip_version
!= 4)
1257 && (p_sdp
->orig_ip_version
!= 6)))
1259 msg_Dbg (p_obj
, "SDP origin not supported: %s", data
);
1260 /* Or maybe out-of-range, but this looks suspicious */
1263 EnsureUTF8 (p_sdp
->orig_host
);
1268 if ((cat
!= 's') || !*data
)
1270 /* MUST be present AND non-empty */
1271 msg_Dbg (p_obj
, "missing SDP session name");
1274 assert (p_sdp
->psz_sessionname
== NULL
); // no memleak here
1275 p_sdp
->psz_sessionname
= strdup (data
);
1276 if (p_sdp
->psz_sessionname
== NULL
)
1278 EnsureUTF8 (p_sdp
->psz_sessionname
);
1283 /* optional (and may be empty) */
1286 assert (p_sdp
->psz_sessioninfo
== NULL
);
1287 p_sdp
->psz_sessioninfo
= strdup (data
);
1288 if (p_sdp
->psz_sessioninfo
== NULL
)
1290 EnsureUTF8 (p_sdp
->psz_sessioninfo
);
1314 if (ParseSDPConnection (data
, &glob_addr
, &glob_len
,
1317 msg_Dbg (p_obj
, "SDP connection infos not supported: "
1325 assert (expect
== 'B');
1333 msg_Dbg (p_obj
, "missing SDP time description");
1339 if ((cat
== 't') || (cat
== 'r'))
1356 attribute_t
*p_attr
= MakeAttribute (data
);
1357 TAB_APPEND( p_sdp
->i_attributes
, p_sdp
->pp_attributes
, p_attr
);
1362 /* Media description */
1369 msg_Dbg (p_obj
, "missing SDP media description");
1372 struct sdp_media_t
*m
;
1373 m
= realloc (p_sdp
->mediav
, (p_sdp
->mediac
+ 1) * sizeof (*m
));
1381 memset (m
, 0, sizeof (*m
));
1382 memcpy (&m
->addr
, &glob_addr
, m
->addrlen
= glob_len
);
1383 m
->n_addr
= glob_count
;
1385 /* TODO: remember media type (if we need multiple medias) */
1386 data
= strchr (data
, ' ');
1389 msg_Dbg (p_obj
, "missing SDP media port");
1392 port
= atoi (++data
);
1393 if (port
<= 0 || port
>= 65536)
1395 msg_Dbg (p_obj
, "invalid transport port %d", port
);
1398 net_SetPort ((struct sockaddr
*)&m
->addr
, htons (port
));
1400 data
= strchr (data
, ' ');
1403 msg_Dbg (p_obj
, "missing SDP media format");
1406 m
->fmt
= strdup (++data
);
1422 struct sdp_media_t
*m
= p_sdp
->mediav
+ p_sdp
->mediac
- 1;
1423 if (ParseSDPConnection (data
, &m
->addr
, &m
->addrlen
,
1426 msg_Dbg (p_obj
, "SDP connection infos not supported: "
1430 net_SetPort ((struct sockaddr
*)&m
->addr
, htons (port
));
1445 assert (expect
== 'a');
1448 attribute_t
*p_attr
= MakeAttribute (data
);
1452 TAB_APPEND (p_sdp
->mediav
[p_sdp
->mediac
- 1].i_attributes
,
1453 p_sdp
->mediav
[p_sdp
->mediac
- 1].pp_attributes
, p_attr
);
1460 msg_Dbg (p_obj
, "unexpected SDP line: 0x%02x", (int)cat
);
1464 msg_Err (p_obj
, "*** BUG in SDP parser! ***");
1476 static int InitSocket( services_discovery_t
*p_sd
, const char *psz_address
,
1479 int i_fd
= net_ListenUDP1 ((vlc_object_t
*)p_sd
, psz_address
, i_port
);
1481 return VLC_EGENERIC
;
1483 shutdown( i_fd
, SHUT_WR
);
1484 TAB_APPEND(p_sd
->p_sys
->i_fd
, p_sd
->p_sys
->pi_fd
, i_fd
);
1488 static int Decompress( const unsigned char *psz_src
, unsigned char **_dst
, int i_len
)
1491 int i_result
, i_dstsize
, n
= 0;
1492 unsigned char *psz_dst
= NULL
;
1495 memset (&d_stream
, 0, sizeof (d_stream
));
1497 i_result
= inflateInit(&d_stream
);
1498 if( i_result
!= Z_OK
)
1501 d_stream
.next_in
= (Bytef
*)psz_src
;
1502 d_stream
.avail_in
= i_len
;
1507 psz_dst
= xrealloc( psz_dst
, n
* 1000 );
1508 d_stream
.next_out
= (Bytef
*)&psz_dst
[(n
- 1) * 1000];
1509 d_stream
.avail_out
= 1000;
1511 i_result
= inflate(&d_stream
, Z_NO_FLUSH
);
1512 if( ( i_result
!= Z_OK
) && ( i_result
!= Z_STREAM_END
) )
1514 inflateEnd( &d_stream
);
1519 while( ( d_stream
.avail_out
== 0 ) && ( d_stream
.avail_in
!= 0 ) &&
1520 ( i_result
!= Z_STREAM_END
) );
1522 i_dstsize
= d_stream
.total_out
;
1523 inflateEnd( &d_stream
);
1525 *_dst
= xrealloc( psz_dst
, i_dstsize
);
1537 static void FreeSDP( sdp_t
*p_sdp
)
1539 free( p_sdp
->psz_sessionname
);
1540 free( p_sdp
->psz_sessioninfo
);
1541 free( p_sdp
->psz_uri
);
1543 for (unsigned j
= 0; j
< p_sdp
->mediac
; j
++)
1545 free (p_sdp
->mediav
[j
].fmt
);
1546 for (int i
= 0; i
< p_sdp
->mediav
[j
].i_attributes
; i
++)
1547 FreeAttribute (p_sdp
->mediav
[j
].pp_attributes
[i
]);
1548 free (p_sdp
->mediav
[j
].pp_attributes
);
1550 free (p_sdp
->mediav
);
1552 for (int i
= 0; i
< p_sdp
->i_attributes
; i
++)
1553 FreeAttribute (p_sdp
->pp_attributes
[i
]);
1555 free (p_sdp
->pp_attributes
);
1559 static int RemoveAnnounce( services_discovery_t
*p_sd
,
1560 sap_announce_t
*p_announce
)
1562 if( p_announce
->p_sdp
)
1564 FreeSDP( p_announce
->p_sdp
);
1565 p_announce
->p_sdp
= NULL
;
1568 if( p_announce
->p_item
)
1570 services_discovery_RemoveItem( p_sd
, p_announce
->p_item
);
1571 input_item_Release( p_announce
->p_item
);
1572 p_announce
->p_item
= NULL
;
1575 TAB_REMOVE(p_sd
->p_sys
->i_announces
, p_sd
->p_sys
->pp_announces
,
1583 * Compare two sessions, when hash is not set (SAP v0)
1585 static bool IsSameSession( sdp_t
*p_sdp1
, sdp_t
*p_sdp2
)
1587 /* A session is identified by
1590 * - network type (which is always IN),
1591 * - address type (currently, this means IP version),
1594 if (strcmp (p_sdp1
->username
, p_sdp2
->username
)
1595 || (p_sdp1
->session_id
!= p_sdp2
->session_id
)
1596 || (p_sdp1
->orig_ip_version
!= p_sdp2
->orig_ip_version
)
1597 || strcmp (p_sdp1
->orig_host
, p_sdp2
->orig_host
))
1603 static inline attribute_t
*MakeAttribute (const char *str
)
1605 attribute_t
*a
= malloc (sizeof (*a
) + strlen (str
) + 1);
1609 strcpy (a
->name
, str
);
1610 EnsureUTF8 (a
->name
);
1611 char *value
= strchr (a
->name
, ':');
1623 static const char *GetAttribute (attribute_t
**tab
, unsigned n
,
1626 for (unsigned i
= 0; i
< n
; i
++)
1627 if (strcasecmp (tab
[i
]->name
, name
) == 0)
1628 return tab
[i
]->value
;
1633 static inline void FreeAttribute (attribute_t
*a
)