dtls: Add new DTLS plugin
authorSebastian Dröge <sebastian@centricular.com>
Mon, 16 Mar 2015 16:33:03 +0000 (17:33 +0100)
committerSebastian Dröge <sebastian@centricular.com>
Mon, 16 Mar 2015 17:23:27 +0000 (18:23 +0100)
This is a copy of the Ericsson DTLS plugin from
https://github.com/EricssonResearch/openwebrtc-gst-plugins/tree/master/ext/erdtls/src

https://bugzilla.gnome.org/show_bug.cgi?id=744582

23 files changed:
configure.ac
ext/Makefile.am
ext/dtls/Makefile.am [new file with mode: 0644]
ext/dtls/gstdtlsagent.c [new file with mode: 0644]
ext/dtls/gstdtlsagent.h [new file with mode: 0644]
ext/dtls/gstdtlscertificate.c [new file with mode: 0644]
ext/dtls/gstdtlscertificate.h [new file with mode: 0644]
ext/dtls/gstdtlscommon.h [new file with mode: 0644]
ext/dtls/gstdtlsconnection.c [new file with mode: 0644]
ext/dtls/gstdtlsconnection.h [new file with mode: 0644]
ext/dtls/gstdtlsdec.c [new file with mode: 0644]
ext/dtls/gstdtlsdec.h [new file with mode: 0644]
ext/dtls/gstdtlsenc.c [new file with mode: 0644]
ext/dtls/gstdtlsenc.h [new file with mode: 0644]
ext/dtls/gstdtlssrtpbin.c [new file with mode: 0644]
ext/dtls/gstdtlssrtpbin.h [new file with mode: 0644]
ext/dtls/gstdtlssrtpdec.c [new file with mode: 0644]
ext/dtls/gstdtlssrtpdec.h [new file with mode: 0644]
ext/dtls/gstdtlssrtpdemux.c [new file with mode: 0644]
ext/dtls/gstdtlssrtpdemux.h [new file with mode: 0644]
ext/dtls/gstdtlssrtpenc.c [new file with mode: 0644]
ext/dtls/gstdtlssrtpenc.h [new file with mode: 0644]
ext/dtls/plugin.c [new file with mode: 0644]

index 4822af1..f466a3e 100644 (file)
@@ -2137,6 +2137,18 @@ AG_GST_CHECK_FEATURE(SRTP, [srtp library], srtp, [
   AC_SUBST(SRTP_CFLAGS)
 ])
 
+dnl *** dtls ***
+translit(dnm, m, l) AM_CONDITIONAL(USE_DTLS, true)
+AG_GST_CHECK_FEATURE(DTLS, [DTLS plugin], dtls, [
+  PKG_CHECK_MODULES(DTLS, [ openssl >= 0.9.5  libcrypto ], [
+    HAVE_DTLS="yes"
+    AC_SUBST(DTLS_CFLAGS)
+    AC_SUBST(DTLS_LIBS)
+  ], [
+    HAVE_DTLS="no"
+  ])
+])
+
 dnl *** linsys ***
 translit(dnm, m, l) AM_CONDITIONAL(USE_LINSYS, true)
 AG_GST_CHECK_FEATURE(LINSYS, [Linear Systems SDI plugin], linsys, [
@@ -3014,6 +3026,7 @@ AM_CONDITIONAL(USE_UVCH264, false)
 AM_CONDITIONAL(USE_WEBP, false)
 AM_CONDITIONAL(USE_OPENH264, false)
 AM_CONDITIONAL(USE_X265, false)
+AM_CONDITIONAL(USE_DTLS, false)
 
 fi dnl of EXT plugins
 
@@ -3310,6 +3323,7 @@ ext/webp/Makefile
 ext/x265/Makefile
 ext/xvid/Makefile
 ext/zbar/Makefile
+ext/dtls/Makefile
 po/Makefile.in
 docs/Makefile
 docs/plugins/Makefile
index 43cb5a8..394c71d 100644 (file)
@@ -412,6 +412,12 @@ else
 X265_DIR=
 endif
 
+if USE_DTLS
+DTLS_DIR=dtls
+else
+DTLS_DIR=
+endif
+
 SUBDIRS=\
        $(VOAACENC_DIR) \
        $(ASSRENDER_DIR) \
@@ -480,7 +486,8 @@ SUBDIRS=\
        $(RTMP_DIR) \
        $(HLS_DIR) \
        $(WEBP_DIR) \
-       $(X265_DIR)
+       $(X265_DIR) \
+       $(DTLS_DIR)
 
 DIST_SUBDIRS = \
        assrender \
@@ -546,6 +553,7 @@ DIST_SUBDIRS = \
        zbar \
        rtmp \
        webp \
-       x265
+       x265 \
+       dtls
 
 include $(top_srcdir)/common/parallel-subdirs.mak
diff --git a/ext/dtls/Makefile.am b/ext/dtls/Makefile.am
new file mode 100644 (file)
index 0000000..5846506
--- /dev/null
@@ -0,0 +1,36 @@
+plugin_LTLIBRARIES = libgstdtls.la
+
+libgstdtls_la_SOURCES = \
+    plugin.c \
+    gstdtlsdec.c \
+    gstdtlsenc.c \
+    gstdtlssrtpdemux.c \
+    gstdtlssrtpbin.c \
+    gstdtlssrtpdec.c \
+    gstdtlssrtpenc.c \
+    gstdtlsagent.c \
+    gstdtlsconnection.c \
+    gstdtlscertificate.c
+
+libgstdtls_la_CFLAGS = \
+    $(GST_PLUGINS_BASE_CFLAGS) \
+    $(GST_BASE_CFLAGS) \
+    $(GST_CFLAGS) \
+    $(DTLS_CFLAGS)
+
+libgstdtls_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) $(DTLS_LIBS)
+libgstdtls_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstdtls_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
+
+noinst_HEADERS = \
+    gstdtlsdec.h \
+    gstdtlsenc.h \
+    gstdtlssrtpdemux.h \
+    gstdtlssrtpbin.h \
+    gstdtlssrtpdec.h \
+    gstdtlssrtpenc.h \
+    gstdtlsagent.h \
+    gstdtlsconnection.h \
+    gstdtlscertificate.h \
+    gstdtlscommon.h
+
diff --git a/ext/dtls/gstdtlsagent.c b/ext/dtls/gstdtlsagent.c
new file mode 100644 (file)
index 0000000..67b552e
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdtlsagent.h"
+
+#include "gstdtlscommon.h"
+
+#ifdef __APPLE__
+# define __AVAILABILITYMACROS__
+# define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
+#endif
+
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+
+#if ER_DTLS_USE_GST_LOG
+    GST_DEBUG_CATEGORY_STATIC(er_dtls_agent_debug);
+#   define GST_CAT_DEFAULT er_dtls_agent_debug
+    G_DEFINE_TYPE_WITH_CODE(ErDtlsAgent, er_dtls_agent, G_TYPE_OBJECT,
+        GST_DEBUG_CATEGORY_INIT(er_dtls_agent_debug, "gstdtlsagent", 0, "Ericsson DTLS Agent"));
+#else
+    G_DEFINE_TYPE(ErDtlsAgent, er_dtls_agent, G_TYPE_OBJECT);
+#endif
+
+#define ER_DTLS_AGENT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ER_TYPE_DTLS_AGENT, ErDtlsAgentPrivate))
+
+enum {
+    PROP_0,
+    PROP_CERTIFICATE,
+    NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+struct _ErDtlsAgentPrivate {
+    SSL_CTX *ssl_context;
+
+    ErDtlsCertificate *certificate;
+};
+
+static void er_dtls_agent_finalize(GObject *gobject);
+static void er_dtls_agent_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *);
+const gchar *er_dtls_agent_peek_id(ErDtlsAgent *);
+
+static GRWLock *ssl_locks;
+
+static void ssl_locking_function(gint mode, gint lock_num, const gchar *file, gint line)
+{
+    gboolean locking;
+    gboolean reading;
+    GRWLock *lock;
+
+    locking = mode & CRYPTO_LOCK;
+    reading = mode & CRYPTO_READ;
+    lock = &ssl_locks[lock_num];
+
+    LOG_LOG(NULL, "%s SSL lock for %s, thread=%p location=%s:%d",
+        locking ? "locking" : "unlocking", reading ? "reading" : "writing",
+        g_thread_self(), file, line);
+
+    if (locking) {
+        if (reading) {
+            g_rw_lock_reader_lock(lock);
+        } else {
+            g_rw_lock_writer_lock(lock);
+        }
+    } else {
+        if (reading) {
+            g_rw_lock_reader_unlock(lock);
+        } else {
+            g_rw_lock_writer_unlock(lock);
+        }
+    }
+}
+
+static gulong ssl_thread_id_function(void)
+{
+    return (gulong) g_thread_self();
+}
+
+void _er_dtls_init_openssl()
+{
+    static gsize is_init = 0;
+    gint i;
+    gint num_locks;
+
+    if (g_once_init_enter(&is_init)) {
+        if (OPENSSL_VERSION_NUMBER < 0x1000100fL) {
+            LOG_WARNING(NULL, "Incorrect OpenSSL version, should be >= 1.0.1, is %s", OPENSSL_VERSION_TEXT);
+            g_assert_not_reached();
+        }
+
+        LOG_INFO(NULL, "initializing openssl %lx", OPENSSL_VERSION_NUMBER);
+        SSL_library_init();
+        SSL_load_error_strings();
+        ERR_load_BIO_strings();
+
+        num_locks = CRYPTO_num_locks();
+        ssl_locks = g_new(GRWLock, num_locks);
+        for (i = 0; i < num_locks; ++i) {
+            g_rw_lock_init(&ssl_locks[i]);
+        }
+        CRYPTO_set_locking_callback(ssl_locking_function);
+        CRYPTO_set_id_callback(ssl_thread_id_function);
+
+        g_once_init_leave(&is_init, 1);
+    }
+}
+
+static void er_dtls_agent_class_init(ErDtlsAgentClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+    g_type_class_add_private(klass, sizeof(ErDtlsAgentPrivate));
+
+    gobject_class->set_property = er_dtls_agent_set_property;
+    gobject_class->finalize = er_dtls_agent_finalize;
+
+    properties[PROP_CERTIFICATE] =
+        g_param_spec_object("certificate",
+            "ErDtlsCertificate",
+            "Sets the certificate of the agent",
+            ER_TYPE_DTLS_CERTIFICATE,
+            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
+
+    _er_dtls_init_openssl();
+}
+
+static void er_dtls_agent_init(ErDtlsAgent *self)
+{
+    ErDtlsAgentPrivate *priv = ER_DTLS_AGENT_GET_PRIVATE(self);
+    self->priv = priv;
+
+    ERR_clear_error();
+
+    priv->ssl_context = SSL_CTX_new(DTLSv1_method());
+    if (ERR_peek_error() || !priv->ssl_context) {
+        char buf[512];
+
+        priv->ssl_context = NULL;
+
+        LOG_WARNING(self, "Error creating SSL Context: %s", ERR_error_string(ERR_get_error(), buf));
+
+        g_return_if_reached();
+    }
+
+    SSL_CTX_set_verify_depth(priv->ssl_context, 2);
+    SSL_CTX_set_tlsext_use_srtp(priv->ssl_context, "SRTP_AES128_CM_SHA1_80");
+    SSL_CTX_set_cipher_list(priv->ssl_context, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+    SSL_CTX_set_read_ahead(priv->ssl_context, 1);
+#if OPENSSL_VERSION_NUMBER >= 0x1000200fL
+    SSL_CTX_set_ecdh_auto(priv->ssl_context, 1);
+#endif
+}
+
+static void er_dtls_agent_finalize(GObject *gobject)
+{
+    ErDtlsAgentPrivate *priv = ER_DTLS_AGENT(gobject)->priv;
+
+    SSL_CTX_free(priv->ssl_context);
+    priv->ssl_context = NULL;
+
+    LOG_DEBUG(gobject, "finalized");
+
+    G_OBJECT_CLASS(er_dtls_agent_parent_class)->finalize(gobject);
+}
+
+static void er_dtls_agent_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    ErDtlsAgent *self = ER_DTLS_AGENT(object);
+    ErDtlsCertificate *certificate;
+
+    switch (prop_id) {
+    case PROP_CERTIFICATE:
+        certificate = ER_DTLS_CERTIFICATE(g_value_get_object(value));
+        g_return_if_fail(ER_IS_DTLS_CERTIFICATE(certificate));
+        g_return_if_fail(self->priv->ssl_context);
+
+        self->priv->certificate = certificate;
+        g_object_ref(certificate);
+
+        if (!SSL_CTX_use_certificate(self->priv->ssl_context, _er_dtls_certificate_get_internal_certificate(certificate))) {
+            LOG_WARNING(self, "could not use certificate");
+            g_return_if_reached();
+        }
+
+        if (!SSL_CTX_use_PrivateKey(self->priv->ssl_context, _er_dtls_certificate_get_internal_key(certificate))) {
+            LOG_WARNING(self, "could not use private key");
+            g_return_if_reached();
+        }
+
+        if (!SSL_CTX_check_private_key(self->priv->ssl_context)) {
+            LOG_WARNING(self, "invalid private key");
+            g_return_if_reached();
+        }
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
+
+ErDtlsCertificate *er_dtls_agent_get_certificate(ErDtlsAgent *self)
+{
+    g_return_val_if_fail(ER_IS_DTLS_AGENT(self), NULL);
+    if (self->priv->certificate) {
+        g_object_ref(self->priv->certificate);
+    }
+    return self->priv->certificate;
+}
+
+gchar *er_dtls_agent_get_certificate_pem(ErDtlsAgent *self)
+{
+    gchar *pem;
+    g_return_val_if_fail(ER_IS_DTLS_AGENT(self), NULL);
+    g_return_val_if_fail(ER_IS_DTLS_CERTIFICATE(self->priv->certificate), NULL);
+
+    g_object_get(self->priv->certificate, "pem", &pem, NULL);
+
+    return pem;
+}
+
+const ErDtlsAgentContext _er_dtls_agent_peek_context(ErDtlsAgent *self)
+{
+    g_return_val_if_fail(ER_IS_DTLS_AGENT(self), NULL);
+    return self->priv->ssl_context;
+}
diff --git a/ext/dtls/gstdtlsagent.h b/ext/dtls/gstdtlsagent.h
new file mode 100644 (file)
index 0000000..71f45ac
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifndef gstdtlsagent_h
+#define gstdtlsagent_h
+
+#include "gstdtlscertificate.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define ER_TYPE_DTLS_AGENT            (er_dtls_agent_get_type())
+#define ER_DTLS_AGENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), ER_TYPE_DTLS_AGENT, ErDtlsAgent))
+#define ER_DTLS_AGENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), ER_TYPE_DTLS_AGENT, ErDtlsAgentClass))
+#define ER_IS_DTLS_AGENT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), ER_TYPE_DTLS_AGENT))
+#define ER_IS_DTLS_AGENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ER_TYPE_DTLS_AGENT))
+#define ER_DTLS_AGENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), ER_TYPE_DTLS_AGENT, ErDtlsAgentClass))
+
+typedef gpointer ErDtlsAgentContext;
+
+typedef struct _ErDtlsAgent        ErDtlsAgent;
+typedef struct _ErDtlsAgentClass   ErDtlsAgentClass;
+typedef struct _ErDtlsAgentPrivate ErDtlsAgentPrivate;
+
+/*
+ * ErDtlsAgent:
+ *
+ * A context for creating ErDtlsConnections with a ErDtlsCertificate.
+ * ErDtlsAgent needs to be constructed with the "certificate" property set.
+ */
+struct _ErDtlsAgent {
+    GObject parent_instance;
+
+    ErDtlsAgentPrivate *priv;
+};
+
+struct _ErDtlsAgentClass {
+    GObjectClass parent_class;
+};
+
+GType er_dtls_agent_get_type(void) G_GNUC_CONST;
+
+/*
+ * Returns the certificate used by the agent.
+ */
+ErDtlsCertificate *er_dtls_agent_get_certificate(ErDtlsAgent *);
+
+/*
+ * Returns the certificate used by the agent, in PEM format.
+ */
+gchar *er_dtls_agent_get_certificate_pem(ErDtlsAgent *self);
+
+/* internal */
+void _er_dtls_init_openssl(void);
+const ErDtlsAgentContext _er_dtls_agent_peek_context(ErDtlsAgent *);
+
+G_END_DECLS
+
+#endif /* gstdtlsagent_h */
diff --git a/ext/dtls/gstdtlscertificate.c b/ext/dtls/gstdtlscertificate.c
new file mode 100644 (file)
index 0000000..1fc3760
--- /dev/null
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdtlscertificate.h"
+
+#include "gstdtlsagent.h"
+#include "gstdtlscommon.h"
+
+#ifdef __APPLE__
+# define __AVAILABILITYMACROS__
+# define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
+#endif
+
+#include <openssl/ssl.h>
+
+#if ER_DTLS_USE_GST_LOG
+    GST_DEBUG_CATEGORY_STATIC(er_dtls_certificate_debug);
+#   define GST_CAT_DEFAULT er_dtls_certificate_debug
+    G_DEFINE_TYPE_WITH_CODE(ErDtlsCertificate, er_dtls_certificate, G_TYPE_OBJECT,
+        GST_DEBUG_CATEGORY_INIT(er_dtls_certificate_debug, "gstdtlscertificate", 0, "Ericsson DTLS Certificate"));
+#else
+    G_DEFINE_TYPE(ErDtlsCertificate, er_dtls_certificate, G_TYPE_OBJECT);
+#endif
+
+#define ER_DTLS_CERTIFICATE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ER_TYPE_DTLS_CERTIFICATE, ErDtlsCertificatePrivate))
+
+enum {
+    PROP_0,
+    PROP_PEM,
+    NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+#define DEFAULT_PEM NULL
+
+struct _ErDtlsCertificatePrivate {
+    X509 *x509;
+    EVP_PKEY *private_key;
+
+    gchar *pem;
+};
+
+static void er_dtls_certificate_finalize(GObject *gobject);
+static void er_dtls_certificate_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *);
+static void er_dtls_certificate_get_property(GObject *, guint prop_id, GValue *, GParamSpec *);
+
+static void init_generated(ErDtlsCertificate *);
+static void init_from_pem_string(ErDtlsCertificate *, const gchar *pem);
+
+static void er_dtls_certificate_class_init(ErDtlsCertificateClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+    g_type_class_add_private(klass, sizeof(ErDtlsCertificatePrivate));
+
+    gobject_class->set_property = er_dtls_certificate_set_property;
+    gobject_class->get_property = er_dtls_certificate_get_property;
+
+    properties[PROP_PEM] =
+        g_param_spec_string("pem",
+            "Pem string",
+            "A string containing a X509 certificate and RSA private key in PEM format",
+            DEFAULT_PEM,
+            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
+
+    _er_dtls_init_openssl();
+
+    gobject_class->finalize = er_dtls_certificate_finalize;
+}
+
+static void er_dtls_certificate_init(ErDtlsCertificate *self)
+{
+    ErDtlsCertificatePrivate *priv = ER_DTLS_CERTIFICATE_GET_PRIVATE(self);
+    self->priv = priv;
+
+    priv->x509 = NULL;
+    priv->private_key = NULL;
+    priv->pem = NULL;
+}
+
+static void er_dtls_certificate_finalize(GObject *gobject)
+{
+    ErDtlsCertificatePrivate *priv = ER_DTLS_CERTIFICATE(gobject)->priv;
+
+    X509_free(priv->x509);
+    priv->x509 = NULL;
+
+    EVP_PKEY_free(priv->private_key);
+    priv->private_key = NULL;
+
+
+    g_free(priv->pem);
+    priv->pem = NULL;
+
+    G_OBJECT_CLASS(er_dtls_certificate_parent_class)->finalize(gobject);
+}
+
+static void er_dtls_certificate_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    ErDtlsCertificate *self = ER_DTLS_CERTIFICATE(object);
+    const gchar *pem;
+
+    switch (prop_id) {
+    case PROP_PEM:
+        pem = g_value_get_string(value);
+        if (pem) {
+            init_from_pem_string(self, pem);
+        } else {
+            init_generated(self);
+        }
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
+
+static void er_dtls_certificate_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    ErDtlsCertificate *self = ER_DTLS_CERTIFICATE(object);
+
+    switch (prop_id) {
+    case PROP_PEM:
+        g_return_if_fail(self->priv->pem);
+        g_value_set_string(value, self->priv->pem);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
+
+static void init_generated(ErDtlsCertificate *self)
+{
+    ErDtlsCertificatePrivate *priv = self->priv;
+    RSA *rsa;
+    X509_NAME *name = NULL;
+
+    g_return_if_fail(!priv->x509);
+    g_return_if_fail(!priv->private_key);
+
+    priv->private_key = EVP_PKEY_new();
+
+    if (!priv->private_key) {
+        LOG_WARNING(self, "failed to create private key");
+        return;
+    }
+
+    priv->x509 = X509_new();
+
+    if (!priv->x509) {
+        LOG_WARNING(self, "failed to create certificate");
+        EVP_PKEY_free(priv->private_key);
+        priv->private_key = NULL;
+        return;
+    }
+    rsa = RSA_generate_key(2048, RSA_F4, NULL, NULL);
+
+    if (!rsa) {
+        LOG_WARNING(self, "failed to generate RSA");
+        EVP_PKEY_free(priv->private_key);
+        priv->private_key = NULL;
+        X509_free(priv->x509);
+        priv->x509 = NULL;
+        return;
+    }
+
+    if (!EVP_PKEY_assign_RSA(priv->private_key, rsa)) {
+        LOG_WARNING(self, "failed to assign RSA");
+        RSA_free(rsa);
+        rsa = NULL;
+        EVP_PKEY_free(priv->private_key);
+        priv->private_key = NULL;
+        X509_free(priv->x509);
+        priv->x509 = NULL;
+        return;
+    }
+    rsa = NULL;
+
+    X509_set_version(priv->x509, 2);
+    ASN1_INTEGER_set(X509_get_serialNumber(priv->x509), 0);
+    X509_gmtime_adj(X509_get_notBefore(priv->x509), 0);
+    X509_gmtime_adj(X509_get_notAfter(priv->x509), 31536000L); /* A year */
+    X509_set_pubkey(priv->x509, priv->private_key);
+
+    name = X509_get_subject_name(priv->x509);
+    X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*) "SE", -1, -1, 0);
+    X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*) "OpenWebRTC", -1, -1, 0);
+    X509_set_issuer_name(priv->x509, name);
+    name = NULL;
+
+    if (!X509_sign(priv->x509, priv->private_key, EVP_sha256())) {
+        LOG_WARNING(self, "failed to sign certificate");
+        EVP_PKEY_free(priv->private_key);
+        priv->private_key = NULL;
+        X509_free(priv->x509);
+        priv->x509 = NULL;
+        return;
+    }
+
+    self->priv->pem = _er_dtls_x509_to_pem(priv->x509);
+}
+
+static void init_from_pem_string(ErDtlsCertificate *self, const gchar *pem)
+{
+    ErDtlsCertificatePrivate *priv = self->priv;
+    BIO *bio;
+
+    g_return_if_fail(pem);
+    g_return_if_fail(!priv->x509);
+    g_return_if_fail(!priv->private_key);
+
+    bio = BIO_new_mem_buf((gpointer) pem, -1);
+    g_return_if_fail(bio);
+
+    priv->x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
+
+    if (!priv->x509) {
+        LOG_WARNING(self, "failed to read certificate from pem string");
+        return;
+    }
+
+    (void) BIO_reset(bio);
+
+    priv->private_key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
+
+    BIO_free(bio);
+    bio = NULL;
+
+    if (!priv->private_key) {
+        LOG_WARNING(self, "failed to read private key from pem string");
+        X509_free(priv->x509);
+        priv->x509 = NULL;
+        return;
+    }
+
+    self->priv->pem = g_strdup(pem);
+}
+
+gchar *_er_dtls_x509_to_pem(gpointer x509)
+{
+#define ER_DTLS_BIO_BUFFER_SIZE 4096
+    BIO *bio;
+    gchar buffer[ER_DTLS_BIO_BUFFER_SIZE] = {0};
+    gint len;
+    gchar *pem = NULL;
+
+    bio = BIO_new(BIO_s_mem());
+    g_return_val_if_fail(bio, NULL);
+
+    if (!PEM_write_bio_X509(bio, (X509 *) x509)) {
+        g_warn_if_reached();
+        goto beach;
+    }
+
+    len = BIO_read(bio, buffer, ER_DTLS_BIO_BUFFER_SIZE);
+    if (!len) {
+        g_warn_if_reached();
+        goto beach;
+    }
+
+    pem = g_strndup(buffer, len);
+
+beach:
+    BIO_free(bio);
+
+    return pem;
+}
+
+ErDtlsCertificateInternalCertificate _er_dtls_certificate_get_internal_certificate(ErDtlsCertificate *self)
+{
+    g_return_val_if_fail(ER_IS_DTLS_CERTIFICATE(self), NULL);
+    return self->priv->x509;
+}
+
+ErDtlsCertificateInternalKey _er_dtls_certificate_get_internal_key(ErDtlsCertificate *self)
+{
+    g_return_val_if_fail(ER_IS_DTLS_CERTIFICATE(self), NULL);
+    return self->priv->private_key;
+}
diff --git a/ext/dtls/gstdtlscertificate.h b/ext/dtls/gstdtlscertificate.h
new file mode 100644 (file)
index 0000000..02fcc55
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifndef gstdtlscertificate_h
+#define gstdtlscertificate_h
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define ER_TYPE_DTLS_CERTIFICATE            (er_dtls_certificate_get_type())
+#define ER_DTLS_CERTIFICATE(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), ER_TYPE_DTLS_CERTIFICATE, ErDtlsCertificate))
+#define ER_DTLS_CERTIFICATE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), ER_TYPE_DTLS_CERTIFICATE, ErDtlsCertificateClass))
+#define ER_IS_DTLS_CERTIFICATE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), ER_TYPE_DTLS_CERTIFICATE))
+#define ER_IS_DTLS_CERTIFICATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ER_TYPE_DTLS_CERTIFICATE))
+#define ER_DTLS_CERTIFICATE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), ER_TYPE_DTLS_CERTIFICATE, ErDtlsCertificateClass))
+
+typedef gpointer ErDtlsCertificateInternalCertificate;
+typedef gpointer ErDtlsCertificateInternalKey;
+
+typedef struct _ErDtlsCertificate        ErDtlsCertificate;
+typedef struct _ErDtlsCertificateClass   ErDtlsCertificateClass;
+typedef struct _ErDtlsCertificatePrivate ErDtlsCertificatePrivate;
+
+/*
+ * ErDtlsCertificate:
+ *
+ * Handles a X509 certificate and a private key.
+ * If a certificate is created without the "pem" property, a self-signed certificate is generated.
+ */
+struct _ErDtlsCertificate {
+    GObject parent_instance;
+
+    ErDtlsCertificatePrivate *priv;
+};
+
+struct _ErDtlsCertificateClass {
+    GObjectClass parent_class;
+};
+
+GType er_dtls_certificate_get_type(void) G_GNUC_CONST;
+
+/* internal */
+ErDtlsCertificateInternalCertificate _er_dtls_certificate_get_internal_certificate(ErDtlsCertificate *);
+ErDtlsCertificateInternalKey _er_dtls_certificate_get_internal_key(ErDtlsCertificate *);
+gchar *_er_dtls_x509_to_pem(gpointer x509);
+
+G_END_DECLS
+
+#endif /* gstdtlscertificate_h */
diff --git a/ext/dtls/gstdtlscommon.h b/ext/dtls/gstdtlscommon.h
new file mode 100644 (file)
index 0000000..1ed901f
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifndef gstdtlscommon_h
+#define gstdtlscommon_h
+
+#ifndef ER_DTLS_USE_GST_LOG
+# define ER_DTLS_USE_GST_LOG 0
+#endif
+
+#if ER_DTLS_USE_GST_LOG
+# include <gst/gst.h>
+#endif
+
+G_BEGIN_DECLS
+
+#define UNUSED(param) while (0) { (void)(param); }
+
+#if ER_DTLS_USE_GST_LOG
+#   define LOG_ERROR(obj, ...) GST_ERROR_OBJECT(obj, __VA_ARGS__ )
+#   define LOG_WARNING(obj, ...) GST_WARNING_OBJECT(obj, __VA_ARGS__ )
+#   define LOG_FIXME(obj, ...) GST_FIXME_OBJECT(obj, __VA_ARGS__ )
+#   define LOG_INFO(obj, ...) GST_INFO_OBJECT(obj, __VA_ARGS__ )
+#   define LOG_DEBUG(obj, ...) GST_DEBUG_OBJECT(obj, __VA_ARGS__ )
+#   define LOG_LOG(obj, ...) GST_LOG_OBJECT(obj, __VA_ARGS__ )
+#   define LOG_TRACE(obj, ...) GST_TRACE_OBJECT(obj, __VA_ARGS__ )
+#else
+#   define LOG_ERROR(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "%p: "fmt, obj, ##__VA_ARGS__)
+#   define LOG_WARNING(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "%p: "fmt, obj, ##__VA_ARGS__)
+#   define LOG_FIXME(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, "%p: "fmt, obj, ##__VA_ARGS__)
+#   define LOG_INFO(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, "%p: "fmt, obj, ##__VA_ARGS__)
+#   define LOG_DEBUG(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "%p: "fmt, obj, ##__VA_ARGS__)
+#   define LOG_LOG(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "%p: "fmt, obj, ##__VA_ARGS__)
+#   define LOG_TRACE(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "%p: "fmt, obj, ##__VA_ARGS__)
+#endif
+
+G_END_DECLS
+
+#endif /* gstdtlscommon_h */
diff --git a/ext/dtls/gstdtlsconnection.c b/ext/dtls/gstdtlsconnection.c
new file mode 100644 (file)
index 0000000..a2969df
--- /dev/null
@@ -0,0 +1,852 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdtlsconnection.h"
+
+#include "gstdtlsagent.h"
+#include "gstdtlscertificate.h"
+#include "gstdtlscommon.h"
+
+#ifdef __APPLE__
+# define __AVAILABILITYMACROS__
+# define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
+#endif
+
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+
+#if ER_DTLS_USE_GST_LOG
+    GST_DEBUG_CATEGORY_STATIC(er_dtls_connection_debug);
+#   define GST_CAT_DEFAULT er_dtls_connection_debug
+    G_DEFINE_TYPE_WITH_CODE(ErDtlsConnection, er_dtls_connection, G_TYPE_OBJECT,
+        GST_DEBUG_CATEGORY_INIT(er_dtls_connection_debug, "gstdtlsconnection", 0, "Ericsson DTLS Connection"));
+#else
+    G_DEFINE_TYPE(ErDtlsConnection, er_dtls_connection, G_TYPE_OBJECT);
+#endif
+
+#define ER_DTLS_CONNECTION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ER_TYPE_DTLS_CONNECTION, ErDtlsConnectionPrivate))
+
+#define SRTP_KEY_LEN 16
+#define SRTP_SALT_LEN 14
+
+enum {
+    SIGNAL_ON_ENCODER_KEY,
+    SIGNAL_ON_DECODER_KEY,
+    SIGNAL_ON_PEER_CERTIFICATE,
+    NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS];
+
+enum {
+    PROP_0,
+    PROP_AGENT,
+    NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+static int connection_ex_index;
+
+struct _ErDtlsConnectionPrivate {
+    SSL *ssl;
+    BIO *bio;
+    GThread *thread;
+
+    gboolean is_client;
+    gboolean is_alive;
+    gboolean keys_exported;
+    gboolean timeout_set;
+
+    GMutex mutex;
+    GCond condition;
+    gpointer bio_buffer;
+    gint bio_buffer_len;
+    gint bio_buffer_offset;
+
+    GClosure *send_closure;
+};
+
+static void er_dtls_connection_finalize(GObject *gobject);
+static void er_dtls_connection_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *);
+
+static void log_state(ErDtlsConnection *, const gchar *str);
+static gpointer connection_timeout_thread_func(ErDtlsConnection *);
+static void export_srtp_keys(ErDtlsConnection *);
+static void openssl_poll(ErDtlsConnection *);
+static int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx);
+
+static BIO_METHOD* BIO_s_er_dtls_connection();
+static int bio_method_write(BIO *, const char *data, int size);
+static int bio_method_read(BIO *, char *out_buffer, int size);
+static long bio_method_ctrl(BIO *, int cmd, long arg1, void *arg2);
+static int bio_method_new(BIO *);
+static int bio_method_free(BIO *);
+
+static void er_dtls_connection_class_init(ErDtlsConnectionClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+    g_type_class_add_private(klass, sizeof(ErDtlsConnectionPrivate));
+
+    gobject_class->set_property = er_dtls_connection_set_property;
+
+    connection_ex_index = SSL_get_ex_new_index(0, "gstdtlsagent connection index", NULL, NULL, NULL);
+
+    signals[SIGNAL_ON_DECODER_KEY] =
+        g_signal_new("on-decoder-key", G_TYPE_FROM_CLASS(klass),
+            G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+            g_cclosure_marshal_generic, G_TYPE_NONE, 3,
+            G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT);
+
+    signals[SIGNAL_ON_ENCODER_KEY] =
+        g_signal_new("on-encoder-key", G_TYPE_FROM_CLASS(klass),
+            G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+            g_cclosure_marshal_generic, G_TYPE_NONE, 3,
+            G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT);
+
+    signals[SIGNAL_ON_PEER_CERTIFICATE] =
+        g_signal_new("on-peer-certificate", G_TYPE_FROM_CLASS(klass),
+            G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+            g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 1,
+            G_TYPE_STRING);
+
+    properties[PROP_AGENT] =
+        g_param_spec_object("agent",
+            "ERDtlsAgent",
+            "Agent to use in creation of the connection",
+            ER_TYPE_DTLS_AGENT,
+            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
+
+    _er_dtls_init_openssl();
+
+    gobject_class->finalize = er_dtls_connection_finalize;
+}
+
+static void er_dtls_connection_init(ErDtlsConnection *self)
+{
+    ErDtlsConnectionPrivate *priv = ER_DTLS_CONNECTION_GET_PRIVATE(self);
+    self->priv = priv;
+
+    priv->ssl = NULL;
+    priv->bio = NULL;
+    priv->thread = NULL;
+
+    priv->send_closure = NULL;
+
+    priv->is_client = FALSE;
+    priv->is_alive = TRUE;
+    priv->keys_exported = FALSE;
+    priv->timeout_set = FALSE;
+
+    priv->bio_buffer = NULL;
+    priv->bio_buffer_len = 0;
+    priv->bio_buffer_offset = 0;
+
+    g_mutex_init(&priv->mutex);
+    g_cond_init(&priv->condition);
+}
+
+static void er_dtls_connection_finalize(GObject *gobject)
+{
+    ErDtlsConnection *self = ER_DTLS_CONNECTION(gobject);
+    ErDtlsConnectionPrivate *priv = self->priv;
+
+
+    SSL_free(priv->ssl);
+    priv->ssl = NULL;
+
+    if (priv->send_closure) {
+        g_closure_unref(priv->send_closure);
+        priv->send_closure = NULL;
+    }
+
+    g_mutex_clear(&priv->mutex);
+    g_cond_clear(&priv->condition);
+
+    LOG_DEBUG(self, "finalized");
+
+    G_OBJECT_CLASS(er_dtls_connection_parent_class)->finalize(gobject);
+}
+
+static void er_dtls_connection_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    ErDtlsConnection *self = ER_DTLS_CONNECTION(object);
+    ErDtlsAgent *agent;
+    ErDtlsConnectionPrivate *priv = self->priv;
+    SSL_CTX *ssl_context;
+
+    switch (prop_id) {
+    case PROP_AGENT:
+        g_return_if_fail(!priv->ssl);
+        agent = ER_DTLS_AGENT(g_value_get_object(value));
+        g_return_if_fail(ER_IS_DTLS_AGENT(agent));
+
+        ssl_context = _er_dtls_agent_peek_context(agent);
+
+        priv->ssl = SSL_new(ssl_context);
+        g_return_if_fail(priv->ssl);
+
+        priv->bio = BIO_new(BIO_s_er_dtls_connection());
+        g_return_if_fail(priv->bio);
+
+        priv->bio->ptr = self;
+        SSL_set_bio(priv->ssl, priv->bio, priv->bio);
+
+        SSL_set_verify(priv->ssl,
+            SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, openssl_verify_callback);
+        SSL_set_ex_data(priv->ssl, connection_ex_index, self);
+
+        log_state(self, "connection created");
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
+
+void er_dtls_connection_start(ErDtlsConnection *self, gboolean is_client)
+{
+    g_return_if_fail(ER_IS_DTLS_CONNECTION(self));
+    ErDtlsConnectionPrivate *priv = self->priv;
+    g_return_if_fail(priv->send_closure);
+    g_return_if_fail(priv->ssl);
+    g_return_if_fail(priv->bio);
+
+    LOG_TRACE(self, "locking @ start");
+    g_mutex_lock(&priv->mutex);
+    LOG_TRACE(self, "locked @ start");
+
+    priv->is_alive = TRUE;
+    priv->timeout_set = FALSE;
+    priv->bio_buffer = NULL;
+    priv->bio_buffer_len = 0;
+    priv->bio_buffer_offset = 0;
+    priv->keys_exported = FALSE;
+
+    priv->is_client = is_client;
+    if (priv->is_client) {
+        SSL_set_connect_state(priv->ssl);
+    } else {
+        SSL_set_accept_state(priv->ssl);
+    }
+    log_state(self, "initial state set");
+
+    openssl_poll(self);
+
+    log_state(self, "first poll done");
+    priv->thread = NULL;
+
+    LOG_TRACE(self, "unlocking @ start");
+    g_mutex_unlock(&priv->mutex);
+}
+
+void er_dtls_connection_start_timeout(ErDtlsConnection *self)
+{
+    g_return_if_fail(ER_IS_DTLS_CONNECTION(self));
+
+    ErDtlsConnectionPrivate *priv = self->priv;
+    GError *error = NULL;
+    gchar *thread_name = g_strdup_printf("connection_thread_%p", self);
+
+    LOG_TRACE(self, "locking @ start_timeout");
+    g_mutex_lock(&priv->mutex);
+    LOG_TRACE(self, "locked @ start_timeout");
+
+    LOG_INFO(self, "starting connection timeout");
+    priv->thread = g_thread_try_new(thread_name,
+            (GThreadFunc) connection_timeout_thread_func, self, &error);
+    if (error) {
+        LOG_WARNING(self, "error creating connection thread: %s (%d)",
+                error->message, error->code);
+        g_clear_error(&error);
+    }
+
+    g_free(thread_name);
+
+    LOG_TRACE(self, "unlocking @ start_timeout");
+    g_mutex_unlock(&priv->mutex);
+}
+
+void er_dtls_connection_stop(ErDtlsConnection *self)
+{
+    g_return_if_fail(ER_IS_DTLS_CONNECTION(self));
+    g_return_if_fail(self->priv->ssl);
+    g_return_if_fail(self->priv->bio);
+
+    LOG_DEBUG(self, "stopping connection");
+
+    LOG_TRACE(self, "locking @ stop");
+    g_mutex_lock(&self->priv->mutex);
+    LOG_TRACE(self, "locked @ stop");
+
+    self->priv->is_alive = FALSE;
+    LOG_TRACE(self, "signaling @ stop");
+    g_cond_signal(&self->priv->condition);
+    LOG_TRACE(self, "signaled @ stop");
+
+    LOG_TRACE(self, "unlocking @ stop");
+    g_mutex_unlock(&self->priv->mutex);
+
+    LOG_DEBUG(self, "stopped connection");
+}
+
+void er_dtls_connection_close(ErDtlsConnection *self)
+{
+    g_return_if_fail(ER_IS_DTLS_CONNECTION(self));
+    g_return_if_fail(self->priv->ssl);
+    g_return_if_fail(self->priv->bio);
+
+    LOG_DEBUG(self, "closing connection");
+
+    LOG_TRACE(self, "locking @ close");
+    g_mutex_lock(&self->priv->mutex);
+    LOG_TRACE(self, "locked @ close");
+
+    if (self->priv->is_alive) {
+        self->priv->is_alive = FALSE;
+        g_cond_signal(&self->priv->condition);
+    }
+
+    LOG_TRACE(self, "unlocking @ close");
+    g_mutex_unlock(&self->priv->mutex);
+
+    if (self->priv->thread) {
+        g_thread_join(self->priv->thread);
+        self->priv->thread = NULL;
+    }
+
+    LOG_DEBUG(self, "closed connection");
+}
+
+void er_dtls_connection_set_send_callback(ErDtlsConnection *self, GClosure *closure)
+{
+    g_return_if_fail(ER_IS_DTLS_CONNECTION(self));
+
+    LOG_TRACE(self, "locking @ set_send_callback");
+    g_mutex_lock(&self->priv->mutex);
+    LOG_TRACE(self, "locked @ set_send_callback");
+
+    self->priv->send_closure = closure;
+
+    if (closure && G_CLOSURE_NEEDS_MARSHAL(closure)) {
+        g_closure_set_marshal(closure, g_cclosure_marshal_generic);
+    }
+
+    LOG_TRACE(self, "unlocking @ set_send_callback");
+    g_mutex_unlock(&self->priv->mutex);
+}
+
+gint er_dtls_connection_process(ErDtlsConnection *self, gpointer data, gint len)
+{
+    g_return_val_if_fail(ER_IS_DTLS_CONNECTION(self), 0);
+    ErDtlsConnectionPrivate *priv = self->priv;
+    gint result;
+
+    g_return_val_if_fail(self->priv->ssl, 0);
+    g_return_val_if_fail(self->priv->bio, 0);
+
+    LOG_TRACE(self, "locking @ process");
+    g_mutex_lock(&priv->mutex);
+    LOG_TRACE(self, "locked @ process");
+
+    g_warn_if_fail(!priv->bio_buffer);
+
+    priv->bio_buffer = data;
+    priv->bio_buffer_len = len;
+    priv->bio_buffer_offset = 0;
+
+    log_state(self, "process start");
+
+    if (SSL_want_write(priv->ssl)) {
+        openssl_poll(self);
+        log_state(self, "process want write, after poll");
+    }
+
+    result = SSL_read(priv->ssl, data, len);
+
+    log_state(self, "process after read");
+
+    openssl_poll(self);
+
+    log_state(self, "process after poll");
+
+    LOG_DEBUG(self, "read result: %d", result);
+
+    LOG_TRACE(self, "unlocking @ process");
+    g_mutex_unlock(&priv->mutex);
+
+    return result;
+}
+
+gint er_dtls_connection_send(ErDtlsConnection *self, gpointer data, gint len)
+{
+    g_return_val_if_fail(ER_IS_DTLS_CONNECTION(self), 0);
+    int ret = 0;
+
+    g_return_val_if_fail(self->priv->ssl, 0);
+    g_return_val_if_fail(self->priv->bio, 0);
+
+    LOG_TRACE(self, "locking @ send");
+    g_mutex_lock(&self->priv->mutex);
+    LOG_TRACE(self, "locked @ send");
+
+    if (SSL_is_init_finished(self->priv->ssl)) {
+        ret = SSL_write(self->priv->ssl, data, len);
+        LOG_DEBUG(self, "data sent: input was %d B, output is %d B", len, ret);
+    } else {
+        LOG_WARNING(self, "tried to send data before handshake was complete");
+        ret = 0;
+    }
+
+    LOG_TRACE(self, "unlocking @ send");
+    g_mutex_unlock(&self->priv->mutex);
+
+    return ret;
+}
+
+/*
+     ######   #######  ##    ##
+    ##    ## ##     ## ###   ##
+    ##       ##     ## ####  ##
+    ##       ##     ## ## ## ##
+    ##       ##     ## ##  ####
+    ##    ## ##     ## ##   ###
+     ######   #######  ##    ##
+*/
+
+static void log_state(ErDtlsConnection *self, const gchar *str)
+{
+    ErDtlsConnectionPrivate *priv = self->priv;
+    guint states = 0;
+
+    states |= (!!SSL_is_init_finished(priv->ssl) << 0);
+    states |= (!!SSL_in_init(priv->ssl) << 4);
+    states |= (!!SSL_in_before(priv->ssl) << 8);
+    states |= (!!SSL_in_connect_init(priv->ssl) << 12);
+    states |= (!!SSL_in_accept_init(priv->ssl) << 16);
+    states |= (!!SSL_want_write(priv->ssl) << 20);
+    states |= (!!SSL_want_read(priv->ssl) << 24);
+
+    LOG_LOG(self, "%s: role=%s buf=(%d,%p:%d/%d) %x|%x %s",
+        str,
+        priv->is_client ? "client" : "server",
+        pqueue_size(priv->ssl->d1->sent_messages),
+        priv->bio_buffer,
+        priv->bio_buffer_offset,
+        priv->bio_buffer_len,
+        states, SSL_get_state(priv->ssl),
+        SSL_state_string_long(priv->ssl));
+}
+
+static gpointer connection_timeout_thread_func(ErDtlsConnection *self)
+{
+    ErDtlsConnectionPrivate *priv = self->priv;
+    struct timeval timeout;
+    gint64 end_time, wait_time;
+    gint ret;
+
+    while (priv->is_alive) {
+        LOG_TRACE(self, "locking @ timeout");
+        g_mutex_lock(&priv->mutex);
+        LOG_TRACE(self, "locked @ timeout");
+
+        if (DTLSv1_get_timeout(priv->ssl, &timeout)) {
+            wait_time = timeout.tv_sec * G_USEC_PER_SEC + timeout.tv_usec;
+
+            if (wait_time) {
+                LOG_DEBUG(self, "waiting for %" G_GINT64_FORMAT " usec", wait_time);
+
+                end_time = g_get_monotonic_time() + wait_time;
+
+                LOG_TRACE(self, "wait @ timeout");
+                g_cond_wait_until(&priv->condition, &priv->mutex, end_time);
+                LOG_TRACE(self, "continued @ timeout");
+            }
+
+            ret = DTLSv1_handle_timeout(priv->ssl);
+
+            LOG_DEBUG(self, "handle timeout returned %d, is_alive: %d", ret, priv->is_alive);
+
+            if (ret < 0) {
+                LOG_TRACE(self, "unlocking @ timeout failed");
+                g_mutex_unlock(&priv->mutex);
+                break; /* self failed after DTLS1_TMO_ALERT_COUNT (12) attempts */
+            }
+
+            if (ret > 0) {
+                log_state(self, "handling timeout before poll");
+                openssl_poll(self);
+                log_state(self, "handling timeout after poll");
+            }
+        } else {
+            LOG_DEBUG(self, "waiting indefinitely");
+
+            priv->timeout_set = FALSE;
+
+            while (!priv->timeout_set && priv->is_alive) {
+                LOG_TRACE(self, "wait @ timeout");
+                g_cond_wait(&priv->condition, &priv->mutex);
+            }
+            LOG_TRACE(self, "continued @ timeout");
+        }
+
+        LOG_TRACE(self, "unlocking @ timeout");
+        g_mutex_unlock(&priv->mutex);
+    }
+
+    log_state(self, "timeout thread exiting");
+
+    return NULL;
+}
+
+static void export_srtp_keys(ErDtlsConnection *self)
+{
+    typedef struct {
+        guint8 v[SRTP_KEY_LEN];
+    } Key;
+
+    typedef struct {
+        guint8 v[SRTP_SALT_LEN];
+    } Salt;
+
+    struct {
+        Key client_key;
+        Key server_key;
+        Salt client_salt;
+        Salt server_salt;
+    } exported_keys;
+
+    struct {
+        Key key;
+        Salt salt;
+    } client_key, server_key;
+
+    SRTP_PROTECTION_PROFILE *profile;
+    ErDtlsSrtpCipher cipher;
+    ErDtlsSrtpAuth auth;
+    gint success;
+
+    static gchar export_string[] = "EXTRACTOR-dtls_srtp";
+
+    success = SSL_export_keying_material(self->priv->ssl,
+        (gpointer) &exported_keys, 60, export_string, strlen(export_string), NULL, 0, 0);
+
+    if (!success) {
+        LOG_WARNING(self, "failed to export srtp keys");
+        return;
+    }
+
+    profile = SSL_get_selected_srtp_profile(self->priv->ssl);
+
+    LOG_INFO(self, "keys received, profile is %s", profile->name);
+
+    switch (profile->id) {
+    case SRTP_AES128_CM_SHA1_80:
+        cipher = ER_DTLS_SRTP_CIPHER_AES_128_ICM;
+        auth = ER_DTLS_SRTP_AUTH_HMAC_SHA1_80;
+        break;
+    case SRTP_AES128_CM_SHA1_32:
+        cipher = ER_DTLS_SRTP_CIPHER_AES_128_ICM;
+        auth = ER_DTLS_SRTP_AUTH_HMAC_SHA1_32;
+        break;
+    default:
+        LOG_WARNING(self, "invalid crypto suite set by handshake");
+        goto beach;
+    }
+
+    client_key.key = exported_keys.client_key;
+    server_key.key = exported_keys.server_key;
+    client_key.salt = exported_keys.client_salt;
+    server_key.salt = exported_keys.server_salt;
+
+    if (self->priv->is_client) {
+        g_signal_emit(self, signals[SIGNAL_ON_ENCODER_KEY], 0, &client_key, cipher, auth);
+        g_signal_emit(self, signals[SIGNAL_ON_DECODER_KEY], 0, &server_key, cipher, auth);
+    } else {
+        g_signal_emit(self, signals[SIGNAL_ON_ENCODER_KEY], 0, &server_key, cipher, auth);
+        g_signal_emit(self, signals[SIGNAL_ON_DECODER_KEY], 0, &client_key, cipher, auth);
+    }
+
+beach:
+    self->priv->keys_exported = TRUE;
+}
+
+static void openssl_poll(ErDtlsConnection *self)
+{
+    int ret;
+    char buf[512];
+    int error;
+
+    log_state(self, "poll: before handshake");
+
+    ret = SSL_do_handshake(self->priv->ssl);
+
+    log_state(self, "poll: after handshake");
+
+    if (ret == 1) {
+        if (!self->priv->keys_exported) {
+            LOG_INFO(self, "handshake just completed successfully, exporting keys");
+            export_srtp_keys(self);
+        } else {
+            LOG_INFO(self, "handshake is completed");
+        }
+        return;
+    } else {
+        if (ret == 0) {
+            LOG_DEBUG(self, "do_handshake encountered EOF");
+        } else if (ret == -1) {
+            LOG_WARNING(self, "do_handshake encountered BIO error");
+        } else {
+            LOG_DEBUG(self, "do_handshake returned %d", ret);
+        }
+    }
+
+    error = SSL_get_error(self->priv->ssl, ret);
+
+    switch (error) {
+    case SSL_ERROR_NONE:
+        LOG_WARNING(self, "no error, handshake should be done");
+        break;
+    case SSL_ERROR_SSL:
+        LOG_LOG(self, "SSL error %d: %s", error, ERR_error_string(ERR_get_error(), buf));
+        break;
+    case SSL_ERROR_WANT_READ:
+        LOG_LOG(self, "SSL wants read");
+        break;
+    case SSL_ERROR_WANT_WRITE:
+        LOG_LOG(self, "SSL wants write");
+        break;
+    case SSL_ERROR_SYSCALL: {
+        LOG_LOG(self, "SSL syscall (error) : %lu", ERR_get_error());
+        break;
+    }
+    default:
+        LOG_WARNING(self, "Unknown SSL error: %d, ret: %d", error, ret);
+    }
+}
+
+static int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
+{
+    ErDtlsConnection *self;
+    SSL *ssl;
+    BIO *bio;
+    gchar *pem = NULL;
+    gboolean accepted = FALSE;
+
+    ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+    self = SSL_get_ex_data(ssl, connection_ex_index);
+    g_return_val_if_fail(ER_IS_DTLS_CONNECTION(self), FALSE);
+
+    pem = _er_dtls_x509_to_pem(x509_ctx->cert);
+
+    if (!pem) {
+        LOG_WARNING(self, "failed to convert received certificate to pem format");
+    } else {
+        bio = BIO_new(BIO_s_mem());
+        if (bio) {
+            gchar buffer[2048];
+            gint len;
+
+            len = X509_NAME_print_ex(bio, X509_get_subject_name(x509_ctx->cert), 1, XN_FLAG_MULTILINE);
+            BIO_read(bio, buffer, len);
+            buffer[len] = '\0';
+            LOG_DEBUG(self, "Peer certificate received:\n%s", buffer);
+            BIO_free(bio);
+        } else {
+            LOG_DEBUG(self, "failed to create certificate print membio");
+        }
+
+        g_signal_emit(self, signals[SIGNAL_ON_PEER_CERTIFICATE], 0, pem, &accepted);
+        g_free(pem);
+    }
+
+    return accepted;
+}
+
+/*
+    ########  ####  #######
+    ##     ##  ##  ##     ##
+    ##     ##  ##  ##     ##
+    ########   ##  ##     ##
+    ##     ##  ##  ##     ##
+    ##     ##  ##  ##     ##
+    ########  ####  #######
+*/
+
+static BIO_METHOD custom_bio_methods = {
+    BIO_TYPE_BIO,
+    "stream",
+    bio_method_write,
+    bio_method_read,
+    NULL,
+    NULL,
+    bio_method_ctrl,
+    bio_method_new,
+    bio_method_free,
+    NULL,
+};
+
+static BIO_METHOD* BIO_s_er_dtls_connection()
+{
+    return &custom_bio_methods;
+}
+
+static int bio_method_write(BIO *bio, const char *data, int size)
+{
+    ErDtlsConnection *self = ER_DTLS_CONNECTION(bio->ptr);
+
+    LOG_LOG(self, "BIO: writing %d", size);
+
+    if (self->priv->send_closure) {
+        GValue values[3] = { G_VALUE_INIT };
+
+        g_value_init(&values[0], ER_TYPE_DTLS_CONNECTION);
+        g_value_set_object(&values[0], self);
+
+        g_value_init(&values[1], G_TYPE_POINTER);
+        g_value_set_pointer(&values[1], (gpointer) data);
+
+        g_value_init(&values[2], G_TYPE_INT);
+        g_value_set_int(&values[2], size);
+
+        g_closure_invoke(self->priv->send_closure, NULL, 3, values, NULL);
+    }
+
+    return size;
+}
+
+static int bio_method_read(BIO *bio, char *out_buffer, int size)
+{
+    ErDtlsConnection *self = ER_DTLS_CONNECTION(bio->ptr);
+    ErDtlsConnectionPrivate *priv = self->priv;
+    guint internal_size;
+    gint copy_size;
+
+    internal_size = priv->bio_buffer_len - priv->bio_buffer_offset;
+
+    if (!priv->bio_buffer) {
+        LOG_LOG(self, "BIO: EOF");
+        return 0;
+    }
+
+    if (!out_buffer || size <= 0) {
+        LOG_WARNING(self, "BIO: read got invalid arguments");
+        if (internal_size) {
+            BIO_set_retry_read(bio);
+        }
+        return internal_size;
+    }
+
+    if (size > internal_size) {
+        copy_size = internal_size;
+    } else {
+        copy_size = size;
+    }
+
+    LOG_DEBUG(self, "reading %d/%d bytes %d at offset %d, output buff size is %d",
+        copy_size, priv->bio_buffer_len, internal_size, priv->bio_buffer_offset, size);
+
+    memcpy(out_buffer, priv->bio_buffer + priv->bio_buffer_offset, copy_size);
+    priv->bio_buffer_offset += copy_size;
+
+    if (priv->bio_buffer_len == priv->bio_buffer_offset) {
+        priv->bio_buffer = NULL;
+    }
+
+    return copy_size;
+}
+
+static long bio_method_ctrl(BIO *bio, int cmd, long arg1, void *arg2)
+{
+    ErDtlsConnection *self = ER_DTLS_CONNECTION(bio->ptr);
+    ErDtlsConnectionPrivate *priv = self->priv;
+
+    switch (cmd) {
+    case BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT:
+    case BIO_CTRL_DGRAM_SET_RECV_TIMEOUT:
+        LOG_LOG(self, "BIO: Timeout set");
+        priv->timeout_set = TRUE;
+        g_cond_signal(&priv->condition);
+        return 1;
+    case BIO_CTRL_RESET:
+        priv->bio_buffer = NULL;
+        priv->bio_buffer_len = 0;
+        priv->bio_buffer_offset = 0;
+        LOG_LOG(self, "BIO: EOF reset");
+        return 1;
+    case BIO_CTRL_EOF: {
+        gint eof = !(priv->bio_buffer_len - priv->bio_buffer_offset);
+        LOG_LOG(self, "BIO: EOF query returned %d", eof);
+        return eof;
+    }
+    case BIO_CTRL_WPENDING:
+        LOG_LOG(self, "BIO: pending write");
+        return 1;
+    case BIO_CTRL_PENDING: {
+        gint pending = priv->bio_buffer_len - priv->bio_buffer_offset;
+        LOG_LOG(self, "BIO: %d bytes pending", pending);
+        return pending;
+    }
+    case BIO_CTRL_FLUSH:
+        LOG_LOG(self, "BIO: flushing");
+        return 1;
+    case BIO_CTRL_DGRAM_QUERY_MTU:
+        LOG_DEBUG(self, "BIO: MTU query, returning 0...");
+        return 0;
+    case BIO_CTRL_DGRAM_MTU_EXCEEDED:
+        LOG_WARNING(self, "BIO: MTU exceeded");
+        return 0;
+    default:
+        LOG_LOG(self, "BIO: unhandled ctrl, %d", cmd);
+        return 0;
+    }
+}
+
+static int bio_method_new(BIO *bio)
+{
+    LOG_LOG(NULL, "BIO: new");
+
+    bio->shutdown = 0;
+    bio->init = 1;
+
+    return 1;
+}
+
+static int bio_method_free(BIO *bio)
+{
+    if (!bio) {
+        LOG_LOG(NULL, "BIO free called with null bio");
+        return 0;
+    }
+
+    LOG_LOG(ER_DTLS_CONNECTION(bio->ptr), "BIO free");
+    return 0;
+}
diff --git a/ext/dtls/gstdtlsconnection.h b/ext/dtls/gstdtlsconnection.h
new file mode 100644 (file)
index 0000000..916d8ad
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifndef gstdtlsconnection_h
+#define gstdtlsconnection_h
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define ER_TYPE_DTLS_CONNECTION            (er_dtls_connection_get_type())
+#define ER_DTLS_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), ER_TYPE_DTLS_CONNECTION, ErDtlsConnection))
+#define ER_DTLS_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), ER_TYPE_DTLS_CONNECTION, ErDtlsConnectionClass))
+#define ER_IS_DTLS_CONNECTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), ER_TYPE_DTLS_CONNECTION))
+#define ER_IS_DTLS_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ER_TYPE_DTLS_CONNECTION))
+#define ER_DTLS_CONNECTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), ER_TYPE_DTLS_CONNECTION, ErDtlsConnectionClass))
+
+typedef struct _ErDtlsConnection        ErDtlsConnection;
+typedef struct _ErDtlsConnectionClass   ErDtlsConnectionClass;
+typedef struct _ErDtlsConnectionPrivate ErDtlsConnectionPrivate;
+
+/**
+ * ErDtlsSrtpCipher:
+ * @ER_DTLS_SRTP_CIPHER_AES_128_ICM: aes-128-icm
+ *
+ * SRTP Cipher selected by the DTLS handshake, should match the enums in gstsrtp
+ */
+typedef enum {
+    ER_DTLS_SRTP_CIPHER_AES_128_ICM = 1
+} ErDtlsSrtpCipher;
+
+/**
+ * ErDtlsSrtpAuth:
+ * @ER_DTLS_SRTP_AUTH_HMAC_SHA1_32: hmac-sha1-32
+ * @ER_DTLS_SRTP_AUTH_HMAC_SHA1_80: hmac-sha1-80
+ *
+ * SRTP Auth selected by the DTLS handshake, should match the enums in gstsrtp
+ */
+typedef enum {
+    ER_DTLS_SRTP_AUTH_HMAC_SHA1_32 = 1,
+    ER_DTLS_SRTP_AUTH_HMAC_SHA1_80 = 2
+} ErDtlsSrtpAuth;
+
+#define ER_DTLS_SRTP_MASTER_KEY_LENGTH 30
+
+/*
+ * ErDtlsConnection:
+ *
+ * A class that handles a single DTLS connection.
+ * Any connection needs to be created with the agent property set.
+ * Once the DTLS handshake is completed, on-encoder-key and on-decoder-key will be signalled.
+ */
+struct _ErDtlsConnection {
+    GObject parent_instance;
+
+    ErDtlsConnectionPrivate *priv;
+};
+
+struct _ErDtlsConnectionClass {
+    GObjectClass parent_class;
+};
+
+GType er_dtls_connection_get_type(void) G_GNUC_CONST;
+
+void er_dtls_connection_start(ErDtlsConnection *, gboolean is_client);
+void er_dtls_connection_start_timeout(ErDtlsConnection *);
+
+/*
+ * Stops the connections, it is not required to call this function.
+ */
+void er_dtls_connection_stop(ErDtlsConnection *);
+
+/*
+ * Closes the connection, the function will block until the connection has been stopped.
+ * If stop is called some time before, close will return instantly.
+ */
+void er_dtls_connection_close(ErDtlsConnection *);
+
+/*
+ * Sets the closure that will be called whenever data needs to be sent.
+ *
+ * The closure will get called with the following arguments:
+ * void cb(ErDtlsConnection *, gpointer data, gint length, gpointer user_data)
+ */
+void er_dtls_connection_set_send_callback(ErDtlsConnection *, GClosure *);
+
+/*
+ * Processes data that has been recevied, the transformation is done in-place.
+ * Returns the length of the plaintext data that was decoded, if no data is available, 0<= will be returned.
+ */
+gint er_dtls_connection_process(ErDtlsConnection *, gpointer ptr, gint len);
+
+/*
+ * If the DTLS handshake is completed this function will encode the given data.
+ * Returns the length of the data sent, or 0 if the DTLS handshake is not completed.
+ */
+gint er_dtls_connection_send(ErDtlsConnection *, gpointer ptr, gint len);
+
+G_END_DECLS
+
+#endif /* gstdtlsconnection_h */
diff --git a/ext/dtls/gstdtlsdec.c b/ext/dtls/gstdtlsdec.c
new file mode 100644 (file)
index 0000000..70865be
--- /dev/null
@@ -0,0 +1,607 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdtlsdec.h"
+
+#include "gstdtlscertificate.h"
+
+static GstStaticPadTemplate sink_template =
+    GST_STATIC_PAD_TEMPLATE("sink",
+        GST_PAD_SINK,
+        GST_PAD_ALWAYS,
+        GST_STATIC_CAPS("application/x-dtls")
+    );
+
+static GstStaticPadTemplate src_template =
+    GST_STATIC_PAD_TEMPLATE("src",
+        GST_PAD_SRC,
+        GST_PAD_REQUEST,
+        GST_STATIC_CAPS_ANY
+    );
+
+GST_DEBUG_CATEGORY_STATIC(er_dtls_dec_debug);
+#define GST_CAT_DEFAULT er_dtls_dec_debug
+
+#define gst_er_dtls_dec_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE(GstErDtlsDec, gst_er_dtls_dec, GST_TYPE_ELEMENT,
+    GST_DEBUG_CATEGORY_INIT(er_dtls_dec_debug, "erdtlsdec", 0, "Ericsson DTLS Decoder"));
+
+#define UNUSED(param) while (0) { (void)(param); }
+
+enum {
+    SIGNAL_ON_KEY_RECEIVED,
+    NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS];
+
+enum {
+    PROP_0,
+    PROP_CONNECTION_ID,
+    PROP_PEM,
+    PROP_PEER_PEM,
+
+    PROP_DECODER_KEY,
+    PROP_SRTP_CIPHER,
+    PROP_SRTP_AUTH,
+    NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+#define DEFAULT_CONNECTION_ID NULL
+#define DEFAULT_PEM NULL
+#define DEFAULT_PEER_PEM NULL
+
+#define DEFAULT_DECODER_KEY NULL
+#define DEFAULT_SRTP_CIPHER 0
+#define DEFAULT_SRTP_AUTH 0
+
+
+static void gst_er_dtls_dec_finalize(GObject *);
+static void gst_er_dtls_dec_dispose(GObject *);
+static void gst_er_dtls_dec_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *);
+static void gst_er_dtls_dec_get_property(GObject *, guint prop_id, GValue *, GParamSpec *);
+
+static GstStateChangeReturn gst_er_dtls_dec_change_state(GstElement *, GstStateChange);
+static GstPad *gst_er_dtls_dec_request_new_pad(GstElement *, GstPadTemplate *, const gchar *name, const GstCaps *);
+static void gst_er_dtls_dec_release_pad(GstElement *, GstPad *);
+
+static void on_key_received(ErDtlsConnection *, gpointer key, guint cipher, guint auth, GstErDtlsDec *);
+static gboolean on_peer_certificate_received(ErDtlsConnection *, gchar *pem, GstErDtlsDec *);
+static GstFlowReturn sink_chain(GstPad *, GstObject *parent, GstBuffer *);
+
+static ErDtlsAgent *get_agent_by_pem(const gchar *pem);
+static void agent_weak_ref_notify(gchar *pem, ErDtlsAgent *);
+static void create_connection(GstErDtlsDec *, gchar *id);
+static void connection_weak_ref_notify(gchar *id, ErDtlsConnection *);
+
+static void gst_er_dtls_dec_class_init(GstErDtlsDecClass *klass)
+{
+    GObjectClass *gobject_class;
+    GstElementClass *element_class;
+
+    gobject_class = (GObjectClass *) klass;
+    element_class = (GstElementClass *) klass;
+
+    gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_finalize);
+    gobject_class->dispose = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_dispose);
+    gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_set_property);
+    gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_get_property);
+
+    element_class->change_state = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_change_state);
+    element_class->request_new_pad = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_request_new_pad);
+    element_class->release_pad = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_release_pad);
+
+    signals[SIGNAL_ON_KEY_RECEIVED] =
+        g_signal_new("on-key-received", G_TYPE_FROM_CLASS(klass),
+            G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+            g_cclosure_marshal_generic, G_TYPE_NONE, 0);
+
+    properties[PROP_CONNECTION_ID] =
+        g_param_spec_string("connection-id",
+            "Connection id",
+            "Every encoder/decoder pair should have the same, unique, connection-id",
+            DEFAULT_CONNECTION_ID,
+            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_PEM] =
+        g_param_spec_string("pem",
+            "PEM string",
+            "A string containing a X509 certificate and RSA private key in PEM format",
+            DEFAULT_PEM,
+            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_PEER_PEM] =
+        g_param_spec_string("peer-pem",
+            "Peer PEM string",
+            "The X509 certificate received in the DTLS handshake, in PEM format",
+            DEFAULT_PEER_PEM,
+            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_DECODER_KEY] =
+        g_param_spec_boxed("decoder-key",
+            "Decoder key",
+            "SRTP key that should be used by the decider",
+            GST_TYPE_CAPS,
+            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_SRTP_CIPHER] =
+        g_param_spec_uint("srtp-cipher",
+            "SRTP cipher",
+            "The SRTP cipher selected in the DTLS handshake. "
+            "The value will be set to an ErDtlsSrtpCipher.",
+            0, ER_DTLS_SRTP_CIPHER_AES_128_ICM, DEFAULT_SRTP_CIPHER,
+            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_SRTP_AUTH] =
+        g_param_spec_uint("srtp-auth",
+            "SRTP authentication",
+            "The SRTP authentication selected in the DTLS handshake. "
+            "The value will be set to an ErDtlsSrtpAuth.",
+            0, ER_DTLS_SRTP_AUTH_HMAC_SHA1_80, DEFAULT_SRTP_AUTH,
+            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
+
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&src_template));
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&sink_template));
+
+    gst_element_class_set_static_metadata(element_class,
+        "DTLS Decoder",
+        "Decoder/Network/DTLS",
+        "Decodes DTLS packets",
+        "Patrik Oldsberg patrik.oldsberg@ericsson.com");
+}
+
+static void gst_er_dtls_dec_init(GstErDtlsDec *self)
+{
+    GstPad *sink;
+    self->agent = get_agent_by_pem(NULL);
+    self->connection_id = NULL;
+    self->connection = NULL;
+    self->peer_pem = NULL;
+
+    self->decoder_key = NULL;
+    self->srtp_cipher = DEFAULT_SRTP_CIPHER;
+    self->srtp_auth = DEFAULT_SRTP_AUTH;
+
+    g_mutex_init(&self->src_mutex);
+
+    self->src = NULL;
+    sink = gst_pad_new_from_static_template(&sink_template, "sink");
+    g_return_if_fail(sink);
+
+    gst_pad_set_chain_function(sink, GST_DEBUG_FUNCPTR(sink_chain));
+
+    gst_element_add_pad(GST_ELEMENT(self), sink);
+}
+
+static void gst_er_dtls_dec_finalize(GObject *object)
+{
+    GstErDtlsDec *self = GST_ER_DTLS_DEC(object);
+
+    if (self->decoder_key) {
+        gst_buffer_unref(self->decoder_key);
+        self->decoder_key = NULL;
+    }
+
+    g_free(self->connection_id);
+    self->connection_id = NULL;
+
+    g_free(self->peer_pem);
+    self->peer_pem = NULL;
+
+    g_mutex_clear(&self->src_mutex);
+
+    GST_LOG_OBJECT(self, "finalized");
+
+    G_OBJECT_CLASS(parent_class)->finalize(object);
+}
+
+static void gst_er_dtls_dec_dispose(GObject *object)
+{
+    GstErDtlsDec *self = GST_ER_DTLS_DEC(object);
+
+    if (self->agent) {
+        g_object_unref(self->agent);
+        self->agent = NULL;
+    }
+
+    if (self->connection) {
+        g_object_unref(self->connection);
+        self->connection = NULL;
+    }
+}
+
+static void gst_er_dtls_dec_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    GstErDtlsDec *self = GST_ER_DTLS_DEC(object);
+
+    switch (prop_id) {
+    case PROP_CONNECTION_ID:
+        g_free(self->connection_id);
+        self->connection_id = g_value_dup_string(value);
+        g_return_if_fail(self->agent);
+        create_connection(self, self->connection_id);
+        break;
+    case PROP_PEM:
+        if (self->agent) {
+            g_object_unref(self->agent);
+        }
+        self->agent = get_agent_by_pem(g_value_get_string(value));
+        if (self->connection_id) {
+            create_connection(self, self->connection_id);
+        }
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
+
+static void gst_er_dtls_dec_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    GstErDtlsDec *self = GST_ER_DTLS_DEC(object);
+
+    switch (prop_id) {
+    case PROP_CONNECTION_ID:
+        g_value_set_string(value, self->connection_id);
+        break;
+    case PROP_PEM:
+        g_value_take_string(value, er_dtls_agent_get_certificate_pem(self->agent));
+        break;
+    case PROP_PEER_PEM:
+        g_value_set_string(value, self->peer_pem);
+        break;
+    case PROP_DECODER_KEY:
+        g_value_set_boxed(value, self->decoder_key);
+        break;
+    case PROP_SRTP_CIPHER:
+        g_value_set_uint(value, self->srtp_cipher);
+        break;
+    case PROP_SRTP_AUTH:
+        g_value_set_uint(value, self->srtp_auth);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
+
+static GstStateChangeReturn gst_er_dtls_dec_change_state(GstElement *element, GstStateChange transition)
+{
+    GstErDtlsDec *self = GST_ER_DTLS_DEC(element);
+    GstStateChangeReturn ret;
+
+    switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+        if (self->connection) {
+            g_signal_connect_object(self->connection,
+                "on-decoder-key", G_CALLBACK(on_key_received), self, 0);
+            g_signal_connect_object(self->connection,
+                "on-peer-certificate", G_CALLBACK(on_peer_certificate_received), self, 0);
+        } else {
+            GST_WARNING_OBJECT(self, "trying to change state to ready without connection id and pem");
+            return GST_STATE_CHANGE_FAILURE;
+        }
+        break;
+    default:
+        break;
+    }
+
+    ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition);
+
+    return ret;
+}
+
+static GstPad *gst_er_dtls_dec_request_new_pad(GstElement *element,
+    GstPadTemplate *tmpl, const gchar *name, const GstCaps *caps)
+{
+    GstErDtlsDec *self = GST_ER_DTLS_DEC(element);
+
+    GST_DEBUG_OBJECT(element, "requesting pad");
+
+    g_return_val_if_fail(!self->src, NULL);
+    g_return_val_if_fail(tmpl->direction == GST_PAD_SRC, NULL);
+
+    g_mutex_lock(&self->src_mutex);
+
+    self->src = gst_pad_new_from_template(tmpl, name);
+    g_return_val_if_fail(self->src, NULL);
+
+    if (caps) {
+        g_object_set(self->src, "caps", caps, NULL);
+    }
+
+    gst_pad_set_active(self->src, TRUE);
+    gst_element_add_pad(element, self->src);
+
+    g_mutex_unlock(&self->src_mutex);
+
+    return self->src;
+}
+
+static void gst_er_dtls_dec_release_pad(GstElement *element, GstPad *pad)
+{
+    GstErDtlsDec *self = GST_ER_DTLS_DEC(element);
+
+    g_mutex_lock(&self->src_mutex);
+
+    g_return_if_fail(self->src == pad);
+    gst_element_remove_pad(element, self->src);
+    self->src = NULL;
+
+    GST_DEBUG_OBJECT(self, "releasing src pad");
+
+    g_mutex_unlock(&self->src_mutex);
+
+    GST_ELEMENT_GET_CLASS(element)->release_pad(element, pad);
+}
+
+static void on_key_received(ErDtlsConnection *connection, gpointer key, guint cipher, guint auth, GstErDtlsDec *self)
+{
+    gpointer key_dup;
+    gchar *key_str;
+
+    UNUSED(connection);
+    g_return_if_fail(GST_IS_ER_DTLS_DEC(self));
+
+    self->srtp_cipher = cipher;
+    self->srtp_auth = auth;
+
+    key_dup = g_memdup(key, ER_DTLS_SRTP_MASTER_KEY_LENGTH);
+    self->decoder_key = gst_buffer_new_wrapped(key_dup, ER_DTLS_SRTP_MASTER_KEY_LENGTH);
+
+    key_str = g_base64_encode(key, ER_DTLS_SRTP_MASTER_KEY_LENGTH);
+    GST_INFO_OBJECT(self, "received key: %s", key_str);
+    g_free(key_str);
+
+    g_signal_emit(self, signals[SIGNAL_ON_KEY_RECEIVED], 0);
+}
+
+static gboolean signal_peer_certificate_received(GWeakRef *ref)
+{
+    GstErDtlsDec *self;
+
+    self = g_weak_ref_get(ref);
+    g_weak_ref_clear(ref);
+    g_free(ref);
+    ref = NULL;
+
+    if (self) {
+        g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_PEER_PEM]);
+        g_object_unref(self);
+        self = NULL;
+    }
+
+    return FALSE;
+}
+
+static gboolean on_peer_certificate_received(ErDtlsConnection *connection, gchar *pem, GstErDtlsDec *self)
+{
+    GWeakRef *ref;
+
+    UNUSED(connection);
+    g_return_val_if_fail(GST_IS_ER_DTLS_DEC(self), TRUE);
+
+    GST_DEBUG_OBJECT(self, "Received peer certificate PEM: \n%s", pem);
+
+    self->peer_pem = g_strdup(pem);
+
+    ref = g_new(GWeakRef, 1);
+    g_weak_ref_init(ref, self);
+
+    g_idle_add((GSourceFunc) signal_peer_certificate_received, ref);
+
+    return TRUE;
+}
+
+static GstFlowReturn sink_chain(GstPad *pad, GstObject *parent, GstBuffer *buffer)
+{
+    GstErDtlsDec *self = GST_ER_DTLS_DEC(parent);
+    GstFlowReturn ret = GST_FLOW_OK;
+    GstMapInfo map_info = GST_MAP_INFO_INIT;
+    gint size;
+
+    if (!self->agent) {
+        gst_buffer_unref(buffer);
+        return GST_FLOW_OK;
+    }
+
+    GST_DEBUG_OBJECT(self, "received buffer from %s with length %zd",
+        self->connection_id, gst_buffer_get_size(buffer));
+
+    gst_buffer_map(buffer, &map_info, GST_MAP_READWRITE);
+
+    if (!map_info.size) {
+        gst_buffer_unmap(buffer, &map_info);
+        return GST_FLOW_OK;
+    }
+
+    size = er_dtls_connection_process(self->connection, map_info.data, map_info.size);
+    gst_buffer_unmap(buffer, &map_info);
+
+    if (size <= 0) {
+        gst_buffer_unref(buffer);
+
+        return GST_FLOW_OK;
+    }
+
+    g_mutex_lock(&self->src_mutex);
+
+    if (self->src) {
+        gst_buffer_set_size(buffer, size);
+        GST_LOG_OBJECT(self, "decoded buffer with length %d, pushing", size);
+        ret = gst_pad_push(self->src, buffer);
+    } else {
+        GST_LOG_OBJECT(self, "dropped buffer with length %d, not linked", size);
+        gst_buffer_unref(buffer);
+    }
+
+    g_mutex_unlock(&self->src_mutex);
+
+    return ret;
+}
+
+static GHashTable *agent_table = NULL;
+G_LOCK_DEFINE_STATIC(agent_table);
+
+static ErDtlsAgent *generated_cert_agent = NULL;
+
+static ErDtlsAgent *get_agent_by_pem(const gchar *pem)
+{
+    ErDtlsAgent *agent;
+
+    if (!pem) {
+        if (g_once_init_enter (&generated_cert_agent)) {
+            ErDtlsAgent *new_agent;
+
+            new_agent = g_object_new(ER_TYPE_DTLS_AGENT, "certificate",
+                g_object_new(ER_TYPE_DTLS_CERTIFICATE, NULL), NULL);
+
+            GST_DEBUG_OBJECT(generated_cert_agent, "no agent with generated cert found, creating new");
+            g_once_init_leave (&generated_cert_agent, new_agent);
+        } else {
+            GST_DEBUG_OBJECT(generated_cert_agent, "using agent with generated cert");
+        }
+
+        agent = generated_cert_agent;
+        g_object_ref(agent);
+    } else {
+        G_LOCK(agent_table);
+
+        if (!agent_table) {
+            agent_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+        }
+
+        agent =  ER_DTLS_AGENT(g_hash_table_lookup(agent_table, pem));
+
+        if (!agent) {
+            agent = g_object_new(ER_TYPE_DTLS_AGENT,
+                "certificate", g_object_new(ER_TYPE_DTLS_CERTIFICATE, "pem", pem, NULL), NULL);
+
+            g_object_weak_ref(G_OBJECT(agent), (GWeakNotify) agent_weak_ref_notify, (gpointer) g_strdup(pem));
+
+            g_hash_table_insert(agent_table, g_strdup(pem), agent);
+
+            GST_DEBUG_OBJECT(agent, "no agent found, created new");
+        } else {
+            g_object_ref(agent);
+            GST_DEBUG_OBJECT(agent, "agent found");
+        }
+
+        G_UNLOCK(agent_table);
+    }
+
+
+    return agent;
+}
+
+static void agent_weak_ref_notify(gchar *pem, ErDtlsAgent *agent)
+{
+    UNUSED(agent);
+
+    G_LOCK(agent_table);
+    g_hash_table_remove(agent_table, pem);
+    G_UNLOCK(agent_table);
+
+    g_free(pem);
+    pem = NULL;
+}
+
+static GHashTable *connection_table = NULL;
+G_LOCK_DEFINE_STATIC(connection_table);
+
+ErDtlsConnection *gst_er_dtls_dec_fetch_connection(gchar *id)
+{
+    ErDtlsConnection *connection;
+    g_return_val_if_fail(id, NULL);
+
+    GST_DEBUG("fetching '%s' from connection table, size is %d",
+        id, g_hash_table_size(connection_table));
+
+    G_LOCK(connection_table);
+
+    connection = g_hash_table_lookup(connection_table, id);
+
+    if (connection) {
+        g_object_ref(connection);
+        g_hash_table_remove(connection_table, id);
+    } else {
+        GST_WARNING("no connection with id '%s' found", id);
+    }
+
+    G_UNLOCK(connection_table);
+
+    return connection;
+}
+
+static void create_connection(GstErDtlsDec *self, gchar *id)
+{
+    g_return_if_fail(GST_IS_ER_DTLS_DEC(self));
+    g_return_if_fail(ER_IS_DTLS_AGENT(self->agent));
+
+    if (self->connection) {
+        g_object_unref(self->connection);
+        self->connection = NULL;
+    }
+
+    G_LOCK(connection_table);
+
+    if (!connection_table) {
+        connection_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+    }
+
+    if (g_hash_table_contains(connection_table, id)) {
+        G_UNLOCK(connection_table);
+
+        g_return_if_reached();
+    }
+
+    self->connection = g_object_new(ER_TYPE_DTLS_CONNECTION, "agent", self->agent, NULL);
+
+    g_object_weak_ref(G_OBJECT(self->connection), (GWeakNotify) connection_weak_ref_notify, g_strdup(id));
+
+    g_hash_table_insert(connection_table, g_strdup(id), self->connection);
+
+    G_UNLOCK(connection_table);
+}
+
+static void connection_weak_ref_notify(gchar *id, ErDtlsConnection *connection)
+{
+    UNUSED(connection);
+
+    G_LOCK(connection_table);
+    g_hash_table_remove(connection_table, id);
+    G_UNLOCK(connection_table);
+
+    g_free(id);
+    id = NULL;
+}
diff --git a/ext/dtls/gstdtlsdec.h b/ext/dtls/gstdtlsdec.h
new file mode 100644 (file)
index 0000000..af8e6f5
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifndef gstdtlsdec_h
+#define gstdtlsdec_h
+
+#include "gstdtlsagent.h"
+#include "gstdtlsconnection.h"
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_ER_DTLS_DEC \
+    (gst_er_dtls_dec_get_type())
+#define GST_ER_DTLS_DEC(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_ER_DTLS_DEC, GstErDtlsDec))
+#define GST_ER_DTLS_DEC_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_ER_DTLS_DEC, GstErDtlsDecClass))
+#define GST_IS_ER_DTLS_DEC(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_ER_DTLS_DEC))
+#define GST_IS_ER_DTLS_DEC_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_ER_DTLS_DEC))
+
+typedef struct _GstErDtlsDec GstErDtlsDec;
+typedef struct _GstErDtlsDecClass GstErDtlsDecClass;
+
+struct _GstErDtlsDec {
+    GstElement element;
+
+    GstPad *src;
+    GMutex src_mutex;
+
+    ErDtlsAgent *agent;
+    ErDtlsConnection *connection;
+    GMutex connection_mutex;
+    gchar *connection_id;
+    gchar *peer_pem;
+
+    GstBuffer *decoder_key;
+    guint srtp_cipher;
+    guint srtp_auth;
+};
+
+struct _GstErDtlsDecClass {
+    GstElementClass parent_class;
+};
+
+GType gst_er_dtls_dec_get_type(void);
+
+gboolean gst_er_dtls_dec_plugin_init(GstPlugin *);
+
+ErDtlsConnection *gst_er_dtls_dec_fetch_connection(gchar *id);
+
+G_END_DECLS
+
+#endif /* gstdtlsdec_h */
diff --git a/ext/dtls/gstdtlsenc.c b/ext/dtls/gstdtlsenc.c
new file mode 100644 (file)
index 0000000..9dadab1
--- /dev/null
@@ -0,0 +1,514 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdtlsenc.h"
+
+#include "gstdtlsdec.h"
+
+static GstStaticPadTemplate sink_template =
+    GST_STATIC_PAD_TEMPLATE("sink",
+        GST_PAD_SINK,
+        GST_PAD_REQUEST,
+        GST_STATIC_CAPS_ANY
+    );
+
+static GstStaticPadTemplate src_template =
+    GST_STATIC_PAD_TEMPLATE("src",
+        GST_PAD_SRC,
+        GST_PAD_ALWAYS,
+        GST_STATIC_CAPS("application/x-dtls")
+    );
+
+GST_DEBUG_CATEGORY_STATIC(er_dtls_enc_debug);
+#define GST_CAT_DEFAULT er_dtls_enc_debug
+
+#define gst_er_dtls_enc_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE(GstErDtlsEnc, gst_er_dtls_enc, GST_TYPE_ELEMENT,
+    GST_DEBUG_CATEGORY_INIT(er_dtls_enc_debug, "erdtlsenc", 0, "Ericsson DTLS Encoder"));
+
+enum {
+    SIGNAL_ON_KEY_RECEIVED,
+    NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS];
+
+enum {
+    PROP_0,
+    PROP_CONNECTION_ID,
+    PROP_IS_CLIENT,
+
+    PROP_ENCODER_KEY,
+    PROP_SRTP_CIPHER,
+    PROP_SRTP_AUTH,
+    NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+#define DEFAULT_CONNECTION_ID NULL
+#define DEFAULT_IS_CLIENT FALSE
+
+#define DEFAULT_ENCODER_KEY NULL
+#define DEFAULT_SRTP_CIPHER 0
+#define DEFAULT_SRTP_AUTH 0
+
+#define INITIAL_QUEUE_SIZE 64
+
+static void gst_er_dtls_enc_finalize(GObject *);
+static void gst_er_dtls_enc_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *);
+static void gst_er_dtls_enc_get_property(GObject *, guint prop_id, GValue *, GParamSpec *);
+
+static GstStateChangeReturn gst_er_dtls_enc_change_state(GstElement *, GstStateChange);
+static GstPad *gst_er_dtls_enc_request_new_pad(GstElement *, GstPadTemplate *, const gchar *name, const GstCaps *);
+
+static gboolean src_activate_mode(GstPad *, GstObject *, GstPadMode, gboolean active);
+static void src_task_loop(GstPad *);
+
+static GstFlowReturn sink_chain(GstPad *, GstObject *, GstBuffer *);
+
+static void on_key_received(ErDtlsConnection *, gpointer key, guint cipher, guint auth, GstErDtlsEnc *);
+static void on_send_data(ErDtlsConnection *, gconstpointer data, gint length, GstErDtlsEnc *);
+
+static void gst_er_dtls_enc_class_init(GstErDtlsEncClass *klass)
+{
+    GObjectClass *gobject_class;
+    GstElementClass *element_class;
+
+    gobject_class = (GObjectClass *) klass;
+    element_class = (GstElementClass *) klass;
+
+    gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_er_dtls_enc_finalize);
+    gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_er_dtls_enc_set_property);
+    gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_er_dtls_enc_get_property);
+
+    element_class->change_state = GST_DEBUG_FUNCPTR(gst_er_dtls_enc_change_state);
+    element_class->request_new_pad = GST_DEBUG_FUNCPTR(gst_er_dtls_enc_request_new_pad);
+
+    signals[SIGNAL_ON_KEY_RECEIVED] =
+        g_signal_new("on-key-received", G_TYPE_FROM_CLASS(klass),
+            G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+            g_cclosure_marshal_generic, G_TYPE_NONE, 0);
+
+    properties[PROP_CONNECTION_ID] =
+        g_param_spec_string("connection-id",
+            "Connection id",
+            "Every encoder/decoder pair should have the same, unique, connection-id",
+            DEFAULT_CONNECTION_ID,
+            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_IS_CLIENT] =
+        g_param_spec_boolean("is-client",
+            "Is client",
+            "Set to true if the decoder should act as"
+            "client and initiate the handshake",
+            DEFAULT_IS_CLIENT,
+            GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_ENCODER_KEY] =
+        g_param_spec_boxed("encoder-key",
+            "Encoder key",
+            "Master key that should be used by the SRTP encoder",
+            GST_TYPE_BUFFER,
+            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_SRTP_CIPHER] =
+        g_param_spec_uint("srtp-cipher",
+            "SRTP cipher",
+            "The SRTP cipher selected in the DTLS handshake. "
+            "The value will be set to an ErDtlsSrtpCipher.",
+            0, ER_DTLS_SRTP_CIPHER_AES_128_ICM, DEFAULT_SRTP_CIPHER,
+            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_SRTP_AUTH] =
+        g_param_spec_uint("srtp-auth",
+            "SRTP authentication",
+            "The SRTP authentication selected in the DTLS handshake. "
+            "The value will be set to an ErDtlsSrtpAuth.",
+            0, ER_DTLS_SRTP_AUTH_HMAC_SHA1_80, DEFAULT_SRTP_AUTH,
+            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
+
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&src_template));
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&sink_template));
+
+    gst_element_class_set_static_metadata(element_class,
+        "DTLS Encoder",
+        "Encoder/Network/DTLS",
+        "Encodes packets with DTLS",
+        "Patrik Oldsberg patrik.oldsberg@ericsson.com");
+}
+
+static void gst_er_dtls_enc_init(GstErDtlsEnc *self)
+{
+    self->connection_id = NULL;
+    self->connection = NULL;
+
+    self->is_client = DEFAULT_IS_CLIENT;
+
+    self->encoder_key = NULL;
+    self->srtp_cipher = DEFAULT_SRTP_CIPHER;
+    self->srtp_auth = DEFAULT_SRTP_AUTH;
+
+    self->queue = g_ptr_array_sized_new(INITIAL_QUEUE_SIZE);
+
+    g_mutex_init(&self->queue_lock);
+    g_cond_init(&self->queue_cond_add);
+
+    self->src = gst_pad_new_from_static_template(&src_template, "src");
+    g_return_if_fail(self->src);
+
+    gst_pad_set_activatemode_function(self->src, GST_DEBUG_FUNCPTR(src_activate_mode));
+
+    gst_element_add_pad(GST_ELEMENT(self), self->src);
+}
+
+static void gst_er_dtls_enc_finalize(GObject *object)
+{
+    GstErDtlsEnc *self = GST_ER_DTLS_ENC(object);
+
+    if (self->encoder_key) {
+        gst_buffer_unref(self->encoder_key);
+        self->encoder_key = NULL;
+    }
+
+    g_mutex_lock(&self->queue_lock);
+
+    g_ptr_array_set_free_func(self->queue, (GDestroyNotify) gst_buffer_unref);
+    g_ptr_array_unref(self->queue);
+    self->queue = NULL;
+
+    g_mutex_unlock(&self->queue_lock);
+
+    g_mutex_clear(&self->queue_lock);
+    g_cond_clear(&self->queue_cond_add);
+
+    GST_LOG_OBJECT(self, "finalized");
+
+    G_OBJECT_CLASS(parent_class)->finalize(object);
+}
+
+static void gst_er_dtls_enc_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    GstErDtlsEnc *self = GST_ER_DTLS_ENC(object);
+
+    switch (prop_id) {
+    case PROP_CONNECTION_ID:
+        self->connection_id = g_value_dup_string(value);
+        break;
+    case PROP_IS_CLIENT:
+        self->is_client = g_value_get_boolean(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
+
+static void gst_er_dtls_enc_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    GstErDtlsEnc *self = GST_ER_DTLS_ENC(object);
+
+    switch (prop_id) {
+    case PROP_CONNECTION_ID:
+        g_value_set_string(value, self->connection_id);
+        break;
+    case PROP_IS_CLIENT:
+        g_value_set_boolean(value, self->is_client);
+        break;
+    case PROP_ENCODER_KEY:
+        g_value_set_boxed(value, self->encoder_key);
+        break;
+    case PROP_SRTP_CIPHER:
+        g_value_set_uint(value, self->srtp_cipher);
+        break;
+    case PROP_SRTP_AUTH:
+        g_value_set_uint(value, self->srtp_auth);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
+
+static GstStateChangeReturn gst_er_dtls_enc_change_state(GstElement *element, GstStateChange transition)
+{
+    GstErDtlsEnc *self = GST_ER_DTLS_ENC(element);
+    GstStateChangeReturn ret;
+
+    switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+        if (self->connection_id) {
+            self->connection = gst_er_dtls_dec_fetch_connection(self->connection_id);
+
+            if (!self->connection) {
+                GST_WARNING_OBJECT(self,
+                    "invalid connection id: '%s', connection not found or already in use",
+                    self->connection_id);
+                return GST_STATE_CHANGE_FAILURE;
+            }
+
+            g_signal_connect_object(self->connection,
+                "on-encoder-key", G_CALLBACK(on_key_received), self, 0);
+
+            er_dtls_connection_set_send_callback(self->connection,
+                g_cclosure_new(G_CALLBACK(on_send_data), self, NULL));
+        } else {
+            GST_WARNING_OBJECT(self, "trying to change state to ready without connection id");
+            return GST_STATE_CHANGE_FAILURE;
+        }
+        break;
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+        GST_DEBUG_OBJECT(self, "starting connection %s", self->connection_id);
+        er_dtls_connection_start(self->connection, self->is_client);
+
+        gst_pad_set_active(self->src, TRUE);
+        break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+        GST_DEBUG_OBJECT(self, "stopping connection %s", self->connection_id);
+        gst_pad_set_active(self->src, FALSE);
+
+        er_dtls_connection_stop(self->connection);
+        break;
+    case GST_STATE_CHANGE_READY_TO_NULL:
+        GST_DEBUG_OBJECT(self, "closing connection %s", self->connection_id);
+
+        if (self->connection) {
+            er_dtls_connection_close(self->connection);
+            er_dtls_connection_set_send_callback(self->connection, NULL);
+            g_object_unref(self->connection);
+            self->connection = NULL;
+        }
+        break;
+    default:
+        break;
+    }
+
+    ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition);
+
+    return ret;
+}
+
+static GstPad *gst_er_dtls_enc_request_new_pad(GstElement *element,
+    GstPadTemplate *templ, const gchar *name, const GstCaps *caps)
+{
+    GstPad *sink;
+    gboolean ret;
+
+    GST_DEBUG_OBJECT(element, "sink pad requested");
+
+    g_return_val_if_fail(templ->direction == GST_PAD_SINK, NULL);
+
+    sink = gst_pad_new_from_template(templ, name);
+    g_return_val_if_fail(sink, NULL);
+
+    if (caps) {
+        g_object_set(sink, "caps", caps, NULL);
+    }
+
+    gst_pad_set_chain_function(sink, GST_DEBUG_FUNCPTR(sink_chain));
+
+    ret = gst_pad_set_active(sink, TRUE);
+    g_warn_if_fail(ret);
+
+    gst_element_add_pad(element, sink);
+
+    return sink;
+}
+
+static gboolean src_activate_mode(GstPad *pad, GstObject *parent, GstPadMode mode, gboolean active)
+{
+    GstErDtlsEnc *self = GST_ER_DTLS_ENC (parent);
+    gboolean success = TRUE;
+    g_return_val_if_fail(mode == GST_PAD_MODE_PUSH, FALSE);
+
+    if (active) {
+        GST_DEBUG_OBJECT(self, "src pad activating in push mode");
+
+        self->send_initial_events = TRUE;
+        success = gst_pad_start_task(pad, (GstTaskFunction) src_task_loop, self->src, NULL);
+        if (!success) {
+            GST_WARNING_OBJECT(self, "failed to activate pad task");
+        }
+    } else {
+        GST_DEBUG_OBJECT(self, "deactivating src pad");
+
+        g_mutex_lock(&self->queue_lock);
+        GST_PAD_MODE(pad) = GST_PAD_MODE_NONE;
+        g_cond_signal(&self->queue_cond_add);
+        g_mutex_unlock(&self->queue_lock);
+        success = gst_pad_stop_task(pad);
+        if (!success) {
+            GST_WARNING_OBJECT(self, "failed to deactivate pad task");
+        }
+    }
+
+    return success;
+}
+
+static void src_task_loop(GstPad *pad)
+{
+    GstErDtlsEnc *self = GST_ER_DTLS_ENC(GST_PAD_PARENT(pad));
+    GstFlowReturn ret;
+    GstPad *peer;
+    gboolean peer_is_active;
+
+    GST_TRACE_OBJECT(self, "src loop: acquiring lock");
+    g_mutex_lock(&self->queue_lock);
+    GST_TRACE_OBJECT(self, "src loop: acquired lock");
+
+    if (!gst_pad_is_active(pad)) {
+        GST_LOG_OBJECT(self, "src task loop entered on inactive pad");
+        GST_TRACE_OBJECT(self, "src loop: releasing lock");
+        g_mutex_unlock(&self->queue_lock);
+        return;
+    }
+
+    while (!self->queue->len) {
+        GST_TRACE_OBJECT(self, "src loop: queue empty, waiting for add");
+        g_cond_wait(&self->queue_cond_add, &self->queue_lock);
+        GST_TRACE_OBJECT(self, "src loop: add signaled");
+
+        if (!gst_pad_is_active(pad)) {
+            GST_LOG_OBJECT(self, "pad inactive, task returning");
+            GST_TRACE_OBJECT(self, "src loop: releasing lock");
+            g_mutex_unlock(&self->queue_lock);
+            return;
+        }
+    }
+    GST_TRACE_OBJECT(self, "src loop: queue has element");
+
+    peer = gst_pad_get_peer(pad);
+    peer_is_active = gst_pad_is_active(peer);
+    gst_object_unref(peer);
+
+    if (peer_is_active) {
+        GstBuffer *buffer;
+        gboolean start_connection_timeout = FALSE;
+
+        if (self->send_initial_events) {
+          GstSegment segment;
+          gchar s_id[32];
+          GstCaps *caps;
+
+          g_snprintf (s_id, sizeof (s_id), "erdtlsenc-%08x", g_random_int ());
+          gst_pad_push_event (self->src, gst_event_new_stream_start (s_id));
+          caps = gst_caps_new_empty_simple ("application/x-dtls");
+          gst_pad_push_event (self->src, gst_event_new_caps (caps));
+          gst_caps_unref (caps);
+          gst_segment_init (&segment, GST_FORMAT_BYTES);
+          gst_pad_push_event (self->src, gst_event_new_segment (&segment));
+          self->send_initial_events = FALSE;
+          start_connection_timeout = TRUE;
+        }
+
+        buffer = g_ptr_array_remove_index(self->queue, 0);
+
+        GST_TRACE_OBJECT(self, "src loop: releasing lock");
+        g_mutex_unlock(&self->queue_lock);
+
+        ret = gst_pad_push(self->src, buffer);
+        if (start_connection_timeout)
+          er_dtls_connection_start_timeout (self->connection);
+
+        if (G_UNLIKELY(ret != GST_FLOW_OK)) {
+            GST_WARNING_OBJECT(self, "failed to push buffer on src pad: %s", gst_flow_get_name(ret));
+        }
+    } else {
+        g_warn_if_reached();
+        GST_TRACE_OBJECT(self, "src loop: releasing lock");
+        g_mutex_unlock(&self->queue_lock);
+    }
+}
+
+static GstFlowReturn sink_chain(GstPad *pad, GstObject *parent, GstBuffer *buffer)
+{
+    GstErDtlsEnc *self = GST_ER_DTLS_ENC(parent);
+    GstMapInfo map_info;
+    gint ret;
+
+    gst_buffer_map(buffer, &map_info, GST_MAP_READ);
+
+    if (map_info.size) {
+        ret = er_dtls_connection_send(self->connection, map_info.data, map_info.size);
+        if (ret != map_info.size) {
+            GST_WARNING_OBJECT(self, "error sending data: %d B were written, expected value was %zd B",
+                ret, map_info.size);
+        }
+    }
+
+    gst_buffer_unmap(buffer, &map_info);
+
+    gst_buffer_unref(buffer);
+
+    return GST_FLOW_OK;
+}
+
+static void on_key_received(ErDtlsConnection *connection, gpointer key, guint cipher, guint auth, GstErDtlsEnc *self)
+{
+    gpointer key_dup;
+    gchar *key_str;
+
+    g_return_if_fail(GST_IS_ER_DTLS_ENC(self));
+    g_return_if_fail(ER_IS_DTLS_CONNECTION(connection));
+
+    self->srtp_cipher = cipher;
+    self->srtp_auth = auth;
+
+    key_dup = g_memdup(key, ER_DTLS_SRTP_MASTER_KEY_LENGTH);
+    self->encoder_key = gst_buffer_new_wrapped(key_dup, ER_DTLS_SRTP_MASTER_KEY_LENGTH);
+
+    key_str = g_base64_encode(key, ER_DTLS_SRTP_MASTER_KEY_LENGTH);
+    GST_INFO_OBJECT(self, "received key: %s", key_str);
+    g_free(key_str);
+
+    g_signal_emit(self, signals[SIGNAL_ON_KEY_RECEIVED], 0);
+}
+
+static void on_send_data(ErDtlsConnection *connection, gconstpointer data, gint length, GstErDtlsEnc *self)
+{
+    GstBuffer *buffer;
+
+    GST_DEBUG_OBJECT(self, "sending data from %s with length %d", self->connection_id, length);
+
+    buffer = gst_buffer_new_wrapped(g_memdup(data, length), length);
+
+    GST_TRACE_OBJECT(self, "send data: acquiring lock");
+    g_mutex_lock(&self->queue_lock);
+    GST_TRACE_OBJECT(self, "send data: acquired lock");
+
+    g_ptr_array_add(self->queue, buffer);
+
+    GST_TRACE_OBJECT(self, "send data: signaling add");
+    g_cond_signal(&self->queue_cond_add);
+
+    GST_TRACE_OBJECT(self, "send data: releasing lock");
+    g_mutex_unlock(&self->queue_lock);
+}
diff --git a/ext/dtls/gstdtlsenc.h b/ext/dtls/gstdtlsenc.h
new file mode 100644 (file)
index 0000000..f6218c1
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifndef gstdtlsenc_h
+#define gstdtlsenc_h
+
+#include "gstdtlsagent.h"
+#include "gstdtlsconnection.h"
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_ER_DTLS_ENC (gst_er_dtls_enc_get_type())
+#define GST_ER_DTLS_ENC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_ER_DTLS_ENC, GstErDtlsEnc))
+#define GST_ER_DTLS_ENC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_ER_DTLS_ENC, GstErDtlsEncClass))
+#define GST_IS_ER_DTLS_ENC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_ER_DTLS_ENC))
+#define GST_IS_ER_DTLS_ENC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_ER_DTLS_ENC))
+
+typedef struct _GstErDtlsEnc GstErDtlsEnc;
+typedef struct _GstErDtlsEncClass GstErDtlsEncClass;
+
+struct _GstErDtlsEnc {
+    GstElement element;
+
+    GstPad *src;
+
+    GPtrArray *queue;
+    GMutex queue_lock;
+    GCond queue_cond_add;
+
+    ErDtlsConnection *connection;
+    gchar *connection_id;
+
+    gboolean is_client;
+
+    GstBuffer *encoder_key;
+    guint srtp_cipher;
+    guint srtp_auth;
+
+    gboolean send_initial_events;
+};
+
+struct _GstErDtlsEncClass {
+    GstElementClass parent_class;
+};
+
+GType gst_er_dtls_enc_get_type(void);
+
+gboolean gst_er_dtls_enc_plugin_init(GstPlugin *);
+
+G_END_DECLS
+
+#endif /* gstdtlsenc_h */
diff --git a/ext/dtls/gstdtlssrtpbin.c b/ext/dtls/gstdtlssrtpbin.c
new file mode 100644 (file)
index 0000000..c477a62
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdtlssrtpbin.h"
+
+#define gst_er_dtls_srtp_bin_parent_class parent_class
+G_DEFINE_ABSTRACT_TYPE(GstErDtlsSrtpBin, gst_er_dtls_srtp_bin, GST_TYPE_BIN);
+
+enum {
+    PROP_0,
+    PROP_CONNECTION_ID,
+    PROP_KEY,
+    PROP_SRTP_AUTH,
+    PROP_SRTP_CIPHER,
+    PROP_SRTCP_AUTH,
+    PROP_SRTCP_CIPHER,
+    NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+#define DEFAULT_CONNECTION_ID NULL
+#define DEFAULT_KEY NULL
+#define DEFAULT_SRTP_AUTH NULL
+#define DEFAULT_SRTP_CIPHER NULL
+#define DEFAULT_SRTCP_AUTH NULL
+#define DEFAULT_SRTCP_CIPHER NULL
+
+static void gst_er_dtls_srtp_bin_finalize(GObject *);
+static void gst_er_dtls_srtp_bin_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *);
+static void gst_er_dtls_srtp_bin_get_property(GObject *, guint prop_id, GValue *, GParamSpec *);
+
+static void gst_er_dtls_srtp_bin_class_init(GstErDtlsSrtpBinClass *klass)
+{
+    GObjectClass *gobject_class;
+
+    gobject_class = (GObjectClass *) klass;
+
+    gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_bin_finalize);
+    gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_bin_set_property);
+    gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_bin_get_property);
+
+    klass->remove_dtls_element = NULL;
+
+    properties[PROP_CONNECTION_ID] =
+        g_param_spec_string("connection-id",
+            "Connection id",
+            "Every encoder/decoder pair should have the same, unique, connection-id",
+            DEFAULT_CONNECTION_ID,
+            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_KEY] =
+        g_param_spec_boxed("key",
+            "Key",
+            "SRTP master key, if this property is set, DTLS will be disabled",
+            GST_TYPE_BUFFER,
+            G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_SRTP_CIPHER] =
+        g_param_spec_string("srtp-cipher",
+            "SRTP Cipher",
+            "SRTP cipher name, should be 'null' or 'aes-128-icm', "
+            "if this property is set, DTLS will be disabled",
+            DEFAULT_SRTP_CIPHER,
+            G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_SRTCP_CIPHER] =
+        g_param_spec_string("srtcp-cipher",
+            "SRTCP Cipher",
+            "SRTCP cipher name, should be 'null' or 'aes-128-icm', "
+            "if this property is set, DTLS will be disabled",
+            DEFAULT_SRTCP_CIPHER,
+            G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_SRTP_AUTH] =
+        g_param_spec_string("srtp-auth",
+            "SRTP Auth",
+            "SRTP auth name, should be 'null', 'hmac-sha1-32' or 'hmac-sha1-80', "
+            "if this property is set, DTLS will be disabled",
+            DEFAULT_SRTP_AUTH,
+            G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_SRTCP_AUTH] =
+        g_param_spec_string("srtcp-auth",
+            "SRTCP Auth",
+            "SRTCP auth name, should be 'null', 'hmac-sha1-32' or 'hmac-sha1-80', "
+            "if this property is set, DTLS will be disabled",
+            DEFAULT_SRTCP_AUTH,
+            G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
+}
+
+static void gst_er_dtls_srtp_bin_init(GstErDtlsSrtpBin *self)
+{
+    self->key = NULL;
+    self->key_is_set = FALSE;
+    self->srtp_auth = NULL;
+    self->srtp_cipher = NULL;
+    self->srtcp_auth = NULL;
+    self->srtcp_cipher = NULL;
+}
+
+static void gst_er_dtls_srtp_bin_finalize(GObject *object)
+{
+    GstErDtlsSrtpBin *self = GST_ER_DTLS_SRTP_BIN(object);
+
+    if (self->key) {
+        gst_buffer_unref(self->key);
+        self->key = NULL;
+    }
+    g_free(self->srtp_auth);
+    self->srtp_auth = NULL;
+    g_free(self->srtp_cipher);
+    self->srtp_cipher = NULL;
+    g_free(self->srtcp_auth);
+    self->srtcp_auth = NULL;
+    g_free(self->srtcp_cipher);
+    self->srtcp_cipher = NULL;
+
+    G_OBJECT_CLASS(parent_class)->finalize(object);
+}
+
+static void gst_er_dtls_srtp_bin_set_property(GObject *object,
+    guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    GstErDtlsSrtpBin *self = GST_ER_DTLS_SRTP_BIN(object);
+    GstErDtlsSrtpBinClass *klass = GST_ER_DTLS_SRTP_BIN_GET_CLASS(self);
+
+    switch (prop_id) {
+    case PROP_CONNECTION_ID:
+        if (self->dtls_element) {
+            g_object_set_property(G_OBJECT(self->dtls_element), "connection-id", value);
+        } else {
+            g_warning("tried to set connection-id after disabling DTLS");
+        }
+        break;
+    case PROP_KEY:
+        if (self->key) {
+            gst_buffer_unref(self->key);
+        }
+        self->key = g_value_dup_boxed(value);
+        self->key_is_set = TRUE;
+        klass->remove_dtls_element(self);
+        break;
+    case PROP_SRTP_AUTH:
+        g_free(self->srtp_auth);
+        self->srtp_auth = g_value_dup_string(value);
+        klass->remove_dtls_element(self);
+        break;
+    case PROP_SRTP_CIPHER:
+        g_free(self->srtp_cipher);
+        self->srtp_cipher = g_value_dup_string(value);
+        klass->remove_dtls_element(self);
+        break;
+    case PROP_SRTCP_AUTH:
+        g_free(self->srtcp_auth);
+        self->srtcp_auth = g_value_dup_string(value);
+        klass->remove_dtls_element(self);
+        break;
+    case PROP_SRTCP_CIPHER:
+        g_free(self->srtcp_cipher);
+        self->srtcp_cipher = g_value_dup_string(value);
+        klass->remove_dtls_element(self);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
+
+static void gst_er_dtls_srtp_bin_get_property(GObject *object,
+    guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    GstErDtlsSrtpBin *self = GST_ER_DTLS_SRTP_BIN(object);
+
+    switch (prop_id) {
+    case PROP_CONNECTION_ID:
+        if (self->dtls_element) {
+            g_object_get_property(G_OBJECT(self->dtls_element), "connection-id", value);
+        } else {
+            g_warning("tried to get connection-id after disabling DTLS");
+        }
+        break;
+    case PROP_KEY:
+        if (self->key) {
+            g_value_set_boxed(value, self->key);
+        }
+        break;
+    case PROP_SRTP_AUTH:
+        g_value_set_string(value, self->srtp_auth);
+        break;
+    case PROP_SRTP_CIPHER:
+        g_value_set_string(value, self->srtp_cipher);
+        break;
+    case PROP_SRTCP_AUTH:
+        g_value_set_string(value, self->srtcp_auth);
+        break;
+    case PROP_SRTCP_CIPHER:
+        g_value_set_string(value, self->srtcp_cipher);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
diff --git a/ext/dtls/gstdtlssrtpbin.h b/ext/dtls/gstdtlssrtpbin.h
new file mode 100644 (file)
index 0000000..54aedc5
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifndef gstdtlssrtpbin_h
+#define gstdtlssrtpbin_h
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_ER_DTLS_SRTP_BIN (gst_er_dtls_srtp_bin_get_type())
+#define GST_ER_DTLS_SRTP_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_ER_DTLS_SRTP_BIN, GstErDtlsSrtpBin))
+#define GST_ER_DTLS_SRTP_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_ER_DTLS_SRTP_BIN, GstErDtlsSrtpBinClass))
+#define GST_ER_DTLS_SRTP_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_ER_DTLS_SRTP_BIN, GstErDtlsSrtpBinClass))
+#define GST_IS_ER_DTLS_SRTP_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_ER_DTLS_SRTP_BIN))
+#define GST_IS_ER_DTLS_SRTP_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_ER_DTLS_SRTP_BIN))
+
+typedef struct _GstErDtlsSrtpBin GstErDtlsSrtpBin;
+typedef struct _GstErDtlsSrtpBinClass GstErDtlsSrtpBinClass;
+
+struct _GstErDtlsSrtpBin {
+    GstBin bin;
+
+    GstElement *dtls_element;
+
+    gboolean key_is_set;
+    GstBuffer *key;
+    gchar *srtp_cipher;
+    gchar *srtp_auth;
+    gchar *srtcp_cipher;
+    gchar *srtcp_auth;
+};
+
+struct _GstErDtlsSrtpBinClass {
+    GstBinClass parent_class;
+
+    void (*remove_dtls_element)(GstErDtlsSrtpBin *);
+};
+
+GType gst_er_dtls_srtp_bin_get_type(void);
+
+G_END_DECLS
+
+#endif /* gstdtlssrtpbin_h */
diff --git a/ext/dtls/gstdtlssrtpdec.c b/ext/dtls/gstdtlssrtpdec.c
new file mode 100644 (file)
index 0000000..7d8b341
--- /dev/null
@@ -0,0 +1,453 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdtlssrtpdec.h"
+
+#include "gstdtlsconnection.h"
+
+static GstStaticPadTemplate sink_template =
+    GST_STATIC_PAD_TEMPLATE("sink",
+        GST_PAD_SINK,
+        GST_PAD_ALWAYS,
+        GST_STATIC_CAPS_ANY
+    );
+
+static GstStaticPadTemplate rtp_src_template =
+    GST_STATIC_PAD_TEMPLATE("rtp_src",
+        GST_PAD_SRC,
+        GST_PAD_ALWAYS,
+        GST_STATIC_CAPS("application/x-rtp;application/x-rtcp")
+    );
+
+static GstStaticPadTemplate data_src_template =
+    GST_STATIC_PAD_TEMPLATE("data_src",
+        GST_PAD_SRC,
+        GST_PAD_REQUEST,
+        GST_STATIC_CAPS_ANY
+    );
+
+GST_DEBUG_CATEGORY_STATIC(er_dtls_srtp_dec_debug);
+#define GST_CAT_DEFAULT er_dtls_srtp_dec_debug
+
+#define gst_er_dtls_srtp_dec_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE(GstErDtlsSrtpDec, gst_er_dtls_srtp_dec, GST_TYPE_ER_DTLS_SRTP_BIN,
+    GST_DEBUG_CATEGORY_INIT(er_dtls_srtp_dec_debug, "erdtlssrtpdec", 0, "Ericsson DTLS Decoder"));
+
+#define UNUSED(param) while (0) { (void)(param); }
+
+enum {
+    PROP_0,
+    PROP_PEM,
+    PROP_PEER_PEM,
+    NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+#define DEFAULT_PEM NULL
+#define DEFAULT_PEER_PEM NULL
+
+static void gst_er_dtls_srtp_dec_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *);
+static void gst_er_dtls_srtp_dec_get_property(GObject *, guint prop_id, GValue *, GParamSpec *);
+
+static GstPad *gst_er_dtls_srtp_dec_request_new_pad(GstElement *, GstPadTemplate *, const gchar *name, const GstCaps *);
+static GstCaps *on_decoder_request_key(GstElement *srtp_decoder, guint ssrc, GstErDtlsSrtpBin *);
+static void on_peer_pem(GstElement *srtp_decoder, GParamSpec *pspec, GstErDtlsSrtpDec *self);
+
+static void gst_er_dtls_srtp_dec_remove_dtls_element(GstErDtlsSrtpBin *);
+static GstPadProbeReturn remove_dtls_decoder_probe_callback(GstPad *, GstPadProbeInfo *, GstElement *);
+
+static GstPadProbeReturn drop_funnel_rtcp_caps(GstPad *, GstPadProbeInfo *, gpointer);
+
+static void gst_er_dtls_srtp_dec_class_init(GstErDtlsSrtpDecClass *klass)
+{
+    GObjectClass *gobject_class;
+    GstElementClass *element_class;
+    GstErDtlsSrtpBinClass *dtls_srtp_bin_class;
+
+    gobject_class = (GObjectClass *) klass;
+    element_class = (GstElementClass *) klass;
+    dtls_srtp_bin_class = (GstErDtlsSrtpBinClass *) klass;
+
+    gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_dec_set_property);
+    gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_dec_get_property);
+
+    element_class->request_new_pad = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_dec_request_new_pad);
+
+    dtls_srtp_bin_class->remove_dtls_element = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_dec_remove_dtls_element);
+
+    properties[PROP_PEM] =
+        g_param_spec_string("pem",
+            "PEM string",
+            "A string containing a X509 certificate and RSA private key in PEM format",
+            DEFAULT_PEM,
+            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    properties[PROP_PEER_PEM] =
+        g_param_spec_string("peer-pem",
+            "Peer PEM string",
+            "The X509 certificate received in the DTLS handshake, in PEM format",
+            DEFAULT_PEER_PEM,
+            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
+
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&sink_template));
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&rtp_src_template));
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&data_src_template));
+
+    gst_element_class_set_static_metadata(element_class,
+        "DTLS-SRTP Decoder",
+        "Decoder/Network/DTLS/SRTP",
+        "Decodes SRTP packets with a key received from DTLS",
+        "Patrik Oldsberg patrik.oldsberg@ericsson.com");
+}
+
+static void gst_er_dtls_srtp_dec_init(GstErDtlsSrtpDec *self)
+{
+    GstElementClass *klass = GST_ELEMENT_GET_CLASS(GST_ELEMENT(self));
+    GstPadTemplate *templ;
+    GstPad *target_pad, *ghost_pad, *pad;
+    gboolean ret;
+
+/*
+                                 +--------------------+
+            +--------------+  .-o|      erdtlsdec     |o-R----------data
+            |          dtls|o-'  +--------------------+
+    sink---o|  dtlsdemux   |
+            |       srt(c)p|o-.  +-----------+     +-----------+
+            +--------------+  '-o|srtp    rtp|o---o|rtp        |
+                                 |  srtpdec  |     |   funnel  |o---rt(c)p
+                                o|srtcp  rtcp|o---o|rtcp       |
+                                 +-----------+     +-----------+
+*/
+
+    self->srtp_dec = gst_element_factory_make("srtpdec", "srtp-decoder");
+    if (!self->srtp_dec) {
+        GST_ERROR_OBJECT(self, "failed to create srtp_dec, is the srtp plugin registered?");
+        return;
+    }
+    self->dtls_srtp_demux = gst_element_factory_make("erdtlssrtpdemux", "dtls-srtp-demux");
+    if (!self->dtls_srtp_demux) {
+        GST_ERROR_OBJECT(self, "failed to create dtls_srtp_demux");
+        return;
+    }
+    self->bin.dtls_element = gst_element_factory_make("erdtlsdec", "dtls-decoder");
+    if (!self->bin.dtls_element) {
+        GST_ERROR_OBJECT(self, "failed to create dtls_dec");
+        return;
+    }
+    self->funnel = gst_element_factory_make("funnel", "funnel");
+    if (!self->funnel) {
+        GST_ERROR_OBJECT(self, "failed to create funnel");
+        return;
+    }
+
+    gst_bin_add_many(GST_BIN(self),
+        self->dtls_srtp_demux,
+        self->bin.dtls_element,
+        self->srtp_dec,
+        self->funnel,
+        NULL);
+
+    ret = gst_element_link_pads(self->dtls_srtp_demux, "dtls_src", self->bin.dtls_element, NULL);
+    g_return_if_fail(ret);
+    ret = gst_element_link_pads(self->dtls_srtp_demux, "rtp_src", self->srtp_dec, "rtp_sink");
+    g_return_if_fail(ret);
+    ret = gst_element_link_pads(self->srtp_dec, "rtp_src", self->funnel, "sink_0");
+    g_return_if_fail(ret);
+    ret = gst_element_link_pads(self->srtp_dec, "rtcp_src", self->funnel, "sink_1");
+    g_return_if_fail(ret);
+
+    pad = gst_element_get_static_pad(self->funnel, "sink_1");
+    gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, drop_funnel_rtcp_caps, NULL, NULL);
+    gst_object_unref(pad);
+
+    templ = gst_element_class_get_pad_template(klass, "rtp_src");
+    target_pad = gst_element_get_static_pad(self->funnel, "src");
+    ghost_pad = gst_ghost_pad_new_from_template("rtp_src", target_pad, templ);
+    gst_object_unref(target_pad);
+    g_return_if_fail(ghost_pad);
+
+    ret = gst_element_add_pad(GST_ELEMENT(self), ghost_pad);
+    g_return_if_fail(ret);
+
+    templ = gst_element_class_get_pad_template(klass, "sink");
+    target_pad = gst_element_get_static_pad(self->dtls_srtp_demux, "sink");
+    ghost_pad = gst_ghost_pad_new_from_template("sink", target_pad, templ);
+    gst_object_unref(target_pad);
+    g_return_if_fail(ghost_pad);
+
+    ret = gst_element_add_pad(GST_ELEMENT(self), ghost_pad);
+    g_return_if_fail(ret);
+
+    g_signal_connect(self->srtp_dec, "request-key", G_CALLBACK(on_decoder_request_key), self);
+    g_signal_connect(self->bin.dtls_element, "notify::peer-pem", G_CALLBACK(on_peer_pem), self);
+}
+
+static void gst_er_dtls_srtp_dec_set_property(GObject *object,
+    guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    GstErDtlsSrtpDec *self = GST_ER_DTLS_SRTP_DEC(object);
+
+    switch (prop_id) {
+    case PROP_PEM:
+        if (self->bin.dtls_element) {
+            g_object_set_property(G_OBJECT(self->bin.dtls_element), "pem", value);
+        } else {
+            GST_WARNING_OBJECT(self, "tried to set pem after disabling DTLS");
+        }
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
+
+static void gst_er_dtls_srtp_dec_get_property(GObject *object,
+    guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    GstErDtlsSrtpDec *self = GST_ER_DTLS_SRTP_DEC(object);
+
+    switch (prop_id) {
+    case PROP_PEM:
+        if (self->bin.dtls_element) {
+            g_object_get_property(G_OBJECT(self->bin.dtls_element), "pem", value);
+        } else {
+            GST_WARNING_OBJECT(self, "tried to get pem after disabling DTLS");
+        }
+        break;
+    case PROP_PEER_PEM:
+        if (self->bin.dtls_element) {
+            g_object_get_property(G_OBJECT(self->bin.dtls_element), "peer-pem", value);
+        } else {
+            GST_WARNING_OBJECT(self, "tried to get peer-pem after disabling DTLS");
+        }
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
+
+static GstPad *gst_er_dtls_srtp_dec_request_new_pad(GstElement *element,
+    GstPadTemplate *templ, const gchar *name, const GstCaps *caps)
+{
+    GstErDtlsSrtpDec *self = GST_ER_DTLS_SRTP_DEC(element);
+    GstElementClass *klass = GST_ELEMENT_GET_CLASS(element);
+    GstPad *ghost_pad = NULL;
+    gboolean ret;
+
+    GST_DEBUG_OBJECT(element, "pad requested");
+
+    g_return_val_if_fail(self->bin.dtls_element, NULL);
+    g_return_val_if_fail(!self->bin.key_is_set, NULL);
+
+    if (templ == gst_element_class_get_pad_template(klass, "data_src")) {
+        GstPad *target_pad;
+
+        target_pad = gst_element_get_request_pad(self->bin.dtls_element, "src");
+
+        ghost_pad = gst_ghost_pad_new_from_template(name, target_pad, templ);
+        gst_object_unref(target_pad);
+        g_return_val_if_fail(ghost_pad, NULL);
+
+        ret = gst_pad_set_active(ghost_pad, TRUE);
+        g_return_val_if_fail(ret, NULL);
+        ret = gst_element_add_pad(element, ghost_pad);
+        g_return_val_if_fail(ret, NULL);
+
+        GST_LOG_OBJECT(self, "added data src pad");
+
+        if (caps) {
+            g_object_set(ghost_pad, "caps", caps, NULL);
+        }
+
+        return ghost_pad;
+    }
+
+    g_return_val_if_reached(NULL);
+}
+
+static GstCaps *on_decoder_request_key(GstElement *srtp_decoder,
+    guint ssrc, GstErDtlsSrtpBin *bin)
+{
+    GstCaps *key_caps;
+    GstBuffer *key_buffer = NULL;
+    guint cipher;
+    guint auth;
+
+    if (bin->key_is_set) {
+        if (bin->key) {
+            if (bin->srtp_cipher && bin->srtp_auth && bin->srtcp_cipher && bin->srtcp_auth) {
+                GST_DEBUG_OBJECT(bin, "setting srtp key");
+                return gst_caps_new_simple("application/x-srtp",
+                    "srtp-key", GST_TYPE_BUFFER, gst_buffer_copy(bin->key),
+                    "srtp-auth", G_TYPE_STRING, bin->srtp_auth,
+                    "srtcp-auth", G_TYPE_STRING, bin->srtcp_auth,
+                    "srtp-cipher", G_TYPE_STRING, bin->srtp_cipher,
+                    "srtcp-cipher", G_TYPE_STRING, bin->srtcp_cipher,
+                    NULL);
+            } else {
+                GST_WARNING_OBJECT(bin, "srtp key is set but not all ciphers and auths");
+                return NULL;
+            }
+        }
+
+        GST_DEBUG_OBJECT(bin, "setting srtp key to null");
+        return gst_caps_new_simple("application/x-srtp",
+            "srtp-key", GST_TYPE_BUFFER, NULL,
+            "srtp-auth", G_TYPE_STRING, "null",
+            "srtcp-auth", G_TYPE_STRING, "null",
+            "srtp-cipher", G_TYPE_STRING, "null",
+            "srtcp-cipher", G_TYPE_STRING, "null",
+            NULL);
+    }
+
+    if (bin->dtls_element) {
+        g_object_get(bin->dtls_element,
+            "decoder-key", &key_buffer,
+            NULL);
+    }
+
+    if (key_buffer) {
+        g_object_get(bin->dtls_element,
+            "srtp-cipher", &cipher,
+            "srtp-auth", &auth,
+            NULL);
+
+        g_return_val_if_fail(cipher == ER_DTLS_SRTP_CIPHER_AES_128_ICM, NULL);
+
+        key_caps = gst_caps_new_simple("application/x-srtp",
+            "srtp-key", GST_TYPE_BUFFER, key_buffer,
+            "srtp-cipher", G_TYPE_STRING, "aes-128-icm",
+            "srtcp-cipher", G_TYPE_STRING, "aes-128-icm",
+            NULL);
+
+        switch (auth) {
+        case ER_DTLS_SRTP_AUTH_HMAC_SHA1_32:
+            gst_caps_set_simple(key_caps,
+                "srtp-auth", G_TYPE_STRING, "hmac-sha1-32",
+                "srtcp-auth", G_TYPE_STRING, "hmac-sha1-32",
+                NULL);
+            break;
+        case ER_DTLS_SRTP_AUTH_HMAC_SHA1_80:
+            gst_caps_set_simple(key_caps,
+                "srtp-auth", G_TYPE_STRING, "hmac-sha1-80",
+                "srtcp-auth", G_TYPE_STRING, "hmac-sha1-80",
+                NULL);
+            break;
+        default:
+            g_return_val_if_reached(NULL);
+            break;
+        }
+
+        return key_caps;
+    }
+
+    return NULL;
+}
+
+static void on_peer_pem(GstElement *srtp_decoder, GParamSpec *pspec, GstErDtlsSrtpDec *self)
+{
+    UNUSED(srtp_decoder);
+    UNUSED(pspec);
+    g_return_if_fail(self);
+    g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_PEER_PEM]);
+}
+
+static void gst_er_dtls_srtp_dec_remove_dtls_element(GstErDtlsSrtpBin *bin)
+{
+    GstErDtlsSrtpDec *self = GST_ER_DTLS_SRTP_DEC(bin);
+    GstPad *demux_pad;
+    gulong id;
+
+    if (!bin->dtls_element) {
+        return;
+    }
+
+    demux_pad = gst_element_get_static_pad(self->dtls_srtp_demux, "dtls_src");
+
+    id = gst_pad_add_probe(demux_pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
+        (GstPadProbeCallback) remove_dtls_decoder_probe_callback, bin->dtls_element, NULL);
+    g_return_if_fail(id);
+    bin->dtls_element = NULL;
+
+    gst_pad_push_event(demux_pad, gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM, gst_structure_new_empty("dummy")));
+
+    gst_object_unref(demux_pad);
+}
+
+static GstPadProbeReturn remove_dtls_decoder_probe_callback(GstPad *pad,
+    GstPadProbeInfo *info, GstElement *element)
+{
+    gst_pad_remove_probe(pad, GST_PAD_PROBE_INFO_ID(info));
+
+    gst_element_set_state(GST_ELEMENT(element), GST_STATE_NULL);
+    gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element);
+
+    return GST_PAD_PROBE_OK;
+}
+
+static GstPadProbeReturn drop_funnel_rtcp_caps(GstPad *pad, GstPadProbeInfo *info, gpointer data)
+{
+    /* FIXME: This is needed for setting the proper caps until
+     * GStreamer supports MIXED caps or another mechanism to
+     * prevent renegotiation all the time when two different caps
+     * are going over the same pad
+     */
+    if (GST_EVENT_TYPE (info->data) == GST_EVENT_CAPS) {
+      GstCaps *caps, *peercaps;
+      GstStructure *s;
+
+      gst_event_parse_caps (GST_EVENT (info->data), &caps);
+      s = gst_caps_get_structure (caps, 0);
+      if (gst_structure_has_name (s, "application/x-rtcp")) {
+        peercaps = gst_pad_query_caps (pad, NULL);
+
+        /* If the peer does not accept RTCP, we are linked to
+         * the RTP sinkpad of rtpbin. In that case we have to
+         * drop the RTCP caps and assume that we sent RTP caps
+         * before here, which is very likely but not guaranteed
+         * if for some reason we receive RTCP before any RTP.
+         * In that unlikely case we will get event misordering
+         * warnings later, instead of getting them always as
+         * happens now.
+         */
+        if (peercaps && !gst_caps_is_subset (caps, peercaps)) {
+          gst_caps_unref (peercaps);
+          return GST_PAD_PROBE_DROP;
+        }
+        gst_caps_replace (&peercaps, NULL);
+      }
+    }
+
+    return GST_PAD_PROBE_OK;
+}
diff --git a/ext/dtls/gstdtlssrtpdec.h b/ext/dtls/gstdtlssrtpdec.h
new file mode 100644 (file)
index 0000000..bb7c25c
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifndef gstdtlssrtpdec_h
+#define gstdtlssrtpdec_h
+
+#include "gstdtlssrtpbin.h"
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_ER_DTLS_SRTP_DEC (gst_er_dtls_srtp_dec_get_type())
+#define GST_ER_DTLS_SRTP_DEC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_ER_DTLS_SRTP_DEC, GstErDtlsSrtpDec))
+#define GST_ER_DTLS_SRTP_DEC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_ER_DTLS_SRTP_DEC, GstErDtlsSrtpDecClass))
+#define GST_IS_ER_DTLS_SRTP_DEC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_ER_DTLS_SRTP_DEC))
+#define GST_IS_ER_DTLS_SRTP_DEC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_ER_DTLS_SRTP_DEC))
+
+typedef struct _GstErDtlsSrtpDec GstErDtlsSrtpDec;
+typedef struct _GstErDtlsSrtpDecClass GstErDtlsSrtpDecClass;
+
+struct _GstErDtlsSrtpDec {
+    GstErDtlsSrtpBin bin;
+
+    GstElement *dtls_srtp_demux;
+    GstElement *srtp_dec;
+    GstElement *funnel;
+};
+
+struct _GstErDtlsSrtpDecClass {
+    GstErDtlsSrtpBinClass parent_class;
+};
+
+GType gst_er_dtls_srtp_dec_get_type(void);
+
+gboolean gst_er_dtls_srtp_dec_plugin_init(GstPlugin *);
+
+G_END_DECLS
+
+#endif /* gstdtlssrtpdec_h */
diff --git a/ext/dtls/gstdtlssrtpdemux.c b/ext/dtls/gstdtlssrtpdemux.c
new file mode 100644 (file)
index 0000000..b4854de
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdtlssrtpdemux.h"
+
+#define PACKET_IS_DTLS(b) (b > 0x13 && b < 0x40)
+#define PACKET_IS_RTP(b) (b > 0x7f && b < 0xc0)
+
+static GstStaticPadTemplate sink_template =
+    GST_STATIC_PAD_TEMPLATE("sink",
+        GST_PAD_SINK,
+        GST_PAD_ALWAYS,
+        GST_STATIC_CAPS_ANY
+    );
+
+static GstStaticPadTemplate rtp_src_template =
+    GST_STATIC_PAD_TEMPLATE("rtp_src",
+        GST_PAD_SRC,
+        GST_PAD_ALWAYS,
+        GST_STATIC_CAPS(
+            "application/x-rtp;"
+            "application/x-rtcp;"
+            "application/x-srtp;"
+            "application/x-srtcp")
+    );
+
+static GstStaticPadTemplate dtls_src_template =
+    GST_STATIC_PAD_TEMPLATE("dtls_src",
+        GST_PAD_SRC,
+        GST_PAD_ALWAYS,
+        GST_STATIC_CAPS("application/x-dtls")
+    );
+
+GST_DEBUG_CATEGORY_STATIC(er_er_dtls_srtp_demux_debug);
+#define GST_CAT_DEFAULT er_er_dtls_srtp_demux_debug
+
+#define gst_er_dtls_srtp_demux_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE(GstErDtlsSrtpDemux, gst_er_dtls_srtp_demux, GST_TYPE_ELEMENT,
+    GST_DEBUG_CATEGORY_INIT(er_er_dtls_srtp_demux_debug, "erdtlssrtpdemux", 0, "Ericsson DTLS SRTP Demultiplexer"));
+
+static GstFlowReturn sink_chain(GstPad *, GstObject *self, GstBuffer *);
+
+static void gst_er_dtls_srtp_demux_class_init(GstErDtlsSrtpDemuxClass *klass)
+{
+    GstElementClass *element_class;
+
+    element_class = (GstElementClass *) klass;
+
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&sink_template));
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&rtp_src_template));
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&dtls_src_template));
+
+    gst_element_class_set_static_metadata(element_class,
+        "DTLS SRTP Demultiplexer",
+        "DTLS/SRTP/Demux",
+        "Demultiplexes DTLS and SRTP packets",
+        "Patrik Oldsberg patrik.oldsberg@ericsson.com");
+}
+
+static void gst_er_dtls_srtp_demux_init(GstErDtlsSrtpDemux *self)
+{
+    GstPad *sink;
+
+    sink = gst_pad_new_from_static_template(&sink_template, "sink");
+    self->rtp_src = gst_pad_new_from_static_template(&rtp_src_template, "rtp_src");
+    self->dtls_src = gst_pad_new_from_static_template(&dtls_src_template, "dtls_src");
+    g_return_if_fail(sink);
+    g_return_if_fail(self->rtp_src);
+    g_return_if_fail(self->dtls_src);
+
+    gst_pad_set_chain_function(sink, GST_DEBUG_FUNCPTR(sink_chain));
+
+    gst_element_add_pad(GST_ELEMENT(self), sink);
+    gst_element_add_pad(GST_ELEMENT(self), self->rtp_src);
+    gst_element_add_pad(GST_ELEMENT(self), self->dtls_src);
+}
+
+static GstFlowReturn sink_chain(GstPad *pad, GstObject *parent, GstBuffer *buffer)
+{
+    GstErDtlsSrtpDemux *self = GST_ER_DTLS_SRTP_DEMUX(parent);
+    guint8 first_byte;
+
+    if (gst_buffer_get_size(buffer) == 0) {
+        GST_LOG_OBJECT(self, "received buffer with size 0");
+        gst_buffer_unref(buffer);
+        return GST_FLOW_OK;
+    }
+
+    if (gst_buffer_extract(buffer, 0, &first_byte, 1) != 1) {
+        GST_WARNING_OBJECT(self, "could not extract first byte from buffer");
+        gst_buffer_unref(buffer);
+        return GST_FLOW_OK;
+    }
+
+    if (PACKET_IS_DTLS(first_byte)) {
+        GST_LOG_OBJECT(self, "pushing dtls packet");
+
+        return gst_pad_push(self->dtls_src, buffer);
+    }
+
+    if (PACKET_IS_RTP(first_byte)) {
+        GST_LOG_OBJECT(self, "pushing rtp packet");
+
+        return gst_pad_push(self->rtp_src, buffer);
+    }
+
+    GST_WARNING_OBJECT(self, "received invalid buffer: %x", first_byte);
+    gst_buffer_unref(buffer);
+    return GST_FLOW_OK;
+}
diff --git a/ext/dtls/gstdtlssrtpdemux.h b/ext/dtls/gstdtlssrtpdemux.h
new file mode 100644 (file)
index 0000000..166c8e9
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifndef gstdtlssrtpdemux_h
+#define gstdtlssrtpdemux_h
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_ER_DTLS_SRTP_DEMUX \
+    (gst_er_dtls_srtp_demux_get_type())
+#define GST_ER_DTLS_SRTP_DEMUX(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_ER_DTLS_SRTP_DEMUX, GstErDtlsSrtpDemux))
+#define GST_ER_DTLS_SRTP_DEMUX_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_ER_DTLS_SRTP_DEMUX, GstErDtlsSrtpDemuxClass))
+#define GST_IS_ER_DTLS_SRTP_DEMUX(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_ER_DTLS_SRTP_DEMUX))
+#define GST_IS_ER_DTLS_SRTP_DEMUX_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_ER_DTLS_SRTP_DEMUX))
+
+typedef struct _GstErDtlsSrtpDemux GstErDtlsSrtpDemux;
+typedef struct _GstErDtlsSrtpDemuxClass GstErDtlsSrtpDemuxClass;
+
+struct _GstErDtlsSrtpDemux {
+    GstElement element;
+
+    GstPad *rtp_src;
+    GstPad *dtls_src;
+};
+
+struct _GstErDtlsSrtpDemuxClass {
+    GstElementClass parent_class;
+};
+
+GType gst_er_dtls_srtp_demux_get_type(void);
+
+gboolean gst_er_dtls_srtp_demux_plugin_init(GstPlugin *);
+
+G_END_DECLS
+
+#endif /* gstdtlssrtpdemux_h */
diff --git a/ext/dtls/gstdtlssrtpenc.c b/ext/dtls/gstdtlssrtpenc.c
new file mode 100644 (file)
index 0000000..34b45a1
--- /dev/null
@@ -0,0 +1,436 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdtlssrtpenc.h"
+
+#include <stdio.h>
+
+static GstStaticPadTemplate rtp_sink_template =
+    GST_STATIC_PAD_TEMPLATE("rtp_sink_%d",
+        GST_PAD_SINK,
+        GST_PAD_REQUEST,
+        GST_STATIC_CAPS("application/x-rtp;application/x-rtcp")
+    );
+
+static GstStaticPadTemplate rtcp_sink_template =
+    GST_STATIC_PAD_TEMPLATE("rtcp_sink_%d",
+        GST_PAD_SINK,
+        GST_PAD_REQUEST,
+        GST_STATIC_CAPS("application/x-rtp;application/x-rtcp")
+    );
+
+static GstStaticPadTemplate data_sink_template =
+    GST_STATIC_PAD_TEMPLATE("data_sink",
+        GST_PAD_SINK,
+        GST_PAD_REQUEST,
+        GST_STATIC_CAPS_ANY
+    );
+
+static GstStaticPadTemplate src_template =
+    GST_STATIC_PAD_TEMPLATE("src",
+        GST_PAD_SRC,
+        GST_PAD_ALWAYS,
+        GST_STATIC_CAPS_ANY
+    );
+
+GST_DEBUG_CATEGORY_STATIC(er_dtls_srtp_enc_debug);
+#define GST_CAT_DEFAULT er_dtls_srtp_enc_debug
+
+#define gst_er_dtls_srtp_enc_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE(GstErDtlsSrtpEnc, gst_er_dtls_srtp_enc, GST_TYPE_ER_DTLS_SRTP_BIN,
+    GST_DEBUG_CATEGORY_INIT(er_dtls_srtp_enc_debug, "erdtlssrtpenc", 0, "Ericsson DTLS Decoder"));
+
+enum {
+    SIGNAL_ON_KEY_SET,
+    NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS];
+
+enum {
+    PROP_0,
+    PROP_IS_CLIENT,
+    NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+#define DEFAULT_IS_CLIENT FALSE
+
+static gboolean transform_enum(GBinding *, const GValue *source_value, GValue *target_value, GEnumClass *);
+
+static void gst_er_dtls_srtp_enc_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *);
+static void gst_er_dtls_srtp_enc_get_property(GObject *, guint prop_id, GValue *, GParamSpec *);
+
+static GstPad *add_ghost_pad(GstElement *, const gchar *name, GstPad *, GstPadTemplate *);
+static GstPad *gst_er_dtls_srtp_enc_request_new_pad(GstElement *, GstPadTemplate *, const gchar *name, const GstCaps *);
+
+static void on_key_received(GObject *encoder, GstErDtlsSrtpEnc *);
+
+static void gst_er_dtls_srtp_enc_remove_dtls_element(GstErDtlsSrtpBin *);
+static GstPadProbeReturn remove_dtls_encoder_probe_callback(GstPad *, GstPadProbeInfo *, GstElement *);
+
+static void gst_er_dtls_srtp_enc_class_init(GstErDtlsSrtpEncClass *klass)
+{
+    GObjectClass *gobject_class;
+    GstElementClass *element_class;
+    GstErDtlsSrtpBinClass *dtls_srtp_bin_class;
+
+    gobject_class = (GObjectClass *) klass;
+    element_class = (GstElementClass *) klass;
+    dtls_srtp_bin_class = (GstErDtlsSrtpBinClass *) klass;
+
+    gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_enc_set_property);
+    gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_enc_get_property);
+
+    element_class->request_new_pad = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_enc_request_new_pad);
+
+    dtls_srtp_bin_class->remove_dtls_element = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_enc_remove_dtls_element);
+
+    signals[SIGNAL_ON_KEY_SET] =
+        g_signal_new("on-key-set", G_TYPE_FROM_CLASS(klass),
+            G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+            g_cclosure_marshal_generic, G_TYPE_NONE, 0);
+
+    properties[PROP_IS_CLIENT] =
+        g_param_spec_boolean("is-client",
+            "Is client",
+            "Set to true if the decoder should act as "
+            "client and initiate the handshake",
+            DEFAULT_IS_CLIENT,
+            GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
+
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&rtp_sink_template));
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&rtcp_sink_template));
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&data_sink_template));
+    gst_element_class_add_pad_template(element_class,
+        gst_static_pad_template_get(&src_template));
+
+    gst_element_class_set_static_metadata(element_class,
+        "DTLS-SRTP Encoder",
+        "Encoder/Network/DTLS/SRTP",
+        "Encodes SRTP packets with a key received from DTLS",
+        "Patrik Oldsberg patrik.oldsberg@ericsson.com");
+}
+
+static void gst_er_dtls_srtp_enc_init(GstErDtlsSrtpEnc *self)
+{
+    GstElementClass *klass = GST_ELEMENT_GET_CLASS(GST_ELEMENT(self));
+    static GEnumClass *cipher_enum_class, *auth_enum_class;
+    gboolean ret;
+
+/*
+                 +--------------------+     +-----------------+
+     rtp_sink-R-o|rtp_sink     rtp_src|o-R-o|                 |
+                 |       srtpenc      |     |                 |
+    rtcp_sink-R-o|srtcp_sink  rtcp_src|o-R-o|                 |
+                 +--------------------+     |     funnel      |o---src
+                                            |                 |
+                 +--------------------+     |                 |
+    data_sink-R-o|      erdtlsenc     |o---o|                 |
+                 +--------------------+     +-----------------+
+*/
+
+    self->srtp_enc = gst_element_factory_make("srtpenc", "srtp-encoder");
+    if (!self->srtp_enc) {
+        GST_ERROR_OBJECT(self, "failed to create srtp encoder, is the srtp plugin registered?");
+        return;
+    }
+    g_return_if_fail(self->srtp_enc);
+    self->bin.dtls_element = gst_element_factory_make("erdtlsenc", "dtls-encoder");
+    if (!self->bin.dtls_element) {
+        GST_ERROR_OBJECT(self, "failed to create dtls encoder");
+        return;
+    }
+    self->funnel = gst_element_factory_make("funnel", "funnel");
+    if (!self->funnel) {
+        GST_ERROR_OBJECT(self, "failed to create funnel");
+        return;
+    }
+
+    gst_bin_add_many(GST_BIN(self), self->bin.dtls_element, self->srtp_enc, self->funnel, NULL);
+
+    ret = gst_element_link(self->bin.dtls_element, self->funnel);
+    g_return_if_fail(ret);
+
+    add_ghost_pad(GST_ELEMENT(self), "src",
+        gst_element_get_static_pad(self->funnel, "src"),
+        gst_element_class_get_pad_template(klass, "src"));
+
+    g_signal_connect(self->bin.dtls_element, "on-key-received", G_CALLBACK(on_key_received), self);
+
+    if (g_once_init_enter(&cipher_enum_class)) {
+        GType type = g_type_from_name("GstSrtpCipherType");
+        g_assert(type);
+        g_once_init_leave(&cipher_enum_class, g_type_class_peek(type));
+    }
+    if (g_once_init_enter(&auth_enum_class)) {
+        GType type = g_type_from_name("GstSrtpAuthType");
+        g_assert(type);
+        g_once_init_leave(&auth_enum_class, g_type_class_peek(type));
+    }
+
+    g_object_set(self->srtp_enc, "random-key", TRUE, NULL);
+
+    g_object_bind_property(G_OBJECT(self), "key", self->srtp_enc, "key", G_BINDING_DEFAULT);
+    g_object_bind_property_full(G_OBJECT(self), "srtp-cipher", self->srtp_enc, "rtp-cipher", G_BINDING_DEFAULT,
+        (GBindingTransformFunc) transform_enum, NULL, cipher_enum_class, NULL);
+    g_object_bind_property_full(G_OBJECT(self), "srtcp-cipher", self->srtp_enc, "rtcp-cipher", G_BINDING_DEFAULT,
+        (GBindingTransformFunc) transform_enum, NULL, cipher_enum_class, NULL);
+    g_object_bind_property_full(G_OBJECT(self), "srtp-auth", self->srtp_enc, "rtp-auth", G_BINDING_DEFAULT,
+        (GBindingTransformFunc) transform_enum, NULL, auth_enum_class, NULL);
+    g_object_bind_property_full(G_OBJECT(self), "srtcp-auth", self->srtp_enc, "rtcp-auth", G_BINDING_DEFAULT,
+        (GBindingTransformFunc) transform_enum, NULL, auth_enum_class, NULL);
+}
+
+static gboolean transform_enum(GBinding *binding, const GValue *source_value, GValue *target_value, GEnumClass *enum_class)
+{
+    GEnumValue *enum_value;
+    const gchar *nick;
+
+    nick = g_value_get_string(source_value);
+    g_return_val_if_fail(nick, FALSE);
+
+    enum_value = g_enum_get_value_by_nick(enum_class, nick);
+    g_return_val_if_fail(enum_value, FALSE);
+
+    GST_DEBUG_OBJECT(g_binding_get_source(binding),
+        "transforming enum from %s to %d", nick, enum_value->value);
+
+    g_value_set_enum(target_value, enum_value->value);
+
+    return TRUE;
+}
+
+static void gst_er_dtls_srtp_enc_set_property(GObject *object,
+    guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    GstErDtlsSrtpEnc *self = GST_ER_DTLS_SRTP_ENC(object);
+
+    switch (prop_id) {
+    case PROP_IS_CLIENT:
+        if (self->bin.dtls_element) {
+            g_object_set_property(G_OBJECT(self->bin.dtls_element), "is-client", value);
+        } else {
+            GST_WARNING_OBJECT(self, "tried to set is-client after disabling DTLS");
+        }
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
+
+static void gst_er_dtls_srtp_enc_get_property(GObject *object,
+    guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    GstErDtlsSrtpEnc *self = GST_ER_DTLS_SRTP_ENC(object);
+
+    switch (prop_id) {
+    case PROP_IS_CLIENT:
+        if (self->bin.dtls_element) {
+            g_object_get_property(G_OBJECT(self->bin.dtls_element), "is-client", value);
+        } else {
+            GST_WARNING_OBJECT(self, "tried to get is-client after disabling DTLS");
+        }
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
+    }
+}
+
+static GstPad *add_ghost_pad(GstElement *element,
+    const gchar *name, GstPad *target, GstPadTemplate *templ)
+{
+    GstPad *pad;
+    gboolean ret;
+
+    pad = gst_ghost_pad_new_from_template(name, target, templ);
+    gst_object_unref(target);
+    target = NULL;
+
+    ret = gst_pad_set_active(pad, TRUE);
+    g_warn_if_fail(ret);
+
+    ret = gst_element_add_pad(element, pad);
+    g_warn_if_fail(ret);
+
+    return pad;
+}
+
+static GstPad *gst_er_dtls_srtp_enc_request_new_pad(GstElement *element,
+    GstPadTemplate *templ, const gchar *name, const GstCaps *caps)
+{
+    GstErDtlsSrtpEnc *self = GST_ER_DTLS_SRTP_ENC(element);
+    GstElementClass *klass = GST_ELEMENT_GET_CLASS(element);
+    GstPad *target_pad;
+    GstPad *ghost_pad = NULL;
+    guint pad_n;
+    gchar *srtp_src_name;
+
+    GST_DEBUG_OBJECT(element, "pad requested");
+
+    g_return_val_if_fail(templ->direction == GST_PAD_SINK, NULL);
+    g_return_val_if_fail(self->srtp_enc, NULL);
+
+    if (templ == gst_element_class_get_pad_template(klass, "rtp_sink_%d")) {
+        target_pad = gst_element_get_request_pad(self->srtp_enc, name);
+        g_return_val_if_fail(target_pad, NULL);
+
+        sscanf(GST_PAD_NAME(target_pad), "rtp_sink_%d", &pad_n);
+        srtp_src_name = g_strdup_printf("rtp_src_%d", pad_n);
+
+        gst_element_link_pads(self->srtp_enc, srtp_src_name, self->funnel, NULL);
+
+        g_free(srtp_src_name);
+
+        ghost_pad = add_ghost_pad(element, name, target_pad, templ);
+
+        GST_LOG_OBJECT(self, "added rtp sink pad");
+    } else if (templ == gst_element_class_get_pad_template(klass, "rtcp_sink_%d")) {
+        target_pad = gst_element_get_request_pad(self->srtp_enc, name);
+        g_return_val_if_fail(target_pad, NULL);
+
+        sscanf(GST_PAD_NAME(target_pad), "rtcp_sink_%d", &pad_n);
+        srtp_src_name = g_strdup_printf("rtcp_src_%d", pad_n);
+
+        gst_element_link_pads(self->srtp_enc, srtp_src_name, self->funnel, NULL);
+
+        g_free(srtp_src_name);
+
+        ghost_pad = add_ghost_pad(element, name, target_pad, templ);
+
+        GST_LOG_OBJECT(self, "added rtcp sink pad");
+    } else if (templ == gst_element_class_get_pad_template(klass, "data_sink")) {
+        g_return_val_if_fail(self->bin.dtls_element, NULL);
+        target_pad = gst_element_get_request_pad(self->bin.dtls_element, "sink");
+
+        ghost_pad = add_ghost_pad(element, name, target_pad, templ);
+
+        GST_LOG_OBJECT(self, "added data sink pad");
+    } else {
+        g_warn_if_reached();
+    }
+
+    if (caps && ghost_pad) {
+        g_object_set(ghost_pad, "caps", caps, NULL);
+    }
+
+    return ghost_pad;
+}
+
+static void on_key_received(GObject *encoder, GstErDtlsSrtpEnc *self)
+{
+    GstErDtlsSrtpBin *bin = GST_ER_DTLS_SRTP_BIN(self);
+    GstBuffer *buffer = NULL;
+    guint cipher, auth;
+
+    if (!(bin->key_is_set || bin->srtp_cipher || bin->srtp_auth
+        || bin->srtcp_cipher || bin->srtcp_auth)) {
+        g_object_get(encoder,
+            "encoder-key", &buffer,
+            "srtp-cipher", &cipher,
+            "srtp-auth", &auth,
+            NULL);
+
+        g_object_set(self->srtp_enc,
+            "rtp-cipher", cipher,
+            "rtcp-cipher", cipher,
+            "rtp-auth", auth,
+            "rtcp-auth", auth,
+            "key", buffer,
+            "random-key", FALSE,
+            NULL);
+
+        g_signal_emit(self, signals[SIGNAL_ON_KEY_SET], 0);
+    } else {
+        GST_DEBUG_OBJECT(self, "ignoring keys received from DTLS handshake, key struct is set");
+    }
+}
+
+static void gst_er_dtls_srtp_enc_remove_dtls_element(GstErDtlsSrtpBin *bin)
+{
+    GstErDtlsSrtpEnc *self = GST_ER_DTLS_SRTP_ENC(bin);
+    GstPad *dtls_sink_pad, *peer_pad;
+    gulong id;
+    guint rtp_cipher = 1, rtcp_cipher = 1, rtp_auth = 1, rtcp_auth = 1;
+
+    if (!bin->dtls_element) {
+        return;
+    }
+
+    g_object_get(self->srtp_enc,
+        "rtp-cipher", &rtp_cipher,
+        "rtcp-cipher", &rtcp_cipher,
+        "rtp-auth", &rtp_auth,
+        "rtcp-auth", &rtcp_auth,
+        NULL);
+
+    if (!rtp_cipher && !rtcp_cipher && !rtp_auth && !rtcp_auth) {
+        g_object_set(self->srtp_enc, "random-key", FALSE, NULL);
+    }
+
+    dtls_sink_pad = gst_element_get_static_pad(bin->dtls_element, "sink");
+
+    if (!dtls_sink_pad) {
+        gst_element_set_state(GST_ELEMENT(bin->dtls_element), GST_STATE_NULL);
+        gst_bin_remove(GST_BIN(self), bin->dtls_element);
+        bin->dtls_element = NULL;
+        return;
+    }
+
+    peer_pad = gst_pad_get_peer(dtls_sink_pad);
+    g_return_if_fail(peer_pad);
+    gst_object_unref(dtls_sink_pad);
+    dtls_sink_pad = NULL;
+
+    id = gst_pad_add_probe(peer_pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
+        (GstPadProbeCallback) remove_dtls_encoder_probe_callback, bin->dtls_element, NULL);
+    g_return_if_fail(id);
+    bin->dtls_element = NULL;
+
+    gst_pad_push_event(peer_pad, gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM, gst_structure_new_empty("dummy")));
+
+    gst_object_unref(peer_pad);
+}
+
+static GstPadProbeReturn remove_dtls_encoder_probe_callback(GstPad *pad,
+    GstPadProbeInfo *info, GstElement *element)
+{
+    gst_pad_remove_probe(pad, GST_PAD_PROBE_INFO_ID(info));
+
+    gst_element_set_state(GST_ELEMENT(element), GST_STATE_NULL);
+    gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element);
+
+    return GST_PAD_PROBE_OK;
+}
diff --git a/ext/dtls/gstdtlssrtpenc.h b/ext/dtls/gstdtlssrtpenc.h
new file mode 100644 (file)
index 0000000..c61a6a4
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifndef gstdtlssrtpenc_h
+#define gstdtlssrtpenc_h
+
+#include "gstdtlssrtpbin.h"
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_ER_DTLS_SRTP_ENC (gst_er_dtls_srtp_enc_get_type())
+#define GST_ER_DTLS_SRTP_ENC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_ER_DTLS_SRTP_ENC, GstErDtlsSrtpEnc))
+#define GST_ER_DTLS_SRTP_ENC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_ER_DTLS_SRTP_ENC, GstErDtlsSrtpEncClass))
+#define GST_IS_ER_DTLS_SRTP_ENC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_ER_DTLS_SRTP_ENC))
+#define GST_IS_ER_DTLS_SRTP_ENC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_ER_DTLS_SRTP_ENC))
+
+typedef struct _GstErDtlsSrtpEnc GstErDtlsSrtpEnc;
+typedef struct _GstErDtlsSrtpEncClass GstErDtlsSrtpEncClass;
+
+struct _GstErDtlsSrtpEnc {
+    GstErDtlsSrtpBin bin;
+
+    GstElement *srtp_enc;
+    GstElement *funnel;
+};
+
+struct _GstErDtlsSrtpEncClass {
+    GstErDtlsSrtpBinClass parent_class;
+};
+
+GType gst_er_dtls_srtp_enc_get_type(void);
+
+gboolean gst_er_dtls_srtp_enc_plugin_init(GstPlugin *);
+
+guint gst_er_dtls_srtp_enc_get_cipher_value_by_nick(const gchar *cipher_nick);
+guint gst_er_dtls_srtp_enc_get_auth_value_by_nick(const gchar *auth_nick);
+
+G_END_DECLS
+
+#endif /* gstdtlssrtpenc_h */
diff --git a/ext/dtls/plugin.c b/ext/dtls/plugin.c
new file mode 100644 (file)
index 0000000..0aa811d
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2014, Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdtlsdec.h"
+#include "gstdtlsenc.h"
+#include "gstdtlssrtpenc.h"
+#include "gstdtlssrtpdec.h"
+#include "gstdtlssrtpdemux.h"
+
+#include <gst/gst.h>
+
+static gboolean plugin_init(GstPlugin *plugin)
+{
+    return gst_element_register(plugin, "erdtlsenc", GST_RANK_NONE, GST_TYPE_ER_DTLS_ENC)
+        && gst_element_register(plugin, "erdtlsdec", GST_RANK_NONE, GST_TYPE_ER_DTLS_DEC)
+        && gst_element_register(plugin, "erdtlssrtpdec", GST_RANK_NONE, GST_TYPE_ER_DTLS_SRTP_DEC)
+        && gst_element_register(plugin, "erdtlssrtpenc", GST_RANK_NONE, GST_TYPE_ER_DTLS_SRTP_ENC)
+        && gst_element_register(plugin, "erdtlssrtpdemux", GST_RANK_NONE, GST_TYPE_ER_DTLS_SRTP_DEMUX);
+}
+
+/* PACKAGE: this is usually set by autotools depending on some _INIT macro
+ * in configure.ac and then written into and defined in config.h, but we can
+ * just set it ourselves here in case someone doesn't use autotools to
+ * compile this code. GST_PLUGIN_DEFINE needs PACKAGE to be defined.
+ */
+#ifndef PACKAGE
+#define PACKAGE "erdtls"
+#endif
+
+/* gstreamer looks for this structure to register plugins
+ */
+GST_PLUGIN_DEFINE(
+    GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    erdtls,
+    "Ericsson DTLS decoder and encoder plugins",
+    plugin_init,
+    PACKAGE_VERSION,
+    "BSD",
+    "OpenWebRTC GStreamer plugins",
+    "http://www.openwebrtc.io/"
+)