1 /*****************************************************************************
2 * sap.c : SAP interface module
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: sap.c,v 1.32 2003/11/08 12:25:59 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)
69 #define MAX_LINE_LENGTH 256
71 /* SAP is always on that port */
72 #define HELLO_PORT 9875
73 #define HELLO_GROUP "224.2.127.254"
76 #define IPV6_ADDR_1 "FF0" /* Scope is inserted between them */
77 #define IPV6_ADDR_2 "::2:7FFE"
80 /*****************************************************************************
82 *****************************************************************************/
83 #define SAP_ADDR_TEXT N_("SAP multicast address")
84 #define SAP_ADDR_LONGTEXT N_("SAP multicast address")
85 #define SAP_IPV4_TEXT N_("IPv4-SAP listening")
86 #define SAP_IPV4_LONGTEXT N_("Set this if you want SAP to listen for IPv4 announces")
87 #define SAP_IPV6_TEXT N_("IPv6-SAP listening")
88 #define SAP_IPV6_LONGTEXT N_("Set this if you want SAP to listen for IPv6 announces")
89 #define SAP_SCOPE_TEXT N_("IPv6 SAP scope")
90 #define SAP_SCOPE_LONGTEXT N_("Sets the scope for IPv6 announces (default is 8)")
92 static int Open ( vlc_object_t * );
93 static void Close( vlc_object_t * );
96 add_category_hint( N_("SAP"), NULL, VLC_TRUE );
97 add_string( "sap-addr", NULL, NULL,
98 SAP_ADDR_TEXT, SAP_ADDR_LONGTEXT, VLC_TRUE );
100 add_bool( "sap-ipv4", 1 , NULL,
101 SAP_IPV4_TEXT,SAP_IPV4_LONGTEXT, VLC_TRUE);
103 add_bool( "sap-ipv6", 0 , NULL,
104 SAP_IPV6_TEXT, SAP_IPV6_LONGTEXT, VLC_TRUE);
106 add_string( "sap-ipv6-scope", "8" , NULL,
107 SAP_SCOPE_TEXT, SAP_SCOPE_LONGTEXT, VLC_TRUE);
109 set_description( _("SAP interface") );
110 set_capability( "interface", 0 );
111 set_callbacks( Open, Close );
114 /*****************************************************************************
116 *****************************************************************************/
118 static void Run ( intf_thread_t *p_intf );
119 static ssize_t NetRead( intf_thread_t *, int fd[2], uint8_t *, int );
121 typedef struct media_descr_t media_descr_t;
122 typedef struct sess_descr_t sess_descr_t;
123 typedef struct attr_descr_t attr_descr_t;
125 static void sess_toitem( intf_thread_t *, sess_descr_t * );
127 static sess_descr_t * parse_sdp( intf_thread_t *, char * ) ;
128 static void free_sd( sess_descr_t * );
130 /* Detect multicast addresses */
131 static int ismult( char * );
133 /* The struct that contains sdp informations */
137 char *psz_sessionname;
138 char *psz_connection;
141 media_descr_t **pp_media;
143 attr_descr_t **pp_attributes;
146 /* All this informations are not useful yet. */
150 char *psz_mediaconnection;
168 /*****************************************************************************
169 * Open: initialize and create stuff
170 *****************************************************************************/
171 static int Open( vlc_object_t *p_this )
173 intf_thread_t *p_intf = (intf_thread_t*)p_this;
174 intf_sys_t *p_sys = malloc( sizeof( intf_sys_t ) );
176 playlist_t *p_playlist;
180 if( config_GetInt( p_intf, "sap-ipv4" ) )
182 char *psz_address = config_GetPsz( p_intf, "sap-addr" );
183 network_socket_t sock;
185 if( psz_address == NULL || *psz_address == '\0' )
187 psz_address = strdup( HELLO_GROUP );
190 /* Prepare the network_socket_t structure */
191 sock.i_type = NETWORK_UDP;
192 sock.psz_bind_addr = psz_address;
193 sock.i_bind_port = HELLO_PORT;
194 sock.psz_server_addr = "";
195 sock.i_server_port = 0;
197 p_intf->p_private = (void*) &sock;
199 p_network = module_Need( p_intf, "network", "ipv4" );
202 p_sys->fd[0] = sock.i_handle;
203 module_Unneed( p_intf, p_network );
207 msg_Warn( p_intf, "failed to open %s:%d", psz_address, HELLO_PORT );
212 if( config_GetInt( p_intf, "sap-ipv6" ) )
214 char psz_address[100];
215 char *psz_scope = config_GetPsz( p_intf, "sap-ipv6-scope" );
216 network_socket_t sock;
219 if( psz_scope == NULL || *psz_scope == '\0' )
221 psz_scope = strdup( "8" );
223 snprintf( psz_address, 100, "[%s%c%s]",IPV6_ADDR_1, psz_scope[0], IPV6_ADDR_2 );
226 sock.i_type = NETWORK_UDP;
227 sock.psz_bind_addr = psz_address;
228 sock.i_bind_port = HELLO_PORT;
229 sock.psz_server_addr = "";
230 sock.i_server_port = 0;
232 p_intf->p_private = (void*) &sock;
234 p_network = module_Need( p_intf, "network", "ipv6" );
237 p_sys->fd[1] = sock.i_handle;
238 module_Unneed( p_intf, p_network );
242 msg_Warn( p_intf, "failed to open %s:%d", psz_address, HELLO_PORT );
245 if( p_sys->fd[0] <= 0 && p_sys->fd[1] <= 0 )
247 msg_Warn( p_intf, "IPV4 and IPV6 failed" );
252 /* Create our playlist group */
253 p_playlist = (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
257 playlist_group_t *p_group = playlist_CreateGroup( p_playlist , "SAP" );
258 p_sys->i_group = p_group->i_id;
259 vlc_object_release( p_playlist );
262 p_intf->pf_run = Run;
263 p_intf->p_sys = p_sys;
268 /*****************************************************************************
270 *****************************************************************************/
271 static void Close( vlc_object_t *p_this )
273 intf_thread_t *p_intf = (intf_thread_t*)p_this;
274 intf_sys_t *p_sys = p_intf->p_sys;
276 if( p_sys->fd[0] > 0 )
278 close( p_sys->fd[0] );
280 if( p_sys->fd[1] > 0 )
282 close( p_sys->fd[1] );
288 /*****************************************************************************
290 *****************************************************************************
291 * Listens to SAP packets, and sends them to packet_handle
292 *****************************************************************************/
293 #define MAX_SAP_BUFFER 2000
295 static void Run( intf_thread_t *p_intf )
297 intf_sys_t *p_sys = p_intf->p_sys;
298 uint8_t buffer[MAX_SAP_BUFFER + 1];
300 /* read SAP packets */
301 while( !p_intf->b_die )
303 int i_read = NetRead( p_intf, p_sys->fd, buffer, MAX_SAP_BUFFER );
306 /* Minimum length is > 6 */
311 msg_Warn( p_intf, "Cannot read in the socket" );
316 buffer[i_read] = '\0';
318 /* Parse the SAP header */
320 p_sdp += (buffer[0]&0x10) ? 16 : 4;
323 while( p_sdp < &buffer[i_read-1] && *p_sdp != '\0' && p_sdp[0] != 'v' && p_sdp[1] != '=' )
331 if( p_sdp < &buffer[i_read] )
333 sess_descr_t *p_sd = parse_sdp( p_intf, p_sdp );
336 sess_toitem ( p_intf, p_sd );
343 /**********************************************************************
345 *********************************************************************
346 * put into *ppsz_uri, the the uri in the cfield, psz_cfield.
347 *********************************************************************/
349 static void cfield_parse( char *psz_cfield, char **ppsz_uri )
355 psz_pos = psz_cfield;
357 while( *psz_pos != ' ' && *psz_pos !='\0' )
362 while( *psz_pos != ' ' && *psz_pos !='\0' )
368 while( *psz_pos != ' ' && *psz_pos !='/'
369 && *psz_pos != '\0' )
385 /**********************************************************************
387 *********************************************************************
388 * put into *ppsz_proto, and *ppsz_port, the protocol and the port.
389 *********************************************************************/
392 static void mfield_parse( char *psz_mfield, char **ppsz_proto,
398 psz_pos = psz_mfield;
399 while( *psz_pos != '\0' && *psz_pos != ' ' )
404 *ppsz_port = psz_pos;
405 while( *psz_pos != '\0' && *psz_pos && *psz_pos !=' ' && *psz_pos!='/' )
409 if( *psz_pos == '/' ) // FIXME does not support multi-port
413 while( *psz_pos != '\0' && *psz_pos !=' ' )
420 *ppsz_proto = psz_pos;
421 while( *psz_pos!='\0' && *psz_pos !=' ' &&
424 *psz_pos = tolower( *psz_pos );
438 /*******************************************************************
439 * sess_toitem : changes a sess_descr_t into a hurd of
440 * playlist_item_t, which are enqueued.
441 *******************************************************************
442 * Note : does not support sessions that take place on consecutive
443 * port or adresses yet.
444 *******************************************************************/
446 static void sess_toitem( intf_thread_t * p_intf, sess_descr_t * p_sd )
448 playlist_item_t * p_item;
449 char *psz_uri, *psz_proto;
451 char *psz_uri_default;
453 vlc_bool_t b_http = VLC_FALSE;
454 char *psz_http_path = NULL;
455 playlist_t *p_playlist;
457 psz_uri_default = NULL;
458 cfield_parse( p_sd->psz_connection, &psz_uri_default );
460 for( i_count = 0 ; i_count < p_sd->i_media ; i_count++ )
462 p_item = malloc( sizeof( playlist_item_t ) );
463 p_item->psz_name = strdup( p_sd->psz_sessionname );
464 p_item->psz_uri = NULL;
465 p_item->i_duration = -1;
466 p_item->ppsz_options= NULL;
467 p_item->i_options = 0;
470 p_item->i_status = 0;
471 p_item->b_autodeletion = VLC_FALSE;
472 p_item->b_enabled = VLC_TRUE;
473 p_item->i_group = p_intf->p_sys->i_group;
474 p_item->psz_author = strdup( "" );
478 /* Build what we have to put in p_item->psz_uri, with the m and
481 if( !p_sd->pp_media[i_count] )
486 mfield_parse( p_sd->pp_media[i_count]->psz_medianame,
487 & psz_proto, & psz_port );
489 if( !psz_proto || !psz_port )
494 if( p_sd->pp_media[i_count]->psz_mediaconnection )
496 cfield_parse( p_sd->pp_media[i_count]->psz_mediaconnection,
501 psz_uri = psz_uri_default;
504 if( psz_uri == NULL )
509 for( i = 0 ; i< p_sd->i_attributes ; i++ )
511 if(!strcasecmp( p_sd->pp_attributes[i]->psz_field , "type") &&
512 strstr( p_sd->pp_attributes[i]->psz_value, "http") )
516 if(!strcasecmp( p_sd->pp_attributes[i]->psz_field , "http-path"))
518 psz_http_path = strdup( p_sd->pp_attributes[i]->psz_value );
523 /* Filling p_item->psz_uri */
524 if( b_http == VLC_FALSE )
526 p_item->psz_uri = malloc( strlen( psz_proto ) + strlen( psz_uri ) +
527 strlen( psz_port ) + 7 );
528 if( ismult( psz_uri ) )
530 sprintf( p_item->psz_uri, "%s://@%s:%s",
531 psz_proto, psz_uri, psz_port );
535 sprintf( p_item->psz_uri, "%s://%s:%s",
536 psz_proto, psz_uri, psz_port );
541 if( psz_http_path == NULL )
543 psz_http_path = strdup( "/" );
546 p_item->psz_uri = malloc( strlen( psz_proto ) + strlen( psz_uri ) +
547 strlen( psz_port ) + strlen(psz_http_path) + 5 );
548 sprintf( p_item->psz_uri, "%s://%s:%s%s", psz_proto,
549 psz_uri, psz_port,psz_http_path );
553 free( psz_http_path );
557 /* Enqueueing p_item in the playlist */
558 p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
559 playlist_AddItem ( p_playlist, p_item, PLAYLIST_CHECK_INSERT, PLAYLIST_END );
560 vlc_object_release( p_playlist );
564 /***********************************************************************
565 * parse_sdp : SDP parsing
566 * *********************************************************************
567 * Make a sess_descr_t with a psz
568 ***********************************************************************/
570 static sess_descr_t * parse_sdp( intf_thread_t * p_intf, char *p_packet )
574 if( p_packet[0] != 'v' || p_packet[1] != '=' )
576 msg_Warn(p_intf, "bad SDP packet");
580 sd = malloc( sizeof( sess_descr_t ) );
581 sd->psz_sessionname = NULL;
582 sd->psz_connection = NULL;
585 sd->i_attributes = 0;
586 sd->pp_attributes = NULL;
588 while( *p_packet != '\0' )
592 /* Search begin of field */
593 while( *p_packet == '\n' || *p_packet == ' ' || *p_packet == '\t' )
597 /* search end of line */
598 if( ( psz_end = strchr( p_packet, '\n' ) ) == NULL )
600 psz_end = p_packet + strlen( p_packet );
602 if( psz_end > p_packet && *(psz_end - 1 ) == '\r' )
607 if( psz_end <= p_packet )
613 if( p_packet[1] != '=' )
615 msg_Warn( p_intf, "packet invalid" );
620 switch( p_packet[0] )
623 sd->i_version = atoi( &p_packet[2] );
626 sd->psz_sessionname = strdup( &p_packet[2] );
638 char *psz_eof = strchr( &p_packet[2], ':' );
640 if( psz_eof && psz_eof[1] != '\0' )
642 attr_descr_t *attr = malloc( sizeof( attr_descr_t ) );
646 attr->psz_field = strdup( &p_packet[2] );
647 attr->psz_value = strdup( psz_eof );
649 TAB_APPEND( sd->i_attributes, sd->pp_attributes, attr );
656 media_descr_t *media = malloc( sizeof( media_descr_t ) );
658 media->psz_medianame = strdup( &p_packet[2] );
659 media->psz_mediaconnection = NULL;
661 TAB_APPEND( sd->i_media, sd->pp_media, media );
666 if( sd->i_media <= 0 )
668 sd->psz_connection = strdup( &p_packet[2] );
672 sd->pp_media[sd->i_media-1]->psz_mediaconnection = strdup( &p_packet[2] );
687 if( p ) { free( p ); (p) = NULL; }
688 static void free_sd( sess_descr_t * p_sd )
692 FREE( p_sd->psz_sessionname );
693 FREE( p_sd->psz_connection );
695 for( i = 0; i < p_sd->i_media ; i++ )
697 FREE( p_sd->pp_media[i]->psz_medianame );
698 FREE( p_sd->pp_media[i]->psz_mediaconnection );
700 for( i = 0; i < p_sd->i_attributes ; i++ )
702 FREE( p_sd->pp_attributes[i]->psz_field );
703 FREE( p_sd->pp_attributes[i]->psz_value );
705 FREE( p_sd->pp_attributes );
706 FREE( p_sd->pp_media );
711 /***********************************************************************
712 * ismult: returns true if we have a multicast address
713 ***********************************************************************/
715 static int ismult( char *psz_uri )
720 i_value = strtol( psz_uri, &psz_end, 0 );
723 if( psz_uri[0] == '[')
725 if( strncasecmp( &psz_uri[1], "FF0" , 3) ||
726 strncasecmp( &psz_uri[2], "FF0" , 3))
732 if( *psz_end != '.' ) { return( VLC_FALSE ); }
734 return( i_value < 224 ? VLC_FALSE : VLC_TRUE );
739 /*****************************************************************************
740 * Read: read on a file descriptor, checking b_die periodically
741 *****************************************************************************
743 *****************************************************************************/
744 static ssize_t NetRead( intf_thread_t *p_intf,
745 int fd[2], uint8_t *p_buffer, int i_len )
750 struct timeval timeout;
753 int i_handle_max = __MAX( fd[0], fd[1] );
755 /* Initialize file descriptor set */
757 if( fd[0] > 0 ) FD_SET( fd[0], &fds );
758 if( fd[1] > 0 ) FD_SET( fd[1], &fds );
760 /* We'll wait 0.5 second if nothing happens */
762 timeout.tv_usec = 500000;
764 /* Find if some data is available */
765 i_ret = select( i_handle_max + 1, &fds, NULL, NULL, &timeout );
767 if( i_ret == -1 && errno != EINTR )
769 msg_Err( p_intf, "network select error (%s)", strerror(errno) );
773 if( fd[0] > 0 && FD_ISSET( fd[0], &fds ) )
775 return recv( fd[0], p_buffer, i_len, 0 );
777 else if( fd[1] > 0 && FD_ISSET( fd[1], &fds ) )
779 return recv( fd[1], p_buffer, i_len, 0 );