blob: 4f7e5d658002444782f3a69762c3565b16fa9d3f [file] [log] [blame]
Harald Welte0e5aad22018-01-21 14:00:41 +01001module GSUP_Emulation {
2
3/* GSUP Emulation, runs on top of IPA_Emulation. It multiplexes/demultiplexes
4 * the individual calls, so there can be separate TTCN-3 components handling
5 * each of the calls
6 *
7 * The GSUP_Emulation.main() function processes GSUP primitives from the IPA/GSUP
8 * socket via the IPA_Emulation, and dispatches them to the per-connection components.
9 *
10 * Outbound GSUP connections are initiated by sending a FIXME primitive
11 * to the component running the GSUP_Emulation.main() function.
12 *
13 * For each new inbound connections, the GsupOps.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 + HUL, and first trigger a connection from BSC side in a
20 * component which then subsequently should also handle the GSUP emulation.
21 *
22 * (C) 2018 by Harald Welte <laforge@gnumonks.org>
23 * All rights reserved.
24 *
25 * Released under the terms of GNU General Public License, Version 2 or
26 * (at your option) any later version.
Harald Welte34b5a952019-05-27 11:54:11 +020027 *
28 * SPDX-License-Identifier: GPL-2.0-or-later
Harald Welte0e5aad22018-01-21 14:00:41 +010029 */
30
31
32import from Osmocom_Types all;
33import from GSUP_Types all;
34import from IPA_Emulation all;
35
36/* General "base class" component definition, of which specific implementations
37 * derive themselves by means of the "extends" feature */
38type component GSUP_ConnHdlr {
39 /* ports towards GSUP Emulator core / call dispatchar */
40 port GSUP_Conn_PT GSUP;
41 port GSUPEM_PROC_PT GSUP_PROC;
42}
43
44/* port between individual per-connection components and this dispatcher */
45type port GSUP_Conn_PT message {
46 inout GSUP_PDU;
47} with { extension "internal" };
48
49
50/* represents a single GSUP call */
51type record ConnectionData {
52 /* reference to the instance of the per-connection component */
53 GSUP_ConnHdlr comp_ref,
54 charstring imsi
55}
56
57type component GSUP_Emulation_CT {
58 /* UNIX DOMAIN socket on the bottom side, using primitives */
59 port IPA_GSUP_PT GSUP;
60 /* GSUP port to the per-connection clients */
61 port GSUP_Conn_PT GSUP_CLIENT;
62
Pau Espin Pedrole1f7a8c2024-02-27 18:24:06 +010063 var ConnectionData GsupImsiTable[256];
Harald Welte0e5aad22018-01-21 14:00:41 +010064
65 /* pending expected incoming connections */
Pau Espin Pedrole1f7a8c2024-02-27 18:24:06 +010066 var ExpectData GsupExpectTable[256];
Harald Welte0e5aad22018-01-21 14:00:41 +010067 /* procedure based port to register for incoming connections */
68 port GSUPEM_PROC_PT GSUP_PROC;
69};
70
71private function f_imsi_known(charstring imsi)
72runs on GSUP_Emulation_CT return boolean {
73 var integer i;
74 for (i := 0; i < sizeof(GsupImsiTable); i := i+1) {
75 if (GsupImsiTable[i].imsi == imsi) {
76 return true;
77 }
78 }
79 return false;
80}
81
82private function f_comp_known(GSUP_ConnHdlr client)
83runs on GSUP_Emulation_CT return boolean {
84 var integer i;
85 for (i := 0; i < sizeof(GsupImsiTable); i := i+1) {
86 if (GsupImsiTable[i].comp_ref == client) {
87 return true;
88 }
89 }
90 return false;
91}
92
93/* resolve component reference by connection ID */
94private function f_comp_by_imsi(charstring imsi)
95runs on GSUP_Emulation_CT return GSUP_ConnHdlr {
96 var integer i;
97 for (i := 0; i < sizeof(GsupImsiTable); i := i+1) {
98 if (GsupImsiTable[i].imsi == imsi) {
99 return GsupImsiTable[i].comp_ref;
100 }
101 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200102 setverdict(fail, "GSUP IMSI table not found by IMSI ", imsi);
103 mtc.stop;
Harald Welte0e5aad22018-01-21 14:00:41 +0100104}
105
106/* resolve connection ID by component reference */
107private function f_imsi_by_comp(GSUP_ConnHdlr client)
108runs on GSUP_Emulation_CT return charstring {
109 for (var integer i := 0; i < sizeof(GsupImsiTable); i := i+1) {
110 if (GsupImsiTable[i].comp_ref == client) {
111 return GsupImsiTable[i].imsi;
112 }
113 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200114 setverdict(fail, "GSUP IMSI table not found by component ", client);
115 mtc.stop;
Harald Welte0e5aad22018-01-21 14:00:41 +0100116}
117
118private function f_imsi_table_init()
119runs on GSUP_Emulation_CT {
120 for (var integer i := 0; i < sizeof(GsupImsiTable); i := i+1) {
121 GsupImsiTable[i].comp_ref := null;
122 GsupImsiTable[i].imsi := "";
123 }
124}
125
Harald Welted95da562018-01-27 21:46:53 +0100126private function f_expect_table_init()
127runs on GSUP_Emulation_CT {
128 for (var integer i := 0; i < sizeof(GsupExpectTable); i := i+1) {
129 GsupExpectTable[i].vc_conn := null;
130 GsupExpectTable[i].imsi := omit;
131 }
132}
133
Harald Welte0e5aad22018-01-21 14:00:41 +0100134private function f_imsi_table_add(GSUP_ConnHdlr comp_ref, charstring imsi)
135runs on GSUP_Emulation_CT {
136 for (var integer i := 0; i < sizeof(GsupImsiTable); i := i+1) {
137 if (GsupImsiTable[i].imsi == "") {
138 GsupImsiTable[i].comp_ref := comp_ref;
139 GsupImsiTable[i].imsi := imsi;
140 log("Added IMSI table entry ", i, comp_ref, imsi);
141 return;
142 }
143 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200144 testcase.stop("GSUP IMSI table full!");
Harald Welte0e5aad22018-01-21 14:00:41 +0100145}
146
147private function f_imsi_table_del(charstring imsi)
148runs on GSUP_Emulation_CT {
149 for (var integer i := 0; i < sizeof(GsupImsiTable); i := i+1) {
150 if (GsupImsiTable[i].imsi == imsi) {
151 log("Deleted GSUP IMSI table entry ", i,
152 GsupImsiTable[i].comp_ref, imsi);
153 GsupImsiTable[i].imsi := "";
154 GsupImsiTable[i].comp_ref := null;
155 return
156 }
157 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200158 setverdict(fail, "GSUP IMSI table attempt to delete non-existant ", imsi);
159 mtc.stop;
Harald Welte0e5aad22018-01-21 14:00:41 +0100160}
161
162
163/* call-back type, to be provided by specific implementation; called when new SCCP connection
164 * arrives */
165type function GsupCreateCallback(GSUP_PDU gsup, charstring id)
166runs on GSUP_Emulation_CT return GSUP_ConnHdlr;
167
168type record GsupOps {
169 GsupCreateCallback create_cb
170}
171
172function main(GsupOps ops, charstring id) runs on GSUP_Emulation_CT {
173
174 f_imsi_table_init();
Harald Welted95da562018-01-27 21:46:53 +0100175 f_expect_table_init();
Harald Welte0e5aad22018-01-21 14:00:41 +0100176
177 while (true) {
178 var GSUP_ConnHdlr vc_conn;
179 var GSUP_ConnHdlr vc_hdlr;
180 var GSUP_PDU gsup;
181 var charstring imsi;
182
183 alt {
184
Vadim Yanitskiya2afacc2020-05-18 21:16:19 +0700185 [] GSUP.receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_ID_ACK)) { repeat; }
186 [] GSUP.receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_UP)) { repeat; }
187 [] GSUP.receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_DOWN)) {
Harald Welte0e5aad22018-01-21 14:00:41 +0100188 setverdict(fail, "GSUP Connection Lost");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200189 mtc.stop;
Harald Welte0e5aad22018-01-21 14:00:41 +0100190 }
191
192 /* GSUP -> Client: call related messages */
193 [] GSUP.receive(GSUP_PDU:?) -> value gsup {
194 imsi := hex2str(gsup.ies[0].val.imsi);
195
196 if (f_imsi_known(imsi)) {
197 vc_conn := f_comp_by_imsi(imsi);
198 GSUP_CLIENT.send(gsup) to vc_conn;
199 } else {
200 /* TODO: Only accept this for SETUP.req? */
201 vc_conn := ops.create_cb.apply(gsup, id)
202 /* store mapping between client components and SCCP connectionId */
203 f_imsi_table_add(vc_conn, imsi);
204 /* handle user payload */
205 GSUP_CLIENT.send(gsup) to vc_conn;
206 }
207 }
208
209 [] GSUP.receive { repeat; }
210
211 /* Client -> GSUP Socket: Normal message */
212 [] GSUP_CLIENT.receive(GSUP_PDU:?) -> value gsup sender vc_conn {
213 /* forward to GSUP socket */
214 GSUP.send(gsup);
215 }
216
217
218 /* Client -> us: procedure call to register expect */
Harald Welte17f32082022-05-16 14:40:07 +0200219 [] GSUP_PROC.getcall(GSUPEM_register_expect:{?,?}) -> param(imsi, vc_hdlr) {
Harald Welte0e5aad22018-01-21 14:00:41 +0100220 f_create_expect(imsi, vc_hdlr);
Harald Welte17f32082022-05-16 14:40:07 +0200221 GSUP_PROC.reply(GSUPEM_register_expect:{imsi, vc_hdlr}) to vc_hdlr;
222 }
223 /* Client -> us: procedure call to unregister expect */
224 [] GSUP_PROC.getcall(GSUPEM_unregister_expect:{?,?}) -> param(imsi, vc_hdlr) {
225 f_destroy_expect(imsi, vc_hdlr);
226 GSUP_PROC.reply(GSUPEM_unregister_expect:{imsi, vc_hdlr}) to vc_hdlr;
Harald Welte0e5aad22018-01-21 14:00:41 +0100227 }
228
Harald Welte130110c2022-05-16 15:45:05 +0200229 [] GSUP_PROC.getcall(GSUPEM_unregister_connhdlr:{?}) -> param(imsi) sender vc_hdlr {
230 f_imsi_table_del(imsi);
231 GSUP_PROC.reply(GSUPEM_unregister_connhdlr:{imsi}) to vc_hdlr;
232 }
Neels Hofmeyr2c4f7d82021-07-28 00:55:14 +0200233 [] GSUP_PROC.getcall(GSUPEM_change_connhdlr:{?,?}) -> param(imsi, vc_hdlr) {
234 f_imsi_table_del(imsi);
235 f_imsi_table_add(vc_hdlr, imsi);
236 GSUP_PROC.reply(GSUPEM_change_connhdlr:{imsi, vc_hdlr}) to vc_hdlr;
237 }
238
Harald Welte0e5aad22018-01-21 14:00:41 +0100239 }
240 }
241}
242
243/***********************************************************************
244 * "Expect" Handling (mapping for expected incoming GSUP calls from IUT)
245 ***********************************************************************/
246
247/* data about an expected future incoming connection */
248type record ExpectData {
249 /* destination number based on which we can match it */
250 charstring imsi optional,
251 /* component reference for this connection */
252 GSUP_ConnHdlr vc_conn
253}
254
255/* procedure based port to register for incoming calls */
Harald Welte17f32082022-05-16 14:40:07 +0200256signature GSUPEM_register_expect(in charstring imsi, in GSUP_ConnHdlr hdlr);
257signature GSUPEM_unregister_expect(in charstring imsi, in GSUP_ConnHdlr hdlr);
Harald Welte130110c2022-05-16 15:45:05 +0200258signature GSUPEM_unregister_connhdlr(in charstring imsi);
Neels Hofmeyr2c4f7d82021-07-28 00:55:14 +0200259signature GSUPEM_change_connhdlr(in charstring imsi, in GSUP_ConnHdlr hdlr);
Harald Welte0e5aad22018-01-21 14:00:41 +0100260
261type port GSUPEM_PROC_PT procedure {
Harald Welte130110c2022-05-16 15:45:05 +0200262 inout GSUPEM_register_expect, GSUPEM_unregister_expect,
263 GSUPEM_unregister_connhdlr, GSUPEM_change_connhdlr;
Harald Welte0e5aad22018-01-21 14:00:41 +0100264} with { extension "internal" };
265
266/* CreateCallback that can be used as create_cb and will use the expectation table */
267function ExpectedCreateCallback(GSUP_PDU gsup, charstring id)
268runs on GSUP_Emulation_CT return GSUP_ConnHdlr {
269 var GSUP_ConnHdlr ret := null;
270 var charstring imsi;
271 var integer i;
272
273 imsi := hex2str(gsup.ies[0].val.imsi);
274
275 for (i := 0; i < sizeof(GsupExpectTable); i:= i+1) {
276 if (not ispresent(GsupExpectTable[i].imsi)) {
277 continue;
278 }
279 if (imsi == GsupExpectTable[i].imsi) {
280 ret := GsupExpectTable[i].vc_conn;
281 /* release this entry to be used again */
282 GsupExpectTable[i].imsi := omit;
283 GsupExpectTable[i].vc_conn := null;
284 log("Found GsupExpect[", i, "] for ", imsi, " handled at ", ret);
285 /* return the component reference */
286 return ret;
287 }
288 }
289 setverdict(fail, "Couldn't find GsupExpect for incoming imsi ", imsi);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200290 mtc.stop;
Harald Welte0e5aad22018-01-21 14:00:41 +0100291 return ret;
292}
293
294/* server/emulation side function to create expect */
295private function f_create_expect(charstring imsi, GSUP_ConnHdlr hdlr)
296runs on GSUP_Emulation_CT {
297 var integer i;
298 for (i := 0; i < sizeof(GsupExpectTable); i := i+1) {
299 if (not ispresent(GsupExpectTable[i].imsi)) {
300 GsupExpectTable[i].imsi := imsi;
301 GsupExpectTable[i].vc_conn := hdlr;
302 log("Created GsupExpect[", i, "] for ", imsi, " to be handled at ", hdlr);
303 return;
304 }
305 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200306 testcase.stop("No space left in GsupExpectTable");
Harald Welte0e5aad22018-01-21 14:00:41 +0100307}
308
Harald Welte17f32082022-05-16 14:40:07 +0200309/* server/emulation side function to destroy expect */
310private function f_destroy_expect(charstring imsi, GSUP_ConnHdlr hdlr)
311runs on GSUP_Emulation_CT {
312 var integer i;
313 for (i := 0; i < sizeof(GsupExpectTable); i := i+1) {
314 if (GsupExpectTable[i].imsi == imsi and GsupExpectTable[i].vc_conn == hdlr) {
315 GsupExpectTable[i].imsi := omit;
316 GsupExpectTable[i].vc_conn := null;
317 log("Destroyed GsupExpect[", i, "] for ", imsi, " to be handled at ", hdlr);
318 return;
319 }
320 }
321 testcase.stop("No matching expect found to be destoyed");
322}
323
Harald Welte0e5aad22018-01-21 14:00:41 +0100324/* client/conn_hdlr side function to use procedure port to create expect in emulation */
325function f_create_gsup_expect(charstring imsi) runs on GSUP_ConnHdlr {
Harald Welte17f32082022-05-16 14:40:07 +0200326 GSUP_PROC.call(GSUPEM_register_expect:{imsi, self}) {
327 [] GSUP_PROC.getreply(GSUPEM_register_expect:{?,?}) {};
Harald Welte0e5aad22018-01-21 14:00:41 +0100328 }
329}
330
Harald Welte17f32082022-05-16 14:40:07 +0200331function f_destroy_gsup_expect(charstring imsi) runs on GSUP_ConnHdlr {
332 GSUP_PROC.call(GSUPEM_unregister_expect:{imsi, self}) {
333 [] GSUP_PROC.getreply(GSUPEM_unregister_expect:{?,?}) {};
334 }
335}
336
Harald Welte130110c2022-05-16 15:45:05 +0200337function f_unregister_gsup_imsi(charstring imsi) runs on GSUP_ConnHdlr {
338 GSUP_PROC.call(GSUPEM_unregister_connhdlr:{imsi}) {
339 [] GSUP_PROC.getreply(GSUPEM_unregister_connhdlr:{?}) {};
340 }
341}
342
343
Harald Welte17f32082022-05-16 14:40:07 +0200344
Oliver Smith6e81f7e2020-01-29 14:25:41 +0100345/* Same as f_create_gsup_expect, but with explicit addressing. Needed when connecting multiple ports to GSUP_PROC. */
346function f_create_gsup_expect_explicit(charstring imsi, GSUP_Emulation_CT ct) runs on GSUP_ConnHdlr {
Harald Welte17f32082022-05-16 14:40:07 +0200347 GSUP_PROC.call(GSUPEM_register_expect:{imsi, self}) to ct {
348 [] GSUP_PROC.getreply(GSUPEM_register_expect:{?,?}) {};
Oliver Smith6e81f7e2020-01-29 14:25:41 +0100349 }
350}
351
Neels Hofmeyr2c4f7d82021-07-28 00:55:14 +0200352function f_gsup_change_connhdlr(charstring imsi) runs on GSUP_ConnHdlr {
353 GSUP_PROC.call(GSUPEM_change_connhdlr:{imsi, self}) {
354 [] GSUP_PROC.getreply(GSUPEM_change_connhdlr:{?,?}) {};
355 }
356}
357
Harald Welte0e5aad22018-01-21 14:00:41 +0100358}