]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
[core] eavesdrop: init L16 codec at right ptime in certain conditions.
authorDragos Oancea <dragos@signalwire.com>
Fri, 4 Sep 2020 08:57:28 +0000 (08:57 +0000)
committerAndrey Volk <andywolk@gmail.com>
Tue, 16 Mar 2021 21:47:27 +0000 (00:47 +0300)
[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
tests/unit/Makefile.am
tests/unit/conf_eavesdrop/freeswitch.xml [new file with mode: 0644]
tests/unit/conf_eavesdrop/gw/eavestest.xml [new file with mode: 0644]
tests/unit/switch_eavesdrop.c [new file with mode: 0644]

index a6972e847c22e1f1a9e068807f3700464ce5ebc8..07f28a36e8f5b5898ed608b85a39ab29a2edb829 100644 (file)
@@ -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:
index 6e7e128d6290d38466ea231b780c78abbdecbb04..2511a143c25d1fc6485689bf583cbc5cb7424495 100644 (file)
@@ -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 (file)
index 0000000..4a65caf
--- /dev/null
@@ -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 (file)
index 0000000..a2f2684
--- /dev/null
@@ -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 (file)
index 0000000..867ad7a
--- /dev/null
@@ -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()
+