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)); } /* 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); 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); 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()); } }