sysinfo: Enforce "1 out of 4" kind of scheduling requirements
diff --git a/sysinfo/Test.ttcn b/sysinfo/Test.ttcn
index 81bbdd5..4e3186f 100644
--- a/sysinfo/Test.ttcn
+++ b/sysinfo/Test.ttcn
@@ -125,6 +125,14 @@
 		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)) {
+			log("Fail: No ", key, " in TC=", tc, "!");
+			setverdict(fail);
+		}
+	}
+
 	/* 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) {
@@ -136,11 +144,56 @@
 		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)) {
-			log("Fail: No ", key, " in TC=", tc, "!");
+	/* 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) {
+			log("Error: Insufficient SI in array");
+			setverdict(fail);
+			return false;
+		}
+		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)) {
+			log("Fail: Not ", n, "/", m, " of ", key, " in TC=", tc, "!");
+			setverdict(fail);
+		}
+	}
+
+	/* 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)) {
+			log("Fail: Not all ", key, " in TC=", tc, "!");
 			setverdict(fail);
 		}
 	}
@@ -178,7 +231,8 @@
 			 * 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);
-			/* FIXME: make sure *ALL* contain SI1 */
+			/* 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 */
@@ -197,7 +251,7 @@
 			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(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_2ter); //FIXME 1/4
+			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) {
@@ -225,10 +279,10 @@
 				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(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_2quater); // FIXME 1/4
+					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(si_per_tc, 5, SYSTEM_INFORMATION_TYPE_2quater, true); // FIXME: 1/4
+				f_ensure_si_vec_contains_n_of_m(si_per_tc, 5, SYSTEM_INFORMATION_TYPE_2quater, true, 1, 4);
 			}
 		}
 		if (cfg.si9_present) {
@@ -244,7 +298,7 @@
 			 * 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(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_13); // FIXME 1/4
+				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);
 			}
@@ -287,9 +341,9 @@
 			 * 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(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_15); // FIXME 1/4
+				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(si_per_tc, 1, SYSTEM_INFORMATION_TYPE_15, true); // FIXME 1/4
+				f_ensure_si_vec_contains_n_of_m(si_per_tc, 1, SYSTEM_INFORMATION_TYPE_15, true, 1, 4);
 			}
 		}
 		if (cfg.si13alt_present) {
@@ -307,7 +361,7 @@
 				setverdict(fail, "Cannot have SI13alt and SI13");
 			}
 			if (not cfg.bcch_extended) {
-				f_ensure_si_vec_contains(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_13alt); // FIXME 1/4
+				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);
 			}
@@ -319,9 +373,9 @@
 			 * 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(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_2n); // FIXME 1/4
+				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(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_2n, true); // FIXME 2/4
+				f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_2n, true, 2, 4);
 			}
 		}
 		if (cfg.si21_present) {
@@ -331,9 +385,9 @@
 			 * 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(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_21); // FIXME 1/4
+				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(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_21, true); // FIXME 1/4
+				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!");
 				}
@@ -353,12 +407,15 @@
 			}
 			if (not cfg.bcch_extended) {
 				setverdict(fail, "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 := 3.0) return SystemInformationVectorPerTc {
+	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;
@@ -385,6 +442,9 @@
 			[] 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;
 	}
 
@@ -409,7 +469,6 @@
 		f_init();
 
 		si_per_tc := f_gsmtap_sample_si(GSMTAP);
-		log("SI per TC: ", si_per_tc);
 		f_validate_si_scheduling(si_cfg, si_per_tc);
 
 		setverdict(pass);