Move closer to an asynchronous structure (still some parsing code to be converted).
[platform/upstream/pulseaudio.git] / src / modules / rtp / rtsp.c
1 /* $Id$ */
2
3 /***
4   This file is part of PulseAudio.
5
6   Copyright 2008 Colin Guthrie
7
8   PulseAudio is free software; you can redistribute it and/or modify
9   it under the terms of the GNU Lesser General Public License as published
10   by the Free Software Foundation; either version 2 of the License,
11   or (at your option) any later version.
12
13   PulseAudio is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with PulseAudio; if not, write to the Free Software
20   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21   USA.
22 ***/
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <fcntl.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <arpa/inet.h>
33 #include <unistd.h>
34 #include <sys/ioctl.h>
35
36 #ifdef HAVE_SYS_FILIO_H
37 #include <sys/filio.h>
38 #endif
39
40 #include <pulse/xmalloc.h>
41
42 #include <pulsecore/core-error.h>
43 #include <pulsecore/core-util.h>
44 #include <pulsecore/socket-util.h>
45 #include <pulsecore/log.h>
46 #include <pulsecore/macro.h>
47 #include <pulsecore/strbuf.h>
48 #include <pulsecore/poll.h>
49
50 #include "rtsp.h"
51
52 struct pa_rtsp_context {
53     pa_socket_client *sc;
54     pa_iochannel *io;
55     pa_rtsp_cb_t callback;
56     void* userdata;
57     const char* useragent;
58     pa_headerlist* headers;
59     char* localip;
60     char* url;
61     uint32_t port;
62     uint32_t cseq;
63     char* session;
64     char* transport;
65     pa_rtsp_state state;
66 };
67
68 /*
69  * read one line from the file descriptor
70  * timeout: msec unit, -1 for infinite
71  * if CR comes then following LF is expected
72  * returned string in line is always null terminated, maxlen-1 is maximum string length
73  */
74 static int pa_read_line(pa_iochannel* io, char *line, int maxlen, int timeout)
75 {
76     int i, rval;
77     int count;
78     int fd;
79     char ch;
80     struct pollfd pfds;
81
82     pa_assert(io);
83     fd = pa_iochannel_get_recv_fd(io);
84
85     count = 0;
86     *line = 0;
87     pfds.events = POLLIN;
88     pfds.fd = fd;
89
90     for (i=0; i<maxlen; ++i) {
91         if (!poll(&pfds, 1, timeout))
92             return 0;
93
94         rval = read(fd, &ch, 1);
95
96         if (-1 == rval) {
97             if (EAGAIN == errno)
98                 return 0;
99             /*ERRMSG("%s:read error: %s\n", __func__, strerror(errno));*/
100             return -1;
101         }
102
103         if (0 == rval) {
104             /*INFMSG("%s:disconnected on the other end\n", __func__);*/
105             return -1;
106         }
107
108         if ('\n' == ch) {
109             *line = 0;
110             return count;
111         }
112
113         if ('\r' == ch)
114             continue;
115
116         *line++ = ch;
117         count++;
118
119         if (count >= maxlen-1)
120             break;
121     }
122
123     *line = 0;
124     return count;
125 }
126
127
128 static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd,
129                         const char* content_type, const char* content,
130                         int expect_response,
131                         pa_headerlist* headers) {
132     pa_strbuf* buf;
133     char* hdrs;
134     ssize_t l;
135
136     pa_assert(c);
137     pa_assert(c->url);
138
139     if (!cmd)
140         return -1;
141
142     buf = pa_strbuf_new();
143     pa_strbuf_printf(buf, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, c->url, ++c->cseq);
144     if (c->session)
145         pa_strbuf_printf(buf, "Session: %s\r\n", c->session);
146
147     /* Add the headers */
148     if (headers) {
149         hdrs = pa_headerlist_to_string(headers);
150         pa_strbuf_puts(buf, hdrs);
151         pa_xfree(hdrs);
152     }
153
154     if (content_type && content) {
155         pa_strbuf_printf(buf, "Content-Type: %s\r\nContent-Length: %d\r\n",
156           content_type, (int)strlen(content));
157     }
158
159     pa_strbuf_printf(buf, "User-Agent: %s\r\n", c->useragent);
160
161     if (c->headers) {
162         hdrs = pa_headerlist_to_string(c->headers);
163         pa_strbuf_puts(buf, hdrs);
164         pa_xfree(hdrs);
165     }
166
167     pa_strbuf_puts(buf, "\r\n");
168
169     if (content_type && content) {
170         pa_strbuf_puts(buf, content);
171     }
172
173     /* Our packet is created... now we can send it :) */
174     hdrs = pa_strbuf_tostring_free(buf);
175     l = pa_iochannel_write(c->io, hdrs, strlen(hdrs));
176     pa_xfree(hdrs);
177
178     return 0;
179 }
180
181
182 pa_rtsp_context* pa_rtsp_context_new(const char* useragent) {
183     pa_rtsp_context *c;
184
185     c = pa_xnew0(pa_rtsp_context, 1);
186     c->headers = pa_headerlist_new();
187
188     if (useragent)
189         c->useragent = useragent;
190     else
191         c->useragent = "PulseAudio RTSP Client";
192
193     return c;
194 }
195
196
197 void pa_rtsp_context_free(pa_rtsp_context* c) {
198     if (c) {
199         if (c->sc)
200             pa_socket_client_unref(c->sc);
201
202         pa_xfree(c->url);
203         pa_xfree(c->localip);
204         pa_xfree(c->session);
205         pa_xfree(c->transport);
206         pa_headerlist_free(c->headers);
207     }
208     pa_xfree(c);
209 }
210
211
212 static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void *userdata) {
213     pa_strbuf* buf;
214     pa_headerlist* response_headers = NULL;
215     char response[1024];
216     int timeout;
217     char* token;
218     char* header;
219     char* delimpos;
220     char delimiters[] = " ";
221     pa_rtsp_context *c = userdata;
222     pa_assert(c);
223
224     /* TODO: convert this to a pa_ioline based reader */
225     if (STATE_CONNECT == c->state) {
226         response_headers = pa_headerlist_new();
227     }
228     timeout = 5000;
229     /* read in any response headers */
230     if (pa_read_line(c->io, response, sizeof(response), timeout) > 0) {
231         const char* token_state = NULL;
232
233         timeout = 1000;
234         pa_xfree(pa_split(response, delimiters, &token_state));
235         token = pa_split(response, delimiters, &token_state);
236         if (!token || strcmp(token, "200")) {
237             pa_xfree(token);
238             pa_log("Invalid Response");
239             /* TODO: Bail out completely */
240             return;
241         }
242         pa_xfree(token);
243
244         /* We want to return the headers? */
245         if (!response_headers) {
246             /* We have no storage, so just clear out the response. */
247             while (pa_read_line(c->io, response, sizeof(response), timeout) > 0);
248         } else {
249             /* TODO: Move header reading into the headerlist. */
250             header = NULL;
251             buf = pa_strbuf_new();
252             while (pa_read_line(c->io, response, sizeof(response), timeout) > 0) {
253                 /* If the first character is a space, it's a continuation header */
254                 if (header && ' ' == response[0]) {
255                     /* Add this line to the buffer (sans the space. */
256                     pa_strbuf_puts(buf, &(response[1]));
257                     continue;
258                 }
259
260                 if (header) {
261                     /* This is not a continuation header so let's dump the full
262                       header/value into our proplist */
263                     pa_headerlist_puts(response_headers, header, pa_strbuf_tostring_free(buf));
264                     pa_xfree(header);
265                     buf = pa_strbuf_new();
266                 }
267
268                 delimpos = strstr(response, ":");
269                 if (!delimpos) {
270                     pa_log("Invalid response header");
271                     return;
272                 }
273
274                 if (strlen(delimpos) > 1) {
275                     /* Cut our line off so we can copy the header name out */
276                     *delimpos++ = '\0';
277
278                     /* Trim the front of any spaces */
279                     while (' ' == *delimpos)
280                         ++delimpos;
281
282                     pa_strbuf_puts(buf, delimpos);
283                 } else {
284                     /* Cut our line off so we can copy the header name out */
285                     *delimpos = '\0';
286                 }
287
288                 /* Save the header name */
289                 header = pa_xstrdup(response);
290             }
291             /* We will have a header left from our looping itteration, so add it in :) */
292             if (header) {
293                 /* This is not a continuation header so let's dump it into our proplist */
294                 pa_headerlist_puts(response_headers, header, pa_strbuf_tostring(buf));
295             }
296             pa_strbuf_free(buf);
297         }
298     }
299
300     /* Deal with a CONNECT response */
301     if (STATE_CONNECT == c->state) {
302         const char* token_state = NULL;
303         const char* pc = NULL;
304         c->session = pa_xstrdup(pa_headerlist_gets(response_headers, "Session"));
305         c->transport = pa_xstrdup(pa_headerlist_gets(response_headers, "Transport"));
306
307         if (!c->session || !c->transport) {
308             pa_headerlist_free(response_headers);
309             return;
310         }
311
312         /* Now parse out the server port component of the response. */
313         c->port = 0;
314         delimiters[0] = ';';
315         while ((token = pa_split(c->transport, delimiters, &token_state))) {
316             if ((pc = strstr(token, "="))) {
317                 if (0 == strncmp(token, "server_port", 11)) {
318                     pa_atou(pc+1, &c->port);
319                     pa_xfree(token);
320                     break;
321                 }
322             }
323             pa_xfree(token);
324         }
325         if (0 == c->port) {
326             /* Error no server_port in response */
327             pa_headerlist_free(response_headers);
328             return;
329         }
330     }
331
332     /* Call our callback */
333     if (c->callback)
334         c->callback(c, c->state, response_headers, c->userdata);
335
336
337     if (response_headers)
338         pa_headerlist_free(response_headers);
339
340     /*
341     if (do_read(u) < 0 || do_write(u) < 0) {
342
343         if (u->io) {
344             pa_iochannel_free(u->io);
345             u->io = NULL;
346         }
347
348        pa_module_unload_request(u->module);
349     }
350     */
351 }
352
353 static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
354     pa_rtsp_context *c = userdata;
355     union {
356         struct sockaddr sa;
357         struct sockaddr_in in;
358         struct sockaddr_in6 in6;
359     } sa;
360     socklen_t sa_len = sizeof(sa);
361
362     pa_assert(sc);
363     pa_assert(c);
364     pa_assert(c->sc == sc);
365
366     pa_socket_client_unref(c->sc);
367     c->sc = NULL;
368
369     if (!io) {
370         pa_log("Connection failed: %s", pa_cstrerror(errno));
371         return;
372     }
373     pa_assert(!c->io);
374     c->io = io;
375     pa_iochannel_set_callback(c->io, io_callback, c);
376
377     /* Get the local IP address for use externally */
378     if (0 == getsockname(pa_iochannel_get_recv_fd(io), &sa.sa, &sa_len)) {
379         char buf[INET6_ADDRSTRLEN];
380         const char *res = NULL;
381
382         if (AF_INET == sa.sa.sa_family) {
383             res = inet_ntop(sa.sa.sa_family, &sa.in.sin_addr, buf, sizeof(buf));
384         } else if (AF_INET6 == sa.sa.sa_family) {
385             res = inet_ntop(AF_INET6, &sa.in6.sin6_addr, buf, sizeof(buf));
386         }
387         if (res)
388             c->localip = pa_xstrdup(res);
389     }
390 }
391
392 int pa_rtsp_connect(pa_rtsp_context *c, pa_mainloop_api *mainloop, const char* hostname, uint16_t port) {
393     pa_assert(c);
394     pa_assert(mainloop);
395     pa_assert(hostname);
396     pa_assert(port > 0);
397
398     if (!(c->sc = pa_socket_client_new_string(mainloop, hostname, port))) {
399         pa_log("failed to connect to server '%s:%d'", hostname, port);
400         return -1;
401     }
402
403     pa_socket_client_set_callback(c->sc, on_connection, c);
404     c->state = STATE_CONNECT;
405     return 0;
406 }
407
408 void pa_rtsp_set_callback(pa_rtsp_context *c, pa_rtsp_cb_t callback, void *userdata) {
409     pa_assert(c);
410
411     c->callback = callback;
412     c->userdata = userdata;
413 }
414
415 void pa_rtsp_disconnect(pa_rtsp_context *c) {
416     pa_assert(c);
417
418     if (c->io)
419         pa_iochannel_free(c->io);
420     c->io = NULL;
421 }
422
423
424 const char* pa_rtsp_localip(pa_rtsp_context* c) {
425     pa_assert(c);
426
427     return c->localip;
428 }
429
430 uint32_t pa_rtsp_serverport(pa_rtsp_context* c) {
431     pa_assert(c);
432
433     return c->port;
434 }
435
436 void pa_rtsp_set_url(pa_rtsp_context* c, const char* url) {
437     pa_assert(c);
438
439     c->url = pa_xstrdup(url);
440 }
441
442 void pa_rtsp_add_header(pa_rtsp_context *c, const char* key, const char* value)
443 {
444     pa_assert(c);
445     pa_assert(key);
446     pa_assert(value);
447
448     pa_headerlist_puts(c->headers, key, value);
449 }
450
451 void pa_rtsp_remove_header(pa_rtsp_context *c, const char* key)
452 {
453     pa_assert(c);
454     pa_assert(key);
455
456     pa_headerlist_remove(c->headers, key);
457 }
458
459 int pa_rtsp_announce(pa_rtsp_context *c, const char* sdp) {
460     pa_assert(c);
461     if (!sdp)
462         return -1;
463
464     c->state = STATE_ANNOUNCE;
465     return pa_rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL);
466 }
467
468
469 int pa_rtsp_setup(pa_rtsp_context* c) {
470     pa_headerlist* headers;
471     int rv;
472
473     pa_assert(c);
474
475     headers = pa_headerlist_new();
476     pa_headerlist_puts(headers, "Transport", "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record");
477
478     c->state = STATE_SETUP;
479     rv = pa_rtsp_exec(c, "SETUP", NULL, NULL, 1, headers);
480     pa_headerlist_free(headers);
481     return rv;
482 }
483
484
485 int pa_rtsp_record(pa_rtsp_context* c) {
486     pa_headerlist* headers;
487     int rv;
488
489     pa_assert(c);
490     if (!c->session) {
491         /* No seesion in progres */
492         return -1;
493     }
494
495     headers = pa_headerlist_new();
496     pa_headerlist_puts(headers, "Range", "npt=0-");
497     pa_headerlist_puts(headers, "RTP-Info", "seq=0;rtptime=0");
498
499     c->state = STATE_RECORD;
500     rv = pa_rtsp_exec(c, "RECORD", NULL, NULL, 1, headers);
501     pa_headerlist_free(headers);
502     return rv;
503 }
504
505
506 int pa_rtsp_teardown(pa_rtsp_context *c) {
507     pa_assert(c);
508
509     c->state = STATE_TEARDOWN;
510     return pa_rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL);
511 }
512
513
514 int pa_rtsp_setparameter(pa_rtsp_context *c, const char* param) {
515     pa_assert(c);
516     if (!param)
517         return -1;
518
519     c->state = STATE_SET_PARAMETER;
520     return pa_rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL);
521 }
522
523
524 int pa_rtsp_flush(pa_rtsp_context *c) {
525     pa_headerlist* headers;
526     int rv;
527
528     pa_assert(c);
529
530     headers = pa_headerlist_new();
531     pa_headerlist_puts(headers, "RTP-Info", "seq=0;rtptime=0");
532
533     c->state = STATE_FLUSH;
534     rv = pa_rtsp_exec(c, "FLUSH", NULL, NULL, 1, headers);
535     pa_headerlist_free(headers);
536     return rv;
537 }