module BSC_Tests_ASCI {

/* Integration Tests for OsmoBSC
 * (C) 2023 by 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
 *
 * This test suite tests OsmoBSC while emulating both multiple BTS + MS as
 * well as the MSC. See README for more details.
 */

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

import from BSC_Tests all;
import from BSSAP_Types all;
import from BSSAP_CodecPort all;
import from BSSMAP_Templates all;
import from RSL_Types all;
import from RSL_Emulation all;
import from MGCP_Emulation all;

import from MobileL3_CommonIE_Types all;
import from MobileL3_Types all;
import from L3_Templates all;
import from GSM_RR_Types all;

import from MSC_ConnectionHandler all;
import from RAN_Adapter all;
import from RAN_Emulation all;

const charstring COORD_VGCS_CALL_EST := "VGCS_CALL_EST";
const charstring COORD_VGCS_CALL_REL := "VGCS_CALL_REL";
const charstring COORD_VGCS_CHANNEL_REL := "VGCS_CHANNEL_REL";
const charstring COORD_VGCS_UPLINK_FREE := "VGCS_UPLINK_FREE";
const charstring COORD_VGCS_UPLINK_BUSY := "VGCS_UPLINK_BUSY";
const charstring COORD_VGCS_ASSIGN_RES := "VGCS_ASSIGN_RES";
const charstring COORD_VGCS_ASSIGN_FAIL := "VGCS_ASSIGN_FAIL";
const charstring COORD_UPLINK_REQUEST_CONFIRM := "UPLINK_REQUEST_CONFIRM";

template (value) DescriptiveGroupOrBroadcastCallReference_V
			ts_BSSMAP_IE_GroupCallRef(integer cr,
						  BIT1 sf,
						  BIT1 af,
						  BIT3 prio,
						  BIT4 ci) := {
	binaryCodingOfGroupOrBroadcastCallReference := int2bit(cr, 27),
	sF := sf,
	aF := af,
	callPriority := prio,
	cipheringInformation := ci,
	spare := '0000'B
}

function f_gen_asci_ass_req(integer bssap_idx := 0,
			    template (value) BSSMAP_IE_ChannelType ch_type := ts_BSSMAP_IE_ChannelType,
			    template (value) OCT1 ass_requirement := '00'O,
			    template (value) BSSMAP_IE_CellIdentifier cell_id := ts_CellId_CI(0),
			    template (value) GroupCallRef group_call_ref := '0000001000'O,
			    charstring aoip_tla :="1.2.3.4")
runs on MSC_ConnHdlr return template (value) PDU_BSSAP {
	if (mp_bssap_cfg[bssap_idx].transport == BSSAP_TRANSPORT_AoIP) {
		var template (value) BSSMAP_IE_AoIP_TransportLayerAddress tla :=
			f_ts_BSSMAP_IE_AoIP_TLA(aoip_tla, 2342);
		var template (value) BSSMAP_IE_SpeechCodecList codec_list :=
			ts_BSSMAP_IE_CodecList({ts_CodecFR});
		return ts_BSSMAP_VGCS_VBS_AssignmentReq(ch_type, ass_requirement, cell_id,
							    group_call_ref, omit, tla, omit, codec_list);
	} else {
		var template (value) BSSMAP_IE_CircuitIdentityCode cic := ts_BSSMAP_IE_CIC(0,1);
		return ts_BSSMAP_VGCS_VBS_AssignmentReq(ch_type, ass_requirement, cell_id,
							    group_call_ref, cic, omit, omit, omit);
	}
}

/*
 * VGCS/VBS call controling connection
 */

private function f_delay_msc() runs on MSC_ConnHdlr {
	var PDU_BSSAP rx_bssap;
	timer T := 2.0;
	T.start;
	alt {
	[] RAN_CONN.receive(tr_BSSAP_BSSMAP) -> value rx_bssap {
		setverdict(fail, "Got BSSAP message from BSC. This is unexpected:", rx_bssap);
		}
	[] T.timeout { }
	}
}

private function f_tc_vgcs_vbs_setup(charstring id) runs on MSC_ConnHdlr {
	var PDU_BSSAP rx_bssap;
	var template (value) DescriptiveGroupOrBroadcastCallReference_V callref :=
								ts_BSSMAP_IE_GroupCallRef(11, '1'B, '0'B, '000'B, '0000'B);
	var template (value) PDU_BSSAP setup_req := ts_BSSMAP_VGCS_VBS_Setup(bit2oct(encvalue(callref)), omit);
	var boolean uplink_free := false;
	var boolean uplink_busy := false;
	var boolean uplink_req := false;
	var boolean uplink_req_conf := false;
	var boolean uplink_rel_ind := false;
	var boolean uplink_rel_ind_failure := false;
	var boolean assign_res := false;
	var boolean assign_fail := false;
	/* Note: This timer is used to receive messages after the expected event.
	 * After timeout, the outcome of the test is checked.
	 * The timeout must be large enough, so all the messages pass before checking the outcome. */
	timer T := 1.0;

	/* Wait for the COORD ports to be connected. */
	f_sleep(1.0);

	/* VGCS/VBS SETUP REQ in SCCP CR (must be the first message) */
	RAN_CONN.send(ts_BSSAP_Conn_Req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc, setup_req));
	RAN_CONN.receive(tr_MSC_CONN_PRIM_CONF_IND);

	alt {
	[] RAN_CONN.receive(tr_BSSMAP_VGCS_VBS_SetupAck) -> value rx_bssap {
		log("VGCS: got setup ack");
		if (not g_pars.asci_test.vgcs_setup_ok) {
			COORD.send(COORD_VGCS_CALL_EST);
			repeat;
		}
		RAN_CONN.send(ts_BSSMAP_ClearCommand(0));
		RAN_CONN.receive(tr_BSSMAP_ClearComplete);
		RAN_CONN.send(ts_MSC_CONN_PRIM_DISC_REQ);
		setverdict(pass);
		return;
		}
	[] RAN_CONN.receive(tr_BSSMAP_VGCS_VBS_SetupRefuse) -> value rx_bssap {
		log("VGCS: got setup refuse");
		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
					"Expected ASCI Setup to succeed, but got fail");
		return;
		}
	[] RAN_CONN.receive(tr_BSSMAP_UplinkReq) -> value rx_bssap {
		log("VGCS: received uplink req");
		uplink_req := true;
		if (g_pars.asci_test.delay_msc) {
			log("VGCS: delay uplink req ack");
			f_delay_msc();
		}
		if (g_pars.asci_test.vgcs_uplink_reject) {
			log("VGCS: sending uplink rej cmd");
			RAN_CONN.send(ts_BSSMAP_UplinkRejCmd(9));
		} else {
			log("VGCS: sending uplink req ack");
			RAN_CONN.send(ts_BSSMAP_UplinkReqAck(omit));
		}
		if (g_pars.asci_test.vgcs_talker_req or
		    g_pars.asci_test.vgcs_uplink_reject) {
			T.start;
		}
		repeat;
		}
	[] RAN_CONN.receive(tr_BSSMAP_UplinkReqConf(?, omit, '1234'O)) -> value rx_bssap {
		log("VGCS: received uplink req confirm");
		uplink_req_conf := true;
		COORD.send(COORD_UPLINK_REQUEST_CONFIRM);
		if (g_pars.asci_test.vgcs_talker_est) {
			T.start;
		}
		repeat;
		}
	[] RAN_CONN.receive(tr_BSSMAP_UplinkRelInd(GSM0808_CAUSE_CALL_CONTROL, omit)) -> value rx_bssap {
		log("VGCS: received uplink rel ind");
		uplink_rel_ind := true;
		if (g_pars.asci_test.vgcs_talker_rel) {
			T.start;
		}
		repeat;
		}
	[] RAN_CONN.receive(tr_BSSMAP_UplinkRelInd(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE, omit)) -> value rx_bssap {
		log("VGCS: received uplink rel ind, caused by failure");
		uplink_rel_ind_failure := true;
		if (g_pars.asci_test.vgcs_talker_fail) {
			T.start;
		}
		repeat;
		}
	[] RAN_CONN.receive(tr_BSSMAP_UplinkRelInd(?, omit)) -> value rx_bssap {
		setverdict(fail, "VGCS: received uplink rel ind, caused by failure with invalid cause: ", rx_bssap);
		}
	[] COORD.receive(COORD_VGCS_ASSIGN_RES) {
		log("VGCS: got assignment result at call control");
		assign_res := true;
		if (g_pars.asci_test.vgcs_assign_ok) {
			T.start;
		}
		if (g_pars.asci_test.vgcs_uplink_seized or
		    g_pars.asci_test.vgcs_uplink_release) {
			log("VGCS: sending Uplink Seized Cmd");
			RAN_CONN.send(ts_BSSMAP_UplinkSeizedCmd(9, omit, omit, omit));
			T.start;
		}
		if (g_pars.asci_test.vgcs_uplink_release) {
			log("VGCS: sending Uplink Release Cmd");
			RAN_CONN.send(ts_BSSMAP_UplinkRelCmd(9));
			T.start;
		}
		if (g_pars.asci_test.vgcs_uplink_seized or
		    g_pars.asci_test.vgcs_uplink_release) {
			T.start;
		}
		repeat;
		}
	[] COORD.receive(COORD_VGCS_ASSIGN_FAIL) {
		log("VGCS: got assignment failure at call control");
		assign_fail := true;
		if (g_pars.asci_test.vgcs_assign_fail) {
			T.start;
		}
		repeat;
		}
	[] COORD.receive(COORD_VGCS_UPLINK_FREE) {
		log("VGCS: Got UPLINK FREE at call control");
		uplink_busy := false;
		uplink_free := true;
		repeat;
		}
	[] COORD.receive(COORD_VGCS_UPLINK_BUSY) {
		log("VGCS: Got UPLINK BUSY at call control");
		uplink_busy := true;
		uplink_free := false;
		repeat;
		}
	[] T.timeout { }
	}

	/* After timeout: Release Channel and Call and see if the outcome of the test is as expected. */
	COORD.send(COORD_VGCS_CHANNEL_REL);
	RAN_CONN.send(ts_BSSMAP_ClearCommand(0));
	RAN_CONN.receive(tr_BSSMAP_ClearComplete);
	RAN_CONN.send(ts_MSC_CONN_PRIM_DISC_REQ);

	if (g_pars.asci_test.vgcs_assign_ok) {
		if (not assign_res) {
			setverdict(fail, "VGCS: Assignment did not pass as expected!");
			return;
		}
		if (not uplink_free) {
			setverdict(fail, "VGCS: Uplink not free as expected!");
			return;
		}
		log("VGCS: Channel assigned and uplink marked free as expected!");
		setverdict(pass);
		return;
	}
	if (g_pars.asci_test.vgcs_assign_fail) {
		if (not assign_fail) {
			setverdict(fail, "VGCS: Assignment did not fail as expected!");
			return;
		}
		log("VGCS: Channel assignment failed as expected!");
		setverdict(pass);
		return;
	}
	if (g_pars.asci_test.vgcs_talker_req) {
		if (not uplink_req) {
			setverdict(fail, "VGCS: No uplink request as expected!");
			return;
		}
		/* UPLINK BUSY is automatically sent by BTS, but not by BSC. */
		if (uplink_busy) {
			setverdict(fail, "VGCS: Uplink set to busy, this is not expected!");
			return;
		}
		log("VGCS: Uplink requested and uplink marked busy as expected!");
		setverdict(pass);
		return;
	}
	if (g_pars.asci_test.vgcs_talker_fail) {
		if (not uplink_rel_ind_failure) {
			setverdict(fail, "VGCS: No uplink release indication as expected!");
			return;
		}
		if (not uplink_free) {
			setverdict(fail, "VGCS: Uplink not free as expected!");
			return;
		}
		log("VGCS: Uplink release and uplink marked free as expected!");
		setverdict(pass);
		return;
	}
	if (g_pars.asci_test.vgcs_talker_est) {
		if (not uplink_req_conf) {
			setverdict(fail, "VGCS: No uplink request confirm as expected!");
			return;
		}
		/* UPLINK BUSY is automatically sent by BTS, but not by BSC. */
		if (uplink_busy) {
			setverdict(fail, "VGCS: Uplink set to busy, this is not expected!");
			return;
		}
		log("VGCS: Uplink established and uplink marked busy as expected!");
		setverdict(pass);
		return;
	}
	if (g_pars.asci_test.vgcs_talker_rel) {
		if (not uplink_rel_ind) {
			setverdict(fail, "VGCS: No uplink release indication as expected!");
			return;
		}
		if (not uplink_free) {
			setverdict(fail, "VGCS: Uplink not free as expected!");
			return;
		}
		log("VGCS: Uplink established+released and uplink marked free as expected!");
		setverdict(pass);
		return;
	}
	if (g_pars.asci_test.vgcs_uplink_seized) {
		if (not uplink_busy) {
			setverdict(fail, "VGCS: Uplink not busy as expected!");
			return;
		}
		log("VGCS: Uplink established+released and uplink marked free as expected!");
		setverdict(pass);
		return;
	}
	if (g_pars.asci_test.vgcs_uplink_release) {
		if (not uplink_free) {
			setverdict(fail, "VGCS: Uplink not free as expected!");
			return;
		}
		log("VGCS: Uplink established+released and uplink marked free as expected!");
		setverdict(pass);
		return;
	}
	if (g_pars.asci_test.vgcs_uplink_reject) {
		if (not uplink_free) {
			setverdict(fail, "VGCS: Uplink not free as expected!");
			return;
		}
		log("VGCS: Uplink rejected and uplink marked free as expected!");
		setverdict(pass);
		return;
	}
}

/*
 * VGCS/VBS resource controling connection
 */

private altstep as_eat_rsl_data() runs on MSC_ConnHdlr {
	[] RSL.receive(tr_RSL_UNITDATA_REQ(g_chan_nr, ?, ?)) {
		log("VGCS: Got RSL UNITDATA REQ on channel");
		repeat;
	}
	[] RSL.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, ?)) {
		log("VGCS: Got RSL DATA REQ on channel");
		repeat;
	}
	[] RSL.receive(tr_RSL_SACCH_INF_MOD(g_chan_nr, ?)) {
		log("VGCS: Got RSL SACCH INFO MODIFY on channel");
		repeat;
	}
}

private function f_delay_bts() runs on MSC_ConnHdlr {
	timer T := 2.0;
	T.start;
	alt {
	[] COORD.receive(COORD_UPLINK_REQUEST_CONFIRM) {
		setverdict(fail, "Got UPLINK REQUEST CONFIRM from BSC. This is unexpected.");
		}
	[] T.timeout { }
	}
}

private function f_tc_asci_assignment(charstring id) runs on MSC_ConnHdlr {
	var PDU_BSSAP rx_bssap;
	/* Hack: the proper way would be to wait for the BSSMAP VGCS Assignment Result and extract the
	 * actual assigned chan_nr from it. But osmo-bsc starts acting on the lchan even before we get a
	 * chance to evaluate the BSSMAP Handover Request ACK. So we need to assume that osmo-bsc will
	 * activate TS 1 and already set up this lchan's RSL emulation
	 * before we get started. */
	var RslChannelNr new_chan_nr := valueof(t_RslChanNr0(1, RSL_CHAN_NR_Bm_ACCH));
	f_rslem_register(0, new_chan_nr);
	g_chan_nr := new_chan_nr;
	var uint3_t expect_target_tsc := c_BtsParams[0].tsc;
	var default eat_rsl_data;

	/* Wait for the COORD ports to be connected. */
	f_sleep(1.0);

	f_create_mgcp_expect(ExpectCriteria:{omit,omit,omit});
	f_MscConnHdlr_init(g_pars.media_nr, "127.0.0.2", "127.0.0.3", FR_AMR);
	var default as_media := activate(as_Media());

	log("VGCS: wait for establishment of call controling connection");
	COORD.receive(COORD_VGCS_CALL_EST);
	log("VGCS: got establishment of call controling connection");

	/* TODO: Encryption */
	var template (value) BSSMAP_IE_ChannelType req_ch_type := ts_BSSMAP_IE_ChannelType;
	var template BSSMAP_IE_ChannelType res_ch_type := tr_BSSMAP_IE_ChannelType('0001'B, ChRate_TCHF, Spdi_TCHF_FR);
	var template (value) BSSMAP_IE_CellIdentifier req_cell_id := ts_CellId_CI(0);
	var template BSSMAP_IE_CellIdentifier res_cell_id := tr_CellId_CI(0);
	var template (value) DescriptiveGroupOrBroadcastCallReference_V callref :=
								ts_BSSMAP_IE_GroupCallRef(11, '1'B, '0'B, '000'B, '0000'B);
	if (g_pars.asci_test.vgcs_assign_fail) {
		callref := ts_BSSMAP_IE_GroupCallRef(10, '1'B, '0'B, '000'B, '0000'B);
	}
	var template (value) PDU_BSSAP ass_req := f_gen_asci_ass_req(ch_type := req_ch_type, cell_id := req_cell_id,
									group_call_ref := bit2oct(encvalue(callref)));
	/* VGCS/VBS ASS REQ in SCCP CR (must be the first message) */
	RAN_CONN.send(ts_BSSAP_Conn_Req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc, ass_req));
	RAN_CONN.receive(tr_MSC_CONN_PRIM_CONF_IND);

	alt {
	[] RAN_CONN.receive(tr_BSSMAP_VGCS_VBS_AssignmentRes(res_ch_type, res_cell_id)) -> value rx_bssap {
		log("VGCS: got assignment result on channel");
		COORD.send(COORD_VGCS_ASSIGN_RES);
		if (g_pars.asci_test.vgcs_talker_req or
		    g_pars.asci_test.vgcs_talker_fail or
		    g_pars.asci_test.vgcs_talker_est or
		    g_pars.asci_test.vgcs_talker_rel or
		    g_pars.asci_test.vgcs_uplink_reject) {
			log("VGCS: sending talker det");
			RSL.send(ts_RSL_TALKER_DET(g_chan_nr));
		}
		if (g_pars.asci_test.vgcs_talker_fail) {
			if (g_pars.asci_test.delay_bts) {
				log("VGCS: delay sending RSL failure ind");
				f_delay_bts();
			}
			log("VGCS: sending RSL failure ind");
			RSL.send(ts_RSL_CONN_FAIL_IND(g_chan_nr, RSL_ERR_TALKER_ACC_FAIL));
		}
		if (g_pars.asci_test.vgcs_talker_est or
		    g_pars.asci_test.vgcs_talker_rel) {
			if (g_pars.asci_test.delay_bts) {
				log("VGCS: delay sending RSL estblish ind");
				f_delay_bts();
			}
			log("VGCS: sending RSL etabish ind");
			RSL.send(ts_RSL_EST_IND(g_chan_nr, ts_RslLinkID_DCCH(0), '1234'O));
		}
		if (g_pars.asci_test.vgcs_talker_rel) {
			log("VGCS: sending RSL release ind");
			RSL.send(ts_RSL_REL_IND(g_chan_nr, ts_RslLinkID_DCCH(0)));
		}
		repeat;
		}
	[] RAN_CONN.receive(tr_BSSMAP_VGCS_VBS_AssignmentFail) -> value rx_bssap {
		log("VGCS: got assignment failure on channel");
		COORD.send(COORD_VGCS_ASSIGN_FAIL);
		log("VGCS: got release order after assignment failure");
		COORD.receive(COORD_VGCS_CHANNEL_REL);
		RAN_CONN.send(ts_BSSMAP_ClearCommand(0));
		RAN_CONN.receive(tr_BSSMAP_ClearComplete);
		RAN_CONN.send(ts_MSC_CONN_PRIM_DISC_REQ);
		return;
		}
	[] COORD.receive(COORD_VGCS_CHANNEL_REL) {
		log("VGCS: got release order of resource controling connection");
		eat_rsl_data := activate(as_eat_rsl_data());
		f_perform_clear_no_rr_rel();
		deactivate(eat_rsl_data);
		return;
		}
	[] RSL.receive(tr_RSL_UNITDATA_REQ(g_chan_nr, ?, '082B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B'O)) {
		log("VGCS: Got UPLINK FREE on channel");
		COORD.send(COORD_VGCS_UPLINK_FREE);
		repeat;
		}
	[] RSL.receive(tr_RSL_UNITDATA_REQ(g_chan_nr, ?, '062A'O)) {
		log("VGCS: Got UPLINK BUSY on channel");
		COORD.send(COORD_VGCS_UPLINK_BUSY);
		repeat;
		}
	[] RSL.receive(tr_RSL_SACCH_INF_MOD(g_chan_nr, ?)) {
		log("VGCS: Got RSL SACCH INFO MODIFY on channel");
		repeat;
	}
	[] COORD.receive(COORD_UPLINK_REQUEST_CONFIRM) { repeat; }
	}

	/* The RSL Emulation magically accepts the Chan Activ behind the scenes. */

	/* we're sure that the channel activation is done now, verify the parameters in it */
	var RSL_Message chan_act := f_rslem_get_last_act(RSL_PROC, 0, g_chan_nr);
	/* TODO: Encryption */
	f_chan_act_verify_tsc(chan_act, expect_target_tsc);

	if (ispresent(rx_bssap.pdu.bssmap.vGCS_VBSAssignmentResult.speechCodec)) {
		if (not g_pars.aoip) {
			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
						"Expected no Speech Codec (Chosen)");
		}
	} else {
		if (g_pars.aoip) {
			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
						"Expected Speech Codec (Chosen)");
		}
	}

}

testcase TC_vgcs_vbs_setup_only() runs on test_CT {
	var MSC_ConnHdlr call_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);

	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	pars.asci_test.vgcs_setup_ok := true;

	call_conn := f_start_handler(refers(f_tc_vgcs_vbs_setup), pars);

	call_conn.done;

	f_shutdown_helper();
}

testcase TC_vgcs_vbs_assignment() runs on test_CT {
	var MSC_ConnHdlr call_conn, chan_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);

	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	pars.asci_test.vgcs_assign_ok := true;

	call_conn := f_start_handler(refers(f_tc_vgcs_vbs_setup), pars);
	chan_conn := f_start_handler(refers(f_tc_asci_assignment), pars);
	/* Connect COORD ports of both functions. The functions will delay before using them. */
	connect(call_conn:COORD, chan_conn:COORD);

	call_conn.done;
	chan_conn.done;

	f_shutdown_helper();
}

testcase TC_vgcs_vbs_assignment_fail() runs on test_CT {
	var MSC_ConnHdlr call_conn, chan_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);

	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	pars.asci_test.vgcs_assign_fail := true;

	call_conn := f_start_handler(refers(f_tc_vgcs_vbs_setup), pars);
	chan_conn := f_start_handler(refers(f_tc_asci_assignment), pars);
	/* Connect COORD ports of both functions. The functions will delay before using them. */
	connect(call_conn:COORD, chan_conn:COORD);

	call_conn.done;
	chan_conn.done;

	f_shutdown_helper();
}

testcase TC_vgcs_vbs_talker_req() runs on test_CT {
	var MSC_ConnHdlr call_conn, chan_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);

	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	pars.asci_test.vgcs_talker_req := true;

	call_conn := f_start_handler(refers(f_tc_vgcs_vbs_setup), pars);
	chan_conn := f_start_handler(refers(f_tc_asci_assignment), pars);
	/* Connect COORD ports of both functions. The functions will delay before using them. */
	connect(call_conn:COORD, chan_conn:COORD);

	call_conn.done;
	chan_conn.done;

	f_shutdown_helper();
}

testcase TC_vgcs_vbs_talker_fail() runs on test_CT {
	var MSC_ConnHdlr call_conn, chan_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);

	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	/* MSC sends acknowledge before link on BTS fail. */
	pars.asci_test.vgcs_talker_fail := true;
	pars.asci_test.delay_bts := true;

	call_conn := f_start_handler(refers(f_tc_vgcs_vbs_setup), pars);
	chan_conn := f_start_handler(refers(f_tc_asci_assignment), pars);
	/* Connect COORD ports of both functions. The functions will delay before using them. */
	connect(call_conn:COORD, chan_conn:COORD);

	call_conn.done;
	chan_conn.done;

	f_shutdown_helper();
}

testcase TC_vgcs_vbs_talker_fail_late_msc() runs on test_CT {
	var MSC_ConnHdlr call_conn, chan_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);

	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	/* MSC sends acknowledge after link on BTS fail. */
	pars.asci_test.vgcs_talker_fail := true;
	pars.asci_test.delay_msc := true;

	call_conn := f_start_handler(refers(f_tc_vgcs_vbs_setup), pars);
	chan_conn := f_start_handler(refers(f_tc_asci_assignment), pars);
	/* Connect COORD ports of both functions. The functions will delay before using them. */
	connect(call_conn:COORD, chan_conn:COORD);

	call_conn.done;
	chan_conn.done;

	f_shutdown_helper();
}

testcase TC_vgcs_vbs_talker_est() runs on test_CT {
	var MSC_ConnHdlr call_conn, chan_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);

	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	/* MSC sends acknowledge before BTS establishes the uplink. */
	pars.asci_test.vgcs_talker_est := true;
	pars.asci_test.delay_bts := true;

	call_conn := f_start_handler(refers(f_tc_vgcs_vbs_setup), pars);
	chan_conn := f_start_handler(refers(f_tc_asci_assignment), pars);
	/* Connect COORD ports of both functions. The functions will delay before using them. */
	connect(call_conn:COORD, chan_conn:COORD);

	call_conn.done;
	chan_conn.done;

	f_shutdown_helper();
}

testcase TC_vgcs_vbs_talker_est_late_msc() runs on test_CT {
	var MSC_ConnHdlr call_conn, chan_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);

	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	/* MSC sends acknowledge after BTS established the uplink. */
	pars.asci_test.vgcs_talker_est := true;
	pars.asci_test.delay_msc := true;

	call_conn := f_start_handler(refers(f_tc_vgcs_vbs_setup), pars);
	chan_conn := f_start_handler(refers(f_tc_asci_assignment), pars);
	/* Connect COORD ports of both functions. The functions will delay before using them. */
	connect(call_conn:COORD, chan_conn:COORD);

	call_conn.done;
	chan_conn.done;

	f_shutdown_helper();
}

testcase TC_vgcs_vbs_talker_rel() runs on test_CT {
	var MSC_ConnHdlr call_conn, chan_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);

	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	pars.asci_test.vgcs_talker_rel := true;
	pars.asci_test.delay_bts := true;

	call_conn := f_start_handler(refers(f_tc_vgcs_vbs_setup), pars);
	chan_conn := f_start_handler(refers(f_tc_asci_assignment), pars);
	/* Connect COORD ports of both functions. The functions will delay before using them. */
	connect(call_conn:COORD, chan_conn:COORD);

	call_conn.done;
	chan_conn.done;

	f_shutdown_helper();
}

testcase TC_vgcs_vbs_talker_rel_late_msc() runs on test_CT {
	var MSC_ConnHdlr call_conn, chan_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);

	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	pars.asci_test.vgcs_talker_rel := true;
	pars.asci_test.delay_msc := true;

	call_conn := f_start_handler(refers(f_tc_vgcs_vbs_setup), pars);
	chan_conn := f_start_handler(refers(f_tc_asci_assignment), pars);
	/* Connect COORD ports of both functions. The functions will delay before using them. */
	connect(call_conn:COORD, chan_conn:COORD);

	call_conn.done;
	chan_conn.done;

	f_shutdown_helper();
}

testcase TC_vgcs_vbs_uplink_reject() runs on test_CT {
	var MSC_ConnHdlr call_conn, chan_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);

	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	pars.asci_test.vgcs_uplink_reject := true;

	call_conn := f_start_handler(refers(f_tc_vgcs_vbs_setup), pars);
	chan_conn := f_start_handler(refers(f_tc_asci_assignment), pars);
	/* Connect COORD ports of both functions. The functions will delay before using them. */
	connect(call_conn:COORD, chan_conn:COORD);

	call_conn.done;
	chan_conn.done;

	f_shutdown_helper();
}

testcase TC_vgcs_vbs_uplink_seized() runs on test_CT {
	var MSC_ConnHdlr call_conn, chan_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);

	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	pars.asci_test.vgcs_uplink_seized := true;

	call_conn := f_start_handler(refers(f_tc_vgcs_vbs_setup), pars);
	chan_conn := f_start_handler(refers(f_tc_asci_assignment), pars);
	/* Connect COORD ports of both functions. The functions will delay before using them. */
	connect(call_conn:COORD, chan_conn:COORD);

	call_conn.done;
	chan_conn.done;

	f_shutdown_helper();
}

testcase TC_vgcs_vbs_uplink_release() runs on test_CT {
	var MSC_ConnHdlr call_conn, chan_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);

	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	pars.asci_test.vgcs_uplink_release := true;

	call_conn := f_start_handler(refers(f_tc_vgcs_vbs_setup), pars);
	chan_conn := f_start_handler(refers(f_tc_asci_assignment), pars);
	/* Connect COORD ports of both functions. The functions will delay before using them. */
	connect(call_conn:COORD, chan_conn:COORD);

	call_conn.done;
	chan_conn.done;

	f_shutdown_helper();
}

control {
	execute( TC_vgcs_vbs_setup_only() );
	execute( TC_vgcs_vbs_assignment() );
	execute( TC_vgcs_vbs_assignment_fail() );
	execute( TC_vgcs_vbs_talker_req() );
	execute( TC_vgcs_vbs_talker_fail() );
	execute( TC_vgcs_vbs_talker_fail_late_msc() );
	execute( TC_vgcs_vbs_talker_est() );
	execute( TC_vgcs_vbs_talker_est_late_msc() );
	execute( TC_vgcs_vbs_talker_rel() );
	execute( TC_vgcs_vbs_talker_rel_late_msc() );
	execute( TC_vgcs_vbs_uplink_reject() );
	execute( TC_vgcs_vbs_uplink_seized() );
	execute( TC_vgcs_vbs_uplink_release() );
}


}
