Add osmo_gsm48_si1ro_nch_pos_{encode,decode} functions

These functions encode/decode the NCH position field within the SI1
rest octets.  This is used within ASCI (VBS/VGCS).

Change-Id: I24a0095ac6eee0197f9d9ef9895c7795df6cdc49
Related: OS#5781
diff --git a/include/osmocom/gsm/gsm48_rest_octets.h b/include/osmocom/gsm/gsm48_rest_octets.h
index c8d63b2..cdb2e80 100644
--- a/include/osmocom/gsm/gsm48_rest_octets.h
+++ b/include/osmocom/gsm/gsm48_rest_octets.h
@@ -13,6 +13,9 @@
 
 /* generate SI1 rest octets */
 int osmo_gsm48_rest_octets_si1_encode(uint8_t *data, uint8_t *nch_pos, int is1800_net);
+int osmo_gsm48_si1ro_nch_pos_decode(uint8_t value, uint8_t *num_blocks, uint8_t *first_block);
+int osmo_gsm48_si1ro_nch_pos_encode(uint8_t num_blocks, uint8_t first_block);
+
 int osmo_gsm48_rest_octets_si2quater_encode(uint8_t *data, uint8_t si2q_index, uint8_t si2q_count,
 					    const uint16_t *uarfcn_list, size_t *u_offset,
 					    size_t uarfcn_length, uint16_t *scramble_list,
diff --git a/src/gsm/gsm48_rest_octets.c b/src/gsm/gsm48_rest_octets.c
index 77fd349..b63c63f 100644
--- a/src/gsm/gsm48_rest_octets.c
+++ b/src/gsm/gsm48_rest_octets.c
@@ -59,6 +59,82 @@
 	return bv.data_len;
 }
 
+struct nch_pos {
+	uint8_t num_blocks;
+	uint8_t first_block;
+};
+
+/* 3GPP TS 44.010 Table 10.5.2.32.1b */
+static const struct nch_pos si1ro_nch_positions[] = {
+	[0x00] = {1,  0},
+	[0x01] = {1,  1},
+	[0x02] = {1,  2},
+	[0x03] = {1,  3},
+	[0x04] = {1,  4},
+	[0x05] = {1,  5},
+	[0x06] = {1,  6},
+
+	[0x07] = {2,  0},
+	[0x08] = {2,  1},
+	[0x08] = {2,  2},
+	[0x0a] = {2,  3},
+	[0x0b] = {2,  4},
+	[0x0c] = {2,  5},
+
+	[0x0d] = {3,  0},
+	[0x0e] = {3,  1},
+	[0x0f] = {3,  2},
+	[0x10] = {3,  3},
+	[0x11] = {3,  4},
+
+	[0x12] = {4,  0},
+	[0x13] = {4,  1},
+	[0x14] = {4,  2},
+	[0x15] = {4,  3},
+
+	[0x16] = {5,  0},
+	[0x17] = {5,  1},
+	[0x18] = {5,  2},
+
+	[0x19] = {6,  0},
+	[0x1a] = {6,  1},
+
+	[0x1b] = {7,  0},
+};
+
+/*! Decode the 5-bit 'NCH position' field within SI1 Rest Octets.
+ *  \param[in] value 5-bit value from SI1 rest octets
+ *  \param[out] num_blocks Number of CCCH used for NCH
+ *  \param[out] first_block First CCCH block used for NCH
+ *  \returns 0 on success; negative in case of error */
+int osmo_gsm48_si1ro_nch_pos_decode(uint8_t value, uint8_t *num_blocks, uint8_t *first_block)
+{
+	if (value >= ARRAY_SIZE(si1ro_nch_positions))
+		return -EINVAL;
+
+	*num_blocks = si1ro_nch_positions[value].num_blocks;
+	*first_block = si1ro_nch_positions[value].first_block;
+
+	return 0;
+}
+
+/*! Encode the 5-bit 'NCH position' field within SI1 Rest Octets.
+ *  \param[in] num_blocks Number of CCCH used for NCH
+ *  \param[in] first_block First CCCH block used for NCH
+ *  \returns 5-bit value for SI1 rest octets on success; negative in case of error */
+int osmo_gsm48_si1ro_nch_pos_encode(uint8_t num_blocks, uint8_t first_block)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(si1ro_nch_positions); i++) {
+		if (si1ro_nch_positions[i].num_blocks == num_blocks &&
+		    si1ro_nch_positions[i].first_block == first_block) {
+			return i;
+		}
+	}
+	return -EINVAL;
+}
+
 /* Append Repeated E-UTRAN Neighbour Cell to bitvec: see 3GPP TS 44.018 Table 10.5.2.33b.1 */
 static inline bool append_eutran_neib_cell(struct bitvec *bv, const struct osmo_earfcn_si2q *e, size_t *e_offset,
 					   uint8_t budget)
diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map
index 0018e1c..003d6e8 100644
--- a/src/gsm/libosmogsm.map
+++ b/src/gsm/libosmogsm.map
@@ -336,6 +336,8 @@
 osmo_gsm48_rest_octets_si13_encode;
 osmo_gsm48_rest_octets_si3_decode;
 osmo_gsm48_rest_octets_si4_decode;
+osmo_gsm48_si1ro_nch_pos_encode;
+osmo_gsm48_si1ro_nch_pos_decode;
 gsm48_rr_short_pd_msg_name;
 gsm48_rr_msg_name;
 gsm48_cc_state_name;