| /* GTPv2 Emulation in TTCN-3 |
| * |
| * (C) 2018-2020 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 |
| */ |
| |
| module GTPv2_Emulation { |
| |
| import from IPL4asp_Types all; |
| import from General_Types all; |
| import from Osmocom_Types all; |
| import from GTPv2_Types all; |
| import from GTPv2_Templates all; |
| import from GTPv2_CodecPort all; |
| import from GTPv2_CodecPort_CtrlFunct all; |
| |
| import from UECUPS_Types all; |
| import from UECUPS_CodecPort all; |
| import from UECUPS_CodecPort_CtrlFunct all; |
| |
| /*********************************************************************** |
| * Main Emulation Component |
| ***********************************************************************/ |
| |
| modulepar { |
| charstring mp_uecups_host := "127.0.0.1"; |
| integer mp_uecups_port := UECUPS_SCTP_PORT; |
| }; |
| |
| const integer GTP2C_PORT := 2123; |
| const integer GTP1U_PORT := 2152; |
| |
| type record Gtp2EmulationCfg { |
| HostName gtpc_bind_ip, |
| IPL4asp_Types.PortNumber gtpc_bind_port, |
| HostName gtpc_remote_ip, |
| IPL4asp_Types.PortNumber gtpc_remote_port, |
| //HostName gtpu_bind_ip, |
| //PortNumber gtpu_bind_port, |
| boolean sgw_role, |
| boolean use_gtpu_daemon |
| }; |
| |
| type component GTPv2_Emulation_CT { |
| /* Communication with underlying GTP CodecPort */ |
| port GTPv2C_PT GTP2C; |
| |
| /* Control port to GTP-U Daemon */ |
| port UECUPS_CODEC_PT UECUPS; |
| |
| /* Communication with Clients */ |
| port GTP2EM_PT TEID0; |
| port GTP2EM_PT CLIENT; |
| port GTP2EM_PROC_PT CLIENT_PROC; |
| |
| /* Configuration by the user */ |
| var Gtp2EmulationCfg g_gtp2_cfg; |
| |
| /* State */ |
| var GtpPeer g_peer; |
| var integer g_gtp2c_id; |
| var OCT1 g_restart_ctr; |
| var uint16_t g_c_seq_nr; |
| var TidTableRec TidTable[256]; |
| var SeqTableRec SeqTable[256]; |
| var ImsiTableRec ImsiTable[256]; |
| var PidTableRec PidTable[256]; |
| var integer g_uecups_conn_id; |
| }; |
| |
| /* local TEID <-> ConnHdlr mapping */ |
| type record TidTableRec { |
| OCT4 teid, |
| GTP2_ConnHdlr vc_conn |
| }; |
| |
| /* local SeqNr <-> ConnHdlr mapping (until a response is received */ |
| type record SeqTableRec { |
| OCT3 seq, |
| GTP2_ConnHdlr vc_conn |
| }; |
| |
| /* IMSI <-> ConnHdlr mapping */ |
| type record ImsiTableRec { |
| hexstring imsi, |
| GTP2_ConnHdlr vc_conn |
| }; |
| |
| /* pid <-> ConnHdlr mapping (for UECUPS process termination indication) */ |
| type record PidTableRec { |
| /* process ID of the running process */ |
| integer pid, |
| /* component that started it */ |
| GTP2_ConnHdlr vc_conn |
| }; |
| |
| private function f_comp_by_teid(OCT4 teid) runs on GTPv2_Emulation_CT return GTP2_ConnHdlr { |
| var integer i; |
| for (i := 0; i < sizeof(TidTable); i := i+1) { |
| if (isbound(TidTable[i].teid) and TidTable[i].teid == teid) { |
| return TidTable[i].vc_conn; |
| } |
| } |
| setverdict(fail, "No Component for TEID ", teid); |
| mtc.stop; |
| } |
| |
| private function f_seq_known(OCT3 seq) runs on GTPv2_Emulation_CT return boolean { |
| var integer i; |
| for (i := 0; i < sizeof(SeqTable); i := i+1) { |
| if (isbound(SeqTable[i].seq) and SeqTable[i].seq == seq) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private function f_comp_by_seq(OCT3 seq) runs on GTPv2_Emulation_CT return GTP2_ConnHdlr { |
| var integer i; |
| for (i := 0; i < sizeof(SeqTable); i := i+1) { |
| if (isbound(SeqTable[i].seq) and SeqTable[i].seq == seq) { |
| return SeqTable[i].vc_conn; |
| } |
| } |
| setverdict(fail, "No Component for SEQ ", seq); |
| mtc.stop; |
| } |
| |
| private function f_comp_by_imsi(hexstring imsi) runs on GTPv2_Emulation_CT return GTP2_ConnHdlr { |
| var integer i; |
| for (i := 0; i < sizeof(ImsiTable); i := i+1) { |
| if (isbound(ImsiTable[i].imsi) and ImsiTable[i].imsi == imsi) { |
| return ImsiTable[i].vc_conn; |
| } |
| } |
| setverdict(fail, "No Component for IMSI ", imsi); |
| mtc.stop; |
| } |
| |
| private function f_comp_by_pid(integer pid) runs on GTPv2_Emulation_CT return GTP2_ConnHdlr { |
| var integer i; |
| for (i := 0; i < sizeof(PidTable); i := i+1) { |
| if (isbound(PidTable[i].pid) and PidTable[i].pid == pid) { |
| /* fixme: remove */ |
| return PidTable[i].vc_conn; |
| } |
| } |
| setverdict(fail, "No Component for PID ", pid); |
| mtc.stop; |
| } |
| |
| private function f_tid_tbl_add(OCT4 teid, GTP2_ConnHdlr vc_conn) runs on GTPv2_Emulation_CT { |
| var integer i; |
| for (i := 0; i < sizeof(TidTable); i := i+1) { |
| if (not isbound(TidTable[i].teid)) { |
| TidTable[i].teid := teid; |
| TidTable[i].vc_conn := vc_conn; |
| return; |
| } |
| } |
| testcase.stop("No Space in TidTable for ", teid); |
| } |
| |
| private function f_seq_tbl_add(OCT3 seq, GTP2_ConnHdlr vc_conn) runs on GTPv2_Emulation_CT { |
| var integer i; |
| for (i := 0; i < sizeof(SeqTable); i := i+1) { |
| if (not isbound(SeqTable[i].seq)) { |
| SeqTable[i].seq := seq; |
| SeqTable[i].vc_conn := vc_conn; |
| return; |
| } |
| } |
| testcase.stop("No Space in SeqTable for ", seq); |
| } |
| |
| private function f_seq_tbl_del(OCT3 seq) runs on GTPv2_Emulation_CT { |
| var integer i; |
| for (i := 0; i < sizeof(SeqTable); i := i+1) { |
| if (isbound(SeqTable[i].seq) and SeqTable[i].seq == seq) { |
| SeqTable[i] := { |
| seq := -, |
| vc_conn := null |
| } |
| } |
| } |
| } |
| |
| private function f_imsi_tbl_add(hexstring imsi, GTP2_ConnHdlr vc_conn) runs on GTPv2_Emulation_CT { |
| var integer i; |
| for (i := 0; i < sizeof(ImsiTable); i := i+1) { |
| if (not isbound(ImsiTable[i].imsi)) { |
| ImsiTable[i].imsi := imsi; |
| ImsiTable[i].vc_conn := vc_conn; |
| return; |
| } |
| } |
| testcase.stop("No Space in IMSI Table for ", imsi); |
| } |
| |
| private function f_pid_tbl_add(integer pid, GTP2_ConnHdlr vc_conn) runs on GTPv2_Emulation_CT { |
| var integer i; |
| for (i := 0; i < sizeof(PidTable); i := i+1) { |
| if (not isbound(PidTable[i].pid)) { |
| PidTable[i].pid := pid; |
| PidTable[i].vc_conn := vc_conn; |
| return; |
| } |
| } |
| testcase.stop("No Space in PID Table for ", pid); |
| } |
| |
| |
| /* allocate an unused local teid */ |
| private function f_alloc_teid() runs on GTPv2_Emulation_CT return OCT4 { |
| var OCT4 teid; |
| var integer i, j; |
| for (i := 0; i < 100; i := i+1) { |
| teid := f_rnd_octstring(4); |
| for (j := 0; j < sizeof(TidTable); j := j+1) { |
| if (isbound(TidTable) and TidTable[i].teid == teid) { |
| continue; |
| } |
| } |
| /* we iterated over all entries and found no match: great! */ |
| return teid; |
| } |
| testcase.stop("Cannot find unused TEID after ", i, " attempts"); |
| } |
| |
| /* obtain the IMSI from a GTPv2C PDU, if there is any IMSI contained. The way how the TITAN |
| * GTPv2 decoders are structured (explict IE members rather than a list/set of generic IE structures) |
| * doesn't make this easy, but requires lots of boilerplate code. Oh well.. */ |
| function f_gtp2c_extract_imsi(PDU_GTPCv2 gtp) return template (omit) hexstring { |
| if (ischosen(gtp.gtpcv2_pdu.createSessionRequest)) { |
| if (ispresent(gtp.gtpcv2_pdu.createSessionRequest.iMSI)) { |
| return gtp.gtpcv2_pdu.createSessionRequest.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.downlinkDataNotification)) { |
| if (ispresent(gtp.gtpcv2_pdu.downlinkDataNotification.iMSI)) { |
| return gtp.gtpcv2_pdu.downlinkDataNotification.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.downlinkDataNotificationAcknowledgement)) { |
| if (ispresent(gtp.gtpcv2_pdu.downlinkDataNotificationAcknowledgement.iMSI)) { |
| return gtp.gtpcv2_pdu.downlinkDataNotificationAcknowledgement.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.downlinkDataNotificationFailureIndication)) { |
| if (ispresent(gtp.gtpcv2_pdu.downlinkDataNotificationFailureIndication.iMSI)) { |
| return gtp.gtpcv2_pdu.downlinkDataNotificationFailureIndication.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.createIndirectDataForwardingTunnelRequest)) { |
| if (ispresent(gtp.gtpcv2_pdu.createIndirectDataForwardingTunnelRequest.iMSI)) { |
| return gtp.gtpcv2_pdu.createIndirectDataForwardingTunnelRequest.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.stopPagingIndication)) { |
| if (ispresent(gtp.gtpcv2_pdu.stopPagingIndication.iMSI)) { |
| return gtp.gtpcv2_pdu.stopPagingIndication.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.forwardRelocationRequest)) { |
| if (ispresent(gtp.gtpcv2_pdu.forwardRelocationRequest.iMSI)) { |
| return gtp.gtpcv2_pdu.forwardRelocationRequest.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.contextRequest)) { |
| if (ispresent(gtp.gtpcv2_pdu.contextRequest.iMSI)) { |
| return gtp.gtpcv2_pdu.contextRequest.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.identificationResponse)) { |
| if (ispresent(gtp.gtpcv2_pdu.identificationResponse.iMSI)) { |
| return gtp.gtpcv2_pdu.identificationResponse.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.changeNotificationRequest)) { |
| if (ispresent(gtp.gtpcv2_pdu.changeNotificationRequest)) { |
| return gtp.gtpcv2_pdu.changeNotificationRequest.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.changeNotificationResponse)) { |
| if (ispresent(gtp.gtpcv2_pdu.changeNotificationResponse.iMSI)) { |
| return gtp.gtpcv2_pdu.changeNotificationResponse.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.relocationCancelRequest)) { |
| if (ispresent(gtp.gtpcv2_pdu.relocationCancelRequest.iMSI)) { |
| return gtp.gtpcv2_pdu.relocationCancelRequest.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.uE_RegistrationQueryRequest)) { |
| if (ispresent(gtp.gtpcv2_pdu.uE_RegistrationQueryRequest.iMSI)) { |
| return gtp.gtpcv2_pdu.uE_RegistrationQueryRequest.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.uE_RegistrationQueryResponse)) { |
| if (ispresent(gtp.gtpcv2_pdu.uE_RegistrationQueryResponse.iMSI)) { |
| return gtp.gtpcv2_pdu.uE_RegistrationQueryResponse.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.suspendNotification)) { |
| if (ispresent(gtp.gtpcv2_pdu.suspendNotification.iMSI)) { |
| return gtp.gtpcv2_pdu.suspendNotification.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.resumeNotification)) { |
| if (ispresent(gtp.gtpcv2_pdu.resumeNotification.iMSI)) { |
| return gtp.gtpcv2_pdu.resumeNotification.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.cSPagingIndication)) { |
| if (ispresent(gtp.gtpcv2_pdu.cSPagingIndication.iMSI)) { |
| return gtp.gtpcv2_pdu.cSPagingIndication.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.pGW_DownlinkTriggeringNotification)) { |
| if (ispresent(gtp.gtpcv2_pdu.pGW_DownlinkTriggeringNotification.iMSI)) { |
| return gtp.gtpcv2_pdu.pGW_DownlinkTriggeringNotification.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.pGW_DownlinkTriggeringAcknowledge)) { |
| if (ispresent(gtp.gtpcv2_pdu.pGW_DownlinkTriggeringAcknowledge.iMSI)) { |
| return gtp.gtpcv2_pdu.pGW_DownlinkTriggeringAcknowledge.iMSI.iMSI_Value; |
| } |
| } else if (ischosen(gtp.gtpcv2_pdu.traceSessionActivation)) { |
| if (ispresent(gtp.gtpcv2_pdu.traceSessionActivation.iMSI)) { |
| return gtp.gtpcv2_pdu.traceSessionActivation.iMSI.iMSI_Value; |
| } |
| } |
| return omit; |
| } |
| |
| private template (value) SctpTuple ts_SCTP(template (omit) integer ppid := omit) := { |
| sinfo_stream := omit, |
| sinfo_ppid := ppid, |
| remSocks := omit, |
| assocId := omit |
| }; |
| |
| function tr_UECUPS_RecvFrom_R(template PDU_UECUPS msg) |
| runs on GTPv2_Emulation_CT return template UECUPS_RecvFrom { |
| var template UECUPS_RecvFrom mrf := { |
| connId := g_uecups_conn_id, |
| remName := ?, |
| remPort := ?, |
| locName := ?, |
| locPort := ?, |
| msg := msg |
| } |
| return mrf; |
| } |
| |
| |
| private template PortEvent tr_SctpAssocChange := { |
| sctpEvent := { |
| sctpAssocChange := ? |
| } |
| } |
| private template PortEvent tr_SctpPeerAddrChange := { |
| sctpEvent := { |
| sctpPeerAddrChange := ? |
| } |
| } |
| |
| private function f_uecups_xceive(template (value) PDU_UECUPS tx, |
| template PDU_UECUPS rx_t := ?, float time_out := 10.0) |
| runs on GTPv2_Emulation_CT return PDU_UECUPS { |
| timer T := time_out; |
| var UECUPS_RecvFrom mrf; |
| |
| UECUPS.send(t_UECUPS_Send(g_uecups_conn_id, tx)); |
| T.start; |
| alt { |
| [] UECUPS.receive(tr_UECUPS_RecvFrom_R(rx_t)) -> value mrf { } |
| [] UECUPS.receive(tr_SctpAssocChange) { repeat; } |
| [] UECUPS.receive(tr_SctpPeerAddrChange) { repeat; } |
| [] T.timeout { |
| setverdict(fail, "Timeout waiting for ", rx_t); |
| mtc.stop; |
| } |
| } |
| return mrf.msg; |
| } |
| |
| private function f_init(Gtp2EmulationCfg cfg) runs on GTPv2_Emulation_CT { |
| var Result res; |
| |
| map(self:GTP2C, system:GTP2C); |
| res := GTPv2_CodecPort_CtrlFunct.f_IPL4_listen(GTP2C, cfg.gtpc_bind_ip, |
| cfg.gtpc_bind_port, {udp:={}}); |
| g_gtp2c_id := res.connId; |
| |
| g_restart_ctr := f_rnd_octstring(1); |
| g_c_seq_nr := f_rnd_int(65535); |
| g_gtp2_cfg := cfg; |
| g_peer := { |
| connId := g_gtp2c_id, |
| remName := g_gtp2_cfg.gtpc_remote_ip, |
| remPort := g_gtp2_cfg.gtpc_remote_port |
| } |
| |
| if (g_gtp2_cfg.use_gtpu_daemon) { |
| map(self:UECUPS, system:UECUPS); |
| res := UECUPS_CodecPort_CtrlFunct.f_IPL4_connect(UECUPS, mp_uecups_host, mp_uecups_port, "", -1, -1, { sctp := valueof(ts_SCTP) }); |
| if (not ispresent(res.connId)) { |
| setverdict(fail, "Could not connect UECUPS socket, check your configuration"); |
| testcase.stop; |
| } |
| g_uecups_conn_id := res.connId; |
| |
| /* clear all tunnel state in the daemon at start */ |
| f_uecups_xceive({reset_all_state := {}}, {reset_all_state_res:=?}, 30.0); |
| } |
| |
| /* make sure we always pass incoming UECUPS indications whenever receiving fom the UECUPS port */ |
| activate(as_uecups_ind()); |
| } |
| |
| private altstep as_uecups_ind() runs on GTPv2_Emulation_CT { |
| var UECUPS_RecvFrom rx; |
| var GTP2_ConnHdlr vc_conn; |
| /* handle incoming program_term_ind; dispatch to whatever component started the process */ |
| [] UECUPS.receive(tr_UECUPS_RecvFrom_R({program_term_ind:=?})) -> value rx { |
| vc_conn := f_comp_by_pid(rx.msg.program_term_ind.pid); |
| CLIENT.send(rx.msg.program_term_ind) to vc_conn; |
| /* FIXME: remove from table */ |
| repeat; |
| } |
| } |
| |
| function main(Gtp2EmulationCfg cfg) runs on GTPv2_Emulation_CT { |
| var Gtp2cUnitdata g2c_ud; |
| var PDU_GTPCv2 g2c; |
| var GTP2_ConnHdlr vc_conn; |
| var hexstring imsi; |
| var OCT4 teid; |
| var PDU_UECUPS rx_uecups; |
| var UECUPS_CreateTun gtc; |
| var UECUPS_DestroyTun gtd; |
| var UECUPS_StartProgram sprog; |
| |
| f_init(cfg); |
| |
| while (true) { |
| alt { |
| /* route inbound GTP2-C based on TEID, SEQ or IMSI */ |
| [] GTP2C.receive(Gtp2cUnitdata:?) -> value g2c_ud { |
| var template hexstring imsi_t := f_gtp2c_extract_imsi(g2c_ud.gtpc); |
| if (not ispresent(g2c_ud.gtpc.tEID) or g2c_ud.gtpc.tEID == int2oct(0, 4)) { |
| /* if this is a response, route by SEQ */ |
| if (match(g2c_ud.gtpc, tr_PDU_GTP2C_msgtypes(gtp2_responses)) |
| and f_seq_known(g2c_ud.gtpc.sequenceNumber)) { |
| vc_conn := f_comp_by_seq(g2c_ud.gtpc.sequenceNumber); |
| CLIENT.send(g2c_ud.gtpc) to vc_conn; |
| } else { |
| TEID0.send(g2c_ud.gtpc); |
| } |
| } else if (ispresent(g2c_ud.gtpc.tEID) and g2c_ud.gtpc.tEID != int2oct(0, 4)) { |
| vc_conn := f_comp_by_teid(g2c_ud.gtpc.tEID); |
| CLIENT.send(g2c_ud.gtpc) to vc_conn; |
| } else if (isvalue(imsi_t)) { |
| vc_conn := f_comp_by_imsi(valueof(imsi_t)); |
| CLIENT.send(g2c_ud.gtpc) to vc_conn; |
| } else { |
| /* Send to all clients */ |
| var integer i; |
| for (i := 0; i < sizeof(TidTable); i := i+1) { |
| if (isbound(TidTable[i].teid) and TidTable[i].teid == teid) { |
| CLIENT.send(g2c_ud.gtpc) to TidTable[i].vc_conn; |
| } |
| } |
| } |
| |
| /* remove sequence number if response was received */ |
| if (match(g2c_ud.gtpc, tr_PDU_GTP2C_msgtypes(gtp2_responses))) { |
| f_seq_tbl_del(g2c_ud.gtpc.sequenceNumber); |
| } |
| |
| } |
| |
| [] TEID0.receive(PDU_GTPCv2:?) -> value g2c sender vc_conn { |
| /* patch in the next sequence number */ |
| /* FIXME: do this only for outbound requests */ |
| g2c.sequenceNumber := int2oct(g_c_seq_nr, 3); |
| g_c_seq_nr := g_c_seq_nr + 1; |
| /* build Gtp2cUnitdata */ |
| g2c_ud := { peer := g_peer, gtpc := g2c }; |
| GTP2C.send(g2c_ud); |
| if (match(g2c, tr_PDU_GTP2C_msgtypes(gtp2_requests))) { |
| f_seq_tbl_add(g2c.sequenceNumber, vc_conn); |
| } |
| } |
| |
| [] CLIENT.receive(PDU_GTPCv2:?) -> value g2c sender vc_conn { |
| /* patch in the next sequence number */ |
| /* FIXME: do this only for outbound requests */ |
| g2c.sequenceNumber := int2oct(g_c_seq_nr, 3); |
| g_c_seq_nr := g_c_seq_nr + 1; |
| /* build Gtp2cUnitdata */ |
| g2c_ud := { peer := g_peer, gtpc := g2c }; |
| GTP2C.send(g2c_ud); |
| if (match(g2c, tr_PDU_GTP2C_msgtypes(gtp2_requests))) { |
| f_seq_tbl_add(g2c.sequenceNumber, vc_conn); |
| } |
| } |
| |
| [] CLIENT_PROC.getcall(GTP2EM_register_imsi:{?}) -> param(imsi) sender vc_conn { |
| f_imsi_tbl_add(imsi, vc_conn); |
| CLIENT_PROC.reply(GTP2EM_register_imsi:{imsi}) to vc_conn; |
| } |
| |
| [] CLIENT_PROC.getcall(GTP2EM_register_teid:{?}) -> param(teid) sender vc_conn { |
| f_tid_tbl_add(teid, vc_conn); |
| CLIENT_PROC.reply(GTP2EM_register_teid:{teid}) to vc_conn; |
| } |
| [] CLIENT_PROC.getcall(GTP2EM_allocate_teid:{}) -> sender vc_conn { |
| var OCT4 t := f_alloc_teid(); |
| f_tid_tbl_add(t, vc_conn); |
| CLIENT_PROC.reply(GTP2EM_allocate_teid:{} value t) to vc_conn; |
| } |
| [] CLIENT_PROC.getcall(GTP2EM_create_tunnel:{?}) -> param(gtc) sender vc_conn { |
| rx_uecups := f_uecups_xceive({create_tun := gtc}, {create_tun_res:={result:=OK}}); |
| CLIENT_PROC.reply(GTP2EM_create_tunnel:{gtc}) to vc_conn; |
| } |
| [] CLIENT_PROC.getcall(GTP2EM_destroy_tunnel:{?}) -> param(gtd) sender vc_conn { |
| rx_uecups := f_uecups_xceive({destroy_tun := gtd}, {destroy_tun_res:={result:=OK}}); |
| CLIENT_PROC.reply(GTP2EM_destroy_tunnel:{gtd}) to vc_conn; |
| } |
| [] CLIENT_PROC.getcall(GTP2EM_start_program:{?}) -> param(sprog) sender vc_conn { |
| rx_uecups := f_uecups_xceive({start_program := sprog}, {start_program_res:=?}); |
| /* if successful: store (pid, vc_conn) tuple so we can route program_term_ind */ |
| if (rx_uecups.start_program_res.result == OK) { |
| f_pid_tbl_add(rx_uecups.start_program_res.pid, vc_conn); |
| } |
| CLIENT_PROC.reply(GTP2EM_start_program:{sprog} value rx_uecups.start_program_res) to vc_conn; |
| } |
| |
| } |
| } |
| } |
| |
| |
| /*********************************************************************** |
| * Interaction between Main and Client Components |
| ***********************************************************************/ |
| type port GTP2EM_PT message { |
| inout PDU_GTPCv2, UECUPS_ProgramTermInd; |
| } with { extension "internal" }; |
| |
| signature GTP2EM_register_imsi(hexstring imsi); |
| signature GTP2EM_register_teid(OCT4 teid); |
| signature GTP2EM_allocate_teid() return OCT4; |
| signature GTP2EM_create_tunnel(UECUPS_CreateTun gtc); |
| signature GTP2EM_destroy_tunnel(UECUPS_DestroyTun gtd); |
| signature GTP2EM_start_program(UECUPS_StartProgram sprog) return UECUPS_StartProgramRes; |
| |
| type port GTP2EM_PROC_PT procedure { |
| inout GTP2EM_register_imsi, GTP2EM_register_teid, GTP2EM_allocate_teid, |
| GTP2EM_create_tunnel, GTP2EM_destroy_tunnel, GTP2EM_start_program; |
| } with { extension "internal" }; |
| |
| /*********************************************************************** |
| * Client Component |
| ***********************************************************************/ |
| |
| type component GTP2_ConnHdlr { |
| port GTP2EM_PT GTP2; |
| port GTP2EM_PROC_PT GTP2_PROC; |
| }; |
| |
| function f_gtp2_register_imsi(hexstring imsi) runs on GTP2_ConnHdlr { |
| GTP2_PROC.call(GTP2EM_register_imsi:{imsi}) { |
| [] GTP2_PROC.getreply(GTP2EM_register_imsi:{imsi}); |
| } |
| } |
| |
| function f_gtp2_register_teid(OCT4 teid) runs on GTP2_ConnHdlr { |
| GTP2_PROC.call(GTP2EM_register_teid:{teid}) { |
| [] GTP2_PROC.getreply(GTP2EM_register_teid:{teid}); |
| } |
| } |
| |
| function f_gtp2_allocate_teid() runs on GTP2_ConnHdlr return OCT4 { |
| var OCT4 t; |
| GTP2_PROC.call(GTP2EM_allocate_teid:{}) { |
| [] GTP2_PROC.getreply(GTP2EM_allocate_teid:{}) -> value t { |
| return t; |
| } |
| } |
| } |
| |
| function f_gtp2_create_tunnel(template (value) UECUPS_CreateTun gtc) |
| runs on GTP2_ConnHdlr { |
| GTP2_PROC.call(GTP2EM_create_tunnel:{valueof(gtc)}) { |
| [] GTP2_PROC.getreply(GTP2EM_create_tunnel:{gtc}); |
| } |
| } |
| |
| function f_gtp2_destroy_tunnel(template (value) UECUPS_DestroyTun gtd) |
| runs on GTP2_ConnHdlr { |
| GTP2_PROC.call(GTP2EM_destroy_tunnel:{valueof(gtd)}) { |
| [] GTP2_PROC.getreply(GTP2EM_destroy_tunnel:{gtd}); |
| } |
| } |
| |
| function f_gtp2_start_program(template (value) UECUPS_StartProgram sprog) |
| runs on GTP2_ConnHdlr return UECUPS_StartProgramRes { |
| var UECUPS_StartProgramRes res; |
| GTP2_PROC.call(GTP2EM_start_program:{valueof(sprog)}) { |
| [] GTP2_PROC.getreply(GTP2EM_start_program:{sprog}) -> value res; |
| } |
| return res; |
| } |
| |
| |
| |
| } |