MGCP_Test: add tests to verify actual RTP flows

The test coverage of the RTP aspects of the MGW is currently very
minima. Lets add a few more testcase to verify RTP behaves as
expected in various situations.

- Add testcase TC_one_crcx_receive_only_rtp:
  Test recvonly mode of the MGW. All packets must be absorbed by
  the MGW, no packets must come back.

- Add testcase TC_one_crcx_loopback_rtp:
  Test loopback mode of the MGW. All packet sent to the MGW must
  come back.

- Add testcase TC_two_crcx_and_rtp_bidir:
  We already test unidirectional transmissions. This test does
  the same as TC_two_crcx_and_rtp but for both directions.

- Add testcase TC_two_crcx_mdcx_and_rtp:
  Simulate a typical behaviour of a normal call. First create
  two half open connections and complete the connections later
  using MDCX.

- Add testcase TC_two_crcx_and_unsolicited_rtp:
  Test what happens when a RTP packets from rogue source are mixed
  into the RTP stream.

- Add testcase TC_two_crcx_and_one_mdcx_rtp_ho:
  Test a typical handover situation. An existing connection is
  handovered to another source on one end but the old source will
  keep transmitting for a while.

Change-Id: I556a6efff0e74aab897bd8165200eec36e46629f
Closes: OS#2703
This commit is contained in:
Philipp Maier 2018-06-27 17:52:04 +02:00
parent 887e8f1e9e
commit 2321ef92a3
3 changed files with 423 additions and 33 deletions

View File

@ -188,19 +188,56 @@ function f_rtpem_stats_get(RTPEM_CTRL_PT pt, boolean rtcp := false) return Rtpem
return stats;
}
function f_rtpem_stats_compare(RtpemStats a, RtpemStats b) return boolean {
log("stats A: ", a);
log("stats B: ", b);
function f_rtpem_stats_compare_value(integer a, integer b, integer tolerance := 0) return boolean {
var integer temp;
if (a.num_pkts_tx != b.num_pkts_rx or
a.num_pkts_rx != b.num_pkts_tx or
a.bytes_payload_tx != b.bytes_payload_rx or
a.bytes_payload_rx != b.bytes_payload_tx) {
temp := (a - b)
if (temp < 0) {
temp := -temp;
}
if (temp > tolerance) {
return false;
}
return true;
}
/* Cross-compare two rtpem-statistics. The transmission statistics on the a side
* must match the reception statistics on the other side and vice versa. The
* user may also supply a tolerance value (number of packets) when deviations
* are acceptable */
function f_rtpem_stats_compare(RtpemStats a, RtpemStats b, integer tolerance := 0) return boolean {
var integer plen;
log("stats A: ", a);
log("stats B: ", b);
log("tolerance: ", tolerance, " packets");
if (f_rtpem_stats_compare_value(a.num_pkts_tx, b.num_pkts_rx, tolerance) == false) {
return false;
}
if (f_rtpem_stats_compare_value(a.num_pkts_rx, b.num_pkts_tx, tolerance) == false) {
return false;
}
if(a.num_pkts_tx > 0) {
plen := a.bytes_payload_tx / a.num_pkts_tx;
} else {
plen := 0;
}
if (f_rtpem_stats_compare_value(a.bytes_payload_tx, b.bytes_payload_rx, tolerance * plen) == false) {
return false;
}
if (f_rtpem_stats_compare_value(a.bytes_payload_rx, b.bytes_payload_tx, tolerance * plen) == false) {
return false;
}
return true;
}
template PDU_RTP ts_RTP(BIT32_BO_LAST ssrc, INT7b pt, LIN2_BO_LAST seq, uint32_t ts,
octetstring payload, BIT1 marker := '0'B) := {

View File

@ -22,8 +22,8 @@ module MGCP_Test {
var ConnectionId g_mgcp_conn_id := -1;
var integer g_trans_id;
var RTP_Emulation_CT vc_RTPEM[2];
port RTPEM_CTRL_PT RTPEM[2];
var RTP_Emulation_CT vc_RTPEM[3];
port RTPEM_CTRL_PT RTPEM[3];
};
function get_next_trans_id() runs on dummy_CT return MgcpTransId {
@ -246,10 +246,10 @@ module MGCP_Test {
MgcpConnectionId mgcp_conn_id optional
}
function f_flow_create(RTPEM_CTRL_PT pt, MgcpEndpoint ep, inout RtpFlowData flow,
/* Create an RTP flow (bidirectional, or receive-only) */
function f_flow_create(RTPEM_CTRL_PT pt, MgcpEndpoint ep, MgcpCallId call_id, charstring mode, inout RtpFlowData flow,
boolean one_phase := true)
runs on dummy_CT {
var MgcpCallId call_id := '1226'H;
var template MgcpCommand cmd;
var MgcpResponse resp;
@ -257,8 +257,12 @@ module MGCP_Test {
f_rtpem_bind(pt, flow.em.hostname, flow.em.portnr);
if (one_phase) {
/* Connect flow to MGW */
cmd := ts_CRCX(get_next_trans_id(), ep, "sendrecv", call_id);
/* Connect flow to MGW using a CRCX that also contains an SDP
* part that tells the MGW where we are listening for RTP streams
* that come from the MGW. We get a fully working connection in
* one go. */
cmd := ts_CRCX(get_next_trans_id(), ep, mode, call_id);
cmd.sdp := ts_SDP(flow.em.hostname, flow.em.hostname, "23", "42",
flow.em.portnr, { int2str(flow.pt) },
{ valueof(ts_SDP_rtpmap(flow.pt, flow.codec)),
@ -269,27 +273,65 @@ module MGCP_Test {
flow.mgw.portnr :=
resp.sdp.media_list[0].media_field.ports.port_number;
} else {
/* first create the MGW side RTP socket */
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
/* Create a half-open connection only. We do not tell the MGW
* where it can send RTP streams to us. This means this
* connection will only be able to receive but can not send
* data back to us. In order to turn the connection in a fully
* bi-directional one, a separate MDCX is needed. */
cmd := ts_CRCX(get_next_trans_id(), ep, mode, call_id);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
flow.mgcp_conn_id := extract_conn_id(resp);
/* extract MGW-side port number from response */
flow.mgw.portnr :=
resp.sdp.media_list[0].media_field.ports.port_number;
/* then connect it to the emulation-side RTP socket using SDP */
cmd := ts_MDCX(get_next_trans_id(), ep, "sendrecv", call_id, flow.mgcp_conn_id);
cmd.sdp := ts_SDP(flow.em.hostname, flow.em.hostname, "23", "42",
flow.em.portnr, { int2str(flow.pt) },
{ valueof(ts_SDP_rtpmap(flow.pt, flow.codec)),
valueof(ts_SDP_ptime(20)) });
resp := mgcp_transceive_mgw(cmd, tr_MDCX_ACK);
}
/* finally, connect the emulation-side RTP socket to the MGW */
f_rtpem_connect(pt, flow.mgw.hostname, flow.mgw.portnr);
}
/* Modify an existing RTP flow */
function f_flow_modify(RTPEM_CTRL_PT pt, MgcpEndpoint ep, MgcpCallId call_id, charstring mode, inout RtpFlowData flow)
runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
/* rebind local RTP emulation socket to the new address */
f_rtpem_bind(pt, flow.em.hostname, flow.em.portnr);
/* connect MGW side RTP socket to the emulation-side RTP socket using SDP */
cmd := ts_MDCX(get_next_trans_id(), ep, mode, call_id, flow.mgcp_conn_id);
cmd.sdp := ts_SDP(flow.em.hostname, flow.em.hostname, "23", "42",
flow.em.portnr, { int2str(flow.pt) },
{ valueof(ts_SDP_rtpmap(flow.pt, flow.codec)),
valueof(ts_SDP_ptime(20)) });
resp := mgcp_transceive_mgw(cmd, tr_MDCX_ACK);
/* extract MGW-side port number from response. (usually this
* will not change, but thats is up to the MGW) */
flow.mgw.portnr :=
resp.sdp.media_list[0].media_field.ports.port_number;
/* reconnect the emulation-side RTP socket to the MGW */
f_rtpem_connect(pt, flow.mgw.hostname, flow.mgw.portnr);
}
/* Delete an existing RTP flow */
function f_flow_delete(RTPEM_CTRL_PT pt, template MgcpEndpoint ep := omit, template MgcpCallId call_id := omit)
runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
/* Switch off RTP flow */
f_rtpem_mode(pt, RTPEM_MODE_NONE);
/* Delete connection on MGW (if needed) */
if (isvalue(call_id) and isvalue(ep)) {
f_sleep(0.1);
f_dlcx_ok(valueof(ep), call_id);
}
}
function f_crcx(charstring ep_prefix) runs on dummy_CT {
var MgcpEndpoint ep := ep_prefix & "2@" & c_mgw_domain;
var template MgcpCommand cmd;
@ -862,14 +904,92 @@ module MGCP_Test {
setverdict(pass);
}
/* create two local RTP emulations; create two connections on MGW EP, exchange some data */
testcase TC_two_crcx_and_rtp() runs on dummy_CT {
/* Create one half open connection in receive-only mode. The MGW must accept
* the packets but must not send any. */
testcase TC_one_crcx_receive_only_rtp() runs on dummy_CT {
var RtpFlowData flow;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "1@" & c_mgw_domain;
var MgcpCallId call_id := '1225'H;
var RtpemStats stats;
f_init(ep);
flow := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 112, "AMR/8000/1"));
flow.em.portnr := 10000;
f_flow_create(RTPEM[0], ep, call_id, "recvonly", flow, false);
f_rtpem_mode(RTPEM[0], RTPEM_MODE_TXONLY);
f_sleep(1.0);
f_flow_delete(RTPEM[0], ep, call_id);
stats := f_rtpem_stats_get(RTPEM[0]);
if (stats.num_pkts_tx < 40) {
setverdict(fail);
}
if (stats.bytes_payload_tx < 190) {
setverdict(fail);
}
if (stats.num_pkts_rx != 0) {
setverdict(fail);
}
if (stats.num_pkts_rx_err_seq != 0) {
setverdict(fail);
}
if (stats.num_pkts_rx_err_ts != 0) {
setverdict(fail);
}
if (stats.num_pkts_rx_err_disabled != 0) {
setverdict(fail);
}
setverdict(pass);
}
/* Create one connection in loopback mode, test if the RTP packets are
* actually reflected */
testcase TC_one_crcx_loopback_rtp() runs on dummy_CT {
var RtpFlowData flow;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "1@" & c_mgw_domain;
var MgcpCallId call_id := '1225'H;
var RtpemStats stats;
f_init(ep);
flow := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 111, "GSM-HR-08/8000/1"));
flow.em.portnr := 10000;
f_flow_create(RTPEM[0], ep, call_id, "loopback", flow);
f_rtpem_mode(RTPEM[0], RTPEM_MODE_BIDIR);
f_sleep(1.0);
f_flow_delete(RTPEM[0], ep, call_id);
stats := f_rtpem_stats_get(RTPEM[0]);
if (stats.num_pkts_tx != stats.num_pkts_rx) {
setverdict(fail);
}
if (stats.bytes_payload_tx != stats.bytes_payload_rx) {
setverdict(fail);
}
if (stats.num_pkts_rx_err_seq != 0) {
setverdict(fail);
}
if (stats.num_pkts_rx_err_ts != 0) {
setverdict(fail);
}
if (stats.num_pkts_rx_err_disabled != 0) {
setverdict(fail);
}
setverdict(pass);
}
function f_TC_two_crcx_and_rtp(boolean bidir) runs on dummy_CT {
var RtpFlowData flow[2];
var RtpemStats stats[2];
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
var MgcpCallId call_id := '1226'H;
var integer tolerance := 0;
f_init(ep);
@ -877,20 +997,181 @@ module MGCP_Test {
flow[0] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 98, "AMR/8000"));
/* bind local RTP emulation sockets */
flow[0].em.portnr := 10000;
f_flow_create(RTPEM[0], ep, flow[0]);
f_flow_create(RTPEM[0], ep, call_id, "sendrecv", flow[0]);
/* from MGW back to us */
flow[1] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 98, "AMR/8000"));
flow[1].em.portnr := 20000;
f_flow_create(RTPEM[1], ep, flow[1]);
f_flow_create(RTPEM[1], ep, call_id, "sendrecv", flow[1]);
if (bidir) {
f_rtpem_mode(RTPEM[0], RTPEM_MODE_BIDIR);
f_rtpem_mode(RTPEM[1], RTPEM_MODE_BIDIR);
/* Note: When we test bidirectional we may
* loose packets during switch off because
* both ends are transmitting and we only
* can switch them off one by one. */
tolerance := 3;
} else {
f_rtpem_mode(RTPEM[0], RTPEM_MODE_RXONLY);
f_rtpem_mode(RTPEM[1], RTPEM_MODE_TXONLY);
}
f_sleep(1.0);
f_flow_delete(RTPEM[1]);
f_flow_delete(RTPEM[0], ep, call_id);
stats[0] := f_rtpem_stats_get(RTPEM[0]);
stats[1] := f_rtpem_stats_get(RTPEM[1]);
if (not f_rtpem_stats_compare(stats[0], stats[1], tolerance)) {
setverdict(fail, "RTP endpoint statistics don't match");
}
setverdict(pass);
}
/* create two local RTP emulations; create two connections on MGW EP, exchange some data */
testcase TC_two_crcx_and_rtp() runs on dummy_CT {
f_TC_two_crcx_and_rtp(false);
}
/* create two local RTP emulations; create two connections on MGW EP,
* exchange some data in both directions */
testcase TC_two_crcx_and_rtp_bidir() runs on dummy_CT {
f_TC_two_crcx_and_rtp(true);
}
/* create two local RTP emulations and pass data in both directions */
testcase TC_two_crcx_mdcx_and_rtp() runs on dummy_CT {
var RtpFlowData flow[2];
var RtpemStats stats[2];
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
var MgcpCallId call_id := '1227'H;
var integer num_pkts_tx[2];
var integer temp;
f_init(ep);
/* Create the first connection in receive only mode */
flow[0] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 3, "GSM/8000/1"));
flow[0].em.portnr := 10000;
f_flow_create(RTPEM[0], ep, call_id, "recvonly", flow[0], false);
/* Create the second connection. This connection will be also
* in receive only mode */
flow[1] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 3, "GSM/8000/1"));
flow[1].em.portnr := 20000;
f_flow_create(RTPEM[1], ep, call_id, "recvonly", flow[1], false);
/* The first leg starts transmitting */
f_rtpem_mode(RTPEM[0], RTPEM_MODE_TXONLY);
f_sleep(0.5);
stats[0] := f_rtpem_stats_get(RTPEM[0]);
if (stats[0].num_pkts_rx_err_disabled != 0) {
setverdict(fail, "received packets from MGW on recvonly connection");
}
stats[1] := f_rtpem_stats_get(RTPEM[1]);
if (stats[1].num_pkts_rx_err_disabled != 0) {
setverdict(fail, "received packets from MGW on recvonly connection");
}
/* The second leg starts transmitting a little later */
f_rtpem_mode(RTPEM[1], RTPEM_MODE_TXONLY);
f_sleep(1.0);
stats[0] := f_rtpem_stats_get(RTPEM[0]);
if (stats[0].num_pkts_rx_err_disabled != 0) {
setverdict(fail, "received packets from MGW on recvonly connection");
}
stats[1] := f_rtpem_stats_get(RTPEM[1]);
if (stats[1].num_pkts_rx_err_disabled != 0) {
setverdict(fail, "received packets from MGW on recvonly connection");
}
/* The first leg will now be switched into bidirectional
* mode, but we do not expect any data comming back yet. */
f_flow_modify(RTPEM[0], ep, call_id, "sendrecv", flow[0]);
f_sleep(0.5);
stats[0] := f_rtpem_stats_get(RTPEM[0]);
if (stats[1].num_pkts_rx_err_disabled != 0) {
setverdict(fail, "received packets from MGW on recvonly connection");
}
stats[1] := f_rtpem_stats_get(RTPEM[1]);
if (stats[1].num_pkts_rx_err_disabled != 0) {
setverdict(fail, "received packets from MGW on recvonly connection");
}
/* When the second leg is switched into bidirectional mode
* as well, then the MGW will connect the two together and
* we should see RTP streams passing through from both ends. */
f_rtpem_mode(RTPEM[0], RTPEM_MODE_BIDIR);
f_rtpem_mode(RTPEM[1], RTPEM_MODE_BIDIR);
stats[0] := f_rtpem_stats_get(RTPEM[0]);
num_pkts_tx[0] := stats[0].num_pkts_tx
stats[1] := f_rtpem_stats_get(RTPEM[1]);
num_pkts_tx[1] := stats[1].num_pkts_tx
f_flow_modify(RTPEM[1], ep, call_id, "sendrecv", flow[1]);
f_sleep(2.0);
stats[0] := f_rtpem_stats_get(RTPEM[0]);
stats[1] := f_rtpem_stats_get(RTPEM[1]);
temp := stats[0].num_pkts_tx - num_pkts_tx[0] - stats[1].num_pkts_rx;
if (temp > 3 or temp < -3) {
setverdict(fail, "number of packets not within normal parameters");
}
temp := stats[1].num_pkts_tx - num_pkts_tx[1] - stats[0].num_pkts_rx;
if (temp > 3 or temp < -3) {
setverdict(fail, "number of packets not within normal parameters");
}
/* Tear down */
f_flow_delete(RTPEM[0]);
f_flow_delete(RTPEM[1], ep, call_id);
setverdict(pass);
}
/* Test what happens when two RTP streams from different sources target
* a single connection. Is the unsolicited stream properly ignored? */
testcase TC_two_crcx_and_unsolicited_rtp() runs on dummy_CT {
var RtpFlowData flow[2];
var RtpemStats stats[2];
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
var MgcpCallId call_id := '1234321326'H;
var integer unsolicited_port := 10002;
f_init(ep);
/* from us to MGW */
flow[0] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 98, "AMR/8000"));
/* bind local RTP emulation sockets */
flow[0].em.portnr := 10000;
f_flow_create(RTPEM[0], ep, call_id, "sendrecv", flow[0]);
/* from MGW back to us */
flow[1] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 98, "AMR/8000"));
flow[1].em.portnr := 20000;
f_flow_create(RTPEM[1], ep, call_id, "sendrecv", flow[1]);
f_rtpem_mode(RTPEM[1], RTPEM_MODE_RXONLY);
f_rtpem_mode(RTPEM[0], RTPEM_MODE_TXONLY);
f_sleep(1.0);
f_sleep(0.5);
f_rtpem_mode(RTPEM[0], RTPEM_MODE_NONE);
f_sleep(0.1);
/* Start inserting unsolicited RTP packets */
f_rtpem_bind(RTPEM[2], "127.0.0.1", unsolicited_port);
f_rtpem_connect(RTPEM[2], "127.0.0.1", flow[0].mgw.portnr);
f_rtpem_mode(RTPEM[2], RTPEM_MODE_TXONLY);
f_sleep(0.5);
f_flow_delete(RTPEM[0]);
f_flow_delete(RTPEM[1], ep, call_id);
stats[0] := f_rtpem_stats_get(RTPEM[0]);
stats[1] := f_rtpem_stats_get(RTPEM[1]);
@ -898,9 +1179,68 @@ module MGCP_Test {
setverdict(fail, "RTP endpoint statistics don't match");
}
f_dlcx_ok(ep, call_id);
setverdict(pass);
}
/* Test a handover situation. We first create two connections transmit
* some data bidirectionally. Then we will simulate a handover situation. */
testcase TC_two_crcx_and_one_mdcx_rtp_ho() runs on dummy_CT {
var RtpFlowData flow[2];
var RtpemStats stats[3];
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "4@" & c_mgw_domain;
var MgcpCallId call_id := '76338'H;
var integer port_old;
f_init(ep);
/* First connection (BTS) */
flow[0] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 110, "GSM-EFR/8000"));
/* bind local RTP emulation sockets */
flow[0].em.portnr := 10000;
f_flow_create(RTPEM[0], ep, call_id, "sendrecv", flow[0]);
/* Second connection (PBX) */
flow[1] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 110, "GSM-EFR/8000"));
flow[1].em.portnr := 20000;
f_flow_create(RTPEM[1], ep, call_id, "sendrecv", flow[1]);
/* Normal rtp flow for one second */
f_rtpem_mode(RTPEM[0], RTPEM_MODE_BIDIR);
f_rtpem_mode(RTPEM[1], RTPEM_MODE_BIDIR);
f_sleep(1.0);
/* Now switch the flow over to a new port (BTS) */
port_old := flow[0].em.portnr;
flow[0].em.portnr := 10002;
f_flow_modify(RTPEM[0], ep, call_id, "sendrecv", flow[0]);
/* When handing over a call, the old source may still keep
* transmitting for a while. We simulate this by injecting
* some unsolicited packets on the behalf of the old source,
* (old remote port) */
f_rtpem_bind(RTPEM[2], "127.0.0.1", port_old);
f_rtpem_connect(RTPEM[2], "127.0.0.1", flow[0].mgw.portnr);
f_rtpem_mode(RTPEM[2], RTPEM_MODE_TXONLY);
f_sleep(1.0);
f_rtpem_mode(RTPEM[2], RTPEM_MODE_NONE);
f_sleep(1.0);
/* Terminate call */
f_flow_delete(RTPEM[0]);
f_flow_delete(RTPEM[1], ep, call_id);
stats[0] := f_rtpem_stats_get(RTPEM[0]);
stats[1] := f_rtpem_stats_get(RTPEM[1]);
if (not f_rtpem_stats_compare(stats[0], stats[1], 5)) {
setverdict(fail, "RTP endpoint statistics don't match");
}
stats[2] := f_rtpem_stats_get(RTPEM[2]);
if (stats[2].num_pkts_rx_err_disabled != 0) {
setverdict(fail, "received packets on old leg after handover");
}
setverdict(pass);
}
/* TODO: Double-DLCX (no retransmission) */
@ -943,6 +1283,13 @@ module MGCP_Test {
execute(TC_crcx_dlcx_30ep());
execute(TC_rtpem_selftest());
execute(TC_one_crcx_receive_only_rtp());
execute(TC_one_crcx_loopback_rtp());
execute(TC_two_crcx_and_rtp());
execute(TC_two_crcx_and_rtp_bidir());
execute(TC_two_crcx_mdcx_and_rtp());
execute(TC_two_crcx_and_unsolicited_rtp());
execute(TC_two_crcx_and_one_mdcx_rtp_ho());
}
}

View File

@ -31,4 +31,10 @@
<testcase classname='MGCP_Test' name='TC_crcx_dlcx_30ep' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_rtpem_selftest' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_two_crcx_and_rtp' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_one_crcx_receive_only_rtp' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_one_crcx_loopback_rtp' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_two_crcx_and_rtp_bidir' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_two_crcx_mdcx_and_rtp' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_two_crcx_and_unsolicited_rtp' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_two_crcx_and_one_mdcx_rtp_ho' time='MASKED'/>
</testsuite>