module Test {
	import from GSM_Types all;
	import from Osmocom_Types all;
	import from GSM_SystemInformation all;
	import from GSM_RR_Types all;
	import from GSMTAP_Types all;
	import from GSMTAP_PortType all;
	import from IPL4_GSMTAP_CtrlFunct all;
	import from TELNETasp_PortType all;
	import from Osmocom_VTY_Functions all;

	const octetstring si1 := '5506198fb38000000000000000000000000000e504002b'O;
	const octetstring si2 := '59061a00000000000000000000000000000000ffe50400'O;
	const octetstring si3 := '49061b000062f22404d2490301275d40e50400392b2b2b'O;
	const octetstring si4 := '31061c62f22404d25d40e504002b2b2b2b2b2b2b2b2b2b'O;
	const octetstring c_si2bis := '550602bfe809b3ff00000000000000000000007900002b'O;
	const octetstring c_si2ter := '010603bf66b0aa0a00000002000000000000002b2b2b2b'O;
	const octetstring c_si2quater := '050607a8a0364aa698d72ff424feee0506d5e7fff02043'O;

	type component dummy_CT {
		port GSMTAP_PT GSMTAP;
		port TELNETasp_PT BSCVTY;
		var boolean initialized := false;
		var SystemInformationConfig si_cfg := {
			bcch_extended := false,
			si1_present := true,
			si2bis_present := false,
			si2ter_present := false,
			si2quater_present := false,
			si7_present := false,
			si8_present := false,
			si9_present := false,
			si13_present := false,
			si13alt_present := false,
			si15_present := false,
			si16_present := false,
			si17_present := false,
			si2n_present := false,
			si21_present := false,
			si22_present := false
		};
	};

	testcase TC_si1() runs on dummy_CT {
		log("SI: ", dec_SystemInformation(si1));
		log("SI: ", dec_SystemInformation(si2));
		log("SI: ", dec_SystemInformation(si3));
		log("SI: ", dec_SystemInformation(si4));
		setverdict(pass);
	}

	template GsmtapHeader t_GsmtapHeader := {
		version := GSMTAP_VERSION,
		hdr_len := 4,
		msg_type := ?,
		timeslot := ?,
		arfcn := ?,
		signal_dbm := ?,
		snr_db := ?,
		frame_number := ?,
		sub_type := ?,
		antenna_nr := ?,
		sub_slot := ?,
		res := ?
	}

	template GsmtapHeader t_GsmtapHeaderUm(template GsmtapChannel ch) modifies t_GsmtapHeader := {
		msg_type := GSMTAP_TYPE_UM,
		sub_type := ch
	}

	template GsmtapMessage t_bcch := {
		header := t_GsmtapHeaderUm(GSMTAP_CHANNEL_BCCH),
		payload := ?
	}

	template GSMTAP_RecvFrom t_recvfrom(template GsmtapChannel ch) := {
		connId := ?,
		remName := ?,
		remPort := ?,
		locName := ?,
		locPort := GSMTAP_PORT,
		proto := {udp:={}},
		userData := ?,
		msg := { header := t_GsmtapHeaderUm(ch), payload := ?}
	}

	/* tuple of gsmtap header + decoded SI */
	type record SystemInformationGsmtap {
		GsmtapHeader gsmtap,
		SystemInformation si
	}

	/* an arbitrary-length vector of decoded SI + gsmtap header */
	type record of SystemInformationGsmtap SystemInformationVector;

	/* an array of SI-vectors indexed by TC value */
	type SystemInformationVector SystemInformationVectorPerTc[8];

	type record of integer IntegerRecord;

	function int2bool(integer int) return boolean {
		if (int != 0) {
			return true;
		} else {
			return false;
		}
	}

	function f_array_contains(IntegerRecord arr, integer key) return boolean {
		for (var integer i:= 0; i< sizeof(arr); i := i + 1) {
			if (arr[i] == key) {
				return true;
			}
		}
		return false;
	}


	/* compute TC as per 45.002 6.3.1.3 */
	function f_gsm_compute_tc(integer fn) return integer {
		return (fn / 51) mod 8;
	}

	/* determine if a given SI vector contains given SI type at least once */
	function f_si_vecslot_contains(SystemInformationVector arr, RrMessageType key, boolean bcch_ext := false) return boolean {
		for (var integer i:= 0; i< sizeof(arr); i := i + 1) {
			var integer fn_mod51 := arr[i].gsmtap.frame_number mod 51;
			if (not bcch_ext and fn_mod51 == 2 or
			        bcch_ext and fn_mod51 == 6) {
				if (arr[i].si.header.message_type == key) {
					return true;
				}
			}
		}
		return false;
	}

	/* ensure a given TC slot of the SI vector contains given SI type at least once at TC */
	function f_ensure_si_vec_contains(SystemInformationVectorPerTc arr, integer tc, RrMessageType key, boolean ext_bcch := false) {
		if (not f_si_vecslot_contains(arr[tc], key, ext_bcch)) {
			setverdict(fail, "Fail: No ", key, " in TC=", tc, "!");
			mtc.stop;
		}
	}

	/* check if a given SI vector contains given SI type at least once on any TC */
	function f_si_vec_contains(SystemInformationVectorPerTc arr, RrMessageType key) return boolean {
		for (var integer tc:= 0; tc < sizeof(arr); tc := tc + 1) {
			if (f_si_vecslot_contains(arr[tc], key) or
			    f_si_vecslot_contains(arr[tc], key, true)) {
				return true;
			}
		}
		return false;
	}

	/* determine if a given SI vector contains given SI type at least N of M times */
	function f_si_vecslot_contains_n_of_m(SystemInformationVector arr, RrMessageType key, boolean bcch_ext := false, integer n := 1, integer m := 4) return boolean {
		var integer count := 0;
		if (sizeof(arr) < m) {
			testcase.stop("Error: Insufficient SI in array");
		}
		for (var integer i:= 0; i < m; i := i + 1) {
			var integer fn_mod51 := arr[i].gsmtap.frame_number mod 51;
			if (not bcch_ext and fn_mod51 == 2 or
			        bcch_ext and fn_mod51 == 6) {
				if (arr[i].si.header.message_type == key) {
					count := count + 1;
				}
			}
		}
		if (count >= n) {
			return true;
		} else {
			return false;
		}
	}

	/* ensure a given TC slot of the SI vector contains given SI type at least N out of M times at TC */
	function f_ensure_si_vec_contains_n_of_m(SystemInformationVectorPerTc arr, integer tc, RrMessageType key, boolean ext_bcch := false, integer n, integer m) {
		if (not f_si_vecslot_contains_n_of_m(arr[tc], key, ext_bcch, n, m)) {
			setverdict(fail, "Fail: Not ", n, "/", m, " of ", key, " in TC=", tc, "!");
			mtc.stop;
		}
	}

	/* determine if a given SI vector contains given SI type at least once */
	function f_si_vecslot_contains_only(SystemInformationVector arr, RrMessageType key, boolean bcch_ext := false) return boolean {
		for (var integer i:= 0; i< sizeof(arr); i := i + 1) {
			var integer fn_mod51 := arr[i].gsmtap.frame_number mod 51;
			if (not bcch_ext and fn_mod51 == 2 or
			        bcch_ext and fn_mod51 == 6) {
				if (arr[i].si.header.message_type != key) {
					return false;
				}
			}
		}
		return true;
	}

	/* ensure a given TC slot of the SI vector contains only given SI type */
	function f_ensure_si_vec_contains_only(SystemInformationVectorPerTc arr, integer tc, RrMessageType key, boolean ext_bcch := false) {
		if (not f_si_vecslot_contains_only(arr[tc], key, ext_bcch)) {
			setverdict(fail, "Fail: Not all ", key, " in TC=", tc, "!");
			mtc.stop;
		}
	}

	/* SI configuration of cell, against which we validate actual SI messages */
	type set SystemInformationConfig {
		boolean bcch_extended,
		boolean si1_present,
		boolean si2bis_present,
		boolean si2ter_present,
		boolean si2quater_present,
		boolean si7_present,
		boolean si8_present,
		boolean si9_present,
		boolean si13_present,
		boolean si13alt_present,
		boolean si15_present,
		boolean si16_present,
		boolean si17_present,
		boolean si2n_present,
		boolean si21_present,
		boolean si22_present
	}

	/* validate the SI scheduling according to TS 45.002 version 14.1.0 Release 14, Section 6.3.1.3 */
	function f_validate_si_scheduling(SystemInformationConfig cfg, SystemInformationVectorPerTc si_per_tc) {
		var integer i;
		for (i := 0; i < sizeof(si_per_tc); i := i + 1) {
			if (sizeof(si_per_tc[i]) == 0) {
				setverdict(fail, "No SI messages for TC=0!");
				mtc.stop;
			}
		}
		if (cfg.si1_present) {
			/* ii) System Information Type 1 needs to be sent if frequency hopping is in use or
			 * when the NCH is present in a cell. If the MS finds another message on BCCH Norm
			 * when TC = 0, it can assume that System Information Type 1 is not in use. */
			f_ensure_si_vec_contains(si_per_tc, 0, SYSTEM_INFORMATION_TYPE_1);
			/* make sure *ALL* contain SI1 */
			f_ensure_si_vec_contains_only(si_per_tc, 0, SYSTEM_INFORMATION_TYPE_1);
		}
		f_ensure_si_vec_contains(si_per_tc, 1, SYSTEM_INFORMATION_TYPE_2);
		/* iii) A SI 2 message will be sent at least every time TC = 1 */
		f_ensure_si_vec_contains(si_per_tc, 2, SYSTEM_INFORMATION_TYPE_3);
		f_ensure_si_vec_contains(si_per_tc, 6, SYSTEM_INFORMATION_TYPE_3);
		f_ensure_si_vec_contains(si_per_tc, 3, SYSTEM_INFORMATION_TYPE_4);
		f_ensure_si_vec_contains(si_per_tc, 7, SYSTEM_INFORMATION_TYPE_4);

		/*  iii) System information type 2 bis or 2 ter messages are sent if needed, as determined by the
		 *  system operator. If only one of them is needed, it is sent when TC = 5. If both are
		 *  needed, 2bis is sent when TC = 5 and 2ter is sent at least once within any of 4
		 *  consecutive occurrences of TC = 4. */
		if (cfg.si2bis_present and not cfg.si2ter_present) {
			f_ensure_si_vec_contains(si_per_tc, 5, SYSTEM_INFORMATION_TYPE_2bis);
		} else if (cfg.si2ter_present and not cfg.si2bis_present) {
			f_ensure_si_vec_contains(si_per_tc, 5, SYSTEM_INFORMATION_TYPE_2ter);
		} else if (cfg.si2ter_present and cfg.si2bis_present) {
			f_ensure_si_vec_contains(si_per_tc, 5, SYSTEM_INFORMATION_TYPE_2bis);
			f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_2ter, false, 1, 4);
		}

		if (cfg.si7_present or cfg.si8_present) {
			/* vi) Use of System Information type 7 and 8 is not always necessary. It is necessary
			 * if System Information type 4 does not contain all information needed for cell
			 * selection and reselection. */
			if (not cfg.bcch_extended) {
				testcase.stop("Error: SI7/SI8 require BCCH Extd.");
			}
			if (cfg.si7_present) {
				f_ensure_si_vec_contains(si_per_tc, 7, SYSTEM_INFORMATION_TYPE_7, true);
			}
			if (cfg.si8_present) {
				f_ensure_si_vec_contains(si_per_tc, 3, SYSTEM_INFORMATION_TYPE_8, true);
			}
		}

		if (cfg.si2quater_present) {
			/*  iii) System information type 2 quater is sent if needed, as determined by the system
			 *  operator.  If sent on BCCH Norm, it shall be sent when TC = 5 if neither of 2bis
			 *  and 2ter are used, otherwise it shall be sent at least once within any of 4
			 *  consecutive occurrences of TC = 4. If sent on BCCH Ext, it is sent at least once
			 *  within any of 4 consecutive occurrences of TC = 5. */
			if (not (cfg.bcch_extended)) {
				if (not (cfg.si2bis_present or cfg.si2ter_present)) {
					f_ensure_si_vec_contains(si_per_tc, 5, SYSTEM_INFORMATION_TYPE_2quater);
				} else {
					f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_2quater, false, 1, 4);
				}
			} else {
				f_ensure_si_vec_contains_n_of_m(si_per_tc, 5, SYSTEM_INFORMATION_TYPE_2quater, true, 1, 4);
			}
		}
		if (cfg.si9_present) {
			/* vi) System Information type 9 is sent in those blocks with TC = 4 which are specified
			 * in system information type 3 as defined in 3GPP TS 44.018. */
			f_ensure_si_vec_contains(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_9); // FIXME SI3
		}
		if (cfg.si13_present) {
			/* vii) System Information type 13 is only related to the GPRS service. System Information
			 * Type 13 need only be sent if GPRS support is indicated in one or more of System
			 * Information Type 3 or 4 or 7 or 8 messages. These messages also indicate if the
			 * message is sent on the BCCH Norm or if the message is transmitted on the BCCH Ext.
			 * In the case that the message is sent on the BCCH Norm, it is sent at least once
			 * within any of 4 consecutive occurrences of TC=4. */
			if (not cfg.bcch_extended) {
				f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_13, false, 1, 4);
			} else {
				f_ensure_si_vec_contains(si_per_tc, 0, SYSTEM_INFORMATION_TYPE_13, true);
			}
			if (f_si_vec_contains(si_per_tc, SYSTEM_INFORMATION_TYPE_13alt)) {
				setverdict(fail, "Cannot have SI13alt and SI13");
				mtc.stop;
			}
		}
		if (cfg.si16_present or cfg.si17_present) {
			/* viii) System Information type 16 and 17 are only related to the SoLSA service. They
			 * should not be sent in a cell where network sharing is used (see rule xv). */
			if (cfg.si22_present) {
				testcase.stop("Error: Cannot have SI16/SI17 and SI22!");
			}
			if (f_si_vec_contains(si_per_tc, SYSTEM_INFORMATION_TYPE_22)) {
				setverdict(fail, "Cannot have SI16/SI17 and SI22!");
				mtc.stop;
			}
			if (not cfg.bcch_extended) {
				testcase.stop("Error: SI16/SI17 requires BCCH Extd!");
			}
			if (cfg.si16_present) {
				f_ensure_si_vec_contains(si_per_tc, 6, SYSTEM_INFORMATION_TYPE_16, true);
			}
			if (cfg.si17_present) {
				f_ensure_si_vec_contains(si_per_tc, 2, SYSTEM_INFORMATION_TYPE_17, true);
			}
		}

			/* ix) System Information type 18 and 20 are sent in order to transmit non-GSM
			 * broadcast information. The frequency with which they are sent is determined by the
			 * system operator. System Information type 9 identifies the scheduling of System
			 * Information type 18 and 20 messages. */

			/* x) System Information Type 19 is sent if COMPACT neighbours exist. If System
			 * Information Type 19 is present, then its scheduling shall be indicated in System
			 * Information Type 9. */

		if (cfg.si15_present) {
			/* xi) System Information Type 15 is broadcast if dynamic ARFCN mapping is used in the
			 * PLMN. If sent on BCCH Norm, it is sent at least once within any of 4 consecutive
			 * occurrences of TC = 4. If sent on BCCH Ext, it is sent at least once within any of
			 * 4 consecutive occurrences of TC = 1. */
			if (not cfg.bcch_extended) {
				f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_15, false, 1, 4);
			} else {
				f_ensure_si_vec_contains_n_of_m(si_per_tc, 1, SYSTEM_INFORMATION_TYPE_15, true, 1, 4);
			}
		}
		if (cfg.si13alt_present) {
			/* xii) System Information type 13 alt is only related to the GERAN Iu mode. System
			 * Information Type 13 alt need only be sent if GERAN Iu mode support is indicated in
			 * one or more of System Information Type 3 or 4 or 7 or 8 messages and SI 13 is not
			 * broadcast. These messages also indicate if the message is sent on the BCCH Norm or
			 * if the message is transmitted on the BCCH Ext. In the case that the message is sent
			 * on the BCCH Norm, it is sent at least once within any of 4 consecutive occurrences
			 * of TC = 4. */
			if (cfg.si13_present) {
				testcase.stop("Error: Cannot have SI13alt and SI13");
			}
			if (f_si_vec_contains(si_per_tc, SYSTEM_INFORMATION_TYPE_13)) {
				setverdict(fail, "Cannot have SI13alt and SI13");
				mtc.stop;
			}
			if (not cfg.bcch_extended) {
				f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_13alt, false, 1, 4);
			} else {
				f_ensure_si_vec_contains(si_per_tc, 0, SYSTEM_INFORMATION_TYPE_13alt, true);
			}
		}
		if (cfg.si2n_present) {
			/* xiii) System Information Type 2n is optionally sent on BCCH Norm or BCCH Ext if needed,
			 * as determined by the system operator. In the case that the message is sent on the
			 * BCCH Norm, it is sent at least once within any of 4 consecutive occurrences of TC =
			 * 4. If the message is sent on BCCH Ext, it is sent at least once within any of 2
			 * consecutive occurrences of TC = 4. */
			if (not cfg.bcch_extended) {
				f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_2n, false, 1, 4);
			} else {
				f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_2n, true, 2, 4);
			}
		}
		if (cfg.si21_present) {
			/* xiv) System Information Type 21 is optionally sent on BCCH Norm or BCCH Ext, as
			 * determined by the system operator. If Extended Access Barring is in use in the cell
			 * then this message is sent at least once within any of 4 consecutive occurrences of
			 * TC = 4 regardless if it is sent on BCCH Norm or BCCH Ext. If BCCH Ext is used in a
			 * cell then this message shall only be sent on BCCH Ext. */
			if (not cfg.bcch_extended) {
				f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_21, false, 1, 4);
			} else {
				f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_21, true, 1, 4);
				if (f_si_vecslot_contains(si_per_tc[4], SYSTEM_INFORMATION_TYPE_21)) {
					setverdict(fail, "Cannot have SI21 on BCCH Norm if BCCH Extd enabled!");
					mtc.stop;
				}
			}
		}
		if (cfg.si22_present) {
			/* xv) System Information Type 22 is sent if network sharing is in use in the cell. It
			 * should not be sent in a cell where SoLSA is used (see rule viii). System
			 * Information Type 22 instances shall be sent on BCCH Ext within any occurrence of TC
			 * =2 and TC=6. */
			if (cfg.si16_present or cfg.si17_present) {
				testcase.stop("Error: Cannot have SI16/SI17 and SI22!");
			}
			if (f_si_vec_contains(si_per_tc, SYSTEM_INFORMATION_TYPE_16) or
			    f_si_vec_contains(si_per_tc, SYSTEM_INFORMATION_TYPE_17)) {
				setverdict(fail, "Cannot have SI16/SI17 and SI22!");
				mtc.stop;
			}
			if (not cfg.bcch_extended) {
				testcase.stop("Error: SI22 requires BCCH Extd!");
			} else {
				f_ensure_si_vec_contains_only(si_per_tc, 2, SYSTEM_INFORMATION_TYPE_22, true);
				f_ensure_si_vec_contains_only(si_per_tc, 6, SYSTEM_INFORMATION_TYPE_22, true);
			}
		}
	}


	function f_gsmtap_sample_si(GSMTAP_PT pt, float duration := 8.0) return SystemInformationVectorPerTc {
		timer T := duration;
		var SystemInformationVectorPerTc si_per_tc;
		var GSMTAP_RecvFrom rf;

		/* initialize all per-TC vectors empty */
		for (var integer i := 0; i < sizeof(si_per_tc); i := i + 1) {
			si_per_tc[i] := {};
		}

		/* flush all previous/buffered elements */
		pt.clear

		T.start;
		alt {
			[] pt.receive(t_recvfrom(GSMTAP_CHANNEL_BCCH)) -> value rf {
					var SystemInformation si := dec_SystemInformation(rf.msg.payload);
					var SystemInformationGsmtap sig := { rf.msg.header, si };
					var integer tc := f_gsm_compute_tc(rf.msg.header.frame_number);
					log("SI received at TC=", tc, ": ", si);
					/* append to the per-TC bucket */
					si_per_tc[tc] := si_per_tc[tc] & { sig };
					repeat;
				}
			[] pt.receive { repeat; };
			[] T.timeout { };
		}
		for (var integer i := 0; i < sizeof(si_per_tc); i := i + 1) {
			log(testcasename(), ": TC=", i, " has #of SI=", sizeof(si_per_tc[i]));
		}
		return si_per_tc;
	}

	function f_gsmtap_get_si(GSMTAP_PT pt, RrMessageType msg_type) return SystemInformation {
		timer T := 10.0;
		var GSMTAP_RecvFrom rf;
		var SystemInformation si;

		/* flush all previous/buffered elements */
		pt.clear

		T.start;
		alt {
			[] pt.receive(t_recvfrom(GSMTAP_CHANNEL_BCCH)) -> value rf {
					si := dec_SystemInformation(rf.msg.payload);
					if (si.header.message_type == msg_type) {
						return si;
					}
					repeat;
				}
			[] pt.receive { repeat; };
			[] T.timeout { testcase.stop("Error waiting for SI on GSMTAP"); };
		}
		return si;
	}

	function f_gsmtap_match_si(GSMTAP_PT pt, RrMessageType msg_type, template SystemInformation t) {
		var SystemInformation si := f_gsmtap_get_si(pt, msg_type);
		if (not match(si, t)) {
			setverdict(fail, "SI ", si, " doesn't match ", t);
			mtc.stop;
		} else {
			setverdict(pass);
		}
	}

	function f_init() runs on dummy_CT {
		if (initialized) {
			return;
		}
		/* GSMTAP initialization */
		map(self:GSMTAP, system:GSMTAP);
		IPL4_GSMTAP_CtrlFunct.f_IPL4_listen(GSMTAP, "0.0.0.0", GSMTAP_PORT, {udp := {}});

		/* VTY initialization */
		map(self:BSCVTY, system:BSCVTY);
		f_vty_set_prompts(BSCVTY)
		f_vty_transceive(BSCVTY, "enable");

		initialized := true;
	}

	testcase TC_si_default() runs on dummy_CT {
		var SystemInformationVectorPerTc si_per_tc;

		f_init();

		si_per_tc := f_gsmtap_sample_si(GSMTAP);
		f_validate_si_scheduling(si_cfg, si_per_tc);

		setverdict(pass);
	}

	testcase TC_si_sched_2bis() runs on dummy_CT {
		var SystemInformationVectorPerTc si_per_tc;
		f_init();

		/* Enable SI2bis + validate scheduling */
		si_cfg.si2bis_present := true;
		f_si_cfg_to_vty();
		si_per_tc := f_gsmtap_sample_si(GSMTAP);
		f_validate_si_scheduling(si_cfg, si_per_tc);

		/* cleanup */
		si_cfg.si2bis_present := false;
		f_si_cfg_to_vty();

		setverdict(pass);
	}

	testcase TC_si_sched_2ter() runs on dummy_CT {
		var SystemInformationVectorPerTc si_per_tc;
		f_init();

		/* Enable SI2ter + validate scheduling */
		si_cfg.si2ter_present := true;
		f_si_cfg_to_vty();
		si_per_tc := f_gsmtap_sample_si(GSMTAP);
		f_validate_si_scheduling(si_cfg, si_per_tc);

		/* cleanup */
		si_cfg.si2ter_present := false;
		f_si_cfg_to_vty();

		setverdict(pass);
	}

	testcase TC_si_sched_2ter_2bis() runs on dummy_CT {
		var SystemInformationVectorPerTc si_per_tc;
		f_init();

		/* Enable SI2bis + SI2ter + validate scheduling */
		si_cfg.si2bis_present := true;
		si_cfg.si2ter_present := true;
		f_si_cfg_to_vty();
		si_per_tc := f_gsmtap_sample_si(GSMTAP);
		f_validate_si_scheduling(si_cfg, si_per_tc);

		/* cleanup */
		si_cfg.si2bis_present := false;
		si_cfg.si2ter_present := false;
		f_si_cfg_to_vty();

		setverdict(pass);
	}

	testcase TC_si_sched_2quater() runs on dummy_CT {
		var SystemInformationVectorPerTc si_per_tc;
		f_init();

		/* Enable SI2quater + validate scheduling */
		si_cfg.si2quater_present := true;
		f_si_cfg_to_vty();

		/* cleanup */
		si_cfg.si2quater_present := false;
		f_si_cfg_to_vty();

		setverdict(pass);
	}

	testcase TC_si_sched_13() runs on dummy_CT {
		var SystemInformationVectorPerTc si_per_tc;
		f_init();

		/* Enable SI2ter + validate scheduling */
		si_cfg.si13_present := true;
		f_si_cfg_to_vty();
		si_per_tc := f_gsmtap_sample_si(GSMTAP);
		f_validate_si_scheduling(si_cfg, si_per_tc);

		/* cleanup */
		si_cfg.si13_present := false;
		f_si_cfg_to_vty();

		setverdict(pass);
	}

	testcase TC_si_sched_13_2bis_2ter_2quater() runs on dummy_CT {
		var SystemInformationVectorPerTc si_per_tc;
		f_init();

		si_cfg.si2bis_present := true;
		si_cfg.si2ter_present := true;
		si_cfg.si2quater_present := true;
		si_cfg.si13_present := true;
		f_si_cfg_to_vty();
		si_per_tc := f_gsmtap_sample_si(GSMTAP);
		f_validate_si_scheduling(si_cfg, si_per_tc);

		/* cleanup */
		si_cfg.si2bis_present := false;
		si_cfg.si2ter_present := false;
		si_cfg.si2quater_present := false;
		si_cfg.si13_present := false;
		f_si_cfg_to_vty();

		setverdict(pass);
	}

	function f_si_cfg_to_vty() runs on dummy_CT {
		if (si_cfg.si2bis_present) {
			f_vty_si_static(BSCVTY, 0, "2bis", c_si2bis);
		} else {
			f_vty_si_computed(BSCVTY, 0, "2bis");
		}
		if (si_cfg.si2ter_present) {
			f_vty_si_static(BSCVTY, 0, "2ter", c_si2ter);
		} else {
			f_vty_si_computed(BSCVTY, 0, "2ter");
		}
		if (si_cfg.si13_present) {
			f_vty_gprs_mode(BSCVTY, 0, "gprs");
		} else {
			f_vty_gprs_mode(BSCVTY, 0, "none");
		}
		if (si_cfg.si2quater_present) {
			f_vty_si2q_add_uarfcn(BSCVTY, 0, 23, 42);
		} else {
			f_vty_si2q_del_uarfcn(BSCVTY, 0, 23, 42);
		}
		/* for debugging */
		f_vty_transceive(BSCVTY, "write terminal");
		/* actually commit the changes from BSC -> BTS */
		f_vty_si_resend(BSCVTY, 0);
	}

	function f_vty_si_static(TELNETasp_PT pt, BtsNr bts, charstring si, octetstring bytes) {
		f_vty_enter_cfg_bts(pt, bts);
		f_vty_transceive(pt, "system-information " & si & " mode static");
		f_vty_transceive(pt, "system-information " & si & " static " & hex2str(oct2hex(bytes)));
		f_vty_transceive(pt, "end");
	}

	function f_vty_si_computed(TELNETasp_PT pt, BtsNr bts, charstring si) {
		f_vty_enter_cfg_bts(pt, bts);
		f_vty_transceive(pt, "system-information " & si & " mode computed");
		f_vty_transceive(pt, "end");
	}

	function f_vty_si_resend(TELNETasp_PT pt, BtsNr bts := 0) {
		f_vty_transceive(pt, "bts " & int2str(bts) & " resend-system-information");
		/* wait for 1s until changes propagate */
		timer T := 1.0;
		T.start;
		T.timeout;
	}

	function f_vty_gprs_mode(TELNETasp_PT pt, integer bts, BtsGprsMode mode) {
		f_vty_enter_cfg_bts(pt, bts);
		f_vty_transceive(pt, "gprs mode " & mode);
		f_vty_transceive(pt, "end");
	}

	function f_vty_si2q_add_uarfcn(TELNETasp_PT pt, BtsNr bts, UmtsArfcn uarfcn, UmtsScramblingCode sc, integer diversity := 0) {
		f_vty_enter_cfg_bts(pt, bts);
		f_vty_transceive(pt, "si2quater neighbor-list add uarfcn " & int2str(uarfcn) & " " & int2str(sc) & " " & int2str(diversity));
		f_vty_transceive(pt, "end");
	}

	function f_vty_si2q_del_uarfcn(TELNETasp_PT pt, BtsNr bts, UmtsArfcn uarfcn, UmtsScramblingCode sc) {
		f_vty_enter_cfg_bts(pt, bts);
		f_vty_transceive(pt, "si2quater neighbor-list del uarfcn " & int2str(uarfcn) & " " & int2str(sc));
		f_vty_transceive(pt, "end");
	}

	testcase TC_telnet() runs on dummy_CT {
		f_init();
		f_vty_transceive(BSCVTY, "show network")
		f_vty_enter_config(BSCVTY);
		f_vty_transceive(BSCVTY, "network")
		f_vty_transceive(BSCVTY, "bts 0")
		f_vty_transceive(BSCVTY, "end")
		setverdict(pass);
	}

	template SystemInformation t_SI_SI3 := {
		header := t_RrHeader(SYSTEM_INFORMATION_TYPE_3, ?),
		payload := { si3 := t_SI3 }
	}

	testcase TC_cellid() runs on dummy_CT {
		var CellIdentity cid := float2int(rnd() * 65535.0);
		var template SystemInformation t := t_SI_SI3;
		t.payload.si3.cell_id := cid;

		f_init();
		f_vty_enter_cfg_bts(BSCVTY, 0);
		f_vty_transceive(BSCVTY, "cell_identity " & int2str(cid));
		f_vty_transceive(BSCVTY, "end")
		f_vty_si_resend(BSCVTY, 0);

		f_gsmtap_match_si(GSMTAP, SYSTEM_INFORMATION_TYPE_3, t);
	}

	testcase TC_lac() runs on dummy_CT {
		var uint16_t lac := float2int(rnd() * 65535.0);

		var template SystemInformation t := t_SI_SI3;
		t.payload.si3.lai.lac := lac;

		f_init();
		f_vty_enter_cfg_bts(BSCVTY, 0);
		f_vty_transceive(BSCVTY, "location_area_code " & int2str(lac));
		f_vty_transceive(BSCVTY, "end")
		f_vty_si_resend(BSCVTY, 0);

		f_gsmtap_match_si(GSMTAP, SYSTEM_INFORMATION_TYPE_3, t);
	}

	testcase TC_rach_tx_int() runs on dummy_CT {
		var uint16_t rach_tx_int := float2int(rnd() * 15.0);

		var template SystemInformation t := t_SI_SI3;
		t.payload.si3.rach_control.tx_integer := int2bit(rach_tx_int, 4);

		f_init();
		f_vty_enter_cfg_bts(BSCVTY, 0);
		f_vty_transceive(BSCVTY, "rach tx integer " & int2str(rach_tx_int));
		f_vty_transceive(BSCVTY, "end")
		f_vty_si_resend(BSCVTY, 0);

		f_gsmtap_match_si(GSMTAP, SYSTEM_INFORMATION_TYPE_3, t);
	}

	testcase TC_rach_max_tx() runs on dummy_CT {
		var uint16_t r := float2int(rnd() * 3.0);
		const integer max_tx_map[4] := { 1, 2, 4, 7 };
		const RachCtrlPar_MR max_tx_map2[4] := { RACH_MAX_RETRANS_1,
							 RACH_MAX_RETRANS_2,
							 RACH_MAX_RETRANS_4,
							 RACH_MAX_RETRANS_7 };
		var template SystemInformation t := t_SI_SI3;
		t.payload.si3.rach_control.max_retrans := max_tx_map2[r];

		f_init();
		f_vty_enter_cfg_bts(BSCVTY, 0);
		f_vty_transceive(BSCVTY, "rach max transmission " & int2str(max_tx_map[r]));
		f_vty_transceive(BSCVTY, "end")
		f_vty_si_resend(BSCVTY, 0);

		f_gsmtap_match_si(GSMTAP, SYSTEM_INFORMATION_TYPE_3, t);
	}

	testcase TC_dtx_ul() runs on dummy_CT {
		var integer i := float2int(rnd() * 3.0);
		var template SystemInformation t := t_SI_SI3;
		var CellOptions_DTX dtx_map[3] := { MS_MAY_USE_UL_DTX,
						    MS_SHALL_USE_UL_DTX,
						    MS_SHALL_NOT_USE_UL_DTX };
		t.payload.si3.cell_options.dtx := dtx_map[i];

		f_init();
		f_vty_enter_cfg_bts(BSCVTY, 0);
		if (i == 0) {
			f_vty_transceive(BSCVTY, "dtx uplink");
		} else if (i == 1) {
			f_vty_transceive(BSCVTY, "dtx uplink force");
		} else {
			f_vty_transceive(BSCVTY, "no dtx uplink");
		}

		f_gsmtap_match_si(GSMTAP, SYSTEM_INFORMATION_TYPE_3, t);
	}

	testcase TC_attach() runs on dummy_CT {
		var integer i := float2int(rnd());
		var template SystemInformation t := t_SI_SI3;
		t.payload.si3.ctrl_chan_desc.att := int2bool(i);

		f_init();
		f_vty_enter_cfg_bts(BSCVTY, 0);
		f_vty_transceive(BSCVTY, "channel-descrption attach " & int2str(i));
		f_vty_transceive(BSCVTY, "end")
		f_vty_si_resend(BSCVTY, 0);

		f_gsmtap_match_si(GSMTAP, SYSTEM_INFORMATION_TYPE_3, t);
	}

	testcase TC_bs_pa_mfrms() runs on dummy_CT {
		var integer i := 2 + float2int(rnd() * 7.0);
		var template SystemInformation t := t_SI_SI3;
		t.payload.si3.ctrl_chan_desc.bs_pa_mfrms := i - 2;

		f_init();
		f_vty_enter_cfg_bts(BSCVTY, 0);
		f_vty_transceive(BSCVTY, "channel-descrption bs-pa-mfrms " & int2str(i));
		f_vty_transceive(BSCVTY, "end")
		f_vty_si_resend(BSCVTY, 0);

		f_gsmtap_match_si(GSMTAP, SYSTEM_INFORMATION_TYPE_3, t);
	}

	testcase TC_bs_ag_blks_res() runs on dummy_CT {
		var integer i := float2int(rnd() * 7.0);
		var template SystemInformation t := t_SI_SI3;
		t.payload.si3.ctrl_chan_desc.bs_ag_blks_res := i;

		f_init();
		f_vty_enter_cfg_bts(BSCVTY, 0);
		f_vty_transceive(BSCVTY, "channel-descrption bs-ag-blks-res " & int2str(i));
		f_vty_transceive(BSCVTY, "end")
		f_vty_si_resend(BSCVTY, 0);

		f_gsmtap_match_si(GSMTAP, SYSTEM_INFORMATION_TYPE_3, t);
	}

	testcase TC_radio_link_timeout() runs on dummy_CT {
		var integer i := float2int(rnd() * 15.0);
		var template SystemInformation t := t_SI_SI3;
		t.payload.si3.cell_options.radio_link_tout_div4 := i;

		f_init();
		f_vty_enter_cfg_bts(BSCVTY, 0);
		f_vty_transceive(BSCVTY, "radio-link-timeout " & int2str(4 + i*4));
		f_vty_transceive(BSCVTY, "end")
		f_vty_si_resend(BSCVTY, 0);

		f_gsmtap_match_si(GSMTAP, SYSTEM_INFORMATION_TYPE_3, t);
	}

	testcase TC_cell_resel_hyst() runs on dummy_CT {
		var integer i := float2int(rnd() * 7.0);
		var template SystemInformation t := t_SI_SI3;
		t.payload.si3.cell_sel_par.cell_resel_hyst_2dB := i;

		f_init();
		f_vty_enter_cfg_bts(BSCVTY, 0);
		f_vty_transceive(BSCVTY, "cell reselection hysteresis " & int2str(i*2));
		f_vty_transceive(BSCVTY, "end")
		f_vty_si_resend(BSCVTY, 0);

		f_gsmtap_match_si(GSMTAP, SYSTEM_INFORMATION_TYPE_3, t);
	}

	testcase TC_rxlev_acc_min() runs on dummy_CT {
		var integer i := float2int(rnd() * 63.0);
		var template SystemInformation t := t_SI_SI3;
		t.payload.si3.cell_sel_par.rxlev_access_min := i;

		f_init();
		f_vty_enter_cfg_bts(BSCVTY, 0);
		f_vty_transceive(BSCVTY, "rxlev access min " & int2str(i));
		f_vty_transceive(BSCVTY, "end")
		f_vty_si_resend(BSCVTY, 0);

		f_gsmtap_match_si(GSMTAP, SYSTEM_INFORMATION_TYPE_3, t);
	}

	testcase TC_neci() runs on dummy_CT {
		var integer i := float2int(rnd() * 1.0);
		var template SystemInformation t := t_SI_SI3;
		t.payload.si3.cell_sel_par.neci := int2bool(i);

		f_init();
		f_vty_enter_cfg_network(BSCVTY);
		f_vty_transceive(BSCVTY, "neci " & int2str(i));
		f_vty_transceive(BSCVTY, "end")
		f_vty_si_resend(BSCVTY, 0);

		f_gsmtap_match_si(GSMTAP, SYSTEM_INFORMATION_TYPE_3, t);
	}

	testcase TC_emerg_allowed() runs on dummy_CT {
		var integer i := float2int(rnd());
		var template SystemInformation t := t_SI_SI3;
		if (i == 1) {
			t.payload.si3.rach_control.acc := '?????0??????????'B;
		} else {
			t.payload.si3.rach_control.acc := '?????1??????????'B;
		}

		f_init();
		f_vty_enter_cfg_bts(BSCVTY, 0);
		f_vty_transceive(BSCVTY, "rach emergency call allowed " & int2str(i));
		f_vty_transceive(BSCVTY, "end")
		f_vty_si_resend(BSCVTY, 0);

		f_gsmtap_match_si(GSMTAP, SYSTEM_INFORMATION_TYPE_3, t);
	}

	/* TODO:
	* 	* don't only validate SI3 but check all other SI with same IE?
	*	* parse + validate rest octets somehow
	*	* validate contents/encoding of neighbor channel lists
	*	* validate si2quater sub-mux scheduling
	*/


	control {
		execute(TC_si1());
		execute(TC_telnet());
		execute(TC_si_default());
		execute(TC_si_sched_2bis());
		execute(TC_si_sched_2ter());
		execute(TC_si_sched_2ter_2bis());
		execute(TC_si_sched_2quater());
		execute(TC_si_sched_13());
		execute(TC_si_sched_13_2bis_2ter_2quater());
		execute(TC_neci());
		execute(TC_cell_resel_hyst());
		execute(TC_rxlev_acc_min());
		execute(TC_cellid());
		execute(TC_lac());
		execute(TC_rach_tx_int());
		execute(TC_rach_max_tx());
		execute(TC_attach());
		execute(TC_dtx_ul());
		execute(TC_emerg_allowed());
		execute(TC_bs_pa_mfrms());
		execute(TC_bs_ag_blks_res());
		execute(TC_radio_link_timeout());
	}
}