1 /*****************************************************************************
2 * sap.c : SAP interface module
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: sap.c,v 1.38 2003/11/22 12:33:04 sigmunau Exp $
7 * Authors: Arnaud Schauly <gitan@via.ecp.fr>
8 * Clément Stenac <zorglub@via.ecp.fr>
9 * Damien Lucas <nitrox@videolan.org>
10 * Laurent Aimar <fenrir@via.ecp.fr>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
25 *****************************************************************************/
27 /*****************************************************************************
29 *****************************************************************************/
30 #include <stdlib.h> /* malloc(), free() */
35 #include <errno.h> /* ENOMEM */
41 #ifdef HAVE_SYS_TIME_H
42 # include <sys/time.h>
46 # include <winsock2.h>
47 # include <ws2tcpip.h>
49 # define IN_MULTICAST(a) IN_CLASSD(a)
52 # include <sys/socket.h>
53 # include <netinet/in.h>
55 # include <arpa/inet.h>
56 # elif defined( SYS_BEOS )
57 # include <net/netdb.h>
62 # define close(a) CloseHandle(a)
63 #elif defined( WIN32 )
64 # define close(a) closesocket(a)
73 #define MAX_LINE_LENGTH 256
75 /* SAP is always on that port */
76 #define HELLO_PORT 9875
77 #define HELLO_GROUP "224.2.127.254"
80 #define IPV6_ADDR_1 "FF0" /* Scope is inserted between them */
81 #define IPV6_ADDR_2 "::2:7FFE"
84 /*****************************************************************************
86 *****************************************************************************/
87 #define SAP_ADDR_TEXT N_("SAP multicast address")
88 #define SAP_ADDR_LONGTEXT N_("SAP multicast address")
89 #define SAP_IPV4_TEXT N_("IPv4-SAP listening")
90 #define SAP_IPV4_LONGTEXT N_("Set this if you want SAP to listen for IPv4 announces")
91 #define SAP_IPV6_TEXT N_("IPv6-SAP listening")
92 #define SAP_IPV6_LONGTEXT N_("Set this if you want SAP to listen for IPv6 announces")
93 #define SAP_SCOPE_TEXT N_("IPv6 SAP scope")
94 #define SAP_SCOPE_LONGTEXT N_("Sets the scope for IPv6 announces (default is 8)")
96 static int Open ( vlc_object_t * );
97 static void Close( vlc_object_t * );
100 add_category_hint( N_("SAP"), NULL, VLC_TRUE );
101 add_string( "sap-addr", NULL, NULL,
102 SAP_ADDR_TEXT, SAP_ADDR_LONGTEXT, VLC_TRUE );
104 add_bool( "sap-ipv4", 1 , NULL,
105 SAP_IPV4_TEXT,SAP_IPV4_LONGTEXT, VLC_TRUE);
107 add_bool( "sap-ipv6", 0 , NULL,
108 SAP_IPV6_TEXT, SAP_IPV6_LONGTEXT, VLC_TRUE);
110 add_string( "sap-ipv6-scope", "8" , NULL,
111 SAP_SCOPE_TEXT, SAP_SCOPE_LONGTEXT, VLC_TRUE);
113 set_description( _("SAP interface") );
114 set_capability( "interface", 0 );
115 set_callbacks( Open, Close );
118 /*****************************************************************************
120 *****************************************************************************/
122 static void Run ( intf_thread_t *p_intf );
123 static ssize_t NetRead( intf_thread_t *, int fd[2], uint8_t *, int );
125 typedef struct media_descr_t media_descr_t;
126 typedef struct sess_descr_t sess_descr_t;
127 typedef struct attr_descr_t attr_descr_t;
129 static void sess_toitem( intf_thread_t *, sess_descr_t * );
131 static sess_descr_t * parse_sdp( intf_thread_t *, char * ) ;
132 static void free_sd( sess_descr_t * );
134 /* Detect multicast addresses */
135 static int ismult( char * );
137 /* The struct that contains sdp informations */
141 char *psz_sessionname;
142 char *psz_connection;
146 media_descr_t **pp_media;
148 attr_descr_t **pp_attributes;
151 /* All this informations are not useful yet. */
155 char *psz_mediaconnection;
174 int do_decompress(unsigned char *src, unsigned char **_dst, int slen) {
175 int result, dstsize, n;
179 d_stream.zalloc = (alloc_func)0;
180 d_stream.zfree = (free_func)0;
181 d_stream.opaque = (voidpf)0;
182 result = inflateInit(&d_stream);
183 if (result != Z_OK) {
184 printf("inflateInit() failed. Result: %d\n", result);
188 d_stream.next_in = (Bytef *)src;
189 d_stream.avail_in = slen;
194 dst = (unsigned char *)realloc(dst, n * 1000);
195 d_stream.next_out = (Bytef *)&dst[(n - 1) * 1000];
196 d_stream.avail_out = 1000;
197 result = inflate(&d_stream, Z_NO_FLUSH);
198 if ((result != Z_OK) && (result != Z_STREAM_END)) {
199 printf("Zlib decompression failed. Result: %d\n", result);
202 } while ((d_stream.avail_out == 0) && (d_stream.avail_in != 0) &&
203 (result != Z_STREAM_END));
205 dstsize = d_stream.total_out;
206 inflateEnd(&d_stream);
208 *_dst = (unsigned char *)realloc(dst, dstsize);
214 /*****************************************************************************
215 * Open: initialize and create stuff
216 *****************************************************************************/
217 static int Open( vlc_object_t *p_this )
219 intf_thread_t *p_intf = (intf_thread_t*)p_this;
220 intf_sys_t *p_sys = malloc( sizeof( intf_sys_t ) );
222 playlist_t *p_playlist;
226 if( config_GetInt( p_intf, "sap-ipv4" ) )
228 char *psz_address = config_GetPsz( p_intf, "sap-addr" );
229 network_socket_t sock;
231 if( psz_address == NULL || *psz_address == '\0' )
233 psz_address = strdup( HELLO_GROUP );
236 /* Prepare the network_socket_t structure */
237 sock.i_type = NETWORK_UDP;
238 sock.psz_bind_addr = psz_address;
239 sock.i_bind_port = HELLO_PORT;
240 sock.psz_server_addr = "";
241 sock.i_server_port = 0;
243 p_intf->p_private = (void*) &sock;
245 p_network = module_Need( p_intf, "network", "ipv4" );
248 p_sys->fd[0] = sock.i_handle;
249 module_Unneed( p_intf, p_network );
253 msg_Warn( p_intf, "failed to open %s:%d", psz_address, HELLO_PORT );
258 if( config_GetInt( p_intf, "sap-ipv6" ) )
260 char psz_address[100];
261 char *psz_scope = config_GetPsz( p_intf, "sap-ipv6-scope" );
262 network_socket_t sock;
265 if( psz_scope == NULL || *psz_scope == '\0' )
267 psz_scope = strdup( "8" );
269 snprintf( psz_address, 100, "[%s%c%s]",IPV6_ADDR_1, psz_scope[0], IPV6_ADDR_2 );
272 sock.i_type = NETWORK_UDP;
273 sock.psz_bind_addr = psz_address;
274 sock.i_bind_port = HELLO_PORT;
275 sock.psz_server_addr = "";
276 sock.i_server_port = 0;
278 p_intf->p_private = (void*) &sock;
280 p_network = module_Need( p_intf, "network", "ipv6" );
283 p_sys->fd[1] = sock.i_handle;
284 module_Unneed( p_intf, p_network );
288 msg_Warn( p_intf, "failed to open %s:%d", psz_address, HELLO_PORT );
291 if( p_sys->fd[0] <= 0 && p_sys->fd[1] <= 0 )
293 msg_Warn( p_intf, "IPV4 and IPV6 failed" );
298 /* Create our playlist group */
299 p_playlist = (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
303 playlist_group_t *p_group = playlist_CreateGroup( p_playlist , "SAP" );
304 p_sys->i_group = p_group->i_id;
305 vlc_object_release( p_playlist );
308 p_intf->pf_run = Run;
309 p_intf->p_sys = p_sys;
314 /*****************************************************************************
316 *****************************************************************************/
317 static void Close( vlc_object_t *p_this )
319 intf_thread_t *p_intf = (intf_thread_t*)p_this;
320 intf_sys_t *p_sys = p_intf->p_sys;
322 if( p_sys->fd[0] > 0 )
324 close( p_sys->fd[0] );
326 if( p_sys->fd[1] > 0 )
328 close( p_sys->fd[1] );
334 /*****************************************************************************
336 *****************************************************************************
337 * Listens to SAP packets, and sends them to packet_handle
338 *****************************************************************************/
339 #define MAX_SAP_BUFFER 5000
341 static void Run( intf_thread_t *p_intf )
343 intf_sys_t *p_sys = p_intf->p_sys;
344 uint8_t buffer[MAX_SAP_BUFFER + 1];
347 /* read SAP packets */
348 while( !p_intf->b_die )
350 int i_read = NetRead( p_intf, p_sys->fd, buffer, MAX_SAP_BUFFER );
359 unsigned char *p_decompressed_buffer;
360 int i_decompressed_size;
362 /* Minimum length is > 6 */
367 msg_Warn( p_intf, "Cannot read in the socket" );
372 buffer[i_read] = '\0';
373 p_end = &buffer[i_read];
375 /* Parse the SAP header */
376 i_version = buffer[0] >> 5;
379 msg_Warn( p_intf, "strange sap version %d found", i_version );
381 i_address_type = buffer[0] & 0x10;
382 b_reserved = buffer[0] & 0x08;
383 if( b_reserved != 0 )
385 msg_Warn( p_intf, "reserved bit incorrectly set" );
387 b_message_type = buffer[0] & 0x04;
388 if( b_message_type != 0 )
390 msg_Warn( p_intf, "got session deletion packet" );
392 b_encrypted = buffer[0] & 0x02;
395 msg_Warn( p_intf, "encrypted packet" );
397 b_compressed = buffer[0] & 0x01;
399 if( i_address_type == 0 ) /* ipv4 source address */
403 else /* ipv6 source address */
410 i_decompressed_size = do_decompress( p_sdp, &p_decompressed_buffer, i_read - ( p_sdp - buffer ) );
411 if( i_decompressed_size > 0 && i_decompressed_size < MAX_SAP_BUFFER )
413 memcpy( p_sdp, p_decompressed_buffer, i_decompressed_size );
414 p_sdp[i_decompressed_size] = '\0';
415 p_end = &p_sdp[i_decompressed_size];
416 free( p_decompressed_buffer );
419 msg_Warn( p_intf, "Ignoring compressed sap packet" );
422 p_sdp += buffer[1]; /* size of signature */
423 while( p_sdp < p_end - 1 && *p_sdp != '\0' && p_sdp[0] != 'v' && p_sdp[1] != '=' )
434 sess_descr_t *p_sd = parse_sdp( p_intf, p_sdp );
437 sess_toitem ( p_intf, p_sd );
443 msg_Warn( p_intf, "ditching sap packet" );
448 /**********************************************************************
450 *********************************************************************
451 * put into *ppsz_uri, the the uri in the cfield, psz_cfield.
452 *********************************************************************/
454 static void cfield_parse( char *psz_cfield, char **ppsz_uri )
460 psz_pos = psz_cfield;
462 while( *psz_pos != ' ' && *psz_pos !='\0' )
467 while( *psz_pos != ' ' && *psz_pos !='\0' )
473 while( *psz_pos != ' ' && *psz_pos !='/'
474 && *psz_pos != '\0' )
490 /**********************************************************************
492 *********************************************************************
493 * put into *ppsz_proto, and *ppsz_port, the protocol and the port.
494 *********************************************************************/
497 static void mfield_parse( char *psz_mfield, char **ppsz_proto,
504 psz_pos = psz_mfield;
505 psz_media = psz_mfield;
506 while( *psz_pos != '\0' && *psz_pos != ' ' )
510 if( *psz_pos != '\0' )
513 if( strcmp( psz_media, "video" ) && strcmp( psz_media, "audio" ) )
521 *ppsz_port = psz_pos;
522 while( *psz_pos != '\0' && *psz_pos !=' ' && *psz_pos!='/' )
526 if( *psz_pos == '/' ) // FIXME does not support multi-port
530 while( *psz_pos != '\0' && *psz_pos !=' ' )
537 *ppsz_proto = psz_pos;
538 while( *psz_pos!='\0' && *psz_pos !=' ' &&
541 *psz_pos = tolower( *psz_pos );
555 /*******************************************************************
556 * sess_toitem : changes a sess_descr_t into a hurd of
557 * playlist_item_t, which are enqueued.
558 *******************************************************************
559 * Note : does not support sessions that take place on consecutive
560 * port or adresses yet.
561 *******************************************************************/
563 static void sess_toitem( intf_thread_t * p_intf, sess_descr_t * p_sd )
565 playlist_item_t * p_item;
566 char *psz_uri, *psz_proto;
568 char *psz_uri_default;
570 vlc_bool_t b_http = VLC_FALSE;
571 char *psz_http_path = NULL;
572 playlist_t *p_playlist = NULL;
574 psz_uri_default = NULL;
575 if( p_sd->i_media > 1 )
577 p_item = malloc( sizeof( playlist_item_t ) );
580 msg_Warn( p_intf, "out of memory" );
583 p_item->psz_name = strdup( p_sd->psz_sessionname );
584 p_item->psz_uri = NULL;
585 p_item->i_duration = -1;
586 p_item->ppsz_options= NULL;
587 p_item->i_options = 0;
590 p_item->i_status = 0;
591 p_item->b_autodeletion = VLC_FALSE;
592 p_item->b_enabled = VLC_TRUE;
593 p_item->i_group = p_intf->p_sys->i_group;
594 p_item->psz_author = strdup( "" );
595 psz_uri = malloc( strlen( p_sd->psz_sdp ) + 7 );
596 if( psz_uri == NULL )
598 msg_Warn( p_intf, "out of memory" );
599 free( p_item->psz_name );
600 free( p_item->psz_author );
604 p_item->psz_uri = psz_uri;
605 memcpy( psz_uri, "sdp://", 6 );
607 memcpy( psz_uri, p_sd->psz_sdp, strlen( p_sd->psz_sdp ) + 1 );
608 /* Enqueueing p_item in the playlist */
609 p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
610 playlist_AddItem ( p_playlist, p_item, PLAYLIST_CHECK_INSERT, PLAYLIST_END );
611 vlc_object_release( p_playlist );
615 cfield_parse( p_sd->psz_connection, &psz_uri_default );
617 for( i_count = 0 ; i_count < p_sd->i_media ; i_count++ )
619 p_item = malloc( sizeof( playlist_item_t ) );
620 p_item->psz_name = strdup( p_sd->psz_sessionname );
621 p_item->psz_uri = NULL;
622 p_item->i_duration = -1;
623 p_item->ppsz_options= NULL;
624 p_item->i_options = 0;
627 p_item->i_status = 0;
628 p_item->b_autodeletion = VLC_FALSE;
629 p_item->b_enabled = VLC_TRUE;
630 p_item->i_group = p_intf->p_sys->i_group;
631 p_item->psz_author = strdup( "" );
635 /* Build what we have to put in p_item->psz_uri, with the m and
638 if( !p_sd->pp_media[i_count] )
643 mfield_parse( p_sd->pp_media[i_count]->psz_medianame,
644 & psz_proto, & psz_port );
646 if( !psz_proto || !psz_port )
651 if( p_sd->pp_media[i_count]->psz_mediaconnection )
653 cfield_parse( p_sd->pp_media[i_count]->psz_mediaconnection,
658 psz_uri = psz_uri_default;
661 if( psz_uri == NULL )
666 for( i = 0 ; i< p_sd->i_attributes ; i++ )
668 if(!strcasecmp( p_sd->pp_attributes[i]->psz_field , "type") &&
669 strstr( p_sd->pp_attributes[i]->psz_value, "http") )
673 if(!strcasecmp( p_sd->pp_attributes[i]->psz_field , "http-path"))
675 psz_http_path = strdup( p_sd->pp_attributes[i]->psz_value );
677 if(!strcasecmp( p_sd->pp_attributes[i]->psz_field , "plgroup"))
681 (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
683 if( p_playlist == NULL )
688 i_id = playlist_GroupToId( p_playlist,
689 p_sd->pp_attributes[i]->psz_value);
692 p_item->i_group = i_id;
696 playlist_group_t *p_group =
697 playlist_CreateGroup( p_playlist,
698 p_sd->pp_attributes[i]->psz_value);
699 p_item->i_group = p_group->i_id;
701 vlc_object_release( p_playlist );
706 /* Filling p_item->psz_uri */
707 if( b_http == VLC_FALSE )
709 p_item->psz_uri = malloc( strlen( psz_proto ) + strlen( psz_uri ) +
710 strlen( psz_port ) + 7 );
711 if( ismult( psz_uri ) )
713 sprintf( p_item->psz_uri, "%s://@%s:%s",
714 psz_proto, psz_uri, psz_port );
718 sprintf( p_item->psz_uri, "%s://%s:%s",
719 psz_proto, psz_uri, psz_port );
724 if( psz_http_path == NULL )
726 psz_http_path = strdup( "/" );
729 p_item->psz_uri = malloc( strlen( psz_proto ) + strlen( psz_uri ) +
730 strlen( psz_port ) + strlen(psz_http_path) + 5 );
731 sprintf( p_item->psz_uri, "%s://%s:%s%s", psz_proto,
732 psz_uri, psz_port,psz_http_path );
736 free( psz_http_path );
740 /* Enqueueing p_item in the playlist */
741 p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
742 playlist_AddItem ( p_playlist, p_item, PLAYLIST_CHECK_INSERT, PLAYLIST_END );
743 vlc_object_release( p_playlist );
747 /***********************************************************************
748 * parse_sdp : SDP parsing
749 * *********************************************************************
750 * Make a sess_descr_t with a psz
751 ***********************************************************************/
753 static sess_descr_t * parse_sdp( intf_thread_t * p_intf, char *p_packet )
757 if( p_packet[0] != 'v' || p_packet[1] != '=' )
759 msg_Warn(p_intf, "bad SDP packet");
763 sd = malloc( sizeof( sess_descr_t ) );
764 sd->psz_sessionname = NULL;
765 sd->psz_connection = NULL;
766 sd->psz_sdp = strdup( p_packet );
770 sd->i_attributes = 0;
771 sd->pp_attributes = NULL;
773 while( *p_packet != '\0' )
777 /* Search begin of field */
778 while( *p_packet == '\n' || *p_packet == ' ' || *p_packet == '\t' )
782 /* search end of line */
783 if( ( psz_end = strchr( p_packet, '\n' ) ) == NULL )
785 psz_end = p_packet + strlen( p_packet );
787 if( psz_end > p_packet && *(psz_end - 1 ) == '\r' )
792 if( psz_end <= p_packet )
798 if( p_packet[1] != '=' )
800 msg_Warn( p_intf, "packet invalid" );
805 switch( p_packet[0] )
808 sd->i_version = atoi( &p_packet[2] );
811 sd->psz_sessionname = strdup( &p_packet[2] );
823 char *psz_eof = strchr( &p_packet[2], ':' );
825 if( psz_eof && psz_eof[1] != '\0' )
827 attr_descr_t *attr = malloc( sizeof( attr_descr_t ) );
831 attr->psz_field = strdup( &p_packet[2] );
832 attr->psz_value = strdup( psz_eof );
834 TAB_APPEND( sd->i_attributes, sd->pp_attributes, attr );
841 media_descr_t *media = malloc( sizeof( media_descr_t ) );
843 media->psz_medianame = strdup( &p_packet[2] );
844 media->psz_mediaconnection = NULL;
846 TAB_APPEND( sd->i_media, sd->pp_media, media );
851 if( sd->i_media <= 0 )
853 sd->psz_connection = strdup( &p_packet[2] );
857 sd->pp_media[sd->i_media-1]->psz_mediaconnection = strdup( &p_packet[2] );
872 if( p ) { free( p ); (p) = NULL; }
873 static void free_sd( sess_descr_t * p_sd )
877 FREE( p_sd->psz_sessionname );
878 FREE( p_sd->psz_connection );
879 FREE( p_sd->psz_sdp );
881 for( i = 0; i < p_sd->i_media ; i++ )
883 FREE( p_sd->pp_media[i]->psz_medianame );
884 FREE( p_sd->pp_media[i]->psz_mediaconnection );
885 FREE( p_sd->pp_media[i] );
887 for( i = 0; i < p_sd->i_attributes ; i++ )
889 FREE( p_sd->pp_attributes[i]->psz_field );
890 FREE( p_sd->pp_attributes[i]->psz_value );
891 FREE( p_sd->pp_attributes[i] );
893 FREE( p_sd->pp_attributes );
894 FREE( p_sd->pp_media );
899 /***********************************************************************
900 * ismult: returns true if we have a multicast address
901 ***********************************************************************/
903 static int ismult( char *psz_uri )
908 i_value = strtol( psz_uri, &psz_end, 0 );
911 if( psz_uri[0] == '[')
913 if( strncasecmp( &psz_uri[1], "FF0" , 3) ||
914 strncasecmp( &psz_uri[2], "FF0" , 3))
920 if( *psz_end != '.' ) { return( VLC_FALSE ); }
922 return( i_value < 224 ? VLC_FALSE : VLC_TRUE );
927 /*****************************************************************************
928 * Read: read on a file descriptor, checking b_die periodically
929 *****************************************************************************
931 *****************************************************************************/
932 static ssize_t NetRead( intf_thread_t *p_intf,
933 int fd[2], uint8_t *p_buffer, int i_len )
938 struct timeval timeout;
941 int i_handle_max = __MAX( fd[0], fd[1] );
943 /* Initialize file descriptor set */
945 if( fd[0] > 0 ) FD_SET( fd[0], &fds );
946 if( fd[1] > 0 ) FD_SET( fd[1], &fds );
948 /* We'll wait 0.5 second if nothing happens */
950 timeout.tv_usec = 500000;
952 /* Find if some data is available */
953 i_ret = select( i_handle_max + 1, &fds, NULL, NULL, &timeout );
955 if( i_ret == -1 && errno != EINTR )
957 msg_Err( p_intf, "network select error (%s)", strerror(errno) );
961 if( fd[0] > 0 && FD_ISSET( fd[0], &fds ) )
963 return recv( fd[0], p_buffer, i_len, 0 );
965 else if( fd[1] > 0 && FD_ISSET( fd[1], &fds ) )
967 return recv( fd[1], p_buffer, i_len, 0 );