rtsp.c 37.9 KB
Newer Older
1 2 3
/*****************************************************************************
 * rtsp.c: RTSP support for RTP stream output module
 *****************************************************************************
4
 * Copyright (C) 2003-2004, 2010 VLC authors and VideoLAN
5 6
 * Copyright © 2007 Rémi Denis-Courmont
 *
Pierre's avatar
Pierre committed
7
 * $Id$
8 9
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
10
 *          Pierre Ynard
11
 *
12 13 14
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
15 16 17 18
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
21
 *
22 23 24
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 26 27 28 29
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
30 31 32 33
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

34
#include <vlc_common.h>
35 36 37 38
#include <vlc_sout.h>

#include <vlc_httpd.h>
#include <vlc_url.h>
39 40
#include <vlc_charset.h>
#include <vlc_fs.h>
41
#include <vlc_network.h>
42
#include <vlc_rand.h>
43
#include <assert.h>
44 45
#include <errno.h>
#include <stdlib.h>
46
#include <time.h>
47

48
#ifndef _WIN32
49 50 51 52 53 54
# include <locale.h>
#endif
#ifdef HAVE_XLOCALE_H
# include <xlocale.h>
#endif

55 56
#include "rtp.h"

57
typedef struct rtsp_session_t rtsp_session_t;
58

59 60 61
struct rtsp_stream_t
{
    vlc_mutex_t     lock;
62 63
    vlc_object_t   *owner;
    vod_media_t    *vod_media;
64 65 66
    httpd_host_t   *host;
    httpd_url_t    *url;
    char           *psz_path;
67
    unsigned        track_id;
68 69 70

    int             sessionc;
    rtsp_session_t **sessionv;
71 72 73

    int             timeout;
    vlc_timer_t     timer;
74 75
};

76

77
static int  RtspCallback( httpd_callback_sys_t *p_args,
78 79
                          httpd_client_t *cl, httpd_message_t *answer,
                          const httpd_message_t *query );
80
static int  RtspCallbackId( httpd_callback_sys_t *p_args,
81 82
                            httpd_client_t *cl, httpd_message_t *answer,
                            const httpd_message_t *query );
83
static void RtspClientDel( rtsp_stream_t *rtsp, rtsp_session_t *session );
84

85 86
static void RtspTimeOut( void *data );

87
rtsp_stream_t *RtspSetup( vlc_object_t *owner, vod_media_t *media,
88
                          const char *path )
89
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
90
    rtsp_stream_t *rtsp = calloc( 1, sizeof( *rtsp ) );
91

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
92
    if( unlikely(rtsp == NULL) )
93
        return NULL;
94

95 96
    rtsp->owner = owner;
    rtsp->vod_media = media;
97
    vlc_mutex_init( &rtsp->lock );
98

99 100 101 102 103 104 105
    rtsp->timeout = var_InheritInteger(owner, "rtsp-timeout");
    if (rtsp->timeout > 0)
    {
        if (vlc_timer_create(&rtsp->timer, RtspTimeOut, rtsp))
            goto error;
    }

106
    rtsp->psz_path = strdup( (path != NULL) ? path : "/" );
107 108 109
    if( rtsp->psz_path == NULL )
        goto error;

110
    msg_Dbg( owner, "RTSP stream at %s", rtsp->psz_path );
111

112
    rtsp->host = vlc_rtsp_HostNew( VLC_OBJECT(owner) );
113 114 115
    if( rtsp->host == NULL )
        goto error;

116 117
    char *user = var_InheritString(owner, "sout-rtsp-user");
    char *pwd = var_InheritString(owner, "sout-rtsp-pwd");
Pierre Ynard's avatar
Pierre Ynard committed
118

119
    rtsp->url = httpd_UrlNew( rtsp->host, rtsp->psz_path, user, pwd );
Pierre Ynard's avatar
Pierre Ynard committed
120 121
    free(user);
    free(pwd);
122 123 124 125 126 127 128
    if( rtsp->url == NULL )
        goto error;

    httpd_UrlCatch( rtsp->url, HTTPD_MSG_DESCRIBE, RtspCallback, (void*)rtsp );
    httpd_UrlCatch( rtsp->url, HTTPD_MSG_SETUP,    RtspCallback, (void*)rtsp );
    httpd_UrlCatch( rtsp->url, HTTPD_MSG_PLAY,     RtspCallback, (void*)rtsp );
    httpd_UrlCatch( rtsp->url, HTTPD_MSG_PAUSE,    RtspCallback, (void*)rtsp );
129 130
    httpd_UrlCatch( rtsp->url, HTTPD_MSG_GETPARAMETER, RtspCallback,
                    (void*)rtsp );
131 132 133 134 135 136
    httpd_UrlCatch( rtsp->url, HTTPD_MSG_TEARDOWN, RtspCallback, (void*)rtsp );
    return rtsp;

error:
    RtspUnsetup( rtsp );
    return NULL;
137 138 139
}


140
void RtspUnsetup( rtsp_stream_t *rtsp )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
141
{
142 143
    if( rtsp->url )
        httpd_UrlDelete( rtsp->url );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
144

145 146 147
    if( rtsp->host )
        httpd_HostDelete( rtsp->host );

148 149 150
    while( rtsp->sessionc > 0 )
        RtspClientDel( rtsp, rtsp->sessionv[0] );

151 152
    if (rtsp->timeout > 0)
        vlc_timer_destroy(rtsp->timer);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
153

154
    free( rtsp->psz_path );
155
    vlc_mutex_destroy( &rtsp->lock );
Rémi Duraffort's avatar
Rémi Duraffort committed
156 157

    free( rtsp );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
158 159 160
}


161 162
struct rtsp_stream_id_t
{
163
    rtsp_stream_t    *stream;
164
    sout_stream_id_sys_t *sout_id;
165
    httpd_url_t      *url;
166
    unsigned          track_id;
167
    uint32_t          ssrc;
168 169
    unsigned          clock_rate; /* needed to compute rtptime in RTP-Info */
    int               mcast_fd;
170 171
};

172

173 174
typedef struct rtsp_strack_t rtsp_strack_t;

175 176 177 178
/* For unicast streaming */
struct rtsp_session_t
{
    rtsp_stream_t *stream;
179
    uint64_t       id;
180
    mtime_t        last_seen; /* for timeouts */
181 182

    /* output (id-access) */
183 184
    int            trackc;
    rtsp_strack_t *trackv;
185 186 187
};


188 189 190
/* Unicast session track */
struct rtsp_strack_t
{
191
    rtsp_stream_id_t  *id;
192
    sout_stream_id_sys_t  *sout_id;
Pierre Ynard's avatar
Pierre Ynard committed
193 194
    int          setup_fd;  /* socket created by the SETUP request */
    int          rtp_fd;    /* socket used by the RTP output, when playing */
195 196
    uint32_t     ssrc;
    uint16_t     seq_init;
197 198
};

199
static void RtspTrackClose( rtsp_strack_t *tr );
200

201 202
#define TRACK_PATH_SIZE (sizeof("/trackID=999") - 1)

203 204
char *RtspAppendTrackPath( rtsp_stream_id_t *id, const char *base )
{
205 206
    const char *sep = strlen( base ) > 0 && base[strlen( base ) - 1] == '/' ?
                      "" : "/";
207
    char *url;
208 209

    if( asprintf( &url, "%s%strackID=%u", base, sep, id->track_id ) == -1 )
210 211 212 213 214
        url = NULL;
    return url;
}


215
rtsp_stream_id_t *RtspAddId( rtsp_stream_t *rtsp, sout_stream_id_sys_t *sid,
216
                             uint32_t ssrc, unsigned clock_rate,
217
                             int mcast_fd)
218
{
219 220 221 222 223 224
    if (rtsp->track_id > 999)
    {
        msg_Err(rtsp->owner, "RTSP: too many IDs!");
        return NULL;
    }

225
    char *urlbuf;
226 227 228 229 230 231
    rtsp_stream_id_t *id = malloc( sizeof( *id ) );
    httpd_url_t *url;

    if( id == NULL )
        return NULL;

232
    id->stream = rtsp;
233
    id->sout_id = sid;
234
    id->track_id = rtsp->track_id;
235
    id->ssrc = ssrc;
236
    id->clock_rate = clock_rate;
237
    id->mcast_fd = mcast_fd;
238

239 240 241 242 243 244 245
    urlbuf = RtspAppendTrackPath( id, rtsp->psz_path );
    if( urlbuf == NULL )
    {
        free( id );
        return NULL;
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
246
    msg_Dbg( rtsp->owner, "RTSP: adding %s", urlbuf );
Pierre Ynard's avatar
Pierre Ynard committed
247

248 249
    char *user = var_InheritString(rtsp->owner, "sout-rtsp-user");
    char *pwd = var_InheritString(rtsp->owner, "sout-rtsp-pwd");
Pierre Ynard's avatar
Pierre Ynard committed
250

251
    url = id->url = httpd_UrlNew( rtsp->host, urlbuf, user, pwd );
Pierre Ynard's avatar
Pierre Ynard committed
252 253
    free( user );
    free( pwd );
254
    free( urlbuf );
255

256
    if( url == NULL )
257
    {
258 259
        free( id );
        return NULL;
260 261
    }

262 263 264 265
    httpd_UrlCatch( url, HTTPD_MSG_DESCRIBE, RtspCallbackId, (void *)id );
    httpd_UrlCatch( url, HTTPD_MSG_SETUP,    RtspCallbackId, (void *)id );
    httpd_UrlCatch( url, HTTPD_MSG_PLAY,     RtspCallbackId, (void *)id );
    httpd_UrlCatch( url, HTTPD_MSG_PAUSE,    RtspCallbackId, (void *)id );
266
    httpd_UrlCatch( url, HTTPD_MSG_GETPARAMETER, RtspCallbackId, (void *)id );
267 268
    httpd_UrlCatch( url, HTTPD_MSG_TEARDOWN, RtspCallbackId, (void *)id );

269 270
    rtsp->track_id++;

271
    return id;
272 273 274
}


275
void RtspDelId( rtsp_stream_t *rtsp, rtsp_stream_id_t *id )
276
{
277 278
    httpd_UrlDelete( id->url );

279 280 281 282 283
    vlc_mutex_lock( &rtsp->lock );
    for( int i = 0; i < rtsp->sessionc; i++ )
    {
        rtsp_session_t *ses = rtsp->sessionv[i];

284
        for( int j = 0; j < ses->trackc; j++ )
285
        {
286
            if( ses->trackv[j].id == id )
287
            {
288
                rtsp_strack_t *tr = ses->trackv + j;
289
                RtspTrackClose( tr );
290
                TAB_ERASE(ses->trackc, ses->trackv, j);
291 292 293 294 295 296
            }
        }
    }

    vlc_mutex_unlock( &rtsp->lock );
    free( id );
297 298 299
}


300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
/** rtsp must be locked */
static void RtspUpdateTimer( rtsp_stream_t *rtsp )
{
    if (rtsp->timeout <= 0)
        return;

    mtime_t timeout = 0;
    for (int i = 0; i < rtsp->sessionc; i++)
    {
        if (timeout == 0 || rtsp->sessionv[i]->last_seen < timeout)
            timeout = rtsp->sessionv[i]->last_seen;
    }
    if (timeout != 0)
        timeout += rtsp->timeout * CLOCK_FREQ;
    vlc_timer_schedule(rtsp->timer, true, timeout, 0);
}


static void RtspTimeOut( void *data )
{
    rtsp_stream_t *rtsp = data;

    vlc_mutex_lock(&rtsp->lock);
    mtime_t now = mdate();
    for (int i = rtsp->sessionc - 1; i >= 0; i--)
    {
        if (rtsp->sessionv[i]->last_seen + rtsp->timeout * CLOCK_FREQ < now)
        {
            if (rtsp->vod_media != NULL)
            {
                char psz_sesbuf[17];
                snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%"PRIx64,
                          rtsp->sessionv[i]->id );
                vod_stop(rtsp->vod_media, psz_sesbuf);
            }
            RtspClientDel(rtsp, rtsp->sessionv[i]);
        }
    }
    RtspUpdateTimer(rtsp);
    vlc_mutex_unlock(&rtsp->lock);
}


343 344
/** rtsp must be locked */
static
345
rtsp_session_t *RtspClientNew( rtsp_stream_t *rtsp )
346
{
347 348 349
    rtsp_session_t *s = malloc( sizeof( *s ) );
    if( s == NULL )
        return NULL;
350

351
    s->stream = rtsp;
352
    vlc_rand_bytes (&s->id, sizeof (s->id));
353 354
    s->trackc = 0;
    s->trackv = NULL;
355

356
    TAB_APPEND( rtsp->sessionc, rtsp->sessionv, s );
357

358
    return s;
359 360 361
}


362 363 364
/** rtsp must be locked */
static
rtsp_session_t *RtspClientGet( rtsp_stream_t *rtsp, const char *name )
365
{
366 367
    char *end;
    uint64_t id;
368 369
    int i;

370 371
    if( name == NULL )
        return NULL;
372

373 374 375 376 377
    errno = 0;
    id = strtoull( name, &end, 0x10 );
    if( errno || *end )
        return NULL;

378 379
    /* FIXME: use a hash/dictionary */
    for( i = 0; i < rtsp->sessionc; i++ )
380
    {
381
        if( rtsp->sessionv[i]->id == id )
382
            return rtsp->sessionv[i];
383 384 385 386 387
    }
    return NULL;
}


388 389 390
/** rtsp must be locked */
static
void RtspClientDel( rtsp_stream_t *rtsp, rtsp_session_t *session )
391 392
{
    int i;
393
    TAB_REMOVE( rtsp->sessionc, rtsp->sessionv, session );
394

395
    for( i = 0; i < session->trackc; i++ )
396
        RtspTrackClose( &session->trackv[i] );
397

398
    free( session->trackv );
399
    free( session );
400 401 402
}


403 404 405 406 407 408 409 410 411 412
/** rtsp must be locked */
static void RtspClientAlive( rtsp_session_t *session )
{
    if (session->stream->timeout <= 0)
        return;

    session->last_seen = mdate();
    RtspUpdateTimer(session->stream);
}

Pierre Ynard's avatar
Pierre Ynard committed
413 414 415
static int dup_socket(int oldfd)
{
    int newfd;
416
#ifndef _WIN32
Pierre Ynard's avatar
Pierre Ynard committed
417 418 419 420 421 422 423 424 425
    newfd = vlc_dup(oldfd);
#else
    WSAPROTOCOL_INFO info;
    WSADuplicateSocket (oldfd, GetCurrentProcessId (), &info);
    newfd = WSASocket (info.iAddressFamily, info.iSocketType,
                       info.iProtocol, &info, 0, 0);
#endif
    return newfd;
}
426

427 428 429
/* Attach a starting VoD RTP id to its RTSP track, and let it
 * initialize with the parameters of the SETUP request */
int RtspTrackAttach( rtsp_stream_t *rtsp, const char *name,
430
                     rtsp_stream_id_t *id, sout_stream_id_sys_t *sout_id,
431 432 433 434 435 436 437 438 439 440 441
                     uint32_t *ssrc, uint16_t *seq_init )
{
    int val = VLC_EGENERIC;
    rtsp_session_t *session;

    vlc_mutex_lock(&rtsp->lock);
    session = RtspClientGet(rtsp, name);

    if (session == NULL)
        goto out;

442
    rtsp_strack_t *tr = NULL;
Pierre Ynard's avatar
Pierre Ynard committed
443
    for (int i = 0; i < session->trackc; i++)
444
    {
445
        if (session->trackv[i].id == id)
446
        {
447 448 449 450
            tr = session->trackv + i;
            break;
        }
    }
451

452 453 454 455 456 457 458 459 460 461 462 463 464 465
    if (tr != NULL)
    {
        tr->sout_id = sout_id;
        tr->rtp_fd = dup_socket(tr->setup_fd);
    }
    else
    {
        /* The track was not SETUP. We still create one because we'll
         * need the sout_id if we set it up later. */
        rtsp_strack_t track = { .id = id, .sout_id = sout_id,
                                .setup_fd = -1, .rtp_fd = -1 };
        vlc_rand_bytes (&track.seq_init, sizeof (track.seq_init));
        vlc_rand_bytes (&track.ssrc, sizeof (track.ssrc));

466
        TAB_APPEND(session->trackc, session->trackv, track);
467
        tr = session->trackv + session->trackc - 1;
468
    }
Pierre Ynard's avatar
Pierre Ynard committed
469

470 471
    *ssrc = ntohl(tr->ssrc);
    *seq_init = tr->seq_init;
472

473 474 475 476 477 478 479 480
    if (tr->rtp_fd != -1)
    {
        uint16_t seq;
        rtp_add_sink(tr->sout_id, tr->rtp_fd, false, &seq);
        /* To avoid race conditions, sout_id->i_seq_sent_next must
         * be set here and now. Make sure the caller did its job
         * properly when passing seq_init. */
        assert(tr->seq_init == seq);
481 482
    }

483
    val = VLC_SUCCESS;
484 485 486 487 488 489 490 491
out:
    vlc_mutex_unlock(&rtsp->lock);
    return val;
}


/* Remove references to the RTP id when it is stopped */
void RtspTrackDetach( rtsp_stream_t *rtsp, const char *name,
492
                      sout_stream_id_sys_t *sout_id )
493 494 495 496 497 498 499 500 501
{
    rtsp_session_t *session;

    vlc_mutex_lock(&rtsp->lock);
    session = RtspClientGet(rtsp, name);

    if (session == NULL)
        goto out;

Pierre Ynard's avatar
Pierre Ynard committed
502
    for (int i = 0; i < session->trackc; i++)
503 504 505 506
    {
        rtsp_strack_t *tr = session->trackv + i;
        if (tr->sout_id == sout_id)
        {
507 508 509 510 511
            if (tr->setup_fd == -1)
            {
                /* No (more) SETUP information: better get rid of the
                 * track so that we can have new random ssrc and
                 * seq_init next time. */
512
                TAB_ERASE(session->trackc, session->trackv, i);
513 514 515 516 517 518 519 520
                break;
            }
            /* We keep the SETUP information of the track, but stop it */
            if (tr->rtp_fd != -1)
            {
                rtp_del_sink(tr->sout_id, tr->rtp_fd);
                tr->rtp_fd = -1;
            }
521 522 523 524 525 526 527 528 529 530 531 532 533
            tr->sout_id = NULL;
            break;
        }
    }

out:
    vlc_mutex_unlock(&rtsp->lock);
}


/** rtsp must be locked */
static void RtspTrackClose( rtsp_strack_t *tr )
{
534 535 536 537 538 539 540 541 542 543
    if (tr->setup_fd != -1)
    {
        if (tr->rtp_fd != -1)
        {
            rtp_del_sink(tr->sout_id, tr->rtp_fd);
            tr->rtp_fd = -1;
        }
        net_Close(tr->setup_fd);
        tr->setup_fd = -1;
    }
544 545 546
}


547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
/** Finds the next transport choice */
static inline const char *transport_next( const char *str )
{
    /* Looks for comma */
    str = strchr( str, ',' );
    if( str == NULL )
        return NULL; /* No more transport options */

    str++; /* skips comma */
    while( strchr( "\r\n\t ", *str ) )
        str++;

    return (*str) ? str : NULL;
}


/** Finds the next transport parameter */
static inline const char *parameter_next( const char *str )
{
    while( strchr( ",;", *str ) == NULL )
        str++;

    return (*str == ';') ? (str + 1) : NULL;
}


573
static int64_t ParseNPT (const char *str)
574 575 576 577 578 579 580 581 582 583
{
    locale_t loc = newlocale (LC_NUMERIC_MASK, "C", NULL);
    locale_t oldloc = uselocale (loc);
    unsigned hour, min;
    float sec;

    if (sscanf (str, "%u:%u:%f", &hour, &min, &sec) == 3)
        sec += ((hour * 60) + min) * 60;
    else
    if (sscanf (str, "%f", &sec) != 1)
584
        sec = -1;
585 586 587 588 589 590

    if (loc != (locale_t)0)
    {
        uselocale (oldloc);
        freelocale (loc);
    }
591
    return sec < 0 ? -1 : sec * CLOCK_FREQ;
592 593 594
}


595 596 597 598 599 600 601 602
/** RTSP requests handler
 * @param id selected track for non-aggregate URLs,
 *           NULL for aggregate URLs
 */
static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
                        httpd_client_t *cl,
                        httpd_message_t *answer,
                        const httpd_message_t *query )
603
{
604
    vlc_object_t *owner = rtsp->owner;
605
    char psz_sesbuf[17];
606
    const char *psz_session = NULL, *psz;
607 608
    char control[sizeof("rtsp://[]:12345") + NI_MAXNUMERICHOST
                  + strlen( rtsp->psz_path )];
609
    bool vod = rtsp->vod_media != NULL;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
610 611 612
    time_t now;

    time (&now);
613

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
614
    if( answer == NULL || query == NULL || cl == NULL )
615
        return VLC_SUCCESS;
616 617 618 619
    else
    {
        /* Build self-referential control URL */
        char ip[NI_MAXNUMERICHOST], *ptr;
620
        int port;
621

622
        httpd_ServerIP( cl, ip, &port );
623 624 625 626 627
        ptr = strchr( ip, '%' );
        if( ptr != NULL )
            *ptr = '\0';

        if( strchr( ip, ':' ) != NULL )
628
            sprintf( control, "rtsp://[%s]:%d%s", ip, port, rtsp->psz_path );
629
        else
630
            sprintf( control, "rtsp://%s:%d%s", ip, port, rtsp->psz_path );
631
    }
632

633
    /* */
634
    answer->i_proto = HTTPD_PROTO_RTSP;
635
    answer->i_version= 0;
636 637 638 639
    answer->i_type   = HTTPD_MSG_ANSWER;
    answer->i_body = 0;
    answer->p_body = NULL;

640
    httpd_MsgAdd( answer, "Server", "VLC/%s", VERSION );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655

    /* Date: is always allowed, and sometimes mandatory with RTSP/2.0. */
    struct tm ut;
    if (gmtime_r (&now, &ut) != NULL)
    {   /* RFC1123 format, GMT is mandatory */
        static const char wdays[7][4] = {
            "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
        static const char mons[12][4] = {
            "Jan", "Feb", "Mar", "Apr", "May", "Jun",
            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
        httpd_MsgAdd (answer, "Date", "%s, %02u %s %04u %02u:%02u:%02u GMT",
                      wdays[ut.tm_wday], ut.tm_mday, mons[ut.tm_mon],
                      1900 + ut.tm_year, ut.tm_hour, ut.tm_min, ut.tm_sec);
    }

656 657 658 659 660
    if( query->i_proto != HTTPD_PROTO_RTSP )
    {
        answer->i_status = 505;
    }
    else
661 662 663
    if( httpd_MsgGet( query, "Require" ) != NULL )
    {
        answer->i_status = 551;
664
        httpd_MsgAdd( answer, "Unsupported", "%s",
665 666 667 668 669 670
                      httpd_MsgGet( query, "Require" ) );
    }
    else
    switch( query->i_type )
    {
        case HTTPD_MSG_DESCRIBE:
671 672 673 674 675 676 677
        {   /* Aggregate-only */
            if( id != NULL )
            {
                answer->i_status = 460;
                break;
            }

678 679
            answer->i_status = 200;
            httpd_MsgAdd( answer, "Content-Type",  "%s", "application/sdp" );
680
            httpd_MsgAdd( answer, "Content-Base",  "%s", control );
681 682 683 684

            answer->p_body = (uint8_t *) ( vod ?
                SDPGenerateVoD( rtsp->vod_media, control ) :
                SDPGenerate( (sout_stream_t *)owner, control ) );
685 686 687 688
            if( answer->p_body != NULL )
                answer->i_body = strlen( (char *)answer->p_body );
            else
                answer->i_status = 500;
689 690 691 692
            break;
        }

        case HTTPD_MSG_SETUP:
693 694
            /* Non-aggregate-only */
            if( id == NULL )
695
            {
696
                answer->i_status = 459;
697 698
                break;
            }
699

700 701 702 703 704 705 706
            psz_session = httpd_MsgGet( query, "Session" );
            answer->i_status = 461;

            for( const char *tpt = httpd_MsgGet( query, "Transport" );
                 tpt != NULL;
                 tpt = transport_next( tpt ) )
            {
707
                bool b_multicast = true, b_unsupp = false;
708
                bool b_multicast_port_set = false;
709
                unsigned loport = 5004, hiport; /* from RFC3551 */
710
                unsigned mloport = 5004, mhiport = mloport + 1;
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727

                /* Check transport protocol. */
                /* Currently, we only support RTP/AVP over UDP */
                if( strncmp( tpt, "RTP/AVP", 7 ) )
                    continue;
                tpt += 7;
                if( strncmp( tpt, "/UDP", 4 ) == 0 )
                    tpt += 4;
                if( strchr( ";,", *tpt ) == NULL )
                    continue;

                /* Parse transport options */
                for( const char *opt = parameter_next( tpt );
                     opt != NULL;
                     opt = parameter_next( opt ) )
                {
                    if( strncmp( opt, "multicast", 9 ) == 0)
728
                        b_multicast = true;
729 730
                    else
                    if( strncmp( opt, "unicast", 7 ) == 0 )
731
                        b_multicast = false;
732
                    else
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
733 734
                    if( sscanf( opt, "client_port=%u-%u", &loport, &hiport )
                                == 2 )
735 736
                        ;
                    else
737 738 739 740
                    if( sscanf( opt, "port=%u-%u", &mloport, &mhiport )
                                == 2 )
                        b_multicast_port_set = true;
                    else
741 742 743 744 745 746
                    if( strncmp( opt, "mode=", 5 ) == 0 )
                    {
                        if( strncasecmp( opt + 5, "play", 4 )
                         && strncasecmp( opt + 5, "\"PLAY\"", 6 ) )
                        {
                            /* Not playing?! */
747
                            b_unsupp = true;
748 749 750 751
                            break;
                        }
                    }
                    else
752 753 754
                    if( strncmp( opt,"destination=", 12 ) == 0 )
                    {
                        answer->i_status = 403;
755
                        b_unsupp = true;
756 757
                    }
                    else
758 759 760 761
                    {
                    /*
                     * Every other option is unsupported:
                     *
762 763
                     * "source" and "append" are invalid (server-only);
                     * "ssrc" also (as clarified per RFC2326bis).
764
                     *
765
                     * For multicast, "layers", "ttl" are set by the
766 767
                     * stream output configuration.
                     *
768
                     * For unicast, we want to decide "server_port" values.
769
                     *
770
                     * "interleaved" is not implemented.
771
                     */
772
                        b_unsupp = true;
773 774 775 776 777 778 779 780 781
                        break;
                    }
                }

                if( b_unsupp )
                    continue;

                if( b_multicast )
                {
782 783 784
                    char dst[NI_MAXNUMERICHOST];
                    int dport, ttl;
                    if( id->mcast_fd == -1 )
785 786
                        continue;

787 788
                    net_GetPeerAddress(id->mcast_fd, dst, &dport);

789 790 791 792 793 794 795 796 797
                    /* Checking for multicast port override */
                    if( b_multicast_port_set
                     && ((unsigned)dport != mloport
                      || (unsigned)dport + 1 != mhiport))
                    {
                        answer->i_status = 551;
                        continue;
                    }

798 799 800 801 802 803
                    ttl = var_InheritInteger(owner, "ttl");
                    if (ttl <= 0)
                    /* FIXME: the TTL is left to the OS default, we can
                     * only guess that it's 1. */
                        ttl = 1;

804 805 806
                    if( psz_session == NULL )
                    {
                        /* Create a dummy session ID */
807 808
                        snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%lu",
                                  vlc_mrand48() );
809 810
                        psz_session = psz_sesbuf;
                    }
811 812 813
                    answer->i_status = 200;

                    httpd_MsgAdd( answer, "Transport",
814
                                  "RTP/AVP/UDP;destination=%s;port=%u-%u;"
815
                                  "ttl=%d;mode=play",
816 817
                                  dst, dport, dport + 1, ttl );
                     /* FIXME: this doesn't work with RTP + RTCP mux */
818 819 820
                }
                else
                {
821
                    char ip[NI_MAXNUMERICHOST], src[NI_MAXNUMERICHOST];
822
                    rtsp_session_t *ses = NULL;
823 824
                    int fd, sport;
                    uint32_t ssrc;
825

826
                    if( httpd_ClientIP( cl, ip, NULL ) == NULL )
827 828 829 830 831
                    {
                        answer->i_status = 500;
                        continue;
                    }

832 833 834
                    fd = net_ConnectDgram( owner, ip, loport, -1,
                                           IPPROTO_UDP );
                    if( fd == -1 )
835
                    {
836
                        msg_Err( owner,
837 838
                                 "cannot create RTP socket for %s port %u",
                                 ip, loport );
839
                        answer->i_status = 500;
840 841 842
                        continue;
                    }

843
                    /* Ignore any unexpected incoming packet */
844
                    setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &(int){ 0 },
845
                                sizeof (int));
846 847
                    net_GetSockAddress( fd, src, &sport );

848
                    vlc_mutex_lock( &rtsp->lock );
849 850
                    if( psz_session == NULL )
                    {
851
                        ses = RtspClientNew( rtsp );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
852
                        snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%"PRIx64,
853 854
                                  ses->id );
                        psz_session = psz_sesbuf;
855 856 857
                    }
                    else
                    {
858
                        ses = RtspClientGet( rtsp, psz_session );
859 860 861
                        if( ses == NULL )
                        {
                            answer->i_status = 454;
862
                            vlc_mutex_unlock( &rtsp->lock );
863
                            net_Close( fd );
864 865
                            continue;
                        }
866
                    }
867
                    RtspClientAlive(ses);
868

869
                    rtsp_strack_t *tr = NULL;
870 871 872 873
                    for (int i = 0; i < ses->trackc; i++)
                    {
                        if (ses->trackv[i].id == id)
                        {
874
                            tr = ses->trackv + i;
875 876 877
                            break;
                        }
                    }
878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896

                    if (tr == NULL)
                    {
                        /* Set up a new track */
                        rtsp_strack_t track = { .id = id,
                                                .sout_id = id->sout_id,
                                                .setup_fd = fd,
                                                .rtp_fd = -1 };

                        if (vod)
                        {
                            vlc_rand_bytes (&track.seq_init,
                                            sizeof (track.seq_init));
                            vlc_rand_bytes (&track.ssrc, sizeof (track.ssrc));
                            ssrc = track.ssrc;
                        }
                        else
                            ssrc = id->ssrc;

897
                        TAB_APPEND(ses->trackc, ses->trackv, track);
898 899 900 901 902 903
                    }
                    else if (tr->setup_fd == -1)
                    {
                        /* The track was not SETUP, but it exists
                         * because there is a sout_id running for it */
                        tr->setup_fd = fd;
Pierre Ynard's avatar
Pierre Ynard committed
904
                        ssrc = tr->ssrc;
905 906
                    }
                    else
907
                    {
908 909 910
                        /* The track is already set up, and we don't
                         * support changing the transport parameters on
                         * the fly */
911 912 913 914 915
                        vlc_mutex_unlock( &rtsp->lock );
                        answer->i_status = 455;
                        net_Close( fd );
                        break;
                    }
916
                    vlc_mutex_unlock( &rtsp->lock );
917

918
                    httpd_ServerIP( cl, ip, NULL );
919

Pierre Ynard's avatar
Pierre Ynard committed
920 921
                    /* Specify source IP only if it is different from the
                     * RTSP control connection server address */
922
                    if( strcmp( src, ip ) )
923 924 925 926 927
                    {
                        char *ptr = strchr( src, '%' );
                        if( ptr != NULL ) *ptr = '\0'; /* remove scope ID */
                    }
                    else
Pierre Ynard's avatar
Pierre Ynard committed
928 929 930 931 932 933 934 935
                        src[0] = '\0';

                    httpd_MsgAdd( answer, "Transport",
                                  "RTP/AVP/UDP;unicast%s%s;"
                                  "client_port=%u-%u;server_port=%u-%u;"
                                  "ssrc=%08X;mode=play",
                                  src[0] ? ";source=" : "", src,
                                  loport, loport + 1, sport, sport + 1, ssrc );
936 937 938 939 940 941 942

                    answer->i_status = 200;
                }
                break;
            }
            break;

943 944 945 946 947
        case HTTPD_MSG_PLAY:
        {
            rtsp_session_t *ses;
            answer->i_status = 200;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
948
            psz_session = httpd_MsgGet( query, "Session" );
949
            int64_t start = -1, end = -1, npt;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
950
            const char *range = httpd_MsgGet (query, "Range");
951
            if (range != NULL)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
952
            {
953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968
                if (strncmp (range, "npt=", 4))
                {
                    answer->i_status =