blob: 94ed1c48132891be914f4e19a6ad37020345a072 [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.
30 */
31
32
33import from Osmocom_Types all;
34import from MNCC_CodecPort all;
35import from MNCC_Types all;
36import from UD_Types all;
37
38/* General "base class" component definition, of which specific implementations
39 * derive themselves by means of the "extends" feature */
40type component MNCC_ConnHdlr {
41 /* ports towards MNCC Emulator core / call dispatchar */
42 port MNCC_Conn_PT MNCC;
43 port MNCCEM_PROC_PT MNCC_PROC;
44}
45
46/* Auxiliary primitive that can happen on the port between per-connection client and this dispatcher */
47type enumerated MNCC_Conn_Prim {
48 /* MNCC tell us that connection was released */
49 MNCC_CONN_PRIM_DISC_IND,
50 /* we tell MNCC to release connection */
51 MNCC_CONN_PRIM_DISC_REQ
52}
53
54type record MNCC_Conn_Req {
55 MNCC_PDU mncc
56}
57
58/* port between individual per-connection components and this dispatcher */
59type port MNCC_Conn_PT message {
60 inout MNCC_PDU, MNCC_Conn_Prim, MNCC_Conn_Req;
61} with { extension "internal" };
62
63
64/* represents a single MNCC call */
65type record ConnectionData {
66 /* reference to the instance of the per-connection component */
67 MNCC_ConnHdlr comp_ref,
68 integer mncc_call_id
69}
70
71type component MNCC_Emulation_CT {
72 /* UNIX DOMAIN socket on the bottom side, using primitives */
73 port MNCC_CODEC_PT MNCC;
74 /* MNCC port to the per-connection clients */
75 port MNCC_Conn_PT MNCC_CLIENT;
76
77 /* use 16 as this is also the number of SCCP connections that SCCP_Emulation can handle */
78 var ConnectionData MnccCallTable[16];
79
80 /* pending expected incoming connections */
81 var ExpectData MnccExpectTable[8];
82 /* procedure based port to register for incoming connections */
83 port MNCCEM_PROC_PT MNCC_PROC;
84
85 var integer g_mncc_ud_id;
86};
87
88private function f_call_id_known(uint32_t mncc_call_id)
89runs on MNCC_Emulation_CT return boolean {
90 var integer i;
91 for (i := 0; i < sizeof(MnccCallTable); i := i+1) {
92 if (MnccCallTable[i].mncc_call_id == mncc_call_id){
93 return true;
94 }
95 }
96 return false;
97}
98
99private function f_comp_known(MNCC_ConnHdlr client)
100runs on MNCC_Emulation_CT return boolean {
101 var integer i;
102 for (i := 0; i < sizeof(MnccCallTable); i := i+1) {
103 if (MnccCallTable[i].comp_ref == client) {
104 return true;
105 }
106 }
107 return false;
108}
109
110/* resolve component reference by connection ID */
111private function f_comp_by_call_id(uint32_t mncc_call_id)
112runs on MNCC_Emulation_CT return MNCC_ConnHdlr {
113 var integer i;
114 for (i := 0; i < sizeof(MnccCallTable); i := i+1) {
115 if (MnccCallTable[i].mncc_call_id == mncc_call_id) {
116 return MnccCallTable[i].comp_ref;
117 }
118 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200119 setverdict(fail, "MNCC Call table not found by MNCC Call ID ", mncc_call_id);
120 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100121}
122
123/* resolve connection ID by component reference */
124private function f_call_id_by_comp(MNCC_ConnHdlr client)
125runs on MNCC_Emulation_CT return integer {
126 for (var integer i := 0; i < sizeof(MnccCallTable); i := i+1) {
127 if (MnccCallTable[i].comp_ref == client) {
128 return MnccCallTable[i].mncc_call_id;
129 }
130 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200131 setverdict(fail, "MNCC Call table not found by component ", client);
132 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100133}
134
135private function f_gen_call_id()
136runs on MNCC_Emulation_CT return integer {
137 var uint32_t call_id;
138
139 do {
140 call_id := float2int(rnd()*4294967296.0);
141 } while (f_call_id_known(call_id) == true);
142
143 return call_id;
144}
145
Daniel Willmannff6abf02018-02-02 18:26:05 +0100146private function f_expect_table_init()
147runs on MNCC_Emulation_CT {
148 for (var integer i := 0; i < sizeof(MnccExpectTable); i := i+1) {
149 MnccExpectTable[i].dest_number := omit;
150 MnccExpectTable[i].vc_conn := null;
151 }
152}
153
Harald Welte876bf9d2018-01-21 19:28:59 +0100154private function f_call_table_init()
155runs on MNCC_Emulation_CT {
156 for (var integer i := 0; i < sizeof(MnccCallTable); i := i+1) {
157 MnccCallTable[i].comp_ref := null;
158 MnccCallTable[i].mncc_call_id := -1;
159 }
160}
161
162private function f_call_table_add(MNCC_ConnHdlr comp_ref, uint32_t mncc_call_id)
163runs on MNCC_Emulation_CT {
164 for (var integer i := 0; i < sizeof(MnccCallTable); i := i+1) {
165 if (MnccCallTable[i].mncc_call_id == -1) {
166 MnccCallTable[i].comp_ref := comp_ref;
167 MnccCallTable[i].mncc_call_id := mncc_call_id;
168 log("Added conn table entry ", i, comp_ref, mncc_call_id);
169 return;
170 }
171 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200172 testcase.stop("MNCC Call table full!");
Harald Welte876bf9d2018-01-21 19:28:59 +0100173}
174
175private function f_call_table_del(uint32_t mncc_call_id)
176runs on MNCC_Emulation_CT {
177 for (var integer i := 0; i < sizeof(MnccCallTable); i := i+1) {
178 if (MnccCallTable[i].mncc_call_id == mncc_call_id) {
179 log("Deleted conn table entry ", i,
180 MnccCallTable[i].comp_ref, mncc_call_id);
181 MnccCallTable[i].mncc_call_id := -1;
182 MnccCallTable[i].comp_ref := null;
183 return
184 }
185 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200186 setverdict(fail, "MNCC Call table attempt to delete non-existant ", mncc_call_id);
187 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100188}
189
190
Harald Welteafec4712018-03-19 22:52:17 +0100191private function f_connect(charstring sock) runs on MNCC_Emulation_CT {
Harald Welte876bf9d2018-01-21 19:28:59 +0100192 var UD_connect_result res;
193 timer T := 5.0;
194
195 T.start;
196 MNCC.send(UD_connect:{sock, -1});
197 alt {
198 [] MNCC.receive(UD_connect_result:?) -> value res {
199 if (ispresent(res.result) and ispresent(res.result.result_code) and res.result.result_code == ERROR) {
200 setverdict(fail, "Error connecting to MNCC socket", res);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200201 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100202 } else {
203 g_mncc_ud_id := res.id;
204 }
205 }
206 [] T.timeout {
207 setverdict(fail, "Timeout connecting to MNCC socket");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200208 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100209 }
210 }
211}
212
Harald Welteafec4712018-03-19 22:52:17 +0100213private function f_listen(charstring sock) runs on MNCC_Emulation_CT {
214 var UD_listen_result res;
215 var UD_connected udc;
216 timer T := 5.0;
217
218 T.start;
219 MNCC.send(UD_listen:{sock});
220 alt {
221 [] MNCC.receive(UD_listen_result:?) -> value res {
222 if (ispresent(res.result) and ispresent(res.result.result_code) and res.result.result_code == ERROR) {
223 setverdict(fail, "Error listening to MNCC socket", res);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200224 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100225 } else {
226 g_mncc_ud_id := res.id;
227 }
228 }
229 [] T.timeout {
230 setverdict(fail, "Timeout listening to MNCC socket");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200231 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100232 }
233 }
234
235 T.start;
236 alt {
237 [] MNCC.receive(UD_connected:?) -> value udc {
238 g_mncc_ud_id := res.id;
239 }
240 [] T.timeout {
241 setverdict(fail, "Timeout waiting for MNCC connection");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200242 mtc.stop;
Harald Welteafec4712018-03-19 22:52:17 +0100243 }
244 }
245}
246
Harald Welte876bf9d2018-01-21 19:28:59 +0100247/* call-back type, to be provided by specific implementation; called when new SCCP connection
248 * arrives */
249type function MnccCreateCallback(MNCC_PDU conn_ind, charstring id)
250runs on MNCC_Emulation_CT return MNCC_ConnHdlr;
251
252type function MnccUnitdataCallback(MNCC_PDU mncc)
253runs on MNCC_Emulation_CT return template MNCC_PDU;
254
255type record MnccOps {
256 MnccCreateCallback create_cb,
257 MnccUnitdataCallback unitdata_cb
258}
259
Harald Welteafec4712018-03-19 22:52:17 +0100260function main(MnccOps ops, charstring id, charstring sock, boolean role_server := false)
261runs on MNCC_Emulation_CT {
Harald Welte876bf9d2018-01-21 19:28:59 +0100262
Harald Welteafec4712018-03-19 22:52:17 +0100263 if (role_server) {
264 f_listen(sock);
Harald Welte14509532018-03-24 22:32:01 +0100265 MNCC.send(t_SD_MNCC(g_mncc_ud_id, ts_MNCC_HELLO));
Harald Welteafec4712018-03-19 22:52:17 +0100266 } else {
267 f_connect(sock);
268 }
Daniel Willmannff6abf02018-02-02 18:26:05 +0100269 f_expect_table_init();
Harald Welte876bf9d2018-01-21 19:28:59 +0100270 f_call_table_init();
271
272 while (true) {
273 var MNCC_send_data sd;
274 var MNCC_Conn_Req creq;
275 var MNCC_ConnHdlr vc_conn;
276 var MNCC_PDU mncc;
277 var MNCC_ConnHdlr vc_hdlr;
278 var charstring dest_nr;
279
280 alt {
281 /* MNCC -> Client: UNIT-DATA (connectionless SCCP) from a BSC */
282 [] MNCC.receive(t_SD_MNCC_MSGT(g_mncc_ud_id, MNCC_SOCKET_HELLO)) -> value sd {
283 /* Connectionless Procedures like HELLO */
284 var template MNCC_PDU resp;
285 resp := ops.unitdata_cb.apply(sd.data);
286 if (isvalue(resp)) {
287 MNCC.send(t_SD_MNCC(g_mncc_ud_id, resp));
288 }
289 }
290
291 /* MNCC -> Client: Release Indication / confirmation */
292 [] MNCC.receive(t_SD_MNCC_MSGT(g_mncc_ud_id, (MNCC_REL_IND, MNCC_REL_CNF))) -> value sd {
293 var uint32_t call_id := f_mncc_get_call_id(sd.data);
294 /* forward to respective client */
295 vc_conn := f_comp_by_call_id(call_id);
296 MNCC_CLIENT.send(sd.data) to vc_conn;
297 /* remove from call table */
298 f_call_table_del(call_id);
299 }
300
301 /* MNCC -> Client: call related messages */
302 [] MNCC.receive(t_SD_MNCC_MSGT(g_mncc_ud_id, ?)) -> value sd {
303 var uint32_t call_id := f_mncc_get_call_id(sd.data);
304
305 if (f_call_id_known(call_id)) {
306 vc_conn := f_comp_by_call_id(call_id);
307 MNCC_CLIENT.send(sd.data) to vc_conn;
308 } else {
309 /* TODO: Only accept this for SETUP.req? */
310 vc_conn := ops.create_cb.apply(sd.data, id)
311 /* store mapping between client components and SCCP connectionId */
312 f_call_table_add(vc_conn, call_id);
313 /* handle user payload */
314 MNCC_CLIENT.send(sd.data) to vc_conn;
315 }
316 }
317
318 /* Client -> MNCC Socket: RELEASE.ind or RELEASE.cnf: forward + drop call table entry */
319 [] MNCC_CLIENT.receive(MNCC_PDU:{msg_type := (MNCC_REL_IND, MNCC_REL_CNF), u:=?}) -> value mncc sender vc_conn {
320 var integer call_id := f_call_id_by_comp(vc_conn);
321 /* forward to MNCC socket */
322 MNCC.send(t_SD_MNCC(g_mncc_ud_id, mncc));
323 /* remove from call table */
324 f_call_table_del(call_id);
325 }
326
Harald Welteafec4712018-03-19 22:52:17 +0100327 /* Client -> MNCC Socket: Normal message */
328 [] MNCC_CLIENT.receive(MNCC_PDU:?) -> value mncc sender vc_conn {
329 if (mncc.msg_type == MNCC_SETUP_REQ and not role_server) {
330 /* ConnHdlr -> MNCC Server: SETUP.req: add to call table */
331 f_call_table_add(vc_conn, f_mncc_get_call_id(mncc));
332 } else if (mncc.msg_type == MNCC_SETUP_IND and role_server) {
333 /* ConnHdlr -> MNCC Client: SETUP.ind: add to call table */
334 f_call_table_add(vc_conn, f_mncc_get_call_id(mncc));
335 }
Harald Welte812f7a42018-01-27 00:49:18 +0100336 /* forward to MNCC socket */
337 MNCC.send(t_SD_MNCC(g_mncc_ud_id, mncc));
338 }
339
Harald Welte876bf9d2018-01-21 19:28:59 +0100340 [] MNCC_CLIENT.receive(MNCC_PDU:?) -> value mncc sender vc_conn {
341 /* forward to MNCC socket */
342 MNCC.send(t_SD_MNCC(g_mncc_ud_id, mncc));
343 }
344
345
346 /* Client -> us: procedure call to register expect */
347 [] MNCC_PROC.getcall(MNCCEM_register:{?,?}) -> param(dest_nr, vc_hdlr) {
348 f_create_expect(dest_nr, vc_hdlr);
Harald Weltee32ad992018-05-31 22:17:46 +0200349 MNCC_PROC.reply(MNCCEM_register:{dest_nr, vc_hdlr}) to vc_hdlr;
Harald Welte876bf9d2018-01-21 19:28:59 +0100350 }
351
352 }
353 }
354}
355
356private function f_mgcp_ep_extract_cic(charstring inp) return integer {
357 var charstring local_part := regexp(inp, "(*)@*", 0);
358 return hex2int(str2hex(local_part));
359
360}
361
362/***********************************************************************
363 * "Expect" Handling (mapping for expected incoming MNCC calls from IUT)
364 ***********************************************************************/
365
366/* data about an expected future incoming connection */
367type record ExpectData {
368 /* destination number based on which we can match it */
369 charstring dest_number optional,
370 /* component reference for this connection */
371 MNCC_ConnHdlr vc_conn
372}
373
374/* procedure based port to register for incoming calls */
375signature MNCCEM_register(in charstring dest_nr, in MNCC_ConnHdlr hdlr);
376
377type port MNCCEM_PROC_PT procedure {
378 inout MNCCEM_register;
379} with { extension "internal" };
380
381/* CreateCallback that can be used as create_cb and will use the expectation table */
382function ExpectedCreateCallback(MNCC_PDU conn_ind, charstring id)
383runs on MNCC_Emulation_CT return MNCC_ConnHdlr {
384 var MNCC_ConnHdlr ret := null;
385 var charstring dest_number;
386 var integer i;
387
Harald Welte9edea882018-03-24 22:32:20 +0100388 if (not ischosen(conn_ind.u.signal) or
389 (conn_ind.msg_type != MNCC_SETUP_IND and conn_ind.msg_type != MNCC_SETUP_REQ)) {
390 setverdict(fail, "MNCC ExpectedCreateCallback needs MNCC_SETUP_{IND,REQ}");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200391 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100392 return ret;
393 }
394 dest_number := conn_ind.u.signal.called.number;
395
396 for (i := 0; i < sizeof(MnccExpectTable); i:= i+1) {
397 if (not ispresent(MnccExpectTable[i].dest_number)) {
398 continue;
399 }
400 if (dest_number == MnccExpectTable[i].dest_number) {
401 ret := MnccExpectTable[i].vc_conn;
402 /* release this entry to be used again */
403 MnccExpectTable[i].dest_number := omit;
404 MnccExpectTable[i].vc_conn := null;
405 log("Found MnccExpect[", i, "] for ", dest_number, " handled at ", ret);
406 /* return the component reference */
407 return ret;
408 }
409 }
410 setverdict(fail, "Couldn't find MnccExpect for incoming call ", dest_number);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200411 mtc.stop;
Harald Welte876bf9d2018-01-21 19:28:59 +0100412 return ret;
413}
414
415/* server/emulation side function to create expect */
416private function f_create_expect(charstring dest_number, MNCC_ConnHdlr hdlr)
417runs on MNCC_Emulation_CT {
418 var integer i;
419 for (i := 0; i < sizeof(MnccExpectTable); i := i+1) {
420 if (not ispresent(MnccExpectTable[i].dest_number)) {
421 MnccExpectTable[i].dest_number := dest_number;
422 MnccExpectTable[i].vc_conn := hdlr;
423 log("Created MnccExpect[", i, "] for ", dest_number, " to be handled at ", hdlr);
424 return;
425 }
426 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200427 testcase.stop("No space left in MnccMnccExpectTable");
Harald Welte876bf9d2018-01-21 19:28:59 +0100428}
429
430/* client/conn_hdlr side function to use procedure port to create expect in emulation */
431function f_create_mncc_expect(charstring dest_number) runs on MNCC_ConnHdlr {
432 MNCC_PROC.call(MNCCEM_register:{dest_number, self}) {
433 [] MNCC_PROC.getreply(MNCCEM_register:{?,?}) {};
434 }
435}
436
437function DummyUnitdataCallback(MNCC_PDU mncc)
438runs on MNCC_Emulation_CT return template MNCC_PDU {
439 log("Ignoring MNCC ", mncc);
440 return omit;
441}
442
443}