X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fservices_discovery%2Fsap.c;h=a7cde36d8d4cd4d238da5aa14250c7e8efe3791c;hb=72fa5a9d8e8f5a1bbe0d83ffd1f30b3265a531c7;hp=e4d9c1fb2e2c144b62fa50c3d7b17826e2263636;hpb=21f58438e96a63e117fd1572d8715e71adad9dc8;p=vlc diff --git a/modules/services_discovery/sap.c b/modules/services_discovery/sap.c index e4d9c1fb2e..a7cde36d8d 100644 --- a/modules/services_discovery/sap.c +++ b/modules/services_discovery/sap.c @@ -26,12 +26,16 @@ /***************************************************************************** * Includes *****************************************************************************/ -#define _GNU_SOURCE -#include /* malloc(), free() */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include -#include -#include #include +#include #include #include @@ -45,11 +49,18 @@ #ifdef HAVE_SYS_TIME_H # include #endif +#ifdef HAVE_POLL +# include +#endif #ifdef HAVE_ZLIB_H # include #endif +#ifndef WIN32 +# include +#endif + /************************************************************************ * Macros and definitions ************************************************************************/ @@ -68,14 +79,6 @@ #define SAP_V4_LINK_ADDRESS "224.0.0.255" #define ADD_SESSION 1 -#define SAP_V6_1 "FF0" -/* Scope is inserted between them */ -#define SAP_V6_2 "::2:7FFE" -/* See RFC3513 for list of valid scopes */ -/* FIXME: find a way to listen to link-local scope */ -static const char ipv6_scopes[] = "1456789ABCDE"; - - /***************************************************************************** * Module descriptor *****************************************************************************/ @@ -85,12 +88,10 @@ static const char ipv6_scopes[] = "1456789ABCDE"; "can specify a specific address." ) #define SAP_IPV4_TEXT N_( "IPv4 SAP" ) #define SAP_IPV4_LONGTEXT N_( \ - "Listen to IPv4 announcements " \ - "on the standard address." ) + "Listen to IPv4 announcements on the standard addresses." ) #define SAP_IPV6_TEXT N_( "IPv6 SAP" ) #define SAP_IPV6_LONGTEXT N_( \ - "Listen to IPv6 announcements " \ - "on the standard addresses." ) + "Listen to IPv6 announcements on the standard addresses." ) #define SAP_SCOPE_TEXT N_( "IPv6 SAP scope" ) #define SAP_SCOPE_LONGTEXT N_( \ "Scope for IPv6 announcements (default is 8)." ) @@ -101,7 +102,7 @@ static const char ipv6_scopes[] = "1456789ABCDE"; #define SAP_PARSE_TEXT N_( "Try to parse the announce" ) #define SAP_PARSE_LONGTEXT N_( \ "This enables actual parsing of the announces by the SAP module. " \ - "Otherwise, all announcements are parsed by the \"livedotcom\" " \ + "Otherwise, all announcements are parsed by the \"live555\" " \ "(RTP/RTSP) module." ) #define SAP_STRICT_TEXT N_( "SAP Strict mode" ) #define SAP_STRICT_LONGTEXT N_( \ @@ -112,9 +113,6 @@ static const char ipv6_scopes[] = "1456789ABCDE"; "This enables a SAP caching mechanism. " \ "This will result in lower SAP startup time, but you could end up " \ "with items corresponding to legacy streams." ) -#define SAP_TIMESHIFT_TEXT N_("Allow timeshifting") -#define SAP_TIMESHIFT_LONGTEXT N_( "This automatically enables timeshifting " \ - "for streams discovered through SAP announcements." ) /* Callbacks */ static int Open ( vlc_object_t * ); @@ -122,40 +120,39 @@ static const char ipv6_scopes[] = "1456789ABCDE"; static int OpenDemux ( vlc_object_t * ); static void CloseDemux ( vlc_object_t * ); -vlc_module_begin(); - set_shortname( _("SAP")); - set_description( _("SAP Announcements") ); - set_category( CAT_PLAYLIST ); - set_subcategory( SUBCAT_PLAYLIST_SD ); +vlc_module_begin () + set_shortname( N_("SAP")) + set_description( N_("SAP Announcements") ) + set_category( CAT_PLAYLIST ) + set_subcategory( SUBCAT_PLAYLIST_SD ) add_string( "sap-addr", NULL, NULL, - SAP_ADDR_TEXT, SAP_ADDR_LONGTEXT, VLC_TRUE ); + SAP_ADDR_TEXT, SAP_ADDR_LONGTEXT, true ) add_bool( "sap-ipv4", 1 , NULL, - SAP_IPV4_TEXT,SAP_IPV4_LONGTEXT, VLC_TRUE ); + SAP_IPV4_TEXT,SAP_IPV4_LONGTEXT, true ) add_bool( "sap-ipv6", 1 , NULL, - SAP_IPV6_TEXT, SAP_IPV6_LONGTEXT, VLC_TRUE ); + SAP_IPV6_TEXT, SAP_IPV6_LONGTEXT, true ) add_integer( "sap-timeout", 1800, NULL, - SAP_TIMEOUT_TEXT, SAP_TIMEOUT_LONGTEXT, VLC_TRUE ); + SAP_TIMEOUT_TEXT, SAP_TIMEOUT_LONGTEXT, true ) add_bool( "sap-parse", 1 , NULL, - SAP_PARSE_TEXT,SAP_PARSE_LONGTEXT, VLC_TRUE ); + SAP_PARSE_TEXT,SAP_PARSE_LONGTEXT, true ) add_bool( "sap-strict", 0 , NULL, - SAP_STRICT_TEXT,SAP_STRICT_LONGTEXT, VLC_TRUE ); + SAP_STRICT_TEXT,SAP_STRICT_LONGTEXT, true ) #if 0 add_bool( "sap-cache", 0 , NULL, - SAP_CACHE_TEXT,SAP_CACHE_LONGTEXT, VLC_TRUE ); + SAP_CACHE_TEXT,SAP_CACHE_LONGTEXT, true ) #endif - add_bool( "sap-timeshift", 0 , NULL, - SAP_TIMESHIFT_TEXT,SAP_TIMESHIFT_LONGTEXT, VLC_TRUE ); + add_obsolete_bool( "sap-timeshift" ) /* Redumdant since 1.0.0 */ - set_capability( "services_discovery", 0 ); - set_callbacks( Open, Close ); + set_capability( "services_discovery", 0 ) + set_callbacks( Open, Close ) - add_submodule(); - set_description( _("SDP file parser for UDP") ); - add_shortcut( "sdp" ); - set_capability( "demux2", 51 ); - set_callbacks( OpenDemux, CloseDemux ); -vlc_module_end(); + add_submodule () + set_description( N_("SDP Descriptions parser") ) + add_shortcut( "sdp" ) + set_capability( "demux", 51 ) + set_callbacks( OpenDemux, CloseDemux ) +vlc_module_end () /***************************************************************************** @@ -198,6 +195,7 @@ struct sdp_t /* "computed" URI */ char *psz_uri; int i_media_type; + unsigned rtcp_port; /* a= global attributes */ int i_attributes; @@ -205,18 +203,20 @@ struct sdp_t /* medias (well, we only support one atm) */ unsigned mediac; - struct sdp_media_t mediav[1]; + struct sdp_media_t *mediav; }; struct attribute_t { const char *value; - char name[0]; + char name[]; }; struct sap_announce_t { mtime_t i_last; + mtime_t i_period; + uint8_t i_period_trust; uint16_t i_hash; uint32_t i_source[4]; @@ -224,29 +224,24 @@ struct sap_announce_t /* SAP annnounces must only contain one SDP */ sdp_t *p_sdp; - int i_input_id; - int i_item_id_cat; - int i_item_id_one; + input_item_t * p_item; }; struct services_discovery_sys_t { + vlc_thread_t thread; + /* Socket descriptors */ int i_fd; int *pi_fd; - /* playlist node */ - playlist_item_t *p_node_cat; - playlist_item_t *p_node_one; - /* Table of announces */ int i_announces; struct sap_announce_t **pp_announces; /* Modes */ - vlc_bool_t b_strict; - vlc_bool_t b_parse; - vlc_bool_t b_timeshift; + bool b_strict; + bool b_parse; int i_timeout; }; @@ -264,7 +259,7 @@ struct demux_sys_t /* Main functions */ static int Demux( demux_t *p_demux ); static int Control( demux_t *, int, va_list ); - static void Run ( services_discovery_t *p_sd ); + static void *Run ( void *p_sd ); /* Main parsing functions */ static int ParseConnection( vlc_object_t *p_obj, sdp_t *p_sdp ); @@ -277,12 +272,19 @@ struct demux_sys_t 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); + static const char *FindAttribute (const sdp_t *sdp, unsigned media, + const char *name); - static vlc_bool_t IsSameSession( sdp_t *p_sdp1, sdp_t *p_sdp2 ); + static bool IsSameSession( sdp_t *p_sdp1, sdp_t *p_sdp2 ); static int InitSocket( services_discovery_t *p_sd, const char *psz_address, int i_port ); static int Decompress( const unsigned char *psz_src, unsigned char **_dst, int i_len ); static void FreeSDP( sdp_t *p_sdp ); +static inline int min_int( int a, int b ) +{ + return a > b ? b : a; +} + /***************************************************************************** * Open: initialize and create stuff *****************************************************************************/ @@ -291,10 +293,11 @@ static int Open( vlc_object_t *p_this ) services_discovery_t *p_sd = ( services_discovery_t* )p_this; services_discovery_sys_t *p_sys = (services_discovery_sys_t *) malloc( sizeof( services_discovery_sys_t ) ); + if( !p_sys ) + return VLC_ENOMEM; p_sys->i_timeout = var_CreateGetInteger( p_sd, "sap-timeout" ); - p_sd->pf_run = Run; p_sd->p_sys = p_sys; p_sys->pi_fd = NULL; @@ -310,19 +313,14 @@ static int Open( vlc_object_t *p_this ) } #endif - /* Cache sap_timeshift value */ - p_sys->b_timeshift = var_CreateGetInteger( p_sd, "sap-timeshift" ) - ? VLC_TRUE : VLC_FALSE; - - /* Create our playlist node */ - pl_Yield( p_sd ); - - playlist_NodesPairCreate( pl_Get( p_sd ), _("SAP sessions"), - &p_sys->p_node_cat, &p_sys->p_node_one, - VLC_TRUE ); - p_sys->p_node_cat->p_input->b_prefers_tree = VLC_TRUE; p_sys->i_announces = 0; p_sys->pp_announces = NULL; + /* 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; + } return VLC_SUCCESS; } @@ -333,11 +331,11 @@ static int Open( vlc_object_t *p_this ) static int OpenDemux( vlc_object_t *p_this ) { demux_t *p_demux = (demux_t *)p_this; - uint8_t *p_peek; - int i_max_sdp = 1024; - int i_sdp = 0; + const uint8_t *p_peek; char *psz_sdp = NULL; sdp_t *p_sdp = NULL; + int errval = VLC_EGENERIC; + size_t i_len; if( !var_CreateGetInteger( p_demux, "sap-parse" ) ) { @@ -345,44 +343,40 @@ static int OpenDemux( vlc_object_t *p_this ) return VLC_EGENERIC; } - /* Probe for SDP */ - if( p_demux->s ) - { - if( stream_Peek( p_demux->s, &p_peek, 7 ) < 7 ) return VLC_EGENERIC; + assert( p_demux->s ); /* this is NOT an access_demux */ - 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; - } - } + /* Probe for SDP */ + if( stream_Peek( p_demux->s, &p_peek, 7 ) < 7 ) + return VLC_EGENERIC; - psz_sdp = (char *)malloc( i_max_sdp ); - if( !psz_sdp ) return VLC_EGENERIC; + if( memcmp( p_peek, "v=0\r\no=", 7 ) && memcmp( p_peek, "v=0\no=", 6 ) ) + return VLC_EGENERIC; /* Gather the complete sdp file */ - for( ;; ) + for( i_len = 0, psz_sdp = NULL; i_len < 65536; ) { - int i_read = stream_Read( p_demux->s, - &psz_sdp[i_sdp], i_max_sdp - i_sdp - 1 ); - - if( i_read < 0 ) + const int i_read_max = 1024; + char *psz_sdp_new = realloc( psz_sdp, i_len + i_read_max ); + size_t i_read; + if( psz_sdp_new == NULL ) { - msg_Err( p_demux, "failed to read SDP" ); + errval = VLC_ENOMEM; goto error; } + psz_sdp = psz_sdp_new; - i_sdp += i_read; - - if( i_read < i_max_sdp - i_sdp - 1 ) + i_read = stream_Read( p_demux->s, &psz_sdp[i_len], i_read_max ); + if( (int)i_read < 0 ) { - psz_sdp[i_sdp] = '\0'; - break; + msg_Err( p_demux, "cannot read SDP" ); + goto error; } + i_len += i_read; - i_max_sdp += 1000; - psz_sdp = (char *)realloc( psz_sdp, i_max_sdp ); + psz_sdp[i_len] = '\0'; + + if( (int)i_read < i_read_max ) + break; // EOF } p_sdp = ParseSDP( VLC_OBJECT(p_demux), psz_sdp ); @@ -397,10 +391,19 @@ static int OpenDemux( vlc_object_t *p_this ) { p_sdp->psz_uri = NULL; } - if( p_sdp->i_media_type != 33 && p_sdp->i_media_type != 32 && - p_sdp->i_media_type != 14 ) - goto error; - + switch (p_sdp->i_media_type) + { /* Should be in sync with modules/demux/rtp.c */ + case 0: /* PCMU/8000 */ + case 8: /* PCMA/8000 */ + case 10: /* L16/44100/2 */ + case 11: /* L16/44100 */ + case 14: /* MPA/90000 */ + case 32: /* MPV/90000 */ + case 33: /* MP2/90000 */ + break; + default: + goto error; + } if( p_sdp->psz_uri == NULL ) goto error; p_demux->p_sys = (demux_sys_t *)malloc( sizeof(demux_sys_t) ); @@ -415,7 +418,7 @@ error: FREENULL( psz_sdp ); if( p_sdp ) FreeSDP( p_sdp ); p_sdp = NULL; stream_Seek( p_demux->s, 0 ); - return VLC_EGENERIC; + return errval; } /***************************************************************************** @@ -425,9 +428,11 @@ static void Close( vlc_object_t *p_this ) { services_discovery_t *p_sd = ( services_discovery_t* )p_this; services_discovery_sys_t *p_sys = p_sd->p_sys; - int i; + vlc_cancel (p_sys->thread); + vlc_join (p_sys->thread, NULL); + for( i = p_sys->i_fd-1 ; i >= 0 ; i-- ) { net_Close( p_sys->pi_fd[i] ); @@ -447,11 +452,6 @@ static void Close( vlc_object_t *p_this ) } FREENULL( p_sys->pp_announces ); - 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 ); free( p_sys ); } @@ -475,10 +475,13 @@ static void CloseDemux( vlc_object_t *p_this ) *****************************************************************************/ #define MAX_SAP_BUFFER 5000 -static void Run( services_discovery_t *p_sd ) +static void *Run( void *data ) { + services_discovery_t *p_sd = data; char *psz_addr; int i; + int timeout = -1; + int canc = vlc_savecancel (); /* Braindead Winsock DNS resolver will get stuck over 2 seconds per failed * DNS queries, even if the DNS server returns an error with milliseconds. @@ -495,65 +498,140 @@ static void Run( services_discovery_t *p_sd ) } if( var_CreateGetInteger( p_sd, "sap-ipv6" ) ) { - char psz_address[] = SAP_V6_1"0"SAP_V6_2; - const char *c_scope; + char psz_address[NI_MAXNUMERICHOST] = "ff02::2:7ffe%"; + +#ifndef WIN32 + 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); + } +#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 + size_t len = sizeof(ifaces); + + 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 + *strchr (psz_address, '%') = '\0'; - for( c_scope = ipv6_scopes; *c_scope; c_scope++ ) + static const char ipv6_scopes[] = "1456789ABCDE"; + for (const char *c_scope = ipv6_scopes; *c_scope; c_scope++) { - psz_address[sizeof(SAP_V6_1) - 1] = *c_scope; + psz_address[3] = *c_scope; InitSocket( p_sd, psz_address, SAP_PORT ); } } psz_addr = var_CreateGetString( p_sd, "sap-addr" ); if( psz_addr && *psz_addr ) - { InitSocket( p_sd, psz_addr, SAP_PORT ); - free( psz_addr ); - } + free( psz_addr ); if( p_sd->p_sys->i_fd == 0 ) { msg_Err( p_sd, "unable to listen on any address" ); - return; + return NULL; } /* read SAP packets */ - while( !p_sd->b_die ) + for (;;) { - int i_read; - uint8_t p_buffer[MAX_SAP_BUFFER+1]; + vlc_restorecancel (canc); + unsigned n = p_sd->p_sys->i_fd; + struct pollfd ufd[n]; - i_read = net_Select( p_sd, p_sd->p_sys->pi_fd, - p_sd->p_sys->i_fd, p_buffer, - MAX_SAP_BUFFER ); - - /* Check for items that need deletion */ - for( i = 0; i < p_sd->p_sys->i_announces; i++ ) + for (unsigned i = 0; i < n; i++) { - mtime_t i_timeout = ( mtime_t ) 1000000 * p_sd->p_sys->i_timeout; + ufd[i].fd = p_sd->p_sys->pi_fd[i]; + ufd[i].events = POLLIN; + ufd[i].revents = 0; + } - if( mdate() - p_sd->p_sys->pp_announces[i]->i_last > i_timeout ) + int val = poll (ufd, n, timeout); + canc = vlc_savecancel (); + if (val > 0) + { + for (unsigned i = 0; i < n; i++) { - RemoveAnnounce( p_sd, p_sd->p_sys->pp_announces[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, + MAX_SAP_BUFFER, false); + 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); + } + } } } - /* Minimum length is > 6 */ - if( i_read <= 6 ) + mtime_t now = mdate(); + + /* A 1 hour timeout correspong to the RFC Implicit timeout. + * This timeout is tuned in the following loop. */ + timeout = 1000 * 60 * 60; + + /* Check for items that need deletion */ + for( i = 0; i < p_sd->p_sys->i_announces; i++ ) { - if( i_read < 0 ) + mtime_t i_timeout = ( mtime_t ) 1000000 * p_sd->p_sys->i_timeout; + sap_announce_t * p_announce = p_sd->p_sys->pp_announces[i]; + mtime_t i_last_period = now - p_announce->i_last; + + /* 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 ) { - msg_Warn( p_sd, "socket read error" ); + RemoveAnnounce( p_sd, p_announce ); + } + else + { + /* Compute next timeout */ + if( p_announce->i_period_trust > 5 ) + timeout = min_int((3 * p_announce->i_period - i_last_period) / 1000, timeout); + timeout = min_int((i_timeout - i_last_period)/1000, timeout); } - continue; } - p_buffer[i_read] = '\0'; - - /* Parse the packet */ - ParseSAP( p_sd, p_buffer, i_read ); + 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. */ } + assert (0); } /********************************************************************** @@ -566,7 +644,6 @@ static int Demux( demux_t *p_demux ) input_thread_t *p_input; input_item_t *p_parent_input; - playlist_t *p_playlist = pl_Yield( p_demux ); p_input = (input_thread_t *)vlc_object_find( p_demux, VLC_OBJECT_INPUT, FIND_PARENT ); assert( p_input ); @@ -576,31 +653,33 @@ static int Demux( demux_t *p_demux ) return VLC_EGENERIC; } - p_parent_input = input_GetItem(p_input); + /* This item hasn't been held by input_GetItem + * don't release it */ + p_parent_input = input_GetItem( p_input ); - vlc_mutex_lock( &p_parent_input->lock ); - FREENULL( p_parent_input->psz_uri ); - p_parent_input->psz_uri = strdup( p_sdp->psz_uri ); - FREENULL( p_parent_input->psz_name ); - p_parent_input->psz_name = strdup( p_sdp->psz_sessionname ); - p_parent_input->i_type = ITEM_TYPE_NET; - - if( p_playlist->status.p_item && - p_playlist->status.p_item->p_input == p_parent_input ) + input_item_SetURI( p_parent_input, p_sdp->psz_uri ); + input_item_SetName( p_parent_input, p_sdp->psz_sessionname ); + if( p_sdp->rtcp_port ) { - playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VLC_TRUE, - p_playlist->status.p_node, p_playlist->status.p_item ); + char *rtcp; + if( asprintf( &rtcp, ":rtcp-port=%u", p_sdp->rtcp_port ) != -1 ) + { + input_item_AddOption( p_parent_input, rtcp, VLC_INPUT_OPTION_TRUSTED ); + free( rtcp ); + } } - vlc_mutex_unlock( &p_parent_input->lock ); - vlc_object_release( p_input ); - vlc_object_release( p_playlist ); + vlc_mutex_lock( &p_parent_input->lock ); + + p_parent_input->i_type = ITEM_TYPE_NET; + vlc_mutex_unlock( &p_parent_input->lock ); return VLC_SUCCESS; } static int Control( demux_t *p_demux, int i_query, va_list args ) { + VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args); return VLC_EGENERIC; } @@ -628,8 +707,8 @@ static int ParseSAP( services_discovery_t *p_sd, const uint8_t *buf, if ((flags >> 5) != 1) return VLC_EGENERIC; - vlc_bool_t b_ipv6 = (flags & 0x10) != 0; - vlc_bool_t b_need_delete = (flags & 0x04) != 0; + bool b_ipv6 = (flags & 0x10) != 0; + bool b_need_delete = (flags & 0x04) != 0; if (flags & 0x02) { @@ -637,7 +716,7 @@ static int ParseSAP( services_discovery_t *p_sd, const uint8_t *buf, return VLC_EGENERIC; } - vlc_bool_t b_compressed = (flags & 0x01) != 0; + bool b_compressed = (flags & 0x01) != 0; uint16_t i_hash = U16_AT (buf + 2); @@ -713,28 +792,46 @@ static int ParseSAP( services_discovery_t *p_sd, const uint8_t *buf, 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 ) + || p_sd->p_sys->b_parse == false ) { free( p_sdp->psz_uri ); if (asprintf( &p_sdp->psz_uri, "sdp://%s", p_sdp->psz_sdp ) == -1) p_sdp->psz_uri = NULL; } - if( p_sdp->psz_uri == NULL ) return VLC_EGENERIC; + if( p_sdp->psz_uri == NULL ) + { + FreeSDP( p_sdp ); + return VLC_EGENERIC; + } for( i = 0 ; i< p_sd->p_sys->i_announces ; i++ ) { + sap_announce_t * p_announce = p_sd->p_sys->pp_announces[i]; /* FIXME: slow */ /* FIXME: we create a new announce each time the sdp changes */ - if( IsSameSession( p_sd->p_sys->pp_announces[i]->p_sdp, p_sdp ) ) + if( IsSameSession( p_announce->p_sdp, p_sdp ) ) { - if( b_need_delete ) + /* We don't support delete announcement as they can easily + * Be used to highjack an announcement by a third party. + * Intead we cleverly implement Implicit Announcement removal. + * + * if( b_need_delete ) + * RemoveAnnounce( p_sd, p_sd->p_sys->pp_announces[i]); + * else + */ + + if( !b_need_delete ) { - RemoveAnnounce( p_sd, p_sd->p_sys->pp_announces[i]); - } - else - { - p_sd->p_sys->pp_announces[i]->i_last = mdate(); + /* No need to go after six, as we start to trust the + * average period at six */ + if( p_announce->i_period_trust <= 5 ) + p_announce->i_period_trust++; + + /* Compute the average period */ + mtime_t now = mdate(); + p_announce->i_period = (p_announce->i_period + (now - p_announce->i_last)) / 2; + p_announce->i_last = now; } FreeSDP( p_sdp ); p_sdp = NULL; return VLC_SUCCESS; @@ -751,7 +848,6 @@ sap_announce_t *CreateAnnounce( services_discovery_t *p_sd, uint16_t i_hash, sdp_t *p_sdp ) { input_item_t *p_input; - playlist_item_t *p_item, *p_child; const char *psz_value; sap_announce_t *p_sap = (sap_announce_t *)malloc( sizeof(sap_announce_t ) ); @@ -762,68 +858,51 @@ sap_announce_t *CreateAnnounce( services_discovery_t *p_sd, uint16_t i_hash, p_sys = p_sd->p_sys; p_sap->i_last = mdate(); + p_sap->i_period = 0; + p_sap->i_period_trust = 0; p_sap->i_hash = i_hash; p_sap->p_sdp = p_sdp; - /* Create the actual playlist item here */ - p_input = input_ItemNewWithType( VLC_OBJECT(p_sd), + /* Released in RemoveAnnounce */ + p_input = input_item_NewWithType( 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; + 0, NULL, 0, -1, ITEM_TYPE_NET ); + p_sap->p_item = p_input; if( !p_input ) { free( p_sap ); return NULL; } - if( p_sys->b_timeshift ) - input_ItemAddOption( p_input, ":access-filter=timeshift" ); + if( p_sdp->rtcp_port ) + { + char *rtcp; + if( asprintf( &rtcp, ":rtcp-port=%u", p_sdp->rtcp_port ) != -1 ) + { + input_item_AddOption( p_input, rtcp, VLC_INPUT_OPTION_TRUSTED ); + free( rtcp ); + } + } psz_value = GetAttribute( p_sap->p_sdp->pp_attributes, p_sap->p_sdp->i_attributes, "tool" ); if( psz_value != NULL ) { - input_ItemAddInfo( p_input, _("Session"),_("Tool"), psz_value ); + input_item_AddInfo( p_input, _("Session"), _("Tool"), "%s", psz_value ); } if( strcmp( p_sdp->username, "-" ) ) { - input_ItemAddInfo( p_input, _("Session"), - _("User"), p_sdp->username ); + input_item_AddInfo( p_input, _("Session"), _("User"), "%s", + p_sdp->username ); } /* Handle group */ - psz_value = GetAttribute( p_sap->p_sdp->pp_attributes, p_sap->p_sdp->i_attributes, "x-plgroup" ); - if( psz_value == NULL ) - psz_value = GetAttribute( p_sap->p_sdp->pp_attributes, p_sap->p_sdp->i_attributes, "plgroup" ); - - if( psz_value != NULL ) - { - p_child = playlist_ChildSearchName( p_sys->p_node_cat, psz_value ); - - if( p_child == NULL ) - { - p_child = playlist_NodeCreate( pl_Get( p_sd ), psz_value, - p_sys->p_node_cat, 0 ); - p_child->i_flags &= ~PLAYLIST_SKIP_FLAG; - } - } + if (p_sap->p_sdp->mediac >= 1) + psz_value = FindAttribute (p_sap->p_sdp, 0, "x-plgroup"); else - { - p_child = p_sys->p_node_cat; - } + psz_value = GetAttribute( p_sap->p_sdp->pp_attributes, p_sap->p_sdp->i_attributes, "x-plgroup" ); - p_item = playlist_NodeAddInput( pl_Get( p_sd ), p_input, p_child, - PLAYLIST_APPEND, PLAYLIST_END, VLC_FALSE ); - p_item->i_flags &= ~PLAYLIST_SKIP_FLAG; - p_item->i_flags &= ~PLAYLIST_SAVE_FLAG; - p_sap->i_item_id_cat = p_item->i_id; - - p_item = playlist_NodeAddInput( pl_Get( p_sd ), p_input, - p_sys->p_node_one, PLAYLIST_APPEND, PLAYLIST_END, - VLC_FALSE ); - p_item->i_flags &= ~PLAYLIST_SKIP_FLAG; - p_item->i_flags &= ~PLAYLIST_SAVE_FLAG; - p_sap->i_item_id_one = p_item->i_id; + services_discovery_AddItem( p_sd, p_input, psz_value /* category name */ ); TAB_APPEND( p_sys->i_announces, p_sys->pp_announces, p_sap ); @@ -844,23 +923,44 @@ static const char *FindAttribute (const sdp_t *sdp, unsigned media, /* Fill p_sdp->psz_uri */ static int ParseConnection( vlc_object_t *p_obj, sdp_t *p_sdp ) { - if (p_sdp->mediac != 1) + if (p_sdp->mediac == 0) + { + msg_Dbg (p_obj, "Ignoring SDP with no media"); return VLC_EGENERIC; + } + + for (unsigned i = 1; i < p_sdp->mediac; i++) + { + if ((p_sdp->mediav[i].n_addr != p_sdp->mediav->n_addr) + || (p_sdp->mediav[i].addrlen != p_sdp->mediav->addrlen) + || memcmp (&p_sdp->mediav[i].addr, &p_sdp->mediav->addr, + p_sdp->mediav->addrlen)) + { + msg_Dbg (p_obj, "Multiple media ports not supported -> live555"); + return VLC_EGENERIC; + } + } + + if (p_sdp->mediav->n_addr != 1) + { + msg_Dbg (p_obj, "Layered encoding not supported -> live555"); + return VLC_EGENERIC; + } char psz_uri[1026]; const char *host; int port; psz_uri[0] = '['; - if (vlc_getnameinfo ((struct sockaddr *)&(p_sdp->mediav[0].addr), - p_sdp->mediav[0].addrlen, psz_uri + 1, + if (vlc_getnameinfo ((struct sockaddr *)&(p_sdp->mediav->addr), + p_sdp->mediav->addrlen, psz_uri + 1, sizeof (psz_uri) - 2, &port, NI_NUMERICHOST)) return VLC_EGENERIC; if (strchr (psz_uri + 1, ':')) { host = psz_uri; - psz_uri[strlen (psz_uri)] = ']'; + strcat (psz_uri, "]"); } else host = psz_uri + 1; @@ -871,18 +971,22 @@ static int ParseConnection( vlc_object_t *p_obj, sdp_t *p_sdp ) return VLC_ENOMEM; char *subtype = strchr (sdp_proto, ' '); - if (sdp_proto == NULL) + if (subtype == NULL) { msg_Dbg (p_obj, "missing SDP media subtype: %s", sdp_proto); - p_sdp->i_media_type = 0; + free (sdp_proto); + return VLC_EGENERIC; } else { *subtype++ = '\0'; - p_sdp->i_media_type = atoi (subtype); + /* FIXME: check for multiple payload types in RTP/AVP case. + * FIXME: check for "mpeg" subtype in raw udp case. */ + if (!strcasecmp (sdp_proto, "udp")) + p_sdp->i_media_type = 33; + else + p_sdp->i_media_type = atoi (subtype); } - if (p_sdp->i_media_type == 0) - p_sdp->i_media_type = 33; /* RTP protocol, nul, VLC shortcut, nul, flags byte as follow: * 0x1: Connection-Oriented media. */ @@ -898,7 +1002,7 @@ static int ParseConnection( vlc_object_t *p_obj, sdp_t *p_sdp ) uint8_t flags = 0; for (const char *proto = proto_match; *proto;) { - if (strcasecmp (proto, sdp_proto)) + if (strcasecmp (proto, sdp_proto) == 0) { vlc_proto = proto + strlen (proto) + 1; flags = vlc_proto[strlen (vlc_proto) + 1]; @@ -916,6 +1020,20 @@ static int ParseConnection( vlc_object_t *p_obj, sdp_t *p_sdp ) return VLC_EGENERIC; } + if (!strcmp (vlc_proto, "udp") || FindAttribute (p_sdp, 0, "rtcp-mux")) + p_sdp->rtcp_port = 0; + else + { + const char *rtcp = FindAttribute (p_sdp, 0, "rtcp"); + if (rtcp) + p_sdp->rtcp_port = atoi (rtcp); + else + if (port & 1) /* odd port -> RTCP; next even port -> RTP */ + p_sdp->rtcp_port = port++; + else /* even port -> RTP; next odd port -> RTCP */ + p_sdp->rtcp_port = port + 1; + } + if (flags & 1) { /* Connection-oriented media */ @@ -936,7 +1054,6 @@ static int ParseConnection( vlc_object_t *p_obj, sdp_t *p_sdp ) else { /* Non-connected (normally multicast) media */ - char psz_source[258] = ""; const char *sfilter = FindAttribute (p_sdp, 0, "source-filter"); if (sfilter != NULL) @@ -948,7 +1065,7 @@ static int ParseConnection( vlc_object_t *p_obj, sdp_t *p_sdp ) psz_source_ip) == 2) { /* According to RFC4570, FQDNs can be used for source-filters, - * but -seriously- this is impractical */ + * but -seriously- this is impractical */ switch (ipv) { #ifdef AF_INET6 @@ -1055,6 +1172,7 @@ static sdp_t *ParseSDP (vlc_object_t *p_obj, const char *psz_sdp) memset (&glob_addr, 0, sizeof (glob_addr)); socklen_t glob_len = 0; unsigned glob_count = 1; + int port = 0; /* TODO: use iconv and charset attribute instead of EnsureUTF8 */ while (*psz_sdp) @@ -1121,14 +1239,14 @@ static sdp_t *ParseSDP (vlc_object_t *p_obj, const char *psz_sdp) goto error; } - if ((sscanf (data, "%63s "I64Fu" "I64Fu" IN IP%u %1023s", + if ((sscanf (data, "%63s %"PRIu64" %"PRIu64" IN IP%u %1023s", p_sdp->username, &p_sdp->session_id, &p_sdp->session_version, &p_sdp->orig_ip_version, p_sdp->orig_host) != 5) || ((p_sdp->orig_ip_version != 4) && (p_sdp->orig_ip_version != 6))) { - msg_Dbg (p_obj, "SDP origin not supported: %s\n", data); + msg_Dbg (p_obj, "SDP origin not supported: %s", data); /* Or maybe out-of-range, but this looks suspicious */ return NULL; } @@ -1147,9 +1265,9 @@ static sdp_t *ParseSDP (vlc_object_t *p_obj, const char *psz_sdp) } assert (p_sdp->psz_sessionname == NULL); // no memleak here p_sdp->psz_sessionname = strdup (data); - EnsureUTF8 (p_sdp->psz_sessionname); if (p_sdp->psz_sessionname == NULL) goto error; + EnsureUTF8 (p_sdp->psz_sessionname); break; } @@ -1218,6 +1336,7 @@ static sdp_t *ParseSDP (vlc_object_t *p_obj, const char *psz_sdp) /* Media description */ case 'm': + media: { expect = 'i'; if (cat != 'm') @@ -1225,8 +1344,16 @@ static sdp_t *ParseSDP (vlc_object_t *p_obj, const char *psz_sdp) msg_Dbg (p_obj, "missing SDP media description"); goto error; } - struct sdp_media_t *m = p_sdp->mediav + p_sdp->mediac; + struct sdp_media_t *m; + m = realloc (p_sdp->mediav, (p_sdp->mediac + 1) * sizeof (*m)); + if (m == NULL) + goto error; + p_sdp->mediav = m; + m += p_sdp->mediac; + p_sdp->mediac++; + + memset (m, 0, sizeof (*m)); memcpy (&m->addr, &glob_addr, m->addrlen = glob_len); m->n_addr = glob_count; @@ -1237,7 +1364,7 @@ static sdp_t *ParseSDP (vlc_object_t *p_obj, const char *psz_sdp) msg_Dbg (p_obj, "missing SDP media port"); goto error; } - int port = atoi (++data); + port = atoi (++data); if (port <= 0 || port >= 65536) { msg_Dbg (p_obj, "invalid transport port %d", port); @@ -1255,7 +1382,6 @@ static sdp_t *ParseSDP (vlc_object_t *p_obj, const char *psz_sdp) if (m->fmt == NULL) goto error; - p_sdp->mediac++; break; } case 'i': @@ -1274,6 +1400,7 @@ static sdp_t *ParseSDP (vlc_object_t *p_obj, const char *psz_sdp) "%s", data); goto error; } + net_SetPort ((struct sockaddr *)&m->addr, htons (port)); break; } case 'b': @@ -1298,11 +1425,7 @@ static sdp_t *ParseSDP (vlc_object_t *p_obj, const char *psz_sdp) } if (cat == 'm') - { - /* TODO */ - msg_Dbg (p_obj, "multi-media SDP not implemented -> live555"); - goto error; - } + goto media; if (cat != 'm') { @@ -1331,7 +1454,7 @@ static int InitSocket( services_discovery_t *p_sd, const char *psz_address, if (i_fd == -1) return VLC_EGENERIC; - net_StopSend( i_fd ); + shutdown( i_fd, SHUT_WR ); INSERT_ELEM (p_sd->p_sys->pi_fd, p_sd->p_sys->i_fd, p_sd->p_sys->i_fd, i_fd); return VLC_SUCCESS; @@ -1395,7 +1518,9 @@ static void FreeSDP( sdp_t *p_sdp ) free (p_sdp->mediav[j].fmt); for (int i = 0; i < p_sdp->mediav[j].i_attributes; i++) FreeAttribute (p_sdp->mediav[j].pp_attributes[i]); + free (p_sdp->mediav[j].pp_attributes); } + free (p_sdp->mediav); for (int i = 0; i < p_sdp->i_attributes; i++) FreeAttribute (p_sdp->pp_attributes[i]); @@ -1415,8 +1540,12 @@ static int RemoveAnnounce( services_discovery_t *p_sd, p_announce->p_sdp = NULL; } - if( p_announce->i_input_id > -1 ) - playlist_DeleteFromInput( pl_Get(p_sd), p_announce->i_input_id, VLC_FALSE ); + if( p_announce->p_item ) + { + services_discovery_RemoveItem( p_sd, p_announce->p_item ); + vlc_gc_decref( p_announce->p_item ); + p_announce->p_item = NULL; + } for( i = 0; i< p_sd->p_sys->i_announces; i++) { @@ -1433,7 +1562,7 @@ static int RemoveAnnounce( services_discovery_t *p_sd, return VLC_SUCCESS; } -static vlc_bool_t IsSameSession( sdp_t *p_sdp1, sdp_t *p_sdp2 ) +static bool IsSameSession( sdp_t *p_sdp1, sdp_t *p_sdp2 ) { /* A session is identified by * - username, @@ -1446,9 +1575,9 @@ static vlc_bool_t IsSameSession( sdp_t *p_sdp1, sdp_t *p_sdp2 ) || (p_sdp1->session_id != p_sdp2->session_id) || (p_sdp1->orig_ip_version != p_sdp2->orig_ip_version) || strcmp (p_sdp1->orig_host, p_sdp2->orig_host)) - return VLC_FALSE; + return false; - return VLC_TRUE; + return true; }