improve API for osmo_routing_area_id

Code review for [1] has asked for providing proper API for struct
osmo_routing_area_id.

For historical reasons, we have struct gprs_ra_id and
struct osmo_routing_area_id serving the exact same purpose: represent a
decoded 3GPP TS 24.008 ยง 10.5.5.15 Routing area identification.

The "better" one is struct osmo_routing_area_id: it allows using API
like osmo_plmn_cmp(), because it is made up of meaningful sub-structs.

Implement de/coding using the functions already available for the
sub-struct osmo_location_area_id, and simply add the RAC.

Add a test in gsm0408_test.c.

Note that other utility functions are already available for struct
osmo_routing_area_id: osmo_rai_name2(), osmo_rai_cmp().

There is no real need to deprecate struct gprs_ra_id, because there is
not really anything wrong with it. It just isn't as well integrated with
other utility API as struct osmo_routing_area_id is. Just add comments.

[1] osmo-hnbgw.git:
    cnpool: extract Mobile Identity from RANAP payload
    https://gerrit.osmocom.org/c/osmo-hnbgw/+/33133
    I373d665c9684b607207f68094188eab63209db51

Change-Id: Ic5e0406d9e20b0d4e1372fa30ba11a1e69f5cc94
diff --git a/include/osmocom/gsm/gsm48.h b/include/osmocom/gsm/gsm48.h
index 8323c72..00fb6f4 100644
--- a/include/osmocom/gsm/gsm48.h
+++ b/include/osmocom/gsm/gsm48.h
@@ -20,7 +20,9 @@
  * To mark an invalid / unset MNC, this value shall be used. */
 #define GSM_MCC_MNC_INVALID 0xFFFF
 
-/* A parsed GPRS routing area */
+/* A parsed GPRS routing area.
+ * Preferably use struct osmo_routing_area_id, it is better integrated with API like osmo_plmn_cmp().
+ */
 struct gprs_ra_id {
 	uint16_t	mcc;
 	uint16_t	mnc;
@@ -104,6 +106,9 @@
 int osmo_mobile_identity_encode_msgb(struct msgb *msg, const struct osmo_mobile_identity *mi, bool allow_hex);
 
 /* Parse Routeing Area Identifier */
+int osmo_routing_area_id_decode(struct osmo_routing_area_id *dst, const uint8_t *ra_data, size_t ra_data_len);
+int osmo_routing_area_id_encode_buf(uint8_t *buf, size_t buflen, const struct osmo_routing_area_id *src);
+int osmo_routing_area_id_encode_msgb(struct msgb *msg, const struct osmo_routing_area_id *src);
 void gsm48_parse_ra(struct gprs_ra_id *raid, const uint8_t *buf);
 void gsm48_encode_ra(struct gsm48_ra_id *out, const struct gprs_ra_id *raid);
 int gsm48_construct_ra(uint8_t *buf, const struct gprs_ra_id *raid) OSMO_DEPRECATED("Use gsm48_encode_ra() instead");
diff --git a/src/gsm/gsm48.c b/src/gsm/gsm48.c
index df455ac..53e0989 100644
--- a/src/gsm/gsm48.c
+++ b/src/gsm/gsm48.c
@@ -1334,7 +1334,64 @@
 	return 1;
 }
 
-/*! Parse TS 04.08 Routing Area Identifier
+/*! Decode to struct osmo_routing_area_id from a 3GPP TS 24.008 § 10.5.5.15 Routing area identification.
+ * \param[out] dst  Store the decoded result here.
+ * \param[in] ra_data  The start of a Routing Area ID in encoded form, to be decoded.
+ * \param[in] ra_data_len  Buffer size available to read from at *ra_data.
+ * \return the number of decoded bytes on success, or negative on error (if the input buffer is too small).
+ */
+int osmo_routing_area_id_decode(struct osmo_routing_area_id *dst, const uint8_t *ra_data, size_t ra_data_len)
+{
+	const struct gsm48_ra_id *ra_id;
+	if (ra_data_len < sizeof(*ra_id))
+		return -ENOSPC;
+
+	gsm48_decode_lai2((void *)ra_data, &dst->lac);
+
+	ra_id = (void *)ra_data;
+	dst->rac = ra_id->rac;
+
+	return sizeof(*ra_id);
+}
+
+/*! Encode struct osmo_routing_area_id to a 3GPP TS 24.008 § 10.5.5.15 Routing area identification: write to a buffer.
+ * \param[out] buf  Return buffer for encoded Mobile Identity.
+ * \param[in] buflen  sizeof(buf).
+ * \param[in] src  RA to encode.
+ * \return Amount of bytes written to buf, or negative on error.
+ */
+int osmo_routing_area_id_encode_buf(uint8_t *buf, size_t buflen, const struct osmo_routing_area_id *src)
+{
+	struct gsm48_ra_id *ra_id;
+	if (buflen < sizeof(*ra_id))
+		return -ENOSPC;
+
+	gsm48_generate_lai2((void *)buf, &src->lac);
+
+	ra_id = (void *)buf;
+	ra_id->rac = src->rac;
+
+	return sizeof(*ra_id);
+}
+
+/*! Encode struct osmo_routing_area_id to a 3GPP TS 24.008 § 10.5.5.15 Routing area identification: append to msgb.
+ * To succeed, the msgb must have tailroom >= sizeof(struct gsm48_ra_id).
+ * \param[out] msg  Append to this msgb.
+ * \param[in] src  Encode this Routing Area ID.
+ * \return Number of bytes appended to msgb, or negative on error.
+ */
+int osmo_routing_area_id_encode_msgb(struct msgb *msg, const struct osmo_routing_area_id *src)
+{
+	int rc = osmo_routing_area_id_encode_buf(msg->tail, msgb_tailroom(msg), src);
+	if (rc <= 0)
+		return rc;
+	msgb_put(msg, rc);
+	return rc;
+}
+
+/*! Parse TS 04.08 Routing Area Identifier.
+ * Preferably use osmo_routing_area_id_decode() instead: struct osmo_routing_area_id is better integrated with other API
+ * like osmo_plmn_cmp().
  *  \param[out] Caller-provided memory for decoded RA ID
  *  \param[in] buf Input buffer pointing to RAI IE value */
 void gsm48_parse_ra(struct gprs_ra_id *raid, const uint8_t *buf)
diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map
index 0e45dc1..1eea819 100644
--- a/src/gsm/libosmogsm.map
+++ b/src/gsm/libosmogsm.map
@@ -415,6 +415,9 @@
 osmo_mobile_identity_encoded_len;
 osmo_mobile_identity_encode_buf;
 osmo_mobile_identity_encode_msgb;
+osmo_routing_area_id_decode;
+osmo_routing_area_id_encode_buf;
+osmo_routing_area_id_encode_msgb;
 gsm48_mm_att_tlvdef;
 gsm48_number_of_paging_subchannels;
 gsm48_parse_ra;
diff --git a/tests/gsm0408/gsm0408_test.c b/tests/gsm0408/gsm0408_test.c
index 4436d51..f4353e2 100644
--- a/tests/gsm0408/gsm0408_test.c
+++ b/tests/gsm0408/gsm0408_test.c
@@ -341,6 +341,171 @@
 	}
 }
 
+static struct osmo_routing_area_id test_osmo_routing_area_id_items[] = {
+	{
+		.lac = {
+			.plmn = {
+				.mcc = 77,
+				.mnc = 121,
+			},
+			.lac = 666,
+		},
+		.rac = 5,
+	},
+	{
+		.lac = {
+			.plmn = {
+				.mcc = 84,
+				.mnc = 98,
+			},
+			.lac = 11,
+		},
+		.rac = 89,
+	},
+	{
+		.lac = {
+			.plmn = {
+				.mcc = 0,
+				.mnc = 0,
+				.mnc_3_digits = false,
+				/* expecting 000-00, BCD = 00 f0 00 */
+			},
+			.lac = 0,
+		},
+		.rac = 0,
+	},
+	{
+		.lac = {
+			.plmn = {
+				.mcc = 0,
+				.mnc = 0,
+				.mnc_3_digits = true,
+				/* expecting 000-000, BCD = 00 00 00 */
+			},
+			.lac = 0,
+		},
+		.rac = 0,
+	},
+	{
+		.lac = {
+			.plmn = {
+				.mcc = 999,
+				.mnc = 999,
+			},
+			.lac = 65535,
+		},
+		.rac = 255,
+	},
+	{
+		.lac = {
+			.plmn = {
+				.mcc = 1,
+				.mnc = 2,
+				.mnc_3_digits = false,
+				/* expecting 001-02, BCD = 00 f1 20 */
+			},
+			.lac = 23,
+		},
+		.rac = 42,
+	},
+	{
+		.lac = {
+			.plmn = {
+				.mcc = 1,
+				.mnc = 2,
+				.mnc_3_digits = true,
+				/* expecting 001-002, BCD = 00 21 00 */
+			},
+			.lac = 23,
+		},
+		.rac = 42,
+	},
+	{
+		.lac = {
+			.plmn = {
+				.mcc = 12,
+				.mnc = 34,
+				.mnc_3_digits = false,
+				/* expecting 012-34, BCD = 10 f2 43 */
+			},
+			.lac = 56,
+		},
+		.rac = 78,
+	},
+	{
+		.lac = {
+			.plmn = {
+				.mcc = 12,
+				.mnc = 34,
+				.mnc_3_digits = true,
+				/* expecting 012-034, BCD = 10 42 30 */
+			},
+			.lac = 23,
+		},
+		.rac = 42,
+	},
+	{
+		.lac = {
+			.plmn = {
+				.mcc = 123,
+				.mnc = 456,
+				.mnc_3_digits = false,
+				/* expecting 123-456, BCD = 21 63 54 (false flag has no effect) */
+			},
+			.lac = 23,
+		},
+		.rac = 42,
+	},
+	{
+		.lac = {
+			.plmn = {
+				.mcc = 123,
+				.mnc = 456,
+				.mnc_3_digits = true,
+				/* expecting 123-456, BCD = 21 63 54 (same) */
+			},
+			.lac = 23,
+		},
+		.rac = 42,
+	},
+};
+
+static inline void dump_osmo_routing_area_id(const struct osmo_routing_area_id *raid)
+{
+	printf("%s%s", osmo_rai_name2(raid), raid->lac.plmn.mnc_3_digits ? " (3-digit MNC)" : "");
+}
+
+static inline void check_osmo_routing_area_id(const struct osmo_routing_area_id *raid)
+{
+	uint8_t buf[sizeof(struct gsm48_ra_id)] = {};
+	struct osmo_routing_area_id raid0 = {};
+	int rc;
+
+	printf("RA ID: ");
+	dump_osmo_routing_area_id(raid);
+
+	rc = osmo_routing_area_id_encode_buf(buf, sizeof(buf), raid);
+	printf("osmo_routing_area_id_encode_buf(): %src=%d\n", osmo_hexdump(buf, sizeof(buf)), rc);
+
+	rc = osmo_routing_area_id_decode(&raid0, buf, sizeof(buf));
+	printf("osmo_routing_area_id_decode(): ");
+	dump_osmo_routing_area_id(&raid0);
+	printf(" rc=%d\n", rc);
+
+	if (osmo_rai_cmp(raid, &raid0))
+		printf("FAIL\n");
+	else
+		printf("ok\n");
+}
+
+static void test_osmo_routing_area_id(void)
+{
+	int i;
+	printf("==%s()==\n", __func__);
+	for (i = 0; i < ARRAY_SIZE(test_osmo_routing_area_id_items); i++)
+		check_osmo_routing_area_id(&test_osmo_routing_area_id_items[i]);
+}
+
 static void dump_cm3(struct gsm48_classmark3 *cm3)
 {
 	printf("mult_band_supp=%02x\n", cm3->mult_band_supp);
@@ -1792,6 +1957,7 @@
 	test_bcd_number_encode_decode();
 	test_ra_cap();
 	test_lai_encode_decode();
+	test_osmo_routing_area_id();
 	test_decode_classmark3();
 
 	test_si_range_helpers();
diff --git a/tests/gsm0408/gsm0408_test.ok b/tests/gsm0408/gsm0408_test.ok
index dc48f84..b966865 100644
--- a/tests/gsm0408/gsm0408_test.ok
+++ b/tests/gsm0408/gsm0408_test.ok
@@ -386,6 +386,40 @@
   Encoded 21 63 54 00 17 
   gsm48_decode_lai2() gives  123-456-23 (3-digit MNC)
   passed
+==test_osmo_routing_area_id()==
+RA ID: 077-121-666-5osmo_routing_area_id_encode_buf(): 70 17 21 02 9a 05 rc=6
+osmo_routing_area_id_decode(): 077-121-666-5 (3-digit MNC) rc=6
+ok
+RA ID: 084-98-11-89osmo_routing_area_id_encode_buf(): 80 f4 89 00 0b 59 rc=6
+osmo_routing_area_id_decode(): 084-98-11-89 rc=6
+ok
+RA ID: 000-00-0-0osmo_routing_area_id_encode_buf(): 00 f0 00 00 00 00 rc=6
+osmo_routing_area_id_decode(): 000-00-0-0 rc=6
+ok
+RA ID: 000-000-0-0 (3-digit MNC)osmo_routing_area_id_encode_buf(): 00 00 00 00 00 00 rc=6
+osmo_routing_area_id_decode(): 000-000-0-0 (3-digit MNC) rc=6
+ok
+RA ID: 999-999-65535-255osmo_routing_area_id_encode_buf(): 99 99 99 ff ff ff rc=6
+osmo_routing_area_id_decode(): 999-999-65535-255 (3-digit MNC) rc=6
+ok
+RA ID: 001-02-23-42osmo_routing_area_id_encode_buf(): 00 f1 20 00 17 2a rc=6
+osmo_routing_area_id_decode(): 001-02-23-42 rc=6
+ok
+RA ID: 001-002-23-42 (3-digit MNC)osmo_routing_area_id_encode_buf(): 00 21 00 00 17 2a rc=6
+osmo_routing_area_id_decode(): 001-002-23-42 (3-digit MNC) rc=6
+ok
+RA ID: 012-34-56-78osmo_routing_area_id_encode_buf(): 10 f2 43 00 38 4e rc=6
+osmo_routing_area_id_decode(): 012-34-56-78 rc=6
+ok
+RA ID: 012-034-23-42 (3-digit MNC)osmo_routing_area_id_encode_buf(): 10 42 30 00 17 2a rc=6
+osmo_routing_area_id_decode(): 012-034-23-42 (3-digit MNC) rc=6
+ok
+RA ID: 123-456-23-42osmo_routing_area_id_encode_buf(): 21 63 54 00 17 2a rc=6
+osmo_routing_area_id_decode(): 123-456-23-42 (3-digit MNC) rc=6
+ok
+RA ID: 123-456-23-42 (3-digit MNC)osmo_routing_area_id_encode_buf(): 21 63 54 00 17 2a rc=6
+osmo_routing_area_id_decode(): 123-456-23-42 (3-digit MNC) rc=6
+ok
 =====cm3_1=====
 mult_band_supp=06
 a5_bits=00