pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/40366?usp=email )
Change subject: library: Introduce NGAP Emulation ...................................................................... library: Introduce NGAP Emulation Only initial features are working, like sending NG Setup Request + Response. Change-Id: I5aea8be12c54cf907e71bffe6456efb5e60eb203 --- A library/NGAP_CodecPort.ttcn A library/NGAP_CodecPort_CtrlFunct.ttcn A library/NGAP_CodecPort_CtrlFunctDef.cc A library/NGAP_Emulation.ttcn A library/NGAP_Functions.ttcn 5 files changed, 890 insertions(+), 0 deletions(-) git pull ssh://gerrit.osmocom.org:29418/osmo-ttcn3-hacks refs/changes/66/40366/1 diff --git a/library/NGAP_CodecPort.ttcn b/library/NGAP_CodecPort.ttcn new file mode 100644 index 0000000..95868ac --- /dev/null +++ b/library/NGAP_CodecPort.ttcn @@ -0,0 +1,83 @@ +module NGAP_CodecPort { + +/* Simple NGAP Codec Port, translating between raw SCTP primitives with + * octetstring payload towards the IPL4asp provider, and NGAP primitives + * which carry the decoded NGAP data types as payload. + * + * (C) 2019 by Harald Welte <lafo...@gnumonks.org> + * (C) 2025 by sysmocom - s.f.m.c. GmbH <i...@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 IPL4asp_PortType all; + import from IPL4asp_Types all; + import from NGAP_PDU_Descriptions all; + import from NGAP_Types all; + + type record NGAP_RecvFrom { + ConnectionId connId, + HostName remName, + PortNumber remPort, + HostName locName, + PortNumber locPort, + NGAP_PDU msg + }; + + template NGAP_RecvFrom t_NGAP_RecvFrom(template NGAP_PDU msg) := { + connId := ?, + remName := ?, + remPort := ?, + locName := ?, + locPort := ?, + msg := msg + } + + type record NGAP_Send { + ConnectionId connId, + NGAP_PDU msg + } + + template NGAP_Send t_NGAP_Send(template ConnectionId connId, template NGAP_PDU msg) := { + connId := connId, + msg := msg + } + + private function IPL4_to_NGAP_RecvFrom(in ASP_RecvFrom pin, out NGAP_RecvFrom pout) { + pout.connId := pin.connId; + pout.remName := pin.remName; + pout.remPort := pin.remPort; + pout.locName := pin.locName; + pout.locPort := pin.locPort; + pout.msg := dec_NGAP_PDU(pin.msg); + } with { extension "prototype(fast)" }; + + private function NGAP_to_IPL4_Send(in NGAP_Send pin, out ASP_Send pout) { + pout.connId := pin.connId; + pout.proto := { + sctp := { + sinfo_stream := omit, + sinfo_ppid := 60, + remSocks := omit, + assocId := omit + } + }; + pout.msg := enc_NGAP_PDU(pin.msg); + } with { extension "prototype(fast)" }; + + type port NGAP_CODEC_PT message { + out NGAP_Send; + in NGAP_RecvFrom, + ASP_ConnId_ReadyToRelease, + ASP_Event; + } with { extension "user IPL4asp_PT + out(NGAP_Send -> ASP_Send:function(NGAP_to_IPL4_Send)) + in(ASP_RecvFrom -> NGAP_RecvFrom: function(IPL4_to_NGAP_RecvFrom); + ASP_ConnId_ReadyToRelease -> ASP_ConnId_ReadyToRelease: simple; + ASP_Event -> ASP_Event: simple)" + } +} diff --git a/library/NGAP_CodecPort_CtrlFunct.ttcn b/library/NGAP_CodecPort_CtrlFunct.ttcn new file mode 100644 index 0000000..d2392d1 --- /dev/null +++ b/library/NGAP_CodecPort_CtrlFunct.ttcn @@ -0,0 +1,44 @@ +module NGAP_CodecPort_CtrlFunct { + + import from NGAP_CodecPort all; + import from IPL4asp_Types all; + + external function f_IPL4_listen( + inout NGAP_CODEC_PT portRef, + in HostName locName, + in PortNumber locPort, + in ProtoTuple proto, + in OptionList options := {} + ) return Result; + + external function f_IPL4_connect( + inout NGAP_CODEC_PT portRef, + in HostName remName, + in PortNumber remPort, + in HostName locName, + in PortNumber locPort, + in ConnectionId connId, + in ProtoTuple proto, + in OptionList options := {} + ) return Result; + + external function f_IPL4_close( + inout NGAP_CODEC_PT portRef, + in ConnectionId id, + in ProtoTuple proto := { unspecified := {} } + ) return Result; + + external function f_IPL4_setUserData( + inout NGAP_CODEC_PT portRef, + in ConnectionId id, + in UserData userData + ) return Result; + + external function f_IPL4_getUserData( + inout NGAP_CODEC_PT portRef, + in ConnectionId id, + out UserData userData + ) return Result; + +} + diff --git a/library/NGAP_CodecPort_CtrlFunctDef.cc b/library/NGAP_CodecPort_CtrlFunctDef.cc new file mode 100644 index 0000000..480c002 --- /dev/null +++ b/library/NGAP_CodecPort_CtrlFunctDef.cc @@ -0,0 +1,56 @@ +#include "IPL4asp_PortType.hh" +#include "NGAP_CodecPort.hh" +#include "IPL4asp_PT.hh" + +namespace NGAP__CodecPort__CtrlFunct { + + IPL4asp__Types::Result f__IPL4__listen( + NGAP__CodecPort::NGAP__CODEC__PT& portRef, + const IPL4asp__Types::HostName& locName, + const IPL4asp__Types::PortNumber& locPort, + const IPL4asp__Types::ProtoTuple& proto, + const IPL4asp__Types::OptionList& options) + { + return f__IPL4__PROVIDER__listen(portRef, locName, locPort, proto, options); + } + + IPL4asp__Types::Result f__IPL4__connect( + NGAP__CodecPort::NGAP__CODEC__PT& portRef, + const IPL4asp__Types::HostName& remName, + const IPL4asp__Types::PortNumber& remPort, + const IPL4asp__Types::HostName& locName, + const IPL4asp__Types::PortNumber& locPort, + const IPL4asp__Types::ConnectionId& connId, + const IPL4asp__Types::ProtoTuple& proto, + const IPL4asp__Types::OptionList& options) + { + return f__IPL4__PROVIDER__connect(portRef, remName, remPort, + locName, locPort, connId, proto, options); + } + + IPL4asp__Types::Result f__IPL4__close( + NGAP__CodecPort::NGAP__CODEC__PT& portRef, + const IPL4asp__Types::ConnectionId& connId, + const IPL4asp__Types::ProtoTuple& proto) + { + return f__IPL4__PROVIDER__close(portRef, connId, proto); + } + + IPL4asp__Types::Result f__IPL4__setUserData( + NGAP__CodecPort::NGAP__CODEC__PT& portRef, + const IPL4asp__Types::ConnectionId& connId, + const IPL4asp__Types::UserData& userData) + { + return f__IPL4__PROVIDER__setUserData(portRef, connId, userData); + } + + IPL4asp__Types::Result f__IPL4__getUserData( + NGAP__CodecPort::NGAP__CODEC__PT& portRef, + const IPL4asp__Types::ConnectionId& connId, + IPL4asp__Types::UserData& userData) + { + return f__IPL4__PROVIDER__getUserData(portRef, connId, userData); + } + +} + diff --git a/library/NGAP_Emulation.ttcn b/library/NGAP_Emulation.ttcn new file mode 100644 index 0000000..d599b4a --- /dev/null +++ b/library/NGAP_Emulation.ttcn @@ -0,0 +1,615 @@ +module NGAP_Emulation { + +/* NGAP Emulation, runs on top of NGAP_CodecPort. It multiplexes/demultiplexes + * the individual subscribers by their UE association (AMF_UE_NGAP_ID/ + * RAN_UE_NGAP_ID identifiers), so there can be separate TTCN-3 components + * handling each of them. + * + * The NGAP_Emulation.main() function processes NGAP primitives from the NGAP + * socket via the NGAP_CodecPort, and dispatches them to the per-subscriber + * components. + * + * For each new subscriber, the NGapOps.create_cb() is called. It can create + * or resolve a TTCN-3 component, and returns a component reference to which + * that subscriber traffic is routed/dispatched. + * + * If a pre-existing component wants to register to handle a future inbound UE + * association, it can do so by registering an "expect" with the expected + * AMF_UE_NGAP_ID/RAN_UE_NGAP_ID identifiers. It is also possible to register + * an expect for a specific procedureCode, in case the expected message is non + * UE related (unit-data). + * + * Inbound non-UE related NGAP messages (such as RESET, SETUP, OVERLOAD) are + * dispatched to the NGapOps.unitdata_cb() callback, which is registered with + * an argument to the main() function below. + * + * (C) 2019 by Harald Welte <lafo...@gnumonks.org> + * (C) 2025 by sysmocom - s.f.m.c. GmbH <i...@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 NGAP_CodecPort all; +import from NGAP_CodecPort_CtrlFunct all; +import from NGAP_Types all; +import from NGAP_Constants all; +import from NGAP_PDU_Contents all; +import from NGAP_PDU_Descriptions all; +import from NGAP_IEs all; +import from NGAP_Templates all; +import from NGAP_Functions all; +import from SCTP_Templates all; + +import from General_Types all; +import from Osmocom_Types all; +import from IPL4asp_Types all; +import from DNS_Helpers all; + + +type component NGAP_ConnHdlr { + port NGAP_Conn_PT NGAP; + /* procedure based port to register for incoming connections */ + port NGAPEM_PROC_PT NGAP_PROC; +} + +/* port between individual per-connection components and this dispatcher */ +type port NGAP_Conn_PT message { + inout NGAP_PDU; +} with { extension "internal" }; + + +type enumerated NGAPEM_EventUpDown { + NGAPEM_EVENT_DOWN, + NGAPEM_EVENT_UP +} + +/* an event indicating us whether or not a connection is physically up or down, + * and whether we have received an ID_ACK */ +type union NGAPEM_Event { + NGAPEM_EventUpDown up_down +} + +/* global test port e.g. for non-imsi/conn specific messages */ +type port NGAP_PT message { + inout NGAP_PDU, NGAPEM_Event; +} with { extension "internal" }; + +/* "Expect" Handling */ + +type record ExpectData { + AMF_UE_NGAP_ID amf_id optional, + RAN_UE_NGAP_ID ran_id optional, + NGAP_ConnHdlr vc_conn +} + +/* represents a single NGAP PDU that we expect. When a matching PDU is seen, it is forwarded to the registered + * component */ +type record ExpectDataProc { + integer procedureCode optional, + NGAP_ConnHdlr vc_conn /* component handling this UE connection */ +}; + +signature NGAPEM_register(in AMF_UE_NGAP_ID amf_id, in RAN_UE_NGAP_ID ran_id, in NGAP_ConnHdlr hdlr); +signature NGAPEM_register_proc(in integer procedureCode, in NGAP_ConnHdlr hdlr); +//signature NGAPEM_derive_nas_token(in octetstring kasme, in NGAP_ConnHdlr hdlr, out OCT32 nas_token); + +type port NGAPEM_PROC_PT procedure { + inout NGAPEM_register; + inout NGAPEM_register_proc; + //inout NGAPEM_derive_nas_token; +} with { extension "internal" }; + +/* Function that can be used as create_cb and will use the expect table */ +function ExpectedCreateCallback(NGAP_PDU msg, + template (omit) AMF_UE_NGAP_ID amf_id, + template (omit) RAN_UE_NGAP_ID ran_id, + charstring id) +runs on NGAP_Emulation_CT return NGAP_ConnHdlr { + var NGAP_ConnHdlr ret := null; + var integer i; + + for (i := 0; i < sizeof(NGapExpectTable); i := i+1) { + if (not ispresent(NGapExpectTable[i].amf_id) and not ispresent(NGapExpectTable[i].ran_id)) { + continue; + } + + if (valueof(amf_id) == NGapExpectTable[i].amf_id and valueof(ran_id) == NGapExpectTable[i].ran_id) { + ret := NGapExpectTable[i].vc_conn; + /* Release this entry */ + NGapExpectTable[i].amf_id := omit; + NGapExpectTable[i].ran_id := omit; + NGapExpectTable[i].vc_conn := null; + log("Found Expect[", i, "] for ", msg, " handled at ", ret); + return ret; + } + } + setverdict(fail, "Couldn't find Expect for ", msg); + mtc.stop; +} + +/* represents a single NGAP Association */ +type record AssociationData { + NGAP_ConnHdlr comp_ref, /* component handling this UE connection */ + uint32_t ran_ue_ngap_id optional, /* eNB side NGAP ID */ + uint40_t amf_ue_ngap_id optional//, /* MME side NGAP ID */ + //EUTRAN_CGI cgi optional, + //TAI tai optional, + //NAS_UE_State nus +}; + +type component NGAP_Emulation_CT { + /* Port facing to the UDP SUT */ + port NGAP_CODEC_PT NGAP; + /* All NGAP_ConnHdlr NGAP ports connect here + * NGAP_Emulation_CT.main needs to figure out what messages + * to send where with CLIENT.send() to vc_conn */ + port NGAP_Conn_PT NGAP_CLIENT; + /* currently tracked connections */ + var AssociationData NGapAssociationTable[16]; + /* pending expected NGAP Association (UE oriented) */ + var ExpectData NGapExpectTable[8]; + /* pending expected NGAP PDU */ + var ExpectDataProc NGapExpectTableProc[8]; + /* procedure based port to register for incoming connections */ + port NGAPEM_PROC_PT NGAP_PROC; + /* test port for unit data messages */ + port NGAP_PT NGAP_UNIT; + + var NGAP_conn_parameters g_pars; + var charstring g_ngap_id; + var integer g_ngap_conn_id := -1; +} + +type function NGAPCreateCallback(NGAP_PDU msg, + template (omit) AMF_UE_NGAP_ID amf_id, + template (omit) RAN_UE_NGAP_ID ran_id, + charstring id) +runs on NGAP_Emulation_CT return NGAP_ConnHdlr; + +type function NGAPUnitdataCallback(NGAP_PDU msg) +runs on NGAP_Emulation_CT return template NGAP_PDU; + +type record NGAPOps { + NGAPCreateCallback create_cb, + NGAPUnitdataCallback unitdata_cb +} + +type record NGAP_conn_parameters { + HostName remote_ip, + IPL4asp_Types.PortNumber remote_sctp_port, + HostName local_ip, + IPL4asp_Types.PortNumber local_sctp_port//, + //NAS_Role role +} + +function tr_NGAP_RecvFrom_R(template NGAP_PDU msg) +runs on NGAP_Emulation_CT return template NGAP_Recv>From { + var template NGAP_RecvFrom mrf := { + connId := g_ngap_conn_id, + remName := ?, + remPort := ?, + locName := ?, + locPort := ?, + msg := msg + } + return mrf; +} + +private function f_ngap_ids_known(template (omit) AMF_UE_NGAP_ID amf_id, + template (omit) RAN_UE_NGAP_ID ran_id) +runs on NGAP_Emulation_CT return boolean { + var integer i; + log("f_ngap_ids_known(",amf_id,", ",ran_id,")"); + for (i := 0; i < sizeof(NGapAssociationTable); i := i+1) { + log("tbl[",i,"]: mme=", NGapAssociationTable[i].amf_ue_ngap_id, + ", enb=", NGapAssociationTable[i].ran_ue_ngap_id); + /* skip empty records */ + if (NGapAssociationTable[i].amf_ue_ngap_id == omit and + NGapAssociationTable[i].ran_ue_ngap_id == omit) { + log("skipping empty ", i); + continue; + } + if (NGapAssociationTable[i].amf_ue_ngap_id == omit) { + log("entry ", i, " has no MME ID yet (enb=", NGapAssociationTable[i].ran_ue_ngap_id); + /* Table doesn't yet know the MME side ID, let's look-up only + * based on the eNB side ID */ + if (match(NGapAssociationTable[i].ran_ue_ngap_id, ran_id)) { + /* update table with MME side ID */ + NGapAssociationTable[i].amf_ue_ngap_id := valueof(amf_id); + return true; + } + } else if (match(NGapAssociationTable[i].ran_ue_ngap_id, ran_id) and + match(NGapAssociationTable[i].amf_ue_ngap_id, amf_id)) { + return true; + } + } + return false; +} +private function f_comp_known(NGAP_ConnHdlr client) +runs on NGAP_Emulation_CT return boolean { + var integer i; + for (i := 0; i < sizeof(NGapAssociationTable); i := i+1) { + if (NGapAssociationTable[i].comp_ref == client) { + return true; + } + } + return false; +} + +private function f_assoc_id_by_ngap_ids(template (omit) AMF_UE_NGAP_ID amf_id, + template (omit) RAN_UE_NGAP_ID ran_id) +runs on NGAP_Emulation_CT return integer { + var integer i; + for (i := 0; i < sizeof(NGapAssociationTable); i := i+1) { + if (istemplatekind(ran_id, "omit") or + match(NGapAssociationTable[i].ran_ue_ngap_id, ran_id)) { + if (istemplatekind(amf_id, "omit")) { + return i; + } else { + if (match(NGapAssociationTable[i].amf_ue_ngap_id, amf_id)) { + return i; + } + } + } + } + setverdict(fail, "NGAP Association Table not found by ENB-ID=", ran_id, " MME-ID=", amf_id); + mtc.stop; +} + +private function f_assoc_id_by_comp(NGAP_ConnHdlr client) +runs on NGAP_Emulation_CT return integer { + var integer i; + for (i := 0; i < sizeof(NGapAssociationTable); i := i+1) { + if (NGapAssociationTable[i].comp_ref == client) { + return i; + } + } + setverdict(fail, "NGAP Association Table not found by component ", client); + mtc.stop; +} + +private function f_assoc_by_comp(NGAP_ConnHdlr client) +runs on NGAP_Emulation_CT return AssociationData { + var integer i := f_assoc_id_by_comp(client); + return NGapAssociationTable[i]; +} + +private function f_ngap_id_table_add(NGAP_ConnHdlr comp_ref, + template (omit) AMF_UE_NGAP_ID amf_id, RAN_UE_NGAP_ID ran_id) +runs on NGAP_Emulation_CT return integer { + var integer i; + for (i := 0; i < sizeof(NGapAssociationTable); i := i+1) { + if (not isvalue(NGapAssociationTable[i].ran_ue_ngap_id)) { + NGapAssociationTable[i].ran_ue_ngap_id := ran_id; + if (istemplatekind(amf_id, "omit")) { + NGapAssociationTable[i].amf_ue_ngap_id := omit; + } else { + NGapAssociationTable[i].amf_ue_ngap_id := valueof(amf_id); + } + NGapAssociationTable[i].comp_ref := comp_ref; + return i; + } + } + testcase.stop("NGAP Association Table full!"); + return -1; +} + +private function f_ngap_id_table_del(NGAP_ConnHdlr comp_ref, RAN_UE_NGAP_ID ran_id) +runs on NGAP_Emulation_CT { + var integer i; + for (i := 0; i < sizeof(NGapAssociationTable); i := i+1) { + if (NGapAssociationTable[i].comp_ref == comp_ref and + NGapAssociationTable[i].ran_ue_ngap_id == ran_id) { + NGapAssociationTable[i].ran_ue_ngap_id := omit; + NGapAssociationTable[i].amf_ue_ngap_id := omit; + NGapAssociationTable[i].comp_ref := null; + return; + } + } + setverdict(fail, "NGAP Association Table: Couldn't find to-be-deleted entry!"); + mtc.stop; +} + + +private function f_ngap_id_table_init() +runs on NGAP_Emulation_CT { + for (var integer i := 0; i < sizeof(NGapAssociationTable); i := i+1) { + NGapAssociationTable[i].amf_ue_ngap_id := omit; + NGapAssociationTable[i].ran_ue_ngap_id := omit; + //NGapAssociationTable[i].cgi := omit; + //NGapAssociationTable[i].tai := omit; + //NGapAssociationTable[i].nus := valueof(t_NAS_UE_State(g_pars.role)); + NGapAssociationTable[i].comp_ref := null; + } +} + +private function f_ngap_xceive(template (value) NGAP_PDU tx, + template NGAP_PDU rx_t := ?) +runs on NGAP_Emulation_CT return NGAP_PDU { + timer T := 10.0; + var NGAP_RecvFrom mrf; + + NGAP.send(t_NGAP_Send(g_ngap_conn_id, tx)); + alt { + [] NGAP.receive(tr_NGAP_RecvFrom_R(rx_t)) -> value mrf { } + [] NGAP.receive(tr_SctpAssocChange) { repeat; } + [] NGAP.receive(tr_SctpPeerAddrChange) { repeat; } + [] T.timeout { + setverdict(fail, "Timeout waiting for ", rx_t); + mtc.stop; + } + } + return mrf.msg; +} + +//function handle_NGAP_UeContextReleaseCmd(template (present) NGAP_PDU rel_cmd) runs on NGAP_Emulation_CT { +// if (ispresent(rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_NGAP_IDs.uE_NGAP_ID_pair)) { +// var template AMF_UE_NGAP_ID mme_ue_id; +// var template RAN_UE_NGAP_ID enb_ue_id; +// var integer assoc_id; +// var NGAP_ConnHdlr vc_conn +// +// mme_ue_id := rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_NGAP_IDs.uE_NGAP_ID_pair.mME_UE_NGAP_ID; +// enb_ue_id := rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_NGAP_IDs.uE_NGAP_ID_pair.eNB_UE_NGAP_ID; +// +// assoc_id := f_assoc_id_by_ngap_ids(mme_ue_id, enb_ue_id); +// vc_conn := NGapAssociationTable[assoc_id].comp_ref; +// +// f_ngap_id_table_del(vc_conn, valueof(enb_ue_id)); +// } else { +// /* TODO: The UE CONTEXT RELEASE COMMAND (see also: 3GPP TS 36.413, section 9.1.4.6), may identify the +// * context by either an uE_NGAP_ID_pair (AMF_UE_NGAP_ID and RAN_UE_NGAP_ID) or an AMF_UE_NGAP_ID alone. +// * The latter case is not implemented here yet. */ +// setverdict(fail, "complete implementation of UeContextReleaseCmd handling"); +// mtc.stop; +// } +//} + +private function SendToNGapExpectTableProc(NGAP_PDU msg) runs on NGAP_Emulation_CT { + var integer procedureCode; + var NGAP_ConnHdlr vc_conn; + + if (ispresent(msg.initiatingMessage.procedureCode)) { + procedureCode := msg.initiatingMessage.procedureCode; + } else if (ispresent(msg.unsuccessfulOutcome.procedureCode)) { + procedureCode := msg.unsuccessfulOutcome.procedureCode; + } else if (ispresent(msg.successfulOutcome.procedureCode)) { + procedureCode := msg.successfulOutcome.procedureCode; + } else { + return; + } + + for (var integer i := 0; i < sizeof(NGapExpectTableProc); i := i+1) { + if (NGapExpectTableProc[i].procedureCode == procedureCode) { + vc_conn := NGapExpectTableProc[i].vc_conn; + if (vc_conn != null) { + NGAP_CLIENT.send(msg) to vc_conn; + } + } + } + + return; +} + +function main(NGAPOps ops, NGAP_conn_parameters p, charstring id) runs on NGAP_Emulation_CT { + var Result res; + g_pars := p; + g_ngap_id := id; + f_ngap_id_table_init(); + f_expect_table_init(); + + map(self:NGAP, system:NGAP_CODEC_PT); + if (p.remote_sctp_port == -1) { + res := NGAP_CodecPort_CtrlFunct.f_IPL4_listen(NGAP, p.local_ip, p.local_sctp_port, + { sctp := valueof(ts_SctpTuple(18)) }); + } else { + res := NGAP_CodecPort_CtrlFunct.f_IPL4_connect(NGAP, p.remote_ip, p.remote_sctp_port, + p.local_ip, p.local_sctp_port, -1, + { sctp := valueof(ts_SctpTuple(18)) }); + } + if (not ispresent(res.connId)) { + setverdict(fail, "Could not connect NGAP socket, check your configuration"); + mtc.stop; + } + g_ngap_conn_id := res.connId; + + /* notify user about SCTP establishment */ + if (p.remote_sctp_port != -1) { + NGAP_UNIT.send(NGAPEM_Event:{up_down:=NGAPEM_EVENT_UP}) + } + + while (true) { + var NGAP_ConnHdlr vc_conn; + //var PDU_NAS_EPS nas; + var AMF_UE_NGAP_ID amf_id; + var RAN_UE_NGAP_ID ran_id; + var integer procedureCode; + var NGAP_RecvFrom mrf; + var NGAP_PDU msg; + var charstring vlr_name, mme_name; + var integer ai; + var octetstring kasme; + + alt { + /* NGAP from client: InitialUE */ + [] NGAP_CLIENT.receive(mw_ngap_initMsg(mw_n2_initialUeMessage)) -> value msg sender vc_conn { + /* create a table entry about this connection */ + ai := f_ngap_id_table_add(vc_conn, omit, valueof(f_NGAP_get_RAN_UE_NGAP_ID(msg))); + /* Store CGI + TAI so we can use it for generating UlNasTransport from NAS */ +// NGapAssociationTable[ai].tai := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[2].value_.TAI; +// NGapAssociationTable[ai].cgi := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[3].value_.EUTRAN_CGI; +// /* Pass message through */ + NGAP.send(t_NGAP_Send(g_ngap_conn_id, msg)); + } + /* NGAP from client: pass on transparently */ + [] NGAP_CLIENT.receive(NGAP_PDU:?) -> value msg sender vc_conn { + /* Pass message through */ + /* FIXME: validate NGAP_IDs ? */ + NGAP.send(t_NGAP_Send(g_ngap_conn_id, msg)); + } + + /* non-UE related NGAP: pass through unmodified/unverified */ + [] NGAP_UNIT.receive(NGAP_PDU:?) -> value msg sender vc_conn { + /* Pass message through */ + NGAP.send(t_NGAP_Send(g_ngap_conn_id, msg)); + } + + /* NGAP received from peer (MME) */ + [] NGAP.receive(tr_NGAP_RecvFrom_R(?)) -> value mrf { +// if (match(mrf.msg, tr_NGAP_nonUErelated)) { + /* non-UE-related NGAP message */ + SendToNGapExpectTableProc(mrf.msg); + var template NGAP_PDU resp := ops.unitdata_cb.apply(mrf.msg); + if (isvalue(resp)) { + NGAP.send(t_NGAP_Send(g_ngap_conn_id, valueof(resp))); + } +// } else { +// /* Ue-related NGAP message */ +// /* obtain MME + ENB UE NGAP ID */ +// var template (omit) AMF_UE_NGAP_ID mme_ue_id := f_NGAP_get_AMF_UE_NGAP_ID(mrf.msg); +// var template (omit) RAN_UE_NGAP_ID enb_ue_id := f_NGAP_get_RAN_UE_NGAP_ID(mrf.msg); +// /* check if those IDs are known in our table */ +// if (f_ngap_ids_known(mme_ue_id, enb_ue_id)) { +// /* if yes, dispatch to the ConnHdlr for this Ue-Connection */ +// var template (omit) octetstring nas_enc; +// var integer assoc_id := f_assoc_id_by_ngap_ids(mme_ue_id, enb_ue_id); +// vc_conn := NGapAssociationTable[assoc_id].comp_ref; +// nas_enc := f_NGAP_get_NAS_PDU(mrf.msg); +// if (isvalue(nas_enc)) { +// nas := dec_PDU_NAS_EPS(valueof(nas_enc)); +// if (match(nas, tr_NAS_EMM_SecurityProtected)) { +// nas := f_nas_try_decaps(NGapAssociationTable[assoc_id].nus, nas); +// } +// /* DL/UlNasTransport are not interesting, don't send them */ +// if (not match(mrf.msg, (tr_NGAP_DlNasTransport, tr_NGAP_UlNasTransport))) { +// /* send raw NGAP */ +// NGAP_CLIENT.send(mrf.msg) to vc_conn; +// } +// /* send decoded NAS */ +// NGAP_CLIENT.send(nas) to vc_conn; +// } else { +// /* send raw NGAP */ +// NGAP_CLIENT.send(mrf.msg) to vc_conn; +// } +// } else { +// /* if not, call create_cb so it can create new ConnHdlr */ +// vc_conn := ops.create_cb.apply(mrf.msg, mme_ue_id, enb_ue_id, id); +// f_ngap_id_table_add(vc_conn, mme_ue_id, valueof(enb_ue_id)); +// NGAP_CLIENT.send(mrf.msg) to vc_conn; +// } +// if (match(mrf.msg, tr_NGAP_UeContextReleaseCmd)) { +// handle_NGAP_UeContextReleaseCmd(mrf.msg); +// } +// } + } + [] NGAP.receive(tr_SctpAssocChange) { } + [] NGAP.receive(tr_SctpPeerAddrChange) { } + [] NGAP_PROC.getcall(NGAPEM_register:{?,?,?}) -> param(amf_id, ran_id, vc_conn) { + f_create_expect(amf_id, ran_id, vc_conn); + NGAP_PROC.reply(NGAPEM_register:{amf_id, ran_id, vc_conn}) to vc_conn; + } + [] NGAP_PROC.getcall(NGAPEM_register_proc:{?,?}) -> param(procedureCode, vc_conn) { + f_create_expect_proc(procedureCode, vc_conn); + NGAP_PROC.reply(NGAPEM_register_proc:{procedureCode, vc_conn}) to vc_conn; + } +// [] NGAP_PROC.getcall(NGAPEM_derive_nas_token:{?, ?, -}) -> param(kasme, vc_conn) { +// var integer assoc_id := f_assoc_id_by_comp(vc_conn); +// var OCT32 nas_token := f_kdf_nas_token(kasme, NGapAssociationTable[assoc_id].nus.tx_count) +// NGapAssociationTable[assoc_id].nus.tx_count := NGapAssociationTable[assoc_id].nus.tx_count + 1; +// NGAP_PROC.reply(NGAPEM_derive_nas_token:{kasme, vc_conn, nas_token}) to vc_conn; +// } + } + } +} + + +private function f_create_expect(template (omit) AMF_UE_NGAP_ID amf_id, + template (omit) RAN_UE_NGAP_ID ran_id, + NGAP_ConnHdlr hdlr) +runs on NGAP_Emulation_CT { + var integer i; + + /* Check an entry like this is not already presnt */ + for (i := 0; i < sizeof(NGapExpectTable); i := i+1) { + if (not ispresent(NGapExpectTable[i].amf_id) and not ispresent(NGapExpectTable[i].ran_id)) { + continue; + } + if (valueof(amf_id) == NGapExpectTable[i].amf_id and valueof(ran_id) == NGapExpectTable[i].ran_id) { + setverdict(fail, "UE AMF id / UE RAN id pair already present", amf_id, ran_id); + mtc.stop; + } + } + for (i := 0; i < sizeof(NGapExpectTable); i := i+1) { + if (not ispresent(NGapExpectTable[i].amf_id) and not ispresent(NGapExpectTable[i].ran_id)) { + NGapExpectTable[i].amf_id := valueof(amf_id); + NGapExpectTable[i].ran_id := valueof(ran_id); + NGapExpectTable[i].vc_conn := hdlr; + log("Created Expect[", i, "] for UE AMF id:", amf_id, ", UE RAN id:", ran_id, " to be handled at ", hdlr); + return; + } + } + testcase.stop("No space left in NGapExpectTable") +} + +/* client/conn_hdlr side function to use procedure port to create expect in emulation */ +function f_create_s1ap_expect(template (omit) AMF_UE_NGAP_ID amf_id, + template (omit) RAN_UE_NGAP_ID ran_id) runs on NGAP_ConnHdlr { + NGAP_PROC.call(NGAPEM_register:{amf_id, ran_id, self}) { + [] NGAP_PROC.getreply(NGAPEM_register:{?,?,?}) {}; + } +} + +private function f_create_expect_proc(integer procedureCode,NGAP_ConnHdlr hdlr) runs on NGAP_Emulation_CT { + var integer i; + + /* Check an entry like this is not already presnt */ + for (i := 0; i < sizeof(NGapExpectTableProc); i := i+1) { + if (NGapExpectTableProc[i].vc_conn == null) { + continue; + } + if (NGapExpectTableProc[i].procedureCode == procedureCode) { + setverdict(fail, "procedureCode ", procedureCode, " already present"); + mtc.stop; + } + } + for (i := 0; i < sizeof(NGapExpectTableProc); i := i+1) { + if (NGapExpectTableProc[i].vc_conn == null) { + NGapExpectTableProc[i].procedureCode := procedureCode; + NGapExpectTableProc[i].vc_conn := hdlr; + log("Created Expect[", i, "] for PDU:", procedureCode, " to be handled at ", hdlr); + return; + } + } + testcase.stop("No space left in NGapExpectTableProc") +} + +/* client/conn_hdlr side function to use procedure port to create expect (PDU) in emulation */ +function f_create_ngap_expect_proc(integer procedureCode, NGAP_ConnHdlr hdlr) runs on NGAP_ConnHdlr +{ + NGAP_PROC.call(NGAPEM_register_proc:{procedureCode, self}) { + [] NGAP_PROC.getreply(NGAPEM_register_proc:{?,?}) {}; + } + + log(procedureCode); +} + +private function f_expect_table_init() +runs on NGAP_Emulation_CT { + var integer i; + for (i := 0; i < sizeof(NGapExpectTable); i := i + 1) { + NGapExpectTable[i].amf_id := omit; + NGapExpectTable[i].ran_id := omit; + NGapExpectTable[i].vc_conn := null; + } + + for (i := 0; i < sizeof(NGapExpectTableProc); i := i + 1) { + NGapExpectTableProc[i].procedureCode := omit; + NGapExpectTableProc[i].vc_conn := null; + } +} + +} diff --git a/library/NGAP_Functions.ttcn b/library/NGAP_Functions.ttcn new file mode 100644 index 0000000..a35ccf3 --- /dev/null +++ b/library/NGAP_Functions.ttcn @@ -0,0 +1,92 @@ +module NGAP_Functions { + +/* (C) 2019 by Harald Welte <lafo...@gnumonks.org> + * (C) 2025 by sysmocom - s.f.m.c. GmbH <i...@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 NGAP_IEs all; +import from NGAP_Types all; +import from NGAP_Constants all; +import from NGAP_PDU_Contents all; +import from NGAP_PDU_Descriptions all; +import from NGAP_Templates all; + +function f_NGAP_get_AMF_UE_NGAP_ID(NGAP_PDU ngap) return template (omit) AMF_UE_NGAP_ID +{ + if (ischosen(ngap.initiatingMessage)) { + var InitiatingMessage im := ngap.initiatingMessage; + select (ngap) { + case (?) { + return omit; + /* TODO */ + /* return im.value_.InitialUEMessage.protocolIEs[0].value_.AMF_UE_NGAP_ID; */ + } + /* TODO */ + } + } else if (ischosen(ngap.successfulOutcome)) { + var SuccessfulOutcome so := ngap.successfulOutcome; + select (ngap) { + case (?) { + return omit; + /* TODO */ + /* return im.value_.SuccessfulOutcome.protocolIEs[0].value_.AMF_UE_NGAP_ID; */ + } + /* TODO */ + } + } else if (ischosen(ngap.unsuccessfulOutcome)) { + var UnsuccessfulOutcome uo := ngap.unsuccessfulOutcome; + select (ngap) { + case (?) { + return omit; + /* TODO */ + /* return im.value_.UnsuccessfulOutcome.protocolIEs[0].value_.AMF_UE_NGAP_ID; */ + } + /* TODO */ + } + } + return omit; +} + +function f_NGAP_get_RAN_UE_NGAP_ID(NGAP_PDU ngap) return template (omit) RAN_UE_NGAP_ID +{ + if (ischosen(ngap.initiatingMessage)) { + var InitiatingMessage im := ngap.initiatingMessage; + select (ngap) { + case (?) { + return omit; + /* TODO */ + /* return im.value_.InitialUEMessage.protocolIEs[0].value_.RAN_UE_NGAP_ID; */ + } + /* TODO */ + } + } else if (ischosen(ngap.successfulOutcome)) { + var SuccessfulOutcome so := ngap.successfulOutcome; + select (ngap) { + case (?) { + return omit; + /* TODO */ + /* return im.value_.SuccessfulOutcome.protocolIEs[0].value_.RAN_UE_NGAP_ID; */ + } + /* TODO */ + } + } else if (ischosen(ngap.unsuccessfulOutcome)) { + var UnsuccessfulOutcome uo := ngap.unsuccessfulOutcome; + select (ngap) { + case (?) { + return omit; + /* TODO */ + /* return im.value_.UnsuccessfulOutcome.protocolIEs[0].value_.RAN_UE_NGAP_ID; */ + } + /* TODO */ + } + } + return omit; +} + +} -- To view, visit https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/40366?usp=email To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email Gerrit-MessageType: newchange Gerrit-Project: osmo-ttcn3-hacks Gerrit-Branch: master Gerrit-Change-Id: I5aea8be12c54cf907e71bffe6456efb5e60eb203 Gerrit-Change-Number: 40366 Gerrit-PatchSet: 1 Gerrit-Owner: pespin <pes...@sysmocom.de>