| module GSUP_Emulation { |
| |
| /* GSUP Emulation, runs on top of IPA_Emulation. It multiplexes/demultiplexes |
| * the individual calls, so there can be separate TTCN-3 components handling |
| * each of the calls |
| * |
| * The GSUP_Emulation.main() function processes GSUP primitives from the IPA/GSUP |
| * socket via the IPA_Emulation, and dispatches them to the per-connection components. |
| * |
| * Outbound GSUP connections are initiated by sending a FIXME primitive |
| * to the component running the GSUP_Emulation.main() function. |
| * |
| * For each new inbound connections, the GsupOps.create_cb() is called. It can create |
| * or resolve a TTCN-3 component, and returns a component reference to which that inbound |
| * connection is routed/dispatched. |
| * |
| * If a pre-existing component wants to register to handle a future inbound call, it can |
| * do so by registering an "expect" with the expected destination phone number. This is e.g. useful |
| * if you are simulating BSC + HUL, and first trigger a connection from BSC side in a |
| * component which then subsequently should also handle the GSUP emulation. |
| * |
| * (C) 2018 by Harald Welte <laforge@gnumonks.org> |
| * All rights reserved. |
| * |
| * Released under the terms of GNU General Public License, Version 2 or |
| * (at your option) any later version. |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| */ |
| |
| |
| import from Osmocom_Types all; |
| import from GSUP_Types all; |
| import from IPA_Emulation all; |
| |
| /* General "base class" component definition, of which specific implementations |
| * derive themselves by means of the "extends" feature */ |
| type component GSUP_ConnHdlr { |
| /* ports towards GSUP Emulator core / call dispatchar */ |
| port GSUP_Conn_PT GSUP; |
| port GSUPEM_PROC_PT GSUP_PROC; |
| } |
| |
| /* port between individual per-connection components and this dispatcher */ |
| type port GSUP_Conn_PT message { |
| inout GSUP_PDU; |
| } with { extension "internal" }; |
| |
| |
| /* represents a single GSUP call */ |
| type record ConnectionData { |
| /* reference to the instance of the per-connection component */ |
| GSUP_ConnHdlr comp_ref, |
| charstring imsi |
| } |
| |
| type component GSUP_Emulation_CT { |
| /* UNIX DOMAIN socket on the bottom side, using primitives */ |
| port IPA_GSUP_PT GSUP; |
| /* GSUP port to the per-connection clients */ |
| port GSUP_Conn_PT GSUP_CLIENT; |
| |
| /* use 16 as this is also the number of SCCP connections that SCCP_Emulation can handle */ |
| var ConnectionData GsupImsiTable[16]; |
| |
| /* pending expected incoming connections */ |
| var ExpectData GsupExpectTable[16]; |
| /* procedure based port to register for incoming connections */ |
| port GSUPEM_PROC_PT GSUP_PROC; |
| }; |
| |
| private function f_imsi_known(charstring imsi) |
| runs on GSUP_Emulation_CT return boolean { |
| var integer i; |
| for (i := 0; i < sizeof(GsupImsiTable); i := i+1) { |
| if (GsupImsiTable[i].imsi == imsi) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private function f_comp_known(GSUP_ConnHdlr client) |
| runs on GSUP_Emulation_CT return boolean { |
| var integer i; |
| for (i := 0; i < sizeof(GsupImsiTable); i := i+1) { |
| if (GsupImsiTable[i].comp_ref == client) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* resolve component reference by connection ID */ |
| private function f_comp_by_imsi(charstring imsi) |
| runs on GSUP_Emulation_CT return GSUP_ConnHdlr { |
| var integer i; |
| for (i := 0; i < sizeof(GsupImsiTable); i := i+1) { |
| if (GsupImsiTable[i].imsi == imsi) { |
| return GsupImsiTable[i].comp_ref; |
| } |
| } |
| setverdict(fail, "GSUP IMSI table not found by IMSI ", imsi); |
| mtc.stop; |
| } |
| |
| /* resolve connection ID by component reference */ |
| private function f_imsi_by_comp(GSUP_ConnHdlr client) |
| runs on GSUP_Emulation_CT return charstring { |
| for (var integer i := 0; i < sizeof(GsupImsiTable); i := i+1) { |
| if (GsupImsiTable[i].comp_ref == client) { |
| return GsupImsiTable[i].imsi; |
| } |
| } |
| setverdict(fail, "GSUP IMSI table not found by component ", client); |
| mtc.stop; |
| } |
| |
| private function f_imsi_table_init() |
| runs on GSUP_Emulation_CT { |
| for (var integer i := 0; i < sizeof(GsupImsiTable); i := i+1) { |
| GsupImsiTable[i].comp_ref := null; |
| GsupImsiTable[i].imsi := ""; |
| } |
| } |
| |
| private function f_expect_table_init() |
| runs on GSUP_Emulation_CT { |
| for (var integer i := 0; i < sizeof(GsupExpectTable); i := i+1) { |
| GsupExpectTable[i].vc_conn := null; |
| GsupExpectTable[i].imsi := omit; |
| } |
| } |
| |
| private function f_imsi_table_add(GSUP_ConnHdlr comp_ref, charstring imsi) |
| runs on GSUP_Emulation_CT { |
| for (var integer i := 0; i < sizeof(GsupImsiTable); i := i+1) { |
| if (GsupImsiTable[i].imsi == "") { |
| GsupImsiTable[i].comp_ref := comp_ref; |
| GsupImsiTable[i].imsi := imsi; |
| log("Added IMSI table entry ", i, comp_ref, imsi); |
| return; |
| } |
| } |
| testcase.stop("GSUP IMSI table full!"); |
| } |
| |
| private function f_imsi_table_del(charstring imsi) |
| runs on GSUP_Emulation_CT { |
| for (var integer i := 0; i < sizeof(GsupImsiTable); i := i+1) { |
| if (GsupImsiTable[i].imsi == imsi) { |
| log("Deleted GSUP IMSI table entry ", i, |
| GsupImsiTable[i].comp_ref, imsi); |
| GsupImsiTable[i].imsi := ""; |
| GsupImsiTable[i].comp_ref := null; |
| return |
| } |
| } |
| setverdict(fail, "GSUP IMSI table attempt to delete non-existant ", imsi); |
| mtc.stop; |
| } |
| |
| |
| /* call-back type, to be provided by specific implementation; called when new SCCP connection |
| * arrives */ |
| type function GsupCreateCallback(GSUP_PDU gsup, charstring id) |
| runs on GSUP_Emulation_CT return GSUP_ConnHdlr; |
| |
| type record GsupOps { |
| GsupCreateCallback create_cb |
| } |
| |
| function main(GsupOps ops, charstring id) runs on GSUP_Emulation_CT { |
| |
| f_imsi_table_init(); |
| f_expect_table_init(); |
| |
| while (true) { |
| var GSUP_ConnHdlr vc_conn; |
| var GSUP_ConnHdlr vc_hdlr; |
| var GSUP_PDU gsup; |
| var charstring imsi; |
| |
| alt { |
| |
| [] GSUP.receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_ID_ACK)) { repeat; } |
| [] GSUP.receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_UP)) { repeat; } |
| [] GSUP.receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_DOWN)) { |
| setverdict(fail, "GSUP Connection Lost"); |
| mtc.stop; |
| } |
| |
| /* GSUP -> Client: call related messages */ |
| [] GSUP.receive(GSUP_PDU:?) -> value gsup { |
| imsi := hex2str(gsup.ies[0].val.imsi); |
| |
| if (f_imsi_known(imsi)) { |
| vc_conn := f_comp_by_imsi(imsi); |
| GSUP_CLIENT.send(gsup) to vc_conn; |
| } else { |
| /* TODO: Only accept this for SETUP.req? */ |
| vc_conn := ops.create_cb.apply(gsup, id) |
| /* store mapping between client components and SCCP connectionId */ |
| f_imsi_table_add(vc_conn, imsi); |
| /* handle user payload */ |
| GSUP_CLIENT.send(gsup) to vc_conn; |
| } |
| } |
| |
| [] GSUP.receive { repeat; } |
| |
| /* Client -> GSUP Socket: Normal message */ |
| [] GSUP_CLIENT.receive(GSUP_PDU:?) -> value gsup sender vc_conn { |
| /* forward to GSUP socket */ |
| GSUP.send(gsup); |
| } |
| |
| |
| /* Client -> us: procedure call to register expect */ |
| [] GSUP_PROC.getcall(GSUPEM_register_expect:{?,?}) -> param(imsi, vc_hdlr) { |
| f_create_expect(imsi, vc_hdlr); |
| GSUP_PROC.reply(GSUPEM_register_expect:{imsi, vc_hdlr}) to vc_hdlr; |
| } |
| /* Client -> us: procedure call to unregister expect */ |
| [] GSUP_PROC.getcall(GSUPEM_unregister_expect:{?,?}) -> param(imsi, vc_hdlr) { |
| f_destroy_expect(imsi, vc_hdlr); |
| GSUP_PROC.reply(GSUPEM_unregister_expect:{imsi, vc_hdlr}) to vc_hdlr; |
| } |
| |
| [] GSUP_PROC.getcall(GSUPEM_unregister_connhdlr:{?}) -> param(imsi) sender vc_hdlr { |
| f_imsi_table_del(imsi); |
| GSUP_PROC.reply(GSUPEM_unregister_connhdlr:{imsi}) to vc_hdlr; |
| } |
| [] GSUP_PROC.getcall(GSUPEM_change_connhdlr:{?,?}) -> param(imsi, vc_hdlr) { |
| f_imsi_table_del(imsi); |
| f_imsi_table_add(vc_hdlr, imsi); |
| GSUP_PROC.reply(GSUPEM_change_connhdlr:{imsi, vc_hdlr}) to vc_hdlr; |
| } |
| |
| } |
| } |
| } |
| |
| /*********************************************************************** |
| * "Expect" Handling (mapping for expected incoming GSUP calls from IUT) |
| ***********************************************************************/ |
| |
| /* data about an expected future incoming connection */ |
| type record ExpectData { |
| /* destination number based on which we can match it */ |
| charstring imsi optional, |
| /* component reference for this connection */ |
| GSUP_ConnHdlr vc_conn |
| } |
| |
| /* procedure based port to register for incoming calls */ |
| signature GSUPEM_register_expect(in charstring imsi, in GSUP_ConnHdlr hdlr); |
| signature GSUPEM_unregister_expect(in charstring imsi, in GSUP_ConnHdlr hdlr); |
| signature GSUPEM_unregister_connhdlr(in charstring imsi); |
| signature GSUPEM_change_connhdlr(in charstring imsi, in GSUP_ConnHdlr hdlr); |
| |
| type port GSUPEM_PROC_PT procedure { |
| inout GSUPEM_register_expect, GSUPEM_unregister_expect, |
| GSUPEM_unregister_connhdlr, GSUPEM_change_connhdlr; |
| } with { extension "internal" }; |
| |
| /* CreateCallback that can be used as create_cb and will use the expectation table */ |
| function ExpectedCreateCallback(GSUP_PDU gsup, charstring id) |
| runs on GSUP_Emulation_CT return GSUP_ConnHdlr { |
| var GSUP_ConnHdlr ret := null; |
| var charstring imsi; |
| var integer i; |
| |
| imsi := hex2str(gsup.ies[0].val.imsi); |
| |
| for (i := 0; i < sizeof(GsupExpectTable); i:= i+1) { |
| if (not ispresent(GsupExpectTable[i].imsi)) { |
| continue; |
| } |
| if (imsi == GsupExpectTable[i].imsi) { |
| ret := GsupExpectTable[i].vc_conn; |
| /* release this entry to be used again */ |
| GsupExpectTable[i].imsi := omit; |
| GsupExpectTable[i].vc_conn := null; |
| log("Found GsupExpect[", i, "] for ", imsi, " handled at ", ret); |
| /* return the component reference */ |
| return ret; |
| } |
| } |
| setverdict(fail, "Couldn't find GsupExpect for incoming imsi ", imsi); |
| mtc.stop; |
| return ret; |
| } |
| |
| /* server/emulation side function to create expect */ |
| private function f_create_expect(charstring imsi, GSUP_ConnHdlr hdlr) |
| runs on GSUP_Emulation_CT { |
| var integer i; |
| for (i := 0; i < sizeof(GsupExpectTable); i := i+1) { |
| if (not ispresent(GsupExpectTable[i].imsi)) { |
| GsupExpectTable[i].imsi := imsi; |
| GsupExpectTable[i].vc_conn := hdlr; |
| log("Created GsupExpect[", i, "] for ", imsi, " to be handled at ", hdlr); |
| return; |
| } |
| } |
| testcase.stop("No space left in GsupExpectTable"); |
| } |
| |
| /* server/emulation side function to destroy expect */ |
| private function f_destroy_expect(charstring imsi, GSUP_ConnHdlr hdlr) |
| runs on GSUP_Emulation_CT { |
| var integer i; |
| for (i := 0; i < sizeof(GsupExpectTable); i := i+1) { |
| if (GsupExpectTable[i].imsi == imsi and GsupExpectTable[i].vc_conn == hdlr) { |
| GsupExpectTable[i].imsi := omit; |
| GsupExpectTable[i].vc_conn := null; |
| log("Destroyed GsupExpect[", i, "] for ", imsi, " to be handled at ", hdlr); |
| return; |
| } |
| } |
| testcase.stop("No matching expect found to be destoyed"); |
| } |
| |
| /* client/conn_hdlr side function to use procedure port to create expect in emulation */ |
| function f_create_gsup_expect(charstring imsi) runs on GSUP_ConnHdlr { |
| GSUP_PROC.call(GSUPEM_register_expect:{imsi, self}) { |
| [] GSUP_PROC.getreply(GSUPEM_register_expect:{?,?}) {}; |
| } |
| } |
| |
| function f_destroy_gsup_expect(charstring imsi) runs on GSUP_ConnHdlr { |
| GSUP_PROC.call(GSUPEM_unregister_expect:{imsi, self}) { |
| [] GSUP_PROC.getreply(GSUPEM_unregister_expect:{?,?}) {}; |
| } |
| } |
| |
| function f_unregister_gsup_imsi(charstring imsi) runs on GSUP_ConnHdlr { |
| GSUP_PROC.call(GSUPEM_unregister_connhdlr:{imsi}) { |
| [] GSUP_PROC.getreply(GSUPEM_unregister_connhdlr:{?}) {}; |
| } |
| } |
| |
| |
| |
| /* Same as f_create_gsup_expect, but with explicit addressing. Needed when connecting multiple ports to GSUP_PROC. */ |
| function f_create_gsup_expect_explicit(charstring imsi, GSUP_Emulation_CT ct) runs on GSUP_ConnHdlr { |
| GSUP_PROC.call(GSUPEM_register_expect:{imsi, self}) to ct { |
| [] GSUP_PROC.getreply(GSUPEM_register_expect:{?,?}) {}; |
| } |
| } |
| |
| function f_gsup_change_connhdlr(charstring imsi) runs on GSUP_ConnHdlr { |
| GSUP_PROC.call(GSUPEM_change_connhdlr:{imsi, self}) { |
| [] GSUP_PROC.getreply(GSUPEM_change_connhdlr:{?,?}) {}; |
| } |
| } |
| |
| } |