fixeria has submitted this change. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/39850?usp=email )
(
3 is the latest approved patch-set. No files were changed between the latest approved patch-set and the submitted one. )Change subject: s1gw: add UE multiplex component ......................................................................
s1gw: add UE multiplex component
The UEMux is built upon the ConnHdlr component, allowing to simulate concurrent activity of multiple virtual UEs. This new component will be used in follow-up patches.
Change-Id: I60e6f5d2b9882c27cecd06a2450bda4909c0532a Related: SYS#7288 --- A s1gw/S1GW_UEMux.ttcn M s1gw/gen_links.sh 2 files changed, 328 insertions(+), 1 deletion(-)
Approvals: fixeria: Looks good to me, approved pespin: Looks good to me, but someone else must approve Jenkins Builder: Verified
diff --git a/s1gw/S1GW_UEMux.ttcn b/s1gw/S1GW_UEMux.ttcn new file mode 100644 index 0000000..0768dea --- /dev/null +++ b/s1gw/S1GW_UEMux.ttcn @@ -0,0 +1,327 @@ +module S1GW_UEMux { + +/* UE Multiplexer, runs on top of ConnHdlr component, dispatches S1AP PDUs + * to/from the UE components. In order to receive S1AP PDUs, a UE component + * needs to subscribe for MME_UE_S1AP_ID and/or ENB_UE_S1AP_ID. + * + * (C) 2025 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * Author: Vadim Yanitskiy vyanitskiy@sysmocom.de + * + * 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 General_Types all; +import from Osmocom_Types all; + +import from S1AP_IEs all; +import from S1AP_Types all; +import from S1AP_PDU_Contents all; +import from S1AP_PDU_Descriptions all; +import from S1AP_Functions all; +import from S1GW_ConnHdlr all; + +private type enumerated S1AP_PDU_DIR { + S1AP_PDU_DIR_FROM_MME, + S1AP_PDU_DIR_FROM_ENB +}; + +private type record S1AP_dPDU { + S1AP_PDU_DIR dir, + S1AP_PDU pdu +}; + +private altstep as_s1ap_dpdu(out S1AP_PDU pdu, + template (present) S1AP_PDU_DIR dir := ?, + template (present) S1AP_PDU tr_pdu := ?) +runs on UEMuxUE { + var S1AP_dPDU dpdu; + + [] UEMUX_CONN.receive(S1AP_dPDU:{dir, tr_pdu}) -> value dpdu { + pdu := dpdu.pdu; + } +} + +type port UEMUX_CONN_PT message { + inout S1AP_dPDU; +} with { extension "internal" }; + +type component UEMuxUE { + port UEMUX_CONN_PT UEMUX_CONN; + port UEMUX_PROC_PT UEMUX_PROC; +}; + +type component UEMux_CT extends ConnHdlr, UEMuxUE { + /* registered UE components */ + var UE_ConnList g_uemux_list := { }; +}; + + +/* represents a single UE connection */ +private type record UE_ConnData { + UEMuxUE vc_conn, + MME_UE_S1AP_ID mme_ue_id optional, + ENB_UE_S1AP_ID enb_ue_id optional +}; +private type record of UE_ConnData UE_ConnList; + +/*********************************************************************************** + * UE connection management API + **********************************************************************************/ + +/* find a UE connection [index] by an MME_UE_S1AP_ID */ +private function f_UEMux_find_ue_by_mme_ue_id(MME_UE_S1AP_ID mme_ue_id) +runs on UEMux_CT return integer { + for (var integer i := 0; i < lengthof(g_uemux_list); i := i + 1) { + if (g_uemux_list[i].mme_ue_id == mme_ue_id) { + return i; + } + } + + return -1; +} + +/* find a UE connection [index] by an ENB_UE_S1AP_ID */ +private function f_UEMux_find_ue_by_enb_ue_id(ENB_UE_S1AP_ID enb_ue_id) +runs on UEMux_CT return integer { + for (var integer i := 0; i < lengthof(g_uemux_list); i := i + 1) { + if (g_uemux_list[i].enb_ue_id == enb_ue_id) { + return i; + } + } + + return -1; +} + +/* find a UE connection [index] for the given S1AP PDU */ +private function f_UEMux_find_ue_for_pdu(in S1AP_PDU pdu) +runs on UEMux_CT return integer { + var template (omit) MME_UE_S1AP_ID mme_ue_id; + var template (omit) ENB_UE_S1AP_ID enb_ue_id; + var integer idx; + + mme_ue_id := f_S1AP_get_MME_UE_S1AP_ID(pdu); + if (isvalue(mme_ue_id)) { + idx := f_UEMux_find_ue_by_mme_ue_id(valueof(mme_ue_id)); + if (idx != -1) { + return idx; + } + } + + enb_ue_id := f_S1AP_get_ENB_UE_S1AP_ID(pdu); + if (isvalue(enb_ue_id)) { + idx := f_UEMux_find_ue_by_enb_ue_id(valueof(enb_ue_id)); + if (idx != -1) { + return idx; + } + } + + return -1; +} + +/* find a UE connection [index] by component reference */ +private function f_UEMux_find_ue_by_vc_conn(UEMuxUE vc_conn) +runs on UEMux_CT return integer { + for (var integer i := 0; i < lengthof(g_uemux_list); i := i + 1) { + if (g_uemux_list[i].vc_conn == vc_conn) { + return i; + } + } + + return -1; +} + +private function f_UEMux_route_pdu(S1AP_dPDU dpdu) +runs on UEMux_CT { + var integer idx; + + idx := f_UEMux_find_ue_for_pdu(dpdu.pdu); + if (idx >= 0) { + UEMUX_CONN.send(dpdu) to g_uemux_list[idx].vc_conn; + } else { + log("S1AP PDU cannot be routed: ", dpdu.pdu); + } +} + +/* add a new UE connection, return its index */ +private function f_UEMux_ue_add(UEMuxUE vc_conn) +runs on UEMux_CT return integer { + var integer idx; + + idx := f_UEMux_find_ue_by_vc_conn(vc_conn); + if (idx != -1) { /* already registered */ + return idx; + } + + idx := lengthof(g_uemux_list); + g_uemux_list[idx] := { vc_conn, omit, omit }; + log("UE ", vc_conn, " is registered (idx=", idx, ")"); + + return idx; +} + +/* subscribe a UE component for S1AP PDUs with the given MME-UE-ID */ +private function f_UEMux_subscribe_for_mme_ue_id(UEMuxUE vc_conn, + MME_UE_S1AP_ID mme_ue_id) +runs on UEMux_CT { + var integer idx; + + if (f_UEMux_find_ue_by_mme_ue_id(mme_ue_id) != -1) { + setverdict(fail, "MME-UE-ID ", mme_ue_id," is already used"); + mtc.stop; + } + + idx := f_UEMux_ue_add(vc_conn); + g_uemux_list[idx].mme_ue_id := mme_ue_id; +} + +/* subscribe a UE component for S1AP PDUs with the given ENB-UE-ID */ +private function f_UEMux_subscribe_for_enb_ue_id(UEMuxUE vc_conn, + ENB_UE_S1AP_ID enb_ue_id) +runs on UEMux_CT { + var integer idx; + + if (f_UEMux_find_ue_by_enb_ue_id(enb_ue_id) != -1) { + setverdict(fail, "ENB-UE-ID ", enb_ue_id," is already used"); + mtc.stop; + } + + idx := f_UEMux_ue_add(vc_conn); + g_uemux_list[idx].enb_ue_id := enb_ue_id; +} + +/* unsubscribe a UE component */ +private function f_UEMux_unsubscribe(UEMuxUE vc_conn) +runs on UEMux_CT { + var UE_ConnList ue_list := { }; + + for (var integer i := 0; i < lengthof(g_uemux_list); i := i + 1) { + if (g_uemux_list[i].vc_conn != vc_conn) { + ue_list := ue_list & { g_uemux_list[i] }; + } + } + + if (lengthof(ue_list) == lengthof(g_uemux_list)) { + setverdict(fail, "UE ", vc_conn, " is not known"); + mtc.stop; + } + + g_uemux_list := ue_list; + log("UE ", vc_conn, " is unregistered"); +} + +signature UEMux_subscribe_for_mme_ue_id(in MME_UE_S1AP_ID mme_ue_id); +signature UEMux_subscribe_for_enb_ue_id(in ENB_UE_S1AP_ID enb_ue_id); +signature UEMux_unsubscribe(); + +type port UEMUX_PROC_PT procedure { + inout UEMux_subscribe_for_mme_ue_id; + inout UEMux_subscribe_for_enb_ue_id; + inout UEMux_unsubscribe; +} with { extension "internal" }; + +/*********************************************************************************** + * public API + **********************************************************************************/ + +function f_UEMuxUE_subscribe_for_mme_ue_id(MME_UE_S1AP_ID mme_ue_id) +runs on UEMuxUE { + UEMUX_PROC.call(UEMux_subscribe_for_mme_ue_id:{mme_ue_id}) { + [] UEMUX_PROC.getreply(UEMux_subscribe_for_mme_ue_id:{mme_ue_id}); + } +} + +function f_UEMuxUE_subscribe_for_enb_ue_id(ENB_UE_S1AP_ID enb_ue_id) +runs on UEMuxUE { + UEMUX_PROC.call(UEMux_subscribe_for_enb_ue_id:{enb_ue_id}) { + [] UEMUX_PROC.getreply(UEMux_subscribe_for_enb_ue_id:{enb_ue_id}); + } +} + +function f_UEMuxUE_unsubscribe() +runs on UEMuxUE { + UEMUX_PROC.call(UEMux_unsubscribe:{}) { + [] UEMUX_PROC.getreply(UEMux_unsubscribe:{}); + } +} + +function f_UEMuxUE_tx_s1ap_from_mme(template (value) S1AP_PDU pdu) +runs on UEMuxUE { + var S1AP_dPDU dpdu := { S1AP_PDU_DIR_FROM_MME, valueof(pdu) }; + UEMUX_CONN.send(dpdu); +} + +function f_UEMuxUE_tx_s1ap_from_enb(template (value) S1AP_PDU pdu) +runs on UEMuxUE { + var S1AP_dPDU dpdu := { S1AP_PDU_DIR_FROM_ENB, valueof(pdu) }; + UEMUX_CONN.send(dpdu); +} + +altstep as_UEMuxUE_s1ap_from_mme(out S1AP_PDU pdu, + template (present) S1AP_PDU tr_pdu := ?) +runs on UEMuxUE { + [] as_s1ap_dpdu(pdu, S1AP_PDU_DIR_FROM_MME, tr_pdu); +} + +altstep as_UEMuxUE_s1ap_from_enb(out S1AP_PDU pdu, + template (present) S1AP_PDU tr_pdu := ?) +runs on UEMuxUE { + [] as_s1ap_dpdu(pdu, S1AP_PDU_DIR_FROM_ENB, tr_pdu); +} + +type function void_fn2(charstring id) runs on UEMux_CT; + +function f_UEMux_init(void_fn2 fn, charstring id, ConnHdlrPars pars) +runs on UEMux_CT { + g_pars := pars; + fn.apply(id); +} + +function main() runs on UEMux_CT { + while (true) { + var MME_UE_S1AP_ID mme_ue_id; + var ENB_UE_S1AP_ID enb_ue_id; + var UEMuxUE vc_conn; + var S1AP_PDU pdu; + + alt { + /* MME originated S1AP PDU to S1GW */ + [] as_s1ap_dpdu(pdu, S1AP_PDU_DIR_FROM_MME) { + f_ConnHdlr_tx_s1ap_from_mme(pdu); + } + /* eNB originated S1AP PDU to S1GW */ + [] as_s1ap_dpdu(pdu, S1AP_PDU_DIR_FROM_ENB) { + f_ConnHdlr_tx_s1ap_from_enb(pdu); + } + + /* MME originated S1AP PDU from S1GW */ + [] as_ConnHdlr_s1ap_from_mme(pdu) { + f_UEMux_route_pdu({S1AP_PDU_DIR_FROM_MME, pdu}); + } + /* eNB originated S1AP PDU from S1GW */ + [] as_ConnHdlr_s1ap_from_enb(pdu) { + f_UEMux_route_pdu({S1AP_PDU_DIR_FROM_ENB, pdu}); + } + + /* UE subsciption procedures */ + [] UEMUX_PROC.getcall(UEMux_subscribe_for_mme_ue_id:{?}) -> param(mme_ue_id) sender vc_conn { + f_UEMux_subscribe_for_mme_ue_id(vc_conn, mme_ue_id); + UEMUX_PROC.reply(UEMux_subscribe_for_mme_ue_id:{mme_ue_id}) to vc_conn; + } + [] UEMUX_PROC.getcall(UEMux_subscribe_for_enb_ue_id:{?}) -> param(enb_ue_id) sender vc_conn { + f_UEMux_subscribe_for_enb_ue_id(vc_conn, enb_ue_id); + UEMUX_PROC.reply(UEMux_subscribe_for_enb_ue_id:{enb_ue_id}) to vc_conn; + } + [] UEMUX_PROC.getcall(UEMux_unsubscribe:{}) -> sender vc_conn { + f_UEMux_unsubscribe(vc_conn); + UEMUX_PROC.reply(UEMux_unsubscribe:{}) to vc_conn; + } + } + } +} + +} diff --git a/s1gw/gen_links.sh b/s1gw/gen_links.sh index 63c826e..875e028 100755 --- a/s1gw/gen_links.sh +++ b/s1gw/gen_links.sh @@ -30,7 +30,7 @@ DIR=../library FILES="Misc_Helpers.ttcn Mutex.ttcn General_Types.ttcn Osmocom_Types.ttcn Native_Functions.ttcn Native_FunctionDefs.cc " FILES+="PFCP_CodecPort.ttcn PFCP_CodecPort_CtrlFunct.ttcn PFCP_CodecPort_CtrlFunctDef.cc PFCP_Emulation.ttcn PFCP_Templates.ttcn " -FILES+="S1AP_CodecPort.ttcn S1AP_CodecPort_CtrlFunctDef.cc S1AP_CodecPort_CtrlFunct.ttcn " +FILES+="S1AP_CodecPort.ttcn S1AP_CodecPort_CtrlFunctDef.cc S1AP_CodecPort_CtrlFunct.ttcn S1AP_Functions.ttcn " FILES+="SCTP_Templates.ttcn " FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcnpp " gen_links $DIR $FILES