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