osmo-ttcn3-hacks/mgw/MGCP_Test.ttcn

1361 lines
41 KiB
Plaintext

module MGCP_Test {
import from Osmocom_Types all;
import from MGCP_Types all;
import from MGCP_Templates all;
import from SDP_Types all;
import from MGCP_CodecPort all;
import from MGCP_CodecPort_CtrlFunct all;
import from RTP_CodecPort all;
import from RTP_CodecPort_CtrlFunct all;
import from RTP_Emulation all;
import from IPL4asp_Types all;
const charstring c_mgw_domain := "mgw";
const charstring c_mgw_ep_rtpbridge := "rtpbridge/";
/* any variables declared in the component will be available to
* all functions that 'run on' the named component, similar to
* class members in C++ */
type component dummy_CT {
port MGCP_CODEC_PT MGCP;
var boolean initialized := false;
var ConnectionId g_mgcp_conn_id := -1;
var integer g_trans_id;
var RTP_Emulation_CT vc_RTPEM[3];
port RTPEM_CTRL_PT RTPEM[3];
};
function get_next_trans_id() runs on dummy_CT return MgcpTransId {
var MgcpTransId tid := int2str(g_trans_id);
g_trans_id := g_trans_id + 1;
return tid;
}
/* all parameters declared here can be modified / overridden by
* the config file in the [MODULE_PARAMETERS] section. If no
* config file is used or the file doesn't specify them, the
* default values assigned below are used */
modulepar {
PortNumber mp_local_udp_port := 2727;
charstring mp_local_ip := "127.0.0.1";
PortNumber mp_remote_udp_port := 2427;
charstring mp_remote_ip := "127.0.0.1";
PortNumber mp_local_rtp_port_base := 10000;
}
private function f_rtpem_init(inout RTP_Emulation_CT comp_ref, integer i)
runs on dummy_CT {
comp_ref := RTP_Emulation_CT.create("RTPEM" & int2str(i));
map(comp_ref:RTP, system:RTP);
map(comp_ref:RTCP, system:RTCP);
comp_ref.start(RTP_Emulation.f_main());
}
/* initialization function, called by each test case at the
* beginning, but 'initialized' variable ensures its body is
* only executed once */
private function f_init(template MgcpEndpoint ep := omit) runs on dummy_CT {
var Result res;
var uint32_t ssrc;
if (initialized == false) {
initialized := true;
/* some random number for the initial transaction id */
g_trans_id := float2int(rnd()*65535.0);
map(self:MGCP, system:MGCP_CODEC_PT);
/* connect the MGCP test port using the given
* source/destionation ip/port and store the connection id in g_mgcp_conn_id
* */
res := MGCP_CodecPort_CtrlFunct.f_IPL4_connect(MGCP, mp_remote_ip, mp_remote_udp_port, mp_local_ip, mp_local_udp_port, 0, { udp := {} });
if (not ispresent(res.connId)) {
setverdict(fail, "Could not connect MGCP, check your configuration");
mtc.stop;
}
g_mgcp_conn_id := res.connId;
for (var integer i := 0; i < sizeof(vc_RTPEM); i := i+1) {
f_rtpem_init(vc_RTPEM[i], i);
connect(vc_RTPEM[i]:CTRL, self:RTPEM[i]);
}
}
if (isvalue(ep)) {
/* do a DLCX on all connections of the EP */
f_dlcx_ignore(valueof(ep));
}
}
testcase TC_selftest() runs on dummy_CT {
const charstring c_auep := "AUEP 158663169 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n";
const charstring c_mdcx3 := "MDCX 18983215 " & c_mgw_ep_rtpbridge & "1@" & c_mgw_domain & " MGCP 1.0\r\n";
const charstring c_mdcx3_ret := "200 18983215 OK\r\n" &
"I: 1\n" &
"\n" &
"v=0\r\n" &
"o=- 1 23 IN IP4 0.0.0.0\r\n" &
"s=-\r\n" &
"c=IN IP4 0.0.0.0\r\n" &
"t=0 0\r\n" &
"m=audio 0 RTP/AVP 126\r\n" &
"a=rtpmap:126 AMR/8000\r\n" &
"a=ptime:20\r\n";
const charstring c_mdcx4 := "MDCX 18983216 " & c_mgw_ep_rtpbridge & "1@" & c_mgw_domain & " MGCP 1.0\r\n" &
"M: sendrecv\r" &
"C: 2\r\n" &
"I: 1\r\n" &
"L: p:20, a:AMR, nt:IN\r\n" &
"\n" &
"v=0\r\n" &
"o=- 1 23 IN IP4 0.0.0.0\r\n" &
"s=-\r\n" &
"c=IN IP4 0.0.0.0\r\n" &
"t=0 0\r\n" &
"m=audio 4441 RTP/AVP 99\r\n" &
"a=rtpmap:99 AMR/8000\r\n" &
"a=ptime:40\r\n";
const charstring c_crcx510_ret := "510 23 FAIL\r\n"
log(c_auep);
log(dec_MgcpCommand(c_auep));
log(c_mdcx3);
log(dec_MgcpCommand(c_mdcx3));
log(c_mdcx3_ret);
log(dec_MgcpResponse(c_mdcx3_ret));
log(c_mdcx4);
log(dec_MgcpCommand(c_mdcx4));
log(ts_CRCX("23", c_mgw_ep_rtpbridge & "42@" & c_mgw_domain, "sendrecv", '1234'H));
log(enc_MgcpCommand(valueof(ts_CRCX("23", c_mgw_ep_rtpbridge & "42@" & c_mgw_domain, "sendrecv", '1234'H))));
log(c_crcx510_ret);
log(dec_MgcpResponse(c_crcx510_ret));
log(dec_MgcpMessage(c_crcx510_ret));
/* We didn't encounter any DTE, so pass the test */
setverdict(pass);
}
/* CRCX test ideas:
* x without mandatory CallId
* - with forbidden parameters (e.g. Capabilities, PackageList, ...
* - CRCX with remote session description and without
*
* general ideas:
* x packetization != 20ms
* x invalid mode
* x unsupported mode (517)
* x bidirectional mode before RemoteConnDesc: 527
* - invalid codec
* x retransmission of same transaction
* - unsupported LocalConnectionOptions ("b", "a", "e", "gc", "s", "r", "k", ..)
*/
/* build a receive template for receiving a MGCP message. You
* pass the MGCP response template in, and it will generate an
* MGCP_RecvFrom template that can match the primitives arriving on the
* MGCP_CodecPort */
function tr_MGCP_RecvFrom_R(template MgcpResponse resp) runs on dummy_CT return template MGCP_RecvFrom {
var template MGCP_RecvFrom mrf := {
connId := g_mgcp_conn_id,
remName := mp_remote_ip,
remPort := mp_remote_udp_port,
locName := mp_local_ip,
locPort := mp_local_udp_port,
msg := { response := resp }
}
return mrf;
}
/* Send a MGCP request + receive a (matching!) response */
function mgcp_transceive_mgw(template MgcpCommand cmd, template MgcpResponse resp := ?) runs on dummy_CT return MgcpResponse {
var MgcpMessage msg := { command := valueof(cmd) };
resp.line.trans_id := cmd.line.trans_id;
var template MGCP_RecvFrom mrt := tr_MGCP_RecvFrom_R(resp);
var MGCP_RecvFrom mrf;
timer T := 5.0;
MGCP.send(t_MGCP_Send(g_mgcp_conn_id, msg));
T.start;
alt {
[] MGCP.receive(mrt) -> value mrf { }
[] MGCP.receive(tr_MGCP_RecvFrom_R(?)) {
setverdict(fail, "Response didn't match template");
mtc.stop;
}
[] MGCP.receive { repeat; }
[] T.timeout {
setverdict(fail, "Timeout waiting for response to ", cmd);
mtc.stop;
}
}
T.stop;
if (isbound(mrf) and isbound(mrf.msg) and ischosen(mrf.msg.response)) {
return mrf.msg.response;
} else {
var MgcpResponse r := { line := { code := "999", trans_id := valueof(cmd.line.trans_id) } };
return r;
}
}
function extract_conn_id(MgcpResponse resp) return MgcpConnectionId {
var integer i;
for (i := 0; i < lengthof(resp.params); i := i + 1) {
var MgcpParameter par := resp.params[i];
if (par.code == "I") {
return str2hex(par.val);
}
}
setverdict(fail, "Could not find conn id for MgcpReponse");
mtc.stop;
return '00000000'H;
}
function f_dlcx(MgcpEndpoint ep, template MgcpResponseCode ret_code, template charstring ret_val,
template MgcpCallId call_id := omit,
template MgcpConnectionId conn_id := omit) runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var template MgcpResponse rtmpl := {
line := {
code := ret_code,
string := ret_val
},
params := *,
sdp := *
};
cmd := ts_DLCX(get_next_trans_id(), ep, call_id, conn_id);
resp := mgcp_transceive_mgw(cmd, rtmpl);
}
/* Send DLCX and expect OK response */
function f_dlcx_ok(MgcpEndpoint ep, template MgcpCallId call_id := omit,
template MgcpConnectionId conn_id := omit) runs on dummy_CT {
f_dlcx(ep, ("200","250"), "OK", call_id, conn_id);
}
/* Send DLCX and accept any response */
function f_dlcx_ignore(MgcpEndpoint ep, template MgcpCallId call_id := omit,
template MgcpConnectionId conn_id := omit) runs on dummy_CT {
f_dlcx(ep, ?, *, call_id, conn_id);
}
type record HostPort {
charstring hostname,
integer portnr optional
}
type record RtpFlowData {
HostPort em, /* emulation side */
HostPort mgw, /* mgw side */
uint7_t pt,
charstring codec,
MgcpConnectionId mgcp_conn_id optional,
RtpemConfig rtp_cfg optional
}
/* 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 template MgcpCommand cmd;
var MgcpResponse resp;
/* bind local RTP emulation socket */
f_rtpem_bind(pt, flow.em.hostname, flow.em.portnr);
/* configure rtp-emulation */
if (ispresent(flow.rtp_cfg)) {
f_rtpem_configure(pt, flow.rtp_cfg);
} else {
var RtpemConfig rtp_cfg := c_RtpemDefaultCfg;
rtp_cfg.tx_payload_type := flow.pt
f_rtpem_configure(pt, rtp_cfg);
}
if (one_phase) {
/* 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)),
valueof(ts_SDP_ptime(20)) });
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
flow.mgcp_conn_id := extract_conn_id(resp);
/* extract port number from response */
flow.mgw.portnr :=
resp.sdp.media_list[0].media_field.ports.port_number;
} else {
/* 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;
}
/* 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);
/* reconfigure rtp-emulation */
if (ispresent(flow.rtp_cfg)) {
f_rtpem_configure(pt, flow.rtp_cfg);
} else {
var RtpemConfig rtp_cfg := c_RtpemDefaultCfg;
rtp_cfg.tx_payload_type := flow.pt
f_rtpem_configure(pt, rtp_cfg);
}
/* 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;
var MgcpResponse resp;
var MgcpCallId call_id := '1234'H;
f_init(ep);
/* create the connection on the MGW */
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
extract_conn_id(resp);
/* clean-up */
f_dlcx_ok(ep, call_id);
}
function f_crcx_no_lco(charstring ep_prefix) runs on dummy_CT {
var MgcpEndpoint ep := ep_prefix & "2@" & c_mgw_domain;
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpCallId call_id := '1234'H;
f_init(ep);
/* create the connection on the MGW */
cmd := ts_CRCX_no_lco(get_next_trans_id(), ep, "recvonly", call_id);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
extract_conn_id(resp);
/* clean-up */
f_dlcx_ok(ep, call_id);
/* See also OS#2658: Even when we omit the LCO information, we
expect the MGW to pick a sane payload type for us. This
payload type should be visible in the SDP of the response. */
if (resp.sdp.media_list[0].media_field.fmts[0] != "0") {
setverdict(fail, "SDP contains unexpected codec");
mtc.stop;
}
/* See also OS#2658: We also expect the MGW to assign a port
number to us. */
if (isbound(resp.sdp.media_list[0].media_field.ports.port_number) == false) {
setverdict(fail, "SDP does not contain a port number");
mtc.stop;
}
}
/* test valid CRCX without SDP */
testcase TC_crcx() runs on dummy_CT {
f_crcx(c_mgw_ep_rtpbridge);
setverdict(pass);
}
/* test valid CRCX without SDP and LCO */
testcase TC_crcx_no_lco() runs on dummy_CT {
f_crcx_no_lco(c_mgw_ep_rtpbridge);
setverdict(pass);
}
/* test valid CRCX without SDP (older method without endpoint prefix) */
testcase TC_crcx_noprefix() runs on dummy_CT {
f_crcx("");
setverdict(pass);
}
/* test CRCX with unsupported mode, expect 517 */
testcase TC_crcx_unsupp_mode() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
var MgcpCallId call_id := '1233'H;
var template MgcpResponse rtmpl := tr_MgcpResp_Err("517");
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "netwtest", call_id);
resp := mgcp_transceive_mgw(cmd, rtmpl);
setverdict(pass);
}
/* test CRCX with early bi-directional mode, expect 527 as
* bi-diretional media can only be established once both local and
* remote side are specified, see MGCP RFC */
testcase TC_crcx_early_bidir_mode() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
var MgcpCallId call_id := '1232'H;
var template MgcpResponse rtmpl := tr_MgcpResp_Err("527");
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "sendrecv", call_id);
resp := mgcp_transceive_mgw(cmd, rtmpl);
setverdict(pass);
}
/* test CRCX with unsupported Parameters */
testcase TC_crcx_unsupp_param() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
var MgcpCallId call_id := '1231'H;
var template MgcpResponse rtmpl := tr_MgcpResp_Err("539");
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
/* osmo-bsc_mgcp/mgw doesn't implement notifications */
f_mgcp_par_append(cmd.params, MgcpParameter:{ "N", "foobar" });
resp := mgcp_transceive_mgw(cmd, rtmpl);
setverdict(pass);
}
/* test CRCX with missing CallId */
testcase TC_crcx_missing_callid() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
var template MgcpResponse rtmpl := tr_MgcpResp_Err(("400","516"));
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", '1230'H);
cmd.params := {
t_MgcpParConnMode("recvonly"),
t_MgcpParLocConnOpt("p:20")
}
resp := mgcp_transceive_mgw(cmd, rtmpl);
setverdict(pass);
}
/* test CRCX with missing Mode */
testcase TC_crcx_missing_mode() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
var MgcpCallId call_id := '1229'H;
var template MgcpResponse rtmpl := tr_MgcpResp_Err(("400","517"));
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
cmd.params := {
ts_MgcpParCallId(call_id),
t_MgcpParLocConnOpt("p:20")
}
resp := mgcp_transceive_mgw(cmd, rtmpl);
setverdict(pass);
}
/* test CRCX with unsupported packetization interval */
testcase TC_crcx_unsupp_packet_intv() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
var MgcpCallId call_id := '1228'H;
var template MgcpResponse rtmpl := tr_MgcpResp_Err("535");
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
cmd.params[2] := t_MgcpParLocConnOpt("p:111");
resp := mgcp_transceive_mgw(cmd, rtmpl);
setverdict(pass);
}
/* test CRCX with illegal double presence of local connection option */
testcase TC_crcx_illegal_double_lco() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
var MgcpCallId call_id := '1227'H;
var template MgcpResponse rtmpl := tr_MgcpResp_Err("524");
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
/* p:20 is permitted only once and not twice! */
cmd.params[2] := t_MgcpParLocConnOpt("p:20, a:AMR, p:20");
resp := mgcp_transceive_mgw(cmd, rtmpl);
setverdict(pass);
}
/* test valid CRCX with valid SDP */
testcase TC_crcx_sdp() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
var MgcpCallId call_id := '1226'H;
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "sendrecv", call_id);
cmd.sdp := ts_SDP("127.0.0.1", "127.0.0.2", "23", "42", 2344, { "98" },
{ valueof(ts_SDP_rtpmap(98, "AMR/8000")),
valueof(ts_SDP_ptime(20)) });
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
/* clean-up */
f_dlcx_ok(ep, call_id);
setverdict(pass);
}
/* test valid wildcarded CRCX */
testcase TC_crcx_wildcarded() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "*@" & c_mgw_domain;
var MgcpCallId call_id := '1234'H;
var MgcpEndpoint ep_assigned;
f_init();
/* create the connection on the MGW */
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
extract_conn_id(resp);
/* extract endpoint name we got assigned by the MGW */
var MgcpMessage resp_msg := {
response := resp
}
if (f_mgcp_find_param(resp_msg, "Z", ep_assigned) == false) {
setverdict(fail, "No SpecificEndpointName in MGCP response", resp);
mtc.stop;
}
/* clean-up */
f_dlcx_ok(ep_assigned, call_id);
setverdict(pass);
}
/* test valid wildcarded CRCX */
testcase TC_crcx_wildcarded_exhaust() runs on dummy_CT {
const integer n_endpoints := 32;
var integer i;
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "*@" & c_mgw_domain;
var MgcpCallId call_id := '1234'H;
var MgcpEndpoint ep_assigned[n_endpoints];
f_init();
/* Exhaust all endpoint resources on the virtual trunk */
for (i := 0; i < n_endpoints; i := i+1) {
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
/* Make sure we got a connection id */
extract_conn_id(resp);
var MgcpMessage resp_msg := {
response := resp
}
if (f_mgcp_find_param(resp_msg, "Z", ep_assigned[i]) == false) {
setverdict(fail, "No SpecificEndpointName in MGCP response", resp);
mtc.stop;
}
}
/* Try to allocate one more endpoint, which should fail */
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
var template MgcpResponse rtmpl := tr_MgcpResp_Err("403");
resp := mgcp_transceive_mgw(cmd, rtmpl);
setverdict(pass);
/* clean-up */
for (i := 0; i < n_endpoints; i := i+1) {
f_dlcx_ok(ep_assigned[i], call_id);
}
setverdict(pass);
}
/* TODO: various SDP related bits */
/* TODO: CRCX with X-Osmux */
/* TODO: double CRCX without force_realloc */
/* TODO: MDCX (various) */
/* TODO: MDCX without CRCX first */
testcase TC_mdcx_without_crcx() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "3@" & c_mgw_domain;
var MgcpCallId call_id := '1225'H;
var template MgcpResponse rtmpl := {
line := {
/* TODO: accept/enforce better error? */
code := "400",
string := ?
},
params:= { },
sdp := omit
};
f_init(ep);
cmd := ts_MDCX(get_next_trans_id(), ep, "sendrecv", call_id, call_id);
cmd.sdp := ts_SDP("127.0.0.1", "127.0.0.2", "23", "42", 2344, { "98" },
{ valueof(ts_SDP_rtpmap(98, "AMR/8000")),
valueof(ts_SDP_ptime(20)) });
resp := mgcp_transceive_mgw(cmd, rtmpl);
setverdict(pass);
}
/* DLCX without CRCX first */
testcase TC_dlcx_without_crcx() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "4@" & c_mgw_domain;
var template MgcpResponse rtmpl := {
line := {
code := ("400", "515"),
string := ?
},
params:= { },
sdp := omit
};
f_init(ep);
cmd := ts_DLCX(get_next_trans_id(), ep, '41234'H);
resp := mgcp_transceive_mgw(cmd, rtmpl);
setverdict(pass);
}
/* test valid wildcarded MDCX */
testcase TC_mdcx_wildcarded() runs on dummy_CT {
/* Note: A wildcarded MDCX is not allowed, so we expect the
* MGW to reject this request */
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "*@" & c_mgw_domain;
var MgcpCallId call_id := '1225'H;
var template MgcpResponse rtmpl := {
line := {
/* TODO: accept/enforce better error? */
code := "507",
string := ?
},
params:= { },
sdp := omit
};
f_init(ep);
cmd := ts_MDCX(get_next_trans_id(), ep, "sendrecv", call_id, call_id);
cmd.sdp := ts_SDP("127.0.0.1", "127.0.0.2", "23", "42", 2344, { "98" },
{ valueof(ts_SDP_rtpmap(98, "AMR/8000")),
valueof(ts_SDP_ptime(20)) });
resp := mgcp_transceive_mgw(cmd, rtmpl);
setverdict(pass);
}
/* test valid wildcarded DLCX */
testcase TC_dlcx_wildcarded() runs on dummy_CT {
/* Note: A wildcarded DLCX is specified, but our MGW does not
* support this feature so we expect the MGW to reject the
* request */
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "*@" & c_mgw_domain;
var template MgcpResponse rtmpl := {
line := {
code := "507",
string := ?
},
params:= { },
sdp := omit
};
f_init(ep);
cmd := ts_DLCX(get_next_trans_id(), ep, '41234'H);
resp := mgcp_transceive_mgw(cmd, rtmpl);
setverdict(pass);
}
/* Test (valid) CRCX followed by (valid) DLCX containig EP+CallId+ConnId */
testcase TC_crcx_and_dlcx_ep_callid_connid() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "5@" & c_mgw_domain;
var MgcpCallId call_id := '51234'H;
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
f_dlcx_ok(ep, call_id, extract_conn_id(resp));
setverdict(pass);
}
function f_crcx_and_dlcx_ep_callid_connid(MgcpEndpoint ep, MgcpCallId call_id) runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
f_dlcx_ok(ep, call_id, extract_conn_id(resp));
setverdict(pass);
}
testcase TC_crcx_dlcx_30ep() runs on dummy_CT {
var MgcpEndpoint ep;
var MgcpCallId call_id;
var integer ep_nr;
f_init();
for (ep_nr := 1; ep_nr < 30; ep_nr := ep_nr+1) {
ep := c_mgw_ep_rtpbridge & hex2str(int2hex(ep_nr, 2)) & "@" & c_mgw_domain;
call_id := int2hex(ep_nr, 2) & '1234'H;
f_crcx_and_dlcx_ep_callid_connid(ep, call_id);
}
}
/* Test (valid) CRCX followed by (valid) DLCX containing EP+CallId */
testcase TC_crcx_and_dlcx_ep_callid() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "5@" & c_mgw_domain;
var MgcpCallId call_id := '51233'H;
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
f_dlcx_ok(ep, call_id);
setverdict(pass);
}
/* Test (valid) CRCX followed by (valid) DLCX containing EP */
testcase TC_crcx_and_dlcx_ep() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "5@" & c_mgw_domain;
var MgcpCallId call_id := '51232'H;
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
f_dlcx_ok(ep);
setverdict(pass);
}
/* CRCX + DLCX of valid endpoint but invalid call-id */
testcase TC_crcx_and_dlcx_ep_callid_inval() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "5@" & c_mgw_domain;
var MgcpCallId call_id := '51231'H;
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
f_dlcx(ep, "516", *, 'ffff'H);
setverdict(pass);
}
/* CRCX + DLCX of valid endpoint and call-id but invalid conn-id */
testcase TC_crcx_and_dlcx_ep_callid_connid_inval() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "5@" & c_mgw_domain;
var MgcpCallId call_id := '51230'H;
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
f_dlcx(ep, "515", *, call_id, 'ffff'H);
setverdict(pass);
}
/* TODO: Double-DLCX (retransmission) */
testcase TC_crcx_and_dlcx_retrans() runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "5@" & c_mgw_domain;
var MgcpCallId call_id := '51229'H;
var template MgcpResponse rtmpl := {
line := {
code := "200",
string := "OK"
},
params:= { },
sdp := omit
};
f_init(ep);
cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
cmd := ts_DLCX(get_next_trans_id(), ep, call_id);
resp := mgcp_transceive_mgw(cmd, rtmpl);
resp := mgcp_transceive_mgw(cmd, rtmpl);
setverdict(pass);
}
template (value) RtpFlowData t_RtpFlow(charstring host_a, charstring host_b, uint7_t pt,
charstring codec) := {
em := {
hostname := host_a,
portnr := omit
},
mgw := {
hostname := host_b,
portnr := omit
},
pt := pt,
codec := codec
}
/* transmit RTP streams between two RTP Emulations back-to-back; expect no loss */
testcase TC_rtpem_selftest() runs on dummy_CT {
var RtpemStats stats[2];
var integer local_port := 10000;
var integer local_port2 := 20000;
f_init();
f_rtpem_bind(RTPEM[0], "127.0.0.1", local_port);
f_rtpem_bind(RTPEM[1], "127.0.0.2", local_port2);
f_rtpem_connect(RTPEM[0], "127.0.0.2", local_port2);
f_rtpem_connect(RTPEM[1], "127.0.0.1", local_port);
log("=== starting");
f_rtpem_mode(RTPEM[0], RTPEM_MODE_BIDIR);
f_rtpem_mode(RTPEM[1], RTPEM_MODE_BIDIR);
f_sleep(5.0);
log("=== stopping");
f_rtpem_mode(RTPEM[1], RTPEM_MODE_RXONLY);
f_rtpem_mode(RTPEM[0], RTPEM_MODE_RXONLY);
f_sleep(0.5);
f_rtpem_mode(RTPEM[1], RTPEM_MODE_NONE);
f_rtpem_mode(RTPEM[0], RTPEM_MODE_NONE);
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])) {
setverdict(fail, "RTP endpoint statistics don't match");
mtc.stop;
}
setverdict(pass);
}
/* 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);
}
f_rtpem_stats_err_check(stats);
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);
}
f_rtpem_stats_err_check(stats);
setverdict(pass);
}
function f_TC_two_crcx_and_rtp(boolean bidir, charstring codec_name_a, integer pt_a,
charstring codec_name_b, integer pt_b) 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 := '1226'H;
var integer tolerance := 0;
f_init(ep);
/* from us to MGW */
flow[0] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, pt_a, codec_name_a));
/* 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, pt_b, codec_name_b));
flow[1].em.portnr := 20000;
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);
/* Disable sending so we don't accidentally receive RTP
* packets on disabled ports during teardown */
f_rtpem_mode(RTPEM[0], RTPEM_MODE_RXONLY);
f_rtpem_mode(RTPEM[1], RTPEM_MODE_RXONLY);
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");
mtc.stop;
}
f_rtpem_stats_err_check(stats[0]);
f_rtpem_stats_err_check(stats[1]);
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, "AMR/8000", 98, "AMR/8000", 98);
}
/* 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, "AMR/8000", 98, "AMR/8000", 98);
}
/* same as TC_two_crcx_and_rtp, but with different PT number on both ends */
testcase TC_two_crcx_diff_pt_and_rtp() runs on dummy_CT {
f_TC_two_crcx_and_rtp(false, "AMR/8000", 98, "AMR/8000", 112);
}
/* same as TC_two_crcx_and_rtp, but with different PT number on both ends */
testcase TC_two_crcx_diff_pt_and_rtp_bidir() runs on dummy_CT {
f_TC_two_crcx_and_rtp(true, "AMR/8000", 98, "AMR/8000", 112);
}
/* 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");
mtc.stop;
}
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");
mtc.stop;
}
/* 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");
mtc.stop;
}
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");
mtc.stop;
}
/* 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");
mtc.stop;
}
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");
mtc.stop;
}
/* 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");
mtc.stop;
}
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");
mtc.stop;
}
f_rtpem_stats_err_check(stats[0]);
f_rtpem_stats_err_check(stats[1]);
/* 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(0.5);
/* Start inserting unsolicited RTP packets */
f_rtpem_bind(RTPEM[2], mp_local_ip, unsolicited_port);
f_rtpem_connect(RTPEM[2], mp_remote_ip, flow[0].mgw.portnr);
f_rtpem_mode(RTPEM[2], RTPEM_MODE_TXONLY);
f_sleep(0.5);
/* Stop transmitting packets and tear down the flows */
f_rtpem_mode(RTPEM[2], RTPEM_MODE_NONE);
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])) {
setverdict(fail, "RTP endpoint statistics don't match");
mtc.stop;
}
f_rtpem_stats_err_check(stats[0]);
f_rtpem_stats_err_check(stats[0]);
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_rtpem_mode(RTPEM[0], RTPEM_MODE_RXONLY);
f_flow_modify(RTPEM[0], ep, call_id, "sendrecv", flow[0]);
f_rtpem_mode(RTPEM[0], RTPEM_MODE_BIDIR);
/* 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], mp_local_ip, port_old);
f_rtpem_connect(RTPEM[2], mp_remote_ip, 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");
mtc.stop;
}
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");
mtc.stop;
}
f_rtpem_stats_err_check(stats[0]);
f_rtpem_stats_err_check(stats[1]);
f_rtpem_stats_err_check(stats[2]);
setverdict(pass);
}
/* TODO: Double-DLCX (no retransmission) */
/* TODO: AUEP (various) */
/* TODO: RSIP (various) */
/* TODO: RQNT (various) */
/* TODO: EPCF (various) */
/* TODO: AUCX (various) */
/* TODO: invalid verb (various) */
control {
execute(TC_selftest());
execute(TC_crcx());
execute(TC_crcx_no_lco());
execute(TC_crcx_noprefix());
execute(TC_crcx_unsupp_mode());
execute(TC_crcx_early_bidir_mode());
execute(TC_crcx_unsupp_param());
execute(TC_crcx_missing_callid());
execute(TC_crcx_missing_mode());
execute(TC_crcx_unsupp_packet_intv());
execute(TC_crcx_illegal_double_lco());
execute(TC_crcx_sdp());
execute(TC_crcx_wildcarded());
execute(TC_crcx_wildcarded_exhaust());
execute(TC_mdcx_without_crcx());
execute(TC_dlcx_without_crcx());
execute(TC_mdcx_wildcarded());
execute(TC_dlcx_wildcarded());
execute(TC_crcx_and_dlcx_ep_callid_connid());
execute(TC_crcx_and_dlcx_ep_callid());
execute(TC_crcx_and_dlcx_ep());
execute(TC_crcx_and_dlcx_ep_callid_inval());
execute(TC_crcx_and_dlcx_ep_callid_connid_inval());
execute(TC_crcx_and_dlcx_retrans());
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_diff_pt_and_rtp());
execute(TC_two_crcx_diff_pt_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());
}
}