blob: 85556c1f571d090e1334fe8f517711b338250660 [file] [log] [blame]
Harald Welte876bf9d2018-01-21 19:28:59 +01001module MNCC_Emulation {
2
3/* MNCC Emulation, runs on top of MNCC_CodecPort. It multiplexes/demultiplexes
4 * the individual calls, so there can be separate TTCN-3 components handling
5 * each of the calls
6 *
7 * The MNCC_Emulation.main() function processes MNCC primitives from the MNCC
8 * socket via the MNCC_CodecPort, and dispatches them to the per-connection components.
9 *
10 * Outbound MNCC connections are initiated by sending a MNCC_Call_Req primitive
11 * to the component running the MNCC_Emulation.main() function.
12 *
13 * For each new inbound connections, the MnccOps.create_cb() is called. It can create
14 * or resolve a TTCN-3 component, and returns a component reference to which that inbound
15 * connection is routed/dispatched.
16 *
17 * If a pre-existing component wants to register to handle a future inbound call, it can
18 * do so by registering an "expect" with the expected destination phone number. This is e.g. useful
19 * if you are simulating BSC + MNCC, and first trigger a connection from BSC side in a
20 * component which then subsequently should also handle the MNCC emulation.
21 *
22 * Inbound Unit Data messages (such as are dispatched to the MnccOps.unitdata_cb() callback,
23 * which is registered with an argument to the main() function below.
24 *
25 * (C) 2018 by Harald Welte <laforge@gnumonks.org>
26 * All rights reserved.
27 *
28 * Released under the terms of GNU General Public License, Version 2 or
29 * (at your option) any later version.
Harald Welte34b5a952019-05-27 11:54:11 +020030 *
31 * SPDX-License-Identifier: GPL-2.0-or-later
Harald Welte876bf9d2018-01-21 19:28:59 +010032 */
33
34
35import from Osmocom_Types all;
36import from MNCC_CodecPort all;
37import from MNCC_Types all;
38import from UD_Types all;
39
Neels Hofmeyr5e3b5d92019-11-28 20:34:57 +010040modulepar {
Vadim Yanitskiy9ff47802021-10-28 14:55:58 +030041 int mp_mncc_version := 8;
Neels Hofmeyr5e3b5d92019-11-28 20:34:57 +010042}
43
Harald Welte876bf9d2018-01-21 19:28:59 +010044/* General "base class" component definition, of which specific implementations
45 * derive themselves by means of the "extends" feature */
46type component MNCC_ConnHdlr {
47 /* ports towards MNCC Emulator core / call dispatchar */
48 port MNCC_Conn_PT MNCC;
49 port MNCCEM_PROC_PT MNCC_PROC;
50}
51
52/* Auxiliary primitive that can happen on the port between per-connection client and this dispatcher */
53type enumerated MNCC_Conn_Prim {
54 /* MNCC tell us that connection was released */
55 MNCC_CONN_PRIM_DISC_IND,
56 /* we tell MNCC to release connection */
57 MNCC_CONN_PRIM_DISC_REQ
58}
59
60type record MNCC_Conn_Req {
61 MNCC_PDU mncc
62}
63
64/* port between individual per-connection components and this dispatcher */
65type port MNCC_Conn_PT message {
66 inout MNCC_PDU, MNCC_Conn_Prim, MNCC_Conn_Req;
67} with { extension "internal" };
68
69
70/* represents a single MNCC call */
71type record ConnectionData {
72 /* reference to the instance of the per-connection component */
73 MNCC_ConnHdlr comp_ref,
74 integer mncc_call_id
75}
76
77type component MNCC_Emulation_CT {
78 /* UNIX DOMAIN socket on the bottom side, using primitives */
79 port MNCC_CODEC_PT MNCC;
80 /* MNCC port to the per-connection clients */
81 port MNCC_Conn_PT MNCC_CLIENT;
82
83 /* use 16 as this is also the number of SCCP connections that SCCP_Emulation can handle */
84 var ConnectionData MnccCallTable[16];
85
86 /* pending expected incoming connections */
87 var ExpectData MnccExpectTable[8];
88 /* procedure based port to register for incoming connections */
89 port MNCCEM_PROC_PT MNCC_PROC;
90
91 var integer g_mncc_ud_id;
92};
93
94private function f_call_id_known(uint32_t mncc_call_id)
95runs on MNCC_Emulation_CT return boolean {
96 var integer i;
97 for (i := 0; i < sizeof(MnccCallTable); i := i+1) {
98 if (MnccCallTable[i].mncc_call_id == mncc_call_id){
99 return true;
100 }
101 }
102 return false;
103}
104
105private function f_comp_known(MNCC_ConnHdlr client)
106runs on MNCC_Emulation_CT return boolean {
107 var integer i;
108 for (i := 0; i < sizeof(MnccCallTable); i := i+1) {
109 if (MnccCallTable[i].comp_ref == client) {
110 return true;
111 }
112 }
113 return false;
114}
115
116/* resolve component reference by connection ID */
117private function f_comp_by_call_id(uint32_t mncc_call_id)
118runs on MNCC_Emulation_CT return MNCC_ConnHdlr {
119 var integer i;
120 for (i := 0; i < sizeof(MnccCallTable); i := i+1) {
121 if (MnccCallTable[i].mncc_call_id == mncc_call_id) {
122 return MnccCallTable[i].comp_ref;
123 }
124 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200125 setverdict(fail, "MNCC Call table not found by MNCC Call ID ", mncc_call_id);
126 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100127}
128
129/* resolve connection ID by component reference */
130private function f_call_id_by_comp(MNCC_ConnHdlr client)
131runs on MNCC_Emulation_CT return integer {
132 for (var integer i := 0; i < sizeof(MnccCallTable); i := i+1) {
133 if (MnccCallTable[i].comp_ref == client) {
134 return MnccCallTable[i].mncc_call_id;
135 }
136 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200137 setverdict(fail, "MNCC Call table not found by component ", client);
138 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100139}
140
141private function f_gen_call_id()
142runs on MNCC_Emulation_CT return integer {
143 var uint32_t call_id;
144
145 do {
146 call_id := float2int(rnd()*4294967296.0);
147 } while (f_call_id_known(call_id) == true);
148
149 return call_id;
150}
151
Daniel Willmannff6abf02018-02-02 18:26:05 +0100152private function f_expect_table_init()
153runs on MNCC_Emulation_CT {
154 for (var integer i := 0; i < sizeof(MnccExpectTable); i := i+1) {
155 MnccExpectTable[i].dest_number := omit;
156 MnccExpectTable[i].vc_conn := null;
157 }
158}
159
Harald Welte876bf9d2018-01-21 19:28:59 +0100160private function f_call_table_init()
161runs on MNCC_Emulation_CT {
162 for (var integer i := 0; i < sizeof(MnccCallTable); i := i+1) {
163 MnccCallTable[i].comp_ref := null;
164 MnccCallTable[i].mncc_call_id := -1;
165 }
166}
167
168private function f_call_table_add(MNCC_ConnHdlr comp_ref, uint32_t mncc_call_id)
169runs on MNCC_Emulation_CT {
170 for (var integer i := 0; i < sizeof(MnccCallTable); i := i+1) {
171 if (MnccCallTable[i].mncc_call_id == -1) {
172 MnccCallTable[i].comp_ref := comp_ref;
173 MnccCallTable[i].mncc_call_id := mncc_call_id;
174 log("Added conn table entry ", i, comp_ref, mncc_call_id);
175 return;
176 }
177 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200178 testcase.stop("MNCC Call table full!");
Harald Welte876bf9d2018-01-21 19:28:59 +0100179}
180
181private function f_call_table_del(uint32_t mncc_call_id)
182runs on MNCC_Emulation_CT {
183 for (var integer i := 0; i < sizeof(MnccCallTable); i := i+1) {
184 if (MnccCallTable[i].mncc_call_id == mncc_call_id) {
185 log("Deleted conn table entry ", i,
186 MnccCallTable[i].comp_ref, mncc_call_id);
187 MnccCallTable[i].mncc_call_id := -1;
188 MnccCallTable[i].comp_ref := null;
189 return
190 }
191 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200192 setverdict(fail, "MNCC Call table attempt to delete non-existant ", mncc_call_id);
193 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100194}
195
196
Harald Welteafec4712018-03-19 22:52:17 +0100197private function f_connect(charstring sock) runs on MNCC_Emulation_CT {
Harald Welte876bf9d2018-01-21 19:28:59 +0100198 var UD_connect_result res;
199 timer T := 5.0;
200
201 T.start;
202 MNCC.send(UD_connect:{sock, -1});
203 alt {
204 [] MNCC.receive(UD_connect_result:?) -> value res {
205 if (ispresent(res.result) and ispresent(res.result.result_code) and res.result.result_code == ERROR) {
206 setverdict(fail, "Error connecting to MNCC socket", res);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200207 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100208 } else {
209 g_mncc_ud_id := res.id;
210 }
211 }
212 [] T.timeout {
213 setverdict(fail, "Timeout connecting to MNCC socket");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200214 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100215 }
216 }
217}
218
Harald Welteafec4712018-03-19 22:52:17 +0100219private function f_listen(charstring sock) runs on MNCC_Emulation_CT {
220 var UD_listen_result res;
221 var UD_connected udc;
222 timer T := 5.0;
223
224 T.start;
225 MNCC.send(UD_listen:{sock});
226 alt {
227 [] MNCC.receive(UD_listen_result:?) -> value res {
228 if (ispresent(res.result) and ispresent(res.result.result_code) and res.result.result_code == ERROR) {
229 setverdict(fail, "Error listening to MNCC socket", res);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200230 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100231 } else {
232 g_mncc_ud_id := res.id;
233 }
234 }
235 [] T.timeout {
236 setverdict(fail, "Timeout listening to MNCC socket");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200237 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100238 }
239 }
240
241 T.start;
242 alt {
243 [] MNCC.receive(UD_connected:?) -> value udc {
244 g_mncc_ud_id := res.id;
245 }
246 [] T.timeout {
247 setverdict(fail, "Timeout waiting for MNCC connection");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200248 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100249 }
250 }
251}
252
Harald Welte876bf9d2018-01-21 19:28:59 +0100253/* call-back type, to be provided by specific implementation; called when new SCCP connection
254 * arrives */
255type function MnccCreateCallback(MNCC_PDU conn_ind, charstring id)
256runs on MNCC_Emulation_CT return MNCC_ConnHdlr;
257
258type function MnccUnitdataCallback(MNCC_PDU mncc)
259runs on MNCC_Emulation_CT return template MNCC_PDU;
260
261type record MnccOps {
262 MnccCreateCallback create_cb,
263 MnccUnitdataCallback unitdata_cb
264}
265
Harald Welteafec4712018-03-19 22:52:17 +0100266function main(MnccOps ops, charstring id, charstring sock, boolean role_server := false)
267runs on MNCC_Emulation_CT {
Harald Welte876bf9d2018-01-21 19:28:59 +0100268
Pau Espin Pedrol563b3d02020-09-09 20:19:52 +0200269 if (not set_MNCC_version(mp_mncc_version)) {
270 setverdict(fail, "Failed configuring MNCC enc/dec to version ", mp_mncc_version);
271 return;
272 }
273
Harald Welteafec4712018-03-19 22:52:17 +0100274 if (role_server) {
275 f_listen(sock);
Neels Hofmeyr5e3b5d92019-11-28 20:34:57 +0100276 MNCC.send(t_SD_MNCC(g_mncc_ud_id, ts_MNCC_HELLO(version := mp_mncc_version)));
Harald Welteafec4712018-03-19 22:52:17 +0100277 } else {
278 f_connect(sock);
279 }
Daniel Willmannff6abf02018-02-02 18:26:05 +0100280 f_expect_table_init();
Harald Welte876bf9d2018-01-21 19:28:59 +0100281 f_call_table_init();
282
283 while (true) {
284 var MNCC_send_data sd;
285 var MNCC_Conn_Req creq;
286 var MNCC_ConnHdlr vc_conn;
287 var MNCC_PDU mncc;
288 var MNCC_ConnHdlr vc_hdlr;
289 var charstring dest_nr;
Neels Hofmeyr2edc2cd2021-07-27 22:32:30 +0200290 var uint32_t mncc_call_id;
Harald Welte876bf9d2018-01-21 19:28:59 +0100291
292 alt {
293 /* MNCC -> Client: UNIT-DATA (connectionless SCCP) from a BSC */
294 [] MNCC.receive(t_SD_MNCC_MSGT(g_mncc_ud_id, MNCC_SOCKET_HELLO)) -> value sd {
295 /* Connectionless Procedures like HELLO */
296 var template MNCC_PDU resp;
297 resp := ops.unitdata_cb.apply(sd.data);
298 if (isvalue(resp)) {
299 MNCC.send(t_SD_MNCC(g_mncc_ud_id, resp));
300 }
301 }
302
303 /* MNCC -> Client: Release Indication / confirmation */
304 [] MNCC.receive(t_SD_MNCC_MSGT(g_mncc_ud_id, (MNCC_REL_IND, MNCC_REL_CNF))) -> value sd {
305 var uint32_t call_id := f_mncc_get_call_id(sd.data);
306 /* forward to respective client */
307 vc_conn := f_comp_by_call_id(call_id);
308 MNCC_CLIENT.send(sd.data) to vc_conn;
309 /* remove from call table */
310 f_call_table_del(call_id);
311 }
312
313 /* MNCC -> Client: call related messages */
314 [] MNCC.receive(t_SD_MNCC_MSGT(g_mncc_ud_id, ?)) -> value sd {
315 var uint32_t call_id := f_mncc_get_call_id(sd.data);
316
317 if (f_call_id_known(call_id)) {
318 vc_conn := f_comp_by_call_id(call_id);
319 MNCC_CLIENT.send(sd.data) to vc_conn;
320 } else {
321 /* TODO: Only accept this for SETUP.req? */
322 vc_conn := ops.create_cb.apply(sd.data, id)
323 /* store mapping between client components and SCCP connectionId */
324 f_call_table_add(vc_conn, call_id);
325 /* handle user payload */
326 MNCC_CLIENT.send(sd.data) to vc_conn;
327 }
328 }
329
330 /* Client -> MNCC Socket: RELEASE.ind or RELEASE.cnf: forward + drop call table entry */
331 [] MNCC_CLIENT.receive(MNCC_PDU:{msg_type := (MNCC_REL_IND, MNCC_REL_CNF), u:=?}) -> value mncc sender vc_conn {
332 var integer call_id := f_call_id_by_comp(vc_conn);
333 /* forward to MNCC socket */
334 MNCC.send(t_SD_MNCC(g_mncc_ud_id, mncc));
335 /* remove from call table */
336 f_call_table_del(call_id);
337 }
338
Harald Welteafec4712018-03-19 22:52:17 +0100339 /* Client -> MNCC Socket: Normal message */
340 [] MNCC_CLIENT.receive(MNCC_PDU:?) -> value mncc sender vc_conn {
341 if (mncc.msg_type == MNCC_SETUP_REQ and not role_server) {
342 /* ConnHdlr -> MNCC Server: SETUP.req: add to call table */
343 f_call_table_add(vc_conn, f_mncc_get_call_id(mncc));
344 } else if (mncc.msg_type == MNCC_SETUP_IND and role_server) {
345 /* ConnHdlr -> MNCC Client: SETUP.ind: add to call table */
346 f_call_table_add(vc_conn, f_mncc_get_call_id(mncc));
347 }
Harald Welte812f7a42018-01-27 00:49:18 +0100348 /* forward to MNCC socket */
349 MNCC.send(t_SD_MNCC(g_mncc_ud_id, mncc));
350 }
351
Harald Welte876bf9d2018-01-21 19:28:59 +0100352 [] MNCC_CLIENT.receive(MNCC_PDU:?) -> value mncc sender vc_conn {
353 /* forward to MNCC socket */
354 MNCC.send(t_SD_MNCC(g_mncc_ud_id, mncc));
355 }
356
357
358 /* Client -> us: procedure call to register expect */
359 [] MNCC_PROC.getcall(MNCCEM_register:{?,?}) -> param(dest_nr, vc_hdlr) {
360 f_create_expect(dest_nr, vc_hdlr);
Harald Weltee32ad992018-05-31 22:17:46 +0200361 MNCC_PROC.reply(MNCCEM_register:{dest_nr, vc_hdlr}) to vc_hdlr;
Harald Welte876bf9d2018-01-21 19:28:59 +0100362 }
363
Neels Hofmeyr2edc2cd2021-07-27 22:32:30 +0200364 [] MNCC_PROC.getcall(MNCCEM_change_connhdlr:{?,?}) -> param(mncc_call_id, vc_hdlr) {
365 f_call_table_del(mncc_call_id);
366 f_call_table_add(vc_hdlr, mncc_call_id);
367 MNCC_PROC.reply(MNCCEM_change_connhdlr:{mncc_call_id, vc_hdlr}) to vc_hdlr;
368 }
Harald Welte876bf9d2018-01-21 19:28:59 +0100369 }
370 }
371}
372
373private function f_mgcp_ep_extract_cic(charstring inp) return integer {
374 var charstring local_part := regexp(inp, "(*)@*", 0);
375 return hex2int(str2hex(local_part));
376
377}
378
379/***********************************************************************
380 * "Expect" Handling (mapping for expected incoming MNCC calls from IUT)
381 ***********************************************************************/
382
383/* data about an expected future incoming connection */
384type record ExpectData {
385 /* destination number based on which we can match it */
386 charstring dest_number optional,
387 /* component reference for this connection */
388 MNCC_ConnHdlr vc_conn
389}
390
391/* procedure based port to register for incoming calls */
392signature MNCCEM_register(in charstring dest_nr, in MNCC_ConnHdlr hdlr);
Neels Hofmeyr2edc2cd2021-07-27 22:32:30 +0200393signature MNCCEM_change_connhdlr(in uint32_t mncc_call_id, in MNCC_ConnHdlr hdlr);
Harald Welte876bf9d2018-01-21 19:28:59 +0100394
395type port MNCCEM_PROC_PT procedure {
Neels Hofmeyr2edc2cd2021-07-27 22:32:30 +0200396 inout MNCCEM_register, MNCCEM_change_connhdlr;
Harald Welte876bf9d2018-01-21 19:28:59 +0100397} with { extension "internal" };
398
399/* CreateCallback that can be used as create_cb and will use the expectation table */
400function ExpectedCreateCallback(MNCC_PDU conn_ind, charstring id)
401runs on MNCC_Emulation_CT return MNCC_ConnHdlr {
402 var MNCC_ConnHdlr ret := null;
403 var charstring dest_number;
404 var integer i;
405
Harald Welte9edea882018-03-24 22:32:20 +0100406 if (not ischosen(conn_ind.u.signal) or
407 (conn_ind.msg_type != MNCC_SETUP_IND and conn_ind.msg_type != MNCC_SETUP_REQ)) {
408 setverdict(fail, "MNCC ExpectedCreateCallback needs MNCC_SETUP_{IND,REQ}");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200409 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100410 return ret;
411 }
412 dest_number := conn_ind.u.signal.called.number;
413
414 for (i := 0; i < sizeof(MnccExpectTable); i:= i+1) {
415 if (not ispresent(MnccExpectTable[i].dest_number)) {
416 continue;
417 }
418 if (dest_number == MnccExpectTable[i].dest_number) {
419 ret := MnccExpectTable[i].vc_conn;
420 /* release this entry to be used again */
421 MnccExpectTable[i].dest_number := omit;
422 MnccExpectTable[i].vc_conn := null;
423 log("Found MnccExpect[", i, "] for ", dest_number, " handled at ", ret);
424 /* return the component reference */
425 return ret;
426 }
427 }
428 setverdict(fail, "Couldn't find MnccExpect for incoming call ", dest_number);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200429 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100430 return ret;
431}
432
433/* server/emulation side function to create expect */
434private function f_create_expect(charstring dest_number, MNCC_ConnHdlr hdlr)
435runs on MNCC_Emulation_CT {
436 var integer i;
437 for (i := 0; i < sizeof(MnccExpectTable); i := i+1) {
438 if (not ispresent(MnccExpectTable[i].dest_number)) {
439 MnccExpectTable[i].dest_number := dest_number;
440 MnccExpectTable[i].vc_conn := hdlr;
441 log("Created MnccExpect[", i, "] for ", dest_number, " to be handled at ", hdlr);
442 return;
443 }
444 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200445 testcase.stop("No space left in MnccMnccExpectTable");
Harald Welte876bf9d2018-01-21 19:28:59 +0100446}
447
448/* client/conn_hdlr side function to use procedure port to create expect in emulation */
449function f_create_mncc_expect(charstring dest_number) runs on MNCC_ConnHdlr {
450 MNCC_PROC.call(MNCCEM_register:{dest_number, self}) {
451 [] MNCC_PROC.getreply(MNCCEM_register:{?,?}) {};
452 }
453}
454
Neels Hofmeyr2edc2cd2021-07-27 22:32:30 +0200455/* Move MNCC handling for a given call id to another MNCC_ConnHdlr test component. */
456function f_mncc_change_connhdlr(uint32_t mncc_call_id) runs on MNCC_ConnHdlr {
457 MNCC_PROC.call(MNCCEM_change_connhdlr:{mncc_call_id, self}) {
458 [] MNCC_PROC.getreply(MNCCEM_change_connhdlr:{?,?}) {};
459 }
460}
461
Harald Welte876bf9d2018-01-21 19:28:59 +0100462function DummyUnitdataCallback(MNCC_PDU mncc)
463runs on MNCC_Emulation_CT return template MNCC_PDU {
464 log("Ignoring MNCC ", mncc);
465 return omit;
466}
467
468}