[vlc-commits] rtp: add an SDP parser

Rémi Denis-Courmont git at videolan.org
Fri Apr 10 17:54:52 CEST 2020


vlc | branch: master | Rémi Denis-Courmont <remi at remlab.net> | Thu Apr  9 22:17:17 2020 +0300| [6ef5f250583f5692ba57b8be2b623aa0c21cd4cf] | committer: Rémi Denis-Courmont

rtp: add an SDP parser

This is rewritten from scratch. The goal is eventually to embed it in
the RTP access. And for that we need a parser under LGPL.

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=6ef5f250583f5692ba57b8be2b623aa0c21cd4cf
---

 doc/Doxyfile.in                |   3 +-
 modules/access/rtp/Makefile.am |   1 +
 modules/access/rtp/sdp.c       | 429 +++++++++++++++++++++++++++++++++++++++++
 modules/access/rtp/sdp.h       | 215 +++++++++++++++++++++
 4 files changed, 647 insertions(+), 1 deletion(-)

diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
index d98b4ccab6..273e337e42 100644
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -761,7 +761,8 @@ WARN_LOGFILE           =
 INPUT                  = @top_srcdir@/src \
                          @top_srcdir@/include \
                          @top_srcdir@/doc/standalone \
-                         @top_srcdir@/modules/access/http
+                         @top_srcdir@/modules/access/http \
+                         @top_srcdir@/modules/access/rtp
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/modules/access/rtp/Makefile.am b/modules/access/rtp/Makefile.am
index c647c0a5e1..2011fdff0f 100644
--- a/modules/access/rtp/Makefile.am
+++ b/modules/access/rtp/Makefile.am
@@ -4,6 +4,7 @@ librtp_plugin_la_SOURCES = \
 	access/rtp/input.c \
 	access/rtp/session.c \
 	access/rtp/xiph.c \
+	access/rtp/sdp.c access/rtp/sdp.h \
 	access/rtp/rtp.c access/rtp/rtp.h
 librtp_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/access/rtp
 librtp_plugin_la_CFLAGS = $(AM_CFLAGS)
diff --git a/modules/access/rtp/sdp.c b/modules/access/rtp/sdp.c
new file mode 100644
index 0000000000..1774ef973e
--- /dev/null
+++ b/modules/access/rtp/sdp.c
@@ -0,0 +1,429 @@
+/**
+ * @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 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;
+
+        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)
+{
+    struct vlc_sdp_attr *a = malloc(sizeof (*a) + len + 1);
+    const char *sep = memchr(str, ':', len);
+    size_t namelen = (sep != NULL) ? (size_t)(sep - str) : len;
+
+    if (unlikely(a == NULL))
+        return NULL;
+
+    memcpy(a->name, str, len);
+    a->name[namelen] = '\0';
+    a->name[len] = '\0';
+    a->value = (sep != NULL) ? a->name + namelen + 1 : 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);
+
+    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;
+}
diff --git a/modules/access/rtp/sdp.h b/modules/access/rtp/sdp.h
new file mode 100644
index 0000000000..ede687b8bf
--- /dev/null
+++ b/modules/access/rtp/sdp.h
@@ -0,0 +1,215 @@
+/**
+ * @file sdp.h
+ * @brief Session Description Protocol (SDP)
+ * @ingroup sdp
+ */
+/*****************************************************************************
+ * 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.
+ ****************************************************************************/
+
+#ifndef VLC_SDP_H
+#define VLC_SDP_H
+
+#include <stdbool.h>
+
+/**
+ * \defgroup sdp Session Description Protocol
+ * \ingroup net
+ * @{
+ */
+
+struct vlc_sdp;
+struct vlc_sdp_media;
+struct vlc_sdp_conn;
+struct vlc_sdp_attr;
+
+/**
+ * Parses an SDP session descriptor.
+ *
+ * \param str start address of the descriptor
+ * \param length bytes length of the descriptor
+ * \return a parsed SDP or NULL on error (@c errno is set)
+ */
+struct vlc_sdp *vlc_sdp_parse(const char *str, size_t length);
+
+/**
+ * Destroys a parsed SDP session descriptor.
+ */
+void vlc_sdp_free(struct vlc_sdp *sdp);
+
+const struct vlc_sdp_attr *vlc_sdp_attr_first_by_name(
+    struct vlc_sdp_attr *const *ap, const char *name);
+
+/** SDP attribute */
+struct vlc_sdp_attr
+{
+    struct vlc_sdp_attr *next; /*< Next attribute (or NULL) */
+    const char *value; /*< Attribute value, or NULL if none */
+    char name[]; /*< Attribute name */
+};
+
+/** SDP connection address */
+struct vlc_sdp_conn
+{
+    struct vlc_sdp_conn *next; /*< Next address (or NULL) */
+    int family; /*< Address family, or AF_UNSPEC if not recognized */
+    unsigned char ttl; /*< Multicast TTL */
+    unsigned short addr_count; /*< Multicast address count */
+    char addr[]; /*< Address name, usually an IP literal */
+};
+
+/** SDP media */
+struct vlc_sdp_media
+{
+    struct vlc_sdp_media *next; /*< Next media in the session (or NULL) */
+    struct vlc_sdp *session; /*< Pointer to containing session */
+    char *type; /*< Media type, e.g. "audio" or "video" */
+    unsigned int port; /*< Media port number */
+    unsigned int port_count; /*< Number of ports (usually 1) */
+    char *proto; /*< Media protocol, e.g. "RTP/AVP" */
+    char *format; /*< Protocol-specific format parameters */
+    struct vlc_sdp_conn *conns; /*< List of media connection addresses */
+    struct vlc_sdp_attr *attrs; /*< List of media attributes */
+};
+
+/**
+ * Gets a media attribute by name.
+ *
+ * \param media Session media descriptor.
+ * \param name Session attribute name.
+ *
+ * \note This function does <b>not</b> look for session attributes, as this is
+ * not always appropriate.
+ * To fallback to session attributes, call vlc_sdp_attr_get() explicitly.
+ *
+ * \return the first attribute with the specified name or NULL if not found.
+ */
+static inline
+const struct vlc_sdp_attr *vlc_sdp_media_attr_get(
+    const struct vlc_sdp_media *media, const char *name)
+{
+    return vlc_sdp_attr_first_by_name(&media->attrs, name);
+}
+
+/**
+ * Checks if a median attribute is present.
+ *
+ * \param media Media descriptor.
+ * \param name Attribute name.
+ *
+ * \retval true if present
+ * \retval false it absent
+ */ 
+static inline
+bool vlc_sdp_media_attr_present(const struct vlc_sdp_media *media,
+                                const char *name)
+{
+    return vlc_sdp_media_attr_get(media, name) != NULL;
+}
+
+/**
+ * Returns a media attribute value.
+ *
+ * \param media Media descriptor.
+ * \param name Attribute name.
+ *
+ * \note This function cannot distinguish the cases of a missing attribute and
+ * of an attribute without a value.
+ * Use vlc_sdp_media_attr_present() to check for value-less attributes.
+ *
+ * \return Nul-terminated attribute value, or NULL if none.
+ */
+static inline
+const char *vlc_sdp_media_attr_value(const struct vlc_sdp_media *media,
+                                     const char *name)
+{
+    const struct vlc_sdp_attr *a = vlc_sdp_media_attr_get(media, name);
+    return (a != NULL) ? a->value : NULL;
+}
+
+/** SDP session descriptor */
+struct vlc_sdp
+{
+    char *name; /*< Session name */
+    char *info; /*< Session description, or NULL if none */
+    struct vlc_sdp_conn *conn; /*< Session connection address or NULL */
+    struct vlc_sdp_attr *attrs; /*< List of session attributes */
+    struct vlc_sdp_media *media; /*< List of session media */
+};
+
+/**
+ * Returns the media connection address list.
+ */
+static inline
+const struct vlc_sdp_conn *vlc_sdp_media_conn(
+    const struct vlc_sdp_media *media)
+{
+    return (media->conns != NULL) ? media->conns : media->session->conn;
+}
+
+/**
+ * Gets a session attribute by name.
+ *
+ * \param sdp Session descriptor.
+ * \param name Attribute name.
+ *
+ * \return the first attribute with the specified name or NULL if not found.
+ */
+static inline
+const struct vlc_sdp_attr *vlc_sdp_attr_get(const struct vlc_sdp *sdp,
+                                            const char *name)
+{
+    return vlc_sdp_attr_first_by_name(&sdp->attrs, name);
+}
+
+/**
+ * Checks if a session attribute is present.
+ *
+ * \param sdp Session descriptor.
+ * \param name Attribute name.
+ *
+ * \retval true if present
+ * \retval false it absent
+ */
+static inline
+bool vlc_sdp_attr_present(const struct vlc_sdp *sdp, const char *name)
+{
+    return vlc_sdp_attr_get(sdp, name) != NULL;
+}
+
+/**
+ * Returns a session attribute value.
+ *
+ * \param sdp Session descriptor.
+ * \param name Attribute name.
+ *
+ * \note This function cannot distinguish the cases of a missing attribute and
+ * of an attribute without a value.
+ * Use vlc_sdp_attr_present() to check for value-less attributes.
+ *
+ * \return Nul-terminated attribute value, or NULL if none.
+ */
+static inline
+const char *vlc_sdp_attr_value(const struct vlc_sdp *sdp, const char *name)
+{
+    const struct vlc_sdp_attr *a = vlc_sdp_attr_get(sdp, name);
+    return (a != NULL) ? a->value : NULL;
+}
+
+/** @} */
+
+#endif



More information about the vlc-commits mailing list