blob: 5e15d0699fa34e3688f47d5ce6df66666811168b [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
63 /* use 16 as this is also the number of SCCP connections that SCCP_Emulation can handle */
64 var ConnectionData GsupImsiTable[16];
65
66 /* pending expected incoming connections */
Vadim Yanitskiyf3906e62020-01-10 00:46:57 +010067 var ExpectData GsupExpectTable[16];
Harald Welte0e5aad22018-01-21 14:00:41 +010068 /* procedure based port to register for incoming connections */
69 port GSUPEM_PROC_PT GSUP_PROC;
70};
71
72private function f_imsi_known(charstring imsi)
73runs on GSUP_Emulation_CT return boolean {
74 var integer i;
75 for (i := 0; i < sizeof(GsupImsiTable); i := i+1) {
76 if (GsupImsiTable[i].imsi == imsi) {
77 return true;
78 }
79 }
80 return false;
81}
82
83private function f_comp_known(GSUP_ConnHdlr client)
84runs on GSUP_Emulation_CT return boolean {
85 var integer i;
86 for (i := 0; i < sizeof(GsupImsiTable); i := i+1) {
87 if (GsupImsiTable[i].comp_ref == client) {
88 return true;
89 }
90 }
91 return false;
92}
93
94/* resolve component reference by connection ID */
95private function f_comp_by_imsi(charstring imsi)
96runs on GSUP_Emulation_CT return GSUP_ConnHdlr {
97 var integer i;
98 for (i := 0; i < sizeof(GsupImsiTable); i := i+1) {
99 if (GsupImsiTable[i].imsi == imsi) {
100 return GsupImsiTable[i].comp_ref;
101 }
102 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200103 setverdict(fail, "GSUP IMSI table not found by IMSI ", imsi);
104 mtc.stop;
Harald Welte0e5aad22018-01-21 14:00:41 +0100105}
106
107/* resolve connection ID by component reference */
108private function f_imsi_by_comp(GSUP_ConnHdlr client)
109runs on GSUP_Emulation_CT return charstring {
110 for (var integer i := 0; i < sizeof(GsupImsiTable); i := i+1) {
111 if (GsupImsiTable[i].comp_ref == client) {
112 return GsupImsiTable[i].imsi;
113 }
114 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200115 setverdict(fail, "GSUP IMSI table not found by component ", client);
116 mtc.stop;
Harald Welte0e5aad22018-01-21 14:00:41 +0100117}
118
119private function f_imsi_table_init()
120runs on GSUP_Emulation_CT {
121 for (var integer i := 0; i < sizeof(GsupImsiTable); i := i+1) {
122 GsupImsiTable[i].comp_ref := null;
123 GsupImsiTable[i].imsi := "";
124 }
125}
126
Harald Welted95da562018-01-27 21:46:53 +0100127private function f_expect_table_init()
128runs on GSUP_Emulation_CT {
129 for (var integer i := 0; i < sizeof(GsupExpectTable); i := i+1) {
130 GsupExpectTable[i].vc_conn := null;
131 GsupExpectTable[i].imsi := omit;
132 }
133}
134
Harald Welte0e5aad22018-01-21 14:00:41 +0100135private function f_imsi_table_add(GSUP_ConnHdlr comp_ref, charstring imsi)
136runs on GSUP_Emulation_CT {
137 for (var integer i := 0; i < sizeof(GsupImsiTable); i := i+1) {
138 if (GsupImsiTable[i].imsi == "") {
139 GsupImsiTable[i].comp_ref := comp_ref;
140 GsupImsiTable[i].imsi := imsi;
141 log("Added IMSI table entry ", i, comp_ref, imsi);
142 return;
143 }
144 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200145 testcase.stop("GSUP IMSI table full!");
Harald Welte0e5aad22018-01-21 14:00:41 +0100146}
147
148private function f_imsi_table_del(charstring imsi)
149runs on GSUP_Emulation_CT {
150 for (var integer i := 0; i < sizeof(GsupImsiTable); i := i+1) {
151 if (GsupImsiTable[i].imsi == imsi) {
152 log("Deleted GSUP IMSI table entry ", i,
153 GsupImsiTable[i].comp_ref, imsi);
154 GsupImsiTable[i].imsi := "";
155 GsupImsiTable[i].comp_ref := null;
156 return
157 }
158 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200159 setverdict(fail, "GSUP IMSI table attempt to delete non-existant ", imsi);
160 mtc.stop;
Harald Welte0e5aad22018-01-21 14:00:41 +0100161}
162
163
164/* call-back type, to be provided by specific implementation; called when new SCCP connection
165 * arrives */
166type function GsupCreateCallback(GSUP_PDU gsup, charstring id)
167runs on GSUP_Emulation_CT return GSUP_ConnHdlr;
168
169type record GsupOps {
170 GsupCreateCallback create_cb
171}
172
173function main(GsupOps ops, charstring id) runs on GSUP_Emulation_CT {
174
175 f_imsi_table_init();
Harald Welted95da562018-01-27 21:46:53 +0100176 f_expect_table_init();
Harald Welte0e5aad22018-01-21 14:00:41 +0100177
178 while (true) {
179 var GSUP_ConnHdlr vc_conn;
180 var GSUP_ConnHdlr vc_hdlr;
181 var GSUP_PDU gsup;
182 var charstring imsi;
183
184 alt {
185
Vadim Yanitskiya2afacc2020-05-18 21:16:19 +0700186 [] GSUP.receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_ID_ACK)) { repeat; }
187 [] GSUP.receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_UP)) { repeat; }
188 [] GSUP.receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_DOWN)) {
Harald Welte0e5aad22018-01-21 14:00:41 +0100189 setverdict(fail, "GSUP Connection Lost");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200190 mtc.stop;
Harald Welte0e5aad22018-01-21 14:00:41 +0100191 }
192
193 /* GSUP -> Client: call related messages */
194 [] GSUP.receive(GSUP_PDU:?) -> value gsup {
195 imsi := hex2str(gsup.ies[0].val.imsi);
196
197 if (f_imsi_known(imsi)) {
198 vc_conn := f_comp_by_imsi(imsi);
199 GSUP_CLIENT.send(gsup) to vc_conn;
200 } else {
201 /* TODO: Only accept this for SETUP.req? */
202 vc_conn := ops.create_cb.apply(gsup, id)
203 /* store mapping between client components and SCCP connectionId */
204 f_imsi_table_add(vc_conn, imsi);
205 /* handle user payload */
206 GSUP_CLIENT.send(gsup) to vc_conn;
207 }
208 }
209
210 [] GSUP.receive { repeat; }
211
212 /* Client -> GSUP Socket: Normal message */
213 [] GSUP_CLIENT.receive(GSUP_PDU:?) -> value gsup sender vc_conn {
214 /* forward to GSUP socket */
215 GSUP.send(gsup);
216 }
217
218
219 /* Client -> us: procedure call to register expect */
Harald Welte17f32082022-05-16 14:40:07 +0200220 [] GSUP_PROC.getcall(GSUPEM_register_expect:{?,?}) -> param(imsi, vc_hdlr) {
Harald Welte0e5aad22018-01-21 14:00:41 +0100221 f_create_expect(imsi, vc_hdlr);
Harald Welte17f32082022-05-16 14:40:07 +0200222 GSUP_PROC.reply(GSUPEM_register_expect:{imsi, vc_hdlr}) to vc_hdlr;
223 }
224 /* Client -> us: procedure call to unregister expect */
225 [] GSUP_PROC.getcall(GSUPEM_unregister_expect:{?,?}) -> param(imsi, vc_hdlr) {
226 f_destroy_expect(imsi, vc_hdlr);
227 GSUP_PROC.reply(GSUPEM_unregister_expect:{imsi, vc_hdlr}) to vc_hdlr;
Harald Welte0e5aad22018-01-21 14:00:41 +0100228 }
229
Harald Welte130110c2022-05-16 15:45:05 +0200230 [] GSUP_PROC.getcall(GSUPEM_unregister_connhdlr:{?}) -> param(imsi) sender vc_hdlr {
231 f_imsi_table_del(imsi);
232 GSUP_PROC.reply(GSUPEM_unregister_connhdlr:{imsi}) to vc_hdlr;
233 }
Neels Hofmeyr2c4f7d82021-07-28 00:55:14 +0200234 [] GSUP_PROC.getcall(GSUPEM_change_connhdlr:{?,?}) -> param(imsi, vc_hdlr) {
235 f_imsi_table_del(imsi);
236 f_imsi_table_add(vc_hdlr, imsi);
237 GSUP_PROC.reply(GSUPEM_change_connhdlr:{imsi, vc_hdlr}) to vc_hdlr;
238 }
239
Harald Welte0e5aad22018-01-21 14:00:41 +0100240 }
241 }
242}
243
244/***********************************************************************
245 * "Expect" Handling (mapping for expected incoming GSUP calls from IUT)
246 ***********************************************************************/
247
248/* data about an expected future incoming connection */
249type record ExpectData {
250 /* destination number based on which we can match it */
251 charstring imsi optional,
252 /* component reference for this connection */
253 GSUP_ConnHdlr vc_conn
254}
255
256/* procedure based port to register for incoming calls */
Harald Welte17f32082022-05-16 14:40:07 +0200257signature GSUPEM_register_expect(in charstring imsi, in GSUP_ConnHdlr hdlr);
258signature GSUPEM_unregister_expect(in charstring imsi, in GSUP_ConnHdlr hdlr);
Harald Welte130110c2022-05-16 15:45:05 +0200259signature GSUPEM_unregister_connhdlr(in charstring imsi);
Neels Hofmeyr2c4f7d82021-07-28 00:55:14 +0200260signature GSUPEM_change_connhdlr(in charstring imsi, in GSUP_ConnHdlr hdlr);
Harald Welte0e5aad22018-01-21 14:00:41 +0100261
262type port GSUPEM_PROC_PT procedure {
Harald Welte130110c2022-05-16 15:45:05 +0200263 inout GSUPEM_register_expect, GSUPEM_unregister_expect,
264 GSUPEM_unregister_connhdlr, GSUPEM_change_connhdlr;
Harald Welte0e5aad22018-01-21 14:00:41 +0100265} with { extension "internal" };
266
267/* CreateCallback that can be used as create_cb and will use the expectation table */
268function ExpectedCreateCallback(GSUP_PDU gsup, charstring id)
269runs on GSUP_Emulation_CT return GSUP_ConnHdlr {
270 var GSUP_ConnHdlr ret := null;
271 var charstring imsi;
272 var integer i;
273
274 imsi := hex2str(gsup.ies[0].val.imsi);
275
276 for (i := 0; i < sizeof(GsupExpectTable); i:= i+1) {
277 if (not ispresent(GsupExpectTable[i].imsi)) {
278 continue;
279 }
280 if (imsi == GsupExpectTable[i].imsi) {
281 ret := GsupExpectTable[i].vc_conn;
282 /* release this entry to be used again */
283 GsupExpectTable[i].imsi := omit;
284 GsupExpectTable[i].vc_conn := null;
285 log("Found GsupExpect[", i, "] for ", imsi, " handled at ", ret);
286 /* return the component reference */
287 return ret;
288 }
289 }
290 setverdict(fail, "Couldn't find GsupExpect for incoming imsi ", imsi);
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200291 mtc.stop;
Harald Welte0e5aad22018-01-21 14:00:41 +0100292 return ret;
293}
294
295/* server/emulation side function to create expect */
296private function f_create_expect(charstring imsi, GSUP_ConnHdlr hdlr)
297runs on GSUP_Emulation_CT {
298 var integer i;
299 for (i := 0; i < sizeof(GsupExpectTable); i := i+1) {
300 if (not ispresent(GsupExpectTable[i].imsi)) {
301 GsupExpectTable[i].imsi := imsi;
302 GsupExpectTable[i].vc_conn := hdlr;
303 log("Created GsupExpect[", i, "] for ", imsi, " to be handled at ", hdlr);
304 return;
305 }
306 }
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200307 testcase.stop("No space left in GsupExpectTable");
Harald Welte0e5aad22018-01-21 14:00:41 +0100308}
309
Harald Welte17f32082022-05-16 14:40:07 +0200310/* server/emulation side function to destroy expect */
311private function f_destroy_expect(charstring imsi, GSUP_ConnHdlr hdlr)
312runs on GSUP_Emulation_CT {
313 var integer i;
314 for (i := 0; i < sizeof(GsupExpectTable); i := i+1) {
315 if (GsupExpectTable[i].imsi == imsi and GsupExpectTable[i].vc_conn == hdlr) {
316 GsupExpectTable[i].imsi := omit;
317 GsupExpectTable[i].vc_conn := null;
318 log("Destroyed GsupExpect[", i, "] for ", imsi, " to be handled at ", hdlr);
319 return;
320 }
321 }
322 testcase.stop("No matching expect found to be destoyed");
323}
324
Harald Welte0e5aad22018-01-21 14:00:41 +0100325/* client/conn_hdlr side function to use procedure port to create expect in emulation */
326function f_create_gsup_expect(charstring imsi) runs on GSUP_ConnHdlr {
Harald Welte17f32082022-05-16 14:40:07 +0200327 GSUP_PROC.call(GSUPEM_register_expect:{imsi, self}) {
328 [] GSUP_PROC.getreply(GSUPEM_register_expect:{?,?}) {};
Harald Welte0e5aad22018-01-21 14:00:41 +0100329 }
330}
331
Harald Welte17f32082022-05-16 14:40:07 +0200332function f_destroy_gsup_expect(charstring imsi) runs on GSUP_ConnHdlr {
333 GSUP_PROC.call(GSUPEM_unregister_expect:{imsi, self}) {
334 [] GSUP_PROC.getreply(GSUPEM_unregister_expect:{?,?}) {};
335 }
336}
337
Harald Welte130110c2022-05-16 15:45:05 +0200338function f_unregister_gsup_imsi(charstring imsi) runs on GSUP_ConnHdlr {
339 GSUP_PROC.call(GSUPEM_unregister_connhdlr:{imsi}) {
340 [] GSUP_PROC.getreply(GSUPEM_unregister_connhdlr:{?}) {};
341 }
342}
343
344
Harald Welte17f32082022-05-16 14:40:07 +0200345
Oliver Smith6e81f7e2020-01-29 14:25:41 +0100346/* Same as f_create_gsup_expect, but with explicit addressing. Needed when connecting multiple ports to GSUP_PROC. */
347function f_create_gsup_expect_explicit(charstring imsi, GSUP_Emulation_CT ct) runs on GSUP_ConnHdlr {
Harald Welte17f32082022-05-16 14:40:07 +0200348 GSUP_PROC.call(GSUPEM_register_expect:{imsi, self}) to ct {
349 [] GSUP_PROC.getreply(GSUPEM_register_expect:{?,?}) {};
Oliver Smith6e81f7e2020-01-29 14:25:41 +0100350 }
351}
352
Neels Hofmeyr2c4f7d82021-07-28 00:55:14 +0200353function f_gsup_change_connhdlr(charstring imsi) runs on GSUP_ConnHdlr {
354 GSUP_PROC.call(GSUPEM_change_connhdlr:{imsi, self}) {
355 [] GSUP_PROC.getreply(GSUPEM_change_connhdlr:{?,?}) {};
356 }
357}
358
Harald Welte0e5aad22018-01-21 14:00:41 +0100359}