]> git.sesse.net Git - vlc/blob - modules/misc/sap.c
* SAP can now handle SAP-announced IPv6 streams
[vlc] / modules / misc / sap.c
1 /*****************************************************************************
2  * sap.c :  SAP interface module
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN
5  * $Id: sap.c,v 1.12 2003/06/16 15:39:11 zorglub Exp $
6  *
7  * Authors: Arnaud Schauly <gitan@via.ecp.fr>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>                                      /* malloc(), free() */
28 #include <string.h>
29
30 #include <errno.h>                                                 /* ENOMEM */
31 #include <stdio.h>
32 #include <ctype.h>
33 #include <signal.h>
34
35 #include <vlc/vlc.h>
36 #include <vlc/intf.h>
37 #include <vlc/vout.h>
38
39 #ifdef HAVE_UNISTD_H
40 #    include <unistd.h>
41 #endif
42
43 #ifdef HAVE_SYS_TIME_H
44 #    include <sys/time.h>
45 #endif
46 #include <sys/types.h>
47
48
49 #ifdef WIN32
50 #   include <winsock2.h>
51 #   include <ws2tcpip.h>
52 #   ifndef IN_MULTICAST
53 #       define IN_MULTICAST(a) IN_CLASSD(a)
54 #   endif
55 #else
56 #   include <sys/socket.h>
57 #   include <netinet/in.h>
58 #   if HAVE_ARPA_INET_H
59 #      include <arpa/inet.h>
60 #   elif defined( SYS_BEOS )
61 #      include <net/netdb.h>
62 #   endif
63 #endif
64
65 #include "network.h"
66
67 #define MAX_LINE_LENGTH 256
68
69 /* SAP is always on that port */
70 #define HELLO_PORT 9875
71 #define HELLO_GROUP "224.2.127.254"
72 #define ADD_SESSION 1
73
74 /*****************************************************************************
75  * Local prototypes
76  *****************************************************************************/
77
78 typedef struct media_descr_t media_descr_t;
79 typedef struct sess_descr_t sess_descr_t;
80
81 static int  Activate     ( vlc_object_t * );
82 static void Run          ( intf_thread_t *p_intf );
83 static int Kill          ( intf_thread_t * );
84
85 static ssize_t NetRead    ( intf_thread_t*, int , byte_t *, size_t );
86
87 /* playlist related functions */
88 static int  sess_toitem( intf_thread_t *, sess_descr_t * );
89
90 /* sap/sdp related functions */
91 static int parse_sap ( char * );
92 static int packet_handle ( intf_thread_t *, char *, int );
93 static sess_descr_t *  parse_sdp( intf_thread_t *, char * ) ;
94
95 /* specific sdp fields parsing */
96
97 static void cfield_parse( char *, char ** );
98 static void mfield_parse( char *psz_mfield, char **ppsz_proto,
99                char **ppsz_port );
100
101 static void free_sd( sess_descr_t * );
102 static int  ismult( char * );
103
104 /* The struct that contains sdp informations */
105 struct  sess_descr_t {
106     char *psz_version;
107     char *psz_origin;
108     char *psz_sessionname;
109     char *psz_information;
110     char *psz_uri;
111     char *psz_emails;
112     char *psz_phone;
113     char *psz_time;
114     char *psz_repeat;
115     char *psz_attribute;
116     char *psz_connection;
117     int  i_media;
118     media_descr_t ** pp_media;
119 };
120
121 /* All this informations are not useful yet.  */
122 struct media_descr_t {
123     char *psz_medianame;
124     char *psz_mediaconnection;
125 };
126
127 /*****************************************************************************
128  * Module descriptor
129  *****************************************************************************/
130 vlc_module_begin();
131     add_category_hint( N_("SAP"), NULL, VLC_TRUE );
132         add_string( "sap-addr", NULL, NULL,
133                      "SAP multicast address", "SAP multicast address", VLC_TRUE );
134     set_description( _("SAP interface") );
135     set_capability( "interface", 0 );
136     set_callbacks( Activate, NULL);
137 vlc_module_end();
138
139 /*****************************************************************************
140  * Activate: initialize and create stuff
141  *****************************************************************************/
142 static int Activate( vlc_object_t *p_this )
143 {
144     intf_thread_t *p_intf = (intf_thread_t*)p_this;
145
146     p_intf->pf_run = Run;
147
148     return VLC_SUCCESS;
149 }
150
151 /*****************************************************************************
152  * Run: sap thread
153  *****************************************************************************
154  * Listens to SAP packets, and sends them to packet_handle
155  *****************************************************************************/
156 #define MAX_SAP_BUFFER 2000
157
158 static void Run( intf_thread_t *p_intf )
159 {
160     char *psz_addr;
161     char *psz_network = NULL;
162     int fd;
163
164     char buffer[MAX_SAP_BUFFER + 1];
165
166     module_t            *p_network;
167     network_socket_t    socket_desc;
168
169     if( !(psz_addr = config_GetPsz( p_intf, "sap-addr" ) ) )
170     {
171         psz_addr = strdup( HELLO_GROUP );
172     }
173
174     /* Prepare the network_socket_t structure */
175     socket_desc.i_type = NETWORK_UDP;
176     socket_desc.psz_bind_addr = psz_addr;
177     socket_desc.i_bind_port   = HELLO_PORT;
178     socket_desc.psz_server_addr   = "";
179     socket_desc.i_server_port     = 0;
180     p_intf->p_private = (void*) &socket_desc;
181
182     psz_network = "ipv4"; // FIXME
183
184     /* Create, Bind the socket, ... with the appropriate module  */
185
186     if( !( p_network = module_Need( p_intf, "network", psz_network ) ) )
187     {
188         msg_Warn( p_intf, "failed to open a connection (udp)" );
189         return;
190     }
191     module_Unneed( p_intf, p_network );
192
193     fd = socket_desc.i_handle;
194
195
196     /* read SAP packets */
197     while( !p_intf->b_die )
198     {
199         int i_read;
200
201         //memset( buffer, 0, MAX_SAP_BUFFER + 1);
202
203         i_read = NetRead( p_intf, fd, buffer, MAX_SAP_BUFFER );
204
205         if( i_read < 0 )
206         {
207             msg_Err( p_intf, "Cannot read in the socket" );
208         }
209         if( i_read == 0 )
210         {
211             continue;
212         }
213         buffer[i_read] = '\0';
214
215         packet_handle( p_intf, buffer, i_read );
216
217     }
218
219     /* Closing socket */
220
221 #ifdef UNDER_CE
222     CloseHandle( socket_desc.i_handle );
223 #elif defined( WIN32 )
224     closesocket( socket_desc.i_handle );
225 #else
226     close( socket_desc.i_handle );
227 #endif
228
229 }
230
231 /********************************************************************
232  * Kill
233  *******************************************************************
234  * Kills the SAP interface.
235  ********************************************************************/
236 static int Kill( intf_thread_t *p_intf )
237 {
238
239     p_intf->b_die = VLC_TRUE;
240
241     return VLC_SUCCESS;
242 }
243
244 /*******************************************************************
245  * sess_toitem : changes a sess_descr_t into a hurd of
246  * playlist_item_t, which are enqueued.
247  *******************************************************************
248  * Note : does not support sessions that take place on consecutive
249  * port or adresses yet.
250  *******************************************************************/
251
252 static int sess_toitem( intf_thread_t * p_intf, sess_descr_t * p_sd )
253 {
254     playlist_item_t * p_item;
255     char *psz_uri, *psz_proto;
256     char *psz_port;
257     char *psz_uri_default;
258     int i_multicast;
259     int i_count;
260     playlist_t *p_playlist;
261
262     psz_uri_default = NULL;
263     cfield_parse( p_sd->psz_connection, &psz_uri_default );
264
265     for( i_count = 0 ; i_count < p_sd->i_media ; i_count++ )
266     {
267         p_item = malloc( sizeof( playlist_item_t ) );
268         if( p_item == NULL )
269         {
270             msg_Err( p_intf, "Not enough memory for p_item in sesstoitem()" );
271             return 0;
272         }
273         p_item->psz_name = strdup( p_sd->psz_sessionname );
274         p_item->i_type = 0;
275         p_item->i_status = 0;
276         p_item->b_autodeletion = VLC_FALSE;
277         p_item->psz_uri = NULL;
278
279         psz_uri = NULL;
280
281         /* Build what we have to put in p_item->psz_uri, with the m and
282          *  c fields  */
283
284         if( !p_sd->pp_media[i_count] )
285         {
286             return 0;
287         }
288
289         mfield_parse( p_sd->pp_media[i_count]->psz_medianame,
290                         & psz_proto, & psz_port );
291
292         if( !psz_proto || !psz_port )
293         {
294             return 0;
295         }
296
297         if( p_sd->pp_media[i_count]->psz_mediaconnection )
298         {
299             cfield_parse( p_sd->pp_media[i_count]->psz_mediaconnection,
300                             & psz_uri );
301         }
302         else
303         {
304             psz_uri = psz_uri_default;
305         }
306
307         if( psz_uri == NULL )
308         {
309             return 0;
310         }
311
312
313         /* Filling p_item->psz_uri */
314         i_multicast = ismult( psz_uri );
315
316         p_item->psz_uri = malloc( strlen( psz_proto ) + strlen( psz_uri ) +
317                         strlen( psz_port ) + 5 +i_multicast );
318         if( p_item->psz_uri == NULL )
319         {
320             msg_Err( p_intf, "Not enough memory");
321             free( p_item );
322             return 0;
323         }
324
325         if( i_multicast == 1)
326         {
327             sprintf( p_item->psz_uri, "%s://@%s:%s", psz_proto,
328                             psz_uri, psz_port );
329         }
330         else
331         {
332             sprintf( p_item->psz_uri, "%s://%s:%s", psz_proto,
333                             psz_uri, psz_port );
334         }
335
336         /* Enqueueing p_item in the playlist */
337
338         if( p_item )
339         {
340             p_playlist = vlc_object_find( p_intf,
341             VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
342
343             playlist_AddItem ( p_playlist, p_item,
344             PLAYLIST_CHECK_INSERT, PLAYLIST_END);
345             vlc_object_release( p_playlist );
346         }
347
348
349     }
350
351
352     return 1;
353
354 }
355
356 /**********************************************************************
357  * cfield_parse
358  *********************************************************************
359  * put into *ppsz_uri, the the uri in the cfield, psz_cfield.
360  *********************************************************************/
361
362 static void cfield_parse( char *psz_cfield, char **ppsz_uri )
363 {
364
365     char *psz_pos;
366     if( psz_cfield )
367     {
368         psz_pos = psz_cfield;
369
370         while( *psz_pos != ' ' && *psz_pos !='\0' )
371         {
372             psz_pos++;
373         }
374         psz_pos++;
375         while( *psz_pos != ' ' && *psz_pos !='\0' )
376         {
377             psz_pos++;
378         }
379         psz_pos++;
380         *ppsz_uri = psz_pos;
381         while( *psz_pos != ' ' && *psz_pos !='/'
382                         && *psz_pos != '\0' )
383         {
384             psz_pos++;
385         }
386         *psz_pos = '\0';
387
388     }
389     else
390     {
391         ppsz_uri = NULL;
392     }
393
394     return;
395
396 }
397
398 /**********************************************************************
399  * mfield_parse
400  *********************************************************************
401  * put into *ppsz_proto, and *ppsz_port, the protocol and the port.
402  *********************************************************************/
403
404
405 static void mfield_parse( char *psz_mfield, char **ppsz_proto,
406                char **ppsz_port )
407 {
408     char *psz_pos;
409     if( psz_mfield )
410     {
411         psz_pos = psz_mfield;
412         while( *psz_pos != '\0' && *psz_pos != ' ' )
413         {
414             psz_pos++;
415         }
416         psz_pos++;
417         *ppsz_port = psz_pos;
418         while( *psz_pos != '\0' && *psz_pos && *psz_pos !=' ' && *psz_pos!='/' )
419         {
420             psz_pos++;
421         }
422         if( *psz_pos == '/' )  // FIXME does not support multi-port
423         {
424             *psz_pos = '\0';
425             psz_pos++;
426             while( *psz_pos != '\0' && *psz_pos !=' ' )
427             {
428             psz_pos++;
429             }
430         }
431         *psz_pos = '\0';
432         psz_pos++;
433         *ppsz_proto = psz_pos;
434         while( *psz_pos!='\0' && *psz_pos !=' ' &&
435                         *psz_pos!='/' )
436         {
437             *psz_pos = tolower( *psz_pos );
438             psz_pos++;
439         }
440         *psz_pos = '\0';
441     }
442     else
443     {
444         *ppsz_proto = NULL;
445         *ppsz_port = NULL;
446     }
447     return;
448 }
449
450 /***********************************************************************
451  * parse_sap : Takes care of the SAP headers
452  ***********************************************************************
453  * checks if the packet has the true headers ;
454  * returns the SAP header lenhth
455  ***********************************************************************/
456
457 static int parse_sap( char *p_packet )
458 {
459     // According to RFC 2974
460     int i_hlen = 4;                           // Minimum header length is 4
461     i_hlen += (p_packet[0] & 0x10) ? 16 : 4;  // Address type IPv6=16bytes
462     i_hlen +=  p_packet[1];                   // Authentification length
463
464     //Looks for the first '\0' byte after length
465     for(;p_packet[i_hlen]!='\0'; i_hlen++);
466
467     return(i_hlen);
468 }
469
470 /*************************************************************************
471  * packet_handle : handle the received packet and enques the
472  * the understated session
473  *************************************************************************/
474
475 static int packet_handle( intf_thread_t * p_intf, char *p_packet, int i_len )
476 {
477     sess_descr_t * p_sd;
478     int i_hlen;                             // Header length
479
480     i_hlen = parse_sap(p_packet);
481
482     if( (i_hlen > 0) && (i_hlen < i_len) )
483     {
484         p_sd = parse_sdp( p_intf, p_packet + i_hlen +1);
485         if(p_sd)
486         {
487             sess_toitem ( p_intf, p_sd );
488             free_sd ( p_sd );
489             return VLC_TRUE;
490         }
491     }
492
493     return VLC_FALSE; // Invalid Packet
494 }
495
496
497
498
499 /***********************************************************************
500  * parse_sdp : SDP parsing
501  * *********************************************************************
502  * Make a sess_descr_t with a psz
503  ***********************************************************************/
504
505 static sess_descr_t *  parse_sdp( intf_thread_t * p_intf, char *p_packet )
506 {
507     sess_descr_t *  sd;
508
509     // According to RFC 2327, the first bytes should be exactly "v="
510     if((p_packet[0] != 'v') || (p_packet[1] != '='))
511     {
512         msg_Warn(p_intf, "Bad SDP packet");
513         return NULL;
514     }
515
516     if( ( sd = malloc( sizeof(sess_descr_t) ) ) == NULL )
517     {
518         msg_Err( p_intf, "Not enough memory for sd in parse_sdp()" );
519         return( NULL );
520     }
521
522     sd->pp_media = NULL;
523     sd->psz_origin = NULL;
524     sd->psz_sessionname = NULL;
525     sd->psz_information = NULL;
526     sd->psz_uri = NULL;
527     sd->psz_emails = NULL;
528     sd->psz_phone = NULL;
529     sd->psz_time = NULL;
530     sd->psz_repeat = NULL;
531     sd->psz_attribute = NULL;
532     sd->psz_connection = NULL;
533
534     sd->i_media = 0;
535
536     while( *p_packet != '\0'  )
537     {
538 #define FIELD_COPY( p ) \
539         p = strndup( &p_packet[2], i_field_len );
540
541         char *psz_end;
542         int  i_field_len;
543
544         while( *p_packet == '\n' || *p_packet == ' ' || *p_packet == '\t' )
545         {
546             p_packet++;
547         }
548         if( *p_packet == '\0' )
549         {
550             break;
551         }
552
553         if( ( psz_end = strchr( p_packet, '\n' ) ) == NULL )
554         {
555             psz_end = p_packet + strlen( p_packet );
556         }
557         i_field_len = psz_end - &p_packet[2];
558
559         if( p_packet[1] == '=' && i_field_len > 0)
560         {
561             switch( *p_packet )
562             {
563                 case( 'v' ):
564                     FIELD_COPY( sd->psz_version );
565                     break;
566                 case ( 'o' ):
567                     FIELD_COPY( sd->psz_origin );
568                     break;
569                 case( 's' ):
570                     FIELD_COPY( sd->psz_sessionname );
571                     break;
572                 case( 'i' ):
573                     FIELD_COPY( sd->psz_information );
574                     break;
575                 case( 'u' ):
576                     FIELD_COPY( sd->psz_uri );
577                     break;
578                 case( 'e' ):
579                     FIELD_COPY( sd->psz_emails );
580                     break;
581                 case( 'p' ):
582                     FIELD_COPY( sd->psz_phone );
583                     break;
584                 case( 't' ):
585                     FIELD_COPY( sd->psz_time );
586                     break;
587                 case( 'r' ):
588                     FIELD_COPY( sd->psz_repeat );
589                     break;
590                 case( 'a' ):
591                     FIELD_COPY( sd->psz_attribute );
592                     break;
593
594                 case( 'm' ):
595                     if( sd->pp_media )
596                     {
597                         sd->pp_media =
598                             realloc( sd->pp_media,
599                                sizeof( media_descr_t ) * ( sd->i_media + 1 ) );
600                     }
601                     else
602                     {
603                         sd->pp_media = malloc( sizeof( void * ) );
604                     }
605
606                     sd->pp_media[sd->i_media] =
607                             malloc( sizeof( media_descr_t ) );
608                     sd->pp_media[sd->i_media]->psz_medianame = NULL;
609                     sd->pp_media[sd->i_media]->psz_mediaconnection = NULL;
610                     sd->pp_media[sd->i_media]->psz_medianame = strndup( &p_packet[2], i_field_len );
611
612                     sd->i_media++;
613                     break;
614
615                 case( 'c' ):
616                     if( sd->i_media <= 0 )
617                     {
618                         FIELD_COPY(sd->psz_connection);
619                     }
620                     else
621                     {
622                         FIELD_COPY(sd->pp_media[sd->i_media - 1]->psz_mediaconnection);
623                     }
624                    break;
625                 default:
626                    break;
627             }
628         }
629         p_packet = psz_end;
630 #undef FIELD_COPY
631     }
632
633     return sd;
634 }
635
636 #define FREE( p ) \
637     if( p ) { free( p ); (p) = NULL; }
638 static void free_sd( sess_descr_t * p_sd )
639 {
640     int i;
641     if( p_sd )
642     {
643         FREE( p_sd->psz_origin );
644         FREE( p_sd->psz_sessionname );
645         FREE( p_sd->psz_information );
646         FREE( p_sd->psz_uri );
647         FREE( p_sd->psz_emails );
648         FREE( p_sd->psz_phone );
649         FREE( p_sd->psz_time );
650         FREE( p_sd->psz_repeat );
651         FREE( p_sd->psz_attribute );
652         FREE( p_sd->psz_connection );
653
654         for( i = 0; i < p_sd->i_media ; i++ )
655         {
656             FREE( p_sd->pp_media[i]->psz_medianame );
657             FREE( p_sd->pp_media[i]->psz_mediaconnection );
658         }
659         FREE( p_sd->pp_media );
660
661         free( p_sd );
662     }
663     else
664     {
665         ;
666     }
667     return;
668 }
669
670 /***********************************************************************
671  * ismult: returns true if we have a multicast address
672  ***********************************************************************/
673
674 static int ismult( char *psz_uri )
675 {
676     char *psz_end;
677     int  i_value;
678
679     i_value = strtol( psz_uri, &psz_end, 0 );
680
681     /* FIXME: This is an ugly way to detect IPv6 and to say that this is
682 multicast */
683     if( psz_uri[0] == '[') { return( VLC_TRUE ); } 
684
685     if( *psz_end != '.' ) { return( VLC_FALSE ); }
686
687     return( i_value < 224 ? VLC_FALSE : VLC_TRUE );
688 }
689
690 /*****************************************************************************
691  * Read: read on a file descriptor, checking b_die periodically
692  *****************************************************************************
693  * Taken from udp.c
694  ******************************************************************************/
695 static ssize_t NetRead( intf_thread_t *p_intf,
696                         int i_handle, byte_t *p_buffer, size_t i_len)
697 {
698 #ifdef UNDER_CE
699     return -1;
700
701 #else
702     struct timeval  timeout;
703     fd_set          fds;
704     int             i_ret;
705
706     /* Initialize file descriptor set */
707     FD_ZERO( &fds );
708     FD_SET( i_handle, &fds );
709
710     /* We'll wait 0.5 second if nothing happens */
711     timeout.tv_sec = 0;
712     timeout.tv_usec = 500000;
713
714     /* Find if some data is available */
715     i_ret = select( i_handle + 1, &fds,
716     NULL, NULL, &timeout );
717
718     if( i_ret == -1 && errno != EINTR )
719     {
720         msg_Err( p_intf, "network select error (%s)", strerror(errno) );
721     }
722     else if( i_ret > 0 )
723     {
724         ssize_t i_recv = recv( i_handle, p_buffer, i_len, 0 );
725
726         if( i_recv < 0 )
727         {
728            msg_Err( p_intf, "recv failed (%s)", strerror(errno) );
729         }
730
731         return i_recv;
732     }
733
734     return 0;
735
736 #endif
737 }
738