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