/* * 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") ); static GstStaticPadTemplate rtcp_src_template = GST_STATIC_PAD_TEMPLATE ("rtcp_src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("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 (gst_dtls_srtp_dec_debug); #define GST_CAT_DEFAULT gst_dtls_srtp_dec_debug #define gst_dtls_srtp_dec_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstDtlsSrtpDec, gst_dtls_srtp_dec, GST_TYPE_DTLS_SRTP_BIN, GST_DEBUG_CATEGORY_INIT (gst_dtls_srtp_dec_debug, "dtlssrtpdec", 0, "DTLS Decoder")); enum { PROP_0, PROP_PEM, PROP_PEER_PEM, PROP_CONNECTION_STATE, NUM_PROPERTIES }; static GParamSpec *properties[NUM_PROPERTIES]; #define DEFAULT_PEM NULL #define DEFAULT_PEER_PEM NULL static void gst_dtls_srtp_dec_set_property (GObject *, guint prop_id, const GValue *, GParamSpec *); static void gst_dtls_srtp_dec_get_property (GObject *, guint prop_id, GValue *, GParamSpec *); static GstPad *gst_dtls_srtp_dec_request_new_pad (GstElement *, GstPadTemplate *, const gchar * name, const GstCaps *); static void gst_dtls_srtp_dec_release_pad (GstElement *, GstPad *); static GstCaps *on_decoder_request_key (GstElement * srtp_decoder, guint ssrc, GstDtlsSrtpBin *); static void on_peer_pem (GstElement * srtp_decoder, GParamSpec * pspec, GstDtlsSrtpDec * self); static void gst_dtls_srtp_dec_remove_dtls_element (GstDtlsSrtpBin *); static GstPadProbeReturn remove_dtls_decoder_probe_callback (GstPad *, GstPadProbeInfo *, GstElement *); static void gst_dtls_srtp_dec_class_init (GstDtlsSrtpDecClass * klass) { GObjectClass *gobject_class; GstElementClass *element_class; GstDtlsSrtpBinClass *dtls_srtp_bin_class; gobject_class = (GObjectClass *) klass; element_class = (GstElementClass *) klass; dtls_srtp_bin_class = (GstDtlsSrtpBinClass *) klass; gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_dtls_srtp_dec_set_property); gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_dtls_srtp_dec_get_property); element_class->request_new_pad = GST_DEBUG_FUNCPTR (gst_dtls_srtp_dec_request_new_pad); element_class->release_pad = GST_DEBUG_FUNCPTR (gst_dtls_srtp_dec_release_pad); dtls_srtp_bin_class->remove_dtls_element = GST_DEBUG_FUNCPTR (gst_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 | GST_PARAM_DOC_SHOW_DEFAULT); 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_CONNECTION_STATE] = g_param_spec_enum ("connection-state", "Connection State", "Current connection state", GST_DTLS_TYPE_CONNECTION_STATE, GST_DTLS_CONNECTION_STATE_NEW, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties); gst_element_class_add_static_pad_template (element_class, &sink_template); gst_element_class_add_static_pad_template (element_class, &rtp_src_template); gst_element_class_add_static_pad_template (element_class, &rtcp_src_template); gst_element_class_add_static_pad_template (element_class, &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 on_connection_state_changed (GObject * object, GParamSpec * pspec, gpointer user_data) { GstDtlsSrtpDec *self = GST_DTLS_SRTP_DEC (user_data); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CONNECTION_STATE]); } static void gst_dtls_srtp_dec_init (GstDtlsSrtpDec * self) { GstElementClass *klass = GST_ELEMENT_GET_CLASS (GST_ELEMENT (self)); GstPadTemplate *templ; GstPad *target_pad, *ghost_pad; gboolean ret; /* +-----------+ +--------------+ .-o| dtlsdec |o-R----data | dtls|o-' +-----------+ sink---o| dtlsdemux | | srt(c)p|o-. +-----------+ +--------------+ '-o|srtp rtp|o------rtp | srtpdec | o|srtcp rtcp|o------rtcp +-----------+ */ self->srtp_dec = gst_element_factory_make ("srtpdec", NULL); 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 ("dtlssrtpdemux", NULL); if (!self->dtls_srtp_demux) { GST_ERROR_OBJECT (self, "failed to create dtls_srtp_demux"); return; } self->bin.dtls_element = gst_element_factory_make ("dtlsdec", NULL); if (!self->bin.dtls_element) { GST_ERROR_OBJECT (self, "failed to create dtls_dec"); return; } gst_bin_add_many (GST_BIN (self), self->dtls_srtp_demux, self->bin.dtls_element, self->srtp_dec, 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); templ = gst_element_class_get_pad_template (klass, "rtp_src"); target_pad = gst_element_get_static_pad (self->srtp_dec, "rtp_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, "rtcp_src"); target_pad = gst_element_get_static_pad (self->srtp_dec, "rtcp_src"); ghost_pad = gst_ghost_pad_new_from_template ("rtcp_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); g_signal_connect (self->bin.dtls_element, "notify::connection-state", G_CALLBACK (on_connection_state_changed), self); } static void gst_dtls_srtp_dec_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstDtlsSrtpDec *self = GST_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_dtls_srtp_dec_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstDtlsSrtpDec *self = GST_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; case PROP_CONNECTION_STATE: if (self->bin.dtls_element) { g_object_get_property (G_OBJECT (self->bin.dtls_element), "connection-state", value); } else { GST_WARNING_OBJECT (self, "tried to get connection-state after disabling DTLS"); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec); } } static GstPad * gst_dtls_srtp_dec_request_new_pad (GstElement * element, GstPadTemplate * templ, const gchar * name, const GstCaps * caps) { GstDtlsSrtpDec *self = GST_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 void gst_dtls_srtp_dec_release_pad (GstElement * element, GstPad * pad) { GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); GstDtlsSrtpDec *self = GST_DTLS_SRTP_DEC (element); if (GST_PAD_PAD_TEMPLATE (pad) == gst_element_class_get_pad_template (klass, "data_src")) { GstGhostPad *ghost_pad; GstPad *target_pad; ghost_pad = GST_GHOST_PAD (pad); target_pad = gst_ghost_pad_get_target (ghost_pad); if (target_pad != NULL) { gst_element_release_request_pad (self->bin.dtls_element, target_pad); gst_object_unref (target_pad); gst_ghost_pad_set_target (GST_GHOST_PAD (pad), NULL); } } gst_element_remove_pad (element, pad); } static GstCaps * on_decoder_request_key (GstElement * srtp_decoder, guint ssrc, GstDtlsSrtpBin * 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 == GST_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 GST_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 GST_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; } gst_buffer_unref (key_buffer); return key_caps; } else { GST_WARNING_OBJECT (bin, "no srtp key available yet"); } return NULL; } static void on_peer_pem (GstElement * srtp_decoder, GParamSpec * pspec, GstDtlsSrtpDec * self) { g_return_if_fail (self); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PEER_PEM]); } static void gst_dtls_srtp_dec_remove_dtls_element (GstDtlsSrtpBin * bin) { GstDtlsSrtpDec *self = GST_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; }