/* MME (Mobility Management Engine) test suite in TTCN-3
 * (C) 2019 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 MME_Tests {

import from General_Types all;
import from Native_Functions all;
import from IPL4asp_Types all;
import from Misc_Helpers all;
import from S1AP_Types all;
import from S1AP_Templates all;
import from S1AP_Functions all;
import from S1AP_Emulation 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 NAS_EPS_Types all;
import from NAS_EPS_Templates all;

import from DIAMETER_Types all;
import from DIAMETER_Templates all;
import from DIAMETER_ts29_272_Templates all;
import from DIAMETER_Emulation all;

import from SGsAP_Types all;
import from SGsAP_Templates all;
import from SGsAP_Emulation all;

import from GTP_Emulation all;
import from GTPC_Types all;
import from GTPv1C_CodecPort all;
import from GTPv1C_Templates all;

import from LTE_CryptoFunctions all;

import from L3_Templates all;
import from DNS_Helpers all;
import from Osmocom_Types all;
import from Osmocom_Gb_Types all;

import from GTPv2_Types all;
import from GTPv2_Templates all;
import from GTPv2_Emulation all;

import from ConnHdlr all;

friend module MME_Tests_SGsAP;
friend module MME_Tests_emergency;

/* (maximum) number of emulated UEs */
const integer NUM_UE := 3;

type component MTC_CT {
	timer g_Tguard := 45.0;

	/* S1 intreface of emulated ENBs */
	var EnbParams g_enb_pars[NUM_ENB];
	var S1AP_Emulation_CT vc_S1AP[NUM_ENB];
	port S1AP_PT S1AP_UNIT[NUM_ENB];
	port S1APEM_PROC_PT S1AP_PROC[NUM_ENB];

	/* S6a/S6d interface of emulated HSS */
	var DIAMETER_Emulation_CT vc_DIAMETER;
	port DIAMETER_PT DIAMETER_UNIT;
	port DIAMETEREM_PROC_PT DIAMETER_PROC;

	/* SGs interface of emulated MSC/VLR */
	var SGsAP_Emulation_CT vc_SGsAP;
	port SGsAP_PT SGsAP_UNIT;
	port SGsAPEM_PROC_PT SGsAP_PROC;

	/* Gn interface (GTPv1C) of emulated SGSN (Rel. 7) */
	var GTP_Emulation_CT vc_GTP;

	/* S11 interface (GTPv2C) of emulated SGW-C */
	var GTPv2_Emulation_CT vc_GTP2;
	port GTP2EM_PT TEIC0;

	var UeParams g_ue_pars[NUM_UE];
}

modulepar {
	/* S1 interface */
	charstring mp_mme_ip := "127.0.0.1";
	integer mp_mme_s1ap_port := 36412;
	charstring mp_s1_local_ip := "127.0.0.1";
	integer mp_s1_local_port := 50000;

	/* S6 interface */
	charstring mp_s6_local_ip := "127.0.0.4";
	integer mp_s6_local_port := 3868;
	charstring mp_s6_diam_realm := "localdomain";
	charstring mp_s6_local_diam_host := "hss.localdomain";
	charstring mp_s6_remote_diam_host := "mme.localdomain";

	/* SGs interface */
	charstring mp_sgs_local_ip := "127.0.0.1";
	integer mp_sgs_local_port := 29118;
	charstring mp_vlr_name := "vlr.example.net";
	charstring mp_mme_name := "mmec01.mmegi0002.mme.epc.mnc001.mcc001.3gppnetwork.org";

	/* Gn interface (GTPv1C) */
	charstring mp_gn_local_ip := "127.0.0.22";
	integer mp_gn_local_port := 2123;
	charstring mp_gn_remote_ip := "127.0.0.2";
	/* RAI+CI served from emulated peer SGSN: */
	integer mp_gn_remote_port := 2123;
	hexstring mp_gn_local_mcc := '262'H;
	hexstring mp_gn_local_mnc := 'f42'H;
	uint16_t mp_gn_local_lac := 39594;
	uint8_t mp_gn_local_rac := 187;
	uint16_t mp_gn_local_ci := 1223;

	/* S11 interface (GTPv2C, interface between MME and SGW) */
	charstring mp_s11_local_ip := "127.0.0.3";
	integer mp_s11_local_port := 2123;
	charstring mp_s11_remote_ip := "127.0.0.2";
	integer mp_s11_remote_port := 2123;

	/* PGW information announced by SGWC. MME never really interacts with these. */
	charstring mp_s5c_pgw_ip := "1.2.3.4";
}

/* send incoming unit data messages (like reset) to global SGsAP_UNIT port */
friend function ForwardUnitdataCallback(PDU_SGsAP msg)
runs on SGsAP_Emulation_CT return template PDU_SGsAP {
	SGsAP_UNIT.send(msg);
	return omit;
}

friend function f_init_sgsap(charstring id) runs on MTC_CT {
	id := id & "-SGsAP";
	var SGsAPOps ops := {
		create_cb := refers(SGsAP_Emulation.ExpectedCreateCallback),
		unitdata_cb := refers(ForwardUnitdataCallback)
	}
	var SGsAP_conn_parameters pars := {
		remote_ip := "",
		remote_sctp_port := -1,
		local_ip := mp_sgs_local_ip,
		local_sctp_port := mp_sgs_local_port
	}

	vc_SGsAP := SGsAP_Emulation_CT.create(id);
	map(vc_SGsAP:SGsAP, system:SGsAP_CODEC_PT);
	connect(vc_SGsAP:SGsAP_PROC, self:SGsAP_PROC);
	connect(vc_SGsAP:SGsAP_UNIT, self:SGsAP_UNIT);
	vc_SGsAP.start(SGsAP_Emulation.main(ops, pars, id));

	SGsAP_UNIT.receive(SGsAPEM_Event:{up_down:=SGsAPEM_EVENT_UP});
}

/* send incoming unit data messages (like reset) to global S1AP_UNIT port */
friend function S1apForwardUnitdataCallback(S1AP_PDU msg)
runs on S1AP_Emulation_CT return template S1AP_PDU {
	S1AP_UNIT.send(msg);
	return omit;
}

friend function f_init_one_enb(charstring id, integer num := 0) runs on MTC_CT {
	id := id & "-S1AP" & int2str(num);
	var S1APOps ops := {
		create_cb := refers(S1AP_Emulation.ExpectedCreateCallback),
		unitdata_cb := refers(S1apForwardUnitdataCallback)
	}
	var S1AP_conn_parameters pars := {
		remote_ip := mp_mme_ip,
		remote_sctp_port := mp_mme_s1ap_port,
		local_ip := mp_s1_local_ip,
		local_sctp_port := mp_s1_local_port + num,
		role := NAS_ROLE_UE
	}
	var PLMNidentity plmn_id := '00f110'O;
	var EnbParams enb_pars := {
		gtp1u_local_ip := mp_s1_local_ip,
		global_enb_id := {
			pLMNidentity := plmn_id,
			eNB_ID := {
				macroENB_ID := int2bit(num, 20)
			},
			iE_Extensions := omit
		},
		cell_identity := num,
		supported_tas := {
			{
				tAC := int2oct(12345, 2),
				broadcastPLMNs := { plmn_id },
				iE_Extensions := omit
			}
		}
	};

	g_enb_pars[num] := enb_pars;
	vc_S1AP[num] := S1AP_Emulation_CT.create(id) alive;
	map(vc_S1AP[num]:S1AP, system:S1AP_CODEC_PT);
	connect(vc_S1AP[num]:S1AP_PROC, self:S1AP_PROC[num]);
	connect(vc_S1AP[num]:S1AP_UNIT, self:S1AP_UNIT[num]);
	vc_S1AP[num].start(S1AP_Emulation.main(ops, pars, id));
	S1AP_UNIT[num].receive(S1APEM_Event:{up_down:=S1APEM_EVENT_UP});
}
friend function f_init_one_ue(inout UeParams uep, integer imsi_suffix) {
	uep := {
		imsi := f_gen_imsi(imsi_suffix),
		ue_ip := "192.168.123.50",
		guti := omit,
		kasme := omit,
		enb_ue_id := imsi_suffix,
		mme_ue_id := omit,
		s11_teic_local := '00000000'O,
		s11_teic_remote := omit,
		s5c_teic_local := '00000000'O,
		s5c_teic_remote := omit,
		bearer := {
			ebi := omit,
			s11_teid_local := omit,
			s11_teid_remote := omit,
			s5c_teid_local := omit,
			s5c_teid_remote := omit
		},
		tmsi := f_gen_tmsi(imsi_suffix)
	}
}
friend function f_init_s1ap(charstring id, integer imsi_suffix) runs on MTC_CT {
	var integer i;
	for (i := 0; i < NUM_ENB; i := i+1) {
		f_init_one_enb(id, i);
	}
	for (i := 0; i < NUM_UE; i := i+1) {
		f_init_one_ue(g_ue_pars[i], i*1000 + imsi_suffix);
	}
}

friend function DiameterForwardUnitdataCallback(PDU_DIAMETER msg)
runs on DIAMETER_Emulation_CT return template PDU_DIAMETER {
	DIAMETER_UNIT.send(msg);
	return omit;
}

friend function f_init_diameter(charstring id) runs on MTC_CT {
	var DIAMETEROps ops := {
		create_cb := refers(DIAMETER_Emulation.ExpectedCreateCallback),
		unitdata_cb := refers(DiameterForwardUnitdataCallback),
		raw := false /* handler mode (IMSI based routing) */
	};
	var DIAMETER_conn_parameters pars := {
		remote_ip := mp_mme_ip,
		remote_sctp_port := -1,
		local_ip := mp_s6_local_ip,
		local_sctp_port := mp_s6_local_port,
		origin_host := "hss.localdomain",
		origin_realm := "localdomain",
		auth_app_id := omit,
		vendor_app_id := c_DIAMETER_3GPP_S6_AID
	};
	vc_DIAMETER := DIAMETER_Emulation_CT.create(id) alive;
	map(vc_DIAMETER:DIAMETER, system:DIAMETER_CODEC_PT);
	connect(vc_DIAMETER:DIAMETER_UNIT, self:DIAMETER_UNIT);
	connect(vc_DIAMETER:DIAMETER_PROC, self:DIAMETER_PROC);
	vc_DIAMETER.start(DIAMETER_Emulation.main(ops, pars, id));

	f_diameter_wait_capability(DIAMETER_UNIT);
}

friend function f_init_gtpv1c_gn(charstring id) runs on MTC_CT {
	id := id & "-GTPv1C-Gn";

	var GtpEmulationCfg gtp_cfg := {
		gtpc_bind_ip := mp_gn_local_ip,
		gtpc_bind_port := mp_gn_local_port,
		sgsn_role := true
	};

	vc_GTP := GTP_Emulation_CT.create(id) alive;
	vc_GTP.start(GTP_Emulation.main(gtp_cfg));
}

friend function f_init_gtpv2c_s11(charstring id) runs on MTC_CT {
	id := id & "-GTPv2C-S11";

	var Gtp2EmulationCfg cfg := {
		gtpc_bind_ip := mp_s11_local_ip,
		gtpc_bind_port := mp_s11_local_port,
		gtpc_remote_ip := mp_s11_remote_ip,
		gtpc_remote_port := mp_s11_remote_port,
		sgw_role := true
	};

	vc_GTP2 := GTPv2_Emulation_CT.create(id) alive;
	map(vc_GTP2:GTP2C, system:GTP2C);
	connect(vc_GTP2:TEIC0, self:TEIC0);
	vc_GTP2.start(GTPv2_Emulation.main(cfg));
}

friend function f_init(integer imsi_suffix := 0,
		       boolean init_diameter := false,
		       boolean init_gtpv2c_s11 := false,
		       boolean init_gtpv1c_gn := false,
		       boolean init_sgsap := false,
		       float t_guard := 45.0) runs on MTC_CT {
	var charstring id := testcasename();

	/* start guard timer and activate it as default */
	g_Tguard.start(t_guard);
	activate(as_Tguard());

	if (init_diameter) {
		f_init_diameter(id);
		f_sleep(10.0);
	}

	if (init_sgsap) {
		f_init_sgsap(id);
	}

	f_init_s1ap(id, imsi_suffix);

	if (init_gtpv2c_s11) {
		f_init_gtpv2c_s11(id);
	}

	if (init_gtpv1c_gn) {
		f_init_gtpv1c_gn(id);
	}
}

/* generate parameters for a connection handler */
friend function f_init_pars(integer ue_idx := 0)
runs on MTC_CT return ConnHdlrPars {
	var integer i;
	var ConnHdlrPars pars := {
		enb_pars := g_enb_pars,
		hss_pars := {
			local_ip := mp_s6_local_ip,
			local_port := mp_s6_local_port,
			diam_realm := mp_s6_diam_realm,
			local_diam_host := mp_s6_local_diam_host,
			remote_diam_host := mp_s6_remote_diam_host
		},
		sgw_pars := {
			local_ip := mp_s11_local_ip,
			local_port := mp_s11_local_port,
			remote_ip := mp_s11_remote_ip,
			remote_port := mp_s11_remote_port,
			s5c_pgw_ip := mp_s5c_pgw_ip
		},
		sgsap_pars := {
			vlr_name := f_enc_dns_hostname(mp_vlr_name),
			mme_name := f_enc_dns_hostname(mp_mme_name),
			lai := valueof(ts_SGsAP_LAI('001'H, '01'H, 43691))
		},
		ue_pars := g_ue_pars[ue_idx],
		mme_idx := 0,
		kset_id := valueof(ts_NAS_KeySetIdentifierV('000'B, c_NAS_TSC_NATIVE_SEC_CTX)),
		emerg_pars := {
			expect_s1_emergency := false,
			s1_emergency := '0'B,
			expect_number := false,
			numbers := 0
		}
	};
	for (i := 0; i < sizeof(pars.emerg_pars.categories); i := i + 1) {
		pars.emerg_pars.categories[i] := '00000'B;
	}
	for (i := 0; i < sizeof(pars.emerg_pars.digits); i := i + 1) {
		pars.emerg_pars.digits[i] := ''O;
	}
	return pars;
}

/* start a connection handler with given parameters */
friend function f_start_handler_with_pars(void_fn fn, ConnHdlrPars pars, integer s1ap_idx := 0)
runs on MTC_CT return ConnHdlr {
	var ConnHdlr vc_conn;
	var charstring id := testcasename() & int2str(s1ap_idx);

	vc_conn := ConnHdlr.create(id);
	/* S1AP part */
	connect(vc_conn:S1AP, vc_S1AP[s1ap_idx]:S1AP_CLIENT);
	connect(vc_conn:S1AP_PROC, vc_S1AP[s1ap_idx]:S1AP_PROC);
	if (isbound(vc_SGsAP)) {
		/* SGsAP part */
		connect(vc_conn:SGsAP, vc_SGsAP:SGsAP_CLIENT);
		connect(vc_conn:SGsAP_PROC, vc_SGsAP:SGsAP_PROC);
	}
	if (isbound(vc_DIAMETER)) {
		connect(vc_conn:DIAMETER, vc_DIAMETER:DIAMETER_CLIENT);
		connect(vc_conn:DIAMETER_PROC, vc_DIAMETER:DIAMETER_PROC);
	}
	if (isbound(vc_GTP)) {
		connect(vc_conn:GTP[0], vc_GTP:CLIENT);
		connect(vc_conn:GTP_PROC[0], vc_GTP:CLIENT_PROC);
	}
	if (isbound(vc_GTP2)) {
		connect(vc_conn:GTP2, vc_GTP2:CLIENT);
		connect(vc_conn:GTP2_PROC, vc_GTP2:CLIENT_PROC);
	}

	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;
	g_gn_iface_peer := {
		connId := 1,
		remName := mp_gn_remote_ip,
		remPort := mp_gn_remote_port
	};

	if (DIAMETER_PROC.checkstate("Connected")) {
		f_diameter_expect_imsi(g_pars.ue_pars.imsi);
	}
	if (SGsAP_PROC.checkstate("Connected")) {
		/* Route all SGsAP mesages for our IMSIto us */
		f_create_sgsap_expect(g_pars.ue_pars.imsi);
	}

	fn.apply();
}

friend function f_s1ap_setup(integer idx := 0, template S1AP_IEs.Cause cause := omit) runs on MTC_CT {
	var template (present) S1AP_IEs.Cause exp_cause;
	var boolean exp_fail := false;
	timer T := 5.0;
	if (not istemplatekind(cause, "omit")) {
		exp_fail := true;
		exp_cause := cause;
	}

	S1AP_UNIT[idx].send(ts_S1AP_SetupReq(g_enb_pars[idx].global_enb_id,
					     g_enb_pars[idx].supported_tas, v32));
	T.start;
	alt {
	[exp_fail] S1AP_UNIT[idx].receive(tr_S1AP_SetupFail(exp_cause)) {
		setverdict(pass);
		}
	[not exp_fail] S1AP_UNIT[idx].receive(tr_S1AP_SetupResp) {
		setverdict(pass);
		}
	[] S1AP_UNIT[idx].receive {
		setverdict(fail, "Received unexpected S1AP");
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for S1AP Setup result");
		}
	}
}

/* S1 Setup procedure to MME using a Global eNB ID containing unknown/foreign PLMN.
 * Related: https://github.com/open5gs/open5gs/issues/3544 */
testcase TC_s1ap_setup_unknown_global_enb_id_plmn() runs on MTC_CT {
	f_init(1);
	g_enb_pars[0].global_enb_id.pLMNidentity := '62F224'O;
	f_s1ap_setup(0);
}

/* Unsuccessful S1 Setup procedure to MME (wrong PLMN) */
testcase TC_s1ap_setup_wrong_tac() runs on MTC_CT {
	f_init(2);
	g_enb_pars[0].supported_tas[0].broadcastPLMNs[0] := '62F224'O;
	f_s1ap_setup(0, {misc:=unknown_PLMN});
}

/* Successful S1 Setup procedure to MME */
testcase TC_s1ap_setup() runs on MTC_CT {
	f_init(3);
	f_s1ap_setup(0);
}

private function f_TC_attach() runs on ConnHdlr {
	f_attach();
}
testcase TC_s1ap_attach() runs on MTC_CT {
	f_init(4, init_diameter := true, init_gtpv2c_s11 := true);
	f_s1ap_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_attach), pars);
	vc_conn.done;
}

private function f_TC_gn_echo_request() runs on ConnHdlr {
	timer T := 5.0;
	f_gtp_register_teic('00000000'O);

	GTP[0].send(ts_GTPC_PING(g_gn_iface_peer, 1));
	T.start;
	alt {
	[] GTP[0].receive(tr_GTPC_PONG(?)) {
		setverdict(pass);
		}
	[] GTP[0].receive {
		setverdict(fail, "unexpected GTPC message from MME");
		}
	[] T.timeout {
		setverdict(fail, "no GTPC ECHO RESPONSE from MME");
		}
	}
}
testcase TC_gn_echo_request() runs on MTC_CT {
	f_init(0, init_gtpv1c_gn := true);
	f_s1ap_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_gn_echo_request), pars);
	vc_conn.done;
}

private function f_TC_RIM_RAN_INF() runs on ConnHdlr {
	timer T := 5.0;
	f_gtp_register_teic('00000000'O);
	var Gtp1cUnitdata req_gtpc_pdu;
	var Gtp1cUnitdata resp_gtpc_pdu;
	var GTP_CellId geran_gtp_ci;

	/* Assemble data of a fictitiously GERAN cell */
	geran_gtp_ci.ra_id.rac := mp_gn_local_rac;
	geran_gtp_ci.ra_id.lai.mcc_mnc := mp_gn_local_mcc & mp_gn_local_mnc;
	geran_gtp_ci.ra_id.lai.lac := mp_gn_local_lac;
	geran_gtp_ci.cell_id := mp_gn_local_ci;
	const octetstring geran_si1 := '198fb100000000000000000000000000007900002b'O;
	const octetstring geran_si3 := '1b753000f110236ec9033c2747407900003c0b2b2b'O;
	const octetstring geran_si13 := '009000185a6fc9e08410ab2b2b2b2b2b2b2b2b2b2b'O;
	const octetstring geran_si := geran_si1 & geran_si3 & geran_si13;

	/* Send initial RAN information request via S1AP to MME and expect the MME to forward the request on GTP-C
	 * (eNB -> MME -> SGSN) */
	S1AP.send(f_make_ts_S1AP_eNBDirectInfTrans(geran_gtp_ci));
	T.start;
	alt {
	[] GTP[0].receive(f_make_tr_GTPC_MsgType(geran_gtp_ci)) -> value req_gtpc_pdu {
		setverdict(pass);
		}
	[] GTP[0].receive {
		setverdict(fail, "unexpected GTPC message from MME");
		}
	[] T.timeout {
		setverdict(fail, "no GTPC RAN INFORMATION REQUEST from MME");
		}
	}

	/* Send RAN information response via GTP-C to MME and expect the MME to forward the respnse on S1AP
	 * (SGSN -> MME -> eNB) */
	f_create_s1ap_expect_proc(id_MMEDirectInformationTransfer, self);
	resp_gtpc_pdu := valueof(f_make_ts_GTPC_RANInfoRelay(req_gtpc_pdu, geran_gtp_ci, geran_si));
	GTP[0].send(resp_gtpc_pdu);
	T.start;
	alt {
	[] S1AP.receive(f_make_tr_S1AP_MMEDirectInfTrans(resp_gtpc_pdu)) {
		setverdict(pass);
		}
	[] S1AP.receive {
		setverdict(fail, "unexpected S1AP message from MME");
		}
	[] T.timeout {
		setverdict(fail, "no S1AP RAN INFORMATION from MME");
		}
	}

	setverdict(pass);
}

testcase TC_RIM_RAN_INF() runs on MTC_CT {
	f_init(0, init_diameter := true, init_gtpv1c_gn := true);
	f_s1ap_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_RIM_RAN_INF), pars);

	vc_conn.done;
}

/* Successful RESET procedure from eNB to MME */
testcase TC_s1ap_reset() runs on MTC_CT {
	f_init(0);
	f_s1ap_setup(0);

	var template (value) S1AP_IEs.Cause reset_cause := {misc := om_intervention};
	var template (value) ResetType reset_type := {s1_Interface := reset_all};
	timer T := 5.0;

	S1AP_UNIT[0].send(ts_S1AP_Reset(reset_cause, reset_type));
	T.start;
	alt {
	[] S1AP_UNIT[0].receive(tr_S1AP_ResetAck_any) {
		setverdict(pass);
		}
	[] S1AP_UNIT[0].receive {
		setverdict(fail, "Received unexpected S1AP");
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for S1AP Setup result");
		}
	}
}

/* Tracking area update with a GUTI (TMSI) that is unknown to the MME. The MME is expected to reject this TAU
 * request. */
private function f_TC_tau_unknown_guti() runs on ConnHdlr {
	var template (value) EPS_MobileIdentityV mi := ts_NAS_MobileId_IMSI(g_pars.ue_pars.imsi);
	var template (value) S1AP_PDU tx;
	var template (value) PDU_NAS_EPS nas_tau;
	timer T := 5.0;

	var hexstring mcc_mnc := f_convert_plmn(g_pars.enb_pars[g_pars.mme_idx].global_enb_id.pLMNidentity);
	var EPS_MobileIdentityLV old_guti := valueof(ts_EPS_MobileId_GUTI(mcc_mnc, '0001'O, '01'O, 'AABBCCDD'O));
	nas_tau := ts_PDU_NAS_EPS_TrackingAreaUpdateRequest(old_guti := old_guti,
							    kset_id := g_pars.kset_id);

	tx := ts_S1AP_InitialUE(p_eNB_value := g_pars.ue_pars.enb_ue_id,
				p_nasPdu := enc_PDU_NAS_EPS(valueof(nas_tau)),
				p_tAI := ts_enb_S1AP_TAI(g_pars.enb_pars[g_pars.mme_idx]),
				p_eUTRAN_CGI := ts_enb_S1AP_CGI(g_pars.enb_pars[g_pars.mme_idx]),
				p_rrcCause := mo_Signalling);

	S1AP.send(tx);

	T.start;
	alt {
	[] S1AP.receive(tr_PDU_NAS_EPS_TrackingAreaUpdateReject) {
		setverdict(pass);
		}
	[] S1AP.receive {
		setverdict(fail, "unexpected S1AP message from MME");
		}
	[] T.timeout {
		setverdict(fail, "no message from MME");
		}
	}

	as_s1ap_handle_UeContextReleaseCmd();
}
testcase TC_s1ap_tau_unknown_guti() runs on MTC_CT {
	f_init(5, init_diameter := true);
	f_s1ap_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_tau_unknown_guti), pars);
	vc_conn.done;
}

private function f_TC_tau_periodic() runs on ConnHdlr {
	f_attach();
	f_tau(c_EPS_UPD_TYPE_TA_UPD_PERIODIC);
}
testcase TC_s1ap_tau_periodic() runs on MTC_CT {
	f_init(10, init_diameter := true, init_gtpv2c_s11:= true);
	f_s1ap_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_tau_periodic), pars);
	vc_conn.done;
}

private function f_TC_ue_cell_reselect_eutran_to_geran() runs on ConnHdlr {
	f_gtp_register_imsi(g_pars.ue_pars.imsi);
	f_attach();

	/* TS 23.401 Figure D.3.5-1 Steps 1,2,3,4: */
	f_gtp_sgsn_context_4g_to_2g();

	/* TS 23.401 Figure D.3.5-1 Step 8: */
	f_DIA_CancelLocation();


	/* TS 23.401 Figure D.3.5-1 Step 13:
	 * After Gn timer triggers, the SGW session is deleted.
	 * Make sure Operation Indication is set to 0, to tell the SGW to keep the Session up at the PGW.
	 */
	as_GTP2C_DeleteSession_success(tr_GTP2C_Indication(oI := '0'B));

	/* TS 23.401 Figure D.3.5-1 Step 13:
	 * Upon rx of SGSN Context Acknowledge, MME released the ENB/UE context:
	 */
	as_s1ap_handle_UeContextReleaseCmd();

	/* Let MME some time to handle the Create Session Response: */
	f_sleep(3.0);
}
testcase TC_ue_cell_reselect_eutran_to_geran() runs on MTC_CT {
	var charstring id := testcasename();

	f_init(6, init_diameter := true, init_gtpv2c_s11:= true, init_gtpv1c_gn := true);
	f_s1ap_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_cell_reselect_eutran_to_geran), pars);
	vc_conn.done;
}

/* Test UE attached to GERAN reselecting a EUTRAN cell. In this scenario, the
 * new MME will attempt to obtain information of the UE from the old SGSN
 * through Gn interface using SGSN Context Request/Response procedure (OS#6294). */
/* 3GPP TS 23.401 D.3.6, TS 23.003 2.8.2.2.2 */
private function f_TC_ue_cell_reselect_geran_to_eutran() runs on ConnHdlr {
	f_gtp_register_imsi(g_pars.ue_pars.imsi);
	f_gtp2_register_imsi(g_pars.ue_pars.imsi);
	/* SGSN Context Req doesn't necessarily contain IMSI, hence expect it through TEID=0 */
	f_gtp_register_teic('00000000'O);
	/* passed in SGSN Context Resp to MME, will be used by MME when answering with SGSN Context Ack: */
	const OCT4 new_sgsn_teid := 'ABABABAB'O;
	f_gtp_register_teic(new_sgsn_teid);

	var template (value) EPS_MobileIdentityV mi := ts_NAS_MobileId_IMSI(g_pars.ue_pars.imsi);
	var template (value) S1AP_PDU tx;
	var template (value) PDU_NAS_EPS nas_tau;
	var RoutingAreaIdentity rai;
	var OCT4 ptmsi := f_gen_tmsi(suffix := 0, nri_v := 0, nri_bitlen := 8);
	var OCT3 ptmsi_sig := f_rnd_octstring(3);
	var NAS_EPS_Types.GUTI guti_val;
	var template (value) EPS_MobileIdentityLV old_guti;
	var S1APEM_Config cfg;
	timer T := 5.0;
	var bitstring ptmsi_bits := oct2bit(ptmsi);
	var OCT4 ptmsi_exp := bit2oct('11'B & substr(ptmsi_bits, 2, 30));
	var template (value) UENetworkCapabilityTLV ue_net_cap := ts_UENetworkCapabilityTLV(c_NAS_defaultUeNetCap);

	rai := valueof(ts_RoutingAreaIdentity(mp_gn_local_mcc, mp_gn_local_mnc,
				      int2oct(mp_gn_local_lac, 2), int2oct(mp_gn_local_rac, 1)));
	rai_ptmsi2_guti(rai, ptmsi, guti_val);
	old_guti := ts_EPS_MobileId_GUTI_(guti_val);

	nas_tau := ts_PDU_NAS_EPS_TrackingAreaUpdateRequest(old_guti := old_guti,
							    kset_id := g_pars.kset_id,
							    old_ptmsi_sig := ts_PTMSI_SignatureTV(ptmsi_sig),
							    old_guti_type := ts_GUTI_TypeTV(GUTI_TYPE_MAPPED),
							    nonce_ue := ts_NonceTV('12345678'O),
							    gprs_cksn := ts_CipheringKeySequenceNumberTV('000'B),
							    ue_net_cap := ue_net_cap);
	tx := ts_S1AP_InitialUE(p_eNB_value := g_pars.ue_pars.enb_ue_id,
				p_nasPdu := enc_PDU_NAS_EPS(valueof(nas_tau)),
				p_tAI := ts_enb_S1AP_TAI(g_pars.enb_pars[g_pars.mme_idx]),
				p_eUTRAN_CGI := ts_enb_S1AP_CGI(g_pars.enb_pars[g_pars.mme_idx]),
				p_rrcCause := mo_Signalling);

	S1AP.send(tx);

	/* NAS counts are reset to zero when a mapped security context is created. */
	cfg := {
		reset_nas_counts := {}
	};
	S1AP.send(cfg);

	as_gtp_sgsn_context_2g_to_4g(new_sgsn_teid, GTP_RAT_TYPE_EUTRAN, rai, ptmsi_exp, ptmsi_sig, exp_auth := true);

	/* We now expect the MME to send a Create Session Request to the SGW-C */
	T.start;
	alt {
	[] as_GTP2C_CreateSession_success();
	[] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("No message from MME")); }
	}

	/* 3GPP TS 23.401 D.3.6 steps 14-21: */
	as_DIA_UpdLoc();

	/* 3GPP TS 23.401 D.3.6 step 22, 23: */
	as_s1ap_handle_IntialCtxSetupReq_TAU_Accept();

	/* We now expect the MME to send a Modify Bearer Request to the SGW-C */
	T.start;
	alt {
	[] as_GTP2C_ModifyBearer_success();
	[] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("No message from MME")); }
	}

	/* Leave some time for MME to handle Modify Bearer Response: */
	f_sleep(1.0);
}
testcase TC_ue_cell_reselect_geran_to_eutran() runs on MTC_CT {
	f_init(7, init_diameter := true, init_gtpv2c_s11:= true, init_gtpv1c_gn := true);
	f_s1ap_setup(0);

	var ConnHdlrPars pars := f_init_pars(ue_idx := 0);
	var ConnHdlr vc_conn;
	pars.kset_id := valueof(ts_NAS_KeySetIdentifierV(identifier := c_NAS_KEY_SET_ID_NO_KEY,
							 tSC := c_NAS_TSC_NATIVE_SEC_CTX));
	vc_conn := f_start_handler_with_pars(refers(f_TC_ue_cell_reselect_geran_to_eutran), pars);
	vc_conn.done;
}

private function f_TC_attach_no_emergency() runs on ConnHdlr {
	f_attach();
}
testcase TC_s1ap_attach_no_emergency() runs on MTC_CT {
	f_init(8, init_diameter := true, init_gtpv2c_s11 := true);
	f_s1ap_setup(0);

	var ConnHdlrPars pars := f_init_pars(ue_idx := 0);
	var ConnHdlr vc_conn;
	pars.emerg_pars.expect_s1_emergency := true;
	pars.emerg_pars.s1_emergency := '0'B;
	vc_conn := f_start_handler_with_pars(refers(f_TC_attach_no_emergency), pars);
	vc_conn.done;
}

control {
	execute( TC_s1ap_setup_unknown_global_enb_id_plmn() );
	execute( TC_s1ap_setup_wrong_tac() );
	execute( TC_s1ap_setup() );
	execute( TC_s1ap_attach() );
	execute( TC_s1ap_tau_unknown_guti() );
	execute( TC_s1ap_tau_periodic() );
	execute( TC_gn_echo_request() );
	execute( TC_RIM_RAN_INF() );
	execute( TC_s1ap_reset() );
	execute( TC_ue_cell_reselect_eutran_to_geran() );
	execute( TC_ue_cell_reselect_geran_to_eutran() );
	execute( TC_s1ap_attach_no_emergency() );
}


}
