module BSC_Tests {

/* Integration Tests for OsmoBSC
 * (C) 2017-2018 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.
 *
 * There are test cases that run in so-called 'handler mode' and test cases
 * that run directly on top of the BSSAP and RSL CodecPorts.  The "handler mode"
 * tests abstract the multiplexing/demultiplexing of multiple SCCP connections
 * and/or RSL channels and are hence suitable for higher-level test cases, while
 * the "raw" tests directly on top of the CodecPorts are more suitable for lower-
 * level testing.
 */

friend module BSC_Tests_VAMOS;
friend module BSC_Tests_CBSP;
friend module BSC_Tests_LCLS;
friend module BSC_Tests_ASCI;

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

import from BSSAP_Types all;
import from SCCP_Adapter all;
import from RAN_Adapter all;
import from BSSAP_LE_Adapter all;
import from BSSAP_LE_CodecPort all;
import from BSSAP_LE_Types all;
import from BSSLAP_Types all;
import from BSSAP_CodecPort all;
import from BSSMAP_Templates all;
import from IPA_Emulation all;
import from IPA_CodecPort all;
import from IPA_Types all;
import from IPA_Testing all;
import from RSL_Types all;
import from RSL_Emulation all;
import from MGCP_Emulation all;
import from MGCP_Templates all;
import from MGCP_Types all;
import from MGCP_CodecPort 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 MobileL3_CommonIE_Types all;
import from MobileL3_Types all;
import from MobileL3_RRM_Types all;
import from L3_Templates all;
import from GSM_RR_Types all;

import from SCCP_Templates all;
import from BSSMAP_Templates all;
import from BSSMAP_LE_Templates all;

import from SCCPasp_Types all;

import from GSM_SystemInformation all;
import from GSM_RestOctets all;
import from TCCConversion_Functions all;

const integer NUM_TRX := 4;
const integer NUM_BTS := 3;
const integer NUM_BTS_CFG := 4; /* we have 4 BTS in the osmo-bsc.cfg (for inter-BSC HO tests) but use only 3 */
const integer NUM_TRX_CFG := 1; /* we support up to 4 TRX per BTS, but have only 1 TRX per BTS in osmo-bsc.cfg */
const integer NUM_MSC := 3;
const integer NUM_MGW := 2;
const float T3101_MAX := 12.0;

/* make sure to sync this with the osmo-bts.cfg you're using */
const integer NUM_TCHH_PER_BTS := 2;
const integer NUM_TCHF_PER_BTS := 4;
const integer NUM_SDCCH_PER_BTS := 3;

friend type record BtsTrxIdx {
	uint8_t bts,
	uint8_t trx
}

private type record BtsParams {
	integer trx_num,
	integer tsc
}

/* Default Training Sequence Code expected for bts[i]:
 * BTS 0 has BSIC 10 (and no explicit timeslot training_sequence_code config), so expecting TSC = (BSIC & 7) = 2.
 * BTS 1 has BSIC 11, TSC = (BSIC & 7) = 3.
 * BTS 2 has BSIC 12, TSC = (BSIC & 7) = 4.
 * BTS 2 has BSIC 12, TSC = (BSIC & 7) = 4.
 */
friend const BtsParams c_BtsParams[NUM_BTS_CFG] := {
	/* BTS0 */ { trx_num := 1, tsc := 2 },
	/* BTS1 */ { trx_num := 1, tsc := 3 },
	/* BTS2 */ { trx_num := 4, tsc := 4 },
	/* BTS3 */ { trx_num := 1, tsc := 4 }
}

private const RSL_IE_Body c_mr_conf_5_90 :=
	valueof(RSL_IE_Body:{multirate_cfg := ts_RSL_MultirateCfg(true, 0, '00000100'B /* 5,90k */)});

/* per-BTS state which we keep */
type record BTS_State {
	/* component reference to the IPA_Client component used for RSL */
	IPA_Client rsl
}

/* Default list of counters for an 'msc' entity. */
const CounterNameVals counternames_msc_mscpool := {
	{ "mscpool:subscr:new", 0 },
	{ "mscpool:subscr:known", 0 },
	{ "mscpool:subscr:reattach", 0 },
	{ "mscpool:subscr:attach_lost", 0 },
	{ "mscpool:subscr:paged", 0 }
};

/* List of global mscpool counters, not related to a specific 'msc' entity. */
const CounterNameVals counternames_bsc_mscpool := {
	{ "mscpool:subscr:no_msc", 0 }
};

/* Default list of counters for 'bsc' and 'bts' entities. */
const CounterNameVals counternames_bsc_bts_handover := {
	{ "assignment:attempted", 0 },
	{ "assignment:completed", 0 },
	{ "assignment:stopped", 0 },
	{ "assignment:no_channel", 0 },
	{ "assignment:timeout", 0 },
	{ "assignment:failed", 0 },
	{ "assignment:error", 0 },

	{ "handover:attempted", 0 },
	{ "handover:completed", 0 },
	{ "handover:stopped", 0 },
	{ "handover:no_channel", 0 },
	{ "handover:timeout", 0 },
	{ "handover:failed", 0 },
	{ "handover:error", 0 },

	{ "intra_cell_ho:attempted", 0 },
	{ "intra_cell_ho:completed", 0 },
	{ "intra_cell_ho:stopped", 0 },
	{ "intra_cell_ho:no_channel", 0 },
	{ "intra_cell_ho:timeout", 0 },
	{ "intra_cell_ho:failed", 0 },
	{ "intra_cell_ho:error", 0 },

	{ "intra_bsc_ho:attempted", 0 },
	{ "intra_bsc_ho:completed", 0 },
	{ "intra_bsc_ho:stopped", 0 },
	{ "intra_bsc_ho:no_channel", 0 },
	{ "intra_bsc_ho:timeout", 0 },
	{ "intra_bsc_ho:failed", 0 },
	{ "intra_bsc_ho:error", 0 },

	{ "interbsc_ho_out:attempted", 0 },
	{ "interbsc_ho_out:completed", 0 },
	{ "interbsc_ho_out:stopped", 0 },
	{ "interbsc_ho_out:timeout", 0 },
	{ "interbsc_ho_out:failed", 0 },
	{ "interbsc_ho_out:error", 0 },

	{ "interbsc_ho_in:attempted", 0 },
	{ "interbsc_ho_in:completed", 0 },
	{ "interbsc_ho_in:stopped", 0 },
	{ "interbsc_ho_in:no_channel", 0 },
	{ "interbsc_ho_in:timeout", 0 },
	{ "interbsc_ho_in:failed", 0 },
	{ "interbsc_ho_in:error", 0 }
};

const CounterNameVals counternames_bts_handover := {
	{ "incoming_intra_bsc_ho:attempted", 0 },
	{ "incoming_intra_bsc_ho:completed", 0 },
	{ "incoming_intra_bsc_ho:stopped", 0 },
	{ "incoming_intra_bsc_ho:no_channel", 0 },
	{ "incoming_intra_bsc_ho:timeout", 0 },
	{ "incoming_intra_bsc_ho:failed", 0 },
	{ "incoming_intra_bsc_ho:error", 0 }
};

/* Set of all System Information received during one RSL port's startup.
 * Note that some System Information may be sent on RSL, but lacking actual SI data, to indicate that the BTS should not
 * broadcast that SI type. That will be reflected as 'omit' here.
 */
type record SystemInformationConfig {
	SystemInformationType1 si1 optional,
	SystemInformationType2 si2 optional,
	SystemInformationType2bis si2bis optional,
	SystemInformationType2ter si2ter optional,
	SI2quaterRestOctetsList si2quater optional,
	SystemInformationType3 si3 optional,
	SystemInformationType4 si4 optional,
	SystemInformationType13 si13 optional,
	SystemInformationType5 si5 optional,
	SystemInformationType5bis si5bis optional,
	SystemInformationType5ter si5ter optional,
	SystemInformationType6 si6 optional
};

const SystemInformationConfig SystemInformationConfig_omit := {
	si1 := omit,
	si2 := omit,
	si2bis := omit,
	si2ter := omit,
	si2quater := omit,
	si3 := omit,
	si4 := omit,
	si13 := omit,
	si5 := omit,
	si5bis := omit,
	si5ter := omit,
	si6 := omit
};

/* tr_EUTRAN_CellDesc with defaults used in BSC_Tests.ttcn */
template EUTRAN_CellDesc tr_EUTRAN_CellDesc_default(template (present) uint16_t e_arfcn := ?,
						    template uint3_t meas_bw := 3)
:= tr_EUTRAN_CellDesc(e_arfcn := e_arfcn,
		      meas_bw_presence := '1'B,
		      meas_bw := meas_bw);

/* tr_EUTRAN_NeighbourCells with defaults used in BSC_Tests.ttcn */
template EUTRAN_NeighbourCells tr_EUTRAN_NeighbourCells_default(template (present) EUTRAN_CellDescs cell_desc_list := { tr_EUTRAN_CellDesc_default },
								template uint3_t prio := 3,
								template (present) uint5_t thresh_high := 20,
								template uint5_t thresh_low := 10,
								template uint5_t qrxlevmin := 22)
:= tr_EUTRAN_NeighbourCells(
	cell_desc_list := cell_desc_list,
	prio_presence := '1'B,
	prio := prio,
	thresh_high := thresh_high,
	thresh_low_presence := '1'B,
	thresh_low := thresh_low,
	qrxlevmin_presence := '1'B,
	qrxlevmin := qrxlevmin);

template SystemInformationConfig SystemInformationConfig_default := {
	si1 := {
	    cell_chan_desc := '8FB38000000000000000000000000000'O,
	    rach_control := {
		max_retrans := RACH_MAX_RETRANS_7,
		tx_integer := '1001'B,
		cell_barr_access := false,
		re_not_allowed := true,
		acc := '0000010000000000'B
	    },
	    rest_octets := ?
	},
	si2 := {
	    bcch_freq_list := '00000000000000000000000000000000'O,
	    ncc_permitted := '11111111'B,
	    rach_control := {
		max_retrans := RACH_MAX_RETRANS_7,
		tx_integer := '1001'B,
		cell_barr_access := false,
		re_not_allowed := true,
		acc := '0000010000000000'B
	    }
	},
	si2bis := omit,
	si2ter := {
	    extd_bcch_freq_list := '8E320000000000000000000000000800'O,
	    rest_octets := ?
	},
	si2quater := {
		tr_SI2quaterRestOctets_EUTRAN( repeated_neigh_cells := { tr_EUTRAN_NeighbourCells_default } )
	},
	si3 := {
	    cell_id := 0,
	    lai := {
		mcc_mnc := '001F01'H,
		lac := 1
	    },
	    ctrl_chan_desc := {
		msc_r99 := true,
		att := true,
		bs_ag_blks_res := 1,
		ccch_conf := CCHAN_DESC_1CCCH_COMBINED,
		si22ind := false,
		cbq3 := CBQ3_IU_MODE_NOT_SUPPORTED,
		spare := '00'B,
		bs_pa_mfrms := 3,
		t3212 := 30
	    },
	    cell_options := {
		dn_ind := false,
		pwrc := false,
		dtx := MS_SHALL_USE_UL_DTX,
		radio_link_tout_div4 := 7
	    },
	    cell_sel_par := {
		cell_resel_hyst_2dB := 2,
		ms_txpwr_max_cch := 7,
		acs := '0'B,
		neci := true,
		rxlev_access_min := 0
	    },
	    rach_control := {
		max_retrans := RACH_MAX_RETRANS_7,
		tx_integer := '1001'B,
		cell_barr_access := false,
		re_not_allowed := true,
		acc := '0000010000000000'B
	    },
	    rest_octets := {
		sel_params := {
		    presence := '0'B,
		    params := omit
		},
		pwr_offset := {
		    presence := '0'B,
		    offset := omit
		},
		si_2ter_ind := '1'B,
		early_cm_ind := '0'B,
		sched_where := {
		    presence := '0'B,
		    where := omit
		},
		gprs_ind := {
		    presence := '1'B,
		    ind := {
			ra_colour := 0,
			si13_pos := '0'B
		    }
		},
		umts_early_cm_ind := '1'B,
		si2_quater_ind := {
		    presence := '1'B,
		    ind := '0'B
		},
		iu_mode_ind := omit,
		si21_ind := {
		    presence := '0'B,
		    pos := omit
		}
	    }
	},
	si4 := {
	    lai := {
		mcc_mnc := '001F01'H,
		lac := 1
	    },
	    cell_sel_par := {
		cell_resel_hyst_2dB := 2,
		ms_txpwr_max_cch := 7,
		acs := '0'B,
		neci := true,
		rxlev_access_min := 0
	    },
	    rach_control := {
		max_retrans := RACH_MAX_RETRANS_7,
		tx_integer := '1001'B,
		cell_barr_access := false,
		re_not_allowed := true,
		acc := '0000010000000000'B
	    },
	    cbch_chan_desc := {
		iei := '64'O,
		v := {
		    chan_nr := {
			u := {
			sdcch4 := {
			    tag := '001'B,
			    sub_chan := 2
			}
			},
			tn := 0
		    },
		    tsc := 2,
		    h := false,
		    arfcn := 871,
		    maio_hsn := omit
		}
	    },
	    cbch_mobile_alloc := omit,
	    rest_octets := {
		sel_params := {
		    presence := '0'B,
		    params := omit
		},
		pwr_offset := {
		    presence := '0'B,
		    offset := omit
		},
		gprs_ind := {
		    presence := '1'B,
		    ind := {
			ra_colour := 0,
			si13_pos := '0'B
		    }
		},
		s_presence := '0'B,
		s := omit
	    }
	},
	si13 := {
		rest_octets := {
			presence := '1'B,
			bcch_change_mark := ?,
			si_change_field := '0000'B,
			presence2 := '0'B,
			si13_change_mark := omit,
			gprs_ma := omit,
			zero := '0'B, /* PBCCH not present in cell */
			rac := 0,
			spgc_ccch_sup := '0'B,
			priority_access_thr := '110'B,
			network_control_order := '00'B,
			gprs_cell_opts := {
				nmo := '01'B,
				t3168 := '011'B,
				t3192 := '010'B,
				drx_timer_max := '011'B,
				access_burst_type := '0'B,
				control_ack_type := '1'B,
				bs_cv_max := 15,
				pan_presence := '1'B,
				pan_dec  := 1,
				pan_inc  := 1,
				pan_max  := '111'B,
				ext_info_presence := ?,
				ext_info_length := *,
				ext_info := *
			},
			gprs_pwr_ctrl_params := {
				alpha := 0,
				t_avg_w := '10000'B,
				t_avg_t := '10000'B,
				pc_meas_chan := '0'B,
				n_avg_i := '1000'B
			}
		}
	},
	si5 := {
	    bcch_freq_list := '10000000000000000000000000000000'O
	},
	si5bis := omit,
	si5ter := {
	    extd_bcch_freq_list := '9E050020000000000000000000000000'O
	},
	si6 := {
	    cell_id := 0,
	    lai := {
		mcc_mnc := '001F01'H,
		lac := 1
	    },
	    cell_options := {
		dtx_ext := '1'B,
		pwrc := false,
		dtx := '01'B,
		radio_link_timeout := '0111'B
	    },
	    ncc_permitted := '11111111'B,
	    rest_octets := {
		pch_nch_info := ?,
		vbs_vgcs_options := ?,
		dtm_support := '0'B,
		rac := omit,
		max_lapdm := omit,
		band_ind := '0'B /* C0 ARFCN indicates 1800 band */
	    }
	}
    };


/* List of all the System Information received on all RSL ports */
type record of SystemInformationConfig SystemInformationConfig_list;

function f_sysinfo_dec_raw(inout SystemInformationConfig si, RSL_Message rsl)
{
	var RSL_IE_Body sysinfo_type_ie;
	var RSL_IE_SysinfoType si_type;
	var octetstring data;

	if (f_rsl_find_ie(rsl, RSL_IE_SYSINFO_TYPE, sysinfo_type_ie) == false) {
		setverdict(fail, "Cannot find RSL_IE_SYSINFO_TYPE");
		mtc.stop;
	}
	si_type := sysinfo_type_ie.sysinfo_type;

	if (rsl.msg_type == RSL_MT_BCCH_INFO) {
		var RSL_IE_Body bcch_ie;
		if (f_rsl_find_ie(rsl, RSL_IE_FULL_BCCH_INFO, bcch_ie)) {
			data := bcch_ie.other.payload;
		}
	} else if (rsl.msg_type == RSL_MT_SACCH_FILL) {
		var RSL_IE_Body l3_ie;
		if (f_rsl_find_ie(rsl, RSL_IE_L3_INFO, l3_ie)) {
			data := l3_ie.l3_info.payload;
		}
	} else {
		setverdict(fail, "Don't understand this System Information message");
		mtc.stop;
	}

	var boolean handled := false;

	if (rsl.msg_type == RSL_MT_BCCH_INFO) {
		handled := true;

		if (si_type == RSL_SYSTEM_INFO_1) {
			if (not isbound(data)) {
				si.si1 := omit;
			} else {
				si.si1 := dec_SystemInformation(data).payload.si1;
			}
		} else if (si_type == RSL_SYSTEM_INFO_2) {
			if (not isbound(data)) {
				si.si2 := omit;
			} else {
				si.si2 := dec_SystemInformation(data).payload.si2;
			}
		} else if (si_type == RSL_SYSTEM_INFO_2bis) {
			if (not isbound(data)) {
				si.si2bis := omit;
			} else {
				si.si2bis := dec_SystemInformation(data).payload.si2bis;
			}
		} else if (si_type == RSL_SYSTEM_INFO_2ter) {
			if (not isbound(data)) {
				si.si2ter := omit;
			} else {
				si.si2ter := dec_SystemInformation(data).payload.si2ter;
			}
		} else if (si_type == RSL_SYSTEM_INFO_2quater) {
			if (not isbound(data)) {
				si.si2quater := {};
			} else {
				var SystemInformationType2quater decoded := dec_SystemInformation(data).payload.si2quater;
				/* this is a *record* of SI2quaterRestOctets! (multiplexed) */
				si.si2quater[decoded.rest_octets.si2quater_index] := decoded.rest_octets;
			}
		} else if (si_type == RSL_SYSTEM_INFO_3) {
			if (not isbound(data)) {
				si.si3 := omit;
			} else {
				si.si3 := dec_SystemInformation(data).payload.si3;
			}
		} else if (si_type == RSL_SYSTEM_INFO_4) {
			if (not isbound(data)) {
				si.si4 := omit;
			} else {
				si.si4 := dec_SystemInformation(data).payload.si4;
			}
		} else if (si_type == RSL_SYSTEM_INFO_13) {
			if (not isbound(data)) {
				si.si13 := omit;
			} else {
				si.si13 := dec_SystemInformation(data).payload.si13;
			}
		} else {
			handled := false;
		}
	} else if (rsl.msg_type == RSL_MT_SACCH_FILL) {
		handled := true;

		if (si_type == RSL_SYSTEM_INFO_5) {
			if (not isbound(data)) {
				si.si5 := omit;
			} else {
				si.si5 := dec_SystemInformation(data).payload.si5;
			}
		} else if (si_type == RSL_SYSTEM_INFO_5bis) {
			if (not isbound(data)) {
				si.si5bis := omit;
			} else {
				si.si5bis := dec_SystemInformation(data).payload.si5bis;
			}
		} else if (si_type == RSL_SYSTEM_INFO_5ter) {
			if (not isbound(data)) {
				si.si5ter := omit;
			} else {
				si.si5ter := dec_SystemInformation(data).payload.si5ter;
			}
		} else if (si_type == RSL_SYSTEM_INFO_6) {
			if (not isbound(data)) {
				si.si6 := omit;
			} else {
				si.si6 := dec_SystemInformation(data).payload.si6;
			}
		} else {
			handled := false;
		}
	}

	if (not handled) {
		setverdict(fail, "Unexpected SI type in ", rsl.msg_type, " message: ", si_type);
	}
}

friend function gen_l3_valid_payload(hexstring imsi := ''H) return octetstring {
	var octetstring l3_payload;
	if (lengthof(imsi) == 0) {
		imsi := f_rnd_imsi('00101'H);
	}
	l3_payload := enc_PDU_ML3_MS_NW(valueof(ts_LU_REQ(LU_Type_IMSI_Attach, ts_MI_LV(ts_MI_IMSI(imsi)))));
	return l3_payload;
}

type component test_CT extends CTRL_Adapter_CT {
	/* Array of per-BTS/TRX state */
	var BTS_State bts[NUM_BTS][NUM_TRX];
	/* RSL common Channel Port (for RSL_Emulation) */
	port RSL_CCHAN_PT RSL_CCHAN[NUM_BTS];
	/* array of per-BTS/TRX RSL test ports */
	port IPA_RSL_PT IPA_RSL[NUM_BTS][NUM_TRX];
	port IPA_CODEC_PT IPA; /* Required for compilation of TC_rsl_unknown_unit_id() */
	/* CTRL muxed over IPA in SCCPlite conn BSC<->MSC (or BSC-NAT) */
	port IPA_CTRL_PT SCCPLITE_IPA_CTRL;
	/* Configure/manage IPA_Emulation per-BTS/TRX port: */
	port IPA_CFG_PT IPA_CFG_PORT[NUM_BTS][NUM_TRX];

	var MGCP_Emulation_CT vc_MGCP[NUM_MGW];
	var integer g_nr_mgw; /* number of vc_MGCP to initialize */
	port TELNETasp_PT BSCVTY;
	port TELNETasp_PT STPVTY;

	/* StatsD */
	var StatsD_Checker_CT vc_STATSD;

	var RAN_Adapter g_bssap[NUM_MSC];
	/* Configure/manage IPA_Emulation RAN_Adapter: */
	port IPA_CFG_PT SCCPLITE_IPA_CFG_PORT[NUM_MSC];
	var BSSAP_LE_Adapter g_bssap_le;
	/* for old legacy-tests only */
	port BSSAP_CODEC_PT BSSAP;
	port BSSAP_LE_CODEC_PT BSSAP_LE;

	/* are we initialized yet */
	var boolean g_initialized := false;

	var boolean g_handler_mode := false;

	/* Osmux is enabled through VTY */
	var boolean g_osmux_enabled_cn := false;
	var boolean g_osmux_enabled_bts := false;

	/*Configure T(tias) over VTY, seconds */
	var integer g_bsc_sccp_timer_ias :=  7 * 60;
	/*Configure T(tiar) over VTY, seconds */
	var integer g_bsc_sccp_timer_iar := 15 * 60;

	/* global test case guard timer (actual timeout value is set in f_init()) */
	timer T_guard := 30.0;

	var CounterNameValsList g_ctr_msc;
	var CounterNameValsList g_ctr_bsc;
	var CounterNameValsList g_ctr_bts;

	/* System Information bytes as received during RSL startup, for each RSL[idx]. */
	var SystemInformationConfig_list g_system_information := {};
}

type record of charstring phys_chan_configs;
modulepar {
	/* IP address at which the BSC can be reached */
	charstring mp_bsc_ip := "127.0.0.1";
	/* port number to which to establish the IPA OML connections */
	integer mp_bsc_oml_port := 3002;
	/* port number to which to establish the IPA RSL connections */
	integer mp_bsc_rsl_port := 3003;
	/* port number to which to establish the IPA CTRL connection */
	integer mp_bsc_ctrl_port := 4249;
	/* port number to which to listen for STATSD metrics */
	integer mp_bsc_statsd_port := 8125;
	/* IP address at which the test binds */
	charstring mp_test_ip := "127.0.0.1";

	RAN_Configurations mp_bssap_cfg := { /* Set in BSC_Tests.default */ };

	/* Must match per BTS config in osmo-bsc.cfg */
	phys_chan_configs phys_chan_config := {
		"CCCH+SDCCH4+CBCH",
		"TCH/F",
		"TCH/F",
		"TCH/F",
		"TCH/F",
		"TCH/H",
		"PDCH",
		"PDCH"
	};

	BSSAP_LE_Configuration mp_bssap_le_cfg := {
		sccp_service_type := "mtp3_itu",
		sctp_addr := { 23908, "127.0.0.1", 2905, "127.0.0.1" },
		own_pc := 190,	/* 0.23.6 SMLC emulation */
		own_ssn := 252,	/* SMLC side SSN */
		peer_pc := 187, /* 0.23.3 osmo-bsc */
		peer_ssn := 250, /* BSC side SSN */
		sio := '03'O, /* NI=International, SI=SCCP */
		rctx := 6
	};
	boolean mp_enable_lcs_tests := true;

	/* Value set in osmo-bsc.cfg "ms max power" */
	uint8_t mp_exp_ms_power_level := 7;

	/* Whether to check for memory leaks */
	boolean mp_verify_talloc_count := true;
}

friend function f_gen_test_hdlr_pars(integer bssap_idx := 0) return TestHdlrParams {

	var TestHdlrParams pars := valueof(t_def_TestHdlrPars);
	if (mp_bssap_cfg[bssap_idx].transport == BSSAP_TRANSPORT_AoIP) {
		pars.aoip := true;
	} else {
		pars.aoip := false;
	}
	pars.exp_ms_power_level := mp_exp_ms_power_level;
	pars.mscpool.bssap_idx := bssap_idx;
	pars.expect_tsc := c_BtsParams[0].tsc;
	pars.imsi := f_rnd_imsi('00101'H);
	pars.imei := f_rnd_imei('00101'H);

	log(testcasename(), ": using IMSI ", pars.imsi);

	return pars;
}

/* Convenience functions for rate counters using g_ctr_msc. */

private function f_ctrs_msc_init(integer mscs_count := NUM_MSC, CounterNameVals counternames := counternames_msc_mscpool) runs on test_CT {
	g_ctr_msc := f_counter_name_vals_get_n(IPA_CTRL, "msc", mscs_count, counternames);
	log("initial msc rate counters: ", g_ctr_msc);
}

private function f_ctrs_msc_add(integer msc_nr, charstring countername, integer val := 1) runs on test_CT {
	f_counter_name_vals_list_add(g_ctr_msc, msc_nr, countername, val);
}

/*  f_ctrs_msc_init();
 *  f_do_thing(on_msc := 0);
 *  f_do_thing(on_msc := 0);
 *  f_do_other(on_msc := 1);
 *  f_ctrs_msc_add(0, "thing", 2);
 *  f_ctrs_msc_add(1, "other");
 *  f_ctrs_msc_verify();
 */
private function f_ctrs_msc_verify() runs on test_CT {
	log("verifying msc rate counters: ", g_ctr_msc);
	f_counter_name_vals_expect_n(IPA_CTRL, "msc", g_ctr_msc);
}

/* convenience: f_ctrs_msc_add() and f_ctrs_msc_verify() in one call.
 *  f_ctrs_msc_init();
 *  f_do_thing(on_msc := 0);
 *  f_do_thing(on_msc := 0);
 *  f_do_thing(on_msc := 0);
 *  f_ctrs_msc_expect(0, "thing", 3);
 */
private function f_ctrs_msc_expect(integer msc_nr, charstring countername, integer val := 1) runs on test_CT {
	f_ctrs_msc_add(msc_nr, countername, val);
	f_ctrs_msc_verify();
}

/* Convenience functions for rate counters using g_ctr_bts, always also including g_ctr_bsc. */

private function f_ctrs_bts_init(integer bts_count := NUM_BTS, CounterNameVals counternames := counternames_bsc_bts_handover) runs on test_CT {
	g_ctr_bts := f_counter_name_vals_get_n(IPA_CTRL, "bts", bts_count, counternames);
	log("initial bts rate counters: ", g_ctr_bts);
}

function f_ctrs_bsc_and_bts_init(integer bts_count := NUM_BTS, CounterNameVals counternames := counternames_bsc_bts_handover) runs on test_CT {
	f_ctrs_bts_init(bts_count, counternames);
	f_ctrs_bsc_init(counternames);
}

private function f_ctrs_bsc_and_bts_handover_init(integer bts_count := NUM_BTS) runs on test_CT {
	var CounterNameVals bts_names := counternames_bsc_bts_handover & counternames_bts_handover;
	f_ctrs_bts_init(bts_count, bts_names);
	f_ctrs_bsc_init(counternames_bsc_bts_handover);
}

private function f_ctrs_bts_add(integer bts_nr, charstring countername, integer val := 1) runs on test_CT {
	f_counter_name_vals_list_add(g_ctr_bts, bts_nr, countername, val);
}

private function f_ctrs_bsc_and_bts_add(integer bts_nr, charstring countername, integer val := 1) runs on test_CT {
	f_ctrs_bts_add(bts_nr, countername, val);
	f_ctrs_bsc_add(countername, val);
}

function f_ctrs_bts_verify() runs on test_CT {
	f_counter_name_vals_expect_n(IPA_CTRL, "bts", g_ctr_bts);
}

/*  f_ctrs_bsc_and_bts_init();
 *  f_do_thing(on_bts := 0);
 *  f_do_thing(on_bts := 0);
 *  f_do_other(on_bts := 1);
 *  f_ctrs_bsc_and_bts_add(0, "thing", 2);
 *  f_ctrs_bsc_and_bts_add(1, "other");
 *  f_ctrs_bsc_and_bts_verify();
 */
private function f_ctrs_bsc_and_bts_verify() runs on test_CT {
	f_ctrs_bts_verify();
	f_ctrs_bsc_verify();
}

/* convenience: f_ctrs_bsc_and_bts_add() and f_ctrs_bsc_and_bts_verify() in one call.
 *  f_ctrs_bsc_and_bts_init();
 *  f_do_thing(on_bts := 0);
 *  f_do_thing(on_bts := 0);
 *  f_do_thing(on_bts := 0);
 *  f_ctrs_bsc_and_bts_expect(0, "thing", 3);
 */
private function f_ctrs_bsc_and_bts_expect(integer bts_nr, charstring countername, integer val := 1) runs on test_CT {
	f_ctrs_bsc_and_bts_add(bts_nr, countername, val);
	f_ctrs_bsc_and_bts_verify();
}


/* Convenience functions for rate counters using g_ctr_bsc. */

private function f_ctrs_bsc_init(CounterNameVals counternames := counternames_bsc_bts_handover) runs on test_CT {
	g_ctr_bsc := f_counter_name_vals_get_n(IPA_CTRL, "bsc", 1, counternames);
	log("initial bsc rate counters: ", g_ctr_bsc);
}

private function f_ctrs_bsc_add(charstring countername, integer val := 1) runs on test_CT {
	f_counter_name_vals_list_add(g_ctr_bsc, 0, countername, val);
}

/*  f_ctrs_bsc_init();
 *  f_do_thing();
 *  f_do_thing();
 *  f_do_other();
 *  f_ctrs_bsc_add("thing", 2);
 *  f_ctrs_bsc_add("other");
 *  f_ctrs_bsc_verify();
 */
private function f_ctrs_bsc_verify() runs on test_CT {
	f_counter_name_vals_expect_n(IPA_CTRL, "bsc", g_ctr_bsc);
}

/* convenience: f_ctrs_bsc_add() and f_ctrs_bsc_verify() in one call.
 *  f_ctrs_bsc_init();
 *  f_do_thing();
 *  f_ctrs_bsc_expect("thing", 1);
 */
private function f_ctrs_bsc_expect(charstring countername, integer val := 1) runs on test_CT {
	f_ctrs_bsc_add(countername, val);
	f_ctrs_bsc_verify();
}


friend function f_shutdown_helper(boolean ho := false) runs on test_CT {
	/* Run the subscr and conn leak test only when the VTY is initialized */
	if (BSCVTY.checkstate("Mapped") and mp_verify_talloc_count) {
		f_verify_talloc_count(BSCVTY, {"struct bsc_subscr", "struct gsm_subscriber_connection"});
	}

	/* Reset handover related configuration */
	if (ho) {
		f_bts_0_cfg(BSCVTY,
			    {"no neighbors",
			     "neighbor-list mode manual-si5",
			     "neighbor-list add arfcn 100",
			     "neighbor-list add arfcn 200",
			     "si5 neighbor-list add arfcn 10",
			     "si5 neighbor-list add arfcn 20",
			     "handover algorithm 1"});
	}

	all component.stop;
	setverdict(pass);
	mtc.stop;
}

private function f_legacy_bssap_reset(integer bssap_idx := 0) runs on test_CT {
	var BSSAP_N_UNITDATA_ind ud_ind;
	var boolean reset_received := false;
	timer T := 5.0;
	BSSAP.send(ts_BSSAP_UNITDATA_req(g_bssap[bssap_idx].sccpa.sccp_addr_peer, g_bssap[bssap_idx].sccpa.sccp_addr_own,
					 ts_BSSMAP_Reset(0, g_osmux_enabled_cn)));
	T.start;
	alt {
	[] BSSAP.receive(tr_BSSAP_UNITDATA_ind(g_bssap[bssap_idx].sccpa.sccp_addr_own, g_bssap[bssap_idx].sccpa.sccp_addr_peer,
					       tr_BSSMAP_ResetAck(g_osmux_enabled_cn))) {
		log("BSSMAP: Received RESET-ACK in response to RESET, we're ready to go!");
		}
	[] BSSAP.receive(tr_BSSAP_UNITDATA_ind(?, ?, tr_BSSMAP_Reset(g_osmux_enabled_cn))) -> value ud_ind {
		log("BSSMAP: Respoding to inbound RESET with RESET-ACK");
		BSSAP.send(ts_BSSAP_UNITDATA_req(ud_ind.callingAddress, ud_ind.calledAddress,
			   ts_BSSMAP_ResetAck(g_osmux_enabled_cn)));
		reset_received := true;
		repeat;
		}
	[] BSSAP.receive { repeat; }
	[] T.timeout {
			/* If we received a RESET after ours was sent, it
			   may be a race condition where the other peer beacame
			   available after we sent it, but we are in a desired
			   state anyway, so go forward. */
			if (not reset_received) {
				Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
							"BSSMAP: Timeout waiting for RESET-ACK after sending RESET");
			} else {
				log("BSSMAP: Timeout waiting for RESET-ACK after sending RESET");
			}
		}
	}
}

type record IPA_Client {
	/* IPA Emulation component reference */
	IPA_Emulation_CT vc_IPA,
	/* Unit-ID and other CCM parameters to use for IPA client emulation */
	IPA_CCM_Parameters ccm_pars,
	/* String identifier for this IPA Client */
	charstring id,
	/* Associated RSL Emulation Component (if any). Only used in "Handler mode" */
	RSL_Emulation_CT vc_RSL optional
}

/*! Start the IPA/RSL related bits for one IPA_Client.
 *  \param clnt IPA_Client for which to establish
 *  \param bsc_host IP address / hostname of the BSC
 *  \param bsc_port TCP port number of the BSC
 *  \param idx BTS/TRX index values
 *  \param handler_mode Start an RSL_Emulation_CT component (true) or not (false) */
function f_ipa_rsl_start(inout IPA_Client clnt, charstring bsc_host, PortNumber bsc_port,
			 BtsTrxIdx idx := {0, 0}, boolean handler_mode := false)
runs on test_CT {
	timer T := 10.0;

	clnt.id := "IPA-BTS" & int2str(idx.bts) & "-TRX" & int2str(idx.trx) & "-RSL";
	clnt.vc_IPA := IPA_Emulation_CT.create(clnt.id & "-IPA") alive;
	clnt.ccm_pars := c_IPA_default_ccm_pars;
	clnt.ccm_pars.name := "Osmocom TTCN-3 BTS Simulator";
	clnt.ccm_pars.unit_id := enc_IpaUnitId({1234 + idx.bts, 0, idx.trx});
	if (handler_mode) {
		clnt.vc_RSL := RSL_Emulation_CT.create(clnt.id & "-RSL") alive;
		connect(clnt.vc_RSL:CCHAN_PT, self:RSL_CCHAN[idx.bts]);
	}

	map(clnt.vc_IPA:IPA_PORT, system:IPA_CODEC_PT);
	connect(clnt.vc_IPA:CFG_PORT, self:IPA_CFG_PORT[idx.bts][idx.trx]);
	if (handler_mode) {
		connect(clnt.vc_IPA:IPA_RSL_PORT, clnt.vc_RSL:IPA_PT);
	} else {
		connect(clnt.vc_IPA:IPA_RSL_PORT, self:IPA_RSL[idx.bts][idx.trx]);
	}

	var integer local_port := 10000 + idx.bts * 1000 + idx.trx;
	clnt.vc_IPA.start(IPA_Emulation.main_client(bsc_host, bsc_port, "", local_port, clnt.ccm_pars));
	if (handler_mode) {
		clnt.vc_RSL.start(RSL_Emulation.main());
		return;
	}

	/* wait for IPA RSL link to connect and send ID ACK */
	T.start;
	alt {
	[] IPA_RSL[idx.bts][idx.trx].receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_ID_ACK)) {
		T.stop;
		}
	[] IPA_RSL[idx.bts][idx.trx].receive(ASP_IPA_Event:?) { repeat }
	[] IPA_RSL[idx.bts][idx.trx].receive { repeat }
	[] T.timeout {
		setverdict(fail, "Timeout RSL waiting for ASP_IPA_EVENT_ID_ACK");
		mtc.stop;
		}
	}
}

function f_ipa_rsl_stop(inout IPA_Client clnt, BtsTrxIdx idx := {0, 0}) runs on test_CT {
	var IPL4asp_Types.Result res := {
		errorCode := omit,
		connId := omit,
		os_error_code := omit,
		os_error_text := omit
	};

	if (not isbound(clnt) or not isbound(clnt.vc_IPA)) {
		return;
	}

	/* Alive components don't finish sockets (TCP FIN) when they are
	 * stopped. Hence, we need to manually call close() on them to make sure
	 * the IUT knows about it. */
	f_ipa_cfg_disconnect(IPA_CFG_PORT[idx.bts][idx.trx], res);

	clnt.vc_IPA.stop;
	if (isbound(clnt.vc_RSL)) {
		clnt.vc_RSL.stop;
	}
}

/* Wait for the OML connection to be brought up by the external osmo-bts-omldummy */
function f_wait_oml(integer bts_nr, charstring status, float secs_max) runs on test_CT {
	timer T := secs_max;
	T.start;
	while (true) {
		if (f_ctrl_get_bts(IPA_CTRL, bts_nr, "oml-connection-state") == status) {
			T.stop;
			/* the 'degraded' state exists from OML connection time, and we have to wait
			 * until all MO's are initialized */
			T.start(1.0);
			T.timeout;
			return;
		}
		f_sleep(0.1);
		if (not T.running) {
			setverdict(fail, "Timeout waiting for BTS" & int2str(bts_nr) & " oml-connection-state ", status);
			mtc.stop;
		}
	}
}

/* global altstep for global guard timer; also takes care of responding RESET witH RESET-ACK */
altstep as_Tguard() runs on test_CT {
	var BSSAP_N_UNITDATA_ind ud_ind;
	[] T_guard.timeout {
			setverdict(fail, "Timeout of T_guard");
			mtc.stop;
		}
	/* always respond with RESET ACK to RESET */
	[] BSSAP.receive(tr_BSSAP_UNITDATA_ind(?, ?, tr_BSSMAP_Reset(g_osmux_enabled_cn))) -> value ud_ind {
		BSSAP.send(ts_BSSAP_UNITDATA_req(ud_ind.callingAddress, ud_ind.calledAddress,
			   ts_BSSMAP_ResetAck(g_osmux_enabled_cn)));
		repeat;
		}
}

altstep no_bssmap_reset() runs on test_CT {
	[] BSSAP.receive(tr_BSSAP_UNITDATA_ind(?, ?, tr_BSSMAP_Reset(g_osmux_enabled_cn))) {
		setverdict(fail, "unexpected BSSMAP Reset");
		mtc.stop;
	}
}

function f_init_mgcp(integer mgw_nr, charstring id) runs on test_CT {
	id := id & "-MGCP-" & int2str(mgw_nr);

	var MGCPOps ops := {
		create_cb := refers(MGCP_Emulation.ExpectedCreateCallback),
		unitdata_cb := refers(MGCP_Emulation.DummyUnitdataCallback)
	};
	var MGCP_conn_parameters mgcp_pars := {
		callagent_ip := mp_bsc_ip,
		callagent_udp_port := -1,
		mgw_ip := mp_test_ip,
		mgw_udp_port := 2427 + mgw_nr,
		/* Enable it for SCCPlite, since we have 2 MGCP sockets towards MGW (UDP one +
		   the one with MGCP over IPA forwarded from MSC one) */
		multi_conn_mode := (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_SCCPlite)
	};

	vc_MGCP[mgw_nr] := MGCP_Emulation_CT.create(id) alive;
	vc_MGCP[mgw_nr].start(MGCP_Emulation.main(ops, mgcp_pars, id));
}

/* Enable or disable (current default) Osmux. When enabling, BSSMAP Reset
 * contains extra IE (OsmuxSupport) and osmo-bsc will handle AssignReq with
 * OsmuxCID IE.
 */
private function f_vty_allow_osmux_cn(boolean allow) runs on test_CT {
	f_vty_enter_cfg_msc(BSCVTY, 0);
	if (allow) {
		f_vty_transceive(BSCVTY, "osmux on");
	} else {
		f_vty_transceive(BSCVTY, "osmux off");
	}
	f_vty_transceive(BSCVTY, "exit");
	f_vty_transceive(BSCVTY, "exit");
}

function f_init_vty(charstring id := "foo") runs on test_CT {
	if (BSCVTY.checkstate("Mapped")) {
		/* skip initialization if already executed once */
		return;
	}
	map(self:BSCVTY, system:BSCVTY);
	f_vty_set_prompts(BSCVTY);
	f_vty_transceive(BSCVTY, "enable");
	f_cs7_inst_0_cfg(BSCVTY, {"sccp-timer ias " & int2str(g_bsc_sccp_timer_ias),
			  "sccp-timer iar " & int2str(g_bsc_sccp_timer_iar)});
}

private function f_init_vty_stp() runs on test_CT {
	if (STPVTY.checkstate("Mapped")) {
		/* skip initialization if already executed once */
		return;
	}
	map(self:STPVTY, system:STPVTY);
	f_vty_set_prompts(STPVTY, prompt_prefix := "OsmoSTP");
	f_vty_transceive(STPVTY, "enable");
}

private function f_vty_stp_bsc_asp_cmd(charstring cmd) runs on test_CT
{
	var charstring asp_name;
	var charstring loc_port;
	var charstring rem_port;
	var charstring asp_proto;
	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_AoIP) {
		asp_name := "bsc0-0";
		loc_port := int2str(1905);
		rem_port := int2str(2905);
		asp_proto := "m3ua";
	} else {
		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "osmo-stp not used in SCCPLite!");
	}
	var charstring asp_node := "asp " & asp_name & " " & loc_port & " " & rem_port & " " & asp_proto;
	f_vty_config2(STPVTY, {"cs7 instance 0", asp_node}, cmd);
}

private function f_sysinfo_seen(integer rsl_idx, RSL_Message rsl) runs on test_CT
{
	if (rsl_idx >= lengthof(g_system_information)) {
		g_system_information[rsl_idx] := SystemInformationConfig_omit
	}
	f_sysinfo_dec_raw(g_system_information[rsl_idx], rsl);
}

altstep as_catch_RSL_sysinfo(integer rsl_idx) runs on test_CT {
	var ASP_RSL_Unitdata rx_rsl_ud;

	/* For handler_mode := false, receiving the RSL bootstrap messages directly on IPA_RSL */
	[] IPA_RSL[rsl_idx][0].receive(tr_ASP_RSL_UD(tr_RSL_NO_BCCH_INFO)) -> value rx_rsl_ud {
		f_sysinfo_seen(rsl_idx, rx_rsl_ud.rsl);
		repeat;
		}
	[] IPA_RSL[rsl_idx][0].receive(tr_ASP_RSL_UD(tr_RSL_BCCH_INFO)) -> value rx_rsl_ud {
		f_sysinfo_seen(rsl_idx, rx_rsl_ud.rsl);
		repeat;
		}
	[] IPA_RSL[rsl_idx][0].receive(tr_ASP_RSL_UD(tr_RSL_NO_SACCH_FILL)) -> value rx_rsl_ud {
		f_sysinfo_seen(rsl_idx, rx_rsl_ud.rsl);
		repeat;
		}
	[] IPA_RSL[rsl_idx][0].receive(tr_ASP_RSL_UD(tr_RSL_SACCH_FILL)) -> value rx_rsl_ud {
		f_sysinfo_seen(rsl_idx, rx_rsl_ud.rsl);
		repeat;
		}

	/* For handler_mode := true, receiving the RSL bootstrap messages via RSL_Emulation  */
	[] RSL_CCHAN[rsl_idx].receive(tr_ASP_RSL_UD(tr_RSL_NO_BCCH_INFO)) -> value rx_rsl_ud {
		f_sysinfo_seen(rsl_idx, rx_rsl_ud.rsl);
		repeat;
		}
	[] RSL_CCHAN[rsl_idx].receive(tr_ASP_RSL_UD(tr_RSL_BCCH_INFO)) -> value rx_rsl_ud {
		f_sysinfo_seen(rsl_idx, rx_rsl_ud.rsl);
		repeat;
		}
	[] RSL_CCHAN[rsl_idx].receive(tr_ASP_RSL_UD(tr_RSL_NO_SACCH_FILL)) -> value rx_rsl_ud {
		f_sysinfo_seen(rsl_idx, rx_rsl_ud.rsl);
		repeat;
		}
	[] RSL_CCHAN[rsl_idx].receive(tr_ASP_RSL_UD(tr_RSL_SACCH_FILL)) -> value rx_rsl_ud {
		f_sysinfo_seen(rsl_idx, rx_rsl_ud.rsl);
		repeat;
		}
}

/* TODO: use BooleanList from COMMON/src/General_Types.ttcn */
private type record of boolean my_BooleanList;

private function f_vty_msc_allow_attach(TELNETasp_PT pt, my_BooleanList allow_attach_list)
{
	var charstring config := f_vty_transceive_ret(pt, "show running-config");

	for (var integer msc_nr := 0; msc_nr < sizeof(allow_attach_list); msc_nr := msc_nr+1) {
		if (f_strstr(config, "\nmsc " & int2str(msc_nr) & "\n") < 0) {
			/* There is no 'msc N' for this msc_nr in the running config, so don't create an empty msc by
			 * stepping into that config node. */
			log("msc ", msc_nr, " is not configured, skipping");
			continue;
		}
		f_vty_enter_cfg_msc(pt, msc_nr);
		if (allow_attach_list[msc_nr]) {
			/* strict := false: ignore if osmo-bsc does not support this config option (latest build) */
			f_vty_transceive(pt, "allow-attach", strict := false);
		} else {
			f_vty_transceive(pt, "no allow-attach", strict := false);
		}
		f_vty_transceive(pt, "exit");
		f_vty_transceive(pt, "exit");
	}
}

private function f_bssap_idx_init(integer bssap_idx) runs on test_CT {
	/* Call a function of our 'parent component' RAN_Adapter_CT to start the
	 * MSC-side BSSAP emulation */
	if (g_handler_mode) {
		var RanOps ranops := MSC_RanOps;
		ranops.use_osmux := g_osmux_enabled_cn;
		f_ran_adapter_init(g_bssap[bssap_idx], mp_bssap_cfg[bssap_idx], "VirtMSC", ranops);
		if (isbound(g_bssap[bssap_idx].sccpa.vc_IPA)) {
			connect(self:SCCPLITE_IPA_CFG_PORT[bssap_idx], g_bssap[bssap_idx].sccpa.vc_IPA:CFG_PORT);
		}
		connect(self:SCCPLITE_IPA_CTRL, g_bssap[bssap_idx].vc_RAN:CTRL_CLIENT);
		f_ran_adapter_start(g_bssap[bssap_idx]);
	} else {
		f_sccp_adapter_init(g_bssap[bssap_idx].sccpa, mp_bssap_cfg[bssap_idx].sccp, "VirtMSC");
		if (isbound(g_bssap[bssap_idx].sccpa.vc_IPA)) {
			connect(self:SCCPLITE_IPA_CFG_PORT[bssap_idx], g_bssap[bssap_idx].sccpa.vc_IPA:CFG_PORT);
		}
		connect(self:BSSAP, g_bssap[bssap_idx].sccpa.vc_SCCP:SCCP_SP_PORT);
		f_sccp_adapter_start(g_bssap[bssap_idx].sccpa);
		f_legacy_bssap_reset();
	}
}

private function f_bssap_idx_disconnect(integer bssap_idx) runs on test_CT {
	var IPL4asp_Types.Result res := {
		errorCode := omit,
		connId := omit,
		os_error_code := omit,
		os_error_text := omit
	};

	if (isbound(g_bssap[bssap_idx].sccpa.vc_IPA)) {
		/* Alive components don't finish sockets (TCP FIN) when they are
		* stopped. Hence, we need to manually call close() on them to make sure
		* the IUT knows about it. */
		f_ipa_cfg_disconnect(SCCPLITE_IPA_CFG_PORT[bssap_idx], res);
	} else {
		if (g_handler_mode) {
			f_ran_adapter_cleanup(g_bssap[bssap_idx]);
		} else {
			f_sccp_adapter_cleanup(g_bssap[bssap_idx].sccpa);
		}
	}
}

/* global initialization function
 * \param nr_bts Number of BTSs we should start/bring up
 * \param handler_mode Start an RSL_Emulation_CT component (true) or not (false).
 * \param nr_msc Number of virtual MSCs to bring up to connect to osmo-bsc.
 */
function f_init(integer nr_bts := NUM_BTS, boolean handler_mode := false,
		integer nr_msc := 1, integer nr_mgw := 1, float guard_timeout := 30.0) runs on test_CT {
	var integer bssap_idx;

	if (g_initialized) {
		return;
	}
	g_initialized := true;

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

	f_init_vty("VirtMSC");
	f_vty_allow_osmux_cn(g_osmux_enabled_cn);

	var my_BooleanList allow_attach := { false, false, false };
	f_init_statsd("VirtMSC", vc_STATSD, mp_test_ip, mp_bsc_statsd_port);

	/* Make sure each MSC's internal state is "DISCONNECTED" at first */
	for (bssap_idx := 0; bssap_idx < NUM_MSC; bssap_idx := bssap_idx+1) {
		f_vty_transceive(BSCVTY, "msc " & int2str(bssap_idx) & " bssmap reset", strict := false);
	}

	g_handler_mode := handler_mode;
	for (bssap_idx := 0; bssap_idx < nr_msc; bssap_idx := bssap_idx+1) {
		allow_attach[bssap_idx] := true;
		f_bssap_idx_init(bssap_idx);
	}

	if (mp_enable_lcs_tests) {
		if (handler_mode) {
			f_bssap_le_adapter_init(g_bssap_le, mp_bssap_le_cfg, "VirtSMLC", SMLC_BssapLeOps);
		} else {
			f_bssap_le_adapter_init(g_bssap_le, mp_bssap_le_cfg, "VirtSMLC", omit);
			connect(self:BSSAP_LE, g_bssap_le.vc_SCCP:SCCP_SP_PORT);
		}
		f_bssap_le_adapter_start(g_bssap_le);
	}

	/* start the test with exactly all enabled MSCs allowed to attach */
	f_vty_msc_allow_attach(BSCVTY, allow_attach);

	f_ipa_ctrl_start_client(mp_bsc_ip, mp_bsc_ctrl_port);

	g_nr_mgw := nr_mgw;
	for (var integer i := 0; i < g_nr_mgw; i := i+1) {
		f_init_mgcp(i, "VirtMGW");
	}

	for (var integer i := 0; i < nr_bts; i := i+1) {
		f_init_bts(i, c_BtsParams[i].trx_num, handler_mode);
	}

	/* Emit a marker to appear in the SUT's own logging output */
	f_logp(BSCVTY, testcasename() & "() start");
}

function f_init_bts(integer bts_idx := 0,
		    integer trx_num := NUM_TRX_CFG,
		    boolean handler_mode := false)
runs on test_CT {
	/* wait until osmo-bts-omldummy has respawned */
	f_wait_oml(bts_idx, "degraded", 5.0);

	/* start RSL connection(s) */
	for (var integer trx_idx := 0; trx_idx < trx_num; trx_idx := trx_idx + 1) {
		f_ipa_rsl_start(bts[bts_idx][trx_idx].rsl,
				mp_bsc_ip, mp_bsc_rsl_port,
				{bts_idx, trx_idx}, handler_mode);
	}
	/* wait until BSC tells us "connected" */
	f_wait_oml(bts_idx, "connected", 5.0);

	/* Set up BTS with VTY commands: */
	f_vty_enter_cfg_bts(BSCVTY, bts_idx);
	if (g_osmux_enabled_bts) {
		f_vty_transceive(BSCVTY, "osmux on");
	} else {
		f_vty_transceive(BSCVTY, "osmux off");
	}
	f_vty_transceive(BSCVTY, "end");
}

function f_init_bts_and_check_sysinfo(integer bts_idx := 0,
				      integer trx_num := NUM_TRX_CFG,
				      boolean handler_mode := false,
				      template SystemInformationConfig expect_si)
runs on test_CT {
	var default sysinfo := activate(as_catch_RSL_sysinfo(bts_idx));

	f_init_bts(bts_idx, trx_num, handler_mode);

	/* Give some time to (hopefully/most likely) collect all system informations from RSL startup.
	 * We could stop as soon as all expected SI are received, but then we might miss SI that we don't expect and
	 * that might be sent afterwards. So rather give a generous timeout and be quite sure to catch all SI.
	 */
	f_sleep(5.0);
	log("RSL ", bts_idx, " SYSTEM INFORMATION: ", g_system_information[bts_idx]);

	deactivate(sysinfo);

	if (match(g_system_information[bts_idx], expect_si)) {
		setverdict(pass);
	} else {
		log("RSL ", bts_idx, ": EXPECTED SI: ", expect_si);
		log("RSL ", bts_idx, ": GOT SI: ", g_system_information[bts_idx]);
		setverdict(fail, "received SI does not match expectations");
		return;
	}
}

/* expect to receive a RSL message matching a specified template on a given BTS / TRX */
function f_exp_ipa_rx(template (present) RSL_Message t_rx,
		      BtsTrxIdx idx := {0, 0},
		      float Tval := 2.0)
runs on test_CT return RSL_Message {
	var ASP_RSL_Unitdata rx_rsl_ud;
	timer T := Tval;

	T.start;
	alt {
	[] IPA_RSL[idx.bts][idx.trx].receive(tr_ASP_RSL_UD(t_rx, ?)) -> value rx_rsl_ud {
		T.stop;
		}
	[] IPA_RSL[idx.bts][idx.trx].receive { repeat; }
	[] T.timeout {
		setverdict(fail, "Timeout expecting ", t_rx);
		mtc.stop;
		}
	}
	return rx_rsl_ud.rsl;
}

/* helper function to transmit RSL on a given BTS/stream */
function f_ipa_tx(template (value) RSL_Message t_tx,
		  BtsTrxIdx idx := {0, 0},
		  IpaStreamId sid := IPAC_PROTO_RSL_TRX0)
runs on test_CT {
	IPA_RSL[idx.bts][idx.trx].send(ts_ASP_RSL_UD(t_tx, sid));
}


/* verify we get a CHAN_ACT after CHAN RQD */
testcase TC_chan_act_noreply() runs on test_CT {
	var BSSAP_N_UNITDATA_ind ud_ind;
	var RSL_Message rsl_unused;

	f_init(1);

	f_ipa_tx(ts_RSL_CHAN_RQD('23'O, 23));
	rsl_unused := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));
	f_shutdown_helper();
}

const CounterNameVals counternames_bts_chreq := {
	{ "chreq:total", 0 },
	{ "chreq:attempted_emerg", 0 },
	{ "chreq:attempted_call", 0 },
	{ "chreq:attempted_location_upd", 0 },
	{ "chreq:attempted_pag", 0 },
	{ "chreq:attempted_pdch", 0 },
	{ "chreq:attempted_other", 0 },
	{ "chreq:attempted_unknown", 0 },
	{ "chreq:successful", 0 },
	{ "chreq:successful_emerg", 0 },
	{ "chreq:successful_call", 0 },
	{ "chreq:successful_location_upd", 0 },
	{ "chreq:successful_pag", 0 },
	{ "chreq:successful_pdch", 0 },
	{ "chreq:successful_other", 0 },
	{ "chreq:successful_unknown", 0 },
	{ "chreq:no_channel", 0 },
	{ "chreq:max_delay_exceeded", 0 }
};

/* verify the "chreq:*" counters */
private function f_chan_act_counter(OCT1 ra, charstring chreq_ctr_suffix) runs on test_CT
{
	var GsmFrameNumber fn := 23;

	f_logp(BSCVTY, "f_chan_act_counter(" & chreq_ctr_suffix & ")");

	var RSL_Message rx_rsl;
	f_ipa_tx(ts_RSL_CHAN_RQD(ra, fn));
	rx_rsl := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));
	var RslChannelNr chan_nr := rx_rsl.ies[0].body.chan_nr;

	f_ctrs_bts_add(0, "chreq:total");
	f_ctrs_bts_add(0, "chreq:attempted_" & chreq_ctr_suffix);
	f_ctrs_bts_verify();

	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, fn+10));
	rx_rsl := f_exp_ipa_rx(tr_RSL_IMM_ASSIGN(0));

	f_ctrs_bts_add(0, "chreq:successful");
	f_ctrs_bts_add(0, "chreq:successful_" & chreq_ctr_suffix);
	f_ctrs_bts_verify();

	/* test is done, release RSL Conn Fail Ind to clean up */
	f_ipa_tx(ts_RSL_CONN_FAIL_IND(chan_nr, RSL_ERR_RADIO_LINK_FAIL));
	rx_rsl := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_RF_CHAN_REL), Tval := 10.0);
	f_ipa_tx(ts_RSL_RF_CHAN_REL_ACK(chan_nr));
	f_sleep(1.0);
}

testcase TC_chan_act_counter() runs on test_CT {
	var BSSAP_N_UNITDATA_ind ud_ind;
	var integer chreq_total;
	var RSL_Message rsl_unused;

	f_init(1);

	f_vty_allow_emerg_bts(true, 0);

	f_ctrs_bts_init(1, counternames_bts_chreq);

	/* emergency call: RA & 0xe0 == 0xa0 --> CHREQ_T_EMERG_CALL */
	f_chan_act_counter('a3'O, "emerg");

	/* voice TCH/H: RA & 0xf0 == 0x40 --> CHREQ_T_VOICE_CALL_TCH_H */
	f_chan_act_counter('43'O, "call");

	/* LU: RA & 0xf0 == 0x00 --> CHREQ_T_LOCATION_UPD */
	f_chan_act_counter('03'O, "location_upd");

	/* Paging: RA & 0xf0 == 0x20 --> CHREQ_T_PAG_R_TCH_F */
	f_chan_act_counter('23'O, "pag");
	/* Paging: RA & 0xf0 == 0x30 --> CHREQ_T_PAG_R_TCH_FH */
	f_chan_act_counter('33'O, "pag");

	/* LU: RA & 0xfc == 0x78 --> CHREQ_T_PDCH_TWO_PHASE */
	/* no PCU, so PDCH not allowed. Skip this test for now. */
	/* f_chan_act_counter('7b'O, "pdch"); */

	/* LU: RA & 0xf0 == 0x10 --> CHREQ_T_SDCCH */
	f_chan_act_counter('13'O, "other");

	f_shutdown_helper();
}

/* CHAN RQD -> CHAN ACT -> CHAN ACT ACK -> RF CHAN REL */
private function f_TC_chan_act_ack_noest(OCT1 ra := '23'O) runs on test_CT {
	var RSL_Message rx_rsl;

	/* Send CHAN RQD and wait for allocation; acknowledge it */
	var RslChannelNr chan_nr := f_chreq_act_ack(ra);

	/* expect BSC to disable the channel again if there's no RLL EST IND */
	rx_rsl := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_RF_CHAN_REL), Tval := T3101_MAX);

	f_shutdown_helper();
}

/* Normal variant */
testcase TC_chan_act_ack_noest() runs on test_CT {
	 f_init(1);
	 f_TC_chan_act_ack_noest();
}

/* Emergency call variant */
testcase TC_chan_act_ack_noest_emerg() runs on test_CT {
	 /* See also: 3GPP TS 04.08, Table 9.9, ra=101xxxxx */
	 f_init(1);
	 f_vty_allow_emerg_bts(true, 0);
	 f_TC_chan_act_ack_noest(ra := 'A5'O);
}

/* Emergency call variant, but emergency calls are not allowed */
testcase TC_chan_rqd_emerg_deny() runs on test_CT {
	 /* See also: 3GPP TS 04.08, Table 9.9, ra=101xxxxx */

	 var RSL_Message rx_rsl;
	 var GsmRrMessage rr;

	 f_init(1);
	 f_vty_allow_emerg_bts(false, 0);

	 IPA_RSL[0][0].clear;
	 f_ipa_tx(ts_RSL_CHAN_RQD('A5'O, 23));

	 rx_rsl := f_exp_ipa_rx(tr_RSL_MsgTypeC(RSL_MT_IMMEDIATE_ASSIGN_CMD));
	 rr := dec_GsmRrMessage(rx_rsl.ies[1].body.full_imm_ass_info.payload);
	 if (rr.header.message_type == IMMEDIATE_ASSIGNMENT_REJECT) {
		setverdict(pass);
	 } else {
		setverdict(fail, "immediate assignment not rejected");
	 }

	f_shutdown_helper();
}

/* Test behavior if MSC never answers to CR */
testcase TC_chan_act_ack_est_ind_noreply() runs on test_CT {
	var RslLinkId main_dcch := valueof(ts_RslLinkID_DCCH(0));
	var IpaStreamId sid := IPAC_PROTO_RSL_TRX0;
	var RSL_Message rx_rsl;
	var ASP_RSL_Unitdata rx_rsl_ud;
	var octetstring l3_payload := gen_l3_valid_payload();

	f_init(1);

	/* Send CHAN RQD and wait for allocation; acknowledge it */
	var RslChannelNr chan_nr := f_chreq_act_ack();

	f_ipa_tx(ts_RSL_EST_IND(chan_nr, valueof(ts_RslLinkID_DCCH(0)), l3_payload));

	BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3_payload)));

	/* expect BSC to disable the channel again if there's no response from MSC */
	/* MS waits 20s (T3210) at LU; 10s (T3230) at CM SERV REQ and 5s (T3220) AT detach */
	f_expect_chan_rel(chan_nr, expect_rll_rel_req := false);
	f_shutdown_helper();
}

/* Test behavior if MSC answers with CREF to CR */
testcase TC_chan_act_ack_est_ind_refused() runs on test_CT {
	var BSSAP_N_CONNECT_ind rx_c_ind;
	var RSL_Message rx_rsl;
	var octetstring l3_payload := gen_l3_valid_payload();

	f_init(1);

	/* Send CHAN RQD and wait for allocation; acknowledge it */
	var RslChannelNr chan_nr := f_chreq_act_ack();

	f_ipa_tx(ts_RSL_EST_IND(chan_nr, valueof(ts_RslLinkID_DCCH(0)), l3_payload));

	BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3_payload))) -> value rx_c_ind;
	BSSAP.send(ts_BSSAP_DISC_req(rx_c_ind.connectionId, 0));

	/* expect BSC to disable the channel */
	f_expect_chan_rel(chan_nr, expect_rll_rel_req := false);
	f_shutdown_helper();
}

/* CHAN RQD -> CHAN ACT -> CHAN ACT NACK -> RF CHAN REL */
testcase TC_chan_act_nack() runs on test_CT {
	var RSL_Message rx_rsl;
	var integer chact_nack;

	f_init(1);

	chact_nack := f_ctrl_get_ratectr_abs(IPA_CTRL, "bts", 0, "chan_act:nack");

	f_ipa_tx(ts_RSL_CHAN_RQD('33'O, 33));
	rx_rsl := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));
	var RslChannelNr chan_nr := rx_rsl.ies[0].body.chan_nr;

	f_ipa_tx(ts_RSL_CHAN_ACT_NACK(chan_nr, RSL_ERR_EQUIPMENT_FAIL));

	/* wait for some time to hope the NACK arrives before the CTRL GET below */
	f_sleep(0.5);

	f_ctrl_get_exp_ratectr_abs(IPA_CTRL, "bts", 0, "chan_act:nack", chact_nack+1);

	f_shutdown_helper();
}

/* Test for channel exhaustion due to RACH overload */
testcase TC_chan_exhaustion() runs on test_CT {
	var ASP_RSL_Unitdata rsl_ud;
	var integer i;
	var integer chreq_total, chreq_nochan;

	f_init(1);

	chreq_total := f_ctrl_get_ratectr_abs(IPA_CTRL, "bts", 0, "chreq:total");
	chreq_nochan := f_ctrl_get_ratectr_abs(IPA_CTRL, "bts", 0, "chreq:no_channel");

	/* GSM 44.018 Table 9.1.8.2:
	 * RA = '33'O -> Establishment cause = 0011xxxx (MS dual rate capable and asks for "TCH/H or TCH/F").
	 * With current setup, expect 4xSDCCH + 4xTCH/F + 1xTCH/H to succeed */
	for (i := 0; i < NUM_TCHF_PER_BTS + NUM_TCHH_PER_BTS + NUM_SDCCH_PER_BTS; i := i+1) {
		var RslChannelNr chan_nr := f_chreq_act_ack('33'O, i);
	}

	IPA_RSL[0][0].clear;

	f_ctrl_get_exp_ratectr_abs(IPA_CTRL, "bts", 0, "chreq:total",
				   chreq_total + NUM_TCHF_PER_BTS + NUM_TCHH_PER_BTS + NUM_SDCCH_PER_BTS);

	/* now expect additional channel activations to fail */
	f_ipa_tx(ts_RSL_CHAN_RQD('42'O, 42));

	alt {
	[] IPA_RSL[0][0].receive(tr_ASP_RSL_UD(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV))) {
		setverdict(fail, "Received CHAN ACT ACK without resources?!?");
		}
	[] IPA_RSL[0][0].receive(tr_ASP_RSL_UD(tr_RSL_IMM_ASSIGN(?))) -> value rsl_ud {
		var GsmRrMessage rr;
		/* match on IMM ASS REJ */
		rr := dec_GsmRrMessage(rsl_ud.rsl.ies[1].body.full_imm_ass_info.payload);
		if (rr.header.message_type == IMMEDIATE_ASSIGNMENT_REJECT) {
			f_ctrl_get_exp_ratectr_abs(IPA_CTRL, "bts", 0, "chreq:total",
						   chreq_total + NUM_TCHF_PER_BTS + NUM_TCHH_PER_BTS + NUM_SDCCH_PER_BTS+1);
			f_ctrl_get_exp_ratectr_abs(IPA_CTRL, "bts", 0, "chreq:no_channel",
						   chreq_nochan+1);
			setverdict(pass);
		} else {
			repeat;
		}
		}
	[] IPA_RSL[0][0].receive { repeat; }
	}
	f_shutdown_helper();
}

/* Test channel deactivation due to silence from MS */
testcase TC_chan_deact_silence() runs on test_CT {
	var RslChannelNr chan_nr;

	f_init(1);

	/* Request for a dedicated channel */
	chan_nr := f_chreq_act_ack('23'O);

	/* Wait some time until the channel is released */
	f_sleep(2.0);

	/* Expect CHANnel RELease */
	alt {
	[] IPA_RSL[0][0].receive(tr_ASP_RSL_UD(tr_RSL_MsgTypeD(RSL_MT_RF_CHAN_REL))) {
		log("Received CHANnel RELease");
		setverdict(pass);
		}
	[] IPA_RSL[0][0].receive(tr_ASP_RSL_UD(tr_RSL_IMM_ASSIGN(?))) {
		/* See OS#3709, OsmoBSC should not send Immediate
		 * Assignment Reject since a dedicated channel was
		 * already allocated, and Immediate Assignment was
		 * already sent. */
		setverdict(fail, "Unexpected Immediate Assignment!");
		}
	[] IPA_RSL[0][0].receive {
		setverdict(fail, "Unexpected RSL message!");
		}
	}
	f_shutdown_helper();
}

/***********************************************************************
 * Assignment Testing
 ***********************************************************************/

/* Verify that the BSC refuses any BSSAP connection from the MSC (They are all BSC->MSC direction,
 * except for the inter-BSC handover, MT side) */
testcase TC_outbound_connect(integer bssap_idx := 0) runs on test_CT {
	f_init(1);

	BSSAP.send(ts_BSSAP_CONNECT_req(g_bssap[bssap_idx].sccpa.sccp_addr_peer, g_bssap[bssap_idx].sccpa.sccp_addr_own,
				        2342, ts_BSSMAP_AssignmentReq));
	BSSAP.receive(tr_BSSAP_DISC_ind(2342, ?, ?));
	f_shutdown_helper();
}

/* Test behavior if MSC answers with CREF to CR */
testcase TC_assignment_cic_only(integer bssap_idx := 0) runs on test_CT {
	var BSSAP_N_CONNECT_ind rx_c_ind;
	var RSL_Message rx_rsl;
	var DchanTuple dt;

	f_init(1);

	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());
	if (mp_bssap_cfg[bssap_idx].transport == BSSAP_TRANSPORT_AoIP) {
		/* send assignment without AoIP IEs */
		BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_AssignmentReq(ts_BSSMAP_IE_CIC(0, 1))));
	} else {
		/* Send assignment without CIC in IPA case */
		var BSSMAP_IE_AoIP_TransportLayerAddress tla :=
						valueof(ts_BSSMAP_IE_AoIP_TLA4('01020304'O, 2342));
		BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_AssignmentReq(omit, tla)));
	}
	alt {
	[] BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_AssignmentComplete)) {
		setverdict(fail, "AoIP BSC cannot accept ASSIGNMENT without AoIP Transport IE");
		}
	/* TODO: Actually expect GSM0808_CAUSE_REQ_A_IF_TYPE_NOT_SUPP */
	[] BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_AssignmentFail)) {
		setverdict(pass);
		}
	[] BSSAP.receive { repeat; }
	}
	f_perform_clear_test_ct(dt);
	f_shutdown_helper();
}

/* generate an assignment request for either AoIP or SCCPlite */
function f_gen_ass_req(boolean osmux_enabled := false, uint8_t ass_cid := 0, integer bssap_idx := 0, charstring aoip_tla := "1.2.3.4") return PDU_BSSAP {
	var PDU_BSSAP ass_cmd;
	var BSSMAP_IE_Osmo_OsmuxCID osmux_cid := valueof(ts_OsmuxCID(ass_cid));
	if (mp_bssap_cfg[bssap_idx].transport == BSSAP_TRANSPORT_AoIP) {
		var BSSMAP_IE_AoIP_TransportLayerAddress tla :=
			valueof(f_ts_BSSMAP_IE_AoIP_TLA(aoip_tla, 2342));
		if (osmux_enabled) {
			ass_cmd := valueof(ts_BSSMAP_AssignmentReq(omit, tla, osmux_cid));
		} else {
			ass_cmd := valueof(ts_BSSMAP_AssignmentReq(omit, tla));
		}
	} else {
		var BIT8 ass_cid_bit := int2bit(ass_cid, 8);
		var BSSMAP_IE_CircuitIdentityCode cic := valueof(ts_BSSMAP_IE_CIC(bit2int(ass_cid_bit >> 5),
										  bit2int(ass_cid_bit and4b '00011111'B)));
		ass_cmd := valueof(ts_BSSMAP_AssignmentReq(cic, omit));
	}
	return ass_cmd;
}

function f_gen_handover_req(integer bssap_idx := 0, charstring aoip_tla := "1.2.3.4",
			    template (value) BSSMAP_IE_CellIdentifier cell_id_source := ts_CellID_LAC_CI(1, 1),
			    template (omit) BSSMAP_oldToNewBSSIEs oldToNewBSSIEs := omit,
			    template (omit) TestHdlrEncrParams enc := omit) return PDU_BSSAP {
	var PDU_BSSAP ho_req;

	var BSSMAP_IE_EncryptionInformation encryptionInformation :=
		valueof(ts_BSSMAP_IE_EncrInfo('0000000000000000'O,'01'O));
	var template (omit) BSSMAP_IE_ChosenEncryptionAlgorithm chosenEncryptionAlgorithm := omit;
	var template (omit) BSSMAP_IE_KC128 kc128 := omit;
	if (ispresent(enc)) {
		var TestHdlrEncrParams v_enc := valueof(enc);
		encryptionInformation := valueof(ts_BSSMAP_IE_EncrInfo(v_enc.enc_key, v_enc.enc_alg_permitted));
		if (ispresent(v_enc.enc_alg_chosen)) {
			chosenEncryptionAlgorithm := valueof(
				ts_BSSMAP_IE_ChosenEncryptionAlgorithm(int2oct(enum2int(
					f_cipher_mode_bssmap_to_rsl(v_enc.enc_alg_chosen)), 1)));
		}
		if (ispresent(v_enc.enc_kc128)) {
			kc128 := ts_BSSMAP_IE_Kc128(v_enc.enc_kc128);
		}
	}

	if (mp_bssap_cfg[bssap_idx].transport == BSSAP_TRANSPORT_AoIP) {
		var BSSMAP_IE_AoIP_TransportLayerAddress tla :=
			valueof(f_ts_BSSMAP_IE_AoIP_TLA(aoip_tla, 2342));
		ho_req := valueof(ts_BSSMAP_HandoverRequest(omit, tla,
							    cell_id_source := cell_id_source,
							    oldToNewBSSIEs := oldToNewBSSIEs,
							    encryptionInformation := encryptionInformation,
							    chosenEncryptionAlgorithm := chosenEncryptionAlgorithm,
							    kC128 := kc128,
							    /* on AoIP, allow "all" codecs (until we add more concise
							     * tests) */
							    codecList := ts_BSSMAP_IE_CodecList(
								{ts_CodecAMR_F, ts_CodecAMR_H,
								 ts_CodecEFR, ts_CodecFR, ts_CodecHR})));
	} else {
		var BSSMAP_IE_CircuitIdentityCode cic := valueof(ts_BSSMAP_IE_CIC(0,1));
		ho_req := valueof(ts_BSSMAP_HandoverRequest(cic, omit,
							    cell_id_source := cell_id_source,
							    oldToNewBSSIEs := oldToNewBSSIEs,
							    encryptionInformation := encryptionInformation,
							    chosenEncryptionAlgorithm := chosenEncryptionAlgorithm,
							    kC128 := kc128));
	}
	return ho_req;
}

/* generate an assignment complete template for either AoIP or SCCPlite */
function f_gen_exp_compl(integer bssap_idx := 0)
runs on MSC_ConnHdlr return template PDU_BSSAP {
	var template PDU_BSSAP exp_compl;
	if (mp_bssap_cfg[bssap_idx].transport == BSSAP_TRANSPORT_AoIP) {
		var template BSSMAP_IE_Osmo_OsmuxCID exp_osmux_cid := omit;
		if (g_pars.use_osmux_cn) {
			var template (present) INT1 exp_cid := ?;
			if (isbound(g_media.mgcp_conn[0].local_osmux_cid) and isbound(g_media.mgcp_conn[1].local_osmux_cid)) {
				exp_cid := (g_media.mgcp_conn[0].local_osmux_cid, g_media.mgcp_conn[1].local_osmux_cid);
			} else if (isbound(g_media.mgcp_conn[0].local_osmux_cid)) {
				exp_cid := g_media.mgcp_conn[0].local_osmux_cid;
			} else if (isbound(g_media.mgcp_conn[1].local_osmux_cid)) {
				exp_cid := g_media.mgcp_conn[1].local_osmux_cid;
			}
			exp_osmux_cid := tr_OsmuxCID(exp_cid);
		}
		exp_compl := tr_BSSMAP_AssignmentComplete(omit, ?, exp_osmux_cid);
	} else {
		/* CIC is optional "*" as the MSC allocated it */
		exp_compl := tr_BSSMAP_AssignmentComplete(*, omit, omit);
	}
	return exp_compl;
}

/* Run everything required up to sending a caller-specified assignment command and expect response */
function f_assignment_exp(PDU_BSSAP ass_cmd, template PDU_BSSAP exp, charstring fail_text)
runs on test_CT return DchanTuple {
	var BSSAP_N_CONNECT_ind rx_c_ind;
	var RSL_Message rx_rsl;
	var DchanTuple dt;

	f_init(1);

	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());
	/* send assignment without AoIP IEs */
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ass_cmd));
	alt {
	[] BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_AssignmentComplete)) {
		if (ischosen(exp.pdu.bssmap.assignmentComplete)) {
			setverdict(pass);
		} else {
			setverdict(fail, fail_text);
		}
		}
	[] BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_AssignmentFail)) {
		if (ischosen(exp.pdu.bssmap.assignmentFailure)) {
			setverdict(pass);
		} else {
			setverdict(fail, fail_text);
		}
		}
	[] BSSAP.receive { repeat; }
	}
	return dt;
}

private function f_tc_assignment_csd(charstring data_rate_str, OCT1 data_rate, boolean transp := true) runs on MSC_ConnHdlr {
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	var SDP_FIELD_PayloadType pt_csd := PT_CSD;

	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelTypeCSD);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecCSData}));

	/* Non-transparent service (3GPP TS 48.008 § 3.2.2.11, oct 5, bit 7) */
	if (not transp) {
		data_rate := data_rate or4b '40'O;
	}

	ass_cmd.pdu.bssmap.assignmentRequest.channelType.speechId_DataIndicator := data_rate;

	log("-----------------------------------------------");
	log("Assignment req with data rate: " & data_rate_str);
	log("-----------------------------------------------");

	f_establish_fully(ass_cmd, exp_compl);

	if (g_media.bts.rtp_pt != enum2int(pt_csd)) {
		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("unexpected RTP payload type: ", g_media.bts.rtp_pt));
	}

	f_perform_clear();
	f_create_mgcp_delete_ep(g_media.mgcp_ep);
}
private function f_tc_assignment_csd_all(charstring id) runs on MSC_ConnHdlr {
	/* Data rates that require multi-slot HSCSD assignment are not tested
	 * on purpose (not supported): T_32k0, T_28k8, NT_43k5, NT_29k0 */
	f_tc_assignment_csd("T_14k4", GSM0808_DATA_RATE_TRANSP_14k4);
	f_tc_assignment_csd("T_9k6", GSM0808_DATA_RATE_TRANSP_9k6);
	f_tc_assignment_csd("T_4k8", GSM0808_DATA_RATE_TRANSP_4k8);
	f_tc_assignment_csd("T_2k4", GSM0808_DATA_RATE_TRANSP_2k4);
	f_tc_assignment_csd("T_1k2", GSM0808_DATA_RATE_TRANSP_1k2);
	f_tc_assignment_csd("T_600", GSM0808_DATA_RATE_TRANSP_600);
	f_tc_assignment_csd("T_1200_75", GSM0808_DATA_RATE_TRANSP_1200_75);

	f_tc_assignment_csd("NT_12000_6000", GSM0808_DATA_RATE_NON_TRANSP_12000_6000, false);
	f_tc_assignment_csd("NT_14k5", GSM0808_DATA_RATE_NON_TRANSP_14k5, false);
	f_tc_assignment_csd("NT_12k0", GSM0808_DATA_RATE_NON_TRANSP_12k0, false);
	f_tc_assignment_csd("NT_6k0", GSM0808_DATA_RATE_NON_TRANSP_6k0, false);
}
testcase TC_assignment_csd() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	pars.encr := valueof(t_EncrParams('01'O, f_rnd_octstring(8)));

	f_init(1, true, guard_timeout := 120.0);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_assignment_csd_all), pars);
	vc_conn.done;

	f_shutdown_helper();
}

testcase TC_assignment_ctm() runs on test_CT {
	var template PDU_BSSAP exp_fail := tr_BSSMAP_AssignmentFail;
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelTypeCTM);
	//exp_fail.pdu.bssmap.assignmentFailure.cause.causeValue := int2bit(enum2int(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL), 7);
	var DchanTuple dt := f_assignment_exp(ass_cmd, exp_fail, "BSC accepted Assignment for Speech+CTM");
	f_perform_clear_test_ct(dt);
	f_shutdown_helper();
}

type record DchanTuple {
	integer sccp_conn_id,
	RslChannelNr rsl_chan_nr,
	BtsTrxIdx idx
}

type record of DchanTuple DchanTuples;

/* Send CHAN RQD and wait for allocation; acknowledge it */
private function f_chreq_act_ack(OCT1 ra := '23'O,
				 GsmFrameNumber fn := 23,
				 BtsTrxIdx idx := {0, 0})
runs on test_CT return RslChannelNr {
	var RSL_Message rx_rsl;
	f_ipa_tx(ts_RSL_CHAN_RQD(ra, fn), {idx.bts, 0});
	rx_rsl := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV), idx);
	var RslChannelNr chan_nr := rx_rsl.ies[0].body.chan_nr;
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, fn+10), idx);
	rx_rsl := f_exp_ipa_rx(tr_RSL_IMM_ASSIGN(0), {idx.bts, 0});
	return chan_nr;
}

/* helper function to establish a dedicated channel via BTS and MSC */
function f_est_dchan(OCT1 ra, GsmFrameNumber fn, octetstring l3,
		     BtsTrxIdx idx := {0, 0})
runs on test_CT return DchanTuple {
	var BSSAP_N_CONNECT_ind rx_c_ind;
	var DchanTuple dt;

	/* Send CHAN RQD and wait for allocation; acknowledge it */
	dt.rsl_chan_nr := f_chreq_act_ack(ra, fn, idx);

	f_ipa_tx(ts_RSL_EST_IND(dt.rsl_chan_nr, valueof(ts_RslLinkID_DCCH(0)), l3), idx);

	BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3))) -> value rx_c_ind;
	dt.sccp_conn_id := rx_c_ind.connectionId;
	BSSAP.send(ts_BSSAP_CONNECT_res(dt.sccp_conn_id));

	dt.idx := idx;
	return dt;
}

/* Like f_est_dchan(), but for the first lchan of a dynamic timeslot: first ACK the deactivation of PDCH. */
function f_est_dchan_dyn(OCT1 ra, GsmFrameNumber fn, octetstring l3,
			 BtsTrxIdx idx := {0, 0})
runs on test_CT return DchanTuple {
	var BSSAP_N_CONNECT_ind rx_c_ind;
	var DchanTuple dt;

	/* Send CHAN RQD */
	var RSL_Message rx_rsl;
	f_ipa_tx(ts_RSL_CHAN_RQD(ra, fn), {idx.bts, 0});

	/* The dyn TS first deactivates PDCH */
	rx_rsl := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_RF_CHAN_REL), idx, Tval := T3101_MAX);
	dt.rsl_chan_nr := rx_rsl.ies[0].body.chan_nr;
	f_ipa_tx(ts_RSL_RF_CHAN_REL_ACK(dt.rsl_chan_nr), idx);

	rx_rsl := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV), idx);
	dt.rsl_chan_nr := rx_rsl.ies[0].body.chan_nr;

	/* Now activates the signalling channel */
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(dt.rsl_chan_nr, fn+10), idx);
	rx_rsl := f_exp_ipa_rx(tr_RSL_IMM_ASSIGN(0), {idx.bts, 0});

	f_ipa_tx(ts_RSL_EST_IND(dt.rsl_chan_nr, valueof(ts_RslLinkID_DCCH(0)), l3), idx);

	BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3))) -> value rx_c_ind;
	dt.sccp_conn_id := rx_c_ind.connectionId;
	BSSAP.send(ts_BSSAP_CONNECT_res(dt.sccp_conn_id));

	dt.idx := idx;
	return dt;
}

/* expect RF CAN REL from BTS, acknowledge it and clear the MSC side */
private function f_exp_chan_rel_and_clear(DchanTuple dt)
runs on test_CT {
	var RSL_Message rx_rsl;
	/* expect BSC to disable the channel */
	rx_rsl := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_RF_CHAN_REL), dt.idx, Tval := T3101_MAX);
	/* respond with CHAN REL ACK */
	f_ipa_tx(ts_RSL_RF_CHAN_REL_ACK(dt.rsl_chan_nr), dt.idx);

	/* expect Clear Complete from BSC */
	BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_ClearComplete));

	/* MSC disconnects as instructed. */
	BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));
}

/* Test behavior of channel release after unilateral RLL REL IND (DISC from MS) */
testcase TC_chan_rel_rll_rel_ind() runs on test_CT {
	var BSSAP_N_DATA_ind rx_di;
	var DchanTuple dt;

	f_init(1);

	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());

	/* simulate RLL REL IND */
	f_ipa_tx(ts_RSL_REL_IND(dt.rsl_chan_nr, valueof(ts_RslLinkID_DCCH(0))));

	/* expect Clear Request on MSC side */
	BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_ClearRequest)) -> value rx_di;

	/* Instruct BSC to clear channel */
	var BssmapCause cause := bit2int(rx_di.userData.pdu.bssmap.clearRequest.cause.causeValue);
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommand(cause)));

	/* expect BSC to disable the channel */
	f_exp_chan_rel_and_clear(dt);

	/* wait for SCCP emulation to do its job */
	f_sleep(1.0);

	f_shutdown_helper();
}

/* Test behavior of channel release after CONN FAIL IND from BTS */
testcase TC_chan_rel_conn_fail() runs on test_CT {
	var BSSAP_N_DATA_ind rx_di;
	var DchanTuple dt;

	f_init(1);

	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());

	/* Sending CONN FAIL IND immediately may trigger a race condition.
	 * Give the BSC some time to process a new SCCP connection (OS#5823). */
	f_sleep(0.2);

	/* simulate CONN FAIL IND */
	f_ipa_tx(ts_RSL_CONN_FAIL_IND(dt.rsl_chan_nr, RSL_ERR_RADIO_LINK_FAIL));
	/* TODO: different cause values? */

	/* expect Clear Request from BSC */
	BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_ClearRequest)) -> value rx_di;

	/* Instruct BSC to clear channel */
	var BssmapCause cause := bit2int(rx_di.userData.pdu.bssmap.clearRequest.cause.causeValue);
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommand(cause)));

	/* expect BSC to disable the channel */
	f_exp_chan_rel_and_clear(dt);

	/* wait for SCCP emulation to do its job */
	f_sleep(1.0);

	f_shutdown_helper();
}

/* Test behavior of early CONN FAIL IND from BTS (before EST IND!) */
/* See also https://www.osmocom.org/issues/3182 */
testcase TC_early_conn_fail() runs on test_CT {
	var RSL_Message rx_rsl;
	var DchanTuple dt;

	f_init(1);

	/* BTS->BSC: Send CHAN RQD and wait for allocation; acknowledge it */
	dt.rsl_chan_nr := f_chreq_act_ack(f_rnd_ra_cs(), 23);

	/* BTS->BSC: simulate CONN FAIL IND */
	f_ipa_tx(ts_RSL_CONN_FAIL_IND(dt.rsl_chan_nr, RSL_ERR_RADIO_LINK_FAIL));

	/* BTS->BSC: Expect RF channel release from BSC on Abis */
	rx_rsl := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_RF_CHAN_REL), Tval := 10.0);

	/* BTS<-BSC: respond with CHAN REL ACK */
	f_ipa_tx(ts_RSL_RF_CHAN_REL_ACK(dt.rsl_chan_nr));

	f_shutdown_helper();
}

/* Test behavior of late CONN FAIL IND from BTS (ater REL IND!) */
/* See also https://www.osmocom.org/issues/3182 */
testcase TC_late_conn_fail() runs on test_CT {
	var RSL_Message rx_rsl;
	var DchanTuple dt;

	f_init(1);

	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());

	/* BSC<-MSC: Instruct BSC to clear connection */
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommand(0)));

	/* BTS->BSC: expect BSC to deactivate SACCH */
	rx_rsl := f_exp_ipa_rx(tr_RSL_DEACT_SACCH(dt.rsl_chan_nr));

	/* BTS->BSC: simulate a late CONN FAIL IND from BTS */
	f_ipa_tx(ts_RSL_CONN_FAIL_IND(dt.rsl_chan_nr, RSL_ERR_RADIO_LINK_FAIL));

	/* BTS<-BSC: Expect RF channel release from BSC on Abis */
	rx_rsl := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_RF_CHAN_REL), Tval := 10.0);
	/* BTS->BSC: respond with CHAN REL ACK */
	f_ipa_tx(ts_RSL_RF_CHAN_REL_ACK(dt.rsl_chan_nr));

	/* BSC->MSC: expect Clear Complete from BSC */
	BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_ClearComplete));

	/* BSC<-MSC: MSC disconnects as requested. */
	BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));

	f_shutdown_helper();
}

private function f_TC_stats_conn_fail(charstring id) runs on MSC_ConnHdlr {
	var template PDU_BSSAP exp_fail := tr_BSSMAP_AssignmentFail;
	var PDU_BSSAP ass_cmd := f_gen_ass_req();

	f_statsd_reset();

	/* Establish SDCCH (invalid DataIndicator for exp_fail) */
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelTypeCSD);
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.speechId_DataIndicator := 'ff'O;
	f_establish_fully(ass_cmd, exp_fail);

	/* Expect stats to be 0 */
	var StatsDExpects expect := {
		{name := "TTCN3.bts.0.chan.rf_fail", mtype := "c", min := 0, max := 0},
		{name := "TTCN3.bts.0.chan.rf_fail_sdcch", mtype := "c", min := 0, max := 0}
	};
	f_statsd_expect(expect);

	/* Simulate CONN FAIL IND on SDCCH */
	RSL.send(ts_ASP_RSL_UD(
			ts_RSL_CONN_FAIL_IND(g_chan_nr, RSL_ERR_RADIO_LINK_FAIL),
			IPAC_PROTO_RSL_TRX0));

	f_sleep(1.0);

	/* Expect stats to be 1 */
	expect := {
		{name := "TTCN3.bts.0.chan.rf_fail", mtype := "c", min := 1, max := 1},
		{name := "TTCN3.bts.0.chan.rf_fail_sdcch", mtype := "c", min := 1, max := 1}
	};
	f_statsd_expect(expect);
	BSSAP.receive(tr_BSSMAP_ClearRequest);
	f_perform_clear();
}
testcase TC_stats_conn_fail() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	vc_conn := f_start_handler(refers(f_TC_stats_conn_fail), pars);
	vc_conn.done;

	f_shutdown_helper();
}

function f_expect_chan_rel(RslChannelNr rsl_chan_nr,
			   BtsTrxIdx idx := {0, 0},
			   boolean expect_deact_sacch := true,
			   boolean expect_rr_chan_rel := true,
			   boolean expect_rll_rel_req := true,
			   boolean handle_rll_rel := true,
			   template CellSelIndValue expect_cells := omit,
			   template (present) RR_Cause expect_rr_cause := ?
			   ) runs on test_CT {

	var RslLinkId main_dcch := valueof(ts_RslLinkID_DCCH(0));
	var boolean got_deact_sacch := false;
	var boolean got_rr_chan_rel := false;
	var boolean got_rll_rel_req := false;
	var ASP_RSL_Unitdata ud;
	var RSL_IE_Body l3_ie;
	var PDU_ML3_NW_MS l3;
	var RR_Cause got_cause;
	log("f_expect_chan_rel() expecting: expect_deact_sacch=", expect_deact_sacch, " expect_rr_chan_rel=", expect_rr_chan_rel,
	    " expect_rll_rel_req=", expect_rll_rel_req);
	alt {
	[] IPA_RSL[idx.bts][idx.trx].receive(tr_ASP_RSL_UD(tr_RSL_DEACT_SACCH(rsl_chan_nr))) {
		got_deact_sacch := true;
		repeat;
	}
	[] IPA_RSL[idx.bts][idx.trx].receive(tr_ASP_RSL_UD(tr_RSL_DATA_REQ(rsl_chan_nr, ?, decmatch tr_RRM_RR_RELEASE))) -> value ud {
		got_rr_chan_rel := true;

		if (f_rsl_find_ie(ud.rsl, RSL_IE_L3_INFO, l3_ie) == false) {
			setverdict(fail, "cannot find L3");
			mtc.stop;
		}
		l3 := dec_PDU_ML3_NW_MS(l3_ie.l3_info.payload);

		if (not istemplatekind(expect_cells, "omit")) {
			var CellSelIndValue cells := dec_CellSelIndValue(
				l3.msgs.rrm.channelRelease.cellSelectionIndicator.cellSelectionIndicatorValue);

			log("GOT RR CHANNEL RELEASE WITH CELLS: ", cells);
			if (match(cells, expect_cells)) {
				setverdict(pass);
			} else {
				log("EXPECTED CELLS: ", expect_cells);
				setverdict(fail, "Received cells list on RR Channel Release does not match expectations");
			}
		}

		int2enum(oct2int(l3.msgs.rrm.channelRelease.rRCause.valuePart), got_cause);
		log("GOT CAUSE CODE: ", l3.msgs.rrm.channelRelease.rRCause.valuePart, " = ", got_cause);
		if (match(got_cause, expect_rr_cause)) {
			setverdict(pass);
		} else {
			log("EXPECTED CAUSE CODE: ", expect_rr_cause);
			setverdict(fail, "Received RR Channel Release Cause code does not match expectations");
		}
		repeat;
	}
	[] IPA_RSL[idx.bts][idx.trx].receive(tr_ASP_RSL_UD(tr_RSL_REL_REQ(rsl_chan_nr, ?))) {
		got_rll_rel_req := true;
		/* FIXME: Why are we getting this for LinkID SACCH? */
		if (handle_rll_rel) {
			f_ipa_tx(ts_RSL_REL_CONF(rsl_chan_nr, main_dcch));
		}
		repeat;
	}
	[] IPA_RSL[idx.bts][idx.trx].receive(tr_ASP_RSL_UD(tr_RSL_MsgTypeD(RSL_MT_RF_CHAN_REL))) {
		/* respond with CHAN REL ACK */
		f_ipa_tx(ts_RSL_RF_CHAN_REL_ACK(rsl_chan_nr));
		}
	/* ignore any user data */
	[] IPA_RSL[idx.bts][idx.trx].receive(tr_ASP_RSL_UD(tr_RSL_MsgTypeR(?))) {
		repeat;
		}
	}

	log("f_expect_chan_rel() summary: got_deact_sacch=", got_deact_sacch, " got_rr_chan_rel=", got_rr_chan_rel,
	    " got_rll_rel_req=", got_rll_rel_req);

	if (expect_deact_sacch != got_deact_sacch) {
		setverdict(fail, "f_expect_chan_rel(): expect_deact_sacch=", expect_deact_sacch, " got_deact_sacch=", got_deact_sacch);
	}
	if (expect_rr_chan_rel != got_rr_chan_rel) {
		setverdict(fail, "f_expect_chan_rel(): expect_rr_chan_rel=", expect_rr_chan_rel, " got_rr_chan_rel=", got_rr_chan_rel);
	}
	if (expect_rll_rel_req != got_rll_rel_req) {
		setverdict(fail, "f_expect_chan_rel(): expect_rll_rel_req=", expect_rll_rel_req, " got_rll_rel_req=", got_rll_rel_req);
	}
}

/* Test behavior of channel release after hard Clear Command from MSC */
testcase TC_chan_rel_hard_clear() runs on test_CT {
	var BSSAP_N_DATA_ind rx_di;
	var DchanTuple dt;

	f_init(1);

	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());

	/* Instruct BSC to clear channel */
	var BssmapCause cause := 0;
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommand(cause)));

	/* expect Clear Complete from BSC on A */
	BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_ClearComplete)) {
		/* release the SCCP connection */
		BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));
	}

	f_expect_chan_rel(dt.rsl_chan_nr, expect_rll_rel_req := false);
	f_shutdown_helper();
}

function f_TC_chan_rel_last_eutran_plmn_hard_clear(boolean tx_csfb_ind) runs on test_CT {
	var BSSAP_N_DATA_ind rx_di;
	var DchanTuple dt;

	f_init(1);

	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());
	/* Send CommonID with some random PLMN (BSC doesn't take it into account
	/* yet when generating the EUTRAN neigh list in RR CHannel Release) */
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_CommonId('001019876543210'H, '323454'O)));

	/* Instruct BSC to clear channel */
	var BssmapCause cause := 0;
	if (tx_csfb_ind) {
		BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommandCSFB(cause)));
	} else {
		BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommand(cause)));
	}

	/* expect Clear Complete from BSC on A */
	BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_ClearComplete)) {
		/* release the SCCP connection */
		BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));
	}

	/* 1 neighbor is added by default in osmo-bts.cfg and
	   SystemInformationConfig_default, use that: */
	var template CellSelIndValue exp_cells := f_tr_rr_chan_rel_earfcns(1);

	f_expect_chan_rel(dt.rsl_chan_nr, expect_rll_rel_req := false, expect_cells := exp_cells);
	f_shutdown_helper();
}

/* Test behavior of RR Channel rRelease after Clear Command without CSFB indicator
   from MSC, previously receiving any CommonID containing the "Last Used E-UTRAN
   PLMN Id". According to spec (3GPP TS 48.008 sec 3.1.30) that's the bit requesting
   EUTRAN neighbor list sent later on by BSC in RR Channel, so receiving CSFB
   Indicator or not shouldn't matter at all. */
testcase TC_chan_rel_last_eutran_plmn_hard_clear_no_csfb() runs on test_CT {
	f_TC_chan_rel_last_eutran_plmn_hard_clear(false);
}

/* Test behavior of RR Channel rRelease after Clear Command with CSFB indicator from
   MSC, previously receiving any CommonID containing the "Last Used E-UTRAN PLMN
   Id". According to spec (3GPP TS 48.008 sec 3.1.30) that's the bit requesting
   EUTRAN neighbor list sent later on by BSC in RR Channel. */
testcase TC_chan_rel_last_eutran_plmn_hard_clear_csfb() runs on test_CT {
	f_TC_chan_rel_last_eutran_plmn_hard_clear(true);
}

/* Test behavior of RR Channel Release after Clear Command with CSFB indicator from
   MSC, without receiving any CommonID containing the "Last Used E-UTRAN PLMN
   Id". According to spec (TS 48.008 version 16.0.0 Release 16 "3.2.1.21") the
   CSFB Indicator should not be used anymore, and hence, there should be no
   EUTRAN neighbor list sent by BSC in RR Channel release since no CommonId with
   Last Used E-UTRAN PLMN Id" IE was sent for this conn. */
testcase TC_chan_rel_hard_clear_csfb() runs on test_CT {
	var BSSAP_N_DATA_ind rx_di;
	var DchanTuple dt;

	f_init(1);

	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());

	/* Instruct BSC to clear channel */
	var BssmapCause cause := 0;
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommandCSFB(cause)));

	/* expect Clear Complete from BSC on A */
	BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_ClearComplete)) {
		/* release the SCCP connection */
		BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));
	}

	f_expect_chan_rel(dt.rsl_chan_nr, expect_rll_rel_req := false);
	f_shutdown_helper();
}

/* Test behavior of channel release after hard RLSD from MSC */
testcase TC_chan_rel_hard_rlsd() runs on test_CT {
	var DchanTuple dt;

	f_init(1);

	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());

	/* release the SCCP connection */
	BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));

	f_expect_chan_rel(dt.rsl_chan_nr, expect_rll_rel_req := false);
	f_shutdown_helper();
}

/* Test behavior of channel release after hard RLSD from MSC and MS is not responding to RLL REL REQ */
testcase TC_chan_rel_hard_rlsd_ms_dead() runs on test_CT {
	var DchanTuple dt;

	f_init(1);

	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());

	/* release the SCCP connection */
	BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));

	f_expect_chan_rel(dt.rsl_chan_nr, expect_rll_rel_req := false);
	f_shutdown_helper();
}

/* Test behavior of channel release after BSSMAP RESET from MSC */
testcase TC_chan_rel_a_reset() runs on test_CT {
	var DchanTuple dt;

	f_init(1);

	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());

	/* Clear the queue, it might still contain stuff like IMMEDIATE ASSIGN */
	IPA_RSL[0][0].clear;

	/* perform BSSAP RESET, expect RESET ACK and DISC.ind on connection */
	BSSAP.send(ts_BSSAP_UNITDATA_req(g_bssap[0].sccpa.sccp_addr_peer, g_bssap[0].sccpa.sccp_addr_own, ts_BSSMAP_Reset(0, g_osmux_enabled_cn)));
	interleave {
	[] BSSAP.receive(tr_BSSAP_UNITDATA_ind(g_bssap[0].sccpa.sccp_addr_own, g_bssap[0].sccpa.sccp_addr_peer, tr_BSSMAP_ResetAck(g_osmux_enabled_cn))) { }
	[] BSSAP.receive(tr_BSSAP_DISC_ind(dt.sccp_conn_id, ?, ?)) { }
	}

	f_expect_chan_rel(dt.rsl_chan_nr, expect_rll_rel_req := false);
	f_shutdown_helper();
}

/* Verify T(iar) triggers and releases the channel */
testcase TC_chan_rel_sccp_tiar_timeout() runs on test_CT {
	var DchanTuple dt;

	/* Set T(iar) in BSC low enough that it will trigger before other side
	   has time to keep alive with a T(ias). Keep recommended ratio of
	   T(iar) >= T(ias)*2 */
	g_bsc_sccp_timer_ias := 2;
	g_bsc_sccp_timer_iar := 5;

	f_init(1);

	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());
	f_expect_chan_rel(dt.rsl_chan_nr, expect_rll_rel_req := false);
	f_shutdown_helper();
}

private function f_tc_chan_rel_rr_cause(myBSSMAP_Cause clear_cmd_cause,
					template (present) RR_Cause expect_rr_cause)
runs on test_CT
{
	var DchanTuple dt;

	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());
	var BssmapCause cause := 0;
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommand(enum2int(clear_cmd_cause))));
	BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_ClearComplete)) {
		BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));
	}

	f_expect_chan_rel(dt.rsl_chan_nr, expect_rll_rel_req := false, expect_rr_cause := expect_rr_cause);
}

/* Test that Clear Command cause codes affect the RR Channel Release cause code */
testcase TC_chan_rel_rr_cause() runs on test_CT {
	f_init(1);

	f_tc_chan_rel_rr_cause(GSM0808_CAUSE_CALL_CONTROL, GSM48_RR_CAUSE_NORMAL);
	f_tc_chan_rel_rr_cause(GSM0808_CAUSE_HANDOVER_SUCCESSFUL, GSM48_RR_CAUSE_NORMAL);
	f_tc_chan_rel_rr_cause(GSM0808_CAUSE_PREEMPTION, GSM48_RR_CAUSE_PREMPTIVE_REL);
	f_tc_chan_rel_rr_cause(GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE, GSM48_RR_CAUSE_PROT_ERROR_UNSPC);
	f_tc_chan_rel_rr_cause(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE, GSM48_RR_CAUSE_ABNORMAL_UNSPEC);
	f_tc_chan_rel_rr_cause(GSM0808_CAUSE_EQUIPMENT_FAILURE, GSM48_RR_CAUSE_ABNORMAL_UNSPEC);

	f_shutdown_helper();
}

/* Test behavior if RSL EST IND for non-active channel */
testcase TC_rll_est_ind_inact_lchan() runs on test_CT {
	timer T := 2.0;

	f_init(1);

	var RslChannelNr chan_nr := valueof(t_RslChanNr_Bm(6));
	var octetstring l3_payload := gen_l3_valid_payload();
	f_ipa_tx(ts_RSL_EST_IND(chan_nr, valueof(ts_RslLinkID_DCCH(0)), l3_payload));

	T.start;
	alt {
	[] BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3_payload))) {
		setverdict(fail, "MSC received COMPL L3 for non-active lchan");
		}
	[] BSSAP.receive {}
	[] IPA_RSL[0][0].receive {}
	[] T.timeout {}
	}

	f_shutdown_helper();
}

/* Test behavior if RSL EST IND for invalid SAPI */
testcase TC_rll_est_ind_inval_sapi1() runs on test_CT {
	var RslChannelNr chan_nr;
	var octetstring l3_payload;

	f_init(1);

	chan_nr := f_chreq_act_ack();
	l3_payload := gen_l3_valid_payload();

	f_ipa_tx(ts_RSL_EST_IND(chan_nr, valueof(ts_RslLinkID_DCCH(1)), l3_payload));

	timer T := 2.0;
	T.start;
	alt {
	[] BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3_payload))) {
		setverdict(fail, "MSC received COMPL L3 for invalid SAPI 1");
		}
	[] BSSAP.receive { repeat; }
	[] IPA_RSL[0][0].receive { repeat; }
	[] T.timeout {}
	}

	f_shutdown_helper();
}

/* Test behavior if RSL EST IND for invalid SAPI */
testcase TC_rll_est_ind_inval_sapi3() runs on test_CT {
	timer T := 2.0;

	f_init(1);

	var RslChannelNr chan_nr := f_chreq_act_ack();
	var octetstring l3_payload := gen_l3_valid_payload();

	f_ipa_tx(ts_RSL_EST_IND(chan_nr, valueof(ts_RslLinkID_DCCH(3)), l3_payload));

	T.start;
	alt {
	[] BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3_payload))) {
		setverdict(fail, "MSC received COMPL L3 for invalid SAPI 3");
		}
	[] BSSAP.receive { repeat; }
	[] IPA_RSL[0][0].receive { repeat; }
	[] T.timeout {}
	}

	f_shutdown_helper();
}

/* Test behavior if RSL EST IND for invalid SACCH */
testcase TC_rll_est_ind_inval_sacch() runs on test_CT {
	timer T := 2.0;

	f_init(1);

	var RslChannelNr chan_nr := f_chreq_act_ack();
	var octetstring l3_payload := gen_l3_valid_payload();

	f_ipa_tx(ts_RSL_EST_IND(chan_nr, valueof(ts_RslLinkID_SACCH(0)), l3_payload));

	T.start;
	alt {
	[] BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3_payload))) {
		setverdict(fail, "MSC received COMPL L3 for invalid Link SACCH");
		}
	[] BSSAP.receive { repeat; }
	[] IPA_RSL[0][0].receive { repeat; }
	[] T.timeout {}
	}

	f_shutdown_helper();
}

/* Verify DLCI / RSL Link ID conversion for MO/MT messages on SAPI0/SAPI3 */
private function f_TC_tch_dlci_link_id_sapi(charstring id) runs on MSC_ConnHdlr {
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();

	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));

	f_establish_fully(ass_cmd, exp_compl);

	/* SAPI0 has already been established by f_establish_fully(), establish SAPI3 */
	RSL.send(ts_RSL_EST_IND(g_chan_nr, ts_RslLinkID_SACCH(3), '0904'O));
	/* Expect BSSAP/DTAP on SAPI3 (DLCI IE) */
	BSSAP.receive(PDU_BSSAP:{
		discriminator := '1'B,
		spare := '0000000'B,
		dlci := 'C3'O,
		lengthIndicator := ?,
		pdu := { dtap := '0904'O }
	});

	/* Send messages on DCCH/SAPI0 and ACCH/SAPI3 */
	for (var integer i := 0; i < 32; i := i + 1) {
		var octetstring l3 := '09'O & f_rnd_octstring(14);
		var template (value) RslLinkId link_id;
		var template (value) OCT1 dlci;

		if (i mod 2 == 0) {
			/* SAPI0 on FACCH or SDCCH */
			link_id := ts_RslLinkID_DCCH(0);
			dlci := '80'O;
		} else {
			/* SAPI3 on SACCH */
			link_id := ts_RslLinkID_SACCH(3);
			dlci := 'C3'O;
		}

		/* Send MO message: RSL -> BSSAP */
		f_mo_l3_transceive(RSL, link_id, dlci, l3);
		/* Send MT message: BSSAP -> RSL */
		f_mt_l3_transceive(RSL, link_id, dlci, l3);
	}
	f_perform_clear();
}
testcase TC_tch_dlci_link_id_sapi() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	vc_conn := f_start_handler(refers(f_TC_tch_dlci_link_id_sapi), pars);
	vc_conn.done;

	f_shutdown_helper();
}

private function f_exp_sapi_n_reject(template (present) GsmSapi sapi := ?,
				     template (present) myBSSMAP_Cause cause := ?,
				     template (present) BIT2 cc := ?,
				     float T_val := 2.0)
runs on test_CT {
	var BSSAP_N_DATA_ind rx_di;
	timer T;

	var template (present) BSSMAP_IE_Cause tr_cause := tr_BSSMAP_IE_Cause(cause);
	var template (present) PDU_BSSAP tr_pdu := tr_BSSMAP_SAPInReject(sapi);

	T.start(T_val);
	alt {
	[] BSSAP.receive(tr_BSSAP_DATA_ind(?, tr_pdu)) -> value rx_di {
		var BSSMAP_IE_Cause rx_cause := rx_di.userData.pdu.bssmap.sAPInReject.cause;
		if (not match(rx_cause, tr_cause)) {
			setverdict(fail, "Rx unexpected Cause IE: ",
				   rx_cause, " vs expected ", tr_cause);
		}

		/* Who ever on the earth decided to define this field as two separate bits?!? */
		var BIT2 rx_cc := rx_di.userData.pdu.bssmap.sAPInReject.dLCI.c2
				& rx_di.userData.pdu.bssmap.sAPInReject.dLCI.c1;
		if (not match(rx_cc, cc)) {
			setverdict(fail, "Rx unexpected Control Channel type: ",
				   rx_cc, " vs expected ", cc);
		}

		setverdict(pass);
		}
	[] BSSAP.receive(BSSAP_N_DATA_ind:?) -> value rx_di {
		setverdict(fail, "Rx unexpected BSSAP PDU: ", rx_di);
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for BSSMAP SAPI N Reject");
		}
	}
}

/* Check if we get SAPI N Reject on receipt of unexpected RLL RELease INDication */
testcase TC_rll_rel_ind_sapi_n_reject() runs on test_CT {
	var RSL_Message rx_rsl;
	var DchanTuple dt;

	f_init(1);

	/* MS establishes a SAPI=0 link on DCCH */
	dt := f_est_dchan(f_rnd_ra_cs(), 23, gen_l3_valid_payload());

	/* MSC sends some data on (not yet established) SAPI=3 link */
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSAP_DTAP(gen_l3_valid_payload(), '03'O)));
	/* BSC attempts to establish a SAPI=3 link on DCCH */
	rx_rsl := f_exp_ipa_rx(tr_RSL_EST_REQ(dt.rsl_chan_nr, tr_RslLinkID_DCCH(3)));

	/* MS sends unexpected RELease INDication on SAPI=3 */
	f_ipa_tx(ts_RSL_REL_IND(dt.rsl_chan_nr, ts_RslLinkID_DCCH(3)));
	/* We expect to receive BSSMAP SAPI N Reject message from the BSC */
	f_exp_sapi_n_reject(3, GSM0808_CAUSE_MS_NOT_EQUIPPED);

	/* Clean up the connection */
	BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));
	f_expect_chan_rel(dt.rsl_chan_nr, expect_rll_rel_req := false);

	f_shutdown_helper();
}

/* Check if we get SAPI N Reject on receipt of unexpected RLL ERROR INDication */
testcase TC_rll_err_ind_sapi_n_reject() runs on test_CT {
	var RSL_Message rx_rsl;
	var DchanTuple dt;

	f_init(1);

	/* MS establishes a SAPI=0 link on DCCH */
	dt := f_est_dchan(f_rnd_ra_cs(), 23, gen_l3_valid_payload());

	/* MSC sends some data on (not yet established) SAPI=3 link */
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSAP_DTAP(gen_l3_valid_payload(), '03'O)));
	/* BSC attempts to establish a SAPI=3 link on DCCH */
	rx_rsl := f_exp_ipa_rx(tr_RSL_EST_REQ(dt.rsl_chan_nr, tr_RslLinkID_DCCH(3)));

	/* BTS sends unexpected ERROR INDication on SAPI=3 */
	f_ipa_tx(ts_RSL_ERROR_IND(dt.rsl_chan_nr, ts_RslLinkID_DCCH(3), ''O));
	/* We expect to receive BSSMAP SAPI N Reject message from the BSC */
	f_exp_sapi_n_reject(3, GSM0808_CAUSE_BSS_NOT_EQUIPPED);

	/* Clean up the connection */
	BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));
	f_expect_chan_rel(dt.rsl_chan_nr, expect_rll_rel_req := false);

	f_shutdown_helper();
}

/* Check if we get SAPI N Reject due to a SAPI=3 link establishment timeout */
testcase TC_rll_timeout_sapi_n_reject() runs on test_CT {
	var RSL_Message rx_rsl;
	var DchanTuple dt;

	f_init(1);

	/* MS establishes a SAPI=0 link on DCCH */
	dt := f_est_dchan(f_rnd_ra_cs(), 23, gen_l3_valid_payload());

	/* MSC sends some data on (not yet established) SAPI=3 link */
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSAP_DTAP(gen_l3_valid_payload(), '03'O)));
	/* BSC attempts to establish a SAPI=3 link on DCCH */
	rx_rsl := f_exp_ipa_rx(tr_RSL_EST_REQ(dt.rsl_chan_nr, tr_RslLinkID_DCCH(3)));

	/* MS does not respond, so the link establishment timeout triggers SAPI N Reject */
	f_exp_sapi_n_reject(3, GSM0808_CAUSE_BSS_NOT_EQUIPPED, T_val := 8.0);

	/* Clean up the connection */
	BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));
	f_expect_chan_rel(dt.rsl_chan_nr, expect_rll_rel_req := false);

	f_shutdown_helper();
}

/* Check DLCI CC (Control Channel type) bits in SAPI N Reject */
testcase TC_rll_sapi_n_reject_dlci_cc() runs on test_CT {
	var RSL_Message rx_rsl;
	var DchanTuple dt;

	f_init(1);

	/* MS establishes a SAPI=0 link on DCCH */
	dt := f_est_dchan(f_rnd_ra_cs(), 23, gen_l3_valid_payload());

	/* MSC sends some data on (not yet established) SAPI=3 link */
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSAP_DTAP(gen_l3_valid_payload(), '03'O)));
	/* BSC attempts to establish a SAPI=3 link on DCCH */
	rx_rsl := f_exp_ipa_rx(tr_RSL_EST_REQ(dt.rsl_chan_nr, tr_RslLinkID_DCCH(3)));

	/* MS sends unexpected ERROR INDication on DCCH/ACCH SAPI=3 */
	f_ipa_tx(ts_RSL_ERROR_IND(dt.rsl_chan_nr, ts_RslLinkID_DCCH(3), ''O));
	f_exp_sapi_n_reject(3, GSM0808_CAUSE_BSS_NOT_EQUIPPED, '10'B);

	/* Clean up the connection */
	BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));
	f_expect_chan_rel(dt.rsl_chan_nr, expect_rll_rel_req := false);

	f_shutdown_helper();
}

testcase TC_si_default() runs on test_CT {
	f_init(0);
	f_init_bts_and_check_sysinfo(0, expect_si := SystemInformationConfig_default);
	f_shutdown_helper();
}

/* We're testing SI2quater with lists of EARFCNs. Instead of just incrementing EARFCNs, also pick some from the edges of
 * the entire value range. This function provides the same EARFCN numbers for the same earfcn_index  */
private function f_test_si2quater_earfcn_by_idx(integer earfcn_index) return uint16_t
{
	select (earfcn_index) {
	case (0) {
		/* E-ARFCN 111 is already added in the osmo-bsc.cfg */
		return 111;
	}
	case (1) {
		return 1;
	}
	case (2) {
		return 0;
	}
	case (3) {
		return 65535;
	}
	case else {
		return 23 * (earfcn_index - 3);
	}
	}
}

function f_test_si2quater(integer total_earfcns, template SystemInformationConfig expect_si,
			  template CellSelIndValue expect_cells := omit) runs on test_CT {

	f_init(0);

	/* E-ARFCN 111 is already added in the osmo-bsc.cfg, so only add more arfcns if total_earfcns > 1 */
	for (var integer i := 1; i < total_earfcns; i := i + 1) {
		f_bts_0_cfg(BSCVTY, {"si2quater neighbor-list add earfcn " & int2str(f_test_si2quater_earfcn_by_idx(i))
				     & " thresh-hi 20 thresh-lo 10 prio 3 qrxlv 22 meas 3"});
	}

	f_init_bts_and_check_sysinfo(0, expect_si := expect_si);

	if (not istemplatekind(expect_cells, "omit")) {
		/* Also check that RR Channel Release contains these EARFCNs.
		 * (copied code from TC_chan_rel_hard_clear_csfb) */
		var BSSAP_N_DATA_ind rx_di;
		var DchanTuple dt;

		dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());
		/* Send CommonID with some random PLMN (BSC doesn't take it into account
		 * yet when generating the EUTRAN neigh list in RR CHannel Release) */
		BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_CommonId('001019876543210'H, '323454'O)));

		/* Instruct BSC to clear channel */
		var BssmapCause cause := 0;
		BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommandCSFB(cause)));

		/* expect Clear Complete from BSC on A */
		BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_ClearComplete)) {
			/* release the SCCP connection */
			BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));
		}

		f_expect_chan_rel(dt.rsl_chan_nr, expect_rll_rel_req := false, expect_cells := expect_cells);
	}

	for (var integer i := 1; i < total_earfcns; i := i + 1) {
		f_bts_0_cfg(BSCVTY, {"si2quater neighbor-list del earfcn " & int2str(f_test_si2quater_earfcn_by_idx(i))});
	}
}

private function f_tr_si2quater_earfcns(integer count) return template SI2quaterRestOctetsList
{
	var template SI2quaterRestOctetsList si2quater := {};
	var integer si2quater_count := (count + 2) / 3;

	for (var integer i := 0; i < count; i := i + 1) {
		var integer earfcn := f_test_si2quater_earfcn_by_idx(i);
		var integer index := i / 3;
		var integer earfcn_index := i mod 3;
		if (index >= lengthof(si2quater)) {
			si2quater[index] := tr_SI2quaterRestOctets_EUTRAN(index := index, count := si2quater_count - 1);
		}
		si2quater[index].rel_additions.rel5.rel6.rel7.rel8.prio_eutran_params_desc.desc.eutran_params_desc.desc.repeated_neigh_cells[0].cell_desc_list[earfcn_index] := tr_EUTRAN_CellDesc_default(e_arfcn := earfcn);
	}

	return si2quater;
}

private function f_tr_rr_chan_rel_earfcns(integer count) return template CellSelIndValue
{
	var template CellSelIndValue_EUTRAN_Descrs cells := {};

	/* the lte neighbors must match the config & vty to pass this test */
	for (var integer i := 0; i < count; i := i + 1) {
		var integer earfcn := f_test_si2quater_earfcn_by_idx(i);
		cells[i] := tr_CellSelIndValue_EUTRAN_Descr(earfcn, '1'B, 3);
	}

	return tr_CellSelIndValue_EUTRAN(cells);
}

private function f_tc_si2quater_n_earfcns(integer n) runs on test_CT
{
	var template SystemInformationConfig sic := SystemInformationConfig_default;
	sic.si2quater := f_tr_si2quater_earfcns(n);
	var template CellSelIndValue cells := f_tr_rr_chan_rel_earfcns(n);
	f_test_si2quater(n, sic, cells);
}

testcase TC_si2quater_2_earfcns() runs on test_CT {
	f_tc_si2quater_n_earfcns(2);
	f_shutdown_helper();
}

testcase TC_si2quater_3_earfcns() runs on test_CT {
	f_tc_si2quater_n_earfcns(3);
	f_shutdown_helper();
}

testcase TC_si2quater_4_earfcns() runs on test_CT {
	f_tc_si2quater_n_earfcns(4);
	f_shutdown_helper();
}

testcase TC_si2quater_5_earfcns() runs on test_CT {
	f_tc_si2quater_n_earfcns(5);
	f_shutdown_helper();
}

testcase TC_si2quater_6_earfcns() runs on test_CT {
	f_tc_si2quater_n_earfcns(6);
	f_shutdown_helper();
}

testcase TC_si2quater_12_earfcns() runs on test_CT {
	f_tc_si2quater_n_earfcns(12);
	f_shutdown_helper();
}

testcase TC_si2quater_23_earfcns() runs on test_CT {
	f_tc_si2quater_n_earfcns(23);
	f_shutdown_helper();
}

testcase TC_si2quater_32_earfcns() runs on test_CT {
	f_tc_si2quater_n_earfcns(32);
	f_shutdown_helper();
}

testcase TC_si2quater_33_earfcns() runs on test_CT {
	f_tc_si2quater_n_earfcns(33);
	f_shutdown_helper();
}

testcase TC_si2quater_42_earfcns() runs on test_CT {
	f_tc_si2quater_n_earfcns(42);
	f_shutdown_helper();
}

testcase TC_si2quater_48_earfcns() runs on test_CT {
	f_tc_si2quater_n_earfcns(48);
	f_shutdown_helper();
}

/* verify the VTY error response when adding too many EARFCNs, and showing that osmo-bsc still sends 16 SI2quater with
 * 48 EARFCNs. */
testcase TC_si2quater_49_earfcns() runs on test_CT {
	var template SystemInformationConfig sic := SystemInformationConfig_default;
	sic.si2quater := f_tr_si2quater_earfcns(48); /* 48, not 49! */
	f_init(0);

	for (var integer i := 1; i < 48; i := i + 1) {
		f_bts_0_cfg(BSCVTY, {"si2quater neighbor-list add earfcn " & int2str(f_test_si2quater_earfcn_by_idx(i))
				     & " thresh-hi 20 thresh-lo 10 prio 3 qrxlv 22 meas 3"});
	}

	/* The 49th EARFCN no longer fits, expect VTY error */
	f_vty_enter_cfg_bts(BSCVTY, 0);
	var charstring vty_error;
	vty_error := f_vty_transceive_ret(BSCVTY,
		"si2quater neighbor-list add earfcn 70 thresh-hi 20 thresh-lo 10 prio 3 qrxlv 22 meas 3")
	f_vty_transceive(BSCVTY, "end");

	if (f_strstr(vty_error, "Unable to add ARFCN 70") >= 0) {
		log("Got expected VTY error: ", vty_error);
		setverdict(pass);
	} else {
		setverdict(fail, "Expected the 49th EUTRAN ARFCN to be rejected by vty config, got: ", vty_error);
	}

	f_init_bts_and_check_sysinfo(0, expect_si := sic);

	for (var integer i := 1; i < 48; i := i + 1) {
		f_bts_0_cfg(BSCVTY, {"si2quater neighbor-list del earfcn " & int2str(f_test_si2quater_earfcn_by_idx(i))});
	}
	f_shutdown_helper();
}

private function f_acc09_count_allowed(AccessControlClass acc) return uint8_t
{
	var uint8_t count := 0;
	for (var integer i := 5; i < 16; i := i + 1) {
		if (acc[i] == '0'B) { /* the list marks barred, we count allowed */
			count := count + 1;
		}
	}
	return count;
}

private function f_recv_next_si1(integer rsl_idx := 0) runs on test_CT return SystemInformationType1
{
	var ASP_RSL_Unitdata rx_rsl_ud;
	var SystemInformationType1 last_si1;

	timer T := 30.0;
	T.start;
	alt {
	[] IPA_RSL[rsl_idx][0].receive(tr_ASP_RSL_UD((tr_RSL_NO_BCCH_INFO,
						      tr_RSL_BCCH_INFO,
						      tr_RSL_NO_SACCH_FILL,
						      tr_RSL_SACCH_FILL))) -> value rx_rsl_ud {
		f_sysinfo_seen(rsl_idx, rx_rsl_ud.rsl);
		if (g_system_information[rsl_idx].si1 == omit) {
			repeat;
		}
		last_si1 := g_system_information[rsl_idx].si1;
		g_system_information[rsl_idx].si1 := omit;
		T.stop;
		}
	[] IPA_RSL[rsl_idx][0].receive { repeat; }
	[] T.timeout { setverdict(fail, "Timeout receiving next SI1"); }
	}
	return last_si1;
}

/* verify ACC rotate feature */
testcase TC_si_acc_rotate() runs on test_CT {
	var template SystemInformationConfig sic := SystemInformationConfig_default;
	var SystemInformationType1 last_si1;
	var AccessControlClass acc;
	var uint8_t count;
	var integer times_allowed[10] := { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

	f_init(0, guard_timeout := 60.0);

	f_bts_0_cfg(BSCVTY, {"rach access-control-class 5 barred",
			     "access-control-class-rotate 3",
			     "access-control-class-rotate-quantum 1"});

	/* Init and get first sysinfo */
	f_init_bts_and_check_sysinfo(0, expect_si := ?);

	for (var integer i:= 0; i < 20; i := i + 1) {
		last_si1 := f_recv_next_si1(0);
		acc := last_si1.rach_control.acc;
		count := f_acc09_count_allowed(acc);
		log("RSL: GOT SI1 ACC len=", count, ": ", acc);

		if (count != 3) {
			log("RSL: EXPECTED SI ACC len=3");
			setverdict(fail, "received SI does not match expectations");
			break;
		}

		for (var integer j := 0; j < 10; j := j + 1) {
			if (acc[16 - 1 - j] == '0'B) { /* the list marks barred, we count allowed */
				times_allowed[j] := times_allowed[j] + 1;
			}
		}
	}

	for (var integer j := 0; j < 10; j := j + 1) {
		log("ACC", j, " allowed ", times_allowed[j], " times" );
		if (j != 5 and times_allowed[j] < 3) {
			setverdict(fail, "ACC", j, " ERROR: allowed ", times_allowed[j], " < 1 times");
		} else if (j == 5 and times_allowed[j] > 0) {
			setverdict(fail, "ACC", j, " ERROR: allowed ", times_allowed[j], " > 0 times");
		}
	}

	f_bts_0_cfg(BSCVTY, {"access-control-class-rotate 10",
			     "rach access-control-class 5 allowed"});
	f_shutdown_helper();
}

/* verify ACC startup ramp+rotate feature */
testcase TC_si_acc_ramp_rotate() runs on test_CT {
	var template SystemInformationConfig sic := SystemInformationConfig_default;
	var SystemInformationType1 last_si1;
	var AccessControlClass acc;
	var ASP_RSL_Unitdata rx_rsl_ud;
	var uint8_t count;
	var uint8_t prev_count;
	var integer times_allowed[10] := { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

	f_init(0, guard_timeout := 80.0);

	f_bts_0_cfg(BSCVTY, {"rach access-control-class 4 barred",
			     "access-control-class-rotate 0",
			     "access-control-class-rotate-quantum 1",
			     "access-control-class-ramping",
			     "access-control-class-ramping-step-interval 5",
			     "access-control-class-ramping-step-size 5"});

	/* Init and get first sysinfo */
	f_init_bts_and_check_sysinfo(0, expect_si := ?);
	last_si1 := g_system_information[0].si1;
	acc := last_si1.rach_control.acc;
	count := f_acc09_count_allowed(acc);
	/* Adm subset size was set to 0 above, so wait until all ACC are barred */
	while (count > 0) {
		last_si1 := f_recv_next_si1(0);
		acc := last_si1.rach_control.acc;
		count := f_acc09_count_allowed(acc);
		log("RSL: wait len()=0: GOT SI1 ACC len=", count, ": ", acc);
	}

	/* Increase adm subset size, we should see ramping start up */
	f_bts_0_cfg(BSCVTY, {"access-control-class-rotate 10"});
	prev_count := 0;
	while (true) {
		last_si1 := f_recv_next_si1(0);
		acc := last_si1.rach_control.acc;
		count := f_acc09_count_allowed(acc);
		log("RSL: GOT SI1 ACC len=", count, ": ", acc);

		if (prev_count > count) {
			setverdict(fail, "ACC allowed count dropped while expecting grow: ", prev_count, " -> ", count);
			break;
		}

		if (count == 9) {
			break; /* Maximum reached (10 - 1 perm barred), done here */
		}

		prev_count := count;
	}

	setverdict(pass);

	f_bts_0_cfg(BSCVTY, {"access-control-class-rotate 10",
			     "rach access-control-class 4 allowed",
			     "no access-control-class-ramping"});
	f_shutdown_helper();
}

/* Verify NCC Permitted in System Information Type 2
 * 3GPP TS 44.018 § 10.5.2.27 */
testcase TC_si2_ncc_permitted() runs on test_CT {
	var ASP_RSL_Unitdata rx_rsl_ud;
	timer T := 5.0;
	var BIT8 exp;

	f_init_vty();

	/* Set NCC Permitted to 6 7 8 */
	f_vty_enter_cfg_bts(BSCVTY, 0);
	f_vty_transceive(BSCVTY, "ncc-permitted 6 7 8");
	f_vty_transceive(BSCVTY, "end");
	exp := '11100000'B;

	f_init(1);
	T.start;

	/* Check value in SI 2 */
	alt {
	[] IPA_RSL[0][0].receive(tr_ASP_RSL_UD(tr_RSL_BCCH_INFO(RSL_SYSTEM_INFO_2))) -> value rx_rsl_ud {
		var RSL_IE_Body ie := rx_rsl_ud.rsl.ies[2].body; /* FULL BCCH Information IE */
		var SystemInformation si := dec_SystemInformation(ie.other.payload);

		if (si.header.message_type != SYSTEM_INFORMATION_TYPE_2) {
			setverdict(fail, "RSL FULL BCCH Information IE contains: ", si);
			repeat;
		}

		if (si.payload.si2.ncc_permitted != exp) {
			setverdict(fail, "NCC Permitted is ", si.payload.si2.ncc_permitted, " but expected ", exp);
			break;
		}

		}
	[] IPA_RSL[0][0].receive { repeat; }
	[] T.timeout {
		setverdict(fail, "Timeout waiting for RSL BCCH INFOrmation (SI2)");
		}
	}

	/* Reset NCC Permitted to default (all permitted). This is already
	 * getting tested in TC_si_default(). */
	f_vty_enter_cfg_bts(BSCVTY, 0);
	f_vty_transceive(BSCVTY, "ncc-permitted all");
	f_vty_transceive(BSCVTY, "end");

	f_shutdown_helper();
}

testcase TC_ctrl_msc_connection_status() runs on test_CT {
	var charstring ctrl_resp;

	f_init(1);

	/* See https://osmocom.org/issues/2729 */
	f_ctrl_get_exp(IPA_CTRL, "msc_connection_status", "connected");
	f_shutdown_helper();
}

testcase TC_ctrl_msc0_connection_status() runs on test_CT {
	var charstring ctrl_resp;

	f_init(1);

	f_ctrl_get_exp(IPA_CTRL, "msc.0.connection_status", "connected");
	f_shutdown_helper();
}

/* Verify correct stats on the number of configured and connected MSCs */
private function f_tc_stat_num_msc_connected_msc_connhdlr(integer expect_num_msc_connected) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var StatsDExpects expect := {
		{ name := "TTCN3.bsc.0.num_msc.connected", mtype := "g", min := expect_num_msc_connected, max := expect_num_msc_connected },
		{ name := "TTCN3.bsc.0.num_msc.total", mtype := "g", min := NUM_MSC, max := NUM_MSC }
	};
	f_statsd_expect(expect);
}

private function f_tc_stat_num_msc_connected_test_ct(void_fn tc_fn, integer nr_msc) runs on test_CT
{
	var MSC_ConnHdlr vc_conn;

	f_init(nr_bts := 1, handler_mode := true, nr_msc := nr_msc);
	f_sleep(1.0);
	vc_conn := f_start_handler(tc_fn);
	vc_conn.done;

	/* Also verify stat exposed on CTRL interface */
	f_ctrl_get_exp(IPA_CTRL, "stat_item.last.bsc.0.num_msc:connected", int2str(nr_msc));
	f_ctrl_get_exp(IPA_CTRL, "stat_item.last.bsc.0.num_msc:total", int2str(NUM_MSC));

	f_shutdown_helper();
}

/* Verify that when 1 MSC is active, that num_msc:connected reports 1. */
private function f_tc_stat_num_msc_connected_1(charstring id) runs on MSC_ConnHdlr {
	f_tc_stat_num_msc_connected_msc_connhdlr(1);
}
testcase TC_stat_num_msc_connected_1() runs on test_CT {
	f_tc_stat_num_msc_connected_test_ct(refers(f_tc_stat_num_msc_connected_1), 1);
}

/* Verify that when 2 MSCs are active, that num_msc:connected reports 2. */
private function f_tc_stat_num_msc_connected_2(charstring id) runs on MSC_ConnHdlr {
	f_tc_stat_num_msc_connected_msc_connhdlr(2);
}
testcase TC_stat_num_msc_connected_2() runs on test_CT {
	f_tc_stat_num_msc_connected_test_ct(refers(f_tc_stat_num_msc_connected_2), 2);
}

/* Verify that when 3 MSCs are active, that num_msc:connected reports 3. */
private function f_tc_stat_num_msc_connected_3(charstring id) runs on MSC_ConnHdlr {
	f_tc_stat_num_msc_connected_msc_connhdlr(3);
}
testcase TC_stat_num_msc_connected_3() runs on test_CT {
	f_tc_stat_num_msc_connected_test_ct(refers(f_tc_stat_num_msc_connected_3), 3);
}

/* When the last local IPA/SCTP link/ASP is terminated, last AS goes inactive
 * and a SCCP N-PCSTATE.ind (unavailable) should arrive to upper layers.
 * As a result, osmo-bsc should immediately mark the MSC link as unusable.
 * The scenario is slightly different in AoIP and SCCPLite:
 * - AoIP: the SCCP N-PCSTATE is triggered by instructing the STP to
 * shutdown the ASP serving the BSC, which will close the SCTP assoc between
 * itself and IUT (BSC), which should then generate an N-PCSTATE.ind.
 * - SCCPLite: Since osmo-bsc is connected directly to us (TTCN3), terminate our
 *   local SCTP conn to trigger the actions.
 */
testcase TC_stat_msc_sctp_disconnected() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var integer exp_total_num_msc;

	f_init(nr_bts := 1, handler_mode := true, nr_msc := 1);
	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_AoIP) {
		exp_total_num_msc := NUM_MSC;
		f_init_vty_stp();
	} else {
		exp_total_num_msc := 1;
	}
	f_sleep(1.0);

	f_ctrs_msc_init();

	f_ctrl_get_exp(IPA_CTRL, "stat_item.last.bsc.0.num_msc:connected", int2str(1));
	f_ctrl_get_exp(IPA_CTRL, "stat_item.last.bsc.0.num_msc:total", int2str(exp_total_num_msc));

	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_AoIP) {
		f_logp(BSCVTY, "Shutting down BSC ASP in STP");
		f_vty_stp_bsc_asp_cmd("shutdown");
	} else {
		f_logp(BSCVTY, "Disconnecting msc0");
		f_bssap_idx_disconnect(0);
	}

	/* Wait for AS PENDING ->INACTIVE transition (T(r) timeout after 2000msec): */

	f_ctrl_exp_trap(IPA_CTRL, "msc." & int2str(0) & ".connection_status", "disconnected", timeout_val := 5.0);
	f_ctrl_get_exp(IPA_CTRL, "stat_item.last.bsc.0.num_msc:connected", int2str(0));
	f_ctrl_get_exp(IPA_CTRL, "stat_item.last.bsc.0.num_msc:total", int2str(exp_total_num_msc));

	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_AoIP) {
		f_logp(BSCVTY, "Restarting BSC ASP in STP");
		f_vty_stp_bsc_asp_cmd("no shutdown");
	} else {
		f_logp(BSCVTY, "Waiting for bsc to reconnect to msc0");
	}

	f_ctrl_exp_trap(IPA_CTRL, "msc." & int2str(0) & ".connection_status", "connected", timeout_val := 10.0);
	f_ctrl_get_exp(IPA_CTRL, "stat_item.last.bsc.0.num_msc:connected", int2str(1));
	f_ctrl_get_exp(IPA_CTRL, "stat_item.last.bsc.0.num_msc:total", int2str(exp_total_num_msc));

	f_shutdown_helper();
}

/* Verify correct stats on the number of configured and connected MSCs */
private function f_tc_stat_num_bts_connected_msc_connhdlr(integer expect_num_bts_connected) runs on MSC_ConnHdlr {
	var integer num_trx_connected := 0;
	var integer num_trx_total := 0;

	for (var integer i := 0; i < lengthof(c_BtsParams); i := i + 1) {
		var integer trx_num := c_BtsParams[i].trx_num;
		num_trx_total := num_trx_total + trx_num;
		if (i < expect_num_bts_connected) {
			num_trx_connected := num_trx_connected + trx_num;
		}
	}

	var StatsDExpects expect := {
		{ name := "TTCN3.bsc.0.num_bts.oml_connected", mtype := "g", min := expect_num_bts_connected, max := NUM_BTS_CFG },
		{ name := "TTCN3.bsc.0.num_bts.all_trx_rsl_connected", mtype := "g", min := expect_num_bts_connected, max := expect_num_bts_connected },
		{ name := "TTCN3.bsc.0.num_bts.total", mtype := "g", min := NUM_BTS_CFG, max := NUM_BTS_CFG },
		{ name := "TTCN3.bsc.0.num_trx.rsl_connected", mtype := "g", min := num_trx_connected, max := num_trx_connected },
		{ name := "TTCN3.bsc.0.num_trx.total", mtype := "g", min := num_trx_total, max := num_trx_total }
	};

	g_pars := f_gen_test_hdlr_pars();
	f_statsd_expect(expect);
}

private function f_tc_stat_num_bts_connected_test_ct(void_fn tc_fn, integer nr_bts) runs on test_CT {
	var integer num_trx_connected := 0;
	var integer num_trx_total := 0;
	var MSC_ConnHdlr vc_conn;

	f_init(nr_bts := nr_bts, handler_mode := true, nr_msc := 1);
	f_sleep(1.0);
	vc_conn := f_start_handler(tc_fn);
	vc_conn.done;

	for (var integer i := 0; i < lengthof(c_BtsParams); i := i + 1) {
		var integer trx_num := c_BtsParams[i].trx_num;
		num_trx_total := num_trx_total + trx_num;
		if (i < nr_bts) {
			num_trx_connected := num_trx_connected + trx_num;
		}
	}

	/* Also verify stat exposed on CTRL interface */
	f_ctrl_get_exp(IPA_CTRL, "stat_item.last.bsc.0.num_bts:all_trx_rsl_connected", int2str(nr_bts));
	f_ctrl_get_exp(IPA_CTRL, "stat_item.last.bsc.0.num_bts:total", int2str(NUM_BTS_CFG));
	f_ctrl_get_exp(IPA_CTRL, "stat_item.last.bsc.0.num_trx:rsl_connected", int2str(num_trx_connected));
	f_ctrl_get_exp(IPA_CTRL, "stat_item.last.bsc.0.num_trx:total", int2str(num_trx_total));

	/* Verify rf_states exposed on CTRL interface */
	var charstring expect_net_rf_states := "";
	for (var integer i := 0; i < NUM_BTS_CFG; i := i + 1) {
		var charstring expect_bts_rf_states := "";

		for (var integer j := 0; j < c_BtsParams[i].trx_num; j := j + 1) {
			expect_bts_rf_states := expect_bts_rf_states &
						int2str(i) & "," & int2str(j) & ",";
			if (i < NUM_BTS) {
				/* In these tests, OML for the first NUM_BTS are always connected via osmo-bts-omldummy */
				expect_bts_rf_states := expect_bts_rf_states & "operational,unlocked,";
			} else {
				/* For remaining i < NUM_BTS_CFG, OML is not connected, i.e. inoperational */
				expect_bts_rf_states := expect_bts_rf_states & "inoperational,locked,";
			}
			/* The RF policy is still global in osmo-bsc, i.e. always "on" */
			expect_bts_rf_states := expect_bts_rf_states & "on,";
			if (i < nr_bts) {
				/* For BTS where RSL is connected, the RSL state will be "up" */
				expect_bts_rf_states := expect_bts_rf_states & "rsl-up;";
			} else {
				expect_bts_rf_states := expect_bts_rf_states & "rsl-down;";
			}
		}

		f_ctrl_get_exp(IPA_CTRL, "bts." & int2str(i) & ".rf_states", expect_bts_rf_states);
		expect_net_rf_states := expect_net_rf_states & expect_bts_rf_states;
	}
	f_ctrl_get_exp(IPA_CTRL, "rf_states", expect_net_rf_states);

	f_shutdown_helper();
}

/* Verify that when 1 BTS is connected, that num_{bts,trx}:*_connected reports 1. */
private function f_tc_stat_num_bts_connected_1(charstring id) runs on MSC_ConnHdlr {
	f_tc_stat_num_bts_connected_msc_connhdlr(1);
}
testcase TC_stat_num_bts_connected_1() runs on test_CT {
	f_tc_stat_num_bts_connected_test_ct(refers(f_tc_stat_num_bts_connected_1), 1);
}

/* Verify that when 2 BTS is connected, that num_{bts,trx}:*_connected reports 2. */
private function f_tc_stat_num_bts_connected_2(charstring id) runs on MSC_ConnHdlr {
	f_tc_stat_num_bts_connected_msc_connhdlr(2);
}
testcase TC_stat_num_bts_connected_2() runs on test_CT {
	f_tc_stat_num_bts_connected_test_ct(refers(f_tc_stat_num_bts_connected_2), 2);
}

/* Verify that when 3 BTS is connected, that num_{bts,trx}:*_connected reports 3. */
private function f_tc_stat_num_bts_connected_3(charstring id) runs on MSC_ConnHdlr {
	f_tc_stat_num_bts_connected_msc_connhdlr(3);
}
testcase TC_stat_num_bts_connected_3() runs on test_CT {
	f_tc_stat_num_bts_connected_test_ct(refers(f_tc_stat_num_bts_connected_3), 3);
}

testcase TC_ctrl() runs on test_CT {
	var charstring ctrl_resp;

	f_init(1);

	/* all below values must match the osmo-bsc.cfg config file used */

	f_ctrl_get_exp(IPA_CTRL, "mcc", "001");
	f_ctrl_get_exp(IPA_CTRL, "mnc", "01");
	f_ctrl_get_exp(IPA_CTRL, "number-of-bts", "4");

	var integer bts_nr := 0;
	f_ctrl_get_exp_bts(IPA_CTRL, bts_nr, "location-area-code", "1");
	f_ctrl_get_exp_bts(IPA_CTRL, bts_nr, "cell-identity", "0");
	f_ctrl_get_exp_bts(IPA_CTRL, bts_nr, "oml-connection-state", "connected");
	f_ctrl_get_exp_bts(IPA_CTRL, bts_nr, "gprs-mode", "gprs");
	f_ctrl_get_exp_bts(IPA_CTRL, bts_nr, "rf_state", "operational,unlocked,on");
	f_ctrl_get_exp_trx(IPA_CTRL, bts_nr, 0, "arfcn", "871");
	f_ctrl_get_exp_trx(IPA_CTRL, bts_nr, 0, "max-power-reduction", "20");

	var integer uptime := str2int(f_ctrl_get_bts(IPA_CTRL, bts_nr, "oml-uptime"));
	f_sleep(2.0);
	if (str2int(f_ctrl_get_bts(IPA_CTRL, bts_nr, "oml-uptime")) < uptime+1) {
		setverdict(fail, "oml-uptime not incrementing as expected");
	}
	/* TODO: Disconnect RSL, imply that OML is disconnected and check for uptime zero? */

	f_ctrl_get_exp_ratectr_abs(IPA_CTRL, "bsc", 0, "paging:attempted", 0);

	f_shutdown_helper();
}

/* Verify that Upon receival of SET "location", BSC forwards a TRAP
  "location-state" over the SCCPlite IPA conn */
testcase TC_ctrl_location() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var integer bts_nr := 0;

	f_init(1, true);
	f_sleep(1.0);

	f_ctrl_set_bts(IPA_CTRL, bts_nr, "location", "1234567,fix3d,0.340000,0.560000,0.780000");
	f_ctrl_exp_trap(SCCPLITE_IPA_CTRL, "bts." & int2str(bts_nr) & ".location-state",
			"1234567,fix3d,0.340000,0.560000,0.780000,operational,unlocked,on,001,01");

	f_ctrl_set(SCCPLITE_IPA_CTRL, "rf_locked", "1");
	f_sleep(2.0);

	f_ctrl_set_bts(IPA_CTRL, bts_nr, "location", "1234888,fix3d,0.350000,0.570000,0.790000");
	f_ctrl_exp_trap(SCCPLITE_IPA_CTRL, "bts." & int2str(bts_nr) & ".location-state",
			"1234888,fix3d,0.350000,0.570000,0.790000,operational,locked,off,001,01");

	/* should match the one from  config */
	f_ctrl_set(SCCPLITE_IPA_CTRL, "rf_locked", "0");

	f_shutdown_helper();
}


/***********************************************************************
 * Paging Testing
 ***********************************************************************/

type record Cell_Identity {
	GsmMcc		mcc,
	GsmMnc		mnc,
	GsmLac		lac,
	GsmCellId	ci
};
private const Cell_Identity cid := { '001'H, '01'H, 1, 0 };
private const Cell_Identity unknown_cid := { '678'H, 'f90'H, 1, 0 };

type set of integer BtsIdList;

private function f_bts_in_list(integer bts_id, BtsIdList bts_ids) return boolean {
	for (var integer j := 0; j < sizeof(bts_ids); j := j + 1) {
		if (bts_id == bts_ids[j]) {
			return true;
		}
	}
	return false;
}

/* core paging test helper function; used by most paging test cases */
private function f_pageing_helper(hexstring imsi,
				  template BSSMAP_FIELD_CellIdentificationList cid_list,
				  BtsIdList bts_ids := { 0 },
				  template RSL_ChanNeeded rsl_chneed := omit,
				  template (omit) OCT4 tmsi := omit) runs on test_CT
{
	var template BSSMAP_IE_ChannelNeeded bssmap_chneed;
	var template MobileIdentityV mi;
	var RSL_Message rx_rsl;
	var integer paging_group := hex2int(imsi[lengthof(imsi)-1]);
	var integer i;

	f_init();

	/* Clear the queue, it might still contain stuff like BCCH FILLING */
	for (i := 0; i < NUM_BTS; i := i + 1) {
		IPA_RSL[i][0].clear;
	}

	if (isvalue(rsl_chneed)) {
		/* The values of 08.08 3.2.2.36 and 08.58 9.3.40 are luckily identical */
		bssmap_chneed := ts_BSSMAP_IE_ChanNeeded(int2bit(enum2int(valueof(rsl_chneed)),2));
	} else {
		bssmap_chneed := omit;
	}

	BSSAP.send(ts_BSSAP_UNITDATA_req(g_bssap[0].sccpa.sccp_addr_peer, g_bssap[0].sccpa.sccp_addr_own,
					 ts_BSSMAP_Paging(imsi, cid_list, tmsi, bssmap_chneed)));

	if (not istemplatekind(tmsi, "omit")) {
		mi := t_MI_TMSI(tmsi);
	} else {
		mi := tr_MI_IMSI(imsi);
	}

	for (i := 0; i < sizeof(bts_ids); i := i + 1) {
		rx_rsl := f_exp_ipa_rx(tr_RSL_PAGING_CMD(mi), idx := {bts_ids[i], 0});
		/* check channel type, paging group */
		if (rx_rsl.ies[1].body.paging_group != paging_group) {
			setverdict(fail, "Paging for wrong paging group");
		}
		if (ispresent(rsl_chneed) and
		    rx_rsl.ies[3].body.chan_needed.chan_needed != valueof(rsl_chneed)) {
			setverdict(fail, "RSL Channel Needed != BSSMAP Channel Needed");
		}
	}
	f_sleep(2.0);
	/* do a quick check on all not-included BTSs if they received paging */
	for (i := 0; i < NUM_BTS; i := i + 1) {
		timer T := 0.1;
		if (f_bts_in_list(i, bts_ids)) {
			continue;
		}
		T.start;
		alt {
		[] IPA_RSL[i][0].receive(tr_ASP_RSL_UD(tr_RSL_PAGING_CMD(mi))) {
			setverdict(fail, "Paging on BTS ", i, " which is not part of ", bts_ids);
			}
		[] IPA_RSL[i][0].receive { repeat; }
		[] T.timeout { }
		}
	}

	setverdict(pass);
}

const BtsIdList c_BtsId_all := { 0, 1, 2 };
const BtsIdList c_BtsId_none := { };
const BtsIdList c_BtsId_LAC1 := { 0, 1 };
const BtsIdList c_BtsId_LAC2 := { 2 };

/* PAGING by IMSI + TMSI */
testcase TC_paging_imsi_nochan() runs on test_CT {
	var BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := valueof(ts_BSSMAP_CIL_noCell);
	f_pageing_helper('001010100000001'H, cid_list, c_BtsId_all, omit, omit);
	f_shutdown_helper();
}

/* PAGING by IMSI + TMSI */
testcase TC_paging_tmsi_nochan() runs on test_CT {
	var BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := valueof(ts_BSSMAP_CIL_noCell);
	f_pageing_helper('001010100000001'H, cid_list, c_BtsId_all, omit, 'A1B2C301'O);
	f_shutdown_helper();
}

/* Paging with different "channel needed' values */
testcase TC_paging_tmsi_any() runs on test_CT {
	var BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := valueof(ts_BSSMAP_CIL_noCell);
	f_pageing_helper('001010100000002'H, cid_list, c_BtsId_all, RSL_CHANNEED_ANY, 'A1B2C302'O);
	f_shutdown_helper();
}
testcase TC_paging_tmsi_sdcch() runs on test_CT {
	var BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := valueof(ts_BSSMAP_CIL_noCell);
	f_pageing_helper('001010100000003'H, cid_list, c_BtsId_all, RSL_CHANNEED_SDCCH, 'A1B2C303'O);
	f_shutdown_helper();
}
testcase TC_paging_tmsi_tch_f() runs on test_CT {
	var BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := valueof(ts_BSSMAP_CIL_noCell);
	f_pageing_helper('001010000000004'H, cid_list, c_BtsId_all, RSL_CHANNEED_TCH_F, 'A1B2C304'O);
	f_shutdown_helper();
}
testcase TC_paging_tmsi_tch_hf() runs on test_CT {
	var BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := valueof(ts_BSSMAP_CIL_noCell);
	f_pageing_helper('001010000000005'H, cid_list, c_BtsId_all, RSL_CHANNEED_TCH_ForH, 'A1B2C305'O);
	f_shutdown_helper();
}

/* Paging by CGI */
testcase TC_paging_imsi_nochan_cgi() runs on test_CT {
	var template BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := { cIl_CGI := { ts_BSSMAP_CI_CGI(cid.mcc, cid.mnc, cid.lac, cid.ci) } };
	f_pageing_helper('001010000000006'H, cid_list, { 0 });
	f_shutdown_helper();
}

/* Paging by LAC+CI */
testcase TC_paging_imsi_nochan_lac_ci() runs on test_CT {
	var template BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := { cIl_LAC_CI := { ts_BSSMAP_CI_LAC_CI(cid.lac, cid.ci) } };
	f_pageing_helper('001010000000007'H, cid_list, { 0 });
	f_shutdown_helper();
}

/* Paging by CI */
testcase TC_paging_imsi_nochan_ci() runs on test_CT {
	var template BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := { cIl_CI := { ts_BSSMAP_CI_CI(cid.ci) } };
	f_pageing_helper('001010000000008'H, cid_list, { 0 });
	f_shutdown_helper();
}

/* Paging by LAI */
testcase TC_paging_imsi_nochan_lai() runs on test_CT {
	var template BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := { cIl_LAI := { ts_BSSMAP_CI_LAI(cid.mcc, cid.mnc, cid.lac) } };
	f_pageing_helper('001010000000009'H, cid_list, c_BtsId_LAC1);
	f_shutdown_helper();
}

/* Paging by LAC */
testcase TC_paging_imsi_nochan_lac() runs on test_CT {
	var template BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := { cIl_LAC := { ts_BSSMAP_CI_LAC(cid.lac) } };
	f_pageing_helper('001010000000010'H, cid_list, c_BtsId_LAC1);
	f_shutdown_helper();
}

/* Paging by "all in BSS" */
testcase TC_paging_imsi_nochan_all() runs on test_CT {
	var template BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := { cIl_allInBSS := ''O };
	f_pageing_helper('001010000000011'H, cid_list, c_BtsId_all);
	f_shutdown_helper();
}

/* Paging by PLMN+LAC+RNC; We do not implement this; Verify nothing is paged */
testcase TC_paging_imsi_nochan_plmn_lac_rnc() runs on test_CT {
	var template BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := { cIl_PLMN_LAC_RNC := { ts_BSSMAP_CI_PLMN_LAC_RNC(cid.mcc, cid.mnc, cid.lac, 12) } };
	f_pageing_helper('001010000000012'H, cid_list, c_BtsId_none);
	f_shutdown_helper();
}

/* Paging by RNC; We do not implement this; Verify nothing is paged */
testcase TC_paging_imsi_nochan_rnc() runs on test_CT {
	var template BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := { cIl_RNC := { int2oct(13, 2) } };
	f_pageing_helper('001010000000013'H, cid_list, c_BtsId_none);
	f_shutdown_helper();
}

/* Paging by LAC+RNC; We do not implement; Verify nothing is paged */
testcase TC_paging_imsi_nochan_lac_rnc() runs on test_CT {
	var template BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := { cIl_LAC_RNC := { ts_BSSMAP_CI_LAC_RNC(cid.lac, 14) } };
	f_pageing_helper('001010000000014'H, cid_list, c_BtsId_none);
	f_shutdown_helper();
}

/* Paging on multiple cells (multiple entries in list): Verify all of them page */
testcase TC_paging_imsi_nochan_lacs() runs on test_CT {
	var template BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := { cIl_LAC := { ts_BSSMAP_CI_LAC(1), ts_BSSMAP_CI_LAC(2) } };
	f_pageing_helper('001010000000015'H, cid_list, c_BtsId_all);
	f_shutdown_helper();
}

/* Paging on empty list: Verify none of them page */
testcase TC_paging_imsi_nochan_lacs_empty() runs on test_CT {
	var template BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := { cIl_LAC := { } };
	f_pageing_helper('001010000000016'H, cid_list, c_BtsId_none);
	f_shutdown_helper();
}

/* Paging by CGI with unknown MCC/MNC: Verify nothing is paged. */
testcase TC_paging_imsi_nochan_cgi_unknown_cid() runs on test_CT {
	var template BSSMAP_FIELD_CellIdentificationList cid_list;
	cid_list := { cIl_CGI := { ts_BSSMAP_CI_CGI(unknown_cid.mcc, unknown_cid.mnc, unknown_cid.lac, unknown_cid.ci) } };
	f_pageing_helper('001010000000006'H, cid_list, c_BtsId_none);
	f_shutdown_helper();
}

/* Send paging response containing invalid (wrongly formatted) MobileIdentity IE. */
testcase TC_paging_imsi_nochan_ci_resp_invalid_mi() runs on test_CT {
	var template BSSMAP_FIELD_CellIdentificationList cid_list;
	var BSSAP_N_CONNECT_ind rx_c_ind;
	var DchanTuple dt;
	var octetstring rr_pag_resp := '06270003535992617965720000'O;
	/* { 06 27 } is { GSM48_PDISC_RR, GSM48_MT_RR_PAG_RESP }
	* see 3GPP TS 44.018, table 9.1.25.1
	* { 00 } or { 01 } is CKSN + Spare Half Octet, not important
	* { 03 53 59 92 } is Mobile Station Classmark
	* { 61 79 65 72 00 00 } is the invalid Mobile Identity IE (3GPP TS 24.008, 10.5.1.4),
	* Length is 0x61 (97 in decimal).
	*/

	cid_list := { cIl_CI := { ts_BSSMAP_CI_CI(cid.ci) } };
	f_pageing_helper('001010000000008'H, cid_list, { 0 });

	/* Send CHAN RQD and wait for allocation; acknowledge it */
	dt.rsl_chan_nr := f_chreq_act_ack();
	dt.idx := {0, 0};

	/* Send unsolicited Paging response (no matching Paging CMD stored in BSC) */
	f_ipa_tx(ts_RSL_EST_IND(dt.rsl_chan_nr, valueof(ts_RslLinkID_DCCH(0)), rr_pag_resp));

	/* Expevct a CR with a matching Paging response on the A-Interface */
	timer T := 5.0;
	T.start;
	alt {
	[] BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(rr_pag_resp))) -> value rx_c_ind {
		setverdict(pass);
		dt.sccp_conn_id := rx_c_ind.connectionId;
		BSSAP.send(ts_BSSAP_CONNECT_res(dt.sccp_conn_id));
		}
	[] BSSAP.receive {
		setverdict(fail, "Received unexpected message on A-Interface!");
		}
	[] T.timeout {
		setverdict(fail, "Received nothing on A-Interface!");
		}
	}

	f_perform_clear_test_ct(dt);
	f_shutdown_helper();
}

/* Verify paging retransmission interval + count */
/* Verify paging stops after channel establishment */
/* Test behavior under paging overload */

/* Verify PCH load */
testcase TC_paging_imsi_load() runs on test_CT {
	var BSSMAP_FIELD_CellIdentificationList cid_list;
	timer T := 4.0;
	timer T_retrans := 1.0;
	cid_list := valueof(ts_BSSMAP_CIL_noCell);
	f_pageing_helper('001010123456789'H, cid_list, c_BtsId_all);

	/* tell BSC there is no paging space anymore */
	f_ipa_tx(ts_RSL_PAGING_LOAD_IND(0));
	f_sleep(0.2);
	IPA_RSL[0][0].clear;

	/* Wait for 4 seconds if any more PAGING CMD are received on RSL. Normally,
	 * there would be 8 retransmissions during 4 seconds */
	T.start;
	T_retrans.start;
	alt {
	[] IPA_RSL[0][0].receive(tr_ASP_RSL_UD(tr_RSL_PAGING_CMD(?))) {
		setverdict(fail, "Received PAGING after LOAD_IND(0)");
		mtc.stop;
		}
	[] T_retrans.timeout {
		/* re-trnsmit the zero-space LOAD IND to avoid BSC 'auto credit' */
		f_ipa_tx(ts_RSL_PAGING_LOAD_IND(0));
		T_retrans.start;
		repeat;
		}
	[] T.timeout {
		setverdict(pass);
		}
	}

	f_shutdown_helper();
}

/* Verify Paging Counter */
testcase TC_paging_counter() runs on test_CT {
	var BSSMAP_FIELD_CellIdentificationList cid_list;
	timer T := 4.0;
	var integer i;
	var integer paging_attempted_bsc;
	var integer paging_attempted_bts[NUM_BTS];
	var integer paging_expired_bsc;
	var integer paging_expired_bts[NUM_BTS];
	cid_list := valueof(ts_BSSMAP_CIL_noCell);

	f_init();

	/* read counters before paging */
	paging_attempted_bsc := f_ctrl_get_ratectr_abs(IPA_CTRL, "bsc", 0, "paging:attempted");
	paging_expired_bsc := f_ctrl_get_ratectr_abs(IPA_CTRL, "bsc", 0, "paging:expired");
	for (i := 0; i < NUM_BTS; i := i+1) {
		paging_attempted_bts[i] := f_ctrl_get_ratectr_abs(IPA_CTRL, "bts", i, "paging:attempted");
		paging_expired_bts[i] := f_ctrl_get_ratectr_abs(IPA_CTRL, "bts", i, "paging:expired");
	}

	f_pageing_helper('001230000000001'H, cid_list, c_BtsId_all);

	/* expect the attempted pages on BSC and each BTSs to have incremented by one */
	f_ctrl_get_exp_ratectr_abs(IPA_CTRL, "bsc", 0, "paging:attempted", paging_attempted_bsc+1);
	for (i := 0; i < NUM_BTS; i := i+1) {
		f_ctrl_get_exp_ratectr_abs(IPA_CTRL, "bts", i, "paging:attempted",
						paging_attempted_bts[i]+1);
	}

	/* assume that 12s later the paging on all BTSs have expired and hence incremented by 1 */
	f_sleep(12.0);
	f_ctrl_get_exp_ratectr_abs(IPA_CTRL, "bsc", 0, "paging:expired", paging_expired_bsc+1);
	for (i := 0; i < NUM_BTS; i := i+1) {
		f_ctrl_get_exp_ratectr_abs(IPA_CTRL, "bts", i, "paging:expired",
						paging_expired_bts[i]+1);
	}

	f_shutdown_helper();
}


/* Verify paging stops after A-RESET */
testcase TC_paging_imsi_a_reset() runs on test_CT {
	var BSSMAP_FIELD_CellIdentificationList cid_list;
	timer T := 3.0;
	cid_list := valueof(ts_BSSMAP_CIL_noCell);
	f_pageing_helper('001010123456789'H, cid_list, c_BtsId_all);

	/* Perform a BSSMAP Reset and wait for ACK */
	BSSAP.send(ts_BSSAP_UNITDATA_req(g_bssap[0].sccpa.sccp_addr_peer, g_bssap[0].sccpa.sccp_addr_own, ts_BSSMAP_Reset(0, g_osmux_enabled_cn)));
	alt {
	[] BSSAP.receive(tr_BSSAP_UNITDATA_ind(g_bssap[0].sccpa.sccp_addr_own, g_bssap[0].sccpa.sccp_addr_peer, tr_BSSMAP_ResetAck(g_osmux_enabled_cn))) { }
	[] BSSAP.receive { repeat; }
	}

	/* Wait to avoid a possible race condition if a paging message is
	 * received right before the reset ACK. */
	f_sleep(0.2);

	/* Clear the queue, it might still contain stuff like BCCH FILLING */
	for (var integer i := 0; i < sizeof(IPA_RSL); i := i+1) {
		IPA_RSL[i][0].clear;
	}

	/* Wait for 3 seconds if any more PAGING CMD are received on RSL */
	T.start;
	alt {
	[] IPA_RSL[0][0].receive(tr_ASP_RSL_UD(tr_RSL_PAGING_CMD(?))) {
		setverdict(fail, "Received PAGING after A-RESET");
		mtc.stop;
		}
	[] IPA_RSL[1][0].receive(tr_ASP_RSL_UD(tr_RSL_PAGING_CMD(?))) {
		setverdict(fail, "Received PAGING after A-RESET");
		mtc.stop;
		}
	[] IPA_RSL[2][0].receive(tr_ASP_RSL_UD(tr_RSL_PAGING_CMD(?))) {
		setverdict(fail, "Received PAGING after A-RESET");
		mtc.stop;
		}
	[] T.timeout {
		setverdict(pass);
		}
	}

	f_shutdown_helper();
}

/* Verify how we handle unsolicited Paging Response. In case of an unsolicit
 * paging response we can not know which MSC is in charge, so we will blindly
 * pick the first configured MSC. This behavior is required in order to make
 * MT-CSFB calls working because in those cases the BSC can not know that the
 * MSC has already paged the subscriver via SGs. So any MT-CSFB call will look
 * like an unsolicited Paging Response to the MSC.
 */
testcase TC_paging_resp_unsol() runs on test_CT {

	f_init(1);
	timer T := 5.0;

	var BSSAP_N_CONNECT_ind rx_c_ind;
	var DchanTuple dt;
	var PDU_ML3_MS_NW l3 := valueof(ts_PAG_RESP(ts_MI_IMSI_LV('001010008880018'H)));
	var octetstring rr_pag_resp := enc_PDU_ML3_MS_NW(l3);

	/* Send CHAN RQD and wait for allocation; acknowledge it */
	dt.rsl_chan_nr := f_chreq_act_ack();
	dt.idx := {0, 0};

	/* Send unsolicited Paging response (no matching Paging CMD stored in BSC) */
	f_ipa_tx(ts_RSL_EST_IND(dt.rsl_chan_nr, valueof(ts_RslLinkID_DCCH(0)), rr_pag_resp));


	/* Expevct a CR with a matching Paging response on the A-Interface */
	T.start;
	alt {
	[] BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(rr_pag_resp))) -> value rx_c_ind {
		setverdict(pass);
		dt.sccp_conn_id := rx_c_ind.connectionId;
		BSSAP.send(ts_BSSAP_CONNECT_res(dt.sccp_conn_id));
		}
	[] BSSAP.receive {
		setverdict(fail, "Received unexpected message on A-Interface!");
		}
	[] T.timeout {
		setverdict(fail, "Received nothing on A-Interface!");
		}
	}

	f_perform_clear_test_ct(dt);
	f_shutdown_helper();
}

/* Verify BSC can schedule N paging requests under one minute if BTS buffer is good enough */
function f_TC_paging_Nreq(integer num_subscribers, boolean send_pag_load_ind) runs on test_CT {
	var ASP_RSL_Unitdata rx_rsl_ud;
	var Hexstrings imsis := {};
	var Booleans rx_paging_done := {};
	var integer rx_paging_num := 0;
	var integer i;
	timer T_rx := 60.0;
	timer T_load_ind := 1.0;

	for (i := 0; i < num_subscribers; i := i + 1) {
		imsis := imsis & {f_gen_imsi(i)};
		rx_paging_done := rx_paging_done & { false };
	}

	f_init(1, guard_timeout := 100.0);

	/* Clear the queue, it might still contain stuff like BCCH FILLING */
	IPA_RSL[0][0].clear;
	if (send_pag_load_ind) {
		/* Tell there's plenty of space at the BTS (UINT16_MAX): */
		f_ipa_tx(ts_RSL_PAGING_LOAD_IND(65535));
	}

	for (i := 0; i < num_subscribers; i := i + 1) {
		/* Page on LAC-CI of BTS0: */
		BSSAP.send(ts_BSSAP_UNITDATA_req(g_bssap[0].sccpa.sccp_addr_peer, g_bssap[0].sccpa.sccp_addr_own,
			   ts_BSSMAP_Paging(imsis[i], valueof(ts_BSSMAP_CIL_LAC_CI({ts_BSSMAP_CI_LAC_CI(1, 0)})),
			   omit, omit)));
	}

	T_rx.start;
	T_load_ind.start;
	alt {
	[] IPA_RSL[0][0].receive(tr_ASP_RSL_UD(tr_RSL_PAGING_CMD(?), IPAC_PROTO_RSL_TRX0)) -> value rx_rsl_ud {
		var hexstring imsi := rx_rsl_ud.rsl.ies[2].body.ms_identity.mobileIdentityV.oddEvenInd_identity.imsi.digits;
		var hexstring imsi_suffix := substr(imsi, lengthof(imsi)-6, 6);
		var charstring imsi_str := hex2str(imsi_suffix);
		var integer imsi_idx := str2int(imsi_str);
		if (rx_paging_done[imsi_idx] == false) {
			rx_paging_done[imsi_idx] := true;
			rx_paging_num := rx_paging_num + 1;
		} else {
			setverdict(fail, "Retrans of ", imsi_str, " happened before Rx initial trans for all reqs. rx_paging_num=", rx_paging_num);
			mtc.stop;
		}
		if (rx_paging_num < num_subscribers) {
			repeat;
		}
		}
	[] IPA_RSL[0][0].receive { repeat; }
	[] T_load_ind.timeout {
		log("[CCH Load Ind timer] received paging requests so far: ", rx_paging_num);
		if (send_pag_load_ind) {
			f_ipa_tx(ts_RSL_PAGING_LOAD_IND(40));
		}
		T_load_ind.start;
		repeat;
		}
	[] T_rx.timeout {
		setverdict(fail, "Timeout expecting paging requests, so far ", rx_paging_num);
		mtc.stop;
		}
	}

	/* Drop OML connection to have all paging requests flushed: */
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_shutdown_helper();
}
/* Verify BSC can schedule 500 paging requests under one minute if BTS buffer is good enough */
testcase TC_paging_500req() runs on test_CT {
	f_TC_paging_Nreq(500, true);
}
/* Same as TC_paging_500req, but without sending CCCH Load Indication, which
 * means BTS is always under CCH Load Threshold, aka capable of sending tons of requests.
 * Since No CCCH Load Ind, BSC uses a conservative estimation of BTS load, which
 * for current config yields ~8req/sec, so 480req/min maximum. */
testcase TC_paging_450req_no_paging_load_ind() runs on test_CT {
	f_TC_paging_Nreq(450, false);
}

/* Test RSL link drop causes counter increment */
testcase TC_rsl_drop_counter() runs on test_CT {
	var integer rsl_fail;

	f_init(1);

	rsl_fail := f_ctrl_get_ratectr_abs(IPA_CTRL, "bts", 0, "rsl_fail");

	f_ipa_rsl_stop(bts[0][0].rsl);

	f_ctrl_get_exp_ratectr_abs(IPA_CTRL, "bts", 0, "rsl_fail", rsl_fail+1);

	f_shutdown_helper();
}

/* TODO: Test OML link drop causes counter increment */

/* The body of TC_rsl_unknown_unit_id() and TC_oml_unknown_unit_id() tests. */
function f_ipa_unknown_unit_id(integer mp_bsc_ipa_port) runs on test_CT return boolean {
	var IPA_Client client;
	timer T := 10.0;

	client.id := "IPA-BTS0-TRX0-RSL";
	client.vc_IPA := IPA_Emulation_CT.create(client.id & "-IPA") alive;
	client.ccm_pars := c_IPA_default_ccm_pars;
	client.ccm_pars.name := "Osmocom TTCN-3 BTS Simulator";
	client.ccm_pars.unit_id := enc_IpaUnitId({99, 0, 0}); /* value which is unknown at BTS */

	f_ipa_ctrl_start_client(mp_bsc_ip, mp_bsc_ctrl_port);

	f_init_mgcp(0, "VirtMGW");

	/* start RSL/OML connection (XXX re-uses RSL port/protocol definitions for OML) */
	map(client.vc_IPA:IPA_PORT, system:IPA);
	connect(client.vc_IPA:IPA_RSL_PORT, self:IPA_RSL[0][0]);
	client.vc_IPA.start(IPA_Emulation.main_client(mp_bsc_ip, mp_bsc_ipa_port, "", 10000, client.ccm_pars));

	/* wait for IPA OML link to connect and then disconnect */
	T.start;
	alt {
	[] IPA_RSL[0][0].receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_DOWN)) {
		T.stop;
		return true;
	}
	[] IPA_RSL[0][0].receive { repeat }
	[] T.timeout {
		return false;
		}
	}
	return false;
}

/* BSC should close an RSL connection from a BTS with unknown unit ID (OS#2714). */
testcase TC_rsl_unknown_unit_id() runs on test_CT {
	if (f_ipa_unknown_unit_id(mp_bsc_rsl_port)) {
		setverdict(pass);
	} else {
		setverdict(fail, "Timeout RSL waiting for connection to close");
	}
	f_shutdown_helper();
}


/* BSC should close an RSL connection from a BTS with unknown unit ID (OS#2714). */
testcase TC_oml_unknown_unit_id() runs on test_CT {
	if (f_ipa_unknown_unit_id(mp_bsc_oml_port)) {
		setverdict(pass);
	} else {
		setverdict(fail, "Timeout OML waiting for connection to close");
	}
	f_shutdown_helper();
}


/***********************************************************************
 * "New world" test cases using RSL_Emulation + RAN_Emulation
 ***********************************************************************/

import from RAN_Emulation all;
import from BSSAP_LE_Emulation all;
import from RSL_Emulation all;
import from MSC_ConnectionHandler all;

type function void_fn(charstring id) runs on MSC_ConnHdlr;

/* helper function to create and connect a MSC_ConnHdlr component
 * TODO: allow connecting to TRX1..N, not only TRX0 */
private function f_connect_handler(inout MSC_ConnHdlr vc_conn, integer bssap_idx := 0, integer mgwpool_idx := 0) runs on test_CT {
	connect(vc_conn:RAN, g_bssap[bssap_idx].vc_RAN:PROC);
	connect(vc_conn:RSL, bts[0][0].rsl.vc_RSL:CLIENT_PT);
	connect(vc_conn:RSL_PROC, bts[0][0].rsl.vc_RSL:RSL_PROC);
	if (isvalue(bts[1][0])) {
		connect(vc_conn:RSL1, bts[1][0].rsl.vc_RSL:CLIENT_PT);
		connect(vc_conn:RSL1_PROC, bts[1][0].rsl.vc_RSL:RSL_PROC);
	}
	if (isvalue(bts[2][0])) {
		connect(vc_conn:RSL2, bts[2][0].rsl.vc_RSL:CLIENT_PT);
		connect(vc_conn:RSL2_PROC, bts[2][0].rsl.vc_RSL:RSL_PROC);
	}
	connect(vc_conn:BSSAP, g_bssap[bssap_idx].vc_RAN:CLIENT);
	if (mp_enable_lcs_tests) {
		connect(vc_conn:BSSAP_LE, g_bssap_le.vc_BSSAP_LE:CLIENT);
		connect(vc_conn:BSSAP_LE_PROC, g_bssap_le.vc_BSSAP_LE:PROC);
	}
	connect(vc_conn:STATSD_PROC, vc_STATSD:STATSD_PROC);
	connect(vc_conn:MGCP_PROC, vc_MGCP[mgwpool_idx]:MGCP_PROC);
	connect(vc_conn:MGCP, vc_MGCP[mgwpool_idx]:MGCP_CLIENT);
	connect(vc_conn:MGCP_MULTI, vc_MGCP[mgwpool_idx]:MGCP_CLIENT_MULTI);
}

function f_start_handler_create(template (omit) TestHdlrParams pars := omit)
runs on test_CT return MSC_ConnHdlr {
	var charstring id := testcasename();
	var MSC_ConnHdlr vc_conn;
	var integer bssap_idx := 0;
	var integer mgwpool_idx := 0;
	if (isvalue(pars)) {
		var TestHdlrParams pars_val := valueof(pars);
		bssap_idx := pars_val.mscpool.bssap_idx;
		mgwpool_idx := pars_val.mgwpool_idx;
	}
	vc_conn := MSC_ConnHdlr.create(id);
	f_connect_handler(vc_conn, bssap_idx, mgwpool_idx);
	return vc_conn;
}

function f_start_handler_run(MSC_ConnHdlr vc_conn, void_fn fn, template (omit) TestHdlrParams pars := omit)
runs on test_CT return MSC_ConnHdlr {
	var charstring id := testcasename();
	vc_conn.start(f_handler_init(fn, id, pars));
	return vc_conn;
}

function f_start_handler(void_fn fn, template (omit) TestHdlrParams pars := omit)
runs on test_CT return MSC_ConnHdlr {
	return f_start_handler_run(f_start_handler_create(pars), fn, pars);
}

/* first function inside ConnHdlr component; sets g_pars + starts function */
private function f_handler_init(void_fn fn, charstring id, template (omit) TestHdlrParams pars := omit)
runs on MSC_ConnHdlr {
	if (isvalue(pars)) {
		g_pars := valueof(pars);
	}
	fn.apply(id);
}

private function f_vty_encryption_a5(charstring options) runs on test_CT {
	f_vty_transceive(BSCVTY, "configure terminal");
	f_vty_transceive(BSCVTY, "network");
	f_vty_transceive(BSCVTY, "encryption a5 " & options);
	f_vty_transceive(BSCVTY, "exit");
	f_vty_transceive(BSCVTY, "exit");
}

const charstring VTY_A5_DEFAULT := "0 1 3";

private function f_vty_encryption_a5_reset() runs on test_CT {
	/* keep in sync with docker-playground.git ttcn3-bsc-test/osmo-bsc.cfg */
	f_vty_encryption_a5(VTY_A5_DEFAULT);
}

/* Establish signalling channel (non-assignment case) followed by cipher mode */
private function f_tc_ciph_mode_a5(charstring id) runs on MSC_ConnHdlr {
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelTypeSIGNAL);
	ass_cmd.pdu.bssmap.assignmentRequest.circuitIdentityCode := omit;
	ass_cmd.pdu.bssmap.assignmentRequest.aoIPTransportLayer := omit;
	exp_compl.pdu.bssmap.assignmentComplete.circuitIdentityCode := omit;
	exp_compl.pdu.bssmap.assignmentComplete.aoIPTransportLayer := omit;

	f_establish_fully(ass_cmd, exp_compl);
	f_perform_clear();
}
testcase TC_ciph_mode_a5_0() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.encr := valueof(t_EncrParams('01'O, f_rnd_octstring(8)));

	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_ciph_mode_a5), pars);
	vc_conn.done;
	f_shutdown_helper();
}
testcase TC_ciph_mode_a5_1() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.encr := valueof(t_EncrParams('02'O, f_rnd_octstring(8)));

	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_ciph_mode_a5), pars);
	vc_conn.done;
	f_shutdown_helper();
}
/* OS#4975: verify that A5/2 is preferred over A5/0 */
testcase TC_ciph_mode_a5_2_0() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	pars.encr := f_encr_params('05'O, '04'O); /* A5/0 and A5/2 (0x01|0x04)*/

	f_init(1, true);
	f_vty_encryption_a5("0 1 2 3");
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_ciph_mode_a5), pars);
	vc_conn.done;
	f_vty_encryption_a5_reset();
	f_shutdown_helper();
}
/* OS#4975: verify that A5/1 is preferred over A5/2 */
testcase TC_ciph_mode_a5_2_1() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	pars.encr := f_encr_params('06'O, '02'O); /* A5/1 and A5/2 (0x02|0x04)*/

	f_init(1, true);
	f_vty_encryption_a5("1 2");
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_ciph_mode_a5), pars);
	vc_conn.done;
	f_vty_encryption_a5_reset();
	f_shutdown_helper();
}
testcase TC_ciph_mode_a5_3() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.encr := valueof(t_EncrParams('08'O, f_rnd_octstring(8)));

	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_ciph_mode_a5), pars);
	vc_conn.done;
	f_shutdown_helper();
}
/* Establish a Signalling channel with A5/4 encryption. */
testcase TC_ciph_mode_a5_4() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.encr := valueof(t_EncrParams('10'O, f_rnd_octstring(8), f_rnd_octstring(16)));

	f_init(1, true);
	f_vty_encryption_a5("0 1 3 4");
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_ciph_mode_a5), pars);
	vc_conn.done;
	f_vty_encryption_a5_reset();
	f_shutdown_helper();
}
/* establish initial channel, enable ciphering followed by assignment to ciphered channel */
private function f_tc_assignment_aoip_tla_v6(charstring id) runs on MSC_ConnHdlr {
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req(aoip_tla := "::3");
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));

	f_establish_fully(ass_cmd, exp_compl);
	f_perform_clear();
}
testcase TC_assignment_aoip_tla_v6() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_assignment_aoip_tla_v6), pars);
	vc_conn.done;
	f_shutdown_helper();
}


/* establish initial channel, enable ciphering followed by assignment to ciphered channel */
private function f_tc_assignment_fr_a5(charstring id) runs on MSC_ConnHdlr {
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();

	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));

	f_establish_fully(ass_cmd, exp_compl);
	f_perform_clear();
}
testcase TC_assignment_fr_a5_0() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.encr := valueof(t_EncrParams('01'O, f_rnd_octstring(8)));

	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_assignment_fr_a5), pars);
	vc_conn.done;
	f_shutdown_helper();
}
testcase TC_assignment_fr_a5_1() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.encr := valueof(t_EncrParams('02'O, f_rnd_octstring(8)));

	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_assignment_fr_a5), pars);
	vc_conn.done;
	f_shutdown_helper();
}
testcase TC_assignment_fr_a5_3() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.encr := valueof(t_EncrParams('08'O, f_rnd_octstring(8)));

	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_assignment_fr_a5), pars);
	vc_conn.done;
	f_shutdown_helper();
}
/* Establish a Signalling channel and re-assign to TCH/F with A5/4 encryption. */
testcase TC_assignment_fr_a5_4() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.encr := valueof(t_EncrParams('10'O, f_rnd_octstring(8), f_rnd_octstring(16)));

	f_init(1, true);
	f_vty_encryption_a5("0 1 3 4");
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_assignment_fr_a5), pars);
	vc_conn.done;
	f_vty_encryption_a5_reset();
	f_shutdown_helper();
}

/* Allow only A5/4, but omit the Kc128 IE from MSC's msg. Expect Cipher Mode Reject. */
testcase TC_assignment_fr_a5_4_fail() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	pars.encr := valueof(t_EncrParams('10'O, f_rnd_octstring(8))); // A5/4 support, but Kc128 missing!
	vc_conn := f_start_handler(refers(f_TC_assignment_a5_not_sup), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* Expect ASSIGNMENT FAIL if mandatory IE is missing */
private function f_tc_assignment_fr_a5_1_codec_missing(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var template PDU_BSSAP exp_fail := tr_BSSMAP_AssignmentFail;
	var PDU_BSSAP ass_cmd := f_gen_ass_req();

	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	/* Omit: ass_cmd.pdu.bssmap.assignmentRequest.codecList */

	f_establish_fully(ass_cmd, exp_fail);
	f_perform_clear();
}
testcase TC_assignment_fr_a5_1_codec_missing() runs on test_CT {
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	vc_conn := f_start_handler(refers(f_tc_assignment_fr_a5_1_codec_missing));
	vc_conn.done;
	f_shutdown_helper();
}

private function f_TC_assignment_a5_not_sup(charstring id) runs on MSC_ConnHdlr {
	var template PDU_BSSAP exp_ass_cpl := f_gen_exp_compl();
	var PDU_BSSAP exp_ass_req := f_gen_ass_req();

	exp_ass_req.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	exp_ass_req.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));

	/* this is like the beginning of f_establish_fully(), but only up to ciphering reject */

	var BSSMAP_FIELD_CodecType codecType;

	codecType := exp_ass_req.pdu.bssmap.assignmentRequest.codecList.codecElements[0].codecType;
	f_MscConnHdlr_init(g_pars.media_nr, host_bts, host_mgw_mgcp, codecType);

	f_create_chan_and_exp();
	/* we should now have a COMPL_L3 at the MSC */

	/* Start ciphering, expect Cipher Mode Reject */
	f_cipher_mode(g_pars.encr, exp_fail := true);
	f_perform_clear();
}
testcase TC_assignment_fr_a5_not_sup() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	pars.encr := valueof(t_EncrParams('20'O, f_rnd_octstring(8), f_rnd_octstring(16)));
	vc_conn := f_start_handler(refers(f_TC_assignment_a5_not_sup), pars);
	vc_conn.done;
	f_shutdown_helper();
}


private function f_tc_assignment_sign(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var template PDU_BSSAP exp_compl := tr_BSSMAP_AssignmentComplete(omit, omit);
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelTypeSIGNAL);

	f_statsd_reset();
	f_establish_fully(ass_cmd, exp_compl);

	var StatsDExpects expect := {
		{ name := "TTCN3.bts.0.chreq.total", mtype := "c", min := 1, max := 1},
		{ name := "TTCN3.bts.0.chreq.successful", mtype := "c", min := 1, max := 1},
		{ name := "TTCN3.bsc.0.assignment.attempted", mtype := "c", min := 1, max := 1},
		{ name := "TTCN3.bsc.0.assignment.completed", mtype := "c", min := 1, max := 1}
	};
	f_statsd_expect(expect);
	f_perform_clear();
}

testcase TC_assignment_sign() runs on test_CT {
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	vc_conn := f_start_handler(refers(f_tc_assignment_sign));
	vc_conn.done;
	f_shutdown_helper();
}

/***********************************************************************
 * Codec (list) testing
 ***********************************************************************/

/* check if the given rsl_mode is compatible with the a_elem */
private function f_match_codec(BSSMAP_FIELD_CodecElement a_elem, RSL_IE_ChannelMode rsl_mode)
return boolean {
	select (a_elem.codecType) {
	case (GSM_FR) {
		if (match(rsl_mode, tr_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM1))) {
			return true;
		}
	}
	case (GSM_HR) {
		if (match(rsl_mode, tr_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM1))) {
			return true;
		}
	}
	case (GSM_EFR) {
		if (match(rsl_mode, tr_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM2))) {
			return true;
		}
	}
	case (FR_AMR) {
		if (match(rsl_mode, tr_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM3))) {
			return true;
		}
	}
	case (HR_AMR) {
		if (match(rsl_mode, tr_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM3))) {
			return true;
		}
	}
	case else { }
	}
	return false;
}

/* check if the given rsl_mode is compatible with the a_list */
private function f_match_codecs(BSSMAP_IE_SpeechCodecList a_list, RSL_IE_ChannelMode rsl_mode)
return boolean {
	for (var integer i := 0; i < sizeof(a_list); i := i+1) {
		if (f_match_codec(a_list.codecElements[i], rsl_mode)) {
			return true;
		}
	}
	return false;
}

/* determine BSSMAP_IE_ChannelType from *first* element of BSSMAP_FIELD_CodecElement */
function f_BSSMAP_chtype_from_codec(BSSMAP_FIELD_CodecElement a_elem)
return BSSMAP_IE_ChannelType {
	/* FIXME: actually look at all elements of BSSMAP_IE_SpeechCodecList */
	var BSSMAP_IE_ChannelType ret := valueof(ts_BSSMAP_IE_ChannelType);
	select (a_elem.codecType) {
	case (GSM_FR) {
		ret.channelRateAndType := ChRate_TCHF;
		ret.speechId_DataIndicator := Spdi_TCHF_FR;
	}
	case (GSM_HR) {
		ret.channelRateAndType := ChRate_TCHH;
		ret.speechId_DataIndicator := Spdi_TCHH_HR;
	}
	case (GSM_EFR) {
		ret.channelRateAndType := ChRate_TCHF;
		ret.speechId_DataIndicator := Spdi_TCHF_EFR;
	}
	case (FR_AMR) {
		ret.channelRateAndType := ChRate_TCHF;
		ret.speechId_DataIndicator := Spdi_TCHF_AMR;
	}
	case (HR_AMR) {
		ret.channelRateAndType := ChRate_TCHH;
		ret.speechId_DataIndicator := Spdi_TCHH_AMR;
	}
	case else {
		setverdict(fail, "Unsupported codec ", a_elem);
		mtc.stop;
	}
	}
	return ret;
}

private function f_rsl_chmod_tmpl_from_codec(BSSMAP_FIELD_CodecElement a_elem)
return template RSL_IE_Body {
	var template RSL_IE_Body mode_ie := {
		chan_mode := {
			len := ?,
			reserved := ?,
			dtx_d := ?,
			dtx_u := ?,
			spd_ind := RSL_SPDI_SPEECH,
			ch_rate_type := -,
			u := { speech := - }
		}
	}

	select (a_elem.codecType) {
	case (GSM_FR) {
		mode_ie.chan_mode.ch_rate_type := RSL_CHRT_TCH_F;
		mode_ie.chan_mode.u.speech := RSL_CMOD_SP_GSM1;
	}
	case (GSM_HR) {
		mode_ie.chan_mode.ch_rate_type := RSL_CHRT_TCH_H;
		mode_ie.chan_mode.u.speech := RSL_CMOD_SP_GSM1;
	}
	case (GSM_EFR) {
		mode_ie.chan_mode.ch_rate_type := RSL_CHRT_TCH_F;
		mode_ie.chan_mode.u.speech := RSL_CMOD_SP_GSM2;
	}
	case (FR_AMR) {
		mode_ie.chan_mode.ch_rate_type := RSL_CHRT_TCH_F;
		mode_ie.chan_mode.u.speech := RSL_CMOD_SP_GSM3;
	}
	case (HR_AMR) {
		mode_ie.chan_mode.ch_rate_type := RSL_CHRT_TCH_H;
		mode_ie.chan_mode.u.speech := RSL_CMOD_SP_GSM3;
	}
	}
	return mode_ie;
}

type record CodecListTest {
	BSSMAP_IE_SpeechCodecList codec_list,
	charstring id
}
type record of CodecListTest CodecListTests

private function f_TC_assignment_codec(charstring id) runs on MSC_ConnHdlr {
	f_assignment_codec(id);
}

private function f_assignment_codec(charstring id, boolean do_perform_clear := true) runs on MSC_ConnHdlr {
	var PDU_BSSAP ass_cmd := f_gen_ass_req(g_pars.use_osmux_cn);
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();

	/* puzzle together the ASSIGNMENT REQ for given codec[s] */
	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_AoIP) {
		ass_cmd.pdu.bssmap.assignmentRequest.codecList := g_pars.ass_codec_list;
		exp_compl.pdu.bssmap.assignmentComplete.speechCodec.codecElements[0] :=
								g_pars.ass_codec_list.codecElements[0];
		if (isvalue(g_pars.expect_mr_s0_s7)) {
			exp_compl.pdu.bssmap.assignmentComplete.speechCodec.codecElements[0].s0_7 :=
								g_pars.expect_mr_s0_s7;
		}
	}
	ass_cmd.pdu.bssmap.assignmentRequest.channelType :=
				f_BSSMAP_chtype_from_codec(g_pars.ass_codec_list.codecElements[0]);
	log("expecting ASS COMPL like this: ", exp_compl);

	f_establish_fully(ass_cmd, exp_compl);

	if (not g_pars.expect_channel_mode_modify) {
		/* Verify that the RSL-side activation actually matches our expectations */
		var RSL_Message rsl := f_rslem_get_last_act(RSL_PROC, 0, g_chan_nr);

		var RSL_IE_Body mode_ie;
		if (f_rsl_find_ie(rsl, RSL_IE_CHAN_MODE, mode_ie) == false) {
			setverdict(fail, "Couldn't find CHAN_MODE IE");
			mtc.stop;
		}
		var template RSL_IE_Body t_mode_ie := f_rsl_chmod_tmpl_from_codec(g_pars.ass_codec_list.codecElements[0]);
		if (not match(mode_ie, t_mode_ie)) {
			log("mode_ie ", mode_ie, " != t_mode_ie ", t_mode_ie);
			setverdict(fail, "RSL Channel Mode IE doesn't match expectation");
		}

		var RSL_IE_Body mr_conf;
		if (g_pars.expect_mr_conf_ie != omit) {
			if (f_rsl_find_ie(rsl, RSL_IE_MR_CONFIG, mr_conf) == false) {
				setverdict(fail, "Missing MR CONFIG IE in RSL Chan Activ");
				mtc.stop;
			}
			log("found RSL MR CONFIG IE: ", mr_conf);

			if (not match(mr_conf, g_pars.expect_mr_conf_ie)) {
				setverdict(fail, "RSL MR CONFIG IE does not match expectation. ",
					   "Expected: ", g_pars.expect_mr_conf_ie, ", got: ", mr_conf);
			}
		} else {
			if (f_rsl_find_ie(rsl, RSL_IE_MR_CONFIG, mr_conf) == true) {
				log("found RSL MR CONFIG IE: ", mr_conf);
				setverdict(fail, "Found MR CONFIG IE in RSL Chan Activ, expecting omit");
				mtc.stop;
			}
		}
	}

	if (do_perform_clear) {
		f_perform_clear();
	}
}

private function f_TC_assignment_codec_fail(charstring id) runs on MSC_ConnHdlr {

	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	var template PDU_BSSAP exp_fail := tr_BSSMAP_AssignmentFail;

	/* puzzle together the ASSIGNMENT REQ for given codec[s] */
	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_AoIP) {
		ass_cmd.pdu.bssmap.assignmentRequest.codecList := g_pars.ass_codec_list;
	}
	ass_cmd.pdu.bssmap.assignmentRequest.channelType :=
				f_BSSMAP_chtype_from_codec(g_pars.ass_codec_list.codecElements[0]);
	log("expecting ASS FAIL like this: ", exp_fail);

	f_establish_fully(ass_cmd, exp_fail);
	f_perform_clear();
}

const CounterNameVals counternames_bsc_bts_assignment := {
	{ "assignment:attempted", 0 },
	{ "assignment:completed", 0 },
	{ "assignment:stopped", 0 },
	{ "assignment:no_channel", 0 },
	{ "assignment:timeout", 0 },
	{ "assignment:failed", 0 },
	{ "assignment:error", 0 }
};

const CounterNameVals counternames_bts_assignment := {
	{ "assignment:attempted_sign", 0 },
	{ "assignment:attempted_speech", 0 },
	{ "assignment:completed_sign", 0 },
	{ "assignment:completed_speech", 0 },
	{ "assignment:stopped_sign", 0 },
	{ "assignment:stopped_speech", 0 },
	{ "assignment:no_channel_sign", 0 },
	{ "assignment:no_channel_speech", 0 },
	{ "assignment:timeout_sign", 0 },
	{ "assignment:timeout_speech", 0 },
	{ "assignment:failed_sign", 0 },
	{ "assignment:failed_speech", 0 },
	{ "assignment:error_sign", 0 },
	{ "assignment:error_speech", 0 }
};

function f_ctrs_bsc_and_bts_assignment_init(integer bts_count := NUM_BTS) runs on test_CT {
	var CounterNameVals bts_names := counternames_bsc_bts_assignment & counternames_bts_assignment;
	f_ctrs_bts_init(bts_count, bts_names);
	f_ctrs_bsc_init(counternames_bsc_bts_assignment);
}

testcase TC_assignment_codec_fr() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);
	f_ctrs_bsc_and_bts_assignment_init(1);

	pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));
	vc_conn := f_start_handler(refers(f_TC_assignment_codec), pars);
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "assignment:attempted", 1);
	f_ctrs_bts_add(0, "assignment:attempted_speech", 1);
	f_ctrs_bsc_and_bts_add(0, "assignment:completed", 1);
	f_ctrs_bts_add(0, "assignment:completed_speech", 1);
	f_ctrs_bts_verify();

	f_shutdown_helper();
}

testcase TC_assignment_codec_hr() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);
	f_ctrs_bsc_and_bts_assignment_init(1);

	pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecHR}));
	vc_conn := f_start_handler(refers(f_TC_assignment_codec), pars);
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "assignment:attempted", 1);
	f_ctrs_bts_add(0, "assignment:attempted_speech", 1);
	f_ctrs_bsc_and_bts_add(0, "assignment:completed", 1);
	f_ctrs_bts_add(0, "assignment:completed_speech", 1);
	f_ctrs_bts_verify();

	f_shutdown_helper();
}

testcase TC_assignment_codec_efr() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecEFR}));
	vc_conn := f_start_handler(refers(f_TC_assignment_codec), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* Allow 5,90k only (current default config) */
private function f_allow_amr_rate_5_90k() runs on test_CT {
	f_vty_cfg_msc(BSCVTY, 0, {
		"amr-config 12_2k forbidden",
		"amr-config 10_2k forbidden",
		"amr-config 7_95k forbidden",
		"amr-config 7_40k forbidden",
		"amr-config 6_70k forbidden",
		"amr-config 5_90k allowed",
		"amr-config 5_15k forbidden",
		"amr-config 4_75k forbidden"
		});
}

/* Allow 4,75k, 5,90k, 4,70k and 12,2k, which are the most common rates
 * ("Config-NB-Code = 1") */
private function f_allow_amr_rate_4_75k_5_90k_7_40k_12_20k() runs on test_CT {
	f_vty_cfg_msc(BSCVTY, 0, {
		"amr-config 12_2k allowed",
		"amr-config 10_2k forbidden",
		"amr-config 7_95k forbidden",
		"amr-config 7_40k allowed",
		"amr-config 6_70k forbidden",
		"amr-config 5_90k allowed",
		"amr-config 5_15k forbidden",
		"amr-config 4_75k allowed"
		});
}

private function f_vty_amr_start_mode_set(boolean fr, charstring startmode) runs on test_CT {
	var charstring tch;
	if (fr) {
		tch := "tch-f";
	} else {
		tch := "tch-h";
	}
	f_vty_cfg_bts(BSCVTY, 0, { "amr " & tch & " start-mode " & startmode });
}

/* Set the AMR start-mode for this TCH back to the default configuration. */
private function f_vty_amr_start_mode_restore(boolean fr) runs on test_CT {
	f_vty_amr_start_mode_set(fr, "auto");
}

testcase TC_assignment_codec_amr_f() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	/* Note: This setups the codec configuration. The parameter payload in
	 * mr_conf must be consistant with the parameter codecElements in pars
	 * and also must match the amr-config in osmo-bsc.cfg! */
	pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecAMR_F}));
	pars.ass_codec_list.codecElements[0].s0_7 := '00000100'B; /* 5,90k */
	pars.ass_codec_list.codecElements[0].s8_15 := '01010111'B;
	pars.expect_mr_conf_ie := c_mr_conf_5_90;

	f_init(1, true);
	f_sleep(1.0);
	f_vty_amr_start_mode_set(true, "1");
	f_ctrs_bsc_and_bts_assignment_init(1);

	vc_conn := f_start_handler(refers(f_TC_assignment_codec), pars);
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "assignment:attempted", 1);
	f_ctrs_bts_add(0, "assignment:attempted_speech", 1);
	f_ctrs_bsc_and_bts_add(0, "assignment:completed", 1);
	f_ctrs_bts_add(0, "assignment:completed_speech", 1);
	f_ctrs_bts_verify();

	f_vty_amr_start_mode_restore(true);
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_h() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	/* See note above */
	pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecAMR_H}));
	pars.ass_codec_list.codecElements[0].s0_7 := '00000100'B; /* 5,90k */
	pars.ass_codec_list.codecElements[0].s8_15 := '00000111'B;
	pars.expect_mr_conf_ie := c_mr_conf_5_90;

	f_init(1, true);
	f_sleep(1.0);
	f_vty_amr_start_mode_set(false, "1");
	f_ctrs_bsc_and_bts_assignment_init(1);

	vc_conn := f_start_handler(refers(f_TC_assignment_codec), pars);
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "assignment:attempted", 1);
	f_ctrs_bts_add(0, "assignment:attempted_speech", 1);
	f_ctrs_bsc_and_bts_add(0, "assignment:completed", 1);
	f_ctrs_bts_add(0, "assignment:completed_speech", 1);
	f_ctrs_bts_verify();

	f_vty_amr_start_mode_restore(false);
	f_shutdown_helper();
}

/* Establish signalling on a TCH/F lchan, and then switch to speech mode without a new Assignment. */
testcase TC_assignment_codec_fr_by_mode_modify() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	/* By disabling all SDCCH, the MS should be given a TCH/F for signalling. Then activating an FR codec should
	 * merely do a Channel Mode Modify, and not assign to a new lchan. f_establish_fully() already accounts for
	 * expecting a Channel Mode Modify if the channel type is compatible. */
	f_disable_all_sdcch();
	f_disable_all_tch_h();

	pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));
	pars.expect_channel_mode_modify := true;
	vc_conn := f_start_handler(refers(f_TC_assignment_codec), pars);
	vc_conn.done;

	f_enable_all_sdcch();
	f_enable_all_tch();
	f_shutdown_helper();
}

/* 'amr start-mode auto' should not keep the (unused) 'smod' bits from previous configuration */
testcase TC_assignment_codec_amr_startmode_cruft() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecAMR_F}));
	pars.ass_codec_list.codecElements[0].s0_7 := '00000100'B; /* 5,90k */
	pars.ass_codec_list.codecElements[0].s8_15 := '01010111'B;
	pars.expect_mr_conf_ie := c_mr_conf_5_90;
	pars.expect_mr_conf_ie.multirate_cfg.icmi := false; /* expect ICMI=0, smod=00: */

	f_init(1, true);
	f_sleep(1.0);

	/* First set nonzero start mode bits */
	f_vty_amr_start_mode_set(true, "4");
	/* Now set to auto, and expect the startmode bits to be zero in the message, i.e. ensure that osmo-bsc does not
	 * let the startmode bits stick around and has deterministic MultiRate config for 'start-mode auto'; that is
	 * ensured by above '2004'O, where 'x0xx'O indicates ICMI = 0, spare = 0, smod = 00. */
	f_vty_amr_start_mode_set(true, "auto");

	vc_conn := f_start_handler(refers(f_TC_assignment_codec), pars);
	vc_conn.done;

	/* Clear the startmode bits to not affect subsequent tests, in case the bits should indeed stick around. */
	f_vty_amr_start_mode_set(true, "1");
	f_vty_amr_start_mode_restore(true);
	f_shutdown_helper();
}

function f_TC_assignment_codec_amr(boolean fr, RSL_IE_MultirateCfg mr_cfg,
				   bitstring s8_s0, bitstring exp_s8_s0,
				   charstring start_mode := "1")
runs on test_CT {

	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	if (fr) {
		pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecAMR_F}));
	} else {
		pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecAMR_H}));
	}
	pars.ass_codec_list.codecElements[0].s0_7 := s8_s0;
	pars.ass_codec_list.codecElements[0].s8_15 := '00000111'B;
	pars.expect_mr_conf_ie := { multirate_cfg := mr_cfg };
	pars.expect_mr_s0_s7 := exp_s8_s0;

	f_init(1, true);
	f_allow_amr_rate_4_75k_5_90k_7_40k_12_20k();
	f_vty_amr_start_mode_set(fr, start_mode);
	f_sleep(1.0);

	vc_conn := f_start_handler(refers(f_TC_assignment_codec), pars);
	vc_conn.done;
	f_allow_amr_rate_5_90k();
	f_vty_amr_start_mode_restore(fr);
}

function f_TC_assignment_codec_amr_fail(boolean fr, bitstring s8_s0)
runs on test_CT {

	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	if (fr) {
		pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecAMR_F}));
	} else {
		pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecAMR_H}));
	}
	pars.ass_codec_list.codecElements[0].s0_7 := s8_s0;
	pars.ass_codec_list.codecElements[0].s8_15 := '00000111'B;

	f_init(1, true);
	f_allow_amr_rate_4_75k_5_90k_7_40k_12_20k();
	f_vty_amr_start_mode_set(fr, "1");
	f_sleep(1.0);

	vc_conn := f_start_handler(refers(f_TC_assignment_codec_fail), pars);
	vc_conn.done;
	f_allow_amr_rate_5_90k();
	f_vty_amr_start_mode_restore(fr);
}

/* Set S1, we expect an AMR multirate configuration IE with all four rates
 * set. */
testcase TC_assignment_codec_amr_f_S1() runs on test_CT {
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '10010101'B,
								      params := '20882208'O));
	f_TC_assignment_codec_amr(true, mr_cfg, '00000011'B, '00000010'B);
	f_shutdown_helper();
}

/* Set S1, we expect an AMR multirate configuration IE with the lower three
 * rates set. */
testcase TC_assignment_codec_amr_h_S1() runs on test_CT {
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '00010101'B,
								      params := '208820'O));
	f_TC_assignment_codec_amr(false, mr_cfg, '00000010'B, '00000010'B);
	f_shutdown_helper();
}

/* Set S1 and two other rates, we expect an AMR MULTIRATE CONFIGURATION IE with
 * all four rates (and only S1 set in the ASSIGNMENT COMPLETE) */
testcase TC_assignment_codec_amr_f_S124() runs on test_CT {
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '10010101'B,
								      params := '20882208'O));
	f_TC_assignment_codec_amr(true, mr_cfg, '00010110'B, '00000010'B);
	f_shutdown_helper();
}

/* Set S1 and two other rates, we expect an AMR MULTIRATE CONFIGURATION IE with
 * all four rates (and only S1 set in the ASSIGNMENT COMPLETE) */
testcase TC_assignment_codec_amr_h_S124() runs on test_CT {
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '00010101'B,
								      params := '208820'O));
	f_TC_assignment_codec_amr(false, mr_cfg, '00010110'B, '00000010'B);
	f_shutdown_helper();
}

/* The following block of tests selects more and more rates until all four
 * possible rates are in the active set (full rate) */
testcase TC_assignment_codec_amr_f_S0() runs on test_CT {
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '00000001'B));
	f_TC_assignment_codec_amr(true, mr_cfg, '00000001'B, '00000001'B);
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_f_S02() runs on test_CT {
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '00000101'B,
								      params := '2080'O));
	f_TC_assignment_codec_amr(true, mr_cfg, '00000101'B, '00000101'B);
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_f_S024() runs on test_CT {
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '00010101'B,
								      params := '208820'O));
	f_TC_assignment_codec_amr(true, mr_cfg, '00010101'B, '00010101'B);
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_f_S0247() runs on test_CT {
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '10010101'B,
								      params := '20882208'O));
	f_TC_assignment_codec_amr(true, mr_cfg, '10010101'B, '10010101'B);
	f_shutdown_helper();
}

/* The following block of tests selects more and more rates until all three
 * possible rates are in the active set (half rate) */
testcase TC_assignment_codec_amr_h_S0() runs on test_CT {
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '00000001'B));
	f_TC_assignment_codec_amr(false, mr_cfg, '00000001'B, '00000001'B);
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_h_S02() runs on test_CT {
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '00000101'B,
								      params := '2080'O));
	f_TC_assignment_codec_amr(false, mr_cfg, '00000101'B, '00000101'B);
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_h_S024() runs on test_CT {
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '00010101'B,
								      params := '208820'O));
	f_TC_assignment_codec_amr(false, mr_cfg, '00010101'B, '00010101'B);
	f_shutdown_helper();
}

/* The following block tests what happens when the MSC does offer rate
 * configurations that are not supported by the BSC. Normally such situations
 * should not happen because the MSC gets informed by the BSC in advance via
 * the L3 COMPLETE message which rates are applicable. The MSC should not try
 * to offer rates that are not applicable anyway. */

testcase TC_assignment_codec_amr_h_S0247() runs on test_CT {
	/* Try to include 12,2k in into the active set even though the channel
	* is half rate only. The BSC is expected to remove the 12,0k */
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '00010101'B,
								      params := '208820'O));
	f_TC_assignment_codec_amr(false, mr_cfg, '10010101'B, '00010101'B);
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_f_S01234567() runs on test_CT {
	/* See what happens when all rates are selected at once. Since then
	* Also S1 is selected, this setting will be prefered and we should
	* get 12.2k, 7,40k, 5,90k, and 4,75k in the active set. */
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '10010101'B,
								      params := '20882208'O));
	f_TC_assignment_codec_amr(true, mr_cfg, '11111111'B, '00000010'B);
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_f_S0234567() runs on test_CT {
	/* Same as above, but with S1 missing, the MSC is then expected to
	* select the currently supported rates, which are also 12.2k, 7,40k,
	* 5,90k, and 4,75k, into the active set. */
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(codec_modes := '10010101'B,
								      params := '20882208'O));
	f_TC_assignment_codec_amr(true, mr_cfg, '11111101'B, '10010101'B);
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_f_zero() runs on test_CT {
	/* Try to select no rates at all */
	f_TC_assignment_codec_amr_fail(true, '00000000'B);
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_f_unsupp() runs on test_CT {
	/* Try to select only unsupported rates */
	f_TC_assignment_codec_amr_fail(true, '01101000'B);
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_h_S7() runs on test_CT {
	/* Try to select 12,2k for half rate */
	f_TC_assignment_codec_amr_fail(false, '10000000'B);
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_f_start_mode_auto() runs on test_CT {
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(icmi := false,
								      codec_modes := '10010101'B,
								      params := '20882208'O));
	f_TC_assignment_codec_amr(true, mr_cfg, '11111111'B, '00000010'B, start_mode := "auto");
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_h_start_mode_auto() runs on test_CT {
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(icmi := false,
								      codec_modes := '00010101'B,
								      params := '208820'O));
	f_TC_assignment_codec_amr(false, mr_cfg, '10010101'B, '00010101'B,
		start_mode := "auto");
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_f_start_mode_4() runs on test_CT {
	/* "amr tch-f modes 0 2 4 7" => total 4 modes and start mode 4 => '11'B on the wire */
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(start_mode := 3,
								      codec_modes := '10010101'B,
								      params := '20882208'O));
	f_TC_assignment_codec_amr(true, mr_cfg, '11111111'B, '00000010'B, start_mode := "4");
	f_shutdown_helper();
}

testcase TC_assignment_codec_amr_h_start_mode_4() runs on test_CT {
	/* "amr tch-h modes 0 2 4" => total 3 modes and start mode 4 => '10'B on the wire */
	var RSL_IE_MultirateCfg mr_cfg := valueof(ts_RSL_MultirateCfg(start_mode := 2,
								      codec_modes := '00010101'B,
								      params := '208820'O));
	f_TC_assignment_codec_amr(false, mr_cfg, '10010101'B, '00010101'B, start_mode := "4");
	f_shutdown_helper();
}

private function f_disable_all_tch_f() runs on test_CT {
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 1 sub-slot 0 borken");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 2 sub-slot 0 borken");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 3 sub-slot 0 borken");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 4 sub-slot 0 borken");
}

private function f_disable_all_tch_h() runs on test_CT {
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 5 sub-slot 0 borken");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 5 sub-slot 1 borken");
}

private function f_enable_all_tch() runs on test_CT {
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 1 sub-slot 0 unused");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 2 sub-slot 0 unused");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 3 sub-slot 0 unused");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 4 sub-slot 0 unused");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 5 sub-slot 0 unused");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 5 sub-slot 1 unused");
}

private function f_disable_all_sdcch() runs on test_CT {
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 0 sub-slot 0 borken");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 0 sub-slot 1 borken");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 0 sub-slot 2 borken");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 0 sub-slot 3 borken");
}

private function f_enable_all_sdcch() runs on test_CT {
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 0 sub-slot 0 unused");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 0 sub-slot 1 unused");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 0 sub-slot 2 unused");
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 0 sub-slot 3 unused");
}

/* Allow HR only */
private function f_TC_assignment_codec_xr_exhausted_req_hr(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.channelRateAndType := '09'O;
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.speechId_DataIndicator := '05'O;
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecHR}));
	f_establish_fully(ass_cmd, exp_compl);
	f_perform_clear();
}

/* Allow FR only */
private function f_TC_assignment_codec_xr_exhausted_req_fr(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.channelRateAndType := '08'O;
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.speechId_DataIndicator := '01'O;
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));
	f_establish_fully(ass_cmd, exp_compl);
	f_perform_clear();
}

/* Allow HR only (expect assignment failure) */
private function f_TC_assignment_codec_xr_exhausted_req_hr_fail(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	var template PDU_BSSAP exp_fail := tr_BSSMAP_AssignmentFail;
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.channelRateAndType := '09'O;
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.speechId_DataIndicator := '05'O;
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecHR}));
	f_establish_fully(ass_cmd, exp_fail);
	f_perform_clear();
}

/* Allow FR only (expect assignment failure) */
private function f_TC_assignment_codec_xr_exhausted_req_fr_fail(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	var template PDU_BSSAP exp_fail := tr_BSSMAP_AssignmentFail;
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.channelRateAndType := '08'O;
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.speechId_DataIndicator := '01'O;
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));
	f_establish_fully(ass_cmd, exp_fail);
	f_perform_clear();
}

/* Allow FR and HR, but prefer FR */
private function f_TC_assignment_codec_fr_exhausted_req_fr_hr(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.channelRateAndType := '0A'O; /* Prefer FR */
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.speechId_DataIndicator := '8105'O;
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR, ts_CodecHR}));
	exp_compl.pdu.bssmap.assignmentComplete.speechVersion.speechVersionIdentifier := '0000101'B; /* Expect HR */
	f_establish_fully(ass_cmd, exp_compl);
	f_perform_clear();
}

/* Allow FR and HR, but prefer HR */
private function f_TC_assignment_codec_fr_exhausted_req_hr_fr(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.channelRateAndType := '0B'O; /* Prefer HR */
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.speechId_DataIndicator := '8501'O;
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecHR, ts_CodecFR}));
	exp_compl.pdu.bssmap.assignmentComplete.speechVersion.speechVersionIdentifier := '0000101'B; /* Expect HR */
	f_establish_fully(ass_cmd, exp_compl);
	f_perform_clear();
}

/* Allow FR and HR, but prefer FR */
private function f_TC_assignment_codec_hr_exhausted_req_fr_hr(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.channelRateAndType := '0A'O; /* Prefer FR */
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.speechId_DataIndicator := '8105'O;
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR, ts_CodecHR}));
	exp_compl.pdu.bssmap.assignmentComplete.speechVersion.speechVersionIdentifier := '0000001'B; /* Expect FR */
	f_establish_fully(ass_cmd, exp_compl);
	f_perform_clear();
}

/* Allow FR and HR, but prefer HR */
private function f_TC_assignment_codec_hr_exhausted_req_hr_fr(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.channelRateAndType := '0B'O; /* Prefer HR */
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.speechId_DataIndicator := '8501'O;
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecHR, ts_CodecFR}));
	exp_compl.pdu.bssmap.assignmentComplete.speechVersion.speechVersionIdentifier := '0000001'B; /* Expect FR */
	f_establish_fully(ass_cmd, exp_compl);
	f_perform_clear();
}

/* Request a HR channel while all FR channels are exhausted, this is expected
 * to work without conflicts */
testcase TC_assignment_codec_fr_exhausted_req_hr() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	f_enable_all_tch();
	f_disable_all_tch_f();
	vc_conn := f_start_handler(refers(f_TC_assignment_codec_xr_exhausted_req_hr));
	vc_conn.done;
	f_enable_all_tch();
	f_shutdown_helper();
}

/* Request a FR channel while all FR channels are exhausted, this is expected
 * to fail. */
testcase TC_assignment_codec_fr_exhausted_req_fr() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	f_enable_all_tch();
	f_disable_all_tch_f();
	vc_conn := f_start_handler(refers(f_TC_assignment_codec_xr_exhausted_req_fr_fail));
	vc_conn.done;
	f_enable_all_tch();
	f_shutdown_helper();
}

/* Request a FR (prefered) or alternatively a HR channel while all FR channels
 * are exhausted, this is expected to be resolved by selecting a HR channel. */
testcase TC_assignment_codec_fr_exhausted_req_fr_hr() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	f_enable_all_tch();
	f_disable_all_tch_f();
	vc_conn := f_start_handler(refers(f_TC_assignment_codec_fr_exhausted_req_fr_hr));
	vc_conn.done;
	f_enable_all_tch();
	f_shutdown_helper();
}

/* Request a HR (prefered) or alternatively a FR channel while all FR channels
 * are exhausted, this is expected to work without conflicts. */
testcase TC_assignment_codec_fr_exhausted_req_hr_fr() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	f_enable_all_tch();
	f_disable_all_tch_f();
	vc_conn := f_start_handler(refers(f_TC_assignment_codec_fr_exhausted_req_hr_fr));
	vc_conn.done;
	f_enable_all_tch();
	f_shutdown_helper();
}

/* Request a FR channel while all HR channels are exhausted, this is expected
 * to work without conflicts */
testcase TC_assignment_codec_hr_exhausted_req_fr() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	f_enable_all_tch();
	f_disable_all_tch_h();
	vc_conn := f_start_handler(refers(f_TC_assignment_codec_xr_exhausted_req_fr));
	vc_conn.done;
	f_enable_all_tch();
	f_shutdown_helper();
}

/* Request a HR channel while all HR channels are exhausted, this is expected
 * to fail. */
testcase TC_assignment_codec_hr_exhausted_req_hr() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	f_enable_all_tch();
	f_disable_all_tch_h();
	vc_conn := f_start_handler(refers(f_TC_assignment_codec_xr_exhausted_req_hr_fail));
	vc_conn.done;
	f_enable_all_tch();
	f_shutdown_helper();
}

/* Request a HR (prefered) or alternatively a FR channel while all HR channels
 * are exhausted, this is expected to be resolved by selecting a FR channel. */
testcase TC_assignment_codec_hr_exhausted_req_hr_fr() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	f_enable_all_tch();
	f_disable_all_tch_h();
	vc_conn := f_start_handler(refers(f_TC_assignment_codec_hr_exhausted_req_hr_fr));
	vc_conn.done;
	f_enable_all_tch();
	f_shutdown_helper();
}

/* Request a FR (prefered) or alternatively a HR channel while all HR channels
 * are exhausted, this is expected to work without conflicts. */
testcase TC_assignment_codec_hr_exhausted_req_fr_hr() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	f_enable_all_tch();
	f_disable_all_tch_h();
	vc_conn := f_start_handler(refers(f_TC_assignment_codec_hr_exhausted_req_fr_hr));
	vc_conn.done;
	f_enable_all_tch();
	f_shutdown_helper();
}

/* Allow FR and HR, but prefer HR */
private function f_TC_assignment_codec_req_hr_fr(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.channelRateAndType := '0B'O; /* Prefer HR */
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.speechId_DataIndicator := '8501'O;
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecHR, ts_CodecFR}));
	exp_compl.pdu.bssmap.assignmentComplete.speechVersion.speechVersionIdentifier := '0000101'B; /* Expect HR */
	f_establish_fully(ass_cmd, exp_compl);
	f_perform_clear();
}

/* Allow FR and HR, but prefer FR */
private function f_TC_assignment_codec_req_fr_hr(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.channelRateAndType := '0A'O; /* Prefer FR */
	ass_cmd.pdu.bssmap.assignmentRequest.channelType.speechId_DataIndicator := '8105'O;
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR, ts_CodecHR}));
	exp_compl.pdu.bssmap.assignmentComplete.speechVersion.speechVersionIdentifier := '0000001'B; /* Expect FR */
	f_establish_fully(ass_cmd, exp_compl);
	f_perform_clear();
}

/* Request a HR (prefered) or alternatively a FR channel, it is expected that
 * HR, which is the prefered type, is selected. */
testcase TC_assignment_codec_req_hr_fr() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	f_enable_all_tch();
	vc_conn := f_start_handler(refers(f_TC_assignment_codec_req_hr_fr));
	vc_conn.done;
	f_shutdown_helper();
}

/* Request a FR (prefered) or alternatively a HR channel, it is expected that
 * FR, which is the prefered type, is selected. */
testcase TC_assignment_codec_req_fr_hr() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	f_enable_all_tch();
	vc_conn := f_start_handler(refers(f_TC_assignment_codec_req_fr_hr));
	vc_conn.done;
	f_shutdown_helper();
}

/* request a signalling channel with all SDCCH exhausted, it is expected that a TCH will be selected */
private function f_TC_assignment_sdcch_exhausted_req_signalling(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	g_pars.ra := '02'O; /* RA containing reason=LU */

	var MobileIdentityLV mi := valueof(ts_MI_IMSI_LV(g_pars.imsi));
	var PDU_ML3_MS_NW l3_info := valueof(ts_CM_SERV_REQ(CM_TYPE_MO_CALL, mi));
	var octetstring l3_enc := enc_PDU_ML3_MS_NW(l3_info);
	var template uint3_t tsc := ?;

	f_MscConnHdlr_init(g_pars.media_nr, host_bts, host_mgw_mgcp, FR_AMR);
	f_create_bssmap_exp(l3_enc);
	/* call helper function for CHAN_RQD -> IMM ASS ->EST_IND */
	RSL_Emulation.f_chan_est(g_pars.ra, l3_enc, g_pars.link_id, g_pars.fn, tsc);

	/* we should now have a COMPL_L3 at the MSC */
	timer T := 10.0;
	T.start;
	alt {
	[] BSSAP.receive(tr_BSSMAP_ComplL3);
	[] T.timeout {
		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for COMPLETE LAYER 3 INFORMATION");
		}
	}

	f_perform_clear();
}
testcase TC_assignment_sdcch_exhausted_req_signalling() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	f_disable_all_sdcch();
	vc_conn := f_start_handler(refers(f_TC_assignment_sdcch_exhausted_req_signalling));
	vc_conn.done;
	f_enable_all_sdcch();
	f_shutdown_helper();
}

/* Request a signalling channel with all SDCCH exhausted, it is
   expected that no TCH will be selected for signalling and assigment will fail
   because it's dictated by VTY config */
testcase TC_assignment_sdcch_exhausted_req_signalling_tch_forbidden() runs on test_CT {
	var RSL_Message rsl_unused, rsl_msg;
	var GsmRrMessage rr;
	f_init(1, false);
	f_sleep(1.0);
	f_vty_allow_tch_for_signalling(false, 0);
	f_disable_all_sdcch();

	/* RA containing reason=LU */
	f_ipa_tx(ts_RSL_CHAN_RQD('02'O, 2342));
	rsl_msg := f_exp_ipa_rx(tr_RSL_IMM_ASSIGN(0));
	rr := dec_GsmRrMessage(rsl_msg.ies[1].body.full_imm_ass_info.payload);
	if (rr.header.message_type != IMMEDIATE_ASSIGNMENT_REJECT) {
		setverdict(fail, "Expected reject");
	}

	f_vty_allow_tch_for_signalling(true, 0);
	f_enable_all_sdcch();
	f_shutdown_helper();
}

/* Request a voice channel with all SDCCH exhausted, it is
 * expected that TCH channel will be allocated since the VTY option is only
 * aimed at signalling requests */
private function f_TC_assignment_sdcch_exhausted_req_voice_tch_forbidden(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	g_pars.ra := '43'O; /* RA containing reason=originating speech call*/

	var MobileIdentityLV mi := valueof(ts_MI_IMSI_LV(g_pars.imsi));
	var PDU_ML3_MS_NW l3_info := valueof(ts_CM_SERV_REQ(CM_TYPE_MO_CALL, mi));
	var octetstring l3_enc := enc_PDU_ML3_MS_NW(l3_info);
	var template uint3_t tsc := ?;

	f_MscConnHdlr_init(g_pars.media_nr, host_bts, host_mgw_mgcp, FR_AMR);
	f_create_bssmap_exp(l3_enc);
	/* call helper function for CHAN_RQD -> IMM ASS ->EST_IND */
	RSL_Emulation.f_chan_est(g_pars.ra, l3_enc, g_pars.link_id, g_pars.fn, tsc);

	/* we should now have a COMPL_L3 at the MSC */
	timer T := 10.0;
	T.start;
	alt {
	[] BSSAP.receive(tr_BSSMAP_ComplL3);
	[] T.timeout {
		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for COMPLETE LAYER 3 INFORMATION");
		}
	}
	f_perform_clear();
}
testcase TC_assignment_sdcch_exhausted_req_voice_tch_forbidden() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	f_vty_allow_tch_for_signalling(false, 0);
	f_disable_all_sdcch();

	vc_conn := f_start_handler(refers(f_TC_assignment_sdcch_exhausted_req_voice_tch_forbidden));
	vc_conn.done;

	f_vty_allow_tch_for_signalling(true, 0);
	f_enable_all_sdcch();
	f_shutdown_helper();
}

/* Test Osmux setup BSC<->MSC */
testcase TC_assignment_osmux_cn() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecAMR_H}));
	pars.ass_codec_list.codecElements[0].s0_7 := '00000100'B; /* 5,90k */
	pars.ass_codec_list.codecElements[0].s8_15 := '00000111'B;
	pars.expect_mr_conf_ie := c_mr_conf_5_90;
	pars.use_osmux_cn := true;

	g_osmux_enabled_cn := true;
	f_init(1, true);
	f_sleep(1.0);
	f_vty_amr_start_mode_set(false, "1");

	vc_conn := f_start_handler(refers(f_TC_assignment_codec), pars);
	vc_conn.done;

	f_vty_amr_start_mode_restore(false);
	f_shutdown_helper();
}

/* Test Osmux setup BTS<->BSC */
testcase TC_assignment_osmux_bts() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecAMR_H}));
	pars.ass_codec_list.codecElements[0].s0_7 := '00000100'B; /* 5,90k */
	pars.ass_codec_list.codecElements[0].s8_15 := '00000111'B;
	pars.expect_mr_conf_ie := c_mr_conf_5_90;
	pars.use_osmux_bts := true;

	g_osmux_enabled_bts := true;
	f_init(1, true);
	f_sleep(1.0);
	f_vty_amr_start_mode_set(false, "1");

	vc_conn := f_start_handler(refers(f_TC_assignment_codec), pars);
	vc_conn.done;

	f_vty_amr_start_mode_restore(false);
	f_shutdown_helper();
}

/* Test non-AMR codecs still work fine as RTP when Osmux is enabled BTS<->BSC<->MSC */
testcase TC_assignment_codec_hr_osmux_on() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	g_osmux_enabled_cn := true;
	g_osmux_enabled_bts := true;
	f_init(1, true);
	f_sleep(1.0);
	f_ctrs_bsc_and_bts_assignment_init(1);

	pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecHR}));
	vc_conn := f_start_handler(refers(f_TC_assignment_codec), pars);
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "assignment:attempted", 1);
	f_ctrs_bts_add(0, "assignment:attempted_speech", 1);
	f_ctrs_bsc_and_bts_add(0, "assignment:completed", 1);
	f_ctrs_bts_add(0, "assignment:completed_speech", 1);
	f_ctrs_bts_verify();

	f_shutdown_helper();
}

/* Test Osmux setup BTS<->BSC<->MSC */
testcase TC_assignment_osmux() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecAMR_H}));
	pars.ass_codec_list.codecElements[0].s0_7 := '00000100'B; /* 5,90k */
	pars.ass_codec_list.codecElements[0].s8_15 := '00000111'B;
	pars.expect_mr_conf_ie := c_mr_conf_5_90;
	pars.use_osmux_cn := true;
	pars.use_osmux_bts := true;

	g_osmux_enabled_cn := true;
	g_osmux_enabled_bts := true;
	f_init(1, true);
	f_sleep(1.0);
	f_vty_amr_start_mode_set(false, "1");

	vc_conn := f_start_handler(refers(f_TC_assignment_codec), pars);
	vc_conn.done;

	f_vty_amr_start_mode_restore(false);
	f_shutdown_helper();
}

/* test the procedure of the MSC requesting a Classmark Update:
 * a) BSSMAP Classmark Request should result in RR CLASSMARK ENQUIRY,
 * b) L3 RR CLASSMARK CHANGE should result in BSSMAP CLASSMARK UPDATE */
private function f_tc_classmark(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();

	f_create_chan_and_exp();
	/* we should now have a COMPL_L3 at the MSC */

	BSSAP.send(ts_BSSMAP_ClassmarkRequest);
	RSL.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, decmatch tr_RRM_CM_ENQUIRY));

	f_rsl_send_l3(ts_RRM_CM_CHG(valueof(ts_CM2)));
	BSSAP.receive(tr_BSSMAP_ClassmarkUpd(?, omit));
	setverdict(pass);

	f_perform_clear();
}
testcase TC_classmark() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_classmark));
	vc_conn.done;
	f_shutdown_helper();
}

/* Send a CommonID from the simulated MSC and verify that the information is used to
 * fill BSC-internal data structures (specifically, bsc_subscr associated with subscr_conn) */
private function f_tc_common_id(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	f_MscConnHdlr_init_vty();

	f_create_chan_and_exp();
	/* we should now have a COMPL_L3 at the MSC */

	/* Send CommonID */
	BSSAP.send(ts_BSSMAP_CommonId(g_pars.imsi));

	/* Use VTY to verify that the IMSI of the subscr_conn is set */
	var charstring regex := "*(IMSI: " & hex2str(g_pars.imsi) & ")*";
	f_vty_transceive_match_regexp_retry(BSCVTY, "show conns", regex, 0, 4, 1.0);

	setverdict(pass);

	f_perform_clear();
}
testcase TC_common_id() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_common_id));
	vc_conn.done;
	f_shutdown_helper();
}

private function f_est_single_l3(template PDU_ML3_MS_NW l3) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	f_create_chan_and_exp();
	/* we should now have a COMPL_L3 at the MSC */

	/* send the single message we want to send */
	f_rsl_send_l3(l3);
}

private function f_bssap_expect_nothing(float sec := 5.00) runs on MSC_ConnHdlr {
	timer T := sec;
	var PDU_BSSAP bssap;
	T.start;
	alt {
	[] BSSAP.receive(PDU_BSSAP:?) -> value bssap {
		setverdict(fail, "Unexpected BSSMAP ", bssap);
		mtc.stop;
		}
	[] T.timeout {
		setverdict(pass);
		}
	}
}

/* unsolicited ASSIGNMENT FAIL (without ASSIGN) from MS shouldn't bring BSC down */
private function f_tc_unsol_ass_fail(charstring id) runs on MSC_ConnHdlr {
	f_est_single_l3(ts_RRM_AssignmentFailure('00'O));
	f_bssap_expect_nothing();
	f_perform_clear();
}
testcase TC_unsol_ass_fail() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_unsol_ass_fail));
	vc_conn.done;
	f_shutdown_helper();
}


/* unsolicited ASSIGNMENT COMPLETE (without ASSIGN) from MS shouldn't bring BSC down */
private function f_tc_unsol_ass_compl(charstring id) runs on MSC_ConnHdlr {
	f_est_single_l3(ts_RRM_AssignmentComplete('00'O));
	f_bssap_expect_nothing();
	f_perform_clear();
}
testcase TC_unsol_ass_compl() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_unsol_ass_compl));
	vc_conn.done;
	f_shutdown_helper();
}


/* unsolicited HANDOVER FAIL (without ASSIGN) from MS shouldn't bring BSC down */
private function f_tc_unsol_ho_fail(charstring id) runs on MSC_ConnHdlr {
	f_est_single_l3(ts_RRM_HandoverFailure('00'O));
	f_bssap_expect_nothing();
	f_perform_clear();
}
testcase TC_unsol_ho_fail() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_unsol_ho_fail));
	vc_conn.done;
	f_shutdown_helper();
}


/* short message from MS should be ignored */
private function f_tc_err_82_short_msg(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	f_create_chan_and_exp();
	/* we should now have a COMPL_L3 at the MSC */

	/* send short message */
	RSL.send(ts_RSL_DATA_IND(g_chan_nr, valueof(ts_RslLinkID_DCCH(0)), ''O));
	f_bssap_expect_nothing();
	f_perform_clear();
}
testcase TC_err_82_short_msg() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_err_82_short_msg));
	vc_conn.done;
	f_shutdown_helper();
}

/* 24.008 8.4 Unknown message must trigger RR STATUS */
private function f_tc_err_84_unknown_msg(charstring id) runs on MSC_ConnHdlr {
	f_est_single_l3(ts_RRM_DTM_ASS_FAIL('00'O));
	timer T := 3.0
	alt {
	[] RSL.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, decmatch tr_RRM_RR_STATUS)) {
		setverdict(pass);
		}
	[] BSSAP.receive { setverdict(fail, "unexpected BSSAP"); }
	[] T.timeout { setverdict(fail, "Timeout waiting for RR STATUS"); }
	}
	f_perform_clear();
}
testcase TC_err_84_unknown_msg() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_err_84_unknown_msg));
	vc_conn.done;
	f_shutdown_helper();
}

/***********************************************************************
 * Handover
 ***********************************************************************/

/* execute a "bts <0-255> trx <0-255> timeslot <0-7> " command on given Dchan */
private function f_vty_ts_action(charstring suffix, integer bts_nr, integer trx_nr, integer ts_nr)
runs on test_CT {
	var charstring cmd := "bts "&int2str(bts_nr)&" trx "&int2str(trx_nr)&
				" timeslot "&int2str(ts_nr)&" ";
	f_vty_transceive(BSCVTY, cmd & suffix);
}

/* execute a "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7>" command on given Dchan */
private function f_vty_ss_action(TELNETasp_PT pt, charstring suffix,
				 uint8_t bts_nr, uint8_t trx_nr,
				 in RslChannelNr chan_nr)
{
	/* FIXME: resolve those from component-global state */
	var integer ts_nr := chan_nr.tn;
	var integer ss_nr;
	if (ischosen(chan_nr.u.ch0)) {
		ss_nr := 0;
	} else if (ischosen(chan_nr.u.lm)) {
		ss_nr := chan_nr.u.lm.sub_chan;
	} else if (ischosen(chan_nr.u.sdcch4)) {
		ss_nr := chan_nr.u.sdcch4.sub_chan;
	} else if (ischosen(chan_nr.u.sdcch8)) {
		ss_nr := chan_nr.u.sdcch8.sub_chan;
	} else {
		setverdict(fail, "Invalid ChanNr ", chan_nr);
		mtc.stop;
	}

	var charstring cmd := "bts "&int2str(bts_nr)&" trx "&int2str(trx_nr)&
				" timeslot "&int2str(ts_nr)&" sub-slot "&int2str(ss_nr)&" ";
	f_vty_transceive(pt, cmd & suffix);
}

/* Even though the VTY command to trigger handover takes a new BTS number as argument, behind the scenes osmo-bsc always
 * translates that to a target ARFCN+BSIC first. See bsc_vty.c trigger_ho_or_as(), which puts the selected BTS' neighbor
 * ident key (ARFCN + BSIC) in the struct passed on to handover_request(). handover_start() then resolves that to a
 * viable actual neighbor cell. So from the internal osmo-bsc perspective, we always request handover to an ARFCN + BSIC
 * pair, not really to a specific BTS number. */
private function f_vty_handover(TELNETasp_PT pt, uint8_t bts_nr, uint8_t trx_nr,
				in RslChannelNr chan_nr, uint8_t new_bts_nr)
{
	f_vty_ss_action(pt, "handover " & int2str(new_bts_nr), bts_nr, trx_nr, chan_nr);
}

/* intra-BSC hand-over between BTS0 and BTS1 */
private function f_tc_ho_int(charstring id) runs on MSC_ConnHdlr {
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();

	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));

	f_establish_fully(ass_cmd, exp_compl);
	f_bts_0_cfg(BSCVTY, {"neighbor bts 1"});

	var HandoverState hs := {
		rr_ho_cmpl_seen := false,
		handover_done := false,
		old_chan_nr := -,
		expect_target_tsc := c_BtsParams[1].tsc
	};

	/* temporarily suspend DChan processing on BTS1 to avoid race with RSLEM_register */
	f_rslem_suspend(RSL1_PROC);
	/* issue hand-over command on VTY, from BTS 0 to BTS 1 */
	f_vty_handover(BSCVTY, 0, 0, g_chan_nr, 1);

	/* From the MGW perspective, a handover is is characterized by
	 * performing one MDCX operation with the MGW. So we expect to see
	 * one more MDCX during handover. */
	g_media.mgcp_conn[0].mdcx_seen_exp := g_media.mgcp_conn[0].crcx_seen_exp + 1;

	alt {
	[] as_handover(hs);
	}

	/* Since this is an internal handover we expect the BSC to inform the
	 * MSC about the event */
	BSSAP.receive(tr_BSSMAP_HandoverPerformed);

	/* Check the amount of MGCP transactions is still consistant with the
	 * test expectation */
	f_check_mgcp_expectations()

	var RSL_Message chan_act := f_rslem_get_last_act(RSL1_PROC, 0, g_chan_nr);

	/* Ensure the Channel Activation for the new channel contained the right encryption params. as_handover() set
	 * g_chan_nr to the new lchan that was handed over to. It lives in bts 1, so look it up at RSL1_PROC. */
	f_verify_encr_info(chan_act);

	f_chan_act_verify_tsc(chan_act, c_BtsParams[1].tsc);

	f_perform_clear(RSL1, RSL1_PROC);

	f_sleep(0.5);
}

testcase TC_ho_int() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;
	f_init(2, true);
	f_sleep(1.0);

	pars.expect_tsc := c_BtsParams[0].tsc;

	f_ctrs_bsc_and_bts_handover_init();

	vc_conn := f_start_handler(refers(f_tc_ho_int), pars);
	vc_conn.done;

	/* from f_establish_fully() */
	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");
	/* from handover */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:completed");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:attempted");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:completed");
	f_ctrs_bts_add(1, "incoming_intra_bsc_ho:attempted");
	f_ctrs_bts_add(1, "incoming_intra_bsc_ho:completed");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

function f_tc_ho_int_a5(OCT1 encr_alg, charstring enc_a5 := "0 1 3") runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.encr := valueof(t_EncrParams(encr_alg, f_rnd_octstring(8), f_rnd_octstring(16)));

	f_init(2, true);
	f_vty_encryption_a5(enc_a5);
	f_sleep(1.0);

	f_ctrs_bsc_and_bts_handover_init();

	vc_conn := f_start_handler(refers(f_tc_ho_int), pars);
	vc_conn.done;

	/* from f_establish_fully() */
	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");
	/* from handover */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:completed");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:attempted");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:completed");
	f_ctrs_bts_add(1, "incoming_intra_bsc_ho:attempted");
	f_ctrs_bts_add(1, "incoming_intra_bsc_ho:completed");
	f_ctrs_bsc_and_bts_verify();
	f_vty_encryption_a5_reset();
	f_shutdown_helper(ho := true);
}

testcase TC_ho_int_a5_0() runs on test_CT {
	f_tc_ho_int_a5('01'O);
}

testcase TC_ho_int_a5_1() runs on test_CT {
	f_tc_ho_int_a5('02'O);
}

testcase TC_ho_int_a5_3() runs on test_CT {
	f_tc_ho_int_a5('08'O);
}

testcase TC_ho_int_a5_4() runs on test_CT {
	f_tc_ho_int_a5('10'O, "0 1 3 4");
}

/* intra-BSC hand-over with CONNection FAILure and cause Radio Link Failure: check RR release cause */
private function f_tc_ho_int_radio_link_failure(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();

	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));

	f_establish_fully(ass_cmd, exp_compl);
	f_bts_0_cfg(BSCVTY, {"neighbor bts 1"});

	var HandoverState hs := {
		rr_ho_cmpl_seen := false,
		handover_done := false,
		old_chan_nr := -,
		expect_target_tsc := c_BtsParams[1].tsc
	};

	/* temporarily suspend DChan processing on BTS1 to avoid race with RSLEM_register */
	f_rslem_suspend(RSL1_PROC);
	/* issue hand-over command on VTY */
	f_vty_handover(BSCVTY, 0, 0, g_chan_nr, 1);

	/* From the MGW perspective, a handover is is characterized by
	 * performing one MDCX operation with the MGW. So we expect to see
	 * one more MDCX during handover. */
	g_media.mgcp_conn[0].mdcx_seen_exp := g_media.mgcp_conn[0].crcx_seen_exp + 1;

	var RSL_Message rsl;
	var PDU_ML3_NW_MS l3;
	var RslChannelNr new_chan_nr;
	var GsmArfcn arfcn;
	RSL.receive(tr_RSL_DATA_REQ(g_chan_nr)) -> value rsl {
		l3 := dec_PDU_ML3_NW_MS(rsl.ies[2].body.l3_info.payload);
		if (not ischosen(l3.msgs.rrm.handoverCommand)) {
			setverdict(fail, "Expected handoverCommand");
			mtc.stop;
		}
	}
	f_ChDesc2RslChanNr(l3.msgs.rrm.handoverCommand.channelDescription2,
			   new_chan_nr, arfcn);

	f_rslem_register(0, new_chan_nr, RSL1_PROC);

	/* resume processing of RSL DChan messages, which was temporarily suspended
	 * before performing a hand-over */
	f_rslem_resume(RSL1_PROC);
	RSL1.receive(tr_RSL_IPA_CRCX(new_chan_nr));

	f_sleep(1.0);

	/* Handover fails because no HANDO DET appears on the new lchan,
	 * and the old lchan reports a Radio Link Failure. */
	RSL.send(ts_RSL_CONN_FAIL_IND(g_chan_nr, RSL_ERR_RADIO_LINK_FAIL));

	var PDU_BSSAP rx_clear_request;
	BSSAP.receive(tr_BSSMAP_ClearRequest) -> value rx_clear_request;
	var BssmapCause cause := bit2int(rx_clear_request.pdu.bssmap.clearRequest.cause.causeValue);
	BSSAP.send(ts_BSSMAP_ClearCommand(cause));

	var RR_Cause rr_cause := GSM48_RR_CAUSE_ABNORMAL_UNSPEC;

	var MgcpCommand mgcp;
	interleave {
	[] RSL.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, decmatch tr_RRM_RR_RELEASE(int2oct(enum2int(rr_cause), 1)))) {}
	[] RSL.receive(tr_RSL_DEACT_SACCH(g_chan_nr)) {}
	[] RSL.receive(tr_RSL_RF_CHAN_REL(g_chan_nr)) {
			RSL.send(ts_RSL_RF_CHAN_REL_ACK(g_chan_nr));
			f_rslem_unregister(0, g_chan_nr);
		}
	[] RSL1.receive(tr_RSL_DEACT_SACCH(new_chan_nr)) {}
	[] RSL1.receive(tr_RSL_RF_CHAN_REL(new_chan_nr)) {
			RSL1.send(ts_RSL_RF_CHAN_REL_ACK(new_chan_nr));
			f_rslem_unregister(0, g_chan_nr, PT := RSL1_PROC);
		}
	[] BSSAP.receive(tr_BSSMAP_ClearComplete) {
			BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ);
		}
	}

	f_sleep(0.5);
	setverdict(pass);
}
testcase TC_ho_int_radio_link_failure() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(2, true);
	f_sleep(1.0);

	f_ctrs_bsc_and_bts_handover_init();

	vc_conn := f_start_handler(refers(f_tc_ho_int_radio_link_failure));
	vc_conn.done;

	/* from f_establish_fully() */
	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");
	/* from handover */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:stopped");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:attempted");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:stopped");
	f_ctrs_bts_add(1, "incoming_intra_bsc_ho:attempted");
	f_ctrs_bts_add(1, "incoming_intra_bsc_ho:stopped");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

/* Expecting MGCP to DLCX the endpoint's two connections: towards BTS and towards MSC */
private function f_expect_dlcx_conns() runs on MSC_ConnHdlr {
	var MgcpCommand mgcp;
	var template MgcpResponse mgcp_resp;
	var MGCP_RecvFrom mrf;
	var template MgcpMessage msg_resp;
	var template MgcpMessage msg_dlcx := {
		command := tr_DLCX()
	}

	if (g_pars.aoip) {
		MGCP.receive(tr_DLCX()) -> value mgcp {
				log("Got first DLCX: ", mgcp);
				MGCP.send(ts_DLCX_ACK2(mgcp.line.trans_id));
			};

		MGCP.receive(tr_DLCX()) -> value mgcp {
				log("Got second DLCX: ", mgcp);
				MGCP.send(ts_DLCX_ACK2(mgcp.line.trans_id));
			};
	} else  {
		/* For SCCPLite, BSC doesn't handle the MSC-side */
		MGCP_MULTI.receive(tr_MGCP_RecvFrom_any(msg_dlcx)) -> value mrf {
			log("Got first DLCX: ", mrf.msg.command);
			msg_resp := {
				response := ts_DLCX_ACK2(mrf.msg.command.line.trans_id)
			}
			MGCP_MULTI.send(t_MGCP_SendToMrf(mrf, msg_resp));
		};
	}
}

private function f_ho_out_of_this_bsc(template (omit) BSSMAP_oldToNewBSSIEs exp_oldToNewBSSIEs := omit,
				      boolean skip_meas_rep := false) runs on MSC_ConnHdlr {

	if (not skip_meas_rep) {
		var NcellReports neighbor_rep := {
			{ rxlev := 20, bcch_freq := 0, bsic := 11 }
		};
		var octetstring l3_mr := enc_GsmRrL3Message(valueof(ts_MEAS_REP(true, 8, 8, reps := neighbor_rep)));
		RSL.send(ts_RSL_MEAS_RES(g_chan_nr, 0, ts_RSL_IE_UplinkMeas, ts_RSL_IE_BS_Power(0), ts_RSL_IE_L1Info,
					 l3_mr, 0));

		BSSAP.receive(tr_BSSMAP_HandoverRequired(exp_oldToNewBSSIEs));
	}

	f_sleep(0.5);
	/* The MSC negotiates Handover Request and Handover Request Ack with
	 * the other BSS and comes back with a BSSMAP Handover Command
	 * containing an RR Handover Command coming from the target BSS... */

	var PDU_ML3_NW_MS rr_ho_cmd := valueof(ts_RR_HandoverCommand);
	log("Remote cell's RR Handover Command passed through as L3 Info: ", rr_ho_cmd);
	var octetstring rr_ho_cmd_enc := enc_PDU_ML3_NW_MS(rr_ho_cmd);
	log("Remote cell's RR Handover Command passed through as L3 Info, encoded: ", rr_ho_cmd_enc);
	BSSAP.send(ts_BSSMAP_HandoverCommand(rr_ho_cmd_enc));

	/* expect the Handover Command to go out on RR */
	var RSL_Message rsl_ho_cmd
	RSL.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, ?)) -> value rsl_ho_cmd;
	log("RSL Data Req went out to first BTS: ", rsl_ho_cmd);
	var RSL_IE_Body rsl_ho_cmd_l3;
	if (not f_rsl_find_ie(rsl_ho_cmd, RSL_IE_L3_INFO, rsl_ho_cmd_l3)) {
		log("RSL message contains no L3 Info IE, expected RR Handover Command");
		setverdict(fail);
	} else {
		log("Found L3 Info: ", rsl_ho_cmd_l3);
		if (rsl_ho_cmd_l3.l3_info.payload != rr_ho_cmd_enc) {
			log("FAIL: the BSC sent out a different L3 Info, not matching the RR Handover Command the other BSS forwarded.");
			setverdict(fail);
		} else {
			log("Success: the BSC sent out the same RR Handover Command the other BSS forwarded.");
			setverdict(pass);
		}
	}

	/* When the other BSS has reported a completed handover, this side is
	 * torn down. */

	var myBSSMAP_Cause cause_val := GSM0808_CAUSE_HANDOVER_SUCCESSFUL;
	var BssmapCause cause := enum2int(cause_val);
	BSSAP.send(ts_BSSMAP_ClearCommand(cause));

	f_expect_dlcx_conns();

	interleave {
	[] RSL.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, decmatch tr_RRM_RR_RELEASE));
	[] RSL.receive(tr_RSL_DEACT_SACCH(g_chan_nr));
	[] RSL.receive(tr_RSL_RF_CHAN_REL(g_chan_nr));
	[] BSSAP.receive(tr_BSSMAP_ClearComplete) {
			BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ);
		}
	}
	setverdict(pass);
}

private function f_tc_ho_out_of_this_bsc(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var PDU_BSSAP ass_req := f_gen_ass_req();
	ass_req.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_req.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	f_establish_fully(ass_req, exp_compl);

	f_ho_out_of_this_bsc();
}
testcase TC_ho_out_of_this_bsc() runs on test_CT {
	var MSC_ConnHdlr vc_conn;

	f_init_vty();
	f_bts_0_cfg(BSCVTY,
		    {"neighbor-list mode automatic",
		     "handover 1",
		     "handover algorithm 2",
		     "handover2 window rxlev averaging 1",
		     "no neighbors",
		     "neighbor lac 99 arfcn 123 bsic any"});
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1, true);
	f_sleep(1.0);

	f_ctrs_bsc_and_bts_handover_init();

	vc_conn := f_start_handler(refers(f_tc_ho_out_of_this_bsc));
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:completed");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:attempted");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:completed");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

private function f_mo_l3_transceive(RSL_DCHAN_PT rsl := RSL,
				    template (value) RslLinkId link_id := ts_RslLinkID_DCCH(0),
				    template (present) OCT1 dlci := ?,
				    octetstring l3 := '0123456789'O)
runs on MSC_ConnHdlr {
	/* The old lchan and conn should still be active. See that arbitrary L3
	 * is still going through. */
	rsl.send(ts_RSL_DATA_IND(g_chan_nr, link_id, l3));
	var template PDU_BSSAP exp_data := {
		    discriminator := '1'B,
		    spare := '0000000'B,
		    dlci := dlci,
		    lengthIndicator := lengthof(l3),
		    pdu := {
			dtap := l3
		    }
		};
	BSSAP.receive(exp_data);
	setverdict(pass);
}

private function f_mt_l3_transceive(RSL_DCHAN_PT rsl := RSL,
				    template (present) RslLinkId link_id := tr_RslLinkID_DCCH(0),
				    template (value) OCT1 dlci := '00'O,
				    octetstring l3 := '0123456789'O)
runs on MSC_ConnHdlr {
	BSSAP.send(PDU_BSSAP:{
		discriminator := '1'B,
		spare := '0000000'B,
		dlci := dlci,
		lengthIndicator := lengthof(l3),
		pdu := {
			dtap := l3
		}
	});
	rsl.receive(tr_RSL_DATA_REQ(g_chan_nr, link_id, l3));
	setverdict(pass);
}

/* BSC asks for inter-BSC HO, but the MSC decides that it won't happen and
 * simply never sends a BSSMAP Handover Command. */
private function f_tc_ho_out_fail_no_msc_response(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();

	var PDU_BSSAP ass_req := f_gen_ass_req();
	ass_req.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_req.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	f_establish_fully(ass_req, exp_compl);

	f_bts_0_cfg(BSCVTY, {"no neighbor lac 99", "neighbor lac 99 arfcn 123 bsic any"});
	f_vty_transceive(BSCVTY, "handover any to arfcn 123 bsic any");

	BSSAP.receive(tr_BSSMAP_HandoverRequired);

	/* osmo-bsc should time out 10 seconds after the handover started.
	 * Let's give it a bit extra. */
	f_sleep(15.0);

	f_mo_l3_transceive();
	f_sleep(1.0);
	f_perform_clear();
}
testcase TC_ho_out_fail_no_msc_response() runs on test_CT {
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	f_ctrs_bsc_and_bts_handover_init();

	vc_conn := f_start_handler(refers(f_tc_ho_out_fail_no_msc_response));
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:timeout");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:attempted");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:timeout");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

/* BSC asks for inter-BSC HO, receives BSSMAP Handover Command, but MS reports
 * RR Handover Failure. */
private function f_tc_ho_out_fail_rr_ho_failure(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();

	var PDU_BSSAP ass_req := f_gen_ass_req();
	ass_req.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_req.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	f_establish_fully(ass_req, exp_compl);

	f_bts_0_cfg(BSCVTY, {"no neighbor lac 99", "neighbor lac 99 arfcn 123 bsic any"});
	f_vty_transceive(BSCVTY, "handover any to arfcn 123 bsic any");

	BSSAP.receive(tr_BSSMAP_HandoverRequired);

	f_sleep(0.5);
	/* The MSC negotiates Handover Request and Handover Request Ack with
	 * the other BSS and comes back with a BSSMAP Handover Command
	 * containing an RR Handover Command coming from the target BSS... */

	var PDU_ML3_NW_MS rr_ho_cmd := valueof(ts_RR_HandoverCommand);
	log("Remote cell's RR Handover Command passed through as L3 Info: ", rr_ho_cmd);
	var octetstring rr_ho_cmd_enc := enc_PDU_ML3_NW_MS(rr_ho_cmd);
	log("Remote cell's RR Handover Command passed through as L3 Info, encoded: ", rr_ho_cmd_enc);
	BSSAP.send(ts_BSSMAP_HandoverCommand(rr_ho_cmd_enc));

	/* expect the Handover Command to go out on RR */
	var RSL_Message rsl_ho_cmd
	RSL.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, ?)) -> value rsl_ho_cmd;
	log("RSL Data Req went out to first BTS: ", rsl_ho_cmd);
	var RSL_IE_Body rsl_ho_cmd_l3;
	if (not f_rsl_find_ie(rsl_ho_cmd, RSL_IE_L3_INFO, rsl_ho_cmd_l3)) {
		log("RSL message contains no L3 Info IE, expected RR Handover Command");
		setverdict(fail);
	} else {
		log("Found L3 Info: ", rsl_ho_cmd_l3);
		if (rsl_ho_cmd_l3.l3_info.payload != rr_ho_cmd_enc) {
			log("FAIL: the BSC sent out a different L3 Info, not matching the RR Handover Command the other BSS forwarded.");
			setverdict(fail);
		} else {
			log("Success: the BSC sent out the same RR Handover Command the other BSS forwarded.");
			setverdict(pass);
		}
	}

	f_sleep(0.2);
	f_rsl_send_l3(ts_RRM_HandoverFailure('00'O));

	/* Should tell the MSC about the failure */
	BSSAP.receive(tr_BSSMAP_HandoverFailure);

	f_sleep(1.0);

	f_mo_l3_transceive();
	f_sleep(1.0);

	setverdict(pass);
	f_sleep(1.0);
	f_perform_clear();
}
testcase TC_ho_out_fail_rr_ho_failure() runs on test_CT {
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	f_ctrs_bsc_and_bts_handover_init();

	vc_conn := f_start_handler(refers(f_tc_ho_out_fail_rr_ho_failure));
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:failed");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:attempted");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:failed");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

/* BSC asks for inter-BSC-out HO, receives BSSMAP Handover Command, but then no reply is received about HO outcome
 * (neither BSSMAP Clear Command for success nor RR Handover Failure). 48.008 3.1.5.3.3 "Abnormal Conditions" applies
 * and the lchan is released. */
private function f_tc_ho_out_fail_no_result_after_ho_cmd(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();

	var PDU_BSSAP ass_req := f_gen_ass_req();
	ass_req.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_req.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	f_establish_fully(ass_req, exp_compl);

	f_bts_0_cfg(BSCVTY, {"no neighbor lac 99", "neighbor lac 99 arfcn 123 bsic any"});
	f_vty_transceive(BSCVTY, "handover any to arfcn 123 bsic any");

	BSSAP.receive(tr_BSSMAP_HandoverRequired);

	f_sleep(0.5);
	/* The MSC negotiates Handover Request and Handover Request Ack with
	 * the other BSS and comes back with a BSSMAP Handover Command
	 * containing an RR Handover Command coming from the target BSS... */

	var PDU_ML3_NW_MS rr_ho_cmd := valueof(ts_RR_HandoverCommand);
	log("Remote cell's RR Handover Command passed through as L3 Info: ", rr_ho_cmd);
	var octetstring rr_ho_cmd_enc := enc_PDU_ML3_NW_MS(rr_ho_cmd);
	log("Remote cell's RR Handover Command passed through as L3 Info, encoded: ", rr_ho_cmd_enc);
	BSSAP.send(ts_BSSMAP_HandoverCommand(rr_ho_cmd_enc));

	/* expect the Handover Command to go out on RR */
	var RSL_Message rsl_ho_cmd
	RSL.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, ?)) -> value rsl_ho_cmd;
	log("RSL Data Req went out to first BTS: ", rsl_ho_cmd);
	var RSL_IE_Body rsl_ho_cmd_l3;
	if (not f_rsl_find_ie(rsl_ho_cmd, RSL_IE_L3_INFO, rsl_ho_cmd_l3)) {
		log("RSL message contains no L3 Info IE, expected RR Handover Command");
		setverdict(fail);
	} else {
		log("Found L3 Info: ", rsl_ho_cmd_l3);
		if (rsl_ho_cmd_l3.l3_info.payload != rr_ho_cmd_enc) {
			log("FAIL: the BSC sent out a different L3 Info, not matching the RR Handover Command the other BSS forwarded.");
			setverdict(fail);
		} else {
			log("Success: the BSC sent out the same RR Handover Command the other BSS forwarded.");
			setverdict(pass);
		}
	}

	/* We get neither success nor failure report from the remote BSS.  Eventually T8 times out and we run into 3GPP
	 * TS 48.008 3.1.5.3.3 "Abnormal Conditions": Clear Request should go to the MSC, and RR should be released
	 * after Clear Command */

	var PDU_BSSAP rx_clear_request;
	BSSAP.receive(tr_BSSMAP_ClearRequest) -> value rx_clear_request;
	log("Got BSSMAP Clear Request");
	/* Instruct BSC to clear channel */
	var BssmapCause cause := bit2int(rx_clear_request.pdu.bssmap.clearRequest.cause.causeValue);
	BSSAP.send(ts_BSSMAP_ClearCommand(cause));

	var MgcpCommand mgcp;
	interleave {
	[] RSL.receive(tr_RSL_DEACT_SACCH(g_chan_nr)) {
			log("Got Deact SACCH");
		}
	[] RSL.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, decmatch tr_RRM_RR_RELEASE)) {
			log("Got RR Release");
		}
	[] RSL.receive(tr_RSL_RF_CHAN_REL(g_chan_nr)) {
			log("Got RF Chan Rel");
			RSL.send(ts_RSL_RF_CHAN_REL_ACK(g_chan_nr));
			f_rslem_unregister(0, g_chan_nr);
		}
	}

	f_expect_dlcx_conns();
	BSSAP.receive(tr_BSSMAP_ClearComplete);
	BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ);

	setverdict(pass);
	f_sleep(1.0);
}
testcase TC_ho_out_fail_no_result_after_ho_cmd() runs on test_CT {
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	f_ctrs_bsc_and_bts_handover_init();

	vc_conn := f_start_handler(refers(f_tc_ho_out_fail_no_result_after_ho_cmd));
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:timeout");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:attempted");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:timeout");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

private function f_ho_into_this_bsc(charstring id, template (omit) BSSMAP_oldToNewBSSIEs oldToNewBSSIEs := omit) runs on MSC_ConnHdlr {
	var PDU_BSSAP rx_bssap;
	/* Hack: the proper way would be to wait for the BSSMAP Handover Request ACK and extract the
	 * actual assigned chan_nr from its L3 (RR Handover Command) message. 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;
	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());

	var PDU_BSSAP ho_req := f_gen_handover_req(aoip_tla := g_pars.host_aoip_tla,
						   cell_id_source := g_pars.cell_id_source,
						   oldToNewBSSIEs := oldToNewBSSIEs,
						   enc := g_pars.encr);
	if (g_pars.inter_bsc_ho_in__ho_req_in_initial_sccp_cr) {
		BSSAP.send(ts_BSSAP_Conn_Req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc, ho_req));
		BSSAP.receive(tr_MSC_CONN_PRIM_CONF_IND);
	} else {
		BSSAP.send(ts_BSSAP_Conn_Req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc, omit));
		BSSAP.receive(tr_MSC_CONN_PRIM_CONF_IND);
		BSSAP.send(ho_req);
	}

	alt {
	[] BSSAP.receive(tr_BSSMAP_HandoverRequestAcknowledge(?)) -> value rx_bssap {
		if (g_pars.expect_ho_fail) {
			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
						"Expected Handover Request to fail, but got Handover Request Ack")
		}
		}
	[] BSSAP.receive(tr_BSSMAP_HandoverFailure) -> value rx_bssap {
		if (not g_pars.expect_ho_fail) {
			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
						"Expected Handover Request to succeed, but got Handover Failure")
		}
		// TODO: evaluate correct cause value. But osmo-bsc doesn't seem to send meaningful causes yet!
		// For now just accept any cause.
		BSSAP.receive(tr_BSSMAP_ClearRequest);
		setverdict(pass);
		return;
		}
	}

	if (g_pars.expect_ho_fail_lchan_est) {
		BSSAP.receive(tr_BSSMAP_HandoverFailure);
		setverdict(pass);

		/* When we let MGCP MDCX run into a timeout, it's still in the
		 * queue and additionally after BSSAP HandoverFailure, two DLCX
		 * get sent (for AoIP, for SCCPlite one DLCX). */
		if (g_pars.aoip) {
			if (g_pars.ignore_mgw_mdcx) {
				MGCP.receive(tr_MDCX);
			}
			MGCP.receive(tr_DLCX);
			MGCP.receive(tr_DLCX);
		} else {
			if (g_pars.ignore_mgw_mdcx) {
				MGCP_MULTI.receive(tr_MGCP_RecvFrom_any({command := tr_MDCX}));
			}
			MGCP_MULTI.receive(tr_MGCP_RecvFrom_any({command := tr_DLCX}));
		}
		return;
	}

	/* 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);
	f_verify_encr_info(chan_act);
	f_chan_act_verify_tsc(chan_act, expect_target_tsc);

	if (ispresent(rx_bssap.pdu.bssmap.handoverRequestAck.codecList)) {
		if (not g_pars.aoip) {
			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
						"handoverRequestAck: Expected no Speech Codec List (BSS Supported), because this is not AoIP");
		}
		/* TODO: check actual codecs? */
	} else {
		if (g_pars.aoip) {
			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
						"handoverRequestAck: Expected Speech Codec List (BSS Supported), but none found");
		}
	}

	if (ispresent(rx_bssap.pdu.bssmap.handoverRequestAck.speechCodec)) {
		if (not g_pars.aoip) {
			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
						"handoverRequestAck: Expected no Speech Codec (Chosen), because this is not AoIP");
		}
		/* TODO: check actual codec? */
	} else {
		if (g_pars.aoip) {
			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
						"handoverRequestAck: Expected Speech Codec (Chosen), but none found");
		}
	}

	var octetstring ho_command_str;
	ho_command_str := rx_bssap.pdu.bssmap.handoverRequestAck.layer3Information.layer3info;
	log("Received L3 Info in HO Request Ack: ", ho_command_str);
	var PDU_ML3_NW_MS ho_command := dec_PDU_ML3_NW_MS(ho_command_str);
	log("L3 Info in HO Request Ack is ", ho_command);

	var GsmArfcn arfcn;
	var RslChannelNr actual_new_chan_nr;
	f_ChDesc2RslChanNr(ho_command.msgs.rrm.handoverCommand.channelDescription2,
			actual_new_chan_nr, arfcn);

	if (actual_new_chan_nr != new_chan_nr) {
		log("ERROR: osmo-bsc assigned a different lchan than we assumed above -- this test will fail now.",
		    " Assumed: ", new_chan_nr, " Assigned: ", actual_new_chan_nr);
		setverdict(fail);
		return;
	}
	log("Handover Command chan_nr is", actual_new_chan_nr);

	var uint3_t got_tsc := rr_chan_desc_tsc(ho_command.msgs.rrm.handoverCommand.channelDescription2);
	if (not match(got_tsc, expect_target_tsc)) {
		setverdict(fail, "RR Handover Command: unexpected TSC in Channel Description: expected ",
			   expect_target_tsc, " got ", got_tsc);
		mtc.stop;
	} else {
		log("handoverCommand: verified TSC = ", got_tsc);
	}

	/* Check the Cipher Mode Setting IE (shall be present during inter-RAT handover) */
	if (ispresent(ho_command.msgs.rrm.handoverCommand.cipherModeSetting)) {
		var CipherModeSetting_TV cms := ho_command.msgs.rrm.handoverCommand.cipherModeSetting;
		var template (present) CipherModeSetting_TV tr_cms := {
			sC := '0'B, /* no ciphering by default */
			algorithmIdentifier := '000'B,
			elementIdentifier := ?
		};
		if (ispresent(g_pars.encr) and g_pars.encr.enc_alg_expect != '01'O) { /* A5/N */
			tr_cms.algorithmIdentifier := f_cipher_mode_bssmap_to_rr(g_pars.encr.enc_alg_expect);
			tr_cms.sC := '1'B;
		}
		if (not match(cms, tr_cms)) {
			setverdict(fail, "RR Handover Command: unexpected Cipher Mode Setting IE: ",
				   cms, ", expected: ", tr_cms);
		}
	} else {
		setverdict(fail, "RR Handover Command: Cipher Mode Setting IE is not present");
	}

	/* Now the MSC forwards the RR Handover Command to the other BSC, which
	 * tells the MS to handover to the new lchan. Here comes the new MS on
	 * the new lchan with a Handover RACH: */

	/* send handover detect */

	RSL.send(ts_RSL_HANDO_DET(new_chan_nr));

	BSSAP.receive(tr_BSSMAP_HandoverDetect);

	/* send handover complete over the new channel */

	var PDU_ML3_MS_NW l3_tx := valueof(ts_RRM_HandoverComplete('00'O));
	RSL.send(ts_RSL_EST_IND(new_chan_nr, valueof(ts_RslLinkID_DCCH(0)),
				enc_PDU_ML3_MS_NW(l3_tx)));

	BSSAP.receive(tr_BSSMAP_HandoverComplete);
	deactivate(as_media);
	setverdict(pass);
}

private function f_tc_ho_into_this_bsc(charstring id) runs on MSC_ConnHdlr {
	var template PDU_ML3_NW_MS exp_rr_rel_tmpl;
	var template (omit) BSSMAP_oldToNewBSSIEs oldToNewBSSIEs := omit;
	if (not istemplatekind(g_pars.last_used_eutran_plmn, "omit")) {
		oldToNewBSSIEs := f_ts_BSSMAP_oldToNewBSSIEs(ts_BSSMAP_LastUsedEUTRANPLMNId(g_pars.last_used_eutran_plmn));
	}
	if (g_pars.exp_fast_return) {
		exp_rr_rel_tmpl := tr_RRM_RR_RELEASE_CellSelectInd;
	} else {
		exp_rr_rel_tmpl := tr_RRM_RR_RELEASE;
	}
	f_ho_into_this_bsc(id, oldToNewBSSIEs);
	if (g_pars.expect_ho_fail) {
		f_perform_clear_no_lchan();
	} else if (g_pars.expect_ho_fail_lchan_est) {
		BSSAP.receive(tr_BSSMAP_ClearRequest);
		f_perform_clear_no_lchan();
	} else {
		f_perform_clear(exp_rr_rel_tmpl := exp_rr_rel_tmpl);
	}
	setverdict(pass);
}
function f_tc_ho_into_this_bsc_main(TestHdlrParams pars, charstring vty_a5_cfg := VTY_A5_DEFAULT) runs on test_CT {
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	f_vty_encryption_a5(vty_a5_cfg);
	f_ctrs_bsc_and_bts_handover_init();

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

	vc_conn := f_start_handler(refers(f_tc_ho_into_this_bsc), pars);
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:attempted");
	if (pars.expect_ho_fail) {
		f_ctrs_bsc_and_bts_add(0, "handover:failed");
		f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:failed");
	} else if (pars.expect_ho_fail_lchan_est) {
		f_ctrs_bsc_and_bts_add(0, "handover:error");
		f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:error");
	} else {
		f_ctrs_bsc_and_bts_add(0, "handover:completed");
		f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:completed");
	}
	f_ctrs_bsc_and_bts_verify();

	f_vty_encryption_a5_reset();
}

testcase TC_ho_into_this_bsc() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	f_tc_ho_into_this_bsc_main(pars);
	f_shutdown_helper(ho := true);
}

function f_tc_ho_into_this_bsc_a5(TestHdlrEncrParams encr, charstring vty_a5_cfg := VTY_A5_DEFAULT,
				  boolean expect_fail := false) runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.encr := encr;
	pars.expect_ho_fail := expect_fail;
	f_tc_ho_into_this_bsc_main(pars, vty_a5_cfg);
	f_shutdown_helper(ho := true);
}

testcase TC_ho_into_this_bsc_a5_0() runs on test_CT {
	f_tc_ho_into_this_bsc_a5(f_encr_params('01'O));
}

testcase TC_ho_into_this_bsc_a5_1() runs on test_CT {
	f_tc_ho_into_this_bsc_a5(f_encr_params('02'O));
}

testcase TC_ho_into_this_bsc_a5_3() runs on test_CT {
	f_tc_ho_into_this_bsc_a5(f_encr_params('08'O));
}

testcase TC_ho_into_this_bsc_a5_4() runs on test_CT {
	f_tc_ho_into_this_bsc_a5(f_encr_params('10'O, kc128 := true), "3 4");
}

/* Report: in inter-BSC incoming handover, when the MSC omits the Chosen Encryption Algorithm IE in the Handover Request
 * message, then osmo-bsc starts an unencrypted lchan even if A5/0 is not permitted.
 *
 * This test verifies that the Encryption Information is present in the Channel Activation when the Chosen Enc Alg is
 * omitted.
 *
 * Related: SYS#5839
 */
testcase TC_ho_into_this_bsc_a5_1_3_no_chosen_enc_alg() runs on test_CT {
	f_tc_ho_into_this_bsc_a5(f_encr_params(alg_permitted := '0a'O, alg_expect := '08'O, alg_chosen := omit));
}

testcase TC_ho_into_this_bsc_a5_1_3() runs on test_CT {
	f_tc_ho_into_this_bsc_a5(f_encr_params(alg_permitted := '0a'O, alg_expect := '08'O));
}

/* Send a permitted algo mask that does not intersect with osmo-bsc.cfg */
testcase TC_ho_into_this_bsc_a5_mismatch() runs on test_CT {
	f_tc_ho_into_this_bsc_a5(f_encr_params(alg_permitted := '18'O, alg_expect := '10'O), "0 1",
				 expect_fail := true); // 0x18 = A5/3 and A5/4
}

testcase TC_ho_into_this_bsc_tla_v6() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.host_aoip_tla := "::6";
	f_tc_ho_into_this_bsc_main(pars);
	f_shutdown_helper(ho := true);
}

/* Similar to TC_ho_into_this_bsc, but when in SRVCC, HO Req contains "Old BSS
   to New BSS Information" IE with "Last Used E-UTRAN PLMN Id", which, when the
   channel is later released (RR CHannel Release), should trigger inclusion of
   IE "Cell Selection Indicator after Release of all TCH and SDCCH" with E-UTRAN
   neighbors. */
testcase TC_srvcc_eutran_to_geran() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.last_used_eutran_plmn := '323454'O;
	pars.exp_fast_return := true;
	f_tc_ho_into_this_bsc_main(pars);

	f_ctrs_bsc_and_bts_add(0, "srvcc:attempted");
	f_ctrs_bsc_and_bts_add(0, "srvcc:completed");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

/* Same as TC_srvcc_eutran_to_geran, but enables ciphering on the target channel. */
testcase TC_srvcc_eutran_to_geran_a5_3() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.encr := f_encr_params('08'O); /* only A5/3 */
	pars.last_used_eutran_plmn := '323454'O;
	pars.exp_fast_return := true;
	f_tc_ho_into_this_bsc_main(pars);

	f_ctrs_bsc_and_bts_add(0, "srvcc:attempted");
	f_ctrs_bsc_and_bts_add(0, "srvcc:completed");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

/* Same as TC_srvcc_eutran_to_geran, but test explicitly forbiding fast return
   on the BTS. As a result, RR Release shouldn't contain the EUTRAN neighbor
   list when the channel is released. */
testcase TC_srvcc_eutran_to_geran_forbid_fast_return() runs on test_CT {
	f_init_vty();
	f_vty_allow_srvcc_fast_return(true, 0)

	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.last_used_eutran_plmn := '323454'O;
	pars.exp_fast_return := false;
	f_tc_ho_into_this_bsc_main(pars);
	f_vty_allow_srvcc_fast_return(false, 0);
	f_shutdown_helper();
}

/* Same as TC_srvcc_eutran_to_geran, but using SAI as serving Cell Identifier. SYS#5838 */
testcase TC_srvcc_eutran_to_geran_src_sai() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.last_used_eutran_plmn := '323454'O;
	pars.cell_id_source := valueof(ts_CellID_SAI('123456'O, 300, 444));
	f_tc_ho_into_this_bsc_main(pars);

	f_ctrs_bsc_and_bts_add(0, "srvcc:attempted");
	f_ctrs_bsc_and_bts_add(0, "srvcc:completed");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper();
}

private function f_tc_srvcc_eutran_to_geran_ho_out(charstring id) runs on MSC_ConnHdlr {
	var template (omit) BSSMAP_oldToNewBSSIEs oldToNewBSSIEs;
	oldToNewBSSIEs := f_ts_BSSMAP_oldToNewBSSIEs(ts_BSSMAP_LastUsedEUTRANPLMNId(g_pars.last_used_eutran_plmn));
	f_ho_into_this_bsc(id, oldToNewBSSIEs);
	f_ho_out_of_this_bsc(oldToNewBSSIEs);
	setverdict(pass);
}

private function f_tc_srvcc_eutran_to_geran_ho_out_main(boolean disable_fast_return)
	runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init_vty();
	f_bts_0_cfg(BSCVTY,
		    {"neighbor-list mode automatic",
		     "handover 1",
		     "handover algorithm 2",
		     "handover2 window rxlev averaging 1",
		     "no neighbors",
		     "neighbor lac 99 arfcn 123 bsic any"});
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1, true);
	if (disable_fast_return) {
		f_vty_allow_srvcc_fast_return(true, 0);
	}
	f_sleep(1.0);

	f_ctrs_bsc_and_bts_handover_init();

	pars.last_used_eutran_plmn := '323454'O;
	pars.sccp_addr_msc := g_bssap[0].sccpa.sccp_addr_own;
	pars.sccp_addr_bsc := g_bssap[0].sccpa.sccp_addr_peer;

	vc_conn := f_start_handler(refers(f_tc_srvcc_eutran_to_geran_ho_out), pars);
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "handover:attempted", 2);
	f_ctrs_bsc_and_bts_add(0, "handover:completed", 2);
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:attempted", 1);
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:completed", 1);
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:attempted", 1);
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:completed", 1);

	f_ctrs_bsc_and_bts_add(0, "srvcc:attempted", 1);
	f_ctrs_bsc_and_bts_add(0, "srvcc:completed", 1);
	f_ctrs_bsc_and_bts_verify();

	if (disable_fast_return) {
		f_vty_allow_srvcc_fast_return(false, 0);
	}
	f_shutdown_helper();
}

/* First, HO into BSC from EUTRAN (SRVCC): HO Request contains "Old BSS to New
   BSS Information" IE with "Last Used E-UTRAN PLMN Id".
   Second, HO to another BSC: HO Required contains "Old BSS to New BSS Information"
   IE with "Last Used E-UTRAN PLMN Id" from first step. */
testcase TC_srvcc_eutran_to_geran_ho_out() runs on test_CT {
	f_tc_srvcc_eutran_to_geran_ho_out_main(false);
}
/* Validate subsequent intra-GSM-HO works the same (with OldBSSToNewBSSInfo IE)
 * independently of fast-reture allowed/forbidden in local BTS */
testcase TC_srvcc_eutran_to_geran_ho_out_forbid_fast_return() runs on test_CT {
	f_tc_srvcc_eutran_to_geran_ho_out_main(true);
}

private function f_tc_ho_in_fail_msc_clears(charstring id) runs on MSC_ConnHdlr {
	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;
	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);
	activate(as_Media());

	BSSAP.send(ts_BSSAP_Conn_Req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc,
				     f_gen_handover_req()));
	BSSAP.receive(tr_MSC_CONN_PRIM_CONF_IND);

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

	var PDU_BSSAP rx_bssap;
	var octetstring ho_command_str;

	BSSAP.receive(tr_BSSMAP_HandoverRequestAcknowledge(?)) -> value rx_bssap;

	ho_command_str := rx_bssap.pdu.bssmap.handoverRequestAck.layer3Information.layer3info;
	log("Received L3 Info in HO Request Ack: ", ho_command_str);
	var PDU_ML3_NW_MS ho_command := dec_PDU_ML3_NW_MS(ho_command_str);
	log("L3 Info in HO Request Ack is ", ho_command);

	var GsmArfcn arfcn;
	var RslChannelNr actual_new_chan_nr;
	f_ChDesc2RslChanNr(ho_command.msgs.rrm.handoverCommand.channelDescription2,
			actual_new_chan_nr, arfcn);

	if (actual_new_chan_nr != new_chan_nr) {
		log("ERROR: osmo-bsc assigned a different lchan than we assumed above -- this test will fail now.",
		    " Assumed: ", new_chan_nr, " Assigned: ", actual_new_chan_nr);
		setverdict(fail);
		return;
	}
	log("Handover Command chan_nr is", actual_new_chan_nr);

	/* For deterministic test results, give some time for the MGW endpoint to be configured */
	f_sleep(1.0);

	/* Now the MSC forwards the RR Handover Command to the other BSC, which
	 * tells the MS to handover to the new lchan. In this case, the MS
	 * reports a Handover Failure to the old BSS, which forwards a BSSMAP
	 * Handover Failure to the MSC. The procedure according to 3GPP TS
	 * 48.008 3.1.5.3.2 "Handover Failure" is then that the MSC sends a
	 * BSSMAP Clear Command: */

	var myBSSMAP_Cause cause_val := GSM0808_CAUSE_RADIO_INTERFACE_FAILURE_REVERSION;
	var BssmapCause cause := enum2int(cause_val);
	BSSAP.send(ts_BSSMAP_ClearCommand(cause));

	f_expect_dlcx_conns();
	BSSAP.receive(tr_BSSMAP_ClearComplete);
	BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ);

	setverdict(pass);
	f_sleep(1.0);

	setverdict(pass);
}
testcase TC_ho_in_fail_msc_clears() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);

	f_ctrs_bsc_and_bts_handover_init();

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

	vc_conn := f_start_handler(refers(f_tc_ho_in_fail_msc_clears), pars);
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:stopped");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:attempted");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:stopped");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

private function f_tc_ho_in_fail_msc_clears_after_ho_detect(charstring id) runs on MSC_ConnHdlr {
	/* Hack: the proper way would be to wait for the BSSMAP Handover Request ACK and extract the
	 * actual assigned chan_nr from its L3 (RR Handover Command) message. 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;
	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);
	activate(as_Media());

	BSSAP.send(ts_BSSAP_Conn_Req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc,
				     f_gen_handover_req()));
	BSSAP.receive(tr_MSC_CONN_PRIM_CONF_IND);

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

	var PDU_BSSAP rx_bssap;
	var octetstring ho_command_str;

	BSSAP.receive(tr_BSSMAP_HandoverRequestAcknowledge(?)) -> value rx_bssap;

	ho_command_str := rx_bssap.pdu.bssmap.handoverRequestAck.layer3Information.layer3info;
	log("Received L3 Info in HO Request Ack: ", ho_command_str);
	var PDU_ML3_NW_MS ho_command := dec_PDU_ML3_NW_MS(ho_command_str);
	log("L3 Info in HO Request Ack is ", ho_command);

	var GsmArfcn arfcn;
	var RslChannelNr actual_new_chan_nr;
	f_ChDesc2RslChanNr(ho_command.msgs.rrm.handoverCommand.channelDescription2,
			actual_new_chan_nr, arfcn);

	if (actual_new_chan_nr != new_chan_nr) {
		log("ERROR: osmo-bsc assigned a different lchan than we assumed above -- this test will fail now.",
		    " Assumed: ", new_chan_nr, " Assigned: ", actual_new_chan_nr);
		setverdict(fail);
		return;
	}
	log("Handover Command chan_nr is", actual_new_chan_nr);

	/* Now the MSC forwards the RR Handover Command to the other BSC, which
	 * tells the MS to handover to the new lchan. Here comes the new MS on
	 * the new lchan with a Handover RACH: */

	/* send handover detect */

	RSL.send(ts_RSL_HANDO_DET(new_chan_nr));

	BSSAP.receive(tr_BSSMAP_HandoverDetect);

	/* The MSC chooses to clear the connection now, maybe we got the
	 * Handover RACH on the new cell but the MS still signaled Handover
	 * Failure to the old BSS? */

	var myBSSMAP_Cause cause_val := GSM0808_CAUSE_RADIO_INTERFACE_FAILURE_REVERSION;
	var BssmapCause cause := enum2int(cause_val);
	BSSAP.send(ts_BSSMAP_ClearCommand(cause));

	f_expect_dlcx_conns();
	RSL.receive(tr_RSL_DEACT_SACCH(new_chan_nr));
	RSL.receive(tr_RSL_RF_CHAN_REL(new_chan_nr));
	RSL.send(ts_RSL_RF_CHAN_REL_ACK(new_chan_nr));
	f_rslem_unregister(0, new_chan_nr);

	BSSAP.receive(tr_BSSMAP_ClearComplete);
	BSSAP.receive(tr_BSSMAP_HandoverFailure);
	BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ);

	f_sleep(1.0);
}
testcase TC_ho_in_fail_msc_clears_after_ho_detect() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);

	f_ctrs_bsc_and_bts_handover_init();

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

	vc_conn := f_start_handler(refers(f_tc_ho_in_fail_msc_clears_after_ho_detect), pars);
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:stopped");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:attempted");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:stopped");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

/* The new BSS's lchan times out before the MSC decides that handover failed. */
private function f_tc_ho_in_fail_no_detect(charstring id) runs on MSC_ConnHdlr {
	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;
	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);
	activate(as_Media());

	BSSAP.send(ts_BSSAP_Conn_Req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc,
				     f_gen_handover_req()));
	BSSAP.receive(tr_MSC_CONN_PRIM_CONF_IND);

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

	var PDU_BSSAP rx_bssap;
	var octetstring ho_command_str;

	BSSAP.receive(tr_BSSMAP_HandoverRequestAcknowledge(?)) -> value rx_bssap;

	ho_command_str := rx_bssap.pdu.bssmap.handoverRequestAck.layer3Information.layer3info;
	log("Received L3 Info in HO Request Ack: ", ho_command_str);
	var PDU_ML3_NW_MS ho_command := dec_PDU_ML3_NW_MS(ho_command_str);
	log("L3 Info in HO Request Ack is ", ho_command);

	var GsmArfcn arfcn;
	var RslChannelNr actual_new_chan_nr;
	f_ChDesc2RslChanNr(ho_command.msgs.rrm.handoverCommand.channelDescription2,
			actual_new_chan_nr, arfcn);

	if (actual_new_chan_nr != new_chan_nr) {
		log("ERROR: osmo-bsc assigned a different lchan than we assumed above -- this test will fail now.",
		    " Assumed: ", new_chan_nr, " Assigned: ", actual_new_chan_nr);
		setverdict(fail);
		return;
	}
	log("Handover Command chan_nr is", actual_new_chan_nr);

	/* Now the MSC forwards the RR Handover Command to the other BSC, which
	 * tells the MS to handover to the new lchan. But the MS never shows up
	 * on the new lchan. */

	BSSAP.receive(tr_BSSMAP_HandoverFailure);

	/* Did osmo-bsc also send a Clear Request? */
	timer T := 0.5;
	T.start;
	alt {
	[] BSSAP.receive(tr_BSSMAP_ClearRequest);
	[] T.timeout { }
	}

	/* MSC plays along with a Clear Command (no matter whether osmo-bsc
	 * asked for it, this is a Handover Failure after all). */

	var myBSSMAP_Cause cause_val := GSM0808_CAUSE_RADIO_INTERFACE_FAILURE_REVERSION;
	var BssmapCause cause := enum2int(cause_val);
	BSSAP.send(ts_BSSMAP_ClearCommand(cause));

	f_expect_dlcx_conns();
	BSSAP.receive(tr_BSSMAP_ClearComplete);
	BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ);

	f_sleep(1.0);
}
testcase TC_ho_in_fail_no_detect() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	pars.fail_on_dlcx := false;

	f_init(1, true);
	f_sleep(1.0);

	f_ctrs_bsc_and_bts_handover_init();

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

	vc_conn := f_start_handler(refers(f_tc_ho_in_fail_no_detect), pars);
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:error");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:attempted");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:error");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

/* Same as f_tc_ho_in_fail_no_detect, but MSC fails to send a Clear Command */
private function f_tc_ho_in_fail_no_detect2(charstring id) runs on MSC_ConnHdlr {
	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;
	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);
	activate(as_Media());

	BSSAP.send(ts_BSSAP_Conn_Req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc,
				     f_gen_handover_req()));
	BSSAP.receive(tr_MSC_CONN_PRIM_CONF_IND);

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

	var PDU_BSSAP rx_bssap;
	var octetstring ho_command_str;

	BSSAP.receive(tr_BSSMAP_HandoverRequestAcknowledge(?)) -> value rx_bssap;

	ho_command_str := rx_bssap.pdu.bssmap.handoverRequestAck.layer3Information.layer3info;
	log("Received L3 Info in HO Request Ack: ", ho_command_str);
	var PDU_ML3_NW_MS ho_command := dec_PDU_ML3_NW_MS(ho_command_str);
	log("L3 Info in HO Request Ack is ", ho_command);

	var GsmArfcn arfcn;
	var RslChannelNr actual_new_chan_nr;
	f_ChDesc2RslChanNr(ho_command.msgs.rrm.handoverCommand.channelDescription2,
			actual_new_chan_nr, arfcn);

	if (actual_new_chan_nr != new_chan_nr) {
		log("ERROR: osmo-bsc assigned a different lchan than we assumed above -- this test will fail now.",
		    " Assumed: ", new_chan_nr, " Assigned: ", actual_new_chan_nr);
		setverdict(fail);
		return;
	}
	log("Handover Command chan_nr is", actual_new_chan_nr);

	/* Now the MSC forwards the RR Handover Command to the other BSC, which
	 * tells the MS to handover to the new lchan. But the MS never shows up
	 * on the new lchan. */

	BSSAP.receive(tr_BSSMAP_HandoverFailure);

	/* MSC plays dumb and sends no Clear Command */
	var PDU_BSSAP rx_clear_request;

	BSSAP.receive(tr_BSSMAP_ClearRequest) -> value rx_clear_request {
		var BssmapCause cause := bit2int(rx_clear_request.pdu.bssmap.clearRequest.cause.causeValue);
		BSSAP.send(ts_BSSMAP_ClearCommand(cause));
	};
	f_expect_dlcx_conns();
	BSSAP.receive(tr_BSSMAP_ClearComplete);
	BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ);

	f_sleep(1.0);
}
testcase TC_ho_in_fail_no_detect2() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	pars.fail_on_dlcx := false;

	f_init(1, true);
	f_sleep(1.0);

	f_ctrs_bsc_and_bts_handover_init();

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

	vc_conn := f_start_handler(refers(f_tc_ho_in_fail_no_detect2), pars);
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:error");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:attempted");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_in:error");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

testcase TC_ho_in_fail_mgw_mdcx_timeout() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	pars.ignore_mgw_mdcx := true;
	pars.expect_ho_fail_lchan_est := true;

	f_tc_ho_into_this_bsc_main(pars);
	f_shutdown_helper(ho := true);
}

testcase TC_ho_in_fail_ipa_crcx_timeout() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	pars.ignore_ipa_media := true;
	pars.expect_ho_fail_lchan_est := true;
	pars.fail_on_dlcx := false;

	f_tc_ho_into_this_bsc_main(pars);
	f_shutdown_helper(ho := true);
}

/* An incoming inter-BSC HO can either issue the Handover Request message attached to the initial SCCP N-Connect (as in
 * the other tests we have so far), or the first CR can be "empty" with the BSSAP request following later. Test the
 * empty N-Connect case. */
testcase TC_ho_into_this_bsc_sccp_cr_without_bssap() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	pars.inter_bsc_ho_in__ho_req_in_initial_sccp_cr := false;
	f_tc_ho_into_this_bsc_main(pars);
	f_shutdown_helper(ho := true);
}

type record of charstring Commands;

private function f_bts_0_cfg(TELNETasp_PT pt, Commands cmds := {})
{
	f_vty_enter_cfg_bts(pt, 0);
	for (var integer i := 0; i < sizeof(cmds); i := i+1) {
		f_vty_transceive(pt, cmds[i]);
	}
	f_vty_transceive(pt, "end");
}

private function f_cs7_inst_0_cfg(TELNETasp_PT pt, Commands cmds := {})
{
	f_vty_enter_cfg_cs7_inst(pt, 0);
	for (var integer i := 0; i < sizeof(cmds); i := i+1) {
		f_vty_transceive(pt, cmds[i]);
	}
	f_vty_transceive(pt, "end");
}

private function f_probe_for_handover(charstring log_label,
				      charstring log_descr,
				      charstring handover_vty_cmd,
				      boolean expect_handover,
				      boolean is_inter_bsc_handover := false,
				      template uint3_t expect_target_tsc := ?)
runs on MSC_ConnHdlr
{
	/* We're going to thwart any and all handover attempts, just be ready to handle (and ignore) handover target
	 * lchans to be established on bts 1 or bts 2. */
	f_rslem_suspend(RSL1_PROC);
	f_rslem_suspend(RSL2_PROC);

	var RSL_Message rsl;

	var charstring log_msg := " (expecting handover)"
	if (not expect_handover) {
		log_msg := " (expecting NO handover)";
	}
	log("f_probe_for_handover starting: " & log_label & ": " & log_descr & log_msg);
	f_vty_transceive(BSCVTY, handover_vty_cmd);

	timer T := 2.0;
	T.start;

	alt {
	[] RSL.receive(tr_RSL_DATA_REQ(g_chan_nr)) -> value rsl {
		var PDU_ML3_NW_MS l3 := dec_PDU_ML3_NW_MS(rsl.ies[2].body.l3_info.payload);
		log("Rx L3 from net: ", l3);
		if (ischosen(l3.msgs.rrm.handoverCommand)) {
			var RslChannelNr new_chan_nr;
			var GsmArfcn arfcn;
			f_ChDesc2RslChanNr(l3.msgs.rrm.handoverCommand.channelDescription2,
					   new_chan_nr, arfcn);
			log("Handover to new chan ", new_chan_nr, " on ARFCN ", arfcn);
			log(l3.msgs.rrm.handoverCommand);

			/* Verify correct TSC in handoverCommand */
			var uint3_t got_tsc := rr_chan_desc_tsc(l3.msgs.rrm.handoverCommand.channelDescription2);
			if (not match(got_tsc, expect_target_tsc)) {
				setverdict(fail, "RR Handover Command: unexpected TSC in Channel Description: expected ",
					   expect_target_tsc, " got ", got_tsc);
				mtc.stop;
			} else {
				log("handoverCommand: verified TSC = ", got_tsc, " (matches ",
				    expect_target_tsc, ")");
			}

			/* Need to register for new lchan on new BTS -- it's either bts 1 or bts 2.  It doesn't really
			 * matter on which BTS it really is, we're not going to follow through an entire handover
			 * anyway. */
			f_rslem_register(0, new_chan_nr, RSL1_PROC);
			f_rslem_resume(RSL1_PROC);
			f_rslem_register(0, new_chan_nr, RSL2_PROC);
			f_rslem_resume(RSL2_PROC);

			if (expect_handover and not is_inter_bsc_handover) {
				setverdict(pass);
				log("f_probe_for_handover(" & log_label & "): Got RSL Handover Command as expected.");
			} else {
				setverdict(fail, "f_probe_for_handover(" & log_label & "): Expected none, but got RSL Handover Command. "
						 & log_label & ": " & log_descr);
			}

			log("f_probe_for_handover(" & log_label & "): Ending the test: Handover Failure stops the procedure.");
			/* osmo-bsc has triggered Handover. That's all we need to know for this test, reply with
			 * Handover Failure. */
			f_rsl_send_l3(ts_RRM_HandoverFailure('00'O));

			/* target BTS is told to release lchan again; don't care which BTS nor what messages. */
			f_sleep(0.5);
			RSL1.clear;
			RSL2.clear;
			log("f_probe_for_handover(" & log_label & "): done (got RSL Handover Command)");
			break;
		} else {
			repeat;
		}
		}
	[] BSSAP.receive(tr_BSSMAP_HandoverRequired) {
			if (expect_handover and is_inter_bsc_handover) {
				setverdict(pass);
				log("f_probe_for_handover(" & log_label & "): Got BSSMAP Handover Required as expected.");
			} else {
				setverdict(fail, "f_probe_for_handover(" & log_label & "): Expected none, but got BSSMAP Handover Required. "
						 & log_label & ": " & log_descr);
			}

			log("f_probe_for_handover(" & log_label & "): done (got BSSMAP Handover Required)");

			/* Note: f_tc_ho_neighbor_config_start() sets T7, the timeout for BSSMAP Handover Required, to
			 * 1 second. There is no legal way to quickly abort a handover after a BSSMAP Handover Required,
			 * setting a short timeout and waiting is the only way. */
			log("f_probe_for_handover(" & log_label & "): waiting for inter-BSC HO to time out...");
			f_sleep(1.5);
			log("f_probe_for_handover(" & log_label & "): ...done");

			break;
		}
	[] T.timeout {
		if (expect_handover) {
			setverdict(fail, "f_probe_for_handover(" & log_label & "): Expected Handover, but got none. "
					 & log_label & ": " & log_descr);
		} else {
			setverdict(pass);
			log("f_probe_for_handover(" & log_label & "): Got no Handover, as expected.");
		}
		log("f_probe_for_handover(" & log_label & "): done (got no Handover)");
		break;
		}
	}

	f_rslem_resume(RSL1_PROC);
	f_rslem_resume(RSL2_PROC);
	f_sleep(3.0);
	RSL.clear;

	log("f_probe_for_handover(" & log_label & "): done clearing");
}

/* Test the effect of various neighbor configuration scenarios:
 *
 * To avoid complexity, block off any actual handover operation, and always remain on the lchan at bts 0.
 * Reconfigure the neighbors for bts 0, trigger a Handover, and probe whether osmo-bsc does or doesn't start HO.
 */
private function f_tc_ho_neighbor_config_start() runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();

	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));

	/* Establish lchan at bts 0 */
	f_establish_fully(ass_cmd, exp_compl);

	/* Shorten the inter-BSC Handover timeout, to not wait so long for inter-BSC Handovers */
	f_vty_enter_cfg_network(BSCVTY);
	f_vty_transceive(BSCVTY, "timer T7 1");
	f_vty_transceive(BSCVTY, "end");
}

private function f_tc_ho_neighbor_config_1(charstring id) runs on MSC_ConnHdlr {
	f_tc_ho_neighbor_config_start();

	/*
	 * bts 0 ARFCN 871 BSIC 10
	 * bts 1 ARFCN 871 BSIC 11
	 * bts 2 ARFCN 871 BSIC 12
	 * bts 3 ARFCN 871 BSIC 12  serves as ambiguity for bts 2, re-using the ARFCN+BSIC
	 */

	log("f_tc_ho_neighbor_config: 1. No 'neighbor' config");
	f_bts_0_cfg(BSCVTY, {"no neighbors"});
	f_probe_for_handover("1.a", "HO to bts 1 works, implicitly listed as neighbor (legacy behavior when none are configured)",
			"handover any to arfcn 871 bsic 11",
			true, expect_target_tsc := c_BtsParams[1].tsc);

	f_probe_for_handover("1.b", "HO to unknown cell does not start",
			"handover any to arfcn 13 bsic 39",
			false);

	f_probe_for_handover("1.c", "HO to 871-12 is ambiguous = error",
			"handover any to arfcn 871 bsic 12",
			false);

	f_probe_for_handover("1.d", "HO to 871-11 still works (verify that this test properly cleans up)",
			"handover any to arfcn 871 bsic 11",
			true, expect_target_tsc := c_BtsParams[1].tsc);

	f_perform_clear();
}
testcase TC_ho_neighbor_config_1() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(3, true, guard_timeout := 60.0);
	f_sleep(1.0);
	f_ctrs_bsc_and_bts_handover_init();
	vc_conn := f_start_handler(refers(f_tc_ho_neighbor_config_1));
	vc_conn.done;

	/* f_tc_ho_neighbor_config_start() */
	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");

	/* 1.a */
	/* "failed" means a handover was triggered and started (which is all this test aims for) and the test ended the
	 * handover quickly by sending a Handover Failure message. */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:failed");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:attempted");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:failed");
	f_ctrs_bts_add(1, "incoming_intra_bsc_ho:attempted");
	f_ctrs_bts_add(1, "incoming_intra_bsc_ho:failed");

	/* 1.b */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:error");

	/* 1.c */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:error");

	/* 1.d */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:failed");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:attempted");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:failed");
	f_ctrs_bts_add(1, "incoming_intra_bsc_ho:attempted");
	f_ctrs_bts_add(1, "incoming_intra_bsc_ho:failed");

	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

private function f_tc_ho_neighbor_config_2(charstring id) runs on MSC_ConnHdlr {
	f_tc_ho_neighbor_config_start();

	/*
	 * bts 0 ARFCN 871 BSIC 10
	 * bts 1 ARFCN 871 BSIC 11
	 * bts 2 ARFCN 871 BSIC 12
	 * bts 3 ARFCN 871 BSIC 12  serves as ambiguity for bts 2, re-using the ARFCN+BSIC
	 */

	log("f_tc_ho_neighbor_config: 2. explicit local neighbor: 'neighbor bts 1'");
	f_bts_0_cfg(BSCVTY, {"neighbor bts 1"});
	f_sleep(0.5);

	f_probe_for_handover("2.a", "HO to bts 1 works, explicitly listed as neighbor",
			"handover any to arfcn 871 bsic 11",
			true, expect_target_tsc := c_BtsParams[1].tsc);

	f_probe_for_handover("2.b", "HO to bts 2 doesn't work, not listed as neighbor",
			"handover any to arfcn 871 bsic 12",
			false);
	f_perform_clear();
}
testcase TC_ho_neighbor_config_2() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(3, true, guard_timeout := 50.0);
	f_sleep(1.0);
	f_ctrs_bsc_and_bts_handover_init();
	vc_conn := f_start_handler(refers(f_tc_ho_neighbor_config_2));
	vc_conn.done;

	/* f_tc_ho_neighbor_config_start() */
	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");

	/* 2.a */
	/* "failed" means a handover was triggered and started (which is all this test aims for) and the test ended the
	 * handover quickly by sending a Handover Failure message. */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:failed");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:attempted");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:failed");
	f_ctrs_bts_add(1, "incoming_intra_bsc_ho:attempted");
	f_ctrs_bts_add(1, "incoming_intra_bsc_ho:failed");

	/* 2.b */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:error");

	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

private function f_tc_ho_neighbor_config_3(charstring id) runs on MSC_ConnHdlr {
	f_tc_ho_neighbor_config_start();

	/*
	 * bts 0 ARFCN 871 BSIC 10
	 * bts 1 ARFCN 871 BSIC 11
	 * bts 2 ARFCN 871 BSIC 12
	 * bts 3 ARFCN 871 BSIC 12  serves as ambiguity for bts 2, re-using the ARFCN+BSIC
	 */

	log("f_tc_ho_neighbor_config: 3. explicit local neighbor: 'neighbor bts 2'");
	f_bts_0_cfg(BSCVTY, {"no neighbors", "neighbor bts 2"});
	f_sleep(0.5);

	f_probe_for_handover("3.a", "HO to bts 1 doesn't work, not listed as neighbor",
			"handover any to arfcn 871 bsic 11",
			false);
	f_probe_for_handover("3.b", "HO to bts 2 works, explicitly listed as neighbor; no ambiguity because bts 3 is not listed as neighbor",
			"handover any to arfcn 871 bsic 12",
			true, expect_target_tsc := c_BtsParams[2].tsc);
	f_perform_clear();
}
testcase TC_ho_neighbor_config_3() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(3, true, guard_timeout := 50.0);
	f_sleep(1.0);
	f_ctrs_bsc_and_bts_handover_init();
	vc_conn := f_start_handler(refers(f_tc_ho_neighbor_config_3));
	vc_conn.done;

	/* f_tc_ho_neighbor_config_start() */
	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");

	/* 3.a */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:error");

	/* 3.b */
	/* "failed" means a handover was triggered and started (which is all this test aims for) and the test ended the
	 * handover quickly by sending a Handover Failure message. */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:failed");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:attempted");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:failed");
	f_ctrs_bts_add(2, "incoming_intra_bsc_ho:attempted");
	f_ctrs_bts_add(2, "incoming_intra_bsc_ho:failed");

	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

private function f_tc_ho_neighbor_config_4(charstring id) runs on MSC_ConnHdlr {
	f_tc_ho_neighbor_config_start();

	/*
	 * bts 0 ARFCN 871 BSIC 10
	 * bts 1 ARFCN 871 BSIC 11
	 * bts 2 ARFCN 871 BSIC 12
	 * bts 3 ARFCN 871 BSIC 12  serves as ambiguity for bts 2, re-using the ARFCN+BSIC
	 */

	log("f_tc_ho_neighbor_config: 4. explicit remote neighbor: 'neighbor lac 99 arfcn 123 bsic 45'");
	f_bts_0_cfg(BSCVTY, {"no neighbors", "neighbor lac 99 arfcn 123 bsic 45"});
	f_sleep(0.5);

	f_probe_for_handover("4.a", "HO to bts 1 doesn't work, not listed as neighbor",
			"handover any to arfcn 871 bsic 11",
			false);
	f_probe_for_handover("4.b", "HO to bts 2 doesn't work, not listed as neighbor",
			"handover any to arfcn 871 bsic 12",
			false);
	f_probe_for_handover("4.c", "HO to 123-45 triggers inter-BSC HO",
			"handover any to arfcn 123 bsic 45",
			true, true);
	f_perform_clear();
}
testcase TC_ho_neighbor_config_4() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(3, true, guard_timeout := 50.0);
	f_sleep(1.0);
	f_ctrs_bsc_and_bts_handover_init();
	vc_conn := f_start_handler(refers(f_tc_ho_neighbor_config_4));
	vc_conn.done;

	/* f_tc_ho_neighbor_config_start() */
	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");

	/* 4.a */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:error");

	/* 4.b */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:error");

	/* 4.c */
	/* "timeout" means a handover was triggered and started (which is all this test aims for) and the test ended the
	 * handover quickly by timing out after the Handover Required message */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:timeout");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:attempted");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:timeout");

	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

private function f_tc_ho_neighbor_config_5(charstring id) runs on MSC_ConnHdlr {
	f_tc_ho_neighbor_config_start();

	/*
	 * bts 0 ARFCN 871 BSIC 10
	 * bts 1 ARFCN 871 BSIC 11
	 * bts 2 ARFCN 871 BSIC 12
	 * bts 3 ARFCN 871 BSIC 12  serves as ambiguity for bts 2, re-using the ARFCN+BSIC
	 */

	log("f_tc_ho_neighbor_config: 5. explicit remote neighbor re-using ARFCN+BSIC: 'neighbor lac 99 arfcn 871 bsic 12'");
	f_bts_0_cfg(BSCVTY, {"no neighbors", "neighbor lac 99 arfcn 871 bsic 12"});
	f_sleep(0.5);

	f_probe_for_handover("5.a", "HO to 871-12 triggers inter-BSC HO (ignoring local cells with same ARFCN+BSIC)",
			"handover any to arfcn 871 bsic 12",
			true, true);
	f_perform_clear();
}
testcase TC_ho_neighbor_config_5() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(3, true);
	f_sleep(1.0);
	f_ctrs_bsc_and_bts_handover_init();
	vc_conn := f_start_handler(refers(f_tc_ho_neighbor_config_5));
	vc_conn.done;

	/* f_tc_ho_neighbor_config_start() */
	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");

	/* 5 */
	/* "timeout" means a handover was triggered and started (which is all this test aims for) and the test ended the
	 * handover quickly by timing out after the Handover Required message */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:timeout");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:attempted");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:timeout");

	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

private function f_tc_ho_neighbor_config_6(charstring id) runs on MSC_ConnHdlr {
	f_tc_ho_neighbor_config_start();

	/*
	 * bts 0 ARFCN 871 BSIC 10
	 * bts 1 ARFCN 871 BSIC 11
	 * bts 2 ARFCN 871 BSIC 12
	 * bts 3 ARFCN 871 BSIC 12  serves as ambiguity for bts 2, re-using the ARFCN+BSIC
	 */

	log("f_tc_ho_neighbor_config: 6. config error: explicit local and remote neighbors with ambiguous ARFCN+BSIC:"
	    & " 'neighbor bts 2; neighbor lac 99 arfcn 871 bsic 12'");
	f_bts_0_cfg(BSCVTY, {"no neighbors", "neighbor bts 2", "neighbor lac 99 arfcn 871 bsic 12"});
	f_sleep(0.5);

	f_probe_for_handover("6.a", "HO to 871-12 is ambiguous = error",
			"handover any to arfcn 871 bsic 12",
			false);
	f_perform_clear();
}
testcase TC_ho_neighbor_config_6() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(3, true);
	f_sleep(1.0);
	f_ctrs_bsc_and_bts_handover_init();
	vc_conn := f_start_handler(refers(f_tc_ho_neighbor_config_6));
	vc_conn.done;

	/* f_tc_ho_neighbor_config_start() */
	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");

	/* 6.a */
	/* "timeout" means a handover was triggered and started (which is all this test aims for) and the test ended the
	 * handover quickly by timing out after the Handover Required message */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:error");

	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

private function f_tc_ho_neighbor_config_7(charstring id) runs on MSC_ConnHdlr {
	f_tc_ho_neighbor_config_start();

	/*
	 * bts 0 ARFCN 871 BSIC 10
	 * bts 1 ARFCN 871 BSIC 11
	 * bts 2 ARFCN 871 BSIC 12
	 * bts 3 ARFCN 871 BSIC 12  serves as ambiguity for bts 2, re-using the ARFCN+BSIC
	 */

	log("f_tc_ho_neighbor_config: 7. explicit local and remote neighbors:"
	    & " 'neighbor bts 2; neighbor lac 99 arfcn 123 bsic 45'");
	f_bts_0_cfg(BSCVTY, {"no neighbors", "neighbor bts 2", "neighbor lac 99 arfcn 123 bsic 45"});
	f_sleep(0.5);

	f_probe_for_handover("7.a", "HO to 871-12 does HO to bts 2",
			"handover any to arfcn 871 bsic 12",
			true, expect_target_tsc := c_BtsParams[2].tsc);
	f_probe_for_handover("7.b", "HO to 123-45 triggers inter-BSC HO",
			"handover any to arfcn 123 bsic 45",
			true, true);
	f_perform_clear();
}
testcase TC_ho_neighbor_config_7() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	f_init(3, true, guard_timeout := 50.0);
	f_sleep(1.0);
	f_ctrs_bsc_and_bts_handover_init();
	vc_conn := f_start_handler(refers(f_tc_ho_neighbor_config_7));
	vc_conn.done;

	/* f_tc_ho_neighbor_config_start() */
	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");

	/* 7.a */
	/* "failed" means a handover was triggered and started (which is all this test aims for) and the test ended the
	 * handover quickly by sending a Handover Failure message. */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:failed");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:attempted");
	f_ctrs_bsc_and_bts_add(0, "intra_bsc_ho:failed");
	f_ctrs_bts_add(2, "incoming_intra_bsc_ho:attempted");
	f_ctrs_bts_add(2, "incoming_intra_bsc_ho:failed");

	/* 7.b */
	/* "timeout" means a handover was triggered and started (which is all this test aims for) and the test ended the
	 * handover quickly by timing out after the Handover Required message */
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:timeout");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:attempted");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:timeout");

	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

/* OS#3041: Open and close N connections in a normal fashion, and expect no
 * BSSMAP Reset just because of that. */
testcase TC_bssap_rlsd_does_not_cause_bssmap_reset() runs on test_CT {
	var default d;
	var integer i;
	var DchanTuple dt;

	f_init();

	/* Wait for initial BSSMAP Reset to pass */
	f_sleep(4.0);

	d := activate(no_bssmap_reset());

	/* Setup up a number of connections and RLSD them again from the MSC
	 * side. In the buggy behavior, the fourth one triggers BSSMAP Reset.
	 * Let's do it some more times for good measure. */
	for (i := 0; i < 4; i := i+1) {
		/* Since we're doing a lot of runs, give each one a fresh
		 * T_guard from the top. */
		T_guard.start;

		/* Setup a BSSAP connection and clear it right away. This is
		 * the MSC telling the BSC about a planned release, it's not an
		 * erratic loss of a connection. */
		dt := f_est_dchan(int2oct(i,1), 23+i, gen_l3_valid_payload());

		/* MSC disconnects (RLSD). */
		BSSAP.send(ts_BSSAP_DISC_req(dt.sccp_conn_id, 0));
	}

	/* In the buggy behavior, a timeout of 2 seconds happens between above
	 * trigger (logs "SIGTRAN connection down, reconnecting...") and the
	 * actual BSSMAP Reset. Wait a bit longer just to make sure. */
	f_sleep(4.0);

	deactivate(d);
	f_shutdown_helper();
}

/* OS#3041: Open and close N connections in a normal fashion, and expect no
 * BSSMAP Reset just because of that. Invoke the release by a BSSMAP Clear from
 * the MSC. */
testcase TC_bssmap_clear_does_not_cause_bssmap_reset() runs on test_CT {
	var default d;
	var integer i;
	var DchanTuple dt;
	var BSSAP_N_DATA_ind rx_di;
	var myBSSMAP_Cause cause_val := GSM0808_CAUSE_CALL_CONTROL;
	var BssmapCause cause := enum2int(cause_val);

	f_init();

	/* Wait for initial BSSMAP Reset to pass */
	f_sleep(4.0);

	d := activate(no_bssmap_reset());

	/* Setup up a number of connections and RLSD them again from the MSC
	 * side. In the buggy behavior, the fourth one triggers BSSMAP Reset.
	 * Let's do it some more times for good measure. */
	for (i := 0; i < 8; i := i+1) {
		/* Since we're doing a lot of runs, give each one a fresh
		 * T_guard from the top. */
		T_guard.start;

		/* Setup a BSSAP connection and clear it right away. This is
		 * the MSC telling the BSC about a planned release, it's not an
		 * erratic loss of a connection. */
		dt := f_est_dchan(int2oct(i,1), 23+i, gen_l3_valid_payload());

		/* Instruct BSC to clear channel */
		BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommand(cause)));

		/* expect BSC to disable the channel */
		f_exp_chan_rel_and_clear(dt);
	}

	/* In the buggy behavior, a timeout of 2 seconds happens between above
	 * trigger (logs "SIGTRAN connection down, reconnecting...") and the
	 * actual BSSMAP Reset. Wait a bit longer just to make sure. */
	f_sleep(4.0);

	deactivate(d);
	f_shutdown_helper();
}

/* OS#3041: Open and close N connections in a normal fashion, and expect no
 * BSSMAP Reset just because of that. Close connections from the MS side with a
 * Release Ind on RSL. */
testcase TC_ms_rel_ind_does_not_cause_bssmap_reset() runs on test_CT {
	var default d;
	var integer i;
	var DchanTuple dt;
	var BSSAP_N_DATA_ind rx_di;
	var integer j;

	f_init();

	/* Wait for initial BSSMAP Reset to pass */
	f_sleep(4.0);

	d := activate(no_bssmap_reset());

	/* Setup up a number of connections and RLSD them again from the MSC
	 * side. In the buggy behavior, the fourth one triggers BSSMAP Reset.
	 * Let's do it some more times for good measure. */
	for (i := 0; i < 8; i := i+1) {
		/* Since we're doing a lot of runs, give each one a fresh
		 * T_guard from the top. */
		T_guard.start;

		/* Setup a BSSAP connection and clear it right away. This is
		 * the MSC telling the BSC about a planned release, it's not an
		 * erratic loss of a connection. */
		dt := f_est_dchan('23'O, 23, gen_l3_valid_payload());

		/* simulate RLL REL IND */
		f_ipa_tx(ts_RSL_REL_IND(dt.rsl_chan_nr, valueof(ts_RslLinkID_DCCH(0))));

		/* expect Clear Request on MSC side */
		BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_ClearRequest)) -> value rx_di;

		/* Instruct BSC to clear channel */
		var BssmapCause cause := bit2int(rx_di.userData.pdu.bssmap.clearRequest.cause.causeValue);
		BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommand(cause)));

		/* expect BSC to disable the channel */
		f_exp_chan_rel_and_clear(dt);
	}

	/* In the buggy behavior, a timeout of 2 seconds happens between above
	 * trigger (logs "SIGTRAN connection down, reconnecting...") and the
	 * actual BSSMAP Reset. Wait a bit longer just to make sure. */
	f_sleep(4.0);

	deactivate(d);
	f_shutdown_helper();
}

/***********************************************************************
 * IPA style dynamic PDCH
 ***********************************************************************/

private function f_dyn_ipa_pdch_act(integer bts_nr, integer trx_nr, integer ts_nr,
				    template (omit) RSL_Cause nack := omit)
runs on test_CT {
	var RslChannelNr chan_nr := valueof(t_RslChanNr_Bm(ts_nr));
	var RSL_Message rsl_unused;
	/* ask BSC via VTY to activate a given IPA style chan as PDCH */
	f_vty_ts_action("pdch activate", bts_nr, trx_nr, ts_nr);
	/* expect the BSC to issue the related RSL command */
	rsl_unused := f_exp_ipa_rx(tr_RSL_IPA_PDCH_ACT(chan_nr));
	if (istemplatekind(nack, "omit")) {
		/* respond with a related acknowledgement */
		f_ipa_tx(ts_RSL_IPA_PDCH_ACT_ACK(chan_nr, ts_RSL_IE_FrameNumber(2342)));
	} else {
		f_ipa_tx(ts_RSL_IPA_PDCH_ACT_NACK(chan_nr, valueof(nack)));
	}
}

private function f_dyn_ipa_pdch_deact(integer bts_nr, integer trx_nr, integer ts_nr,
				      template (omit) RSL_Cause nack := omit)
runs on test_CT {
	var RslChannelNr chan_nr := valueof(t_RslChanNr_Bm(ts_nr));
	var RSL_Message rsl_unused;
	/* ask BSC via VTY to activate a given IPA style chan as PDCH */
	f_vty_ts_action("pdch deactivate", bts_nr, trx_nr, ts_nr);
	/* expect the BSC to issue the related RSL command */
	rsl_unused := f_exp_ipa_rx(tr_RSL_IPA_PDCH_DEACT(chan_nr));
	if (istemplatekind(nack, "omit")) {
		/* respond with a related acknowledgement */
		f_ipa_tx(ts_RSL_IPA_PDCH_DEACT_ACK(chan_nr));
	} else {
		f_ipa_tx(ts_RSL_IPA_PDCH_DEACT_NACK(chan_nr, valueof(nack)));
	}
}

private function f_ts_dyn_mode_get(integer bts_nr, integer trx_nr, integer ts_nr)
runs on test_CT return charstring {
	var charstring cmd, resp;
	cmd := "show timeslot "&int2str(bts_nr)&" "&int2str(trx_nr)&" "&int2str(ts_nr);
	return f_vty_transceive_match_regexp_retry(BSCVTY, cmd, "*\((*)\)*", 0, 4, 1.0);
}

private function f_ts_dyn_mode_assert(integer bts_nr, integer trx_nr, integer ts_nr,
					template charstring exp)
runs on test_CT {
	var charstring mode := f_ts_dyn_mode_get(bts_nr, trx_nr, ts_nr);
	if (not match(mode, exp)) {
		setverdict(fail, "Unexpected TS Mode: ", mode);
		mtc.stop;
	}
}

private function f_ts_set_chcomb(integer bts_nr, integer trx_nr, integer ts_nr, charstring chcomb)
runs on test_CT {
	f_vty_enter_cfg_ts(BSCVTY, bts_nr, trx_nr, ts_nr);
	f_vty_transceive(BSCVTY, "phys_chan_config " & chcomb);
	f_vty_transceive(BSCVTY, "end");
}


private function f_ts_reset_chcomb(integer bts_nr) runs on test_CT {
	var integer i;
	for (i := 0; i < 8; i := i + 1) {
		f_ts_set_chcomb(bts_nr, 0, i, phys_chan_config[i]);
	}
}

private const charstring TCHF_MODE := "TCH/F mode";
private const charstring TCHH_MODE := "TCH/H mode";
private const charstring PDCH_MODE := "PDCH mode";
private const charstring NONE_MODE := "NONE mode";
private const charstring SDCCH8_MODE := "SDCCH8 mode";

/* Test IPA PDCH activation / deactivation triggered by VTY */
testcase TC_dyn_pdch_ipa_act_deact() runs on test_CT {
	var RSL_Message rsl_unused;

	/* change Timeslot 6 before f_init() starts RSL */
	f_init_vty();
	f_ts_set_chcomb(0, 0, 6, "TCH/F_PDCH");
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1, false);
	f_sleep(1.0);

	var RslChannelNr chan_nr := valueof(t_RslChanNr_Bm(6));

	log("TCH/F_PDCH pchan starts out in TCH/F mode:");
	f_ts_dyn_mode_assert(0, 0, chan_nr.tn, TCHF_MODE);
	/* The BSC will activate the dynamic PDCH by default, so confirm that */
	rsl_unused := f_exp_ipa_rx(tr_RSL_IPA_PDCH_ACT(chan_nr));
	f_ipa_tx(ts_RSL_IPA_PDCH_ACT_ACK(chan_nr, ts_RSL_IE_FrameNumber(2342)));
	f_sleep(1.0);
	log("TCH/F_PDCH pchan, PDCH ACT was ACKed, so now in PDCH mode:");
	f_ts_dyn_mode_assert(0, 0, chan_nr.tn, PDCH_MODE);

	/* De-activate it via VTY */
	f_dyn_ipa_pdch_deact(0, 0, chan_nr.tn);
	f_sleep(1.0);
	log("TCH/F_PDCH pchan, PDCH DEACT via VTY, so now back in TCH/F mode:");
	f_ts_dyn_mode_assert(0, 0, chan_nr.tn, TCHF_MODE);

	/* re-activate it via VTY */
	f_dyn_ipa_pdch_act(0, 0, chan_nr.tn);
	f_sleep(1.0);
	log("TCH/F_PDCH pchan, PDCH ACT via VTY, so now in PDCH mode:");
	f_ts_dyn_mode_assert(0, 0, chan_nr.tn, PDCH_MODE);

	/* and finally de-activate it again */
	f_dyn_ipa_pdch_deact(0, 0, chan_nr.tn);
	f_sleep(1.0);
	log("TCH/F_PDCH pchan, PDCH DEACT via VTY, so now back in TCH/F mode:");
	f_ts_dyn_mode_assert(0, 0, chan_nr.tn, TCHF_MODE);

	/* clean up config */
	f_ts_set_chcomb(0, 0, 6, "PDCH");

	f_shutdown_helper();
}

/* Test IPA PDCH activation NACK */
testcase TC_dyn_pdch_ipa_act_nack() runs on test_CT {
	var RSL_Message rsl_unused;

	/* change Timeslot 6 before f_init() starts RSL */
	f_init_vty();
	f_ts_set_chcomb(0, 0, 6, "TCH/F_PDCH");
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1, false);
	f_sleep(1.0);

	var RslChannelNr chan_nr := valueof(t_RslChanNr_Bm(6));

	f_ts_dyn_mode_assert(0, 0, chan_nr.tn, TCHF_MODE);
	/* The BSC will activate the dynamic PDCH by default, so confirm that */
	rsl_unused := f_exp_ipa_rx(tr_RSL_IPA_PDCH_ACT(chan_nr));
	f_ipa_tx(ts_RSL_IPA_PDCH_ACT_ACK(chan_nr, ts_RSL_IE_FrameNumber(2342)));
	f_sleep(1.0);
	f_ts_dyn_mode_assert(0, 0, chan_nr.tn, PDCH_MODE);

	/* De-activate it via VTY */
	f_dyn_ipa_pdch_deact(0, 0, chan_nr.tn);
	f_sleep(1.0);
	f_ts_dyn_mode_assert(0, 0, chan_nr.tn, TCHF_MODE);

	/* re-activate it via VTY, but fail that; check BSC still assumes TCH/F mode */
	f_dyn_ipa_pdch_act(0, 0, chan_nr.tn, RSL_ERR_EQUIPMENT_FAIL);
	f_sleep(1.0);
	f_ts_dyn_mode_assert(0, 0, chan_nr.tn, TCHF_MODE);

	/* clean up config */
	f_ts_set_chcomb(0, 0, 6, "PDCH");

	f_shutdown_helper();
}


/***********************************************************************
 * Osmocom style dynamic PDCH
 ***********************************************************************/

private function f_dyn_osmo_pdch_act(integer bts_nr, integer trx_nr, integer ts_nr,
				     template (omit) RSL_Cause nack := omit)
runs on test_CT {
	var RslChannelNr chan_nr := valueof(t_RslChanNr_PDCH(ts_nr));
	var RSL_Message rsl_unused;
	/* ask BSC via VTY to activate a given OSMO style chan as PDCH */
	/* FIXME: no VTY command to activate Osmocom PDCH !! */
	/* expect the BSC to issue the related RSL command */
	rsl_unused := f_exp_ipa_rx(tr_RSL_CHAN_ACT(chan_nr, ?));
	if (istemplatekind(nack, "omit")) {
		/* respond with a related acknowledgement */
		f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, 2342));
	} else {
		f_ipa_tx(ts_RSL_CHAN_ACT_NACK(chan_nr, valueof(nack)));
	}
}

private function f_dyn_osmo_pdch_deact(integer bts_nr, integer trx_nr, integer ts_nr,
				       template (omit) RSL_Cause nack := omit)
runs on test_CT {
	var RslChannelNr chan_nr := valueof(t_RslChanNr_PDCH(ts_nr));
	var RSL_Message rsl_unused;
	/* ask BSC via VTY to activate a given OSMO style chan as PDCH */
	/* FIXME: no VTY command to activate Osmocom PDCH !! */
	/* expect the BSC to issue the related RSL command */
	rsl_unused := f_exp_ipa_rx(tr_RSL_RF_CHAN_REL(chan_nr));
	if (istemplatekind(nack, "omit")) {
		/* respond with a related acknowledgement */
		f_ipa_tx(ts_RSL_RF_CHAN_REL_ACK(chan_nr));
	} else {
		//f_ipa_tx(ts_RSL_RF_CHAN_REL_NACK(chan_nr, valueof(nack)));
	}
}

/* Test Osmocom dyn PDCH activation / deactivation triggered by VTY */
testcase TC_dyn_pdch_osmo_act_deact() runs on test_CT {
	var RSL_Message rsl_unused;

	/* change Timeslot 6 before f_init() starts RSL */
	f_init_vty();
	f_ts_set_chcomb(0, 0, 6, "TCH/F_TCH/H_PDCH");
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1, false);
	f_sleep(1.0);

	var RslChannelNr chan_nr := valueof(t_RslChanNr_PDCH(6));

	log("TCH/F_TCH/H_PDCH pchan starts out in disabled mode:");
	f_ts_dyn_mode_assert(0, 0, chan_nr.tn, NONE_MODE);
	/* The BSC will activate the dynamic PDCH by default, so confirm that */
	rsl_unused := f_exp_ipa_rx(tr_RSL_CHAN_ACT_PDCH(chan_nr));

	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, 2342));
	f_sleep(1.0);
	log("TCH/F_TCH/H_PDCH requested to PDCH ACT on startup, which was ACKed, so now in PDCH:");
	f_ts_dyn_mode_assert(0, 0, chan_nr.tn, PDCH_MODE);

	/* clean up config */
	f_ts_set_chcomb(0, 0, 6, "PDCH");

	f_shutdown_helper();
}

/* Test Osmocom dyn PDCH activation NACK behavior */
testcase TC_dyn_pdch_osmo_act_nack() runs on test_CT {
	var RSL_Message rsl_unused;

	/* change Timeslot 6 before f_init() starts RSL */
	f_init_vty();
	f_ts_set_chcomb(0, 0, 6, "TCH/F_TCH/H_PDCH");
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1, false);
	f_sleep(1.0);

	var RslChannelNr chan_nr := valueof(t_RslChanNr_PDCH(6));

	f_ts_dyn_mode_assert(0, 0, chan_nr.tn, NONE_MODE);
	/* The BSC will activate the dynamic PDCH by default, so confirm that */
	rsl_unused := f_exp_ipa_rx(tr_RSL_CHAN_ACT_PDCH(chan_nr));

	/* NACK this activation and expect the "show timeslot" mode still to be NONE */
	f_ipa_tx(ts_RSL_CHAN_ACT_NACK(chan_nr, RSL_ERR_EQUIPMENT_FAIL));
	f_sleep(1.0);
	f_ts_dyn_mode_assert(0, 0, chan_nr.tn, NONE_MODE);

	/* clean up config */
	f_ts_set_chcomb(0, 0, 6, "PDCH");

	f_shutdown_helper();
}

/* Test Osmocom dyn TS SDCCH8 activation / deactivation */
testcase TC_dyn_ts_sdcch8_act_deact() runs on test_CT {
	var RSL_Message rsl_unused, rsl_msg;
	var DchanTuple dt;
	var BSSAP_N_CONNECT_ind rx_c_ind;

	/* change Timeslot 6 before f_init() starts RSL */
	f_init_vty();
	f_ts_set_chcomb(0, 0, 6, "TCH/F_TCH/H_SDCCH8_PDCH");
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1, false);
	f_sleep(1.0);

	var RslChannelNr pdch_chan_nr := valueof(t_RslChanNr_PDCH(6));

	log("TCH/F_TCH/H_SDCCH8_PDCH pchan starts out in disabled mode:");
	f_ts_dyn_mode_assert(0, 0, pdch_chan_nr.tn, NONE_MODE);
	/* The BSC will activate the dynamic PDCH by default, so confirm that */
	rsl_unused := f_exp_ipa_rx(tr_RSL_CHAN_ACT_PDCH(pdch_chan_nr));

	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(pdch_chan_nr, 2342));
	f_sleep(1.0);
	log("TCH/F_TCH/H_SDCCH8_PDC requested to PDCH ACT on startup, which was ACKed, so now in PDCH:");
	f_ts_dyn_mode_assert(0, 0, pdch_chan_nr.tn, PDCH_MODE);

	/* Fill TS0 SDCCH channels (NOTE: only 3 SDCCH/4 channels are available
	 * on CCCH+SDCCH4+CBCH) */
	var DchanTuples sdcch_cleanup := {};
	var integer i;
	for (i := 0; i < NUM_SDCCH_PER_BTS; i := i + 1) {
		dt := f_est_dchan('23'O, i, gen_l3_valid_payload());
		sdcch_cleanup := sdcch_cleanup & { dt };
	}

	/* Now the dyn ts is selected. First PDCH is released, then sdcch chan is activated */
	f_ipa_tx(ts_RSL_CHAN_RQD(int2oct(oct2int('23'O) + i, 1), 2342));
	rsl_unused := f_exp_ipa_rx(tr_RSL_RF_CHAN_REL(pdch_chan_nr));
	f_ipa_tx(ts_RSL_RF_CHAN_REL_ACK(pdch_chan_nr));

	rsl_msg := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));
	dt.rsl_chan_nr := rsl_msg.ies[0].body.chan_nr;
	dt.idx := {0, 0};

	f_ts_dyn_mode_assert(0, 0, dt.rsl_chan_nr.tn, SDCCH8_MODE);
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(dt.rsl_chan_nr, 2342));
	rsl_msg := f_exp_ipa_rx(tr_RSL_IMM_ASSIGN(0));
	f_ts_dyn_mode_assert(0, 0, dt.rsl_chan_nr.tn, SDCCH8_MODE);

	f_ipa_tx(ts_RSL_EST_IND(dt.rsl_chan_nr, valueof(ts_RslLinkID_DCCH(0)), '1234'O));
	BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3('1234'O))) -> value rx_c_ind;
	dt.sccp_conn_id := rx_c_ind.connectionId;
	BSSAP.send(ts_BSSAP_CONNECT_res(dt.sccp_conn_id));

	/* Instruct BSC to clear channel */
	var BssmapCause cause := 0;
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommand(cause)));
	f_exp_chan_rel_and_clear(dt);

	/* The BSC will switch the TS back to PDCH once the only lchan using it is released: */
	rsl_unused := f_exp_ipa_rx(tr_RSL_CHAN_ACT_PDCH(pdch_chan_nr));
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(pdch_chan_nr, 2342));
	f_sleep(1.0);
	f_ts_dyn_mode_assert(0, 0, pdch_chan_nr.tn, PDCH_MODE);

	/* Clean up SDCCH lchans */
	for (i := 0; i < lengthof(sdcch_cleanup); i := i + 1) {
		f_perform_clear_test_ct(sdcch_cleanup[i]);
	}

	/* clean up config */
	f_ts_set_chcomb(0, 0, 6, "PDCH");

	f_shutdown_helper();
}

/* Validate all 8 subslots of a dynamics TS configured as SDCCH8 are used */
testcase TC_dyn_ts_sdcch8_all_subslots_used() runs on test_CT {
	var ASP_RSL_Unitdata rsl_ud;
	var integer i;
	var integer chreq_total, chreq_nochan;

	f_init_vty();
	for (i := 1; i < 8; i := i + 1) {
		if (i == 2) {
			f_ts_set_chcomb(0, 0, i, "TCH/F_TCH/H_SDCCH8_PDCH");
		} else {
			f_ts_set_chcomb(0, 0, i, "PDCH");
		}
	}
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1, guard_timeout := 60.0);

	/* The dyn TS want to activate PDCH mode, ACK that. */
	var RslChannelNr chan_nr;
	chan_nr := valueof(t_RslChanNr_PDCH(2));
	f_exp_ipa_rx(tr_RSL_CHAN_ACT_PDCH(chan_nr));
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, 2342));

	f_sleep(1.0);

	/* Exhaust all dedicated SDCCH lchans.
	/* GSM 44.018 Table 9.1.8.2:
	 * RA = '13'O -> Establishment cause = 0001xxxx (MS dual rate capable and asks for "SDCCH").
	 */
	var DchanTuples chan_cleanup := {};
	for (i := 0; i < NUM_SDCCH_PER_BTS; i := i+1) {
		chan_cleanup := chan_cleanup & { f_est_dchan('13'O, NUM_SDCCH_PER_BTS + i, gen_l3_valid_payload()) };
	}

	/* Only the dyn TS is still available. Its first lchan gets converted to SDCCH8 */
	chan_cleanup := chan_cleanup & { f_est_dchan_dyn('33'O, NUM_SDCCH_PER_BTS + i, gen_l3_valid_payload()) };
	/* Also occupy the seven other SDCCH of the dyn TS */
	for (i := 0; i < 7; i := i+1) {
		chan_cleanup := chan_cleanup & { f_est_dchan('33'O, NUM_SDCCH_PER_BTS + i, gen_l3_valid_payload()) };
	}

	/* Clean up SDCCH lchans */
	for (i := 0; i < lengthof(chan_cleanup); i := i + 1) {
		f_perform_clear_test_ct(chan_cleanup[i]);
	}

	/* clean up config */
	f_ts_reset_chcomb(0);

	f_shutdown_helper();
}

/* Test Osmocom dyn TS SDCCH8 activation / deactivation: If activating dyn TS as
   SDCCH8 would end up in having no free TCH, then BSC should decide to activate
   it as TCH directly instead. SYS#5309. */
testcase TC_dyn_ts_sdcch8_tch_call_act_deact() runs on test_CT {
	var RSL_Message rsl_unused, rsl_msg;
	var DchanTuple dt;
	var BSSAP_N_CONNECT_ind rx_c_ind;
	var integer i;

	/* change Timeslot 6 before f_init() starts RSL */
	f_init_vty();
	for (i := 1; i < 8; i := i + 1) {
		if (i == 6) {
			f_ts_set_chcomb(0, 0, i, "TCH/F_TCH/H_SDCCH8_PDCH");
		} else {
			f_ts_set_chcomb(0, 0, i, "PDCH");
		}
	}
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1, false);
	f_sleep(1.0);

	var RslChannelNr pdch_chan_nr := valueof(t_RslChanNr_PDCH(6));

	log("TCH/F_TCH/H_SDCCH8_PDCH pchan starts out in disabled mode:");
	f_ts_dyn_mode_assert(0, 0, pdch_chan_nr.tn, NONE_MODE);
	/* The BSC will activate the dynamic PDCH by default, so confirm that */
	rsl_unused := f_exp_ipa_rx(tr_RSL_CHAN_ACT_PDCH(pdch_chan_nr));

	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(pdch_chan_nr, 2342));
	f_sleep(1.0);
	log("TCH/F_TCH/H_SDCCH8_PDC requested to PDCH ACT on startup, which was ACKed, so now in PDCH:");
	f_ts_dyn_mode_assert(0, 0, pdch_chan_nr.tn, PDCH_MODE);

	/* Fill TS0 SDCCH channels (NOTE: only 3 SDCCH/4 channels are available
	 * on CCCH+SDCCH4+CBCH) */
	var DchanTuples chan_cleanup := {};
	var OCT1 ra := '43'O; /* RA containing reason=originating speech call*/
	for (i := 0; i < NUM_SDCCH_PER_BTS; i := i + 1) {
		dt := f_est_dchan(ra, i, gen_l3_valid_payload());
		chan_cleanup := chan_cleanup & { dt };
	}

	/* Now the dyn ts is selected. First PDCH is released, then TCH chan is activated */
	f_ipa_tx(ts_RSL_CHAN_RQD(int2oct(oct2int(ra) + i, 1), 2342));
	rsl_unused := f_exp_ipa_rx(tr_RSL_RF_CHAN_REL(pdch_chan_nr));
	f_ipa_tx(ts_RSL_RF_CHAN_REL_ACK(pdch_chan_nr));

	rsl_msg := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));
	dt.rsl_chan_nr := rsl_msg.ies[0].body.chan_nr;
	dt.idx := {0, 0};

	f_ts_dyn_mode_assert(0, 0, dt.rsl_chan_nr.tn, TCHH_MODE);
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(dt.rsl_chan_nr, 2342));
	rsl_msg := f_exp_ipa_rx(tr_RSL_IMM_ASSIGN(0));
	f_ts_dyn_mode_assert(0, 0, dt.rsl_chan_nr.tn, TCHH_MODE);

	f_ipa_tx(ts_RSL_EST_IND(dt.rsl_chan_nr, valueof(ts_RslLinkID_DCCH(0)), '1234'O));
	BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3('1234'O))) -> value rx_c_ind;
	dt.sccp_conn_id := rx_c_ind.connectionId;
	BSSAP.send(ts_BSSAP_CONNECT_res(dt.sccp_conn_id));

	/* Instruct BSC to clear channel */
	var BssmapCause cause := 0;
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommand(cause)));
	f_exp_chan_rel_and_clear(dt);

	/* The BSC will switch the TS back to PDCH once the only lchan using it is released: */
	rsl_unused := f_exp_ipa_rx(tr_RSL_CHAN_ACT_PDCH(pdch_chan_nr));
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(pdch_chan_nr, 2342));
	f_sleep(1.0);
	f_ts_dyn_mode_assert(0, 0, pdch_chan_nr.tn, PDCH_MODE);

	/* Clean up SDCCH lchans */
	for (i := 0; i < lengthof(chan_cleanup); i := i + 1) {
		f_perform_clear_test_ct(chan_cleanup[i]);
	}

	/* clean up config */
	f_ts_reset_chcomb(0);
	/* TODO: clean up other channels? */

	f_shutdown_helper();
}

/* Test Osmocom dyn TS SDCCH8 activation / deactivation when SDCCH fails at BTS */
testcase TC_dyn_ts_sdcch8_act_nack() runs on test_CT {
	var RSL_Message rsl_unused, rsl_msg;
	var DchanTuple dt;
	var BSSAP_N_CONNECT_ind rx_c_ind;
	var GsmRrMessage rr;

	/* change Timeslot 6 before f_init() starts RSL */
	f_init_vty();
	f_ts_set_chcomb(0, 0, 6, "TCH/F_TCH/H_SDCCH8_PDCH");
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1, false);
	f_sleep(1.0);

	var RslChannelNr pdch_chan_nr := valueof(t_RslChanNr_PDCH(6));

	log("TCH/F_TCH/H_SDCCH8_PDCH pchan starts out in disabled mode:");
	f_ts_dyn_mode_assert(0, 0, pdch_chan_nr.tn, NONE_MODE);
	/* The BSC will activate the dynamic PDCH by default, so confirm that */
	rsl_unused := f_exp_ipa_rx(tr_RSL_CHAN_ACT_PDCH(pdch_chan_nr));

	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(pdch_chan_nr, 2342));
	f_sleep(1.0);
	log("TCH/F_TCH/H_SDCCH8_PDC requested to PDCH ACT on startup, which was ACKed, so now in PDCH:");
	f_ts_dyn_mode_assert(0, 0, pdch_chan_nr.tn, PDCH_MODE);

	/* Fill TS0 SDCCH channels (NOTE: only 3 SDCCH/4 channels are available
	 * on CCCH+SDCCH4+CBCH) */
	var DchanTuples chan_cleanup := {};
	var integer i;
	for (i := 0; i < NUM_SDCCH_PER_BTS; i := i + 1) {
		dt := f_est_dchan('23'O, i, gen_l3_valid_payload());
		chan_cleanup := chan_cleanup & { dt };
	}

	/* Now the dyn ts is selected. First PDCH is released, then sdcch chan is activated */
	f_ipa_tx(ts_RSL_CHAN_RQD(int2oct(oct2int('23'O) + i, 1), 2342));
	rsl_unused := f_exp_ipa_rx(tr_RSL_RF_CHAN_REL(pdch_chan_nr));
	f_ipa_tx(ts_RSL_RF_CHAN_REL_ACK(pdch_chan_nr));

	rsl_msg := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));
	dt.rsl_chan_nr := rsl_msg.ies[0].body.chan_nr;
	dt.idx := {0, 0};

	f_ts_dyn_mode_assert(0, 0, dt.rsl_chan_nr.tn, SDCCH8_MODE);
	f_ipa_tx(ts_RSL_CHAN_ACT_NACK(dt.rsl_chan_nr, RSL_ERR_EQUIPMENT_FAIL));
	rsl_msg := f_exp_ipa_rx(tr_RSL_IMM_ASSIGN(0));
	rr := dec_GsmRrMessage(rsl_msg.ies[1].body.full_imm_ass_info.payload);
	if (rr.header.message_type != IMMEDIATE_ASSIGNMENT_REJECT) {
		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Expected reject");
	}

	/* FIXME? Currently the TS stays in state BORKEN: */

	/* The BSC will switch the TS back to PDCH once the only lchan using it is released: */
	/* rsl_unused := f_exp_ipa_rx(tr_RSL_CHAN_ACT_PDCH(pdch_chan_nr));
	 * f_ipa_tx(ts_RSL_CHAN_ACT_ACK(pdch_chan_nr, 2342));
	 * f_sleep(1.0);
	 * f_ts_dyn_mode_assert(0, 0, pdch_chan_nr.tn, PDCH_MODE)
	 */

	/* Clean up SDCCH lchans */
	for (i := 0; i < lengthof(chan_cleanup); i := i + 1) {
		f_perform_clear_test_ct(chan_cleanup[i]);
	}

	/* clean up config */
	f_ts_set_chcomb(0, 0, 6, "PDCH");

	f_shutdown_helper();
}

testcase TC_chopped_ipa_ping() runs on test_CT {
	const Integers bsc_ipa_ports := {mp_bsc_rsl_port, mp_bsc_oml_port, mp_bsc_ctrl_port};
	for (var integer i := 0; i < lengthof(bsc_ipa_ports); i := i + 1) {
		IPA_Testing.f_run_TC_chopped_ipa_ping(mp_bsc_ip, bsc_ipa_ports[i], CONNECT_TO_SERVER);
	}
	f_shutdown_helper();
}

testcase TC_chopped_ipa_payload() runs on test_CT {
	const Integers bsc_ipa_ports := {mp_bsc_rsl_port, mp_bsc_oml_port
					/* TODO: mp_bsc_ctrl_port does not work yet */};
	for (var integer i := 0; i < lengthof(bsc_ipa_ports); i := i + 1) {
		IPA_Testing.f_run_TC_chopped_ipa_payload(mp_bsc_ip, bsc_ipa_ports[i], CONNECT_TO_SERVER);
	}
	f_shutdown_helper();
}

/* Verify the BSC sends the MS Power Parameters IE during CHAN ACT to make sure
   the BTS does autonomous MS power control loop */
testcase TC_assignment_verify_ms_power_params_ie() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	//pars.encr := valueof(t_EncrParams('01'O, f_rnd_octstring(8)));
	pars.exp_ms_power_params := true;

	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_assignment_fr_a5), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* Verify activation and deactivation of the BCCH carrier power reduction mode */
testcase TC_c0_power_red_mode() runs on test_CT {
	f_init(1);

	for (var integer red := 6; red >= 0; red := red - 2) {
		/* Configure BCCH carrier power reduction mode via the VTY */
		f_vty_transceive(BSCVTY, "bts 0 c0-power-reduction " & int2str(red));

		/* Expect Osmocom specific BS Power Control message on the RSL */
		var template (present) RSL_Message tr_rsl_pdu := tr_RSL_BS_PWR_CTRL(
			chan_nr := t_RslChanNr_BCCH(0),
			bs_power := tr_RSL_IE_BS_Power(red / 2));
		tr_rsl_pdu.msg_disc := tr_RSL_MsgDisc(RSL_MDISC_CCHAN, false);
		var RSL_Message unused := f_exp_ipa_rx(tr_rsl_pdu);

		/* Additionally verify the applied value over the CTRL interface */
		var CtrlValue cred := f_ctrl_get_bts(IPA_CTRL, 0, "c0-power-reduction");
		if (cred != int2str(red)) {
			setverdict(fail, "Unexpected BCCH carrier power reduction value ",
					 cred, " (expected ", red, ")");
		}
	}

	f_shutdown_helper();
}

/***********************************************************************
 * MSC Pooling
 ***********************************************************************/

template MobileIdentityLV ts_MI_TMSI_NRI_LV(integer nri_v, integer nri_bitlen := 10) :=
	ts_MI_TMSI_LV(tmsi := f_gen_tmsi(suffix := 0, nri_v := nri_v, nri_bitlen := nri_bitlen));

private function f_expect_lchan_rel(RSL_DCHAN_PT rsl_pt, RSLEM_PROC_PT rsl_proc_pt, template PDU_ML3_NW_MS exp_rr_rel_tmpl := tr_RRM_RR_RELEASE)
runs on MSC_ConnHdlr {
	interleave {
	[] rsl_pt.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, decmatch exp_rr_rel_tmpl)) {
			f_logp(BSCVTY, "Got RSL RR Release");
		}
	[] rsl_pt.receive(tr_RSL_DEACT_SACCH(g_chan_nr)) {
			f_logp(BSCVTY, "Got RSL Deact SACCH");
		}
	[] rsl_pt.receive(tr_RSL_RF_CHAN_REL(g_chan_nr)) {
			f_logp(BSCVTY, "Got RSL RF Chan Rel, sending Rel Ack");
			rsl_pt.send(ts_RSL_RF_CHAN_REL_ACK(g_chan_nr));
			f_rslem_unregister(0, g_chan_nr, PT := rsl_proc_pt);
			break;
		}
	}
}

private altstep as_mgcp_ack_all_dlcx() runs on MSC_ConnHdlr {
	var MgcpCommand mgcp_cmd;
	var template MgcpMessage msg_dlcx := {
		command := tr_DLCX
	}
	var MGCP_RecvFrom mrf;

	[g_pars.aoip] MGCP.receive(tr_DLCX(?)) -> value mgcp_cmd {
		MGCP.send(ts_DLCX_ACK2(mgcp_cmd.line.trans_id));
		repeat;
	}

	[not g_pars.aoip] MGCP_MULTI.receive(tr_MGCP_RecvFrom_any(msg_dlcx)) -> value mrf {
		MGCP_MULTI.send(t_MGCP_SendToMrf(mrf, MgcpMessage:{
			response := ts_DLCX_ACK2(mrf.msg.command.line.trans_id)
		}));
		repeat;
	}
}

private altstep as_rsl_ack_all_rel_req() runs on MSC_ConnHdlr {
	var RslLinkId main_dcch := valueof(ts_RslLinkID_DCCH(0));
	[] RSL.receive(tr_RSL_REL_REQ(g_chan_nr, ?)) {
		RSL.send(ts_RSL_REL_CONF(g_chan_nr, main_dcch));
		repeat;
	}
}

friend function f_perform_clear(RSL_DCHAN_PT rsl_pt := RSL, RSLEM_PROC_PT rsl_proc_pt := RSL_PROC,
				template PDU_ML3_NW_MS exp_rr_rel_tmpl := tr_RRM_RR_RELEASE,
				boolean send_rel_ack := true)
runs on MSC_ConnHdlr {
	var default ack_dlcx := activate(as_mgcp_ack_all_dlcx());
	var default ack_rel_req := activate(as_rsl_ack_all_rel_req());
	f_logp(BSCVTY, "MSC instructs BSC to clear channel");
	BSSAP.send(ts_BSSMAP_ClearCommand(0));
	interleave {
	[] rsl_pt.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, decmatch exp_rr_rel_tmpl)) {
			f_logp(BSCVTY, "Got RSL RR Release");
		}
	[] rsl_pt.receive(tr_RSL_DEACT_SACCH(g_chan_nr)) {
			f_logp(BSCVTY, "Got RSL Deact SACCH");
		}
	[] BSSAP.receive(tr_BSSMAP_ClearComplete) {
			f_logp(BSCVTY, "Got BSSMAP Clear Complete");
			/* Also drop the SCCP connection */
			BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ);
		}
	[] rsl_pt.receive(tr_RSL_RF_CHAN_REL(g_chan_nr)) {
			f_logp(BSCVTY, "Got RSL RF Chan Rel, sending Rel Ack");
			if (send_rel_ack) {
				rsl_pt.send(ts_RSL_RF_CHAN_REL_ACK(g_chan_nr));
				f_rslem_unregister(0, g_chan_nr, PT := rsl_proc_pt);
			}
		}
	}
	deactivate(ack_dlcx);
	deactivate(ack_rel_req);
}

friend function f_perform_clear_no_rr_rel(RSL_DCHAN_PT rsl_pt := RSL, RSLEM_PROC_PT rsl_proc_pt := RSL_PROC)
runs on MSC_ConnHdlr {
	var default ack_dlcx := activate(as_mgcp_ack_all_dlcx());
	var default ack_rel_req := activate(as_rsl_ack_all_rel_req());
	f_logp(BSCVTY, "MSC instructs BSC to clear channel");
	BSSAP.send(ts_BSSMAP_ClearCommand(0));
	interleave {
	[] rsl_pt.receive(tr_RSL_DEACT_SACCH(g_chan_nr)) {
			f_logp(BSCVTY, "Got RSL Deact SACCH");
		}
	[] BSSAP.receive(tr_BSSMAP_ClearComplete) {
			f_logp(BSCVTY, "Got BSSMAP Clear Complete");
			/* Also drop the SCCP connection */
			BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ);
		}
	[] rsl_pt.receive(tr_RSL_RF_CHAN_REL(g_chan_nr)) {
			f_logp(BSCVTY, "Got RSL RF Chan Rel, sending Rel Ack");
			rsl_pt.send(ts_RSL_RF_CHAN_REL_ACK(g_chan_nr));
			f_rslem_unregister(0, g_chan_nr, PT := rsl_proc_pt);
		}
	}
	deactivate(ack_dlcx);
	deactivate(ack_rel_req);
}

friend function f_perform_clear_no_lchan()
runs on MSC_ConnHdlr {
	f_logp(BSCVTY, "MSC instructs BSC to clear channel");
	BSSAP.send(ts_BSSMAP_ClearCommand(0));
	BSSAP.receive(tr_BSSMAP_ClearComplete);
	f_logp(BSCVTY, "Got BSSMAP Clear Complete");
	/* Also drop the SCCP connection */
	BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ);
}

friend function f_perform_clear_test_ct(DchanTuple dt)
	runs on test_CT
{
	/* Instruct BSC to clear channel */
	var BssmapCause cause := 0;
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommand(cause)));
	f_exp_chan_rel_and_clear(dt);
}

private function f_perform_compl_l3(RSL_DCHAN_PT rsl_pt, RSLEM_PROC_PT rsl_proc_pt,
                                    template PDU_ML3_MS_NW l3_info, boolean do_clear := true, boolean expect_bssmap_l3 := true)
runs on MSC_ConnHdlr {
	timer T := 10.0;
	var octetstring l3_enc := enc_PDU_ML3_MS_NW(valueof(l3_info));

	f_logp(BSCVTY, "establish channel, send Complete Layer 3 Info");
	f_create_bssmap_exp(l3_enc);

	/* RSL_Emulation.f_chan_est() on rsl_pt:
	 * This is basically code dup with s/RSL/rsl_pt from:
	 * RSL_Emulation.f_chan_est(g_pars.ra, l3_enc, g_pars.link_id, g_pars.fn);
	 */
	var RSL_Message rx_rsl;
	var GsmRrMessage rr;

	/* request a channel to be established */
	rsl_pt.send(ts_RSLDC_ChanRqd(g_pars.ra, g_pars.fn));
	/* expect immediate assignment.
	 * Code dup with s/RSL/rsl_pt from:
	 * rx_rsl := f_rx_or_fail(tr_RSL_IMM_ASSIGN);
	 */
	timer Tt := 10.0;

	/* request a channel to be established */
	Tt.start;
	alt {
		[] rsl_pt.receive(tr_RSL_IMM_ASSIGN) -> value rx_rsl {
			Tt.stop;
		}
		[] rsl_pt.receive {
			setverdict(fail, "Unexpected RSL message on DCHAN");
			mtc.stop;
		}
		[] Tt.timeout {
			setverdict(fail, "Timeout waiting for RSL on DCHAN");
			mtc.stop;
		}
	}
	rr := dec_GsmRrMessage(rx_rsl.ies[1].body.full_imm_ass_info.payload);
	g_chan_nr := rr.payload.imm_ass.chan_desc.chan_nr;
	rsl_pt.send(ts_RSL_EST_IND(g_chan_nr, valueof(g_pars.link_id), l3_enc));


	if (expect_bssmap_l3) {
		f_logp(BSCVTY, "expect BSSAP Complete Layer 3 Info at MSC");
		var template PDU_BSSAP exp_l3_compl;
		exp_l3_compl := tr_BSSMAP_ComplL3()
		if (g_pars.aoip == false) {
			exp_l3_compl.pdu.bssmap.completeLayer3Information.codecList := omit;
		} else {
			exp_l3_compl.pdu.bssmap.completeLayer3Information.codecList := ?;
		}

		var PDU_BSSAP bssap;
		T.start;
		alt {
		[] BSSAP.receive(exp_l3_compl) -> value bssap {
			f_logp(BSCVTY, "received expected Complete Layer 3 Info at MSC");
			log("rx exp_l3_compl = ", bssap);
			}
		[] BSSAP.receive(tr_BSSMAP_ComplL3) {
			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Received non-matching COMPLETE LAYER 3 INFORMATION");
			}
		[] T.timeout {
			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for COMPLETE LAYER 3 INFORMATION");
			}
		}

		/* start ciphering, if requested */
		if (ispresent(g_pars.encr)) {
			f_logp(BSCVTY, "start ciphering");
			f_cipher_mode(g_pars.encr, rsl_pt := rsl_pt, rsl_proc_pt := rsl_proc_pt);
		}
	}

	if (do_clear) {
		f_perform_clear(rsl_pt, rsl_proc_pt);
	}
	setverdict(pass);
	f_sleep(1.0);
}

private function f_tc_mscpool_compl_l3(charstring id) runs on MSC_ConnHdlr {
	f_MscConnHdlr_init(g_pars.media_nr, "127.0.0.2", "127.0.0.3", FR_AMR);
	if (g_pars.mscpool.rsl_idx == 0) {
		f_perform_compl_l3(RSL, RSL_PROC, g_pars.mscpool.l3_info);
	} else if (g_pars.mscpool.rsl_idx == 1) {
		f_perform_compl_l3(RSL1, RSL1_PROC, g_pars.mscpool.l3_info);
	} else if (g_pars.mscpool.rsl_idx == 2) {
		f_perform_compl_l3(RSL2, RSL2_PROC, g_pars.mscpool.l3_info);
	}
}

/* Various Complete Layer 3 by IMSI all end up with the first MSC, because the other MSCs are not connected. */
private function f_tc_mscpool_L3Compl_on_1_msc(charstring id) runs on MSC_ConnHdlr {
	f_MscConnHdlr_init(g_pars.media_nr, "127.0.0.2", "127.0.0.3", FR_AMR);
	f_perform_compl_l3(RSL, RSL_PROC, ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_IMSI_LV('001010000000001'H)), '00F110'O) );
	f_perform_compl_l3(RSL, RSL_PROC, ts_CM_SERV_REQ(CM_TYPE_MO_SMS, valueof(ts_MI_IMSI_LV('001010000000002'H))) );
	f_perform_compl_l3(RSL, RSL_PROC, ts_PAG_RESP(ts_MI_IMSI_LV('001010000000003'H)) );
	f_perform_compl_l3(RSL, RSL_PROC, ts_ML3_MO_MM_IMSI_DET_Ind(valueof(ts_MI_IMSI_LV('001010000000004'H))) );
}
testcase TC_mscpool_L3Compl_on_1_msc() runs on test_CT {

	f_init(1, true);
	f_sleep(1.0);
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_ctrs_msc_init();

	vc_conn := f_start_handler(refers(f_tc_mscpool_L3Compl_on_1_msc), pars);
	vc_conn.done;

	f_ctrs_msc_expect(0, "mscpool:subscr:new", 4);
	f_shutdown_helper();
}

/* Three Layer 3 Complete by IMSI are round-robin'ed across two connected MSCs */
/* FIXME: each run is using a separate RSLem: RSL, RSL1, RSL2. It should work
 * just as well using only RSL. */
testcase TC_mscpool_L3Complete_by_imsi_round_robin() runs on test_CT {

	f_init(nr_bts := 3, handler_mode := true, nr_msc := 2);
	f_sleep(1.0);

	/* Control which MSC gets chosen next by the round-robin, otherwise
	 * would be randomly affected by which other tests ran before this. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 0");

	f_ctrs_msc_init();

	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars1.mscpool.rsl_idx := 0;
	pars1.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_IMSI_LV('001010000000001'H)), '00F110'O));
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars1);
	vc_conn1.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:new");

	var MSC_ConnHdlr vc_conn2;
	var TestHdlrParams pars2 := f_gen_test_hdlr_pars(bssap_idx := 1);
	pars2.mscpool.rsl_idx := 1;
	pars2.mscpool.l3_info := valueof(ts_CM_SERV_REQ(CM_TYPE_MO_CALL, valueof(ts_MI_IMSI_LV('001010000000002'H))));
	vc_conn2 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars2);
	vc_conn2.done;
	f_ctrs_msc_expect(1, "mscpool:subscr:new");

	/* Test round-robin wrap to the first MSC */
	var MSC_ConnHdlr vc_conn3;
	var TestHdlrParams pars3 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars3.mscpool.rsl_idx := 2;
	pars3.mscpool.l3_info := valueof(ts_PAG_RESP(ts_MI_IMSI_LV('001010000000003'H)));
	vc_conn3 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars3);
	vc_conn3.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:new");
	f_shutdown_helper();
}

/* Three LU by TMSI are round-robin'ed across two connected MSCs, because they contain the NULL-NRI 0
 * (configured in osmo-bsc.cfg). */
/* FIXME: each run is using a separate RSLem: RSL, RSL1, RSL2. It should work
 * just as well using only RSL. */
testcase TC_mscpool_LU_by_tmsi_null_nri_0_round_robin() runs on test_CT {

	f_init(nr_bts := 3, handler_mode := true, nr_msc := 2);
	f_sleep(1.0);

	/* Control which MSC gets chosen next by the round-robin, otherwise
	 * would be randomly affected by which other tests ran before this. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 0");

	f_ctrs_msc_init();

	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars1.mscpool.rsl_idx := 0;
	pars1.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_TMSI_NRI_LV(0)), '00F110'O));
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars1);
	vc_conn1.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:reattach");

	var MSC_ConnHdlr vc_conn2;
	var TestHdlrParams pars2 := f_gen_test_hdlr_pars(bssap_idx := 1);
	pars2.mscpool.rsl_idx := 1;
	pars2.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_TMSI_NRI_LV(0)), '00F110'O));
	vc_conn2 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars2);
	vc_conn2.done;
	f_ctrs_msc_expect(1, "mscpool:subscr:reattach");

	/* Test round-robin wrap to the first MSC */
	var MSC_ConnHdlr vc_conn3;
	var TestHdlrParams pars3 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars3.mscpool.rsl_idx := 2;
	pars3.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_TMSI_NRI_LV(0)), '00F110'O));
	vc_conn3 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars3);
	vc_conn3.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:reattach");
	f_shutdown_helper();
}

/* Three LU by TMSI are round-robin'ed across two connected MSCs, because they contain the NULL-NRI 1
 * (configured in osmo-bsc.cfg). In this case, one of the MSC also has the NULL-NRI as part of its owned NRIs, but the
 * NULL-NRI setting is stronger than that. */
/* FIXME: each run is using a separate RSLem: RSL, RSL1, RSL2. It should work
 * just as well using only RSL. */
testcase TC_mscpool_LU_by_tmsi_null_nri_1_round_robin() runs on test_CT {

	f_init(nr_bts := 3, handler_mode := true, nr_msc := 2);
	f_sleep(1.0);

	/* Control which MSC gets chosen next by the round-robin, otherwise
	 * would be randomly affected by which other tests ran before this. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 0");

	f_ctrs_msc_init();

	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars1.mscpool.rsl_idx := 0;
	pars1.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_TMSI_NRI_LV(1)), '00F110'O));
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars1);
	vc_conn1.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:reattach");

	var MSC_ConnHdlr vc_conn2;
	var TestHdlrParams pars2 := f_gen_test_hdlr_pars(bssap_idx := 1);
	pars2.mscpool.rsl_idx := 1;
	pars2.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_TMSI_NRI_LV(1)), '00F110'O));
	vc_conn2 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars2);
	vc_conn2.done;
	f_ctrs_msc_expect(1, "mscpool:subscr:reattach");

	/* Test round-robin wrap to the first MSC */
	var MSC_ConnHdlr vc_conn3;
	var TestHdlrParams pars3 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars3.mscpool.rsl_idx := 2;
	pars3.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_TMSI_NRI_LV(1)), '00F110'O));
	vc_conn3 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars3);
	vc_conn3.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:reattach");
	f_shutdown_helper();
}

/* Three Layer 3 Complete by TMSI are round-robin'ed across two connected MSCs, because they contain an NRI not
 * assigned to any MSC (configured in osmo-bsc.cfg). */
/* FIXME: each run is using a separate RSLem: RSL, RSL1, RSL2. It should work
 * just as well using only RSL. */
testcase TC_mscpool_L3Complete_by_tmsi_unassigned_nri_round_robin() runs on test_CT {

	f_init(nr_bts := 3, handler_mode := true, nr_msc := 2);
	f_sleep(1.0);

	/* Control which MSC gets chosen next by the round-robin, otherwise
	 * would be randomly affected by which other tests ran before this. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 0");

	f_ctrs_msc_init();

	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars1.mscpool.rsl_idx := 0;
	/* An NRI that is not assigned to any MSC */
	pars1.mscpool.l3_info := valueof(ts_ML3_MO_MM_IMSI_DET_Ind(valueof(ts_MI_TMSI_NRI_LV(1023))));
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars1);
	vc_conn1.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:new");

	var MSC_ConnHdlr vc_conn2;
	var TestHdlrParams pars2 := f_gen_test_hdlr_pars(bssap_idx := 1);
	pars2.mscpool.rsl_idx := 1;
	/* An NRI that is not assigned to any MSC */
	pars2.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_TMSI_NRI_LV(768)), '00F110'O));
	vc_conn2 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars2);
	vc_conn2.done;
	f_ctrs_msc_expect(1, "mscpool:subscr:new");

	/* Test round-robin wrap to the first MSC */
	var MSC_ConnHdlr vc_conn3;
	var TestHdlrParams pars3 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars3.mscpool.rsl_idx := 2;
	/* An NRI that is not assigned to any MSC */
	pars3.mscpool.l3_info := valueof(ts_CM_SERV_REQ(CM_TYPE_SS_ACT, valueof(ts_MI_TMSI_NRI_LV(819))));
	vc_conn3 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars3);
	vc_conn3.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:new");
	f_shutdown_helper();
}

/* Three Layer 3 Complete by TMSI are round-robin'ed across two connected MSCs, because they contain an NRI
 * assigned to an MSC that is currently not connected (configured in osmo-bsc.cfg). */
/* FIXME: each run is using a separate RSLem: RSL, RSL1, RSL2. It should work
 * just as well using only RSL. */
testcase TC_mscpool_L3Complete_by_tmsi_valid_nri_msc_not_connected_round_robin() runs on test_CT {

	f_init(nr_bts := 3, handler_mode := true, nr_msc := 2);
	f_sleep(1.0);

	/* Control which MSC gets chosen next by the round-robin, otherwise
	 * would be randomly affected by which other tests ran before this. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 0");

	f_ctrs_msc_init();

	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars1.mscpool.rsl_idx := 0;
	/* An NRI that is assigned to an unconnected MSC */
	pars1.mscpool.l3_info := valueof(ts_PAG_RESP(ts_MI_TMSI_NRI_LV(512)));
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars1);
	vc_conn1.done;
	f_ctrs_msc_add(2, "mscpool:subscr:attach_lost");
	f_ctrs_msc_add(0, "mscpool:subscr:new");
	f_ctrs_msc_verify();

	var MSC_ConnHdlr vc_conn2;
	var TestHdlrParams pars2 := f_gen_test_hdlr_pars(bssap_idx := 1);
	pars2.mscpool.rsl_idx := 1;
	/* An NRI that is assigned to an unconnected MSC */
	pars2.mscpool.l3_info := valueof(ts_ML3_MO_MM_IMSI_DET_Ind(valueof(ts_MI_TMSI_NRI_LV(767))));
	vc_conn2 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars2);
	vc_conn2.done;
	f_ctrs_msc_add(2, "mscpool:subscr:attach_lost");
	f_ctrs_msc_add(1, "mscpool:subscr:new");
	f_ctrs_msc_verify();

	/* Test round-robin wrap to the first MSC */
	var MSC_ConnHdlr vc_conn3;
	var TestHdlrParams pars3 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars3.mscpool.rsl_idx := 2;
	/* An NRI that is assigned to an unconnected MSC */
	pars3.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_TMSI_NRI_LV(750)), '00F110'O));
	vc_conn3 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars3);
	vc_conn3.done;
	f_ctrs_msc_add(2, "mscpool:subscr:attach_lost");
	f_ctrs_msc_add(0, "mscpool:subscr:new");
	f_ctrs_msc_verify();
	f_shutdown_helper();
}

/* Three Layer 3 Complete by TMSI with valid NRI for the second MSC are all directed to the second MSC (configured in
 * osmo-bsc.cfg). */
/* FIXME: each run is using a separate RSLem: RSL, RSL1, RSL2. It should work
 * just as well using only RSL. */
testcase TC_mscpool_L3Complete_by_tmsi_valid_nri_1() runs on test_CT {

	f_init(nr_bts := 3, handler_mode := true, nr_msc := 2);
	f_sleep(1.0);

	/* All TMSIs in this test point at the second MSC, set the round robin to point at the first MSC to make sure
	 * this is not using round-robin. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 0");

	f_ctrs_msc_init();

	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 1);
	pars1.mscpool.rsl_idx := 0;
	/* An NRI of the second MSC's range (256-511) */
	pars1.mscpool.l3_info := valueof(ts_CM_SERV_REQ(CM_TYPE_MO_SMS, valueof(ts_MI_TMSI_NRI_LV(256))));
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars1);
	vc_conn1.done;
	f_ctrs_msc_expect(1, "mscpool:subscr:known");

	var MSC_ConnHdlr vc_conn2;
	var TestHdlrParams pars2 := f_gen_test_hdlr_pars(bssap_idx := 1);
	pars2.mscpool.rsl_idx := 1;
	/* An NRI of the second MSC's range (256-511) */
	pars2.mscpool.l3_info := valueof(ts_PAG_RESP(ts_MI_TMSI_NRI_LV(260)));
	vc_conn2 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars2);
	vc_conn2.done;
	f_ctrs_msc_expect(1, "mscpool:subscr:known");

	var MSC_ConnHdlr vc_conn3;
	var TestHdlrParams pars3 := f_gen_test_hdlr_pars(bssap_idx := 1);
	pars3.mscpool.rsl_idx := 2;
	/* An NRI of the second MSC's range (256-511) */
	pars3.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_TMSI_NRI_LV(511)), '00F110'O));
	vc_conn3 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars3);
	vc_conn3.done;
	f_ctrs_msc_expect(1, "mscpool:subscr:known");
	f_shutdown_helper();
}

/* Layer 3 Complete by TMSI with valid NRI for the third MSC are directed to the third MSC (configured in osmo-bsc.cfg),
 * while a round-robin remains unaffected by that. */
/* FIXME: each run is using a separate RSLem: RSL, RSL1, RSL2. It should work
 * just as well using only RSL. */
testcase TC_mscpool_L3Complete_by_tmsi_valid_nri_2() runs on test_CT {

	f_init(nr_bts := 3, handler_mode := true, nr_msc := 3);
	f_sleep(1.0);

	/* All TMSIs in this test point at the third MSC, set the round robin to point at the second MSC to make sure
	 * this is not using round-robin. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 1");

	f_ctrs_msc_init();

	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 2);
	pars1.mscpool.rsl_idx := 0;
	/* An NRI of the third MSC's range (512-767) */
	pars1.mscpool.l3_info := valueof(ts_CM_SERV_REQ(CM_TYPE_MO_SMS, valueof(ts_MI_TMSI_NRI_LV(512))));
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars1);
	vc_conn1.done;
	f_ctrs_msc_expect(2, "mscpool:subscr:known");

	var MSC_ConnHdlr vc_conn2;
	var TestHdlrParams pars2 := f_gen_test_hdlr_pars(bssap_idx := 2);
	pars2.mscpool.rsl_idx := 1;
	/* An NRI of the third MSC's range (512-767) */
	pars2.mscpool.l3_info := valueof(ts_PAG_RESP(ts_MI_TMSI_NRI_LV(678)));
	vc_conn2 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars2);
	vc_conn2.done;
	f_ctrs_msc_expect(2, "mscpool:subscr:known");

	/* The above forwardings to third MSC have not affected the round robin, which still points at the second MSC */
	var MSC_ConnHdlr vc_conn3;
	var TestHdlrParams pars3 := f_gen_test_hdlr_pars(bssap_idx := 1);
	pars3.mscpool.rsl_idx := 2;
	pars3.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_IMSI_LV('001010000000013'H)), '00F110'O));
	vc_conn3 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars3);
	vc_conn3.done;
	f_ctrs_msc_expect(1, "mscpool:subscr:new");
	f_shutdown_helper();
}

/* LU with a TMSI but indicating a different PLMN in its previous LAI: ignore the NRI. */
/* FIXME: each run is using a separate RSLem: RSL, RSL1, RSL2. It should work
 * just as well using only RSL. */
testcase TC_mscpool_LU_by_tmsi_from_other_PLMN() runs on test_CT {

	f_init(nr_bts := 3, handler_mode := true, nr_msc := 3);
	f_sleep(1.0);

	/* The TMSIs in this test points at the second MSC, but since it is from a different PLMN, round-robin is used
	 * instead, and hits msc 0. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 0");

	f_ctrs_msc_init();

	/* An NRI of the second MSC's range (256-511), but a PLMN that doesn't match with osmo-bsc.cfg */
	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars1.mscpool.rsl_idx := 0;
	pars1.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_TMSI_NRI_LV(260)), '99F999'O));
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars1);
	vc_conn1.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:new");

	/* An NRI of the third MSC's range (512-767) and a matching PLMN gets directed by NRI. */
	var MSC_ConnHdlr vc_conn2;
	var TestHdlrParams pars2 := f_gen_test_hdlr_pars(bssap_idx := 2);
	pars2.mscpool.rsl_idx := 1;
	pars2.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_TMSI_NRI_LV(555)), '00F110'O));
	vc_conn2 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars2);
	vc_conn2.done;
	f_ctrs_msc_expect(2, "mscpool:subscr:known");
	f_shutdown_helper();
}

/* Make sure that whichever MSC paged a subscriber will also get the Paging Response. Page by IMSI, which would be
 * round-robined to another MSC, to make sure the Paging->Response relation is stronger than the NRI->MSC mapping. */
private function f_tc_mscpool_paging_imsi(charstring id) runs on MSC_ConnHdlr {
	var template BSSMAP_FIELD_CellIdentificationList cid_list := { cIl_CI := { ts_BSSMAP_CI_CI(0) } };
	//cid_list := { cIl_allInBSS := ''O };
	var RSL_ChanNeeded rsl_chneed := RSL_CHANNEED_SDCCH;
	var template BSSMAP_IE_ChannelNeeded bssmap_chneed := ts_BSSMAP_IE_ChanNeeded(int2bit(enum2int(valueof(rsl_chneed)),2));
	var BSSAP_N_UNITDATA_req paging;
	var hexstring imsi := '001010000000123'H;

	f_MscConnHdlr_init(g_pars.media_nr, "127.0.0.2", "127.0.0.3", FR_AMR);

	paging := valueof(ts_BSSAP_UNITDATA_req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc,
						valueof(ts_BSSMAP_Paging(imsi, cid_list, omit, bssmap_chneed))));
	BSSAP.send(paging);

	/* Register any RSL conn so that the Paging Command gets received here. With the current RSL_Emulation's main()
	 * handling of '[bts_role] IPA_PT.receive(tr_ASP_RSL_UD(tr_RSL_PAGING_CMD()))' it doesn't matter at all which
	 * channel number is picked here. */
	var RslChannelNr new_chan_nr := valueof(t_RslChanNr0(0, RSL_CHAN_NR_INVALID));
	f_rslem_register(0, new_chan_nr);
	RSL.receive(tr_RSL_PAGING_CMD(tr_MI_IMSI(imsi)));
	f_rslem_unregister(0, new_chan_nr);

	/* Despite the round robin pointing at the second MSC ('roundrobin next 1'), the earlier Paging for the same IMSI
	 * causes this Paging Response to go to the first MSC (bssap_idx := 0). */
	f_perform_compl_l3(RSL, RSL_PROC, ts_PAG_RESP(ts_MI_IMSI_LV(imsi)) );
	f_sleep(1.0);
}
testcase TC_mscpool_paging_and_response_imsi() runs on test_CT {
	f_init(nr_bts := 1, handler_mode := true, nr_msc := 3);
	f_sleep(1.0);

	/* Testing a Paging on the first MSC to get a Paging Response back to the first MSC. Set round robin to the
	 * second MSC to make sure we're getting the Paging logic, not a coincidental round robin match. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 1");

	f_ctrs_msc_init();

	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars1.mscpool.rsl_idx := 0;
	pars1.sccp_addr_bsc := g_bssap[pars1.mscpool.bssap_idx].sccpa.sccp_addr_peer;
	pars1.sccp_addr_msc := g_bssap[pars1.mscpool.bssap_idx].sccpa.sccp_addr_own;
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_paging_imsi), pars1);
	vc_conn1.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:paged");
	f_shutdown_helper();
}

/* Make sure that whichever MSC paged a subscriber will also get the Paging Response.  Page by TMSI with an NRI value
 * that matches a different MSC, to make sure the Paging->Response relation is stronger than the NRI->MSC mapping. */
private function f_tc_mscpool_paging_tmsi(charstring id) runs on MSC_ConnHdlr {
	var template BSSMAP_FIELD_CellIdentificationList cid_list := { cIl_CI := { ts_BSSMAP_CI_CI(0) } };
	//cid_list := { cIl_allInBSS := ''O };
	var RSL_ChanNeeded rsl_chneed := RSL_CHANNEED_SDCCH;
	var template BSSMAP_IE_ChannelNeeded bssmap_chneed := ts_BSSMAP_IE_ChanNeeded(int2bit(enum2int(valueof(rsl_chneed)),2));
	var integer nri_v := 300; /* <-- second MSC's NRI */
	var octetstring tmsi := f_gen_tmsi(suffix := 0, nri_v := nri_v);
	var BSSAP_N_UNITDATA_req paging;

	f_MscConnHdlr_init(g_pars.media_nr, "127.0.0.2", "127.0.0.3", FR_AMR);

	paging := valueof(ts_BSSAP_UNITDATA_req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc,
						valueof(ts_BSSMAP_Paging('001010000000011'H, cid_list, tmsi, bssmap_chneed))));
	BSSAP.send(paging);

	/* Register any RSL conn so that the Paging Command gets received here. With the current RSL_Emulation's main()
	 * handling of '[bts_role] IPA_PT.receive(tr_ASP_RSL_UD(tr_RSL_PAGING_CMD()))' it doesn't matter at all which
	 * channel number is picked here. */
	var RslChannelNr new_chan_nr := valueof(t_RslChanNr0(0, RSL_CHAN_NR_INVALID));
	f_rslem_register(0, new_chan_nr);
	RSL.receive(tr_RSL_PAGING_CMD(t_MI_TMSI(tmsi)));
	f_rslem_unregister(0, new_chan_nr);

	/* Despite the NRI matching the second MSC (NRI from 'msc 1' in osmo-bsc.cfg) and round robin pointing at the
	 * third MSC ('roundrobin next 2'), the earlier Paging for the same TMSI causes this Paging Response to go to
	 * the first MSC (bssap_idx := 0). */
	f_perform_compl_l3(RSL, RSL_PROC, ts_PAG_RESP(ts_MI_TMSI_NRI_LV(nri_v)) );
	f_sleep(1.0);
}
testcase TC_mscpool_paging_and_response_tmsi() runs on test_CT {
	f_init(nr_bts := 1, handler_mode := true, nr_msc := 3);
	f_sleep(1.0);

	/* Testing a Paging on the first MSC to get a Paging Response back to the first MSC. Set round robin to the
	 * third MSC to make sure we're getting the Paging logic, not a coincidental round robin match. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 2");

	f_ctrs_msc_init();

	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars1.mscpool.rsl_idx := 0;
	pars1.sccp_addr_bsc := g_bssap[pars1.mscpool.bssap_idx].sccpa.sccp_addr_peer;
	pars1.sccp_addr_msc := g_bssap[pars1.mscpool.bssap_idx].sccpa.sccp_addr_own;
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_paging_tmsi), pars1);
	vc_conn1.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:paged");
	f_shutdown_helper();
}

/* For round-robin, skip an MSC that has 'no allow-attach' set. */
/* FIXME: each run is using a separate RSLem: RSL, RSL1, RSL2. It should work
 * just as well using only RSL. */
testcase TC_mscpool_no_allow_attach_round_robin() runs on test_CT {

	f_init(nr_bts := 3, handler_mode := true, nr_msc := 3);
	f_sleep(1.0);
	/* Mark the second MSC as offloading, round-robin should skip this MSC now. */
	f_vty_msc_allow_attach(BSCVTY, {true, false, true});

	/* Control which MSC gets chosen next by the round-robin, otherwise
	 * would be randomly affected by which other tests ran before this. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 0");

	f_ctrs_msc_init();

	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars1.mscpool.rsl_idx := 0;
	pars1.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_IMSI_LV('001010000000001'H)), '00F110'O));
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars1);
	vc_conn1.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:new");

	var MSC_ConnHdlr vc_conn2;
	var TestHdlrParams pars2 := f_gen_test_hdlr_pars(bssap_idx := 2);
	pars2.mscpool.rsl_idx := 1;
	pars2.mscpool.l3_info := valueof(ts_CM_SERV_REQ(CM_TYPE_MO_CALL, valueof(ts_MI_IMSI_LV('001010000000002'H))));
	vc_conn2 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars2);
	vc_conn2.done;
	f_ctrs_msc_expect(2, "mscpool:subscr:new");

	var MSC_ConnHdlr vc_conn3;
	var TestHdlrParams pars3 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars3.mscpool.rsl_idx := 2;
	pars3.mscpool.l3_info := valueof(ts_PAG_RESP(ts_MI_IMSI_LV('001010000000003'H)));
	vc_conn3 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars3);
	vc_conn3.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:new");
	f_shutdown_helper();
}

/* An MSC that has 'no allow-attach' set should still serve subscribers that are already attached according to their
 * TMSI NRI. */
testcase TC_mscpool_no_allow_attach_valid_nri() runs on test_CT {

	f_init(nr_bts := 3, handler_mode := true, nr_msc := 3);
	f_sleep(1.0);

	/* Mark the second MSC as offloading, round-robin should skip this MSC now. */
	f_vty_msc_allow_attach(BSCVTY, {true, false, true});

	/* Control which MSC gets chosen next by the round-robin, otherwise
	 * would be randomly affected by which other tests ran before this. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 0");

	f_ctrs_msc_init();

	/* Round robin points at msc 0, but the valid NRI directs to msc 1, even though msc 1 has 'no allow-attach'. */
	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 1);
	pars1.mscpool.rsl_idx := 0;
	/* An NRI of the second MSC's range (256-511) */
	pars1.mscpool.l3_info := valueof(ts_CM_SERV_REQ(CM_TYPE_MO_CALL, valueof(ts_MI_TMSI_NRI_LV(260))));
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars1);
	vc_conn1.done;
	f_ctrs_msc_expect(1, "mscpool:subscr:known");

	var MSC_ConnHdlr vc_conn2;
	var TestHdlrParams pars2 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars2.mscpool.rsl_idx := 1;
	pars2.mscpool.l3_info := valueof(ts_CM_SERV_REQ(CM_TYPE_MO_CALL, valueof(ts_MI_IMSI_LV('001010000000002'H))));
	vc_conn2 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars2);
	vc_conn2.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:new");

	var MSC_ConnHdlr vc_conn3;
	var TestHdlrParams pars3 := f_gen_test_hdlr_pars(bssap_idx := 2);
	pars3.mscpool.rsl_idx := 2;
	pars3.mscpool.l3_info := valueof(ts_CM_SERV_REQ(CM_TYPE_MO_CALL, valueof(ts_MI_IMSI_LV('001010000000003'H))));
	vc_conn3 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars3);
	vc_conn3.done;
	f_ctrs_msc_expect(2, "mscpool:subscr:new");
	f_shutdown_helper();
}

/* When a peer point-code gets an SCCP N-PCSTATE saying it is unreachable,
 * immediately mark the MSC link as unusable.
 * In this scenario, the SCCP N-PCSTATE is triggered by locally dropping the
 * SCTP assoc at the virtual MSC (TTCN3). When STP osmo-bsc finds out about
 * the SCTP conn STP<->MSC going down, it will send an M3UA DUNA to the IUT
 * (BSC) which should then generate an N-PCSTATE.ind.
 */
testcase TC_mscpool_sccp_n_pcstate_detaches_msc() runs on test_CT {

	f_init(nr_bts := 3, handler_mode := true, nr_msc := 2);
	f_sleep(1.0);

	/* Control which MSC gets chosen next by the round-robin, otherwise
	 * would be randomly affected by which other tests ran before this. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 0");

	f_ctrs_msc_init();

	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars1.mscpool.rsl_idx := 0;
	pars1.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_IMSI_LV('001010000000001'H)), '00F110'O));
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars1);
	vc_conn1.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:new");

	var MSC_ConnHdlr vc_conn2;
	var TestHdlrParams pars2 := f_gen_test_hdlr_pars(bssap_idx := 1);
	pars2.mscpool.rsl_idx := 1;
	pars2.mscpool.l3_info := valueof(ts_CM_SERV_REQ(CM_TYPE_MO_CALL, valueof(ts_MI_IMSI_LV('001010000000002'H))));
	vc_conn2 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars2);
	vc_conn2.done;
	f_ctrs_msc_expect(1, "mscpool:subscr:new");

	f_logp(BSCVTY, "disconnecting msc0");
	f_bssap_idx_disconnect(0);
	f_ctrl_exp_trap(IPA_CTRL, "msc." & int2str(0) & ".connection_status", "disconnected", timeout_val := 5.0);

	/* Now round-robin would wrap to the first MSC, but since the first MSC is disconnected, it wraps around to msc
	 * 2 again. */
	var MSC_ConnHdlr vc_conn3;
	var TestHdlrParams pars3 := f_gen_test_hdlr_pars(bssap_idx := 1);
	pars3.mscpool.rsl_idx := 2;
	pars3.mscpool.l3_info := valueof(ts_PAG_RESP(ts_MI_IMSI_LV('001010000000003'H)));
	vc_conn3 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars3);
	vc_conn3.done;
	f_ctrs_msc_expect(1, "mscpool:subscr:new");
	f_shutdown_helper();
}

/* When an MSC point-code gets an SCCP N-PCSTATE saying it is now reachable, immediately trigger RESET and bring up the
 * MSC. */
testcase TC_mscpool_sccp_n_pcstate_attaches_msc() runs on test_CT {

	f_init(nr_bts := 3, handler_mode := true, nr_msc := 1);
	f_sleep(1.0);

	/* Control which MSC gets chosen next by the round-robin, otherwise
	 * would be randomly affected by which other tests ran before this. */
	f_vty_transceive(BSCVTY, "mscpool roundrobin next 0");

	f_ctrs_msc_init();

	/* There is only one MSC, round robin stays on msc0 */
	var MSC_ConnHdlr vc_conn1;
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars1.mscpool.rsl_idx := 0;
	pars1.mscpool.l3_info := valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_IMSI_LV('0010100230000001'H)), '00F110'O));
	vc_conn1 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars1);
	vc_conn1.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:new");

	var MSC_ConnHdlr vc_conn2;
	var TestHdlrParams pars2 := f_gen_test_hdlr_pars(bssap_idx := 0);
	pars2.mscpool.rsl_idx := 1;
	pars2.mscpool.l3_info := valueof(ts_CM_SERV_REQ(CM_TYPE_MO_CALL, valueof(ts_MI_IMSI_LV('001010023000002'H))));
	vc_conn2 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars2);
	vc_conn2.done;
	f_ctrs_msc_expect(0, "mscpool:subscr:new");

	f_logp(BSCVTY, "connecting msc1");
	f_vty_msc_allow_attach(BSCVTY, { true, true });
	f_bssap_idx_init(1);
	f_sleep(1.0);

	/* This time round-robin wraps to the second MSC, because it is now online. */
	var MSC_ConnHdlr vc_conn4;
	var TestHdlrParams pars4 := f_gen_test_hdlr_pars(bssap_idx := 1);
	pars4.mscpool.rsl_idx := 2;
	pars4.mscpool.l3_info := valueof(ts_PAG_RESP(ts_MI_IMSI_LV('001010023000003'H)));
	vc_conn4 := f_start_handler(refers(f_tc_mscpool_compl_l3), pars4);
	vc_conn4.done;
	f_ctrs_msc_expect(1, "mscpool:subscr:new");

	f_shutdown_helper();
}

/* Allow/Deny emergency calls globally via VTY */
private function f_vty_allow_emerg_msc(boolean allow) runs on test_CT {
	f_vty_enter_cfg_msc(BSCVTY, 0);
	if (allow) {
		f_vty_transceive(BSCVTY, "allow-emergency allow");
	} else {
		f_vty_transceive(BSCVTY, "allow-emergency deny");
	}
	f_vty_transceive(BSCVTY, "exit");
	f_vty_transceive(BSCVTY, "exit");
}

/* Allow/Deny emergency calls per BTS via VTY */
private function f_vty_allow_emerg_bts(boolean allow, integer bts_nr) runs on test_CT {
	f_vty_enter_cfg_bts(BSCVTY, bts_nr);
	if (allow) {
		f_vty_transceive(BSCVTY, "rach emergency call allowed 1");
	} else {
		f_vty_transceive(BSCVTY, "rach emergency call allowed 0");
	}
	f_vty_transceive(BSCVTY, "exit");
	f_vty_transceive(BSCVTY, "exit");
	f_vty_transceive(BSCVTY, "exit");
}

/* Allow/Forbid Fast Return after SRVCC on a given BTS via VTY */
private function f_vty_allow_srvcc_fast_return(boolean allow, integer bts_nr) runs on test_CT {
	f_vty_enter_cfg_bts(BSCVTY, bts_nr);
	if (allow) {
		f_vty_transceive(BSCVTY, "srvcc fast-return allow");
	} else {
		f_vty_transceive(BSCVTY, "srvcc fast-return forbid");
	}
	f_vty_transceive(BSCVTY, "exit");
	f_vty_transceive(BSCVTY, "exit");
	f_vty_transceive(BSCVTY, "exit");
}

/* Allow/Forbid TCH for signalling if SDCCH exhausted on a given BTS via VTY */
private function f_vty_allow_tch_for_signalling(boolean allow, integer bts_nr) runs on test_CT {
	f_vty_enter_cfg_bts(BSCVTY, bts_nr);
	if (allow) {
		f_vty_transceive(BSCVTY, "channel allocator allow-tch-for-signalling 1");
	} else {
		f_vty_transceive(BSCVTY, "channel allocator allow-tch-for-signalling 0");
	}
	f_vty_transceive(BSCVTY, "exit");
	f_vty_transceive(BSCVTY, "exit");
	f_vty_transceive(BSCVTY, "exit");
}

/* Enable/Disable SMLC via VTY */
private function f_vty_enable_smlc(boolean enable) runs on test_CT {
	f_vty_enter_cfg_smlc(BSCVTY);
	if (enable) {
		f_vty_transceive(BSCVTY, "enable");
	} else {
		f_vty_transceive(BSCVTY, "no enable");
	}
	f_vty_transceive(BSCVTY, "exit");
	f_vty_transceive(BSCVTY, "exit");
}

/* Begin assignment procedure and send an EMERGENCY SETUP (RR) */
private function f_assignment_emerg_setup() runs on MSC_ConnHdlr {
	var PDU_ML3_MS_NW emerg_setup;
	var octetstring emerg_setup_enc;
	var RSL_Message emerg_setup_data_ind;

	f_establish_fully(omit, omit);

	emerg_setup := valueof(ts_ML3_MO_CC_EMERG_SETUP(1, valueof(ts_Bcap_voice)));
	emerg_setup_enc := enc_PDU_ML3_MS_NW(emerg_setup);
	emerg_setup_data_ind := valueof(ts_RSL_DATA_IND(g_chan_nr, valueof(ts_RslLinkID_DCCH(0)), emerg_setup_enc));

	RSL.send(emerg_setup_data_ind);
}

/* expect EmergencySetup on BSSAP after calling f_assignment_emerg_setup() */
private function f_assignment_emerg_setup_exp_bssap()
runs on MSC_ConnHdlr {
	var PDU_BSSAP emerg_setup_data_ind_bssap;
	var PDU_ML3_MS_NW emerg_setup;
	timer T := 3.0;

	T.start;
	alt {
	[] BSSAP.receive(tr_BSSAP_DTAP) -> value emerg_setup_data_ind_bssap {
		emerg_setup := dec_PDU_ML3_MS_NW(emerg_setup_data_ind_bssap.pdu.dtap);
		if (not isbound(emerg_setup.msgs.cc.emergencySetup)) {
			setverdict(fail, "no emergency setup");
		}
		}
	[] BSSAP.receive {
		setverdict(fail, "unexpected BSSAP message!");
		}
	[] T.timeout {
		setverdict(fail, "timout waiting for EMERGENCY SETUP!");
		}
	}
}

private function f_assignment_emerg_setup_voice()
runs on MSC_ConnHdlr {
	/* Go on with voice call assignment */
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();

	/* Below speechOrDataIndicator and codecList are copied from an emergency call captured during tests.
	 * They seem a bit weird (AMR-WB, and the order differ between speechId_DataIndicator and the codecList), but
	 * seems a good idea to see how osmo-bsc reacts to this. */
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := {
		elementIdentifier := '0B'O,	/* overwritten */
		lengthIndicator := 0,		/* overwritten */
		speechOrDataIndicator := '0001'B,	/* speech */
		spare1_4 := '0000'B,
		channelRateAndType := ChRate_TCHForH_Fpref,
		speechId_DataIndicator := 'c2918105'O
	};
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({
		ts_CodecHR, ts_CodecAMR_WB, ts_CodecEFR, ts_CodecFR}));

	f_rslem_dchan_queue_enable();

	var ExpectCriteria mgcpcrit := {
		connid := omit,
		endpoint := omit,
		transid := omit
	};
	f_create_mgcp_expect(mgcpcrit);

	BSSAP.send(ass_cmd);

	var AssignmentState st := valueof(ts_AssignmentStateInit);
	st.rtp_stream := true;
	st.is_assignment := false;
	alt {
	[] as_modify(st);
	[] as_Media();
	[st.modify_done] BSSAP.receive(exp_compl) {
		setverdict(pass);
		}
	}

	/* Voice call carries on ... */
	f_sleep(2.0);
}

/* Test if the EMERGENCY SETUP gets passed on to the MSC via A when EMERGENCY
 * CALLS are permitted by the BSC config. */
private function f_TC_assignment_emerg_setup_allow(charstring id) runs on MSC_ConnHdlr {

	/* Make sure the CHAN RQD indicates an emergency call (0b101xxxxx). The difference is that osmo-bsc directly
	 * assigns a TCH lchan and establishing voice for the emergency call will use Mode Modify, not reassignment to
	 * another lchan. */
	g_pars.ra := f_rnd_ra_emerg();
	f_assignment_emerg_setup();
	f_assignment_emerg_setup_exp_bssap();
	f_assignment_emerg_setup_voice();

	setverdict(pass);
	f_perform_clear();
}

/* Test if the EMERGENCY SETUP gets blocked by the BSC if EMERGENCY CALLS are
 * forbidden by the BSC config. */
private function f_TC_assignment_emerg_setup_deny(charstring id) runs on MSC_ConnHdlr {
	var PDU_BSSAP emerg_setup_data_ind_bssap;
	timer T := 3.0;

	f_assignment_emerg_setup();

	T.start;
	alt {
	[] RSL.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, decmatch tr_RRM_RR_RELEASE)) {
		setverdict(pass);
		}
	[] RSL.receive {
		setverdict(fail, "unexpected RSL message!");
		}
	[] T.timeout {
		setverdict(fail, "timout waiting for RR CHANNEL RELEASE!");
		}
	}
	BSSAP.receive(tr_BSSMAP_ClearRequest);
	f_perform_clear_no_rr_rel();
}

/* EMERGENCY CALL situation #1, allowed globally and by BTS */
testcase TC_assignment_emerg_setup_allow() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	f_vty_allow_emerg_msc(true);
	f_vty_allow_emerg_bts(true, 0);
	vc_conn := f_start_handler(refers(f_TC_assignment_emerg_setup_allow), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* Test MO emergency call using MobileIdentity=IMEI (possible for emergency
 * calls from phones without SIM card).
 * 3GPP TS 24.008 section 10.5.1.4, OS#5849 */
testcase TC_assignment_emerg_setup_allow_imei() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	/* Remove IMSI set by f_gen_test_hdlr_pars(), then IMEI will be used to place the call */
	pars.imsi := omit;

	f_init(1, true);
	f_sleep(1.0);

	f_vty_allow_emerg_msc(true);
	f_vty_allow_emerg_bts(true, 0);
	vc_conn := f_start_handler(refers(f_TC_assignment_emerg_setup_allow), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* EMERGENCY CALL situation #2, forbidden globally but allowed by BTS */
testcase TC_assignment_emerg_setup_deny_msc() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	f_vty_allow_emerg_msc(false);
	f_vty_allow_emerg_bts(true, 0);
	/* Make sure the CHAN RQD indicates an emergency call (0b101xxxxx): */
	pars.ra := f_rnd_ra_emerg();
	vc_conn := f_start_handler(refers(f_TC_assignment_emerg_setup_deny), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* EMERGENCY CALL situation #3, allowed globally but forbidden by BTS.
 * The RACH req (and hence CHAN RQD) indicate other than emergency call.
 * Hence BSC only learns about it being an emergency call later during call setup.
 * If interested in the  ra="emergency call" + deny bts policy case,
 * see TC_chan_rqd_emerg_deny.
 */
testcase TC_assignment_emerg_setup_deny_bts() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	/* Note: This simulates a spec violation by the MS, correct MS
	 * implementations would not try to establish an emergency call because
	 * the system information tells in advance that emergency calls are
	 * not allowed */

	f_init(1, true);
	f_sleep(1.0);

	f_vty_allow_emerg_msc(true);
	f_vty_allow_emerg_bts(false, 0);
	/* Note: Here we implicitly leave default g_pars.ra which is different than "emergency call" */
	vc_conn := f_start_handler(refers(f_TC_assignment_emerg_setup_deny), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* Test what happens when an emergency call arrives while all TCH channels are
 * busy, the BSC is expected to terminate one call in favor of the incoming
 * emergency call */
testcase TC_emerg_premption() runs on test_CT {
	var ASP_RSL_Unitdata rsl_ud;
	var integer i;
	var integer chreq_total, chreq_nochan;
	var RSL_Message rx_rsl;
	var octetstring l3_payload := gen_l3_valid_payload();

	f_init(1);
	f_sleep(1.0);

	f_vty_allow_emerg_msc(true);
	f_vty_allow_emerg_bts(true, 0);

	/* Fill up all channels on the BTS */
	chreq_total := f_ctrl_get_ratectr_abs(IPA_CTRL, "bts", 0, "chreq:total");
	chreq_nochan := f_ctrl_get_ratectr_abs(IPA_CTRL, "bts", 0, "chreq:no_channel");
	for (i := 0; i < NUM_TCHF_PER_BTS + NUM_TCHH_PER_BTS + NUM_SDCCH_PER_BTS; i := i+1) {
		f_chreq_act_ack('33'O, i);
	}
	IPA_RSL[0][0].clear;
	f_ctrl_get_exp_ratectr_abs(IPA_CTRL, "bts", 0, "chreq:total",
				   chreq_total + NUM_TCHF_PER_BTS + NUM_TCHH_PER_BTS + NUM_SDCCH_PER_BTS);

	/* Send EST IND for the first TCH, so we get to test the RR release cause */
	var RslChannelNr first_tch := valueof(t_RslChanNr_Bm(1));
	f_ipa_tx(ts_RSL_EST_IND(first_tch, valueof(ts_RslLinkID_DCCH(0)), l3_payload));

	/* Accept BSSAP conn, so we get to test the Clear Request cause */
	var BSSAP_N_CONNECT_ind rx_c_ind;
	BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3_payload))) -> value rx_c_ind;
	var integer sccp_conn_id := rx_c_ind.connectionId;
	BSSAP.send(ts_BSSAP_CONNECT_res(rx_c_ind.connectionId));

	/* Send Channel request for emegergency call */
	f_ipa_tx(ts_RSL_CHAN_RQD('A5'O, 23));

	/* Expect the BSC to release one (the first) TCH/F on the BTS */
	f_expect_chan_rel(first_tch, expect_rr_chan_rel := true, expect_rll_rel_req := false,
			  expect_rr_cause := GSM48_RR_CAUSE_PREMPTIVE_REL);

	/* Also expect a BSSMAP Clear Request with PREEMPTION */
	var BSSAP_N_DATA_ind rx_clear_req;
	const myBSSMAP_Cause preemption := GSM0808_CAUSE_PREEMPTION;
	BSSAP.receive(tr_BSSAP_DATA_ind(sccp_conn_id, tr_BSSMAP_ClearRequest)) -> value rx_clear_req;
	log("XXX ", rx_clear_req);
	if (rx_clear_req.userData.pdu.bssmap.clearRequest.cause.causeValue != int2bit(enum2int(preemption), 7)) {
		setverdict(fail, "BSSMAP Clear Request: expected cause PREEMPTION");
	}

	/* Expect the BSC to send activate/assign the channel for the emergency call */
	rx_rsl := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));
	if (first_tch != rx_rsl.ies[0].body.chan_nr) {
		setverdict(fail, "different TCH lchan activated than expected");
	}
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(first_tch, 33));
	rx_rsl := f_exp_ipa_rx(tr_RSL_IMM_ASSIGN(0));

	/* complete the BSSMAP Clear to satisfy the conn leak check */
	BSSAP.send(ts_BSSAP_DATA_req(sccp_conn_id, ts_BSSMAP_ClearCommand(enum2int(preemption))));
	BSSAP.receive(tr_BSSAP_DATA_ind(sccp_conn_id, tr_BSSMAP_ClearComplete)) {
		BSSAP.send(ts_BSSAP_DISC_req(sccp_conn_id, 0));
	}

	f_shutdown_helper();
}

/* Hopping parameters per a timeslot */
private type record length(0..64) of GsmArfcn ArfcnList;
private type record FHParamsTs {
	boolean		enabled,
	uint6_t		hsn,
	uint6_t		maio,
	ArfcnList	ma
};

/* Hopping parameters per a transceiver */
private type record FHParamsTrx {
	GsmBandArfcn	arfcn,
	FHParamsTs	ts[8]
};

/* Randomly generate the hopping parameters for the given timeslot numbers */
private function f_TC_fh_params_gen(template integer tr_tn := (1, 3, 5))
runs on test_CT return FHParamsTrx {
	var FHParamsTrx fhp;

	/* Generate a random ARFCN in the range of 0 - 3. This ARFCN will
	 * fall in the GSM900 band. */
	fhp.arfcn.arfcn := f_rnd_int(3);
	fhp.arfcn.pcs := false;

	for (var integer tn := 0; tn < 8; tn := tn + 1) {
		if (not match(tn, tr_tn)) {
			fhp.ts[tn].enabled := false;
			fhp.ts[tn].ma := { };
			continue;
		}

		/* Random HSN / MAIO values: 0..63 */
		fhp.ts[tn].hsn := f_rnd_int(64);
		fhp.ts[tn].maio := f_rnd_int(64);
		fhp.ts[tn].ma := { };

		/* Random Mobile Allocation (hopping channels) */
		var integer ma_len := 2 + f_rnd_int(9); /* 2..10 channels */
		var integer step := 3 + f_rnd_int(4); /* 3..6 stepping */
		for (var integer i := 1; i <= ma_len; i := i + 1) {
			fhp.ts[tn].ma := fhp.ts[tn].ma & { i * step };
		}

		fhp.ts[tn].enabled := true;
	}

	log("f_TC_fh_params_gen(): ", fhp);
	return fhp;
}

/* Make sure that the given Channel Description IE matches the hopping configuration */
private function f_TC_fh_params_match_chan_desc(in FHParamsTrx fhp, in ChannelDescription cd)
{
	var template (present) ChannelDescription tr_cd;
	var template (present) MaioHsn tr_maio_hsn;
	var uint3_t tn := cd.chan_nr.tn;

	if (fhp.ts[tn].enabled) {
		tr_maio_hsn := tr_HsnMaio(fhp.ts[tn].hsn, fhp.ts[tn].maio);
		tr_cd := tr_ChanDescH1(cd.chan_nr, tr_maio_hsn);
	} else {
		tr_cd := tr_ChanDescH0(cd.chan_nr, fhp.arfcn.arfcn);
	}

	if (not match(cd, tr_cd)) {
		setverdict(fail, "Channel Description IE does not match: ",
			   cd, " vs expected ", tr_cd);
	}
}

/* Make sure that the given Mobile Allocation IE matches the hopping configuration */
private function f_TC_fh_params_match_ma(in FHParamsTrx fhp, uint3_t tn,
					 in MobileAllocationLV ma)
{
	var template MobileAllocationLV tr_ma := f_TC_fh_params_gen_tr_ma(fhp, tn, ma);

	if (not match(ma, tr_ma)) {
		setverdict(fail, "Mobile Allocation IE does not match (tn := ",
			   tn, "): ", ma, " vs expected: ", tr_ma);
	} else {
		setverdict(pass);
	}
}

private function f_TC_fh_params_gen_tr_ma(in FHParamsTrx fhp, uint3_t tn,
					  in MobileAllocationLV ma)
return template MobileAllocationLV {
	/* Mobile Allocation IE is expected to be empty if hopping is not enabled */
	if (not fhp.ts[tn].enabled) {
		return { len := 0, ma := ''B };
	}

	var bitstring full_mask := f_pad_bit(''B, 1024, '0'B);
	var bitstring slot_mask := f_pad_bit(''B, 1024, '0'B);
	var bitstring ma_mask := ''B;

	/* Compose the full bit-mask (all channels, up to 1024 entries) */
	for (var integer i := 0; i < lengthof(fhp.ts); i := i + 1) {
		for (var integer j := 0; j < lengthof(fhp.ts[i].ma); j := j + 1) {
			if (full_mask[fhp.ts[i].ma[j]] == '1'B)
				{ continue; }
			full_mask[fhp.ts[i].ma[j]] := '1'B;
		}
	}

	/* Take ARFCN of the TRX itself into account */
	full_mask[fhp.arfcn.arfcn] := '1'B;

	/* Compose a bit-mask for the given timeslot number */
	for (var integer i := 0; i < lengthof(fhp.ts[tn].ma); i := i + 1) {
		slot_mask[fhp.ts[tn].ma[i]] := '1'B;
	}

	/* Finally, compose the Mobile Allocation bit-mask */
	for (var integer i := 1; i < lengthof(full_mask); i := i + 1) {
		if (full_mask[i] != '1'B)
			{ continue; }

		/* FIXME: ma_mask := ma_mask & slot_mask[i]; // triggers a bug in TITAN */
		if (slot_mask[i] == '1'B) {
			ma_mask := ma_mask & '1'B;
		} else {
			ma_mask := ma_mask & '0'B;
		}
	}

	/* ARFCN 0 (if present) goes to the last position of the bit-mask */
	if (full_mask[0] == '1'B) {
		/* FIXME: ma_mask := ma_mask & slot_mask[0]; // triggers a bug in TITAN */
		if (slot_mask[0] == '1'B) {
			ma_mask := ma_mask & '1'B;
		} else {
			ma_mask := ma_mask & '0'B;
		}
	}

	/* Ensure that ma_mask is octet-aligned */
	var integer ma_mask_len := (lengthof(ma_mask) + 8 - 1) / 8;
	ma_mask := f_pad_bit(ma_mask, ma_mask_len * 8, '0'B);

	return { len := ma_mask_len, ma := ma_mask };
}

/* Configure the appropriate band for a given arfcn, exc */
private function f_TC_set_band_by_arfcn(integer bts_nr, GsmBandArfcn arfcn) runs on test_CT
{
	var charstring band;
	var GsmBandArfcn arfcn_ := valueof(ts_GsmBandArfcn(arfcn.arfcn, arfcn.pcs, false));

	select (arfcn_) {
	case (tr_GsmBandArfcn((259..293), false, ?)) { band := "GSM450"; }
	case (tr_GsmBandArfcn((306..340), false, ?)) { band := "GSM480"; }
	case (tr_GsmBandArfcn((438..511), false, ?)) { band := "GSM750"; }
	case (tr_GsmBandArfcn((128..251), false, ?)) { band := "GSM850"; }
	case (tr_GsmBandArfcn((0..124), false, ?)) { band := "GSM900"; }
	case (tr_GsmBandArfcn((955..1023), false, ?)) { band := "GSM900"; }
	case (tr_GsmBandArfcn((512..885), false, ?)) { band := "DCS1800"; }
	case (tr_GsmBandArfcn((512..810), true, ?)) { band := "PCS1900"; }
	case else { return; }
	}

	f_vty_enter_cfg_bts(BSCVTY, bts_nr);
	f_vty_transceive(BSCVTY, "band " & band);
	f_vty_transceive(BSCVTY, "end");
}

/* Configure the hopping parameters in accordance with the given record */
private function f_TC_fh_params_set(in FHParamsTrx fhp,
				    uint8_t bts_nr := 0,
				    uint8_t trx_nr := 0)
runs on test_CT {

	f_TC_set_band_by_arfcn(bts_nr, fhp.arfcn);

	/* Enter the configuration node for the given BTS/TRX numbers */
	f_vty_enter_cfg_trx(BSCVTY, bts_nr, trx_nr);

	f_vty_transceive(BSCVTY, "arfcn " & int2str(fhp.arfcn.arfcn));

	for (var integer tn := 0; tn < lengthof(fhp.ts); tn := tn + 1) {
		f_vty_transceive(BSCVTY, "timeslot " & int2str(tn));

		if (not fhp.ts[tn].enabled) {
			f_vty_transceive(BSCVTY, "hopping enabled 0");
			f_vty_transceive(BSCVTY, "exit"); /* go back */
			continue;
		}

		/* Configure HSN / MAIO values */
		f_vty_transceive(BSCVTY, "hopping sequence-number " & int2str(fhp.ts[tn].hsn));
		f_vty_transceive(BSCVTY, "hopping maio " & int2str(fhp.ts[tn].maio));

		/* Configure the Mobile Allocation (hopping channels) */
		for (var integer i := 0; i < lengthof(fhp.ts[tn].ma); i := i + 1) {
			f_vty_transceive(BSCVTY, "hopping arfcn add " & int2str(fhp.ts[tn].ma[i]));
		}

		f_vty_transceive(BSCVTY, "hopping enabled 1");
		f_vty_transceive(BSCVTY, "exit"); /* go back */
	}

	f_vty_transceive(BSCVTY, "end");
}

/* Disable frequency hopping on all timeslots */
private function f_TC_fh_params_unset(in FHParamsTrx fhp,
				      uint8_t bts_nr := 0,
				      uint8_t trx_nr := 0,
				      GsmBandArfcn arfcn := {pcs := false, arfcn := 871})
runs on test_CT {

	f_TC_set_band_by_arfcn(bts_nr, arfcn);

	/* Enter the configuration node for the given BTS/TRX numbers */
	f_vty_enter_cfg_trx(BSCVTY, bts_nr, trx_nr);

	f_vty_transceive(BSCVTY, "arfcn " & int2str(arfcn.arfcn));

	for (var integer tn := 0; tn < lengthof(fhp.ts); tn := tn + 1) {
		f_vty_transceive(BSCVTY, "timeslot " & int2str(tn));

		/* Delete all ARFCNs from the Mobile Allocation (if any) */
		for (var integer i := 0; i < lengthof(fhp.ts[tn].ma); i := i + 1) {
			f_vty_transceive(BSCVTY, "hopping arfcn del " & int2str(fhp.ts[tn].ma[i]));
		}

		f_vty_transceive(BSCVTY, "hopping enabled 0");
		f_vty_transceive(BSCVTY, "exit"); /* go back */
	}

	f_vty_transceive(BSCVTY, "end");
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");
}

/* Verify presence and correctness of the hopping parameters (HSN, MAIO)
 * in the Channel Identification IE of the RSL CHANnel ACTIVation message. */
testcase TC_fh_params_chan_activ() runs on test_CT {
	var FHParamsTrx fhp := f_TC_fh_params_gen();
	var RSL_Message rsl_msg;
	var RSL_IE_Body ie;

	f_init_vty();

	f_TC_fh_params_set(fhp); /* Enable frequency hopping */
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1);

	/* CS domain: 3 (SDCCH/4+CBCH) + 4 (TCH/F) + 2 (TCH/H) channels available */
	for (var integer i := 0; i < 9; i := i + 1) {
		f_ipa_tx(ts_RSL_CHAN_RQD(f_rnd_ra_cs(), 23));
		rsl_msg := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));

		/* Make sure that Channel Identification IE is present */
		if (not f_rsl_find_ie(rsl_msg, RSL_IE_CHAN_IDENT, ie)) {
			setverdict(fail, "RSL Channel Identification IE is absent");
			continue;
		}

		/* Make sure that hopping parameters (HSN/MAIO) match */
		f_TC_fh_params_match_chan_desc(fhp, ie.chan_ident.ch_desc.v);

		/* "Mobile Allocation shall be included but empty" - let's check this */
		if (ie.chan_ident.ma.v.len != 0) {
			setverdict(fail, "Mobile Allocation IE is not empty: ",
				   ie.chan_ident.ma, ", despite it shall be");
			continue;
		}
	}

	/* Disable frequency hopping */
	f_TC_fh_params_unset(fhp);

	f_shutdown_helper();
}

/* Verify the hopping parameters (HSN, MAIO, MA) in (RR) Immediate Assignment */
testcase TC_fh_params_imm_ass() runs on test_CT {
	var FHParamsTrx fhp := f_TC_fh_params_gen();
	var RSL_Message rsl_msg;
	var RSL_IE_Body ie;

	f_init_vty();

	f_TC_fh_params_set(fhp); /* Enable frequency hopping */
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1);

	/* CS domain: 3 (SDCCH/4+CBCH) + 4 (TCH/F) + 2 (TCH/H) channels available */
	for (var integer i := 0; i < 9; i := i + 1) {
		f_ipa_tx(ts_RSL_CHAN_RQD(f_rnd_ra_cs(), 23));
		rsl_msg := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));

		f_ipa_tx(ts_RSL_CHAN_ACT_ACK(rsl_msg.ies[0].body.chan_nr, 33));
		rsl_msg := f_exp_ipa_rx(tr_RSL_MsgTypeC(RSL_MT_IMMEDIATE_ASSIGN_CMD));

		/* Make sure that Full Immediate Assign Info IE is present */
		if (not f_rsl_find_ie(rsl_msg, RSL_IE_FULL_IMM_ASS_INFO, ie)) {
			setverdict(fail, "RSL Full Immediate Assign Info IE is absent");
			continue;
		}

		/* Decode the actual Immediate Assignment message */
		var GsmRrMessage rr_msg := dec_GsmRrMessage(ie.full_imm_ass_info.payload);
		if (not match(rr_msg.header, t_RrHeader(IMMEDIATE_ASSIGNMENT, ?))) {
			setverdict(fail, "Failed to match Immediate Assignment: ", rr_msg);
			continue;
		}

		/* Make sure that hopping parameters (HSN/MAIO) match */
		f_TC_fh_params_match_chan_desc(fhp, rr_msg.payload.imm_ass.chan_desc);

		/* Make sure that the Mobile Allocation IE matches */
		f_TC_fh_params_match_ma(fhp, rr_msg.payload.imm_ass.chan_desc.chan_nr.tn,
					rr_msg.payload.imm_ass.mobile_allocation);
	}

	/* Disable frequency hopping */
	f_TC_fh_params_unset(fhp);

	f_shutdown_helper();
}

/* Verify the hopping parameters (HSN, MAIO, MA) in (RR) Assignment Command */
testcase TC_fh_params_assignment_cmd() runs on test_CT {
	var FHParamsTrx fhp := f_TC_fh_params_gen();
	var RSL_Message rsl_msg;
	var RSL_IE_Body ie;

	f_init_vty();

	f_TC_fh_params_set(fhp); /* Enable frequency hopping */
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1);

	/* HACK: work around "Couldn't find Expect for CRCX" */
	vc_MGCP[0].stop;

	var template PDU_BSSAP ass_cmd := f_gen_ass_req();
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := ts_BSSMAP_IE_CodecList({ts_CodecFR});

	/* CS domain (TCH): 4 (TCH/F) + 2 (TCH/H) channels available
	 * NOTE: only 3 SDCCH/4 channels are available on CCCH+SDCCH4+CBCH */
	for (var integer i := 0; i < 3; i := i + 1) {
		/* Establish a dedicated channel, so we can trigger (late) TCH assignment */
		var DchanTuple dt := f_est_dchan(f_rnd_ra_cs(), 23, gen_l3_valid_payload());

		/* Send a BSSMAP Assignment Command, expect CHANnel ACTIVation */
		BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ass_cmd));
		rsl_msg := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));

		/* ACKnowledge CHANnel ACTIVation, expect RSL DATA REQuest */
		f_ipa_tx(ts_RSL_CHAN_ACT_ACK(rsl_msg.ies[0].body.chan_nr, 33));
		rsl_msg := f_exp_ipa_rx(tr_RSL_MsgTypeR(RSL_MT_DATA_REQ));

		/* Make sure that L3 Information IE is present */
		if (not f_rsl_find_ie(rsl_msg, RSL_IE_L3_INFO, ie)) {
			setverdict(fail, "RSL L3 Information IE is absent");
			continue;
		}

		/* Decode the L3 message and make sure it is (RR) Assignment Command */
		var GsmRrL3Message l3_msg := dec_GsmRrL3Message(ie.l3_info.payload);
		if (not match(l3_msg.header, t_RrL3Header(ASSIGNMENT_COMMAND))) {
			setverdict(fail, "Failed to match Assignment Command: ", l3_msg);
			continue;
		}

		/* Make sure that hopping parameters (HSN/MAIO) match */
		var ChannelDescription chan_desc := l3_msg.payload.ass_cmd.chan_desc;
		f_TC_fh_params_match_chan_desc(fhp, chan_desc);

		/* Make sure that Cell Channel Description IE is present if FH is enabled */
		if (chan_desc.h and not ispresent(l3_msg.payload.ass_cmd.cell_chan_desc)) {
			setverdict(fail, "FH enabled, but Cell Channel Description IE is absent");
			continue;
		}

		/* Make sure that the Mobile Allocation IE matches (if present) */
		var boolean ma_present := ispresent(l3_msg.payload.ass_cmd.mobile_allocation);
		if (chan_desc.h and ma_present) {
			f_TC_fh_params_match_ma(fhp, chan_desc.chan_nr.tn,
						l3_msg.payload.ass_cmd.mobile_allocation.v);
		} else if (chan_desc.h and not ma_present) {
			setverdict(fail, "FH enabled, but Mobile Allocation IE is absent");
			continue;
		} else if (not chan_desc.h and ma_present) {
			setverdict(fail, "FH disabled, but Mobile Allocation IE is present");
			continue;
		}

		f_perform_clear_test_ct(dt);
	}

	/* Give the IUT some time to release all channels */
	f_sleep(3.0);

	/* Disable frequency hopping */
	f_TC_fh_params_unset(fhp);

	f_shutdown_helper();
}

/* Verify the hopping parameters (HSN, MAIO, MA) in (RR) Handover Command */
private function f_TC_fh_params_handover_cmd(in FHParamsTrx fhp)
runs on test_CT {
	var RSL_Message rsl_msg;
	var RSL_IE_Body ie;
	var DchanTuple dt;

	/* Establish a dedicated channel, so we can trigger handover */
	dt := f_est_dchan(f_rnd_ra_cs(), 23, gen_l3_valid_payload());
	f_sleep(0.5);

	/* Trigger handover from BTS0 to BTS1 */
	f_bts_0_cfg(BSCVTY, { "neighbor bts 1" });
	f_vty_handover(BSCVTY, 0, 0, dt.rsl_chan_nr, 1);

	/* Expect RSL CHANnel ACTIVation on BTS1/TRX0/TS1 */
	rsl_msg := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV), idx := {1, 0});

	/* ACKnowledge channel activation and expect (RR) Handover Command */
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(rsl_msg.ies[0].body.chan_nr, 33), idx := {1, 0});
	rsl_msg := f_exp_ipa_rx(tr_RSL_MsgTypeR(RSL_MT_DATA_REQ));

	/* Make sure that L3 Information IE is present */
	if (not f_rsl_find_ie(rsl_msg, RSL_IE_L3_INFO, ie)) {
		setverdict(fail, "RSL L3 Information IE is absent");
		return;
	}

	/* Decode the L3 message and make sure it is (RR) Handover Command */
	var GsmRrL3Message l3_msg := dec_GsmRrL3Message(ie.l3_info.payload);
	if (not match(l3_msg.header, t_RrL3Header(HANDOVER_COMMAND))) {
		setverdict(fail, "Failed to match Handover Command: ", l3_msg);
		return;
	}

	/* Make sure that we've got SDCCH/8 on TS1 (expected to be hopping) */
	var ChannelDescription chan_desc := l3_msg.payload.ho_cmd.chan_desc;
	if (not match(chan_desc.chan_nr, t_RslChanNr_SDCCH8(1, ?))) {
		setverdict(fail, "Unexpected channel number: ", chan_desc.chan_nr);
		return;
	}

	/* Make sure that hopping parameters (HSN/MAIO) match */
	f_TC_fh_params_match_chan_desc(fhp, chan_desc);

	/* Make sure that Cell Channel Description IE is present */
	if (not ispresent(l3_msg.payload.ho_cmd.cell_chan_desc)) {
		setverdict(fail, "FH enabled, but Cell Channel Description IE is absent");
		return;
	}

	/* Make sure that the Mobile Allocation (after time) IE is present and matches */
	var boolean ma_present := ispresent(l3_msg.payload.ho_cmd.mobile_allocation);
	if (ma_present) {
		f_TC_fh_params_match_ma(fhp, chan_desc.chan_nr.tn,
					l3_msg.payload.ho_cmd.mobile_allocation.v);
	} else {
		setverdict(fail, "FH enabled, but Mobile Allocation IE is absent");
		return;
	}

	f_perform_clear_test_ct(dt);
}
testcase TC_fh_params_handover_cmd() runs on test_CT {
	var FHParamsTrx fhp := f_TC_fh_params_gen();

	f_init_vty();

	/* (Re)configure TS0 as BCCH and TS1 as SDCCH8 on BTS1/TRX0 */
	f_vty_enter_cfg_trx(BSCVTY, bts := 1, trx := 0);

	f_vty_transceive(BSCVTY, "timeslot 0");
	f_vty_transceive(BSCVTY, "phys_chan_config ccch");
	f_vty_transceive(BSCVTY, "exit"); /* go back */

	f_vty_transceive(BSCVTY, "timeslot 1");
	f_vty_transceive(BSCVTY, "phys_chan_config sdcch8");
	f_vty_transceive(BSCVTY, "end"); /* we're done */

	f_TC_fh_params_set(fhp, 1); /* Enable frequency hopping on BTS1 */
	f_vty_transceive(BSCVTY, "drop bts connection 1 oml");

	f_init(2);

	f_TC_fh_params_handover_cmd(fhp);

	/* Disable frequency hopping on BTS1 */
	f_TC_fh_params_unset(fhp, 1);

	/* (Re)configure TS0 as CCCH+SDCCH4+CBCH and TS1 as TCH/F */
	f_vty_enter_cfg_trx(BSCVTY, bts := 1, trx := 0);

	f_vty_transceive(BSCVTY, "timeslot 0");
	f_vty_transceive(BSCVTY, "phys_chan_config ccch+sdcch4+cbch");
	f_vty_transceive(BSCVTY, "exit"); /* go back */

	f_vty_transceive(BSCVTY, "timeslot 1");
	f_vty_transceive(BSCVTY, "phys_chan_config tch/f");
	f_vty_transceive(BSCVTY, "end"); /* we're done */

	f_shutdown_helper();
}

/* Verify the hopping parameters in System Information Type 4 */
testcase TC_fh_params_si4_cbch() runs on test_CT {
	var FHParamsTrx fhp := f_TC_fh_params_gen(tr_tn := 1);
	var ASP_RSL_Unitdata rx_rsl_ud;
	timer T := 5.0;

	f_init_vty();

	/* (Re)configure TS0 as BCCH and TS1 as SDCCH8+CBCH */
	f_vty_enter_cfg_trx(BSCVTY, trx := 0);

	f_vty_transceive(BSCVTY, "timeslot 0");
	f_vty_transceive(BSCVTY, "phys_chan_config ccch");
	f_vty_transceive(BSCVTY, "exit"); /* go back */

	f_vty_transceive(BSCVTY, "timeslot 1");
	f_vty_transceive(BSCVTY, "phys_chan_config sdcch8+cbch");
	f_vty_transceive(BSCVTY, "end"); /* we're done */

	f_TC_fh_params_set(fhp); /* Enable frequency hopping */
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1);

	T.start;
	alt {
	[] IPA_RSL[0][0].receive(tr_ASP_RSL_UD(tr_RSL_BCCH_INFO(RSL_SYSTEM_INFO_4))) -> value rx_rsl_ud {
		var RSL_IE_Body ie := rx_rsl_ud.rsl.ies[2].body; /* FULL BCCH Information IE */
		var SystemInformation si := dec_SystemInformation(ie.other.payload);

		/* Make sure that what we decoded is System Information Type 4 */
		if (si.header.message_type != SYSTEM_INFORMATION_TYPE_4) {
			setverdict(fail, "RSL FULL BCCH Information IE contains: ", si);
			repeat;
		}

		/* Make sure that CBCH Channel Description IE is present */
		if (not ispresent(si.payload.si4.cbch_chan_desc)) {
			setverdict(fail, "CBCH Channel Description IE is absent");
			break;
		}

		/* Finally, check the hopping parameters (HSN, MAIO) */
		var ChannelDescription chan_desc := si.payload.si4.cbch_chan_desc.v;
		f_TC_fh_params_match_chan_desc(fhp, chan_desc);

		/* 3GPP TS 44.018, section 9.1.36.2 "CBCH Mobile Allocation":
		 * The CBCH Mobile Allocation IE *shall* be present if FH is enabled. */
		if (chan_desc.h and not ispresent(si.payload.si4.cbch_mobile_alloc)) {
			setverdict(fail, "FH enabled, but Mobile Allocation IE is absent");
			break;
		} else if (chan_desc.h and ispresent(si.payload.si4.cbch_mobile_alloc)) {
			f_TC_fh_params_match_ma(fhp, chan_desc.chan_nr.tn,
						si.payload.si4.cbch_mobile_alloc.v);
		}
		}
	[] IPA_RSL[0][0].receive { repeat; }
	[] T.timeout {
		setverdict(fail, "Timeout waiting for RSL BCCH INFOrmation (SI4)");
		}
	}

	/* Disable frequency hopping */
	f_TC_fh_params_unset(fhp);

	/* (Re)configure TS0 as CCCH+SDCCH4+CBCH and TS1 as TCH/F */
	f_vty_enter_cfg_trx(BSCVTY, trx := 0);

	f_vty_transceive(BSCVTY, "timeslot 0");
	f_vty_transceive(BSCVTY, "phys_chan_config ccch+sdcch4+cbch");
	f_vty_transceive(BSCVTY, "exit"); /* go back */

	f_vty_transceive(BSCVTY, "timeslot 1");
	f_vty_transceive(BSCVTY, "phys_chan_config tch/f");
	f_vty_transceive(BSCVTY, "end"); /* we're done */

	f_shutdown_helper();
}

template (value) PDU_BSSAP_LE ts_BSSMAP_LE_BSSLAP(template (value) BSSLAP_PDU bsslap)
	:= ts_BSSMAP_LE_ConnInfo(BSSMAP_LE_PROT_BSSLAP, data := enc_BSSLAP_PDU(valueof(bsslap)));

private function f_match_bsslap(PDU_BSSAP_LE got_bsslap_msg,
				template (present) BSSLAP_PDU expect_bsslap)
{
	var BSSLAP_PDU bsslap := dec_BSSLAP_PDU(got_bsslap_msg.pdu.bssmap.co_info.bsslap_apdu.data);
	if (not match(bsslap, expect_bsslap)) {
		log("EXPECTING BSSLAP: ", expect_bsslap);
		log("GOT BSSLAP: ", bsslap);
		setverdict(fail, "BSSLAP is not as expected");
		mtc.stop;
	}
	setverdict(pass);
}

/* GAD: this is an Ellipsoid point with uncertainty circle, encoded as in 3GPP TS 23.032 §7.3.2. */
const octetstring gad_ell_point_unc_circle := '10b0646d0d5f6627'O;

private function f_expect_bsslap(template (present) BSSLAP_PDU expect_rx_bsslap) runs on MSC_ConnHdlr {
	var PDU_BSSAP_LE rx_bsslap;
	BSSAP_LE.receive(tr_BSSMAP_LE_ConnInfo(BSSMAP_LE_PROT_BSSLAP, ?)) -> value(rx_bsslap);
	f_match_bsslap(rx_bsslap, expect_rx_bsslap);
}

/* With an active lchan, start BSSMAP Perform Location Request on A interface, starting BSSMAP-LE Perform Location
 * Request on Lb interface. Either with or without the SMLC doing a BSSLAP TA Request. */
private function f_lcs_loc_req_for_active_ms(boolean do_ta_request := false) runs on MSC_ConnHdlr {
	f_sleep(1.0);

	f_establish_fully(omit, omit);
	f_bssap_le_register_imsi(g_pars.imsi, omit);

	BSSAP.send(valueof(ts_BSSMAP_Perform_Location_Request(ts_BSSMAP_Imsi(g_pars.imsi),
					ts_CellId_CGI('262'H, '42'H, 23, 42))));

	var PDU_BSSAP_LE plr;
	BSSAP_LE.receive(tr_BSSMAP_LE_PerfLocReq(BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC_LOC, ?, ?)) -> value(plr);

	if (not do_ta_request) {
		/* verify TA Layer 3 in APDU. First the APDU type (BSSLAP), then the BSSLAP data contents. */
		var template BSSMAP_LE_IE_APDU expect_apdu := tr_BSSMAP_LE_APDU(BSSMAP_LE_PROT_BSSLAP, ?);
		if (not match(plr.pdu.bssmap.perf_loc_req.bsslap_apdu, expect_apdu)) {
			log("EXPECTING BSSMAP-LE APDU IE ", expect_apdu);
			log("GOT BSSMAP-LE APDU IE ", plr.pdu.bssmap.perf_loc_req.bsslap_apdu);
			setverdict(fail, "BSSMAP-LE APDU IE is not as expected");
			mtc.stop;
		}
		var template BSSLAP_PDU expect_ta_layer3 := tr_BSSLAP_TA_Layer3(tr_BSSLAP_IE_TA(0));
		var BSSLAP_PDU bsslap := dec_BSSLAP_PDU(plr.pdu.bssmap.perf_loc_req.bsslap_apdu.data);
		if (not match(bsslap, expect_ta_layer3)) {
			log("EXPECTING BSSLAP TA Layer 3: ", expect_ta_layer3);
			log("GOT BSSLAP: ", bsslap);
			setverdict(fail, "BSSLAP is not as expected");
			mtc.stop;
		}
		/* OsmoBSC directly sent the TA as BSSLAP APDU in the BSSMAP-LE Perform Location Request to the SMLC. The SMLC
		 * has no need to request the TA from the BSC and directly responds. */
	} else {
		/* SMLC wants to ask the TA from the BSC explicitly in a BSSLAP TA Request message */
		BSSAP_LE.send(ts_BSSMAP_LE_BSSLAP(ts_BSSLAP_TA_Req));
		f_expect_bsslap(tr_BSSLAP_TA_Resp(?, ?));
	}

	/* SMLC got the TA from the BSC, now responds with geo information data. */
	BSSAP_LE.send(ts_BSSMAP_LE_PerfLocResp(gad_ell_point_unc_circle, omit));
	BSSAP_LE.receive(BSSAP_LE_Conn_Prim:CONN_PRIM_DISC_IND);
	BSSAP.receive(tr_BSSMAP_Perform_Location_Response(tr_BSSMAP_IE_LocationEstimate(gad_ell_point_unc_circle)));

	/* The LCS was using an active A-interface conn. It should still remain active after this. */
	f_mo_l3_transceive();

	f_perform_clear();

	f_sleep(2.0);
	setverdict(pass);
}

/* With an active lchan, start BSSMAP Perform Location Request on A interface, starting BSSMAP-LE Perform Location
 * Request on Lb interface.  Without the SMLC doing a BSSLAP TA Request. */
private function f_tc_lcs_loc_req_for_active_ms(charstring id) runs on MSC_ConnHdlr {
	f_lcs_loc_req_for_active_ms(false);
}
testcase TC_lcs_loc_req_for_active_ms() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_lcs_loc_req_for_active_ms), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* With an active lchan, start BSSMAP Perform Location Request on A interface, starting BSSMAP-LE Perform Location
 * Request on Lb interface.  With the SMLC doing a BSSLAP TA Request. */
private function f_tc_lcs_loc_req_for_active_ms_ta_req(charstring id) runs on MSC_ConnHdlr {
	f_lcs_loc_req_for_active_ms(true);
}
testcase TC_lcs_loc_req_for_active_ms_ta_req() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_lcs_loc_req_for_active_ms_ta_req), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* Clear the A-interface conn only, without doing anything on Abis. Useful for LCS, for cases where there is only an A
 * conn without an active lchan. */
private function f_clear_A_conn() runs on MSC_ConnHdlr
{
	var BssmapCause cause := 0;
	BSSAP.send(ts_BSSMAP_ClearCommand(cause));
	BSSAP.receive(tr_BSSMAP_ClearComplete);
	BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ);

	timer no_more_bssap := 5.0;
	no_more_bssap.start;
	alt {
	[] no_more_bssap.timeout { break; }
	[] BSSAP.receive(tr_BSSAP_BSSMAP) {
		setverdict(fail, "Expected no more BSSAP after Clear Complete");
		mtc.stop;
	}
	}
	setverdict(pass);
}

/* Verify that the A-interface connection is still working, and then clear it, without doing anything on Abis.  Useful
 * for LCS, for cases where there is only an A conn without an active lchan. */
private function f_verify_active_A_conn_and_clear() runs on MSC_ConnHdlr
{
	f_logp(BSCVTY, "f_verify_active_A_conn_and_clear: test A link, then clear");

	/* When an lchan is active, we can send some L3 data from the BTS side and verify that it shows up on the other
	 * side towards the MSC. When there is no lchan, this is not possible. To probe whether the A-interface
	 * connection is still up, we need something that echos back on the A-interface. Another LCS request! */
	BSSAP.send(valueof(ts_BSSMAP_Perform_Location_Request(ts_BSSMAP_Imsi(g_pars.imsi),
					ts_CellId_CGI('262'H, '42'H, 23, 42))));
	BSSAP_LE.receive(tr_BSSMAP_LE_PerfLocReq(BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC_LOC, ?, ?));

	/* Right, the Perform Location Request showed up on Lb, now we can clear the A conn. */
	f_clear_A_conn();
	BSSAP_LE.receive(tr_BSSMAP_LE_PerfLocAbort(BSSMAP_LE_LCS_CAUSE_REQUEST_ABORTED));
	BSSAP_LE.receive(BSSAP_LE_Conn_Prim:CONN_PRIM_DISC_IND);
}

/* With *no* active lchan, start BSSMAP Perform Location Request on A interface, starting BSSMAP-LE Perform Location
 * Request on Lb interface. BSC will Page for the subscriber as soon as we (virtual SMLC) request the TA via BSSLAP.
 */
private function f_tc_lcs_loc_req_for_idle_ms(charstring id) runs on MSC_ConnHdlr {
	f_sleep(1.0);

	f_MscConnHdlr_init(g_pars.media_nr, "127.0.0.2", "127.0.0.3", FR_AMR);
	f_bssap_le_register_imsi(g_pars.imsi, omit);

	/* Register to receive the Paging Command */
	var RslChannelNr new_chan_nr := valueof(t_RslChanNr0(1, RSL_CHAN_NR_Bm_ACCH));
	g_chan_nr := new_chan_nr;
	f_rslem_register(0, g_chan_nr);

	BSSAP.send(ts_BSSAP_Conn_Req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc,
				     valueof(ts_BSSMAP_Perform_Location_Request(ts_BSSMAP_Imsi(g_pars.imsi),
										ts_CellId_CGI('001'H, '01'H, 1, 0)))));
	BSSAP.receive(tr_MSC_CONN_PRIM_CONF_IND);

	var PDU_BSSAP_LE plr;
	BSSAP_LE.receive(tr_BSSMAP_LE_PerfLocReq(BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC_LOC, ?, ?)) -> value(plr);

	/* SMLC wants to ask the TA from the BSC explicitly in a BSSLAP TA Request message */
	BSSAP_LE.send(ts_BSSMAP_LE_BSSLAP(ts_BSSLAP_TA_Req));

	/* OsmoBSC needs to Page */
	RSL.receive(tr_RSL_PAGING_CMD(tr_MI_IMSI(g_pars.imsi)));
	f_logp(BSCVTY, "got Paging Command");

	/* MS requests channel. Since the Paging was for LCS, the Paging Response does not trigger a Complete Layer 3 to
	 * the MSC, and releases the lchan directly. */
	f_perform_compl_l3(RSL, RSL_PROC, ts_PAG_RESP(ts_MI_IMSI_LV(g_pars.imsi)), do_clear := false, expect_bssmap_l3 := false);
	f_expect_lchan_rel(RSL, RSL_PROC);

	/* From the Paging Response, the TA is now known to the BSC, and it responds to the SMLC. */

	f_expect_bsslap(tr_BSSLAP_TA_Resp(?, ?));

	/* SMLC got the TA from the BSC, now responds with geo information data. */
	BSSAP_LE.send(ts_BSSMAP_LE_PerfLocResp(gad_ell_point_unc_circle, omit));
	BSSAP_LE.receive(BSSAP_LE_Conn_Prim:CONN_PRIM_DISC_IND);

	BSSAP.receive(tr_BSSMAP_Perform_Location_Response(tr_BSSMAP_IE_LocationEstimate(gad_ell_point_unc_circle)));

	/* The lchan is gone, the A-interface conn was created for the LCS only.
	 * Still it is clearly the MSC's job to decide whether to tear down the conn or not. */
	f_verify_active_A_conn_and_clear();

	f_sleep(2.0);
	setverdict(pass);
}
testcase TC_lcs_loc_req_for_idle_ms() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);

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

	vc_conn := f_start_handler(refers(f_tc_lcs_loc_req_for_idle_ms), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* With no active lchan, start BSSMAP Perform Location Request on A interface, but omit IMSI; expect failure response.
 */
private function f_tc_lcs_loc_req_no_subscriber(charstring id) runs on MSC_ConnHdlr {
	f_sleep(1.0);

	f_MscConnHdlr_init(g_pars.media_nr, "127.0.0.2", "127.0.0.3", FR_AMR);
	f_bssap_le_register_imsi(g_pars.imsi, omit);

	/* provoke an abort by omitting both IMSI and IMEI */
	BSSAP.send(ts_BSSAP_Conn_Req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc,
				valueof(ts_BSSMAP_Perform_Location_Request(omit,
					ts_CellId_CGI('262'H, '42'H, 23, 42)))));
	BSSAP.receive(tr_MSC_CONN_PRIM_CONF_IND);

	/* BSC tells MSC about failure */
	BSSAP.receive(tr_BSSMAP_Perform_Location_Response(
			locationEstimate := omit, positioningData := omit,
			lCS_Cause := tr_BSSMAP_LcsCause(BSSMAP_LCS_CAUSE_DATA_MISSING_IN_REQ)));

	/* There is no lchan. Still the MSC's job to decide whether to tear down the conn or not. */
	f_verify_active_A_conn_and_clear();

	f_sleep(2.0);
	setverdict(pass);
}
testcase TC_lcs_loc_req_no_subscriber() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);

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

	vc_conn := f_start_handler(refers(f_tc_lcs_loc_req_no_subscriber), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* With an active lchan, start a Perform Location Request on the A-interface, but virtual SMLC does not answer with
 * BSSMAP-LE Perform Location Response (before or after sending a BSSLAP TA Request) */
private function f_lcs_loc_req_for_active_ms_le_timeout(boolean do_ta) runs on MSC_ConnHdlr {
	f_sleep(1.0);

	f_establish_fully(omit, omit);
	f_bssap_le_register_imsi(g_pars.imsi, omit);

	BSSAP.send(valueof(ts_BSSMAP_Perform_Location_Request(ts_BSSMAP_Imsi(g_pars.imsi),
					ts_CellId_CGI('262'H, '42'H, 23, 42))));

	var PDU_BSSAP_LE plr;
	BSSAP_LE.receive(tr_BSSMAP_LE_PerfLocReq(BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC_LOC, ?, ?)) -> value(plr);

	if (do_ta) {
		/* SMLC wants to ask the TA from the BSC explicitly in a BSSLAP TA Request message */
		BSSAP_LE.send(ts_BSSMAP_LE_BSSLAP(ts_BSSLAP_TA_Req));
		f_expect_bsslap(tr_BSSLAP_TA_Resp(?, ?));
	}

	/* SMLC fails to respond, BSC runs into timeout */
	BSSAP_LE.receive(tr_BSSMAP_LE_PerfLocAbort(BSSMAP_LE_LCS_CAUSE_SYSTEM_FAILURE));
	BSSAP_LE.receive(BSSAP_LE_Conn_Prim:CONN_PRIM_DISC_IND);

	BSSAP.receive(tr_BSSMAP_Perform_Location_Response(
			locationEstimate := omit, positioningData := omit,
			lCS_Cause := tr_BSSMAP_LcsCause(BSSMAP_LCS_CAUSE_SYSTEM_FAILURE)));

	/* There is no lchan. Still the MSC's job to decide whether to tear down the conn or not. */
	f_verify_active_A_conn_and_clear();

	f_sleep(2.0);
	setverdict(pass);
}

/* With an active lchan, start a Perform Location Request on the A-interface, but virtual SMLC does not answer with
 * BSSMAP-LE Perform Location Response, without sending a BSSLAP TA Request. */
private function f_tc_lcs_loc_req_for_active_ms_le_timeout(charstring id) runs on MSC_ConnHdlr {
	f_lcs_loc_req_for_active_ms_le_timeout(false);
}

testcase TC_lcs_loc_req_for_active_ms_le_timeout() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_lcs_loc_req_for_active_ms_le_timeout), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* With an active lchan, start a Perform Location Request on the A-interface, but virtual SMLC does not answer with
 * BSSMAP-LE Perform Location Response, after sending a BSSLAP TA Request. */
private function f_tc_lcs_loc_req_for_active_ms_le_timeout2(charstring id) runs on MSC_ConnHdlr {
	f_lcs_loc_req_for_active_ms_le_timeout(true);
}

testcase TC_lcs_loc_req_for_active_ms_le_timeout2() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_lcs_loc_req_for_active_ms_le_timeout2), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* With *no* active lchan, start a Perform Location Request, expecting that the MS will be Paged. */
private function f_tc_lcs_loc_req_for_idle_ms_no_pag_resp(charstring id) runs on MSC_ConnHdlr {
	f_sleep(1.0);

	f_MscConnHdlr_init(g_pars.media_nr, "127.0.0.2", "127.0.0.3", FR_AMR);
	f_bssap_le_register_imsi(g_pars.imsi, omit);

	/* Register to receive the Paging Command */
	var RslChannelNr new_chan_nr := valueof(t_RslChanNr0(1, RSL_CHAN_NR_Bm_ACCH));
	g_chan_nr := new_chan_nr;
	f_rslem_register(0, g_chan_nr);

	BSSAP.send(ts_BSSAP_Conn_Req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc,
				     valueof(ts_BSSMAP_Perform_Location_Request(ts_BSSMAP_Imsi(g_pars.imsi),
										ts_CellId_CGI('001'H, '01'H, 1, 0)))));
	BSSAP.receive(tr_MSC_CONN_PRIM_CONF_IND);

	var PDU_BSSAP_LE plr;
	BSSAP_LE.receive(tr_BSSMAP_LE_PerfLocReq(BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC_LOC, ?, ?)) -> value(plr);

	/* SMLC wants to ask the TA from the BSC explicitly in a BSSLAP TA Request message */
	BSSAP_LE.send(ts_BSSMAP_LE_BSSLAP(ts_BSSLAP_TA_Req));

	/* OsmoBSC needs to Page */
	var PDU_BSSAP_LE rx_bsslap;
	alt {
	[] RSL.receive(tr_RSL_PAGING_CMD(tr_MI_IMSI(g_pars.imsi))) {
		f_logp(BSCVTY, "got Paging Command");
		repeat;
	}
	[] BSSAP_LE.receive(tr_BSSMAP_LE_ConnInfo(BSSMAP_LE_PROT_BSSLAP, ?)) -> value(rx_bsslap) {
		/* MS does not respond to Paging, TA Req runs into timeout. */
		f_match_bsslap(rx_bsslap, tr_BSSLAP_Abort(?));
	}
	}

	/* SMLC responds with failure */
	BSSAP_LE.send(ts_BSSMAP_LE_PerfLocResp(omit, BSSMAP_LE_LCS_CAUSE_REQUEST_ABORTED));
	BSSAP_LE.receive(BSSAP_LE_Conn_Prim:CONN_PRIM_DISC_IND);

	/* BSC tells MSC about failure */
	BSSAP.receive(tr_BSSMAP_Perform_Location_Response(
			locationEstimate := omit, positioningData := omit,
			lCS_Cause := tr_BSSMAP_LcsCause(BSSMAP_LCS_CAUSE_REQUEST_ABORTED)));

	/* There is no lchan. Still the MSC's job to decide whether to tear down the conn or not. */
	f_verify_active_A_conn_and_clear();

	f_sleep(2.0);
	setverdict(pass);
}
testcase TC_lcs_loc_req_for_idle_ms_no_pag_resp() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);

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

	vc_conn := f_start_handler(refers(f_tc_lcs_loc_req_for_idle_ms_no_pag_resp), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* During an ongoing Location Request, the MS sends a CM Service Request. Expect the same A-conn to be re-used / taken
 * over. */
private function f_tc_cm_service_during_lcs_loc_req(charstring id) runs on MSC_ConnHdlr {
	f_sleep(1.0);

	f_MscConnHdlr_init(g_pars.media_nr, "127.0.0.2", "127.0.0.3", FR_AMR);
	f_bssap_le_register_imsi(g_pars.imsi, omit);

	/* Register to receive the Paging Command */
	var RslChannelNr new_chan_nr := valueof(t_RslChanNr0(1, RSL_CHAN_NR_Bm_ACCH));
	g_chan_nr := new_chan_nr;
	f_rslem_register(0, g_chan_nr);

	BSSAP.send(ts_BSSAP_Conn_Req(g_pars.sccp_addr_bsc, g_pars.sccp_addr_msc,
				     valueof(ts_BSSMAP_Perform_Location_Request(ts_BSSMAP_Imsi(g_pars.imsi),
										ts_CellId_CGI('001'H, '01'H, 1, 0)))));
	BSSAP.receive(tr_MSC_CONN_PRIM_CONF_IND);

	var PDU_BSSAP_LE plr;
	BSSAP_LE.receive(tr_BSSMAP_LE_PerfLocReq(BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC_LOC, ?, ?)) -> value(plr);

	/* As the A-interface conn was established for LCS, the MS coincidentally decides to issue a CM Service Request
	 * and establish Layer 3. It should use the existing A-interface conn. */
	f_perform_compl_l3(RSL, RSL_PROC, valueof(ts_CM_SERV_REQ(CM_TYPE_MO_CALL, valueof(ts_MI_IMSI_LV(g_pars.imsi)))),
			do_clear := false, expect_bssmap_l3 := true);

	/* SMLC wants to ask the TA from the BSC explicitly in a BSSLAP TA Request message */
	BSSAP_LE.send(ts_BSSMAP_LE_BSSLAP(ts_BSSLAP_TA_Req));

	/* OsmoBSC already has an lchan, no need to Page, just returns the TA */
	f_expect_bsslap(tr_BSSLAP_TA_Resp(?, ?));

	/* SMLC got the TA from the BSC, now responds with geo information data. */
	BSSAP_LE.send(ts_BSSMAP_LE_PerfLocResp(gad_ell_point_unc_circle, omit));
	BSSAP_LE.receive(BSSAP_LE_Conn_Prim:CONN_PRIM_DISC_IND);
	BSSAP.receive(tr_BSSMAP_Perform_Location_Response(tr_BSSMAP_IE_LocationEstimate(gad_ell_point_unc_circle)));

	/* The lchan should still exist, it was from a CM Service Request. */
	f_mo_l3_transceive();

	f_perform_clear();

	f_sleep(2.0);
	setverdict(pass);
}
testcase TC_cm_service_during_lcs_loc_req() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);

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

	vc_conn := f_start_handler(refers(f_tc_cm_service_during_lcs_loc_req), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* During an ongoing Perform Location Request, do a Handover, an expect a BSSLAP Reset message from the BSC to indicate
 * the new lchan after handover. */
private function f_tc_ho_during_lcs_loc_req(charstring id) runs on MSC_ConnHdlr {
	f_sleep(1.0);

	f_establish_fully(omit, omit);
	f_bssap_le_register_imsi(g_pars.imsi, omit);

	BSSAP.send(valueof(ts_BSSMAP_Perform_Location_Request(ts_BSSMAP_Imsi(g_pars.imsi),
					ts_CellId_CGI('262'H, '42'H, 23, 42))));

	var PDU_BSSAP_LE plr;
	BSSAP_LE.receive(tr_BSSMAP_LE_PerfLocReq(BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC_LOC, ?, ?)) -> value(plr);

	/* SMLC ponders the Location Request, in the meantime the BSC decides to handover */
	f_bts_0_cfg(BSCVTY, {"neighbor bts 1"});

	var HandoverState hs := {
		rr_ho_cmpl_seen := false,
		handover_done := false,
		old_chan_nr := -,
		expect_target_tsc := c_BtsParams[1].tsc
	};

	/* temporarily suspend DChan processing on BTS1 to avoid race with RSLEM_register */
	f_rslem_suspend(RSL1_PROC);
	/* issue hand-over command on VTY */
	f_vty_handover(BSCVTY, 0, 0, g_chan_nr, 1);

	/* From the MGW perspective, a handover is is characterized by
	 * performing one MDCX operation with the MGW. So we expect to see
	 * one more MDCX during handover. */
	g_media.mgcp_conn[0].mdcx_seen_exp := g_media.mgcp_conn[0].crcx_seen_exp + 1;

	alt {
	[] as_handover(hs);
	}

	var PDU_BSSAP_LE rx_bsslap;

	interleave {
	/* Expect the BSC to inform the MSC about the handover */
	[] BSSAP.receive(tr_BSSMAP_HandoverPerformed);

	/* Expect the BSC to inform the SMLC about the handover */
	[] BSSAP_LE.receive(tr_BSSMAP_LE_ConnInfo(BSSMAP_LE_PROT_BSSLAP, ?)) -> value(rx_bsslap) {
		f_match_bsslap(rx_bsslap, tr_BSSLAP_Reset(BSSLAP_CAUSE_INTRA_BSS_HO));
	}
	}

	/* SMLC now responds with geo information data. */
	BSSAP_LE.send(ts_BSSMAP_LE_PerfLocResp(gad_ell_point_unc_circle, omit));
	BSSAP_LE.receive(BSSAP_LE_Conn_Prim:CONN_PRIM_DISC_IND);
	BSSAP.receive(tr_BSSMAP_Perform_Location_Response(tr_BSSMAP_IE_LocationEstimate(gad_ell_point_unc_circle)));

	/* lchan still active */
	f_mo_l3_transceive(RSL1);

	/* MSC decides it is done now. */
	f_perform_clear(RSL1, RSL1_PROC);

	f_sleep(2.0);
	setverdict(pass);
}
testcase TC_ho_during_lcs_loc_req() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(2, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_ho_during_lcs_loc_req), pars);
	vc_conn.done;
	f_shutdown_helper(ho := true);
}

private function f_tc_emerg_call_and_lcs_loc_req(charstring id) runs on MSC_ConnHdlr
{
	/* Make sure the CHAN RQD indicates an emergency call (0b101xxxxx). The difference is that osmo-bsc directly
	 * assigns a TCH lchan and establishing voice for the emergency call will use Mode Modify, not reassignment to
	 * another lchan. */
	g_pars.ra := f_rnd_ra_emerg();
	f_assignment_emerg_setup();
	f_assignment_emerg_setup_exp_bssap();

	/* Here would usually be a CC Call Proceeding from the MSC, but what does the BSC care about DTAP. */

	/* Do a Location Request in-between the CC call setup */
	f_bssap_le_register_imsi(g_pars.imsi, omit);
	BSSAP.send(valueof(ts_BSSMAP_Perform_Location_Request(ts_BSSMAP_Imsi(g_pars.imsi),
					ts_CellId_CGI('262'H, '42'H, 23, 42))));
	BSSAP_LE.receive(tr_BSSMAP_LE_PerfLocReq(BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC_LOC, ?, ?));
	/* SMLC got the TA from the BSC, now responds with geo information data. */
	BSSAP_LE.send(ts_BSSMAP_LE_PerfLocResp(gad_ell_point_unc_circle, omit));
	BSSAP_LE.receive(BSSAP_LE_Conn_Prim:CONN_PRIM_DISC_IND);
	BSSAP.receive(tr_BSSMAP_Perform_Location_Response(tr_BSSMAP_IE_LocationEstimate(gad_ell_point_unc_circle)));

	f_assignment_emerg_setup_voice();

	setverdict(pass);
	f_perform_clear();
}

testcase TC_emerg_call_and_lcs_loc_req() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);
	f_vty_allow_emerg_msc(true);
	f_vty_allow_emerg_bts(true, 0);
	vc_conn := f_start_handler(refers(f_tc_emerg_call_and_lcs_loc_req), pars);
	vc_conn.done;
	f_shutdown_helper();
}

private altstep no_bssmap_clear_req() runs on MSC_ConnHdlr {
	[] BSSAP.receive(tr_BSSMAP_ClearRequest) {
		setverdict(fail, "unexpected BSSMAP Clear Request");
		mtc.stop;
	}
}

private type enumerated RslRel {
	RSLREL_REL_IND,
	RSLREL_CONN_FAIL
};

private function f_emerg_call_and_lcs_loc_req_early_lchan_release(RslRel rsl_rel) runs on MSC_ConnHdlr
{
	g_pars.ra := f_rnd_ra_emerg();
	f_assignment_emerg_setup();
	f_assignment_emerg_setup_exp_bssap();

	/* Start a Location Request to locate the emergency */
	f_bssap_le_register_imsi(g_pars.imsi, omit);
	BSSAP.send(valueof(ts_BSSMAP_Perform_Location_Request(ts_BSSMAP_Imsi(g_pars.imsi),
					ts_CellId_CGI('262'H, '42'H, 23, 42))));
	BSSAP_LE.receive(tr_BSSMAP_LE_PerfLocReq(BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC_LOC, ?, ?));

	/* As long as this LCS is going on, the BSC should not send any Clear Request. Later on, it is up to the MSC to
	 * do a Clear Command when the Location Response arrives. */
	activate(no_bssmap_clear_req());

	/* the lchan gets interrupted while the Location Request has no response */
	select (rsl_rel) {
	case (RSLREL_REL_IND) {
			RSL.send(ts_RSL_REL_IND(g_chan_nr, valueof(ts_RslLinkID_DCCH(0))));
			f_expect_lchan_rel(RSL, RSL_PROC);
		}
	case (RSLREL_CONN_FAIL) {
			RSL.send(ts_RSL_CONN_FAIL_IND(g_chan_nr, RSL_ERR_RADIO_LINK_FAIL));
		}
	case else {
			setverdict(fail, "Unknown RslRel type");
			mtc.stop;
		}
	}

	/* Still expect the Location Response to find its way to the MSC. */
	BSSAP_LE.send(ts_BSSMAP_LE_PerfLocResp(gad_ell_point_unc_circle, omit));
	BSSAP_LE.receive(BSSAP_LE_Conn_Prim:CONN_PRIM_DISC_IND);
	BSSAP.receive(tr_BSSMAP_Perform_Location_Response(tr_BSSMAP_IE_LocationEstimate(gad_ell_point_unc_circle)));

	setverdict(pass);

	select (rsl_rel) {
	case (RSLREL_REL_IND) {
			f_perform_clear_no_lchan();
		}
	case (RSLREL_CONN_FAIL) {
			f_perform_clear();
		}
	}
}

private function f_tc_emerg_call_and_lcs_loc_req_early_lchan_rel_ind(charstring id) runs on MSC_ConnHdlr
{
	f_emerg_call_and_lcs_loc_req_early_lchan_release(RSLREL_REL_IND);
}

testcase TC_emerg_call_and_lcs_loc_req_early_lchan_rel_ind() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);
	f_vty_allow_emerg_msc(true);
	f_vty_allow_emerg_bts(true, 0);
	vc_conn := f_start_handler(refers(f_tc_emerg_call_and_lcs_loc_req_early_lchan_rel_ind), pars);
	vc_conn.done;
	f_shutdown_helper();
}

private function f_tc_emerg_call_and_lcs_loc_req_early_lchan_conn_fail(charstring id) runs on MSC_ConnHdlr
{
	f_emerg_call_and_lcs_loc_req_early_lchan_release(RSLREL_CONN_FAIL);
}

testcase TC_emerg_call_and_lcs_loc_req_early_lchan_conn_fail() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_sleep(1.0);
	f_vty_allow_emerg_msc(true);
	f_vty_allow_emerg_bts(true, 0);
	vc_conn := f_start_handler(refers(f_tc_emerg_call_and_lcs_loc_req_early_lchan_conn_fail), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* Ensure that Location Requests from MSC without SMLC configured lead to an
 * error towards the MSC (SYS#7714) */
private function f_tc_lcs_loc_req_without_smlc(charstring id) runs on MSC_ConnHdlr {
	f_sleep(1.0);

	f_establish_fully(omit, omit);
	f_bssap_le_register_imsi(g_pars.imsi, omit);

	BSSAP.send(valueof(ts_BSSMAP_Perform_Location_Request(ts_BSSMAP_Imsi(g_pars.imsi),
					ts_CellId_CGI('262'H, '42'H, 23, 42))));

	BSSAP.receive(tr_BSSMAP_Perform_Location_Response(
			locationEstimate := omit, positioningData := omit,
			lCS_Cause := tr_BSSMAP_LcsCause(BSSMAP_LCS_CAUSE_SYSTEM_FAILURE)));

	f_perform_clear();

	f_sleep(2.0);
	setverdict(pass);
}
testcase TC_lcs_loc_req_without_smlc() runs on test_CT {
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_init(1, true);
	f_vty_enable_smlc(false);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_tc_lcs_loc_req_without_smlc), pars);
	vc_conn.done;
	f_vty_enable_smlc(true);
	f_shutdown_helper();
}

/* Attempt Complete Layer 3 without any MSC available (OS#4832) */
private function f_tc_no_msc(charstring id) runs on MSC_ConnHdlr {
	f_MscConnHdlr_init(g_pars.media_nr, "127.0.0.2", "127.0.0.3", FR_AMR);

	/* Also disable attach for the single connected MSC */
	f_vty_msc_allow_attach(BSCVTY, { false });

	var octetstring l3_enc := enc_PDU_ML3_MS_NW(valueof(ts_LU_REQ(LU_Type_IMSI_Attach, valueof(ts_MI_IMSI_LV('001010000100001'H)), '00F110'O) ));
	f_chan_est(g_pars.ra, l3_enc, g_pars.link_id, g_pars.fn);

	/* No MSC is found, expecting a proper release on RSL */
	interleave {
	[] RSL.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, decmatch tr_RRM_RR_RELEASE)) {
			f_logp(BSCVTY, "Got RSL RR Release");
		}
	[] RSL.receive(tr_RSL_DEACT_SACCH(g_chan_nr)) {
			f_logp(BSCVTY, "Got RSL Deact SACCH");
		}
	[] RSL.receive(tr_RSL_RF_CHAN_REL(g_chan_nr)) {
			f_logp(BSCVTY, "Got RSL RF Chan Rel, sending Rel Ack");
			RSL.send(ts_RSL_RF_CHAN_REL_ACK(g_chan_nr));
			f_rslem_unregister(0, g_chan_nr);
		}
	}
	setverdict(pass);
}
testcase TC_no_msc() runs on test_CT {

	f_init(1, true);
	f_sleep(1.0);
	var MSC_ConnHdlr vc_conn;
	var TestHdlrParams pars := f_gen_test_hdlr_pars();

	f_ctrs_bsc_init(counternames_bsc_mscpool);

	vc_conn := f_start_handler(refers(f_tc_no_msc), pars);
	vc_conn.done;

	f_ctrs_bsc_add("mscpool:subscr:no_msc");
	f_ctrs_bsc_verify();
	f_shutdown_helper();
}

/* Dyn PDCH todo:
   * activate OSMO as TCH/F
   * activate OSMO as TCH/H
   * does the BSC-located PCU socket get the updated INFO?
   * what if no PCU is connected at the time?
   * is the info correct on delayed PCU (re)connect?
 */

private function f_TC_refuse_mode_modif_to_vamos(charstring id) runs on MSC_ConnHdlr {
	var PDU_BSSAP ass_cmd := f_gen_ass_req(g_pars.use_osmux_cn);
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();

	/* puzzle together the ASSIGNMENT REQ for given codec[s] */
	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_AoIP) {
		ass_cmd.pdu.bssmap.assignmentRequest.codecList := g_pars.ass_codec_list;
		exp_compl.pdu.bssmap.assignmentComplete.speechCodec.codecElements[0] :=
								g_pars.ass_codec_list.codecElements[0];
		if (isvalue(g_pars.expect_mr_s0_s7)) {
			exp_compl.pdu.bssmap.assignmentComplete.speechCodec.codecElements[0].s0_7 :=
								g_pars.expect_mr_s0_s7;
		}
	}
	ass_cmd.pdu.bssmap.assignmentRequest.channelType :=
				f_BSSMAP_chtype_from_codec(g_pars.ass_codec_list.codecElements[0]);
	log("expecting ASS COMPL like this: ", exp_compl);

	f_establish_fully(ass_cmd, exp_compl);

	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 1 sub-slot 0 modify vamos tsc 2 3");

	var RSL_Message rsl;

	timer T := 5.0;
	T.start;
	alt {
	[] RSL.receive(tr_RSL_DATA_REQ(g_chan_nr)) -> value rsl {
		var PDU_ML3_NW_MS l3 := dec_PDU_ML3_NW_MS(rsl.ies[2].body.l3_info.payload);
		log("Rx L3 from net: ", l3);
		if (ischosen(l3.msgs.rrm.channelModeModify)) {
			setverdict(fail, "Mode Modify to VAMOS succeeded even though BTS does not support VAMOS");
			mtc.stop;
		}
		}
	[] RSL.receive(tr_RSL_MODE_MODIFY_REQ(g_chan_nr, ?)) -> value rsl {
		setverdict(fail, "Mode Modify to VAMOS succeeded even though BTS does not support VAMOS");
		mtc.stop;
		}
	[] T.timeout {
		/* The BTS does not exhibit BTS_FEAT_VAMOS, so no VAMOS related Mode Modify should happen. */
		setverdict(pass);
		}
	}
	T.stop;

	f_perform_clear();
}

/* The BSC does *not* indicate BTS_FEAT_VAMOS; make sure that a channel Mode Modify to VAMOS mode is refused by
 * osmo-bsc. */
testcase TC_refuse_mode_modif_to_vamos() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));
	vc_conn := f_start_handler(refers(f_TC_refuse_mode_modif_to_vamos), pars);
	vc_conn.done;
	f_shutdown_helper();
}

/* The BSC does *not* indicate BTS_FEAT_VAMOS; make sure that a channel activation to VAMOS mode is refused by osmo-bsc.
 */
testcase TC_refuse_chan_act_to_vamos() runs on test_CT {
	f_init_vty();

	f_init(1, false);
	f_sleep(1.0);

	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot 1 sub-slot 0 activate-vamos fr");

	var ASP_RSL_Unitdata rx_rsl_ud;
	timer T := 5.0;

	T.start;
	alt {
	[] IPA_RSL[0][0].receive(tr_ASP_RSL_UD(?, IPAC_PROTO_RSL_TRX0)) -> value rx_rsl_ud {
		if (rx_rsl_ud.rsl.msg_type == RSL_MT_CHAN_ACTIV) {
			T.stop;
			setverdict(fail, "CHANnel ACTivate in VAMOS mode succeeded even though BTS does not support VAMOS");
			mtc.stop;
		}
		repeat;
		}
	[] T.timeout {
		/* The BTS does not exhibit BTS_FEAT_VAMOS, so no VAMOS related CHANnel ACTivate should happen. */
		setverdict(pass);
		}
	}
}

private function f_TC_reassignment_codec(charstring id) runs on MSC_ConnHdlr {
	/* First fully set up a speech lchan */
	f_assignment_codec(id, do_perform_clear := false);

	/* Trigger re-assignment to another lchan */
	var AssignmentState assignment_st := valueof(ts_AssignmentStateInit);

	/* Re-Assignment should tell the MGW endpoint the new lchan's RTP address and port, so expecting to see exactly
	 * one MDCX on MGCP. */
	g_media.mgcp_conn[0].mdcx_seen_exp := g_media.mgcp_conn[0].mdcx_seen_exp + 1;

	/* The new lchan will see all-new IPAC_CRCX and IPAC_MDCX messages telling the BTS the same RTP address and port
	 * as the old lchan used. */
	g_media.bts.ipa_crcx_seen := false;
	g_media.bts.ipa_mdcx_seen := false;

	/* Send different BTS side RTP port number for the new lchan */
	g_media.bts.bts.port_nr := 4223;

	f_rslem_register(0, valueof(ts_RslChanNr_Bm(2))); /* <-- FIXME: can we somehow infer the timeslot that will be used? */

	/* Trigger re-assignment. */
	f_vty_transceive(BSCVTY, "bts 0 trx 0 timeslot " & int2str(g_chan_nr.tn) & " sub-slot 0 assignment");

	timer T := 5.0;
	T.start;
	alt {
	[] as_assignment(assignment_st);
	[] as_Media();
	[] T.timeout {
			break;
		}
	}

	if (not assignment_st.assignment_done) {
		setverdict(fail, "Assignment did not complete");
		mtc.stop;
	}

	f_check_mgcp_expectations()
	setverdict(pass);

	f_sleep(2.0);
	log("show lchan summary: ", f_vty_transceive_ret(BSCVTY, "show lchan summary"));

	/* Instruct BSC to clear channel */
	var BssmapCause cause := 0;
	BSSAP.send(ts_BSSMAP_ClearCommand(cause));
	interleave {
	[] RSL.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, decmatch tr_RRM_RR_RELEASE)) {}
	[] RSL.receive(tr_RSL_DEACT_SACCH(g_chan_nr)) {}
	[] RSL.receive(tr_RSL_RF_CHAN_REL(g_chan_nr)) {
			RSL.send(ts_RSL_RF_CHAN_REL_ACK(g_chan_nr));
			f_rslem_unregister(0, g_chan_nr);
		}
	[] BSSAP.receive(tr_BSSMAP_ClearComplete) {
			BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ);
		}
	}
	f_expect_dlcx_conns();

	f_sleep(0.5);
}

testcase TC_reassignment_fr() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	f_ctrs_bsc_and_bts_handover_init();

	pars.ass_codec_list := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));
	vc_conn := f_start_handler(refers(f_TC_reassignment_codec), pars);
	vc_conn.done;

	/* from f_establish_fully() */
	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");
	/* from re-assignment */
	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper();
}

const charstring REEST_LOST_CONNECTION := "REEST_LOST_CONNECTION";
const charstring REEST_CLEAR := "REEST_CLEAR";
const charstring REEST_CLEAR_DONE := "REEST_CLEAR_DONE";

/* CM Re-Establishment, 3GPP TS 24.008 4.5.1.6.
 * The MS <-> BTS loses radio connection, MS shows up on second BTS and asks for CM Re-Establishment.
 * BSC should establish a separate A conn for the same MS, the original A conn is then cleared by
 * the MSC as the CM Re-Establishment is handled.
 *
 * MS      bts0       bts1     bsc    msc  test-component
 *  |<----->|<----------------->|<-0-->|   _1  Establish channel on bts 0
 *  |                                  |   _1  wait a bit, to settle down
 *  |<-x x--|                          |   _1  "lose connection"
 *  |                                  |   REEST_LOST_CONNECTION
 *  |----------------->|------->|--1-->|   _2  new A-conn: Chan Rqd, Imm Ass, Compl L3 with CM Re-Establishment Req
 *  |                                  |   REEST_CLEAR
 *  |                           |<-0---|   _1  Clear Command on first A-conn
 *  |                           |--0-->|   _1  Clear Complete
 *  |       |<----------------->|      |   _1  Release first channel
 *  |                                  |   REEST_CLEAR_DONE
 *  |<-----------------|<-------|<-1---|   _2  Chan Activ, Assignment Command
 *  |<-----------------|<-------|<-1---|   _2  Clear Command, Release
 *
 */
private function f_tc_cm_reestablishment_1(charstring id) runs on MSC_ConnHdlr {
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();

	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));

	f_establish_fully(ass_cmd, exp_compl);

	/* The original channel loses connection, MS attemts CM Re-Establishment on another cell, see
	 * f_tc_cm_reestablishment_2(). This established channel stays active until MSC sends a Clear Command. The time
	 * when exactly that happens is determined by f_tc_cm_reestablishment_2(). */
	f_sleep(2.0);
	COORD.send(REEST_LOST_CONNECTION);

	alt {
	[] COORD.receive(REEST_CLEAR);
	[] RSL.receive(tr_RSL_DEACT_SACCH(g_chan_nr)) {
			setverdict(fail, "Unexpected channel release");
			mtc.stop;
		}
	[] RSL.receive(tr_RSL_RF_CHAN_REL(g_chan_nr)) {
			setverdict(fail, "Unexpected channel release");
			mtc.stop;
		}
	}
	f_perform_clear()
	f_create_mgcp_delete_ep(g_media.mgcp_ep);
	COORD.send(REEST_CLEAR_DONE);
}

private function f_tc_cm_reestablishment_2(charstring id) runs on MSC_ConnHdlr {
	f_MscConnHdlr_init(g_pars.media_nr, host_bts, host_mgw_mgcp, FR_AMR);

	/* The MS lost the connection on the first channel, now establishes another one */
	COORD.receive(REEST_LOST_CONNECTION);

	var MobileIdentityLV mi := valueof(ts_MI_IMSI_LV(g_pars.imsi));
	var PDU_ML3_MS_NW l3_info := valueof(ts_CM_REESTABL_REQ(mi));
	var octetstring l3_enc := enc_PDU_ML3_MS_NW(l3_info);

	f_create_bssmap_exp(l3_enc);
	RSL_Emulation.f_chan_est(g_pars.ra, l3_enc, g_pars.link_id, g_pars.fn, rsl_pt := RSL1, rsl_proc_pt := RSL1_PROC);
	BSSAP.receive(tr_BSSMAP_ComplL3(l3_enc));

	/* MSC got the CM Re-Establishment request and first off clears the previous conn. */
	COORD.send(REEST_CLEAR);
	COORD.receive(REEST_CLEAR_DONE);

	f_sleep(2.0);

	/* Answer the CM Re-Establishment with an Assignment Command. */
	var template PDU_BSSAP expect_assignment_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));

	var AssignmentState st := valueof(ts_AssignmentStateInit);
	st.rtp_stream := true;
	st.is_assignment := true;

	var ExpectCriteria mgcpcrit := {
		connid := omit,
		endpoint := omit,
		transid := omit
	};
	f_create_mgcp_expect(mgcpcrit);

	f_rslem_dchan_queue_enable(RSL1_PROC);

	BSSAP.send(ass_cmd);

	var PDU_BSSAP bssap;

	alt {
	[] as_assignment(st, rsl_pt := RSL1, rsl_proc_pt := RSL1_PROC);
	[] as_Media_ipacc(RSL1, RSL2);
	[] as_Media_mgw();
	[st.assignment_done] BSSAP.receive(expect_assignment_compl) {
			break;
		}
	}

	f_sleep(3.0);

	f_logp(BSCVTY, "f_tc_cm_reestablishment_2 clearing");
	f_perform_clear(RSL1, RSL1_PROC);
}

testcase TC_cm_reestablishment() runs on test_CT {
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn1;

	var TestHdlrParams pars2 := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn2;
	pars2.imsi := pars1.imsi;
	pars2.media_nr := 2;
	pars2.expect_tsc := c_BtsParams[1].tsc;

	f_init(2, true, guard_timeout := 40.0);
	f_sleep(1.0);

	vc_conn1 := f_start_handler_create(pars1);
	vc_conn2 := f_start_handler_create(pars2);
	connect(vc_conn1:COORD, vc_conn2:COORD);
	f_start_handler_run(vc_conn1, refers(f_tc_cm_reestablishment_1), pars1);
	f_start_handler_run(vc_conn2, refers(f_tc_cm_reestablishment_2), pars2);
	vc_conn1.done;
	vc_conn2.done;

	f_shutdown_helper();
}

function f_exp_ipa_rx_nonfatal(template (present) RSL_Message t_rx,
			       boolean ignore_other_rx := true,
			       BtsTrxIdx idx := {0, 0},
			       float Tval := 2.0)
runs on test_CT return template (omit) RSL_Message {
	var ASP_RSL_Unitdata rx_rsl_ud;
	timer T := Tval;

	T.start;
	alt {
	[] IPA_RSL[idx.bts][idx.trx].receive(tr_ASP_RSL_UD(t_rx, ?)) -> value rx_rsl_ud {
		T.stop;
		}
	[ignore_other_rx] IPA_RSL[idx.bts][idx.trx].receive { repeat; }
	[not ignore_other_rx] IPA_RSL[idx.bts][idx.trx].receive {
			log("f_exp_ipa_rx_nonfatal(): Got different message than ", t_rx);
			T.stop;
			return omit;
		}
	[] T.timeout {
			return omit;
		}
	}
	return rx_rsl_ud.rsl;
}

private function f_vty_set_imm_ass(TELNETasp_PT pt, BtsNr bts_nr := 0, charstring imm_ass_setting := "post-chan-ack") {
	f_vty_enter_cfg_bts(pt, bts_nr);
	f_vty_transceive(pt, "immediate-assignment " & imm_ass_setting);
	f_vty_transceive(pt, "exit");
	f_vty_transceive(pt, "exit");
	f_vty_transceive(pt, "exit");
}

private function f_verify_imm_ass(RSL_Message imm_ass, template uint8_t ra := ?, template GsmFrameNumber fn := ?,
				  template (present) RslChannelNr chan_nr := ?,
				  template (present) uint12_t arfcn := ?,
				  template (present) uint3_t tsc := ?)
{
	var RSL_IE_Body full_imm_ass_info;
	if (not f_rsl_find_ie(imm_ass, RSL_IE_FULL_IMM_ASS_INFO, full_imm_ass_info)) {
		setverdict(fail, "RSL Full Immediate Assign Info IE is absent");
		mtc.stop;
	}

	var GsmRrMessage rr_imm_ass := dec_GsmRrMessage(full_imm_ass_info.full_imm_ass_info.payload);
	var template GsmRrMessage expect_imm_ass := tr_IMM_ASS(ra := ra,
							       fn := fn,
							       ch_desc := tr_ChanDescH0(chan_nr, arfcn, tsc),
							       page_mode := ?);
	if (not match(rr_imm_ass, expect_imm_ass)) {
		log("Error: expected ", expect_imm_ass, " got ", rr_imm_ass);
		setverdict(fail, "Failed to match Immediate Assignment");
		mtc.stop;
	}
}

testcase TC_imm_ass_post_chan_ack() runs on test_CT {
	var RSL_Message chan_act;
	var RSL_Message imm_ass;
	var octetstring l3_payload := gen_l3_valid_payload();

	f_init(1, false);
	f_sleep(1.0);

	/* (should be the default anyway, just to make things clear) */
	f_vty_set_imm_ass(BSCVTY, 0, "post-chan-ack");

	/* RA containing reason=LU */
	var GsmFrameNumber fn := 2342;
	var uint8_t ra := 2;
	f_ipa_tx(ts_RSL_CHAN_RQD(int2oct(ra, 1), fn));

	chan_act := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));

	/* First send the Chan Act ACK */
	var RslChannelNr chan_nr := chan_act.ies[0].body.chan_nr;
	var DchanTuple dt := {-, chan_nr, {0, 0}};
	var RSL_IE_Body chan_ident_ie;
	if (not f_rsl_find_ie(chan_act, RSL_IE_CHAN_IDENT, chan_ident_ie)) {
		setverdict(fail, "RSL Channel Identification IE is absent");
		mtc.stop;
	}

	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, fn + 10));

	/* Then expect the Immediate Assignment, after we ACKed the chan act */
	imm_ass := f_exp_ipa_rx(tr_RSL_IMM_ASSIGN(0));

	f_verify_imm_ass(imm_ass, ra, fn, chan_nr, chan_ident_ie.chan_ident.ch_desc.v.arfcn,
			 chan_ident_ie.chan_ident.ch_desc.v.tsc);

	/* Check that the lchan is working */
	f_ipa_tx(ts_RSL_EST_IND(chan_nr, valueof(ts_RslLinkID_DCCH(0)), l3_payload));

	var BSSAP_N_CONNECT_ind rx_c_ind;
	BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3_payload))) -> value rx_c_ind;
	dt.sccp_conn_id := rx_c_ind.connectionId;
	BSSAP.send(ts_BSSAP_CONNECT_res(rx_c_ind.connectionId));

	f_sleep(1.0);
	f_perform_clear_test_ct(dt);
	f_shutdown_helper();
}

testcase TC_imm_ass_pre_chan_ack() runs on test_CT {
	var RSL_Message chan_act;
	var RSL_Message imm_ass;
	var octetstring l3_payload := gen_l3_valid_payload();

	f_init(1, false);
	f_sleep(1.0);

	f_vty_set_imm_ass(BSCVTY, 0, "pre-chan-ack");

	/* RA containing reason=LU */
	var GsmFrameNumber fn := 2342;
	var uint8_t ra := 2;
	f_ipa_tx(ts_RSL_CHAN_RQD(int2oct(ra, 1), fn));

	chan_act := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));
	var RslChannelNr chan_nr := chan_act.ies[0].body.chan_nr;
	var DchanTuple dt := {-, chan_nr, {0, 0}};
	var RSL_IE_Body chan_ident_ie;
	if (not f_rsl_find_ie(chan_act, RSL_IE_CHAN_IDENT, chan_ident_ie)) {
		setverdict(fail, "RSL Channel Identification IE is absent");
		mtc.stop;
	}

	/* (set bts 0 cfg back to default) */
	f_vty_set_imm_ass(BSCVTY);

	/* *FIRST* expect the Immediate Assignment, before we ACK the chan act */
	imm_ass := f_exp_ipa_rx(tr_RSL_IMM_ASSIGN(0));
	f_verify_imm_ass(imm_ass, ra, fn, chan_nr, chan_ident_ie.chan_ident.ch_desc.v.arfcn,
			 chan_ident_ie.chan_ident.ch_desc.v.tsc);

	/* Only now send the Chan Act ACK */
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, fn+10));

	/* Check that the lchan is working */
	f_ipa_tx(ts_RSL_EST_IND(chan_nr, valueof(ts_RslLinkID_DCCH(0)), l3_payload));

	var BSSAP_N_CONNECT_ind rx_c_ind;
	BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3_payload))) -> value rx_c_ind;
	dt.sccp_conn_id := rx_c_ind.connectionId;
	BSSAP.send(ts_BSSAP_CONNECT_res(rx_c_ind.connectionId));

	f_sleep(1.0);
	f_perform_clear_test_ct(dt);
	f_shutdown_helper();
}

testcase TC_imm_ass_pre_ts_ack() runs on test_CT {
	var RSL_Message chan_act;
	var RSL_Message imm_ass;
	var octetstring l3_payload := gen_l3_valid_payload();

	f_init(1, false);
	f_sleep(1.0);

	f_vty_set_imm_ass(BSCVTY, 0, "pre-ts-ack");

	/* RA containing reason=LU */
	var GsmFrameNumber fn := 2342;
	var uint8_t ra := 2;
	f_ipa_tx(ts_RSL_CHAN_RQD(int2oct(ra, 1), fn));

	chan_act := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));
	var RslChannelNr chan_nr := chan_act.ies[0].body.chan_nr;
	var DchanTuple dt := {-, chan_nr, {0, 0}};
	var RSL_IE_Body chan_ident_ie;
	if (not f_rsl_find_ie(chan_act, RSL_IE_CHAN_IDENT, chan_ident_ie)) {
		setverdict(fail, "RSL Channel Identification IE is absent");
		mtc.stop;
	}

	/* (set bts 0 cfg back to default) */
	f_vty_set_imm_ass(BSCVTY);

	/* *FIRST* expect the Immediate Assignment, before we ACK the chan act */
	imm_ass := f_exp_ipa_rx(tr_RSL_IMM_ASSIGN(0));
	f_verify_imm_ass(imm_ass, ra, fn, chan_nr, chan_ident_ie.chan_ident.ch_desc.v.arfcn,
			 chan_ident_ie.chan_ident.ch_desc.v.tsc);

	/* Only now send the Chan Act ACK */
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, fn+10));

	/* Check that the lchan is working */
	f_ipa_tx(ts_RSL_EST_IND(chan_nr, valueof(ts_RslLinkID_DCCH(0)), l3_payload));

	var BSSAP_N_CONNECT_ind rx_c_ind;
	BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3_payload))) -> value rx_c_ind;
	dt.sccp_conn_id := rx_c_ind.connectionId;
	BSSAP.send(ts_BSSAP_CONNECT_res(rx_c_ind.connectionId));

	f_sleep(1.0);
	f_perform_clear_test_ct(dt);
	f_shutdown_helper();
}

testcase TC_imm_ass_pre_chan_ack_dyn_ts() runs on test_CT {
	/* change Timeslot 6 before f_init() starts RSL */
	f_init_vty();
	f_ts_set_chcomb(0, 0, 6, "TCH/F_TCH/H_PDCH");
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1, false);
	f_sleep(1.0);

	var octetstring l3_payload := gen_l3_valid_payload();
	var RslChannelNr chan_nr := valueof(t_RslChanNr_PDCH(6));
	/* The BSC will activate the dynamic PDCH by default, so confirm that */
	f_exp_ipa_rx(tr_RSL_CHAN_ACT_PDCH(chan_nr));
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, 2323));

	/* clean up timeslot 6 config, will only take effect when the OML drops the next time */
	f_ts_set_chcomb(0, 0, 6, "PDCH");

	/* block all static timeslots so that the dyn TS will be used */
	f_disable_all_tch_f();
	f_disable_all_tch_h();
	f_disable_all_sdcch();

	var RSL_Message chan_act;
	var RSL_Message imm_ass;

	f_vty_set_imm_ass(BSCVTY, 0, "pre-chan-ack");

	/* RA containing reason=LU */
	var GsmFrameNumber fn := 2342;
	var uint8_t ra := 2;
	f_ipa_tx(ts_RSL_CHAN_RQD(int2oct(ra, 1), fn));

	/* Expect the dyn TS to deactivate PDCH first */
	f_exp_ipa_rx(tr_RSL_RF_CHAN_REL(chan_nr));
	f_ipa_tx(ts_RSL_RF_CHAN_REL_ACK(chan_nr));

	/* Now activation as SDCCH8 */
	chan_nr := valueof(t_RslChanNr_SDCCH8(tn := 6, sub_slot := 0));
	var DchanTuple dt := {-, chan_nr, {0, 0}};

	chan_act := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));
	var RSL_IE_Body chan_ident_ie;
	if (not f_rsl_find_ie(chan_act, RSL_IE_CHAN_IDENT, chan_ident_ie)) {
		setverdict(fail, "RSL Channel Identification IE is absent");
		mtc.stop;
	}

	/* *FIRST* expect the Immediate Assignment, before we ACK the chan act */
	imm_ass := f_exp_ipa_rx(tr_RSL_IMM_ASSIGN(0));
	f_verify_imm_ass(imm_ass, ra, fn, chan_nr, chan_ident_ie.chan_ident.ch_desc.v.arfcn,
			 chan_ident_ie.chan_ident.ch_desc.v.tsc);

	/* Only now send the Chan Act ACK */
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, fn+10));

	/* Check that the lchan is working */
	f_ipa_tx(ts_RSL_EST_IND(chan_nr, valueof(ts_RslLinkID_DCCH(0)), l3_payload));

	var BSSAP_N_CONNECT_ind rx_c_ind;
	BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3_payload))) -> value rx_c_ind;
	dt.sccp_conn_id := rx_c_ind.connectionId;
	BSSAP.send(ts_BSSAP_CONNECT_res(rx_c_ind.connectionId));

	/* (set bts 0 cfg back to default) */
	f_vty_set_imm_ass(BSCVTY);

	f_sleep(1.0);
	f_perform_clear_test_ct(dt);
	f_shutdown_helper();
}

testcase TC_imm_ass_pre_ts_ack_dyn_ts() runs on test_CT {
	/* change Timeslot 6 before f_init() starts RSL */
	f_init_vty();
	f_ts_set_chcomb(0, 0, 6, "TCH/F_TCH/H_PDCH");
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1, false);
	f_sleep(1.0);

	var octetstring l3_payload := gen_l3_valid_payload();
	var RslChannelNr chan_nr := valueof(t_RslChanNr_PDCH(6));
	/* The BSC will activate the dynamic PDCH by default, so confirm that */
	f_exp_ipa_rx(tr_RSL_CHAN_ACT_PDCH(chan_nr));
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, 2323));

	/* clean up timeslot 6 config, will only take effect when the OML drops the next time */
	f_ts_set_chcomb(0, 0, 6, "PDCH");

	/* block all static timeslots so that the dyn TS will be used */
	f_disable_all_tch_f();
	f_disable_all_tch_h();
	f_disable_all_sdcch();

	var RSL_Message chan_act;
	var RSL_Message imm_ass;

	f_vty_set_imm_ass(BSCVTY, 0, "pre-ts-ack");

	/* RA containing reason=LU */
	var GsmFrameNumber fn := 2342;
	var uint8_t ra := 2;
	f_ipa_tx(ts_RSL_CHAN_RQD(int2oct(ra, 1), fn));

	/* Expect the dyn TS to deactivate PDCH first */
	f_exp_ipa_rx(tr_RSL_RF_CHAN_REL(chan_nr));

	/* And already the Immediate Assignment even before the PDCH Deact ACK */
	imm_ass := f_exp_ipa_rx(tr_RSL_IMM_ASSIGN(0));

	/* continue the Osmo style PDCH Deact (usual chan rel) */
	f_ipa_tx(ts_RSL_RF_CHAN_REL_ACK(chan_nr));

	/* Now activation as SDCCH8 */
	chan_nr := valueof(t_RslChanNr_SDCCH8(tn := 6, sub_slot := 0));
	var DchanTuple dt := {-, chan_nr, {0, 0}};

	chan_act := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV));
	var RSL_IE_Body chan_ident_ie;
	if (not f_rsl_find_ie(chan_act, RSL_IE_CHAN_IDENT, chan_ident_ie)) {
		setverdict(fail, "RSL Channel Identification IE is absent");
		mtc.stop;
	}
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, fn+10));

	f_verify_imm_ass(imm_ass, ra, fn, chan_nr, chan_ident_ie.chan_ident.ch_desc.v.arfcn,
			 chan_ident_ie.chan_ident.ch_desc.v.tsc);

	/* Check that the lchan is working */
	f_ipa_tx(ts_RSL_EST_IND(chan_nr, valueof(ts_RslLinkID_DCCH(0)), l3_payload));

	var BSSAP_N_CONNECT_ind rx_c_ind;
	BSSAP.receive(tr_BSSAP_CONNECT_ind(?, ?, tr_BSSMAP_ComplL3(l3_payload))) -> value rx_c_ind;
	dt.sccp_conn_id := rx_c_ind.connectionId;
	BSSAP.send(ts_BSSAP_CONNECT_res(rx_c_ind.connectionId));

	/* (set bts 0 cfg back to default) */
	f_vty_set_imm_ass(BSCVTY);

	f_sleep(1.0);
	f_perform_clear_test_ct(dt);
	f_shutdown_helper();
}

/* GET and SET the bts.N.trx.M.rf_locked CTRL variable */
testcase TC_ctrl_trx_rf_locked() runs on test_CT {
	var MSC_ConnHdlr vc_conn;

	f_init(nr_bts := 2, handler_mode := true, nr_msc := 1);
	f_sleep(1.0);

	/* BTS 0, 1, 2 are OML unlocked, only BTS 0, 1 are actually connected to RSL. */
	f_ctrl_get_exp(IPA_CTRL, "rf_states",
		       "0,0,operational,unlocked,on,rsl-up;" &
		       "1,0,operational,unlocked,on,rsl-up;" &
		       "2,0,operational,unlocked,on,rsl-down;" &
		       "2,1,operational,unlocked,on,rsl-down;" &
		       "2,2,operational,unlocked,on,rsl-down;" &
		       "2,3,operational,unlocked,on,rsl-down;" &
		       "3,0,inoperational,locked,on,rsl-down;");

	f_logp(BSCVTY, "TC_ctrl_trx_rf_locked: lock BTS 1 TRX 0");
	f_ctrl_set(IPA_CTRL, "bts.1.trx.0.rf_locked", "1");
	/* give it a moment to settle the FSM status */
	f_sleep(1.0);

	/* Now BTS 1 TRX 0 should reflect "locked". Note the RF policy stays "on", because this is still handled
	 * globally in osmo-bsc. Probably after sending "rf_locked 1" for a TRX, that TRX should reflect an RF policy
	 * of "off"? But that's for a future patch if at all. */
	f_ctrl_get_exp(IPA_CTRL, "rf_states",
		       "0,0,operational,unlocked,on,rsl-up;" &
		       "1,0,operational,locked,on,rsl-up;" &
		       "2,0,operational,unlocked,on,rsl-down;" &
		       "2,1,operational,unlocked,on,rsl-down;" &
		       "2,2,operational,unlocked,on,rsl-down;" &
		       "2,3,operational,unlocked,on,rsl-down;" &
		       "3,0,inoperational,locked,on,rsl-down;");

	f_logp(BSCVTY, "TC_ctrl_trx_rf_locked: lock the already locked TRX, nothing should change");
	f_ctrl_set(IPA_CTRL, "bts.1.trx.0.rf_locked", "1");
	f_sleep(1.0);
	f_ctrl_get_exp(IPA_CTRL, "rf_states",
		       "0,0,operational,unlocked,on,rsl-up;" &
		       "1,0,operational,locked,on,rsl-up;" &
		       "2,0,operational,unlocked,on,rsl-down;" &
		       "2,1,operational,unlocked,on,rsl-down;" &
		       "2,2,operational,unlocked,on,rsl-down;" &
		       "2,3,operational,unlocked,on,rsl-down;" &
		       "3,0,inoperational,locked,on,rsl-down;");

	f_logp(BSCVTY, "TC_ctrl_trx_rf_locked: unlock BTS 1 TRX 0");
	f_ctrl_set(IPA_CTRL, "bts.1.trx.0.rf_locked", "0");
	f_sleep(1.0);
	f_ctrl_get_exp(IPA_CTRL, "rf_states",
		       "0,0,operational,unlocked,on,rsl-up;" &
		       "1,0,operational,unlocked,on,rsl-up;" &
		       "2,0,operational,unlocked,on,rsl-down;" &
		       "2,1,operational,unlocked,on,rsl-down;" &
		       "2,2,operational,unlocked,on,rsl-down;" &
		       "2,3,operational,unlocked,on,rsl-down;" &
		       "3,0,inoperational,locked,on,rsl-down;");

	f_logp(BSCVTY, "TC_ctrl_trx_rf_locked: unlock an already unlocked TRX, nothing should change");
	f_ctrl_set(IPA_CTRL, "bts.0.trx.0.rf_locked", "0");
	f_sleep(1.0);
	f_ctrl_get_exp(IPA_CTRL, "rf_states",
		       "0,0,operational,unlocked,on,rsl-up;" &
		       "1,0,operational,unlocked,on,rsl-up;" &
		       "2,0,operational,unlocked,on,rsl-down;" &
		       "2,1,operational,unlocked,on,rsl-down;" &
		       "2,2,operational,unlocked,on,rsl-down;" &
		       "2,3,operational,unlocked,on,rsl-down;" &
		       "3,0,inoperational,locked,on,rsl-down;");

	f_logp(BSCVTY, "TC_ctrl_trx_rf_locked: unlock an inoperational TRX");
	f_ctrl_set(IPA_CTRL, "bts.3.trx.0.rf_locked", "0");
	f_sleep(1.0);
	f_ctrl_get_exp(IPA_CTRL, "rf_states",
		       "0,0,operational,unlocked,on,rsl-up;" &
		       "1,0,operational,unlocked,on,rsl-up;" &
		       "2,0,operational,unlocked,on,rsl-down;" &
		       "2,1,operational,unlocked,on,rsl-down;" &
		       "2,2,operational,unlocked,on,rsl-down;" &
		       "2,3,operational,unlocked,on,rsl-down;" &
		       "3,0,inoperational,locked,on,rsl-down;");

	f_shutdown_helper();
}

const CounterNameVals counternames_cm_serv_rej := {
	{ "cm_serv_rej", 0 },
	{ "cm_serv_rej:imsi_unknown_in_hlr", 0 },
	{ "cm_serv_rej:illegal_ms", 0 },
	{ "cm_serv_rej:imsi_unknown_in_vlr", 0 },
	{ "cm_serv_rej:imei_not_accepted", 0 },
	{ "cm_serv_rej:illegal_me", 0 },
	{ "cm_serv_rej:plmn_not_allowed", 0 },
	{ "cm_serv_rej:loc_not_allowed", 0 },
	{ "cm_serv_rej:roaming_not_allowed", 0 },
	{ "cm_serv_rej:network_failure", 0 },
	{ "cm_serv_rej:synch_failure", 0 },
	{ "cm_serv_rej:congestion", 0 },
	{ "cm_serv_rej:srv_opt_not_supported", 0 },
	{ "cm_serv_rej:rqd_srv_opt_not_supported", 0 },
	{ "cm_serv_rej:srv_opt_tmp_out_of_order", 0 },
	{ "cm_serv_rej:call_can_not_be_identified", 0 },
	{ "cm_serv_rej:incorrect_message", 0 },
	{ "cm_serv_rej:invalid_mandantory_inf", 0 },
	{ "cm_serv_rej:msg_type_not_implemented", 0 },
	{ "cm_serv_rej:msg_type_not_compatible", 0 },
	{ "cm_serv_rej:inf_eleme_not_implemented", 0 },
	{ "cm_serv_rej:condtional_ie_error", 0 },
	{ "cm_serv_rej:msg_not_compatible", 0 },
	{ "cm_serv_rej:protocol_error", 0 },
	{ "cm_serv_rej:retry_in_new_cell", 0 }
};

private function f_TC_cm_serv_rej(charstring id) runs on MSC_ConnHdlr
{
	f_create_chan_and_exp();
	/* we should now have a COMPL_L3 at the MSC */
	BSSAP.send(ts_PDU_DTAP_MT(ts_CM_SERV_REJ('02'O), '00'O));
	RSL.receive(tr_RSL_DATA_REQ(g_chan_nr, ?, decmatch tr_CM_SERV_REJ));
	f_perform_clear();
	f_sleep(1.0);
}
testcase TC_cm_serv_rej() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	f_init(1, true);
	f_sleep(1.0);

	f_ctrs_bts_init(1, counternames_cm_serv_rej);

	vc_conn := f_start_handler(refers(f_TC_cm_serv_rej), pars);
	vc_conn.done;

	f_ctrs_bts_add(0, "cm_serv_rej", 1);
	f_ctrs_bts_add(0, "cm_serv_rej:imsi_unknown_in_hlr", 1);
	f_ctrs_bts_verify();

	f_sleep(1.0);
	f_shutdown_helper();
}

/* Reproduce a segfault happening when the SDCCH (primary) lchan is lost in-between a TCH Channel Activ and its Channel
 * Activ Ack (SYS#5627). */
private function f_TC_lost_sdcch_during_assignment(charstring id) runs on MSC_ConnHdlr {
	var PDU_BSSAP ass_cmd := f_gen_ass_req();

	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));

	var BSSMAP_FIELD_CodecType codecType;
	codecType := valueof(ass_cmd.pdu.bssmap.assignmentRequest.codecList.codecElements[0].codecType);

	f_MscConnHdlr_init(g_pars.media_nr, host_bts, host_mgw_mgcp, codecType);

	/* First establish a signalling lchan */
	f_create_chan_and_exp();
	f_rslem_dchan_queue_enable();

	/* we should now have a COMPL_L3 at the MSC */

	f_create_mgcp_expect(ExpectCriteria:{omit,omit,omit});
	activate(as_Media_mgw());

	var RslChannelNr chan_nr := { u := { ch0 := RSL_CHAN_NR_Bm_ACCH }, tn := 1 };
	f_rslem_register(0, chan_nr);

	f_rslem_set_auto_chan_act_ack(RSL_PROC, false);
	BSSAP.send(ass_cmd);


	/* Wait for the Channel Activ for the TCH channel */
	var ASP_RSL_Unitdata rx_rsl_ud;
	RSL.receive(tr_ASP_RSL_UD(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV), sid := ?)) -> value rx_rsl_ud;

	/* make the original SDCCH disappear */
	RSL.send(ts_RSL_REL_IND(g_chan_nr, valueof(ts_RslLinkID_DCCH(0))));

	/* and ACK the TCH channel activation. This caused a segfault up to OsmoBSC 1.7.0 (SYS#5627) */
	RSL.send(ts_ASP_RSL_UD(ts_RSL_CHAN_ACT_ACK(chan_nr, 23), rx_rsl_ud.streamId));

	interleave {
	[] BSSAP.receive(tr_BSSMAP_AssignmentFail);
	[] BSSAP.receive(tr_BSSMAP_ClearRequest);
	}

	BSSAP.send(ts_BSSMAP_ClearCommand(0));
	BSSAP.receive(tr_BSSMAP_ClearComplete);
	BSSAP.send(ts_MSC_CONN_PRIM_DISC_REQ);

	var MgcpCommand mgcp;
	var MGCP_RecvFrom mrf;
	var template MgcpMessage msg_dlcx := { command := tr_DLCX };
	alt {
	[g_pars.aoip] MGCP.receive(tr_DLCX) -> value mgcp {
		MGCP.send(ts_DLCX_ACK2(mgcp.line.trans_id));
		}
	[not g_pars.aoip] MGCP_MULTI.receive(tr_MGCP_RecvFrom_any(msg_dlcx)) -> value mrf {
		MGCP_MULTI.send(t_MGCP_SendToMrf(mrf, MgcpMessage:{
			response := ts_DLCX_ACK2(mrf.msg.command.line.trans_id)
		}));
		}
	}

	f_sleep(0.5);
}
testcase TC_lost_sdcch_during_assignment() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;

	pars.fail_on_dlcx := false;

	f_init(1, true);
	f_sleep(1.0);

	vc_conn := f_start_handler(refers(f_TC_lost_sdcch_during_assignment), pars);
	vc_conn.done;

	f_shutdown_helper();
}

const CounterNameVals counternames_bsc_bts_all_available_allocated := {
	{ "all_allocated:sdcch", 0 },
	{ "all_allocated:static_sdcch", 0 },
	{ "all_allocated:tch", 0 },
	{ "all_allocated:static_tch", 0 }
}

private function f_all_allocated_expect_counter_change(charstring_list expect_changed) runs on test_CT
{
	/* Make sure counters settle first */
	f_sleep(1.0);

	/* Take a baseline of counters */
	f_ctrs_bsc_and_bts_init(1, counternames_bsc_bts_all_available_allocated);

	/* Elapse some time so that we see changes in counters, hopefully where expected */
	f_sleep(2.0);

	/* Get new counters */
	var charstring_list all_changed := {};
	all_changed := all_changed & f_counter_name_vals_get_changed_n(IPA_CTRL, "bsc", g_ctr_bsc);
	all_changed := all_changed & f_counter_name_vals_get_changed_n(IPA_CTRL, "bts", g_ctr_bts);

	/* Compare with expectations */
	var charstring_list all_expect_changed := {};
	for (var integer i := 0; i < lengthof(expect_changed); i := i + 1) {
		all_expect_changed := all_expect_changed & { "bsc.0." & expect_changed[i], "bts.0." & expect_changed[i] };
	}
	f_counter_name_vals_expect_changed_list(all_changed, all_expect_changed);
}

testcase TC_ratectr_all_available_allocated() runs on test_CT {
	var ASP_RSL_Unitdata rsl_ud;
	var integer i;
	var integer chreq_total, chreq_nochan;

	f_init(1, guard_timeout := 60.0);
	f_sleep(1.0);

	/* Exhaust all dedicated SDCCH lchans.
	/* GSM 44.018 Table 9.1.8.2:
	 * RA = '13'O -> Establishment cause = 0001xxxx (MS dual rate capable and asks for "SDCCH").
	 */
	var DchanTuples chan_cleanup := {};
	for (i := 0; i < NUM_SDCCH_PER_BTS; i := i+1) {
		chan_cleanup := chan_cleanup & { f_est_dchan('13'O, NUM_SDCCH_PER_BTS + i, gen_l3_valid_payload()) };
	}

	/* Since only bts 0 is connected, expecting all_allocated to become true for both bts 0 and the "global" bsc
	 * level.
	 * All SDCCH are now occupied. */
	f_all_allocated_expect_counter_change({"all_allocated:sdcch", "all_allocated:static_sdcch"});

	/* Also fill up all remaining (TCH) channels */
	for (i := 0; i < NUM_TCHF_PER_BTS + NUM_TCHH_PER_BTS; i := i+1) {
		chan_cleanup := chan_cleanup & { f_est_dchan('33'O, NUM_SDCCH_PER_BTS + i, gen_l3_valid_payload()) };
	}

	/* All TCH are now also occupied */
	f_all_allocated_expect_counter_change({"all_allocated:sdcch", "all_allocated:static_sdcch",
			"all_allocated:tch", "all_allocated:static_tch"});

	/* Clean up SDCCH lchans */
	for (i := 0; i < lengthof(chan_cleanup); i := i + 1) {
		f_perform_clear_test_ct(chan_cleanup[i]);
	}

	f_shutdown_helper();
}

testcase TC_ratectr_all_available_allocated_dyn() runs on test_CT {
	var ASP_RSL_Unitdata rsl_ud;
	var integer i;
	var integer chreq_total, chreq_nochan;

	f_init_vty();
	f_ts_set_chcomb(0, 0, 2, "TCH/F_TCH/H_PDCH");
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");
	/* Now we have 3 TCH/F, 1 OSMO_DYN, 1 TCH/H */

	f_init(1, guard_timeout := 60.0);
	f_sleep(1.0);

	/* The dyn TS wants to activate PDCH mode, ACK that. */
	var RslChannelNr chan_nr;
	chan_nr := valueof(t_RslChanNr_PDCH(2));
	f_exp_ipa_rx(tr_RSL_CHAN_ACT_PDCH(chan_nr));
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, 2342));

	/* Exhaust all dedicated SDCCH lchans.
	/* GSM 44.018 Table 9.1.8.2:
	 * RA = '13'O -> Establishment cause = 0001xxxx (MS dual rate capable and asks for "SDCCH").
	 */
	var DchanTuples chan_cleanup := {};
	for (i := 0; i < NUM_SDCCH_PER_BTS; i := i+1) {
		chan_cleanup := chan_cleanup & { f_est_dchan('13'O, NUM_SDCCH_PER_BTS + i, gen_l3_valid_payload()) };
	}

	/* The static SDCCH should now be occupied, while still 3x8 dynamic SDCCH potentially remain. So only
	 * all_allocated:static_sdcch is counted, all_allocated:sdcch remains zero. */
	f_all_allocated_expect_counter_change({"all_allocated:static_sdcch"});

	/* Request more SDCCH, hence convert the first dyn TS to SDCCH8.
	 * Will release them later, so remember all the DchanTuples. */
	var DchanTuples dyn_sddch := {};
	dyn_sddch := dyn_sddch & { f_est_dchan_dyn('33'O, NUM_SDCCH_PER_BTS + i, gen_l3_valid_payload()) };

	/* Also occupy the seven other SDCCH of the dyn TS */
	for (i := 0; i < 7; i := i+1) {
		dyn_sddch := dyn_sddch & { f_est_dchan('33'O, NUM_SDCCH_PER_BTS + i, gen_l3_valid_payload()) };
	}

	/* Now all dynamic SDCCH are also occupied, so for the first time all_allocated:sdcch will trigger... */
	f_all_allocated_expect_counter_change({"all_allocated:sdcch", "all_allocated:static_sdcch"});

	/* occupy the remaining TCH, three TCH/F and two TCH/H lchans */
	for (i := 0; i < 5; i := i+1) {
		chan_cleanup := chan_cleanup & { f_est_dchan('33'O, NUM_SDCCH_PER_BTS + i, gen_l3_valid_payload()) };
	}

	/* All TCH lchans are now also occupied, both static and dynamic */
	f_all_allocated_expect_counter_change({"all_allocated:sdcch", "all_allocated:static_sdcch",
			"all_allocated:tch", "all_allocated:static_tch"});

	/* Starting to release the dyn TS: as soon as the first SDCCH gets released, all_allocated:sdcch stops
	 * incrementing. */
	var BssmapCause cause := 0;
	var DchanTuple dt := dyn_sddch[0];
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommand(cause)));
	f_exp_chan_rel_and_clear(dt);

	/* one dyn TS SDCCH is free again, so only the static_sdcch should increment. For tch, both static and dynamic
	 * count as occupied, so those still both increment. */
	f_all_allocated_expect_counter_change({"all_allocated:static_sdcch",
			"all_allocated:tch", "all_allocated:static_tch"});

	/* Release the remaining SDCCH of the dyn TS, so it becomes available as TCH again */
	for (i := 1; i < lengthof(dyn_sddch); i := i+1) {
		dt := dyn_sddch[i];
		BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ts_BSSMAP_ClearCommand(cause)));
		f_exp_chan_rel_and_clear(dt);
	}

	/* All SDCCH on the dyn TS are released, the dyn TS wants to activate PDCH again */
	chan_nr := valueof(t_RslChanNr_PDCH(2));
	f_exp_ipa_rx(tr_RSL_CHAN_ACT_PDCH(chan_nr));
	f_ipa_tx(ts_RSL_CHAN_ACT_ACK(chan_nr, 2342));

	/* Now all channels are occupied except the dyn TS, so expecting only the static counters to increment */
	f_all_allocated_expect_counter_change({"all_allocated:static_sdcch", "all_allocated:static_tch"});

	/* Clean up SDCCH lchans */
	for (i := 0; i < lengthof(chan_cleanup); i := i + 1) {
		f_perform_clear_test_ct(chan_cleanup[i]);
	}

	/* clean up config */
	f_ts_reset_chcomb(0);

	f_shutdown_helper();
}

private function f_TC_chan_alloc_algo(DchanTuple dt, BtsTrxIdx idx)
runs on test_CT {
	/* MSC sends an Assignment Request */
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));
	BSSAP.send(ts_BSSAP_DATA_req(dt.sccp_conn_id, ass_cmd));

	/* Expect the BSC to allocate a TCH channel on the given BTS/TRX */
	var RSL_Message rsl_chan_act := f_exp_ipa_rx(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV), idx);
	/* Send CHAN ACT NACK, so that the requested TCH channel becomes BORKEN */
	f_ipa_tx(ts_RSL_CHAN_ACT_NACK(rsl_chan_act.ies[0].body.chan_nr, RSL_ERR_EQUIPMENT_FAIL), idx);
	/* Expect to receive an Assignment Failure */
	BSSAP.receive(tr_BSSAP_DATA_ind(dt.sccp_conn_id, tr_BSSMAP_AssignmentFail));
}

testcase TC_chan_alloc_algo_ascending() runs on test_CT {
	/* We need to access BTS2, which has 4 TRXs */
	f_init(nr_bts := 3);

	/* HACK: work around "Couldn't find Expect for CRCX" */
	vc_MGCP[0].stop;

	f_vty_enter_cfg_bts(BSCVTY, 2);
	f_vty_transceive(BSCVTY, "channel allocator ascending");
	f_vty_transceive(BSCVTY, "end");

	/* Expect the BSC to allocate 4 x TCH/F channels on BTS2/TRX0 */
	for (var integer i := 0; i < 4; i := i + 1) {
		var DchanTuple dt := f_est_dchan('23'O, 23, gen_l3_valid_payload(), {2, 0});
		f_TC_chan_alloc_algo(dt, {2, 0});
		f_perform_clear_test_ct(dt);
	}

	/* At this point all 4 x TCH/F channels are BORKEN, but they will be
	 * resurrected upon the A-bis/OML link re-establishment. */
	f_shutdown_helper();
}

testcase TC_chan_alloc_algo_descending() runs on test_CT {
	/* We need to access BTS2, which has 4 TRXs */
	f_init(nr_bts := 3);

	/* HACK: work around "Couldn't find Expect for CRCX" */
	vc_MGCP[0].stop;

	f_vty_enter_cfg_bts(BSCVTY, 2);
	f_vty_transceive(BSCVTY, "channel allocator descending");
	f_vty_transceive(BSCVTY, "end");

	/* Expect the BSC to allocate 5 x TCH/F channels on BTS2/TRX3 */
	for (var integer i := 0; i < 5; i := i + 1) {
		var DchanTuple dt := f_est_dchan('23'O, 23, gen_l3_valid_payload(), {2, 0});
		f_TC_chan_alloc_algo(dt, {2, 3});
		f_perform_clear_test_ct(dt);
	}

	/* At this point all 5 x TCH/F channels are BORKEN, but they will be
	 * resurrected upon the A-bis/OML link re-establishment. */
	f_shutdown_helper();
}

testcase TC_chan_alloc_algo_ass_dynamic() runs on test_CT {
	const BtsTrxIdx TRX0 := {2, 0};
	const BtsTrxIdx TRX3 := {2, 3};

	/* We need to access BTS2, which has 4 TRXs */
	f_init(nr_bts := 3);

	/* HACK: work around "Couldn't find Expect for CRCX" */
	vc_MGCP[0].stop;

	f_vty_enter_cfg_bts(BSCVTY, 2);
	f_vty_transceive(BSCVTY, "channel allocator mode assignment dynamic");
	f_vty_transceive(BSCVTY, "channel allocator dynamic-param ul-rxlev thresh 50 avg-num 2");
	f_vty_transceive(BSCVTY, "channel allocator dynamic-param c0-chan-load thresh 0");
	f_vty_transceive(BSCVTY, "end");

	var DchanTuple dt;

	f_logp(BSCVTY, "Case a) Unknown Uplink RxLev, fall-back to ascending");
	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload(), TRX0);
	f_TC_chan_alloc_algo(dt, TRX0);
	f_perform_clear_test_ct(dt);

	f_logp(BSCVTY, "Case b) Not enough RxLev samples, use ascending");
	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload(), TRX0);
	f_ipa_tx(ts_RSL_MEAS_RES_EMPTY(dt.rsl_chan_nr, 0,
				       ts_RSL_IE_UplinkMeas(30, 0),
				       ts_RSL_IE_BS_Power(0)), TRX0);
	f_sleep(0.3); /* give the IUT some time to process sent MRs */
	f_TC_chan_alloc_algo(dt, TRX0);
	f_perform_clear_test_ct(dt);

	f_logp(BSCVTY, "Case c) Uplink RxLev below the threshold, use ascending");
	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload(), TRX0);
	f_ipa_tx(ts_RSL_MEAS_RES_EMPTY(dt.rsl_chan_nr, 0,
				       ts_RSL_IE_UplinkMeas(45, 0),
				       ts_RSL_IE_BS_Power(0)), TRX0);
	f_ipa_tx(ts_RSL_MEAS_RES_EMPTY(dt.rsl_chan_nr, 1,
				       ts_RSL_IE_UplinkMeas(48, 0),
				       ts_RSL_IE_BS_Power(0)), TRX0);
	f_sleep(0.3); /* give the IUT some time to process sent MRs */
	f_TC_chan_alloc_algo(dt, TRX0);
	f_perform_clear_test_ct(dt);

	f_logp(BSCVTY, "Case d) Uplink RxLev above the threshold, use descending");
	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload(), TRX0);
	f_ipa_tx(ts_RSL_MEAS_RES_EMPTY(dt.rsl_chan_nr, 0,
				       ts_RSL_IE_UplinkMeas(50, 0),
				       ts_RSL_IE_BS_Power(0)), TRX0);
	f_ipa_tx(ts_RSL_MEAS_RES_EMPTY(dt.rsl_chan_nr, 1,
				       ts_RSL_IE_UplinkMeas(58, 0),
				       ts_RSL_IE_BS_Power(0)), TRX0);
	f_sleep(0.3); /* give the IUT some time to process sent MRs */
	f_TC_chan_alloc_algo(dt, TRX3);
	f_perform_clear_test_ct(dt);

	f_vty_enter_cfg_bts(BSCVTY, 2);
	f_vty_transceive(BSCVTY, "channel allocator dynamic-param c0-chan-load thresh 90");
	f_vty_transceive(BSCVTY, "end");

	f_logp(BSCVTY, "Case e) Uplink RxLev above the threshold, but C0 load is not");
	dt := f_est_dchan('23'O, 23, gen_l3_valid_payload(), TRX0);
	f_ipa_tx(ts_RSL_MEAS_RES_EMPTY(dt.rsl_chan_nr, 0,
				       ts_RSL_IE_UplinkMeas(50, 0),
				       ts_RSL_IE_BS_Power(0)), TRX0);
	f_ipa_tx(ts_RSL_MEAS_RES_EMPTY(dt.rsl_chan_nr, 1,
				       ts_RSL_IE_UplinkMeas(58, 0),
				       ts_RSL_IE_BS_Power(0)), TRX0);
	f_sleep(0.3); /* give the IUT some time to process sent MRs */
	f_TC_chan_alloc_algo(dt, TRX0);
	f_perform_clear_test_ct(dt);

	f_vty_enter_cfg_bts(BSCVTY, 2);
	f_vty_transceive(BSCVTY, "channel allocator ascending");
	f_vty_transceive(BSCVTY, "end");

	/* At this point some TCH/F channels are BORKEN, but they will be
	 * resurrected upon the A-bis/OML link re-establishment. */
	f_shutdown_helper();
}

private function f_vty_mgw_enable(integer mgw_nr := 1) runs on test_CT {
	var rof_charstring cmds := {
		"remote-ip " & mp_test_ip,
		"remote-port " & int2str(2427 + mgw_nr)
	};
	f_vty_config3(BSCVTY, {"network", "mgw " & int2str(mgw_nr)}, cmds);
	f_vty_transceive(BSCVTY, "mgw " & int2str(mgw_nr) & " reconnect")
}
private function f_vty_mgw_disable(integer mgw_nr := 1) runs on test_CT {
	f_vty_config3(BSCVTY, {"network"}, { "no mgw " &int2str(mgw_nr) });
}
private function f_vty_mgw_block(integer mgw_nr := 1, boolean blocked := true) runs on test_CT {
	var charstring arg;
	if (blocked) {
		arg := "block";
	} else {
		arg := "unblock";
	}
	f_vty_transceive(BSCVTY, "mgw " & int2str(mgw_nr) & " " & arg);
}
private function f_vty_mgw_cfg_keepalive(integer mgw_nr := 1, integer req_intval := 0, charstring req_endp := "null", integer rx_timeout := 0) runs on test_CT {
	var charstring arg;
	var rof_charstring cmds := {
		"keepalive request-interval " & int2str(req_intval),
		"keepalive request-endpoint " & req_endp,
		"keepalive timeout " & int2str(rx_timeout)
	};
	f_vty_config3(BSCVTY, {"network", "mgw " & int2str(mgw_nr)}, cmds);
}
private const charstring COORD_CMD_ESTABLISHED := "COORD_CMD_ESTABLISHED";
private function f_TC_mgwpool_call_seq_1(charstring id) runs on MSC_ConnHdlr {
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req(ass_cid := 0);
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));

	f_establish_fully(ass_cmd, exp_compl);
	COORD.send(COORD_CMD_ESTABLISHED);

	COORD.receive(COORD_CMD_ESTABLISHED);
	f_perform_clear()
	f_create_mgcp_delete_ep(g_media.mgcp_ep);
}
private function f_TC_mgwpool_call_seq_2(charstring id) runs on MSC_ConnHdlr {
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req(ass_cid := 1);
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));


	COORD.receive(COORD_CMD_ESTABLISHED);
	f_establish_fully(ass_cmd, exp_compl);
	COORD.send(COORD_CMD_ESTABLISHED);

	f_perform_clear()
	f_create_mgcp_delete_ep(g_media.mgcp_ep);
}
/* Test load is spread around 2 available MGWs */
testcase TC_mgwpool_all_used() runs on test_CT {
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn1;
	pars1.mgwpool_idx := 0;

	var TestHdlrParams pars2 := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn2;
	pars2.mgwpool_idx := 1;
	pars2.media_nr := 2;

	f_init(1, true, nr_mgw := 2);
	f_sleep(1.0);

	f_vty_mgw_enable(1);

	vc_conn1 := f_start_handler_create(pars1);
	vc_conn2 := f_start_handler_create(pars2);
	connect(vc_conn1:COORD, vc_conn2:COORD);
	f_start_handler_run(vc_conn1, refers(f_TC_mgwpool_call_seq_1), pars1);
	f_start_handler_run(vc_conn2, refers(f_TC_mgwpool_call_seq_2), pars2);
	vc_conn1.done;
	vc_conn2.done;

	f_vty_mgw_disable(1);

	f_shutdown_helper();
}

/* Test blocked MGW in the pool are not selected */
testcase TC_mgwpool_blocked_not_used() runs on test_CT {
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn1;
	pars1.mgwpool_idx := 0;

	var TestHdlrParams pars2 := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn2;
	pars2.mgwpool_idx := 0; /* expect it in the first one, since the second will be blocked */
	pars2.media_nr := 2;

	f_init(1, true, nr_mgw := 2);
	f_sleep(1.0);

	f_vty_mgw_enable(1);
	f_vty_mgw_block(1, true);

	vc_conn1 := f_start_handler_create(pars1);
	vc_conn2 := f_start_handler_create(pars2);
	connect(vc_conn1:COORD, vc_conn2:COORD);
	f_start_handler_run(vc_conn1, refers(f_TC_mgwpool_call_seq_1), pars1);
	f_start_handler_run(vc_conn2, refers(f_TC_mgwpool_call_seq_2), pars2);
	vc_conn1.done;
	vc_conn2.done;

	f_vty_mgw_disable(1);

	f_shutdown_helper();
}

/* Test BTS pinning to an MGW is applied */
testcase TC_mgwpool_pin_bts() runs on test_CT {
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn1;
	pars1.mgwpool_idx := 0;

	var TestHdlrParams pars2 := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn2;
	pars2.mgwpool_idx := 0; /* expect it in the first one, since the BTS is pinned to the first MGW */
	pars2.media_nr := 2;

	f_init(1, true, nr_mgw := 2);
	f_sleep(1.0);

	f_vty_mgw_enable(1);
	f_vty_cfg_bts(BSCVTY, 0, { "mgw pool-target 0" });

	vc_conn1 := f_start_handler_create(pars1);
	vc_conn2 := f_start_handler_create(pars2);
	connect(vc_conn1:COORD, vc_conn2:COORD);
	f_start_handler_run(vc_conn1, refers(f_TC_mgwpool_call_seq_1), pars1);
	f_start_handler_run(vc_conn2, refers(f_TC_mgwpool_call_seq_2), pars2);
	vc_conn1.done;
	vc_conn2.done;

	f_vty_cfg_bts(BSCVTY, 0, { "no mgw pool-target" } );
	f_vty_mgw_disable(1);

	f_shutdown_helper();
}

/* Test keepalive procedure on the MGW */
private function f_TC_mgwpool_keepalive_mgw0(charstring id) runs on MSC_ConnHdlr {
	/* Make sure the MGW is considered DOWN by the keepalive. See "timeout"
	 * param in TC_mgwpool_keepalive. */
	f_sleep(3.0);

	COORD.send(COORD_CMD_ESTABLISHED);

	/* Wait for signal that call was established successfully over the other MSC/MGW: */
	COORD.receive(COORD_CMD_ESTABLISHED);
}
private function f_TC_mgwpool_keepalive_mgw1(charstring id) runs on MSC_ConnHdlr {
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();
	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));

	/* Wait for other MSC/MGW component to tell us the timeout is done and
	 * we can start the call, which will be assigned to use because the other
	 * MGW will be seen as DOWN. */
	COORD.receive(COORD_CMD_ESTABLISHED);
	f_establish_fully(ass_cmd, exp_compl);
	/* signal that call was established successfully over the other MSC/MGW: */
	COORD.send(COORD_CMD_ESTABLISHED);

	f_perform_clear()
	f_create_mgcp_delete_ep(g_media.mgcp_ep);
}
testcase TC_mgwpool_keepalive_down_not_used() runs on test_CT {
	var TestHdlrParams pars1 := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn1;
	pars1.mgwpool_idx := 0;

	var TestHdlrParams pars2 := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn2;
	pars2.mgwpool_idx := 1; /* expect it in the second one, since the first will be considered DOWN */
	pars2.media_nr := 2;

	f_init(1, true, nr_mgw := 2);
	f_sleep(1.0);

	f_vty_mgw_cfg_keepalive(0, req_intval := 1, req_endp := "null", rx_timeout := 3);
	f_vty_mgw_enable(1);
	f_vty_mgw_block(1, false);

	vc_conn1 := f_start_handler_create(pars1);
	vc_conn2 := f_start_handler_create(pars2);
	connect(vc_conn1:COORD, vc_conn2:COORD);
	f_start_handler_run(vc_conn1, refers(f_TC_mgwpool_keepalive_mgw0), pars1);
	f_start_handler_run(vc_conn2, refers(f_TC_mgwpool_keepalive_mgw1), pars2);
	vc_conn1.done;
	vc_conn2.done;

	f_vty_mgw_disable(1);
	/* disable keepalive (default status): */
	f_vty_mgw_cfg_keepalive(0, req_intval := 0, req_endp := "null", rx_timeout := 0);

	f_shutdown_helper();
}

private function f_tc_ho_meas_rep_multi_band(charstring id) runs on MSC_ConnHdlr {
	g_pars := f_gen_test_hdlr_pars();
	var PDU_BSSAP ass_req := f_gen_ass_req();
	ass_req.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_req.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	f_establish_fully(ass_req, exp_compl);

	/* Send a measurement report with bad rxlev except on 3rd entry. The
	 * measurement report is divided into two sub lists, as described in
	 * 3GPP TS 04.08 § 10.5.2.20. */
	var NcellReports neighbor_rep := {
		/* Sub list 1: same band */
		{ rxlev := 0, bcch_freq := 0, bsic := 12 },  /* ARFCN  800, band 1800, LAC 98 */
		/* Sub list 2: different band */
		{ rxlev := 0, bcch_freq := 1, bsic := 13 },  /* ARFCN  200, band  850, LAC 97 */
		{ rxlev := 40, bcch_freq := 2, bsic := 14 }, /* ARFCN 1000, band  900, LAC 99 */
		{ rxlev := 0, bcch_freq := 3, bsic := 11 }   /* ARFCN    0, band  900, LAC 96 */
	};
	var octetstring l3_mr := enc_GsmRrL3Message(valueof(ts_MEAS_REP(true, 8, 8, reps := neighbor_rep)));
	RSL.send(ts_RSL_MEAS_RES(g_chan_nr, 0, ts_RSL_IE_UplinkMeas, ts_RSL_IE_BS_Power(0), ts_RSL_IE_L1Info,
				 l3_mr, 0));

	/* Expect a handover to the third entry. If the BSC parsed the report
	 * correctly, the third entry has LAC 99. */
	var template BSSMAP_FIELD_CellIdentificationList cid_list := {
		cIl_LAC := { ts_BSSMAP_CI_LAC(99) }
	};
	alt {
	[] BSSAP.receive(tr_BSSMAP_HandoverRequired(cid_list := cid_list)) {
			setverdict(pass);
		}
	[] BSSAP.receive(tr_BSSMAP_HandoverRequired()) {
			setverdict(fail, "Handover has unexpected LAC in Cell Identification List. The BSC probably"
				   & " didn't parse the multi-band measurement report correctly.");
		}
	}

	f_ho_out_of_this_bsc(skip_meas_rep := true);
}
testcase TC_ho_meas_rep_multi_band() runs on test_CT {
	/* Verify that the BSC parses the measurement report correctly when
	 * neighbors in multiple bands are configured (OS#5717). See
	 * gsm_arfcn2band_rc() in libosmocore src/gsm/gsm_utils.c for the
	 * ARFCN -> band mapping. The MS is connected to band 1800. */
	var MSC_ConnHdlr vc_conn;

	f_init_vty();
	f_bts_0_cfg(BSCVTY,
		    {"neighbor-list mode automatic",
		     "handover 1",
		     "handover algorithm 2",
		     "handover2 window rxlev averaging 1",
		     "no neighbors",
		     "neighbor lac 99 arfcn 1000 bsic any",  /* band 900 */
		     "neighbor lac 98 arfcn 800 bsic any",   /* band 1800 */
		     "neighbor lac 97 arfcn 200 bsic any",   /* band 850 */
		     "neighbor lac 96 arfcn 0 bsic any"});   /* band 900 */
	f_vty_transceive(BSCVTY, "drop bts connection 0 oml");

	f_init(1, true);
	f_sleep(1.0);

	f_ctrs_bsc_and_bts_handover_init();

	vc_conn := f_start_handler(refers(f_tc_ho_meas_rep_multi_band));
	vc_conn.done;

	f_ctrs_bsc_and_bts_add(0, "assignment:attempted");
	f_ctrs_bsc_and_bts_add(0, "assignment:completed");
	f_ctrs_bsc_and_bts_add(0, "handover:attempted");
	f_ctrs_bsc_and_bts_add(0, "handover:completed");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:attempted");
	f_ctrs_bsc_and_bts_add(0, "interbsc_ho_out:completed");
	f_ctrs_bsc_and_bts_verify();
	f_shutdown_helper(ho := true);
}

private function f_vty_expect_borken(TELNETasp_PT pt, boolean expect_borken)
{
	var charstring lchan_summary;
	lchan_summary := f_vty_transceive_ret(pt, "show lchan summary");
	var boolean found_borken := (f_strstr(lchan_summary, "State BORKEN") >= 0);
	if (found_borken != expect_borken) {
		if (expect_borken) {
			setverdict(fail, "Expected 'BORKEN' state in ", lchan_summary);
		} else {
			setverdict(fail, "Expected no 'BORKEN' state in ", lchan_summary);
		}
		mtc.stop;
	}
}

private function f_tc_unbreak_lchan_after_missing_rel_ack(integer test_variant) runs on MSC_ConnHdlr {

	/* Establish a channel, so we can mess with the release of it. */
	var template PDU_BSSAP exp_compl := f_gen_exp_compl();
	var PDU_BSSAP ass_cmd := f_gen_ass_req();

	ass_cmd.pdu.bssmap.assignmentRequest.channelType := valueof(ts_BSSMAP_IE_ChannelType);
	ass_cmd.pdu.bssmap.assignmentRequest.codecList := valueof(ts_BSSMAP_IE_CodecList({ts_CodecFR}));

	f_establish_fully(ass_cmd, exp_compl);

	/* shorten X28, the time after which a BORKEN lchan recovers, from 30s to something short */
	f_vty_enter_cfg_network(BSCVTY);
	f_vty_transceive(BSCVTY, "timer X28 3");
	f_vty_transceive(BSCVTY, "end");

	/* Allow us to include handling of Channel Activation in this component, to test recovery from BORKEN */
	f_rslem_set_auto_chan_act_ack(RSL_PROC, false);

	/* give a moment before releasing */
	f_sleep(2.0);

	/* Perform a channel release, but omit the RF CHAN REL ACK. The aim is to get the lchan to go BORKEN. */
	f_perform_clear(send_rel_ack := false);

	f_vty_expect_borken(BSCVTY, false);

	/* After a timeout of X6, the lchan ends up in LCHAN_ST_BORKEN */
	f_sleep(6.0);

	f_vty_expect_borken(BSCVTY, true);

	/* Now within two seconds, we expect OsmoBSC to attempt recovering from the broken state.
	 *
	 * test_variant == 1:
	 * It will first attempt a Channel Activation. If the BTS accepts that, it means the state is back in sync.
	 * Then OsmoBSC releases the lchan again and makes it available for use.
	 *
	 * test_variant == 2:
	 * It will first attempt a Channel Activation and the BTS NACKs that.
	 * It will then attempt a release, which we ACK, and the channel will be available again.
	 */

	/* Receive the Channel Activation */
	var ASP_RSL_Unitdata rx_rsl_ud;
	RSL.receive(tr_ASP_RSL_UD(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV), sid := ?)) -> value rx_rsl_ud;

	var RslChannelNr chan_nr;
	var RSL_IE_Body ie;
	if (f_rsl_find_ie(rx_rsl_ud.rsl, RSL_IE_CHAN_NR, ie) == true) {
		chan_nr := ie.chan_nr;
	} else {
		setverdict(fail, "Unable to find RSL_IE_CHAN_NR in ", rx_rsl_ud);
		return;
	}

	if (test_variant == 1) {
		RSL.send(ts_ASP_RSL_UD(ts_RSL_CHAN_ACT_ACK(chan_nr, 23), rx_rsl_ud.streamId));

		/* Back in sync, receive the Channel Release */
		RSL.receive(tr_RSL_RF_CHAN_REL(chan_nr));
		RSL.send(ts_RSL_RF_CHAN_REL_ACK(chan_nr));
	} else if (test_variant == 2) {
		RSL.send(ts_ASP_RSL_UD(ts_RSL_CHAN_ACT_NACK(chan_nr, RSL_ERR_EQUIPMENT_FAIL), rx_rsl_ud.streamId));

		/* Instead tries a release */
		RSL.receive(tr_RSL_RF_CHAN_REL(chan_nr));
		RSL.send(ts_RSL_RF_CHAN_REL_ACK(chan_nr));
		/* That worked, channel usable again */
	} else {
		setverdict(fail, "undefined test_variant ", test_variant);
	}

	f_vty_expect_borken(BSCVTY, false);
	setverdict(pass);
}

/* Test recovery path from a BORKEN lchan */
private function f_TC_unbreak_lchan_after_missing_rel_ack_1(charstring id) runs on MSC_ConnHdlr {
	f_tc_unbreak_lchan_after_missing_rel_ack(test_variant := 1);
}
testcase TC_unbreak_lchan_after_missing_rel_ack_1() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_TC_unbreak_lchan_after_missing_rel_ack_1), pars);
	vc_conn.done;
	f_shutdown_helper();
}

private function f_TC_unbreak_lchan_after_missing_rel_ack_2(charstring id) runs on MSC_ConnHdlr {
	f_tc_unbreak_lchan_after_missing_rel_ack(test_variant := 2);
}
testcase TC_unbreak_lchan_after_missing_rel_ack_2() runs on test_CT {
	var TestHdlrParams pars := f_gen_test_hdlr_pars();
	var MSC_ConnHdlr vc_conn;
	f_init(1, true);
	f_sleep(1.0);
	vc_conn := f_start_handler(refers(f_TC_unbreak_lchan_after_missing_rel_ack_2), pars);
	vc_conn.done;
	f_shutdown_helper();
}

control {
	/* CTRL interface testing */
	execute( TC_ctrl_msc_connection_status() );
	execute( TC_ctrl_msc0_connection_status() );
	/* In SCCPlite tests, only one MSC is configured. These tests assume that three MSCs are configured, so only run
	 * these in the AoIP test suite. */
	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_AoIP) {
		execute( TC_stat_num_msc_connected_1() );
		execute( TC_stat_num_msc_connected_2() );
		execute( TC_stat_num_msc_connected_3() );
	}
	execute( TC_stat_num_bts_connected_1() );
	execute( TC_stat_num_bts_connected_2() );
	execute( TC_stat_num_bts_connected_3() );
	/* This "nightly" if can be removed once libosmo-sigtran (osmo-stp) > 2.1.0 is released.
	 * Currently released osmo-stp 2.1.0 ends up in in broken AS-ACTIVE+ASP-DOWN state when
	 * restarting the ASP after exiting the related asp node through VTY: */
	if (Misc_Helpers.f_osmo_repo_is("nightly")) {
		execute( TC_stat_msc_sctp_disconnected() );
	}
	execute( TC_ctrl() );
	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_SCCPlite) {
		execute( TC_ctrl_location() );
	}

	execute( TC_si_default() );
	execute( TC_si2quater_2_earfcns() );
	execute( TC_si2quater_3_earfcns() );
	execute( TC_si2quater_4_earfcns() );
	execute( TC_si2quater_5_earfcns() );
	execute( TC_si2quater_6_earfcns() );
	execute( TC_si2quater_12_earfcns() );
	execute( TC_si2quater_23_earfcns() );
	execute( TC_si2quater_32_earfcns() );
	execute( TC_si2quater_33_earfcns() );
	execute( TC_si2quater_42_earfcns() );
	execute( TC_si2quater_48_earfcns() );
	execute( TC_si2quater_49_earfcns() );
	execute( TC_si_acc_rotate() );
	execute( TC_si_acc_ramp_rotate() );
	execute( TC_si2_ncc_permitted() ); /* TODO: test in SI6 too */

	/* RSL DCHAN Channel ACtivation / Deactivation */
	execute( TC_chan_act_noreply() );
	execute( TC_chan_act_counter() );
	execute( TC_chan_act_ack_noest() );
	execute( TC_chan_act_ack_noest_emerg() );
	execute( TC_chan_rqd_emerg_deny() );
	execute( TC_chan_act_ack_est_ind_noreply() );
	execute( TC_chan_act_ack_est_ind_refused() );
	execute( TC_chan_act_nack() );
	execute( TC_chan_exhaustion() );
	execute( TC_chan_deact_silence() );
	execute( TC_chan_rel_rll_rel_ind() );
	execute( TC_chan_rel_conn_fail() );
	execute( TC_chan_rel_hard_clear() );
	execute( TC_chan_rel_last_eutran_plmn_hard_clear_no_csfb() );
	execute( TC_chan_rel_last_eutran_plmn_hard_clear_csfb() );
	execute( TC_chan_rel_hard_clear_csfb() );
	execute( TC_chan_rel_hard_rlsd() );
	execute( TC_chan_rel_hard_rlsd_ms_dead() );
	execute( TC_chan_rel_a_reset() );
	execute( TC_chan_rel_sccp_tiar_timeout() );
	execute( TC_chan_rel_rr_cause() );

	execute( TC_outbound_connect() );

	/* Assignment related */
	execute( TC_assignment_cic_only() );
	execute( TC_assignment_csd() );
	execute( TC_assignment_ctm() );
	execute( TC_assignment_sign() );
	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_AoIP) {
		execute( TC_assignment_aoip_tla_v6() );
	}
	execute( TC_assignment_fr_a5_0() );
	execute( TC_assignment_fr_a5_1() );
	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_AoIP) {
		execute( TC_assignment_fr_a5_1_codec_missing() );
	}
	execute( TC_assignment_fr_a5_3() );
	execute( TC_assignment_fr_a5_4() );
	execute( TC_assignment_fr_a5_4_fail() );
	execute( TC_assignment_fr_a5_not_sup() );
	execute( TC_ciph_mode_a5_0() );
	execute( TC_ciph_mode_a5_1() );
	execute( TC_ciph_mode_a5_2_0() );
	execute( TC_ciph_mode_a5_2_1() );
	execute( TC_ciph_mode_a5_3() );
	execute( TC_ciph_mode_a5_4() );

	execute( TC_assignment_codec_fr() );
	execute( TC_assignment_codec_fr_by_mode_modify() );
	execute( TC_assignment_codec_hr() );
	execute( TC_assignment_codec_efr() );
	execute( TC_assignment_codec_amr_f() );
	execute( TC_assignment_codec_amr_h() );

	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_AoIP) {
		execute( TC_assignment_codec_amr_f_S1() );
		execute( TC_assignment_codec_amr_h_S1() );
		execute( TC_assignment_codec_amr_f_S124() );
		execute( TC_assignment_codec_amr_h_S124() );
		execute( TC_assignment_codec_amr_f_S0() );
		execute( TC_assignment_codec_amr_f_S02() );
		execute( TC_assignment_codec_amr_f_S024() );
		execute( TC_assignment_codec_amr_f_S0247() );
		execute( TC_assignment_codec_amr_h_S0() );
		execute( TC_assignment_codec_amr_h_S02() );
		execute( TC_assignment_codec_amr_h_S024() );
		execute( TC_assignment_codec_amr_h_S0247() );
		execute( TC_assignment_codec_amr_f_S01234567() );
		execute( TC_assignment_codec_amr_f_S0234567() );
		execute( TC_assignment_codec_amr_f_zero() );
		execute( TC_assignment_codec_amr_f_unsupp() );
		execute( TC_assignment_codec_amr_h_S7() );
		execute( TC_assignment_codec_amr_f_start_mode_auto() );
		execute( TC_assignment_codec_amr_h_start_mode_auto() );
		execute( TC_assignment_codec_amr_f_start_mode_4() );
		execute( TC_assignment_codec_amr_h_start_mode_4() );
		execute( TC_assignment_codec_amr_startmode_cruft() );
	}

	execute( TC_assignment_codec_fr_exhausted_req_hr() );
	execute( TC_assignment_codec_fr_exhausted_req_fr() );
	execute( TC_assignment_codec_fr_exhausted_req_fr_hr() );
	execute( TC_assignment_codec_fr_exhausted_req_hr_fr() );
	execute( TC_assignment_codec_hr_exhausted_req_fr() );
	execute( TC_assignment_codec_hr_exhausted_req_hr() );
	execute( TC_assignment_codec_hr_exhausted_req_hr_fr() );
	execute( TC_assignment_codec_hr_exhausted_req_fr_hr() );
	execute( TC_assignment_codec_req_hr_fr() );
	execute( TC_assignment_codec_req_fr_hr() );
	execute( TC_assignment_sdcch_exhausted_req_signalling() );
	execute( TC_assignment_sdcch_exhausted_req_signalling_tch_forbidden() );
	execute( TC_assignment_sdcch_exhausted_req_voice_tch_forbidden() );

	execute( TC_assignment_codec_hr_osmux_on() );
	execute( TC_assignment_osmux() );
	execute( TC_assignment_osmux_cn() );
	execute( TC_assignment_osmux_bts() );

	/* RLL Establish Indication on inactive DCHAN / SAPI */
	execute( TC_rll_est_ind_inact_lchan() );
	execute( TC_rll_est_ind_inval_sapi1() );
	execute( TC_rll_est_ind_inval_sapi3() );
	execute( TC_rll_est_ind_inval_sacch() );

	/* DLCI / RSL Link ID conversion for MO/MT messages on SAPI0/SAPI3 */
	execute( TC_tch_dlci_link_id_sapi() );

	/* SAPI N Reject triggered by RLL establishment failures */
	execute( TC_rll_rel_ind_sapi_n_reject() );
	execute( TC_rll_err_ind_sapi_n_reject() );
	execute( TC_rll_timeout_sapi_n_reject() );
	execute( TC_rll_sapi_n_reject_dlci_cc() );

	/* Paging related tests */
	execute( TC_paging_imsi_nochan() );
	execute( TC_paging_tmsi_nochan() );
	execute( TC_paging_tmsi_any() );
	execute( TC_paging_tmsi_sdcch() );
	execute( TC_paging_tmsi_tch_f() );
	execute( TC_paging_tmsi_tch_hf() );
	execute( TC_paging_imsi_nochan_cgi() );
	execute( TC_paging_imsi_nochan_lac_ci() );
	execute( TC_paging_imsi_nochan_ci() );
	execute( TC_paging_imsi_nochan_lai() );
	execute( TC_paging_imsi_nochan_lac() );
	execute( TC_paging_imsi_nochan_all() );
	execute( TC_paging_imsi_nochan_plmn_lac_rnc() );
	execute( TC_paging_imsi_nochan_rnc() );
	execute( TC_paging_imsi_nochan_lac_rnc() );
	execute( TC_paging_imsi_nochan_lacs() );
	execute( TC_paging_imsi_nochan_lacs_empty() );
	execute( TC_paging_imsi_nochan_cgi_unknown_cid() );
	execute( TC_paging_imsi_a_reset() );
	execute( TC_paging_imsi_load() );
	execute( TC_paging_counter() );
	execute( TC_paging_resp_unsol() );
	execute( TC_paging_500req() );
	execute( TC_paging_450req_no_paging_load_ind() );
	execute( TC_paging_imsi_nochan_ci_resp_invalid_mi() );

	execute( TC_rsl_drop_counter() );
	execute( TC_rsl_unknown_unit_id() );

	execute( TC_oml_unknown_unit_id() );

	execute( TC_classmark() );
	execute( TC_common_id() );
	execute( TC_unsol_ass_fail() );
	execute( TC_unsol_ass_compl() );
	execute( TC_unsol_ho_fail() );
	execute( TC_err_82_short_msg() );
	execute( TC_err_84_unknown_msg() );

	execute( TC_ho_int() );
	execute( TC_ho_int_a5_0() );
	execute( TC_ho_int_a5_1() );
	execute( TC_ho_int_a5_3() );
	execute( TC_ho_int_a5_4() );
	execute( TC_ho_int_radio_link_failure() );

	/* TC_ho_out_of_this_bsc is run last, see comment below */
	execute( TC_ho_out_fail_no_msc_response() );
	execute( TC_ho_out_fail_rr_ho_failure() );
	execute( TC_ho_out_fail_no_result_after_ho_cmd() );

	execute( TC_ho_into_this_bsc() );
	execute( TC_ho_into_this_bsc_a5_0() );
	execute( TC_ho_into_this_bsc_a5_1() );
	execute( TC_ho_into_this_bsc_a5_3() );
	execute( TC_ho_into_this_bsc_a5_4() );
	execute( TC_ho_into_this_bsc_a5_1_3_no_chosen_enc_alg() );
	execute( TC_ho_into_this_bsc_a5_1_3() );
	execute( TC_ho_into_this_bsc_a5_mismatch() );
	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_AoIP) {
		execute( TC_ho_into_this_bsc_tla_v6() );
	}
	execute( TC_srvcc_eutran_to_geran() );
	execute( TC_srvcc_eutran_to_geran_a5_3() );
	execute( TC_srvcc_eutran_to_geran_src_sai() );
	execute( TC_srvcc_eutran_to_geran_ho_out() );
	execute( TC_srvcc_eutran_to_geran_forbid_fast_return() );
	execute( TC_srvcc_eutran_to_geran_ho_out_forbid_fast_return() );
	execute( TC_ho_in_fail_msc_clears() );
	execute( TC_ho_in_fail_msc_clears_after_ho_detect() );
	execute( TC_ho_in_fail_no_detect() );
	execute( TC_ho_in_fail_no_detect2() );
	execute( TC_ho_in_fail_mgw_mdcx_timeout() );
	execute( TC_ho_in_fail_ipa_crcx_timeout() );
	execute( TC_ho_into_this_bsc_sccp_cr_without_bssap() );

	execute( TC_ho_neighbor_config_1() );
	execute( TC_ho_neighbor_config_2() );
	execute( TC_ho_neighbor_config_3() );
	execute( TC_ho_neighbor_config_4() );
	execute( TC_ho_neighbor_config_5() );
	execute( TC_ho_neighbor_config_6() );
	execute( TC_ho_neighbor_config_7() );

	execute( TC_bssap_rlsd_does_not_cause_bssmap_reset() );
	execute( TC_bssmap_clear_does_not_cause_bssmap_reset() );
	execute( TC_ms_rel_ind_does_not_cause_bssmap_reset() );

	execute( TC_dyn_pdch_ipa_act_deact() );
	execute( TC_dyn_pdch_ipa_act_nack() );
	execute( TC_dyn_pdch_osmo_act_deact() );
	execute( TC_dyn_pdch_osmo_act_nack() );
	execute( TC_dyn_ts_sdcch8_act_deact() );
	execute( TC_dyn_ts_sdcch8_all_subslots_used() );
	execute( TC_dyn_ts_sdcch8_tch_call_act_deact() );
	execute( TC_dyn_ts_sdcch8_act_nack() );

	execute( TC_chopped_ipa_ping() );
	execute( TC_chopped_ipa_payload() );

	/* Power control related */
	execute( TC_assignment_verify_ms_power_params_ie() );
	execute( TC_c0_power_red_mode() );

	/* MSC pooling */
	/* FIXME: in SCCPlite, indicating how many MSCs should be connected does currently not work. Since
	 * RESET->RESET-ACK is unconditionally negotiated for all configured MSCs, they always all appear as connected
	 * to osmo-bsc. The MSC pooling tests however require disconnecting selected MSCs, and hence don't work out as
	 * intended on SCCPlite. So for now, run these only for SCCP/M3UA. */
	if (mp_bssap_cfg[0].transport == BSSAP_TRANSPORT_AoIP) {
		execute( TC_mscpool_L3Compl_on_1_msc() );
		execute( TC_mscpool_L3Complete_by_imsi_round_robin() );
		execute( TC_mscpool_LU_by_tmsi_null_nri_0_round_robin() );
		execute( TC_mscpool_LU_by_tmsi_null_nri_1_round_robin() );
		execute( TC_mscpool_L3Complete_by_tmsi_unassigned_nri_round_robin() );
		execute( TC_mscpool_L3Complete_by_tmsi_valid_nri_msc_not_connected_round_robin() );
		execute( TC_mscpool_L3Complete_by_tmsi_valid_nri_1() );
		execute( TC_mscpool_L3Complete_by_tmsi_valid_nri_2() );
		execute( TC_mscpool_LU_by_tmsi_from_other_PLMN() );
		execute( TC_mscpool_paging_and_response_imsi() );
		execute( TC_mscpool_paging_and_response_tmsi() );
		execute( TC_mscpool_no_allow_attach_round_robin() );
		execute( TC_mscpool_no_allow_attach_valid_nri() );

		execute( TC_mscpool_sccp_n_pcstate_detaches_msc() );
		execute( TC_mscpool_sccp_n_pcstate_attaches_msc() );
	}

	execute( TC_early_conn_fail() );
	execute( TC_late_conn_fail() );
	execute( TC_stats_conn_fail() );

	/* Emergency call handling (deny / allow) */
	execute( TC_assignment_emerg_setup_allow() );
	execute( TC_assignment_emerg_setup_allow_imei() );
	execute( TC_assignment_emerg_setup_deny_msc() );
	execute( TC_assignment_emerg_setup_deny_bts() );
	execute( TC_emerg_premption() );

	/* Frequency hopping parameters handling */
	execute( TC_fh_params_chan_activ() );
	execute( TC_fh_params_imm_ass() );
	execute( TC_fh_params_assignment_cmd() );
	execute( TC_fh_params_handover_cmd() );
	execute( TC_fh_params_si4_cbch() );

	if (mp_enable_lcs_tests) {
		execute( TC_lcs_loc_req_for_active_ms() );
		execute( TC_lcs_loc_req_for_active_ms_ta_req() );
		execute( TC_lcs_loc_req_for_idle_ms() );
		execute( TC_lcs_loc_req_no_subscriber() );
		execute( TC_lcs_loc_req_for_active_ms_le_timeout() );
		execute( TC_lcs_loc_req_for_active_ms_le_timeout2() );
		execute( TC_lcs_loc_req_for_idle_ms_no_pag_resp() );
		execute( TC_cm_service_during_lcs_loc_req() );
		execute( TC_ho_during_lcs_loc_req() );
		execute( TC_emerg_call_and_lcs_loc_req() );
		execute( TC_emerg_call_and_lcs_loc_req_early_lchan_rel_ind() );
		execute( TC_emerg_call_and_lcs_loc_req_early_lchan_conn_fail() );

		/* Remove "is nightly" check after osmo-bsc > 1.13 is released
		 * with Ie8a66f9decf9fff602f7fecfb71a78fab900066f */
		if (Misc_Helpers.f_osmo_repo_is("nightly")) {
			execute( TC_lcs_loc_req_without_smlc() );
		}
	}

	execute( TC_no_msc() );

	execute( TC_refuse_chan_act_to_vamos() );
	execute( TC_refuse_mode_modif_to_vamos() );

	execute( TC_reassignment_fr() );

	execute( TC_cm_reestablishment() );

	execute( TC_imm_ass_post_chan_ack() );
	execute( TC_imm_ass_pre_chan_ack() );
	execute( TC_imm_ass_pre_ts_ack() );
	execute( TC_imm_ass_pre_chan_ack_dyn_ts() );
	execute( TC_imm_ass_pre_ts_ack_dyn_ts() );

	execute( TC_ctrl_trx_rf_locked() );

	execute( TC_ratectr_all_available_allocated() );
	execute( TC_ratectr_all_available_allocated_dyn() );

	execute( TC_cm_serv_rej() );

	execute( TC_lost_sdcch_during_assignment() );

	/* Channel allocator */
	execute( TC_chan_alloc_algo_ascending() );
	execute( TC_chan_alloc_algo_descending() );
	execute( TC_chan_alloc_algo_ass_dynamic() );

	/* MGW pool */
	execute( TC_mgwpool_all_used() );
	execute( TC_mgwpool_blocked_not_used() );
	execute( TC_mgwpool_pin_bts() );
	execute( TC_mgwpool_keepalive_down_not_used() );

	execute( TC_ho_meas_rep_multi_band() );

	/* Run TC_ho_out_of_this_bsc last, because it may trigger a segfault before osmo-bsc's patch
	 * with change-id I5a3345ab0005a73597f5c27207480912a2f5aae6 */
	execute( TC_ho_out_of_this_bsc() );

	execute( TC_unbreak_lchan_after_missing_rel_ack_1() );
	execute( TC_unbreak_lchan_after_missing_rel_ack_2() );
}

}
