/* 5GC (55G Core) test suite in TTCN-3 * (C) 2025 by sysmocom - s.f.m.c. GmbH * All rights reserved. * Author: Pau Espin Pedrol * * 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 C5G_Tests { import from General_Types all; import from Native_Functions all; import from IPL4asp_Types all; import from Misc_Helpers all; import from Osmocom_Types all; import from GSM_Types all; import from DNS_Helpers all; import from Milenage_Functions all; import from NGAP_PDU_Descriptions all; import from NGAP_IEs all; import from NGAP_PDU_Contents all; import from NGAP_Constants all; import from NGAP_Types all; import from NGAP_Pixits all; import from NGAP_Templates all; import from NGAP_Functions all; import from NGAP_Emulation all; import from NAS_CommonTypeDefs all; import from NAS_CommonTemplates all; import from NG_NAS_Common all; import from NG_NAS_TypeDefs all; import from NG_NAS_MsgContainers all; import from NG_NAS_Templates all; import from NG_NAS_Osmo_Templates all; import from NG_NAS_Functions all; import from NG_CryptoFunctions all; import from GTPv1U_Emulation all; import from ConnHdlr all; modulepar { /* NG-C interface */ charstring mp_5gc_ngap_ip := "127.0.0.1"; integer mp_5gc_ngap_port := 38412; charstring mp_local_ngap_ip := "127.0.0.1"; integer mp_local_ngap_port := 50000; GsmMcc mp_mcc := '999'H; GsmMnc mp_mnc := '70'H; HEX15n mp_imsi := '999700000000000'H; octetstring mp_usim_key := '762a2206fe0b4151ace403c86a11e479'O; octetstring mp_usim_opc := '3c6e0b8a9c15224a8228b9a98ca1531d'O; uint24_t mp_tac := 1; charstring mp_apn := "internet"; charstring mp_local_gtpu_ip := "127.0.0.20"; charstring mp_ping_hostname := "10.45.0.1"; charstring mp_run_prog_log_path := "/tmp"; charstring mp_run_prog_as_user := "osmocom"; } template (value) RunProgParams ts_RunProgParams(integer imsi_suffix) := { run_as_user := mp_run_prog_as_user, log_path_prefix := mp_run_prog_log_path, tun_netns_name := "tun" & int2str(imsi_suffix), tun_dev_name := "tun" & int2str(imsi_suffix), ping_hostname := mp_ping_hostname } template (value) PDUSessionParams ts_PDUSessionParams(template (value) PDUSessionID id, template (value) RunProgParams run_prog_pars, template (value) OCT4 ran_gtpu_teid) := { id := id, run_prog_pars := run_prog_pars, ran_gtpu_ip := mp_local_gtpu_ip, ran_gtpu_teid := ran_gtpu_teid, cn_gtpu_ip := omit, cn_gtpu_teid := omit, qos_rules := omit, ue_ip := omit }; template (value) UeParams ts_UeParams(integer idx) := { idx := idx, imsi := f_concat_pad(lengthof(mp_imsi), substr(mp_imsi, 0, lengthof(mp_imsi) - 6), idx), imeisv := f_rnd_imeisv(), usim_key := mp_usim_key, usim_opc := mp_usim_opc, apn := mp_apn, ran_id := idx, amf_id := omit, guti := omit, pti := '00'O, sess_pars := ts_PDUSessionParams(id := 1, run_prog_pars := ts_RunProgParams(idx), ran_gtpu_teid := int2oct(idx + 1, 4)) } type component MTC_CT { /* S1 intreface of emulated ENBs */ var NGRANParams g_ngran_pars[NUM_NGRAN]; var NGAP_Emulation_CT vc_NGAP[NUM_NGRAN]; port NGAP_PT NGAP_UNIT[NUM_NGRAN]; port NGAPEM_PROC_PT NGAP_PROC[NUM_NGRAN]; var GTPv1U_Emulation_CT vc_GTP1U; port GTP1UEM_PT TEID0; timer g_Tguard := 30.0; } /* send incoming unit data messages (like reset) to global NGAP_UNIT port */ private function NGapForwardUnitdataCallback(NGAP_PDU msg) runs on NGAP_Emulation_CT return template NGAP_PDU { NGAP_UNIT.send(msg); return omit; } private function f_init_one_ngran(integer num := 0) runs on MTC_CT { var charstring id := testcasename() & "-NGAP" & int2str(num); var NGAPOps ops := { create_cb := refers(NGAP_Emulation.ExpectedCreateCallback), unitdata_cb := refers(NGapForwardUnitdataCallback) }; var NGAP_conn_parameters pars := { remote_ip := mp_5gc_ngap_ip, remote_sctp_port := mp_5gc_ngap_port, local_ip := mp_local_ngap_ip, local_sctp_port := mp_local_ngap_port + num, role := NG_NAS_ROLE_UE }; var PLMNIdentity plmn_id := f_enc_mcc_mnc(mp_mcc, mp_mnc); var NGRANParams ngran_pars := { global_ngran_id := valueof(m_globalRANNodeID_globalGNB_ID(m_ie_globalGnbId(plmn_id, int2bit(num, 22)))), cell_identity := { nR_CGI := valueof(m_nR_CGI(plmn_id, int2bit(num, 36))) }, supported_ta_list := { { tAC := int2oct(mp_tac, 3), broadcastPLMNList := { valueof(m_ie_broadcastPLMNItem(plmn_id, { m_sliceSupportItem(m_s_NSSAI('01'O)) })) }, iE_Extensions := omit } } }; g_ngran_pars[num] := ngran_pars; vc_NGAP[num] := NGAP_Emulation_CT.create(id); map(vc_NGAP[num]:NGAP, system:NGAP_CODEC_PT); connect(vc_NGAP[num]:NGAP_PROC, self:NGAP_PROC[num]); connect(vc_NGAP[num]:NGAP_UNIT, self:NGAP_UNIT[num]); vc_NGAP[num].start(NGAP_Emulation.main(ops, pars, id)); NGAP_UNIT[num].receive(NGAPEM_Event:{up_down:=NGAPEM_EVENT_UP}); } private function f_init_ngap(integer imsi_suffix := 0) runs on MTC_CT { var integer i; for (i := 0; i < NUM_NGRAN; i := i+1) { f_init_one_ngran(i); } } private function f_init_gtp1u() runs on MTC_CT { var Gtp1uEmulationCfg cfg := { gtpu_bind_ip := omit, /* using gtpu daemon */ gtpu_bind_port := omit, /* using gtpu daemon */ use_gtpu_daemon := true }; vc_GTP1U := GTPv1U_Emulation_CT.create("GTP1U_EM"); map(vc_GTP1U:GTP1U, system:GTP1U); connect(vc_GTP1U:TEID0, self:TEID0); vc_GTP1U.start(GTPv1U_Emulation.main(cfg)); } private function f_init(integer imsi_suffix := 0, float t_guard := 30.0) runs on MTC_CT { /* start guard timer and activate it as default */ g_Tguard.start(t_guard); activate(as_Tguard()); f_init_gtp1u(); f_init_ngap(imsi_suffix); } /* generate parameters for a connection handler */ private function f_init_pars(integer ue_idx := 0) runs on MTC_CT return ConnHdlrPars { var ConnHdlrPars pars := { ngran_pars := g_ngran_pars, ue_pars := valueof(ts_UeParams(ue_idx)), c5g_idx := 0, kset_id := valueof(cs_NAS_KeySetIdentifier_lv(tsc_NasKsi_NoKey, '0'B)) }; return pars; } /* start a connection handler with given parameters */ private function f_start_handler_with_pars(void_fn fn, ConnHdlrPars pars, integer ngap_idx := 0) runs on MTC_CT return ConnHdlr { var ConnHdlr vc_conn; var charstring id := testcasename() & int2str(ngap_idx); vc_conn := ConnHdlr.create(id); /* NGAP part */ connect(vc_conn:NGAP, vc_NGAP[ngap_idx]:NGAP_CLIENT); connect(vc_conn:NGAP_PROC, vc_NGAP[ngap_idx]:NGAP_PROC); /* GTPv1U */ connect(vc_conn:GTP1U[0], vc_GTP1U:CLIENT); connect(vc_conn:GTP1U_PROC[0], vc_GTP1U:CLIENT_PROC); /* We cannot use vc_conn.start(f_init_handler(fn, id, pars)); as we cannot have * a stand-alone 'derefers()' call, see https://www.eclipse.org/forums/index.php/t/1091364/ */ vc_conn.start(f_init_handler(fn, pars)); return vc_conn; } /* altstep for the global guard timer */ private altstep as_Tguard()runs on MTC_CT { [] g_Tguard.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Tguard timeout"); } } private function f_init_handler(void_fn fn, ConnHdlrPars pars) runs on ConnHdlr { /* make parameters available via component variable */ g_pars := pars; fn.apply(); } private function f_ngap_setup(integer idx := 0, template (omit) NGAP_IEs.Cause cause := omit) runs on MTC_CT { var template (present) NGAP_IEs.Cause exp_cause := ?; var boolean exp_fail := false; timer T := 5.0; var template (value) NGAP_PDU tx_pdu; var template (present) NGAP_PDU exp_pdu; var NGAP_PDU rx_pdu; tx_pdu := m_ngap_initMsg(m_n2_NGSetupRequest(g_ngran_pars[idx].global_ngran_id, g_ngran_pars[idx].supported_ta_list, v32)); if (not istemplatekind(cause, "omit")) { exp_fail := true; exp_cause := cause; } if (exp_fail) { exp_pdu := mw_ngap_unsuccMsg((mw_n2_NGSetupFailure(exp_cause), f_mw_n2_NGSetupFailure(exp_cause, p_timeToWait := ?))); } else { exp_pdu := mw_ngap_succMsg(mw_n2_NGSetupResponse); } NGAP_UNIT[idx].send(tx_pdu); T.start; alt { [] NGAP_UNIT[idx].receive(exp_pdu) { setverdict(pass); } [] NGAP_UNIT[idx].receive(NGAP_PDU:?) -> value rx_pdu { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Received unexpected NGAP ", rx_pdu, " vs exp ", exp_pdu)); } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for NGAP Setup result"); } } } /* NG Setup procedure to 5GC using a Global gNB ID containing unknown/foreign PLMN. * Related: https://github.com/open5gs/open5gs/issues/3544 */ testcase TC_ng_setup_unknown_global_gnb_id_plmn() runs on MTC_CT { f_init(); g_ngran_pars[0].global_ngran_id.globalGNB_ID.pLMNIdentity := '62F224'O; f_ngap_setup(0); } /* Unsuccessful NG Setup procedure to AMF (wrong PLMN) */ testcase TC_ng_setup_wrong_tac() runs on MTC_CT { f_init(); g_ngran_pars[0].supported_ta_list[0].broadcastPLMNList[0].pLMNIdentity := '62F224'O; f_ngap_setup(0, {misc:=unknown_PLMN_or_SNPN}); } /* NG Setup procedure to 5GC using a correct Global gNB ID. */ testcase TC_ng_setup() runs on MTC_CT { f_init(); f_ngap_setup(0); } private function f_TC_register() runs on ConnHdlr { f_register(); f_deregister(); } testcase TC_ng_register() runs on MTC_CT { f_init(); f_ngap_setup(0); var ConnHdlrPars pars := f_init_pars(ue_idx := 0); var ConnHdlr vc_conn; vc_conn := f_start_handler_with_pars(refers(f_TC_register), pars); vc_conn.done; } private function f_TC_periodic_registration_updating() runs on ConnHdlr { f_register(); f_periodic_register_update(); f_deregister(); } testcase TC_periodic_registration_updating() runs on MTC_CT { f_init(); f_ngap_setup(0); var ConnHdlrPars pars := f_init_pars(ue_idx := 0); var ConnHdlr vc_conn; vc_conn := f_start_handler_with_pars(refers(f_TC_periodic_registration_updating), pars); vc_conn.done; } /* 3GPP TS 23.502 4.2.6 AN Release */ private function f_TC_ue_context_release_no_pdu_session() runs on ConnHdlr { f_register(); f_ue_context_release(); } testcase TC_ue_context_release_no_pdu_session() runs on MTC_CT { f_init(); f_ngap_setup(0); var ConnHdlrPars pars := f_init_pars(ue_idx := 0); var ConnHdlr vc_conn; vc_conn := f_start_handler_with_pars(refers(f_TC_ue_context_release_no_pdu_session), pars); vc_conn.done; } private function f_TC_ue_context_release_with_pdu_session() runs on ConnHdlr { f_register(); f_pdu_sess_establish(false); f_sleep(1.0); f_ue_context_release(); } testcase TC_ue_context_release_with_pdu_session() runs on MTC_CT { f_init(); f_ngap_setup(0); var ConnHdlrPars pars := f_init_pars(ue_idx := 0); var ConnHdlr vc_conn; vc_conn := f_start_handler_with_pars(refers(f_TC_ue_context_release_with_pdu_session), pars); vc_conn.done; } private function f_TC_pdu_sess_modification() runs on ConnHdlr { f_register(); f_pdu_sess_establish(false); f_sleep(1.0); f_pdu_sess_modify(); f_pdu_sess_release(); f_deregister(); } testcase TC_pdu_sess_modification() runs on MTC_CT { f_init(); f_ngap_setup(0); var ConnHdlrPars pars := f_init_pars(ue_idx := 0); var ConnHdlr vc_conn; vc_conn := f_start_handler_with_pars(refers(f_TC_pdu_sess_modification), pars); vc_conn.done; } private function f_TC_register_ping4() runs on ConnHdlr { f_register(); f_pdu_sess_establish(); f_sleep(1.0); f_ping4(g_pars.ue_pars.sess_pars.run_prog_pars.ping_hostname); f_pdu_sess_release(); f_deregister(); } testcase TC_ng_register_ping4() runs on MTC_CT { f_init(); f_ngap_setup(0); var ConnHdlrPars pars := f_init_pars(ue_idx := 0); var ConnHdlr vc_conn; vc_conn := f_start_handler_with_pars(refers(f_TC_register_ping4), pars); vc_conn.done; } testcase TC_ng_register_ping4_256() runs on MTC_CT { var ConnHdlr vc_conn[256]; var integer i; f_init(); f_ngap_setup(0); for (i := 0; i < sizeof(vc_conn); i := i + 1) { var ConnHdlrPars pars := f_init_pars(ue_idx := i); vc_conn[i] := f_start_handler_with_pars(refers(f_TC_register_ping4), pars); } for (i := 0; i < sizeof(vc_conn); i := i + 1) { vc_conn[i].done; } } control { execute( TC_ng_setup() ); execute( TC_ng_setup_unknown_global_gnb_id_plmn() ); execute( TC_ng_setup_wrong_tac() ); execute( TC_ng_register() ); execute( TC_periodic_registration_updating() ); execute( TC_ue_context_release_no_pdu_session() ); execute( TC_ue_context_release_with_pdu_session() ); execute( TC_pdu_sess_modification() ); execute( TC_ng_register_ping4() ); execute( TC_ng_register_ping4_256() ); } /* TODO: * - Emergency call (PDU Sess Establish "Request Type"="Emergency Request") * - Handover from/to EPS vs 5GC * - Handover from/to 3GPP vs non-3GPP (PDU Sess Establish "Request Type"="Existing PDU Session" and RAT Type change) * - PDU Session Type IPv6 * - PDU Session Type IPv4v6 * - VoLTE: * -- " If the UE requested P-CSCF discovery then the message shall also include the P-CSCF IP address(es) as determined by the SMF and as described in clause 5.16.3.4 of TS 23.50" * - Fill in PCO during PDU Sess Establish Req. * - "If the PDU Session being established was requested to be an always-on PDU Session, the SMF shall indicate whether the request is accepted by including an Always-on PDU Session Granted indication in the PDU Session Establishment Accept message" */ }