blob: 0c37840b1115fe2dd31a5663d190e92e00bd4518 [file] [log] [blame]
Harald Welteaf5bce42018-10-09 09:20:45 +02001module SGsAP_Emulation {
2
3/* SGsAP Emulation, runs on top of SGsAP_CodecPort. It multiplexes/demultiplexes
4 * the individual IMSIs/subscribers, so there can be separate TTCN-3 components handling
5 * each of them.
6 *
7 * The SGsAP_Emulation.main() function processes SGsAP primitives from the SGsAP
8 * socket via the SGsAP_CodecPort, and dispatches them to the per-IMSI components.
9 *
10 * For each new IMSI, the SgsapOps.create_cb() is called. It can create
11 * or resolve a TTCN-3 component, and returns a component reference to which that IMSI
12 * is routed/dispatched.
13 *
14 * If a pre-existing component wants to register to handle a future inbound IMSI, it can
15 * do so by registering an "expect" with the expected IMSI.
16 *
17 * Inbound SGsAP messages without IMSI (such as RESET-IND/ACK) are dispatched to
18 * the SgsapOps.unitdata_cb() callback, which is registered with an argument to the
19 * main() function below.
20 *
21 * (C) 2018 by Harald Welte <laforge@gnumonks.org>
22 * All rights reserved.
23 *
24 * Released under the terms of GNU General Public License, Version 2 or
25 * (at your option) any later version.
26 */
27
28import from SGsAP_CodecPort all;
29import from SGsAP_CodecPort_CtrlFunct all;
30import from SGsAP_Types all;
31import from SGsAP_Templates all;
32import from Osmocom_Types all;
33import from IPL4asp_Types all;
34
35type component SGsAP_ConnHdlr {
36 port SGsAP_Conn_PT SGsAP;
37 /* procedure based port to register for incoming connections */
38 port SGsAPEM_PROC_PT SGsAP_PROC;
39}
40
41/* port between individual per-connection components and this dispatcher */
42type port SGsAP_Conn_PT message {
43 inout PDU_SGsAP;
44} with { extension "internal" };
45
46/* represents a single SGsAP Association */
47type record AssociationData {
48 SGsAP_ConnHdlr comp_ref,
49 hexstring imsi optional
50};
51
52type component SGsAP_Emulation_CT {
53 /* Port facing to the UDP SUT */
54 port SGsAP_CODEC_PT SGsAP;
55 /* All SGsAP_ConnHdlr SGsAP ports connect here
56 * SGsAP_Emulation_CT.main needs to figure out what messages
57 * to send where with CLIENT.send() to vc_conn */
58 port SGsAP_Conn_PT SGsAP_CLIENT;
59 /* currently tracked connections */
60 var AssociationData SgsapAssociationTable[16];
61 /* pending expected CRCX */
62 var ExpectData SgsapExpectTable[8];
63 /* procedure based port to register for incoming connections */
64 port SGsAPEM_PROC_PT SGsAP_PROC;
65
66 var charstring g_sgsap_id;
67 var integer g_sgsap_conn_id := -1;
68}
69
70type function SGsAPCreateCallback(PDU_SGsAP msg, hexstring imsi, charstring id)
71runs on SGsAP_Emulation_CT return SGsAP_ConnHdlr;
72
73type function SGsAPUnitdataCallback(PDU_SGsAP msg)
74runs on SGsAP_Emulation_CT return template PDU_SGsAP;
75
76type record SGsAPOps {
77 SGsAPCreateCallback create_cb,
78 SGsAPUnitdataCallback unitdata_cb
79}
80
81type record SGsAP_conn_parameters {
82 HostName remote_ip,
83 PortNumber remote_sctp_port,
84 HostName local_ip,
85 PortNumber local_sctp_port
86}
87
88function tr_SGsAP_RecvFrom_R(template PDU_SGsAP msg)
89runs on SGsAP_Emulation_CT return template SGsAP_RecvFrom {
90 var template SGsAP_RecvFrom mrf := {
91 connId := g_sgsap_conn_id,
92 remName := ?,
93 remPort := ?,
94 locName := ?,
95 locPort := ?,
96 msg := msg
97 }
98 return mrf;
99}
100
101private function f_imsi_known(hexstring imsi)
102runs on SGsAP_Emulation_CT return boolean {
103 var integer i;
104 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
105 if (SgsapAssociationTable[i].imsi == imsi) {
106 return true;
107 }
108 }
109 return false;
110}
111
112private function f_comp_known(SGsAP_ConnHdlr client)
113runs on SGsAP_Emulation_CT return boolean {
114 var integer i;
115 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
116 if (SgsapAssociationTable[i].comp_ref == client) {
117 return true;
118 }
119 }
120 return false;
121}
122
123private function f_comp_by_imsi(hexstring imsi)
124runs on SGsAP_Emulation_CT return SGsAP_ConnHdlr {
125 var integer i;
126 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
127 if (SgsapAssociationTable[i].imsi == imsi) {
128 return SgsapAssociationTable[i].comp_ref;
129 }
130 }
131 setverdict(fail, "SGsAP Association Table not found by IMSI", imsi);
132 mtc.stop;
133}
134
135private function f_imsi_by_comp(SGsAP_ConnHdlr client)
136runs on SGsAP_Emulation_CT return hexstring {
137 var integer i;
138 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
139 if (SgsapAssociationTable[i].comp_ref == client) {
140 return SgsapAssociationTable[i].imsi;
141 }
142 }
143 setverdict(fail, "SGsAP Association Table not found by component ", client);
144 mtc.stop;
145}
146
147private function f_imsi_table_add(SGsAP_ConnHdlr comp_ref, hexstring imsi)
148runs on SGsAP_Emulation_CT {
149 var integer i;
150 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
151 if (not isvalue(SgsapAssociationTable[i].imsi)) {
152 SgsapAssociationTable[i].imsi := imsi;
153 SgsapAssociationTable[i].comp_ref := comp_ref;
154 return;
155 }
156 }
157 testcase.stop("SGsAP Association Table full!");
158}
159
160private function f_imsi_table_del(SGsAP_ConnHdlr comp_ref, hexstring imsi)
161runs on SGsAP_Emulation_CT {
162 var integer i;
163 for (i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
164 if (SgsapAssociationTable[i].comp_ref == comp_ref and
165 SgsapAssociationTable[i].imsi == imsi) {
166 SgsapAssociationTable[i].imsi := omit;
167 SgsapAssociationTable[i].comp_ref := null;
168 return;
169 }
170 }
171 setverdict(fail, "SGsAP Association Table: Couldn't find to-be-deleted entry!");
172 mtc.stop;
173}
174
175
176private function f_imsi_table_init()
177runs on SGsAP_Emulation_CT {
178 for (var integer i := 0; i < sizeof(SgsapAssociationTable); i := i+1) {
179 SgsapAssociationTable[i].comp_ref := null;
180 SgsapAssociationTable[i].imsi := omit;
181 }
182}
183
184private function f_SGsAP_get_imsi(PDU_SGsAP pdu) return template (omit) IMSI
185{
186 if (ischosen(pdu.sGsAP_ALERT_ACK)) {
187 return pdu.sGsAP_ALERT_ACK.iMSI;
188 } else if (ischosen(pdu.sGsAP_ALERT_REJECT)) {
189 return pdu.sGsAP_ALERT_REJECT.iMSI;
190 } else if (ischosen(pdu.sGsAP_ALERT_REQUEST)) {
191 return pdu.sGsAP_ALERT_REQUEST.iMSI;
192 } else if (ischosen(pdu.sGsAP_DOWNLINK_UNITDATA)) {
193 return pdu.sGsAP_DOWNLINK_UNITDATA.iMSI;
194 } else if (ischosen(pdu.sGsAP_EPS_DETACH_ACK)) {
195 return pdu.sGsAP_EPS_DETACH_ACK.iMSI;
196 } else if (ischosen(pdu.sGsAP_EPS_DETACH_INDICATION)) {
197 return pdu.sGsAP_EPS_DETACH_INDICATION.iMSI;
198 } else if (ischosen(pdu.sGsAP_IMSI_DETACH_ACK)) {
199 return pdu.sGsAP_IMSI_DETACH_ACK.iMSI;
200 } else if (ischosen(pdu.sGsAP_IMSI_DETACH_INDICATION)) {
201 return pdu.sGsAP_IMSI_DETACH_INDICATION.iMSI;
202 } else if (ischosen(pdu.sGsAP_LOCATION_UPDATE_ACCEPT)) {
203 return pdu.sGsAP_LOCATION_UPDATE_ACCEPT.iMSI;
204 } else if (ischosen(pdu.sGsAP_LOCATION_UPDATE_REJECT)) {
205 return pdu.sGsAP_LOCATION_UPDATE_REJECT.iMSI;
206 } else if (ischosen(pdu.sGsAP_LOCATION_UPDATE_REQUEST)) {
207 return pdu.sGsAP_LOCATION_UPDATE_REQUEST.iMSI;
208 } else if (ischosen(pdu.sGsAP_MM_INFORMATION_REQUEST)) {
209 return pdu.sGsAP_MM_INFORMATION_REQUEST.iMSI;
210 } else if (ischosen(pdu.sGsAP_PAGING_REJECT)) {
211 return pdu.sGsAP_PAGING_REJECT.iMSI;
212 } else if (ischosen(pdu.sGsAP_PAGING_REQUEST)) {
213 return pdu.sGsAP_PAGING_REQUEST.iMSI;
214 } else if (ischosen(pdu.sGsAP_SERVICE_REQUEST)) {
215 return pdu.sGsAP_SERVICE_REQUEST.iMSI;
216 } else if (ischosen(pdu.sGsAP_STATUS)) {
217 return pdu.sGsAP_STATUS.iMSI;
218 } else if (ischosen(pdu.sGsAP_TMSI_REALLOCATION_COMPLETE)) {
219 return pdu.sGsAP_TMSI_REALLOCATION_COMPLETE.iMSI;
220 } else if (ischosen(pdu.sGsAP_UE_ACTIVITY_INDICATION)) {
221 return pdu.sGsAP_UE_ACTIVITY_INDICATION.iMSI;
222 } else if (ischosen(pdu.sGsAP_UE_UNREACHABLE)) {
223 return pdu.sGsAP_UE_UNREACHABLE.iMSI;
224 } else if (ischosen(pdu.sGsAP_UPLINK_UNITDATA)) {
225 return pdu.sGsAP_UPLINK_UNITDATA.iMSI;
226 } else if (ischosen(pdu.sGsAP_RELEASE_REQUEST)) {
227 return pdu.sGsAP_RELEASE_REQUEST.iMSI;
228 } else if (ischosen(pdu.sGsAP_SERVICE_ABORT_REQUEST)) {
229 return pdu.sGsAP_SERVICE_ABORT_REQUEST.iMSI;
230 } else if (ischosen(pdu.sGsAP_MO_CSFB_INDICATION)) {
231 return pdu.sGsAP_MO_CSFB_INDICATION.iMSI;
232 }
233 return omit;
234}
235
236private template (value) SctpTuple ts_SCTP(template (omit) integer ppid := omit) := {
237 sinfo_stream := omit,
238 sinfo_ppid := ppid,
239 remSocks := omit,
240 assocId := omit
241};
242
243private template PortEvent tr_SctpAssocChange := {
244 sctpEvent := {
245 sctpAssocChange := ?
246 }
247}
248private template PortEvent tr_SctpPeerAddrChange := {
249 sctpEvent := {
250 sctpPeerAddrChange := ?
251 }
252}
253
254private function f_sgsap_xceive(template (value) PDU_SGsAP tx,
255 template PDU_SGsAP rx_t := ?)
256runs on SGsAP_Emulation_CT return PDU_SGsAP {
257 timer T := 10.0;
258 var SGsAP_RecvFrom mrf;
259
260 SGsAP.send(t_SGsAP_Send(g_sgsap_conn_id, tx));
261 alt {
262 [] SGsAP.receive(tr_SGsAP_RecvFrom_R(rx_t)) -> value mrf { }
263 [] SGsAP.receive(tr_SctpAssocChange) { repeat; }
264 [] SGsAP.receive(tr_SctpPeerAddrChange) { repeat; }
265 [] T.timeout {
266 setverdict(fail, "Timeout waiting for ", rx_t);
267 mtc.stop;
268 }
269 }
270 return mrf.msg;
271}
272
273function main(SGsAPOps ops, SGsAP_conn_parameters p, charstring id) runs on SGsAP_Emulation_CT {
274 var Result res;
275 g_sgsap_id := id;
276 f_imsi_table_init();
277 f_expect_table_init();
278
279 map(self:SGsAP, system:SGsAP_CODEC_PT);
280 if (p.remote_sctp_port == -1) {
281 res := SGsAP_CodecPort_CtrlFunct.f_IPL4_listen(SGsAP, p.local_ip, p.local_sctp_port, { sctp := valueof(ts_SCTP) });
282 } else {
283 res := SGsAP_CodecPort_CtrlFunct.f_IPL4_connect(SGsAP, p.remote_ip, p.remote_sctp_port,
284 p.local_ip, p.local_sctp_port, -1, { sctp := valueof(ts_SCTP) });
285 }
286 if (not ispresent(res.connId)) {
287 setverdict(fail, "Could not connect SGsAP socket, check your configuration");
288 mtc.stop;
289 }
290 g_sgsap_conn_id := res.connId;
291
292 while (true) {
293 var SGsAP_ConnHdlr vc_conn;
294 var template IMSI imsi_t;
295 var hexstring imsi;
296 var SGsAP_RecvFrom mrf;
297 var PDU_SGsAP msg;
298
299 alt {
300 /* SGsAP from client */
301 [] SGsAP_CLIENT.receive(PDU_SGsAP:?) -> value msg sender vc_conn {
302 /* Pass message through */
303 /* TODO: check which ConnectionID client has allocated + store in table? */
304 SGsAP.send(t_SGsAP_Send(g_sgsap_conn_id, msg));
305 }
306 [] SGsAP.receive(tr_SGsAP_RecvFrom_R(?)) -> value mrf {
307 imsi_t := f_SGsAP_get_imsi(mrf.msg);
308 if (isvalue(imsi_t)) {
309 imsi := valueof(imsi_t.iMSI.digits);
310 if (f_imsi_known(imsi)) {
311 vc_conn := f_comp_by_imsi(imsi);
312 SGsAP_CLIENT.send(mrf.msg) to vc_conn;
313 } else {
314 vc_conn := ops.create_cb.apply(mrf.msg, imsi, id);
315 f_imsi_table_add(vc_conn, imsi);
316 SGsAP_CLIENT.send(mrf.msg) to vc_conn;
317 }
318 } else {
319 /* message contained no IMSI; is not IMSI-oriented */
320 var template PDU_SGsAP resp := ops.unitdata_cb.apply(mrf.msg);
321 if (isvalue(resp)) {
322 SGsAP.send(t_SGsAP_Send(g_sgsap_conn_id, valueof(resp)));
323 }
324 }
325 }
326 [] SGsAP.receive(tr_SctpAssocChange) { }
327 [] SGsAP.receive(tr_SctpPeerAddrChange) { }
328 [] SGsAP_PROC.getcall(SGsAPEM_register:{?,?}) -> param(imsi, vc_conn) {
329 f_create_expect(imsi, vc_conn);
330 SGsAP_PROC.reply(SGsAPEM_register:{imsi, vc_conn}) to vc_conn;
331 }
332 }
333
334 }
335}
336
337/* "Expect" Handling */
338
339type record ExpectData {
340 hexstring imsi optional,
341 SGsAP_ConnHdlr vc_conn
342}
343
344signature SGsAPEM_register(in hexstring imsi, in SGsAP_ConnHdlr hdlr);
345
346type port SGsAPEM_PROC_PT procedure {
347 inout SGsAPEM_register;
348} with { extension "internal" };
349
350/* Function that can be used as create_cb and will usse the expect table */
351function ExpectedCreateCallback(PDU_SGsAP msg, hexstring imsi, charstring id)
352runs on SGsAP_Emulation_CT return SGsAP_ConnHdlr {
353 var SGsAP_ConnHdlr ret := null;
354 var integer i;
355
356 for (i := 0; i < sizeof(SgsapExpectTable); i := i+1) {
357 if (not ispresent(SgsapExpectTable[i].imsi)) {
358 continue;
359 }
360 if (imsi == SgsapExpectTable[i].imsi) {
361 ret := SgsapExpectTable[i].vc_conn;
362 /* Release this entry */
363 SgsapExpectTable[i].imsi := omit;
364 SgsapExpectTable[i].vc_conn := null;
365 log("Found Expect[", i, "] for ", msg, " handled at ", ret);
366 return ret;
367 }
368 }
369 setverdict(fail, "Couldn't find Expect for ", msg);
370 mtc.stop;
371}
372
373private function f_create_expect(hexstring imsi, SGsAP_ConnHdlr hdlr)
374runs on SGsAP_Emulation_CT {
375 var integer i;
376
377 /* Check an entry like this is not already presnt */
378 for (i := 0; i < sizeof(SgsapExpectTable); i := i+1) {
379 if (imsi == SgsapExpectTable[i].imsi) {
380 setverdict(fail, "IMSI already present", imsi);
381 mtc.stop;
382 }
383 }
384 for (i := 0; i < sizeof(SgsapExpectTable); i := i+1) {
385 if (not ispresent(SgsapExpectTable[i].imsi)) {
386 SgsapExpectTable[i].imsi := imsi;
387 SgsapExpectTable[i].vc_conn := hdlr;
388 log("Created Expect[", i, "] for ", imsi, " to be handled at ", hdlr);
389 return;
390 }
391 }
392 testcase.stop("No space left in SgsapExpectTable")
393}
394
395/* client/conn_hdlr side function to use procedure port to create expect in emulation */
396function f_create_sgsap_expect(hexstring imsi) runs on SGsAP_ConnHdlr {
397 SGsAP_PROC.call(SGsAPEM_register:{imsi, self}) {
398 [] SGsAP_PROC.getreply(SGsAPEM_register:{?,?}) {};
399 }
400}
401
402
403private function f_expect_table_init()
404runs on SGsAP_Emulation_CT {
405 var integer i;
406 for (i := 0; i < sizeof(SgsapExpectTable); i := i + 1) {
407 SgsapExpectTable[i].imsi := omit;
408 }
409}
410
411function DummyUnitdataCallback(PDU_SGsAP msg)
412runs on SGsAP_Emulation_CT return template PDU_SGsAP {
413 log("Ignoring SGsAP ", msg);
414 return omit;
415}
416
417
418}