]> git.sesse.net Git - vlc/blob - modules/access/rtsp/rtsp.c
Update LGPL license blurb, choosing v2.1+.
[vlc] / modules / access / rtsp / rtsp.c
1 /*****************************************************************************
2  * rtsp.c: minimalistic implementation of rtsp protocol.
3  *         Not RFC 2326 compilant yet and only handle REAL RTSP.
4  *****************************************************************************
5  * Copyright (C) 2002-2004 the xine project
6  * Copyright (C) 2005 VideoLAN
7  * $Id$
8  *
9  * Authors: Gildas Bazin <gbazin@videolan.org>
10  *          Adapted from xine which itself adapted it from joschkas real tools.
11  *
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.
16  *
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.
21  *
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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <vlc_common.h>
32
33 #include "rtsp.h"
34
35 #define BUF_SIZE 4096
36 #define HEADER_SIZE 1024
37 #define MAX_FIELDS 256
38
39 struct rtsp_s {
40
41   int           s;
42
43   char         *host;
44   int           port;
45   char         *path;
46   char         *mrl;
47   char         *user_agent;
48
49   char         *server;
50   unsigned int  server_state;
51   uint32_t      server_caps;
52
53   unsigned int  cseq;
54   char         *session;
55
56   char        *answers[MAX_FIELDS];   /* data of last message */
57   char        *scheduled[MAX_FIELDS]; /* will be sent with next message */
58 };
59
60 /*
61  * constants
62  */
63
64 const char rtsp_protocol_version[]="RTSP/1.0";
65
66 /* server states */
67 #define RTSP_CONNECTED 1
68 #define RTSP_INIT      2
69 #define RTSP_READY     4
70 #define RTSP_PLAYING   8
71 #define RTSP_RECORDING 16
72
73 /* server capabilities */
74 #define RTSP_OPTIONS       0x001
75 #define RTSP_DESCRIBE      0x002
76 #define RTSP_ANNOUNCE      0x004
77 #define RTSP_SETUP         0x008
78 #define RTSP_GET_PARAMETER 0x010
79 #define RTSP_SET_PARAMETER 0x020
80 #define RTSP_TEARDOWN      0x040
81 #define RTSP_PLAY          0x080
82 #define RTSP_RECORD        0x100
83
84 /*
85  * rtsp_get gets a line from stream
86  * and returns a null terminated string (must be freed).
87  */
88  
89 static char *rtsp_get( rtsp_client_t *rtsp )
90 {
91   char *psz_buffer = malloc( BUF_SIZE );
92   char *psz_string = NULL;
93
94   if( rtsp->pf_read_line( rtsp->p_userdata, (uint8_t*)psz_buffer, (unsigned int)BUF_SIZE ) >= 0 )
95   {
96     //printf( "<< '%s'\n", psz_buffer );
97       psz_string = strdup( psz_buffer );
98   }
99
100   free( psz_buffer );
101   return psz_string;
102 }
103
104
105 /*
106  * rtsp_put puts a line on stream
107  */
108
109 static int rtsp_put( rtsp_client_t *rtsp, const char *psz_string )
110 {
111     unsigned int i_buffer = strlen( psz_string );
112     char *psz_buffer = malloc( i_buffer + 3 );
113     int i_ret;
114
115     strcpy( psz_buffer, psz_string );
116     psz_buffer[i_buffer] = '\r'; psz_buffer[i_buffer+1] = '\n';
117     psz_buffer[i_buffer+2] = 0;
118
119     i_ret = rtsp->pf_write( rtsp->p_userdata, (uint8_t*)psz_buffer, i_buffer + 2 );
120
121     free( psz_buffer );
122     return i_ret;
123 }
124
125 /*
126  * extract server status code
127  */
128
129 static int rtsp_get_status_code( rtsp_client_t *rtsp, const char *psz_string )
130 {
131     VLC_UNUSED(rtsp);
132     char psz_buffer[4];
133     int i_code = 0;
134
135     if( !strncmp( psz_string, "RTSP/1.0", sizeof("RTSP/1.0") - 1 ) )
136     {
137         memcpy( psz_buffer, psz_string + sizeof("RTSP/1.0"), 3 );
138         psz_buffer[3] = 0;
139         i_code = atoi( psz_buffer );
140     }
141     else if( !strncmp( psz_string, "SET_PARAMETER", 8 ) )
142     {
143         return RTSP_STATUS_SET_PARAMETER;
144     }
145
146     if( i_code != 200 )
147     {
148         //fprintf( stderr, "librtsp: server responds: '%s'\n", psz_string );
149     }
150
151     return i_code;
152 }
153
154 /*
155  * send a request
156  */
157
158 static int rtsp_send_request( rtsp_client_t *rtsp, const char *psz_type,
159                               const char *psz_what )
160 {
161     char **ppsz_payload = rtsp->p_private->scheduled;
162     char *psz_buffer;
163     int i_ret;
164
165     psz_buffer = malloc( strlen(psz_type) + strlen(psz_what) +
166                          sizeof("RTSP/1.0") + 2 );
167
168     sprintf( psz_buffer, "%s %s %s", psz_type, psz_what, "RTSP/1.0" );
169     i_ret = rtsp_put( rtsp, psz_buffer );
170     free( psz_buffer );
171
172     if( ppsz_payload )
173         while( *ppsz_payload )
174         {
175             rtsp_put( rtsp, *ppsz_payload );
176             ppsz_payload++;
177         }
178     rtsp_put( rtsp, "" );
179     rtsp_unschedule_all( rtsp );
180
181     return i_ret;
182 }
183
184 /*
185  * schedule standard fields
186  */
187
188 static void rtsp_schedule_standard( rtsp_client_t *rtsp )
189 {
190     char tmp[17];
191
192     sprintf( tmp, "Cseq: %u", rtsp->p_private->cseq);
193     rtsp_schedule_field( rtsp, tmp );
194
195     if( rtsp->p_private->session )
196     {
197         char *buf;
198         buf = malloc( strlen(rtsp->p_private->session) + 15 );
199         sprintf( buf, "Session: %s", rtsp->p_private->session );
200         rtsp_schedule_field( rtsp, buf );
201         free( buf );
202     }
203 }
204
205 /*
206  * get the answers, if server responses with something != 200, return NULL
207  */
208
209 static int rtsp_get_answers( rtsp_client_t *rtsp )
210 {
211     char *answer = NULL;
212     unsigned int answer_seq;
213     char **answer_ptr = rtsp->p_private->answers;
214     int code;
215     int ans_count = 0;
216
217     answer = rtsp_get( rtsp );
218     if( !answer ) return 0;
219     code = rtsp_get_status_code( rtsp, answer );
220     free( answer );
221
222     rtsp_free_answers( rtsp );
223
224     do { /* while we get answer lines */
225
226       answer = rtsp_get( rtsp );
227       if( !answer ) return 0;
228
229       if( !strncasecmp( answer, "Cseq:", 5 ) )
230       {
231           sscanf( answer, "%*s %u", &answer_seq );
232           if( rtsp->p_private->cseq != answer_seq )
233           {
234             //fprintf( stderr, "warning: Cseq mismatch. got %u, assumed %u",
235             //       answer_seq, rtsp->p_private->cseq );
236
237               rtsp->p_private->cseq = answer_seq;
238           }
239       }
240       if( !strncasecmp( answer, "Server:", 7 ) )
241       {
242           char *buf = malloc( strlen(answer) );
243           sscanf( answer, "%*s %s", buf );
244           free( rtsp->p_private->server );
245           rtsp->p_private->server = buf;
246       }
247       if( !strncasecmp( answer, "Session:", 8 ) )
248       {
249           char *buf = malloc( strlen(answer) );
250           sscanf( answer, "%*s %s", buf );
251           if( rtsp->p_private->session )
252           {
253               if( strcmp( buf, rtsp->p_private->session ) )
254               {
255                   //fprintf( stderr,
256                   //         "rtsp: warning: setting NEW session: %s\n", buf );
257                   free( rtsp->p_private->session );
258                   rtsp->p_private->session = strdup( buf );
259               }
260           }
261           else
262           {
263               //fprintf( stderr, "setting session id to: %s\n", buf );
264               rtsp->p_private->session = strdup( buf );
265           }
266           free( buf );
267       }
268
269       *answer_ptr = answer;
270       answer_ptr++;
271     } while( (strlen(answer) != 0) && (++ans_count < MAX_FIELDS) );
272
273     rtsp->p_private->cseq++;
274
275     *answer_ptr = NULL;
276     rtsp_schedule_standard( rtsp );
277
278     return code;
279 }
280
281 /*
282  * send an ok message
283  */
284
285 int rtsp_send_ok( rtsp_client_t *rtsp )
286 {
287     char cseq[16];
288
289     rtsp_put( rtsp, "RTSP/1.0 200 OK" );
290     sprintf( cseq, "CSeq: %u", rtsp->p_private->cseq );
291     rtsp_put( rtsp, cseq );
292     rtsp_put( rtsp, "" );
293     return 0;
294 }
295
296 /*
297  * implementation of must-have rtsp requests; functions return
298  * server status code.
299  */
300
301 int rtsp_request_options( rtsp_client_t *rtsp, const char *what )
302 {
303     char *buf;
304
305     if( what ) buf = strdup(what);
306     else
307     {
308         buf = malloc( strlen(rtsp->p_private->host) + 16 );
309         sprintf( buf, "rtsp://%s:%i", rtsp->p_private->host,
310                  rtsp->p_private->port );
311     }
312     rtsp_send_request( rtsp, "OPTIONS", buf );
313     free( buf );
314
315     return rtsp_get_answers( rtsp );
316 }
317
318 int rtsp_request_describe( rtsp_client_t *rtsp, const char *what )
319 {
320     char *buf;
321
322     if( what )
323     {
324         buf = strdup(what);
325     }
326     else
327     {
328         buf = malloc( strlen(rtsp->p_private->host) +
329                       strlen(rtsp->p_private->path) + 16 );
330         sprintf( buf, "rtsp://%s:%i/%s", rtsp->p_private->host,
331                  rtsp->p_private->port, rtsp->p_private->path );
332     }
333     rtsp_send_request( rtsp, "DESCRIBE", buf );
334     free( buf );
335
336     return rtsp_get_answers( rtsp );
337 }
338
339 int rtsp_request_setup( rtsp_client_t *rtsp, const char *what )
340 {
341     rtsp_send_request( rtsp, "SETUP", what );
342     return rtsp_get_answers( rtsp );
343 }
344
345 int rtsp_request_setparameter( rtsp_client_t *rtsp, const char *what )
346 {
347     char *buf;
348
349     if( what )
350     {
351         buf = strdup(what);
352     }
353     else
354     {
355         buf = malloc( strlen(rtsp->p_private->host) +
356                       strlen(rtsp->p_private->path) + 16 );
357         sprintf( buf, "rtsp://%s:%i/%s", rtsp->p_private->host,
358                  rtsp->p_private->port, rtsp->p_private->path );
359     }
360
361     rtsp_send_request( rtsp, "SET_PARAMETER", buf );
362     free( buf );
363
364     return rtsp_get_answers( rtsp );
365 }
366
367 int rtsp_request_play( rtsp_client_t *rtsp, const char *what )
368 {
369     char *buf;
370
371     if( what )
372     {
373         buf = strdup( what );
374     }
375     else
376     {
377         buf = malloc( strlen(rtsp->p_private->host) +
378                       strlen(rtsp->p_private->path) + 16 );
379         sprintf( buf, "rtsp://%s:%i/%s", rtsp->p_private->host,
380                  rtsp->p_private->port, rtsp->p_private->path );
381     }
382
383     rtsp_send_request( rtsp, "PLAY", buf );
384     free( buf );
385
386     return rtsp_get_answers( rtsp );
387 }
388
389 int rtsp_request_tearoff( rtsp_client_t *rtsp, const char *what )
390 {
391     rtsp_send_request( rtsp, "TEAROFF", what );
392     return rtsp_get_answers( rtsp );
393 }
394
395 /*
396  * read opaque data from stream
397  */
398
399 int rtsp_read_data( rtsp_client_t *rtsp, uint8_t *buffer, unsigned int size )
400 {
401     int i, seq;
402
403     if( size >= 4 )
404     {
405         i = rtsp->pf_read( rtsp->p_userdata, (uint8_t*)buffer, (unsigned int) 4 );
406         if( i < 4 ) return i;
407
408         if( buffer[0]=='S' && buffer[1]=='E' && buffer[2]=='T' &&
409             buffer[3]=='_' )
410         {
411             char *rest = rtsp_get( rtsp );
412             if( !rest ) return -1;
413
414             seq = -1;
415             do
416             {
417                 free( rest );
418                 rest = rtsp_get( rtsp );
419                 if( !rest ) return -1;
420
421                 if( !strncasecmp( rest, "Cseq:", 5 ) )
422                     sscanf( rest, "%*s %u", &seq );
423             } while( *rest );
424             free( rest );
425
426             if( seq < 0 )
427             {
428                 //fprintf(stderr, "warning: cseq not recognized!\n");
429                 seq = 1;
430             }
431
432             /* lets make the server happy */
433             rtsp_put( rtsp, "RTSP/1.0 451 Parameter Not Understood" );
434             rest = malloc(17);
435             sprintf( rest,"CSeq: %u", seq );
436             rtsp_put( rtsp, rest );
437             rtsp_put( rtsp, "" );
438             free( rest );
439             i = rtsp->pf_read( rtsp->p_userdata, (unsigned char*)buffer, size );
440         }
441         else
442         {
443             i = rtsp->pf_read( rtsp->p_userdata, (unsigned char*)buffer + 4, size - 4 );
444             i += 4;
445         }
446     }
447     else i = rtsp->pf_read( rtsp->p_userdata, (unsigned char*)buffer, size );
448
449     //fprintf( stderr, "<< %d of %d bytes\n", i, size );
450
451     return i;
452 }
453
454 /*
455  * connect to a rtsp server
456  */
457
458 int rtsp_connect( rtsp_client_t *rtsp, const char *psz_mrl,
459                   const char *psz_user_agent )
460 {
461     rtsp_t *s;
462     char *mrl_ptr;
463     char *slash, *colon;
464     unsigned int hostend, pathbegin, i;
465
466     if( !psz_mrl ) return -1;
467     s = malloc( sizeof(rtsp_t) );
468     rtsp->p_private = s;
469
470     if( !strncmp( psz_mrl, "rtsp://", 7 ) ) psz_mrl += 7;
471     mrl_ptr = strdup( psz_mrl );
472
473     for( i=0; i<MAX_FIELDS; i++ )
474     {
475         s->answers[i]=NULL;
476         s->scheduled[i]=NULL;
477     }
478
479     s->host = NULL;
480     s->port = 554; /* rtsp standard port */
481     s->path = NULL;
482     s->mrl  = strdup(psz_mrl);
483
484     s->server = NULL;
485     s->server_state = 0;
486     s->server_caps = 0;
487
488     s->cseq = 0;
489     s->session = NULL;
490
491     if( psz_user_agent ) s->user_agent = strdup( psz_user_agent );
492     else s->user_agent = strdup( "User-Agent: RealMedia Player Version "
493                                  "6.0.9.1235 (linux-2.0-libc6-i386-gcc2.95)" );
494
495     slash = strchr( mrl_ptr, '/' );
496     colon = strchr( mrl_ptr, ':' );
497
498     if( !slash ) slash = mrl_ptr + strlen(mrl_ptr) + 1;
499     if( !colon ) colon = slash;
500     if( colon > slash ) colon = slash;
501
502     pathbegin = slash - mrl_ptr;
503     hostend = colon - mrl_ptr;
504
505     s->host = malloc(hostend+1);
506     strncpy( s->host, mrl_ptr, hostend );
507     s->host[hostend] = 0;
508
509     if( pathbegin < strlen(mrl_ptr) ) s->path = strdup(mrl_ptr+pathbegin+1);
510     if( colon != slash )
511     {
512         char buffer[pathbegin-hostend];
513
514         strncpy( buffer, mrl_ptr+hostend+1, pathbegin-hostend-1 );
515         buffer[pathbegin-hostend-1] = 0;
516         s->port = atoi(buffer);
517         if( s->port < 0 || s->port > 65535 ) s->port = 554;
518     }
519
520     free( mrl_ptr );
521     //fprintf( stderr, "got mrl: %s %i %s\n", s->host, s->port, s->path );
522
523     s->s = rtsp->pf_connect( rtsp->p_userdata, s->host, s->port );
524
525     if( s->s < 0 )
526     {
527         //fprintf(stderr, "rtsp: failed to connect to '%s'\n", s->host);
528         rtsp_close( rtsp );
529         return -1;
530     }
531
532     s->server_state = RTSP_CONNECTED;
533
534     /* now lets send an options request. */
535     rtsp_schedule_field( rtsp, "CSeq: 1");
536     rtsp_schedule_field( rtsp, s->user_agent);
537     rtsp_schedule_field( rtsp, "ClientChallenge: "
538                                "9e26d33f2984236010ef6253fb1887f7");
539     rtsp_schedule_field( rtsp, "PlayerStarttime: [28/03/2003:22:50:23 00:00]");
540     rtsp_schedule_field( rtsp, "CompanyID: KnKV4M4I/B2FjJ1TToLycw==" );
541     rtsp_schedule_field( rtsp, "GUID: 00000000-0000-0000-0000-000000000000" );
542     rtsp_schedule_field( rtsp, "RegionData: 0" );
543     rtsp_schedule_field( rtsp, "ClientID: "
544                                "Linux_2.4_6.0.9.1235_play32_RN01_EN_586" );
545     /*rtsp_schedule_field( rtsp, "Pragma: initiate-session" );*/
546     rtsp_request_options( rtsp, NULL );
547
548     return 0;
549 }
550
551 /*
552  * closes an rtsp connection
553  */
554
555 void rtsp_close( rtsp_client_t *rtsp )
556 {
557     if( rtsp->p_private->server_state )
558     {
559         /* TODO: send a TEAROFF */
560         rtsp->pf_disconnect( rtsp->p_userdata );
561     }
562
563     free( rtsp->p_private->path );
564     free( rtsp->p_private->host );
565     free( rtsp->p_private->mrl );
566     free( rtsp->p_private->session );
567     free( rtsp->p_private->user_agent );
568     free( rtsp->p_private->server );
569     rtsp_free_answers( rtsp );
570     rtsp_unschedule_all( rtsp );
571     free( rtsp->p_private );
572 }
573
574 /*
575  * search in answers for tags. returns a pointer to the content
576  * after the first matched tag. returns NULL if no match found.
577  */
578
579 char *rtsp_search_answers( rtsp_client_t *rtsp, const char *tag )
580 {
581     char **answer;
582     char *ptr;
583
584     if( !rtsp->p_private->answers ) return NULL;
585     answer = rtsp->p_private->answers;
586
587     while(*answer)
588     {
589         if( !strncasecmp( *answer, tag, strlen(tag) ) )
590         {
591             ptr = strchr(*answer, ':');
592             ptr++;
593             while( *ptr == ' ' ) ptr++;
594             return ptr;
595         }
596         answer++;
597     }
598
599     return NULL;
600 }
601
602 /*
603  * session id management
604  */
605
606 void rtsp_set_session( rtsp_client_t *rtsp, const char *id )
607 {
608     free( rtsp->p_private->session );
609     rtsp->p_private->session = strdup(id);
610 }
611
612 char *rtsp_get_session( rtsp_client_t *rtsp )
613 {
614     return rtsp->p_private->session;
615 }
616
617 char *rtsp_get_mrl( rtsp_client_t *rtsp )
618 {
619     return rtsp->p_private->mrl;
620 }
621
622 /*
623  * schedules a field for transmission
624  */
625
626 void rtsp_schedule_field( rtsp_client_t *rtsp, const char *string )
627 {
628     int i = 0;
629
630     if( !string ) return;
631
632     while( rtsp->p_private->scheduled[i] ) i++;
633
634     rtsp->p_private->scheduled[i] = strdup(string);
635 }
636
637 /*
638  * removes the first scheduled field which prefix matches string.
639  */
640
641 void rtsp_unschedule_field( rtsp_client_t *rtsp, const char *string )
642 {
643     char **ptr = rtsp->p_private->scheduled;
644
645     if( !string ) return;
646
647     while( *ptr )
648     {
649       if( !strncmp(*ptr, string, strlen(string)) ) break;
650     }
651     free( *ptr );
652     ptr++;
653     do
654     {
655         *(ptr-1) = *ptr;
656     } while( *ptr );
657 }
658
659 /*
660  * unschedule all fields
661  */
662
663 void rtsp_unschedule_all( rtsp_client_t *rtsp )
664 {
665     char **ptr;
666
667     if( !rtsp->p_private->scheduled ) return;
668     ptr = rtsp->p_private->scheduled;
669
670     while( *ptr )
671     {
672         free( *ptr );
673         *ptr = NULL;
674         ptr++;
675     }
676 }
677 /*
678  * free answers
679  */
680
681 void rtsp_free_answers( rtsp_client_t *rtsp )
682 {
683     char **answer;
684
685     if( !rtsp->p_private->answers ) return;
686     answer = rtsp->p_private->answers;
687
688     while( *answer )
689     {
690         free( *answer );
691         *answer = NULL;
692         answer++;
693     }
694 }