297 lines
8.9 KiB
Plaintext
297 lines
8.9 KiB
Plaintext
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.
|
|
*/
|
|
|
|
|
|
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[8];
|
|
/* 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(ASP_IPA_Event:{up_down:=ASP_IPA_EVENT_ID_ACK}) { repeat; }
|
|
[] GSUP.receive(ASP_IPA_Event:{up_down:=ASP_IPA_EVENT_UP}) { repeat; }
|
|
[] GSUP.receive(ASP_IPA_Event:{up_down:=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:{?,?}) -> param(imsi, vc_hdlr) {
|
|
f_create_expect(imsi, vc_hdlr);
|
|
GSUP_PROC.reply(GSUPEM_register:{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(in charstring imsi, in GSUP_ConnHdlr hdlr);
|
|
|
|
type port GSUPEM_PROC_PT procedure {
|
|
inout GSUPEM_register;
|
|
} 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");
|
|
}
|
|
|
|
/* 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:{imsi, self}) {
|
|
[] GSUP_PROC.getreply(GSUPEM_register:{?,?}) {};
|
|
}
|
|
}
|
|
|
|
}
|