From a73703760be3261c2cfbbe8d8d983176fdc113bf Mon Sep 17 00:00:00 2001 From: Saul Pablo Labajo Izquierdo Date: Mon, 20 Jan 2020 17:22:41 +0100 Subject: [PATCH 01/11] merge into kms-elements of kms-siprtpendpoint 1st try --- src/gst-plugins/rtpendpoint/CMakeLists.txt | 6 + .../rtpendpoint/kms-rtp-enumtypes.h | 14 + .../rtpendpoint/kmsrtpconnection.c | 150 ++++ .../rtpendpoint/kmsrtpconnection.h | 16 + .../rtpendpoint/kmsrtpendpoint-plugin-init.c | 41 + src/gst-plugins/rtpendpoint/kmsrtpendpoint.c | 16 +- .../rtpendpoint/kmsrtpfilterutils.c | 229 ++++++ .../rtpendpoint/kmsrtpfilterutils.h | 48 ++ .../rtpendpoint/kmssiprtpendpoint.c | 702 ++++++++++++++++ .../rtpendpoint/kmssiprtpendpoint.h | 68 ++ .../rtpendpoint/kmssiprtpsession.c | 241 ++++++ .../rtpendpoint/kmssiprtpsession.h | 75 ++ .../rtpendpoint/kmssipsrtpsession.c | 241 ++++++ .../rtpendpoint/kmssipsrtpsession.h | 76 ++ .../rtpendpoint/kmssrtpconnection.c | 239 ++++++ .../rtpendpoint/kmssrtpconnection.h | 19 + src/server/CMakeLists.txt | 2 + .../objects/ComposedObjectImpl.cpp | 333 ++++++++ .../objects/ComposedObjectImpl.hpp | 157 ++++ .../objects/FacadeRtpEndpointImpl.cpp | 546 +++++++++++++ .../objects/FacadeRtpEndpointImpl.hpp | 200 +++++ .../objects/SipRtpEndpointImpl.cpp | 212 +++++ .../objects/SipRtpEndpointImpl.hpp | 89 +++ .../elements.SipRtpEndpoint.kmd.json | 80 ++ tests/server/CMakeLists.txt | 42 + tests/server/sipRtpEndpoint.cpp | 539 +++++++++++++ tests/server/sipRtpEndpointPlay.cpp | 755 ++++++++++++++++++ 27 files changed, 5121 insertions(+), 15 deletions(-) create mode 100644 src/gst-plugins/rtpendpoint/kms-rtp-enumtypes.h create mode 100644 src/gst-plugins/rtpendpoint/kmsrtpendpoint-plugin-init.c create mode 100644 src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c create mode 100644 src/gst-plugins/rtpendpoint/kmsrtpfilterutils.h create mode 100644 src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c create mode 100644 src/gst-plugins/rtpendpoint/kmssiprtpendpoint.h create mode 100644 src/gst-plugins/rtpendpoint/kmssiprtpsession.c create mode 100644 src/gst-plugins/rtpendpoint/kmssiprtpsession.h create mode 100644 src/gst-plugins/rtpendpoint/kmssipsrtpsession.c create mode 100644 src/gst-plugins/rtpendpoint/kmssipsrtpsession.h create mode 100644 src/server/implementation/objects/ComposedObjectImpl.cpp create mode 100644 src/server/implementation/objects/ComposedObjectImpl.hpp create mode 100644 src/server/implementation/objects/FacadeRtpEndpointImpl.cpp create mode 100644 src/server/implementation/objects/FacadeRtpEndpointImpl.hpp create mode 100644 src/server/implementation/objects/SipRtpEndpointImpl.cpp create mode 100644 src/server/implementation/objects/SipRtpEndpointImpl.hpp create mode 100644 src/server/interface/elements.SipRtpEndpoint.kmd.json create mode 100644 tests/server/sipRtpEndpoint.cpp create mode 100644 tests/server/sipRtpEndpointPlay.cpp diff --git a/src/gst-plugins/rtpendpoint/CMakeLists.txt b/src/gst-plugins/rtpendpoint/CMakeLists.txt index 52b45ea28..9a8cfabff 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 000000000..ed244b167 --- /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 5dcd58a4a..42ba1f6fe 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,151 @@ 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 (GHashTable *conns, const GstSDPMedia * media, GSocket **rtp, GSocket **rtcp) +{ + gchar *media_key; + KmsRtpConnection *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_CONNECTION (g_hash_table_lookup (conns, media_key)); + 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); + + if (filter_info != 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 17c8772b3..3ea65ee2c 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 (GHashTable *conns, const GstSDPMedia * media, 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 000000000..20d43aff7 --- /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 0e3de8213..ef0256ce9 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpendpoint.c +++ b/src/gst-plugins/rtpendpoint/kmsrtpendpoint.c @@ -1108,7 +1108,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); @@ -1173,17 +1173,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 000000000..e9b9cf529 --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c @@ -0,0 +1,229 @@ +/* + * (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 gboolean +check_ssrc (guint32 ssrc, SipFilterSsrcInfo* filter_info) +{ + if (filter_info->expected == 0) { + GList* it = filter_info->old; + + while (it != NULL) { + if (ssrc == GPOINTER_TO_UINT(it->data)) + return TRUE; + it = it->next; + } + filter_info->expected = ssrc; + return FALSE; + } + if (ssrc == filter_info->expected) + return FALSE; + return TRUE; +} + +static GstPadProbeReturn +filter_ssrc_rtp_buffer (GstBuffer *buffer, SipFilterSsrcInfo* filter_info) +{ + GstRTPBuffer rtp_buffer = GST_RTP_BUFFER_INIT; + + if (gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp_buffer)) { + GST_DEBUG ("filter old ssrc RTP buffer"); + guint32 checked_ssrc = gst_rtp_buffer_get_ssrc (&rtp_buffer); + + gst_rtp_buffer_unmap (&rtp_buffer); + if (check_ssrc (checked_ssrc, filter_info)) { + GST_INFO ("RTP packet dropped from a previous RTP flow with SSRC %u", checked_ssrc); + 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); + return GST_PAD_PROBE_OK; + } + } + + 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 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_READ, &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"); + gst_rtcp_buffer_unmap (&rtcp_buffer); + return GST_PAD_PROBE_DROP; + + while (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); + if (check_ssrc (ssrc, filter_info)) { + GST_DEBUG("Unexpected SSRC RTCP packet received: %u, expected: %u", ssrc, filter_info->expected); + gst_rtcp_packet_remove (&packet); + } + } + has_packet = gst_rtcp_packet_move_to_next (&packet); + } + 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); + +} + +SipFilterSsrcInfo* +kms_sip_rtp_filter_create_filtering_info (guint32 expected, SipFilterSsrcInfo* previous) +{ + SipFilterSsrcInfo* info = g_new (SipFilterSsrcInfo, 1); + + info->expected = expected; + info->old = NULL; + if (previous != NULL) { + GList* it = previous->old; + + if (previous->expected != 0) + info->old = g_list_append (info->old, GUINT_TO_POINTER(previous->expected)); + while (it != NULL) { + info->old = g_list_append (info->old, it->data); + it = it->next; + } + } + + return info; +} + +void kms_sip_rtp_filter_release_filtering_info (SipFilterSsrcInfo* info) +{ + 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 000000000..f825be4f6 --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.h @@ -0,0 +1,48 @@ +/* + * (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; + GList* old; +}; + +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); + +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 000000000..de790730d --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c @@ -0,0 +1,702 @@ +/* + * (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" + +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; +}; + +/* 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) +{ + 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) +{ + KmsSipRtpEndpointCloneData *data = g_malloc(sizeof (KmsSipRtpEndpointCloneData)); + SipFilterSsrcInfo* audio_filter_info; + SipFilterSsrcInfo* video_filter_info; + + 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); + + audio_filter_info = kms_sip_rtp_filter_create_filtering_info (audio_ssrc, sip_ses->audio_filter_info); + video_filter_info = kms_sip_rtp_filter_create_filtering_info (video_ssrc, sip_ses->video_filter_info); + } else if (KMS_IS_SIP_SRTP_SESSION (ses)) { + KmsSipSrtpSession* sip_ses = KMS_SIP_SRTP_SESSION (ses); + + audio_filter_info = kms_sip_rtp_filter_create_filtering_info (audio_ssrc, sip_ses->audio_filter_info); + video_filter_info = kms_sip_rtp_filter_create_filtering_info (video_ssrc, sip_ses->video_filter_info); + } + + 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) +{ + 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); + + 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) { + 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) { + 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; + + 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, 2, G_TYPE_POINTER, G_TYPE_STRING); + + + 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 000000000..64e0cc93c --- /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); +}; + +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 000000000..6ef03f96f --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmssiprtpsession.c @@ -0,0 +1,241 @@ +/* + * (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 "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 = g_object_ref (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 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_connection_retrieve_sockets (self->priv->conns, media, &rtp_sock, &rtcp_sock); + + } + + media_str = gst_sdp_media_get_media (media); + + if (g_strcmp0 (VIDEO_STREAM_NAME, media_str) == 0) { + filter_info = self->video_filter_info; + }else if (g_strcmp0 (AUDIO_STREAM_NAME, media_str) == 0) { + filter_info = self->audio_filter_info; + } + 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_object_unref (info->conn); + 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); +} + +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 000000000..427537365 --- /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 000000000..8987d4d88 --- /dev/null +++ b/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c @@ -0,0 +1,241 @@ +/* + * (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 +#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 = g_object_ref (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 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_connection_retrieve_sockets (self->priv->conns, media, &rtp_sock, &rtcp_sock); + } + media_str = gst_sdp_media_get_media (media); + + if (g_strcmp0 (VIDEO_STREAM_NAME, media_str) == 0) { + filter_info = self->video_filter_info; + }else if (g_strcmp0 (AUDIO_STREAM_NAME, media_str) == 0) { + filter_info = self->audio_filter_info; + } + 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_object_unref (info->conn); + 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); +} + + +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 000000000..51c4567a5 --- /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 aeda685fa..e9aabf550 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,239 @@ 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 (GHashTable *conns, const GstSDPMedia * media, GSocket **rtp, GSocket **rtcp) +{ + gchar *media_key; + KmsSrtpConnection *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_SRTP_CONNECTION (g_hash_table_lookup (conns, media_key)); + 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); + + if (filter_info != 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 9f531fdd0..be7ea9082 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 (GHashTable *conns, const GstSDPMedia * media, 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 1feb0120b..6c0f46299 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 000000000..6c69cbcd5 --- /dev/null +++ b/src/server/implementation/objects/ComposedObjectImpl.cpp @@ -0,0 +1,333 @@ +/* + * (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) { + //We don't raise internal connection events' + if (event.getSource()==srcPt) + return; + if (event.getSink () == sinkPt) + return; + raiseEvent (event, shared_from_this(), signalElementDisconnected); + }); + + connElementDisconnectedSink = std::dynamic_pointer_cast(sinkPt)->signalElementDisconnected.connect([ & ] ( + ElementDisconnected event) { + //We don't raise internal connection events' + if (event.getSource()==srcPt) + return; + if (event.getSink () == sinkPt) + return; + raiseEvent (event, shared_from_this(), signalElementDisconnected); + }); + + 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 000000000..effe613f1 --- /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 000000000..2e7270ce9 --- /dev/null +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp @@ -0,0 +1,546 @@ +/* + * (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 "ComposedObjectImpl.hpp" +#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 +{ + +FacadeRtpEndpointImpl::FacadeRtpEndpointImpl (const boost::property_tree::ptree &conf, + std::shared_ptr mediaPipeline, + std::shared_ptr crypto, bool useIpv6) + : ComposedObjectImpl (conf, + std::dynamic_pointer_cast (mediaPipeline)), cryptoCache (crypto), useIpv6Cache (useIpv6) +{ + rtp_ep = std::shared_ptr(new SipRtpEndpointImpl (config, mediaPipeline, crypto, useIpv6)); +} + +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 () +{ + std::string offer; + + try { + offer = this->rtp_ep->generateOffer(); + 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()); + std::shared_ptr newEndpoint = std::shared_ptr(new SipRtpEndpointImpl (config, getMediaPipeline (), cryptoCache, useIpv6Cache)); + + newEndpoint->postConstructor(); + renewInternalEndpoint (newEndpoint); + offer = newEndpoint->generateOffer(); + 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; + } 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 FacadeRtpEndpointImpl::processOffer (const std::string &offer) +{ + std::string answer; + try { + answer = this->rtp_ep->processOffer(offer); + GST_DEBUG ("ProcessOffer: \n%s", answer.c_str()); + return answer; + } 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()); + std::shared_ptr newEndpoint = std::shared_ptr(new SipRtpEndpointImpl (config, getMediaPipeline (), cryptoCache, useIpv6Cache)); + + newEndpoint->postConstructor(); + renewInternalEndpoint (newEndpoint); + answer = newEndpoint->processOffer(offer); + GST_DEBUG ("2nd try ProcessOffer: \n%s", answer.c_str()); + GST_INFO("Consecutive process Offer on %s, endpoint cloned and offer processed", this->getId().c_str()); + return answer; + } 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 FacadeRtpEndpointImpl::processAnswer (const std::string &answer) +{ + std::string result; + + try { + result = this->rtp_ep->processAnswer(answer); + GST_DEBUG ("ProcessAnswer: \n%s", result.c_str()); + return result; + } 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()); + std::shared_ptr newEndpoint = rtp_ep->getCleanEndpoint (config, getMediaPipeline (), cryptoCache, useIpv6Cache, answer); + std::string unusedOffer; + std::shared_ptr oldEndpoint; + + newEndpoint->postConstructor(); + oldEndpoint = renewInternalEndpoint (newEndpoint); + unusedOffer = newEndpoint->generateOffer(); + GST_DEBUG ("2nd try ProcessAnswer - Unused offer: \n%s", unusedOffer.c_str()); + result = newEndpoint->processAnswer(answer); + 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; + } 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 FacadeRtpEndpointImpl::getLocalSessionDescriptor () +{ + return this->rtp_ep->getLocalSessionDescriptor(); +} + +std::string FacadeRtpEndpointImpl::getRemoteSessionDescriptor () +{ + return this->rtp_ep->getRemoteSessionDescriptor(); +} + + +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); + }); + +} + +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); + + 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); +} +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); +} +void FacadeRtpEndpointImpl::setVideoFormat (std::shared_ptr caps) +{ + this->rtp_ep->setVideoFormat(caps); +} + +/*virtual void release () override; */ + +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 000000000..3ab6c2bb0 --- /dev/null +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp @@ -0,0 +1,200 @@ +/* + * (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 + + +namespace kurento +{ + +class MediaPipeline; + +class FacadeRtpEndpointImpl; + +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 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 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: + + 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; + + void + disconnectForwardSignals (); + + void + connectForwardSignals (); + + std::shared_ptr + renewInternalEndpoint (std::shared_ptr newEndpoint); + + + + 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 000000000..fe415277a --- /dev/null +++ b/src/server/implementation/objects/SipRtpEndpointImpl.cpp @@ -0,0 +1,212 @@ +/* + * (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 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, 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) +{ + std::shared_ptr newEndpoint = std::shared_ptr(new SipRtpEndpointImpl (conf, mediaPipeline, crypto, useIpv6)); + + this->cloneToNewEndpoint (newEndpoint, sdp); + return newEndpoint; +} + +std::shared_ptr SipRtpEndpointImpl::cloneToNewEndpoint (std::shared_ptr newEp, const std::string &sdp) +{ + g_signal_emit_by_name (element, "clone-to-new-ep", newEp->element, sdp.c_str()); + + return newEp; +} + + + +} /* kurento */ diff --git a/src/server/implementation/objects/SipRtpEndpointImpl.hpp b/src/server/implementation/objects/SipRtpEndpointImpl.hpp new file mode 100644 index 000000000..41896026b --- /dev/null +++ b/src/server/implementation/objects/SipRtpEndpointImpl.hpp @@ -0,0 +1,89 @@ +/* + * (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); + + /* 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); + + 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 000000000..81b3b8623 --- /dev/null +++ b/src/server/interface/elements.SipRtpEndpoint.kmd.json @@ -0,0 +1,80 @@ +{ + "remoteClasses": [ + { + "name": "SipRtpEndpoint", + "extends": "BaseRtpEndpoint", + "doc": "Endpoint that provides the same functionality and features that :rom:cls:`RtpEndpoint` providing SDP renegotiation features that allow easy implementation of SIP 183 like flows. This endpoint inherits from :rom:cls:`BaseRtpEndpoint` +

+

+ The API and methods to establish a RTP/SRTP communication are exactly the same as in :rom:cls:`RtpEndpoint` +

+ 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. +

+ ", + "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": "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 b34c75f2b..ae200cc01 100644 --- a/tests/server/CMakeLists.txt +++ b/tests/server/CMakeLists.txt @@ -109,3 +109,45 @@ 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} +) diff --git a/tests/server/sipRtpEndpoint.cpp b/tests/server/sipRtpEndpoint.cpp new file mode 100644 index 000000000..91536484b --- /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 000000000..40e95efd6 --- /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; +} From 5a0639780f3e4d8fea56bbaee9214437b9b51603 Mon Sep 17 00:00:00 2001 From: Saul Pablo Labajo Izquierdo Date: Wed, 22 Jan 2020 11:09:20 +0100 Subject: [PATCH 02/11] Fixed bug, properties were not beign preserved on renegotiation --- .../objects/FacadeRtpEndpointImpl.cpp | 30 +++++++++++++++++++ .../objects/FacadeRtpEndpointImpl.hpp | 5 ++++ 2 files changed, 35 insertions(+) diff --git a/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp index 2e7270ce9..4501e9c8f 100644 --- a/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp @@ -30,6 +30,8 @@ #include #include #include +#include "RembParams.hpp" + #define GST_CAT_DEFAULT kurento_sip_rtp_endpoint_impl @@ -55,6 +57,9 @@ FacadeRtpEndpointImpl::FacadeRtpEndpointImpl (const boost::property_tree::ptree std::dynamic_pointer_cast (mediaPipeline)), cryptoCache (crypto), useIpv6Cache (useIpv6) { rtp_ep = std::shared_ptr(new SipRtpEndpointImpl (config, mediaPipeline, crypto, useIpv6)); + audioCapsSet = NULL; + videoCapsSet = NULL; + rembParamsSet = NULL; } FacadeRtpEndpointImpl::~FacadeRtpEndpointImpl() @@ -271,6 +276,27 @@ FacadeRtpEndpointImpl::connectForwardSignals () } +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) { @@ -282,6 +308,7 @@ FacadeRtpEndpointImpl::renewInternalEndpoint (std::shared_ptr FacadeRtpEndpointImpl::getRembParams () void FacadeRtpEndpointImpl::setRembParams (std::shared_ptr rembParams) { this->rtp_ep->setRembParams (rembParams); + rembParamsSet = rembParams; } sigc::signal FacadeRtpEndpointImpl::getSignalMediaStateChanged () { @@ -437,10 +465,12 @@ std::vector> FacadeRtpEndpointImpl::getSi 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; } /*virtual void release () override; */ diff --git a/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp b/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp index 3ab6c2bb0..e4700e319 100644 --- a/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp @@ -177,7 +177,12 @@ class FacadeRtpEndpointImpl : public ComposedObjectImpl, public virtual SipRtpEn 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; From 69812ba3faa347f003bf80bf8babfc5cfeed401c Mon Sep 17 00:00:00 2001 From: Saul Pablo Labajo Izquierdo Date: Wed, 22 Jan 2020 11:09:38 +0100 Subject: [PATCH 03/11] Updated documentation --- src/server/interface/elements.SipRtpEndpoint.kmd.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/server/interface/elements.SipRtpEndpoint.kmd.json b/src/server/interface/elements.SipRtpEndpoint.kmd.json index 81b3b8623..f44d04e28 100644 --- a/src/server/interface/elements.SipRtpEndpoint.kmd.json +++ b/src/server/interface/elements.SipRtpEndpoint.kmd.json @@ -3,7 +3,10 @@ { "name": "SipRtpEndpoint", "extends": "BaseRtpEndpoint", - "doc": "Endpoint that provides the same functionality and features that :rom:cls:`RtpEndpoint` providing SDP renegotiation features that allow easy implementation of SIP 183 like flows. This endpoint inherits from :rom:cls:`BaseRtpEndpoint` + "doc": "Endpoint that provides the same functionality and features that :rom:cls:`RtpEndpoint` providing SDP renegotiation features that allow easy implementation of SIP 183 like flows. This endpoint inherits from :rom:cls:`BaseRtpEndpoint` +

+

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

The API and methods to establish a RTP/SRTP communication are exactly the same as in :rom:cls:`RtpEndpoint` From 48096fb10c40992c338390fb9775344510e3af4a Mon Sep 17 00:00:00 2001 From: Saul Pablo Labajo Izquierdo Date: Mon, 17 Feb 2020 16:33:00 +0100 Subject: [PATCH 04/11] Added crypto agnostic feature --- .../rtpendpoint/kmsrtpconnection.c | 17 +- .../rtpendpoint/kmsrtpconnection.h | 2 +- .../rtpendpoint/kmsrtpfilterutils.c | 5 +- .../rtpendpoint/kmssiprtpsession.c | 35 +- .../rtpendpoint/kmssipsrtpsession.c | 35 +- .../rtpendpoint/kmssrtpconnection.c | 17 +- .../rtpendpoint/kmssrtpconnection.h | 2 +- .../objects/FacadeRtpEndpointImpl.cpp | 580 ++++++++++++++++-- .../objects/FacadeRtpEndpointImpl.hpp | 38 +- .../objects/SipRtpEndpointImpl.cpp | 11 +- .../objects/SipRtpEndpointImpl.hpp | 5 +- .../elements.SipRtpEndpoint.kmd.json | 33 +- tests/server/CMakeLists.txt | 21 + 13 files changed, 715 insertions(+), 86 deletions(-) diff --git a/src/gst-plugins/rtpendpoint/kmsrtpconnection.c b/src/gst-plugins/rtpendpoint/kmsrtpconnection.c index 42ba1f6fe..d3341c5c3 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpconnection.c +++ b/src/gst-plugins/rtpendpoint/kmsrtpconnection.c @@ -393,23 +393,8 @@ kms_rtp_connection_interface_init (KmsIRtpConnectionInterface * iface) void -kms_sip_rtp_connection_retrieve_sockets (GHashTable *conns, const GstSDPMedia * media, GSocket **rtp, GSocket **rtcp) +kms_sip_rtp_connection_retrieve_sockets (KmsRtpConnection *conn, GSocket **rtp, GSocket **rtcp) { - gchar *media_key; - KmsRtpConnection *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_CONNECTION (g_hash_table_lookup (conns, media_key)); if (conn != NULL) { // Retrieve the sockets *rtcp = g_object_ref (conn->priv->rtcp_socket); diff --git a/src/gst-plugins/rtpendpoint/kmsrtpconnection.h b/src/gst-plugins/rtpendpoint/kmsrtpconnection.h index 3ea65ee2c..b86a65e0e 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpconnection.h +++ b/src/gst-plugins/rtpendpoint/kmsrtpconnection.h @@ -66,7 +66,7 @@ kms_sip_rtp_connection_add_probes (KmsRtpConnection *conn, SipFilterSsrcInfo* fi void kms_sip_rtp_connection_release_probes (KmsRtpConnection *conn, gulong rtp_probe_id, gulong rtcp_probe_id); -void kms_sip_rtp_connection_retrieve_sockets (GHashTable *conns, const GstSDPMedia * media, GSocket **rtp, GSocket **rtcp); +void kms_sip_rtp_connection_retrieve_sockets (KmsRtpConnection *conn, GSocket **rtp, GSocket **rtcp); G_END_DECLS diff --git a/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c index e9b9cf529..e0daef68c 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c +++ b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c @@ -136,7 +136,10 @@ filter_ssrc_rtcp (GstPad *pad, GstPadProbeInfo *info, gpointer user_data) gst_rtcp_packet_sr_get_sender_info (&packet, &ssrc, &ntptime, &rtptime, &packet_count, &octet_count); if (check_ssrc (ssrc, filter_info)) { GST_DEBUG("Unexpected SSRC RTCP packet received: %u, expected: %u", ssrc, filter_info->expected); - gst_rtcp_packet_remove (&packet); + // If any packet in a buffer has an unexpectd SSRc, all buffer can be dropped + return GST_PAD_PROBE_DROP; + } else { + return GST_PAD_PROBE_OK; } } has_packet = gst_rtcp_packet_move_to_next (&packet); diff --git a/src/gst-plugins/rtpendpoint/kmssiprtpsession.c b/src/gst-plugins/rtpendpoint/kmssiprtpsession.c index 6ef03f96f..38a85d1fe 100644 --- a/src/gst-plugins/rtpendpoint/kmssiprtpsession.c +++ b/src/gst-plugins/rtpendpoint/kmssiprtpsession.c @@ -20,6 +20,7 @@ #endif #include "kmssiprtpsession.h" +#include "kmssrtpconnection.h" #include "kmsrtpfilterutils.h" #include #include @@ -93,6 +94,38 @@ kms_sip_rtp_session_store_rtp_filtering_info (KmsSipRtpSession *ses, KmsRtpConne 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 KmsIRtpConnection * kms_sip_rtp_session_create_connection (KmsBaseRtpSession * base_rtp_sess, const GstSDPMedia * media, const gchar * name, guint16 min_port, @@ -116,7 +149,7 @@ kms_sip_rtp_session_create_connection (KmsBaseRtpSession * base_rtp_sess, if (self->priv->conns != NULL) { // If we are recovering a previous session, due to a renegotation (consecutive processAnswer) - kms_sip_rtp_connection_retrieve_sockets (self->priv->conns, media, &rtp_sock, &rtcp_sock); + kms_sip_rtp_session_retrieve_sockets (self->priv->conns, media, &rtp_sock, &rtcp_sock); } diff --git a/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c b/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c index 8987d4d88..a4ff315e5 100644 --- a/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c +++ b/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c @@ -20,6 +20,7 @@ #endif #include "kmssipsrtpsession.h" +#include "kmsrtpconnection.h" #include #include #include @@ -94,6 +95,38 @@ kms_sip_srtp_session_store_rtp_filtering_info (KmsSipSrtpSession *ses, KmsSrtpCo 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 KmsIRtpConnection * kms_sip_srtp_session_create_connection (KmsBaseRtpSession * base_rtp_sess, const GstSDPMedia * media, const gchar * name, guint16 min_port, @@ -117,7 +150,7 @@ kms_sip_srtp_session_create_connection (KmsBaseRtpSession * base_rtp_sess, if (self->priv->conns != NULL) { // If we are recovering a previous session, due to a renegotation (consecutive processAnswer) - kms_sip_srtp_connection_retrieve_sockets (self->priv->conns, media, &rtp_sock, &rtcp_sock); + kms_sip_srtp_session_retrieve_sockets (self->priv->conns, media, &rtp_sock, &rtcp_sock); } media_str = gst_sdp_media_get_media (media); diff --git a/src/gst-plugins/rtpendpoint/kmssrtpconnection.c b/src/gst-plugins/rtpendpoint/kmssrtpconnection.c index e9aabf550..e406f8eeb 100644 --- a/src/gst-plugins/rtpendpoint/kmssrtpconnection.c +++ b/src/gst-plugins/rtpendpoint/kmssrtpconnection.c @@ -617,23 +617,8 @@ kms_srtp_connection_interface_init (KmsIRtpConnectionInterface * iface) } void -kms_sip_srtp_connection_retrieve_sockets (GHashTable *conns, const GstSDPMedia * media, GSocket **rtp, GSocket **rtcp) +kms_sip_srtp_connection_retrieve_sockets (KmsSrtpConnection *conn, GSocket **rtp, GSocket **rtcp) { - gchar *media_key; - KmsSrtpConnection *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_SRTP_CONNECTION (g_hash_table_lookup (conns, media_key)); if (conn != NULL) { // Retrieve the sockets *rtcp = g_object_ref (conn->priv->rtcp_socket); diff --git a/src/gst-plugins/rtpendpoint/kmssrtpconnection.h b/src/gst-plugins/rtpendpoint/kmssrtpconnection.h index be7ea9082..17fd6ba35 100644 --- a/src/gst-plugins/rtpendpoint/kmssrtpconnection.h +++ b/src/gst-plugins/rtpendpoint/kmssrtpconnection.h @@ -72,7 +72,7 @@ kms_sip_srtp_connection_add_probes (KmsSrtpConnection *conn, SipFilterSsrcInfo* void kms_sip_srtp_connection_release_probes (KmsSrtpConnection *conn, gulong rtp_probe_id, gulong rtcp_probe_id); -void kms_sip_srtp_connection_retrieve_sockets (GHashTable *conns, const GstSDPMedia * media, GSocket **rtp, GSocket **rtcp); +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); diff --git a/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp index 4501e9c8f..a8d239443 100644 --- a/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp @@ -14,6 +14,7 @@ * limitations under the License. * */ +#include #include #include "MediaPipeline.hpp" #include "ComposedObjectImpl.hpp" @@ -26,12 +27,13 @@ #include #include #include +#include #include #include +#include +#include #include #include -#include "RembParams.hpp" - #define GST_CAT_DEFAULT kurento_sip_rtp_endpoint_impl @@ -52,10 +54,14 @@ namespace kurento FacadeRtpEndpointImpl::FacadeRtpEndpointImpl (const boost::property_tree::ptree &conf, std::shared_ptr mediaPipeline, - std::shared_ptr crypto, bool useIpv6) + 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; @@ -140,19 +146,15 @@ std::string FacadeRtpEndpointImpl::generateOffer () try { offer = this->rtp_ep->generateOffer(); + 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()); - std::shared_ptr newEndpoint = std::shared_ptr(new SipRtpEndpointImpl (config, getMediaPipeline (), cryptoCache, useIpv6Cache)); - - newEndpoint->postConstructor(); - renewInternalEndpoint (newEndpoint); - offer = newEndpoint->generateOffer(); - 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; } else { GST_WARNING ("Exception generating offer in SipRtpEndpoint: %s - %s", e.getType().c_str(), e.getMessage().c_str()); throw e; @@ -161,26 +163,44 @@ std::string FacadeRtpEndpointImpl::generateOffer () 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(); + if (this->isCryptoAgnostic()) { + this->generateCryptoAgnosticOffer (offer); + GST_INFO ("GenerateOffer: generating 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; } 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 { - answer = this->rtp_ep->processOffer(offer); - GST_DEBUG ("ProcessOffer: \n%s", answer.c_str()); - return answer; + bool renewEp = false; + + if (this->isCryptoAgnostic ()) + renewEp = this->checkCryptoOffer (modifiableOffer, cryptoToUse); + + if (!renewEp) { + answer = this->rtp_ep->processOffer(modifiableOffer); + GST_DEBUG ("ProcessOffer: \n%s", answer.c_str()); + 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()); - std::shared_ptr newEndpoint = std::shared_ptr(new SipRtpEndpointImpl (config, getMediaPipeline (), cryptoCache, useIpv6Cache)); - - newEndpoint->postConstructor(); - renewInternalEndpoint (newEndpoint); - answer = newEndpoint->processOffer(offer); - GST_DEBUG ("2nd try ProcessOffer: \n%s", answer.c_str()); - GST_INFO("Consecutive process Offer on %s, endpoint cloned and offer processed", this->getId().c_str()); - return answer; + cryptoToUse = cryptoCache; } else { GST_WARNING ("Exception generating offer in SipRtpEndpoint: %s - %s", e.getType().c_str(), e.getMessage().c_str()); throw e; @@ -189,31 +209,59 @@ std::string FacadeRtpEndpointImpl::processOffer (const std::string &offer) 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); + GST_DEBUG ("2nd try ProcessOffer: \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 { - result = this->rtp_ep->processAnswer(answer); - GST_DEBUG ("ProcessAnswer: \n%s", result.c_str()); - return result; + bool renewEp = false; + + 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()); - std::shared_ptr newEndpoint = rtp_ep->getCleanEndpoint (config, getMediaPipeline (), cryptoCache, useIpv6Cache, answer); - std::string unusedOffer; - std::shared_ptr oldEndpoint; - - newEndpoint->postConstructor(); - oldEndpoint = renewInternalEndpoint (newEndpoint); - unusedOffer = newEndpoint->generateOffer(); - GST_DEBUG ("2nd try ProcessAnswer - Unused offer: \n%s", unusedOffer.c_str()); - result = newEndpoint->processAnswer(answer); - 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; + cryptoToUse = cryptoCache; } else { GST_WARNING ("Exception generating offer in SipRtpEndpoint: %s - %s", e.getType().c_str(), e.getMessage().c_str()); throw e; @@ -222,6 +270,18 @@ std::string FacadeRtpEndpointImpl::processAnswer (const std::string &answer) GST_WARNING ("Exception generating offer in SipRtpEndpoint: %s", e1.what()); throw e1; } + std::string unusedOffer; + std::shared_ptr oldEndpoint; + + newEndpoint = rtp_ep->getCleanEndpoint (config, getMediaPipeline (), cryptoToUse, useIpv6Cache, answer); + 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 () @@ -235,6 +295,444 @@ std::string FacadeRtpEndpointImpl::getRemoteSessionDescriptor () } +bool +FacadeRtpEndpointImpl::isCryptoAgnostic () +{ + return this->cryptoAgnostic; +} + +void +FacadeRtpEndpointImpl::replaceSsrc (GstSDPMedia *media, guint idx, gchar *newSsrcStr) +{ + const GstSDPAttribute *attr; + GstSDPAttribute *new_attr; + gchar* ssrc; + GRegex *regex; + gchar* newSsrc; + + 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); + newSsrc = g_regex_replace_literal (regex, ssrc, strlen (ssrc), 0, newSsrcStr, (GRegexMatchFlags)0, NULL); + g_regex_unref (regex); + gst_sdp_attribute_set (new_attr, "ssrc", newSsrc); + g_free (newSsrc); + gst_sdp_media_replace_attribute (media, idx, new_attr); + } +} + +void +FacadeRtpEndpointImpl::replaceAllSsrcAttrs (GstSDPMedia *media, std::list sscrIdxs) +{ + // set the ssrc attribute + guint32 newSsrc = g_random_int (); + gchar newSsrcStr [11]; + + g_snprintf (newSsrcStr, 11, "%ud", newSsrc); + for (std::list::iterator it=sscrIdxs.begin(); it != sscrIdxs.end(); ++it) { + replaceSsrc (media, *it, newSsrcStr); + } +} + +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; + + 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); + + // 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 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; +} + +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; +} + + +static void +splitMediaByCrypto (std::list &nonCryptoList, std::list &cryptoList) +{ + // For each media we check if it is using a crypto protocol or not + for (std::list::iterator it=nonCryptoList.begin(); it != nonCryptoList.end(); ){ + GstSDPMedia *media = (GstSDPMedia*) *it; + if ((g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/SAVP") == 0) + || (g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/SAVPF") == 0)) { + if (isMediaActive (media)) { + cryptoList.push_back(media); + it = nonCryptoList.erase (it); + continue; + } + } + ++it; + } +} + +static std::list +getMediasFromSdp (GstSDPMessage *sdp) +{ + std::list 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; +} + +bool +FacadeRtpEndpointImpl::generateCryptoAgnosticOffer (std::string& offer) +{ + std::list mediaList; + GstSDPMessage *sdpOffer; + gchar* result; + + // If not crypto configured, cannot generate crypto agnostic offer + if (this->cryptoCache == NULL) { + 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::list::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 std::shared_ptr +is_crypto_sdp (std::list mediaList) +{ + std::shared_ptr sdes (new SDES()); + std::list cryptoMediaList; + + splitMediaByCrypto (mediaList, cryptoMediaList); + + if (cryptoMediaList.size () > 0) { + // 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 (cryptoMediaList.front (), sdes)) { + // No valid key found, + GST_ERROR ("Crypto offer found, but no supported key found in offer, cannot answer"); + } else { + GST_INFO ("Valid crypto info found in offer"); + } + } else { + GST_INFO ("No crypto offer found"); + } + + return sdes; +} + +bool +FacadeRtpEndpointImpl::checkCryptoOffer (std::string& offer, std::shared_ptr& crypto) +{ + std::list mediaList; + GstSDPMessage* sdpOffer; + std::shared_ptr sdes; + + sdpOffer = parseSDP (offer); + if (sdpOffer == NULL) + return false; + + mediaList = getMediasFromSdp (sdpOffer); + + sdes = is_crypto_sdp (mediaList); + + // The easiest way to proceed is recreate the endpoint in any case + crypto = sdes; + gst_sdp_message_free (sdpOffer); + return true; +} + +static bool +isCryptoCompatible (std::shared_ptr original, std::shared_ptr answer) +{ + bool result = true; + + if (original->isSetCrypto() != answer->isSetCrypto()) { + result = false; + } else { + if (original->getCrypto() != answer->getCrypto()) + result = false; + } + return result; +} + +static bool +removeMediaLines (GstSDPMessage* sdpAnswer, std::list mediaList, bool crypto) +{ + std::list mediaIdxToRemove; + guint idx = 0; + bool result = false; + + // For each media we check if it is using a crypto protocol or not + for (std::list::iterator it=mediaList.begin(); it != mediaList.end(); ){ + GstSDPMedia *media = (GstSDPMedia*) *it; + if ((g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/SAVP") == 0) + || (g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/SAVPF") == 0)) { + if (crypto) { + mediaIdxToRemove.push_back (idx); + } + } else if ((g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/AVP") == 0) + || (g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/AVPF") == 0)){ + if (!crypto) { + mediaIdxToRemove.push_back (idx); + } + } + ++it; + ++idx; + } + + // Remove media lines that are not if there are other lines + // on mediaIdxToRemote we have the indexes of media lines that are not + // So we remove thos indexes only if there are other media lines. + if ((mediaIdxToRemove.size () > 0) && (mediaList.size() > mediaIdxToRemove.size ())) { + for (std::list::reverse_iterator it=mediaIdxToRemove.rbegin(); it != mediaIdxToRemove.rend(); ++it) { + g_array_remove_index (sdpAnswer->medias, *it); + } + result = true; + } + + return result;; +} + +static std::shared_ptr +fitMediaAnswer (std::string& answer, bool isLocalCrypto) +{ + std::list mediaList; + GstSDPMessage* sdpAnswer; + std::shared_ptr sdes; + gchar * newAnswer; + + sdpAnswer = parseSDP (answer); + if (sdpAnswer == NULL) + return sdes; + + mediaList = getMediasFromSdp (sdpAnswer); + + sdes = is_crypto_sdp (mediaList); + + if (removeMediaLines (sdpAnswer, mediaList, !isLocalCrypto)) { + 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 () { @@ -317,7 +815,6 @@ FacadeRtpEndpointImpl::renewInternalEndpoint (std::shared_ptr caps) videoCapsSet = caps; } -/*virtual void release () override; */ +void FacadeRtpEndpointImpl::release () +{ + this->linkMediaElement(NULL, NULL); + ComposedObjectImpl::release (); + +} std::string FacadeRtpEndpointImpl::getGstreamerDot () { diff --git a/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp b/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp index e4700e319..524630824 100644 --- a/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp @@ -23,6 +23,9 @@ #include #include #include +#include + + namespace kurento @@ -32,6 +35,9 @@ class MediaPipeline; class FacadeRtpEndpointImpl; +class SDES; +class CryptoSuite; + void Serialize (std::shared_ptr &object, JsonSerializer &serializer); @@ -42,7 +48,9 @@ class FacadeRtpEndpointImpl : public ComposedObjectImpl, public virtual SipRtpEn FacadeRtpEndpointImpl (const boost::property_tree::ptree &conf, std::shared_ptr mediaPipeline, - std::shared_ptr crypto, bool useIpv6); + std::shared_ptr crypto, + bool cryptoAgnostic, + bool useIpv6); virtual ~FacadeRtpEndpointImpl (); @@ -107,7 +115,7 @@ class FacadeRtpEndpointImpl : public ComposedObjectImpl, public virtual SipRtpEn void setAudioFormat (std::shared_ptr caps) override; void setVideoFormat (std::shared_ptr caps) override; - /*virtual void release () override; */ + virtual void release () override; virtual std::string getGstreamerDot () override; virtual std::string getGstreamerDot (std::shared_ptr @@ -156,6 +164,8 @@ class FacadeRtpEndpointImpl : public ComposedObjectImpl, public virtual SipRtpEn private: + bool cryptoAgnostic; + sigc::signal signalMediaStateChanged; sigc::signal signalConnectionStateChanged; sigc::signal signalMediaSessionStarted; @@ -168,6 +178,30 @@ class FacadeRtpEndpointImpl : public ComposedObjectImpl, public virtual SipRtpEn sigc::connection connMediaSessionTerminated; sigc::connection connOnKeySoftLimit; + 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); + + void + replaceAllSsrcAttrs (GstSDPMedia *media, std::list sscrIdxs); + + void + removeCryptoAttrs (GstSDPMedia *media, std::list cryptoIdx); + + void + addAgnosticMedia (GstSDPMedia *media, GstSDPMessage *sdpOffer); + void disconnectForwardSignals (); diff --git a/src/server/implementation/objects/SipRtpEndpointImpl.cpp b/src/server/implementation/objects/SipRtpEndpointImpl.cpp index fe415277a..6c7eb5e76 100644 --- a/src/server/implementation/objects/SipRtpEndpointImpl.cpp +++ b/src/server/implementation/objects/SipRtpEndpointImpl.cpp @@ -47,7 +47,8 @@ namespace kurento SipRtpEndpointImpl::SipRtpEndpointImpl (const boost::property_tree::ptree &conf, std::shared_ptr mediaPipeline, - std::shared_ptr crypto, bool useIpv6) + std::shared_ptr crypto, + bool useIpv6) : BaseRtpEndpointImpl (conf, std::dynamic_pointer_cast (mediaPipeline), FACTORY_NAME, useIpv6) @@ -113,6 +114,7 @@ SipRtpEndpointImpl::SipRtpEndpointImpl (const boost::property_tree::ptree &conf, "crypto-suite", crypto->getCrypto()->getValue(), NULL); } + SipRtpEndpointImpl::~SipRtpEndpointImpl() { if (handlerOnKeySoftLimit > 0) { @@ -137,7 +139,9 @@ SipRtpEndpointImpl::postConstructor () MediaObjectImpl * SipRtpEndpointImplFactory::createObject (const boost::property_tree::ptree &conf, std::shared_ptr mediaPipeline, - std::shared_ptr crypto, bool useIpv6) const + 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. @@ -150,7 +154,7 @@ SipRtpEndpointImplFactory::createObject (const boost::property_tree::ptree &conf // 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, useIpv6); + return new FacadeRtpEndpointImpl (conf, mediaPipeline, crypto, cryptoAgnostic, useIpv6); } @@ -196,6 +200,7 @@ std::shared_ptr SipRtpEndpointImpl::getCleanEndpoint (const { 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); return newEndpoint; } diff --git a/src/server/implementation/objects/SipRtpEndpointImpl.hpp b/src/server/implementation/objects/SipRtpEndpointImpl.hpp index 41896026b..9655bed9a 100644 --- a/src/server/implementation/objects/SipRtpEndpointImpl.hpp +++ b/src/server/implementation/objects/SipRtpEndpointImpl.hpp @@ -41,7 +41,8 @@ class SipRtpEndpointImpl : public BaseRtpEndpointImpl, public virtual SipRtpEndp SipRtpEndpointImpl (const boost::property_tree::ptree &conf, std::shared_ptr mediaPipeline, - std::shared_ptr crypto, bool useIpv6); + std::shared_ptr crypto, + bool useIpv6); virtual ~SipRtpEndpointImpl (); @@ -52,6 +53,8 @@ class SipRtpEndpointImpl : public BaseRtpEndpointImpl, public virtual SipRtpEndp std::shared_ptr crypto, bool useIpv6, const std::string &sdp); + bool isEncrypted (); + /* Next methods are automatically implemented by code generator */ using BaseRtpEndpointImpl::connect; virtual bool connect (const std::string &eventType, diff --git a/src/server/interface/elements.SipRtpEndpoint.kmd.json b/src/server/interface/elements.SipRtpEndpoint.kmd.json index f44d04e28..e7175696a 100644 --- a/src/server/interface/elements.SipRtpEndpoint.kmd.json +++ b/src/server/interface/elements.SipRtpEndpoint.kmd.json @@ -3,14 +3,16 @@ { "name": "SipRtpEndpoint", "extends": "BaseRtpEndpoint", - "doc": "Endpoint that provides the same functionality and features that :rom:cls:`RtpEndpoint` providing SDP renegotiation features that allow easy implementation of SIP 183 like flows. This endpoint inherits from :rom:cls:`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 renegotation feature, to use the :rom:cls:`RtpEndpoint` to avoid unneeded overload due to the components instatiated to allow renegotiation. + 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.

- The API and methods to establish a RTP/SRTP communication are exactly the same as in :rom:cls:`RtpEndpoint` -

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.

@@ -35,6 +37,22 @@ 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
  • +
+

", "constructor": { @@ -52,6 +70,13 @@ "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.", diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt index ae200cc01..937858956 100644 --- a/tests/server/CMakeLists.txt +++ b/tests/server/CMakeLists.txt @@ -151,3 +151,24 @@ target_link_libraries(test_sip_rtp_endpoint_play ${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} +) From 4c2848c48b08a7911e24c5a0b600b33a24423dde Mon Sep 17 00:00:00 2001 From: Saul Pablo Labajo Izquierdo Date: Mon, 17 Feb 2020 16:46:35 +0100 Subject: [PATCH 05/11] unneeded prototype --- src/server/implementation/objects/SipRtpEndpointImpl.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/server/implementation/objects/SipRtpEndpointImpl.hpp b/src/server/implementation/objects/SipRtpEndpointImpl.hpp index 9655bed9a..b806be6b6 100644 --- a/src/server/implementation/objects/SipRtpEndpointImpl.hpp +++ b/src/server/implementation/objects/SipRtpEndpointImpl.hpp @@ -53,8 +53,6 @@ class SipRtpEndpointImpl : public BaseRtpEndpointImpl, public virtual SipRtpEndp std::shared_ptr crypto, bool useIpv6, const std::string &sdp); - bool isEncrypted (); - /* Next methods are automatically implemented by code generator */ using BaseRtpEndpointImpl::connect; virtual bool connect (const std::string &eventType, From f915cca89425146a11cf8bbdb492227e6dcb8b6c Mon Sep 17 00:00:00 2001 From: Saul Pablo Labajo Izquierdo Date: Wed, 4 Mar 2020 10:49:56 +0100 Subject: [PATCH 06/11] Fixed bug for crypto agnostic feature --- .../objects/FacadeRtpEndpointImpl.cpp | 156 ++-- tests/server/sipRtpEndpoint_agnostic_srtp.cpp | 800 ++++++++++++++++++ 2 files changed, 892 insertions(+), 64 deletions(-) create mode 100644 tests/server/sipRtpEndpoint_agnostic_srtp.cpp diff --git a/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp index a8d239443..296fd6c6f 100644 --- a/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp @@ -177,6 +177,44 @@ std::string FacadeRtpEndpointImpl::generateOffer () return offer; } +static std::list +getMediasFromSdp (GstSDPMessage *sdp) +{ + std::list 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; @@ -187,12 +225,13 @@ std::string FacadeRtpEndpointImpl::processOffer (const std::string &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 ("ProcessOffer: \n%s", answer.c_str()); + GST_DEBUG ("Generated Answer: \n%s", answer.c_str()); return answer; } else { GST_INFO ("ProcessOffer: Regenerating endpoint fro agnostic crypto"); @@ -217,7 +256,7 @@ std::string FacadeRtpEndpointImpl::processOffer (const std::string &offer) newEndpoint->postConstructor(); renewInternalEndpoint (newEndpoint); answer = newEndpoint->processOffer(modifiableOffer); - GST_DEBUG ("2nd try ProcessOffer: \n%s", answer.c_str()); + 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; } @@ -248,6 +287,7 @@ std::string FacadeRtpEndpointImpl::processAnswer (const std::string &answer) try { bool renewEp = false; + GST_DEBUG("ProcessAnswer: \n%s", answer.c_str()); if (this->isCryptoAgnostic ()) renewEp = this->checkCryptoAnswer (modifiableAnswer, cryptoToUse); @@ -306,19 +346,32 @@ FacadeRtpEndpointImpl::replaceSsrc (GstSDPMedia *media, guint idx, gchar *newSsr { const GstSDPAttribute *attr; GstSDPAttribute *new_attr; - gchar* ssrc; + std::string ssrc; + std::string oldSsrc; GRegex *regex; - gchar* newSsrc; + 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); - newSsrc = g_regex_replace_literal (regex, ssrc, strlen (ssrc), 0, newSsrcStr, (GRegexMatchFlags)0, NULL); + g_regex_match (regex, ssrc.c_str(), (GRegexMatchFlags)0, &match_info); + if (g_match_info_matches (match_info)) { + oldSsrc = g_match_info_fetch_named (match_info, "ssrc"); + } + g_match_info_free (match_info); g_regex_unref (regex); - gst_sdp_attribute_set (new_attr, "ssrc", newSsrc); - g_free (newSsrc); + + ssrcIdx = ssrc.find(oldSsrc); + if (ssrcIdx != std::string::npos) { + newSsrc = ssrc.substr(0, ssrcIdx).append(newSsrcStr).append (ssrc.substr(ssrcIdx+oldSsrc.length(), std::string::npos)); + } + + gst_sdp_attribute_set (new_attr, "ssrc", newSsrc.c_str()); gst_sdp_media_replace_attribute (media, idx, new_attr); } } @@ -392,21 +445,6 @@ FacadeRtpEndpointImpl::addAgnosticMedia (GstSDPMedia *media, GstSDPMessage *sdpO gst_sdp_message_add_media (sdpOffer, newMedia); } -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; -} - static bool isMediaActive (GstSDPMedia *media) { @@ -439,28 +477,6 @@ splitMediaByCrypto (std::list &nonCryptoList, std::list -getMediasFromSdp (GstSDPMessage *sdp) -{ - std::list 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; -} - bool FacadeRtpEndpointImpl::generateCryptoAgnosticOffer (std::string& offer) { @@ -650,31 +666,47 @@ isCryptoCompatible (std::shared_ptr original, std::shared_ptr answer return result; } -static bool -removeMediaLines (GstSDPMessage* sdpAnswer, std::list mediaList, bool crypto) +static void +removeMediaLines (GstSDPMessage* sdpAnswer, std::list mediaList) { std::list mediaIdxToRemove; guint idx = 0; - bool result = false; + bool got_audio = false, got_video = false; // For each media we check if it is using a crypto protocol or not for (std::list::iterator it=mediaList.begin(); it != mediaList.end(); ){ GstSDPMedia *media = (GstSDPMedia*) *it; - if ((g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/SAVP") == 0) - || (g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/SAVPF") == 0)) { - if (crypto) { - mediaIdxToRemove.push_back (idx); - } - } else if ((g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/AVP") == 0) - || (g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/AVPF") == 0)){ - if (!crypto) { - mediaIdxToRemove.push_back (idx); + + // We just get the first audio and first video that are active (or port != 0 + if (!isMediaActive (media)) { + mediaIdxToRemove.push_back (idx); + } else { + if (g_strcmp0 (media->media, "audio") == 0) { + if (!got_audio) { + got_audio = true; + } else { + mediaIdxToRemove.push_back (idx); + } + } else if (g_strcmp0 (media->media, "video") == 0) { + if (!got_video) { + got_video = true; + } else { + mediaIdxToRemove.push_back (idx); + } + } } ++it; ++idx; } + // We cannot remove all medias, we need to leave at least one audio and one video + // If mediaIdxToRemove has all medias, we just remove two last ones + if (mediaIdxToRemove.size () == mediaList.size()) { + mediaIdxToRemove.pop_back (); + mediaIdxToRemove.pop_back (); + } + // Remove media lines that are not if there are other lines // on mediaIdxToRemote we have the indexes of media lines that are not // So we remove thos indexes only if there are other media lines. @@ -682,10 +714,7 @@ removeMediaLines (GstSDPMessage* sdpAnswer, std::list mediaList, b for (std::list::reverse_iterator it=mediaIdxToRemove.rbegin(); it != mediaIdxToRemove.rend(); ++it) { g_array_remove_index (sdpAnswer->medias, *it); } - result = true; } - - return result;; } static std::shared_ptr @@ -704,11 +733,10 @@ fitMediaAnswer (std::string& answer, bool isLocalCrypto) sdes = is_crypto_sdp (mediaList); - if (removeMediaLines (sdpAnswer, mediaList, !isLocalCrypto)) { - newAnswer = gst_sdp_message_as_text (sdpAnswer); - answer = newAnswer; - g_free ((gpointer)newAnswer); - } + removeMediaLines (sdpAnswer, mediaList); + newAnswer = gst_sdp_message_as_text (sdpAnswer); + answer = newAnswer; + g_free ((gpointer)newAnswer); gst_sdp_message_free (sdpAnswer); diff --git a/tests/server/sipRtpEndpoint_agnostic_srtp.cpp b/tests/server/sipRtpEndpoint_agnostic_srtp.cpp new file mode 100644 index 000000000..03162135e --- /dev/null +++ b/tests/server/sipRtpEndpoint_agnostic_srtp.cpp @@ -0,0 +1,800 @@ +/* + * (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 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 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 +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 +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); +} + + + + + + + + + + + + +test_suite * +init_unit_test_suite ( int , char *[] ) +{ + test_suite *test = BOOST_TEST_SUITE ( "SipRtpEndpoint" ); + + 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); + return test; +} From eb67bda1201885245209c827f134f16287f5c686 Mon Sep 17 00:00:00 2001 From: Saul Pablo Labajo Izquierdo Date: Thu, 2 Apr 2020 11:59:47 +0200 Subject: [PATCH 07/11] Fixes for SRTP agnostic feature --- .../rtpendpoint/kmssiprtpendpoint.c | 63 +++ .../objects/FacadeRtpEndpointImpl.cpp | 380 +++++++++++++----- .../objects/FacadeRtpEndpointImpl.hpp | 11 +- .../objects/SipRtpEndpointImpl.cpp | 9 + .../objects/SipRtpEndpointImpl.hpp | 4 + tests/server/sipRtpEndpoint_agnostic_srtp.cpp | 284 ++++++++++++- 6 files changed, 636 insertions(+), 115 deletions(-) diff --git a/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c b/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c index de790730d..3ed5695f6 100644 --- a/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c +++ b/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c @@ -31,6 +31,10 @@ #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 @@ -68,6 +72,15 @@ struct _KmsSipRtpEndpointPrivate GList *sessionData; }; +/* Properties */ +enum +{ + PROP_0, + PROP_AUDIO_SSRC, + PROP_VIDEO_SSRC +}; + + /* Signals and args */ enum { @@ -124,6 +137,8 @@ kms_sip_rtp_endpoint_get_rtpbin (KmsSipRtpEndpoint * self) static KmsSipRtpEndpointCloneData* kms_sip_rtp_endpoint_get_clone_data (GList *sessionData) { + if (sessionData == NULL) + return NULL; return ((KmsSipRtpEndpointCloneData*)sessionData->data); } @@ -579,6 +594,25 @@ kms_sip_rtp_endpoint_set_property (GObject * object, guint prop_id, 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; @@ -596,6 +630,22 @@ kms_sip_rtp_endpoint_get_property (GObject * object, guint prop_id, 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; @@ -662,6 +712,19 @@ kms_sip_rtp_endpoint_class_init (KmsSipRtpEndpointClass * klass) 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), diff --git a/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp index 296fd6c6f..70cd61413 100644 --- a/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include @@ -52,6 +54,69 @@ GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); 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, @@ -66,6 +131,12 @@ FacadeRtpEndpointImpl::FacadeRtpEndpointImpl (const boost::property_tree::ptree 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() @@ -170,17 +241,17 @@ std::string FacadeRtpEndpointImpl::generateOffer () offer = newEndpoint->generateOffer(); if (this->isCryptoAgnostic()) { this->generateCryptoAgnosticOffer (offer); - GST_INFO ("GenerateOffer: generating crypto agnostic 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::list +static std::vector getMediasFromSdp (GstSDPMessage *sdp) { - std::list mediaList; + std::vector mediaList; guint idx = 0; guint medias_len; @@ -232,6 +303,7 @@ std::string FacadeRtpEndpointImpl::processOffer (const std::string &offer) 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"); @@ -256,6 +328,7 @@ std::string FacadeRtpEndpointImpl::processOffer (const std::string &offer) 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; @@ -301,7 +374,7 @@ std::string FacadeRtpEndpointImpl::processAnswer (const std::string &answer) } 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; + //cryptoToUse = cryptoCache; } else { GST_WARNING ("Exception generating offer in SipRtpEndpoint: %s - %s", e.getType().c_str(), e.getMessage().c_str()); throw e; @@ -313,7 +386,24 @@ std::string FacadeRtpEndpointImpl::processAnswer (const std::string &answer) std::string unusedOffer; std::shared_ptr oldEndpoint; - newEndpoint = rtp_ep->getCleanEndpoint (config, getMediaPipeline (), cryptoToUse, useIpv6Cache, answer); + newEndpoint = rtp_ep->getCleanEndpoint (config, getMediaPipeline (), cryptoToUse, useIpv6Cache, modifiableAnswer); + 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(); @@ -342,12 +432,15 @@ FacadeRtpEndpointImpl::isCryptoAgnostic () } void -FacadeRtpEndpointImpl::replaceSsrc (GstSDPMedia *media, guint idx, gchar *newSsrcStr) +FacadeRtpEndpointImpl::replaceSsrc (GstSDPMedia *media, + guint idx, + gchar *newSsrcStr, + guint32 &oldSsrc) { const GstSDPAttribute *attr; GstSDPAttribute *new_attr; std::string ssrc; - std::string oldSsrc; + std::string oldSsrcStr; GRegex *regex; std::string newSsrc; std::size_t ssrcIdx; @@ -361,31 +454,33 @@ FacadeRtpEndpointImpl::replaceSsrc (GstSDPMedia *media, guint idx, gchar *newSsr 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)) { - oldSsrc = g_match_info_fetch_named (match_info, "ssrc"); + oldSsrcStr = g_match_info_fetch_named (match_info, "ssrc"); } g_match_info_free (match_info); g_regex_unref (regex); - ssrcIdx = ssrc.find(oldSsrc); + ssrcIdx = ssrc.find(oldSsrcStr); if (ssrcIdx != std::string::npos) { - newSsrc = ssrc.substr(0, ssrcIdx).append(newSsrcStr).append (ssrc.substr(ssrcIdx+oldSsrc.length(), 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) +FacadeRtpEndpointImpl::replaceAllSsrcAttrs (GstSDPMedia *media, std::list sscrIdxs, guint32 &oldSsrc, guint32 &newSsrc) { // set the ssrc attribute - guint32 newSsrc = g_random_int (); gchar newSsrcStr [11]; - g_snprintf (newSsrcStr, 11, "%ud", newSsrc); + 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); + replaceSsrc (media, *it, newSsrcStr, oldSsrc); } } @@ -404,6 +499,8 @@ FacadeRtpEndpointImpl::addAgnosticMedia (GstSDPMedia *media, GstSDPMessage *sdpO 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"); @@ -436,7 +533,14 @@ FacadeRtpEndpointImpl::addAgnosticMedia (GstSDPMedia *media, GstSDPMessage *sdpO idx++; } - replaceAllSsrcAttrs (newMedia, sscrIdxs); + 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); @@ -459,33 +563,28 @@ isMediaActive (GstSDPMedia *media) } -static void -splitMediaByCrypto (std::list &nonCryptoList, std::list &cryptoList) +static bool +isCryptoSDES (std::shared_ptr sdes) { - // For each media we check if it is using a crypto protocol or not - for (std::list::iterator it=nonCryptoList.begin(); it != nonCryptoList.end(); ){ - GstSDPMedia *media = (GstSDPMedia*) *it; - if ((g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/SAVP") == 0) - || (g_strcmp0 (gst_sdp_media_get_proto (media), "RTP/SAVPF") == 0)) { - if (isMediaActive (media)) { - cryptoList.push_back(media); - it = nonCryptoList.erase (it); - continue; - } - } - ++it; - } + if (sdes == NULL) + return false; + + if (sdes->isSetCrypto()) + return true; + + return false; } + bool FacadeRtpEndpointImpl::generateCryptoAgnosticOffer (std::string& offer) { - std::list mediaList; + std::vector mediaList; GstSDPMessage *sdpOffer; gchar* result; // If not crypto configured, cannot generate crypto agnostic offer - if (this->cryptoCache == NULL) { + if (!isCryptoSDES(this->cryptoCache)) { GST_WARNING ("cryptoAgnostic configured, but no crypto info set, cannot generate cryptoAgnostic endpoint, reverting to non crypto endpoint"); return false; } @@ -498,7 +597,7 @@ FacadeRtpEndpointImpl::generateCryptoAgnosticOffer (std::string& offer) // 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::list::iterator it=mediaList.begin(); it != mediaList.end(); ++it) { + for (std::vector::iterator it=mediaList.begin(); it != mediaList.end(); ++it) { addAgnosticMedia (*it, sdpOffer); } @@ -551,7 +650,7 @@ build_crypto (std::shared_ptr suite, std::string &key) } static bool -get_valid_crypto_info_from_offer (GstSDPMedia *media, std::shared_ptr& crypto) +get_valid_crypto_info_from_offer (GstSDPMedia *media, std::shared_ptr &crypto) { guint idx, attrs_len; @@ -606,37 +705,119 @@ get_valid_crypto_info_from_offer (GstSDPMedia *media, std::shared_ptr& cry return false; } -static std::shared_ptr -is_crypto_sdp (std::list mediaList) +static void +makeUpSdp (bool isCrypto, GstSDPMessage* sdp, + std::set cryptoMedias, + std::set nonCryptoMedias) { - std::shared_ptr sdes (new SDES()); - std::list cryptoMediaList; + std::set *mediaToAdd; + int numMedias, idx; - splitMediaByCrypto (mediaList, cryptoMediaList); + 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 ())); - if (cryptoMediaList.size () > 0) { // 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 (cryptoMediaList.front (), sdes)) { + 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 sdes; + return false; } bool FacadeRtpEndpointImpl::checkCryptoOffer (std::string& offer, std::shared_ptr& crypto) { - std::list mediaList; + std::vector mediaList; + std::set nonCryptoMedias; + std::set cryptoMedias; GstSDPMessage* sdpOffer; - std::shared_ptr sdes; + bool isCrypto = false; + std::shared_ptr sdes (new SDES()); + gchar *modifiedSdpStr; sdpOffer = parseSDP (offer); if (sdpOffer == NULL) @@ -644,86 +825,49 @@ FacadeRtpEndpointImpl::checkCryptoOffer (std::string& offer, std::shared_ptr original, std::shared_ptr answer) -{ - bool result = true; + 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; - if (original->isSetCrypto() != answer->isSetCrypto()) { - result = false; - } else { - if (original->getCrypto() != answer->getCrypto()) - result = false; + while (idx < mediaList.size ()) { + nonCryptoMedias.insert(idx); + idx++; + } } - return result; -} + getCryptoMedias (mediaList, nonCryptoMedias, cryptoMedias); -static void -removeMediaLines (GstSDPMessage* sdpAnswer, std::list mediaList) -{ - std::list mediaIdxToRemove; - guint idx = 0; - bool got_audio = false, got_video = false; + isCrypto = getCryptoInfoFromMedia (mediaList, cryptoMedias, sdes); - // For each media we check if it is using a crypto protocol or not - for (std::list::iterator it=mediaList.begin(); it != mediaList.end(); ){ - GstSDPMedia *media = (GstSDPMedia*) *it; + makeUpSdp (isCrypto, sdpOffer, cryptoMedias, nonCryptoMedias); - // We just get the first audio and first video that are active (or port != 0 - if (!isMediaActive (media)) { - mediaIdxToRemove.push_back (idx); - } else { - if (g_strcmp0 (media->media, "audio") == 0) { - if (!got_audio) { - got_audio = true; - } else { - mediaIdxToRemove.push_back (idx); - } - } else if (g_strcmp0 (media->media, "video") == 0) { - if (!got_video) { - got_video = true; - } else { - mediaIdxToRemove.push_back (idx); - } + crypto = sdes; + modifiedSdpStr = gst_sdp_message_as_text (sdpOffer); + offer = modifiedSdpStr; - } - } - ++it; - ++idx; - } + gst_sdp_message_free (sdpOffer); + g_free (modifiedSdpStr); - // We cannot remove all medias, we need to leave at least one audio and one video - // If mediaIdxToRemove has all medias, we just remove two last ones - if (mediaIdxToRemove.size () == mediaList.size()) { - mediaIdxToRemove.pop_back (); - mediaIdxToRemove.pop_back (); - } + crypto = sdes; - // Remove media lines that are not if there are other lines - // on mediaIdxToRemote we have the indexes of media lines that are not - // So we remove thos indexes only if there are other media lines. - if ((mediaIdxToRemove.size () > 0) && (mediaList.size() > mediaIdxToRemove.size ())) { - for (std::list::reverse_iterator it=mediaIdxToRemove.rbegin(); it != mediaIdxToRemove.rend(); ++it) { - g_array_remove_index (sdpAnswer->medias, *it); - } - } + if (isCryptoCompatible(cryptoCache, sdes)) + return false; + + return true; } static std::shared_ptr fitMediaAnswer (std::string& answer, bool isLocalCrypto) { - std::list mediaList; + std::vector mediaList; + std::set nonCryptoMedias; + std::set cryptoMedias; GstSDPMessage* sdpAnswer; - std::shared_ptr sdes; + std::shared_ptr sdes (new SDES()); gchar * newAnswer; + bool isCrypto; sdpAnswer = parseSDP (answer); if (sdpAnswer == NULL) @@ -731,9 +875,23 @@ fitMediaAnswer (std::string& answer, bool isLocalCrypto) mediaList = getMediasFromSdp (sdpAnswer); - sdes = is_crypto_sdp (mediaList); + 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); - removeMediaLines (sdpAnswer, mediaList); + makeUpSdp (isCrypto, sdpAnswer, cryptoMedias, nonCryptoMedias); newAnswer = gst_sdp_message_as_text (sdpAnswer); answer = newAnswer; g_free ((gpointer)newAnswer); diff --git a/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp b/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp index 524630824..47645331f 100644 --- a/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp @@ -178,6 +178,13 @@ class FacadeRtpEndpointImpl : public ComposedObjectImpl, public virtual SipRtpEn sigc::connection connMediaSessionTerminated; sigc::connection connOnKeySoftLimit; + + + guint32 agnosticCryptoAudioSsrc; + guint32 agnosticCryptoVideoSsrc; + guint32 agnosticNonCryptoAudioSsrc; + guint32 agnosticNonCryptoVideoSsrc; + bool isCryptoAgnostic (); @@ -191,10 +198,10 @@ class FacadeRtpEndpointImpl : public ComposedObjectImpl, public virtual SipRtpEn checkCryptoAnswer (std::string& answer, std::shared_ptr& crypto); void - replaceSsrc (GstSDPMedia *media, guint idx, gchar *newSsrcStr); + replaceSsrc (GstSDPMedia *media, guint idx, gchar *newSsrcStr, guint32 &oldSsrc); void - replaceAllSsrcAttrs (GstSDPMedia *media, std::list sscrIdxs); + replaceAllSsrcAttrs (GstSDPMedia *media, std::list sscrIdxs, guint32 &oldSsrc, guint32 &newSsrc); void removeCryptoAttrs (GstSDPMedia *media, std::list cryptoIdx); diff --git a/src/server/implementation/objects/SipRtpEndpointImpl.cpp b/src/server/implementation/objects/SipRtpEndpointImpl.cpp index 6c7eb5e76..75872d08b 100644 --- a/src/server/implementation/objects/SipRtpEndpointImpl.cpp +++ b/src/server/implementation/objects/SipRtpEndpointImpl.cpp @@ -212,6 +212,15 @@ std::shared_ptr SipRtpEndpointImpl::cloneToNewEndpoint (std: 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 index b806be6b6..3f45dbb81 100644 --- a/src/server/implementation/objects/SipRtpEndpointImpl.hpp +++ b/src/server/implementation/objects/SipRtpEndpointImpl.hpp @@ -53,6 +53,10 @@ class SipRtpEndpointImpl : public BaseRtpEndpointImpl, public virtual SipRtpEndp std::shared_ptr crypto, bool useIpv6, const std::string &sdp); + + 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, diff --git a/tests/server/sipRtpEndpoint_agnostic_srtp.cpp b/tests/server/sipRtpEndpoint_agnostic_srtp.cpp index 03162135e..ad4661f93 100644 --- a/tests/server/sipRtpEndpoint_agnostic_srtp.cpp +++ b/tests/server/sipRtpEndpoint_agnostic_srtp.cpp @@ -55,8 +55,8 @@ 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"); + 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); @@ -170,6 +170,115 @@ static std::shared_ptr createTestSrc() { 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) { @@ -184,6 +293,51 @@ static std::shared_ptr getMediaElement (std::shared_ptr (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 @@ -253,6 +407,96 @@ reconnection_generate_offer_state_changes_impl (bool cryptoOffer, bool agnosticO releaseTestSrc (src); } +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) { @@ -741,6 +985,34 @@ srtp_agnostic_case_16() } +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); +} + @@ -756,6 +1028,12 @@ 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); @@ -796,5 +1074,7 @@ init_unit_test_suite ( int , char *[] ) 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); + return test; } From 835c576534afc83b42d8418bbc4b725a55944f89 Mon Sep 17 00:00:00 2001 From: Saul Pablo Labajo Izquierdo Date: Wed, 24 Jun 2020 15:21:21 +0200 Subject: [PATCH 08/11] Added VoIP stream switching support --- .../rtpendpoint/kmsrtpconnection.c | 4 +- .../rtpendpoint/kmsrtpfilterutils.c | 315 ++++++++++++++++-- .../rtpendpoint/kmsrtpfilterutils.h | 13 +- .../rtpendpoint/kmssiprtpendpoint.c | 10 +- .../rtpendpoint/kmssiprtpsession.c | 33 +- .../rtpendpoint/kmssipsrtpsession.c | 35 +- .../rtpendpoint/kmssrtpconnection.c | 4 +- .../objects/ComposedObjectImpl.cpp | 32 +- .../elements.SipRtpEndpoint.kmd.json | 3 + 9 files changed, 386 insertions(+), 63 deletions(-) diff --git a/src/gst-plugins/rtpendpoint/kmsrtpconnection.c b/src/gst-plugins/rtpendpoint/kmsrtpconnection.c index d3341c5c3..6d23f5878 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpconnection.c +++ b/src/gst-plugins/rtpendpoint/kmsrtpconnection.c @@ -481,9 +481,7 @@ kms_sip_rtp_connection_new (guint16 min_port, guint16 max_port, gboolean use_ipv priv->rtcp_udpsink = gst_element_factory_make ("multiudpsink", NULL); priv->rtcp_udpsrc = gst_element_factory_make ("udpsrc", NULL); - if (filter_info != NULL) { - kms_sip_rtp_connection_add_probes (conn, filter_info, rtp_probe_id, rtcp_probe_id); - } + 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); diff --git a/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c index e0daef68c..c259c3d9a 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c +++ b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c @@ -22,44 +22,221 @@ #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) +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) { - GList* it = filter_info->old; + 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); - while (it != NULL) { - if (ssrc == GPOINTER_TO_UINT(it->data)) - return TRUE; - it = it->next; + // 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; } - filter_info->expected = ssrc; + 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; } - if (ssrc == filter_info->expected) +} + +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; - return TRUE; + } 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_READ, &rtp_buffer)) { + 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_rtp_buffer_unmap (&rtp_buffer); - if (check_ssrc (checked_ssrc, filter_info)) { + 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); - return GST_PAD_PROBE_OK; + 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; } } @@ -105,6 +282,24 @@ filter_ssrc_rtp (GstPad *pad, GstPadProbeInfo *info, gpointer user_data) 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) { @@ -116,17 +311,15 @@ filter_ssrc_rtcp (GstPad *pad, GstPadProbeInfo *info, gpointer user_data) 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_READ, &rtcp_buffer)) { + 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"); - gst_rtcp_buffer_unmap (&rtcp_buffer); - return GST_PAD_PROBE_DROP; - while (has_packet) { + if (has_packet) { GstRTCPType packet_type = gst_rtcp_packet_get_type (&packet); if (packet_type == GST_RTCP_TYPE_SR) { @@ -134,15 +327,60 @@ filter_ssrc_rtcp (GstPad *pad, GstPadProbeInfo *info, gpointer user_data) guint64 ntptime; gst_rtcp_packet_sr_get_sender_info (&packet, &ssrc, &ntptime, &rtptime, &packet_count, &octet_count); - if (check_ssrc (ssrc, filter_info)) { + 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; } } - has_packet = gst_rtcp_packet_move_to_next (&packet); } gst_rtcp_buffer_unmap (&rtcp_buffer); } @@ -200,22 +438,44 @@ kms_sip_rtp_filter_release_probe_rtcp (GstPad *pad, gulong 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) +kms_sip_rtp_filter_create_filtering_info (guint32 expected, SipFilterSsrcInfo* previous, guint32 media_session) { SipFilterSsrcInfo* info = g_new (SipFilterSsrcInfo, 1); + // Initialize filter_info info->expected = expected; + info->current = expected; info->old = NULL; - if (previous != NULL) { - GList* it = previous->old; + 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); - if (previous->expected != 0) + 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) { info->old = g_list_append (info->old, GUINT_TO_POINTER(previous->expected)); - while (it != NULL) { - info->old = g_list_append (info->old, it->data); - it = it->next; } + // 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; @@ -223,6 +483,7 @@ kms_sip_rtp_filter_create_filtering_info (guint32 expected, SipFilterSsrcInfo* p 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); } diff --git a/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.h b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.h index f825be4f6..4b7accde3 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.h +++ b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.h @@ -25,7 +25,16 @@ typedef struct _SipFilterSsrcInfo SipFilterSsrcInfo; struct _SipFilterSsrcInfo { guint32 expected; - GList* old; + 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 @@ -41,7 +50,7 @@ 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); +kms_sip_rtp_filter_create_filtering_info (guint32 expected, SipFilterSsrcInfo* previous, guint32 media_session); void kms_sip_rtp_filter_release_filtering_info (SipFilterSsrcInfo* info); diff --git a/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c b/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c index 3ed5695f6..410a04828 100644 --- a/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c +++ b/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c @@ -511,13 +511,15 @@ kms_sip_rtp_endpoint_create_clone_data (KmsSipRtpEndpoint *self, KmsBaseRtpSessi if (KMS_IS_SIP_RTP_SESSION (ses)) { KmsSipRtpSession* sip_ses = KMS_SIP_RTP_SESSION (ses); - audio_filter_info = kms_sip_rtp_filter_create_filtering_info (audio_ssrc, sip_ses->audio_filter_info); - video_filter_info = kms_sip_rtp_filter_create_filtering_info (video_ssrc, sip_ses->video_filter_info); + 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); + video_filter_info = kms_sip_rtp_filter_create_filtering_info (video_ssrc, sip_ses->video_filter_info, VIDEO_RTP_SESSION); } else if (KMS_IS_SIP_SRTP_SESSION (ses)) { KmsSipSrtpSession* sip_ses = KMS_SIP_SRTP_SESSION (ses); - audio_filter_info = kms_sip_rtp_filter_create_filtering_info (audio_ssrc, sip_ses->audio_filter_info); - video_filter_info = kms_sip_rtp_filter_create_filtering_info (video_ssrc, sip_ses->video_filter_info); + 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); + video_filter_info = kms_sip_rtp_filter_create_filtering_info (video_ssrc, sip_ses->video_filter_info, VIDEO_RTP_SESSION); } data->audio_filter_info = audio_filter_info; diff --git a/src/gst-plugins/rtpendpoint/kmssiprtpsession.c b/src/gst-plugins/rtpendpoint/kmssiprtpsession.c index 38a85d1fe..b897f31d1 100644 --- a/src/gst-plugins/rtpendpoint/kmssiprtpsession.c +++ b/src/gst-plugins/rtpendpoint/kmssiprtpsession.c @@ -87,7 +87,7 @@ kms_sip_rtp_session_store_rtp_filtering_info (KmsSipRtpSession *ses, KmsRtpConne GST_WARNING ("No memory, some leak may happen"); } - info->conn = g_object_ref (conn); + info->conn = conn; info->rtp_probe = rtp_probe; info->rtcp_probe = rtcp_probe; @@ -124,7 +124,31 @@ kms_sip_rtp_session_retrieve_sockets (GHashTable *conns, const GstSDPMedia * med } } +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); + 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, @@ -155,11 +179,8 @@ kms_sip_rtp_session_create_connection (KmsBaseRtpSession * base_rtp_sess, media_str = gst_sdp_media_get_media (media); - if (g_strcmp0 (VIDEO_STREAM_NAME, media_str) == 0) { - filter_info = self->video_filter_info; - }else if (g_strcmp0 (AUDIO_STREAM_NAME, media_str) == 0) { - filter_info = self->audio_filter_info; - } + 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); diff --git a/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c b/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c index a4ff315e5..0426aad00 100644 --- a/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c +++ b/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c @@ -88,7 +88,7 @@ kms_sip_srtp_session_store_rtp_filtering_info (KmsSipSrtpSession *ses, KmsSrtpCo GST_WARNING ("No memory, some leak may happen"); } - info->conn = g_object_ref (conn); + info->conn = conn; info->rtp_probe = rtp_probe; info->rtcp_probe = rtcp_probe; @@ -127,6 +127,32 @@ kms_sip_srtp_session_retrieve_sockets (GHashTable *conns, const GstSDPMedia * me +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); + 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, @@ -154,11 +180,8 @@ kms_sip_srtp_session_create_connection (KmsBaseRtpSession * base_rtp_sess, } media_str = gst_sdp_media_get_media (media); - if (g_strcmp0 (VIDEO_STREAM_NAME, media_str) == 0) { - filter_info = self->video_filter_info; - }else if (g_strcmp0 (AUDIO_STREAM_NAME, media_str) == 0) { - filter_info = self->audio_filter_info; - } + 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); diff --git a/src/gst-plugins/rtpendpoint/kmssrtpconnection.c b/src/gst-plugins/rtpendpoint/kmssrtpconnection.c index e406f8eeb..60b7e4432 100644 --- a/src/gst-plugins/rtpendpoint/kmssrtpconnection.c +++ b/src/gst-plugins/rtpendpoint/kmssrtpconnection.c @@ -797,9 +797,7 @@ kms_sip_srtp_connection_new (guint16 min_port, guint16 max_port, gboolean use_ip priv->rtcp_udpsink = gst_element_factory_make ("multiudpsink", NULL); priv->rtcp_udpsrc = gst_element_factory_make ("udpsrc", NULL); - if (filter_info != NULL) { - kms_sip_srtp_connection_add_probes (conn, filter_info, rtp_probe_id, rtcp_probe_id); - } + 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); diff --git a/src/server/implementation/objects/ComposedObjectImpl.cpp b/src/server/implementation/objects/ComposedObjectImpl.cpp index 6c69cbcd5..d465beb93 100644 --- a/src/server/implementation/objects/ComposedObjectImpl.cpp +++ b/src/server/implementation/objects/ComposedObjectImpl.cpp @@ -106,22 +106,30 @@ ComposedObjectImpl::connectForwardSignals () connElementDisconnectedSrc = std::dynamic_pointer_cast(srcPt)->signalElementDisconnected.connect([ & ] ( ElementDisconnected event) { - //We don't raise internal connection events' - if (event.getSource()==srcPt) - return; - if (event.getSink () == sinkPt) - return; - raiseEvent (event, shared_from_this(), signalElementDisconnected); + 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) { - //We don't raise internal connection events' - if (event.getSource()==srcPt) - return; - if (event.getSink () == sinkPt) - return; - raiseEvent (event, shared_from_this(), signalElementDisconnected); + 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([ & ] ( diff --git a/src/server/interface/elements.SipRtpEndpoint.kmd.json b/src/server/interface/elements.SipRtpEndpoint.kmd.json index e7175696a..24780cdc7 100644 --- a/src/server/interface/elements.SipRtpEndpoint.kmd.json +++ b/src/server/interface/elements.SipRtpEndpoint.kmd.json @@ -53,6 +53,9 @@
  • Then
  • +

    + Another element that many VoIP providers present 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. +

    ", "constructor": { From a77b630527aa55851de75c0387da3dcdc4b471ad Mon Sep 17 00:00:00 2001 From: Saul Pablo Labajo Izquierdo Date: Fri, 10 Jul 2020 22:13:23 +0200 Subject: [PATCH 09/11] Fix fake renegotiations that do not change RTP streams --- .../rtpendpoint/kmsrtpconnection.c | 8 +- .../rtpendpoint/kmsrtpfilterutils.c | 4 +- .../rtpendpoint/kmsrtpfilterutils.h | 2 +- .../rtpendpoint/kmssiprtpendpoint.c | 20 +- .../rtpendpoint/kmssiprtpendpoint.h | 2 +- .../rtpendpoint/kmssiprtpsession.c | 2 +- .../rtpendpoint/kmssipsrtpsession.c | 2 +- .../rtpendpoint/kmssrtpconnection.c | 8 +- .../objects/FacadeRtpEndpointImpl.cpp | 117 ++++++++-- .../objects/FacadeRtpEndpointImpl.hpp | 9 + .../objects/SipRtpEndpointImpl.cpp | 12 +- .../objects/SipRtpEndpointImpl.hpp | 6 +- .../elements.SipRtpEndpoint.kmd.json | 5 +- tests/server/sipRtpEndpoint_agnostic_srtp.cpp | 216 +++++++++++++++++- 14 files changed, 365 insertions(+), 48 deletions(-) diff --git a/src/gst-plugins/rtpendpoint/kmsrtpconnection.c b/src/gst-plugins/rtpendpoint/kmsrtpconnection.c index 6d23f5878..9cbd073cf 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpconnection.c +++ b/src/gst-plugins/rtpendpoint/kmsrtpconnection.c @@ -407,10 +407,10 @@ kms_sip_rtp_connection_retrieve_sockets (KmsRtpConnection *conn, GSocket **rtp, 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); +// 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; diff --git a/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c index c259c3d9a..e4c15ccd4 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c +++ b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.c @@ -451,7 +451,7 @@ filtering_info_add_ssrc_info (GList** target, GList* source) } SipFilterSsrcInfo* -kms_sip_rtp_filter_create_filtering_info (guint32 expected, SipFilterSsrcInfo* previous, guint32 media_session) +kms_sip_rtp_filter_create_filtering_info (guint32 expected, SipFilterSsrcInfo* previous, guint32 media_session, gboolean continue_stream) { SipFilterSsrcInfo* info = g_new (SipFilterSsrcInfo, 1); @@ -471,7 +471,7 @@ kms_sip_rtp_filter_create_filtering_info (guint32 expected, SipFilterSsrcInfo* p 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) { + 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) diff --git a/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.h b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.h index 4b7accde3..408732c89 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.h +++ b/src/gst-plugins/rtpendpoint/kmsrtpfilterutils.h @@ -50,7 +50,7 @@ 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); +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); diff --git a/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c b/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c index 410a04828..56732f9b8 100644 --- a/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c +++ b/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.c @@ -499,11 +499,11 @@ kms_sip_rtp_endpoint_start_transport_send (KmsBaseSdpEndpoint *base_sdp_endpoint } static KmsSipRtpEndpointCloneData* -kms_sip_rtp_endpoint_create_clone_data (KmsSipRtpEndpoint *self, KmsBaseRtpSession *ses, guint32 audio_ssrc, guint32 video_ssrc) +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; - SipFilterSsrcInfo* video_filter_info; + 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; @@ -512,14 +512,14 @@ kms_sip_rtp_endpoint_create_clone_data (KmsSipRtpEndpoint *self, KmsBaseRtpSessi 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); - video_filter_info = kms_sip_rtp_filter_create_filtering_info (video_ssrc, sip_ses->video_filter_info, VIDEO_RTP_SESSION); + 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); - video_filter_info = kms_sip_rtp_filter_create_filtering_info (video_ssrc, sip_ses->video_filter_info, VIDEO_RTP_SESSION); + 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; @@ -549,7 +549,7 @@ kms_sip_rtp_endpoint_free_clone_data (GList *data) } static void -kms_sip_rtp_endpoint_clone_to_new_ep (KmsSipRtpEndpoint *self, KmsSipRtpEndpoint *cloned, const gchar* sdp_str) +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); @@ -573,7 +573,7 @@ kms_sip_rtp_endpoint_clone_to_new_ep (KmsSipRtpEndpoint *self, KmsSipRtpEndpoint 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); + 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); } @@ -732,7 +732,7 @@ kms_sip_rtp_endpoint_class_init (KmsSipRtpEndpointClass * klass) 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, 2, G_TYPE_POINTER, G_TYPE_STRING); + 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)); diff --git a/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.h b/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.h index 64e0cc93c..9d5045bd8 100644 --- a/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.h +++ b/src/gst-plugins/rtpendpoint/kmssiprtpendpoint.h @@ -57,7 +57,7 @@ struct _KmsSipRtpEndpointClass /* signals */ - void (*clone_to_new_ep) (KmsSipRtpEndpoint *obj, KmsSipRtpEndpoint *cloned, const gchar* sdp); + void (*clone_to_new_ep) (KmsSipRtpEndpoint *obj, KmsSipRtpEndpoint *cloned, const gchar* sdp, gboolean, gboolean); }; GType kms_sip_rtp_endpoint_get_type (void); diff --git a/src/gst-plugins/rtpendpoint/kmssiprtpsession.c b/src/gst-plugins/rtpendpoint/kmssiprtpsession.c index b897f31d1..5f55a5103 100644 --- a/src/gst-plugins/rtpendpoint/kmssiprtpsession.c +++ b/src/gst-plugins/rtpendpoint/kmssiprtpsession.c @@ -139,7 +139,7 @@ km_sip_rtp_session_setup_filter_info (KmsSipRtpSession *self, const gchar *media } if (filter_info == NULL) { - filter_info = kms_sip_rtp_filter_create_filtering_info (0, NULL, media_type); + 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) { diff --git a/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c b/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c index 0426aad00..735c3830d 100644 --- a/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c +++ b/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c @@ -142,7 +142,7 @@ km_sip_rtp_session_setup_filter_info (KmsSipSrtpSession *self, const gchar *medi } if (filter_info == NULL) { - filter_info = kms_sip_rtp_filter_create_filtering_info (0, NULL, media_type); + 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) { diff --git a/src/gst-plugins/rtpendpoint/kmssrtpconnection.c b/src/gst-plugins/rtpendpoint/kmssrtpconnection.c index 60b7e4432..d117379ca 100644 --- a/src/gst-plugins/rtpendpoint/kmssrtpconnection.c +++ b/src/gst-plugins/rtpendpoint/kmssrtpconnection.c @@ -630,10 +630,10 @@ kms_sip_srtp_connection_retrieve_sockets (KmsSrtpConnection *conn, GSocket **rtp 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); +// 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; diff --git a/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp index 70cd61413..9d2d06507 100644 --- a/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp @@ -385,8 +385,15 @@ std::string FacadeRtpEndpointImpl::processAnswer (const std::string &answer) } std::string unusedOffer; std::shared_ptr oldEndpoint; + bool continue_audio_stream, continue_video_stream; - newEndpoint = rtp_ep->getCleanEndpoint (config, getMediaPipeline (), cryptoToUse, useIpv6Cache, modifiableAnswer); + 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) { @@ -431,6 +438,100 @@ 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, @@ -549,20 +650,6 @@ FacadeRtpEndpointImpl::addAgnosticMedia (GstSDPMedia *media, GstSDPMessage *sdpO gst_sdp_message_add_media (sdpOffer, newMedia); } -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; -} - - static bool isCryptoSDES (std::shared_ptr sdes) { diff --git a/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp b/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp index 47645331f..4b35dae30 100644 --- a/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp @@ -185,6 +185,15 @@ class FacadeRtpEndpointImpl : public ComposedObjectImpl, public virtual SipRtpEn 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 (); diff --git a/src/server/implementation/objects/SipRtpEndpointImpl.cpp b/src/server/implementation/objects/SipRtpEndpointImpl.cpp index 75872d08b..159bbe5c6 100644 --- a/src/server/implementation/objects/SipRtpEndpointImpl.cpp +++ b/src/server/implementation/objects/SipRtpEndpointImpl.cpp @@ -196,16 +196,22 @@ SipRtpEndpointImpl::StaticConstructor::StaticConstructor() 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) + 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); + 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) +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()); diff --git a/src/server/implementation/objects/SipRtpEndpointImpl.hpp b/src/server/implementation/objects/SipRtpEndpointImpl.hpp index 3f45dbb81..0c2b3a5aa 100644 --- a/src/server/implementation/objects/SipRtpEndpointImpl.hpp +++ b/src/server/implementation/objects/SipRtpEndpointImpl.hpp @@ -51,7 +51,9 @@ class SipRtpEndpointImpl : public BaseRtpEndpointImpl, public virtual SipRtpEndp std::shared_ptr getCleanEndpoint (const boost::property_tree::ptree &conf, std::shared_ptr mediaPipeline, std::shared_ptr crypto, bool useIpv6, - const std::string &sdp); + const std::string &sdp, + bool continue_audio_stream, + bool continue_video_stream); void setAudioSsrc (guint32 ssrc); @@ -77,7 +79,7 @@ class SipRtpEndpointImpl : public BaseRtpEndpointImpl, public virtual SipRtpEndp gulong handlerOnKeySoftLimit = 0; void onKeySoftLimit (gchar *media); - std::shared_ptr cloneToNewEndpoint (std::shared_ptr newEp, const std::string &sdp); + std::shared_ptr cloneToNewEndpoint (std::shared_ptr newEp, const std::string &sdp, bool continue_audio_stream, bool continue_video_stream); class StaticConstructor { diff --git a/src/server/interface/elements.SipRtpEndpoint.kmd.json b/src/server/interface/elements.SipRtpEndpoint.kmd.json index 24780cdc7..b3b55861c 100644 --- a/src/server/interface/elements.SipRtpEndpoint.kmd.json +++ b/src/server/interface/elements.SipRtpEndpoint.kmd.json @@ -54,7 +54,10 @@

    - Another element that many VoIP providers present 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. + 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": diff --git a/tests/server/sipRtpEndpoint_agnostic_srtp.cpp b/tests/server/sipRtpEndpoint_agnostic_srtp.cpp index ad4661f93..959d085ce 100644 --- a/tests/server/sipRtpEndpoint_agnostic_srtp.cpp +++ b/tests/server/sipRtpEndpoint_agnostic_srtp.cpp @@ -55,8 +55,8 @@ 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"); +// 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); @@ -123,6 +123,19 @@ createRtpEndpoint (bool useCrypto, bool cryptoAgnostic) 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) { @@ -407,6 +420,202 @@ reconnection_generate_offer_state_changes_impl (bool cryptoOffer, bool agnosticO 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 () { @@ -1075,6 +1284,7 @@ init_unit_test_suite ( int , char *[] ) 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; } From ceb10ba771ee306d3cec90acb106e6e81b81c2ae Mon Sep 17 00:00:00 2001 From: Saul Pablo Labajo Izquierdo Date: Fri, 14 Aug 2020 14:06:15 +0200 Subject: [PATCH 10/11] Fixed object leak --- src/gst-plugins/rtpendpoint/kmssiprtpsession.c | 4 +++- src/gst-plugins/rtpendpoint/kmssipsrtpsession.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gst-plugins/rtpendpoint/kmssiprtpsession.c b/src/gst-plugins/rtpendpoint/kmssiprtpsession.c index 5f55a5103..53aa237eb 100644 --- a/src/gst-plugins/rtpendpoint/kmssiprtpsession.c +++ b/src/gst-plugins/rtpendpoint/kmssiprtpsession.c @@ -230,7 +230,6 @@ kms_sip_rtp_session_free_filter_info (gpointer data) GST_DEBUG ("Releasing RTP/RTCP filtering probes"); kms_sip_rtp_connection_release_probes (info->conn, info->rtp_probe, info->rtcp_probe); - g_object_unref (info->conn); g_free (data); } @@ -257,6 +256,9 @@ kms_sip_rtp_session_finalize (GObject *object) } GST_DEBUG ("Finalized RTP Session %p", object); + + /* chain up */ + G_OBJECT_CLASS (parent_class)->finalize (object); } static void diff --git a/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c b/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c index 735c3830d..e832fd0bc 100644 --- a/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c +++ b/src/gst-plugins/rtpendpoint/kmssipsrtpsession.c @@ -230,7 +230,6 @@ kms_sip_rtp_session_free_filter_info (gpointer data) GST_DEBUG ("Releasing SRTP/SRTCP filtering probes"); kms_sip_srtp_connection_release_probes (info->conn, info->rtp_probe, info->rtcp_probe); - g_object_unref (info->conn); g_free (data); } @@ -257,6 +256,9 @@ kms_sip_srtp_session_finalize (GObject *object) } GST_DEBUG ("Finalized SRTP Session %p", object); + + /* chain up */ + G_OBJECT_CLASS (parent_class)->finalize (object); } From 919e1b1237ab9b671425f1c6190e7ef7d99416e4 Mon Sep 17 00:00:00 2001 From: Daniel Pocock Date: Sat, 23 Oct 2021 21:31:09 +0200 Subject: [PATCH 11/11] SipRtpEndpoint: update for latest master, adding new generateOffer(options) --- .../objects/FacadeRtpEndpointImpl.cpp | 1896 +++++++++-------- .../objects/FacadeRtpEndpointImpl.hpp | 34 +- 2 files changed, 1062 insertions(+), 868 deletions(-) diff --git a/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp index 9d2d06507..4e6e10b2c 100644 --- a/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.cpp @@ -57,77 +57,86 @@ 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) + 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) + std::dynamic_pointer_cast (mediaPipeline) ), + cryptoCache (crypto), useIpv6Cache (useIpv6) { this->cryptoAgnostic = cryptoAgnostic; - rtp_ep = std::shared_ptr(new SipRtpEndpointImpl (config, mediaPipeline, crypto, useIpv6)); + rtp_ep = std::shared_ptr (new SipRtpEndpointImpl (config, + mediaPipeline, crypto, useIpv6) ); audioCapsSet = NULL; videoCapsSet = NULL; rembParamsSet = NULL; @@ -141,7 +150,7 @@ FacadeRtpEndpointImpl::FacadeRtpEndpointImpl (const boost::property_tree::ptree FacadeRtpEndpointImpl::~FacadeRtpEndpointImpl() { - linkMediaElement(NULL, NULL); + linkMediaElement (NULL, NULL); } void @@ -150,12 +159,13 @@ FacadeRtpEndpointImpl::postConstructor () ComposedObjectImpl::postConstructor (); rtp_ep->postConstructor(); - linkMediaElement(rtp_ep, rtp_ep); + linkMediaElement (rtp_ep, rtp_ep); } -FacadeRtpEndpointImpl::StaticConstructor FacadeRtpEndpointImpl::staticConstructor; +FacadeRtpEndpointImpl::StaticConstructor +FacadeRtpEndpointImpl::staticConstructor; FacadeRtpEndpointImpl::StaticConstructor::StaticConstructor() { @@ -166,44 +176,55 @@ FacadeRtpEndpointImpl::StaticConstructor::StaticConstructor() // 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) +bool FacadeRtpEndpointImpl::connect (const std::string &eventType, + std::shared_ptr handler) { - std::weak_ptr wh = 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); + 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) + const std::string &methodName, const Json::Value ¶ms, + Json::Value &response) { - this->rtp_ep->invoke(obj, methodName, params, response); + this->rtp_ep->invoke (obj, methodName, params, response); } @@ -213,795 +234,928 @@ void FacadeRtpEndpointImpl::invoke (std::shared_ptr obj, std::string FacadeRtpEndpointImpl::generateOffer () { - std::string offer; - - try { - offer = this->rtp_ep->generateOffer(); - 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(); - 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 + 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; + 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; - // 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); - sdpMedia = gst_sdp_message_get_media (sdp, idx); - if (sdpMedia != NULL) { - idx++; - mediaList.push_back((GstSDPMedia*)sdpMedia); - } - } + if (sdpMedia != NULL) { + idx++; + mediaList.push_back ( (GstSDPMedia *) sdpMedia); + } + } - return mediaList; + return mediaList; } -static GstSDPMessage* -parseSDP (const std::string& sdp) { - GstSDPMessage *sdpObject = NULL; +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; + } - 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; + 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; + 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 ()); + 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 != NULL) { - if (origSdes->isSetCrypto()) - sdes->setCrypto(origSdes->getCrypto()); - if (origSdes->isSetKey()) - sdes->setKey(origSdes->getKey()); - if (origSdes->isSetKeyBase64()) - sdes->setKeyBase64(origSdes->getKeyBase64()); - } - return sdes; + 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 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(); + return this->rtp_ep->getLocalSessionDescriptor(); } std::string FacadeRtpEndpointImpl::getRemoteSessionDescriptor () { - return this->rtp_ep->getRemoteSessionDescriptor(); + return this->rtp_ep->getRemoteSessionDescriptor(); } bool FacadeRtpEndpointImpl::isCryptoAgnostic () { - return this->cryptoAgnostic; + 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; +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; + const GstSDPConnection *conn1, *conn2; - conn1 = gst_sdp_message_get_connection (sdp1); - conn2 = gst_sdp_message_get_connection (sdp2); + 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); + 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; + const gchar *inactive; - inactive = gst_sdp_media_get_attribute_val (media, "inactive"); + inactive = gst_sdp_media_get_attribute_val (media, "inactive"); - if (inactive == NULL) - return (gst_sdp_media_get_port (media) != 0); + if (inactive == NULL) { + return (gst_sdp_media_get_port (media) != 0); + } - return false; + 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); +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; + } - if (!sameConnection (sdpAnswer, oldSdpAnswer)) { - // Not same connection no compatible medias possible - return; - } + sdpAnswer = parseSDP (answer); + oldSdpAnswer = parseSDP (oldAnswer); - mediaList = getMediasFromSdp (sdpAnswer); + if (!sameConnection (sdpAnswer, oldSdpAnswer) ) { + // Not same connection no compatible medias possible + return; + } - 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; - } - } - } - } + 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); + 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); - } + 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) +FacadeRtpEndpointImpl::replaceAllSsrcAttrs (GstSDPMedia *media, + std::list sscrIdxs, guint32 &oldSsrc, guint32 &newSsrc) { - // set the ssrc attribute - gchar newSsrcStr [11]; + // set the ssrc attribute + gchar newSsrcStr [11]; + + newSsrc = g_random_int (); + g_snprintf (newSsrcStr, 11, "%u", newSsrc); - 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); - } + for (std::list::iterator it = sscrIdxs.begin(); it != sscrIdxs.end(); + ++it) { + replaceSsrc (media, *it, newSsrcStr, oldSsrc); + } } void -FacadeRtpEndpointImpl::removeCryptoAttrs (GstSDPMedia *media, std::list cryptoIdx) +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); - } + // 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); +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 == NULL) { + return false; + } - if (sdes->isSetCrypto()) - return true; + if (sdes->isSetCrypto() ) { + return true; + } - return false; + return false; } bool -FacadeRtpEndpointImpl::generateCryptoAgnosticOffer (std::string& offer) +FacadeRtpEndpointImpl::generateCryptoAgnosticOffer (std::string &offer) { - std::vector mediaList; - GstSDPMessage *sdpOffer; - gchar* result; + 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; - } + // 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); + sdpOffer = parseSDP (offer); - // 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); - } + if (sdpOffer == NULL) { + return false; + } + + mediaList = getMediasFromSdp (sdpOffer); - result = gst_sdp_message_as_text (sdpOffer); - offer = result; - g_free (result); - gst_sdp_message_free (sdpOffer); - return true; + // 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) +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; + 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) +get_crypto_key_from_str (gchar *str) { - gchar **attrs; - std::string result; + gchar **attrs; + std::string result; - attrs = g_strsplit (str, "|", 0); + 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); + 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 ()); + std::shared_ptr sdes (new SDES () ); - sdes->setCrypto (suite); - sdes->setKeyBase64 (key.c_str()); - return 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; +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; + 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--; - } - } +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) +isCryptoCompatible (std::shared_ptr original, + std::shared_ptr answer) { - bool result = true; + 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; + 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) +getActiveMedias (std::vector mediaList, + std::set &usableMedia) { - guint idx = 0; + guint idx = 0; + + for (std::vector::iterator it = mediaList.begin(); + it != mediaList.end(); ) { + GstSDPMedia *media = (GstSDPMedia *) *it; + + if (isMediaActive (media) ) { + usableMedia.insert (idx); + } - for (std::vector::iterator it=mediaList.begin(); it != mediaList.end(); ){ - GstSDPMedia *media = (GstSDPMedia*) *it; - if (isMediaActive (media)) { - usableMedia.insert(idx); - } - ++it; - 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); +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); + } - 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; - } + ++it; + } - // And remove cryptos found from noncrypto list - for (std::set::iterator it=cryptoMedias.begin(); it != cryptoMedias.end (); ++it) { - nonCryptoMedias.erase (*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; +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) +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; + 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); - sdpOffer = parseSDP (offer); - if (sdpOffer == NULL) - return false; + if (sdpOffer == NULL) { + return false; + } + + mediaList = getMediasFromSdp (sdpOffer); - mediaList = getMediasFromSdp (sdpOffer); + getActiveMedias (mediaList, nonCryptoMedias); - 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; - 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++; + } + } - while (idx < mediaList.size ()) { - nonCryptoMedias.insert(idx); - idx++; - } - } - getCryptoMedias (mediaList, nonCryptoMedias, cryptoMedias); + getCryptoMedias (mediaList, nonCryptoMedias, cryptoMedias); - isCrypto = getCryptoInfoFromMedia (mediaList, cryptoMedias, sdes); + isCrypto = getCryptoInfoFromMedia (mediaList, cryptoMedias, sdes); - makeUpSdp (isCrypto, sdpOffer, cryptoMedias, nonCryptoMedias); + makeUpSdp (isCrypto, sdpOffer, cryptoMedias, nonCryptoMedias); - crypto = sdes; - modifiedSdpStr = gst_sdp_message_as_text (sdpOffer); - offer = modifiedSdpStr; + crypto = sdes; + modifiedSdpStr = gst_sdp_message_as_text (sdpOffer); + offer = modifiedSdpStr; - gst_sdp_message_free (sdpOffer); - g_free (modifiedSdpStr); + gst_sdp_message_free (sdpOffer); + g_free (modifiedSdpStr); - crypto = sdes; + crypto = sdes; - if (isCryptoCompatible(cryptoCache, sdes)) - return false; + if (isCryptoCompatible (cryptoCache, sdes) ) { + return false; + } - return true; + return true; } static std::shared_ptr -fitMediaAnswer (std::string& answer, bool isLocalCrypto) +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; + 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; + sdpAnswer = parseSDP (answer); - mediaList = getMediasFromSdp (sdpAnswer); + if (sdpAnswer == NULL) { + return sdes; + } - getActiveMedias (mediaList, nonCryptoMedias); + mediaList = getMediasFromSdp (sdpAnswer); - 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; + getActiveMedias (mediaList, nonCryptoMedias); - while (idx < mediaList.size ()) { - nonCryptoMedias.insert (idx); - idx++; - } - } + 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; - getCryptoMedias (mediaList, nonCryptoMedias, cryptoMedias); - isCrypto = getCryptoInfoFromMedia (mediaList, cryptoMedias, sdes); + 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); + makeUpSdp (isCrypto, sdpAnswer, cryptoMedias, nonCryptoMedias); + newAnswer = gst_sdp_message_as_text (sdpAnswer); + answer = newAnswer; + g_free ( (gpointer) newAnswer); - gst_sdp_message_free (sdpAnswer); + gst_sdp_message_free (sdpAnswer); - return sdes; + return sdes; } bool -FacadeRtpEndpointImpl::checkCryptoAnswer (std::string& answer, std::shared_ptr& crypto) +FacadeRtpEndpointImpl::checkCryptoAnswer (std::string &answer, + std::shared_ptr &crypto) { - std::shared_ptr sdes; - bool isLocalCrypto = crypto->isSetCrypto() && (crypto->getCrypto() != NULL); + std::shared_ptr sdes; + bool isLocalCrypto = crypto->isSetCrypto() && (crypto->getCrypto() != NULL); + + sdes = fitMediaAnswer (answer, isLocalCrypto); - 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; + // 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; } @@ -1009,151 +1163,174 @@ FacadeRtpEndpointImpl::checkCryptoAnswer (std::string& answer, std::shared_ptr(rtp_ep)->signalMediaStateChanged.connect([ & ] ( - MediaStateChanged event) { - raiseEvent (event, shared_from_this(), signalMediaStateChanged); - }); + 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); - }); + 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); - }); + 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); - }); + 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); - }); + connOnKeySoftLimit = rtp_ep->signalOnKeySoftLimit.connect ([ & ] ( + OnKeySoftLimit event) { + raiseEvent (event, shared_from_this(), signalOnKeySoftLimit); + }); } -void FacadeRtpEndpointImpl::setProperties (std::shared_ptr from) +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 ()); - } + 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) +FacadeRtpEndpointImpl::renewInternalEndpoint ( + std::shared_ptr newEndpoint) { - std::shared_ptr tmp = rtp_ep; + std::shared_ptr tmp = rtp_ep; - if (rtp_ep != NULL) { - disconnectForwardSignals (); - } + if (rtp_ep != NULL) { + disconnectForwardSignals (); + } - rtp_ep = newEndpoint; - linkMediaElement(newEndpoint, newEndpoint); - setProperties (tmp); + rtp_ep = newEndpoint; + linkMediaElement (newEndpoint, newEndpoint); + setProperties (tmp); - if (rtp_ep != NULL) { - connectForwardSignals (); - } + if (rtp_ep != NULL) { + connectForwardSignals (); + } - return tmp; + return tmp; } /*----------------- MEthods from BaseRtpEndpoint ---------------*/ int FacadeRtpEndpointImpl::getMinVideoRecvBandwidth () { - return this->rtp_ep->getMinVideoRecvBandwidth(); + return this->rtp_ep->getMinVideoRecvBandwidth(); } void FacadeRtpEndpointImpl::setMinVideoRecvBandwidth (int minVideoRecvBandwidth) { - this->rtp_ep->setMinVideoRecvBandwidth(minVideoRecvBandwidth); + this->rtp_ep->setMinVideoRecvBandwidth (minVideoRecvBandwidth); } -int FacadeRtpEndpointImpl::getMinVideoSendBandwidth () { - return this->rtp_ep->getMinVideoSendBandwidth (); +int FacadeRtpEndpointImpl::getMinVideoSendBandwidth () +{ + return this->rtp_ep->getMinVideoSendBandwidth (); } void FacadeRtpEndpointImpl::setMinVideoSendBandwidth (int minVideoSendBandwidth) { - this->rtp_ep->setMinVideoSendBandwidth (minVideoSendBandwidth); + this->rtp_ep->setMinVideoSendBandwidth (minVideoSendBandwidth); } int FacadeRtpEndpointImpl::getMaxVideoSendBandwidth () { - return this->rtp_ep->getMaxVideoSendBandwidth(); + return this->rtp_ep->getMaxVideoSendBandwidth(); } void FacadeRtpEndpointImpl::setMaxVideoSendBandwidth (int maxVideoSendBandwidth) { - this->rtp_ep->setMaxVideoSendBandwidth(maxVideoSendBandwidth); + this->rtp_ep->setMaxVideoSendBandwidth (maxVideoSendBandwidth); } std::shared_ptr FacadeRtpEndpointImpl::getMediaState () { - return this->rtp_ep->getMediaState(); + return this->rtp_ep->getMediaState(); } std::shared_ptr FacadeRtpEndpointImpl::getConnectionState () { - return this->rtp_ep->getConnectionState(); + return this->rtp_ep->getConnectionState(); } std::shared_ptr FacadeRtpEndpointImpl::getRembParams () { - return this->rtp_ep->getRembParams(); + return this->rtp_ep->getRembParams(); } -void FacadeRtpEndpointImpl::setRembParams (std::shared_ptr rembParams) +void FacadeRtpEndpointImpl::setRembParams (std::shared_ptr + rembParams) { - this->rtp_ep->setRembParams (rembParams); - rembParamsSet = rembParams; + this->rtp_ep->setRembParams (rembParams); + rembParamsSet = rembParams; } -sigc::signal FacadeRtpEndpointImpl::getSignalMediaStateChanged () +sigc::signal +FacadeRtpEndpointImpl::getSignalMediaStateChanged () { - return this->rtp_ep->signalMediaStateChanged; + return this->rtp_ep->signalMediaStateChanged; } -sigc::signal FacadeRtpEndpointImpl::getSignalConnectionStateChanged () +sigc::signal +FacadeRtpEndpointImpl::getSignalConnectionStateChanged () { - return this->rtp_ep->signalConnectionStateChanged; + return this->rtp_ep->signalConnectionStateChanged; } int FacadeRtpEndpointImpl::getMtu () { - return this->rtp_ep->getMtu (); + return this->rtp_ep->getMtu (); } void FacadeRtpEndpointImpl::setMtu (int mtu) { - this->rtp_ep->setMtu (mtu); + this->rtp_ep->setMtu (mtu); } @@ -1162,171 +1339,182 @@ void FacadeRtpEndpointImpl::setMtu (int mtu) /*---------------- Overloaded methods from SDP Endpoint ---------------*/ int FacadeRtpEndpointImpl::getMaxVideoRecvBandwidth () { - return this->rtp_ep->getMaxVideoRecvBandwidth(); + return this->rtp_ep->getMaxVideoRecvBandwidth(); } void FacadeRtpEndpointImpl::setMaxVideoRecvBandwidth (int maxVideoRecvBandwidth) { - this->rtp_ep->setMaxVideoRecvBandwidth(maxVideoRecvBandwidth); + this->rtp_ep->setMaxVideoRecvBandwidth (maxVideoRecvBandwidth); } int FacadeRtpEndpointImpl::getMaxAudioRecvBandwidth () { - return this->rtp_ep->getMaxAudioRecvBandwidth (); + return this->rtp_ep->getMaxAudioRecvBandwidth (); } void FacadeRtpEndpointImpl::setMaxAudioRecvBandwidth (int maxAudioRecvBandwidth) { - this->rtp_ep->setMaxAudioRecvBandwidth(maxAudioRecvBandwidth); + this->rtp_ep->setMaxAudioRecvBandwidth (maxAudioRecvBandwidth); } /*----------------------- Overloaded methods from Media Element --------------*/ -std::map > FacadeRtpEndpointImpl::getStats () +std::map > + FacadeRtpEndpointImpl::getStats () { - return this->rtp_ep->getStats(); + return this->rtp_ep->getStats(); } std::map > FacadeRtpEndpointImpl::getStats ( std::shared_ptr mediaType) { - return this->rtp_ep->getStats(mediaType); + return this->rtp_ep->getStats (mediaType); } std::vector> -FacadeRtpEndpointImpl::getSourceConnections () + FacadeRtpEndpointImpl::getSourceConnections () { - // TODO Verify this behaviour - //return this->rtp_ep->getSourceConnections(); - return this->srcPt->getSourceConnections(); + // TODO Verify this behaviour + //return this->rtp_ep->getSourceConnections(); + return this->srcPt->getSourceConnections(); } std::vector> -FacadeRtpEndpointImpl::getSourceConnections ( + FacadeRtpEndpointImpl::getSourceConnections ( std::shared_ptr mediaType) { - // TODO: Verifiy this behaviour - //return this->rtp_ep->getSourceConnections(mediaType); - return this->srcPt->getSourceConnections(mediaType); + // TODO: Verifiy this behaviour + //return this->rtp_ep->getSourceConnections(mediaType); + return this->srcPt->getSourceConnections (mediaType); } std::vector> -FacadeRtpEndpointImpl::getSourceConnections ( + 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); + // 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(); + FacadeRtpEndpointImpl::getSinkConnections () +{ + // TODO Verify this behaviour + //return this->rtp_ep->getSinkConnections(); + return this->sinkPt->getSinkConnections(); } -std::vector> FacadeRtpEndpointImpl::getSinkConnections ( +std::vector> + FacadeRtpEndpointImpl::getSinkConnections ( std::shared_ptr mediaType) { - // TODO: verify this behviour - //return this->rtp_ep->getSinkConnections(mediaType); - return this->sinkPt->getSinkConnections(mediaType); + // TODO: verify this behviour + //return this->rtp_ep->getSinkConnections(mediaType); + return this->sinkPt->getSinkConnections (mediaType); } -std::vector> FacadeRtpEndpointImpl::getSinkConnections ( +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); + // 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; + this->rtp_ep->setAudioFormat (caps); + audioCapsSet = caps; } void FacadeRtpEndpointImpl::setVideoFormat (std::shared_ptr caps) { - this->rtp_ep->setVideoFormat(caps); - videoCapsSet = caps; + this->rtp_ep->setVideoFormat (caps); + videoCapsSet = caps; } void FacadeRtpEndpointImpl::release () { - this->linkMediaElement(NULL, NULL); - ComposedObjectImpl::release (); + this->linkMediaElement (NULL, NULL); + ComposedObjectImpl::release (); } std::string FacadeRtpEndpointImpl::getGstreamerDot () { - return this->rtp_ep->getGstreamerDot(); + return this->rtp_ep->getGstreamerDot(); } -std::string FacadeRtpEndpointImpl::getGstreamerDot (std::shared_ptr - details) +std::string FacadeRtpEndpointImpl::getGstreamerDot ( + std::shared_ptr + details) { - return this->rtp_ep->getGstreamerDot(details); + return this->rtp_ep->getGstreamerDot (details); } void FacadeRtpEndpointImpl::setOutputBitrate (int bitrate) { - this->rtp_ep->setOutputBitrate(bitrate); + this->rtp_ep->setOutputBitrate (bitrate); } -bool FacadeRtpEndpointImpl::isMediaFlowingIn (std::shared_ptr mediaType) +bool FacadeRtpEndpointImpl::isMediaFlowingIn (std::shared_ptr + mediaType) { - return this->rtp_ep->isMediaFlowingIn(mediaType); + return this->rtp_ep->isMediaFlowingIn (mediaType); } -bool FacadeRtpEndpointImpl::isMediaFlowingIn (std::shared_ptr mediaType, - const std::string &sinkMediaDescription) +bool FacadeRtpEndpointImpl::isMediaFlowingIn (std::shared_ptr + mediaType, + const std::string &sinkMediaDescription) { - return this->rtp_ep->isMediaFlowingIn(mediaType, sinkMediaDescription); + return this->rtp_ep->isMediaFlowingIn (mediaType, sinkMediaDescription); } -bool FacadeRtpEndpointImpl::isMediaFlowingOut (std::shared_ptr mediaType) +bool FacadeRtpEndpointImpl::isMediaFlowingOut (std::shared_ptr + mediaType) { - return this->rtp_ep->isMediaFlowingOut(mediaType); + return this->rtp_ep->isMediaFlowingOut (mediaType); } -bool FacadeRtpEndpointImpl::isMediaFlowingOut (std::shared_ptr mediaType, - const std::string &sourceMediaDescription) +bool FacadeRtpEndpointImpl::isMediaFlowingOut (std::shared_ptr + mediaType, + const std::string &sourceMediaDescription) { - return this->rtp_ep->isMediaFlowingOut(mediaType, sourceMediaDescription); + return this->rtp_ep->isMediaFlowingOut (mediaType, sourceMediaDescription); } -bool FacadeRtpEndpointImpl::isMediaTranscoding (std::shared_ptr mediaType) +bool FacadeRtpEndpointImpl::isMediaTranscoding (std::shared_ptr + mediaType) { - return this->rtp_ep->isMediaTranscoding(mediaType); + return this->rtp_ep->isMediaTranscoding (mediaType); } -bool FacadeRtpEndpointImpl::isMediaTranscoding (std::shared_ptr mediaType, - const std::string &binName) +bool FacadeRtpEndpointImpl::isMediaTranscoding (std::shared_ptr + mediaType, + const std::string &binName) { - return this->rtp_ep->isMediaTranscoding(mediaType, binName); + return this->rtp_ep->isMediaTranscoding (mediaType, binName); } int FacadeRtpEndpointImpl::getMinOuputBitrate () { - return this->rtp_ep->getMinOuputBitrate(); + return this->rtp_ep->getMinOuputBitrate(); } void FacadeRtpEndpointImpl::setMinOuputBitrate (int minOuputBitrate) { - this->rtp_ep->setMinOuputBitrate(minOuputBitrate); + this->rtp_ep->setMinOuputBitrate (minOuputBitrate); } int FacadeRtpEndpointImpl::getMinOutputBitrate () { - return this->rtp_ep->getMinOutputBitrate(); + return this->rtp_ep->getMinOutputBitrate(); } void FacadeRtpEndpointImpl::setMinOutputBitrate (int minOutputBitrate) { - this->rtp_ep->setMinOutputBitrate(minOutputBitrate); + this->rtp_ep->setMinOutputBitrate (minOutputBitrate); } int FacadeRtpEndpointImpl::getMaxOuputBitrate () { - return this->rtp_ep->getMaxOuputBitrate(); + return this->rtp_ep->getMaxOuputBitrate(); } void FacadeRtpEndpointImpl::setMaxOuputBitrate (int maxOuputBitrate) { - this->rtp_ep->setMaxOuputBitrate(maxOuputBitrate); + this->rtp_ep->setMaxOuputBitrate (maxOuputBitrate); } int FacadeRtpEndpointImpl::getMaxOutputBitrate () { - return this->rtp_ep->getMaxOutputBitrate(); + return this->rtp_ep->getMaxOutputBitrate(); } void FacadeRtpEndpointImpl::setMaxOutputBitrate (int maxOutputBitrate) { - this->rtp_ep->setMaxOutputBitrate(maxOutputBitrate); + this->rtp_ep->setMaxOutputBitrate (maxOutputBitrate); } diff --git a/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp b/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp index 4b35dae30..024c31f70 100644 --- a/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp +++ b/src/server/implementation/objects/FacadeRtpEndpointImpl.hpp @@ -41,16 +41,17 @@ class CryptoSuite; void Serialize (std::shared_ptr &object, JsonSerializer &serializer); -class FacadeRtpEndpointImpl : public ComposedObjectImpl, public virtual SipRtpEndpoint +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); + std::shared_ptr mediaPipeline, + std::shared_ptr crypto, + bool cryptoAgnostic, + bool useIpv6); virtual ~FacadeRtpEndpointImpl (); @@ -72,7 +73,7 @@ class FacadeRtpEndpointImpl : public ComposedObjectImpl, public virtual SipRtpEn std::shared_ptr getConnectionState () override; std::shared_ptr getRembParams () override; - void setRembParams (std::shared_ptr rembParams)override; + void setRembParams (std::shared_ptr rembParams) override; sigc::signal getSignalMediaStateChanged (); sigc::signal getSignalConnectionStateChanged (); @@ -87,6 +88,7 @@ class FacadeRtpEndpointImpl : public ComposedObjectImpl, public virtual SipRtpEn 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; @@ -99,7 +101,8 @@ class FacadeRtpEndpointImpl : public ComposedObjectImpl, public virtual SipRtpEn std::shared_ptr mediaType) override; - std::vector> getSourceConnections () override; + std::vector> getSourceConnections () + override; std::vector> getSourceConnections ( std::shared_ptr mediaType) override; @@ -189,28 +192,31 @@ class FacadeRtpEndpointImpl : public ComposedObjectImpl, public virtual SipRtpEn sameConnection (GstSDPMessage *sdp1, GstSDPMessage *sdp2); bool - findCompatibleMedia (GstSDPMedia* media, GstSDPMessage *oldAnswer); + findCompatibleMedia (GstSDPMedia *media, GstSDPMessage *oldAnswer); void - answerHasCompatibleMedia (const std::string& answer, bool& audio_compatible, bool& video_compatible); + answerHasCompatibleMedia (const std::string &answer, bool &audio_compatible, + bool &video_compatible); bool isCryptoAgnostic (); bool - generateCryptoAgnosticOffer (std::string& offer); + generateCryptoAgnosticOffer (std::string &offer); bool - checkCryptoOffer (std::string& offer, std::shared_ptr& crypto); + checkCryptoOffer (std::string &offer, std::shared_ptr &crypto); bool - checkCryptoAnswer (std::string& answer, std::shared_ptr& crypto); + checkCryptoAnswer (std::string &answer, std::shared_ptr &crypto); void - replaceSsrc (GstSDPMedia *media, guint idx, gchar *newSsrcStr, guint32 &oldSsrc); + replaceSsrc (GstSDPMedia *media, guint idx, gchar *newSsrcStr, + guint32 &oldSsrc); void - replaceAllSsrcAttrs (GstSDPMedia *media, std::list sscrIdxs, guint32 &oldSsrc, guint32 &newSsrc); + replaceAllSsrcAttrs (GstSDPMedia *media, std::list sscrIdxs, + guint32 &oldSsrc, guint32 &newSsrc); void removeCryptoAttrs (GstSDPMedia *media, std::list cryptoIdx);