]> git.sesse.net Git - vlc/blob - modules/misc/sap.c
* it works !
[vlc] / modules / misc / sap.c
1 /*****************************************************************************
2  * sap.c :  SAP interface module
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN
5  * $Id: sap.c,v 1.10 2003/03/30 18:14:38 gbazin 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 * );
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 );
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  ***********************************************************************/
455
456 static int parse_sap( char *p_packet )
457 {  /* Dummy Parser : does nothing !*/
458
459     return( VLC_TRUE );
460 }
461
462 /*************************************************************************
463  * packet_handle : handle the received packet and enques the
464  * the understated session
465  *************************************************************************/
466
467 static int packet_handle( intf_thread_t * p_intf, char *p_packet )
468 {
469     sess_descr_t * p_sd;
470
471     if( parse_sap( p_packet ) )
472     {
473         p_sd = parse_sdp( p_intf, p_packet);
474
475         sess_toitem ( p_intf, p_sd );
476
477         free_sd ( p_sd );
478         return VLC_TRUE;
479     }
480
481     return VLC_FALSE; // Invalid Packet
482 }
483
484
485
486
487 /***********************************************************************
488  * parse_sdp : SDP parsing
489  * *********************************************************************
490  * Make a sess_descr_t with a psz
491  ***********************************************************************/
492
493 static sess_descr_t *  parse_sdp( intf_thread_t * p_intf, char *p_packet )
494 {
495     sess_descr_t *  sd;
496
497     if( ( sd = malloc( sizeof(sess_descr_t) ) ) == NULL )
498     {
499         msg_Err( p_intf, "Not enough memory for sd in parse_sdp()" );
500         return( NULL );
501     }
502
503     sd->pp_media = NULL;
504     sd->psz_origin = NULL;
505     sd->psz_sessionname = NULL;
506     sd->psz_information = NULL;
507     sd->psz_uri = NULL;
508     sd->psz_emails = NULL;
509     sd->psz_phone = NULL;
510     sd->psz_time = NULL;
511     sd->psz_repeat = NULL;
512     sd->psz_attribute = NULL;
513     sd->psz_connection = NULL;
514
515     sd->i_media = 0;
516
517
518     while( *p_packet != '\0'  )
519     {
520 #define FIELD_COPY( p ) \
521         p = strndup( &p_packet[2], i_field_len );
522
523         char *psz_end;
524         int  i_field_len;
525
526         while( *p_packet == '\n' || *p_packet == ' ' || *p_packet == '\t' )
527         {
528             p_packet++;
529         }
530         if( *p_packet == '\0' )
531         {
532             break;
533         }
534
535         if( ( psz_end = strchr( p_packet, '\n' ) ) == NULL )
536         {
537             psz_end = p_packet + strlen( p_packet );
538         }
539         i_field_len = psz_end - &p_packet[2];
540
541         if( p_packet[1] == '=' && i_field_len > 0)
542         {
543             switch( *p_packet )
544             {
545                 case( 'v' ):
546                     FIELD_COPY( sd->psz_version );
547                     break;
548                 case ( 'o' ):
549                     FIELD_COPY( sd->psz_origin );
550                     break;
551                 case( 's' ):
552                     FIELD_COPY( sd->psz_sessionname );
553                     break;
554                 case( 'i' ):
555                     FIELD_COPY( sd->psz_information );
556                     break;
557                 case( 'u' ):
558                     FIELD_COPY( sd->psz_uri );
559                     break;
560                 case( 'e' ):
561                     FIELD_COPY( sd->psz_emails );
562                     break;
563                 case( 'p' ):
564                     FIELD_COPY( sd->psz_phone );
565                     break;
566                 case( 't' ):
567                     FIELD_COPY( sd->psz_time );
568                     break;
569                 case( 'r' ):
570                     FIELD_COPY( sd->psz_repeat );
571                     break;
572                 case( 'a' ):
573                     FIELD_COPY( sd->psz_attribute );
574                     break;
575
576                 case( 'm' ):
577                     if( sd->pp_media )
578                     {
579                         sd->pp_media =
580                             realloc( sd->pp_media,
581                                      sizeof( media_descr_t ) * ( sd->i_media + 1 ) );
582                     }
583                     else
584                     {
585                         sd->pp_media = malloc( sizeof( void * ) );
586                     }
587
588                     sd->pp_media[sd->i_media] =
589                             malloc( sizeof( media_descr_t ) );
590                     sd->pp_media[sd->i_media]->psz_medianame = NULL;
591                     sd->pp_media[sd->i_media]->psz_mediaconnection = NULL;
592                     sd->pp_media[sd->i_media]->psz_medianame = strndup( &p_packet[2], i_field_len );
593
594                     sd->i_media++;
595                     break;
596
597                 case( 'c' ):
598                     if( sd->i_media <= 0 )
599                     {
600                         sd->psz_connection = strndup( &p_packet[2], i_field_len );
601                     }
602                     else
603                     {
604                         sd->pp_media[sd->i_media - 1]->psz_mediaconnection = strndup( &p_packet[2], i_field_len );
605                     }
606                    break;
607                 default:
608                    break;
609             }
610         }
611         p_packet = psz_end;
612 #undef FIELD_COPY
613     }
614
615     return sd;
616 }
617
618 #define FREE( p ) \
619     if( p ) { free( p ); (p) = NULL; }
620 static void free_sd( sess_descr_t * p_sd )
621 {
622     int i;
623     if( p_sd )
624     {
625         FREE( p_sd->psz_origin );
626         FREE( p_sd->psz_sessionname );
627         FREE( p_sd->psz_information );
628         FREE( p_sd->psz_uri );
629         FREE( p_sd->psz_emails );
630         FREE( p_sd->psz_phone );
631         FREE( p_sd->psz_time );
632         FREE( p_sd->psz_repeat );
633         FREE( p_sd->psz_attribute );
634         FREE( p_sd->psz_connection );
635
636         for( i = 0; i < p_sd->i_media ; i++ )
637         {
638             FREE( p_sd->pp_media[i]->psz_medianame );
639             FREE( p_sd->pp_media[i]->psz_mediaconnection );
640         }
641         FREE( p_sd->pp_media );
642
643         free( p_sd );
644     }
645     else
646     {
647         ;
648     }
649     return;
650 }
651
652 /***********************************************************************
653  * ismult
654  ***********************************************************************/
655
656 static int ismult( char *psz_uri )
657 {
658     char *psz_end;
659     int  i_value;
660
661     i_value = strtol( psz_uri, &psz_end, 0 );
662
663     if( *psz_end != '.' )
664     {
665         return( VLC_FALSE );
666     }
667
668     return( i_value < 224 ? VLC_FALSE : VLC_TRUE );
669 }
670
671 /*****************************************************************************
672  * Read: read on a file descriptor, checking b_die periodically
673  *****************************************************************************
674  * Taken from udp.c
675  ******************************************************************************/
676 static ssize_t NetRead( intf_thread_t *p_intf,
677                         int i_handle, byte_t *p_buffer, size_t i_len)
678 {
679 #ifdef UNDER_CE
680     return -1;
681
682 #else
683     struct timeval  timeout;
684     fd_set          fds;
685     int             i_ret;
686
687     /* Initialize file descriptor set */
688     FD_ZERO( &fds );
689     FD_SET( i_handle, &fds );
690
691     /* We'll wait 0.5 second if nothing happens */
692     timeout.tv_sec = 0;
693     timeout.tv_usec = 500000;
694
695     /* Find if some data is available */
696     i_ret = select( i_handle + 1, &fds,
697     NULL, NULL, &timeout );
698
699     if( i_ret == -1 && errno != EINTR )
700     {
701         msg_Err( p_intf, "network select error (%s)", strerror(errno) );
702     }
703     else if( i_ret > 0 )
704     {
705         ssize_t i_recv = recv( i_handle, p_buffer, i_len, 0 );
706
707         if( i_recv < 0 )
708         {
709            msg_Err( p_intf, "recv failed (%s)", strerror(errno) );
710         }
711
712         return i_recv;
713     }
714
715     return 0;
716
717 #endif
718 }
719