You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
464 lines
11 KiB
464 lines
11 KiB
/**
|
|
* @file sdp.c
|
|
* @brief Real-Time Protocol (RTP) demux module for VLC media player
|
|
*/
|
|
/*****************************************************************************
|
|
* Copyright © 2020 Rémi Denis-Courmont
|
|
*
|
|
* This library 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 (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this library; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "sdp.h"
|
|
#include <vlc_common.h>
|
|
|
|
static bool istokenchar(unsigned char c)
|
|
{ /* RFC4566 §9 */
|
|
if (c < 0x21 || c > 0x7E)
|
|
return false;
|
|
if (memchr("\x22\x28\x29\x2C\x2F\x5B\x5C\x5D", c, 8) != NULL)
|
|
return false;
|
|
if (c > 0x39 && c < 0x41)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static size_t vlc_sdp_token_length(const char *str)
|
|
{
|
|
const char *p = str;
|
|
|
|
while (istokenchar(*p))
|
|
p++;
|
|
return p - str;
|
|
}
|
|
|
|
static bool vlc_sdp_is_token(const char *str)
|
|
{
|
|
return str[vlc_sdp_token_length(str)] == '\0';
|
|
}
|
|
|
|
static void vlc_sdp_conn_free(struct vlc_sdp_conn **conn)
|
|
{
|
|
struct vlc_sdp_conn *c = *conn;
|
|
|
|
*conn = c->next;
|
|
free(c);
|
|
}
|
|
|
|
static struct vlc_sdp_conn *vlc_sdp_conn_parse(const char *str, size_t len)
|
|
{
|
|
const char *end = str + len;
|
|
const char *net_type = str;
|
|
const char *addr_type = memchr(str, ' ', len);
|
|
|
|
if (addr_type == NULL) {
|
|
bad:
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
addr_type++; /* skip white space */
|
|
const char *addr = memchr(addr_type, ' ', end - addr_type);
|
|
|
|
if (addr == NULL)
|
|
goto bad;
|
|
|
|
addr++; /* skip white space */
|
|
|
|
if (memchr(addr, ' ', end - addr) != NULL)
|
|
goto bad;
|
|
|
|
size_t addrlen = end - addr;
|
|
struct vlc_sdp_conn *c = malloc(sizeof (*c) + addrlen + 1);
|
|
if (unlikely(c == NULL))
|
|
return NULL;
|
|
|
|
c->next = NULL;
|
|
c->family = 0;
|
|
c->ttl = 255;
|
|
c->addr_count = 1;
|
|
memcpy(c->addr, addr, addrlen);
|
|
c->addr[addrlen] = '\0';
|
|
|
|
if (len >= 7 && memcmp(net_type, "IN ", 3) == 0) {
|
|
int offset, val = -1;
|
|
|
|
if (memcmp(addr_type, "IP4 ", 4) == 0) {
|
|
/* IPv4 */
|
|
c->family = 4;
|
|
val = sscanf(c->addr, "%*[^/]%n/%hhu/%hu", &offset, &c->ttl,
|
|
&c->addr_count);
|
|
|
|
} else if (memcmp(addr_type, "IP6 ", 4) == 0) {
|
|
/* IPv6 */
|
|
c->family = 6;
|
|
val = sscanf(c->addr, "%*[^/]%n/%hu", &offset, &c->addr_count);
|
|
}
|
|
|
|
if (val >= 0)
|
|
c->addr[offset] = '\0';
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
static struct vlc_sdp_attr *vlc_sdp_attr_parse(const char *str, size_t len)
|
|
{
|
|
size_t namelen = vlc_sdp_token_length(str);
|
|
if (namelen < len && str[namelen] != ':') {
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
struct vlc_sdp_attr *a = malloc(sizeof (*a) + len + 1);
|
|
if (unlikely(a == NULL))
|
|
return NULL;
|
|
|
|
memcpy(a->name, str, len);
|
|
a->name[namelen] = '\0';
|
|
if (namelen < len) {
|
|
a->name[len] = '\0';
|
|
a->value = a->name + namelen + 1;
|
|
} else
|
|
a->value = NULL;
|
|
a->next = NULL;
|
|
return a;
|
|
}
|
|
|
|
static void vlc_sdp_attr_free(struct vlc_sdp_attr **attr)
|
|
{
|
|
struct vlc_sdp_attr *a = *attr;
|
|
|
|
*attr = a->next;
|
|
free(a);
|
|
}
|
|
|
|
static void vlc_sdp_media_free(struct vlc_sdp_media **media)
|
|
{
|
|
struct vlc_sdp_media *m = *media;
|
|
|
|
while (m->conns != NULL)
|
|
vlc_sdp_conn_free(&m->conns);
|
|
while (m->attrs != NULL)
|
|
vlc_sdp_attr_free(&m->attrs);
|
|
|
|
*media = m->next;
|
|
free(m->format);
|
|
free(m->proto);
|
|
free(m->type);
|
|
free(m);
|
|
}
|
|
|
|
static struct vlc_sdp_media *vlc_sdp_media_parse(struct vlc_sdp *sdp,
|
|
const char *str, size_t len)
|
|
{
|
|
const char *end = str + len;
|
|
const char *media = str;
|
|
const char *media_end = memchr(str, ' ', end - str);
|
|
|
|
if (media_end == NULL) {
|
|
bad:
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
const char *port = media_end + 1;
|
|
char *port_end = memchr(port, ' ', end - port);
|
|
|
|
if (port_end == NULL)
|
|
goto bad;
|
|
|
|
const char *proto = port_end + 1;
|
|
unsigned long port_start = strtoul(port, &port_end, 10);
|
|
unsigned long port_count = 1;
|
|
|
|
if (*port_end == '/')
|
|
port_count = strtoul(port_end + 1, &port_end, 10);
|
|
if (*port_end != ' ')
|
|
goto bad;
|
|
|
|
const char *proto_end = memchr(proto, ' ', end - proto);
|
|
|
|
if (proto_end == NULL)
|
|
goto bad;
|
|
|
|
const char *format = proto_end + 1;
|
|
|
|
if (format >= end)
|
|
goto bad;
|
|
|
|
struct vlc_sdp_media *m = malloc(sizeof (*m));
|
|
if (unlikely(m == NULL))
|
|
return NULL;
|
|
|
|
m->next = NULL;
|
|
m->session = sdp;
|
|
m->conns = NULL;
|
|
m->attrs = NULL;
|
|
m->type = strndup(media, media_end - media);
|
|
m->port = port_start;
|
|
m->port_count = port_count;
|
|
m->proto = strndup(proto, proto_end - proto);
|
|
m->format = strndup(format, end - format);
|
|
|
|
if (unlikely(m->type == NULL || m->proto == NULL || m->format == NULL))
|
|
vlc_sdp_media_free(&m);
|
|
if (!vlc_sdp_is_token(m->type)) {
|
|
vlc_sdp_media_free(&m);
|
|
errno = EINVAL;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
struct vlc_sdp_input
|
|
{
|
|
const char *cursor;
|
|
const char *end;
|
|
};
|
|
|
|
static int vlc_sdp_getline(struct vlc_sdp_input *restrict in,
|
|
const char **restrict pp, size_t *restrict lenp)
|
|
{
|
|
assert(in->end >= in->cursor);
|
|
*lenp = 0;
|
|
|
|
if (in->end == in->cursor)
|
|
return 0; /* end */
|
|
|
|
const char *lf = memchr(in->cursor, '\n', in->end - in->cursor);
|
|
|
|
if (lf == NULL)
|
|
goto error; /* cannot locate end of line */
|
|
|
|
const char *end = memchr(in->cursor, '\r', lf - in->cursor);
|
|
if (end != NULL) {
|
|
/* CR should be present. If so, it must be right before LF. */
|
|
if (end != lf - 1)
|
|
goto error; /* CR within a line is not permitted. */
|
|
} else
|
|
end = lf;
|
|
|
|
if ((end - in->cursor) < 2 || in->cursor[1] != '=')
|
|
goto error;
|
|
|
|
int c = (unsigned char)in->cursor[0];
|
|
|
|
*pp = in->cursor + 2;
|
|
*lenp = end - *pp;
|
|
in->cursor = lf + 1;
|
|
|
|
return c;
|
|
|
|
error:
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
const struct vlc_sdp_attr *vlc_sdp_attr_first_by_name(
|
|
struct vlc_sdp_attr *const *ap, const char *name)
|
|
{
|
|
for (const struct vlc_sdp_attr *a = *ap; a != NULL; a = a->next)
|
|
if (!strcmp(a->name, name))
|
|
return a;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void vlc_sdp_free(struct vlc_sdp *sdp)
|
|
{
|
|
while (sdp->media != NULL)
|
|
vlc_sdp_media_free(&sdp->media);
|
|
|
|
while (sdp->attrs != NULL)
|
|
vlc_sdp_attr_free(&sdp->attrs);
|
|
|
|
if (sdp->conn != NULL)
|
|
vlc_sdp_conn_free(&sdp->conn);
|
|
|
|
free(sdp->info);
|
|
free(sdp->name);
|
|
free(sdp);
|
|
}
|
|
|
|
struct vlc_sdp *vlc_sdp_parse(const char *str, size_t length)
|
|
{
|
|
if (memchr(str, 0, length) != NULL) {
|
|
/* Nul byte inside the SDP is not permitted. */
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
struct vlc_sdp_input in = { str, str + length };
|
|
const char *line;
|
|
size_t linelen;
|
|
int c;
|
|
|
|
/* Version line, must be "0" */
|
|
if (vlc_sdp_getline(&in, &line, &linelen) != 'v'
|
|
|| linelen != 1 || memcmp(line, "0", 1)) {
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
/* Origin line (ignored for now) */
|
|
if (vlc_sdp_getline(&in, &line, &linelen) != 'o') {
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
struct vlc_sdp *sdp = malloc(sizeof (*sdp));
|
|
if (unlikely(sdp == NULL))
|
|
return NULL;
|
|
|
|
sdp->name = NULL;
|
|
sdp->info = NULL;
|
|
sdp->conn = NULL;
|
|
sdp->attrs = NULL;
|
|
sdp->media = NULL;
|
|
|
|
/* Session name line */
|
|
if (vlc_sdp_getline(&in, &line, &linelen) != 's')
|
|
goto bad;
|
|
|
|
sdp->name = strndup(line, linelen);
|
|
if (unlikely(sdp->name == NULL))
|
|
goto error;
|
|
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
|
|
/* Session information line (optional) */
|
|
if (c == 'i') {
|
|
sdp->info = strndup(line, linelen);
|
|
if (unlikely(sdp->info == NULL))
|
|
goto error;
|
|
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
}
|
|
|
|
/* URL line (optional) */
|
|
if (c == 'u')
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
|
|
/* Email lines */
|
|
while (c == 'e')
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
|
|
/* Phone number lines */
|
|
while (c == 'p')
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
|
|
/* Session connection line (optional) */
|
|
if (c == 'c') {
|
|
sdp->conn = vlc_sdp_conn_parse(line, linelen);
|
|
if (sdp->conn == NULL)
|
|
goto error;
|
|
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
}
|
|
|
|
/* Session bandwidth lines */
|
|
while (c == 'b')
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
|
|
/* Time descriptions / Session time lines */
|
|
while (c == 't') {
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
|
|
/* Repeat lines */
|
|
while (c == 'r')
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
}
|
|
|
|
/* Time adjustment lines */
|
|
while (c == 'z')
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
|
|
/* Session encryption key line (unused in real life) */
|
|
if (c == 'k')
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
|
|
/* Session attribute lines */
|
|
for (struct vlc_sdp_attr **ap = &sdp->attrs; c == 'a';) {
|
|
struct vlc_sdp_attr *a = vlc_sdp_attr_parse(line, linelen);
|
|
if (a == NULL)
|
|
goto error;
|
|
|
|
*ap = a;
|
|
ap = &a->next;
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
}
|
|
|
|
/* Media descriptions / Media lines */
|
|
for (struct vlc_sdp_media **mp = &sdp->media; c == 'm';) {
|
|
struct vlc_sdp_media *m = vlc_sdp_media_parse(sdp, line, linelen);
|
|
if (m == NULL)
|
|
goto error;
|
|
|
|
*mp = m;
|
|
mp = &m->next;
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
|
|
/* Media title line */
|
|
if (c == 'i')
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
|
|
/* Media connection lines */
|
|
for (struct vlc_sdp_conn **cp = &m->conns; c == 'c';) {
|
|
struct vlc_sdp_conn *conn = vlc_sdp_conn_parse(line, linelen);
|
|
if (conn == NULL)
|
|
goto error;
|
|
|
|
*cp = conn;
|
|
cp = &conn->next;
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
}
|
|
|
|
/* Media bandwidth lines */
|
|
while (c == 'b')
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
|
|
/* Media encryption key line (unused in real life) */
|
|
if (c == 'k')
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
|
|
/* Session attribute lines */
|
|
for (struct vlc_sdp_attr **ap = &m->attrs; c == 'a';) {
|
|
struct vlc_sdp_attr *a = vlc_sdp_attr_parse(line, linelen);
|
|
if (a == NULL)
|
|
goto error;
|
|
|
|
*ap = a;
|
|
ap = &a->next;
|
|
c = vlc_sdp_getline(&in, &line, &linelen);
|
|
}
|
|
}
|
|
|
|
if (c == 0)
|
|
return sdp;
|
|
|
|
bad:
|
|
errno = EINVAL;
|
|
error:
|
|
vlc_sdp_free(sdp);
|
|
return NULL;
|
|
}
|
|
|