| /* |
| * GStreamer - GStreamer SRTP decoder |
| * |
| * Copyright 2009-2011 Collabora Ltd. |
| * @author: Gabriel Millaire <gabriel.millaire@collabora.co.uk> |
| * @author: Olivier Crete <olivier.crete@collabora.com> |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| * DEALINGS IN THE SOFTWARE. |
| * |
| * Alternatively, the contents of this file may be used under the |
| * GNU Lesser General Public License Version 2.1 (the "LGPL"), in |
| * which case the following provisions apply instead of the ones |
| * mentioned above: |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| /** |
| * SECTION:element-srtpdec |
| * @see_also: srtpenc |
| * |
| * gstrtpdec acts as a decoder that removes security from SRTP and SRTCP |
| * packets (encryption and authentication) and out RTP and RTCP. It |
| * receives packet of type 'application/x-srtp' or 'application/x-srtcp' |
| * on its sink pad, and outs packets of type 'application/x-rtp' or |
| * 'application/x-rtcp' on its sink pad. |
| * |
| * For each packet received, it checks if the internal SSRC is in the list |
| * of streams already in use. If this is not the case, it sends a signal to |
| * the user to get the needed parameters to create a new stream : master |
| * key, encryption and authentication mecanisms for both RTP and RTCP. If |
| * the user can't provide those parameters, the buffer is dropped and a |
| * warning is emitted. |
| * |
| * This element uses libsrtp library. The encryption and authentication |
| * mecanisms available are : |
| * |
| * Encryption |
| * - AES_ICM 256 bits (maximum security) |
| * - AES_ICM 128 bits (default) |
| * - NULL |
| * |
| * Authentication |
| * - HMAC_SHA1 80 bits (default, maximum protection) |
| * - HMAC_SHA1 32 bits |
| * - NULL |
| * |
| * Note that for SRTP protection, authentication is mandatory (non-null) |
| * if encryption is used (non-null). |
| * |
| * Each packet received is first analysed (checked for valid SSRC) then |
| * its buffer is unprotected with libsrtp, then pushed on the source pad. |
| * If protection failed or the stream could not be created, the buffer |
| * is dropped and a warning is emitted. |
| * |
| * When the maximum usage of the master key is reached, a soft-limit |
| * signal is sent to the user, and new parameters (master key) are needed |
| * in return. If the hard limit is reached, a flag is set and every |
| * subsequent packet is dropped, until a new key is set and the stream |
| * has been updated. |
| * |
| * If a stream is to be shared between multiple clients the SRTP |
| * rollover counter for a given SSRC must be set in the caps "roc" field |
| * when the request-key signal is emitted by the decoder. The rollover |
| * counters should have been transmitted by a signaling protocol by some |
| * other means. If no rollover counter is provided by the user, 0 is |
| * used by default. |
| * |
| * <refsect2> |
| * <title>Example pipelines</title> |
| * |[ |
| * gst-launch-1.0 udpsrc port=5004 caps='application/x-srtp, payload=(int)8, ssrc=(uint)1356955624, srtp-key=(buffer)012345678901234567890123456789012345678901234567890123456789, srtp-cipher=(string)aes-128-icm, srtp-auth=(string)hmac-sha1-80, srtcp-cipher=(string)aes-128-icm, srtcp-auth=(string)hmac-sha1-80' ! srtpdec ! rtppcmadepay ! alawdec ! pulsesink |
| * ]| Receive PCMA SRTP packets through UDP using caps to specify |
| * master key and protection. |
| * |[ |
| * gst-launch-1.0 audiotestsrc ! alawenc ! rtppcmapay ! 'application/x-rtp, payload=(int)8, ssrc=(uint)1356955624' ! srtpenc key="012345678901234567890123456789012345678901234567890123456789" ! udpsink port=5004 |
| * ]| Send PCMA SRTP packets through UDP, nothing how the SSRC is forced so |
| * that the receiver will recognize it. |
| * </refsect2> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <gst/gst.h> |
| #include <gst/rtp/gstrtpbuffer.h> |
| #include <string.h> |
| |
| #include "gstsrtp.h" |
| #include "gstsrtp-enumtypes.h" |
| |
| #include "gstsrtpdec.h" |
| |
| #include <srtp/srtp_priv.h> |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_srtp_dec_debug); |
| #define GST_CAT_DEFAULT gst_srtp_dec_debug |
| |
| /* Filter signals and args */ |
| enum |
| { |
| SIGNAL_REQUEST_KEY = 1, |
| SIGNAL_CLEAR_KEYS, |
| SIGNAL_SOFT_LIMIT, |
| SIGNAL_HARD_LIMIT, |
| SIGNAL_REMOVE_KEY, |
| LAST_SIGNAL |
| }; |
| |
| enum |
| { |
| PROP_0 |
| }; |
| |
| /* the capabilities of the inputs and outputs. |
| * |
| * describe the real formats here. |
| */ |
| static GstStaticPadTemplate rtp_sink_template = |
| GST_STATIC_PAD_TEMPLATE ("rtp_sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("application/x-srtp") |
| ); |
| |
| 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_sink_template = |
| GST_STATIC_PAD_TEMPLATE ("rtcp_sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("application/x-srtcp") |
| ); |
| |
| static GstStaticPadTemplate rtcp_src_template = |
| GST_STATIC_PAD_TEMPLATE ("rtcp_src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("application/x-rtcp") |
| ); |
| |
| static guint gst_srtp_dec_signals[LAST_SIGNAL] = { 0 }; |
| |
| G_DEFINE_TYPE (GstSrtpDec, gst_srtp_dec, GST_TYPE_ELEMENT); |
| |
| static void gst_srtp_dec_clear_streams (GstSrtpDec * filter); |
| static void gst_srtp_dec_remove_stream (GstSrtpDec * filter, guint ssrc); |
| |
| static gboolean gst_srtp_dec_sink_event_rtp (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static gboolean gst_srtp_dec_sink_event_rtcp (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| |
| static gboolean gst_srtp_dec_sink_query_rtp (GstPad * pad, GstObject * parent, |
| GstQuery * query); |
| static gboolean gst_srtp_dec_sink_query_rtcp (GstPad * pad, |
| GstObject * parent, GstQuery * query); |
| |
| |
| static GstIterator *gst_srtp_dec_iterate_internal_links_rtp (GstPad * pad, |
| GstObject * parent); |
| static GstIterator *gst_srtp_dec_iterate_internal_links_rtcp (GstPad * pad, |
| GstObject * parent); |
| |
| static GstFlowReturn gst_srtp_dec_chain_rtp (GstPad * pad, |
| GstObject * parent, GstBuffer * buf); |
| static GstFlowReturn gst_srtp_dec_chain_rtcp (GstPad * pad, |
| GstObject * parent, GstBuffer * buf); |
| |
| static GstStateChangeReturn gst_srtp_dec_change_state (GstElement * element, |
| GstStateChange transition); |
| |
| static GstSrtpDecSsrcStream *request_key_with_signal (GstSrtpDec * filter, |
| guint32 ssrc, gint signal); |
| |
| struct _GstSrtpDecSsrcStream |
| { |
| guint32 ssrc; |
| |
| guint32 roc; |
| GstBuffer *key; |
| GstSrtpCipherType rtp_cipher; |
| GstSrtpAuthType rtp_auth; |
| GstSrtpCipherType rtcp_cipher; |
| GstSrtpAuthType rtcp_auth; |
| }; |
| |
| #define STREAM_HAS_CRYPTO(stream) \ |
| (stream->rtp_cipher != GST_SRTP_CIPHER_NULL || \ |
| stream->rtcp_cipher != GST_SRTP_CIPHER_NULL || \ |
| stream->rtp_auth != GST_SRTP_AUTH_NULL || \ |
| stream->rtcp_auth != GST_SRTP_AUTH_NULL) |
| |
| |
| /* initialize the srtpdec's class */ |
| static void |
| gst_srtp_dec_class_init (GstSrtpDecClass * klass) |
| { |
| GstElementClass *gstelement_class; |
| |
| gstelement_class = (GstElementClass *) klass; |
| |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&rtp_src_template)); |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&rtp_sink_template)); |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&rtcp_src_template)); |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&rtcp_sink_template)); |
| |
| gst_element_class_set_static_metadata (gstelement_class, "SRTP decoder", |
| "Filter/Network/SRTP", |
| "A SRTP and SRTCP decoder", |
| "Gabriel Millaire <millaire.gabriel@collabora.com>"); |
| |
| /* Install callbacks */ |
| gstelement_class->change_state = |
| GST_DEBUG_FUNCPTR (gst_srtp_dec_change_state); |
| |
| klass->clear_streams = GST_DEBUG_FUNCPTR (gst_srtp_dec_clear_streams); |
| klass->remove_stream = GST_DEBUG_FUNCPTR (gst_srtp_dec_remove_stream); |
| |
| /* Install signals */ |
| /** |
| * GstSrtpDec::request-key: |
| * @gstsrtpdec: the element on which the signal is emitted |
| * @ssrc: The unique SSRC of the stream |
| * |
| * Signal emited to get the parameters relevant to stream |
| * with @ssrc. User should provide the key and the RTP and |
| * RTCP encryption ciphers and authentication, and return |
| * them wrapped in a GstCaps. |
| */ |
| gst_srtp_dec_signals[SIGNAL_REQUEST_KEY] = |
| g_signal_new ("request-key", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT); |
| |
| /** |
| * GstSrtpDec::clear-keys: |
| * @gstsrtpdec: the element on which the signal is emitted |
| * |
| * Clear the internal list of streams |
| */ |
| gst_srtp_dec_signals[SIGNAL_CLEAR_KEYS] = |
| g_signal_new ("clear-keys", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
| G_STRUCT_OFFSET (GstSrtpDecClass, clear_streams), NULL, NULL, NULL, |
| G_TYPE_NONE, 0, G_TYPE_NONE); |
| |
| /** |
| * GstSrtpDec::soft-limit: |
| * @gstsrtpdec: the element on which the signal is emitted |
| * @ssrc: The unique SSRC of the stream |
| * |
| * Signal emited when the stream with @ssrc has reached the |
| * soft limit of utilisation of it's master encryption key. |
| * User should provide a new key and new RTP and RTCP encryption |
| * ciphers and authentication, and return them wrapped in a |
| * GstCaps. |
| */ |
| gst_srtp_dec_signals[SIGNAL_SOFT_LIMIT] = |
| g_signal_new ("soft-limit", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT); |
| |
| /** |
| * GstSrtpDec::hard-limit: |
| * @gstsrtpdec: the element on which the signal is emitted |
| * @ssrc: The unique SSRC of the stream |
| * |
| * Signal emited when the stream with @ssrc has reached the |
| * hard limit of utilisation of it's master encryption key. |
| * User should provide a new key and new RTP and RTCP encryption |
| * ciphers and authentication, and return them wrapped in a |
| * GstCaps. If user could not provide those parameters or signal |
| * is not answered, the buffers of this stream will be dropped. |
| */ |
| gst_srtp_dec_signals[SIGNAL_HARD_LIMIT] = |
| g_signal_new ("hard-limit", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT); |
| |
| /** |
| * GstSrtpDec::remove-key: |
| * @gstsrtpdec: the element on which the signal is emitted |
| * @ssrc: The SSRC for which to remove the key. |
| * |
| * Removes keys for a specific SSRC |
| */ |
| gst_srtp_dec_signals[SIGNAL_REMOVE_KEY] = |
| g_signal_new ("remove-key", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
| G_STRUCT_OFFSET (GstSrtpDecClass, remove_stream), NULL, NULL, NULL, |
| G_TYPE_NONE, 1, G_TYPE_UINT); |
| |
| } |
| |
| /* initialize the new element |
| * instantiate pads and add them to element |
| * set pad calback functions |
| * initialize instance structure |
| */ |
| static void |
| gst_srtp_dec_init (GstSrtpDec * filter) |
| { |
| filter->rtp_sinkpad = |
| gst_pad_new_from_static_template (&rtp_sink_template, "rtp_sink"); |
| gst_pad_set_event_function (filter->rtp_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_srtp_dec_sink_event_rtp)); |
| gst_pad_set_query_function (filter->rtp_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_srtp_dec_sink_query_rtp)); |
| gst_pad_set_iterate_internal_links_function (filter->rtp_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_srtp_dec_iterate_internal_links_rtp)); |
| gst_pad_set_chain_function (filter->rtp_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_srtp_dec_chain_rtp)); |
| |
| filter->rtp_srcpad = |
| gst_pad_new_from_static_template (&rtp_src_template, "rtp_src"); |
| gst_pad_set_iterate_internal_links_function (filter->rtp_srcpad, |
| GST_DEBUG_FUNCPTR (gst_srtp_dec_iterate_internal_links_rtp)); |
| |
| gst_pad_set_element_private (filter->rtp_sinkpad, filter->rtp_srcpad); |
| gst_pad_set_element_private (filter->rtp_srcpad, filter->rtp_sinkpad); |
| |
| gst_element_add_pad (GST_ELEMENT (filter), filter->rtp_sinkpad); |
| gst_element_add_pad (GST_ELEMENT (filter), filter->rtp_srcpad); |
| |
| |
| filter->rtcp_sinkpad = |
| gst_pad_new_from_static_template (&rtcp_sink_template, "rtcp_sink"); |
| gst_pad_set_event_function (filter->rtcp_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_srtp_dec_sink_event_rtcp)); |
| gst_pad_set_query_function (filter->rtcp_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_srtp_dec_sink_query_rtcp)); |
| gst_pad_set_iterate_internal_links_function (filter->rtcp_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_srtp_dec_iterate_internal_links_rtcp)); |
| gst_pad_set_chain_function (filter->rtcp_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_srtp_dec_chain_rtcp)); |
| |
| filter->rtcp_srcpad = |
| gst_pad_new_from_static_template (&rtcp_src_template, "rtcp_src"); |
| gst_pad_set_iterate_internal_links_function (filter->rtcp_srcpad, |
| GST_DEBUG_FUNCPTR (gst_srtp_dec_iterate_internal_links_rtcp)); |
| |
| gst_pad_set_element_private (filter->rtcp_sinkpad, filter->rtcp_srcpad); |
| gst_pad_set_element_private (filter->rtcp_srcpad, filter->rtcp_sinkpad); |
| |
| gst_element_add_pad (GST_ELEMENT (filter), filter->rtcp_sinkpad); |
| gst_element_add_pad (GST_ELEMENT (filter), filter->rtcp_srcpad); |
| |
| filter->first_session = TRUE; |
| filter->roc_changed = FALSE; |
| } |
| |
| static void |
| gst_srtp_dec_remove_stream (GstSrtpDec * filter, guint ssrc) |
| { |
| GstSrtpDecSsrcStream *stream = NULL; |
| |
| if (filter->streams == NULL) |
| return; |
| |
| stream = g_hash_table_lookup (filter->streams, GUINT_TO_POINTER (ssrc)); |
| |
| if (stream) { |
| srtp_remove_stream (filter->session, ssrc); |
| g_hash_table_remove (filter->streams, GUINT_TO_POINTER (ssrc)); |
| } |
| } |
| |
| static GstSrtpDecSsrcStream * |
| find_stream_by_ssrc (GstSrtpDec * filter, guint32 ssrc) |
| { |
| return g_hash_table_lookup (filter->streams, GUINT_TO_POINTER (ssrc)); |
| } |
| |
| |
| /* get info from buffer caps |
| */ |
| static GstSrtpDecSsrcStream * |
| get_stream_from_caps (GstSrtpDec * filter, GstCaps * caps, guint32 ssrc) |
| { |
| GstSrtpDecSsrcStream *stream; |
| GstStructure *s; |
| GstBuffer *buf; |
| const gchar *rtp_cipher, *rtp_auth, *rtcp_cipher, *rtcp_auth; |
| |
| /* Create new stream structure and set default values */ |
| stream = g_slice_new0 (GstSrtpDecSsrcStream); |
| stream->ssrc = ssrc; |
| stream->key = NULL; |
| |
| /* Get info from caps */ |
| s = gst_caps_get_structure (caps, 0); |
| if (!s) |
| goto error; |
| |
| rtp_cipher = gst_structure_get_string (s, "srtp-cipher"); |
| rtp_auth = gst_structure_get_string (s, "srtp-auth"); |
| rtcp_cipher = gst_structure_get_string (s, "srtcp-cipher"); |
| rtcp_auth = gst_structure_get_string (s, "srtcp-auth"); |
| if (!rtp_cipher || !rtp_auth || !rtcp_cipher || !rtcp_auth) |
| goto error; |
| |
| gst_structure_get_uint (s, "roc", &stream->roc); |
| |
| stream->rtp_cipher = enum_value_from_nick (GST_TYPE_SRTP_CIPHER_TYPE, |
| rtp_cipher); |
| stream->rtp_auth = enum_value_from_nick (GST_TYPE_SRTP_AUTH_TYPE, rtp_auth); |
| stream->rtcp_cipher = enum_value_from_nick (GST_TYPE_SRTP_CIPHER_TYPE, |
| rtcp_cipher); |
| stream->rtcp_auth = enum_value_from_nick (GST_TYPE_SRTP_AUTH_TYPE, rtcp_auth); |
| |
| if ((gint) stream->rtp_cipher == -1 || (gint) stream->rtp_auth == -1 || |
| (gint) stream->rtcp_cipher == -1 || (gint) stream->rtcp_auth == -1) { |
| GST_WARNING_OBJECT (filter, "Invalid caps for stream," |
| " unknown cipher or auth type"); |
| goto error; |
| } |
| |
| if (stream->rtcp_cipher != NULL_CIPHER && stream->rtcp_auth == NULL_AUTH) { |
| GST_WARNING_OBJECT (filter, |
| "Cannot have SRTP NULL authentication with a not-NULL encryption" |
| " cipher."); |
| goto error; |
| } |
| |
| if (gst_structure_get (s, "srtp-key", GST_TYPE_BUFFER, &buf, NULL) || !buf) { |
| GST_DEBUG_OBJECT (filter, "Got key [%p] for SSRC %u", buf, ssrc); |
| stream->key = buf; |
| } else if (STREAM_HAS_CRYPTO (stream)) { |
| goto error; |
| } |
| |
| return stream; |
| |
| error: |
| g_slice_free (GstSrtpDecSsrcStream, stream); |
| return NULL; |
| } |
| |
| /* Get SRTP params by signal |
| */ |
| static GstCaps * |
| signal_get_srtp_params (GstSrtpDec * filter, guint32 ssrc, gint signal) |
| { |
| GstCaps *caps = NULL; |
| |
| g_signal_emit (filter, gst_srtp_dec_signals[signal], 0, ssrc, &caps); |
| |
| if (caps != NULL) |
| GST_DEBUG_OBJECT (filter, "Caps received"); |
| |
| return caps; |
| } |
| |
| /* Create a stream in the session |
| */ |
| static err_status_t |
| init_session_stream (GstSrtpDec * filter, guint32 ssrc, |
| GstSrtpDecSsrcStream * stream) |
| { |
| err_status_t ret; |
| srtp_policy_t policy; |
| GstMapInfo map; |
| guchar tmp[1]; |
| |
| memset (&policy, 0, sizeof (srtp_policy_t)); |
| |
| if (!stream) |
| return err_status_bad_param; |
| |
| GST_INFO_OBJECT (filter, "Setting RTP policy..."); |
| set_crypto_policy_cipher_auth (stream->rtp_cipher, stream->rtp_auth, |
| &policy.rtp); |
| GST_INFO_OBJECT (filter, "Setting RTCP policy..."); |
| set_crypto_policy_cipher_auth (stream->rtcp_cipher, stream->rtcp_auth, |
| &policy.rtcp); |
| |
| if (stream->key) { |
| gst_buffer_map (stream->key, &map, GST_MAP_READ); |
| policy.key = (guchar *) map.data; |
| } else { |
| policy.key = tmp; |
| } |
| |
| policy.ssrc.value = ssrc; |
| policy.ssrc.type = ssrc_specific; |
| policy.next = NULL; |
| |
| /* If it is the first stream, create the session |
| * If not, add the stream policy to the session |
| */ |
| if (filter->first_session) |
| ret = srtp_create (&filter->session, &policy); |
| else |
| ret = srtp_add_stream (filter->session, &policy); |
| |
| if (stream->key) |
| gst_buffer_unmap (stream->key, &map); |
| |
| if (ret == err_status_ok) { |
| srtp_stream_t srtp_stream; |
| |
| srtp_stream = srtp_get_stream (filter->session, htonl (ssrc)); |
| if (srtp_stream) { |
| /* Here, we just set the ROC, but we also need to set the initial |
| * RTP sequence number later, otherwise libsrtp will not be able |
| * to get the right packet index. */ |
| rdbx_set_roc (&srtp_stream->rtp_rdbx, stream->roc); |
| filter->roc_changed = TRUE; |
| } |
| |
| filter->first_session = FALSE; |
| g_hash_table_insert (filter->streams, GUINT_TO_POINTER (stream->ssrc), |
| stream); |
| } |
| |
| return ret; |
| } |
| |
| /* Return a stream structure for a given buffer |
| */ |
| static GstSrtpDecSsrcStream * |
| validate_buffer (GstSrtpDec * filter, GstBuffer * buf, guint32 * ssrc, |
| gboolean * is_rtcp) |
| { |
| GstSrtpDecSsrcStream *stream = NULL; |
| |
| if (!(*is_rtcp)) { |
| GstRTPBuffer rtpbuf = GST_RTP_BUFFER_INIT; |
| |
| if (gst_rtp_buffer_map (buf, GST_MAP_READ, &rtpbuf)) { |
| if (gst_rtp_buffer_get_payload_type (&rtpbuf) < 64 |
| || gst_rtp_buffer_get_payload_type (&rtpbuf) > 80) { |
| *ssrc = gst_rtp_buffer_get_ssrc (&rtpbuf); |
| |
| gst_rtp_buffer_unmap (&rtpbuf); |
| goto have_ssrc; |
| } |
| gst_rtp_buffer_unmap (&rtpbuf); |
| } |
| } |
| |
| if (rtcp_buffer_get_ssrc (buf, ssrc)) { |
| *is_rtcp = TRUE; |
| } else { |
| GST_WARNING_OBJECT (filter, "No SSRC found in buffer"); |
| return NULL; |
| } |
| |
| have_ssrc: |
| |
| stream = find_stream_by_ssrc (filter, *ssrc); |
| |
| if (stream) |
| return stream; |
| |
| return request_key_with_signal (filter, *ssrc, SIGNAL_REQUEST_KEY); |
| } |
| |
| static void |
| free_stream (GstSrtpDecSsrcStream * stream) |
| { |
| if (stream->key) |
| gst_buffer_unref (stream->key); |
| g_slice_free (GstSrtpDecSsrcStream, stream); |
| } |
| |
| /* Create new stream from params in caps |
| */ |
| static GstSrtpDecSsrcStream * |
| update_session_stream_from_caps (GstSrtpDec * filter, guint32 ssrc, |
| GstCaps * caps) |
| { |
| GstSrtpDecSsrcStream *stream = NULL; |
| GstSrtpDecSsrcStream *old_stream = NULL; |
| err_status_t err; |
| |
| g_return_val_if_fail (GST_IS_SRTP_DEC (filter), NULL); |
| g_return_val_if_fail (GST_IS_CAPS (caps), NULL); |
| |
| stream = get_stream_from_caps (filter, caps, ssrc); |
| |
| old_stream = find_stream_by_ssrc (filter, ssrc); |
| if (stream && old_stream && |
| stream->rtp_cipher == old_stream->rtp_cipher && |
| stream->rtcp_cipher == old_stream->rtcp_cipher && |
| stream->rtp_auth == old_stream->rtp_auth && |
| stream->rtcp_auth == old_stream->rtcp_auth && |
| stream->key && old_stream->key && |
| gst_buffer_get_size (stream->key) == |
| gst_buffer_get_size (old_stream->key)) { |
| GstMapInfo info; |
| |
| if (gst_buffer_map (old_stream->key, &info, GST_MAP_READ)) { |
| gboolean equal; |
| |
| equal = (gst_buffer_memcmp (stream->key, 0, info.data, info.size) == 0); |
| gst_buffer_unmap (old_stream->key, &info); |
| |
| if (equal) { |
| free_stream (stream); |
| return old_stream; |
| } |
| } |
| } |
| |
| /* Remove existing stream, if any */ |
| gst_srtp_dec_remove_stream (filter, ssrc); |
| |
| if (stream) { |
| /* Create new session stream */ |
| err = init_session_stream (filter, ssrc, stream); |
| |
| if (err != err_status_ok) { |
| if (stream->key) |
| gst_buffer_unref (stream->key); |
| g_slice_free (GstSrtpDecSsrcStream, stream); |
| stream = NULL; |
| } |
| } |
| |
| return stream; |
| } |
| |
| static gboolean |
| remove_yes (gpointer key, gpointer value, gpointer user_data) |
| { |
| return TRUE; |
| } |
| |
| /* Clear the policy list |
| */ |
| static void |
| gst_srtp_dec_clear_streams (GstSrtpDec * filter) |
| { |
| guint nb = 0; |
| |
| GST_OBJECT_LOCK (filter); |
| |
| if (!filter->first_session) |
| srtp_dealloc (filter->session); |
| |
| if (filter->streams) |
| nb = g_hash_table_foreach_remove (filter->streams, remove_yes, NULL); |
| |
| filter->first_session = TRUE; |
| |
| GST_OBJECT_UNLOCK (filter); |
| |
| GST_DEBUG_OBJECT (filter, "Cleared %d streams", nb); |
| } |
| |
| /* Send a signal |
| */ |
| static GstSrtpDecSsrcStream * |
| request_key_with_signal (GstSrtpDec * filter, guint32 ssrc, gint signal) |
| { |
| GstCaps *caps; |
| GstSrtpDecSsrcStream *stream = NULL; |
| |
| caps = signal_get_srtp_params (filter, ssrc, signal); |
| |
| if (caps) { |
| stream = update_session_stream_from_caps (filter, ssrc, caps); |
| if (stream) |
| GST_DEBUG_OBJECT (filter, "New stream set with SSRC %u", ssrc); |
| else |
| GST_WARNING_OBJECT (filter, "Could not set stream with SSRC %u", ssrc); |
| gst_caps_unref (caps); |
| } |
| |
| return stream; |
| } |
| |
| static gboolean |
| gst_srtp_dec_sink_setcaps (GstPad * pad, GstObject * parent, |
| GstCaps * caps, gboolean is_rtcp) |
| { |
| GstSrtpDec *filter = GST_SRTP_DEC (parent); |
| GstPad *otherpad; |
| GstStructure *ps; |
| gboolean ret = FALSE; |
| |
| g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); |
| |
| ps = gst_caps_get_structure (caps, 0); |
| |
| if (gst_structure_has_field_typed (ps, "ssrc", G_TYPE_UINT) && |
| gst_structure_has_field_typed (ps, "srtp-cipher", G_TYPE_STRING) && |
| gst_structure_has_field_typed (ps, "srtp-auth", G_TYPE_STRING) && |
| gst_structure_has_field_typed (ps, "srtcp-cipher", G_TYPE_STRING) && |
| gst_structure_has_field_typed (ps, "srtcp-auth", G_TYPE_STRING)) { |
| guint ssrc; |
| |
| gst_structure_get_uint (ps, "ssrc", &ssrc); |
| |
| if (!update_session_stream_from_caps (filter, ssrc, caps)) { |
| GST_WARNING_OBJECT (pad, "Could not create session from pad caps: %" |
| GST_PTR_FORMAT, caps); |
| return FALSE; |
| } |
| } |
| |
| caps = gst_caps_copy (caps); |
| ps = gst_caps_get_structure (caps, 0); |
| gst_structure_remove_fields (ps, "srtp-key", "srtp-cipher", "srtp-auth", |
| "srtcp-cipher", "srtcp-auth", NULL); |
| |
| if (is_rtcp) |
| gst_structure_set_name (ps, "application/x-rtcp"); |
| else |
| gst_structure_set_name (ps, "application/x-rtp"); |
| |
| otherpad = gst_pad_get_element_private (pad); |
| |
| ret = gst_pad_set_caps (otherpad, caps); |
| |
| gst_caps_unref (caps); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_srtp_dec_sink_event_rtp (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| GstCaps *caps; |
| GstSrtpDec *filter = GST_SRTP_DEC (parent); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_CAPS: |
| gst_event_parse_caps (event, &caps); |
| return gst_srtp_dec_sink_setcaps (pad, parent, caps, FALSE); |
| case GST_EVENT_SEGMENT: |
| filter->rtp_has_segment = TRUE; |
| break; |
| case GST_EVENT_FLUSH_STOP: |
| filter->rtp_has_segment = FALSE; |
| break; |
| default: |
| break; |
| } |
| |
| return gst_pad_event_default (pad, parent, event); |
| } |
| |
| static gboolean |
| gst_srtp_dec_sink_event_rtcp (GstPad * pad, GstObject * parent, |
| GstEvent * event) |
| { |
| GstCaps *caps; |
| GstSrtpDec *filter = GST_SRTP_DEC (parent); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_CAPS: |
| gst_event_parse_caps (event, &caps); |
| return gst_srtp_dec_sink_setcaps (pad, parent, caps, TRUE); |
| case GST_EVENT_SEGMENT: |
| filter->rtcp_has_segment = TRUE; |
| break; |
| case GST_EVENT_FLUSH_STOP: |
| filter->rtcp_has_segment = FALSE; |
| break; |
| default: |
| break; |
| } |
| |
| return gst_pad_event_default (pad, parent, event); |
| } |
| |
| static gboolean |
| gst_srtp_dec_sink_query (GstPad * pad, GstObject * parent, GstQuery * query, |
| gboolean is_rtcp) |
| { |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_CAPS: |
| { |
| GstCaps *filter = NULL; |
| GstCaps *other_filter = NULL; |
| GstCaps *template_caps; |
| GstPad *otherpad; |
| GstCaps *other_caps; |
| GstCaps *ret; |
| int i; |
| |
| gst_query_parse_caps (query, &filter); |
| |
| otherpad = (GstPad *) gst_pad_get_element_private (pad); |
| |
| if (filter) { |
| other_filter = gst_caps_copy (filter); |
| |
| for (i = 0; i < gst_caps_get_size (other_filter); i++) { |
| GstStructure *ps = gst_caps_get_structure (other_filter, i); |
| if (is_rtcp) |
| gst_structure_set_name (ps, "application/x-rtcp"); |
| else |
| gst_structure_set_name (ps, "application/x-rtp"); |
| gst_structure_remove_fields (ps, "srtp-key", "srtp-cipher", |
| "srtp-auth", "srtcp-cipher", "srtcp-auth", NULL); |
| } |
| } |
| |
| |
| other_caps = gst_pad_peer_query_caps (otherpad, other_filter); |
| if (other_filter) |
| gst_caps_unref (other_filter); |
| if (!other_caps) { |
| goto return_template; |
| } |
| |
| template_caps = gst_pad_get_pad_template_caps (otherpad); |
| ret = gst_caps_intersect_full (other_caps, template_caps, |
| GST_CAPS_INTERSECT_FIRST); |
| gst_caps_unref (other_caps); |
| gst_caps_unref (template_caps); |
| |
| ret = gst_caps_make_writable (ret); |
| |
| for (i = 0; i < gst_caps_get_size (ret); i++) { |
| GstStructure *ps = gst_caps_get_structure (ret, i); |
| if (is_rtcp) |
| gst_structure_set_name (ps, "application/x-srtcp"); |
| else |
| gst_structure_set_name (ps, "application/x-srtp"); |
| } |
| |
| if (filter) { |
| GstCaps *tmp; |
| |
| tmp = gst_caps_intersect (ret, filter); |
| gst_caps_unref (ret); |
| ret = tmp; |
| } |
| |
| gst_query_set_caps_result (query, ret); |
| gst_caps_unref (ret); |
| return TRUE; |
| |
| return_template: |
| |
| ret = gst_pad_get_pad_template_caps (pad); |
| gst_query_set_caps_result (query, ret); |
| gst_caps_unref (ret); |
| return TRUE; |
| } |
| default: |
| return gst_pad_query_default (pad, parent, query); |
| } |
| } |
| |
| static gboolean |
| gst_srtp_dec_sink_query_rtp (GstPad * pad, GstObject * parent, GstQuery * query) |
| { |
| return gst_srtp_dec_sink_query (pad, parent, query, FALSE); |
| } |
| |
| static gboolean |
| gst_srtp_dec_sink_query_rtcp (GstPad * pad, GstObject * parent, |
| GstQuery * query) |
| { |
| return gst_srtp_dec_sink_query (pad, parent, query, TRUE); |
| } |
| |
| static GstIterator * |
| gst_srtp_dec_iterate_internal_links (GstPad * pad, GstObject * parent, |
| gboolean is_rtcp) |
| { |
| GstSrtpDec *filter = GST_SRTP_DEC (parent); |
| GstPad *otherpad = NULL; |
| GstIterator *it = NULL; |
| |
| otherpad = (GstPad *) gst_pad_get_element_private (pad); |
| |
| if (otherpad) { |
| GValue val = { 0 }; |
| |
| g_value_init (&val, GST_TYPE_PAD); |
| g_value_set_object (&val, otherpad); |
| it = gst_iterator_new_single (GST_TYPE_PAD, &val); |
| g_value_unset (&val); |
| } else { |
| GST_ELEMENT_ERROR (GST_ELEMENT_CAST (filter), CORE, PAD, (NULL), |
| ("Unable to get linked pad")); |
| } |
| |
| return it; |
| } |
| |
| static GstIterator * |
| gst_srtp_dec_iterate_internal_links_rtp (GstPad * pad, GstObject * parent) |
| { |
| return gst_srtp_dec_iterate_internal_links (pad, parent, FALSE); |
| } |
| |
| static GstIterator * |
| gst_srtp_dec_iterate_internal_links_rtcp (GstPad * pad, GstObject * parent) |
| { |
| return gst_srtp_dec_iterate_internal_links (pad, parent, TRUE); |
| } |
| |
| static void |
| gst_srtp_dec_push_early_events (GstSrtpDec * filter, GstPad * pad, |
| GstPad * otherpad, gboolean is_rtcp) |
| { |
| GstEvent *otherev, *ev; |
| |
| ev = gst_pad_get_sticky_event (pad, GST_EVENT_STREAM_START, 0); |
| if (ev) { |
| gst_event_unref (ev); |
| } else { |
| gchar *new_stream_id; |
| |
| otherev = gst_pad_get_sticky_event (otherpad, GST_EVENT_STREAM_START, 0); |
| |
| if (otherev) { |
| const gchar *other_stream_id; |
| |
| gst_event_parse_stream_start (otherev, &other_stream_id); |
| |
| new_stream_id = g_strdup_printf ("%s/%s", other_stream_id, |
| is_rtcp ? "rtcp" : "rtp"); |
| gst_event_unref (otherev); |
| } else { |
| new_stream_id = gst_pad_create_stream_id (pad, GST_ELEMENT (filter), |
| is_rtcp ? "rtcp" : "rtp"); |
| } |
| |
| ev = gst_event_new_stream_start (new_stream_id); |
| g_free (new_stream_id); |
| |
| gst_pad_push_event (pad, ev); |
| } |
| |
| ev = gst_pad_get_sticky_event (pad, GST_EVENT_CAPS, 0); |
| if (ev) { |
| gst_event_unref (ev); |
| } else { |
| GstCaps *caps; |
| |
| if (is_rtcp) |
| caps = gst_caps_new_empty_simple ("application/x-rtcp"); |
| else |
| caps = gst_caps_new_empty_simple ("application/x-rtp"); |
| |
| gst_pad_set_caps (pad, caps); |
| gst_caps_unref (caps); |
| } |
| |
| ev = gst_pad_get_sticky_event (pad, GST_EVENT_SEGMENT, 0); |
| if (ev) { |
| gst_event_unref (ev); |
| } else { |
| ev = gst_pad_get_sticky_event (otherpad, GST_EVENT_SEGMENT, 0); |
| |
| if (ev) |
| gst_pad_push_event (pad, ev); |
| } |
| |
| if (is_rtcp) |
| filter->rtcp_has_segment = TRUE; |
| else |
| filter->rtp_has_segment = TRUE; |
| |
| } |
| |
| static GstFlowReturn |
| gst_srtp_dec_chain (GstPad * pad, GstObject * parent, GstBuffer * buf, |
| gboolean is_rtcp) |
| { |
| GstSrtpDec *filter = GST_SRTP_DEC (parent); |
| GstPad *otherpad; |
| err_status_t err = err_status_ok; |
| GstSrtpDecSsrcStream *stream = NULL; |
| GstFlowReturn ret = GST_FLOW_OK; |
| gint size; |
| guint32 ssrc = 0; |
| GstMapInfo map; |
| |
| GST_OBJECT_LOCK (filter); |
| |
| /* Check if this stream exists, if not create a new stream */ |
| |
| if (!(stream = validate_buffer (filter, buf, &ssrc, &is_rtcp))) { |
| GST_OBJECT_UNLOCK (filter); |
| GST_WARNING_OBJECT (filter, "Invalid buffer, dropping"); |
| goto drop_buffer; |
| } |
| |
| if (!STREAM_HAS_CRYPTO (stream)) { |
| GST_OBJECT_UNLOCK (filter); |
| goto push_out; |
| } |
| |
| GST_LOG_OBJECT (pad, "Received %s buffer of size %" G_GSIZE_FORMAT |
| " with SSRC = %u", is_rtcp ? "RTCP" : "RTP", gst_buffer_get_size (buf), |
| ssrc); |
| |
| /* Change buffer to remove protection */ |
| buf = gst_buffer_make_writable (buf); |
| |
| unprotect: |
| |
| gst_buffer_map (buf, &map, GST_MAP_READWRITE); |
| size = map.size; |
| |
| gst_srtp_init_event_reporter (); |
| |
| if (is_rtcp) |
| err = srtp_unprotect_rtcp (filter->session, map.data, &size); |
| else { |
| /* If ROC has changed, we know we need to set the initial RTP |
| * sequence number too. */ |
| if (filter->roc_changed) { |
| srtp_stream_t stream; |
| |
| stream = srtp_get_stream (filter->session, htonl (ssrc)); |
| |
| if (stream) { |
| guint16 seqnum = 0; |
| GstRTPBuffer rtpbuf = GST_RTP_BUFFER_INIT; |
| |
| gst_rtp_buffer_map (buf, GST_MAP_READ, &rtpbuf); |
| seqnum = gst_rtp_buffer_get_seq (&rtpbuf); |
| gst_rtp_buffer_unmap (&rtpbuf); |
| |
| /* We finally add the RTP sequence number to the current |
| * rollover counter. */ |
| stream->rtp_rdbx.index &= ~0xFFFF; |
| stream->rtp_rdbx.index |= seqnum; |
| } |
| |
| filter->roc_changed = FALSE; |
| } |
| err = srtp_unprotect (filter->session, map.data, &size); |
| } |
| |
| gst_buffer_unmap (buf, &map); |
| |
| GST_OBJECT_UNLOCK (filter); |
| |
| if (err != err_status_ok) { |
| GST_WARNING_OBJECT (pad, |
| "Unable to unprotect buffer (unprotect failed code %d)", err); |
| |
| /* Signal user depending on type of error */ |
| switch (err) { |
| case err_status_key_expired: |
| GST_OBJECT_LOCK (filter); |
| |
| /* Update stream */ |
| if (find_stream_by_ssrc (filter, ssrc)) { |
| GST_OBJECT_UNLOCK (filter); |
| if (request_key_with_signal (filter, ssrc, SIGNAL_HARD_LIMIT)) { |
| GST_OBJECT_LOCK (filter); |
| goto unprotect; |
| } else { |
| GST_WARNING_OBJECT (filter, "Hard limit reached, no new key, " |
| "dropping"); |
| } |
| } else { |
| GST_WARNING_OBJECT (filter, "Could not find matching stream, " |
| "dropping"); |
| } |
| break; |
| case err_status_auth_fail: |
| GST_WARNING_OBJECT (filter, "Error authentication packet, dropping"); |
| break; |
| case err_status_cipher_fail: |
| GST_WARNING_OBJECT (filter, "Error while decrypting packet, dropping"); |
| break; |
| default: |
| GST_WARNING_OBJECT (filter, "Other error, dropping"); |
| break; |
| } |
| |
| goto drop_buffer; |
| } |
| |
| gst_buffer_set_size (buf, size); |
| |
| /* If all is well, we may have reached soft limit */ |
| if (gst_srtp_get_soft_limit_reached ()) |
| request_key_with_signal (filter, ssrc, SIGNAL_SOFT_LIMIT); |
| |
| push_out: |
| /* Push buffer to source pad */ |
| if (is_rtcp) { |
| otherpad = filter->rtcp_srcpad; |
| if (!filter->rtcp_has_segment) |
| gst_srtp_dec_push_early_events (filter, filter->rtcp_srcpad, |
| filter->rtp_srcpad, TRUE); |
| } else { |
| otherpad = filter->rtp_srcpad; |
| if (!filter->rtp_has_segment) |
| gst_srtp_dec_push_early_events (filter, filter->rtp_srcpad, |
| filter->rtcp_srcpad, FALSE); |
| } |
| ret = gst_pad_push (otherpad, buf); |
| |
| return ret; |
| |
| drop_buffer: |
| /* Drop buffer, except if gst_pad_push returned OK or an error */ |
| |
| gst_buffer_unref (buf); |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_srtp_dec_chain_rtp (GstPad * pad, GstObject * parent, GstBuffer * buf) |
| { |
| return gst_srtp_dec_chain (pad, parent, buf, FALSE); |
| } |
| |
| static GstFlowReturn |
| gst_srtp_dec_chain_rtcp (GstPad * pad, GstObject * parent, GstBuffer * buf) |
| { |
| return gst_srtp_dec_chain (pad, parent, buf, TRUE); |
| } |
| |
| static GstStateChangeReturn |
| gst_srtp_dec_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstStateChangeReturn res; |
| GstSrtpDec *filter; |
| |
| filter = GST_SRTP_DEC (element); |
| GST_OBJECT_LOCK (filter); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_READY_TO_PAUSED: |
| filter->streams = g_hash_table_new_full (g_direct_hash, g_direct_equal, |
| NULL, (GDestroyNotify) free_stream); |
| filter->rtp_has_segment = FALSE; |
| filter->rtcp_has_segment = FALSE; |
| break; |
| case GST_STATE_CHANGE_PAUSED_TO_PLAYING: |
| break; |
| default: |
| break; |
| } |
| |
| GST_OBJECT_UNLOCK (filter); |
| |
| res = GST_ELEMENT_CLASS (gst_srtp_dec_parent_class)->change_state (element, |
| transition); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PLAYING_TO_PAUSED: |
| break; |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| gst_srtp_dec_clear_streams (filter); |
| g_hash_table_unref (filter->streams); |
| filter->streams = NULL; |
| break; |
| case GST_STATE_CHANGE_READY_TO_NULL: |
| break; |
| default: |
| break; |
| } |
| return res; |
| } |
| |
| |
| /* entry point to initialize the plug-in |
| * initialize the plug-in itself |
| * register the element factories and other features |
| */ |
| gboolean |
| gst_srtp_dec_plugin_init (GstPlugin * srtpdec) |
| { |
| GST_DEBUG_CATEGORY_INIT (gst_srtp_dec_debug, "srtpdec", 0, "SRTP dec"); |
| |
| return gst_element_register (srtpdec, "srtpdec", GST_RANK_NONE, |
| GST_TYPE_SRTP_DEC); |
| } |