| /* |
| * 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 (gst_dtls_enc_debug); |
| #define GST_CAT_DEFAULT gst_dtls_enc_debug |
| |
| #define gst_dtls_enc_parent_class parent_class |
| G_DEFINE_TYPE_WITH_CODE (GstDtlsEnc, gst_dtls_enc, GST_TYPE_ELEMENT, |
| GST_DEBUG_CATEGORY_INIT (gst_dtls_enc_debug, "dtlsenc", 0, "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_dtls_enc_finalize (GObject *); |
| static void gst_dtls_enc_set_property (GObject *, guint prop_id, |
| const GValue *, GParamSpec *); |
| static void gst_dtls_enc_get_property (GObject *, guint prop_id, GValue *, |
| GParamSpec *); |
| |
| static GstStateChangeReturn gst_dtls_enc_change_state (GstElement *, |
| GstStateChange); |
| static GstPad *gst_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 gboolean sink_event (GstPad * pad, GstObject * parent, GstEvent * event); |
| |
| static void on_key_received (GstDtlsConnection *, gpointer key, guint cipher, |
| guint auth, GstDtlsEnc *); |
| static void on_send_data (GstDtlsConnection *, gconstpointer data, gint length, |
| GstDtlsEnc *); |
| |
| static void |
| gst_dtls_enc_class_init (GstDtlsEncClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *element_class; |
| |
| gobject_class = (GObjectClass *) klass; |
| element_class = (GstElementClass *) klass; |
| |
| gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_dtls_enc_finalize); |
| gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_dtls_enc_set_property); |
| gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_dtls_enc_get_property); |
| |
| element_class->change_state = GST_DEBUG_FUNCPTR (gst_dtls_enc_change_state); |
| element_class->request_new_pad = |
| GST_DEBUG_FUNCPTR (gst_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 GstDtlsSrtpCipher.", |
| 0, GST_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 GstDtlsSrtpAuth.", |
| 0, GST_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_static_pad_template (element_class, &src_template); |
| gst_element_class_add_static_pad_template (element_class, &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_dtls_enc_init (GstDtlsEnc * 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; |
| |
| g_queue_init (&self->queue); |
| 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_dtls_enc_finalize (GObject * object) |
| { |
| GstDtlsEnc *self = GST_DTLS_ENC (object); |
| |
| if (self->encoder_key) { |
| gst_buffer_unref (self->encoder_key); |
| self->encoder_key = NULL; |
| } |
| |
| if (self->connection_id) { |
| g_free (self->connection_id); |
| self->connection_id = NULL; |
| } |
| |
| g_mutex_lock (&self->queue_lock); |
| g_queue_foreach (&self->queue, (GFunc) gst_buffer_unref, NULL); |
| g_queue_clear (&self->queue); |
| 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_dtls_enc_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstDtlsEnc *self = GST_DTLS_ENC (object); |
| |
| switch (prop_id) { |
| case PROP_CONNECTION_ID: |
| if (self->connection_id != NULL) { |
| g_free (self->connection_id); |
| self->connection_id = NULL; |
| } |
| 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_dtls_enc_get_property (GObject * object, guint prop_id, GValue * value, |
| GParamSpec * pspec) |
| { |
| GstDtlsEnc *self = GST_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_dtls_enc_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstDtlsEnc *self = GST_DTLS_ENC (element); |
| GstStateChangeReturn ret; |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_NULL_TO_READY: |
| if (self->connection_id) { |
| self->connection = gst_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); |
| |
| gst_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_PAUSED_TO_READY: |
| GST_DEBUG_OBJECT (self, "stopping connection %s", self->connection_id); |
| |
| gst_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) { |
| gst_dtls_connection_close (self->connection); |
| gst_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); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_READY_TO_PAUSED: |
| GST_DEBUG_OBJECT (self, "starting connection %s", self->connection_id); |
| gst_dtls_connection_start (self->connection, self->is_client); |
| break; |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static GstPad * |
| gst_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)); |
| gst_pad_set_event_function (sink, GST_DEBUG_FUNCPTR (sink_event)); |
| |
| 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) |
| { |
| GstDtlsEnc *self = GST_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->flushing = FALSE; |
| 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); |
| g_queue_foreach (&self->queue, (GFunc) gst_buffer_unref, NULL); |
| g_queue_clear (&self->queue); |
| self->flushing = TRUE; |
| 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) |
| { |
| GstDtlsEnc *self = GST_DTLS_ENC (GST_PAD_PARENT (pad)); |
| GstFlowReturn ret; |
| GstBuffer *buffer; |
| gboolean check_connection_timeout = FALSE; |
| |
| GST_TRACE_OBJECT (self, "src loop: acquiring lock"); |
| g_mutex_lock (&self->queue_lock); |
| GST_TRACE_OBJECT (self, "src loop: acquired lock"); |
| |
| if (self->flushing) { |
| 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 (g_queue_is_empty (&self->queue)) { |
| 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 (self->flushing) { |
| 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"); |
| |
| buffer = g_queue_pop_head (&self->queue); |
| g_mutex_unlock (&self->queue_lock); |
| |
| if (self->send_initial_events) { |
| GstSegment segment; |
| gchar s_id[32]; |
| GstCaps *caps; |
| |
| self->send_initial_events = FALSE; |
| |
| g_snprintf (s_id, sizeof (s_id), "dtlsenc-%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)); |
| check_connection_timeout = TRUE; |
| } |
| |
| GST_TRACE_OBJECT (self, "src loop: releasing lock"); |
| |
| ret = gst_pad_push (self->src, buffer); |
| if (check_connection_timeout) |
| gst_dtls_connection_check_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)); |
| } |
| } |
| |
| static GstFlowReturn |
| sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) |
| { |
| GstDtlsEnc *self = GST_DTLS_ENC (parent); |
| GstMapInfo map_info; |
| gint ret; |
| |
| gst_buffer_map (buffer, &map_info, GST_MAP_READ); |
| |
| if (map_info.size) { |
| ret = |
| gst_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 %" |
| G_GSIZE_FORMAT " B", ret, map_info.size); |
| } |
| } |
| |
| gst_buffer_unmap (buffer, &map_info); |
| |
| gst_buffer_unref (buffer); |
| |
| return GST_FLOW_OK; |
| } |
| |
| |
| static gboolean |
| sink_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| gboolean ret = FALSE; |
| |
| switch (GST_EVENT_TYPE (event)) { |
| /* Drop segment, stream-start as we will push our own from the src pad |
| * task. |
| * FIXME: do we need any information from upstream for pushing our own? */ |
| case GST_EVENT_SEGMENT: |
| case GST_EVENT_STREAM_START: |
| gst_event_unref (event); |
| ret = TRUE; |
| break; |
| default: |
| ret = gst_pad_event_default (pad, parent, event); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static void |
| on_key_received (GstDtlsConnection * connection, gpointer key, guint cipher, |
| guint auth, GstDtlsEnc * self) |
| { |
| gpointer key_dup; |
| gchar *key_str; |
| |
| g_return_if_fail (GST_IS_DTLS_ENC (self)); |
| g_return_if_fail (GST_IS_DTLS_CONNECTION (connection)); |
| |
| self->srtp_cipher = cipher; |
| self->srtp_auth = auth; |
| |
| key_dup = g_memdup (key, GST_DTLS_SRTP_MASTER_KEY_LENGTH); |
| |
| if (self->encoder_key) { |
| gst_buffer_unref (self->encoder_key); |
| self->encoder_key = NULL; |
| } |
| |
| self->encoder_key = |
| gst_buffer_new_wrapped (key_dup, GST_DTLS_SRTP_MASTER_KEY_LENGTH); |
| |
| key_str = g_base64_encode (key, GST_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 (GstDtlsConnection * connection, gconstpointer data, gint length, |
| GstDtlsEnc * 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_queue_push_tail (&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); |
| } |