blob: 8746c38d6da0f0da85721856934b20dc3e35c60a [file] [log] [blame]
Harald Welte00a067f2017-09-13 23:27:17 +02001module MGCP_Test {
Harald Weltef07c2862017-11-18 17:16:24 +01002 import from Osmocom_Types all;
Harald Welte00a067f2017-09-13 23:27:17 +02003 import from MGCP_Types all;
Harald Welte4029e8c2017-11-23 22:00:42 +01004 import from MGCP_Templates all;
Harald Welte3c6ebb92017-09-16 00:56:57 +08005 import from SDP_Types all;
6 import from MGCP_CodecPort all;
7 import from MGCP_CodecPort_CtrlFunct all;
Harald Weltef07c2862017-11-18 17:16:24 +01008 import from RTP_CodecPort all;
9 import from RTP_CodecPort_CtrlFunct all;
Harald Weltebb7523b2018-03-29 08:52:01 +020010 import from RTP_Emulation all;
Harald Welte3c6ebb92017-09-16 00:56:57 +080011 import from IPL4asp_Types all;
Harald Welte00a067f2017-09-13 23:27:17 +020012
Philipp Maierbb7a01c2018-02-01 12:32:57 +010013 const charstring c_mgw_domain := "mgw";
14 const charstring c_mgw_ep_rtpbridge := "rtpbridge/";
15
Harald Welte21ba5572017-09-19 17:55:05 +080016 /* any variables declared in the component will be available to
17 * all functions that 'run on' the named component, similar to
18 * class members in C++ */
Harald Welte00a067f2017-09-13 23:27:17 +020019 type component dummy_CT {
Harald Welte3c6ebb92017-09-16 00:56:57 +080020 port MGCP_CODEC_PT MGCP;
21 var boolean initialized := false;
Harald Welte55015362017-11-18 16:02:42 +010022 var ConnectionId g_mgcp_conn_id := -1;
Harald Weltee1e18c52017-09-17 16:23:07 +080023 var integer g_trans_id;
Harald Weltef07c2862017-11-18 17:16:24 +010024
Philipp Maier2321ef92018-06-27 17:52:04 +020025 var RTP_Emulation_CT vc_RTPEM[3];
26 port RTPEM_CTRL_PT RTPEM[3];
Harald Welte00a067f2017-09-13 23:27:17 +020027 };
28
Harald Weltee1e18c52017-09-17 16:23:07 +080029 function get_next_trans_id() runs on dummy_CT return MgcpTransId {
30 var MgcpTransId tid := int2str(g_trans_id);
31 g_trans_id := g_trans_id + 1;
32 return tid;
33 }
34
Harald Welte21ba5572017-09-19 17:55:05 +080035 /* all parameters declared here can be modified / overridden by
36 * the config file in the [MODULE_PARAMETERS] section. If no
37 * config file is used or the file doesn't specify them, the
38 * default values assigned below are used */
Harald Welte3c6ebb92017-09-16 00:56:57 +080039 modulepar {
40 PortNumber mp_local_udp_port := 2727;
41 charstring mp_local_ip := "127.0.0.1";
42 PortNumber mp_remote_udp_port := 2427;
43 charstring mp_remote_ip := "127.0.0.1";
Harald Weltef07c2862017-11-18 17:16:24 +010044 PortNumber mp_local_rtp_port_base := 10000;
Harald Welte3c6ebb92017-09-16 00:56:57 +080045 }
46
Harald Weltebb7523b2018-03-29 08:52:01 +020047 private function f_rtpem_init(inout RTP_Emulation_CT comp_ref, integer i)
48 runs on dummy_CT {
49 comp_ref := RTP_Emulation_CT.create("RTPEM" & int2str(i));
50 map(comp_ref:RTP, system:RTP);
51 map(comp_ref:RTCP, system:RTCP);
52 comp_ref.start(RTP_Emulation.f_main());
53 }
54
Harald Welte21ba5572017-09-19 17:55:05 +080055 /* initialization function, called by each test case at the
56 * beginning, but 'initialized' variable ensures its body is
57 * only executed once */
Harald Welteedc45c12017-11-18 19:15:05 +010058 private function f_init(template MgcpEndpoint ep := omit) runs on dummy_CT {
Harald Welte3c6ebb92017-09-16 00:56:57 +080059 var Result res;
Harald Weltef07c2862017-11-18 17:16:24 +010060 var uint32_t ssrc;
Harald Welteedc45c12017-11-18 19:15:05 +010061 if (initialized == false) {
62 initialized := true;
63
64 /* some random number for the initial transaction id */
65 g_trans_id := float2int(rnd()*65535.0);
66 map(self:MGCP, system:MGCP_CODEC_PT);
67 /* connect the MGCP test port using the given
68 * source/destionation ip/port and store the connection id in g_mgcp_conn_id
69 * */
70 res := MGCP_CodecPort_CtrlFunct.f_IPL4_connect(MGCP, mp_remote_ip, mp_remote_udp_port, mp_local_ip, mp_local_udp_port, 0, { udp := {} });
Harald Welte9220f632018-05-23 20:27:02 +020071 if (not ispresent(res.connId)) {
72 setverdict(fail, "Could not connect MGCP, check your configuration");
73 self.stop;
74 }
Harald Welteedc45c12017-11-18 19:15:05 +010075 g_mgcp_conn_id := res.connId;
76
Harald Weltebb7523b2018-03-29 08:52:01 +020077 for (var integer i := 0; i < sizeof(vc_RTPEM); i := i+1) {
78 f_rtpem_init(vc_RTPEM[i], i);
79 connect(vc_RTPEM[i]:CTRL, self:RTPEM[i]);
80 }
Harald Welte3c6ebb92017-09-16 00:56:57 +080081 }
Harald Welte3c6ebb92017-09-16 00:56:57 +080082
Harald Welteedc45c12017-11-18 19:15:05 +010083 if (isvalue(ep)) {
84 /* do a DLCX on all connections of the EP */
85 f_dlcx_ignore(valueof(ep));
86 }
Harald Welte3c6ebb92017-09-16 00:56:57 +080087 }
88
Harald Welte00a067f2017-09-13 23:27:17 +020089 testcase TC_selftest() runs on dummy_CT {
90 const charstring c_auep := "AUEP 158663169 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n";
Philipp Maierbb7a01c2018-02-01 12:32:57 +010091 const charstring c_mdcx3 := "MDCX 18983215 " & c_mgw_ep_rtpbridge & "1@" & c_mgw_domain & " MGCP 1.0\r\n";
Harald Welte00a067f2017-09-13 23:27:17 +020092 const charstring c_mdcx3_ret := "200 18983215 OK\r\n" &
93 "I: 1\n" &
94 "\n" &
95 "v=0\r\n" &
96 "o=- 1 23 IN IP4 0.0.0.0\r\n" &
97 "s=-\r\n" &
98 "c=IN IP4 0.0.0.0\r\n" &
99 "t=0 0\r\n" &
100 "m=audio 0 RTP/AVP 126\r\n" &
101 "a=rtpmap:126 AMR/8000\r\n" &
102 "a=ptime:20\r\n";
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100103 const charstring c_mdcx4 := "MDCX 18983216 " & c_mgw_ep_rtpbridge & "1@" & c_mgw_domain & " MGCP 1.0\r\n" &
Harald Welte00a067f2017-09-13 23:27:17 +0200104 "M: sendrecv\r" &
105 "C: 2\r\n" &
106 "I: 1\r\n" &
107 "L: p:20, a:AMR, nt:IN\r\n" &
108 "\n" &
109 "v=0\r\n" &
110 "o=- 1 23 IN IP4 0.0.0.0\r\n" &
Harald Welte2871d0b2017-09-14 22:42:12 +0800111 "s=-\r\n" &
Harald Welte00a067f2017-09-13 23:27:17 +0200112 "c=IN IP4 0.0.0.0\r\n" &
113 "t=0 0\r\n" &
114 "m=audio 4441 RTP/AVP 99\r\n" &
115 "a=rtpmap:99 AMR/8000\r\n" &
116 "a=ptime:40\r\n";
Harald Welte3c6ebb92017-09-16 00:56:57 +0800117 const charstring c_crcx510_ret := "510 23 FAIL\r\n"
Harald Welte00a067f2017-09-13 23:27:17 +0200118
119 log(c_auep);
120 log(dec_MgcpCommand(c_auep));
121
122 log(c_mdcx3);
123 log(dec_MgcpCommand(c_mdcx3));
124
125 log(c_mdcx3_ret);
126 log(dec_MgcpResponse(c_mdcx3_ret));
127
128 log(c_mdcx4);
129 log(dec_MgcpCommand(c_mdcx4));
Harald Welte3c6ebb92017-09-16 00:56:57 +0800130
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100131 log(ts_CRCX("23", c_mgw_ep_rtpbridge & "42@" & c_mgw_domain, "sendrecv", '1234'H));
132 log(enc_MgcpCommand(valueof(ts_CRCX("23", c_mgw_ep_rtpbridge & "42@" & c_mgw_domain, "sendrecv", '1234'H))));
Harald Welte3c6ebb92017-09-16 00:56:57 +0800133
134 log(c_crcx510_ret);
135 log(dec_MgcpResponse(c_crcx510_ret));
136 log(dec_MgcpMessage(c_crcx510_ret));
137 }
138
Harald Weltee636afd2017-09-17 16:24:09 +0800139 /* CRCX test ideas:
Harald Weltee0b331f2017-11-18 20:34:33 +0100140 * x without mandatory CallId
Harald Weltee636afd2017-09-17 16:24:09 +0800141 * - with forbidden parameters (e.g. Capabilities, PackageList, ...
142 * - CRCX with remote session description and without
143 *
144 * general ideas:
Harald Weltee0b331f2017-11-18 20:34:33 +0100145 * x packetization != 20ms
146 * x invalid mode
Harald Weltee636afd2017-09-17 16:24:09 +0800147 * x unsupported mode (517)
148 * x bidirectional mode before RemoteConnDesc: 527
149 * - invalid codec
Harald Weltee0b331f2017-11-18 20:34:33 +0100150 * x retransmission of same transaction
Harald Weltee636afd2017-09-17 16:24:09 +0800151 * - unsupported LocalConnectionOptions ("b", "a", "e", "gc", "s", "r", "k", ..)
152 */
153
Harald Welte21ba5572017-09-19 17:55:05 +0800154 /* build a receive template for receiving a MGCP message. You
155 * pass the MGCP response template in, and it will generate an
156 * MGCP_RecvFrom template that can match the primitives arriving on the
157 * MGCP_CodecPort */
Harald Weltee636afd2017-09-17 16:24:09 +0800158 function tr_MGCP_RecvFrom_R(template MgcpResponse resp) runs on dummy_CT return template MGCP_RecvFrom {
159 var template MGCP_RecvFrom mrf := {
Harald Welte55015362017-11-18 16:02:42 +0100160 connId := g_mgcp_conn_id,
Harald Weltee636afd2017-09-17 16:24:09 +0800161 remName := mp_remote_ip,
162 remPort := mp_remote_udp_port,
163 locName := mp_local_ip,
164 locPort := mp_local_udp_port,
165 msg := { response := resp }
166 }
167 return mrf;
168 }
169
170 /* Send a MGCP request + receive a (matching!) response */
171 function mgcp_transceive_mgw(template MgcpCommand cmd, template MgcpResponse resp := ?) runs on dummy_CT return MgcpResponse {
172 var MgcpMessage msg := { command := valueof(cmd) };
173 resp.line.trans_id := cmd.line.trans_id;
174 var template MGCP_RecvFrom mrt := tr_MGCP_RecvFrom_R(resp);
Harald Welte3c6ebb92017-09-16 00:56:57 +0800175 var MGCP_RecvFrom mrf;
176 timer T := 5.0;
177
Harald Welte55015362017-11-18 16:02:42 +0100178 MGCP.send(t_MGCP_Send(g_mgcp_conn_id, msg));
Harald Welte3c6ebb92017-09-16 00:56:57 +0800179 T.start;
180 alt {
Harald Weltee636afd2017-09-17 16:24:09 +0800181 [] MGCP.receive(mrt) -> value mrf { }
182 [] MGCP.receive(tr_MGCP_RecvFrom_R(?)) { setverdict(fail); }
Harald Welte3c6ebb92017-09-16 00:56:57 +0800183 [] MGCP.receive { repeat; }
184 [] T.timeout { setverdict(fail); }
185 }
186 T.stop;
Harald Weltee636afd2017-09-17 16:24:09 +0800187
188 if (isbound(mrf) and isbound(mrf.msg) and ischosen(mrf.msg.response)) {
189 return mrf.msg.response;
190 } else {
191 var MgcpResponse r := { line := { code := "999", trans_id := valueof(cmd.line.trans_id) } };
192 return r;
193 }
Harald Welte00a067f2017-09-13 23:27:17 +0200194 }
195
Harald Welteba62c8c2017-11-18 18:26:49 +0100196 function extract_conn_id(MgcpResponse resp) return MgcpConnectionId {
197 var integer i;
198 for (i := 0; i < lengthof(resp.params); i := i + 1) {
199 var MgcpParameter par := resp.params[i];
200 if (par.code == "I") {
201 return str2hex(par.val);
202 }
203 }
204 setverdict(fail);
205 return '00000000'H;
206 }
207
Harald Welte10889c12017-11-18 19:40:31 +0100208 function f_dlcx(MgcpEndpoint ep, template MgcpResponseCode ret_code, template charstring ret_val,
209 template MgcpCallId call_id := omit,
210 template MgcpConnectionId conn_id := omit) runs on dummy_CT {
Harald Welteba62c8c2017-11-18 18:26:49 +0100211 var template MgcpCommand cmd;
212 var MgcpResponse resp;
213 var template MgcpResponse rtmpl := {
214 line := {
Harald Welte10889c12017-11-18 19:40:31 +0100215 code := ret_code,
216 string := ret_val
Harald Welteba62c8c2017-11-18 18:26:49 +0100217 },
218 params := *,
219 sdp := *
220 };
Harald Weltec40e0c32017-11-18 19:08:22 +0100221 cmd := ts_DLCX(get_next_trans_id(), ep, call_id, conn_id);
Harald Welteba62c8c2017-11-18 18:26:49 +0100222 resp := mgcp_transceive_mgw(cmd, rtmpl);
223 }
224
Harald Welte10889c12017-11-18 19:40:31 +0100225 /* Send DLCX and expect OK response */
226 function f_dlcx_ok(MgcpEndpoint ep, template MgcpCallId call_id := omit,
227 template MgcpConnectionId conn_id := omit) runs on dummy_CT {
Harald Weltef91edf32017-12-28 14:16:21 +0100228 f_dlcx(ep, ("200","250"), "OK", call_id, conn_id);
Harald Welte10889c12017-11-18 19:40:31 +0100229 }
230
Harald Welteba62c8c2017-11-18 18:26:49 +0100231 /* Send DLCX and accept any response */
Harald Weltec40e0c32017-11-18 19:08:22 +0100232 function f_dlcx_ignore(MgcpEndpoint ep, template MgcpCallId call_id := omit,
Harald Welteba62c8c2017-11-18 18:26:49 +0100233 template MgcpConnectionId conn_id := omit) runs on dummy_CT {
Harald Welte10889c12017-11-18 19:40:31 +0100234 f_dlcx(ep, ?, *, call_id, conn_id);
Harald Welteba62c8c2017-11-18 18:26:49 +0100235 }
236
Harald Weltebb7523b2018-03-29 08:52:01 +0200237 type record HostPort {
238 charstring hostname,
239 integer portnr optional
240 }
241 type record RtpFlowData {
242 HostPort em, /* emulation side */
243 HostPort mgw, /* mgw side */
244 uint7_t pt,
245 charstring codec,
246 MgcpConnectionId mgcp_conn_id optional
247 }
248
Philipp Maier2321ef92018-06-27 17:52:04 +0200249 /* Create an RTP flow (bidirectional, or receive-only) */
250 function f_flow_create(RTPEM_CTRL_PT pt, MgcpEndpoint ep, MgcpCallId call_id, charstring mode, inout RtpFlowData flow,
Harald Weltebb7523b2018-03-29 08:52:01 +0200251 boolean one_phase := true)
252 runs on dummy_CT {
Harald Weltebb7523b2018-03-29 08:52:01 +0200253 var template MgcpCommand cmd;
254 var MgcpResponse resp;
255
256 /* bind local RTP emulation socket */
257 f_rtpem_bind(pt, flow.em.hostname, flow.em.portnr);
258
259 if (one_phase) {
Philipp Maier2321ef92018-06-27 17:52:04 +0200260 /* Connect flow to MGW using a CRCX that also contains an SDP
261 * part that tells the MGW where we are listening for RTP streams
262 * that come from the MGW. We get a fully working connection in
263 * one go. */
264
265 cmd := ts_CRCX(get_next_trans_id(), ep, mode, call_id);
Harald Welteedc88742018-03-29 18:01:16 +0200266 cmd.sdp := ts_SDP(flow.em.hostname, flow.em.hostname, "23", "42",
Harald Weltebb7523b2018-03-29 08:52:01 +0200267 flow.em.portnr, { int2str(flow.pt) },
268 { valueof(ts_SDP_rtpmap(flow.pt, flow.codec)),
269 valueof(ts_SDP_ptime(20)) });
270 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
271 flow.mgcp_conn_id := extract_conn_id(resp);
272 /* extract port number from response */
273 flow.mgw.portnr :=
274 resp.sdp.media_list[0].media_field.ports.port_number;
275 } else {
Philipp Maier2321ef92018-06-27 17:52:04 +0200276 /* Create a half-open connection only. We do not tell the MGW
277 * where it can send RTP streams to us. This means this
278 * connection will only be able to receive but can not send
279 * data back to us. In order to turn the connection in a fully
280 * bi-directional one, a separate MDCX is needed. */
281
282 cmd := ts_CRCX(get_next_trans_id(), ep, mode, call_id);
Harald Weltebb7523b2018-03-29 08:52:01 +0200283 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
284 flow.mgcp_conn_id := extract_conn_id(resp);
285 /* extract MGW-side port number from response */
286 flow.mgw.portnr :=
287 resp.sdp.media_list[0].media_field.ports.port_number;
Harald Weltebb7523b2018-03-29 08:52:01 +0200288 }
289 /* finally, connect the emulation-side RTP socket to the MGW */
290 f_rtpem_connect(pt, flow.mgw.hostname, flow.mgw.portnr);
291 }
292
Philipp Maier2321ef92018-06-27 17:52:04 +0200293 /* Modify an existing RTP flow */
294 function f_flow_modify(RTPEM_CTRL_PT pt, MgcpEndpoint ep, MgcpCallId call_id, charstring mode, inout RtpFlowData flow)
295 runs on dummy_CT {
296 var template MgcpCommand cmd;
297 var MgcpResponse resp;
298
299 /* rebind local RTP emulation socket to the new address */
300 f_rtpem_bind(pt, flow.em.hostname, flow.em.portnr);
301
302 /* connect MGW side RTP socket to the emulation-side RTP socket using SDP */
303 cmd := ts_MDCX(get_next_trans_id(), ep, mode, call_id, flow.mgcp_conn_id);
304 cmd.sdp := ts_SDP(flow.em.hostname, flow.em.hostname, "23", "42",
305 flow.em.portnr, { int2str(flow.pt) },
306 { valueof(ts_SDP_rtpmap(flow.pt, flow.codec)),
307 valueof(ts_SDP_ptime(20)) });
308 resp := mgcp_transceive_mgw(cmd, tr_MDCX_ACK);
309
310 /* extract MGW-side port number from response. (usually this
311 * will not change, but thats is up to the MGW) */
312 flow.mgw.portnr :=
313 resp.sdp.media_list[0].media_field.ports.port_number;
314
315 /* reconnect the emulation-side RTP socket to the MGW */
316 f_rtpem_connect(pt, flow.mgw.hostname, flow.mgw.portnr);
317 }
318
319 /* Delete an existing RTP flow */
320 function f_flow_delete(RTPEM_CTRL_PT pt, template MgcpEndpoint ep := omit, template MgcpCallId call_id := omit)
321 runs on dummy_CT {
322 var template MgcpCommand cmd;
323 var MgcpResponse resp;
324
325 /* Switch off RTP flow */
326 f_rtpem_mode(pt, RTPEM_MODE_NONE);
327
328 /* Delete connection on MGW (if needed) */
329 if (isvalue(call_id) and isvalue(ep)) {
330 f_sleep(0.1);
331 f_dlcx_ok(valueof(ep), call_id);
332 }
333 }
334
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100335 function f_crcx(charstring ep_prefix) runs on dummy_CT {
336 var MgcpEndpoint ep := ep_prefix & "2@" & c_mgw_domain;
Harald Weltee636afd2017-09-17 16:24:09 +0800337 var template MgcpCommand cmd;
338 var MgcpResponse resp;
Harald Welteba62c8c2017-11-18 18:26:49 +0100339 var MgcpCallId call_id := '1234'H;
Harald Weltee636afd2017-09-17 16:24:09 +0800340
Harald Welteedc45c12017-11-18 19:15:05 +0100341 f_init(ep);
Harald Weltee636afd2017-09-17 16:24:09 +0800342
Harald Welteba62c8c2017-11-18 18:26:49 +0100343 /* create the connection on the MGW */
344 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
Harald Welte9988d282017-11-18 19:22:00 +0100345 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
Harald Welteba62c8c2017-11-18 18:26:49 +0100346 extract_conn_id(resp);
347
348 /* clean-up */
349 f_dlcx_ok(ep, call_id);
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100350 }
Harald Welteba62c8c2017-11-18 18:26:49 +0100351
Philipp Maier45635f42018-06-05 17:28:02 +0200352 function f_crcx_no_lco(charstring ep_prefix) runs on dummy_CT {
353 var MgcpEndpoint ep := ep_prefix & "2@" & c_mgw_domain;
354 var template MgcpCommand cmd;
355 var MgcpResponse resp;
356 var MgcpCallId call_id := '1234'H;
357
358 f_init(ep);
359
360 /* create the connection on the MGW */
361 cmd := ts_CRCX_no_lco(get_next_trans_id(), ep, "recvonly", call_id);
362 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
363 extract_conn_id(resp);
364
365 /* clean-up */
366 f_dlcx_ok(ep, call_id);
367
368 /* See also OS#2658: Even when we omit the LCO information, we
369 expect the MGW to pick a sane payload type for us. This
370 payload type should be visible in the SDP of the response. */
Philipp Maier76a8d012018-06-21 10:03:13 +0200371 if (resp.sdp.media_list[0].media_field.fmts[0] != "0") {
Philipp Maier45635f42018-06-05 17:28:02 +0200372 setverdict(fail, "SDP contains unexpected codec");
373 }
374
375 /* See also OS#2658: We also expect the MGW to assign a port
376 number to us. */
377 if (isbound(resp.sdp.media_list[0].media_field.ports.port_number) == false) {
378 setverdict(fail, "SDP does not contain a port number");
379 }
380 }
381
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100382 /* test valid CRCX without SDP */
383 testcase TC_crcx() runs on dummy_CT {
384 f_crcx(c_mgw_ep_rtpbridge);
385 setverdict(pass);
386 }
387
Philipp Maier45635f42018-06-05 17:28:02 +0200388 /* test valid CRCX without SDP and LCO */
389 testcase TC_crcx_no_lco() runs on dummy_CT {
390 f_crcx_no_lco(c_mgw_ep_rtpbridge);
391 setverdict(pass);
392 }
393
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100394 /* test valid CRCX without SDP (older method without endpoint prefix) */
395 testcase TC_crcx_noprefix() runs on dummy_CT {
396 f_crcx("");
Harald Weltee636afd2017-09-17 16:24:09 +0800397 setverdict(pass);
398 }
399
400 /* test CRCX with unsupported mode, expect 517 */
401 testcase TC_crcx_unsupp_mode() runs on dummy_CT {
402 var template MgcpCommand cmd;
403 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100404 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
Harald Welteba62c8c2017-11-18 18:26:49 +0100405 var MgcpCallId call_id := '1233'H;
Harald Weltee636afd2017-09-17 16:24:09 +0800406 var template MgcpResponse rtmpl := tr_MgcpResp_Err("517");
407
Harald Welteedc45c12017-11-18 19:15:05 +0100408 f_init(ep);
Harald Weltee636afd2017-09-17 16:24:09 +0800409
Harald Welteba62c8c2017-11-18 18:26:49 +0100410 cmd := ts_CRCX(get_next_trans_id(), ep, "netwtest", call_id);
Harald Weltee636afd2017-09-17 16:24:09 +0800411 resp := mgcp_transceive_mgw(cmd, rtmpl);
412 setverdict(pass);
413 }
414
Harald Welte21ba5572017-09-19 17:55:05 +0800415 /* test CRCX with early bi-directional mode, expect 527 as
416 * bi-diretional media can only be established once both local and
417 * remote side are specified, see MGCP RFC */
Harald Weltee636afd2017-09-17 16:24:09 +0800418 testcase TC_crcx_early_bidir_mode() runs on dummy_CT {
419 var template MgcpCommand cmd;
420 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100421 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
Harald Welteba62c8c2017-11-18 18:26:49 +0100422 var MgcpCallId call_id := '1232'H;
Harald Weltee636afd2017-09-17 16:24:09 +0800423 var template MgcpResponse rtmpl := tr_MgcpResp_Err("527");
424
Harald Welteedc45c12017-11-18 19:15:05 +0100425 f_init(ep);
Harald Weltee636afd2017-09-17 16:24:09 +0800426
Harald Welteba62c8c2017-11-18 18:26:49 +0100427 cmd := ts_CRCX(get_next_trans_id(), ep, "sendrecv", call_id);
Harald Weltee636afd2017-09-17 16:24:09 +0800428 resp := mgcp_transceive_mgw(cmd, rtmpl);
429 setverdict(pass);
430 }
431
432 /* test CRCX with unsupported Parameters */
433 testcase TC_crcx_unsupp_param() runs on dummy_CT {
434 var template MgcpCommand cmd;
435 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100436 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
Harald Welteba62c8c2017-11-18 18:26:49 +0100437 var MgcpCallId call_id := '1231'H;
Harald Weltee636afd2017-09-17 16:24:09 +0800438 var template MgcpResponse rtmpl := tr_MgcpResp_Err("539");
439
Harald Welteedc45c12017-11-18 19:15:05 +0100440 f_init(ep);
Harald Weltee636afd2017-09-17 16:24:09 +0800441
Harald Welteba62c8c2017-11-18 18:26:49 +0100442 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
Harald Weltea01e38d2017-11-18 18:40:01 +0100443 /* osmo-bsc_mgcp/mgw doesn't implement notifications */
444 f_mgcp_par_append(cmd.params, MgcpParameter:{ "N", "foobar" });
445
Harald Weltee636afd2017-09-17 16:24:09 +0800446 resp := mgcp_transceive_mgw(cmd, rtmpl);
447 setverdict(pass);
448 }
449
450 /* test CRCX with missing CallId */
451 testcase TC_crcx_missing_callid() runs on dummy_CT {
452 var template MgcpCommand cmd;
453 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100454 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
Harald Weltef91edf32017-12-28 14:16:21 +0100455 var template MgcpResponse rtmpl := tr_MgcpResp_Err(("400","516"));
Harald Weltee636afd2017-09-17 16:24:09 +0800456
Harald Welteedc45c12017-11-18 19:15:05 +0100457 f_init(ep);
Harald Weltee636afd2017-09-17 16:24:09 +0800458
Harald Welteba62c8c2017-11-18 18:26:49 +0100459 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", '1230'H);
Harald Weltee636afd2017-09-17 16:24:09 +0800460 cmd.params := {
461 t_MgcpParConnMode("recvonly"),
462 t_MgcpParLocConnOpt("p:20")
463 }
464 resp := mgcp_transceive_mgw(cmd, rtmpl);
465 setverdict(pass);
Harald Welteba62c8c2017-11-18 18:26:49 +0100466
Harald Weltee636afd2017-09-17 16:24:09 +0800467 }
468
469 /* test CRCX with missing Mode */
470 testcase TC_crcx_missing_mode() runs on dummy_CT {
471 var template MgcpCommand cmd;
472 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100473 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
Harald Welteba62c8c2017-11-18 18:26:49 +0100474 var MgcpCallId call_id := '1229'H;
Harald Weltef91edf32017-12-28 14:16:21 +0100475 var template MgcpResponse rtmpl := tr_MgcpResp_Err(("400","517"));
Harald Weltee636afd2017-09-17 16:24:09 +0800476
Harald Welteedc45c12017-11-18 19:15:05 +0100477 f_init(ep);
Harald Weltee636afd2017-09-17 16:24:09 +0800478
Harald Welteba62c8c2017-11-18 18:26:49 +0100479 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
Harald Weltee636afd2017-09-17 16:24:09 +0800480 cmd.params := {
Harald Welteba62c8c2017-11-18 18:26:49 +0100481 ts_MgcpParCallId(call_id),
Harald Weltee636afd2017-09-17 16:24:09 +0800482 t_MgcpParLocConnOpt("p:20")
483 }
484 resp := mgcp_transceive_mgw(cmd, rtmpl);
485 setverdict(pass);
486 }
487
488 /* test CRCX with unsupported packetization interval */
489 testcase TC_crcx_unsupp_packet_intv() runs on dummy_CT {
490 var template MgcpCommand cmd;
491 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100492 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
Harald Welteba62c8c2017-11-18 18:26:49 +0100493 var MgcpCallId call_id := '1228'H;
Harald Welte0d198612017-11-18 19:58:31 +0100494 var template MgcpResponse rtmpl := tr_MgcpResp_Err("535");
Harald Weltee636afd2017-09-17 16:24:09 +0800495
Harald Welteedc45c12017-11-18 19:15:05 +0100496 f_init(ep);
Harald Weltee636afd2017-09-17 16:24:09 +0800497
Harald Welteba62c8c2017-11-18 18:26:49 +0100498 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
Harald Weltea01e38d2017-11-18 18:40:01 +0100499 cmd.params[2] := t_MgcpParLocConnOpt("p:111");
Harald Weltee636afd2017-09-17 16:24:09 +0800500 resp := mgcp_transceive_mgw(cmd, rtmpl);
501 setverdict(pass);
502 }
503
504 /* test CRCX with illegal double presence of local connection option */
505 testcase TC_crcx_illegal_double_lco() runs on dummy_CT {
506 var template MgcpCommand cmd;
507 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100508 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
Harald Welteba62c8c2017-11-18 18:26:49 +0100509 var MgcpCallId call_id := '1227'H;
Harald Weltee636afd2017-09-17 16:24:09 +0800510 var template MgcpResponse rtmpl := tr_MgcpResp_Err("524");
511
Harald Welteedc45c12017-11-18 19:15:05 +0100512 f_init(ep);
Harald Weltee636afd2017-09-17 16:24:09 +0800513
Harald Welteba62c8c2017-11-18 18:26:49 +0100514 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
Harald Weltea01e38d2017-11-18 18:40:01 +0100515 /* p:20 is permitted only once and not twice! */
516 cmd.params[2] := t_MgcpParLocConnOpt("p:20, a:AMR, p:20");
Harald Weltee636afd2017-09-17 16:24:09 +0800517 resp := mgcp_transceive_mgw(cmd, rtmpl);
518 setverdict(pass);
519 }
520
521 /* test valid CRCX with valid SDP */
522 testcase TC_crcx_sdp() runs on dummy_CT {
523 var template MgcpCommand cmd;
524 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100525 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
Harald Welteba62c8c2017-11-18 18:26:49 +0100526 var MgcpCallId call_id := '1226'H;
Harald Weltee636afd2017-09-17 16:24:09 +0800527
Harald Welteedc45c12017-11-18 19:15:05 +0100528 f_init(ep);
Harald Weltee636afd2017-09-17 16:24:09 +0800529
Harald Welteba62c8c2017-11-18 18:26:49 +0100530 cmd := ts_CRCX(get_next_trans_id(), ep, "sendrecv", call_id);
Harald Weltee636afd2017-09-17 16:24:09 +0800531 cmd.sdp := ts_SDP("127.0.0.1", "127.0.0.2", "23", "42", 2344, { "98" },
532 { valueof(ts_SDP_rtpmap(98, "AMR/8000")),
533 valueof(ts_SDP_ptime(20)) });
Harald Welte9988d282017-11-18 19:22:00 +0100534 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
Philipp Maierdffa6a42018-02-02 11:55:44 +0100535
536 /* clean-up */
537 f_dlcx_ok(ep, call_id);
538
Harald Weltee636afd2017-09-17 16:24:09 +0800539 setverdict(pass);
540 }
541
Philipp Maier5e06cee2018-02-01 18:28:08 +0100542 /* test valid wildcarded CRCX */
543 testcase TC_crcx_wildcarded() runs on dummy_CT {
544 var template MgcpCommand cmd;
545 var MgcpResponse resp;
546 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "*@" & c_mgw_domain;
547 var MgcpCallId call_id := '1234'H;
548 var MgcpEndpoint ep_assigned;
549 f_init();
550
551 /* create the connection on the MGW */
552 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
553 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
554 extract_conn_id(resp);
555
556 /* extract endpoint name we got assigned by the MGW */
557 var MgcpMessage resp_msg := {
558 response := resp
559 }
560 if (f_mgcp_find_param(resp_msg, "Z", ep_assigned) == false) {
561 setverdict(fail, "No SpecificEndpointName in MGCP response", resp);
562 }
563
564 /* clean-up */
565 f_dlcx_ok(ep_assigned, call_id);
566
567 setverdict(pass);
568 }
569
570 /* test valid wildcarded CRCX */
571 testcase TC_crcx_wildcarded_exhaust() runs on dummy_CT {
572 const integer n_endpoints := 32;
573 var integer i;
574 var template MgcpCommand cmd;
575 var MgcpResponse resp;
576 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "*@" & c_mgw_domain;
577 var MgcpCallId call_id := '1234'H;
578 var MgcpEndpoint ep_assigned[n_endpoints];
579 f_init();
580
581 /* Exhaust all endpoint resources on the virtual trunk */
582 for (i := 0; i < n_endpoints; i := i+1) {
583 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
584 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
585
586 /* Make sure we got a connection id */
587 extract_conn_id(resp);
588
589 var MgcpMessage resp_msg := {
590 response := resp
591 }
592 if (f_mgcp_find_param(resp_msg, "Z", ep_assigned[i]) == false) {
593 setverdict(fail, "No SpecificEndpointName in MGCP response", resp);
594 }
595 }
596
597 /* Try to allocate one more endpoint, which should fail */
598 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
599 var template MgcpResponse rtmpl := tr_MgcpResp_Err("403");
600 resp := mgcp_transceive_mgw(cmd, rtmpl);
601 setverdict(pass);
602
603 /* clean-up */
604 for (i := 0; i < n_endpoints; i := i+1) {
605 f_dlcx_ok(ep_assigned[i], call_id);
606 }
607 setverdict(pass);
608 }
609
Harald Weltee636afd2017-09-17 16:24:09 +0800610 /* TODO: various SDP related bits */
611
612
613 /* TODO: CRCX with X-Osmux */
614 /* TODO: double CRCX without force_realloc */
615
616 /* TODO: MDCX (various) */
617
618 /* TODO: MDCX without CRCX first */
619 testcase TC_mdcx_without_crcx() runs on dummy_CT {
620 var template MgcpCommand cmd;
621 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100622 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "3@" & c_mgw_domain;
Harald Welteba62c8c2017-11-18 18:26:49 +0100623 var MgcpCallId call_id := '1225'H;
Harald Weltee636afd2017-09-17 16:24:09 +0800624 var template MgcpResponse rtmpl := {
625 line := {
626 /* TODO: accept/enforce better error? */
627 code := "400",
628 string := ?
629 },
630 params:= { },
631 sdp := omit
632 };
633
Harald Welteedc45c12017-11-18 19:15:05 +0100634 f_init(ep);
Harald Weltee636afd2017-09-17 16:24:09 +0800635
Harald Welte2bcfd3a2017-11-18 22:14:35 +0100636 cmd := ts_MDCX(get_next_trans_id(), ep, "sendrecv", call_id, call_id);
Harald Weltee636afd2017-09-17 16:24:09 +0800637 cmd.sdp := ts_SDP("127.0.0.1", "127.0.0.2", "23", "42", 2344, { "98" },
638 { valueof(ts_SDP_rtpmap(98, "AMR/8000")),
639 valueof(ts_SDP_ptime(20)) });
640 resp := mgcp_transceive_mgw(cmd, rtmpl);
641 setverdict(pass);
642 }
643
644 /* DLCX without CRCX first */
645 testcase TC_dlcx_without_crcx() runs on dummy_CT {
646 var template MgcpCommand cmd;
647 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100648 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "4@" & c_mgw_domain;
Harald Weltee636afd2017-09-17 16:24:09 +0800649 var template MgcpResponse rtmpl := {
650 line := {
Harald Weltef91edf32017-12-28 14:16:21 +0100651 code := ("400", "515"),
Harald Weltee636afd2017-09-17 16:24:09 +0800652 string := ?
653 },
654 params:= { },
655 sdp := omit
656 };
657
Harald Welteedc45c12017-11-18 19:15:05 +0100658 f_init(ep);
Harald Weltee636afd2017-09-17 16:24:09 +0800659
Harald Welteedc45c12017-11-18 19:15:05 +0100660 cmd := ts_DLCX(get_next_trans_id(), ep, '41234'H);
Harald Weltee636afd2017-09-17 16:24:09 +0800661 resp := mgcp_transceive_mgw(cmd, rtmpl);
662 setverdict(pass);
663 }
664
Philipp Maier8a3dc922018-02-02 14:55:12 +0100665 /* test valid wildcarded MDCX */
666 testcase TC_mdcx_wildcarded() runs on dummy_CT {
667 /* Note: A wildcarded MDCX is not allowed, so we expect the
668 * MGW to reject this request */
669 var template MgcpCommand cmd;
670 var MgcpResponse resp;
671 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "*@" & c_mgw_domain;
672 var MgcpCallId call_id := '1225'H;
673 var template MgcpResponse rtmpl := {
674 line := {
675 /* TODO: accept/enforce better error? */
676 code := "507",
677 string := ?
678 },
679 params:= { },
680 sdp := omit
681 };
682
683 f_init(ep);
684
685 cmd := ts_MDCX(get_next_trans_id(), ep, "sendrecv", call_id, call_id);
686 cmd.sdp := ts_SDP("127.0.0.1", "127.0.0.2", "23", "42", 2344, { "98" },
687 { valueof(ts_SDP_rtpmap(98, "AMR/8000")),
688 valueof(ts_SDP_ptime(20)) });
689 resp := mgcp_transceive_mgw(cmd, rtmpl);
690 setverdict(pass);
691 }
692
693 /* test valid wildcarded DLCX */
694 testcase TC_dlcx_wildcarded() runs on dummy_CT {
695 /* Note: A wildcarded DLCX is specified, but our MGW does not
696 * support this feature so we expect the MGW to reject the
697 * request */
698 var template MgcpCommand cmd;
699 var MgcpResponse resp;
700 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "*@" & c_mgw_domain;
701 var template MgcpResponse rtmpl := {
702 line := {
703 code := "507",
704 string := ?
705 },
706 params:= { },
707 sdp := omit
708 };
709
710 f_init(ep);
711
712 cmd := ts_DLCX(get_next_trans_id(), ep, '41234'H);
713 resp := mgcp_transceive_mgw(cmd, rtmpl);
714 setverdict(pass);
715 }
716
Harald Welte79181ff2017-11-18 19:26:11 +0100717 /* Test (valid) CRCX followed by (valid) DLCX containig EP+CallId+ConnId */
718 testcase TC_crcx_and_dlcx_ep_callid_connid() runs on dummy_CT {
719 var template MgcpCommand cmd;
720 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100721 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "5@" & c_mgw_domain;
Harald Welte79181ff2017-11-18 19:26:11 +0100722 var MgcpCallId call_id := '51234'H;
723
724 f_init(ep);
725
726 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
727 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
728
729 f_dlcx_ok(ep, call_id, extract_conn_id(resp));
730
731 setverdict(pass);
732 }
733
Harald Welte646ecdb2017-12-28 03:21:57 +0100734 function f_crcx_and_dlcx_ep_callid_connid(MgcpEndpoint ep, MgcpCallId call_id) runs on dummy_CT {
735 var template MgcpCommand cmd;
736 var MgcpResponse resp;
737
738 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
739 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
740
741 f_dlcx_ok(ep, call_id, extract_conn_id(resp));
742
743 setverdict(pass);
744 }
745
746 testcase TC_crcx_dlcx_30ep() runs on dummy_CT {
747 var MgcpEndpoint ep;
748 var MgcpCallId call_id;
749 var integer ep_nr;
750
751 f_init();
752
753 for (ep_nr := 1; ep_nr < 30; ep_nr := ep_nr+1) {
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100754 ep := c_mgw_ep_rtpbridge & hex2str(int2hex(ep_nr, 2)) & "@" & c_mgw_domain;
Harald Welte646ecdb2017-12-28 03:21:57 +0100755 call_id := int2hex(ep_nr, 2) & '1234'H;
756 f_crcx_and_dlcx_ep_callid_connid(ep, call_id);
757 }
758 }
759
Harald Welte79181ff2017-11-18 19:26:11 +0100760 /* Test (valid) CRCX followed by (valid) DLCX containing EP+CallId */
761 testcase TC_crcx_and_dlcx_ep_callid() runs on dummy_CT {
Harald Welte5b4c44e2017-09-17 16:35:27 +0800762 var template MgcpCommand cmd;
763 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100764 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "5@" & c_mgw_domain;
Harald Welte6d167f82017-11-18 19:41:35 +0100765 var MgcpCallId call_id := '51233'H;
Harald Welte5b4c44e2017-09-17 16:35:27 +0800766
Harald Welteedc45c12017-11-18 19:15:05 +0100767 f_init(ep);
Harald Welte5b4c44e2017-09-17 16:35:27 +0800768
Harald Welteba62c8c2017-11-18 18:26:49 +0100769 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
Harald Welte9988d282017-11-18 19:22:00 +0100770 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
Harald Welte5b4c44e2017-09-17 16:35:27 +0800771
Harald Welteba62c8c2017-11-18 18:26:49 +0100772 f_dlcx_ok(ep, call_id);
Harald Welte5b4c44e2017-09-17 16:35:27 +0800773
774 setverdict(pass);
775 }
776
Harald Welte79181ff2017-11-18 19:26:11 +0100777 /* Test (valid) CRCX followed by (valid) DLCX containing EP */
778 testcase TC_crcx_and_dlcx_ep() runs on dummy_CT {
779 var template MgcpCommand cmd;
780 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100781 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "5@" & c_mgw_domain;
Harald Welte6d167f82017-11-18 19:41:35 +0100782 var MgcpCallId call_id := '51232'H;
Harald Welte79181ff2017-11-18 19:26:11 +0100783
784 f_init(ep);
785
786 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
787 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
788
789 f_dlcx_ok(ep);
790
791 setverdict(pass);
792 }
793
794
Harald Welte6d167f82017-11-18 19:41:35 +0100795 /* CRCX + DLCX of valid endpoint but invalid call-id */
796 testcase TC_crcx_and_dlcx_ep_callid_inval() runs on dummy_CT {
797 var template MgcpCommand cmd;
798 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100799 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "5@" & c_mgw_domain;
Harald Welte6d167f82017-11-18 19:41:35 +0100800 var MgcpCallId call_id := '51231'H;
801
802 f_init(ep);
803
804 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
805 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
806
807 f_dlcx(ep, "516", *, 'ffff'H);
808
809 setverdict(pass);
810 }
811
812
813 /* CRCX + DLCX of valid endpoint and call-id but invalid conn-id */
814 testcase TC_crcx_and_dlcx_ep_callid_connid_inval() runs on dummy_CT {
815 var template MgcpCommand cmd;
816 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100817 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "5@" & c_mgw_domain;
Harald Welte6d167f82017-11-18 19:41:35 +0100818 var MgcpCallId call_id := '51230'H;
819
820 f_init(ep);
821
822 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
823 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
824
825 f_dlcx(ep, "515", *, call_id, 'ffff'H);
826
827 setverdict(pass);
828 }
829
830
Harald Weltee636afd2017-09-17 16:24:09 +0800831 /* TODO: Double-DLCX (retransmission) */
Harald Weltef53f1642017-11-18 19:57:11 +0100832 testcase TC_crcx_and_dlcx_retrans() runs on dummy_CT {
833 var template MgcpCommand cmd;
834 var MgcpResponse resp;
Philipp Maierbb7a01c2018-02-01 12:32:57 +0100835 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "5@" & c_mgw_domain;
Harald Weltef53f1642017-11-18 19:57:11 +0100836 var MgcpCallId call_id := '51229'H;
837 var template MgcpResponse rtmpl := {
838 line := {
839 code := "200",
840 string := "OK"
841 },
842 params:= { },
843 sdp := omit
844 };
845
846 f_init(ep);
847
848 cmd := ts_CRCX(get_next_trans_id(), ep, "recvonly", call_id);
849 resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK);
850
851 cmd := ts_DLCX(get_next_trans_id(), ep, call_id);
852 resp := mgcp_transceive_mgw(cmd, rtmpl);
853 resp := mgcp_transceive_mgw(cmd, rtmpl);
854
855 setverdict(pass);
856 }
857
Harald Weltebb7523b2018-03-29 08:52:01 +0200858 template (value) RtpFlowData t_RtpFlow(charstring host_a, charstring host_b, uint7_t pt,
859 charstring codec) := {
860 em := {
861 hostname := host_a,
862 portnr := omit
863 },
864 mgw := {
865 hostname := host_b,
866 portnr := omit
867 },
868 pt := pt,
869 codec := codec
870 }
Harald Weltef53f1642017-11-18 19:57:11 +0100871
Harald Weltebb7523b2018-03-29 08:52:01 +0200872 /* transmit RTP streams between two RTP Emulations back-to-back; expect no loss */
873 testcase TC_rtpem_selftest() runs on dummy_CT {
874 var RtpemStats stats[2];
875 var integer local_port := 10000;
876 var integer local_port2 := 20000;
877
878 f_init();
879
880 f_rtpem_bind(RTPEM[0], "127.0.0.1", local_port);
881 f_rtpem_bind(RTPEM[1], "127.0.0.2", local_port2);
882
883 f_rtpem_connect(RTPEM[0], "127.0.0.2", local_port2);
884 f_rtpem_connect(RTPEM[1], "127.0.0.1", local_port);
885
886 log("=== starting");
887 f_rtpem_mode(RTPEM[0], RTPEM_MODE_BIDIR);
888 f_rtpem_mode(RTPEM[1], RTPEM_MODE_BIDIR);
889
890 f_sleep(5.0);
891
892 log("=== stopping");
893 f_rtpem_mode(RTPEM[1], RTPEM_MODE_RXONLY);
894 f_rtpem_mode(RTPEM[0], RTPEM_MODE_RXONLY);
895 f_sleep(0.5);
896 f_rtpem_mode(RTPEM[1], RTPEM_MODE_NONE);
897 f_rtpem_mode(RTPEM[0], RTPEM_MODE_NONE);
898
899 stats[0] := f_rtpem_stats_get(RTPEM[0]);
900 stats[1] := f_rtpem_stats_get(RTPEM[1]);
901 if (not f_rtpem_stats_compare(stats[0], stats[1])) {
902 setverdict(fail, "RTP endpoint statistics don't match");
903 }
904 setverdict(pass);
905 }
906
Philipp Maier2321ef92018-06-27 17:52:04 +0200907 /* Create one half open connection in receive-only mode. The MGW must accept
908 * the packets but must not send any. */
909 testcase TC_one_crcx_receive_only_rtp() runs on dummy_CT {
910 var RtpFlowData flow;
911 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "1@" & c_mgw_domain;
912 var MgcpCallId call_id := '1225'H;
913 var RtpemStats stats;
914
915 f_init(ep);
916 flow := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 112, "AMR/8000/1"));
917 flow.em.portnr := 10000;
918 f_flow_create(RTPEM[0], ep, call_id, "recvonly", flow, false);
919
920 f_rtpem_mode(RTPEM[0], RTPEM_MODE_TXONLY);
921 f_sleep(1.0);
922 f_flow_delete(RTPEM[0], ep, call_id);
923
924 stats := f_rtpem_stats_get(RTPEM[0]);
925
926 if (stats.num_pkts_tx < 40) {
927 setverdict(fail);
928 }
929 if (stats.bytes_payload_tx < 190) {
930 setverdict(fail);
931 }
932 if (stats.num_pkts_rx != 0) {
933 setverdict(fail);
934 }
935 if (stats.num_pkts_rx_err_seq != 0) {
936 setverdict(fail);
937 }
938 if (stats.num_pkts_rx_err_ts != 0) {
939 setverdict(fail);
940 }
941 if (stats.num_pkts_rx_err_disabled != 0) {
942 setverdict(fail);
943 }
944
945 setverdict(pass);
946 }
947
948 /* Create one connection in loopback mode, test if the RTP packets are
949 * actually reflected */
950 testcase TC_one_crcx_loopback_rtp() runs on dummy_CT {
951 var RtpFlowData flow;
952 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "1@" & c_mgw_domain;
953 var MgcpCallId call_id := '1225'H;
954 var RtpemStats stats;
955
956 f_init(ep);
957 flow := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 111, "GSM-HR-08/8000/1"));
958 flow.em.portnr := 10000;
959 f_flow_create(RTPEM[0], ep, call_id, "loopback", flow);
960
961 f_rtpem_mode(RTPEM[0], RTPEM_MODE_BIDIR);
962 f_sleep(1.0);
963 f_flow_delete(RTPEM[0], ep, call_id);
964
965 stats := f_rtpem_stats_get(RTPEM[0]);
966
967 if (stats.num_pkts_tx != stats.num_pkts_rx) {
968 setverdict(fail);
969 }
970 if (stats.bytes_payload_tx != stats.bytes_payload_rx) {
971 setverdict(fail);
972 }
973 if (stats.num_pkts_rx_err_seq != 0) {
974 setverdict(fail);
975 }
976 if (stats.num_pkts_rx_err_ts != 0) {
977 setverdict(fail);
978 }
979 if (stats.num_pkts_rx_err_disabled != 0) {
980 setverdict(fail);
981 }
982
983 setverdict(pass);
984 }
985
986 function f_TC_two_crcx_and_rtp(boolean bidir) runs on dummy_CT {
Harald Weltebb7523b2018-03-29 08:52:01 +0200987 var RtpFlowData flow[2];
988 var RtpemStats stats[2];
Harald Weltebb7523b2018-03-29 08:52:01 +0200989 var MgcpResponse resp;
990 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
991 var MgcpCallId call_id := '1226'H;
Philipp Maier2321ef92018-06-27 17:52:04 +0200992 var integer tolerance := 0;
Harald Weltebb7523b2018-03-29 08:52:01 +0200993
994 f_init(ep);
995
996 /* from us to MGW */
997 flow[0] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 98, "AMR/8000"));
998 /* bind local RTP emulation sockets */
999 flow[0].em.portnr := 10000;
Philipp Maier2321ef92018-06-27 17:52:04 +02001000 f_flow_create(RTPEM[0], ep, call_id, "sendrecv", flow[0]);
Harald Weltebb7523b2018-03-29 08:52:01 +02001001
1002 /* from MGW back to us */
1003 flow[1] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 98, "AMR/8000"));
1004 flow[1].em.portnr := 20000;
Philipp Maier2321ef92018-06-27 17:52:04 +02001005 f_flow_create(RTPEM[1], ep, call_id, "sendrecv", flow[1]);
1006
1007 if (bidir) {
1008 f_rtpem_mode(RTPEM[0], RTPEM_MODE_BIDIR);
1009 f_rtpem_mode(RTPEM[1], RTPEM_MODE_BIDIR);
1010
1011 /* Note: When we test bidirectional we may
1012 * loose packets during switch off because
1013 * both ends are transmitting and we only
1014 * can switch them off one by one. */
1015 tolerance := 3;
1016 } else {
1017 f_rtpem_mode(RTPEM[0], RTPEM_MODE_RXONLY);
1018 f_rtpem_mode(RTPEM[1], RTPEM_MODE_TXONLY);
1019 }
1020
1021 f_sleep(1.0);
1022
1023 f_flow_delete(RTPEM[1]);
1024 f_flow_delete(RTPEM[0], ep, call_id);
1025
1026 stats[0] := f_rtpem_stats_get(RTPEM[0]);
1027 stats[1] := f_rtpem_stats_get(RTPEM[1]);
1028 if (not f_rtpem_stats_compare(stats[0], stats[1], tolerance)) {
1029 setverdict(fail, "RTP endpoint statistics don't match");
1030 }
1031
1032 setverdict(pass);
1033 }
1034
1035 /* create two local RTP emulations; create two connections on MGW EP, exchange some data */
1036 testcase TC_two_crcx_and_rtp() runs on dummy_CT {
1037 f_TC_two_crcx_and_rtp(false);
1038 }
1039
1040 /* create two local RTP emulations; create two connections on MGW EP,
1041 * exchange some data in both directions */
1042 testcase TC_two_crcx_and_rtp_bidir() runs on dummy_CT {
1043 f_TC_two_crcx_and_rtp(true);
1044 }
1045
1046 /* create two local RTP emulations and pass data in both directions */
1047 testcase TC_two_crcx_mdcx_and_rtp() runs on dummy_CT {
1048 var RtpFlowData flow[2];
1049 var RtpemStats stats[2];
1050 var MgcpResponse resp;
1051 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
1052 var MgcpCallId call_id := '1227'H;
1053 var integer num_pkts_tx[2];
1054 var integer temp;
1055
1056 f_init(ep);
1057
1058 /* Create the first connection in receive only mode */
1059 flow[0] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 3, "GSM/8000/1"));
1060 flow[0].em.portnr := 10000;
1061 f_flow_create(RTPEM[0], ep, call_id, "recvonly", flow[0], false);
1062
1063 /* Create the second connection. This connection will be also
1064 * in receive only mode */
1065 flow[1] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 3, "GSM/8000/1"));
1066 flow[1].em.portnr := 20000;
1067 f_flow_create(RTPEM[1], ep, call_id, "recvonly", flow[1], false);
1068
1069 /* The first leg starts transmitting */
1070 f_rtpem_mode(RTPEM[0], RTPEM_MODE_TXONLY);
1071 f_sleep(0.5);
1072 stats[0] := f_rtpem_stats_get(RTPEM[0]);
1073 if (stats[0].num_pkts_rx_err_disabled != 0) {
1074 setverdict(fail, "received packets from MGW on recvonly connection");
1075 }
1076 stats[1] := f_rtpem_stats_get(RTPEM[1]);
1077 if (stats[1].num_pkts_rx_err_disabled != 0) {
1078 setverdict(fail, "received packets from MGW on recvonly connection");
1079 }
1080
1081 /* The second leg starts transmitting a little later */
1082 f_rtpem_mode(RTPEM[1], RTPEM_MODE_TXONLY);
1083 f_sleep(1.0);
1084 stats[0] := f_rtpem_stats_get(RTPEM[0]);
1085 if (stats[0].num_pkts_rx_err_disabled != 0) {
1086 setverdict(fail, "received packets from MGW on recvonly connection");
1087 }
1088 stats[1] := f_rtpem_stats_get(RTPEM[1]);
1089 if (stats[1].num_pkts_rx_err_disabled != 0) {
1090 setverdict(fail, "received packets from MGW on recvonly connection");
1091 }
1092
1093 /* The first leg will now be switched into bidirectional
1094 * mode, but we do not expect any data comming back yet. */
1095 f_flow_modify(RTPEM[0], ep, call_id, "sendrecv", flow[0]);
1096 f_sleep(0.5);
1097 stats[0] := f_rtpem_stats_get(RTPEM[0]);
1098 if (stats[1].num_pkts_rx_err_disabled != 0) {
1099 setverdict(fail, "received packets from MGW on recvonly connection");
1100 }
1101 stats[1] := f_rtpem_stats_get(RTPEM[1]);
1102 if (stats[1].num_pkts_rx_err_disabled != 0) {
1103 setverdict(fail, "received packets from MGW on recvonly connection");
1104 }
1105
1106 /* When the second leg is switched into bidirectional mode
1107 * as well, then the MGW will connect the two together and
1108 * we should see RTP streams passing through from both ends. */
1109 f_rtpem_mode(RTPEM[0], RTPEM_MODE_BIDIR);
1110 f_rtpem_mode(RTPEM[1], RTPEM_MODE_BIDIR);
1111 stats[0] := f_rtpem_stats_get(RTPEM[0]);
1112 num_pkts_tx[0] := stats[0].num_pkts_tx
1113 stats[1] := f_rtpem_stats_get(RTPEM[1]);
1114 num_pkts_tx[1] := stats[1].num_pkts_tx
1115
1116 f_flow_modify(RTPEM[1], ep, call_id, "sendrecv", flow[1]);
1117 f_sleep(2.0);
1118
1119 stats[0] := f_rtpem_stats_get(RTPEM[0]);
1120 stats[1] := f_rtpem_stats_get(RTPEM[1]);
1121
1122 temp := stats[0].num_pkts_tx - num_pkts_tx[0] - stats[1].num_pkts_rx;
1123 if (temp > 3 or temp < -3) {
1124 setverdict(fail, "number of packets not within normal parameters");
1125 }
1126
1127 temp := stats[1].num_pkts_tx - num_pkts_tx[1] - stats[0].num_pkts_rx;
1128 if (temp > 3 or temp < -3) {
1129 setverdict(fail, "number of packets not within normal parameters");
1130 }
1131
1132 /* Tear down */
1133 f_flow_delete(RTPEM[0]);
1134 f_flow_delete(RTPEM[1], ep, call_id);
1135 setverdict(pass);
1136 }
1137
1138 /* Test what happens when two RTP streams from different sources target
1139 * a single connection. Is the unsolicited stream properly ignored? */
1140 testcase TC_two_crcx_and_unsolicited_rtp() runs on dummy_CT {
1141 var RtpFlowData flow[2];
1142 var RtpemStats stats[2];
1143 var MgcpResponse resp;
1144 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
1145 var MgcpCallId call_id := '1234321326'H;
1146 var integer unsolicited_port := 10002;
1147
1148 f_init(ep);
1149
1150 /* from us to MGW */
1151 flow[0] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 98, "AMR/8000"));
1152 /* bind local RTP emulation sockets */
1153 flow[0].em.portnr := 10000;
1154 f_flow_create(RTPEM[0], ep, call_id, "sendrecv", flow[0]);
1155
1156 /* from MGW back to us */
1157 flow[1] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 98, "AMR/8000"));
1158 flow[1].em.portnr := 20000;
1159 f_flow_create(RTPEM[1], ep, call_id, "sendrecv", flow[1]);
Harald Weltebb7523b2018-03-29 08:52:01 +02001160
1161 f_rtpem_mode(RTPEM[1], RTPEM_MODE_RXONLY);
1162 f_rtpem_mode(RTPEM[0], RTPEM_MODE_TXONLY);
1163
Philipp Maier2321ef92018-06-27 17:52:04 +02001164 f_sleep(0.5);
Harald Weltebb7523b2018-03-29 08:52:01 +02001165
Philipp Maier2321ef92018-06-27 17:52:04 +02001166 /* Start inserting unsolicited RTP packets */
1167 f_rtpem_bind(RTPEM[2], "127.0.0.1", unsolicited_port);
1168 f_rtpem_connect(RTPEM[2], "127.0.0.1", flow[0].mgw.portnr);
1169 f_rtpem_mode(RTPEM[2], RTPEM_MODE_TXONLY);
1170
1171 f_sleep(0.5);
1172
1173 f_flow_delete(RTPEM[0]);
1174 f_flow_delete(RTPEM[1], ep, call_id);
Harald Weltebb7523b2018-03-29 08:52:01 +02001175
1176 stats[0] := f_rtpem_stats_get(RTPEM[0]);
1177 stats[1] := f_rtpem_stats_get(RTPEM[1]);
1178 if (not f_rtpem_stats_compare(stats[0], stats[1])) {
1179 setverdict(fail, "RTP endpoint statistics don't match");
1180 }
1181
Harald Weltebb7523b2018-03-29 08:52:01 +02001182 setverdict(pass);
Philipp Maier2321ef92018-06-27 17:52:04 +02001183 }
Harald Weltebb7523b2018-03-29 08:52:01 +02001184
Philipp Maier2321ef92018-06-27 17:52:04 +02001185 /* Test a handover situation. We first create two connections transmit
1186 * some data bidirectionally. Then we will simulate a handover situation. */
1187 testcase TC_two_crcx_and_one_mdcx_rtp_ho() runs on dummy_CT {
1188 var RtpFlowData flow[2];
1189 var RtpemStats stats[3];
1190 var MgcpResponse resp;
1191 var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "4@" & c_mgw_domain;
1192 var MgcpCallId call_id := '76338'H;
1193 var integer port_old;
1194
1195 f_init(ep);
1196
1197 /* First connection (BTS) */
1198 flow[0] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 110, "GSM-EFR/8000"));
1199 /* bind local RTP emulation sockets */
1200 flow[0].em.portnr := 10000;
1201 f_flow_create(RTPEM[0], ep, call_id, "sendrecv", flow[0]);
1202
1203 /* Second connection (PBX) */
1204 flow[1] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 110, "GSM-EFR/8000"));
1205 flow[1].em.portnr := 20000;
1206 f_flow_create(RTPEM[1], ep, call_id, "sendrecv", flow[1]);
1207
1208 /* Normal rtp flow for one second */
1209 f_rtpem_mode(RTPEM[0], RTPEM_MODE_BIDIR);
1210 f_rtpem_mode(RTPEM[1], RTPEM_MODE_BIDIR);
1211 f_sleep(1.0);
1212
1213 /* Now switch the flow over to a new port (BTS) */
1214 port_old := flow[0].em.portnr;
1215 flow[0].em.portnr := 10002;
1216 f_flow_modify(RTPEM[0], ep, call_id, "sendrecv", flow[0]);
1217
1218 /* When handing over a call, the old source may still keep
1219 * transmitting for a while. We simulate this by injecting
1220 * some unsolicited packets on the behalf of the old source,
1221 * (old remote port) */
1222 f_rtpem_bind(RTPEM[2], "127.0.0.1", port_old);
1223 f_rtpem_connect(RTPEM[2], "127.0.0.1", flow[0].mgw.portnr);
1224 f_rtpem_mode(RTPEM[2], RTPEM_MODE_TXONLY);
1225 f_sleep(1.0);
1226 f_rtpem_mode(RTPEM[2], RTPEM_MODE_NONE);
1227 f_sleep(1.0);
1228
1229 /* Terminate call */
1230 f_flow_delete(RTPEM[0]);
1231 f_flow_delete(RTPEM[1], ep, call_id);
1232
1233 stats[0] := f_rtpem_stats_get(RTPEM[0]);
1234 stats[1] := f_rtpem_stats_get(RTPEM[1]);
1235 if (not f_rtpem_stats_compare(stats[0], stats[1], 5)) {
1236 setverdict(fail, "RTP endpoint statistics don't match");
1237 }
1238 stats[2] := f_rtpem_stats_get(RTPEM[2]);
1239 if (stats[2].num_pkts_rx_err_disabled != 0) {
1240 setverdict(fail, "received packets on old leg after handover");
1241 }
1242
1243 setverdict(pass);
Harald Weltebb7523b2018-03-29 08:52:01 +02001244 }
Harald Weltef53f1642017-11-18 19:57:11 +01001245
Harald Weltee636afd2017-09-17 16:24:09 +08001246 /* TODO: Double-DLCX (no retransmission) */
1247
1248
1249
1250 /* TODO: AUEP (various) */
1251 /* TODO: RSIP (various) */
1252 /* TODO: RQNT (various) */
1253 /* TODO: EPCF (various) */
1254 /* TODO: AUCX (various) */
1255 /* TODO: invalid verb (various) */
1256
Harald Welte00a067f2017-09-13 23:27:17 +02001257 control {
1258 execute(TC_selftest());
Harald Welte3c6ebb92017-09-16 00:56:57 +08001259 execute(TC_crcx());
Philipp Maier45635f42018-06-05 17:28:02 +02001260 execute(TC_crcx_no_lco());
Philipp Maierbb7a01c2018-02-01 12:32:57 +01001261 execute(TC_crcx_noprefix());
Harald Weltee636afd2017-09-17 16:24:09 +08001262 execute(TC_crcx_unsupp_mode());
1263 execute(TC_crcx_early_bidir_mode());
1264 execute(TC_crcx_unsupp_param());
1265 execute(TC_crcx_missing_callid());
1266 execute(TC_crcx_missing_mode());
1267 execute(TC_crcx_unsupp_packet_intv());
1268 execute(TC_crcx_illegal_double_lco());
1269 execute(TC_crcx_sdp());
Philipp Maier5e06cee2018-02-01 18:28:08 +01001270 execute(TC_crcx_wildcarded());
1271 execute(TC_crcx_wildcarded_exhaust());
Harald Weltee636afd2017-09-17 16:24:09 +08001272 execute(TC_mdcx_without_crcx());
1273 execute(TC_dlcx_without_crcx());
Philipp Maier8a3dc922018-02-02 14:55:12 +01001274 execute(TC_mdcx_wildcarded());
1275 execute(TC_dlcx_wildcarded());
Harald Welte79181ff2017-11-18 19:26:11 +01001276 execute(TC_crcx_and_dlcx_ep_callid_connid());
1277 execute(TC_crcx_and_dlcx_ep_callid());
1278 execute(TC_crcx_and_dlcx_ep());
Harald Welte6d167f82017-11-18 19:41:35 +01001279 execute(TC_crcx_and_dlcx_ep_callid_inval());
1280 execute(TC_crcx_and_dlcx_ep_callid_connid_inval());
Harald Weltef53f1642017-11-18 19:57:11 +01001281 execute(TC_crcx_and_dlcx_retrans());
Harald Welte33d82162017-12-28 03:21:57 +01001282
1283 execute(TC_crcx_dlcx_30ep());
Harald Weltebb7523b2018-03-29 08:52:01 +02001284
1285 execute(TC_rtpem_selftest());
Philipp Maier2321ef92018-06-27 17:52:04 +02001286
1287 execute(TC_one_crcx_receive_only_rtp());
1288 execute(TC_one_crcx_loopback_rtp());
Harald Weltebb7523b2018-03-29 08:52:01 +02001289 execute(TC_two_crcx_and_rtp());
Philipp Maier2321ef92018-06-27 17:52:04 +02001290 execute(TC_two_crcx_and_rtp_bidir());
1291 execute(TC_two_crcx_mdcx_and_rtp());
1292 execute(TC_two_crcx_and_unsolicited_rtp());
1293 execute(TC_two_crcx_and_one_mdcx_rtp_ho());
Harald Welte00a067f2017-09-13 23:27:17 +02001294 }
1295}