diff --git a/src/gst-plugins/rtpendpoint/CMakeLists.txt b/src/gst-plugins/rtpendpoint/CMakeLists.txt index 52b45ea2..9a8cfabf 100644 --- a/src/gst-plugins/rtpendpoint/CMakeLists.txt +++ b/src/gst-plugins/rtpendpoint/CMakeLists.txt @@ -9,11 +9,17 @@ set(KMS_RTPENDPOINT_SOURCES kmsrtpendpoint.c kmssocketutils.c kmsrandom.c + kmsrtpfilterutils.c + kmssiprtpendpoint.c + kmssiprtpsession.c + kmssipsrtpsession.c + kmsrtpendpoint-plugin-init.c ) set(KMS_RTPENDPOINT_HEADERS kmsrtpendpoint.h kmssocketutils.h + kmssiprtpendpoint.h ) set(ENUM_HEADERS diff --git a/src/gst-plugins/rtpendpoint/kms-rtp-enumtypes.h b/src/gst-plugins/rtpendpoint/kms-rtp-enumtypes.h new file mode 100644 index 00000000..ed244b16 --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kms-rtp-enumtypes.h @@ -0,0 +1,14 @@ +#ifndef __KMS_KMS_RTP_ENUMTYPES_ENUM_TYPES_H__ +#define __KMS_KMS_RTP_ENUMTYPES_ENUM_TYPES_H__ + +#include + +G_BEGIN_DECLS + +/* enumerations from "kmsrtpsdescryptosuite.h" */ +GType kms_rtp_sdes_crypto_suite_get_type (void); +#define KMS_TYPE_RTP_SDES_CRYPTO_SUITE (kms_rtp_sdes_crypto_suite_get_type()) + +G_END_DECLS + +#endif /* __KMS_KMS_RTP_ENUMTYPES_ENUM_TYPES_H__ */ diff --git a/src/gst-plugins/rtpendpoint/kmsrtpconnection.c b/src/gst-plugins/rtpendpoint/kmsrtpconnection.c index 5dcd58a4..9cbd073c 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpconnection.c +++ b/src/gst-plugins/rtpendpoint/kmsrtpconnection.c @@ -17,6 +17,8 @@ #include "kmsrtpconnection.h" #include "kmssocketutils.h" +#include "kmsrtpfilterutils.h" +#include #define GST_CAT_DEFAULT kmsrtpconnection GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -388,3 +390,134 @@ kms_rtp_connection_interface_init (KmsIRtpConnectionInterface * iface) iface->set_latency_callback = kms_rtp_base_connection_set_latency_callback; iface->collect_latency_stats = kms_rtp_connection_collect_latency_stats; } + + +void +kms_sip_rtp_connection_retrieve_sockets (KmsRtpConnection *conn, GSocket **rtp, GSocket **rtcp) +{ + if (conn != NULL) { + // Retrieve the sockets + *rtcp = g_object_ref (conn->priv->rtcp_socket); + *rtp = g_object_ref (conn->priv->rtp_socket); + + // remove sockets from multiudpsink and udpsrc so that they are disconnected from previous endpoint + // so that they are not released on previoues endpoint finalization + g_object_set (conn->priv->rtp_udpsink, "close-socket", FALSE, NULL); + g_object_set (conn->priv->rtcp_udpsink, "close-socket", FALSE, NULL); + g_object_set (conn->priv->rtp_udpsrc, "close-socket", FALSE, NULL); + g_object_set (conn->priv->rtcp_udpsrc, "close-socket", FALSE, NULL); + +// g_object_set (conn->priv->rtp_udpsink, "socket", NULL); +// g_object_set (conn->priv->rtp_udpsrc, "socket", NULL); +// g_object_set (conn->priv->rtcp_udpsink, "socket", NULL); +// g_object_set (conn->priv->rtcp_udpsrc, "socket", NULL); + + conn->priv->rtcp_socket = NULL; + conn->priv->rtp_socket = NULL; + } +} + + +void +kms_sip_rtp_connection_add_probes (KmsRtpConnection *conn, SipFilterSsrcInfo* filter_info, gulong *rtp_probe_id, gulong *rtcp_probe_id) +{ + KmsRtpConnectionPrivate *priv = conn->priv; + + // If we are reusing sockets, it is possible that packets from old connection (old ssrcs) arrive to the sockets + // They should be avoided as they may auto setup the new connection for old SSRCs, preventing the new connection to succed + GstPad *pad; + + pad = gst_element_get_static_pad (priv->rtcp_udpsrc, "src"); + + *rtcp_probe_id = kms_sip_rtp_filter_setup_probe_rtcp (pad, filter_info); + gst_object_unref (pad); + + pad = gst_element_get_static_pad (priv->rtp_udpsrc, "src"); + *rtp_probe_id = kms_sip_rtp_filter_setup_probe_rtp (pad, filter_info); + gst_object_unref (pad); +} + + +KmsRtpConnection * +kms_sip_rtp_connection_new (guint16 min_port, guint16 max_port, gboolean use_ipv6, GSocket *rtp_sock, GSocket *rtcp_sock, + SipFilterSsrcInfo* filter_info, gulong *rtp_probe_id, gulong *rtcp_probe_id) +{ + // TODO: When this integrated in kms-elements we can modify kms_rtp_connection_new to allow espcifying + // the gstreamer object factory for the connection, so that we can simplify this function + GObject *obj; + KmsRtpConnection *conn; + KmsRtpConnectionPrivate *priv; + GSocketFamily socket_family; + + obj = g_object_new (KMS_TYPE_RTP_CONNECTION, NULL); + conn = KMS_RTP_CONNECTION (obj); + priv = conn->priv; + + if (use_ipv6) { + socket_family = G_SOCKET_FAMILY_IPV6; + } else { + socket_family = G_SOCKET_FAMILY_IPV4; + } + + // TODO: This is what we need to update on kms_rtp_connection-new + if ((rtp_sock != NULL) && (rtcp_sock != NULL)) { + priv->rtp_socket = rtp_sock; + priv->rtcp_socket = rtcp_sock; + } else { + // ^^^^^^^^^^^^^^^^^^^^^^^^^ + // TODO: Up to here + if (!kms_rtp_connection_get_rtp_rtcp_sockets + (&priv->rtp_socket, &priv->rtcp_socket, min_port, max_port, + socket_family)) { + GST_ERROR_OBJECT (obj, "Cannot get ports"); + g_object_unref (obj); + return NULL; + } + } + + priv->rtp_udpsink = gst_element_factory_make ("multiudpsink", NULL); + priv->rtp_udpsrc = gst_element_factory_make ("udpsrc", NULL); + + priv->rtcp_udpsink = gst_element_factory_make ("multiudpsink", NULL); + priv->rtcp_udpsrc = gst_element_factory_make ("udpsrc", NULL); + + kms_sip_rtp_connection_add_probes (conn, filter_info, rtp_probe_id, rtcp_probe_id); + + g_object_set (priv->rtp_udpsink, "socket", priv->rtp_socket, + "sync", FALSE, "async", FALSE, NULL); + g_object_set (priv->rtp_udpsrc, "socket", priv->rtp_socket, "auto-multicast", + FALSE, NULL); + + g_object_set (priv->rtcp_udpsink, "socket", priv->rtcp_socket, + "sync", FALSE, "async", FALSE, NULL); + g_object_set (priv->rtcp_udpsrc, "socket", priv->rtcp_socket, + "auto-multicast", FALSE, NULL); + + + kms_i_rtp_connection_connected_signal (KMS_I_RTP_CONNECTION (conn)); + + + + return conn; +} + +void +kms_sip_rtp_connection_release_probes (KmsRtpConnection *conn, gulong rtp_probe_id, gulong rtcp_probe_id) +{ + KmsRtpConnectionPrivate *priv; + GstPad *pad; + + priv = conn->priv; + + // Release RTCP probe + pad = gst_element_get_static_pad (priv->rtcp_udpsrc, "src"); + kms_sip_rtp_filter_release_probe_rtcp (pad, rtcp_probe_id); + gst_object_unref (pad); + + // Release RTP probe + pad = gst_element_get_static_pad (priv->rtp_udpsrc, "src"); + kms_sip_rtp_filter_release_probe_rtp (pad, rtp_probe_id); + gst_object_unref (pad); +} + + diff --git a/src/gst-plugins/rtpendpoint/kmsrtpconnection.h b/src/gst-plugins/rtpendpoint/kmsrtpconnection.h index 17c8772b..b86a65e0 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpconnection.h +++ b/src/gst-plugins/rtpendpoint/kmsrtpconnection.h @@ -19,6 +19,9 @@ #define __KMS_RTP_CONNECTION_H__ #include "kmsrtpbaseconnection.h" +#include "kmsrtpfilterutils.h" +#include +#include G_BEGIN_DECLS #define KMS_TYPE_RTP_CONNECTION \ @@ -53,5 +56,18 @@ GType kms_rtp_connection_get_type (void); KmsRtpConnection *kms_rtp_connection_new (guint16 min_port, guint16 max_port, gboolean use_ipv6); +KmsRtpConnection * +kms_sip_rtp_connection_new (guint16 min_port, guint16 max_port, gboolean use_ipv6, GSocket *rtp_sock, GSocket *rtcp_sock, + SipFilterSsrcInfo* filter_info, gulong *rtp_probe_id, gulong *rtcp_probe_id); + +void +kms_sip_rtp_connection_add_probes (KmsRtpConnection *conn, SipFilterSsrcInfo* filter_info, gulong *rtp_probe_id, gulong *rtcp_probe_id); + +void +kms_sip_rtp_connection_release_probes (KmsRtpConnection *conn, gulong rtp_probe_id, gulong rtcp_probe_id); + +void kms_sip_rtp_connection_retrieve_sockets (KmsRtpConnection *conn, GSocket **rtp, GSocket **rtcp); + + G_END_DECLS #endif /* __KMS_RTP_CONNECTION_H__ */ diff --git a/src/gst-plugins/rtpendpoint/kmsrtpendpoint-plugin-init.c b/src/gst-plugins/rtpendpoint/kmsrtpendpoint-plugin-init.c new file mode 100644 index 00000000..20d43aff --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmsrtpendpoint-plugin-init.c @@ -0,0 +1,41 @@ +/* + * (C) Copyright 2013 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifdef HAVE_CONFIG_H +# include +#endif + + +#include "kmsrtpendpoint.h" +#include "kmssiprtpendpoint.h" + +#define RTP_PLUGIN_NAME "rtpendpoint" +#define SIP_RTP_PLUGIN_NAME "siprtpendpoint" + +gboolean +kms_rtp_endpoint_plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, RTP_PLUGIN_NAME, GST_RANK_NONE, KMS_TYPE_RTP_ENDPOINT) && + gst_element_register (plugin, SIP_RTP_PLUGIN_NAME, GST_RANK_NONE, KMS_TYPE_SIP_RTP_ENDPOINT); +} + + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + kmsrtpendpoint, + "Kurento rtp endpoint", + kms_rtp_endpoint_plugin_init, VERSION, GST_LICENSE_UNKNOWN, + "Kurento Elements", "http://kurento.com/") diff --git a/src/gst-plugins/rtpendpoint/kmsrtpendpoint.c b/src/gst-plugins/rtpendpoint/kmsrtpendpoint.c index 200af354..091de482 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpendpoint.c +++ b/src/gst-plugins/rtpendpoint/kmsrtpendpoint.c @@ -1106,7 +1106,7 @@ kms_rtp_endpoint_class_init (KmsRtpEndpointClass * klass) "RtpEndpoint", "RTP/Stream/RtpEndpoint", "Rtp Endpoint element", - "José Antonio Santos Cadenas "); + "Jose Antonio Santos Cadenas "); GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, PLUGIN_NAME, 0, PLUGIN_NAME); base_sdp_endpoint_class = KMS_BASE_SDP_ENDPOINT_CLASS (klass); @@ -1171,17 +1171,3 @@ kms_rtp_endpoint_init (KmsRtpEndpoint * self) "max-video-recv-bandwidth", 0, NULL); /* FIXME: remove max-video-recv-bandwidth when it b=AS:X is in the SDP offer */ } - -gboolean -kms_rtp_endpoint_plugin_init (GstPlugin * plugin) -{ - return gst_element_register (plugin, PLUGIN_NAME, GST_RANK_NONE, - KMS_TYPE_RTP_ENDPOINT); -} - -GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, - GST_VERSION_MINOR, - kmsrtpendpoint, - "Kurento rtp endpoint", - kms_rtp_endpoint_plugin_init, VERSION, GST_LICENSE_UNKNOWN, - "Kurento Elements", "http://kurento.com/") diff --git a/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c new file mode 100644 index 00000000..e4c15ccd --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c @@ -0,0 +1,493 @@ +/* + * (C) Copyright 2015 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +#include "kmsrtpfilterutils.h" +#include +#include +#include + + +static void +adjust_filter_info_ts_info (SipFilterSsrcInfo* filter_info, guint16 seq, guint32 ts) +{ + g_rec_mutex_lock (&filter_info->mutex); + if (filter_info->last_seq < seq) { + filter_info->last_ts_delta = (ts - filter_info->last_ts) / (seq - filter_info->last_seq); + filter_info->last_seq = seq; + filter_info->last_ts = ts; + } + g_rec_mutex_unlock (&filter_info->mutex); +} + +static gboolean +check_ssrc (guint32 ssrc, SipFilterSsrcInfo* filter_info, guint16 seq, guint32 ts) +{ + GST_DEBUG("Check_ssrc %u, expected %u, current %u", ssrc, filter_info->expected, filter_info->current); + if (filter_info->expected == 0) { + gboolean result, init; + + init = FALSE; + g_rec_mutex_lock (&filter_info->mutex); + if (filter_info->expected == 0) { + // Not yet received first SSRC + init = TRUE; + + // If SSRC is in list of old ones, we discard buffer + if (g_list_index(filter_info->old, GUINT_TO_POINTER(ssrc)) != -1) { + result = TRUE; + } else { + // If not, first SSRc will be fixed for pipeline in current media connection + filter_info->expected = ssrc; + filter_info->current = ssrc; + filter_info->last_seq = seq; + filter_info->last_ts = ts; + result = FALSE; + } + } + g_rec_mutex_unlock (&filter_info->mutex); + if (init) { + // If not init then a concurrent thread has just initted. + return result; + } + } + + if (ssrc == filter_info->current) { + adjust_filter_info_ts_info (filter_info, seq, ts); + + // SSRC is expected one we let buffer to continue processing + return FALSE; + } else { + // If SSRC is in list of old ones, we discard buffer + g_rec_mutex_lock (&filter_info->mutex); + if (g_list_index(filter_info->old, GUINT_TO_POINTER(ssrc)) != -1) { + g_rec_mutex_unlock (&filter_info->mutex); + return TRUE; + } + g_rec_mutex_unlock (&filter_info->mutex); + + + // SSRC is not expected one, but also does not seem late packets from previous media connections + // We can assume peer has just switched SSRC (VoIP PBX?), and buffer should be affected + // In this case SSRC in buffer should be changed to expected one so that pipeline does not complain + // and stop processing due to not linked error + + // We cannot let the buffer continue as it would pause the streaming task, + // But this is fixed later, by now we let the buffer go + return FALSE; + } +} + +static gboolean +check_ssrc_rtcp (guint32 ssrc, SipFilterSsrcInfo* filter_info) +{ + if (filter_info->expected == 0) { + gboolean result, init; + + init = FALSE; + g_rec_mutex_lock (&filter_info->mutex); + if (filter_info->expected == 0) { + // Not yet received first SSRC + init = TRUE; + + // If SSRC is in list of old ones, we discard buffer + if (g_list_index(filter_info->old, GUINT_TO_POINTER(ssrc)) != -1) { + result = TRUE; + } else { + // If not, first SSRc will be fixed for pipeline in current media connection + filter_info->expected = ssrc; + filter_info->current = ssrc; + result = FALSE; + } + } + g_rec_mutex_unlock (&filter_info->mutex); + if (init) { + // If not init then a concurrent thread has just initted. + return result; + } + } + + if (ssrc == filter_info->current) { + // SSRC is expected one we let buffer to continue processing + return FALSE; + } else { + return TRUE; + } +} + +static void +fix_rtp_buffer_voip_switched_ssrc (GstRTPBuffer *rtp_buffer, SipFilterSsrcInfo* filter_info) +{ + guint32 seq_number = gst_rtp_buffer_get_seq (rtp_buffer); + guint32 ts = gst_rtp_buffer_get_timestamp (rtp_buffer); + guint32 fixed_ts; + + // Fix SSRC to keep pipelime happy + gst_rtp_buffer_set_ssrc (rtp_buffer, filter_info->expected); + + // We fix timestamping to keep kmsrtpsynchronizer happy + if (filter_info->jump_ts != 0) { + gint64 aux_ts; + + aux_ts = ts; + aux_ts -= filter_info->jump_ts; + + if (aux_ts < 0) + aux_ts += G_MAXUINT32; + + fixed_ts = aux_ts; + gst_rtp_buffer_set_timestamp (rtp_buffer, fixed_ts); + } + + GST_DEBUG ("Fixing RTP info: ssrc %u, sequence %u and ts %u", filter_info->expected, seq_number, fixed_ts); +} + +static gint64 +calculate_jump_ts (SipFilterSsrcInfo* filter_info, guint32 seq, guint32 ts) +{ + gint64 new_jump; + + new_jump = filter_info->jump_ts; + if (filter_info->last_ts < ts) { + new_jump += (ts - filter_info->last_ts) - filter_info->last_ts_delta; + } else { + new_jump -= (filter_info->last_ts - ts) + filter_info->last_ts_delta; + } + + return new_jump; +} + +static GstPadProbeReturn +filter_ssrc_rtp_buffer (GstBuffer *buffer, SipFilterSsrcInfo* filter_info) +{ + GstRTPBuffer rtp_buffer = GST_RTP_BUFFER_INIT; + GstPadProbeReturn result = GST_PAD_PROBE_OK; + + if (gst_rtp_buffer_map (buffer, GST_MAP_READWRITE, &rtp_buffer)) { + GST_DEBUG ("filter old ssrc RTP buffer"); + guint32 checked_ssrc = gst_rtp_buffer_get_ssrc (&rtp_buffer); + guint32 seq_number = gst_rtp_buffer_get_seq (&rtp_buffer); + guint32 ts = gst_rtp_buffer_get_timestamp (&rtp_buffer); + + GST_DEBUG("Filtering RTP buffer with ssrc %u and sequence %u, and ts %u", checked_ssrc, seq_number, ts); + if (check_ssrc (checked_ssrc, filter_info, seq_number, ts)) { + GST_INFO ("RTP packet dropped from a previous RTP flow with SSRC %u", checked_ssrc); + gst_rtp_buffer_unmap (&rtp_buffer); + return GST_PAD_PROBE_DROP; + } else { + // We are pushing an EXPECTED SSRC, so after its processing this probe is no longer needed + GST_DEBUG ("filter old ssrc forwarded buffer %u", checked_ssrc); + if (checked_ssrc != filter_info->expected) { + gboolean ssrc_switched = FALSE; + + // SSRC not expected and not from last stream, stream switching is happening + if (checked_ssrc != filter_info->current) { + g_rec_mutex_lock (&filter_info->mutex); + if (checked_ssrc != filter_info->current) { + // Old stream must be filtered out from now on + filter_info->old = g_list_append (filter_info->old, GUINT_TO_POINTER(filter_info->current)); + + // Get sure next Buffers will be easily checked for new current stream + filter_info->current = checked_ssrc; + // Calculate ts jump so that we can adapt RTP buffers and SR for new stream to keep kmsrtpsynchronizer happy + filter_info->jump_ts = calculate_jump_ts (filter_info, seq_number, ts); + + GST_DEBUG ("SSRC switched, calculated ts jump %ld, last ts %u, current ts %u, seq number: %u", filter_info->jump_ts, filter_info->last_ts, ts, seq_number); + ssrc_switched = TRUE; + } + g_rec_mutex_unlock (&filter_info->mutex); + } + + // We have just switched SSRCs some anomalous situation + // Kind of hack: we will change SSRC in buffer to original one so that + // pipeline does not get disrupted and media continue flowing to already connected elements + if (filter_info->media_session == VIDEO_RTP_SESSION) { + // if this is video, this is an unexpected media switching, to allow further media comm + // We just correct SSRC and let buffer continue flow, otherwise streaming task would be stopped + GST_DEBUG("Switching SSRC, original: %u, switched: %u", filter_info->expected, checked_ssrc); + gst_rtp_buffer_set_ssrc (&rtp_buffer, filter_info->expected); + } else if (filter_info->media_session == AUDIO_RTP_SESSION) { + // If this is audio, it may be a VoIP situation of media switching. + // IT is marked by marker and SSRC is switched and timestamping process is restarted to a random point + if (ssrc_switched) { + GST_INFO("VoIP RTP flow internally switched, old SSRC %u, new one %u", filter_info->expected, checked_ssrc); + } + + // We fix ssrc and timestamping + fix_rtp_buffer_voip_switched_ssrc (&rtp_buffer, filter_info); + + // Let buffer continue processing + result = GST_PAD_PROBE_OK; + } + + } + gst_rtp_buffer_unmap (&rtp_buffer); + return result; + } + } + + GST_WARNING ("Buffer not mapped to RTP"); + return GST_PAD_PROBE_OK; +} + + +static gboolean +filter_buffer (GstBuffer ** buffer, guint idx, gpointer user_data) +{ + SipFilterSsrcInfo* filter_info = (SipFilterSsrcInfo*)user_data; + + if (filter_ssrc_rtp_buffer(*buffer, filter_info) == GST_PAD_PROBE_DROP) + *buffer = NULL; + + return TRUE; +} + +static GstPadProbeReturn +filter_ssrc_rtp (GstPad *pad, GstPadProbeInfo *info, gpointer user_data) +{ + SipFilterSsrcInfo* filter_info = (SipFilterSsrcInfo*) user_data; + GstBuffer *buffer; + + GST_DEBUG ("Filtering RTP packets from previous flows to this receiver"); + buffer = GST_PAD_PROBE_INFO_BUFFER (info); + if (buffer != NULL) { + GST_DEBUG ("RTP buffer received from Filtering RTP packets from previous flows to this receiver"); + + return filter_ssrc_rtp_buffer (buffer, filter_info); + } else { + GstBufferList *buffer_list; + + buffer_list = gst_pad_probe_info_get_buffer_list (info); + + if (buffer_list != NULL) { + GST_DEBUG ("filter old ssrc buffer list RTP"); + if (!gst_buffer_list_foreach(buffer_list, filter_buffer, user_data)) + GST_WARNING("Filtering buffer list for old ssrc failed"); + } + } + return GST_PAD_PROBE_OK; +} + + +static guint32 +fix_rtcp_ts (SipFilterSsrcInfo* filter_info, guint32 rtptime) +{ + gint64 aux_ts = rtptime; + + // We fix timestamping to keep kmsrtpsynchronizer happy + if (filter_info->jump_ts != 0) { + aux_ts -= filter_info->jump_ts; + + if (aux_ts < 0) + aux_ts += G_MAXUINT32; + } + + GST_DEBUG ("Fixing RTCP TS: original %u, fixed %ld", rtptime, aux_ts); + return aux_ts; +} + +static GstPadProbeReturn +filter_ssrc_rtcp (GstPad *pad, GstPadProbeInfo *info, gpointer user_data) +{ + SipFilterSsrcInfo* filter_info = (SipFilterSsrcInfo*)user_data; + GstBuffer *buffer; + + buffer = GST_PAD_PROBE_INFO_BUFFER (info); + + GstRTCPBuffer rtcp_buffer = GST_RTCP_BUFFER_INIT; + + GST_DEBUG ("Filtering RTCP buffer from previous flows to this receiver"); + if (gst_rtcp_buffer_map (buffer, GST_MAP_READWRITE, &rtcp_buffer)) { + GstRTCPPacket packet; + gboolean has_packet; + + has_packet = gst_rtcp_buffer_get_first_packet (&rtcp_buffer, &packet); + + GST_DEBUG ("Filtering RTCP packets from previous flows to this receiver"); + + if (has_packet) { + GstRTCPType packet_type = gst_rtcp_packet_get_type (&packet); + + if (packet_type == GST_RTCP_TYPE_SR) { + guint32 ssrc, rtptime, packet_count, octet_count; + guint64 ntptime; + + gst_rtcp_packet_sr_get_sender_info (&packet, &ssrc, &ntptime, &rtptime, &packet_count, &octet_count); + GST_DEBUG ("Got RTCP ssrc: %u ntptime: %lu, rtptime: %u, packet count: %u, octect_count: %u", ssrc, ntptime, rtptime, packet_count, octet_count); + if (check_ssrc_rtcp (ssrc, filter_info)) { + GST_DEBUG("Unexpected SSRC RTCP packet received: %u, expected: %u", ssrc, filter_info->expected); + // If any packet in a buffer has an unexpectd SSRc, all buffer can be dropped + gst_rtcp_buffer_unmap (&rtcp_buffer); + return GST_PAD_PROBE_DROP; + } else { + if (ssrc != filter_info->expected) { + if (filter_info->media_session == AUDIO_RTP_SESSION) { + while (has_packet) { + switch (packet_type) { + case GST_RTCP_TYPE_SR: + { + guint32 fixed_rtptime = fix_rtcp_ts (filter_info, rtptime); + GST_DEBUG ("Fixed RTCP ssrc: %u ntptime: %lu, rtptime: %u, packet count: %u, octect_count: %u", filter_info->expected, ntptime, fixed_rtptime, packet_count, octet_count); + gst_rtcp_packet_sr_set_sender_info (&packet, filter_info->expected, ntptime, fixed_rtptime, packet_count, octet_count); + } + break; + case GST_RTCP_TYPE_BYE: + gst_rtcp_packet_bye_add_ssrc (&packet, filter_info->expected); + break; + + // Feedback packets, should let them go + case GST_RTCP_TYPE_APP: + case GST_RTCP_TYPE_RR: + case GST_RTCP_TYPE_XR: + case GST_RTCP_TYPE_RTPFB: + case GST_RTCP_TYPE_PSFB: + break; + case GST_RTCP_TYPE_SDES: + case GST_RTCP_TYPE_INVALID: + default: + gst_rtcp_packet_remove (&packet); + break; + } + has_packet = gst_rtcp_packet_move_to_next (&packet); + packet_type = gst_rtcp_packet_get_type (&packet); + } + } else if (filter_info->media_session == VIDEO_RTP_SESSION) { + while (has_packet) { + if (packet_type == GST_RTCP_TYPE_SR) { + gst_rtcp_packet_sr_set_sender_info (&packet, filter_info->expected, ntptime, rtptime, packet_count, octet_count); + } else { + gst_rtcp_packet_remove (&packet); + } + has_packet = gst_rtcp_packet_move_to_next (&packet); + packet_type = gst_rtcp_packet_get_type (&packet); + } + } + } + gst_rtcp_buffer_unmap (&rtcp_buffer); + return GST_PAD_PROBE_OK; + } + } + } + gst_rtcp_buffer_unmap (&rtcp_buffer); + } + + return GST_PAD_PROBE_OK; +} + + +gulong +kms_sip_rtp_filter_setup_probe_rtp (GstPad *pad, SipFilterSsrcInfo* filter_info) +{ + if (filter_info != NULL) { + GST_DEBUG("Installing RTP probe for %s", GST_ELEMENT_NAME(gst_pad_get_parent_element (pad))); + return gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST | GST_PAD_PROBE_TYPE_PUSH | GST_PAD_PROBE_TYPE_PULL, + (GstPadProbeCallback) filter_ssrc_rtp, GUINT_TO_POINTER(filter_info), NULL); + } else { + GST_DEBUG("No RTP probe installed for %s", GST_ELEMENT_NAME(gst_pad_get_parent_element (pad))); + return 0; + } +} + +gulong +kms_sip_rtp_filter_setup_probe_rtcp (GstPad *pad, SipFilterSsrcInfo* filter_info) +{ + if (filter_info != NULL) { + GST_DEBUG("Installing RTCP probe for %s", GST_ELEMENT_NAME(gst_pad_get_parent_element (pad))); + return gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, + (GstPadProbeCallback) filter_ssrc_rtcp, filter_info, NULL); + } else { + GST_DEBUG("No RTCP probe installed for %s", GST_ELEMENT_NAME(gst_pad_get_parent_element (pad))); + return 0; + } +} + + +void +kms_sip_rtp_filter_release_probe_rtp (GstPad *pad, gulong probe_id) +{ + if (probe_id == 0) + return; + + GST_DEBUG("Removing RTP probe for %s", GST_ELEMENT_NAME(gst_pad_get_parent_element (pad))); + gst_pad_remove_probe (pad, probe_id); + +} + +void +kms_sip_rtp_filter_release_probe_rtcp (GstPad *pad, gulong probe_id) +{ + if (probe_id == 0) + return; + + GST_DEBUG("Removing RTCP probe for %s", GST_ELEMENT_NAME(gst_pad_get_parent_element (pad))); + gst_pad_remove_probe (pad, probe_id); + +} + +static void +filtering_info_add_ssrc_info (GList** target, GList* source) +{ + GList* it = source; + + while (it != NULL) { + GST_DEBUG("add_ssrc_info, setting old ssrc %u", GPOINTER_TO_UINT(it->data)); + *target = g_list_append (*target, it->data); + it = it->next; + } +} + +SipFilterSsrcInfo* +kms_sip_rtp_filter_create_filtering_info (guint32 expected, SipFilterSsrcInfo* previous, guint32 media_session, gboolean continue_stream) +{ + SipFilterSsrcInfo* info = g_new (SipFilterSsrcInfo, 1); + + // Initialize filter_info + info->expected = expected; + info->current = expected; + info->old = NULL; + info->media_session = media_session; + info->last_seq = 0; + info->last_ts = 0; + info->last_ts_delta = 0; + info->jump_ts = 0; + + g_rec_mutex_init (&info->mutex); + + GST_DEBUG("create_filtering_info, setting expected ssrc: %u", expected); + if (previous != NULL) { + GST_DEBUG("create_filtering_info, setting old ssrc: %u", previous->expected); + // If we have previous media connection, we need to take note of previous SSRC to discard late packets + if ((previous->expected != 0) && !continue_stream) { + info->old = g_list_append (info->old, GUINT_TO_POINTER(previous->expected)); + } + // We add all previous media connections old SSRC as old ones for current media connection (just in case old packets happen) + filtering_info_add_ssrc_info (&(info->old), previous->old); + } + + return info; +} + +void kms_sip_rtp_filter_release_filtering_info (SipFilterSsrcInfo* info) +{ + g_rec_mutex_clear (&info->mutex); + if (info->old != NULL) { + g_list_free (info->old); + } + g_free (info); +} + + diff --git a/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.h b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.h new file mode 100644 index 00000000..408732c8 --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.h @@ -0,0 +1,57 @@ +/* + * (C) Copyright 2015 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +#ifndef KMSRTPFILTERUTILS_H_ +#define KMSRTPFILTERUTILS_H_ + +#include + +typedef struct _SipFilterSsrcInfo SipFilterSsrcInfo; + +struct _SipFilterSsrcInfo { + guint32 expected; + guint32 current; + GList* old; + guint32 media_session; + GRecMutex mutex; + + // Needed for fixing RTP streams internally switched on VoiP applications (MEDIA_AUDIO) + guint32 last_ts; + guint16 last_seq; + guint32 last_ts_delta; + gint64 jump_ts; +}; + +gulong +kms_sip_rtp_filter_setup_probe_rtp (GstPad *pad, SipFilterSsrcInfo* filter_info); + +gulong +kms_sip_rtp_filter_setup_probe_rtcp (GstPad *pad, SipFilterSsrcInfo* filter_info); + +void +kms_sip_rtp_filter_release_probe_rtp (GstPad *pad, gulong probe_id); + +void +kms_sip_rtp_filter_release_probe_rtcp (GstPad *pad, gulong probe_id); + +SipFilterSsrcInfo* +kms_sip_rtp_filter_create_filtering_info (guint32 expected, SipFilterSsrcInfo* previous, guint32 media_session, gboolean continue_stream); + +void kms_sip_rtp_filter_release_filtering_info (SipFilterSsrcInfo* info); + +#endif /* KMSRTPFILTERUTILS_H_ */ diff --git a/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c b/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c new file mode 100644 index 00000000..56732f9b --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c @@ -0,0 +1,767 @@ +/* + * (C) Copyright 2013 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include "kmssiprtpendpoint.h" +#include "kmssiprtpsession.h" +#include "kmssipsrtpsession.h" +#include "kmsrtpconnection.h" +#include "kmssrtpconnection.h" +#include +#include +#include + +#define PLUGIN_NAME "siprtpendpoint" + +#define DEFAULT_AUDIO_SSRC 0 +#define DEFAULT_VIDEO_SSRC 0 + + +GST_DEBUG_CATEGORY_STATIC (kms_sip_rtp_endpoint_debug); +#define GST_CAT_DEFAULT kms_sip_rtp_endpoint_debug + +#define kms_sip_rtp_endpoint_parent_class parent_class +G_DEFINE_TYPE (KmsSipRtpEndpoint, kms_sip_rtp_endpoint, KMS_TYPE_RTP_ENDPOINT); + + +#define KMS_SIP_RTP_ENDPOINT_GET_PRIVATE(obj) ( \ + G_TYPE_INSTANCE_GET_PRIVATE ( \ + (obj), \ + KMS_TYPE_SIP_RTP_ENDPOINT, \ + KmsSipRtpEndpointPrivate \ + ) \ +) + + +typedef struct _KmsSipRtpEndpointCloneData KmsSipRtpEndpointCloneData; + + +struct _KmsSipRtpEndpointCloneData +{ + guint32 local_audio_ssrc; + guint32 local_video_ssrc; + + SipFilterSsrcInfo* audio_filter_info; + SipFilterSsrcInfo* video_filter_info; + + GHashTable *conns; +}; + +struct _KmsSipRtpEndpointPrivate +{ + gboolean *use_sdes_cache; + + GList *sessionData; +}; + +/* Properties */ +enum +{ + PROP_0, + PROP_AUDIO_SSRC, + PROP_VIDEO_SSRC +}; + + +/* Signals and args */ +enum +{ + /* signals */ + SIGNAL_CLONE_TO_NEW_EP, + + LAST_SIGNAL +}; + +static guint obj_signals[LAST_SIGNAL] = { 0 }; + +static KmsBaseSdpEndpointClass *base_sdp_endpoint_type; + + +/*----------- Session cloning ---------------*/ + +static void +kms_sip_rtp_endpoint_clone_rtp_session (GstElement * rtpbin, guint sessionId, guint32 ssrc, gchar *rtpbin_pad_name) +{ + GObject *rtpSession; + GstPad *pad; + + /* Create RtpSession requesting the pad */ + pad = gst_element_get_request_pad (rtpbin, rtpbin_pad_name); + g_object_unref (pad); + + g_signal_emit_by_name (rtpbin, "get-internal-session", sessionId, &rtpSession); + if (rtpSession != NULL) { + g_object_set (rtpSession, "internal-ssrc", ssrc, NULL); + } + + g_object_unref(rtpSession); +} +static GstElement* +kms_sip_rtp_endpoint_get_rtpbin (KmsSipRtpEndpoint * self) +{ + GstElement *result = NULL; + GList* rtpEndpointChildren = GST_BIN_CHILDREN(GST_BIN(self)); + + while (rtpEndpointChildren != NULL) { + gchar* objectName = gst_element_get_name (GST_ELEMENT(rtpEndpointChildren->data)); + + if (g_str_has_prefix (objectName, "rtpbin")) { + result = GST_ELEMENT(rtpEndpointChildren->data); + g_free (objectName); + break; + } + g_free (objectName); + rtpEndpointChildren = rtpEndpointChildren->next; + } + return result; +} + +static KmsSipRtpEndpointCloneData* +kms_sip_rtp_endpoint_get_clone_data (GList *sessionData) +{ + if (sessionData == NULL) + return NULL; + return ((KmsSipRtpEndpointCloneData*)sessionData->data); +} + +static void +kms_sip_rtp_endpoint_preserve_rtp_session_data (KmsSipRtpSession *ses, + GHashTable *conns) +{ + KMS_SIP_RTP_SESSION_CLASS(G_OBJECT_GET_CLASS(ses))->clone_connections (ses,conns); +} + +static void +kms_sip_rtp_endpoint_preserve_srtp_session_data (KmsSipSrtpSession *ses, + GHashTable *conns) +{ + KMS_SIP_SRTP_SESSION_CLASS(G_OBJECT_GET_CLASS(ses))->clone_connections (ses,conns); +} + +static void +kms_sip_rtp_endpoint_clone_session (KmsSipRtpEndpoint * self, KmsSdpSession ** sess) +{ + GstElement *rtpbin = kms_sip_rtp_endpoint_get_rtpbin (self); + GList *sessionToClone = self->priv->sessionData; + + if (rtpbin != NULL) { + gboolean is_srtp = FALSE; + + is_srtp = KMS_IS_SIP_SRTP_SESSION (*sess); + // TODO: Multisession seems not used on RTPEndpoint, anyway we are doing something probably incorrect + // once multisession is used, that is to assume that creation order of sessions are maintained among all + // endpoints, and so order can be used to correlate internal rtp sessions. + KmsBaseRtpSession *clonedSes = KMS_BASE_RTP_SESSION (*sess); + guint32 ssrc; + GHashTable *conns; + + conns = kms_sip_rtp_endpoint_get_clone_data(sessionToClone)->conns; + + /* TODO: think about this when multiple audio/video medias */ + // Audio + // Clone SSRC + ssrc = kms_sip_rtp_endpoint_get_clone_data(sessionToClone)->local_audio_ssrc; + clonedSes->local_audio_ssrc = ssrc; + kms_sip_rtp_endpoint_clone_rtp_session (rtpbin, AUDIO_RTP_SESSION, ssrc, AUDIO_RTPBIN_SEND_RTP_SINK); + + // Video + // Clone SSRC + ssrc = kms_sip_rtp_endpoint_get_clone_data(sessionToClone)->local_video_ssrc; + clonedSes->local_video_ssrc = ssrc; + kms_sip_rtp_endpoint_clone_rtp_session (rtpbin, VIDEO_RTP_SESSION, ssrc, VIDEO_RTPBIN_SEND_RTP_SINK); + + if (is_srtp) { + kms_sip_rtp_endpoint_preserve_srtp_session_data (KMS_SIP_SRTP_SESSION(*sess), conns); + } else { + kms_sip_rtp_endpoint_preserve_rtp_session_data (KMS_SIP_RTP_SESSION(*sess), conns); + } + } +} + + + +static gboolean isUseSdes (KmsSipRtpEndpoint * self) +{ + if (self->priv->use_sdes_cache == NULL) { + gboolean useSdes; + + g_object_get (G_OBJECT(self), "use-sdes", &useSdes, NULL); + self->priv->use_sdes_cache = g_malloc(sizeof(gboolean)); + *self->priv->use_sdes_cache = useSdes; + } + return *self->priv->use_sdes_cache; +} + + +static void +kms_sip_rtp_endpoint_set_addr (KmsSipRtpEndpoint * self) +{ + GList *ips, *l; + gboolean done = FALSE; + + ips = nice_interfaces_get_local_ips (FALSE); + for (l = ips; l != NULL && !done; l = l->next) { + GInetAddress *addr; + gboolean is_ipv6 = FALSE; + + GST_DEBUG_OBJECT (self, "Check local address: %s", (const gchar*)l->data); + addr = g_inet_address_new_from_string (l->data); + + if (G_IS_INET_ADDRESS (addr)) { + switch (g_inet_address_get_family (addr)) { + case G_SOCKET_FAMILY_INVALID: + case G_SOCKET_FAMILY_UNIX: + /* Ignore this addresses */ + break; + case G_SOCKET_FAMILY_IPV6: + is_ipv6 = TRUE; + case G_SOCKET_FAMILY_IPV4: + { + gchar *addr_str; + gboolean use_ipv6; + + g_object_get (self, "use-ipv6", &use_ipv6, NULL); + if (is_ipv6 != use_ipv6) { + GST_DEBUG_OBJECT (self, "Skip address (wanted IPv6: %d)", use_ipv6); + break; + } + + addr_str = g_inet_address_to_string (addr); + if (addr_str != NULL) { + g_object_set (self, "addr", addr_str, NULL); + g_free (addr_str); + done = TRUE; + } + break; + } + } + } + + if (G_IS_OBJECT (addr)) { + g_object_unref (addr); + } + } + + g_list_free_full (ips, g_free); + + if (!done) { + GST_WARNING_OBJECT (self, "Addr not set"); + } +} + +static void +kms_sip_rtp_endpoint_create_session_internal (KmsBaseSdpEndpoint * base_sdp, + gint id, KmsSdpSession ** sess) +{ + KmsIRtpSessionManager *manager = KMS_I_RTP_SESSION_MANAGER (base_sdp); + KmsSipRtpEndpoint *self = KMS_SIP_RTP_ENDPOINT (base_sdp); + gboolean use_ipv6 = FALSE; + KmsSipRtpEndpointCloneData *data = NULL; + + /* Get ip address now that session is being created */ + kms_sip_rtp_endpoint_set_addr (self); + + g_object_get (self, "use-ipv6", &use_ipv6, NULL); + if (isUseSdes(self)) { + KmsSipSrtpSession *sip_srtp_ses = kms_sip_srtp_session_new (base_sdp, id, manager, use_ipv6); + *sess = KMS_SDP_SESSION (sip_srtp_ses); + if (self->priv->sessionData != NULL) { + data = (KmsSipRtpEndpointCloneData*) self->priv->sessionData->data; + sip_srtp_ses->audio_filter_info = data->audio_filter_info; + sip_srtp_ses->video_filter_info = data->video_filter_info; + } + } else { + KmsSipRtpSession *sip_rtp_ses = kms_sip_rtp_session_new (base_sdp, id, manager, use_ipv6); + *sess = KMS_SDP_SESSION (sip_rtp_ses); + if (self->priv->sessionData != NULL) { + data = (KmsSipRtpEndpointCloneData*) self->priv->sessionData->data; + sip_rtp_ses->audio_filter_info = data->audio_filter_info; + sip_rtp_ses->video_filter_info = data->video_filter_info; + } + } + + /* Chain up */ + base_sdp_endpoint_type->create_session_internal (base_sdp, id, sess); +// KMS_BASE_SDP_ENDPOINT_CLASS( +// (KMS_RTP_ENDPOINT_CLASS +// (kms_sip_rtp_endpoint_parent_class)->parent_class)-> +// ->create_session_internal (base_sdp, id, sess); + + if (self->priv->sessionData != NULL) { + kms_sip_rtp_endpoint_clone_session (self, sess); + } + +} + +/* Internal session management end */ + + +static void +kms_sip_rtp_endpoint_create_media_handler (KmsBaseSdpEndpoint * base_sdp, + const gchar * media, KmsSdpMediaHandler ** handler) +{ + KMS_BASE_SDP_ENDPOINT_CLASS(kms_sip_rtp_endpoint_parent_class)->create_media_handler (base_sdp, media, handler); + +} + + + + +/* Configure media SDP begin */ +static gboolean +kms_sip_rtp_endpoint_configure_media (KmsBaseSdpEndpoint * base_sdp_endpoint, + KmsSdpSession * sess, KmsSdpMediaHandler * handler, GstSDPMedia * media) +{ + gboolean ret = TRUE; + + /* Chain up */ + ret = KMS_BASE_SDP_ENDPOINT_CLASS(kms_sip_rtp_endpoint_parent_class)-> + configure_media (base_sdp_endpoint, sess, handler, media); + return ret; +} + +/* Configure media SDP end */ + +//static void +//kms_sip_rtp_endpoint_set_connection_filter_probe (KmsSipRtpEndpoint *self, KmsIRtpConnection *conn, KmsBaseRtpSession *ses, guint32 expected_ssrc) +//{ +// if (KMS_IS_RTP_CONNECTION (conn)) { +// gulong rtp_probe, rtcp_probe; +// KmsSipRtpSession *sip_ses = KMS_SIP_RTP_SESSION (ses); +// KmsRtpConnection *rtp_conn = KMS_RTP_CONNECTION (conn); +// +// kms_sip_rtp_connection_add_probes (rtp_conn, filter_info, &rtp_probe, &rtcp_probe); +// KMS_SIP_RTP_SESSION_CLASS(G_OBJECT_GET_CLASS(ses))->store_rtp_filtering_info (sip_ses, rtp_conn, rtp_probe, rtcp_probe); +// } else if (KMS_IS_SRTP_CONNECTION (conn)) { +// gulong rtp_probe, rtcp_probe; +// KmsSipSrtpSession *sip_ses = KMS_SIP_SRTP_SESSION (ses); +// KmsSrtpConnection *rtp_conn = KMS_SRTP_CONNECTION (conn); +// +// kms_sip_srtp_connection_add_probes (rtp_conn, filter_info, &rtp_probe, &rtcp_probe); +// KMS_SIP_SRTP_SESSION_CLASS(G_OBJECT_GET_CLASS(ses))->store_rtp_filtering_info (sip_ses, rtp_conn, rtp_probe, rtcp_probe); +// } +//} + + +//static void +//kms_sip_rtp_endpoint_set_filter_probes (KmsSipRtpEndpoint *self, guint32 expected_audio_ssrc, guint32 expected_video_ssrc) +//{ +// GHashTable * sessions = kms_base_sdp_endpoint_get_sessions (KMS_BASE_SDP_ENDPOINT(self)); +// GList *sessionKeys = g_hash_table_get_keys (sessions); +// gint i; +// KmsIRtpConnection *conn; +// +// // In fact SipRtpEndpoint should have only one session, if not, this loop should be revised +// for (i = 0; i < g_hash_table_size(sessions); i++) { +// gpointer sesKey = sessionKeys->data; +// KmsBaseRtpSession *ses = KMS_BASE_RTP_SESSION (g_hash_table_lookup (sessions, sesKey)); +// +// // AUDIO +// conn = g_hash_table_lookup (ses->conns, AUDIO_RTP_SESSION_STR); +// kms_sip_rtp_endpoint_set_connection_filter_probe (self, conn, ses, expected_audio_ssrc); +// +// // VIDEO +// conn = g_hash_table_lookup (ses->conns, VIDEO_RTP_SESSION_STR); +// kms_sip_rtp_endpoint_set_connection_filter_probe (self, conn, ses, expected_video_ssrc); +// } +// g_list_free(sessionKeys); +//} + +static guint +ssrc_str_to_uint (const gchar * ssrc_str) +{ + gint64 val; + guint ssrc = 0; + + val = g_ascii_strtoll (ssrc_str, NULL, 10); + if (val > G_MAXUINT32) { + GST_ERROR ("SSRC %" G_GINT64_FORMAT " not valid", val); + } else { + ssrc = val; + } + + return ssrc; +} + +static gchar * +sdp_media_get_ssrc_str (const GstSDPMedia * media) +{ + gchar *ssrc = NULL; + const gchar *val; + GRegex *regex; + GMatchInfo *match_info = NULL; + + val = gst_sdp_media_get_attribute_val (media, "ssrc"); + if (val == NULL) { + return NULL; + } + + regex = g_regex_new ("^(?[0-9]+)(.*)?$", 0, 0, NULL); + g_regex_match (regex, val, 0, &match_info); + g_regex_unref (regex); + + if (g_match_info_matches (match_info)) { + ssrc = g_match_info_fetch_named (match_info, "ssrc"); + } + g_match_info_free (match_info); + + return ssrc; +} + +static guint32 +kms_sip_rtp_endpoint_get_ssrc (const GstSDPMedia* media) +{ + gchar *ssrc_str; + guint32 ssrc = 0; + + ssrc_str = sdp_media_get_ssrc_str (media); + if (ssrc_str == NULL) { + return 0; + } + + ssrc = ssrc_str_to_uint (ssrc_str); + g_free (ssrc_str); + + return ssrc; +} + + +static gboolean +kms_sip_rtp_endpoint_get_expected_ssrc (const GstSDPMessage *sdp, guint32 *audio_ssrc, guint32 *video_ssrc) +{ + const GstSDPMedia *media; + guint idx = 0; + guint num_medias = 0; + gboolean result = TRUE; + + // We are expecting an SDP answer with just one audio media and just one video media + // If this was to change, this function would need reconsidering + num_medias = gst_sdp_message_medias_len (sdp); + while (idx < num_medias) { + const gchar* media_name; + + media = gst_sdp_message_get_media (sdp, idx); + media_name = gst_sdp_media_get_media (media); + GST_DEBUG("Found media %s", media_name); + + if (g_strcmp0 (AUDIO_STREAM_NAME, media_name) == 0) { + *audio_ssrc = kms_sip_rtp_endpoint_get_ssrc (media); + } else if (g_strcmp0 (VIDEO_STREAM_NAME, media_name) == 0) { + *video_ssrc = kms_sip_rtp_endpoint_get_ssrc (media); + } else { + result = FALSE; + } + idx++; + } + + return result; +} + + + +static gboolean +kms_sip_rtp_endpoint_process_answer (KmsBaseSdpEndpoint * ep, + const gchar * sess_id, GstSDPMessage * answer) +{ +// KmsSipRtpEndpoint *self = KMS_SIP_RTP_ENDPOINT(ep); +// guint32 expected_audio_ssrc = 0; +// guint32 expected_video_ssrc = 0; +// +// kms_sip_rtp_endpoint_get_expected_ssrc (answer, &expected_audio_ssrc, &expected_video_ssrc); +// kms_sip_rtp_endpoint_set_filter_probes (self); + return KMS_BASE_SDP_ENDPOINT_CLASS(kms_sip_rtp_endpoint_parent_class)->process_answer (ep, sess_id, answer); +} + +static void +kms_sip_rtp_endpoint_start_transport_send (KmsBaseSdpEndpoint *base_sdp_endpoint, + KmsSdpSession *sess, gboolean offerer) +{ + KMS_BASE_SDP_ENDPOINT_CLASS(kms_sip_rtp_endpoint_parent_class)->start_transport_send (base_sdp_endpoint, sess, offerer); + +} + +static KmsSipRtpEndpointCloneData* +kms_sip_rtp_endpoint_create_clone_data (KmsSipRtpEndpoint *self, KmsBaseRtpSession *ses, guint32 audio_ssrc, guint32 video_ssrc, gboolean continue_audio_stream, gboolean continue_video_stream) +{ + KmsSipRtpEndpointCloneData *data = g_malloc(sizeof (KmsSipRtpEndpointCloneData)); + SipFilterSsrcInfo* audio_filter_info = NULL; + SipFilterSsrcInfo* video_filter_info = NULL; + + data->local_audio_ssrc = ses->local_audio_ssrc; + data->local_video_ssrc = ses->local_video_ssrc; + + if (KMS_IS_SIP_RTP_SESSION (ses)) { + KmsSipRtpSession* sip_ses = KMS_SIP_RTP_SESSION (ses); + + GST_DEBUG ("kms_sip_rtp_endpoint_create_clone_data audio filter %p, video filter %p", sip_ses->audio_filter_info, sip_ses->video_filter_info); + audio_filter_info = kms_sip_rtp_filter_create_filtering_info (audio_ssrc, sip_ses->audio_filter_info, AUDIO_RTP_SESSION, continue_audio_stream); + video_filter_info = kms_sip_rtp_filter_create_filtering_info (video_ssrc, sip_ses->video_filter_info, VIDEO_RTP_SESSION, continue_video_stream); + } else if (KMS_IS_SIP_SRTP_SESSION (ses)) { + KmsSipSrtpSession* sip_ses = KMS_SIP_SRTP_SESSION (ses); + + GST_DEBUG ("kms_sip_rtp_endpoint_create_clone_data srtp audio filter %p, video filter %p", sip_ses->audio_filter_info, sip_ses->video_filter_info); + audio_filter_info = kms_sip_rtp_filter_create_filtering_info (audio_ssrc, sip_ses->audio_filter_info, AUDIO_RTP_SESSION, continue_audio_stream); + video_filter_info = kms_sip_rtp_filter_create_filtering_info (video_ssrc, sip_ses->video_filter_info, VIDEO_RTP_SESSION, continue_video_stream); + } + + data->audio_filter_info = audio_filter_info; + data->video_filter_info = video_filter_info; + data->conns = g_hash_table_ref(ses->conns); + + return data; +} + +static void +kms_sip_rtp_endpoint_free_clone_data (GList *data) +{ + GList *it = data; + + while (it != NULL) { + KmsSipRtpEndpointCloneData* data = (KmsSipRtpEndpointCloneData*) it->data; + + if (data->conns != NULL) { + g_hash_table_unref(data->conns); + data->conns = NULL; + } + + it = it->next; + } + + g_list_free_full (data, g_free); +} + +static void +kms_sip_rtp_endpoint_clone_to_new_ep (KmsSipRtpEndpoint *self, KmsSipRtpEndpoint *cloned, const gchar* sdp_str, gboolean continue_audio_stream, gboolean continue_video_stream) +{ + GHashTable * sessions = kms_base_sdp_endpoint_get_sessions (KMS_BASE_SDP_ENDPOINT(self)); + GList *sessionKeys = g_hash_table_get_keys (sessions); + gint i; + GList *sessionsData = NULL; + guint32 remote_audio_ssrc = 0; + guint32 remote_video_ssrc = 0; + GstSDPMessage *sdp; + + gst_sdp_message_new (&sdp); + if (gst_sdp_message_parse_buffer ((const guint8*) sdp_str, strlen (sdp_str), sdp) != GST_SDP_OK) + GST_ERROR("Could not parse SDP answer"); + + if (!kms_sip_rtp_endpoint_get_expected_ssrc (sdp, &remote_audio_ssrc, &remote_video_ssrc)) { + GST_INFO("Could not find SSRCs on SDP answer, assuming first SSRC different from previous is valid"); + } + + gst_sdp_message_free (sdp); + + // In fact SipRtpEndpoint should have only one session, if not, this loop should be revised + for (i = 0; i < g_hash_table_size(sessions); i++) { + gpointer sesKey = sessionKeys->data; + KmsBaseRtpSession *ses = KMS_BASE_RTP_SESSION (g_hash_table_lookup (sessions, sesKey)); + KmsSipRtpEndpointCloneData *data = kms_sip_rtp_endpoint_create_clone_data (self, ses, remote_audio_ssrc, remote_video_ssrc, continue_audio_stream, continue_video_stream); + + sessionsData = g_list_append (sessionsData, (gpointer)data); + } + g_list_free(sessionKeys); + + KMS_ELEMENT_LOCK (cloned); + if (cloned->priv->sessionData != NULL) { + kms_sip_rtp_endpoint_free_clone_data (cloned->priv->sessionData); + } + cloned->priv->sessionData = sessionsData; + KMS_ELEMENT_UNLOCK (cloned); +} + +static void +kms_sip_rtp_endpoint_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + KmsSipRtpEndpoint *self = KMS_SIP_RTP_ENDPOINT (object); + + KMS_ELEMENT_LOCK (self); + + switch (prop_id) { + KmsSipRtpEndpointCloneData* clone; + guint32 ssrc; + + case PROP_AUDIO_SSRC: + clone = kms_sip_rtp_endpoint_get_clone_data (self->priv->sessionData); + ssrc = g_value_get_uint (value); + + if (clone != NULL) { + clone->local_audio_ssrc = ssrc; + } + break; + case PROP_VIDEO_SSRC: + clone = kms_sip_rtp_endpoint_get_clone_data (self->priv->sessionData); + ssrc = g_value_get_uint (value); + + if (clone != NULL) { + clone->local_video_ssrc = ssrc; + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + KMS_ELEMENT_UNLOCK (self); +} + +static void +kms_sip_rtp_endpoint_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + KmsSipRtpEndpoint *self = KMS_SIP_RTP_ENDPOINT (object); + + KMS_ELEMENT_LOCK (self); + + switch (prop_id) { + KmsSipRtpEndpointCloneData* clone; + + case PROP_AUDIO_SSRC: + clone = kms_sip_rtp_endpoint_get_clone_data (self->priv->sessionData); + + if (clone != NULL) { + g_value_set_uint (value, clone->local_audio_ssrc); + } + break; + case PROP_VIDEO_SSRC: + clone = kms_sip_rtp_endpoint_get_clone_data (self->priv->sessionData); + + if (clone != NULL) { + g_value_set_uint (value, clone->local_video_ssrc); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + KMS_ELEMENT_UNLOCK (self); +} + +static void +kms_sip_rtp_endpoint_finalize (GObject * object) +{ + KmsSipRtpEndpoint *self = KMS_SIP_RTP_ENDPOINT (object); + + GST_DEBUG_OBJECT (self, "finalize"); + + if (self->priv->use_sdes_cache != NULL) + g_free (self->priv->use_sdes_cache); + + if (self->priv->sessionData != NULL) + kms_sip_rtp_endpoint_free_clone_data(self->priv->sessionData); + + GST_DEBUG ("Finalizing Sip RTP Endpoint %p", object); + + /* chain up */ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + + +static void +kms_sip_rtp_endpoint_class_init (KmsSipRtpEndpointClass * klass) +{ + GObjectClass *gobject_class; + KmsBaseSdpEndpointClass *base_sdp_endpoint_class; + GstElementClass *gstelement_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->set_property = kms_sip_rtp_endpoint_set_property; + gobject_class->get_property = kms_sip_rtp_endpoint_get_property; + gobject_class->finalize = kms_sip_rtp_endpoint_finalize; + + gstelement_class = GST_ELEMENT_CLASS (klass); + gst_element_class_set_details_simple (gstelement_class, + "SipRtpEndpoint", + "SIP RTP/Stream/RtpEndpoint", + "Sip Rtp Endpoint element", + "Saul Pablo Labajo Izquierdo "); + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, PLUGIN_NAME, 0, PLUGIN_NAME); + + base_sdp_endpoint_class = KMS_BASE_SDP_ENDPOINT_CLASS (klass); + base_sdp_endpoint_class->create_session_internal = + kms_sip_rtp_endpoint_create_session_internal; + base_sdp_endpoint_class->start_transport_send = + kms_sip_rtp_endpoint_start_transport_send; + + base_sdp_endpoint_class->process_answer = + kms_sip_rtp_endpoint_process_answer; + + /* Media handler management */ + base_sdp_endpoint_class->create_media_handler = + kms_sip_rtp_endpoint_create_media_handler; + + + base_sdp_endpoint_class->configure_media = kms_sip_rtp_endpoint_configure_media; + + klass->clone_to_new_ep = kms_sip_rtp_endpoint_clone_to_new_ep; + + g_object_class_install_property (gobject_class, PROP_AUDIO_SSRC, + g_param_spec_uint ("audio-ssrc", + "Audio SSRC", "Set to assign the local audio SSRC", + 0, G_MAXUINT, DEFAULT_AUDIO_SSRC, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_VIDEO_SSRC, + g_param_spec_uint ("video-ssrc", + "Video SSRC", "Set to assign the local video SSRC", + 0, G_MAXUINT, DEFAULT_VIDEO_SSRC, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + + obj_signals[SIGNAL_CLONE_TO_NEW_EP] = + g_signal_new ("clone-to-new-ep", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_ACTION | G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (KmsSipRtpEndpointClass, clone_to_new_ep), NULL, NULL, + NULL, G_TYPE_NONE, 4, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN); + + + g_type_class_add_private (klass, sizeof (KmsSipRtpEndpointPrivate)); + + // Kind of hack to use GLib type system in an unusual way: + // RTPEndpoint implementation is very final in the sense that it does not + // intend to be subclassed, this makes difficult to reimplement virtual + // methods that need chaining up like create_session_internal. The only way + // is to call directly the virtual method in the grandparent class + // Well, there is another way, to enrich base class implementation to allow + // subclasses to reimplement the virtual method (in the particular case of + // create_session_internal just need to skip session creation if already created. + // TODO: When integrate on kms-elements get rid off this hack changing kms_rtp_endpoint_create_session_internal + GType type = g_type_parent (g_type_parent (G_TYPE_FROM_CLASS (klass))); + // TODO: This introduces a memory leak, this is reserved and never freed, but it is just a pointer (64 bits) + // A possible alternative would be to implement the class_finalize method + gpointer typePointer = g_type_class_ref(type); + base_sdp_endpoint_type = KMS_BASE_SDP_ENDPOINT_CLASS(typePointer); +} + +/* TODO: not add abs-send-time extmap */ + +static void +kms_sip_rtp_endpoint_init (KmsSipRtpEndpoint * self) +{ + self->priv = KMS_SIP_RTP_ENDPOINT_GET_PRIVATE (self); + + self->priv->use_sdes_cache = NULL; + self->priv->sessionData = NULL; + + GST_DEBUG ("Initialized RTP Endpoint %p", self); +} diff --git a/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.h b/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.h new file mode 100644 index 00000000..9d5045bd --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.h @@ -0,0 +1,68 @@ +/* + * (C) Copyright 2013 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef __KMS_SIP_RTP_ENDPOINT_H__ +#define __KMS_SIP_RTP_ENDPOINT_H__ + +#include +#include +#include + +G_BEGIN_DECLS +/* #defines don't like whitespacey bits */ +#define KMS_TYPE_SIP_RTP_ENDPOINT \ + (kms_sip_rtp_endpoint_get_type()) +#define KMS_SIP_RTP_ENDPOINT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),KMS_TYPE_SIP_RTP_ENDPOINT,KmsSipRtpEndpoint)) +#define KMS_SIP_RTP_ENDPOINT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),KMS_TYPE_SIP_RTP_ENDPOINT,KmsSipRtpEndpointClass)) +#define KMS_IS_SIP_RTP_ENDPOINT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),KMS_TYPE_SIP_RTP_ENDPOINT)) +#define KMS_IS_SIP_RTP_ENDPOINT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),KMS_TYPE_SIP_RTP_ENDPOINT)) +#define KMS_SIP_RTP_ENDPOINT_CAST(obj) ((KmsSipRtpEndpoint*)(obj)) +typedef struct _KmsSipRtpEndpoint KmsSipRtpEndpoint; +typedef struct _KmsSipRtpEndpointClass KmsSipRtpEndpointClass; +typedef struct _KmsSipRtpEndpointPrivate KmsSipRtpEndpointPrivate; + +#define KMS_SIP_RTP_ENDPOINT_LOCK(elem) \ + (g_rec_mutex_lock (&KMS_SIP_RTP_ENDPOINT_CAST ((elem))->media_mutex)) +#define KMS_SIP_RTP_ENDPOINT_UNLOCK(elem) \ + (g_rec_mutex_unlock (&KMS_SIP_RTP_ENDPOINT_CAST ((elem))->media_mutex)) + +struct _KmsSipRtpEndpoint +{ + KmsRtpEndpoint parent; + + KmsSipRtpEndpointPrivate *priv; +}; + +struct _KmsSipRtpEndpointClass +{ + + KmsRtpEndpointClass parent_class; + + + /* signals */ + void (*clone_to_new_ep) (KmsSipRtpEndpoint *obj, KmsSipRtpEndpoint *cloned, const gchar* sdp, gboolean, gboolean); +}; + +GType kms_sip_rtp_endpoint_get_type (void); + +gboolean kms_sip_rtp_endpoint_plugin_init (GstPlugin * plugin); + +G_END_DECLS +#endif /* __KMS_SIP_RTP_ENDPOINT_H__ */ diff --git a/src/gst-plugins/rtpendpoint/kmssiprtpsession.c b/src/gst-plugins/rtpendpoint/kmssiprtpsession.c new file mode 100644 index 00000000..53aa237e --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmssiprtpsession.c @@ -0,0 +1,297 @@ +/* + * (C) Copyright 2015 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "kmssiprtpsession.h" +#include "kmssrtpconnection.h" +#include "kmsrtpfilterutils.h" +#include +#include +#include + +#define GST_DEFAULT_NAME "kmssiprtpsession" +#define GST_CAT_DEFAULT kms_sip_rtp_session_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define kms_sip_rtp_session_parent_class parent_class +G_DEFINE_TYPE (KmsSipRtpSession, kms_sip_rtp_session, KMS_TYPE_RTP_SESSION); + +#define KMS_SIP_RTP_SESSION_GET_PRIVATE(obj) ( \ + G_TYPE_INSTANCE_GET_PRIVATE ( \ + (obj), \ + KMS_TYPE_SIP_RTP_SESSION, \ + KmsSipRtpSessionPrivate \ + ) \ +) + +typedef struct _KmsSipRtpProbeFilteringInfo KmsSipRtpProbeFilteringInfo; + +struct _KmsSipRtpProbeFilteringInfo +{ + KmsRtpConnection *conn; + gulong rtp_probe; + gulong rtcp_probe; +}; + +struct _KmsSipRtpSessionPrivate +{ + GHashTable *conns; + GList *rtp_filtering_info; +}; + + +KmsSipRtpSession * +kms_sip_rtp_session_new (KmsBaseSdpEndpoint * ep, guint id, + KmsIRtpSessionManager * manager, gboolean use_ipv6) +{ + GObject *obj; + KmsSipRtpSession *self; + + obj = g_object_new (KMS_TYPE_SIP_RTP_SESSION, NULL); + self = KMS_SIP_RTP_SESSION (obj); + self->audio_filter_info = NULL; + self->video_filter_info = NULL; + KMS_RTP_SESSION_CLASS (G_OBJECT_GET_CLASS (self))->post_constructor + (KMS_RTP_SESSION(self), ep, id, manager, use_ipv6); + + return self; +} + +/* Connection management begin */ + + +static void +kms_sip_rtp_session_store_rtp_filtering_info (KmsSipRtpSession *ses, KmsRtpConnection *conn, gulong rtp_probe, gulong rtcp_probe) +{ + KmsSipRtpProbeFilteringInfo *info; + + info = g_try_malloc0 (sizeof (KmsSipRtpProbeFilteringInfo)); + if (info == NULL) { + GST_WARNING ("No memory, some leak may happen"); + } + + info->conn = conn; + info->rtp_probe = rtp_probe; + info->rtcp_probe = rtcp_probe; + + ses->priv->rtp_filtering_info = g_list_append (ses->priv->rtp_filtering_info, info); +} + +static void +kms_sip_rtp_session_retrieve_sockets (GHashTable *conns, const GstSDPMedia * media, GSocket **rtp, GSocket **rtcp) +{ + gchar *media_key; + KmsRtpBaseConnection *conn; + + const gchar *media_str = gst_sdp_media_get_media (media); + + /* TODO: think about this when multiple audio/video medias */ + if (g_strcmp0 (AUDIO_STREAM_NAME, media_str) == 0) { + media_key = AUDIO_RTP_SESSION_STR; + } else if (g_strcmp0 (VIDEO_STREAM_NAME, media_str) == 0) { + media_key = VIDEO_RTP_SESSION_STR; + } else { + media_key = ""; + } + + conn = KMS_RTP_BASE_CONNECTION (g_hash_table_lookup (conns, media_key)); + + if (KMS_IS_RTP_CONNECTION (conn)) { + KmsRtpConnection *rtpConn = KMS_RTP_CONNECTION (conn); + + kms_sip_rtp_connection_retrieve_sockets (rtpConn, rtp, rtcp); + } else if (KMS_IS_SRTP_CONNECTION (conn)) { + KmsSrtpConnection *srtpConn = KMS_SRTP_CONNECTION (conn); + + kms_sip_srtp_connection_retrieve_sockets (srtpConn, rtp, rtcp); + } +} + +static SipFilterSsrcInfo* +km_sip_rtp_session_setup_filter_info (KmsSipRtpSession *self, const gchar *media_str) +{ + SipFilterSsrcInfo* filter_info; + guint32 media_type; + + if (g_strcmp0 (VIDEO_STREAM_NAME, media_str) == 0) { + filter_info = self->video_filter_info; + media_type = VIDEO_RTP_SESSION; + }else if (g_strcmp0 (AUDIO_STREAM_NAME, media_str) == 0) { + filter_info = self->audio_filter_info; + media_type = AUDIO_RTP_SESSION; + } + + if (filter_info == NULL) { + filter_info = kms_sip_rtp_filter_create_filtering_info (0, NULL, media_type, TRUE); + if (media_type == AUDIO_RTP_SESSION) { + self->audio_filter_info = filter_info; + } else if (media_type == VIDEO_RTP_SESSION) { + self->video_filter_info = filter_info; + } + } + + return filter_info; +} + +static KmsIRtpConnection * +kms_sip_rtp_session_create_connection (KmsBaseRtpSession * base_rtp_sess, + const GstSDPMedia * media, const gchar * name, guint16 min_port, + guint16 max_port) +{ + KmsSipRtpSession *self = KMS_SIP_RTP_SESSION(base_rtp_sess); + + // TODO: Here is where we need to interacto to clone connecitons from a previous session + // kms_rtp_connection_new creates a KmsRtpConnection, and creates its multiudpsink and udpsrc + // and creates the sockets for RTP and RTCP iterating to fid free ports + // We need to define a kms_sip_rtp_connection_new that if no previous session to clone should + // behave exactly as kms_rtp_connection_new and if not should create the connection recovering the + // sockets from the previous session (the equivalent connection). correlation should be done using ssrc and media type + GSocket *rtp_sock = NULL; + GSocket *rtcp_sock = NULL; + SipFilterSsrcInfo* filter_info = NULL; + gulong rtp_probe = 0; + gulong rtcp_probe = 0; + const gchar *media_str; + KmsRtpConnection *conn; + + if (self->priv->conns != NULL) { + // If we are recovering a previous session, due to a renegotation (consecutive processAnswer) + kms_sip_rtp_session_retrieve_sockets (self->priv->conns, media, &rtp_sock, &rtcp_sock); + + } + + media_str = gst_sdp_media_get_media (media); + + filter_info = km_sip_rtp_session_setup_filter_info (self, media_str); + + conn = kms_sip_rtp_connection_new (min_port, max_port, + KMS_RTP_SESSION (base_rtp_sess)->use_ipv6, rtp_sock, rtcp_sock, filter_info, &rtp_probe, &rtcp_probe); + + if ((rtp_probe != 0) || (rtcp_probe != 0)) { + kms_sip_rtp_session_store_rtp_filtering_info (self, conn, rtp_probe, rtcp_probe); + } + + return KMS_I_RTP_CONNECTION (conn); +} + +static void +kms_sip_rtp_session_clone_connections (KmsSipRtpSession *self, GHashTable *conns) +{ + self->priv->conns = g_hash_table_ref (conns); +} + +/* Connection management end */ + +static void +kms_sip_rtp_session_post_constructor (KmsRtpSession * self, + KmsBaseSdpEndpoint * ep, guint id, KmsIRtpSessionManager * manager, + gboolean use_ipv6) +{ + KmsBaseRtpSession *base_rtp_session = KMS_BASE_RTP_SESSION (self); + + self->use_ipv6 = use_ipv6; + KMS_BASE_RTP_SESSION_CLASS + (kms_sip_rtp_session_parent_class)->post_constructor (base_rtp_session, ep, + id, manager); +} + +static void +kms_sip_rtp_session_init (KmsSipRtpSession * self) +{ + self->priv = KMS_SIP_RTP_SESSION_GET_PRIVATE (self); + + self->priv->conns = NULL; + self->priv->rtp_filtering_info = NULL; + + GST_DEBUG ("Initialized Kms Sip RTP Session %p", self); +} + +static void +kms_sip_rtp_session_free_filter_info (gpointer data) +{ + KmsSipRtpProbeFilteringInfo *info = (KmsSipRtpProbeFilteringInfo*) data; + + GST_DEBUG ("Releasing RTP/RTCP filtering probes"); + kms_sip_rtp_connection_release_probes (info->conn, info->rtp_probe, info->rtcp_probe); + g_free (data); +} + + +static void +kms_sip_rtp_session_finalize (GObject *object) +{ + KmsSipRtpSession *self = KMS_SIP_RTP_SESSION(object); + + if (self->priv->conns != NULL) { + g_hash_table_unref (self->priv->conns); + } + + // Release RTP/RTCP filtering info + if (self->priv->rtp_filtering_info != NULL) + g_list_free_full (self->priv->rtp_filtering_info, kms_sip_rtp_session_free_filter_info); + + if (self->audio_filter_info != NULL) { + kms_sip_rtp_filter_release_filtering_info (self->audio_filter_info); + } + if (self->video_filter_info != NULL) { + kms_sip_rtp_filter_release_filtering_info (self->video_filter_info); + + + } + GST_DEBUG ("Finalized RTP Session %p", object); + + /* chain up */ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +kms_sip_rtp_session_class_init (KmsSipRtpSessionClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + KmsBaseRtpSessionClass *base_rtp_session_class; + KmsRtpSessionClass *rtp_session_class; + + gobject_class = G_OBJECT_CLASS(klass); + gobject_class->finalize = kms_sip_rtp_session_finalize; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, GST_DEFAULT_NAME, 0, + GST_DEFAULT_NAME); + + rtp_session_class = KMS_RTP_SESSION_CLASS(klass); + + rtp_session_class->post_constructor = kms_sip_rtp_session_post_constructor; + + base_rtp_session_class = KMS_BASE_RTP_SESSION_CLASS (klass); + /* Connection management */ + base_rtp_session_class->create_connection = kms_sip_rtp_session_create_connection; + + klass->clone_connections = kms_sip_rtp_session_clone_connections; + klass->store_rtp_filtering_info = kms_sip_rtp_session_store_rtp_filtering_info; + + gst_element_class_set_details_simple (gstelement_class, + "SipRtpSession", + "Generic", + "Base bin to manage elements related with a SIP RTP session.", + "Saul Pablo Labajo Izquierdo "); + + g_type_class_add_private (klass, sizeof (KmsSipRtpSessionPrivate)); + +} diff --git a/src/gst-plugins/rtpendpoint/kmssiprtpsession.h b/src/gst-plugins/rtpendpoint/kmssiprtpsession.h new file mode 100644 index 00000000..42753736 --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmssiprtpsession.h @@ -0,0 +1,75 @@ +/* + * (C) Copyright 2015 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __KMS_SIP_RTP_SESSION_H__ +#define __KMS_SIP_RTP_SESSION_H__ + +#include +#include +#include "kmsrtpconnection.h" +#include "kmsrtpfilterutils.h" + +G_BEGIN_DECLS + +typedef struct _KmsIRtpSessionManager KmsIRtpSessionManager; + +/* #defines don't like whitespacey bits */ +#define KMS_TYPE_SIP_RTP_SESSION \ + (kms_sip_rtp_session_get_type()) +#define KMS_SIP_RTP_SESSION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),KMS_TYPE_SIP_RTP_SESSION,KmsSipRtpSession)) +#define KMS_SIP_RTP_SESSION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),KMS_TYPE_SIP_RTP_SESSION,KmsSipRtpSessionClass)) +#define KMS_IS_SIP_RTP_SESSION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),KMS_TYPE_SIP_RTP_SESSION)) +#define KMS_IS_SIP_RTP_SESSION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),KMS_TYPE_SIP_RTP_SESSION)) +#define KMS_SIP_RTP_SESSION_CAST(obj) ((KmsSipRtpSession*)(obj)) + +typedef struct _KmsSipRtpSession KmsSipRtpSession; +typedef struct _KmsSipRtpSessionClass KmsSipRtpSessionClass; +typedef struct _KmsSipRtpSessionPrivate KmsSipRtpSessionPrivate; + +struct _KmsSipRtpSession +{ + KmsRtpSession parent; + + gboolean use_ipv6; + + SipFilterSsrcInfo* audio_filter_info; + SipFilterSsrcInfo* video_filter_info; + + KmsSipRtpSessionPrivate *priv; +}; + +struct _KmsSipRtpSessionClass +{ + KmsRtpSessionClass parent_class; + + /* signals */ + void (*clone_connections) (KmsSipRtpSession *self, GHashTable *conns); + + void (*store_rtp_filtering_info) (KmsSipRtpSession *ses, KmsRtpConnection *conn, gulong rtp_probe, gulong rtcp_probe); + +}; + +GType kms_sip_rtp_session_get_type (void); + +KmsSipRtpSession * kms_sip_rtp_session_new (KmsBaseSdpEndpoint * ep, guint id, KmsIRtpSessionManager * manager, gboolean use_ipv6); + +G_END_DECLS +#endif /* __KMS_SIP_RTP_SESSION_H__ */ diff --git a/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c b/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c new file mode 100644 index 00000000..e832fd0b --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c @@ -0,0 +1,299 @@ +/* + * (C) Copyright 2015 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "kmssipsrtpsession.h" +#include "kmsrtpconnection.h" +#include +#include +#include + +#define GST_DEFAULT_NAME "kmssipsrtpsession" +#define GST_CAT_DEFAULT kms_sip_srtp_session_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define kms_sip_srtp_session_parent_class parent_class + +G_DEFINE_TYPE (KmsSipSrtpSession, kms_sip_srtp_session, KMS_TYPE_SRTP_SESSION); + +#define KMS_SIP_SRTP_SESSION_GET_PRIVATE(obj) ( \ + G_TYPE_INSTANCE_GET_PRIVATE ( \ + (obj), \ + KMS_TYPE_SIP_SRTP_SESSION, \ + KmsSipSrtpSessionPrivate \ + ) \ +) + +typedef struct _KmsSipSrtpProbeFilteringInfo KmsSipSrtpProbeFilteringInfo; + +struct _KmsSipSrtpProbeFilteringInfo +{ + KmsSrtpConnection *conn; + gulong rtp_probe; + gulong rtcp_probe; +}; + + +struct _KmsSipSrtpSessionPrivate +{ + GHashTable *conns; + GList *rtp_filtering_info; +}; + + + +KmsSipSrtpSession * +kms_sip_srtp_session_new (KmsBaseSdpEndpoint * ep, guint id, + KmsIRtpSessionManager * manager, gboolean use_ipv6) +{ + GObject *obj; + KmsSipSrtpSession *self; + + obj = g_object_new (KMS_TYPE_SIP_SRTP_SESSION, NULL); + self = KMS_SIP_SRTP_SESSION (obj); + self->audio_filter_info = NULL; + self->video_filter_info = NULL; + KMS_SRTP_SESSION_CLASS (G_OBJECT_GET_CLASS (self))->post_constructor + (KMS_SRTP_SESSION(self), ep, id, manager, use_ipv6); + + return self; +} + +/* Connection management begin */ + +static void +kms_sip_srtp_session_store_rtp_filtering_info (KmsSipSrtpSession *ses, KmsSrtpConnection *conn, gulong rtp_probe, gulong rtcp_probe) +{ + KmsSipSrtpProbeFilteringInfo *info; + + info = g_try_malloc0 (sizeof (KmsSipSrtpProbeFilteringInfo)); + if (info == NULL) { + GST_WARNING ("No memory, some leak may happen"); + } + + info->conn = conn; + info->rtp_probe = rtp_probe; + info->rtcp_probe = rtcp_probe; + + ses->priv->rtp_filtering_info = g_list_append (ses->priv->rtp_filtering_info, info); +} + +static void +kms_sip_srtp_session_retrieve_sockets (GHashTable *conns, const GstSDPMedia * media, GSocket **rtp, GSocket **rtcp) +{ + gchar *media_key; + KmsRtpBaseConnection *conn; + + const gchar *media_str = gst_sdp_media_get_media (media); + + /* TODO: think about this when multiple audio/video medias */ + if (g_strcmp0 (AUDIO_STREAM_NAME, media_str) == 0) { + media_key = AUDIO_RTP_SESSION_STR; + } else if (g_strcmp0 (VIDEO_STREAM_NAME, media_str) == 0) { + media_key = VIDEO_RTP_SESSION_STR; + } else { + media_key = ""; + } + + conn = KMS_RTP_BASE_CONNECTION (g_hash_table_lookup (conns, media_key)); + + if (KMS_IS_RTP_CONNECTION (conn)) { + KmsRtpConnection *rtpConn = KMS_RTP_CONNECTION (conn); + + kms_sip_rtp_connection_retrieve_sockets (rtpConn, rtp, rtcp); + } else if (KMS_IS_SRTP_CONNECTION (conn)) { + KmsSrtpConnection *srtpConn = KMS_SRTP_CONNECTION (conn); + + kms_sip_srtp_connection_retrieve_sockets (srtpConn, rtp, rtcp); + } +} + + + +static SipFilterSsrcInfo* +km_sip_rtp_session_setup_filter_info (KmsSipSrtpSession *self, const gchar *media_str) +{ + SipFilterSsrcInfo* filter_info; + guint32 media_type; + + if (g_strcmp0 (VIDEO_STREAM_NAME, media_str) == 0) { + filter_info = self->video_filter_info; + media_type = VIDEO_RTP_SESSION; + }else if (g_strcmp0 (AUDIO_STREAM_NAME, media_str) == 0) { + filter_info = self->audio_filter_info; + media_type = AUDIO_RTP_SESSION; + } + + if (filter_info == NULL) { + filter_info = kms_sip_rtp_filter_create_filtering_info (0, NULL, media_type, TRUE); + if (media_type == AUDIO_RTP_SESSION) { + self->audio_filter_info = filter_info; + } else if (media_type == VIDEO_RTP_SESSION) { + self->video_filter_info = filter_info; + } + } + + return filter_info; +} + +static KmsIRtpConnection * +kms_sip_srtp_session_create_connection (KmsBaseRtpSession * base_rtp_sess, + const GstSDPMedia * media, const gchar * name, guint16 min_port, + guint16 max_port) +{ + KmsSipSrtpSession *self = KMS_SIP_SRTP_SESSION(base_rtp_sess); + + // TODO: Here is where we need to interacto to clone connecitons from a previous session + // kms_rtp_connection_new creates a KmsRtpConnection, and creates its multiudpsink and udpsrc + // and creates the sockets for RTP and RTCP iterating to fid free ports + // We need to define a kms_sip_rtp_connection_new that if no previous session to clone should + // behave exactly as kms_rtp_connection_new and if not should create the connection recovering the + // sockets from the previous session (the equivalent connection). correlation should be done using ssrc and media type + GSocket *rtp_sock = NULL; + GSocket *rtcp_sock = NULL; + SipFilterSsrcInfo* filter_info = NULL; + gulong rtp_probe = 0; + gulong rtcp_probe = 0; + const gchar *media_str; + KmsSrtpConnection *conn; + + if (self->priv->conns != NULL) { + // If we are recovering a previous session, due to a renegotation (consecutive processAnswer) + kms_sip_srtp_session_retrieve_sockets (self->priv->conns, media, &rtp_sock, &rtcp_sock); + } + media_str = gst_sdp_media_get_media (media); + + filter_info = km_sip_rtp_session_setup_filter_info (self, media_str); + + conn = kms_sip_srtp_connection_new (min_port, max_port, + KMS_SIP_SRTP_SESSION (base_rtp_sess)->use_ipv6, rtp_sock, rtcp_sock, filter_info, &rtp_probe, &rtcp_probe); + + if ((rtp_probe != 0) || (rtcp_probe != 0)) { + kms_sip_srtp_session_store_rtp_filtering_info (self, conn, rtp_probe, rtcp_probe); + } + + return KMS_I_RTP_CONNECTION (conn); +} + +static void +kms_sip_srtp_session_clone_connections (KmsSipSrtpSession *self, GHashTable *conns) +{ + self->priv->conns = g_hash_table_ref (conns); +} + +/* Connection management end */ + +static void +kms_sip_srtp_session_post_constructor (KmsSrtpSession * self, + KmsBaseSdpEndpoint * ep, guint id, KmsIRtpSessionManager * manager, + gboolean use_ipv6) +{ + KmsBaseRtpSession *base_rtp_session = KMS_BASE_RTP_SESSION (self); + + self->use_ipv6 = use_ipv6; + KMS_BASE_RTP_SESSION_CLASS (parent_class)->post_constructor (base_rtp_session, + ep, id, manager); +} + +static void +kms_sip_srtp_session_init (KmsSipSrtpSession * self) +{ + self->priv = KMS_SIP_SRTP_SESSION_GET_PRIVATE (self); + + self->priv->conns = NULL; + self->priv->rtp_filtering_info = NULL; + + GST_DEBUG ("Initialized SIP SRTP Session %p", self); +} + +static void +kms_sip_rtp_session_free_filter_info (gpointer data) +{ + KmsSipSrtpProbeFilteringInfo *info = (KmsSipSrtpProbeFilteringInfo*) data; + + GST_DEBUG ("Releasing SRTP/SRTCP filtering probes"); + kms_sip_srtp_connection_release_probes (info->conn, info->rtp_probe, info->rtcp_probe); + g_free (data); +} + +static void +kms_sip_srtp_session_finalize (GObject *object) +{ + KmsSipSrtpSession *self = KMS_SIP_SRTP_SESSION(object); + + if (self->priv->conns != NULL) { + g_hash_table_unref (self->priv->conns); + } + + // Release RTP/RTCP filtering info + if (self->priv->rtp_filtering_info != NULL) + g_list_free_full (self->priv->rtp_filtering_info, kms_sip_rtp_session_free_filter_info); + + if (self->audio_filter_info != NULL) { + kms_sip_rtp_filter_release_filtering_info (self->audio_filter_info); + } + if (self->video_filter_info != NULL) { + kms_sip_rtp_filter_release_filtering_info (self->video_filter_info); + + + } + + GST_DEBUG ("Finalized SRTP Session %p", object); + + /* chain up */ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + + +static void +kms_sip_srtp_session_class_init (KmsSipSrtpSessionClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + KmsBaseRtpSessionClass *base_rtp_session_class; + KmsSrtpSessionClass *srtp_session_class; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, GST_DEFAULT_NAME, 0, + GST_DEFAULT_NAME); + + gobject_class = G_OBJECT_CLASS(klass); + gobject_class->finalize = kms_sip_srtp_session_finalize; + + srtp_session_class = KMS_SRTP_SESSION_CLASS (klass); + + srtp_session_class->post_constructor = kms_sip_srtp_session_post_constructor; + + base_rtp_session_class = KMS_BASE_RTP_SESSION_CLASS (klass); + /* Connection management */ + base_rtp_session_class->create_connection = + kms_sip_srtp_session_create_connection; + + klass->clone_connections = kms_sip_srtp_session_clone_connections; + klass->store_rtp_filtering_info = kms_sip_srtp_session_store_rtp_filtering_info; + + gst_element_class_set_details_simple (gstelement_class, + "SipSrtpSession", + "Generic", + "Base bin to manage elements related with a SIP SRTP session.", + "Saul Pablo Labajo Izquierdo "); + + g_type_class_add_private (klass, sizeof (KmsSipSrtpSessionPrivate)); + +} diff --git a/src/gst-plugins/rtpendpoint/kmssipsrtpsession.h b/src/gst-plugins/rtpendpoint/kmssipsrtpsession.h new file mode 100644 index 00000000..51c4567a --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmssipsrtpsession.h @@ -0,0 +1,76 @@ +/* + * (C) Copyright 2015 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __KMS_SIP_SRTP_SESSION_H__ +#define __KMS_SIP_SRTP_SESSION_H__ + +#include +#include +#include "kmssrtpconnection.h" +#include "kmsrtpfilterutils.h" + +G_BEGIN_DECLS + +typedef struct _KmsIRtpSessionManager KmsIRtpSessionManager; + +/* #defines don't like whitespacey bits */ +#define KMS_TYPE_SIP_SRTP_SESSION \ + (kms_sip_srtp_session_get_type()) +#define KMS_SIP_SRTP_SESSION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),KMS_TYPE_SIP_SRTP_SESSION,KmsSipSrtpSession)) +#define KMS_SIP_SRTP_SESSION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),KMS_TYPE_SIP_SRTP_SESSION,KmsSipSrtpSessionClass)) +#define KMS_IS_SIP_SRTP_SESSION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),KMS_TYPE_SIP_SRTP_SESSION)) +#define KMS_IS_SIP_SRTP_SESSION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),KMS_TYPE_SIP_SRTP_SESSION)) +#define KMS_SIP_SRTP_SESSION_CAST(obj) ((KmsSipSrtpSession*)(obj)) + +typedef struct _KmsSipSrtpSession KmsSipSrtpSession; +typedef struct _KmsSipSrtpSessionClass KmsSipSrtpSessionClass; +typedef struct _KmsSipSrtpSessionPrivate KmsSipSrtpSessionPrivate; + + +struct _KmsSipSrtpSession +{ + KmsSrtpSession parent; + + gboolean use_ipv6; + + SipFilterSsrcInfo* audio_filter_info; + SipFilterSsrcInfo* video_filter_info; + + KmsSipSrtpSessionPrivate *priv; +}; + +struct _KmsSipSrtpSessionClass +{ + KmsSrtpSessionClass parent_class; + + /* signals */ + void (*clone_connections) (KmsSipSrtpSession *self, GHashTable *conns); + + void (*store_rtp_filtering_info) (KmsSipSrtpSession *ses, KmsSrtpConnection *conn, gulong rtp_probe, gulong rtcp_probe); + +}; + +GType kms_sip_srtp_session_get_type (void); + +KmsSipSrtpSession *kms_sip_srtp_session_new (KmsBaseSdpEndpoint * ep, guint id, KmsIRtpSessionManager * manager, gboolean use_ipv6); + +G_END_DECLS +#endif /* __KMS_SIP_SRTP_SESSION_H__ */ diff --git a/src/gst-plugins/rtpendpoint/kmssrtpconnection.c b/src/gst-plugins/rtpendpoint/kmssrtpconnection.c index aeda685f..d117379c 100644 --- a/src/gst-plugins/rtpendpoint/kmssrtpconnection.c +++ b/src/gst-plugins/rtpendpoint/kmssrtpconnection.c @@ -17,6 +17,9 @@ #include "kmssrtpconnection.h" #include "kmssocketutils.h" +#include "kmsrtpfilterutils.h" +#include + #define GST_CAT_DEFAULT kmsrtpconnection GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -612,3 +615,222 @@ kms_srtp_connection_interface_init (KmsIRtpConnectionInterface * iface) iface->set_latency_callback = kms_rtp_base_connection_set_latency_callback; iface->collect_latency_stats = kms_srtp_connection_collect_latency_stats; } + +void +kms_sip_srtp_connection_retrieve_sockets (KmsSrtpConnection *conn, GSocket **rtp, GSocket **rtcp) +{ + if (conn != NULL) { + // Retrieve the sockets + *rtcp = g_object_ref (conn->priv->rtcp_socket); + *rtp = g_object_ref (conn->priv->rtp_socket); + + // remove sockets from multiudpsink and udpsrc so that they are disconnected from previous endpoint + // so that they are not released on previoues endpoint finalization + g_object_set (conn->priv->rtp_udpsink, "close-socket", FALSE, NULL); + g_object_set (conn->priv->rtcp_udpsink, "close-socket", FALSE, NULL); + g_object_set (conn->priv->rtp_udpsrc, "close-socket", FALSE, NULL); + g_object_set (conn->priv->rtcp_udpsrc, "close-socket", FALSE, NULL); +// g_object_set (conn->priv->rtp_udpsink, "socket", NULL); +// g_object_set (conn->priv->rtp_udpsrc, "socket", NULL); +// g_object_set (conn->priv->rtcp_udpsink, "socket", NULL); +// g_object_set (conn->priv->rtcp_udpsrc, "socket", NULL); + + conn->priv->rtcp_socket = NULL; + conn->priv->rtp_socket = NULL; + } +} + + +static void +kms_sip_srtp_connection_new_pad_cb (GstElement * element, GstPad * pad, + KmsSrtpConnection * conn) +{ + GstPadTemplate *templ; + GstPad *sinkpad = NULL; + + templ = gst_pad_get_pad_template (pad); + + if (g_strcmp0 (GST_PAD_TEMPLATE_NAME_TEMPLATE (templ), "rtp_src_%u") == 0) { + sinkpad = gst_element_get_static_pad (conn->priv->rtp_udpsink, "sink"); + } else if (g_strcmp0 (GST_PAD_TEMPLATE_NAME_TEMPLATE (templ), + "rtcp_src_%u") == 0) { + sinkpad = gst_element_get_static_pad (conn->priv->rtcp_udpsink, "sink"); + } else { + goto end; + } + + gst_pad_link (pad, sinkpad); + +end: + g_object_unref (templ); + g_clear_object (&sinkpad); +} + + +static GstCaps * +kms_sip_srtp_connection_request_remote_key_cb (GstElement * srtpdec, guint ssrc, + KmsSrtpConnection * conn) +{ + GstCaps *caps = NULL; + + KMS_RTP_BASE_CONNECTION_LOCK (conn); + + if (!conn->priv->r_key_set) { + GST_DEBUG_OBJECT (conn, "key is not yet set"); + goto end; + } + + if (!conn->priv->r_updated) { + GST_DEBUG_OBJECT (conn, "Key is not yet updated"); + } else { + GST_DEBUG_OBJECT (conn, "Using new key"); + conn->priv->r_updated = FALSE; + } + + caps = create_key_caps (ssrc, conn->priv->r_key, conn->priv->r_auth, + conn->priv->r_cipher); + + GST_DEBUG_OBJECT (srtpdec, "Key Caps: %" GST_PTR_FORMAT, caps); + +end: + KMS_RTP_BASE_CONNECTION_UNLOCK (conn); + + return caps; +} + + +static gint key_soft_limit_signal = -1; + +static gint +getKeySoftLimitSignal () +{ + if (key_soft_limit_signal == -1) { + key_soft_limit_signal = g_signal_lookup ("key-soft-limit", KMS_TYPE_SRTP_CONNECTION); + } + return key_soft_limit_signal; +} + +static GstCaps * +kms_sip_srtp_connection_soft_key_limit_cb (GstElement * srtpdec, guint ssrc, + KmsSrtpConnection * conn) +{ + g_signal_emit (conn, getKeySoftLimitSignal(), 0); + + /* FIXME: Key is about to expire, a new one should be provided */ + /* when renegotiation is supported */ + + return NULL; +} + +void +kms_sip_srtp_connection_add_probes (KmsSrtpConnection *conn, SipFilterSsrcInfo* filter_info, gulong *rtp_probe_id, gulong *rtcp_probe_id) +{ + KmsSrtpConnectionPrivate *priv = conn->priv; + + // If we are reusing sockets, it is possible that packets from old connection (old ssrcs) arrive to the sockets + // They should be avoided as they may auto setup the new connection for old SSRCs, preventing the new connection to succed + GstPad *pad; + + pad = gst_element_get_static_pad (priv->rtcp_udpsrc, "src"); + + *rtcp_probe_id = kms_sip_rtp_filter_setup_probe_rtcp (pad, filter_info); + gst_object_unref (pad); + + pad = gst_element_get_static_pad (priv->rtp_udpsrc, "src"); + *rtp_probe_id = kms_sip_rtp_filter_setup_probe_rtp (pad, filter_info); + gst_object_unref (pad); +} + +KmsSrtpConnection * +kms_sip_srtp_connection_new (guint16 min_port, guint16 max_port, gboolean use_ipv6, + GSocket *rtp_sock, GSocket *rtcp_sock, + SipFilterSsrcInfo* filter_info, gulong *rtp_probe_id, gulong *rtcp_probe_id) +{ + // TODO: When this integrated in kms-elements we can modify kms_rtp_connection_new to allow espcifying + // the gstreamer object factory for the connection, so that we can simplify this function + GObject *obj; + KmsSrtpConnection *conn; + KmsSrtpConnectionPrivate *priv; + GSocketFamily socket_family; + + obj = g_object_new (KMS_TYPE_SRTP_CONNECTION, NULL); + conn = KMS_SRTP_CONNECTION (obj); + priv = conn->priv; + + if (use_ipv6) { + socket_family = G_SOCKET_FAMILY_IPV6; + } else { + socket_family = G_SOCKET_FAMILY_IPV4; + } + + // TODO: This is what we need to update on kms_rtp_connection-new + if ((rtp_sock != NULL) && (rtcp_sock != NULL)) { + priv->rtp_socket = rtp_sock; + priv->rtcp_socket = rtcp_sock; + } else { + // ^^^^^^^^^^^^^^^^^^^^^^^^^ + // TODO: Up to here + if (!kms_rtp_connection_get_rtp_rtcp_sockets + (&priv->rtp_socket, &priv->rtcp_socket, min_port, max_port, + socket_family)) { + GST_ERROR_OBJECT (obj, "Cannot get ports"); + g_object_unref (obj); + return NULL; + } + } + + priv->r_updated = FALSE; + priv->r_key_set = FALSE; + + priv->srtpenc = gst_element_factory_make ("srtpenc", NULL); + priv->srtpdec = gst_element_factory_make ("srtpdec", NULL); + g_signal_connect (priv->srtpenc, "pad-added", + G_CALLBACK (kms_sip_srtp_connection_new_pad_cb), obj); + g_signal_connect (priv->srtpdec, "request-key", + G_CALLBACK (kms_sip_srtp_connection_request_remote_key_cb), obj); + g_signal_connect (priv->srtpdec, "soft-limit", + G_CALLBACK (kms_sip_srtp_connection_soft_key_limit_cb), obj); + + priv->rtp_udpsink = gst_element_factory_make ("multiudpsink", NULL); + priv->rtp_udpsrc = gst_element_factory_make ("udpsrc", NULL); + + priv->rtcp_udpsink = gst_element_factory_make ("multiudpsink", NULL); + priv->rtcp_udpsrc = gst_element_factory_make ("udpsrc", NULL); + + kms_sip_srtp_connection_add_probes (conn, filter_info, rtp_probe_id, rtcp_probe_id); + + g_object_set (priv->rtp_udpsink, "socket", priv->rtp_socket, + "sync", FALSE, "async", FALSE, NULL); + g_object_set (priv->rtp_udpsrc, "socket", priv->rtp_socket, "auto-multicast", + FALSE, NULL); + + g_object_set (priv->rtcp_udpsink, "socket", priv->rtcp_socket, + "sync", FALSE, "async", FALSE, NULL); + g_object_set (priv->rtcp_udpsrc, "socket", priv->rtcp_socket, + "auto-multicast", FALSE, NULL); + + kms_i_rtp_connection_connected_signal (KMS_I_RTP_CONNECTION (conn)); + + return conn; +} + + +void +kms_sip_srtp_connection_release_probes (KmsSrtpConnection *conn, gulong rtp_probe_id, gulong rtcp_probe_id) +{ + KmsSrtpConnectionPrivate *priv; + GstPad *pad; + + priv = conn->priv; + + // Release RTCP probe + pad = gst_element_get_static_pad (priv->rtcp_udpsrc, "src"); + kms_sip_rtp_filter_release_probe_rtcp (pad, rtcp_probe_id); + gst_object_unref (pad); + + // Release RTP probe + pad = gst_element_get_static_pad (priv->rtp_udpsrc, "src"); + kms_sip_rtp_filter_release_probe_rtp (pad, rtp_probe_id); + gst_object_unref (pad); +} + diff --git a/src/gst-plugins/rtpendpoint/kmssrtpconnection.h b/src/gst-plugins/rtpendpoint/kmssrtpconnection.h index 9f531fdd..17fd6ba3 100644 --- a/src/gst-plugins/rtpendpoint/kmssrtpconnection.h +++ b/src/gst-plugins/rtpendpoint/kmssrtpconnection.h @@ -19,6 +19,9 @@ #define __KMS_SRTP_CONNECTION_H__ #include "kmsrtpbaseconnection.h" +#include "kmsrtpfilterutils.h" +#include +#include G_BEGIN_DECLS @@ -58,5 +61,21 @@ GType kms_srtp_connection_get_type (void); KmsSrtpConnection *kms_srtp_connection_new (guint16 min_port, guint16 max_port, gboolean use_ipv6); void kms_srtp_connection_set_key (KmsSrtpConnection *conn, const gchar *key, guint auth, guint cipher, gboolean local); +KmsSrtpConnection * +kms_sip_srtp_connection_new (guint16 min_port, guint16 max_port, gboolean use_ipv6, + GSocket *rtp_sock, GSocket *rtcp_sock, + SipFilterSsrcInfo* filter_info, gulong *rtp_probe_id, gulong *rtcp_probe_id); + +void +kms_sip_srtp_connection_add_probes (KmsSrtpConnection *conn, SipFilterSsrcInfo* filter_info, gulong *rtp_probe_id, gulong *rtcp_probe_id); + +void +kms_sip_srtp_connection_release_probes (KmsSrtpConnection *conn, gulong rtp_probe_id, gulong rtcp_probe_id); + +void kms_sip_srtp_connection_retrieve_sockets (KmsSrtpConnection *conn, GSocket **rtp, GSocket **rtcp); + +void kms_sip_srtp_connection_set_key (KmsSrtpConnection *conn, const gchar *key, guint auth, guint cipher, gboolean local); + + G_END_DECLS #endif /* __KMS_RTP_CONNECTION_H__ */ diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 1feb0120..6c0f4629 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -4,6 +4,8 @@ add_subdirectory(implementation/HttpServer) set(KMS_ELEMENTS_IMPL_SOURCES implementation/CertificateManager.cpp + implementation/objects/FacadeRtpEndpointImpl.cpp + implementation/objects/ComposedObjectImpl.cpp ) set(KMS_ELEMENTS_IMPL_HEADERS diff --git a/src/server/implementation/objects/ComposedObjectImpl.cpp b/src/server/implementation/objects/ComposedObjectImpl.cpp new file mode 100644 index 00000000..d465beb9 --- /dev/null +++ b/src/server/implementation/objects/ComposedObjectImpl.cpp @@ -0,0 +1,341 @@ +/* + * (C) Copyright 2016 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "ComposedObjectImpl.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +#define GST_CAT_DEFAULT kurento_composed_object_impl +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); +#define GST_DEFAULT_NAME "ComposedObjectImpl" + +#define FACTORY_NAME "passthrough" + +/* In theory the Master key can be shorter than the maximum length, but + * the GStreamer's SRTP plugin enforces using the maximum length possible + * for the type of cypher used (in file 'gstsrtpenc.c'). So, KMS also expects + * that the maximum Master key size is used. */ +#define KMS_SRTP_CIPHER_AES_CM_128_SIZE ((gsize)30) +#define KMS_SRTP_CIPHER_AES_CM_256_SIZE ((gsize)46) + +namespace kurento +{ + +const static std::string DEFAULT = "default"; + + +ComposedObjectImpl::ComposedObjectImpl (const boost::property_tree::ptree &conf, + std::shared_ptr mediaPipeline) + : MediaElementImpl (conf, + std::dynamic_pointer_cast (mediaPipeline), FACTORY_NAME) +{ + + sinkPt = std::shared_ptr(new PassThroughImpl(config, mediaPipeline)); + srcPt = std::shared_ptr(new PassThroughImpl(config, mediaPipeline)); + linkedSource = NULL; + linkedSink = NULL; + origElem = NULL; +} + +ComposedObjectImpl::~ComposedObjectImpl() +{ + element = origElem; + + disconnectForwardSignals (); +} + + + + +void +ComposedObjectImpl::disconnectForwardSignals () +{ + connElementConnectedSrc.disconnect (); + connElementConnectedSink.disconnect (); + connElementDisconnectedSrc.disconnect (); + connElementDisconnectedSink.disconnect (); + connMediaTranscodingStateChangeSrc.disconnect (); + connMediaTranscodingStateChangeSink.disconnect (); + connMediaFlowOutStateChange.disconnect (); + connMediaFlowInStateChange.disconnect (); + connErrorSrc.disconnect (); + connErrorSink.disconnect (); +} + +void +ComposedObjectImpl::connectForwardSignals () +{ + connElementConnectedSrc = std::dynamic_pointer_cast(srcPt)->signalElementConnected.connect([ & ] ( + ElementConnected event) { + //We don't raise internal connection events' + if (event.getSource()==srcPt) + return; + if (event.getSink () == sinkPt) + return; + raiseEvent (event, shared_from_this(), signalElementConnected); + }); + + connElementConnectedSink = std::dynamic_pointer_cast(sinkPt)->signalElementConnected.connect([ & ] ( + ElementConnected event) { + //We don't raise internal connection events' + if (event.getSource()==srcPt) + return; + if (event.getSink () == sinkPt) + return; + raiseEvent (event, shared_from_this(), signalElementConnected); + }); + + connElementDisconnectedSrc = std::dynamic_pointer_cast(srcPt)->signalElementDisconnected.connect([ & ] ( + ElementDisconnected event) { + try { + //We don't raise internal connection events' + if (event.getSource()==srcPt) + return; + if (event.getSink () == sinkPt) + return; + raiseEvent (event, shared_from_this(), signalElementDisconnected); + } catch (const std::bad_weak_ptr &e) { + // shared_from_this() + } + }); + + connElementDisconnectedSink = std::dynamic_pointer_cast(sinkPt)->signalElementDisconnected.connect([ & ] ( + ElementDisconnected event) { + try { + //We don't raise internal connection events' + if (event.getSource()==srcPt) + return; + if (event.getSink () == sinkPt) + return; + raiseEvent (event, shared_from_this(), signalElementDisconnected); + } catch (const std::bad_weak_ptr &e) { + // shared_from_this() + } + }); + + connMediaTranscodingStateChangeSrc = std::dynamic_pointer_cast(srcPt)->signalMediaTranscodingStateChange.connect([ & ] ( + MediaTranscodingStateChange event) { + raiseEvent (event, shared_from_this(), signalMediaTranscodingStateChange); + }); + + connMediaTranscodingStateChangeSink = std::dynamic_pointer_cast(sinkPt)->signalMediaTranscodingStateChange.connect([ & ] ( + MediaTranscodingStateChange event) { + raiseEvent (event, shared_from_this(), signalMediaTranscodingStateChange); + }); + + connMediaFlowOutStateChange = std::dynamic_pointer_cast(sinkPt)->signalMediaFlowOutStateChange.connect([ & ] ( + MediaFlowOutStateChange event) { + raiseEvent (event, shared_from_this(), signalMediaFlowOutStateChange); + }); + + connMediaFlowInStateChange = std::dynamic_pointer_cast(srcPt)->signalMediaFlowInStateChange.connect([ & ] ( + MediaFlowInStateChange event) { + raiseEvent (event, shared_from_this(), signalMediaFlowInStateChange); + }); + + connErrorSrc = std::dynamic_pointer_cast(srcPt)->signalError.connect([ & ] ( + Error event) { + raiseEvent (event, shared_from_this(), signalError); + }); + + connErrorSink = std::dynamic_pointer_cast(sinkPt)->signalError.connect([ & ] ( + Error event) { + raiseEvent (event, shared_from_this(), signalError); + }); +} + + +bool ComposedObjectImpl::connect (const std::string &eventType, std::shared_ptr handler) +{ + std::weak_ptr wh = handler; + + if ("ElementConnected" == eventType) { + sigc::connection conn = connectEventToExternalHandler (signalElementConnected, wh); + handler->setConnection (conn); + return true; + } + + if ("ElementDisconnected" == eventType) { + sigc::connection conn = connectEventToExternalHandler (signalElementDisconnected, wh); + handler->setConnection (conn); + return true; + } + + if ("MediaFlowOutStateChange" == eventType) { + sigc::connection conn = connectEventToExternalHandler (signalMediaFlowOutStateChange, wh); + handler->setConnection (conn); + return true; + } + + if ("MediaFlowInStateChange" == eventType) { + sigc::connection conn = connectEventToExternalHandler (signalMediaFlowInStateChange, wh); + handler->setConnection (conn); + return true; + } + + if ("MediaTranscodingStateChange" == eventType) { + sigc::connection conn = connectEventToExternalHandler (signalMediaTranscodingStateChange, wh); + handler->setConnection (conn); + return true; + } + + if ("Error" == eventType) { + sigc::connection conn = connectEventToExternalHandler (signalError, wh); + handler->setConnection (conn); + return true; + } + + return false; +} + + +void +ComposedObjectImpl::postConstructor () +{ + MediaElementImpl::postConstructor (); + + origElem = getGstreamerElement (); + element = srcPt->getGstreamerElement(); + + connectForwardSignals (); +} + +ComposedObjectImpl::StaticConstructor ComposedObjectImpl::staticConstructor; + +ComposedObjectImpl::StaticConstructor::StaticConstructor() +{ + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, GST_DEFAULT_NAME, 0, + GST_DEFAULT_NAME); +} + + +void ComposedObjectImpl::linkMediaElement(std::shared_ptr linkSrc, std::shared_ptr linkSink) +{ + GST_DEBUG ("Linking object to facade"); + linkMutex.lock(); + + // Unlink source and sink from previous composed object + if (linkedSource != NULL) { + // Unlink source + linkedSource->disconnect(sinkPt); + + connErrorlinkedSrc.disconnect (); + } + if (linkedSink != NULL) { + // Unlink sink + srcPt->disconnect(linkedSink); + + if (linkedSink != linkedSource) + connErrorlinkedSink.disconnect (); + } + + linkedSource = linkSrc; + linkedSink = linkSink; + + // Link source and sink from new composed object + if (linkedSource != NULL) { + // Link Source + linkedSource->connect(sinkPt); + + connErrorlinkedSrc = std::dynamic_pointer_cast(linkedSource)->signalError.connect([ & ] ( + Error event) { + raiseEvent (event, shared_from_this(), signalError); + }); + + } + if (linkedSink != NULL) { + // Link sink + srcPt->connect(linkedSink); + + if (linkedSink != linkedSource) { + connErrorlinkedSink = std::dynamic_pointer_cast(linkedSink)->signalError.connect([ & ] ( + Error event) { + raiseEvent (event, shared_from_this(), signalError); + }); + } + } + + linkMutex.unlock(); +} + + +void ComposedObjectImpl::connect (std::shared_ptr sink) +{ + GST_DEBUG ("Connecting (A+V+D) facade to sink"); + + // TODO: signals emitted from sinkPt due to connection changes should be + // elevated to be re-emitted from this Composed Object + // Until mediaDescriptions are really used, we just connect audio an video + this->sinkPt->connect(sink, std::make_shared(MediaType::AUDIO), DEFAULT, + DEFAULT); + this->sinkPt->connect(sink, std::make_shared(MediaType::VIDEO), DEFAULT, + DEFAULT); + this->sinkPt->connect(sink, std::make_shared(MediaType::DATA), DEFAULT, DEFAULT); +} + +void ComposedObjectImpl::connect (std::shared_ptr sink, + std::shared_ptr mediaType) +{ + GST_DEBUG ("Connecting (%s) facade to sink", mediaType->getString().c_str()); + this->sinkPt->connect (sink, mediaType, DEFAULT, DEFAULT); +} + +void ComposedObjectImpl::connect (std::shared_ptr sink, + std::shared_ptr mediaType, + const std::string &sourceMediaDescription) +{ + GST_DEBUG ("Connecting (%s) facade (%s) to sink", mediaType->getString().c_str(), sourceMediaDescription.c_str()); + + this->sinkPt->connect (sink, mediaType, sourceMediaDescription, DEFAULT); +} + +void ComposedObjectImpl::disconnect (std::shared_ptr sink) +{ + GST_DEBUG ("Disconnecting (A+V+D) facade from sink"); + + // Until mediaDescriptions are really used, we just connect audio an video + this->sinkPt->disconnect(sink, std::make_shared(MediaType::AUDIO), DEFAULT, + DEFAULT); + this->sinkPt->disconnect(sink, std::make_shared(MediaType::VIDEO), DEFAULT, + DEFAULT); + this->sinkPt->disconnect(sink, std::make_shared(MediaType::DATA), DEFAULT, DEFAULT); +} + +void ComposedObjectImpl::disconnect (std::shared_ptr sink, + std::shared_ptr mediaType) +{ + GST_DEBUG ("Disconnecting (%s) facade to sink", mediaType->getString().c_str()); + this->sinkPt->disconnect (sink, mediaType, DEFAULT, DEFAULT); +} + +void ComposedObjectImpl::disconnect (std::shared_ptr sink, + std::shared_ptr mediaType, + const std::string &sourceMediaDescription) +{ + GST_DEBUG ("Connecting (%s) facade (%s) to sink", mediaType->getString().c_str(), sourceMediaDescription.c_str()); + this->sinkPt->disconnect (sink, mediaType, sourceMediaDescription, DEFAULT); +} + + + + +} /* kurento */ diff --git a/src/server/implementation/objects/ComposedObjectImpl.hpp b/src/server/implementation/objects/ComposedObjectImpl.hpp new file mode 100644 index 00000000..effe613f --- /dev/null +++ b/src/server/implementation/objects/ComposedObjectImpl.hpp @@ -0,0 +1,157 @@ +/* + * (C) Copyright 2016 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef __COMPOSED_OBJECT_IMPL_HPP__ +#define __COMPOSED_OBJECT_IMPL_HPP__ + +#include +#include +#include + +namespace kurento +{ + +class MediaPipeline; + +class ComposedObjectImpl; + +void Serialize (std::shared_ptr &object, + JsonSerializer &serializer); + +class ComposedObjectImpl : public MediaElementImpl +{ + +public: + + ComposedObjectImpl (const boost::property_tree::ptree &conf, + std::shared_ptr mediaPipeline); + + virtual ~ComposedObjectImpl (); + + + void linkMediaElement (std::shared_ptr linkSrc, std::shared_ptr linkSink); + + // Connectivity methods needed to override to make the composition of objects + + void connect (std::shared_ptr sink) override; + void connect (std::shared_ptr sink, + std::shared_ptr mediaType) override; + void connect (std::shared_ptr sink, + std::shared_ptr mediaType, + const std::string &sourceMediaDescription) override; + + + void disconnect (std::shared_ptr sink) override; + void disconnect (std::shared_ptr sink, + std::shared_ptr mediaType) override; + void disconnect (std::shared_ptr sink, + std::shared_ptr mediaType, + const std::string &sourceMediaDescription) override; + + +protected: + virtual void postConstructor () override; + bool connect (const std::string &eventType, std::shared_ptr handler); + std::shared_ptr sinkPt; + std::shared_ptr srcPt; + + template + sigc::connection connectEventToExternalHandler (sigc::signal& signal, std::weak_ptr& wh) + { + sigc::connection conn = signal.connect ([ &, wh] (T event) { + std::shared_ptr lh = wh.lock(); + if (!lh) + return; + + std::shared_ptr ev_ref (new T(event)); + auto object = this->shared_from_this(); + + lh->sendEventAsync ([ev_ref, object, lh] { + JsonSerializer s (true); + + s.Serialize ("data", ev_ref.get()); + s.Serialize ("object", object.get()); + s.JsonValue["type"] = T::getName().c_str(); + + lh->sendEvent (s.JsonValue); + }); + }); + return conn; + } + + template void + raiseEvent (T& event, std::shared_ptr self, sigc::signal& signal) + { + try { + T event2 (event); + + event2.setSource(self); + sigcSignalEmit(signal, event2); + } catch (const std::bad_weak_ptr &e) { + // shared_from_this() + GST_ERROR ("BUG creating %s: %s", T::getName ().c_str (), + e.what ()); + } + } + + + +private: + + GstElement* origElem; + std::shared_ptr linkedSource; + std::shared_ptr linkedSink; + + sigc::signal signalElementConnected; + sigc::signal signalElementDisconnected; + sigc::signal signalMediaFlowOutStateChange; + sigc::signal signalMediaFlowInStateChange; + sigc::signal signalMediaTranscodingStateChange; + sigc::signal signalError; + + sigc::connection connElementConnectedSrc; + sigc::connection connElementConnectedSink; + sigc::connection connElementDisconnectedSrc; + sigc::connection connElementDisconnectedSink; + sigc::connection connMediaTranscodingStateChangeSrc; + sigc::connection connMediaTranscodingStateChangeSink; + sigc::connection connMediaFlowOutStateChange; + sigc::connection connMediaFlowInStateChange; + sigc::connection connErrorSrc; + sigc::connection connErrorSink; + sigc::connection connErrorlinkedSrc; + sigc::connection connErrorlinkedSink; + + + + std::recursive_mutex linkMutex; + + class StaticConstructor + { + public: + StaticConstructor(); + }; + + static StaticConstructor staticConstructor; + + void connectForwardSignals (); + void disconnectForwardSignals (); + +}; + +} /* kurento */ + +#endif /* __COMPOSED_OBJECT_IMPL_HPP__ */ diff --git a/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp new file mode 100644 index 00000000..4e6e10b2 --- /dev/null +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp @@ -0,0 +1,1539 @@ +/* + * (C) Copyright 2016 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include +#include "MediaPipeline.hpp" +#include "ComposedObjectImpl.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define GST_CAT_DEFAULT kurento_sip_rtp_endpoint_impl +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); +#define GST_DEFAULT_NAME "KurentoSipRtpEndpointImpl" + +#define FACTORY_NAME "siprtpendpoint" + +/* In theory the Master key can be shorter than the maximum length, but + * the GStreamer's SRTP plugin enforces using the maximum length possible + * for the type of cypher used (in file 'gstsrtpenc.c'). So, KMS also expects + * that the maximum Master key size is used. */ +#define KMS_SRTP_CIPHER_AES_CM_128_SIZE ((gsize)30) +#define KMS_SRTP_CIPHER_AES_CM_256_SIZE ((gsize)46) + +namespace kurento +{ + +static void +completeSdpAnswer (std::string &answer, const std::string &offer) +{ + GstSDPMessage *sdpOffer, *sdpAnswer; + guint offerMediaNum, answerMediaNum; + GArray *mediaAnswer, *mediaOffer; + guint idx; + gchar *answerStr; + + gst_sdp_message_new (&sdpOffer); + gst_sdp_message_new (&sdpAnswer); + gst_sdp_message_parse_buffer ( (const guint8 *) offer.c_str(), offer.length(), + sdpOffer); + gst_sdp_message_parse_buffer ( (const guint8 *) answer.c_str(), answer.length(), + sdpAnswer); + + offerMediaNum = gst_sdp_message_medias_len (sdpOffer); + answerMediaNum = gst_sdp_message_medias_len (sdpAnswer); + + mediaOffer = sdpOffer->medias; + mediaAnswer = sdpAnswer->medias; + + idx = 0; + + while (idx < offerMediaNum) { + GstSDPMedia *offIdxMedia; + GstSDPMedia *ansIdxMedia; + bool addFakeMedia = false; + + offIdxMedia = &g_array_index (mediaOffer, GstSDPMedia, idx); + + if (idx >= answerMediaNum) { + addFakeMedia = true; + } else { + ansIdxMedia = &g_array_index (mediaAnswer, GstSDPMedia, idx); + + if (g_strcmp0 (offIdxMedia->media, ansIdxMedia->media) == 0) { + if (g_strcmp0 (offIdxMedia->proto, ansIdxMedia->proto) != 0) { + addFakeMedia = true; + } + } else { + addFakeMedia = true; + } + } + + if (addFakeMedia) { + GstSDPMedia *fakeMedia; + + gst_sdp_media_new (&fakeMedia); + gst_sdp_media_set_media (fakeMedia, offIdxMedia->media); + gst_sdp_media_set_proto (fakeMedia, offIdxMedia->proto); + gst_sdp_media_set_port_info (fakeMedia, 0, 1); + gst_sdp_media_add_attribute (fakeMedia, "inactive", NULL); + mediaAnswer = g_array_insert_val (mediaAnswer, idx, *fakeMedia); + answerMediaNum++; + } + + idx++; + } + + answerStr = gst_sdp_message_as_text (sdpAnswer); + answer = answerStr; + gst_sdp_message_free (sdpOffer); + gst_sdp_message_free (sdpAnswer); + g_free (answerStr); +} + + + +FacadeRtpEndpointImpl::FacadeRtpEndpointImpl (const boost::property_tree::ptree + &conf, + std::shared_ptr mediaPipeline, + std::shared_ptr crypto, + bool cryptoAgnostic, + bool useIpv6) + : ComposedObjectImpl (conf, + std::dynamic_pointer_cast (mediaPipeline) ), + cryptoCache (crypto), useIpv6Cache (useIpv6) +{ + this->cryptoAgnostic = cryptoAgnostic; + + rtp_ep = std::shared_ptr (new SipRtpEndpointImpl (config, + mediaPipeline, crypto, useIpv6) ); + audioCapsSet = NULL; + videoCapsSet = NULL; + rembParamsSet = NULL; + + // Magic values to assess no change on SSRC + this->agnosticCryptoAudioSsrc = 0; + this->agnosticCryptoVideoSsrc = 0; + this->agnosticNonCryptoAudioSsrc = 0; + this->agnosticNonCryptoVideoSsrc = 0; +} + +FacadeRtpEndpointImpl::~FacadeRtpEndpointImpl() +{ + linkMediaElement (NULL, NULL); +} + +void +FacadeRtpEndpointImpl::postConstructor () +{ + ComposedObjectImpl::postConstructor (); + + rtp_ep->postConstructor(); + linkMediaElement (rtp_ep, rtp_ep); + +} + + +FacadeRtpEndpointImpl::StaticConstructor +FacadeRtpEndpointImpl::staticConstructor; + +FacadeRtpEndpointImpl::StaticConstructor::StaticConstructor() +{ + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, GST_DEFAULT_NAME, 0, + GST_DEFAULT_NAME); +} + + +// The methods connect and invoke are automatically generated in the SipRtpEndpoint class +// but no in the Facadde, so we have to redirect the implementation to the one in SipRtpEndpoint +bool FacadeRtpEndpointImpl::connect (const std::string &eventType, + std::shared_ptr handler) +{ + std::weak_ptr wh = handler; + + if ("OnKeySoftLimit" == eventType) { + sigc::connection conn = connectEventToExternalHandler + (signalOnKeySoftLimit, wh); + handler->setConnection (conn); + return true; + } + + if ("MediaStateChanged" == eventType) { + sigc::connection conn = connectEventToExternalHandler + (signalMediaStateChanged, wh); + handler->setConnection (conn); + return true; + } + + if ("ConnectionStateChanged" == eventType) { + sigc::connection conn = connectEventToExternalHandler + (signalConnectionStateChanged, wh); + handler->setConnection (conn); + return true; + } + + if ("MediaSessionStarted" == eventType) { + sigc::connection conn = connectEventToExternalHandler + (signalMediaSessionStarted, wh); + handler->setConnection (conn); + return true; + } + + if ("MediaSessionTerminated" == eventType) { + sigc::connection conn = connectEventToExternalHandler + (signalMediaSessionTerminated, wh); + handler->setConnection (conn); + return true; + } + + return ComposedObjectImpl::connect (eventType, handler); +} + + +void FacadeRtpEndpointImpl::invoke (std::shared_ptr obj, + const std::string &methodName, const Json::Value ¶ms, + Json::Value &response) +{ + this->rtp_ep->invoke (obj, methodName, params, response); +} + + + + +/*--------------------- Implementation of SipRtpEndpoint specific features ---------------------------------*/ + +std::string FacadeRtpEndpointImpl::generateOffer () +{ + generateOffer (std::make_shared() ); +} + +std::string FacadeRtpEndpointImpl::generateOffer (std::shared_ptr + options) +{ + std::string offer; + + try { + offer = this->rtp_ep->generateOffer (options); + + if (this->isCryptoAgnostic() ) { + this->generateCryptoAgnosticOffer (offer); + GST_INFO ("GenerateOffer: generating crypto agnostic offer"); + } + + GST_DEBUG ("GenerateOffer: \n%s", offer.c_str() ); + return offer; + } catch (kurento::KurentoException &e) { + if (e.getCode() == SDP_END_POINT_ALREADY_NEGOTIATED) { + GST_INFO ("Consecutive generate Offer on %s, cloning endpoint", + this->getId().c_str() ); + } else { + GST_WARNING ("Exception generating offer in SipRtpEndpoint: %s - %s", + e.getType().c_str(), e.getMessage().c_str() ); + throw e; + } + } catch (std::exception &e1) { + GST_WARNING ("Exception generating offer in SipRtpEndpoint: %s", e1.what() ); + throw e1; + } + + std::shared_ptr newEndpoint = + std::shared_ptr (new SipRtpEndpointImpl (config, + getMediaPipeline (), cryptoCache, useIpv6Cache) ); + + newEndpoint->postConstructor(); + renewInternalEndpoint (newEndpoint); + offer = newEndpoint->generateOffer (options); + + if (this->isCryptoAgnostic() ) { + this->generateCryptoAgnosticOffer (offer); + GST_INFO ("GenerateOffer: generated crypto agnostic offer"); + } + + GST_DEBUG ("2nd try GenerateOffer: \n%s", offer.c_str() ); + GST_INFO ("Consecutive generate Offer on %s, endpoint cloned and offer processed", + this->getId().c_str() ); + return offer; +} + +static std::vector +getMediasFromSdp (GstSDPMessage *sdp) +{ + std::vector mediaList; + guint idx = 0; + guint medias_len; + + // Get media lines from SDP offer + medias_len = gst_sdp_message_medias_len (sdp); + + while (idx < medias_len) { + const GstSDPMedia *sdpMedia; + + sdpMedia = gst_sdp_message_get_media (sdp, idx); + + if (sdpMedia != NULL) { + idx++; + mediaList.push_back ( (GstSDPMedia *) sdpMedia); + } + } + + return mediaList; +} + +static GstSDPMessage * +parseSDP (const std::string &sdp) +{ + GstSDPMessage *sdpObject = NULL; + + if (gst_sdp_message_new (&sdpObject) != GST_SDP_OK) { + GST_ERROR ("Could not create SDP object"); + return NULL; + } + + if (gst_sdp_message_parse_buffer ( (const guint8 *) sdp.c_str(), + strlen (sdp.c_str() ), sdpObject) != GST_SDP_OK) { + GST_ERROR ("Could not parse SDP answer"); + return NULL; + } + + return sdpObject; +} + + +std::string FacadeRtpEndpointImpl::processOffer (const std::string &offer) +{ + std::string answer; + std::shared_ptr cryptoToUse (new SDES() ); + std::shared_ptr newEndpoint; + std::string modifiableOffer (offer); + + try { + bool renewEp = false; + + GST_DEBUG ("ProcessOffer: \n%s", offer.c_str() ); + + if (this->isCryptoAgnostic () ) { + renewEp = this->checkCryptoOffer (modifiableOffer, cryptoToUse); + } + + if (!renewEp) { + answer = this->rtp_ep->processOffer (modifiableOffer); + GST_DEBUG ("Generated Answer: \n%s", answer.c_str() ); + completeSdpAnswer (answer, offer); + return answer; + } else { + GST_INFO ("ProcessOffer: Regenerating endpoint fro agnostic crypto"); + } + } catch (kurento::KurentoException &e) { + if (e.getCode() == SDP_END_POINT_ALREADY_NEGOTIATED) { + GST_INFO ("Consecutive process Offer on %s, cloning endpoint", + this->getId().c_str() ); + cryptoToUse = cryptoCache; + } else { + GST_WARNING ("Exception generating offer in SipRtpEndpoint: %s - %s", + e.getType().c_str(), e.getMessage().c_str() ); + throw e; + } + } catch (std::exception &e1) { + GST_WARNING ("Exception generating offer in SipRtpEndpoint: %s", e1.what() ); + throw e1; + } + + // If we get here is either SDP offer didn't match existing endpoint regarding crypto + // or existing endpoint was already negotiated. + // In either case, cryptoToUse contains the cryptoCofniguration needed to instantiate new SipRtpEndpoint + newEndpoint = std::shared_ptr (new SipRtpEndpointImpl ( + config, getMediaPipeline (), cryptoToUse, useIpv6Cache) ); + newEndpoint->postConstructor(); + renewInternalEndpoint (newEndpoint); + answer = newEndpoint->processOffer (modifiableOffer); + completeSdpAnswer (answer, offer); + GST_DEBUG ("2nd try Generated Answer: \n%s", answer.c_str() ); + GST_INFO ("Consecutive process Offer on %s, endpoint cloned and offer processed", + this->getId().c_str() ); + return answer; +} + +static std::shared_ptr +copySDES (std::shared_ptr origSdes) +{ + std::shared_ptr sdes (new SDES () ); + + if (origSdes != NULL) { + if (origSdes->isSetCrypto() ) { + sdes->setCrypto (origSdes->getCrypto() ); + } + + if (origSdes->isSetKey() ) { + sdes->setKey (origSdes->getKey() ); + } + + if (origSdes->isSetKeyBase64() ) { + sdes->setKeyBase64 (origSdes->getKeyBase64() ); + } + } + + return sdes; +} + +std::string FacadeRtpEndpointImpl::processAnswer (const std::string &answer) +{ + std::string result; + std::shared_ptr cryptoToUse = copySDES (this->cryptoCache); + std::shared_ptr newEndpoint; + std::string modifiableAnswer (answer); + + try { + bool renewEp = false; + + GST_DEBUG ("ProcessAnswer: \n%s", answer.c_str() ); + + if (this->isCryptoAgnostic () ) { + renewEp = this->checkCryptoAnswer (modifiableAnswer, cryptoToUse); + } + + if (!renewEp) { + result = this->rtp_ep->processAnswer (modifiableAnswer); + GST_DEBUG ("ProcessAnswer: \n%s", result.c_str() ); + return result; + } else { + GST_INFO ("ProcessAnswer: Regenerating endpoint fro agnostic crypto"); + } + } catch (kurento::KurentoException &e) { + if (e.getCode() == SDP_END_POINT_ANSWER_ALREADY_PROCCESED) { + GST_INFO ("Consecutive process Answer on %s, cloning endpoint", + this->getId().c_str() ); + //cryptoToUse = cryptoCache; + } else { + GST_WARNING ("Exception generating offer in SipRtpEndpoint: %s - %s", + e.getType().c_str(), e.getMessage().c_str() ); + throw e; + } + } catch (std::exception &e1) { + GST_WARNING ("Exception generating offer in SipRtpEndpoint: %s", e1.what() ); + throw e1; + } + + std::string unusedOffer; + std::shared_ptr oldEndpoint; + bool continue_audio_stream, continue_video_stream; + + answerHasCompatibleMedia (answer, continue_audio_stream, continue_video_stream); + + if (continue_audio_stream) { + GST_INFO ("No change in audio stream, it is expected that received audio will preserve IP, port, SSRC and base timestamp"); + } + + if (continue_video_stream) { + GST_INFO ("No change in video stream, it is expected that received audio will preserve IP, port, SSRC and base timestamp"); + } + + newEndpoint = rtp_ep->getCleanEndpoint (config, getMediaPipeline (), + cryptoToUse, useIpv6Cache, modifiableAnswer, continue_audio_stream, + continue_video_stream); + + if (this->isCryptoAgnostic () ) { + if (cryptoToUse->isSetCrypto() ) { + if (this->agnosticCryptoAudioSsrc != 0) { + newEndpoint->setAudioSsrc (this->agnosticCryptoAudioSsrc); + } + + if (this->agnosticCryptoVideoSsrc != 0) { + newEndpoint->setVideoSsrc (this->agnosticCryptoVideoSsrc); + } + } else { + if (this->agnosticNonCryptoAudioSsrc != 0) { + newEndpoint->setAudioSsrc (this->agnosticNonCryptoAudioSsrc); + } + + if (this->agnosticNonCryptoVideoSsrc != 0) { + newEndpoint->setVideoSsrc (this->agnosticNonCryptoVideoSsrc); + } + } + } + + newEndpoint->postConstructor(); + oldEndpoint = renewInternalEndpoint (newEndpoint); + unusedOffer = newEndpoint->generateOffer(); + GST_DEBUG ("2nd try ProcessAnswer - Unused offer: \n%s", unusedOffer.c_str() ); + result = newEndpoint->processAnswer (modifiableAnswer); + GST_DEBUG ("2nd try ProcessAnswer: \n%s", result.c_str() ); + GST_INFO ("Consecutive process Answer on %s, endpoint cloned and answer processed", + this->getId().c_str() ); + return result; +} + +std::string FacadeRtpEndpointImpl::getLocalSessionDescriptor () +{ + return this->rtp_ep->getLocalSessionDescriptor(); +} + +std::string FacadeRtpEndpointImpl::getRemoteSessionDescriptor () +{ + return this->rtp_ep->getRemoteSessionDescriptor(); +} + + +bool +FacadeRtpEndpointImpl::isCryptoAgnostic () +{ + return this->cryptoAgnostic; +} + +bool +FacadeRtpEndpointImpl::findCompatibleMedia (GstSDPMedia *media, + GstSDPMessage *oldAnswer) +{ + std::vector mediaList; + + mediaList = getMediasFromSdp (oldAnswer); + + for (std::vector::iterator it = mediaList.begin(); + it != mediaList.end(); ++it) { + if (g_strcmp0 (gst_sdp_media_get_media (*it), + gst_sdp_media_get_media (media) ) == 0) { + // Same media + if (g_strcmp0 (gst_sdp_media_get_proto (*it), + gst_sdp_media_get_proto (media) ) == 0) { + // same proto + if (gst_sdp_media_get_port (*it) == gst_sdp_media_get_port (media) ) { + // same port, so it is compatible + return true; + } + } + } + } + + return false; +} + +bool +FacadeRtpEndpointImpl::sameConnection (GstSDPMessage *sdp1, GstSDPMessage *sdp2) +{ + const GstSDPConnection *conn1, *conn2; + + conn1 = gst_sdp_message_get_connection (sdp1); + conn2 = gst_sdp_message_get_connection (sdp2); + + return (g_strcmp0 (conn1->nettype, conn2->nettype) == 0) && + (g_strcmp0 (conn1->addrtype, conn2->addrtype) == 0) && + (g_strcmp0 (conn1->address, conn2->address) == 0); +} + +static bool +isMediaActive (GstSDPMedia *media) +{ + const gchar *inactive; + + inactive = gst_sdp_media_get_attribute_val (media, "inactive"); + + if (inactive == NULL) { + return (gst_sdp_media_get_port (media) != 0); + } + + return false; +} + + + + +void +FacadeRtpEndpointImpl::answerHasCompatibleMedia (const std::string &answer, + bool &audio_compatible, bool &video_compatible) +{ + GstSDPMessage *sdpAnswer; + GstSDPMessage *oldSdpAnswer; + std::vector mediaList; + std::string oldAnswer; + + audio_compatible = false; + video_compatible = false; + + try { + oldAnswer = this->rtp_ep->getRemoteSessionDescriptor (); + } catch (KurentoException &xcp) { + // No remote descriptor, no compatible medias possible + return; + } + + sdpAnswer = parseSDP (answer); + oldSdpAnswer = parseSDP (oldAnswer); + + if (!sameConnection (sdpAnswer, oldSdpAnswer) ) { + // Not same connection no compatible medias possible + return; + } + + mediaList = getMediasFromSdp (sdpAnswer); + + for (std::vector::iterator it = mediaList.begin(); + it != mediaList.end(); ++it) { + if (isMediaActive (*it) ) { + if (findCompatibleMedia (*it, oldSdpAnswer) ) { + if (g_strcmp0 (gst_sdp_media_get_media (*it), "audio") == 0) { + audio_compatible = true; + } else if (g_strcmp0 (gst_sdp_media_get_media (*it), "video") == 0) { + video_compatible = true; + } + } + } + } + + + gst_sdp_message_free (sdpAnswer); + gst_sdp_message_free (oldSdpAnswer); +} + +void +FacadeRtpEndpointImpl::replaceSsrc (GstSDPMedia *media, + guint idx, + gchar *newSsrcStr, + guint32 &oldSsrc) +{ + const GstSDPAttribute *attr; + GstSDPAttribute *new_attr; + std::string ssrc; + std::string oldSsrcStr; + GRegex *regex; + std::string newSsrc; + std::size_t ssrcIdx; + GMatchInfo *match_info = NULL; + + attr = gst_sdp_media_get_attribute (media, idx); + + if (attr != NULL) { + new_attr = (GstSDPAttribute *) g_malloc (sizeof (GstSDPAttribute) ); + ssrc = attr->value; + + regex = g_regex_new ("^(?[0-9]+)(.*)?$", (GRegexCompileFlags) 0, + (GRegexMatchFlags) 0, NULL); + g_regex_match (regex, ssrc.c_str(), (GRegexMatchFlags) 0, &match_info); + + if (g_match_info_matches (match_info) ) { + oldSsrcStr = g_match_info_fetch_named (match_info, "ssrc"); + } + + g_match_info_free (match_info); + g_regex_unref (regex); + + ssrcIdx = ssrc.find (oldSsrcStr); + + if (ssrcIdx != std::string::npos) { + newSsrc = ssrc.substr (0, + ssrcIdx).append (newSsrcStr).append (ssrc.substr (ssrcIdx + oldSsrcStr.length(), + std::string::npos) ); + } + + gst_sdp_attribute_set (new_attr, "ssrc", newSsrc.c_str() ); + gst_sdp_media_replace_attribute (media, idx, new_attr); + + oldSsrc = g_ascii_strtoull (oldSsrcStr.c_str(), NULL, 10); + } +} + +void +FacadeRtpEndpointImpl::replaceAllSsrcAttrs (GstSDPMedia *media, + std::list sscrIdxs, guint32 &oldSsrc, guint32 &newSsrc) +{ + // set the ssrc attribute + gchar newSsrcStr [11]; + + newSsrc = g_random_int (); + g_snprintf (newSsrcStr, 11, "%u", newSsrc); + + for (std::list::iterator it = sscrIdxs.begin(); it != sscrIdxs.end(); + ++it) { + replaceSsrc (media, *it, newSsrcStr, oldSsrc); + } +} + +void +FacadeRtpEndpointImpl::removeCryptoAttrs (GstSDPMedia *media, + std::list cryptoIdx) +{ + // Remove the crypto attributes, to not change atttribute index we go backward + for (std::list::reverse_iterator rit = cryptoIdx.rbegin(); + rit != cryptoIdx.rend(); ++rit) { + gst_sdp_media_remove_attribute (media, *rit); + } +} + +void +FacadeRtpEndpointImpl::addAgnosticMedia (GstSDPMedia *media, + GstSDPMessage *sdpOffer) +{ + std::list sscrIdxs, cryptoIdxs; + GstSDPMedia *newMedia; + guint idx, attrs_len; + guint32 agnosticMediaSsrc; + guint32 oldSsrc; + + if (gst_sdp_media_copy (media, &newMedia) != GST_SDP_OK) { + GST_ERROR ("Could not copy media, cannot generate secure agnostic media"); + return; + } + + // Only non crypto lines should need to be generated + if (g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/SAVP") == 0) { + gst_sdp_media_set_proto (newMedia, "RTP/AVP"); + } else if (g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/SAVPF") == 0) { + gst_sdp_media_set_proto (newMedia, "RTP/AVPF"); + } else { + // Not supported protocol not processing + gst_sdp_media_free (newMedia); + return; + } + + // Gets relevant attributes + idx = 0; + attrs_len = gst_sdp_media_attributes_len (newMedia); + + while (idx < attrs_len) { + const GstSDPAttribute *attr; + + attr = gst_sdp_media_get_attribute (newMedia, idx); + + if (g_strcmp0 (attr->key, "ssrc") == 0) { + sscrIdxs.push_back (idx); + } else if (g_strcmp0 (attr->key, "crypto") == 0) { + cryptoIdxs.push_back (idx); + } + + idx++; + } + + replaceAllSsrcAttrs (newMedia, sscrIdxs, oldSsrc, agnosticMediaSsrc); + + if (g_strcmp0 (gst_sdp_media_get_media (newMedia), "audio") == 0) { + this->agnosticCryptoAudioSsrc = oldSsrc; + this->agnosticNonCryptoAudioSsrc = agnosticMediaSsrc; + } else if (g_strcmp0 (gst_sdp_media_get_media (newMedia), "video") == 0) { + this->agnosticCryptoVideoSsrc = oldSsrc; + this->agnosticNonCryptoVideoSsrc = agnosticMediaSsrc; + } + + // Remove crypto attribute + removeCryptoAttrs (newMedia, cryptoIdxs); + + // Add new media to the offer so it is crypto agnostic + gst_sdp_message_add_media (sdpOffer, newMedia); +} + +static bool +isCryptoSDES (std::shared_ptr sdes) +{ + if (sdes == NULL) { + return false; + } + + if (sdes->isSetCrypto() ) { + return true; + } + + return false; +} + + +bool +FacadeRtpEndpointImpl::generateCryptoAgnosticOffer (std::string &offer) +{ + std::vector mediaList; + GstSDPMessage *sdpOffer; + gchar *result; + + // If not crypto configured, cannot generate crypto agnostic offer + if (!isCryptoSDES (this->cryptoCache) ) { + GST_WARNING ("cryptoAgnostic configured, but no crypto info set, cannot generate cryptoAgnostic endpoint, reverting to non crypto endpoint"); + return false; + } + + sdpOffer = parseSDP (offer); + + if (sdpOffer == NULL) { + return false; + } + + mediaList = getMediasFromSdp (sdpOffer); + + // For each media line we generate a new line with different ssrc, same port and different protocol (is current protocol is RTP/SAVP, + // new one is RTP/AVP) + // Keep in mind that we already have crypto lines, so only non-crypto lines must be generated + for (std::vector::iterator it = mediaList.begin(); + it != mediaList.end(); ++it) { + addAgnosticMedia (*it, sdpOffer); + } + + result = gst_sdp_message_as_text (sdpOffer); + offer = result; + g_free (result); + gst_sdp_message_free (sdpOffer); + return true; +} + +static std::shared_ptr +get_crypto_suite_from_str (gchar *str) +{ + if (g_strcmp0 (str, "AES_CM_128_HMAC_SHA1_32") == 0) { + return std::shared_ptr (new CryptoSuite ( + CryptoSuite::AES_128_CM_HMAC_SHA1_32) ); + } + + if (g_strcmp0 (str, "AES_CM_128_HMAC_SHA1_80") == 0) { + return std::shared_ptr (new CryptoSuite ( + CryptoSuite::AES_128_CM_HMAC_SHA1_80) ); + } + + if (g_strcmp0 (str, "AES_256_CM_HMAC_SHA1_32") == 0) { + return std::shared_ptr (new CryptoSuite ( + CryptoSuite::AES_256_CM_HMAC_SHA1_32) ); + } + + if (g_strcmp0 (str, "AES_256_CM_HMAC_SHA1_80") == 0) { + return std::shared_ptr (new CryptoSuite ( + CryptoSuite::AES_256_CM_HMAC_SHA1_80) ); + } + + return NULL; +} + +static std::string +get_crypto_key_from_str (gchar *str) +{ + gchar **attrs; + std::string result; + + attrs = g_strsplit (str, "|", 0); + + if (attrs[0] == NULL) { + GST_WARNING ("Noy key provided in crypto attribute"); + return result; + } + + result = attrs [0]; + g_strfreev (attrs); + return result; +} + +static std::shared_ptr +build_crypto (std::shared_ptr suite, std::string &key) +{ + std::shared_ptr sdes (new SDES () ); + + sdes->setCrypto (suite); + sdes->setKeyBase64 (key.c_str() ); + return sdes; +} + +static bool +get_valid_crypto_info_from_offer (GstSDPMedia *media, + std::shared_ptr &crypto) +{ + guint idx, attrs_len; + + attrs_len = gst_sdp_media_attributes_len (media); + + for (idx = 0; idx < attrs_len; idx++) { + // We can only support the same crypto information for all medias (audio and video) in an offer + // RtpEndpoint currently only supports that + // And as the offer may have several crypto to select, we choose the first one that may be supported + const gchar *cryptoStr = gst_sdp_media_get_attribute_val_n (media, "crypto", + idx); + + if (cryptoStr != NULL) { + gchar **attrs; + std::string key; + std::shared_ptr cryptoSuite = NULL; + + attrs = g_strsplit (cryptoStr, " ", 0); + + if (attrs[0] == NULL) { + GST_WARNING ("Bad crypto attribute format"); + goto next_iter; + } + + if (attrs[1] == NULL) { + GST_WARNING ("No crypto suite provided"); + goto next_iter; + } + + cryptoSuite = get_crypto_suite_from_str (attrs[1]); + + if (cryptoSuite == NULL) { + GST_WARNING ("No valid crypto suite"); + goto next_iter; + } + + if (attrs[2] == NULL) { + GST_WARNING ( "No key parameters provided"); + goto next_iter; + } + + if (!g_str_has_prefix (attrs[2], "inline:") ) { + GST_WARNING ("Unsupported key method provided"); + goto next_iter; + } + + key = get_crypto_key_from_str (attrs[2] + strlen ("inline:") ); + + if (key.length () == 0) { + GST_WARNING ("No key provided"); + goto next_iter; + } + + crypto = build_crypto (cryptoSuite, key); + GST_INFO ("Crypto offer and valid key found"); + g_strfreev (attrs); + return true; + +next_iter: + g_strfreev (attrs); + } + } + + return false; +} + +static void +makeUpSdp (bool isCrypto, GstSDPMessage *sdp, + std::set cryptoMedias, + std::set nonCryptoMedias) +{ + std::set *mediaToAdd; + int numMedias, idx; + + if (isCrypto) { + mediaToAdd = &cryptoMedias; + } else { + mediaToAdd = &nonCryptoMedias; + } + + if (!mediaToAdd->empty() ) { + numMedias = gst_sdp_message_medias_len (sdp); + idx = numMedias - 1; + + while (idx >= 0) { + if (mediaToAdd->find (idx) == mediaToAdd->end() ) { + sdp->medias = g_array_remove_index (sdp->medias, idx); + } + + idx--; + } + } +} + +static bool +isCryptoCompatible (std::shared_ptr original, + std::shared_ptr answer) +{ + bool result = true; + + if (original->isSetCrypto() != answer->isSetCrypto() ) { + result = false; + } else { + if (original->isSetCrypto() ) { + if (original->getCrypto()->getValue() != answer->getCrypto()->getValue() ) { + result = false; + } + } + } + + return result; +} + +static void +getActiveMedias (std::vector mediaList, + std::set &usableMedia) +{ + guint idx = 0; + + for (std::vector::iterator it = mediaList.begin(); + it != mediaList.end(); ) { + GstSDPMedia *media = (GstSDPMedia *) *it; + + if (isMediaActive (media) ) { + usableMedia.insert (idx); + } + + ++it; + idx++; + } +} + +static void +getCryptoMedias (std::vector mediaList, + std::set &nonCryptoMedias, std::set &cryptoMedias) +{ + // For each media we check if it is using a crypto protocol or not + for (std::set::iterator it = nonCryptoMedias.begin(); + it != nonCryptoMedias.end(); ) { + guint mediaIdx = *it; + GstSDPMedia *media = mediaList.at (mediaIdx); + + if ( (g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/SAVP") == 0) + || (g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/SAVPF") == 0) ) { + cryptoMedias.insert (mediaIdx); + } + + ++it; + } + + // And remove cryptos found from noncrypto list + for (std::set::iterator it = cryptoMedias.begin(); + it != cryptoMedias.end (); ++it) { + nonCryptoMedias.erase (*it); + } +} + +static bool +getCryptoInfoFromMedia (std::vector mediaList, + std::set cryptoMedias, + std::shared_ptr &sdes) +{ + if (cryptoMedias.size () > 0) { + GstSDPMedia *media = mediaList.at (* (cryptoMedias.begin () ) ); + + // Offer has crypto info, so we need to ensure + // - First, that the endpoint supports crypto + // - Second, that crypto suite and master key correspond to that in the offer + if (!get_valid_crypto_info_from_offer (media, sdes) ) { + // No valid key found, + GST_ERROR ("Crypto offer found, but no supported key found in offer, cannot answer"); + return false; + } else { + GST_INFO ("Valid crypto info found in offer"); + return true; + } + } else { + GST_INFO ("No crypto offer found"); + } + + return false; +} + +bool +FacadeRtpEndpointImpl::checkCryptoOffer (std::string &offer, + std::shared_ptr &crypto) +{ + std::vector mediaList; + std::set nonCryptoMedias; + std::set cryptoMedias; + GstSDPMessage *sdpOffer; + bool isCrypto = false; + std::shared_ptr sdes (new SDES() ); + gchar *modifiedSdpStr; + + sdpOffer = parseSDP (offer); + + if (sdpOffer == NULL) { + return false; + } + + mediaList = getMediasFromSdp (sdpOffer); + + getActiveMedias (mediaList, nonCryptoMedias); + + if (nonCryptoMedias.size() == 0) { + // No active medias offered + // We just leave the first 2 medias as base RtpEndpoint is what it needs + guint idx = 0; + + while (idx < mediaList.size () ) { + nonCryptoMedias.insert (idx); + idx++; + } + } + + getCryptoMedias (mediaList, nonCryptoMedias, cryptoMedias); + + isCrypto = getCryptoInfoFromMedia (mediaList, cryptoMedias, sdes); + + makeUpSdp (isCrypto, sdpOffer, cryptoMedias, nonCryptoMedias); + + crypto = sdes; + modifiedSdpStr = gst_sdp_message_as_text (sdpOffer); + offer = modifiedSdpStr; + + gst_sdp_message_free (sdpOffer); + g_free (modifiedSdpStr); + + crypto = sdes; + + if (isCryptoCompatible (cryptoCache, sdes) ) { + return false; + } + + return true; +} + +static std::shared_ptr +fitMediaAnswer (std::string &answer, bool isLocalCrypto) +{ + std::vector mediaList; + std::set nonCryptoMedias; + std::set cryptoMedias; + GstSDPMessage *sdpAnswer; + std::shared_ptr sdes (new SDES() ); + gchar *newAnswer; + bool isCrypto; + + sdpAnswer = parseSDP (answer); + + if (sdpAnswer == NULL) { + return sdes; + } + + mediaList = getMediasFromSdp (sdpAnswer); + + getActiveMedias (mediaList, nonCryptoMedias); + + if (nonCryptoMedias.size() == 0) { + // No active medias found, so we must restrict answer to first 2 medias + // as they should be crypto ones + guint idx = 0; + + while (idx < mediaList.size () ) { + nonCryptoMedias.insert (idx); + idx++; + } + } + + getCryptoMedias (mediaList, nonCryptoMedias, cryptoMedias); + isCrypto = getCryptoInfoFromMedia (mediaList, cryptoMedias, sdes); + + makeUpSdp (isCrypto, sdpAnswer, cryptoMedias, nonCryptoMedias); + newAnswer = gst_sdp_message_as_text (sdpAnswer); + answer = newAnswer; + g_free ( (gpointer) newAnswer); + + gst_sdp_message_free (sdpAnswer); + + return sdes; +} + +bool +FacadeRtpEndpointImpl::checkCryptoAnswer (std::string &answer, + std::shared_ptr &crypto) +{ + std::shared_ptr sdes; + bool isLocalCrypto = crypto->isSetCrypto() && (crypto->getCrypto() != NULL); + + sdes = fitMediaAnswer (answer, isLocalCrypto); + + // If the sdp answer is cryptocompatible with the endpoint, we need not regenerate the endpoint + if (isCryptoCompatible (crypto, sdes) ) { + return false; + } else { + crypto = sdes; + } + + return true; +} + + + +void +FacadeRtpEndpointImpl::disconnectForwardSignals () +{ + connMediaStateChanged.disconnect (); + connConnectionStateChanged.disconnect (); + connMediaSessionStarted.disconnect (); + connMediaSessionTerminated.disconnect (); + connOnKeySoftLimit.disconnect (); +} + +void +FacadeRtpEndpointImpl::connectForwardSignals () +{ + + connMediaStateChanged = std::dynamic_pointer_cast + (rtp_ep)->signalMediaStateChanged.connect ([ & ] ( + MediaStateChanged event) { + raiseEvent (event, shared_from_this(), + signalMediaStateChanged); + }); + + connConnectionStateChanged = std::dynamic_pointer_cast + (rtp_ep)->signalConnectionStateChanged.connect ([ & ] ( + ConnectionStateChanged event) { + raiseEvent (event, shared_from_this(), + signalConnectionStateChanged); + }); + + connMediaSessionStarted = std::dynamic_pointer_cast + (rtp_ep)->signalMediaSessionStarted.connect ([ & ] ( + MediaSessionStarted event) { + raiseEvent (event, shared_from_this(), + signalMediaSessionStarted); + }); + + connMediaSessionTerminated = std::dynamic_pointer_cast + (rtp_ep)->signalMediaSessionTerminated.connect ([ & ] ( + MediaSessionTerminated event) { + raiseEvent (event, shared_from_this(), + signalMediaSessionTerminated); + }); + + connOnKeySoftLimit = rtp_ep->signalOnKeySoftLimit.connect ([ & ] ( + OnKeySoftLimit event) { + raiseEvent (event, shared_from_this(), signalOnKeySoftLimit); + }); + +} + +void FacadeRtpEndpointImpl::setProperties (std::shared_ptr + from) +{ + if (rtp_ep != NULL) { + rtp_ep->setName (from->getName() ); + rtp_ep->setSendTagsInEvents (from->getSendTagsInEvents() ); + + if (audioCapsSet != NULL) { + rtp_ep->setAudioFormat (audioCapsSet); + } + + rtp_ep->setMaxOutputBitrate (from->getMaxOutputBitrate() ); + rtp_ep->setMinOutputBitrate (from->getMinOutputBitrate() ); + + if (videoCapsSet != NULL) { + rtp_ep->setVideoFormat (videoCapsSet); + } + + rtp_ep->setMaxAudioRecvBandwidth (from->getMaxAudioRecvBandwidth () ); + rtp_ep->setMaxVideoRecvBandwidth (from->getMaxVideoRecvBandwidth() ); + rtp_ep->setMaxVideoSendBandwidth (from->getMaxVideoSendBandwidth() ); + rtp_ep->setMinVideoRecvBandwidth (from->getMinVideoRecvBandwidth () ); + + if (rembParamsSet != NULL) { + rtp_ep->setRembParams (rembParamsSet); + } + + rtp_ep->setMtu (from->getMtu () ); + } +} + +std::shared_ptr +FacadeRtpEndpointImpl::renewInternalEndpoint ( + std::shared_ptr newEndpoint) +{ + std::shared_ptr tmp = rtp_ep; + + if (rtp_ep != NULL) { + disconnectForwardSignals (); + } + + rtp_ep = newEndpoint; + linkMediaElement (newEndpoint, newEndpoint); + setProperties (tmp); + + if (rtp_ep != NULL) { + connectForwardSignals (); + } + + return tmp; +} + +/*----------------- MEthods from BaseRtpEndpoint ---------------*/ +int FacadeRtpEndpointImpl::getMinVideoRecvBandwidth () +{ + return this->rtp_ep->getMinVideoRecvBandwidth(); +} + +void FacadeRtpEndpointImpl::setMinVideoRecvBandwidth (int minVideoRecvBandwidth) +{ + this->rtp_ep->setMinVideoRecvBandwidth (minVideoRecvBandwidth); +} + +int FacadeRtpEndpointImpl::getMinVideoSendBandwidth () +{ + return this->rtp_ep->getMinVideoSendBandwidth (); +} + +void FacadeRtpEndpointImpl::setMinVideoSendBandwidth (int minVideoSendBandwidth) +{ + this->rtp_ep->setMinVideoSendBandwidth (minVideoSendBandwidth); +} + +int FacadeRtpEndpointImpl::getMaxVideoSendBandwidth () +{ + return this->rtp_ep->getMaxVideoSendBandwidth(); +} + +void FacadeRtpEndpointImpl::setMaxVideoSendBandwidth (int maxVideoSendBandwidth) +{ + this->rtp_ep->setMaxVideoSendBandwidth (maxVideoSendBandwidth); +} + +std::shared_ptr FacadeRtpEndpointImpl::getMediaState () +{ + return this->rtp_ep->getMediaState(); +} +std::shared_ptr FacadeRtpEndpointImpl::getConnectionState () +{ + return this->rtp_ep->getConnectionState(); +} + +std::shared_ptr FacadeRtpEndpointImpl::getRembParams () +{ + return this->rtp_ep->getRembParams(); +} +void FacadeRtpEndpointImpl::setRembParams (std::shared_ptr + rembParams) +{ + this->rtp_ep->setRembParams (rembParams); + rembParamsSet = rembParams; +} +sigc::signal +FacadeRtpEndpointImpl::getSignalMediaStateChanged () +{ + return this->rtp_ep->signalMediaStateChanged; +} + +sigc::signal +FacadeRtpEndpointImpl::getSignalConnectionStateChanged () +{ + return this->rtp_ep->signalConnectionStateChanged; +} + +int FacadeRtpEndpointImpl::getMtu () +{ + return this->rtp_ep->getMtu (); +} + +void FacadeRtpEndpointImpl::setMtu (int mtu) +{ + this->rtp_ep->setMtu (mtu); +} + + + + +/*---------------- Overloaded methods from SDP Endpoint ---------------*/ +int FacadeRtpEndpointImpl::getMaxVideoRecvBandwidth () +{ + return this->rtp_ep->getMaxVideoRecvBandwidth(); +} +void FacadeRtpEndpointImpl::setMaxVideoRecvBandwidth (int maxVideoRecvBandwidth) +{ + this->rtp_ep->setMaxVideoRecvBandwidth (maxVideoRecvBandwidth); +} +int FacadeRtpEndpointImpl::getMaxAudioRecvBandwidth () +{ + return this->rtp_ep->getMaxAudioRecvBandwidth (); +} +void FacadeRtpEndpointImpl::setMaxAudioRecvBandwidth (int maxAudioRecvBandwidth) +{ + this->rtp_ep->setMaxAudioRecvBandwidth (maxAudioRecvBandwidth); +} + +/*----------------------- Overloaded methods from Media Element --------------*/ +std::map > + FacadeRtpEndpointImpl::getStats () +{ + return this->rtp_ep->getStats(); +} +std::map > FacadeRtpEndpointImpl::getStats ( + std::shared_ptr mediaType) +{ + return this->rtp_ep->getStats (mediaType); +} + + +std::vector> + FacadeRtpEndpointImpl::getSourceConnections () +{ + // TODO Verify this behaviour + //return this->rtp_ep->getSourceConnections(); + return this->srcPt->getSourceConnections(); +} +std::vector> + FacadeRtpEndpointImpl::getSourceConnections ( + std::shared_ptr mediaType) +{ + // TODO: Verifiy this behaviour + //return this->rtp_ep->getSourceConnections(mediaType); + return this->srcPt->getSourceConnections (mediaType); +} +std::vector> + FacadeRtpEndpointImpl::getSourceConnections ( + std::shared_ptr mediaType, const std::string &description) +{ + // TODO: Verify this behaviour + //return this->rtp_ep->getSourceConnections(mediaType, description); + return this->srcPt->getSourceConnections (mediaType, description); +} +std::vector> + FacadeRtpEndpointImpl::getSinkConnections () +{ + // TODO Verify this behaviour + //return this->rtp_ep->getSinkConnections(); + return this->sinkPt->getSinkConnections(); +} +std::vector> + FacadeRtpEndpointImpl::getSinkConnections ( + std::shared_ptr mediaType) +{ + // TODO: verify this behviour + //return this->rtp_ep->getSinkConnections(mediaType); + return this->sinkPt->getSinkConnections (mediaType); +} +std::vector> + FacadeRtpEndpointImpl::getSinkConnections ( + std::shared_ptr mediaType, const std::string &description) +{ + // TODO: Verify this behaviour + //return this->rtp_ep->getSinkConnections(mediaType, description); + return this->sinkPt->getSinkConnections (mediaType, description); +} +void FacadeRtpEndpointImpl::setAudioFormat (std::shared_ptr caps) +{ + this->rtp_ep->setAudioFormat (caps); + audioCapsSet = caps; +} +void FacadeRtpEndpointImpl::setVideoFormat (std::shared_ptr caps) +{ + this->rtp_ep->setVideoFormat (caps); + videoCapsSet = caps; +} + +void FacadeRtpEndpointImpl::release () +{ + this->linkMediaElement (NULL, NULL); + ComposedObjectImpl::release (); + +} + +std::string FacadeRtpEndpointImpl::getGstreamerDot () +{ + return this->rtp_ep->getGstreamerDot(); +} +std::string FacadeRtpEndpointImpl::getGstreamerDot ( + std::shared_ptr + details) +{ + return this->rtp_ep->getGstreamerDot (details); +} + +void FacadeRtpEndpointImpl::setOutputBitrate (int bitrate) +{ + this->rtp_ep->setOutputBitrate (bitrate); +} + +bool FacadeRtpEndpointImpl::isMediaFlowingIn (std::shared_ptr + mediaType) +{ + return this->rtp_ep->isMediaFlowingIn (mediaType); +} +bool FacadeRtpEndpointImpl::isMediaFlowingIn (std::shared_ptr + mediaType, + const std::string &sinkMediaDescription) +{ + return this->rtp_ep->isMediaFlowingIn (mediaType, sinkMediaDescription); +} +bool FacadeRtpEndpointImpl::isMediaFlowingOut (std::shared_ptr + mediaType) +{ + return this->rtp_ep->isMediaFlowingOut (mediaType); +} +bool FacadeRtpEndpointImpl::isMediaFlowingOut (std::shared_ptr + mediaType, + const std::string &sourceMediaDescription) +{ + return this->rtp_ep->isMediaFlowingOut (mediaType, sourceMediaDescription); +} +bool FacadeRtpEndpointImpl::isMediaTranscoding (std::shared_ptr + mediaType) +{ + return this->rtp_ep->isMediaTranscoding (mediaType); +} +bool FacadeRtpEndpointImpl::isMediaTranscoding (std::shared_ptr + mediaType, + const std::string &binName) +{ + return this->rtp_ep->isMediaTranscoding (mediaType, binName); +} + +int FacadeRtpEndpointImpl::getMinOuputBitrate () +{ + return this->rtp_ep->getMinOuputBitrate(); +} +void FacadeRtpEndpointImpl::setMinOuputBitrate (int minOuputBitrate) +{ + this->rtp_ep->setMinOuputBitrate (minOuputBitrate); +} + +int FacadeRtpEndpointImpl::getMinOutputBitrate () +{ + return this->rtp_ep->getMinOutputBitrate(); +} +void FacadeRtpEndpointImpl::setMinOutputBitrate (int minOutputBitrate) +{ + this->rtp_ep->setMinOutputBitrate (minOutputBitrate); +} + +int FacadeRtpEndpointImpl::getMaxOuputBitrate () +{ + return this->rtp_ep->getMaxOuputBitrate(); +} +void FacadeRtpEndpointImpl::setMaxOuputBitrate (int maxOuputBitrate) +{ + this->rtp_ep->setMaxOuputBitrate (maxOuputBitrate); +} + +int FacadeRtpEndpointImpl::getMaxOutputBitrate () +{ + return this->rtp_ep->getMaxOutputBitrate(); +} +void FacadeRtpEndpointImpl::setMaxOutputBitrate (int maxOutputBitrate) +{ + this->rtp_ep->setMaxOutputBitrate (maxOutputBitrate); +} + + +void +FacadeRtpEndpointImpl::Serialize (JsonSerializer &serializer) +{ + if (serializer.IsWriter) { + try { + Json::Value v (getId() ); + + serializer.JsonValue = v; + } catch (std::bad_cast &e) { + } + } else { + throw KurentoException (MARSHALL_ERROR, + "'SipRtpEndpointImpl' cannot be deserialized as an object"); + } +} + + + +} /* kurento */ diff --git a/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp b/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp new file mode 100644 index 00000000..024c31f7 --- /dev/null +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp @@ -0,0 +1,261 @@ +/* + * (C) Copyright 2016 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef __SIP_RTP_ENDPOINT_IMPL_HPP__ +#define __SIP_RTP_ENDPOINT_IMPL_HPP__ + +#include "ComposedObjectImpl.hpp" +#include "SipRtpEndpoint.hpp" +#include +#include +#include +#include +#include + + + + +namespace kurento +{ + +class MediaPipeline; + +class FacadeRtpEndpointImpl; + +class SDES; +class CryptoSuite; + +void Serialize (std::shared_ptr &object, + JsonSerializer &serializer); + +class FacadeRtpEndpointImpl : public ComposedObjectImpl, + public virtual SipRtpEndpoint +{ + +public: + + FacadeRtpEndpointImpl (const boost::property_tree::ptree &conf, + std::shared_ptr mediaPipeline, + std::shared_ptr crypto, + bool cryptoAgnostic, + bool useIpv6); + + virtual ~FacadeRtpEndpointImpl (); + + operator BaseRtpEndpointImpl(); + + + + /*----------------- MEthods from BaseRtpEndpoint ---------------*/ + int getMinVideoRecvBandwidth () override; + void setMinVideoRecvBandwidth (int minVideoRecvBandwidth) override; + + int getMinVideoSendBandwidth () override; + void setMinVideoSendBandwidth (int minVideoSendBandwidth) override; + + int getMaxVideoSendBandwidth () override; + void setMaxVideoSendBandwidth (int maxVideoSendBandwidth) override; + + std::shared_ptr getMediaState () override; + std::shared_ptr getConnectionState () override; + + std::shared_ptr getRembParams () override; + void setRembParams (std::shared_ptr rembParams) override; + + sigc::signal getSignalMediaStateChanged (); + sigc::signal getSignalConnectionStateChanged (); + + virtual int getMtu (); + virtual void setMtu (int mtu); + + + /*---------------- Overloaded methods from SDP Endpoint ---------------*/ + int getMaxVideoRecvBandwidth () override; + void setMaxVideoRecvBandwidth (int maxVideoRecvBandwidth) override; + int getMaxAudioRecvBandwidth () override; + void setMaxAudioRecvBandwidth (int maxAudioRecvBandwidth) override; + std::string generateOffer () override; + std::string generateOffer (std::shared_ptr options) override; + std::string processOffer (const std::string &offer) override; + std::string processAnswer (const std::string &answer) override; + std::string getLocalSessionDescriptor () override; + std::string getRemoteSessionDescriptor () override; + + + /*----------------------- Overloaded methods from Media Element --------------*/ + std::map > getStats () override; + std::map > getStats ( + std::shared_ptr mediaType) override; + + + std::vector> getSourceConnections () + override; + std::vector> + getSourceConnections ( + std::shared_ptr mediaType) override; + std::vector> + getSourceConnections ( + std::shared_ptr mediaType, const std::string &description) override; + std::vector> + getSinkConnections () override; + std::vector> getSinkConnections ( + std::shared_ptr mediaType) override; + std::vector> getSinkConnections ( + std::shared_ptr mediaType, const std::string &description) override; + void setAudioFormat (std::shared_ptr caps) override; + void setVideoFormat (std::shared_ptr caps) override; + + virtual void release () override; + + virtual std::string getGstreamerDot () override; + virtual std::string getGstreamerDot (std::shared_ptr + details) override; + + virtual void setOutputBitrate (int bitrate) override; + + bool isMediaFlowingIn (std::shared_ptr mediaType) override; + bool isMediaFlowingIn (std::shared_ptr mediaType, + const std::string &sinkMediaDescription) override; + bool isMediaFlowingOut (std::shared_ptr mediaType) override; + bool isMediaFlowingOut (std::shared_ptr mediaType, + const std::string &sourceMediaDescription) override; + bool isMediaTranscoding (std::shared_ptr mediaType) override; + bool isMediaTranscoding (std::shared_ptr mediaType, + const std::string &binName) override; + + virtual int getMinOuputBitrate () override; + virtual void setMinOuputBitrate (int minOuputBitrate) override; + + virtual int getMinOutputBitrate () override; + virtual void setMinOutputBitrate (int minOutputBitrate) override; + + virtual int getMaxOuputBitrate () override; + virtual void setMaxOuputBitrate (int maxOuputBitrate) override; + + virtual int getMaxOutputBitrate () override; + virtual void setMaxOutputBitrate (int maxOutputBitrate) override; + + + /* Next methods are automatically implemented by code generator */ + using ComposedObjectImpl::connect; + virtual bool connect (const std::string &eventType, + std::shared_ptr handler) override; + + + + virtual void invoke (std::shared_ptr obj, + const std::string &methodName, const Json::Value ¶ms, + Json::Value &response) override; + + virtual void Serialize (JsonSerializer &serializer) override; + +protected: + virtual void postConstructor () override; + +private: + + bool cryptoAgnostic; + + sigc::signal signalMediaStateChanged; + sigc::signal signalConnectionStateChanged; + sigc::signal signalMediaSessionStarted; + sigc::signal signalMediaSessionTerminated; + sigc::signal signalOnKeySoftLimit; + + sigc::connection connMediaStateChanged; + sigc::connection connConnectionStateChanged; + sigc::connection connMediaSessionStarted; + sigc::connection connMediaSessionTerminated; + sigc::connection connOnKeySoftLimit; + + + + guint32 agnosticCryptoAudioSsrc; + guint32 agnosticCryptoVideoSsrc; + guint32 agnosticNonCryptoAudioSsrc; + guint32 agnosticNonCryptoVideoSsrc; + + bool + sameConnection (GstSDPMessage *sdp1, GstSDPMessage *sdp2); + + bool + findCompatibleMedia (GstSDPMedia *media, GstSDPMessage *oldAnswer); + + void + answerHasCompatibleMedia (const std::string &answer, bool &audio_compatible, + bool &video_compatible); + + bool + isCryptoAgnostic (); + + bool + generateCryptoAgnosticOffer (std::string &offer); + + bool + checkCryptoOffer (std::string &offer, std::shared_ptr &crypto); + + bool + checkCryptoAnswer (std::string &answer, std::shared_ptr &crypto); + + void + replaceSsrc (GstSDPMedia *media, guint idx, gchar *newSsrcStr, + guint32 &oldSsrc); + + void + replaceAllSsrcAttrs (GstSDPMedia *media, std::list sscrIdxs, + guint32 &oldSsrc, guint32 &newSsrc); + + void + removeCryptoAttrs (GstSDPMedia *media, std::list cryptoIdx); + + void + addAgnosticMedia (GstSDPMedia *media, GstSDPMessage *sdpOffer); + + void + disconnectForwardSignals (); + + void + connectForwardSignals (); + + std::shared_ptr + renewInternalEndpoint (std::shared_ptr newEndpoint); + + void + setProperties (std::shared_ptr from); + + std::shared_ptr audioCapsSet; + std::shared_ptr videoCapsSet; + std::shared_ptr rembParamsSet; + + std::shared_ptr rtp_ep; + + std::shared_ptr cryptoCache; + + bool useIpv6Cache; + + class StaticConstructor + { + public: + StaticConstructor(); + }; + + static StaticConstructor staticConstructor; + +}; + +} /* kurento */ + +#endif /* __SIP_RTP_ENDPOINT_IMPL_HPP__ */ diff --git a/src/server/implementation/objects/SipRtpEndpointImpl.cpp b/src/server/implementation/objects/SipRtpEndpointImpl.cpp new file mode 100644 index 00000000..159bbe5c --- /dev/null +++ b/src/server/implementation/objects/SipRtpEndpointImpl.cpp @@ -0,0 +1,232 @@ +/* + * (C) Copyright 2016 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include "MediaPipeline.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GST_CAT_DEFAULT kurento_sip_rtp_endpoint_impl +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); +#define GST_DEFAULT_NAME "KurentoSipRtpEndpointImpl" + +#define FACTORY_NAME "siprtpendpoint" +//#define FACTORY_NAME "rtpendpoint" + +/* In theory the Master key can be shorter than the maximum length, but + * the GStreamer's SRTP plugin enforces using the maximum length possible + * for the type of cypher used (in file 'gstsrtpenc.c'). So, KMS also expects + * that the maximum Master key size is used. */ +#define KMS_SRTP_CIPHER_AES_CM_128_SIZE ((gsize)30) +#define KMS_SRTP_CIPHER_AES_CM_256_SIZE ((gsize)46) + +namespace kurento +{ + +SipRtpEndpointImpl::SipRtpEndpointImpl (const boost::property_tree::ptree &conf, + std::shared_ptr mediaPipeline, + std::shared_ptr crypto, + bool useIpv6) + : BaseRtpEndpointImpl (conf, + std::dynamic_pointer_cast (mediaPipeline), + FACTORY_NAME, useIpv6) +{ + if (!crypto->isSetCrypto() ) { + return; + } + + if (!crypto->isSetKey() && !crypto->isSetKeyBase64()) { + /* Use random key */ + g_object_set (element, "crypto-suite", crypto->getCrypto()->getValue(), + NULL); + return; + } + + gsize expect_size; + + switch (crypto->getCrypto()->getValue() ) { + case CryptoSuite::AES_128_CM_HMAC_SHA1_32: + case CryptoSuite::AES_128_CM_HMAC_SHA1_80: + expect_size = KMS_SRTP_CIPHER_AES_CM_128_SIZE; + break; + case CryptoSuite::AES_256_CM_HMAC_SHA1_32: + case CryptoSuite::AES_256_CM_HMAC_SHA1_80: + expect_size = KMS_SRTP_CIPHER_AES_CM_256_SIZE; + break; + default: + throw KurentoException (MEDIA_OBJECT_ILLEGAL_PARAM_ERROR, + "Invalid crypto suite"); + } + + std::string key_b64; + gsize key_data_size = 0; + + if (crypto->isSetKey()) { + std::string tmp = crypto->getKey(); + key_data_size = tmp.length(); + + gchar *tmp_b64 = g_base64_encode ((const guchar *)tmp.data(), tmp.length()); + key_b64 = std::string (tmp_b64); + g_free(tmp_b64); + } + else if (crypto->isSetKeyBase64()) { + key_b64 = crypto->getKeyBase64(); + guchar *tmp_b64 = g_base64_decode (key_b64.data(), &key_data_size); + if (!tmp_b64) { + GST_ERROR_OBJECT (element, "Master key is not valid Base64"); + throw KurentoException (MEDIA_OBJECT_ILLEGAL_PARAM_ERROR, + "Master key is not valid Base64"); + } + g_free (tmp_b64); + } + + if (key_data_size != expect_size) { + GST_ERROR_OBJECT (element, + "Bad Base64-decoded master key size: got %lu, expected %lu", + key_data_size, expect_size); + throw KurentoException (MEDIA_OBJECT_ILLEGAL_PARAM_ERROR, + "Master key size is wrong"); + } + + g_object_set (element, "master-key", key_b64.data(), + "crypto-suite", crypto->getCrypto()->getValue(), NULL); +} + + +SipRtpEndpointImpl::~SipRtpEndpointImpl() +{ + if (handlerOnKeySoftLimit > 0) { + unregister_signal_handler (element, handlerOnKeySoftLimit); + } +} + +void +SipRtpEndpointImpl::postConstructor () +{ + BaseRtpEndpointImpl::postConstructor (); + + handlerOnKeySoftLimit = register_signal_handler (G_OBJECT (element), + "key-soft-limit", + std::function + (std::bind (&SipRtpEndpointImpl::onKeySoftLimit, this, + std::placeholders::_2) ), + std::dynamic_pointer_cast + (shared_from_this() ) ); +} + +MediaObjectImpl * +SipRtpEndpointImplFactory::createObject (const boost::property_tree::ptree &conf, + std::shared_ptr mediaPipeline, + std::shared_ptr crypto, + bool cryptoAgnostic, + bool useIpv6) const +{ + // Here we have made a real special construct to deal with Kurento object system to inreface with + // an implementation of and object composed of others. + // When Kurento compiles the interface of a remote object generates an schema to execute the + // methods in the remote object that the client demands. This consist of implementing the + // invoke mnethod for the Impl class in the generated sources (so that it cannot be changed) + // and also chains its execution to base classes + // Here we need to implement a "fake" class that resembles the interface we defined + // but that in fact is composed of other objects. + // SO, in fact we createObject a different class that acts as Facade of this + // and that needs to implement all methods from this object interface and surely + // delegate on this class (or other depending on the funtionality). + return new FacadeRtpEndpointImpl (conf, mediaPipeline, crypto, cryptoAgnostic, useIpv6); +} + + + +void +SipRtpEndpointImpl::onKeySoftLimit (gchar *media) +{ + std::shared_ptr type; + + if (g_strcmp0 (media, "audio") == 0) { + type = std::make_shared(MediaType::AUDIO); + } else if (g_strcmp0 (media, "video") == 0) { + type = std::make_shared(MediaType::VIDEO); + } else if (g_strcmp0 (media, "data") == 0) { + type = std::make_shared(MediaType::DATA); + } else { + GST_ERROR ("Unsupported media %s", media); + return; + } + + try { + OnKeySoftLimit event (shared_from_this (), OnKeySoftLimit::getName (), + type); + sigcSignalEmit(signalOnKeySoftLimit, event); + } catch (const std::bad_weak_ptr &e) { + // shared_from_this() + GST_ERROR ("BUG creating %s: %s", OnKeySoftLimit::getName ().c_str (), + e.what ()); + } +} +SipRtpEndpointImpl::StaticConstructor SipRtpEndpointImpl::staticConstructor; + +SipRtpEndpointImpl::StaticConstructor::StaticConstructor() +{ + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, GST_DEFAULT_NAME, 0, + GST_DEFAULT_NAME); +} + +std::shared_ptr SipRtpEndpointImpl::getCleanEndpoint (const boost::property_tree::ptree &conf, + std::shared_ptr mediaPipeline, + std::shared_ptr crypto, bool useIpv6, + const std::string &sdp, + bool continue_audio_stream, + bool continue_video_stream) +{ + std::shared_ptr newEndpoint = std::shared_ptr(new SipRtpEndpointImpl (conf, mediaPipeline, crypto, useIpv6)); + + // Recover ports (sockets) from last SipRtpEndpoint and SSRCs to filter out old traffic + this->cloneToNewEndpoint (newEndpoint, sdp, continue_audio_stream, continue_video_stream); + return newEndpoint; +} + +std::shared_ptr SipRtpEndpointImpl::cloneToNewEndpoint ( + std::shared_ptr newEp, + const std::string &sdp, + bool continue_audio_stream, + bool continue_video_stream) +{ + g_signal_emit_by_name (element, "clone-to-new-ep", newEp->element, sdp.c_str()); + + return newEp; +} + +void SipRtpEndpointImpl::setAudioSsrc (guint32 ssrc) +{ + g_object_set (element, "audio_ssrc", ssrc, NULL); +} + +void SipRtpEndpointImpl::setVideoSsrc (guint32 ssrc) +{ + g_object_set (element, "video_ssrc", ssrc, NULL); +} + + +} /* kurento */ diff --git a/src/server/implementation/objects/SipRtpEndpointImpl.hpp b/src/server/implementation/objects/SipRtpEndpointImpl.hpp new file mode 100644 index 00000000..0c2b3a5a --- /dev/null +++ b/src/server/implementation/objects/SipRtpEndpointImpl.hpp @@ -0,0 +1,96 @@ +/* + * (C) Copyright 2016 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef __REUSABLE_RTP_ENDPOINT_IMPL_HPP__ +#define __REUSABLE_RTP_ENDPOINT_IMPL_HPP__ + +#include "BaseRtpEndpointImpl.hpp" +#include "SipRtpEndpoint.hpp" +#include "PassThroughImpl.hpp" +#include +#include + +namespace kurento +{ + +class MediaPipeline; + +class SipRtpEndpointImpl; + +void Serialize (std::shared_ptr &object, + JsonSerializer &serializer); + +// TODO: SipRtpEndpoint can inherit from RtpEndpoint, but we need to add a protected constructor to RtpEndpointImpl that allows us to specify the FACTORY_NAME for GStreamer element +class SipRtpEndpointImpl : public BaseRtpEndpointImpl, public virtual SipRtpEndpoint +{ + +public: + + SipRtpEndpointImpl (const boost::property_tree::ptree &conf, + std::shared_ptr mediaPipeline, + std::shared_ptr crypto, + bool useIpv6); + + virtual ~SipRtpEndpointImpl (); + + sigc::signal signalOnKeySoftLimit; + + std::shared_ptr getCleanEndpoint (const boost::property_tree::ptree &conf, + std::shared_ptr mediaPipeline, + std::shared_ptr crypto, bool useIpv6, + const std::string &sdp, + bool continue_audio_stream, + bool continue_video_stream); + + + void setAudioSsrc (guint32 ssrc); + void setVideoSsrc (guint32 ssrc); + + /* Next methods are automatically implemented by code generator */ + using BaseRtpEndpointImpl::connect; + virtual bool connect (const std::string &eventType, + std::shared_ptr handler) override; + + virtual void invoke (std::shared_ptr obj, + const std::string &methodName, const Json::Value ¶ms, + Json::Value &response) override; + + virtual void Serialize (JsonSerializer &serializer) override; + + virtual void postConstructor () override; + + +protected: +private: + + gulong handlerOnKeySoftLimit = 0; + void onKeySoftLimit (gchar *media); + + std::shared_ptr cloneToNewEndpoint (std::shared_ptr newEp, const std::string &sdp, bool continue_audio_stream, bool continue_video_stream); + + class StaticConstructor + { + public: + StaticConstructor(); + }; + + static StaticConstructor staticConstructor; + +}; + +} /* kurento */ + +#endif /* __SIP_RTP_ENDPOINT_IMPL_HPP__ */ diff --git a/src/server/interface/elements.SipRtpEndpoint.kmd.json b/src/server/interface/elements.SipRtpEndpoint.kmd.json new file mode 100644 index 00000000..b3b55861 --- /dev/null +++ b/src/server/interface/elements.SipRtpEndpoint.kmd.json @@ -0,0 +1,114 @@ +{ + "remoteClasses": [ + { + "name": "SipRtpEndpoint", + "extends": "BaseRtpEndpoint", + "doc": "Endpoint that provides the same functionality and features that :rom:cls:`RtpEndpoint` providing flexibility to allow integrating with legacy VoIP SIP networks. It mainly provides two features: +
    +
  • providing SDP renegotiation features that allow easy implementation of SIP 183 like flows. This endpoint inherits from :rom:cls:`BaseRtpEndpoint`
  • +
  • PRoviding flexible adaptation to use SRTP or not. That is, if this endpoint is configured with crypto, it can also be configured to offer SRTP only or SRTP and RTP communication channels, and can also process RTP or SRTP offers transperently.
  • +
+

+

+ It is however recommended that if you do not need any of thess features, to use the :rom:cls:`RtpEndpoint` to avoid unneeded overload due to the components instatiated to allow renegotiation. +

+

+ Unlike :rom:cls:`RtpEndpoint` this endpoint allows calling the SDP negotiation methods `generateOffer`, `processOffer` and `processOffer` + at any time regardless of the status of any previous SDP negotiation. +

+ When any of the SDP negotiation methods are called and no previous negotiation has been made, the same rules that apply to + :rom:cls:`RtpEndpoint` will apply now. +

+

+ If any of the SDP negotiation methods are called and a previous negotiation were initiated (it does not matter if it is completed and media flowing or not) + the previous media is discarded and new media will be established according to the new SDP (offer or answer) exchanged. +

+

+ More specifically: +

    +
  • `generateOffer`, if this method is called with a previous negotiation initiated or even completed, the old media will be discarded and a new SDP offer will be generated. much like it would be done with a new RtpEndpoint. Ports and SSRC may change. Codecs will usually not change
  • +
  • `processOffer`, much like the previous one, if a previous negotiation was initiated or even completed, media will be discarded, ports and ssrcs may also change as it will be answering to a different SDP offer
  • +
  • `processAnswer`, similar to the previous this has some subtelties, as we are processing a new answer, original offer is maintained, this implies that ports, ssrcs and codecs offered by this endpoint are preserved. This implies that this new answer must be generated as a response to the original offer
  • +
+

+

+ If the answer changes chosen codecs, it may be possible that the endpoint processing the answer also instantiates a transcoder to adapt to the new codecs. + This is needed because, if the original negotiation established some codecs, this affect at the pipeline and any other connected media Element. + So, if renegotiation implies some change in codecs, the pipeline cannot change codecs in the other MediaElements, so a transocding is needed to adapt media with new codecs to the + already established ones. +

+

+ This Endpoint also allows for flexibility when negotiating an RTP or SRTP endpoint. This is intended for easy integration with legacy VoIP/SIP networks where you cannot know in advance if the remote endpoint will support SRTP or not. + In that case, the process is as follows: +

    +
  • To configure an endpoint to be SRTP/RTP agnostic, it must be created with the cryptoAgnostic parameter in the builder set to true. When it is set to true + it means that if processing an offer, it will process and accept both offers with RTP/AVP profile and with RTP/SAVP profiles
  • +
  • If it also has configured the crypto argument when built, when generating and offer (call to generateOffer), will generate two sets of media lines, one for RTP/AVP profile and the other for RTP/SAVP profile. + Thus allowing the remote peer to select which lines it is interested in (secured or not secured). And provide the answer according to the chosen set. It is important to note that the remote peer should also + answer to the m lines it is not interested in with the port set to 0
  • +
  • Thus, if the endpoint is built with agnosticCrypto and crypto information, the offers generated will contain both RTP/AVP and RTP/SAVP lines, and it will accept answers to that offer that should accept a single + set of m lines (in those the answer should set the port of those lines to a valid number different from 0), and reject the lines (setting the port of those lines to 0). It is important to note, that if for any reason + the answer processed contains valid m lines (port set to a valid number different from 0) for both RTP/AVP and RTP/SAVP lines, the endpoint will + just establish one of the set (if possible the one corresponding to RTP/SAVP profile)
  • +
  • Then
  • +
+

+

+ There are some elements to take into account when dealing with VoIP environments. First one is that they can change SSRC on the fly of a live RTP flow. This may be due to internal switching of media in VoIP provider, but it makes the RtpEndpoint useless as any change on the fly to the SSRC takes the RtpEndpoint to an open ended pipeline that causes a 'not linked' error and pauses the RtpEndpoint. SipRtpEndpoint supports SSRC switcjing on the fly by examining incoming SSRC in RTP/RTCP packets and if not used in previous media connections it let them pass, but changing in the RTP/RTCP packet the SSRC to the one of the first packet received in current RTP flow. +

+

+ Another one is that when dealing with early media (used with SIP answer to INVITE 183). Sometimes the remote peer signals an early media (SIP answer 183 with an SDP) and then a definitive answer (200 with another SDP). But in fact there is no stream switching. SSRC, base timestamp, all is preserved. The point is that there is no change in media and no renegotiation is needed. The bad point is how to decide when renegotiation happens (two consecutive processAnswer) if packets correspondo to old stream or if no stream switching has happened. If control application is able to know that no stream switching is happening, and no renegotiation is needed it could just skip renegotiation (second processAnswer). But if not we can only make a guess: if second answer include same streams that first answer (same IP, port and profile), ost surely there is no stream switching. so that guess is also made on SipRtpEndpoint so that it behaves as expected in VoIP environments +

+ ", + "constructor": + { + "doc": "Builder for the :rom:cls:`SipRtpEndpoint`", + "params": [ + { + "name": "mediaPipeline", + "doc": "the :rom:cls:`MediaPipeline` to which the endpoint belongs", + "type": "MediaPipeline" + }, + { + "name": "crypto", + "doc": "SDES-type param. If present, this parameter indicates that the communication will be encrypted. By default no encryption is used. This behaves exactly the same as the crypto builder parameter for RtpEndpoint.", + "type": "SDES", + "optional": true, + "defaultValue": {} + }, + { + "name": "cryptoAgnostic", + "doc": "This connfigured the endpoint to be SRTP/RTP agnostic, that is to be able to offer and accept both secure and not secure profiles (SRTP/SAVP and RTP/AVP)", + "type": "boolean", + "optional": true, + "defaultValue": false + }, + { + "name": "useIpv6", + "doc": "This configures the endpoint to use IPv6 instead of IPv4.", + "type": "boolean", + "optional": true, + "defaultValue": false + } + ] + }, + "events": [ + "OnKeySoftLimit" + ] + } + ], + "events": [ + { + "name": "OnKeySoftLimit", + "doc": "Fired when encryption is used and any stream reached the soft key usage limit, which means it will expire soon.", + "extends": "Media", + "properties": [ + { + "name": "mediaType", + "doc": "The media stream", + "type": "MediaType" + } + ] + } + ] +} diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt index 52a37337..645288e6 100644 --- a/tests/server/CMakeLists.txt +++ b/tests/server/CMakeLists.txt @@ -108,3 +108,66 @@ target_link_libraries(test_player ${LIBRARY_NAME}impl ${KMSCORE_LIBRARIES} ) + +add_test_program(test_sip_rtp_endpoint sipRtpEndpoint.cpp) +add_dependencies(test_sip_rtp_endpoint kmselementsplugins) +set_property(TARGET test_sip_rtp_endpoint + PROPERTY INCLUDE_DIRECTORIES + ${KmsJsonRpc_INCLUDE_DIRS} + ${sigc++-2.0_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/server/implementation/objects + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/server/implementation + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/server/interface + ${CMAKE_CURRENT_BINARY_DIR}/../../src/server/interface/generated-cpp + ${CMAKE_CURRENT_BINARY_DIR}/../../src/server/implementation/generated-cpp + ${KMSCORE_INCLUDE_DIRS} + ${gstreamer-1.5_INCLUDE_DIRS} +) +target_link_libraries(test_sip_rtp_endpoint + ${LIBRARY_NAME}impl + ${KMSCORE_LIBRARIES} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} +) + +add_test_program(test_sip_rtp_endpoint_play sipRtpEndpointPlay.cpp) +add_dependencies(test_sip_rtp_endpoint_play kmselementsplugins) +set_property(TARGET test_sip_rtp_endpoint_play + PROPERTY INCLUDE_DIRECTORIES + ${KmsJsonRpc_INCLUDE_DIRS} + ${sigc++-2.0_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/server/implementation/objects + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/server/implementation + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/server/interface + ${CMAKE_CURRENT_BINARY_DIR}/../../src/server/interface/generated-cpp + ${CMAKE_CURRENT_BINARY_DIR}/../../src/server/implementation/generated-cpp + ${KMSCORE_INCLUDE_DIRS} + ${gstreamer-1.5_INCLUDE_DIRS} +) +target_link_libraries(test_sip_rtp_endpoint_play + ${LIBRARY_NAME}impl + ${KMSCORE_LIBRARIES} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} +) + +add_test_program(test_sip_rtp_endpoint_agnostic_srtp sipRtpEndpoint_agnostic_srtp.cpp) +add_dependencies(test_sip_rtp_endpoint_agnostic_srtp kmssiprtpmodule) +set_property(TARGET test_sip_rtp_endpoint_agnostic_srtp + PROPERTY INCLUDE_DIRECTORIES + ${KmsJsonRpc_INCLUDE_DIRS} + ${sigc++-2.0_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/server/implementation/objects + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/server/implementation + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/server/interface + ${CMAKE_CURRENT_BINARY_DIR}/../../src/server/interface/generated-cpp + ${CMAKE_CURRENT_BINARY_DIR}/../../src/server/implementation/generated-cpp + ${KMSCORE_INCLUDE_DIRS} + ${gstreamer-1.5_INCLUDE_DIRS} +) +target_link_libraries(test_sip_rtp_endpoint_agnostic_srtp + ${LIBRARY_NAME}impl + ${KMSCORE_LIBRARIES} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} +) diff --git a/tests/server/sipRtpEndpoint.cpp b/tests/server/sipRtpEndpoint.cpp new file mode 100644 index 00000000..91536484 --- /dev/null +++ b/tests/server/sipRtpEndpoint.cpp @@ -0,0 +1,539 @@ +/* + * (C) Copyright 2016 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define BOOST_TEST_STATIC_LINK +#define BOOST_TEST_PROTECTED_VIRTUAL + +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +//#include + +#include + +#include + +using namespace kurento; +using namespace boost::unit_test; + +boost::property_tree::ptree config; +std::string mediaPipelineId; +ModuleManager moduleManager; + +struct GF { + GF(); + ~GF(); +}; + +BOOST_GLOBAL_FIXTURE (GF); + +GF::GF() +{ + boost::property_tree::ptree ac, audioCodecs, vc, videoCodecs; + gst_init(nullptr, nullptr); + +// moduleManager.loadModulesFromDirectories ("./src/server:../../kms-omni-build:../../src/server:../../../../kms-omni-build"); + moduleManager.loadModulesFromDirectories ("../../src/server"); + + config.add ("configPath", "../../../tests" ); + config.add ("modules.kurento.SdpEndpoint.numAudioMedias", 1); + config.add ("modules.kurento.SdpEndpoint.numVideoMedias", 1); + + ac.put ("name", "opus/48000/2"); + audioCodecs.push_back (std::make_pair ("", ac) ); + config.add_child ("modules.kurento.SdpEndpoint.audioCodecs", audioCodecs); + + vc.put ("name", "VP8/90000"); + videoCodecs.push_back (std::make_pair ("", vc) ); + config.add_child ("modules.kurento.SdpEndpoint.videoCodecs", videoCodecs); + + mediaPipelineId = moduleManager.getFactory ("MediaPipeline")->createObject ( + config, "", + Json::Value() )->getId(); +} + +GF::~GF() +{ + MediaSet::deleteMediaSet(); +} + +#define CRYPTOKEY "00108310518720928b30d38f41149351559761969b71d79f8218a39259a7" + +//static std::shared_ptr getCrypto () +//{ +// std::shared_ptr crypto = std::make_shared(new kurento::SDES()); +// std::shared_ptr cryptoSuite = std::make_shared (new kurento::CryptoSuite (kurento::CryptoSuite::AES_128_CM_HMAC_SHA1_80)); +// +// crypto->setCrypto(cryptoSuite); +// crypto->setKey(CRYPTOKEY); +// return crypto; +//} + + +static std::shared_ptr +createRtpEndpoint (bool useIpv6, bool useCrypto) +{ + std::shared_ptr rtpEndpoint; + Json::Value constructorParams; + + constructorParams ["mediaPipeline"] = mediaPipelineId; + constructorParams ["useIpv6"] = useIpv6; +// if (useCrypto) { +// constructorParams ["crypto"] = getCrypto ()->; +// } + + rtpEndpoint = moduleManager.getFactory ("SipRtpEndpoint")->createObject ( + config, "", + constructorParams ); + + return std::dynamic_pointer_cast (rtpEndpoint); +} + +static void +releaseRtpEndpoint (std::shared_ptr &ep) +{ + std::string id = ep->getId(); + + ep.reset(); + MediaSet::getMediaSet ()->release (id); +} + +static std::shared_ptr createTestSrc() { + std::shared_ptr src = std::dynamic_pointer_cast + (MediaSet::getMediaSet()->ref (new MediaElementImpl ( + boost::property_tree::ptree(), + MediaSet::getMediaSet()->getMediaObject (mediaPipelineId), + "dummysrc") ) ); + + g_object_set (src->getGstreamerElement(), "audio", TRUE, "video", TRUE, NULL); + + return std::dynamic_pointer_cast (src); +} + +static void +releaseTestSrc (std::shared_ptr &ep) +{ + std::string id = ep->getId(); + + ep.reset(); + MediaSet::getMediaSet ()->release (id); +} + +static void +media_state_changes_impl (bool useIpv6, bool useCrypto) +{ + std::atomic media_state_changed (false); + std::condition_variable cv; + std::mutex mtx; + std::unique_lock lck (mtx); + + std::shared_ptr rtpEpOfferer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr src = createTestSrc(); + + src->connect (rtpEpOfferer); + + sigc::connection conn = rtpEpAnswerer->getSignalMediaStateChanged ().connect ([&] ( + MediaStateChanged event) { + std::shared_ptr state = event.getNewState(); + BOOST_CHECK (state->getValue() == MediaState::CONNECTED); + media_state_changed = true; + cv.notify_one(); + }); + + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer: " + offer); + + std::string answer = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer: " + answer); + + rtpEpOfferer->processAnswer (answer); + + cv.wait (lck, [&] () { + return media_state_changed.load(); + }); + + if (!media_state_changed) { + BOOST_ERROR ("Not media state chagned"); + } + + conn.disconnect (); + + releaseTestSrc (src); + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); + +} + +static void +media_state_changes () +{ + BOOST_TEST_MESSAGE ("Start test: media_state_changes"); + media_state_changes_impl (false, false); +} + +static void +media_state_changes_ipv6 () +{ + BOOST_TEST_MESSAGE ("Start test: media_state_changes_ipv6"); + media_state_changes_impl (true, false); +} + +static void +reconnection_generate_offer_state_changes_impl (bool useIpv6, bool useCrypto) +{ + std::shared_ptr rtpEpOfferer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (useIpv6, useCrypto); + std::atomic conn_state_changed (false); + std::condition_variable cv; + std::mutex mtx; + std::unique_lock lck (mtx); + + rtpEpAnswerer->getSignalConnectionStateChanged().connect ([&] ( + ConnectionStateChanged event) { + conn_state_changed = true; + cv.notify_one(); + }); + + try { + std::string offer1 = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer1: " + offer1); + + std::string offer2 = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer2: " + offer2); + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::DISCONNECTED) { + BOOST_ERROR ("Connection must be disconnected"); + } + + std::string answer = rtpEpAnswerer->processOffer (offer2); + BOOST_TEST_MESSAGE ("answer: " + answer); + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::DISCONNECTED) { + BOOST_ERROR ("Connection must be disconnected"); + } + + rtpEpOfferer->processAnswer (answer); + + cv.wait (lck, [&] () { + return conn_state_changed.load(); + }); + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + if (!conn_state_changed) { + BOOST_ERROR ("Not conn state chagned"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); +} + + +static void +reconnection_process_offer_state_changes_impl (bool useIpv6, bool useCrypto) +{ + std::shared_ptr rtpEpOfferer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (useIpv6, useCrypto); + std::atomic conn_state_changed (false); + std::condition_variable cv; + std::mutex mtx; + std::unique_lock lck (mtx); + + rtpEpAnswerer->getSignalConnectionStateChanged().connect ([&] ( + ConnectionStateChanged event) { + conn_state_changed = true; + cv.notify_one(); + }); + + try { + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer: " + offer); + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::DISCONNECTED) { + BOOST_ERROR ("Connection must be disconnected"); + } + + std::string answer1 = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer1: " + answer1); + + std::string answer2 = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer2: " + answer2); + + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::DISCONNECTED) { + BOOST_ERROR ("Connection must be disconnected"); + } + + rtpEpOfferer->processAnswer (answer2); + + cv.wait (lck, [&] () { + return conn_state_changed.load(); + }); + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + if (!conn_state_changed) { + BOOST_ERROR ("Not conn state chagned"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); +} + +static void +reconnection_process_answer_state_changes_impl (bool useIpv6, bool useCrypto) +{ + std::shared_ptr rtpEpOfferer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer2 = createRtpEndpoint (useIpv6, useCrypto); + std::atomic conn_state_changed (false); + std::condition_variable cv; + std::mutex mtx; + std::unique_lock lck (mtx); + + rtpEpOfferer->getSignalConnectionStateChanged().connect ([&] ( + ConnectionStateChanged event) { + conn_state_changed = true; + cv.notify_one(); + }); + + try { + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer: " + offer); + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::DISCONNECTED) { + BOOST_ERROR ("Connection must be disconnected"); + } + + std::string answer1 = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer1: " + answer1); + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::DISCONNECTED) { + BOOST_ERROR ("Connection must be disconnected"); + } + + rtpEpOfferer->processAnswer (answer1); + + cv.wait (lck, [&] () { + return conn_state_changed.load(); + }); + + std::string answer2 = rtpEpAnswerer2->processOffer (offer); + BOOST_TEST_MESSAGE ("answer2: " + answer2); + + rtpEpOfferer->processAnswer (answer2); + + cv.wait (lck, [&] () { + return conn_state_changed.load(); + }); + + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + if (!conn_state_changed) { + BOOST_ERROR ("Not conn state chagned"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); + releaseRtpEndpoint (rtpEpAnswerer2); +} + + +static void +connection_state_changes_impl (bool useIpv6, bool useCrypto) +{ + std::shared_ptr rtpEpOfferer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (useIpv6, useCrypto); + std::atomic conn_state_changed (false); + std::condition_variable cv; + std::mutex mtx; + std::unique_lock lck (mtx); + + rtpEpAnswerer->getSignalConnectionStateChanged().connect ([&] ( + ConnectionStateChanged event) { + conn_state_changed = true; + cv.notify_one(); + }); + + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer: " + offer); + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::DISCONNECTED) { + BOOST_ERROR ("Connection must be disconnected"); + } + + std::string answer = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer: " + answer); + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::DISCONNECTED) { + BOOST_ERROR ("Connection must be disconnected"); + } + + rtpEpOfferer->processAnswer (answer); + + cv.wait (lck, [&] () { + return conn_state_changed.load(); + }); + + if (!conn_state_changed) { + BOOST_ERROR ("Not conn state chagned"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); +} + +static void +connection_state_changes () +{ + BOOST_TEST_MESSAGE ("Start test: connection_state_changes"); + connection_state_changes_impl (false, false); +} + +static void +connection_state_changes_ipv6 () +{ + BOOST_TEST_MESSAGE ("Start test: connection_state_changes_ipv6"); + connection_state_changes_impl (true, false); +} + +static void +reconnection_generate_offer_state_changes() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_generate_offer_state_changes"); + reconnection_generate_offer_state_changes_impl (false, false); +} + +static void +reconnection_generate_offer_state_changes_ipv6() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_generate_offer_state_changes_ipv6"); + reconnection_generate_offer_state_changes_impl (true, false); +} + +static void +reconnection_process_offer_state_changes() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_process_offer_state_changes"); + reconnection_process_offer_state_changes_impl (false, false); +} + +static void +reconnection_process_offer_state_changes_ipv6() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_process_offer_state_changes_ipv6"); + reconnection_process_offer_state_changes_impl (true, false); +} + +static void +reconnection_process_answer_state_changes() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_process_offer_state_changes"); + reconnection_process_answer_state_changes_impl (false, false); +} + +static void +reconnection_process_answer_state_changes_ipv6() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_process_offer_state_changes_ipv6"); + reconnection_process_answer_state_changes_impl (true, false); +} + + +test_suite * +init_unit_test_suite ( int , char *[] ) +{ + test_suite *test = BOOST_TEST_SUITE ( "SipRtpEndpoint" ); + + test->add (BOOST_TEST_CASE ( &media_state_changes ), 0, /* timeout */ 15); + test->add (BOOST_TEST_CASE ( &connection_state_changes ), 0, /* timeout */ 15); + test->add (BOOST_TEST_CASE ( &reconnection_generate_offer_state_changes ), 0, /* timeout */ 15); + test->add (BOOST_TEST_CASE ( &reconnection_process_offer_state_changes ), 0, /* timeout */ 15); + test->add (BOOST_TEST_CASE ( &reconnection_process_answer_state_changes ), 0, /* timeout */ 15); + + if (false) { + test->add (BOOST_TEST_CASE ( &media_state_changes_ipv6 ), 0, /* timeout */ 15); + test->add (BOOST_TEST_CASE ( &connection_state_changes_ipv6 ), 0, /* timeout */ 15); + test->add (BOOST_TEST_CASE ( &reconnection_generate_offer_state_changes_ipv6 ), 0, /* timeout */ 15); + test->add (BOOST_TEST_CASE ( &reconnection_process_offer_state_changes_ipv6 ), 0, /* timeout */ 15); + test->add (BOOST_TEST_CASE ( &reconnection_process_answer_state_changes_ipv6 ), 0, /* timeout */ 15); + } + return test; +} diff --git a/tests/server/sipRtpEndpointPlay.cpp b/tests/server/sipRtpEndpointPlay.cpp new file mode 100644 index 00000000..40e95efd --- /dev/null +++ b/tests/server/sipRtpEndpointPlay.cpp @@ -0,0 +1,755 @@ +/* + * (C) Copyright 2016 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define BOOST_TEST_STATIC_LINK +#define BOOST_TEST_PROTECTED_VIRTUAL + +#include +#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +//#include + +#include + +#include + + +#define PLAYER_MEDIA_1 "" +#define PLAYER_MEDIA_2 "" +#define PLAYER_MEDIA_3 "" + + +using namespace kurento; +using namespace boost::unit_test; + +boost::property_tree::ptree config; +std::string mediaPipelineId; +ModuleManager moduleManager; + +struct GF { + GF(); + ~GF(); +}; + +BOOST_GLOBAL_FIXTURE (GF); + +GF::GF() +{ + boost::property_tree::ptree ac, audioCodecs, vc, videoCodecs; + gst_init(nullptr, nullptr); + +// moduleManager.loadModulesFromDirectories ("../../src/server:./src/server:../../kms-omni-build:../../src/server:../../../../kms-omni-build"); + moduleManager.loadModulesFromDirectories ("../../src/server"); + + config.add ("configPath", "../../../tests" ); + config.add ("modules.kurento.SdpEndpoint.numAudioMedias", 1); + config.add ("modules.kurento.SdpEndpoint.numVideoMedias", 1); + + ac.put ("name", "opus/48000/2"); + audioCodecs.push_back (std::make_pair ("", ac) ); + config.add_child ("modules.kurento.SdpEndpoint.audioCodecs", audioCodecs); + + vc.put ("name", "VP8/90000"); + videoCodecs.push_back (std::make_pair ("", vc) ); + config.add_child ("modules.kurento.SdpEndpoint.videoCodecs", videoCodecs); + + mediaPipelineId = moduleManager.getFactory ("MediaPipeline")->createObject ( + config, "", + Json::Value() )->getId(); +} + +GF::~GF() +{ + MediaSet::deleteMediaSet(); +} + +#define CRYPTOKEY "00108310518720928b30d38f41149351559761969b71d79f8218a39259a7" + +//static std::shared_ptr getCrypto () +//{ +// std::shared_ptr crypto = std::make_shared(new kurento::SDES()); +// std::shared_ptr cryptoSuite = std::make_shared (new kurento::CryptoSuite (kurento::CryptoSuite::AES_128_CM_HMAC_SHA1_80)); +// +// crypto->setCrypto(cryptoSuite); +// crypto->setKey(CRYPTOKEY); +// return crypto; +//} + +static std::shared_ptr +createPassThrough () +{ + std::shared_ptr pt; + Json::Value constructorParams; + + constructorParams ["mediaPipeline"] = mediaPipelineId; + + pt = moduleManager.getFactory ("PassThrough")->createObject ( + config, "", + constructorParams ); + + return std::dynamic_pointer_cast (pt); +} + +static void +releasePassTrhough (std::shared_ptr &ep) +{ + std::string id = ep->getId(); + + ep.reset(); + MediaSet::getMediaSet ()->release (id); +} + + +static std::shared_ptr +createRtpEndpoint (bool useIpv6, bool useCrypto) +{ + std::shared_ptr rtpEndpoint; + Json::Value constructorParams; + + constructorParams ["mediaPipeline"] = mediaPipelineId; + constructorParams ["useIpv6"] = useIpv6; +// if (useCrypto) { +// constructorParams ["crypto"] = getCrypto ()->; +// } + + rtpEndpoint = moduleManager.getFactory ("SipRtpEndpoint")->createObject ( + config, "", + constructorParams ); + + return std::dynamic_pointer_cast (rtpEndpoint); +} + +static void +releaseRtpEndpoint (std::shared_ptr &ep) +{ + std::string id = ep->getId(); + + ep.reset(); + MediaSet::getMediaSet ()->release (id); +} + +static std::shared_ptr createTestSrc() { + std::shared_ptr src = std::dynamic_pointer_cast + (MediaSet::getMediaSet()->ref (new MediaElementImpl ( + boost::property_tree::ptree(), + MediaSet::getMediaSet()->getMediaObject (mediaPipelineId), + "dummysrc") ) ); + + g_object_set (src->getGstreamerElement(), "audio", TRUE, "video", TRUE, NULL); + + return std::dynamic_pointer_cast (src); +} + +static std::shared_ptr createTestAudioSrc() { + std::shared_ptr src = std::dynamic_pointer_cast + (MediaSet::getMediaSet()->ref (new MediaElementImpl ( + boost::property_tree::ptree(), + MediaSet::getMediaSet()->getMediaObject (mediaPipelineId), + "dummysrc") ) ); + + g_object_set (src->getGstreamerElement(), "audio", TRUE, "video", FALSE, NULL); + + return std::dynamic_pointer_cast (src); +} + +static void +releaseTestSrc (std::shared_ptr &ep) +{ + std::string id = ep->getId(); + + ep.reset(); + MediaSet::getMediaSet ()->release (id); +} + +static std::shared_ptr getMediaElement (std::shared_ptr element) +{ + return std::dynamic_pointer_cast (element); +} + + +static void +media_state_changes_impl (bool useIpv6, bool useCrypto) +{ + std::atomic media_state_changed (false); + std::condition_variable cv; + std::mutex mtx; + std::unique_lock lck (mtx); + + std::shared_ptr rtpEpOfferer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr src = createTestSrc(); + std::shared_ptr pt = createPassThrough (); + + src->connect (rtpEpOfferer); + + rtpEpAnswerer->connect(pt); + + sigc::connection conn = getMediaElement(pt)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + if (state->getValue() == MediaFlowState::FLOWING) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed = true; + cv.notify_one(); + } + } + ); + + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer: " + offer); + + std::string answer = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer: " + answer); + + rtpEpOfferer->processAnswer (answer); + + cv.wait (lck, [&] () { + return media_state_changed.load(); + }); + + if (!media_state_changed) { + BOOST_ERROR ("Not media Flowing"); + } + + conn.disconnect (); + + releaseTestSrc (src); + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); + releasePassTrhough (pt); + +} + +static void +media_state_changes () +{ + BOOST_TEST_MESSAGE ("Start test: media_state_changes"); + media_state_changes_impl (false, false); +} + +static void +media_state_changes_ipv6 () +{ + BOOST_TEST_MESSAGE ("Start test: media_state_changes_ipv6"); + media_state_changes_impl (true, false); +} + +static void +reconnection_generate_offer_state_changes_impl (bool useIpv6, bool useCrypto) +{ + std::atomic media_state_changed (false); + std::atomic media_state_changed2 (false); + std::shared_ptr rtpEpOfferer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer2 = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr pt = createPassThrough (); + std::shared_ptr pt2 = createPassThrough (); + std::shared_ptr src = createTestSrc(); + std::condition_variable cv; + std::condition_variable cv2; + std::mutex mtx; + std::unique_lock lck (mtx); + std::mutex mtx2; + std::unique_lock lck2 (mtx2); + + src->connect(rtpEpOfferer); + rtpEpAnswerer->connect(pt); + rtpEpAnswerer2->connect(pt2); + + sigc::connection conn = getMediaElement(pt)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + if (state->getValue() == MediaFlowState::FLOWING) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed = true; + cv.notify_one(); + } + } + ); + + sigc::connection conn2 = getMediaElement(pt2)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + if (state->getValue() == MediaFlowState::FLOWING) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed2 = true; + cv2.notify_one(); + } + } + ); + + try { + std::string offer1 = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer1: " + offer1); + + std::string answer1 = rtpEpAnswerer->processOffer(offer1); + BOOST_TEST_MESSAGE ("answer1: " + answer1); + + rtpEpOfferer->processAnswer(answer1); + + // First stream + cv.wait (lck, [&] () { + return media_state_changed.load(); + }); + conn.disconnect (); + + if (!media_state_changed) { + BOOST_ERROR ("Not media Flowing"); + } + + std::string offer2 = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer2: " + offer2); + + std::string answer2 = rtpEpAnswerer2->processOffer(offer2); + BOOST_TEST_MESSAGE ("answer2: " + answer2); + + rtpEpOfferer->processAnswer(answer2); + + // Second stream + cv2.wait (lck2, [&] () { + return media_state_changed2.load(); + }); + conn2.disconnect (); + + + if (!media_state_changed2) { + BOOST_ERROR ("Not media Flowing"); + } + + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); + releaseRtpEndpoint (rtpEpAnswerer2); + releasePassTrhough (pt); + releasePassTrhough (pt2); +} + + +static void +reconnection_process_offer_state_changes_impl (bool useIpv6, bool useCrypto) +{ + std::atomic media_state_changed (false); + std::atomic media_state_changed2 (false); + std::shared_ptr rtpEpOfferer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpOfferer2 = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr pt = createPassThrough (); + std::shared_ptr pt2 = createPassThrough (); + std::shared_ptr src = createTestSrc(); + std::condition_variable cv; + std::condition_variable cv2; + std::mutex mtx; + std::unique_lock lck (mtx); + std::mutex mtx2; + std::unique_lock lck2 (mtx2); + + src->connect(rtpEpAnswerer); + rtpEpOfferer->connect(pt); + rtpEpOfferer2->connect(pt2); + + sigc::connection conn = getMediaElement(pt)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + if (state->getValue() == MediaFlowState::FLOWING) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed = true; + cv.notify_one(); + } + } + ); + + sigc::connection conn2 = getMediaElement(pt2)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + if (state->getValue() == MediaFlowState::FLOWING) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed2 = true; + cv2.notify_one(); + } + } + ); + + try { + std::string offer1 = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer1: " + offer1); + + std::string answer1 = rtpEpAnswerer->processOffer (offer1); + BOOST_TEST_MESSAGE ("answer1: " + answer1); + + rtpEpOfferer->processAnswer(answer1); + + // First stream + cv.wait (lck, [&] () { + return media_state_changed.load(); + }); + conn.disconnect (); + + if (!media_state_changed) { + BOOST_ERROR ("Not media Flowing"); + } + + std::string offer2 = rtpEpOfferer2->generateOffer (); + BOOST_TEST_MESSAGE ("offer2: " + offer2); + + std::string answer2 = rtpEpAnswerer->processOffer (offer2); + BOOST_TEST_MESSAGE ("answer2: " + answer2); + + rtpEpOfferer2->processAnswer(answer2); + + // Second stream + cv2.wait (lck2, [&] () { + return media_state_changed2.load(); + }); + conn2.disconnect (); + + if (!media_state_changed2) { + BOOST_ERROR ("Not media Flowing"); + } + + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpOfferer2); + releaseRtpEndpoint (rtpEpAnswerer); + releasePassTrhough (pt); + releasePassTrhough (pt2); +} + +static void +reconnection_process_answer_state_changes_impl (bool useIpv6, bool useCrypto) +{ + std::atomic media_state_changed (false); + std::atomic media_state_changed2 (false); + std::shared_ptr rtpEpOfferer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer2 = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr pt = createPassThrough (); + std::shared_ptr pt2 = createPassThrough (); + std::shared_ptr src = createTestSrc(); + std::condition_variable cv; + std::condition_variable cv2; + std::mutex mtx; + std::unique_lock lck (mtx); + std::mutex mtx2; + std::unique_lock lck2 (mtx2); + + src->connect(rtpEpOfferer); + rtpEpAnswerer->connect(pt); + rtpEpAnswerer2->connect(pt2); + + sigc::connection conn = getMediaElement(pt)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + if (state->getValue() == MediaFlowState::FLOWING) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed = true; + cv.notify_one(); + } + } + ); + + sigc::connection conn2 = getMediaElement(pt2)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + if (state->getValue() == MediaFlowState::FLOWING) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed2 = true; + cv2.notify_one(); + } + } + ); + + try { + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer: " + offer); + + std::string answer1 = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer1: " + answer1); + + rtpEpOfferer->processAnswer(answer1); + + // First stream + cv.wait (lck, [&] () { + return media_state_changed.load(); + }); + conn.disconnect (); + + if (!media_state_changed) { + BOOST_ERROR ("Not media Flowing"); + } + + std::string answer2 = rtpEpAnswerer2->processOffer (offer); + BOOST_TEST_MESSAGE ("answer2: " + answer2); + + rtpEpOfferer->processAnswer (answer2); + + // Second stream + cv2.wait (lck2, [&] () { + return media_state_changed2.load(); + }); + conn2.disconnect (); + + if (!media_state_changed2) { + BOOST_ERROR ("Not media Flowing"); + } + + + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); + releaseRtpEndpoint (rtpEpAnswerer2); + releasePassTrhough (pt); + releasePassTrhough (pt2); + releaseTestSrc (src); +} + +static void +reconnection_process_answer_back_state_changes_impl (bool useIpv6, bool useCrypto) +{ + std::atomic media_state_changed (false); + std::atomic media_state_changed2 (false); + std::shared_ptr rtpEpOfferer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr rtpEpAnswerer2 = createRtpEndpoint (useIpv6, useCrypto); + std::shared_ptr pt = createPassThrough (); + std::shared_ptr src = createTestAudioSrc(); + std::shared_ptr src2 = createTestSrc(); + std::condition_variable cv; + std::condition_variable cv2; + std::mutex mtx; + std::unique_lock lck (mtx); + std::mutex mtx2; + std::unique_lock lck2 (mtx2); + + rtpEpOfferer->connect(pt); + src->connect(rtpEpAnswerer); + src2->connect(rtpEpAnswerer2); + + sigc::connection conn = getMediaElement(pt)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + std::shared_ptr media = event.getMediaType(); + + if ((state->getValue() == MediaFlowState::FLOWING) && (media->getValue() == MediaType::AUDIO)) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed = true; + cv.notify_one(); + } + } + ); + + try { + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer: " + offer); + + std::string answer1 = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer1: " + answer1); + + rtpEpOfferer->processAnswer(answer1); + + // First stream + cv.wait (lck, [&] () { + return media_state_changed.load(); + }); + conn.disconnect (); + + conn = getMediaElement(pt)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + std::shared_ptr media = event.getMediaType(); + + if ((state->getValue() == MediaFlowState::FLOWING) && (media->getValue() == MediaType::VIDEO)) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed2 = true; + cv2.notify_one(); + } + } + ); + + if (!media_state_changed) { + BOOST_ERROR ("Not media Flowing"); + } + + std::string answer2 = rtpEpAnswerer2->processOffer (offer); + BOOST_TEST_MESSAGE ("answer2: " + answer2); + + rtpEpOfferer->processAnswer (answer2); + + // First stream + cv2.wait (lck2, [&] () { + return media_state_changed2.load(); + }); + conn.disconnect (); + + if (!media_state_changed2) { + BOOST_ERROR ("Not media Flowing"); + } + + + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); + releaseRtpEndpoint (rtpEpAnswerer2); + releasePassTrhough (pt); + releaseTestSrc (src); + releaseTestSrc (src2); +} + + +static void +reconnection_generate_offer_state_changes() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_generate_offer_state_changes"); + reconnection_generate_offer_state_changes_impl (false, false); +} + +static void +reconnection_generate_offer_state_changes_ipv6() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_generate_offer_state_changes_ipv6"); + reconnection_generate_offer_state_changes_impl (true, false); +} + +static void +reconnection_process_offer_state_changes() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_process_offer_state_changes"); + reconnection_process_offer_state_changes_impl (false, false); +} + +static void +reconnection_process_offer_state_changes_ipv6() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_process_offer_state_changes_ipv6"); + reconnection_process_offer_state_changes_impl (true, false); +} + +static void +reconnection_process_answer_state_changes() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_process_offer_state_changes"); + reconnection_process_answer_state_changes_impl (false, false); +} + +static void +reconnection_process_answer_state_changes_ipv6() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_process_offer_state_changes_ipv6"); + reconnection_process_answer_state_changes_impl (true, false); +} + +static void +reconnection_process_answer_back_state_changes() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_process_answer_back_state_changes"); + reconnection_process_answer_back_state_changes_impl (false, false); +} + +static void +reconnection_process_answer_back_state_changes_ipv6() +{ + BOOST_TEST_MESSAGE ("Start test: reconnection_process_answer_back_state_changes_ipv6"); + reconnection_process_answer_back_state_changes_impl (true, false); +} + + + +test_suite * +init_unit_test_suite ( int , char *[] ) +{ + test_suite *test = BOOST_TEST_SUITE ( "SipRtpEndpoint" ); + + test->add (BOOST_TEST_CASE ( &media_state_changes ), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &reconnection_generate_offer_state_changes ), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &reconnection_process_offer_state_changes ), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &reconnection_process_answer_state_changes ), 0, /* timeout */ 1500000); + test->add (BOOST_TEST_CASE ( &reconnection_process_answer_back_state_changes ), 0, /* timeout */ 1500000); + + if (false) { + test->add (BOOST_TEST_CASE ( &media_state_changes_ipv6 ), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &reconnection_generate_offer_state_changes_ipv6 ), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &reconnection_process_offer_state_changes_ipv6 ), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &reconnection_process_answer_state_changes_ipv6 ), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &reconnection_process_answer_back_state_changes_ipv6 ), 0, /* timeout */ 15000); + } + return test; +} diff --git a/tests/server/sipRtpEndpoint_agnostic_srtp.cpp b/tests/server/sipRtpEndpoint_agnostic_srtp.cpp new file mode 100644 index 00000000..959d085c --- /dev/null +++ b/tests/server/sipRtpEndpoint_agnostic_srtp.cpp @@ -0,0 +1,1290 @@ +/* + * (C) Copyright 2016 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define BOOST_TEST_STATIC_LINK +#define BOOST_TEST_PROTECTED_VIRTUAL + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "SDES.hpp" +#include "CryptoSuite.hpp" + +#include + +#include + +using namespace kurento; +using namespace boost::unit_test; + +boost::property_tree::ptree config; +std::string mediaPipelineId; +ModuleManager moduleManager; + +struct GF { + GF(); + ~GF(); +}; + +BOOST_GLOBAL_FIXTURE (GF); + +GF::GF() +{ + boost::property_tree::ptree ac, audioCodecs, vc, videoCodecs; + gst_init(nullptr, nullptr); + +// moduleManager.loadModulesFromDirectories ("./src/server:../../kms-omni-build:../../src/server:../../../../kms-omni-build"); + moduleManager.loadModulesFromDirectories ("../../src/server"); + + config.add ("configPath", "../../../tests" ); + config.add ("modules.kurento.SdpEndpoint.numAudioMedias", 1); + config.add ("modules.kurento.SdpEndpoint.numVideoMedias", 1); + + ac.put ("name", "opus/48000/2"); + audioCodecs.push_back (std::make_pair ("", ac) ); + config.add_child ("modules.kurento.SdpEndpoint.audioCodecs", audioCodecs); + + vc.put ("name", "VP8/90000"); + videoCodecs.push_back (std::make_pair ("", vc) ); + config.add_child ("modules.kurento.SdpEndpoint.videoCodecs", videoCodecs); + + mediaPipelineId = moduleManager.getFactory ("MediaPipeline")->createObject ( + config, "", + Json::Value() )->getId(); +} + +GF::~GF() +{ + MediaSet::deleteMediaSet(); +} + +#define CRYPTOKEY "00108310518720928b30d38f411493" + +static std::shared_ptr getCrypto () +{ + std::shared_ptr crypto (new SDES()); + std::shared_ptr cryptoSuite (new kurento::CryptoSuite (kurento::CryptoSuite::AES_128_CM_HMAC_SHA1_80)); + + crypto->setCrypto(cryptoSuite); + crypto->setKey(CRYPTOKEY); + return crypto; +} + + +static Json::Value +createSdesJson (std::shared_ptr sdes) +{ + Json::Value sdesParams; + + sdesParams ["key"] = sdes->getKey(); + sdesParams ["crypto"] = sdes->getCrypto()->getString(); + + return sdesParams; +} + +static std::shared_ptr +createRtpEndpoint (bool useCrypto, bool cryptoAgnostic) +{ + std::shared_ptr rtpEndpoint; + Json::Value constructorParams; + + constructorParams ["mediaPipeline"] = mediaPipelineId; + constructorParams ["cryptoAgnostic"] = cryptoAgnostic; + if (useCrypto) { + constructorParams ["crypto"] = createSdesJson (getCrypto ()); + } + + rtpEndpoint = moduleManager.getFactory ("SipRtpEndpoint")->createObject ( + config, "", + constructorParams ); + + return std::dynamic_pointer_cast (rtpEndpoint); +} + +static std::shared_ptr createTestAudioSrc() { + std::shared_ptr src = std::dynamic_pointer_cast + (MediaSet::getMediaSet()->ref (new MediaElementImpl ( + boost::property_tree::ptree(), + MediaSet::getMediaSet()->getMediaObject (mediaPipelineId), + "dummysrc") ) ); + + g_object_set (src->getGstreamerElement(), "audio", TRUE, "video", FALSE, NULL); + + return std::dynamic_pointer_cast (src); +} + + +static void +releaseRtpEndpoint (std::shared_ptr &ep) +{ + std::string id = ep->getId(); + + ep.reset(); + MediaSet::getMediaSet ()->release (id); +} + +static std::shared_ptr +createPassThrough () +{ + std::shared_ptr pt; + Json::Value constructorParams; + + constructorParams ["mediaPipeline"] = mediaPipelineId; + + pt = moduleManager.getFactory ("PassThrough")->createObject ( + config, "", + constructorParams ); + + return std::dynamic_pointer_cast (pt); +} + +static void +releasePassTrhough (std::shared_ptr &ep) +{ + std::string id = ep->getId(); + + ep.reset(); + MediaSet::getMediaSet ()->release (id); +} + + + +static std::shared_ptr createTestSrc() { + std::shared_ptr src = std::dynamic_pointer_cast + (MediaSet::getMediaSet()->ref (new MediaElementImpl ( + boost::property_tree::ptree(), + MediaSet::getMediaSet()->getMediaObject (mediaPipelineId), + "dummysrc") ) ); + + g_object_set (src->getGstreamerElement(), "audio", TRUE, "video", TRUE, NULL); + + return std::dynamic_pointer_cast (src); +} + +static std::string sdp_test_1 = "v=0\r\n" + "o=iPECSCM 7619561 7619561 IN IP4 192.168.131.114\r\n" + "s=SIP Call\r\n" + "c=IN IP4 192.168.131.114\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/SAVP 0\r\n" + "a=crypto:1 AES_CM_128_HMAC_SHA1_80 dummy\r\n" + "a=inactive\r\n" + "m=video 0 RTP/SAVP 98\r\n" + "a=crypto:1 AES_CM_128_HMAC_SHA1_80 dummy\r\n" + "a=inactive\r\n" + "m=audio 23042 RTP/AVP 8 0 18 111\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:18 G729/8000\r\n" + "a=rtpmap:111 X-nt-inforeq/8000\r\n" + "a=fmtp:18 annexb=no\r\n" + "a=ptime:20\r\n" + "a=sendrecv\r\n" + "m=video 0 RTP/AVP 98\r\n" + "a=inactive\r\n"; + +static std::string sdp_test_2 = "v=0\r\n" + "o=iPECSCM 7619561 7619561 IN IP4 192.168.131.114\r\n" + "s=SIP Call\r\n" + "c=IN IP4 192.168.131.114\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/SAVPF 0\r\n" + "a=crypto:1 AES_CM_128_HMAC_SHA1_80 dummy\r\n" + "a=inactive\r\n" + "m=audio 23042 RTP/AVPF 8 0 18 111\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:18 G729/8000\r\n" + "a=rtpmap:111 X-nt-inforeq/8000\r\n" + "a=fmtp:18 annexb=no\r\n" + "a=ptime:20\r\n" + "a=sendrecv\r\n"; + + +static std::string sdp_test_3 = "v=0\r\n" + "o=iPECSCM 7619561 7619561 IN IP4 192.168.131.114\r\n" + "s=SIP Call\r\n" + "c=IN IP4 192.168.131.114\r\n" + "t=0 0\r\n" + "m=audio 23042 RTP/AVP 8 0 18 111\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:18 G729/8000\r\n" + "a=rtpmap:111 X-nt-inforeq/8000\r\n" + "a=fmtp:18 annexb=no\r\n" + "a=ptime:20\r\n" + "a=sendrecv\r\n"; + +static std::string sdp_test_4 = "v=0\r\n" + "o=- 3794323608 3794323608 IN IP4 172.17.0.2\r\n" + "s=Kurento Media Server\r\n" + "c=IN IP4 172.17.0.2\r\n" + "t=0 0\r\n" + "m=audio 1268 RTP/SAVPF 96 0 97\r\n" + "a=setup:actpass\r\n" + "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" + "a=rtpmap:96 opus/48000/2\r\n" + "a=rtpmap:97 AMR/8000\r\n" + "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:MDAxMDgzMTA1MTg3MjA5MjhiMzBkMzhmNDExNDkz\r\n" + "a=sendrecv\r\n" + "a=mid:audio0\r\n" + "a=ssrc:2630252136 cname:user2385001219@host-3046046\r\n" + "m=video 54142 RTP/SAVPF 102 103\r\n" + "a=setup:actpass\r\n" + "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" + "a=rtpmap:102 VP8/90000\r\n" + "a=rtpmap:103 H264/90000\r\n" + "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:MDAxMDgzMTA1MTg3MjA5MjhiMzBkMzhmNDExNDkz\r\n" + "a=sendrecv\r\n" + "a=mid:video0\r\n" + "a=rtcp-fb:102 nack\r\n" + "a=rtcp-fb:102 nack pli\r\n" + "a=rtcp-fb:102 goog-remb\r\n" + "a=rtcp-fb:102 ccm fir\r\n" + "a=rtcp-fb:103 nack\r\n" + "a=rtcp-fb:103 nack pli\r\n" + "a=rtcp-fb:103 ccm fir\r\n" + "a=ssrc:1395487615 cname:user2385001219@host-3046046\r\n" + "m=audio 1268 RTP/AVPF 96 0 97\r\n" + "a=setup:actpass\r\n" + "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" + "a=rtpmap:96 opus/48000/2\r\n" + "a=rtpmap:97 AMR/8000\r\n" + "a=sendrecv\r\n" + "a=mid:audio0\r\n" + "a=ssrc:868439451 cname:user2385001219@host-3046046\r\n" + "m=video 54142 RTP/AVPF 102 103\r\n" + "a=setup:actpass\r\n" + "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" + "a=rtpmap:102 VP8/90000\r\n" + "a=rtpmap:103 H264/90000\r\n" + "a=sendrecv\r\n" + "a=mid:video0\r\n" + "a=rtcp-fb:102 nack\r\n" + "a=rtcp-fb:102 nack pli\r\n" + "a=rtcp-fb:102 goog-remb\r\n" + "a=rtcp-fb:102 ccm fir\r\n" + "a=rtcp-fb:103 nack\r\n" + "a=rtcp-fb:103 nack pli\r\n" + "a=rtcp-fb:103 ccm fir\r\n" + "a=ssrc:756966127 cname:user2385001219@host-3046046\r\n"; + + +static void +releaseTestSrc (std::shared_ptr &ep) +{ + std::string id = ep->getId(); + + ep.reset(); + MediaSet::getMediaSet ()->release (id); +} + +static std::shared_ptr getMediaElement (std::shared_ptr element) +{ + return std::dynamic_pointer_cast (element); +} + +static bool +check_valid_answer (std::string sdp_answer) +{ + GstSDPMessage *sdp; + const GstSDPMedia *media; + guint medias_number; + guint idx = 0; + + gst_sdp_message_new (&sdp); + gst_sdp_message_parse_buffer ((const guint8 *)sdp_answer.c_str(), sdp_answer.length(), sdp); + medias_number = gst_sdp_message_medias_len (sdp); + while (idx < medias_number) { + const gchar *attr_value; + + media = gst_sdp_message_get_media (sdp, idx); + attr_value = gst_sdp_media_get_attribute_val (media, "inactive"); + if (attr_value == NULL) { + if (gst_sdp_media_get_port (media) != 0) + return true; + } + + idx++; + } + return false; +} + +static void +test_valid_answer (std::string test_sdp) +{ + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (true, true); + + try { + std::string answer = rtpEpAnswerer->processOffer (test_sdp); + BOOST_TEST_MESSAGE ("answer: " + answer); + + if (!check_valid_answer (answer)) { + BOOST_ERROR ("Ther must be at least one valid media"); + } + + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + releaseRtpEndpoint (rtpEpAnswerer); +} + + +static void +reconnection_generate_offer_state_changes_impl (bool cryptoOffer, bool agnosticOffer, bool cryptoAnswer, bool agnosticAnswer, bool mediaShouldFlow) +{ + std::atomic media_state_changed (false); + std::shared_ptr rtpEpOfferer = createRtpEndpoint (cryptoOffer, agnosticOffer); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (cryptoAnswer, agnosticAnswer); + std::shared_ptr src = createTestSrc(); + std::shared_ptr pt = createPassThrough (); + std::atomic conn_state_changed (false); + std::condition_variable cv; + std::mutex mtx; + std::unique_lock lck (mtx); + + src->connect(rtpEpOfferer); + rtpEpAnswerer->connect(pt); + + sigc::connection conn = getMediaElement(pt)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + if (state->getValue() == MediaFlowState::FLOWING) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed = true; + cv.notify_one(); + } + } + ); + + try { + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer1: " + offer); + + std::string answer = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer: " + answer); + + rtpEpOfferer->processAnswer (answer); + + cv.wait_for (lck, std::chrono::milliseconds(1500), [&] () { + return media_state_changed.load(); + }); + + conn.disconnect (); + if (!media_state_changed && mediaShouldFlow) { + BOOST_ERROR ("Not media Flowing"); + } + + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + src->disconnect(rtpEpOfferer); + rtpEpAnswerer->disconnect (pt); + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); + releasePassTrhough (pt); + releaseTestSrc (src); +} + +static std::string offer_1_group_call_crash = + "v=0\n" + "o=iPECSCM 356207 356207 IN IP4 192.168.125.52\n" + "s=iPECSCM Call\n" + "c=IN IP4 192.168.125.52\n" + "t=0 0\n" + "m=audio 0 RTP/SAVP 0\n" + "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:YzQ2NTQzOGNhZThhODU4YjA5ZDIzMjkxYjE2NjIy\n" + "a=inactive\n" + "m=video 0 RTP/SAVP 98\n" + "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:ZDU0MTJmODdkNzViYmY4NjQ5MDI5ZDFlMGZhMDhh\n" + "a=inactive\n" + "m=audio 8092 RTP/AVP 8 0 18 111\n" + "a=rtpmap:8 PCMA/8000\n" + "a=rtpmap:0 PCMU/8000\n" + "a=rtpmap:18 G729/8000\n" + "a=rtpmap:111 X-nt-inforeq/8000\n" + "a=fmtp:18 annexb=no\n" + "a=ptime:20\n" + "a=sendrecv\n" + "m=video 0 RTP/AVP 98\n" + "a=inactive"; + +static std::string offer_2_group_call_crash = + "v=0\n" + "o=iPECSCM 356272 356272 IN IP4 192.168.125.52\n" + "s=iPECSCM Call\n" + "c=IN IP4 192.168.125.52\n" + "t=0 0\n" + "m=audio 0 RTP/SAVP 0\n" + "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:MjcxNzRkZjI2YjJiOTlkYTA3MmNmOWRiNmZlZjk0\n" + "a=inactive\n" + "m=video 0 RTP/SAVP 98\n" + "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:ZjgzOGI1ODY1OTkyNDU1ODc2YWY5MmMxMWM3MTgx\n" + "a=inactive\n" + "m=audio 8092 RTP/AVP 0 111\n" + "a=rtpmap:0 PCMU/8000\n" + "a=rtpmap:111 X-nt-inforeq/8000\n" + "a=ptime:20\n" + "a=sendrecv\n" + "m=video 0 RTP/AVP 98\n" + "a=inactive"; + + +static std::string same_port_answer = + "v=0\n" + "o=iPECSCM 973597 973597 IN IP4 192.168.122.185\n" + "s=iPECSCM Call\n" + "c=IN IP4 192.168.122.185\n" + "t=0 0\n" + "m=audio 6050 RTP/AVP 0 111\n" + "a=rtpmap:0 PCMU/8000\n" + "a=rtpmap:111 X-nt-inforeq/8000\n" + "a=ptime:20\n" + "a=sendrecv"; + +static void +test_same_port_crash () +{ + std::atomic media_state_changed (false); + std::atomic media_state_changed2 (false); + std::shared_ptr rtpEpOfferer = createRtpEndpoint (true, true); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (false, true); + std::shared_ptr pt = createPassThrough (); + std::shared_ptr pt2 = createPassThrough (); + std::shared_ptr src = createTestAudioSrc(); + std::condition_variable cv; + std::condition_variable cv2; + std::mutex mtx; + std::unique_lock lck (mtx); + std::mutex mtx2; + std::unique_lock lck2 (mtx2); + + rtpEpOfferer->connect(pt); + src->connect(rtpEpAnswerer); + + sigc::connection conn = getMediaElement(pt)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + std::shared_ptr media = event.getMediaType(); + + if ((state->getValue() == MediaFlowState::FLOWING) && (media->getValue() == MediaType::AUDIO)) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed = true; + cv.notify_one(); + } + } + ); + +try { + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer: " + offer); + + std::string answer = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer1: " + answer); + + rtpEpOfferer->processAnswer(answer); + + // First stream + cv.wait (lck, [&] () { + return media_state_changed.load(); + }); + conn.disconnect (); + rtpEpOfferer->disconnect(pt); + + if (!media_state_changed) { + BOOST_ERROR ("Not media Flowing"); + } + + rtpEpOfferer->processAnswer (answer); + + conn = getMediaElement(pt2)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + std::shared_ptr media = event.getMediaType(); + + if ((state->getValue() == MediaFlowState::FLOWING) && (media->getValue() == MediaType::AUDIO)) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed2 = true; + cv2.notify_one(); + } + } + ); + rtpEpOfferer->connect(pt2); + +// std::shared_ptr audioTypePtr (new MediaType (MediaType::AUDIO)); +// while (!(pt->isMediaFlowingIn(audioTypePtr))) { +// std::this_thread::sleep_for(std::chrono::milliseconds(500)); +// } + + // First stream + cv2.wait (lck2, [&] () { + return media_state_changed2.load(); + }); + conn.disconnect (); + +// if (!media_state_changed2) { +// BOOST_ERROR ("Not media Flowing"); +// } + + +} catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); +} + +if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { +BOOST_ERROR ("Connection must be connected"); +} + +if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { +BOOST_ERROR ("Connection must be connected"); +} + +releaseRtpEndpoint (rtpEpOfferer); +releaseRtpEndpoint (rtpEpAnswerer); +releasePassTrhough (pt); +releaseTestSrc (src); +} + +static void +test_group_call_crash () +{ + bool cryptoOffer = false; + bool agnosticOffer = true; + + std::atomic media_state_changed (false); + std::shared_ptr rtpElement = createRtpEndpoint (cryptoOffer, agnosticOffer); + std::shared_ptr pt = createPassThrough (); + + + rtpElement->connect(pt); + try { + std::string offer = offer_1_group_call_crash; + BOOST_TEST_MESSAGE ("offer1: " + offer); + + std::string answer = rtpElement->processOffer(offer); + + BOOST_TEST_MESSAGE ("answer1: " + answer); + + offer = offer_2_group_call_crash; + BOOST_TEST_MESSAGE ("offer1: " + offer); + answer = rtpElement->processOffer(offer); + BOOST_TEST_MESSAGE ("answer1: " + answer); + + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + rtpElement->disconnect(pt); + + releaseRtpEndpoint (rtpElement); + releasePassTrhough (pt); +} + +static void +test_error_on_answer_without_one_media () +{ + bool cryptoOffer = true; + bool agnosticOffer = true; + bool mediaShouldFlow = true; + + std::atomic media_state_changed (false); + std::shared_ptr rtpEpOfferer = createRtpEndpoint (cryptoOffer, agnosticOffer); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (true, false); + std::shared_ptr src = createTestSrc(); + std::shared_ptr pt = createPassThrough (); + std::atomic conn_state_changed (false); + std::condition_variable cv; + std::mutex mtx; + std::unique_lock lck (mtx); + + src->connect(rtpEpOfferer); + rtpEpAnswerer->connect(pt); + + sigc::connection conn = getMediaElement(pt)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + if (state->getValue() == MediaFlowState::FLOWING) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed = true; + cv.notify_one(); + } + } + ); + + try { + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer1: " + offer); + + std::string mline ("m="); + std::size_t mediaStart, removedMediaStart; + + mediaStart = offer.find (mline); + if (mediaStart != std::string::npos) { + removedMediaStart = offer.find(mline, mediaStart+1); + if (removedMediaStart != std::string::npos) { + offer = offer.substr(0, removedMediaStart); + } + } + + std::string answer = rtpEpAnswerer->processOffer(offer); + + answer = answer.append("m=video 0 RTP/SAVP 98\r\n" + "a=crypto:1 AES_CM_128_HMAC_SHA1_80 dummy\r\n" + "a=inactive\r\n" + "m=audio 0 RTP/AVP 0\r\n" + "a=inactive\r\n" + "m=video 0 RTP/AVP 98\r\n" + "a=inactive"); + + rtpEpOfferer->processAnswer (answer); + + cv.wait_for (lck, std::chrono::milliseconds(1500), [&] () { + return media_state_changed.load(); + }); + + conn.disconnect (); + if (!media_state_changed && mediaShouldFlow) { + BOOST_ERROR ("Not media Flowing"); + } + + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + src->disconnect(rtpEpOfferer); + rtpEpAnswerer->disconnect (pt); + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); + releasePassTrhough (pt); + releaseTestSrc (src); +} + + +static std::string +removeCryptoMedias (std::string sdp) +{ + std::string mline ("m="); + std::size_t mediaStart, nonCryptoMediaStart; + + mediaStart = sdp.find (mline); + if (mediaStart != std::string::npos) { + nonCryptoMediaStart = sdp.find(mline, mediaStart+1); + if (nonCryptoMediaStart != std::string::npos) { + nonCryptoMediaStart = sdp.find(mline, nonCryptoMediaStart+1); + if (nonCryptoMediaStart != std::string::npos) { + return sdp.substr(0, mediaStart).append(sdp.substr (nonCryptoMediaStart)); + } + } + } + return sdp; +} + +static std::string +addCryptoMedias (std::string sdp) +{ + std::string mline ("m="); + std::size_t mediaStart; + std::string cryptoLines ("m=audio 0 RTP/SAVPF 96\r\na=inactive\r\nm=video 0 RTP/SAVPF 111\r\na=inactive\r\n"); + + mediaStart = sdp.find (mline); + if (mediaStart != std::string::npos) { + return sdp.substr(0, mediaStart).append(cryptoLines).append(sdp.substr(mediaStart, std::string::npos)); + } + return sdp; +} + +static std::string +removeNonCryptoMedias (std::string sdp) +{ + std::string mline ("m="); + std::size_t mediaStart, nonCryptoMediaStart; + + mediaStart = sdp.find (mline); + if (mediaStart != std::string::npos) { + nonCryptoMediaStart = sdp.find(mline, mediaStart+1); + if (nonCryptoMediaStart != std::string::npos) { + nonCryptoMediaStart = sdp.find(mline, nonCryptoMediaStart+1); + if (nonCryptoMediaStart != std::string::npos) { + return sdp.substr(0, nonCryptoMediaStart); + } + } + } + return sdp; +} + +static std::string +addNonCryptoMedias (std::string sdp) +{ + std::string mline ("m="); + std::string nonCryptoLines ("m=audio 0 RTP/AVPF 96\r\na=inactive\r\nm=video 0 RTP/AVPF 111\r\na=inactive\r\n"); + + return sdp.append(nonCryptoLines); +} + +static void +reconnection_generate_offer_state_changes_impl_alt () +{ + bool cryptoOffer = true, agnosticOffer = true, cryptoAnswer = false, agnosticAnswer = false, mediaShouldFlow = false; + std::atomic media_state_changed (false); + std::shared_ptr rtpEpOfferer = createRtpEndpoint (cryptoOffer, agnosticOffer); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (cryptoAnswer, agnosticAnswer); + std::shared_ptr src = createTestSrc(); + std::shared_ptr pt = createPassThrough (); + std::atomic conn_state_changed (false); + std::condition_variable cv; + std::mutex mtx; + std::unique_lock lck (mtx); + + src->connect(rtpEpOfferer); + rtpEpAnswerer->connect(pt); + + sigc::connection conn = getMediaElement(pt)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + if (state->getValue() == MediaFlowState::FLOWING) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed = true; + cv.notify_one(); + } + } + ); + + try { + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer1: " + offer); + + offer = removeCryptoMedias (offer); + + std::string answer = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer: " + answer); + + rtpEpOfferer->processAnswer (answer); + + cv.wait_for (lck, std::chrono::seconds(5), [&] () { + return media_state_changed.load(); + }); + + conn.disconnect (); + if (!media_state_changed && mediaShouldFlow) { + BOOST_ERROR ("Not media Flowing"); + } + + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + src->disconnect(rtpEpOfferer); + rtpEpAnswerer->disconnect (pt); + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); + releasePassTrhough (pt); + releaseTestSrc (src); +} + +static void +reconnection_generate_offer_state_changes_impl_alt2 () +{ + bool cryptoOffer = true, agnosticOffer = true, cryptoAnswer = false, agnosticAnswer = false, mediaShouldFlow = false; + std::atomic media_state_changed (false); + std::shared_ptr rtpEpOfferer = createRtpEndpoint (cryptoOffer, agnosticOffer); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (cryptoAnswer, agnosticAnswer); + std::shared_ptr src = createTestSrc(); + std::shared_ptr pt = createPassThrough (); + std::atomic conn_state_changed (false); + std::condition_variable cv; + std::mutex mtx; + std::unique_lock lck (mtx); + + src->connect(rtpEpOfferer); + rtpEpAnswerer->connect(pt); + + sigc::connection conn = getMediaElement(pt)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + if (state->getValue() == MediaFlowState::FLOWING) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed = true; + cv.notify_one(); + } + } + ); + + try { + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer1: " + offer); + + offer = removeCryptoMedias (offer); + + std::string answer = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer: " + answer); + + answer = addCryptoMedias (answer); + + rtpEpOfferer->processAnswer (answer); + + cv.wait_for (lck, std::chrono::seconds(5), [&] () { + return media_state_changed.load(); + }); + + conn.disconnect (); + if (!media_state_changed && mediaShouldFlow) { + BOOST_ERROR ("Not media Flowing"); + } + + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + src->disconnect(rtpEpOfferer); + rtpEpAnswerer->disconnect (pt); + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); + releasePassTrhough (pt); + releaseTestSrc (src); +} + +static void +reconnection_generate_offer_state_changes_impl_alt_crypto () +{ + bool cryptoOffer = true, agnosticOffer = true, cryptoAnswer = true, agnosticAnswer = false, mediaShouldFlow = false; + std::atomic media_state_changed (false); + std::shared_ptr rtpEpOfferer = createRtpEndpoint (cryptoOffer, agnosticOffer); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (cryptoAnswer, agnosticAnswer); + std::shared_ptr src = createTestSrc(); + std::shared_ptr pt = createPassThrough (); + std::atomic conn_state_changed (false); + std::condition_variable cv; + std::mutex mtx; + std::unique_lock lck (mtx); + + src->connect(rtpEpOfferer); + rtpEpAnswerer->connect(pt); + + sigc::connection conn = getMediaElement(pt)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + if (state->getValue() == MediaFlowState::FLOWING) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed = true; + cv.notify_one(); + } + } + ); + + try { + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer1: " + offer); + + offer = removeNonCryptoMedias (offer); + + std::string answer = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer: " + answer); + + rtpEpOfferer->processAnswer (answer); + + cv.wait_for (lck, std::chrono::seconds(5), [&] () { + return media_state_changed.load(); + }); + + conn.disconnect (); + if (!media_state_changed && mediaShouldFlow) { + BOOST_ERROR ("Not media Flowing"); + } + + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + src->disconnect(rtpEpOfferer); + rtpEpAnswerer->disconnect (pt); + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); + releasePassTrhough (pt); + releaseTestSrc (src); +} + +static void +reconnection_generate_offer_state_changes_impl_alt2_crypto () +{ + bool cryptoOffer = true, agnosticOffer = true, cryptoAnswer = true, agnosticAnswer = false, mediaShouldFlow = false; + std::atomic media_state_changed (false); + std::shared_ptr rtpEpOfferer = createRtpEndpoint (cryptoOffer, agnosticOffer); + std::shared_ptr rtpEpAnswerer = createRtpEndpoint (cryptoAnswer, agnosticAnswer); + std::shared_ptr src = createTestSrc(); + std::shared_ptr pt = createPassThrough (); + std::atomic conn_state_changed (false); + std::condition_variable cv; + std::mutex mtx; + std::unique_lock lck (mtx); + + src->connect(rtpEpOfferer); + rtpEpAnswerer->connect(pt); + + sigc::connection conn = getMediaElement(pt)->signalMediaFlowInStateChange.connect([&] ( + MediaFlowInStateChange event) { + std::shared_ptr state = event.getState(); + if (state->getValue() == MediaFlowState::FLOWING) { + BOOST_CHECK (state->getValue() == MediaFlowState::FLOWING); + media_state_changed = true; + cv.notify_one(); + } + } + ); + + try { + std::string offer = rtpEpOfferer->generateOffer (); + BOOST_TEST_MESSAGE ("offer1: " + offer); + + offer = removeNonCryptoMedias (offer); + + std::string answer = rtpEpAnswerer->processOffer (offer); + BOOST_TEST_MESSAGE ("answer: " + answer); + + answer = addNonCryptoMedias (answer); + + rtpEpOfferer->processAnswer (answer); + + cv.wait_for (lck, std::chrono::seconds(5), [&] () { + return media_state_changed.load(); + }); + + conn.disconnect (); + if (!media_state_changed && mediaShouldFlow) { + BOOST_ERROR ("Not media Flowing"); + } + + } catch (kurento::KurentoException& e) { + BOOST_ERROR("Unwanted Kurento Exception managing offer/answer"); + } + + if (rtpEpAnswerer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + if (rtpEpOfferer->getConnectionState ()->getValue () != + ConnectionState::CONNECTED) { + BOOST_ERROR ("Connection must be connected"); + } + + src->disconnect(rtpEpOfferer); + rtpEpAnswerer->disconnect (pt); + releaseRtpEndpoint (rtpEpOfferer); + releaseRtpEndpoint (rtpEpAnswerer); + releasePassTrhough (pt); + releaseTestSrc (src); +} + + +static void +srtp_agnostic_case_1() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: no crypto no agnostic, answerer: no crypto no agnostic"); + reconnection_generate_offer_state_changes_impl (false, false, false, false, true); +} + +static void +srtp_agnostic_case_2() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: no crypto no agnostic, answerer: no crypto yes agnostic"); + reconnection_generate_offer_state_changes_impl (false, false, false, true, true); +} + +static void +srtp_agnostic_case_3() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: no crypto no agnostic, answerer: yes crypto no agnostic"); + reconnection_generate_offer_state_changes_impl (false, false, true, false, false); +} + +static void +srtp_agnostic_case_4() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: no crypto no agnostic, answerer: yes crypto yes agnostic"); + reconnection_generate_offer_state_changes_impl (false, false, true, true, true); +} + +static void +srtp_agnostic_case_5() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: no crypto yes agnostic, answerer: no crypto no agnostic"); + reconnection_generate_offer_state_changes_impl (false, true, false, false, true); +} + +static void +srtp_agnostic_case_6() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: no crypto yes agnostic, answerer: no crypto yes agnostic"); + reconnection_generate_offer_state_changes_impl (false, true, false, true, true); +} + +static void +srtp_agnostic_case_7() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: no crypto yes agnostic, answerer: yes crypto no agnostic"); + reconnection_generate_offer_state_changes_impl (false, true, true, false, false); +} + +static void +srtp_agnostic_case_8() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: no crypto yes agnostic, answerer: yes crypto yes agnostic"); + reconnection_generate_offer_state_changes_impl (false, true, true, true, true); +} + +static void +srtp_agnostic_case_9() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: yes crypto no agnostic, answerer: no crypto no agnostic"); + reconnection_generate_offer_state_changes_impl (true, false, false, false, false); +} + +static void +srtp_agnostic_case_10() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: yes crypto no agnostic, answerer: no crypto yes agnostic"); + reconnection_generate_offer_state_changes_impl (true, false, false, true, true); +} + +static void +srtp_agnostic_case_11() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: yes crypto no agnostic, answerer: yes crypto no agnostic"); + reconnection_generate_offer_state_changes_impl (true, false, true, false, true); +} + +static void +srtp_agnostic_case_12() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: yes crypto no agnostic, answerer: yes crypto yes agnostic"); + reconnection_generate_offer_state_changes_impl (true, false, true, true, true); +} + +static void +srtp_agnostic_case_13() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: yes crypto yes agnostic, answerer: no crypto no agnostic"); + reconnection_generate_offer_state_changes_impl (true, true, false, false, false); +} + +static void +srtp_agnostic_case_13_b() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: yes crypto yes agnostic, answerer: no crypto no agnostic (alternate version)"); + reconnection_generate_offer_state_changes_impl_alt (); +} + +static void +srtp_agnostic_case_13_c() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: yes crypto yes agnostic, answerer: no crypto no agnostic (alternate version full media answer)"); + reconnection_generate_offer_state_changes_impl_alt2 (); +} + + +static void +srtp_agnostic_case_14() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: yes crypto yes agnostic, answerer: no crypto yes agnostic"); + reconnection_generate_offer_state_changes_impl (true, true, false, true, true); +} + +static void +srtp_agnostic_case_15() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: yes crypto yes agnostic, answerer: yes crypto no agnostic"); + reconnection_generate_offer_state_changes_impl (true, true, true, false, true); +} + +static void +srtp_agnostic_case_15_b() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: yes crypto yes agnostic, answerer: yes crypto no agnostic (alternate version)"); + reconnection_generate_offer_state_changes_impl_alt_crypto (); +} + +static void +srtp_agnostic_case_15_c() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: yes crypto yes agnostic, answerer: yes crypto no agnostic (alternate veriosn full media answer"); + reconnection_generate_offer_state_changes_impl_alt2_crypto (); +} + +static void +srtp_agnostic_case_16() +{ + BOOST_TEST_MESSAGE ("Start test: offerer: yes crypto yes agnostic, answerer: yes crypto yes agnostic"); + reconnection_generate_offer_state_changes_impl (true, true, true, true, true); +} + + +static void +test_sdp_offer_1() +{ + BOOST_TEST_MESSAGE ("Start test: Testing SDP 1"); + test_valid_answer (sdp_test_1); +} + +static void +test_sdp_offer_2() +{ + BOOST_TEST_MESSAGE ("Start test: Testing SDP 2"); + test_valid_answer (sdp_test_2); +} + +static void +test_sdp_offer_3() +{ + BOOST_TEST_MESSAGE ("Start test: Testing SDP 3"); + test_valid_answer (sdp_test_3); +} + +static void +test_sdp_offer_4() +{ + BOOST_TEST_MESSAGE ("Start test: Testing SDP 4"); + test_valid_answer (sdp_test_4); +} + + + + + + + + + + + +test_suite * +init_unit_test_suite ( int , char *[] ) +{ + test_suite *test = BOOST_TEST_SUITE ( "SipRtpEndpoint" ); + + test->add (BOOST_TEST_CASE(&test_sdp_offer_4), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE(&test_sdp_offer_3), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE(&test_sdp_offer_2), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE(&test_sdp_offer_1), 0, /* timeout */ 15000); + + + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_1), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_2), 0, /* timeout */ 15000); + + // This should fail as the answerer is configured as crypto (no agnostic) and the offerer is not crypto + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_3), 0, /* timeout */ 15000); + + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_4), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_5), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_6), 0, /* timeout */ 15000); + + // This should fail as the answerer is configured as crypto (no agnostic) and the offerer is non crypto + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_7), 0, /* timeout */ 15000); + + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_8), 0, /* timeout */ 15000); + + // This should fail as the answerer is configured as non crypto (no agnostic) and the offerer is crypto + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_9), 0, /* timeout */ 15000); + + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_10), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_11), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_12), 0, /* timeout */ 15000); + + // Odd case, it should work if answerer could support more than 1 audio media and 1 video media + // The problem is that there is some "bug" on RtpEndpoint. The problem is that + // the offer presents 2 audio and 2 video medias, but the answere an only support 1 audio and 1 video media + // If the two first medias (audio and video) are supported by the answerer, the connection should process ok + // But if two first medias are not supporte by the answerer, they are rejected, but the two following are outside the boundaries of + // number of medias accepted and are also rejected + // RElated: bug in kmssdpagent.c (1817), if an offer is answered with more medias than offered, reaches this point that kills the process + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_13), 0, /* timeout */ 15000); + + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_13_b), 0, /* timeout */ 15000); + + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_13_c), 0, /* timeout */ 15000); + + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_14), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_15), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_15_b), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_15_c), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &srtp_agnostic_case_16), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &test_error_on_answer_without_one_media), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &test_group_call_crash), 0, /* timeout */ 15000); + test->add (BOOST_TEST_CASE ( &test_same_port_crash), 0, /* timeout */ 15000); + return test; +}