From 5aabb54f68f495bfd51c083bd89d4fdaa1a9ba6c Mon Sep 17 00:00:00 2001
From: Dragos Oancea <dragos@signalwire.com>
Date: Fri, 4 Sep 2020 08:57:28 +0000
Subject: [PATCH] [core] eavesdrop: init L16 codec at right ptime in certain
 conditions.

[core] eavesdrop: avoid eavesdropping on itself and return error.

[core] eavesdrop: adjust buffer operations for ptime mismatch and for when ptimes are the same.

[core] eavesdrop: add buffering based on LCM (Least Common Multiple) when ptime mismatch,
and have audio write thread enabled when ptime eavesdropee < ptime eavesdropper.

[unit-tests] add unit-tests for eavesdrop.
---
 src/switch_ivr_async.c                     | 113 +++++-
 tests/unit/Makefile.am                     |   5 +
 tests/unit/conf_eavesdrop/freeswitch.xml   | 238 +++++++++++++
 tests/unit/conf_eavesdrop/gw/eavestest.xml |  14 +
 tests/unit/switch_eavesdrop.c              | 392 +++++++++++++++++++++
 5 files changed, 743 insertions(+), 19 deletions(-)
 create mode 100644 tests/unit/conf_eavesdrop/freeswitch.xml
 create mode 100644 tests/unit/conf_eavesdrop/gw/eavestest.xml
 create mode 100644 tests/unit/switch_eavesdrop.c

diff --git a/src/switch_ivr_async.c b/src/switch_ivr_async.c
index a6972e847c..07f28a36e8 100644
--- a/src/switch_ivr_async.c
+++ b/src/switch_ivr_async.c
@@ -2184,6 +2184,25 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_eavesdrop_update_display(switch_core_
 	return status;
 }
 
+/*Greatest Common Divisor*/
+static uint32_t switch_gcd(uint32_t x, uint32_t y)
+{
+	if (y == 0) {
+		return x;
+	} 
+
+	return switch_gcd(y, x % y);
+}
+
+/*Least Common Multiple*/
+static uint32_t switch_lcm(uint32_t x, uint32_t y) 
+{
+	uint32_t gcd = switch_gcd(x, y); 
+
+	if (gcd) return (x * y) / gcd;
+
+	return 0;
+}
 
 SWITCH_DECLARE(switch_status_t) switch_ivr_eavesdrop_session(switch_core_session_t *session,
 															 const char *uuid, const char *require_group, switch_eavesdrop_flag_t flags)
@@ -2213,11 +2232,17 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_eavesdrop_session(switch_core_session
 		const char *vval;
 		int buf_size = 0;
 		int channels;
+		int lcm, buff_min_len, buffered = 1;
 
 		if (!switch_channel_media_up(channel)) {
 			goto end;
 		}
 
+		if (tsession == session) {
+			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Eavesdrop target invalid.\n");
+			goto end;
+		}
+
 		while(switch_channel_state_change_pending(tchannel) || !switch_channel_media_up(tchannel)) {
 			switch_yield(10000);
 			if (!--sanity) break;
@@ -2286,8 +2311,21 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_eavesdrop_session(switch_core_session
 			goto end;
 		}
 
-
-		if (switch_core_codec_init(&codec,
+		if (tread_impl.decoded_bytes_per_packet < read_impl.decoded_bytes_per_packet) {
+			if (switch_core_codec_init(&codec,
+									   "L16",
+									   NULL,
+									   NULL,
+									   read_impl.actual_samples_per_second,
+									   read_impl.microseconds_per_packet / 1000,
+									   read_impl.number_of_channels,
+									   SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
+									   NULL, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) {
+				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot init codec\n");
+				goto end;
+			}
+		} else {
+			if (switch_core_codec_init(&codec,
 								   "L16",
 								   NULL,
 								   NULL,
@@ -2298,10 +2336,10 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_eavesdrop_session(switch_core_session
 								   NULL, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) {
 			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot init codec\n");
 			goto end;
+			}
+			buffered = 0;
 		}
 
-		switch_core_session_get_read_impl(session, &read_impl);
-
 		ep->read_impl = read_impl;
 		ep->tread_impl = tread_impl;
 
@@ -2440,6 +2478,8 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_eavesdrop_session(switch_core_session
 			switch_core_session_receive_message(tsession, &msg);
 		}
 
+		lcm = switch_lcm(tread_impl.decoded_bytes_per_packet, read_impl.decoded_bytes_per_packet);
+
 		while (switch_channel_up_nosig(tchannel) && switch_channel_ready(channel)) {
 			uint32_t len = sizeof(buf);
 			switch_event_t *event = NULL;
@@ -2569,15 +2609,24 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_eavesdrop_session(switch_core_session
 				channels = 1;
 			}
 
-			tlen = tread_impl.decoded_bytes_per_packet * channels;
+			tlen = ep->read_impl.decoded_bytes_per_packet * channels;
 
 			if (len > tlen) {
 				len = tlen;
 			}
 
+			if (buffered) {
+				buff_min_len = lcm * 2;
+				if (switch_buffer_inuse(ep->buffer) < buff_min_len) {
+					continue;
+				}
+			} else {
+				buff_min_len = len;
+			}
+
 			if (ep->buffer) {
 				switch_buffer_lock(ep->buffer);
-				while (switch_buffer_inuse(ep->buffer) >= len) {
+				while (switch_buffer_inuse(ep->buffer) >= buff_min_len) {
 					int tchanged = 0, changed = 0;
 
 					write_frame.datalen = (uint32_t) switch_buffer_read(ep->buffer, buf, len);
@@ -2592,7 +2641,7 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_eavesdrop_session(switch_core_session
 						tchanged = 1;
 					}
 
-					if (read_impl.number_of_channels != ep->tread_impl.number_of_channels ||
+					if (read_impl.number_of_channels != ep->read_impl.number_of_channels ||
 						read_impl.actual_samples_per_second != ep->read_impl.actual_samples_per_second) {
 						changed = 1;
 					}
@@ -2606,6 +2655,13 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_eavesdrop_session(switch_core_session
 											  ep->read_impl.number_of_channels,
 											  read_impl.actual_samples_per_second,
 											  read_impl.number_of_channels);
+
+							tlen = read_impl.decoded_bytes_per_packet * channels;
+
+							if (len > tlen) {
+								len = tlen;
+							}
+
 						}
 
 						if (tchanged) {
@@ -2615,28 +2671,44 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_eavesdrop_session(switch_core_session
 											  ep->tread_impl.number_of_channels,
 											  tread_impl.actual_samples_per_second,
 											  tread_impl.number_of_channels);
+						}
+						
 
-							tlen = tread_impl.decoded_bytes_per_packet * channels;
-
-							if (len > tlen) {
-								len = tlen;
-							}
-
-							switch_core_codec_destroy(&codec);
+						switch_core_codec_destroy(&codec);
 
+						if (tread_impl.decoded_bytes_per_packet < read_impl.decoded_bytes_per_packet) {
 							if (switch_core_codec_init(&codec,
 													   "L16",
 													   NULL,
 													   NULL,
-													   tread_impl.actual_samples_per_second,
-													   tread_impl.microseconds_per_packet / 1000,
-													   tread_impl.number_of_channels,
+													   read_impl.actual_samples_per_second,
+													   read_impl.microseconds_per_packet / 1000,
+													   read_impl.number_of_channels,
 													   SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
 													   NULL, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) {
+									switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot init codec\n");
+									switch_buffer_unlock(ep->buffer);
+									goto end;
+							} 
+							buffered = 1;
+							lcm = switch_lcm(tread_impl.decoded_bytes_per_packet, read_impl.decoded_bytes_per_packet);
+						} else {
+							if (switch_core_codec_init(&codec,
+												   "L16",
+												   NULL,
+												   NULL,
+												   tread_impl.actual_samples_per_second,
+												   tread_impl.microseconds_per_packet / 1000,
+												   tread_impl.number_of_channels,
+												   SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
+												   NULL, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) {
 								switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot init codec\n");
 								switch_buffer_unlock(ep->buffer);
 								goto end;
 							}
+							if (buffered == 1) {
+								buffered = 0;
+							}
 						}
 
 						ep->read_impl = read_impl;
@@ -2658,11 +2730,14 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_eavesdrop_session(switch_core_session
 
 					switch_buffer_unlock(ep->buffer);
 					switch_buffer_lock(ep->buffer);
-				}
 
+					if (ep->tread_impl.decoded_bytes_per_packet == ep->read_impl.decoded_bytes_per_packet) {
+						/* push just the number of samples worth of a packet. */
+						break;
+					}
+				}
 				switch_buffer_unlock(ep->buffer);
 			}
-
 		}
 
 	  end_loop:
diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am
index 6e7e128d62..2511a143c2 100644
--- a/tests/unit/Makefile.am
+++ b/tests/unit/Makefile.am
@@ -7,8 +7,13 @@ noinst_PROGRAMS += switch_core_video switch_core_db switch_vad switch_core_asr
 AM_LDFLAGS += -avoid-version -no-undefined $(SWITCH_AM_LDFLAGS) $(openssl_LIBS)
 AM_LDFLAGS += $(FREESWITCH_LIBS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS)
 
+# "make check" will not run these.
+examples = switch_eavesdrop
+
 if HAVE_FVAD
 AM_CFLAGS += -DSWITCH_HAVE_FVAD
 endif
 
 TESTS = $(noinst_PROGRAMS)
+
+bin_PROGRAMS = $(examples)
diff --git a/tests/unit/conf_eavesdrop/freeswitch.xml b/tests/unit/conf_eavesdrop/freeswitch.xml
new file mode 100644
index 0000000000..4a65caf6d8
--- /dev/null
+++ b/tests/unit/conf_eavesdrop/freeswitch.xml
@@ -0,0 +1,238 @@
+<?xml version="1.0"?>
+<document type="freeswitch/xml">
+  <X-PRE-PROCESS cmd="exec-set" data="test=echo 1234"/>
+  <X-PRE-PROCESS cmd="set" data="default_password=$${test}"/>
+  <X-PRE-PROCESS cmd="set" data="core_video_blank_image=$${conf_dir}/freeswitch-logo.png"/>
+  <section name="configuration" description="Various Configuration">
+    <configuration name="modules.conf" description="Modules">
+		<modules>
+			<load module="mod_sofia"/>
+			<load module="mod_console"/>
+			<load module="mod_loopback"/>
+			<load module="mod_commands"/>
+			<load module="mod_dptools"/>
+			<load module="mod_dialplan_xml"/>
+			<load module="mod_tone_stream"/>
+			<load module="mod_commands"/>
+			<load module="mod_sndfile"/>
+      </modules>
+    </configuration>
+
+    <configuration name="console.conf" description="Console Logger">
+      <mappings>
+        <map name="all" value="console,debug,info,notice,warning,err,crit,alert"/>
+      </mappings>
+      <settings>
+        <param name="colorize" value="true"/>
+        <param name="loglevel" value="debug"/>
+      </settings>
+    </configuration>
+
+    <configuration name="timezones.conf" description="Timezones">
+      <timezones>
+          <zone name="GMT" value="GMT0" />
+      </timezones>
+    </configuration>
+
+    <configuration name="sofia.conf" description="SofiaSIP">
+        <profiles>
+    <profile name="external">
+        <gateways>
+
+           <gateway name="eavestest">
+                <param name="username" value="not-used"/>
+                <param name="password" value="not-used"/>
+                <param name="proxy" value="$${local_ip_v4}:61068"/>
+                <param name="register" value="false"/>
+                <param name="retry-seconds" value="30"/>
+                <param name="dtmf-type" value="rfc2833"/>
+                <variables>   
+                 <variable name="rtp_secure_media"  value="false"  direction="outbound"/>  
+                </variables>
+            </gateway>
+        </gateways>
+
+      <domains>
+        <domain name="all" alias="false" parse="true"/>
+      </domains>
+
+      <settings>
+        <param name="debug" value="1"/>
+        <param name="shutdown-on-fail" value="true"/>
+        <param name="p-asserted-id-parse" value="verbatim"/>
+        <param name="username" value="SignalWire-STACK"/>
+        <param name="user-agent-string" value="SignalWire STACK Unit Test"/>
+        <param name="sip-trace" value="yes"/>
+        <param name="sip-capture" value="no"/>
+        <param name="rfc2833-pt" value="101"/>
+        <param name="sip-port" value="61068"/>
+        <param name="dialplan" value="XML"/>
+        <param name="context" value="default"/>
+        <param name="dtmf-duration" value="2000"/>
+        <param name="inbound-codec-prefs" value="PCMU"/>
+        <param name="outbound-codec-prefs" value="PCMU"/>
+        <param name="rtp-timer-name" value="soft"/>
+        <param name="local-network-acl" value="localnet.auto"/>
+        <param name="manage-presence" value="false"/>
+        <param name="inbound-codec-negotiation" value="generous"/>
+        <param name="nonce-ttl" value="60"/>
+        <param name="inbound-late-negotiation" value="true"/>
+        <param name="inbound-zrtp-passthru" value="false"/>
+        <param name="rtp-ip" value="$${local_ip_v4}"/>
+        <param name="sip-ip" value="$${local_ip_v4}"/>
+        <param name="ext-rtp-ip" value="$${local_ip_v4}"/>
+        <param name="ext-sip-ip" value="$${local_ip_v4}"/>
+        <param name="rtp-timeout-sec" value="300"/>
+        <param name="rtp-hold-timeout-sec" value="1800"/>
+        <param name="session-timeout" value="600"/>
+        <param name="minimum-session-expires" value="90"/>
+        <param name="tls" value="false"/>
+      </settings>
+  </profile>
+
+    <profile name="internal">
+        <gateways>
+    </gateways>
+
+      <domains>
+        <domain name="all" alias="false" parse="true"/>
+      </domains>
+
+      <settings>
+        <param name="debug" value="1"/>
+        <param name="shutdown-on-fail" value="true"/>
+        <param name="p-asserted-id-parse" value="verbatim"/>
+        <param name="username" value="SignalWire-STACK"/>
+        <param name="user-agent-string" value="SignalWire STACK Unit Test"/>
+        <param name="sip-trace" value="yes"/>
+        <param name="sip-capture" value="no"/>
+        <param name="rfc2833-pt" value="101"/>
+        <param name="sip-port" value="61069"/>
+        <param name="dialplan" value="XML"/>
+        <param name="context" value="default"/>
+        <param name="dtmf-duration" value="2000"/>
+        <param name="inbound-codec-prefs" value="PCMU"/>
+        <param name="outbound-codec-prefs" value="PCMU"/>
+        <param name="rtp-timer-name" value="soft"/>
+        <param name="local-network-acl" value="localnet.auto"/>
+        <param name="manage-presence" value="false"/>
+        <param name="inbound-codec-negotiation" value="generous"/>
+        <param name="nonce-ttl" value="60"/>
+        <param name="inbound-late-negotiation" value="true"/>
+        <param name="inbound-zrtp-passthru" value="false"/>
+        <param name="rtp-ip" value="$${local_ip_v4}"/>
+        <param name="sip-ip" value="$${local_ip_v4}"/>
+        <param name="ext-rtp-ip" value="$${local_ip_v4}"/>
+        <param name="ext-sip-ip" value="$${local_ip_v4}"/>
+        <param name="rtp-timeout-sec" value="300"/>
+        <param name="rtp-hold-timeout-sec" value="1800"/>
+        <param name="session-timeout" value="600"/>
+        <param name="minimum-session-expires" value="90"/>
+        <param name="tls" value="false"/>
+      </settings>
+  </profile>
+
+  </profiles>
+   </configuration>
+
+   <configuration name="switch.conf" description="Switch">
+     <param name="rtp-start-port" value="20000"/>
+     <param name="rtp-end-port" value="30000"/>
+     <param name="threaded-system-exec" value="true"/>
+   </configuration>
+  </section>
+
+  <section name="dialplan" description="Regex/XML Dialplan">
+    <context name="default">
+		<extension name="one">
+			<condition field="destination_number" expression="^\+15553332220$">
+				<action application="set" data="absolute_codec_string=PCMU@20i"/>
+				<action application="info"/>
+				<action application="answer"/>
+				<action application="park""/>
+			</condition>
+		</extension>
+		<extension name="two">
+			<condition field="destination_number" expression="^\+15553332221$">
+				<action application="set" data="absolute_codec_string=PCMU@10i"/>
+				<action application="info"/>
+				<action application="answer"/>
+				<action application="playback" data="{loops=-1}tone_stream://%(251,0,300)"/>
+			</condition>
+		</extension>
+		<extension name="three">
+			<condition field="destination_number" expression="^\+15553332222$">
+				<action application="set" data="absolute_codec_string=PCMU@30i"/>
+				<action application="info"/>
+				<action application="answer"/>
+				<action application="playback" data="{loops=-1}tone_stream://%(251,0,300)"/>
+			</condition>
+		</extension>
+		<extension name="four">
+			<condition field="destination_number" expression="^\+15553332223$">
+				<action application="set" data="absolute_codec_string=PCMU@40i"/>
+				<action application="info"/>
+				<action application="answer"/>
+				<action application="playback" data="{loops=-1}tone_stream://%(251,0,300)"/>
+			</condition>
+		</extension>
+		<extension name="five">
+			<condition field="destination_number" expression="^\+15553332224$">
+				<action application="set" data="absolute_codec_string=PCMU@80i"/>
+				<action application="info"/>
+				<action application="answer"/>
+				<action application="playback" data="{loops=-1}tone_stream://%(251,0,300)"/>
+			</condition>
+		</extension>
+		<extension name="six">
+			<condition field="destination_number" expression="^\+15553332225$">
+				<action application="set" data="absolute_codec_string=PCMU@10i"/>
+				<action application="info"/>
+				<action application="answer"/>
+				<action application="playback" data="{loops=-1}tone_stream://%(251,0,300)"/>
+			</condition>
+		</extension>
+		<extension name="seven">
+			<condition field="destination_number" expression="^\+15553332226$">
+				<action application="set" data="absolute_codec_string=PCMU@20i"/>
+				<action application="info"/>
+				<action application="answer"/>
+				<action application="record_session" data="/tmp/eaves-${uuid}.wav"/>
+				<action application="playback" data="{loops=-1}tone_stream://%(251,0,300)"/>
+			</condition>
+		</extension>
+		<extension name="eavesdropper_20ms">
+			<condition field="destination_number" expression="^\+15553332230$">
+				<action application="answer"/>
+				<action application="record_session" data="/tmp/eaves-${sip_h_X-UnitTestRecfile}.wav"/>
+				<action application="playback" data="silence_stream://-1,1400"/>
+			</condition>
+		</extension>
+		<extension name="eight">
+			<condition field="destination_number" expression="^\+15553332231$">
+				<action application="answer"/>
+				<action application="park"/>
+		</condition>
+		</extension>
+
+		<extension name="eavesdropper_30ms">
+			<condition field="destination_number" expression="^\+15553332240$">
+				<action application="set" data="absolute_codec_string=PCMU@30i"/>
+				<action application="answer"/>
+				<action application="record_session" data="/tmp/eaves-${sip_h_X-UnitTestRecfile}.wav"/>
+				<action application="playback" data="silence_stream://-1,1400"/>
+			</condition>
+		</extension>
+
+		<extension name="ten">
+			<condition field="destination_number" expression="^\+15553332241$">
+				<action application="set" data="absolute_codec_string=PCMU@30i"/>
+				<action application="info"/>
+				<action application="answer"/>
+				<action application="playback" data="{loops=-1}tone_stream://%(251,0,300)"/>
+			</condition>
+		</extension>
+
+    </context>
+  </section>
+</document>
diff --git a/tests/unit/conf_eavesdrop/gw/eavestest.xml b/tests/unit/conf_eavesdrop/gw/eavestest.xml
new file mode 100644
index 0000000000..a2f268424e
--- /dev/null
+++ b/tests/unit/conf_eavesdrop/gw/eavestest.xml
@@ -0,0 +1,14 @@
+<include/>
+    <gateway name="eavestest">
+      <param name="username" value="not-used"/>
+      <param name="password" value="not-used"/>
+      <param name="proxy" value="127.0.0.1"/>
+      <param name="register" value="false"/>
+      <param name="retry-seconds" value="30"/>
+      <param name="dtmf-type" value="rfc2833"/>
+      <variables>   
+        <variable name="rtp_secure_media"  value="false"  direction="outbound"/>  
+      </variables>
+    </gateway>
+</include>
+
diff --git a/tests/unit/switch_eavesdrop.c b/tests/unit/switch_eavesdrop.c
new file mode 100644
index 0000000000..867ad7a6bc
--- /dev/null
+++ b/tests/unit/switch_eavesdrop.c
@@ -0,0 +1,392 @@
+#include <strings.h>
+#include <switch.h>
+#include <test/switch_test.h>
+
+static switch_memory_pool_t *pool = NULL;
+
+static switch_status_t test_detect_long_tone_in_file(const char *filepath, int rate, int freq, int ptime) {
+	teletone_multi_tone_t mt;
+	teletone_tone_map_t map;
+	int16_t data[SWITCH_RECOMMENDED_BUFFER_SIZE] = { 0 };
+	size_t len = (rate * ptime / 1000) /*packet len in samples */ * 8; /*length of chunk that must contain tone*/
+	size_t fin = 0; 
+	switch_status_t status;
+	switch_file_handle_t fh = { 0 };
+	uint8_t fail = 0, gaps = 0, audio = 0;
+	uint32_t pos = 0;
+	size_t full_len = 0;
+
+	status = switch_core_file_open(&fh, filepath, 1, rate, SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL);
+	if (status != SWITCH_STATUS_SUCCESS) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot open file [%s]\n", filepath);
+		return SWITCH_STATUS_FALSE;
+	} 
+
+	mt.sample_rate = rate;
+	map.freqs[0] = (teletone_process_t)freq;
+
+	teletone_multi_tone_init(&mt, &map);
+
+	len = (rate * 2 / 100) /*packet len in samples */ * 8;
+
+	while (switch_core_file_read(&fh, &data, &len) == SWITCH_STATUS_SUCCESS) {
+		fin += len;
+		/*skip silence at the beginning of the file, 1 second max. */
+		if (!teletone_multi_tone_detect(&mt, data, len)) {
+			if ((fin > rate && !audio) || gaps > 30) { 
+				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Too many gaps in audio or no tone detected 1st second. [%u][%d]\n", fin, gaps);
+				fail = 1;
+				break;
+			}
+			gaps++;
+			continue;
+		} else {
+			audio++;
+		}
+	}
+
+	switch_core_file_close(&fh);
+
+	if (fail) {
+		return SWITCH_STATUS_FALSE;
+	}
+	return SWITCH_STATUS_SUCCESS;
+}
+
+FST_CORE_BEGIN("./conf_eavesdrop")
+
+{
+FST_SUITE_BEGIN(switch_eavesdrop)
+{
+	FST_SETUP_BEGIN()
+	{
+		fst_requires_module("mod_loopback");
+		fst_requires_module("mod_sofia");
+		switch_core_set_variable("link_ip", switch_core_get_variable("local_ip_v4"));
+	}
+	FST_SETUP_END()
+
+	FST_TEARDOWN_BEGIN()
+	{
+	}
+	FST_TEARDOWN_END()
+
+	FST_TEST_BEGIN(test_eavesdrop_bridged_same_ptime_20ms)
+	{
+		switch_core_session_t *session1 = NULL;
+		switch_core_session_t *session2 = NULL;
+		switch_core_session_t *session3 = NULL;
+
+		switch_channel_t *channel1 = NULL;
+		switch_channel_t *channel2 = NULL;
+		switch_channel_t *channel3 = NULL;
+
+		switch_status_t status;
+		switch_call_cause_t cause;
+		switch_stream_handle_t stream = { 0 };
+		char eavesdrop_command[256] = { 0 };
+		char rec_path[256];
+		char rec_uuid[SWITCH_UUID_FORMATTED_LENGTH + 1] = { 0 };
+		char eaves_dialstr[256] = { 0 };
+
+		switch_uuid_str(rec_uuid, sizeof(rec_uuid));
+
+		/*parked 20 ms ptime */
+		status = switch_ivr_originate(NULL, &session1, &cause, "{ignore_early_media=true}sofia/gateway/eavestest/+15553332220", 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+		fst_requires(session1);
+		fst_check(status == SWITCH_STATUS_SUCCESS);
+		channel1 = switch_core_session_get_channel(session1);
+		fst_requires(channel1);
+
+		snprintf(eaves_dialstr, sizeof(eaves_dialstr), "{ignore_early_media=true}{sip_h_X-UnitTestRecfile=%s}sofia/gateway/eavestest/+15553332230", rec_uuid);
+
+		/*eavesdropper 20 ms ptime*/
+		status = switch_ivr_originate(NULL, &session2, &cause, eaves_dialstr, 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+		fst_requires(session2);
+		fst_check(status == SWITCH_STATUS_SUCCESS);
+		channel2 = switch_core_session_get_channel(session2);
+		fst_requires(channel2);
+
+		/*milliwatt tone 20 ms ptime*/
+		status = switch_ivr_originate(NULL, &session3, &cause, "{ignore_early_media=true}sofia/gateway/eavestest/+15553332226", 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+		fst_requires(session3);
+		fst_check(status == SWITCH_STATUS_SUCCESS);
+		channel3 = switch_core_session_get_channel(session3);
+		fst_requires(channel3);
+
+		SWITCH_STANDARD_STREAM(stream);
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command), "uuid_bridge %s %s", switch_core_session_get_uuid(session1), switch_core_session_get_uuid(session2));
+		switch_api_execute("bgapi", eavesdrop_command, session1, &stream);
+		memset(eavesdrop_command, 0, sizeof(eavesdrop_command));
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command),"uuid_setvar_multi %s eavesdrop_enable_dtmf=false;eavesdrop_whisper_bleg=true;eavesdrop_whisper_aleg=false", switch_core_session_get_uuid(session3));
+		switch_api_execute("bgapi", eavesdrop_command, session3, &stream);
+		memset(eavesdrop_command, 0, sizeof(eavesdrop_command));
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command), "uuid_transfer %s 'eavesdrop:%s' inline", switch_core_session_get_uuid(session3), switch_core_session_get_uuid(session2)); 
+		switch_api_execute("bgapi", eavesdrop_command, session3, &stream);
+		switch_safe_free(stream.data);
+
+		sleep(5); // it will record ~ 5 secs
+
+		snprintf(rec_path, sizeof(rec_path), "/tmp/eaves-%s.wav", rec_uuid);
+
+		fst_requires(switch_file_exists(rec_path, fst_pool) == SWITCH_STATUS_SUCCESS);
+
+		fst_requires(test_detect_long_tone_in_file(rec_path, 8000, 300, 20) == SWITCH_STATUS_SUCCESS);
+
+		unlink(rec_path);
+
+		switch_channel_hangup(channel1, SWITCH_CAUSE_NORMAL_CLEARING);
+		switch_channel_hangup(channel2, SWITCH_CAUSE_NORMAL_CLEARING);
+		switch_channel_hangup(channel3, SWITCH_CAUSE_NORMAL_CLEARING);
+
+		switch_core_session_rwunlock(session1);
+		switch_core_session_rwunlock(session2);
+		switch_core_session_rwunlock(session3);
+
+	}
+	FST_TEST_END()
+
+	FST_TEST_BEGIN(test_eavesdrop_bridged_ptime_mismatch_20ms_30ms)
+	{
+		switch_core_session_t *session1 = NULL;
+		switch_core_session_t *session2 = NULL;
+		switch_core_session_t *session3 = NULL;
+
+		switch_channel_t *channel1 = NULL;
+		switch_channel_t *channel2 = NULL;
+		switch_channel_t *channel3 = NULL;
+
+		switch_status_t status;
+		switch_call_cause_t cause;
+		switch_stream_handle_t stream = { 0 };
+		char eavesdrop_command[256] = { 0 };
+		char rec_path[256];
+		char rec_uuid[SWITCH_UUID_FORMATTED_LENGTH + 1] = { 0 };
+		char eaves_dialstr[256] = { 0 };
+
+		switch_uuid_str(rec_uuid, sizeof(rec_uuid));
+
+		/*parked 20 ms ptime */
+		status = switch_ivr_originate(NULL, &session1, &cause, "{ignore_early_media=true}sofia/gateway/eavestest/+15553332220", 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+		fst_requires(session1);
+		fst_check(status == SWITCH_STATUS_SUCCESS);
+		channel1 = switch_core_session_get_channel(session1);
+		fst_requires(channel1);
+
+		snprintf(eaves_dialstr, sizeof(eaves_dialstr), "{ignore_early_media=true}{sip_h_X-UnitTestRecfile=%s}sofia/gateway/eavestest/+15553332230", rec_uuid);
+
+		/*eavesdropper 20 ms ptime*/
+		status = switch_ivr_originate(NULL, &session2, &cause, eaves_dialstr, 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+		fst_requires(session2);
+		fst_check(status == SWITCH_STATUS_SUCCESS);
+		channel2 = switch_core_session_get_channel(session2);
+		fst_requires(channel2);
+
+		/*milliwatt tone 30 ms ptime*/
+		status = switch_ivr_originate(NULL, &session3, &cause, "{ignore_early_media=true}sofia/gateway/eavestest/+15553332222", 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+		fst_requires(session3);
+		fst_check(status == SWITCH_STATUS_SUCCESS);
+		channel3 = switch_core_session_get_channel(session3);
+		fst_requires(channel3);
+
+		SWITCH_STANDARD_STREAM(stream);
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command), "uuid_bridge %s %s", switch_core_session_get_uuid(session1), switch_core_session_get_uuid(session2));
+		switch_api_execute("bgapi", eavesdrop_command, session1, &stream);
+		memset(eavesdrop_command, 0, sizeof(eavesdrop_command));
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command),"uuid_setvar_multi %s eavesdrop_enable_dtmf=false;eavesdrop_whisper_bleg=true;eavesdrop_whisper_aleg=false", switch_core_session_get_uuid(session3));
+		switch_api_execute("bgapi", eavesdrop_command, session3, &stream);
+		memset(eavesdrop_command, 0, sizeof(eavesdrop_command));
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command), "uuid_transfer %s 'eavesdrop:%s' inline", switch_core_session_get_uuid(session3), switch_core_session_get_uuid(session2)); 
+		switch_api_execute("bgapi", eavesdrop_command, session3, &stream);
+		switch_safe_free(stream.data);
+
+		sleep(5); // it will record ~ 5 secs
+
+		snprintf(rec_path, sizeof(rec_path), "/tmp/eaves-%s.wav", rec_uuid);
+
+		fst_requires(switch_file_exists(rec_path, fst_pool) == SWITCH_STATUS_SUCCESS);
+
+		fst_requires(test_detect_long_tone_in_file(rec_path, 8000, 300, 20) == SWITCH_STATUS_SUCCESS);
+
+		unlink(rec_path);
+
+		switch_channel_hangup(channel1, SWITCH_CAUSE_NORMAL_CLEARING);
+		switch_channel_hangup(channel2, SWITCH_CAUSE_NORMAL_CLEARING);
+		switch_channel_hangup(channel3, SWITCH_CAUSE_NORMAL_CLEARING);
+
+		switch_core_session_rwunlock(session1);
+		switch_core_session_rwunlock(session2);
+		switch_core_session_rwunlock(session3);
+
+	}
+	FST_TEST_END()
+
+	FST_TEST_BEGIN(test_eavesdrop_bridged_ptime_mismatch_30ms_20ms) 
+	{
+		switch_core_session_t *session1 = NULL;
+		switch_core_session_t *session2 = NULL;
+		switch_core_session_t *session3 = NULL;
+
+		switch_channel_t *channel1 = NULL;
+		switch_channel_t *channel2 = NULL;
+		switch_channel_t *channel3 = NULL;
+
+		switch_status_t status;
+		switch_call_cause_t cause;
+		switch_stream_handle_t stream = { 0 };
+		char eavesdrop_command[256] = { 0 };
+		char rec_path[256];
+		char rec_uuid[SWITCH_UUID_FORMATTED_LENGTH + 1] = { 0 };
+		char eaves_dialstr[256] = { 0 };
+
+		switch_uuid_str(rec_uuid, sizeof(rec_uuid));
+
+		/*parked 30 ms ptime */
+		status = switch_ivr_originate(NULL, &session1, &cause, "{ignore_early_media=true}sofia/gateway/eavestest/+15553332231", 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+		fst_requires(session1);
+		fst_check(status == SWITCH_STATUS_SUCCESS);
+		channel1 = switch_core_session_get_channel(session1);
+		fst_requires(channel1);
+
+		snprintf(eaves_dialstr, sizeof(eaves_dialstr), "{ignore_early_media=true}{sip_h_X-UnitTestRecfile=%s}sofia/gateway/eavestest/+15553332240", rec_uuid);
+
+		/*eavesdropper 30 ms ptime*/
+		status = switch_ivr_originate(NULL, &session2, &cause, eaves_dialstr, 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+		fst_requires(session2);
+		fst_check(status == SWITCH_STATUS_SUCCESS);
+		channel2 = switch_core_session_get_channel(session2);
+		fst_requires(channel2);
+
+		/*milliwatt tone 20 ms ptime*/
+		status = switch_ivr_originate(NULL, &session3, &cause, "{ignore_early_media=true}sofia/gateway/eavestest/+15553332226", 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+		fst_requires(session3);
+		fst_check(status == SWITCH_STATUS_SUCCESS);
+		channel3 = switch_core_session_get_channel(session3);
+		fst_requires(channel3);
+
+		SWITCH_STANDARD_STREAM(stream);
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command), "uuid_bridge %s %s", switch_core_session_get_uuid(session1), switch_core_session_get_uuid(session2));
+		switch_api_execute("bgapi", eavesdrop_command, session1, &stream);
+		memset(eavesdrop_command, 0, sizeof(eavesdrop_command));
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command),"uuid_setvar_multi %s eavesdrop_enable_dtmf=false;eavesdrop_whisper_bleg=true;eavesdrop_whisper_aleg=false", switch_core_session_get_uuid(session3));
+		switch_api_execute("bgapi", eavesdrop_command, session3, &stream);
+		memset(eavesdrop_command, 0, sizeof(eavesdrop_command));
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command), "uuid_transfer %s 'eavesdrop:%s' inline", switch_core_session_get_uuid(session3), switch_core_session_get_uuid(session2)); 
+		switch_api_execute("bgapi", eavesdrop_command, session3, &stream);
+		switch_safe_free(stream.data);
+
+		sleep(5); // it will record ~ 5 secs
+
+		snprintf(rec_path, sizeof(rec_path), "/tmp/eaves-%s.wav", rec_uuid);
+
+		fst_requires(switch_file_exists(rec_path, fst_pool) == SWITCH_STATUS_SUCCESS);
+
+		fst_requires(test_detect_long_tone_in_file(rec_path, 8000, 300, 30) == SWITCH_STATUS_SUCCESS);
+
+		unlink(rec_path);
+
+		switch_channel_hangup(channel1, SWITCH_CAUSE_NORMAL_CLEARING);
+		switch_channel_hangup(channel2, SWITCH_CAUSE_NORMAL_CLEARING);
+		switch_channel_hangup(channel3, SWITCH_CAUSE_NORMAL_CLEARING);
+
+		switch_core_session_rwunlock(session1);
+		switch_core_session_rwunlock(session2);
+		switch_core_session_rwunlock(session3);
+
+	}
+	FST_TEST_END()
+
+	FST_TEST_BEGIN(test_eavesdrop_bridged_ptime_mismatch_reneg) 
+	{
+		switch_core_session_t *session1 = NULL;
+		switch_core_session_t *session2 = NULL;
+		switch_core_session_t *session3 = NULL;
+
+		switch_channel_t *channel1 = NULL;
+		switch_channel_t *channel2 = NULL;
+		switch_channel_t *channel3 = NULL;
+
+		switch_status_t status;
+		switch_call_cause_t cause;
+		switch_stream_handle_t stream = { 0 };
+		char eavesdrop_command[256] = { 0 };
+		char rec_path[256];
+		char rec_uuid[SWITCH_UUID_FORMATTED_LENGTH + 1] = { 0 };
+		char eaves_dialstr[256] = { 0 };
+
+		switch_uuid_str(rec_uuid, sizeof(rec_uuid));
+
+		/*parked 30 ms ptime */
+		status = switch_ivr_originate(NULL, &session1, &cause, "{ignore_early_media=true}sofia/gateway/eavestest/+15553332231", 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+		fst_requires(session1);
+		fst_check(status == SWITCH_STATUS_SUCCESS);
+		channel1 = switch_core_session_get_channel(session1);
+		fst_requires(channel1);
+
+		snprintf(eaves_dialstr, sizeof(eaves_dialstr), "{ignore_early_media=true}{sip_h_X-UnitTestRecfile=%s}sofia/gateway/eavestest/+15553332240", rec_uuid);
+
+		/*eavesdropper 30 ms ptime*/
+		status = switch_ivr_originate(NULL, &session2, &cause, eaves_dialstr, 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+		fst_requires(session2);
+		fst_check(status == SWITCH_STATUS_SUCCESS);
+		channel2 = switch_core_session_get_channel(session2);
+		fst_requires(channel2);
+
+		/*milliwatt tone 20 ms ptime*/
+		status = switch_ivr_originate(NULL, &session3, &cause, "{ignore_early_media=true}sofia/gateway/eavestest/+15553332226", 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+		fst_requires(session3);
+		fst_check(status == SWITCH_STATUS_SUCCESS);
+		channel3 = switch_core_session_get_channel(session3);
+		fst_requires(channel3);
+
+		SWITCH_STANDARD_STREAM(stream);
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command), "uuid_bridge %s %s", switch_core_session_get_uuid(session1), switch_core_session_get_uuid(session2));
+		switch_api_execute("bgapi", eavesdrop_command, session1, &stream);
+		memset(eavesdrop_command, 0, sizeof(eavesdrop_command));
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command),"uuid_setvar_multi %s eavesdrop_enable_dtmf=false;eavesdrop_whisper_bleg=true;eavesdrop_whisper_aleg=false", switch_core_session_get_uuid(session3));
+		switch_api_execute("bgapi", eavesdrop_command, session3, &stream);
+		memset(eavesdrop_command, 0, sizeof(eavesdrop_command));
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command), "uuid_transfer %s 'eavesdrop:%s' inline", switch_core_session_get_uuid(session3), switch_core_session_get_uuid(session2)); 
+		switch_api_execute("bgapi", eavesdrop_command, session3, &stream);
+
+		sleep(2); 
+
+		// codec reneg for eavesdropper
+		memset(eavesdrop_command, 0, sizeof(eavesdrop_command));
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command), "uuid_media_reneg %s = PCMU@20i", switch_core_session_get_uuid(session2)); 
+		switch_api_execute("bgapi", eavesdrop_command, session3, &stream);
+
+		sleep(1);
+
+		// codec reneg for eavesdroppee
+		memset(eavesdrop_command, 0, sizeof(eavesdrop_command));
+		switch_snprintf(eavesdrop_command, sizeof(eavesdrop_command), "uuid_media_reneg %s = PCMU@30i", switch_core_session_get_uuid(session3)); 
+		switch_api_execute("bgapi", eavesdrop_command, session3, &stream);
+		switch_safe_free(stream.data);
+		
+		sleep(2);
+
+		snprintf(rec_path, sizeof(rec_path), "/tmp/eaves-%s.wav", rec_uuid);
+
+		fst_requires(switch_file_exists(rec_path, fst_pool) == SWITCH_STATUS_SUCCESS);
+
+		fst_requires(test_detect_long_tone_in_file(rec_path, 8000, 300, 30) == SWITCH_STATUS_SUCCESS);
+
+		unlink(rec_path);
+
+		switch_channel_hangup(channel1, SWITCH_CAUSE_NORMAL_CLEARING);
+		switch_channel_hangup(channel2, SWITCH_CAUSE_NORMAL_CLEARING);
+		switch_channel_hangup(channel3, SWITCH_CAUSE_NORMAL_CLEARING);
+
+		switch_core_session_rwunlock(session1);
+		switch_core_session_rwunlock(session2);
+		switch_core_session_rwunlock(session3);
+
+	}
+	FST_TEST_END()
+
+}
+FST_SUITE_END()
+}
+FST_CORE_END()
+