Update SI data structures and generation

To support segmented SI2quater as per 3GPP TS 44.018 we'll have to
support multiple SI messages (up to 16 for SI2q) for a given type in
contrast to existing 1:1 mapping:

* expand storage space to hold up to 16 SI messages (spec limit)
* add assertions for budget calculations
* generate multiple SI2q messages
* adjust SI2q-related tests
* use precise check for number of SIq messages instead of approximate
  estimation

Change-Id: Ic516ec9f0b821557d9461ae9f1c0afdd786f3b05
Related: OS#1660
diff --git a/openbsc/src/libbsc/bsc_init.c b/openbsc/src/libbsc/bsc_init.c
index 25f3fdc..b95c7b0 100644
--- a/openbsc/src/libbsc/bsc_init.c
+++ b/openbsc/src/libbsc/bsc_init.c
@@ -101,7 +101,7 @@
 static int rsl_si(struct gsm_bts_trx *trx, enum osmo_sysinfo_type i, int si_len)
 {
 	struct gsm_bts *bts = trx->bts;
-	int rc;
+	int rc, j;
 
 	DEBUGP(DRR, "SI%s: %s\n", get_value_string(osmo_sitype_strs, i),
 		osmo_hexdump(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN));
@@ -114,6 +114,10 @@
 		rc = rsl_sacch_filling(trx, osmo_sitype2rsl(i),
 				       GSM_BTS_SI(bts, i), si_len);
 		break;
+	case SYSINFO_TYPE_2quater:
+		for (j = 0; j <= bts->si2q_count; j++)
+			rc = rsl_bcch_info(trx, i, (const uint8_t *)GSM_BTS_SI2Q(bts, j), GSM_MACBLOCK_LEN);
+		break;
 	default:
 		rc = rsl_bcch_info(trx, osmo_sitype2rsl(i),
 				   GSM_BTS_SI(bts, i), si_len);
diff --git a/openbsc/src/libbsc/bsc_vty.c b/openbsc/src/libbsc/bsc_vty.c
index b05d3d9..2fc39ab 100644
--- a/openbsc/src/libbsc/bsc_vty.c
+++ b/openbsc/src/libbsc/bsc_vty.c
@@ -2774,6 +2774,7 @@
 	return CMD_SUCCESS;
 }
 
+/* help text should be kept in sync with EARFCN_*_INVALID defines */
 DEFUN(cfg_bts_si2quater_neigh_add, cfg_bts_si2quater_neigh_add_cmd,
       "si2quater neighbor-list add earfcn <0-65535> thresh-hi <0-31> "
       "thresh-lo <0-32> prio <0-8> qrxlv <0-32> meas <0-8>",
@@ -2791,54 +2792,37 @@
 	uint16_t arfcn = atoi(argv[0]);
 	uint8_t thresh_hi = atoi(argv[1]), thresh_lo = atoi(argv[2]),
 		prio = atoi(argv[3]), qrx = atoi(argv[4]), meas = atoi(argv[5]);
-	int r = osmo_earfcn_add(e, arfcn,
-				(meas < 8) ? meas : OSMO_EARFCN_MEAS_INVALID);
+	int r = bts_earfcn_add(bts, arfcn, thresh_hi, thresh_lo, prio, qrx, meas);
 
-	if (r < 0) {
-		vty_out(vty, "Unable to add ARFCN %u: %s%s", arfcn, strerror(-r),
-			VTY_NEWLINE);
-		return CMD_WARNING;
+	switch (r) {
+	case 1:
+		vty_out(vty, "Warning: multiple threshold-high are not supported, overriding with %u%s",
+			thresh_hi, VTY_NEWLINE);
+		break;
+	case EARFCN_THRESH_LOW_INVALID:
+		vty_out(vty, "Warning: multiple threshold-low are not supported, overriding with %u%s",
+			thresh_lo, VTY_NEWLINE);
+		break;
+	case EARFCN_QRXLV_INVALID + 1:
+		vty_out(vty, "Warning: multiple QRXLEVMIN are not supported, overriding with %u%s",
+			qrx, VTY_NEWLINE);
+		break;
+	case EARFCN_PRIO_INVALID:
+		vty_out(vty, "Warning: multiple priorities are not supported, overriding with %u%s",
+			prio, VTY_NEWLINE);
+		break;
+	default:
+		if (r < 0) {
+			vty_out(vty, "Unable to add ARFCN %u: %s%s", arfcn, strerror(-r), VTY_NEWLINE);
+			return CMD_WARNING;
+		}
 	}
 
-	if (e->thresh_hi && thresh_hi != e->thresh_hi)
-		vty_out(vty, "Warning: multiple threshold-high are not "
-			"supported, overriding previous threshold %u%s",
-			e->thresh_hi, VTY_NEWLINE);
-
-	e->thresh_hi = thresh_hi;
-
-	if (thresh_lo != 32) {
-		if (e->thresh_lo_valid && e->thresh_lo != thresh_lo)
-			vty_out(vty, "Warning: multiple threshold-low are not "
-				"supported, overriding previous threshold %u%s",
-				e->thresh_lo, VTY_NEWLINE);
-		e->thresh_lo = thresh_lo;
-		e->thresh_lo_valid = true;
-	}
-
-	if (qrx != 32) {
-		if (e->qrxlm_valid && e->qrxlm != qrx)
-			vty_out(vty, "Warning: multiple QRXLEVMIN are not "
-				"supported, overriding previous value %u%s",
-				e->qrxlm, VTY_NEWLINE);
-		e->qrxlm = qrx;
-		e->qrxlm_valid = true;
-	}
-
-	if (prio != 8) {
-		if (e->prio_valid && e->prio != prio)
-			vty_out(vty, "Warning: multiple priorities are not "
-				"supported, overriding previous value %u%s",
-				e->prio, VTY_NEWLINE);
-		e->prio = prio;
-		e->prio_valid = true;
-	}
-
-	if (si2q_num(bts) < 2) /* FIXME: use SI2Q_MAX_NUM */
+	if (si2q_num(bts) <= SI2Q_MAX_NUM)
 		return CMD_SUCCESS;
 
 	vty_out(vty, "Warning: not enough space in SI2quater (%u/%u used) for a given EARFCN %u%s",
-		bts->si2q_count, 2, arfcn, VTY_NEWLINE); /* FIXME: use SI2Q_MAX_NUM */
+		bts->si2q_count, SI2Q_MAX_NUM, arfcn, VTY_NEWLINE);
 	osmo_earfcn_del(e, arfcn);
 
 	return CMD_WARNING;
@@ -2877,16 +2861,14 @@
 
 	switch(bts_uarfcn_add(bts, arfcn, scramble, atoi(argv[2]))) {
 	case -ENOMEM:
-		vty_out(vty, "Unable to add arfcn: max number of UARFCNs (%u) "
-			"reached%s", MAX_EARFCN_LIST, VTY_NEWLINE);
+		vty_out(vty, "Unable to add UARFCN: max number of UARFCNs (%u) reached%s", MAX_EARFCN_LIST, VTY_NEWLINE);
 		return CMD_WARNING;
 	case -ENOSPC:
-		vty_out(vty, "Warning: not enough space in si2quater for a "
-			"given arfcn%s", VTY_NEWLINE);
+		vty_out(vty, "Warning: not enough space in SI2quater for a given UARFCN (%u, %u)%s",
+			arfcn, scramble, VTY_NEWLINE);
 		return CMD_WARNING;
 	case -EADDRINUSE:
-		vty_out(vty, "Unable to add arfcn: (%u, %u) is already added%s",
-			arfcn, scramble, VTY_NEWLINE);
+		vty_out(vty, "Unable to add UARFCN: (%u, %u) is already added%s", arfcn, scramble, VTY_NEWLINE);
 		return CMD_WARNING;
 	}
 
diff --git a/openbsc/src/libbsc/rest_octets.c b/openbsc/src/libbsc/rest_octets.c
index a6fdf46..fdab70a 100644
--- a/openbsc/src/libbsc/rest_octets.c
+++ b/openbsc/src/libbsc/rest_octets.c
@@ -65,6 +65,12 @@
 	unsigned i, skip = 0;
 	size_t offset = bts->e_offset;
 	uint8_t rem = budget - 6, earfcn_budget; /* account for mandatory stop bit and THRESH_E-UTRAN_high */
+
+	if (budget <= 6)
+		return;
+
+	OSMO_ASSERT(budget <= SI2Q_MAX_LEN);
+
 	/* first we have to properly adjust budget requirements */
 	if (e->prio_valid) /* E-UTRAN_PRIORITY: 3GPP TS 45.008*/
 		rem -= 4;
@@ -87,16 +93,17 @@
 			if (skip < offset) {
 				skip++; /* ignore EARFCNs added on previous calls */
 			} else {
-				earfcn_budget = 17; /* computer budget per-EARFCN */
+				earfcn_budget = 17; /* compute budget per-EARFCN */
 				if (OSMO_EARFCN_MEAS_INVALID == e->meas_bw[i])
 					earfcn_budget++;
 				else
 					earfcn_budget += 4;
 
-				if (rem - earfcn_budget < 0) {
+				if (rem - earfcn_budget < 0)
 					break;
-				} else {
+				else {
 					bts->e_offset++;
+					rem -= earfcn_budget;
 					bitvec_set_bit(bv, 1); /* EARFCN: */
 					bitvec_set_uint(bv, e->arfcn[i], 16);
 
@@ -143,6 +150,12 @@
 
 static inline void append_earfcn(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget)
 {
+	int rem = budget - 25;
+	if (rem <= 0)
+		return;
+
+	OSMO_ASSERT(budget <= SI2Q_MAX_LEN);
+
 	/* Additions in Rel-5: */
 	bitvec_set_bit(bv, H);
 	/* No 3G Additional Measurement Param. Descr. */
@@ -191,7 +204,7 @@
 	bitvec_set_bit(bv, 1);
 
 	/* N. B: 25 bits are set in append_earfcn() - keep it in sync with budget adjustment below: */
-	append_eutran_neib_cell(bv, bts, budget - 25);
+	append_eutran_neib_cell(bv, bts, rem);
 
 	/* stop bit - end of Repeated E-UTRAN Neighbour Cells sequence: */
 	bitvec_set_bit(bv, 0);
@@ -267,7 +280,12 @@
 	const uint16_t *u = bts->si_common.data.uarfcn_list, *sc = bts->si_common.data.scramble_list;
 	int i, j, k, rc, st = 0, a[bts->si_common.uarfcn_length];
 	uint16_t cu = u[bts->u_offset]; /* caller ensures that length is positive */
-	uint8_t rem = budget - 7; /* account for constant bits right away */
+	uint8_t rem = budget - 7, offset_diff; /* account for constant bits right away */
+
+	OSMO_ASSERT(budget <= SI2Q_MAX_LEN);
+
+	if (budget <= 7)
+		return -ENOMEM;
 
 	/* 3G Neighbour Cell Description */
 	bitvec_set_bit(bv, 1);
@@ -282,20 +300,22 @@
 	bitvec_set_bit(bv, 0);
 
 	for (i = bts->u_offset; i < bts->si_common.uarfcn_length; i++) {
-		for (j = st, k = 0; j < i; j++)
+		offset_diff = 0;
+		for (j = st, k = 0; j < i; j++) {
 			a[k++] = sc[j]; /* copy corresponding SCs */
-
+			offset_diff++; /* compute proper offset step */
+		}
 		if (u[i] != cu) { /* we've reached new UARFCN */
 			rc = append_utran_fdd_length(cu, a, bts->si_common.uarfcn_length, k);
 			if (rc < 0) { /* estimate bit length requirements */
 				return rc;
 			}
 
-			if (rem - rc < 0) {
+			if (rem - rc <= 0)
 				break; /* we have ran out of budget in current SI2q */
-			} else {
+			else {
 				rem -= append_utran_fdd(bv, cu, a, k);
-				bts->u_offset++;
+				bts->u_offset += offset_diff;
 			}
 			cu = u[i];
 			st = i; /* update start position */
@@ -303,9 +323,11 @@
 	}
 
 	if (rem > 22) {	/* add last UARFCN not covered by previous cycle if it could possibly fit into budget */
-		for (i = st, k = 0; i < bts->si_common.uarfcn_length; i++)
+		offset_diff = 0;
+		for (i = st, k = 0; i < bts->si_common.uarfcn_length; i++) {
 			a[k++] = sc[i];
-
+			offset_diff++;
+		}
 		rc = append_utran_fdd_length(cu, a, bts->si_common.uarfcn_length, k);
 		if (rc < 0) {
 			return rc;
@@ -313,7 +335,7 @@
 
 		if (rem - rc >= 0) {
 			rem -= append_utran_fdd(bv, cu, a, k);
-			bts->u_offset++;
+			bts->u_offset += offset_diff;
 		}
 	}
 
@@ -331,6 +353,10 @@
 {
 	int rc;
 	struct bitvec bv;
+
+	if (bts->si2q_count < bts->si2q_index)
+		return -EINVAL;
+
 	bv.data = data;
 	bv.data_len = 20;
 	bitvec_zero(&bv);
@@ -362,34 +388,28 @@
 	/* No extension (length) */
 	bitvec_set_bit(&bv, 0);
 
-	if (bts->si_common.uarfcn_length) {
-		/* Even if we do not append EARFCN we still need to set 3 bits */
-		rc = append_uarfcns(&bv, bts, SI2Q_MAX_LEN - (bv.cur_bit + 3));
+	rc = SI2Q_MAX_LEN - (bv.cur_bit + 3);
+	if (rc > 0 && bts->si_common.uarfcn_length - bts->u_offset > 0) {
+		rc = append_uarfcns(&bv, bts, rc);
 		if (rc < 0) {
-			LOGP(DRR, LOGL_ERROR, "SI2quater: failed to append %zu UARFCNs due to range encoding failure: %s\n",
-			     bts->si_common.uarfcn_length, strerror(-rc));
+			LOGP(DRR, LOGL_ERROR, "SI2quater [%u/%u]: failed to append %zu UARFCNs due to range encoding "
+			     "failure: %s\n",
+			     bts->si2q_index, bts->si2q_count, bts->si_common.uarfcn_length, strerror(-rc));
 			return rc;
 		}
-	} else { /* No 3G Neighbour Cell Description */
+	} else /* No 3G Neighbour Cell Description */
 		bitvec_set_bit(&bv, 0);
-	}
 
 	/* No 3G Measurement Parameters Description */
 	bitvec_set_bit(&bv, 0);
 	/* No GPRS_3G_MEASUREMENT Parameters Descr. */
 	bitvec_set_bit(&bv, 0);
 
-	if (si2q_earfcn_count(&bts->si_common.si2quater_neigh_list)) {
-		append_earfcn(&bv, bts, SI2Q_MAX_LEN - bv.cur_bit);
-
-		/* FIXME: remove following check once multiple SI2q are properly supported */
-		if ((bts->e_offset != si2q_earfcn_count(&bts->si_common.si2quater_neigh_list)) ||
-		    si2q_earfcn_count(&bts->si_common.si2quater_neigh_list) > 5)
-			return -ENOMEM;
-	} else {
-		/* No Additions in Rel-5: */
+	rc = SI2Q_MAX_LEN - bv.cur_bit;
+	if (rc > 0 && si2q_earfcn_count(&bts->si_common.si2quater_neigh_list) - bts->e_offset > 0)
+		append_earfcn(&bv, bts, rc);
+	else /* No Additions in Rel-5: */
 		bitvec_set_bit(&bv, L);
-	}
 
 	bitvec_spare_padding(&bv, (bv.data_len * 8) - 1);
 	return bv.data_len;
diff --git a/openbsc/src/libbsc/system_information.c b/openbsc/src/libbsc/system_information.c
index a074a78..dcabbbd 100644
--- a/openbsc/src/libbsc/system_information.c
+++ b/openbsc/src/libbsc/system_information.c
@@ -122,50 +122,73 @@
 	}
 }
 
-static inline unsigned earfcn_size(const struct gsm_bts *bts)
+size_t si2q_earfcn_count(const struct osmo_earfcn_si2q *e)
 {
-	const struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list; /* EARFCN */
+	unsigned i, ret = 0;
 
-	/* account for all the constant bits in append_earfcn() */
-	return 25 + osmo_earfcn_bit_size_ext(e, bts->e_offset);
+	if (!e)
+		return 0;
+
+	for (i = 0; i < e->length; i++)
+		if (e->arfcn[i] != OSMO_EARFCN_INVALID)
+			ret++;
+
+	return ret;
 }
 
-static inline unsigned uarfcn_size(const struct gsm_bts *bts)
+/* generate SI2quater messages, return rest octets length of last generated message or negative error code */
+static int make_si2quaters(struct gsm_bts *bts, bool counting)
 {
-	const uint16_t *u = bts->si_common.data.uarfcn_list;
-	uint16_t cu = u[bts->u_offset]; /* UARFCN */
-	/* account for all the constant bits in append_uarfcns() */
-	unsigned s = 7, append = 22, r = 0, i, st = 0, j, k;
+	int rc;
+	bool memory_exceeded = true;
+	struct gsm48_system_information_type_2quater *si2q;
 
-	for (i = bts->u_offset; i < bts->si_common.uarfcn_length; i++) {
-		for (j = st, k = 0; j < i; j++, k++);
-		if (u[i] != cu) { /* we've reached new UARFCN */
-			r += (append + range1024_p(k));
-			cu = u[i];
-			st = i; /* update start position */
+	for (bts->si2q_index = 0; bts->si2q_index < SI2Q_MAX_NUM; bts->si2q_index++) {
+		si2q = GSM_BTS_SI2Q(bts, bts->si2q_index);
+		if (counting) { /* that's legitimate if we're called for counting purpose: */
+			if (bts->si2q_count < bts->si2q_index)
+				bts->si2q_count = bts->si2q_index;
+		} else {
+			memset(si2q, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+			si2q->header.l2_plen = GSM48_LEN2PLEN(22);
+			si2q->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+			si2q->header.skip_indicator = 0;
+			si2q->header.system_information = GSM48_MT_RR_SYSINFO_2quater;
+		}
+
+		rc = rest_octets_si2quater(si2q->rest_octets, bts);
+		if (rc < 0)
+			return rc;
+
+		if (bts->u_offset >= bts->si_common.uarfcn_length &&
+		    bts->e_offset >= si2q_earfcn_count(&bts->si_common.si2quater_neigh_list)) {
+			memory_exceeded = false;
+			break;
 		}
 	}
 
-	/* add last UARFCN not covered by previous cycle */
-	for (i = st, k = 0; i < bts->si_common.uarfcn_length; i++, k++);
+	if (memory_exceeded)
+		return -ENOMEM;
 
-	return s + r + append + range1024_p(k);
+	return rc;
 }
 
+/* we generate SI2q rest octets twice to get proper estimation but it's one time cost anyway */
 uint8_t si2q_num(struct gsm_bts *bts)
 {
-	size_t est, e_sz = 1, u_sz = 1;
+	int rc = make_si2quaters(bts, true);
+	uint8_t num = bts->si2q_index + 1; /* number of SI2quater messages */
 
-	if (&bts->si_common.si2quater_neigh_list) /* EARFCN */
-		e_sz = earfcn_size(bts);
+	/* N. B: si2q_num() should NEVER be called during actualSI2q rest octets generation
+	   we're not re-entrant because of the following code: */
+	bts->u_offset = 0;
+	bts->e_offset = 0;
 
-	if (bts->si_common.uarfcn_length) /* UARFCN */
-		u_sz = uarfcn_size(bts);
+	if (rc < 0)
+		return 0xFF; /* return impossible index as an indicator of error in generating SI2quater */
 
-	/* 2 bits are used in between UARFCN and EARFCN structs */
-	est = 1 + (e_sz + u_sz) / (SI2Q_MAX_LEN - (SI2Q_MIN_LEN + 2));
-
-	return est;
+	return num;
 }
 
 /* 3GPP TS 44.018, Table 9.1.54.1 - prepend diversity bit to scrambling code */
@@ -176,6 +199,44 @@
 	return scramble;
 }
 
+int bts_earfcn_add(struct gsm_bts *bts, uint16_t earfcn, uint8_t thresh_hi, uint8_t thresh_lo, uint8_t prio,
+		   uint8_t qrx, uint8_t meas_bw)
+{
+	struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+	int r = osmo_earfcn_add(e, earfcn, (meas_bw < EARFCN_MEAS_BW_INVALID) ? meas_bw : OSMO_EARFCN_MEAS_INVALID);
+
+	if (r < 0)
+		return r;
+
+	if (e->thresh_hi && thresh_hi != e->thresh_hi)
+		r = 1;
+
+	e->thresh_hi = thresh_hi;
+
+	if (thresh_lo != EARFCN_THRESH_LOW_INVALID) {
+		if (e->thresh_lo_valid && e->thresh_lo != thresh_lo)
+			r = EARFCN_THRESH_LOW_INVALID;
+		e->thresh_lo = thresh_lo;
+		e->thresh_lo_valid = true;
+	}
+
+	if (qrx != EARFCN_QRXLV_INVALID) {
+		if (e->qrxlm_valid && e->qrxlm != qrx)
+			r = EARFCN_QRXLV_INVALID + 1;
+		e->qrxlm = qrx;
+		e->qrxlm_valid = true;
+	}
+
+	if (prio != EARFCN_PRIO_INVALID) {
+		if (e->prio_valid && e->prio != prio)
+			r = EARFCN_PRIO_INVALID;
+		e->prio = prio;
+		e->prio_valid = true;
+	}
+
+	return r;
+}
+
 int bts_uarfcn_del(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble)
 {
 	uint16_t sc0 = encode_fdd(scramble, false), sc1 = encode_fdd(scramble, true),
@@ -237,8 +298,10 @@
 	scl[k] = scr;
 	bts->si_common.uarfcn_length++;
 
-	if (si2q_num(bts) < 2) /* FIXME: use SI2Q_MAX_NUM */
+	if (si2q_num(bts) <= SI2Q_MAX_NUM) {
+		bts->si2q_count = si2q_num(bts) - 1;
 		return 0;
+	}
 
 	bts_uarfcn_del(bts, arfcn, scramble);
 	return -ENOSPC;
@@ -689,39 +752,26 @@
 	return false;
 }
 
-size_t si2q_earfcn_count(const struct osmo_earfcn_si2q *e)
-{
-	unsigned i, ret = 0;
-
-	if (!e)
-		return 0;
-
-	for (i = 0; i < e->length; i++)
-		if (e->arfcn[i] != OSMO_EARFCN_INVALID)
-			ret++;
-
-	return ret;
-}
-
 static int generate_si2quater(enum osmo_sysinfo_type t, struct gsm_bts *bts)
 {
 	int rc;
-	struct gsm48_system_information_type_2quater *si2q = GSM_BTS_SI2Q(bts);
+	struct gsm48_system_information_type_2quater *si2q;
 
 	if (si2quater_not_needed(bts)) /* generate rest_octets for SI2q only when necessary */
 		return GSM_MACBLOCK_LEN;
 
-	memset(si2q, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+	bts->u_offset = 0;
+	bts->e_offset = 0;
+	bts->si2q_index = 0;
+	bts->si2q_count = si2q_num(bts) - 1;
 
-	si2q->header.l2_plen = GSM48_LEN2PLEN(22);
-	si2q->header.rr_protocol_discriminator = GSM48_PDISC_RR;
-	si2q->header.skip_indicator = 0;
-	si2q->header.system_information = GSM48_MT_RR_SYSINFO_2quater;
-
-	rc = rest_octets_si2quater(si2q->rest_octets, bts);
+	rc = make_si2quaters(bts, false);
 	if (rc < 0)
 		return rc;
 
+	OSMO_ASSERT(bts->si2q_count == bts->si2q_index);
+	OSMO_ASSERT(bts->si2q_count <= SI2Q_MAX_NUM);
+
 	return sizeof(*si2q) + rc;
 }