module HNBGW_Tests {

/* Integration Tests for OsmoHNBGW
 * (C) 2021 by sysmocom - s.f.m.c. GmbH <info@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
 *
 * This test suite tests OsmoHNBGW while emulating the hNodeB as well as MSC, SGSN, MGW
 * See README for more details.
 */

import from Misc_Helpers all;
import from General_Types all;
import from GSM_Types all;
import from Osmocom_Types all;
import from IPL4asp_Types all;

import from Osmocom_CTRL_Functions all;
import from Osmocom_CTRL_Types all;
import from Osmocom_CTRL_Adapter all;

import from StatsD_Types all;
import from StatsD_CodecPort all;
import from StatsD_CodecPort_CtrlFunct all;
import from StatsD_Checker all;

import from Osmocom_VTY_Functions all;
import from TELNETasp_PortType all;

import from HNBAP_Templates all;
import from HNBAP_PDU_Descriptions all;

import from RUA_IEs all;
import from RUA_Templates all;
import from RUA_Emulation all;

import from Iuh_Emulation all;

import from RANAP_Types all;
import from RANAP_PDU_Descriptions all;
import from RANAP_PDU_Contents all;
import from RANAP_IEs all;
import from RANAP_Templates all;

import from RAN_Adapter all;

import from RAN_Adapter all;
import from RAN_Emulation all;

import from MGCP_Emulation all;
import from MGCP_Types all;
import from MGCP_Templates all;
import from MGCP_CodecPort all;
import from SDP_Types all;

import from PFCP_Types all;
import from PFCP_Emulation all;
import from PFCP_Templates all;
import from PFCP_CodecPort all;

modulepar {
	/* IP address at which the HNodeB can be reached */
	charstring mp_hnodeb_ip := "127.0.0.1";
	integer mp_hnodeb_port := -1;

	/* IP address at which the test binds */
	charstring mp_hnbgw_ip := "127.0.0.1";
	integer mp_hnbgw_iuh_port := 29169;

	charstring mp_mgw_ip := "127.0.0.1";
	integer mp_mgw_port := 2427;

	RAN_Configuration mp_msc_cfg := {
		transport := RANAP_TRANSPORT_IuCS,
		sccp_service_type := "mtp3_itu",
		sctp_addr := { 23905, "127.0.0.1", 2905, "127.0.0.1" },
		own_pc := 188,	/* 0.23.4 first MSC emulation */
		own_ssn := 142,
		peer_pc := 189, /* 0.23.5 osmo-hnbgw */
		peer_ssn := 142,
		sio := '83'O,
		rctx := 1
	};
	RAN_Configuration mp_sgsn_cfg := {
		transport := RANAP_TRANSPORT_IuCS,
		sccp_service_type := "mtp3_itu",
		sctp_addr := { 23906, "127.0.0.1", 2905, "127.0.0.1" },
		own_pc := 185,	/* 0.23.1 first SGSN emulation */
		own_ssn := 142,
		peer_pc := 189, /* 0.23.5 osmo-hnbgw */
		peer_ssn := 142,
		sio := '83'O,
		rctx := 2
	};

	boolean mp_enable_pfcp_tests := false;

	/* IP address at which we listen for PFCP to emulate a UPF in ttcn3 */
	charstring mp_pfcp_ip_local := "127.0.0.1";

	/* IP address from which the SUT (osmo-hnbgw) sends PFCP requests, and to which the ttcn3 UPF emulation sends
	 * PFCP responses. */
	charstring mp_pfcp_ip_remote := "127.0.0.2";
}

function MSC_UnitdataCallback(RANAP_PDU ranap) runs on RAN_Emulation_CT return template RANAP_PDU {
	// TODO: Actually implement unitdata handling
	return ts_RANAP_Reset(ts_RanapCause_om_intervention, cs_domain);
}

const RanOps MSC_RanOps := {
	ranap_create_cb := refers(RAN_Emulation.RanapExpectedCreateCallback),
	ranap_unitdata_cb := refers(MSC_UnitdataCallback),
	ps_domain := false,
	decode_dtap := false,
	role_ms := false,
	protocol := RAN_PROTOCOL_RANAP,
	transport := RANAP_TRANSPORT_IuCS,
	use_osmux := false,
	bssap_reset_retries := 1,
	sccp_addr_local := omit,
	sccp_addr_peer := omit
}

type record CrcxResponse {
	integer resp,
	HostName mgw_rtp_ip,
	PortNumber mgw_rtp_port,
	MgcpConnectionId mgcp_connection_id
}
type record MgcpParameters {
	integer got_crcx_count,
	integer got_dlcx_count,
	MgcpCallId mgcp_call_id optional,
	MgcpEndpoint mgcp_ep,
	CrcxResponse mgw_conn_1,
	CrcxResponse mgw_conn_2,
	uint7_t rtp_payload_type,
	charstring rtp_sdp_format,
	HostName hnb_rtp_ip,
	PortNumber hnb_rtp_port,
	HostName cn_rtp_ip,
	PortNumber cn_rtp_port,
	boolean use_osmux,
	integer got_osmux_count
}

template (value) MgcpParameters t_MgcpParams := {
	got_crcx_count := 0,
	got_dlcx_count := 0,
	mgcp_call_id := omit,
	mgcp_ep := "rtpbridge/1@mgw",
	mgw_conn_1 := {
		resp := 1,
		mgw_rtp_ip := "127.1.2.1",
		mgw_rtp_port := 10000,
		mgcp_connection_id := '11111'H
	},
	mgw_conn_2 := {
		resp := 1,
		mgw_rtp_ip := "127.1.2.2",
		mgw_rtp_port := 20000,
		mgcp_connection_id := '22222'H
	},
	rtp_payload_type := 23,
	rtp_sdp_format := "FOO",
	hnb_rtp_ip := "127.1.1.1",
	hnb_rtp_port := 10001,
	cn_rtp_ip := "127.1.3.1",
	cn_rtp_port := 20001,
	use_osmux := false,
	got_osmux_count := 0
}

type record TestHdlrParams {
	integer hnb_idx,
	hexstring imsi,
	boolean ps_domain,
	MgcpParameters mgcp_pars optional,
	HnbConfig hnb optional,
	boolean separate_sccp_cr,
	charstring pfcp_local_addr
}

/* We extend:
   * RUA_ConnHdlr (for the Iuh side, emulating the HNB)
   * RAN_ConnHdlr (for the Iu side, emulating the MSC)
   * MGCP_ConnHdlr (for the MGCP side, emulating the MGW)
 */
type component ConnHdlr extends RAN_ConnHdlr, MGCP_ConnHdlr, RUA_ConnHdlr, PFCP_ConnHdlr {
	var integer g_sccp_conn_id;
	var TestHdlrParams g_pars;
	timer g_Tguard;
}


const MGCPOps MSC_MGCPOps := {
	create_cb := refers(MGCP_Emulation.ExpectedCreateCallback),
	unitdata_cb := refers(MGCP_Emulation.DummyUnitdataCallback)
}

function f_create_ranap_exp(octetstring l3_enc) runs on ConnHdlr {
	BSSAP_PROC.call(RAN_register:{l3_enc, self}) {
		[] BSSAP_PROC.getreply(RAN_register:{?, ?}) {};
	}
}


const integer NUM_HNB := 2;

type record HnbConfig {
	LocationAreaIdentification lai,
	integer sac
}

type component test_CT extends CTRL_Adapter_CT {
	var boolean g_initialized := false;

	/********************* Iu side */
	var RAN_Adapter g_msc;
	var RAN_Adapter g_sgsn;
	/* SGSN IuPS missing */

	/********************* Iuh side */
	var HnbConfig g_hnb_cfg[NUM_HNB];
	var Iuh_Emulation_CT vc_Iuh[NUM_HNB];
	var RUA_Emulation_CT vc_RUA[NUM_HNB];
	port HNBAP_PT HNBAP[NUM_HNB];
	/* Number of HNBs to be used/started by the test */
	var integer g_num_hnbs := NUM_HNB;

	var MGCP_Emulation_CT vc_MGCP;
	port TELNETasp_PT HNBGWVTY;
	/* global test case guard timer (actual timeout value is set in f_init()) */
	timer T_guard := 30.0;
}

/* global altstep for global guard timer; */
altstep as_Tguard() runs on test_CT {
	[] T_guard.timeout {
			setverdict(fail, "Timeout of T_guard");
			mtc.stop;
		}
}

friend function f_logp(TELNETasp_PT pt, charstring log_msg)
{
	// log on TTCN3 log output
	log(log_msg);
	// log in stderr log
	f_vty_transceive(pt, "logp lglobal notice TTCN3 f_logp(): " & log_msg);
}

function f_init_vty(charstring id := "foo") runs on test_CT {
	if (HNBGWVTY.checkstate("Mapped")) {
		/* skip initialization if already executed once */
		return;
	}
	map(self:HNBGWVTY, system:HNBGWVTY);
	f_vty_set_prompts(HNBGWVTY);
	f_vty_transceive(HNBGWVTY, "enable");
}

function f_init_mgcp(charstring id) runs on test_CT {
	id := id & "-MGCP";
	var MGCPOps ops := {
		create_cb := refers(MGCP_Emulation.ExpectedCreateCallback),
		unitdata_cb := refers(MGCP_Emulation.DummyUnitdataCallback)
	}
	var MGCP_conn_parameters pars := {
		callagent_ip := mp_hnbgw_ip,
		callagent_udp_port := -1,
		mgw_ip := mp_mgw_ip,
		mgw_udp_port := mp_mgw_port,
		multi_conn_mode := false
	}

	vc_MGCP := MGCP_Emulation_CT.create(id);
	map(vc_MGCP:MGCP, system:MGCP_CODEC_PT);
	vc_MGCP.start(MGCP_Emulation.main(ops, pars, id));
}

function f_init_pfcp(charstring id) runs on ConnHdlr {
	id := id & "-PFCP";

	var PFCP_Emulation_Cfg pfcp_cfg := {
		pfcp_bind_ip := mp_pfcp_ip_local,
		pfcp_bind_port := PFCP_PORT,
		pfcp_remote_ip := mp_pfcp_ip_remote,
		pfcp_remote_port := PFCP_PORT,
		role := UPF
	};

	vc_PFCP := PFCP_Emulation_CT.create(id) alive;
	connect(self:PFCP, vc_PFCP:CLIENT);
	connect(self:PFCP_PROC, vc_PFCP:CLIENT_PROC);
	vc_PFCP.start(PFCP_Emulation.main(pfcp_cfg));
}

function f_init_hnodeb(charstring id, integer hnb_idx, RuaOps rua_ops) runs on test_CT {
	id := id & "-Iuh" & int2str(hnb_idx);

	/* Iuh lower layer (RUA/HNBAP demux) */
	var Iuh_conn_parameters iuh_pars;
	iuh_pars.remote_ip := mp_hnbgw_ip;
	iuh_pars.remote_sctp_port := mp_hnbgw_iuh_port;
	iuh_pars.local_ip := mp_hnodeb_ip;
	iuh_pars.local_sctp_port := mp_hnodeb_port + hnb_idx;
	vc_Iuh[hnb_idx] := Iuh_Emulation_CT.create(id);
	connect(self:HNBAP[hnb_idx], vc_Iuh[hnb_idx]:HNBAP);

	vc_RUA[hnb_idx] := RUA_Emulation_CT.create(id & "-RUA");
	connect(vc_RUA[hnb_idx]:RUA, vc_Iuh[hnb_idx]:RUA);

	/* Start Iuh side components */
	vc_Iuh[hnb_idx].start(Iuh_Emulation.main(iuh_pars, id));
	vc_RUA[hnb_idx].start(RUA_Emulation.main(rua_ops, id & "-RUA"));
}

/* global initialization function */
function f_init(charstring id := "HNBGW", float guard_timeout := 30.0) runs on test_CT {

	T_guard.start(guard_timeout);
	activate(as_Tguard());

	/* RUA/RANAP emulation on top of lower-layer Iuh */
	var RuaOps rua_ops := {
		create_cb := refers(IuhRanapCreateCallback),
		unitdata_cb := refers(IuhRanapUnitdataCallback)
	};
	for (var integer i := 0; i < g_num_hnbs; i := i+1) {
		g_hnb_cfg[i] := {
			lai := {
				mcc_mnc := '00101'H,
				lac := 2342 + i
			},
			sac := 55
		};
		f_init_hnodeb(testcasename(), i, rua_ops);
	}

	/* MSC emulation */
	var RanOps ranops := {
		ranap_create_cb := refers(RAN_Emulation.RanapExpectedCreateCallback),
		ranap_unitdata_cb := omit,
		ps_domain := false,
		decode_dtap := false,
		role_ms := false,
		protocol := RAN_PROTOCOL_RANAP,
		transport := RANAP_TRANSPORT_IuCS,
		use_osmux := false,
		bssap_reset_retries := 1,
		sccp_addr_local := omit,
		sccp_addr_peer := omit
	};
	f_ran_adapter_init(g_msc, mp_msc_cfg, "HNBGW_Test", ranops);
	f_ran_adapter_start(g_msc);

	/* SGSN emulation */
	ranops.ps_domain := true;
	f_ran_adapter_init(g_sgsn, mp_sgsn_cfg, "HNBGW_Test", ranops);
	f_ran_adapter_start(g_sgsn);

	f_init_mgcp(id);
	f_init_vty("VirtHNBGW");
}

friend function f_shutdown_helper() runs on test_CT {
	all component.stop;
	setverdict(pass);
	mtc.stop;
}

/* helper function to start all of the simulated hNodeBs */
function f_start_hnbs() runs on test_CT {
	for (var integer i:= 0; i < g_num_hnbs; i := i+1) {
		f_hnbap_register(i, i);
	}
}

/***********************************************************************
 * code running in test_CT, preparing start of per-UE ConnHdlr
 ***********************************************************************/

/* inbound RUA connection establishment on Iuh side */
function IuhRanapCreateCallback(ContextId context_id, RUA_IEs.CN_DomainIndicator domain, charstring id)
runs on RUA_Emulation_CT return RUA_ConnHdlr {
	log("CreateCallback");
	return null;
}

/* inbound RUA connectionless data on Iuh side */
function IuhRanapUnitdataCallback(RANAP_PDU ranap)
runs on RUA_Emulation_CT return template RANAP_PDU {
	log("UnitdataCallback");
	return omit;
}

private function f_start_handler_create(TestHdlrParams pars) runs on test_CT return ConnHdlr {
	var ConnHdlr vc_conn;
	var charstring id := testcasename() & int2str(pars.hnb_idx);

	vc_conn := ConnHdlr.create(id);

	/* Iuh RUA part */
	connect(vc_conn:RUA, vc_RUA[pars.hnb_idx]:CLIENT);

	if (pars.ps_domain) {
		/* SGSN side */
		connect(vc_conn:BSSAP, g_sgsn.vc_RAN:CLIENT);
		connect(vc_conn:BSSAP_PROC, g_sgsn.vc_RAN:PROC);
	} else {
		/* MSC side */
		connect(vc_conn:BSSAP, g_msc.vc_RAN:CLIENT);
		connect(vc_conn:BSSAP_PROC, g_msc.vc_RAN:PROC);
	}
	/* MGCP part */
	connect(vc_conn:MGCP, vc_MGCP:MGCP_CLIENT);
	connect(vc_conn:MGCP_PROC, vc_MGCP:MGCP_PROC);

	return vc_conn;
}

private function f_start_handler_run(ConnHdlr vc_conn, void_fn fn, TestHdlrParams pars) runs on test_CT {
	var charstring id := testcasename(); // & int2str(pars.ran_idx);
	/* 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/ */
	pars.hnb := g_hnb_cfg[pars.hnb_idx];
	pars.mgcp_pars := valueof(t_MgcpParams);
	vc_conn.start(derefers(fn)(id, pars));
}

function f_start_handler_with_pars(void_fn fn, template (value) TestHdlrParams pars)
runs on test_CT return ConnHdlr {
	var ConnHdlr vc_conn;
	vc_conn := f_start_handler_create(valueof(pars));
	f_start_handler_run(vc_conn, fn, valueof(pars));
	return vc_conn;
}

/***********************************************************************
 * code running inside per-UE ConnHdlr
 ***********************************************************************/

type function void_fn(charstring id, TestHdlrParams pars) runs on ConnHdlr;

function f_init_handler(TestHdlrParams pars, float t_guard := 20.0) runs on ConnHdlr {
	/* make parameters available via component variable */
	g_pars := pars;

	f_init_pfcp(testcasename());

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

	/* TODO: CTRL? */
	/* TODO: VTY? */
}

/* global altstep for global guard timer; */
private altstep as_Tguard_ConnHdlr() runs on ConnHdlr {
	[] g_Tguard.timeout {
		setverdict(fail, "Timeout of T_guard");
		mtc.stop;
	}
}

/* send RANAP on Iuh and expect it to show up on Iu */
function f_iuh2iu(template (present) RANAP_PDU tx, template RANAP_PDU exp_rx := omit)
runs on ConnHdlr return RANAP_PDU {
	var RANAP_PDU rx;
	timer T := 5.0;

	if (istemplatekind(exp_rx, "omit")) {
		exp_rx := tx;
	}

	RUA.send(tx);
	T.start;

	alt {
	[] BSSAP.receive(exp_rx) -> value rx {
		setverdict(pass);
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for Iu ", exp_rx);
		}
	}
	return rx;
}

/* send RANAP on Iu and expect it to show up on Iuh */
function f_iu2iuh(template (present) RANAP_PDU tx, template RANAP_PDU exp_rx := omit)
runs on ConnHdlr return RANAP_PDU {
	var RANAP_PDU rx;
	timer T := 5.0;

	if (istemplatekind(exp_rx, "omit")) {
		exp_rx := tx;
	}

	BSSAP.send(tx);
	T.start;

	alt {
	[] RUA.receive(exp_rx) -> value rx {
		setverdict(pass);
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for Iuh ", exp_rx);
		}
	}
	return rx;
}

/* send RANAP on Iuh and expect it to show up on Iu */
function f_iuh2iu_connect(template (present) RANAP_PDU tx, template RANAP_PDU exp_rx := omit)
runs on ConnHdlr return RANAP_PDU {
	var RANAP_PDU rx;
	timer T := 5.0;

	if (istemplatekind(exp_rx, "omit")) {
		exp_rx := tx;
	}

	/* create an expect on the Iu side for the random NAS portion */
	if (g_pars.separate_sccp_cr) {
		f_ran_register_sccp_cr_without_payload();
	} else {
		var template (omit) octetstring nas := f_ranap_extract_l3(valueof(tx));
		f_ran_register_exp(valueof(nas));
	}

	/* send it via Iuh (creating a RUA connection) */
	RUA.send(RUA_Conn_Req:{g_pars.ps_domain, tx});

	if (g_pars.separate_sccp_cr) {
		/* Acknowledge the empty SCCP CR. RAN_Emulation does the confirmation, no need to respond. */
		BSSAP.receive(tr_RANAP_Conn_Req());
	}

	/* expect to receive it on the Iu side */
	T.start;
	alt {
	[] BSSAP.receive(exp_rx) -> value rx {
		setverdict(pass);
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for Iu ", exp_rx);
		}
	}
	return rx;
}

function f_iuh2iu_disconnect(template (present) RANAP_PDU tx, RUA_IEs.Cause cause,
			     template RANAP_PDU exp_rx := omit)
runs on ConnHdlr return RANAP_PDU {
	var RANAP_PDU rx
	timer T := 5.0;

	if (istemplatekind(exp_rx, "omit")) {
		exp_rx := tx;
	}

	/* send it via Iuh (creating a RUA connection) */
	RUA.send(RUA_Disc_Req:{tx, cause});

	/* expect to receive it on the Iu side */
	T.start;
	alt {
	[] BSSAP.receive(exp_rx) -> value rx {
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for Iu ", exp_rx);
		return rx;
		}
	}

	/* expect disconnect on the Iu side */
	alt {
	[] BSSAP.receive(RAN_Conn_Prim:MSC_CONN_PRIM_DISC_IND) {
		setverdict(pass);
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for Iu disconnect");
		return rx;
		}

	}
	return rx;
}

/* build a RANAP InitialUE based on the TestHdlrParams */
friend function f_build_initial_ue(TestHdlrParams pars) return RANAP_PDU {
	var LAI lai := {
		pLMNidentity := hex2oct(pars.hnb.lai.mcc_mnc),
		lAC := int2oct(pars.hnb.lai.lac, 2),
		iE_Extensions := omit
	};
	var SAI sai := {
		pLMNidentity := lai.pLMNidentity,
		lAC := lai.lAC,
		sAC := int2oct(pars.hnb.sac, 2),
		iE_Extensions := omit
	}
	var octetstring nas := f_rnd_octstring(10);
	var IuSignallingConnectionIdentifier sigc_id := int2bit(f_rnd_int(1000), 24);
	var GlobalRNC_ID grnc_id := {
		pLMNidentity := lai.pLMNidentity,
		rNC_ID := 2342
	}

	if (pars.ps_domain) {
		var RAC rac := '00'O;
		return valueof(ts_RANAP_initialUE_PS(lai, rac, sai, nas, sigc_id, grnc_id));
	} else {
		return valueof(ts_RANAP_initialUE_CS(lai, sai, nas, sigc_id, grnc_id));
	}
}

/* build a RANAP RAB AssignmentResponse based on the TestHdlrParams */
friend function f_build_rab_ass_resp(TestHdlrParams pars) return RANAP_PDU {
	var template RAB_SetupOrModifiedList rab_sml;

	rab_sml := ts_RAB_SMdL(t_RAB_id(23), f_ts_RAB_TLA("192.168.1.23"), t_RAB_binding_port(1234));

	return valueof(ts_RANAP_RabAssResp(rab_sml));
}


/***********************************************************************
 * HNBAP Testing
 ***********************************************************************/


function f_hnbap_register(integer hnb_idx := 0, integer cell_id := 0, boolean expect_reject := false) runs on test_CT
{
	timer T := 2.0;

	HNBAP[hnb_idx].send(ts_HNBAP_HNBRegisterRequest(char2oct("TTCN3 HNodeB"),
					'00F110'O,
					int2bit(1 + cell_id, 28),
					int2oct(2, 2),
					int2oct(3, 1),
					int2oct(4, 2)));

	T.start;
	alt {
	[] HNBAP[hnb_idx].receive(tr_HNBAP_HNBRegisterAccept(?)) {
		if (expect_reject) {
			setverdict(fail, "Rx HNB Register Accept while expecting reject");
		} else {
			setverdict(pass);
		}
	}
	[] HNBAP[hnb_idx].receive(tr_HNBAP_HNBRegisterReject(?)) {
		if (expect_reject) {
			setverdict(pass);
		} else {
			setverdict(fail, "Rx HNB Register Reject while expecting accept");
		}
	}
	[] HNBAP[hnb_idx].receive(IUHEM_Event:?) {
		repeat;
	}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for HNB Register response");
	}
	}
}

testcase TC_hnb_register() runs on test_CT {
	g_num_hnbs := 1;
	f_init();
	f_hnbap_register(0);
	f_shutdown_helper();
}

/* Try to register the same HNB from 2 different concurrent connections. Second
 * one should be rejected. */
testcase TC_hnb_register_duplicate() runs on test_CT {
	g_num_hnbs := 2;
	f_init();
	f_hnbap_register(0);
	f_hnbap_register(1, 0, expect_reject := true);
	f_verify_talloc_count(HNBGWVTY, {"struct hnb_context"}, expect_count := 1);
	f_shutdown_helper();
}

/* Try to register the same HNB in the same connection already established, aka
 * duplicate HNB Register Request. It should be accepted and new configuration
 * applied. TS 25.469 8.2.4 */
testcase TC_hnb_register_duplicate_reuse_sctp_assoc() runs on test_CT {
	g_num_hnbs := 1;
	f_init();
	f_hnbap_register(0);
	f_hnbap_register(0);
	f_verify_talloc_count(HNBGWVTY, {"struct hnb_context"}, expect_count := 1);
	f_shutdown_helper();
}

/* Drop HNBAP conn (HNBAP DEREG) and reconnect it (HNBAP REG) using same SCTP association.
 * Related: OS#5676, SYS#6113 */
testcase TC_hnb_reregister_reuse_sctp_assoc() runs on test_CT {
	g_num_hnbs := 1;
	f_init();
	f_hnbap_register(0);
	HNBAP[0].send(ts_HNBAP_HNBDe_Register(ts_HnbapCause(unspecified)));
	f_hnbap_register(0);
	f_verify_talloc_count(HNBGWVTY, {"struct hnb_context"}, expect_count := 1);
	f_shutdown_helper();
}

/***********************************************************************
 * RUA / RANAP Testing
 ***********************************************************************/

private template (value) TestHdlrParams
t_pars(integer imsi_suffix, boolean ps_domain := false, integer hnb_idx := 0,
       boolean separate_sccp_cr := false) := {
	hnb_idx := hnb_idx,
	imsi := f_gen_imsi(imsi_suffix),
	ps_domain := ps_domain,
	hnb := omit,	/* filled in later */
	separate_sccp_cr := separate_sccp_cr,
	pfcp_local_addr := mp_pfcp_ip_local
}

/* Create an Iuh connection; send InitialUE; expect it to appear on new SCCP conenction */
friend function f_tc_initial_ue(charstring id, TestHdlrParams pars) runs on ConnHdlr {
	f_init_handler(pars);
	var RANAP_PDU tx := f_build_initial_ue(g_pars);
	f_iuh2iu_connect(tx);
}
testcase TC_ranap_cs_initial_ue() runs on test_CT {
	var ConnHdlr vc_conn;

	g_num_hnbs := 1;
	f_init();
	f_start_hnbs();

	vc_conn := f_start_handler_with_pars(refers(f_tc_initial_ue), t_pars(1));
	vc_conn.done;
}
testcase TC_ranap_ps_initial_ue() runs on test_CT {
	var ConnHdlr vc_conn;

	g_num_hnbs := 1;
	f_init();
	f_start_hnbs();

	vc_conn := f_start_handler_with_pars(refers(f_tc_initial_ue), t_pars(2, true));
	vc_conn.done;
}

private function f_vty_set_sccp_cr_max_payload_len(TELNETasp_PT pt, integer val := 999999)
{
	f_vty_enter_config(pt);
	f_vty_transceive(pt, "hnbgw");
	f_vty_transceive(pt, "sccp cr max-payload-len " & int2str(val));
	f_vty_transceive(pt, "end");
}

testcase TC_ranap_cs_initial_ue_empty_cr() runs on test_CT {
	var ConnHdlr vc_conn;

	g_num_hnbs := 1;
	f_init();
	f_start_hnbs();

	f_vty_set_sccp_cr_max_payload_len(HNBGWVTY, 0);

	vc_conn := f_start_handler_with_pars(refers(f_tc_initial_ue), t_pars(1, separate_sccp_cr := true));
	vc_conn.done;

	/* reset */
	f_vty_set_sccp_cr_max_payload_len(HNBGWVTY);
}
testcase TC_ranap_ps_initial_ue_empty_cr() runs on test_CT {
	var ConnHdlr vc_conn;

	g_num_hnbs := 1;
	f_init();
	f_start_hnbs();

	f_vty_set_sccp_cr_max_payload_len(HNBGWVTY, 0);

	vc_conn := f_start_handler_with_pars(refers(f_tc_initial_ue), t_pars(2, true, separate_sccp_cr := true));
	vc_conn.done;

	/* reset */
	f_vty_set_sccp_cr_max_payload_len(HNBGWVTY);
}

/* Reply to a received CRCX with an OK (or the reply configured in cpars), using the given parameters.
 * Return true when an OK reply was sent, false otherwise.
 * Count occurrence of Osmux, include Osmux parameters in the reply if necessary. */
function f_handle_crcx(inout MgcpParameters pars, MgcpCommand mgcp_cmd) return template MgcpResponse {
	var CrcxResponse conn := pars.mgw_conn_1;
	if (pars.got_crcx_count > 0) {
		conn := pars.mgw_conn_2;
	}
	pars.got_crcx_count := pars.got_crcx_count + 1;

	var MgcpMessage mgcp_msg := {
		command := mgcp_cmd
	}
	var template MgcpResponse mgcp_resp;
	var MgcpOsmuxCID osmux_cid;
	var MgcpCallId call_id := f_MgcpCmd_extract_call_id(mgcp_cmd);
	if (ispresent(pars.mgcp_call_id)) {
		if (pars.mgcp_call_id != call_id) {
			setverdict(fail, "CRCX contained unexpected call id. Expected:", pars.mgcp_call_id, " got:", call_id);
			mtc.stop;
		}
	} else {
		pars.mgcp_call_id := call_id;
	}

	/* When the endpoint contains a wildcard we keep the endpoint
	 * identifier we have set up in pars. Otherwise we use the
	 * endpoint name that the call agent has supplied */
	if (match(mgcp_cmd.line.ep, t_MGCP_EP_wildcard) == false) {
		pars.mgcp_ep := mgcp_cmd.line.ep;
	}

	if (conn.resp == -1) {
		/* Reply with rror */
		var MgcpResponse mgcp_rsp := {
			line := {
				code := "542",
				trans_id := mgcp_cmd.line.trans_id,
				string := "FORCED_FAIL"
			},
			sdp := omit

		}
		var MgcpParameter mgcp_rsp_param := {
			code := "Z",
			val := pars.mgcp_ep
		};
		mgcp_rsp.params[0] := mgcp_rsp_param;
		return mgcp_rsp;
	}

	if (conn.resp == 0) {
		/* Do not reply at all */
		return omit;
	}

	if (conn.resp != 1) {
		setverdict(fail, "Unexpected value for pars.mgw_conn_*.resp, expect -1, 0 or 1");
		mtc.stop;
	}

	var SDP_Message sdp := valueof(ts_SDP(conn.mgw_rtp_ip, conn.mgw_rtp_ip,
						hex2str(pars.mgcp_call_id), "42",
						conn.mgw_rtp_port,
						{ int2str(pars.rtp_payload_type) },
						{ valueof(ts_SDP_rtpmap(pars.rtp_payload_type,
									pars.rtp_sdp_format)),
						  valueof(ts_SDP_ptime(20)) }));

	if (f_mgcp_contains_par(mgcp_msg, "X-OSMUX")) {
		if (not pars.use_osmux) {
			setverdict(fail, "MSC sent X-Osmux parameter in MGCP, but not expecting any Osmux");
			mtc.stop;
		}
		pars.got_osmux_count := pars.got_osmux_count + 1;
		/* we expect MSC to use wildcard here, i.e. osmux_cid == -1 */
		osmux_cid := f_MgcpCmd_extract_osmux_cid(mgcp_cmd);
		log("f_handle_crcx(): got Osmux CID: ", osmux_cid);
		if (osmux_cid != -1) {
			setverdict(fail, "MSC using unexpected CID " & int2str(osmux_cid) & " != -1");
			mtc.stop;
		}

		osmux_cid := 0;
		mgcp_resp := ts_CRCX_ACK_osmux(mgcp_cmd.line.trans_id, conn.mgcp_connection_id, osmux_cid, sdp);
	} else {
		mgcp_resp := ts_CRCX_ACK(mgcp_cmd.line.trans_id, conn.mgcp_connection_id, sdp);
	}

	f_mgcp_par_append(mgcp_resp.params, ts_MgcpParSpecEP(pars.mgcp_ep));

	return mgcp_resp;
}

friend function f_create_rab(inout MgcpParameters pars) runs on ConnHdlr {
	f_rab_ass_req(pars);
	f_rab_ass_resp(pars);
}

friend function f_rab_ass_req(inout MgcpParameters pars) runs on ConnHdlr {
	var MgcpCommand mgcp_cmd;
	var RANAP_PDU tx;
	var template RAB_SetupOrModifyList rab_sml;
	timer T := 5.0;

	/* Send RAB Assignment Request */
	rab_sml := ts_RAB_SML(t_RAB_id(23), f_ts_RAB_TLA(pars.cn_rtp_ip), t_RAB_binding_port(pars.cn_rtp_port));
	tx := valueof(ts_RANAP_RabAssReq(rab_sml));
	BSSAP.send(tx);
	T.start;

	/* Handle MGCP CRCX */
	alt {
	[] MGCP.receive(tr_CRCX) -> value mgcp_cmd {
		log("CRCX1", mgcp_cmd);
		var template MgcpResponse mgcp_rsp := f_handle_crcx(pars, mgcp_cmd);
		MGCP.send(valueof(mgcp_rsp));
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for MGCP");
		}
	}

	/* Expect RAB Assignment Request with IP/port from CRCX ACK via Iuh */
	rab_sml := ts_RAB_SML(t_RAB_id(23), f_ts_RAB_TLA(pars.mgw_conn_1.mgw_rtp_ip), t_RAB_binding_port(pars.mgw_conn_1.mgw_rtp_port));
	tx := valueof(ts_RANAP_RabAssReq(rab_sml));

	alt {
	[] RUA.receive(tx) {
		setverdict(pass);
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for Iuh ", tx);
		}
	}
}

friend function f_rab_ass_resp(inout MgcpParameters pars) runs on ConnHdlr {
	var MgcpCommand mgcp_cmd;
	var RANAP_PDU tx;
	var template RAB_SetupOrModifiedList rab_smdl;
	timer T := 5.0;

	/* Send back RAB Assignment Response via Iuh */
	rab_smdl := ts_RAB_SMdL(t_RAB_id(23), f_ts_RAB_TLA(pars.hnb_rtp_ip), t_RAB_binding_port(pars.hnb_rtp_port));
	tx := valueof(ts_RANAP_RabAssResp(rab_smdl));
	RUA.send(tx);
	T.start;

	interleave {
	/* Expect MDCX with IP/port from RAB Assignment Response */
	[] MGCP.receive(tr_MDCX(tr_SDP(pars.hnb_rtp_ip, pars.hnb_rtp_port))) -> value mgcp_cmd {
		log("MDCX1", mgcp_cmd);
		/* Verify SDP of MDCX */
		var SDP_Message sdp := valueof(ts_SDP(pars.mgw_conn_1.mgw_rtp_ip, pars.mgw_conn_1.mgw_rtp_ip, hex2str(pars.mgcp_call_id), "42", pars.mgw_conn_1.mgw_rtp_port,
			{ int2str(pars.rtp_payload_type) }, { valueof(ts_SDP_rtpmap(pars.rtp_payload_type, pars.rtp_sdp_format)), valueof(ts_SDP_ptime(20)) } ));
		var template MgcpResponse mgcp_rsp := ts_MDCX_ACK(mgcp_cmd.line.trans_id, pars.mgw_conn_1.mgcp_connection_id, sdp);
		MGCP.send(valueof(mgcp_rsp));
		}
	/* Handle CRCX for second leg of endpoint, answer with IP/port */
	[] MGCP.receive(tr_CRCX(pars.mgcp_ep, tr_SDP(pars.cn_rtp_ip, pars.cn_rtp_port))) -> value mgcp_cmd {
		log("CRCX2", mgcp_cmd);
		/* Verify SDP of CRCX */
		var template MgcpResponse mgcp_rsp := f_handle_crcx(pars, mgcp_cmd);
		MGCP.send(valueof(mgcp_rsp));
	}
	}

	/* Expect RAB Assignment Response with IP/port from second CRCX ACK */
	rab_smdl := ts_RAB_SMdL(t_RAB_id(23), f_ts_RAB_TLA(pars.mgw_conn_2.mgw_rtp_ip), t_RAB_binding_port(pars.mgw_conn_2.mgw_rtp_port));
	tx := valueof(ts_RANAP_RabAssResp(rab_smdl));

	alt {
	[] BSSAP.receive(tx) {
		setverdict(pass);
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for Iuh ", tx);
		}
	}
}

private altstep as_mgcp_dlcx(inout TestHdlrParams pars) runs on ConnHdlr {
	var MgcpCommand mgcp_cmd;

	[] MGCP.receive(tr_DLCX(pars.mgcp_pars.mgcp_ep)) -> value mgcp_cmd {
		log("DLCX", mgcp_cmd);
		MGCP.send(ts_DLCX_ACK2(mgcp_cmd.line.trans_id));
		pars.mgcp_pars.got_dlcx_count := pars.mgcp_pars.got_dlcx_count + 1;
		if (pars.mgcp_pars.got_dlcx_count != pars.mgcp_pars.got_crcx_count) {
			repeat;
		}
		setverdict(pass);
	}
}

friend function f_tc_rab_assignment(charstring id, TestHdlrParams pars) runs on ConnHdlr {
	var MgcpCommand mgcp_cmd;
	var RANAP_PDU tx;
	timer T := 5.0;

	f_init_handler(pars);
	f_create_mgcp_expect(ExpectCriteria:{omit,omit,omit});

	tx := f_build_initial_ue(g_pars);
	f_iuh2iu_connect(tx);

	f_create_rab(pars.mgcp_pars);

	/* Send Iu Release */
	tx := valueof(ts_RANAP_IuReleaseCommand(ts_RanapCause_om_intervention));
	f_iu2iuh(tx);

	T.start;
	alt {
	[] as_mgcp_dlcx(pars) {}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for DLCX");
	}
	}

	tx := valueof(ts_RANAP_IuReleaseComplete());
	f_iuh2iu(tx);
}

testcase TC_rab_assignment() runs on test_CT {
	var ConnHdlr vc_conn;
	g_num_hnbs := 1;
	f_init();
	f_start_hnbs();

	vc_conn := f_start_handler_with_pars(refers(f_tc_rab_assignment), t_pars(3));
	vc_conn.done;
}

friend function f_tc_rab_assign_fail(charstring id, TestHdlrParams pars) runs on ConnHdlr {
	var MgcpCommand mgcp_cmd;
	var RANAP_PDU tx;
	timer T := 5.0;

	f_init_handler(pars);
	f_create_mgcp_expect(ExpectCriteria:{omit,omit,omit});

	tx := f_build_initial_ue(g_pars);
	f_iuh2iu_connect(tx);

	f_rab_ass_req(pars.mgcp_pars);

	/* Send RAB failed list in response */
	tx := valueof(ts_RANAP_RabAssResp(rab_fl := ts_RAB_FL(t_RAB_id(23), ts_RanapCause_om_intervention)));
	f_iuh2iu(tx);

	T.start;
	alt {
	[] as_mgcp_dlcx(pars) {}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for DLCX");
	}
	}
}

testcase TC_rab_assign_fail() runs on test_CT {
	var ConnHdlr vc_conn;
	g_num_hnbs := 1;
	f_init();
	f_start_hnbs();

	vc_conn := f_start_handler_with_pars(refers(f_tc_rab_assign_fail), t_pars(4));
	vc_conn.done;
}

friend function f_tc_rab_release(charstring id, TestHdlrParams pars) runs on ConnHdlr {
	var MgcpCommand mgcp_cmd;
	var RANAP_PDU tx;
	timer T := 15.0;

	f_init_handler(pars);
	f_create_mgcp_expect(ExpectCriteria:{omit,omit,omit});

	tx := f_build_initial_ue(g_pars);
	f_iuh2iu_connect(tx);

	f_create_rab(pars.mgcp_pars);

	/* Send RAB Release */
	tx := valueof(ts_RANAP_RabAssReq(rab_rl := ts_RAB_RL(t_RAB_id(23), ts_RanapCause_om_intervention)));
	BSSAP.send(tx);

	T.start;

	alt {
	[] as_mgcp_dlcx(pars) {}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for DLCX");
	}
	}

	alt {
	[] RUA.receive(tx) {
		setverdict(pass);
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for Iuh ", tx);
		}
	}

}

testcase TC_rab_release() runs on test_CT {
	var ConnHdlr vc_conn;
	g_num_hnbs := 1;
	f_init();
	f_start_hnbs();

	vc_conn := f_start_handler_with_pars(refers(f_tc_rab_release), t_pars(5));
	vc_conn.done;
}

friend function f_tc_rab_assign_mgcp_to(charstring id, TestHdlrParams pars) runs on ConnHdlr {
	var MgcpCommand mgcp_cmd;
	var RANAP_PDU tx;
	var template RAB_SetupOrModifyList rab_sml;
	timer T := 15.0;

	T.start;
	f_init_handler(pars);
	f_create_mgcp_expect(ExpectCriteria:{omit,omit,omit});

	tx := f_build_initial_ue(g_pars);
	f_iuh2iu_connect(tx);


	/* Send RAB Assignment Request */
	rab_sml := ts_RAB_SML(t_RAB_id(23), f_ts_RAB_TLA(pars.mgcp_pars.cn_rtp_ip), t_RAB_binding_port(pars.mgcp_pars.cn_rtp_port));
	tx := valueof(ts_RANAP_RabAssReq(rab_sml));
	BSSAP.send(tx);

	/* Ignore MGCP CRCX */
	alt {
	[] MGCP.receive(tr_CRCX) -> value mgcp_cmd {
		log("Ignoreing CRCX1", mgcp_cmd);
		repeat;
		}
	[] BSSAP.receive(tr_RANAP_IuReleaseRequest(?)) { }
	[] T.timeout {
		setverdict(fail, "Timeout waiting for IuRelease");
		}
	}

	/* Send Iu Release */
	tx := valueof(ts_RANAP_IuReleaseCommand(ts_RanapCause_om_intervention));
	f_iu2iuh(tx);

	tx := valueof(ts_RANAP_IuReleaseComplete());
	f_iuh2iu(tx);
}

testcase TC_rab_assign_mgcp_to() runs on test_CT {
	var ConnHdlr vc_conn;
	g_num_hnbs := 1;
	f_init();
	f_start_hnbs();

	vc_conn := f_start_handler_with_pars(refers(f_tc_rab_assign_mgcp_to), t_pars(6));
	vc_conn.done;
}

/* Create an Iuh connection; send InitialUE; transceive data both directions */
friend function f_tc_ranap_bidir(charstring id, TestHdlrParams pars) runs on ConnHdlr {
	f_init_handler(pars);

	/* HNB -> MSC: InitialUE */
	f_iuh2iu_connect(f_build_initial_ue(g_pars));

	/* MSC <- HNB: DirectTransfer */
	f_iu2iuh(ts_RANAP_DirectTransfer(f_rnd_octstring(10)));
	/* MSC -> HNB: DirectTransfer */
	f_iuh2iu(ts_RANAP_DirectTransfer(f_rnd_octstring(10)));

	/* HNB <- MSC:  CommonID */
	f_iu2iuh(ts_RANAP_CommonId(hex2oct(pars.imsi)));
}
testcase TC_ranap_cs_bidir() runs on test_CT {
	var ConnHdlr vc_conn;
	g_num_hnbs := 1;
	f_init();
	f_start_hnbs();

	vc_conn := f_start_handler_with_pars(refers(f_tc_ranap_bidir), t_pars(3));
	vc_conn.done;
}
testcase TC_ranap_ps_bidir() runs on test_CT {
	var ConnHdlr vc_conn;
	g_num_hnbs := 1;
	f_init();
	f_start_hnbs();

	vc_conn := f_start_handler_with_pars(refers(f_tc_ranap_bidir), t_pars(4, true));
	vc_conn.done;
}


private function f_tc_ranap_mo_disconnect(charstring id, TestHdlrParams pars) runs on ConnHdlr {
	f_init_handler(pars);

	/* HNB -> MSC: InitialUE */
	f_iuh2iu_connect(f_build_initial_ue(g_pars));

	/* MSC <- HNB: DirectTransfer */
	f_iu2iuh(ts_RANAP_DirectTransfer(f_rnd_octstring(10)));
	/* MSC -> HNB: DirectTransfer */
	f_iuh2iu(ts_RANAP_DirectTransfer(f_rnd_octstring(10)));

	/* MSC <- HNB: RUA disconnect */
	f_iuh2iu_disconnect(ts_RANAP_IuReleaseComplete, RUA_IEs.Cause:{misc:=processing_overload});
}
testcase TC_ranap_cs_mo_disconnect() runs on test_CT {
	var ConnHdlr vc_conn;
	g_num_hnbs := 1;
	f_init();
	f_start_hnbs();

	vc_conn := f_start_handler_with_pars(refers(f_tc_ranap_mo_disconnect), t_pars(5));
	vc_conn.done;
}
testcase TC_ranap_ps_mo_disconnect() runs on test_CT {
	var ConnHdlr vc_conn;
	g_num_hnbs := 1;
	f_init();
	f_start_hnbs();

	vc_conn := f_start_handler_with_pars(refers(f_tc_ranap_mo_disconnect), t_pars(6));
	vc_conn.done;
}

type record FTeid {
	HostName addr,
	OCT4 teid
}

type record FTeids {
	FTeid local,
	FTeid remote
}


/* 'local' and 'remote' refer to the GTP information from the UPF's point of view:
 * HNB                             UPF                 CN
 * access.remote <---> access.local | core.local <---> core.remote
 */
type record GtpParameters {
	FTeids core,
	FTeids access
}

/* HNB                             UPF                 CN
 * access.remote <---> access.local | core.local <---> core.remote
 * 127.0.0.4           127.0.0.3      127.0.0.2        127.0.0.1
 * 0x44004400          0x30303030     0x22002200       0x10101010
 */
template GtpParameters t_GtpParameters := {
	core := {
		local := {
			addr := "127.0.0.2",
			teid := '22002200'O
		},
		remote := {
			addr := "127.0.0.1",
			teid := '10101010'O
		}
	},
	access := {
		local := {
			addr := "127.0.0.3",
			teid := '30303030'O
		},
		remote := {
			addr := "127.0.0.4",
			teid := '44004400'O
		}
	}
}

friend function f_tc_ps_rab_assignment_with_pfcp(charstring id, TestHdlrParams pars) runs on ConnHdlr {
	var RANAP_PDU tx;
	var RANAP_PDU rx;
	timer T := 5.0;

	f_init_handler(pars);

	f_pfcp_register();

	var PDU_PFCP m;
	var Node_ID upf_node_id := valueof(ts_PFCP_Node_ID_fqdn("\07osmocom\03org"));

	PFCP.receive(tr_PFCP_Assoc_Setup_Req()) -> value m;
	PFCP.send(ts_PFCP_Assoc_Setup_Resp(m.sequence_number, upf_node_id,
					   ts_PFCP_Cause(REQUEST_ACCEPTED), 1234));

	tx := f_build_initial_ue(g_pars);
	f_iuh2iu_connect(tx);

	var GtpParameters gtp_pars := valueof(t_GtpParameters);
	var template RAB_SetupOrModifyList rab_sml;

	/* Send RAB Assignment Request */
	rab_sml := ts_RAB_SML_ps(t_RAB_id(23), f_ts_RAB_TLA(gtp_pars.core.remote.addr), gtp_pars.core.remote.teid);
	tx := valueof(ts_RANAP_RabAssReq(rab_sml));
	BSSAP.send(tx);

	/* Expect PFCP Session Establishment Request. */
	PFCP.receive(tr_PFCP_Session_Est_Req()) -> value m;
	var F_SEID hnbgw_f_seid := m.message_body.pfcp_session_establishment_request.CP_F_SEID;
	var PFCP_Session_Establishment_Request serq := m.message_body.pfcp_session_establishment_request;

	/* Acting as UPF, invent a new PFCP SEID to send to HNBGW. Respond to the Session Establishment.
	 * The PFCP response must have the same sequence_number as the request. */
	var F_SEID up_f_seid := valueof(ts_PFCP_F_SEID_ipv4("127.0.0.1", '1111111111111111'O));
	var template PDU_PFCP r := ts_PFCP_Session_Est_Resp(m.sequence_number, upf_node_id, hnbgw_f_seid.seid);
	r.message_body.pfcp_session_establishment_response := {
		offending_ie := omit,
		UP_F_SEID := up_f_seid,
		created_PDR_list := {
			ts_PFCP_Created_PDR(pdr_id := serq.create_PDR_list[0].grouped_ie.pdr_id,
					    local_F_TEID := ts_PFCP_F_TEID_ipv4(gtp_pars.core.local.teid,
										gtp_pars.core.local.addr)),
			ts_PFCP_Created_PDR(pdr_id := serq.create_PDR_list[1].grouped_ie.pdr_id,
					    local_F_TEID := ts_PFCP_F_TEID_ipv4(gtp_pars.access.local.teid,
										gtp_pars.access.local.addr))
		},
		load_control_information := omit,
		overload_control_information := omit,
		node_list := omit,
		failed_rule_id := omit,
		created_traffic_endpoint_list := omit
	};
	PFCP.send(r);

	/* Expect on Iuh: RAB Assignment Request with IP/port from PFCP Session Est Resp */
	rab_sml := ts_RAB_SML_ps(t_RAB_id(23), f_ts_RAB_TLA(gtp_pars.access.local.addr),
				 gtp_pars.access.local.teid);
	rx := valueof(ts_RANAP_RabAssReq(rab_sml));
	RUA.receive(rx);

	/* Send back RAB Assignment Response via Iuh */
	var template RAB_SetupOrModifiedList rab_smdl;
	rab_smdl := ts_RAB_SMdL_ps(t_RAB_id(23), f_ts_RAB_TLA(gtp_pars.access.remote.addr),
				   gtp_pars.access.remote.teid);
	tx := valueof(ts_RANAP_RabAssResp(rab_smdl));
	RUA.send(tx);
	T.start;

	PFCP.receive(tr_PFCP_Session_Mod_Req(up_f_seid.seid)) -> value m;
	r := ts_PFCP_Session_Mod_Resp(m.sequence_number, hnbgw_f_seid.seid);
	PFCP.send(r);

	rab_smdl := ts_RAB_SMdL_ps(t_RAB_id(23), f_ts_RAB_TLA(gtp_pars.core.local.addr), gtp_pars.core.local.teid);
	BSSAP.receive(tr_RANAP_RabAssResp(rab_smdl));

	f_sleep(2.0);
	tx := valueof(ts_RANAP_IuReleaseCommand(ts_RanapCause_om_intervention));
	f_iu2iuh(tx);

	tx := valueof(ts_RANAP_IuReleaseComplete());
	f_iuh2iu(tx);

	PFCP.receive(tr_PFCP_Session_Del_Req(up_f_seid.seid)) -> value m;
	PFCP.send(ts_PFCP_Session_Del_Resp(m.sequence_number, hnbgw_f_seid.seid));

	f_sleep(2.0);
}

testcase TC_ps_rab_assignment_with_pfcp() runs on test_CT {
	var ConnHdlr vc_conn;
	g_num_hnbs := 1;
	f_init();
	f_start_hnbs();
	f_sleep(1.0);

	vc_conn := f_start_handler_with_pars(refers(f_tc_ps_rab_assignment_with_pfcp), t_pars(7, ps_domain := true));
	vc_conn.done;
}

altstep as_disallow_pfcp() runs on ConnHdlr {
	[] PFCP.receive(PDU_PFCP:?) {
			setverdict(fail, "Received PFCP message, but no PFCP communication expected");
			mtc.stop;
		}
}

friend function f_tc_ps_rab_assignment_without_pfcp(charstring id, TestHdlrParams pars) runs on ConnHdlr {
	var RANAP_PDU tx;
	var RANAP_PDU rx;
	timer T := 5.0;

	f_init_handler(pars);

	f_pfcp_register();
	activate(as_disallow_pfcp());

	tx := f_build_initial_ue(g_pars);
	f_iuh2iu_connect(tx);

	var GtpParameters gtp_pars := valueof(t_GtpParameters);
	var template RAB_SetupOrModifyList rab_sml;

	/* Send RAB Assignment Request */
	rab_sml := ts_RAB_SML_ps(t_RAB_id(23), f_ts_RAB_TLA(gtp_pars.core.remote.addr), gtp_pars.core.remote.teid);
	tx := valueof(ts_RANAP_RabAssReq(rab_sml));
	BSSAP.send(tx);

	/* Expect on Iuh: unmodified RAB Assignment Request */
	rx := valueof(ts_RANAP_RabAssReq(rab_sml));
	RUA.receive(rx);

	/* Send back RAB Assignment Response via Iuh */
	var template RAB_SetupOrModifiedList rab_smdl;
	rab_smdl := ts_RAB_SMdL_ps(t_RAB_id(23), f_ts_RAB_TLA(gtp_pars.access.remote.addr),
				   gtp_pars.access.remote.teid);
	tx := valueof(ts_RANAP_RabAssResp(rab_smdl));
	RUA.send(tx);

	/* Expect on IuPS: unmodified RAB Assignment Response */
	BSSAP.receive(tr_RANAP_RabAssResp(rab_smdl));

	f_sleep(2.0);
	tx := valueof(ts_RANAP_IuReleaseCommand(ts_RanapCause_om_intervention));
	f_iu2iuh(tx);

	tx := valueof(ts_RANAP_IuReleaseComplete());
	f_iuh2iu(tx);

	f_sleep(2.0);
}

testcase TC_ps_rab_assignment_without_pfcp() runs on test_CT {
	var ConnHdlr vc_conn;
	f_init();
	f_start_hnbs();
	f_sleep(1.0);

	vc_conn := f_start_handler_with_pars(refers(f_tc_ps_rab_assignment_without_pfcp), t_pars(7, ps_domain := true));
	vc_conn.done;
}

control {
	execute(TC_hnb_register());
	execute(TC_hnb_register_duplicate());
	execute(TC_hnb_register_duplicate_reuse_sctp_assoc());
	execute(TC_ranap_cs_initial_ue());
	execute(TC_ranap_ps_initial_ue());
	execute(TC_ranap_cs_initial_ue_empty_cr());
	execute(TC_ranap_ps_initial_ue_empty_cr());
	execute(TC_ranap_cs_bidir());
	execute(TC_ranap_ps_bidir());
	execute(TC_rab_assignment());
	execute(TC_rab_release());
	execute(TC_rab_assign_fail());
	execute(TC_rab_assign_mgcp_to());
	execute(TC_ranap_cs_mo_disconnect());
	execute(TC_ranap_ps_mo_disconnect());

	if (mp_enable_pfcp_tests) {
		execute(TC_ps_rab_assignment_with_pfcp());
	} else {
		execute(TC_ps_rab_assignment_without_pfcp());
	}

	/* Run at the end since it makes osmo-hnbgw <= 1.3.0 crash: OS#5676 */
	execute(TC_hnb_reregister_reuse_sctp_assoc());
}

}
