| /* OsmoS1GW (S1AP Gateway) test suite in TTCN-3 |
| * |
| * (C) 2024 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 |
| */ |
| |
| module S1GW_Tests { |
| |
| import from General_Types all; |
| import from Osmocom_Types all; |
| import from Native_Functions all; |
| import from IPL4asp_Types all; |
| import from Misc_Helpers all; |
| |
| import from S1AP_CodecPort all; |
| import from S1AP_CodecPort_CtrlFunct all; |
| import from S1AP_Types all; |
| import from S1AP_Templates all; |
| import from S1AP_PDU_Descriptions all; |
| import from S1AP_IEs all; |
| import from S1AP_PDU_Contents all; |
| import from S1AP_Constants all; |
| |
| import from SCTP_Templates all; |
| import from S1AP_Server all; |
| |
| modulepar { |
| charstring mp_s1gw_ip := "127.0.1.1"; |
| charstring mp_mme_bind_ip := "127.0.2.10"; |
| } |
| |
| private type record of ConnHdlr ConnHdlrList; |
| |
| type component ConnHdlr extends S1APSRV_ConnHdlr { |
| port S1AP_CODEC_PT S1AP_ENB; |
| var ConnectionId g_s1ap_conn_id := -1; |
| }; |
| |
| type component test_CT { |
| timer g_Tguard; |
| var S1AP_Server_CT vc_S1APSRV; |
| }; |
| |
| private altstep as_Tguard() runs on test_CT { |
| [] g_Tguard.timeout { |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Tguard timeout"); |
| } |
| } |
| |
| template Global_ENB_ID |
| ts_Global_ENB_ID(integer enb_id := 0, |
| OCT3 plmn_id := '00f110'O) := { |
| pLMNidentity := plmn_id, |
| eNB_ID := { |
| macroENB_ID := int2bit(enb_id, 20) |
| }, |
| iE_Extensions := omit |
| } |
| |
| function f_init(float Tval := 20.0) runs on test_CT { |
| g_Tguard.start(Tval); |
| activate(as_Tguard()); |
| } |
| |
| function f_init_s1ap_srv() runs on test_CT { |
| var S1APSRV_ConnParams cpars := { |
| local_ip := mp_mme_bind_ip, |
| local_port := 36412 |
| }; |
| |
| vc_S1APSRV := S1AP_Server_CT.create("S1APSRV-" & testcasename()); |
| vc_S1APSRV.start(S1AP_Server.main(cpars)); |
| } |
| |
| type union ConnHdlrPars { |
| integer seed |
| }; |
| |
| type function void_fn(ConnHdlrPars pars) runs on ConnHdlr; |
| |
| function f_ConnHdlr_spawn(void_fn fn, ConnHdlrPars pars) |
| runs on test_CT return ConnHdlr { |
| var ConnHdlr vc_conn; |
| |
| vc_conn := ConnHdlr.create("ConnHdlr-" & testcasename()); |
| if (vc_S1APSRV.running) { |
| connect(vc_conn:S1AP_CONN, vc_S1APSRV:S1AP_CLIENT); |
| connect(vc_conn:S1AP_PROC, vc_S1APSRV:S1AP_PROC); |
| } |
| vc_conn.start(derefers(fn)(pars)); |
| |
| return vc_conn; |
| } |
| |
| function f_ConnHdlr_connect() runs on ConnHdlr { |
| var Result res; |
| timer T; |
| |
| map(self:S1AP_ENB, system:S1AP_CODEC_PT); |
| |
| /* initiate SCTP connection establishment */ |
| res := S1AP_CodecPort_CtrlFunct.f_IPL4_connect(S1AP_ENB, |
| mp_s1gw_ip, 36412, |
| "0.0.0.0", 0, -1, |
| { sctp := c_SctpTuple_S1AP }); |
| if (not ispresent(res.connId)) { |
| setverdict(fail, "Could not create an S1AP socket, check your configuration"); |
| mtc.stop; |
| } |
| g_s1ap_conn_id := res.connId; |
| |
| /* wait for the establishment confirmation */ |
| T.start(2.0); |
| alt { |
| [] S1AP_ENB.receive(tr_SctpAssocChange(SCTP_COMM_UP, g_s1ap_conn_id)) { |
| log("eNB connection established"); |
| } |
| [] S1AP_ENB.receive(PortEvent:{sctpEvent := ?}) { repeat; } |
| [] T.timeout { |
| setverdict(fail, "eNB connection establishment timeout"); |
| self.stop; |
| } |
| } |
| } |
| |
| function f_ConnHdlr_disconnect() runs on ConnHdlr { |
| var Result res; |
| |
| S1AP_CodecPort_CtrlFunct.f_IPL4_close(S1AP_ENB, g_s1ap_conn_id, |
| { sctp := c_SctpTuple_S1AP }); |
| g_s1ap_conn_id := -1; |
| unmap(self:S1AP_ENB, system:S1AP_CODEC_PT); |
| |
| S1AP_CONN.receive(S1APSRV_Event:S1APSRV_EVENT_CONN_DOWN); |
| |
| log("eNB connection closed"); |
| } |
| |
| function f_ConnHdlr_setup(Global_ENB_ID genb_id) runs on ConnHdlr { |
| var S1AP_PDU pdu; |
| timer T; |
| |
| var SupportedTAs supported_tas_dummy := { |
| { |
| tAC := '0000'O, |
| broadcastPLMNs := { '00f000'O }, |
| iE_Extensions := omit |
| } |
| }; |
| |
| S1AP_ENB.send(t_S1AP_Send(g_s1ap_conn_id, |
| ts_S1AP_SetupReq(genb_id, |
| supported_tas_dummy, |
| v32))); |
| T.start(1.0); |
| alt { |
| [] S1AP_CONN.receive(S1APSRV_Event:S1APSRV_EVENT_CONN_UP) { repeat; } |
| [] S1AP_CONN.receive(tr_S1AP_SetupReq) { |
| setverdict(pass); |
| T.stop; |
| } |
| [] S1AP_CONN.receive(S1AP_PDU:?) -> value pdu { |
| setverdict(fail, "Rx unexpected S1AP PDU: ", pdu); |
| T.stop; |
| } |
| [] T.timeout { |
| setverdict(fail, "Timeout waiting for S1AP SetupReq"); |
| } |
| } |
| } |
| |
| |
| function f_TC_setup(ConnHdlrPars pars) runs on ConnHdlr { |
| var Global_ENB_ID genb_id := valueof(ts_Global_ENB_ID(pars.seed)); |
| |
| f_ConnHdlr_register(genb_id); |
| |
| f_ConnHdlr_connect(); |
| f_ConnHdlr_setup(genb_id); |
| f_sleep(0.5); /* keep the connection idle for some time */ |
| f_ConnHdlr_disconnect(); |
| |
| f_ConnHdlr_unregister(genb_id); |
| } |
| testcase TC_setup() runs on test_CT { |
| var ConnHdlrPars pars := { seed := 0 }; |
| var ConnHdlr vc_conn; |
| |
| f_init(); |
| f_init_s1ap_srv(); |
| |
| vc_conn := f_ConnHdlr_spawn(refers(f_TC_setup), pars); |
| vc_conn.done; |
| } |
| testcase TC_setup_multi() runs on test_CT { |
| var ConnHdlrList vc_conns := { }; |
| |
| f_init(); |
| f_init_s1ap_srv(); |
| |
| for (var integer i := 0; i < 42; i := i + 1) { |
| var ConnHdlrPars pars := { seed := i }; |
| var ConnHdlr vc_conn := f_ConnHdlr_spawn(refers(f_TC_setup), pars); |
| vc_conns := vc_conns & { vc_conn }; |
| } |
| |
| for (var integer i := 0; i < 42; i := i + 1) { |
| vc_conns[i].done; |
| } |
| } |
| |
| |
| /* MME terminates connection, expect S1GW to terminate the eNB connection */ |
| function f_TC_conn_term_by_mme(ConnHdlrPars pars) runs on ConnHdlr { |
| var Global_ENB_ID genb_id := valueof(ts_Global_ENB_ID(pars.seed)); |
| |
| f_ConnHdlr_register(genb_id); |
| |
| f_ConnHdlr_connect(); |
| f_ConnHdlr_setup(genb_id); |
| f_sleep(0.5); /* keep the connection idle for some time */ |
| |
| /* MME (S1AP_Server_CT) terminates connection */ |
| f_ConnHdlr_close_conn(genb_id); |
| /* expect our eNB connection to be released gracefully */ |
| S1AP_ENB.receive(tr_SctpShutDownEvent(g_s1ap_conn_id)); |
| S1AP_ENB.receive(tr_SctpAssocChange(SCTP_SHUTDOWN_COMP, g_s1ap_conn_id)); |
| S1AP_ENB.receive(PortEvent:{connClosed := ?}); |
| |
| f_ConnHdlr_unregister(genb_id); |
| } |
| testcase TC_conn_term_by_mme() runs on test_CT { |
| var ConnHdlrPars pars := { seed := 0 }; |
| var ConnHdlr vc_conn; |
| |
| f_init(); |
| f_init_s1ap_srv(); |
| |
| vc_conn := f_ConnHdlr_spawn(refers(f_TC_conn_term_by_mme), pars); |
| vc_conn.done; |
| } |
| |
| |
| /* MME is not available, expect S1GW to terminate the eNB connection */ |
| function f_TC_conn_term_mme_unavail(ConnHdlrPars pars) runs on ConnHdlr { |
| /* establish an eNB connection to the S1GW */ |
| f_ConnHdlr_connect(); |
| /* expect our eNB connection to be released gracefully */ |
| S1AP_ENB.receive(tr_SctpShutDownEvent(g_s1ap_conn_id)); |
| S1AP_ENB.receive(tr_SctpAssocChange(SCTP_SHUTDOWN_COMP, g_s1ap_conn_id)); |
| S1AP_ENB.receive(PortEvent:{connClosed := ?}); |
| setverdict(pass); |
| } |
| testcase TC_conn_term_mme_unavail() runs on test_CT { |
| var ConnHdlrPars pars := { seed := 0 }; |
| var ConnHdlr vc_conn; |
| |
| f_init(); |
| f_init_s1ap_srv(); |
| vc_S1APSRV.stop; |
| |
| vc_conn := f_ConnHdlr_spawn(refers(f_TC_conn_term_mme_unavail), pars); |
| vc_conn.done; |
| } |
| |
| control { |
| execute( TC_setup() ); |
| execute( TC_setup_multi() ); |
| execute( TC_conn_term_by_mme() ); |
| execute( TC_conn_term_mme_unavail() ); |
| } |
| |
| } |