gprs_bssgp: add utilities to send and parse BSSGP rim PDUs

At the moment libosmogb offers no convinient way to send RIM PDUs. Also
parsing an incoming RIM messages into destination, source routing
info and RIM container is not available.

Change-Id: I18134fd9938040d2facb6beee3732628b167ce8c
Related: SYS#5103
diff --git a/src/gb/gprs_bssgp.c b/src/gb/gprs_bssgp.c
index 4551427..6fdacce 100644
--- a/src/gb/gprs_bssgp.c
+++ b/src/gb/gprs_bssgp.c
@@ -675,6 +675,8 @@
 	nmp.nsei = nsei;
 	nmp.bvci = bvci;
 	nmp.tp = tp;
+	if (bssgp_parse_rim_pdu(&nmp.u.rim_pdu, msg) < 0)
+		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
 	osmo_prim_init(&nmp.oph, SAP_BSSGP_RIM, prim, PRIM_OP_INDICATION, msg);
 	bssgp_prim_cb(&nmp.oph, NULL);
 
@@ -1111,7 +1113,7 @@
 	case BSSGP_PDUT_RAN_INFO_ACK:
 	case BSSGP_PDUT_RAN_INFO_ERROR:
 	case BSSGP_PDUT_RAN_INFO_APP_ERROR:
-		bssgp_rx_rim(msg, tp, bvci);
+		rc = bssgp_rx_rim(msg, tp, bvci);
 		break;
 
 	/* those only exist in the SGSN -> BSS direction */
diff --git a/src/gb/gprs_bssgp_rim.c b/src/gb/gprs_bssgp_rim.c
index 0dc6d21..7094f5f 100644
--- a/src/gb/gprs_bssgp_rim.c
+++ b/src/gb/gprs_bssgp_rim.c
@@ -96,6 +96,7 @@
 {
 	int rc;
 	struct gprs_ra_id raid_temp;
+	int len;
 
 	buf[0] = ri->discr & 0x0f;
 	buf++;
@@ -105,11 +106,13 @@
 		rc = bssgp_create_cell_id(buf, &ri->geran.raid, ri->geran.cid);
 		if (rc < 0)
 			return -EINVAL;
-		return rc + 1;
+		len = rc + 1;
+		break;
 	case BSSGP_RIM_ROUTING_INFO_UTRAN:
 		gsm48_encode_ra((struct gsm48_ra_id *)buf, &ri->utran.raid);
 		osmo_store16be(ri->utran.rncid, buf + 6);
-		return 9;
+		len = 9;
+		break;
 	case BSSGP_RIM_ROUTING_INFO_EUTRAN:
 		/* Note: 3GPP TS 24.301 Figure 9.9.3.32.1 and 3GPP TS 24.008
 		 * Figure 10.5.130 specify MCC/MNC encoding in the same way,
@@ -126,10 +129,14 @@
 			    sizeof(ri->eutran.global_enb_id));
 		memcpy(buf + 5, ri->eutran.global_enb_id,
 		       ri->eutran.global_enb_id_len);
-		return ri->eutran.global_enb_id_len + 6;
+		len = ri->eutran.global_enb_id_len + 6;
+		break;
 	default:
 		return -EINVAL;
 	}
+
+	OSMO_ASSERT(len <= BSSGP_RIM_ROUTING_INFO_MAXLEN);
+	return len;
 }
 
 /*! Decode a RAN Information Request Application Container for NACC (3GPP TS 48.018, section 11.3.63.1.1).
diff --git a/src/gb/gprs_bssgp_util.c b/src/gb/gprs_bssgp_util.c
index 92896c1..d93c9df 100644
--- a/src/gb/gprs_bssgp_util.c
+++ b/src/gb/gprs_bssgp_util.c
@@ -588,3 +588,218 @@
 
 	return bssgp_ns_send(bssgp_ns_send_data, msg);
 }
+
+/* Chapter 10.6.1: RAN-INFORMATION-REQUEST */
+int bssgp_tx_rim(const struct bssgp_ran_information_pdu *pdu, uint16_t nsei)
+{
+	struct msgb *msg;
+	struct bssgp_normal_hdr *bgph;
+
+	/* Encode RIM PDU into mesage buffer */
+	msg = bssgp_encode_rim_pdu(pdu);
+	if (!msg) {
+		LOGP(DLBSSGP, LOGL_ERROR,
+		     "BSSGP RIM (NSEI=%u) unable to encode BSSGP RIM PDU\n", nsei);
+		return -EINVAL;
+	}
+
+	msgb_nsei(msg) = nsei;
+	msgb_bvci(msg) = 0;	/* Signalling */
+
+	bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+	DEBUGP(DLBSSGP, "BSSGP BVCI=0 Tx RIM-PDU:%s\n", bssgp_pdu_str(bgph->pdu_type));
+
+	return bssgp_ns_send(bssgp_ns_send_data, msg);
+}
+
+/* Parse a given message buffer into a rim-pdu struct */
+int bssgp_parse_rim_pdu(struct bssgp_ran_information_pdu *pdu, const struct msgb *msg)
+{
+	struct tlv_parsed tp[2];
+	struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+	int data_len;
+	int rc;
+	uint16_t nsei = msgb_nsei(msg);
+
+	memset(pdu, 0, sizeof(*pdu));
+
+	data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+	if (data_len < 0)
+		return -EINVAL;
+
+	rc = osmo_tlv_prot_parse(&osmo_pdef_bssgp, tp, ARRAY_SIZE(tp), bgph->pdu_type, bgph->data, data_len, 0, 0,
+				 DLBSSGP, __func__);
+	if (rc < 0)
+		return -EINVAL;
+
+	if (TLVP_PRESENT(&tp[0], BSSGP_IE_RIM_ROUTING_INFO)) {
+		rc = bssgp_parse_rim_ri(&pdu->routing_info_dest, TLVP_VAL(&tp[0], BSSGP_IE_RIM_ROUTING_INFO),
+					TLVP_LEN(&tp[0], BSSGP_IE_RIM_ROUTING_INFO));
+		if (rc < 0) {
+			LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) invalid Destination Cell Identifier IE\n", nsei);
+			return -EINVAL;
+		}
+	} else {
+		LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing Destination Cell Identifier IE\n", nsei);
+		return -EINVAL;
+	}
+
+	if (TLVP_PRESENT(&tp[1], BSSGP_IE_RIM_ROUTING_INFO)) {
+		rc = bssgp_parse_rim_ri(&pdu->routing_info_src, TLVP_VAL(&tp[1], BSSGP_IE_RIM_ROUTING_INFO),
+					TLVP_LEN(&tp[1], BSSGP_IE_RIM_ROUTING_INFO));
+		if (rc < 0) {
+			LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) invalid Destination Cell Identifier IE\n", nsei);
+			return -EINVAL;
+		}
+	} else {
+		LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing Source Cell Identifier IE\n", nsei);
+		return -EINVAL;
+	}
+
+	if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_REQ_RIM_CONTAINER))
+		pdu->rim_cont_iei = BSSGP_IE_RI_REQ_RIM_CONTAINER;
+	else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_RIM_CONTAINER))
+		pdu->rim_cont_iei = BSSGP_IE_RI_RIM_CONTAINER;
+	else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_APP_ERROR_RIM_CONT))
+		pdu->rim_cont_iei = BSSGP_IE_RI_APP_ERROR_RIM_CONT;
+	else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_ACK_RIM_CONTAINER))
+		pdu->rim_cont_iei = BSSGP_IE_RI_ACK_RIM_CONTAINER;
+	else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_ERROR_RIM_COINTAINER))
+		pdu->rim_cont_iei = BSSGP_IE_RI_ERROR_RIM_COINTAINER;
+	else {
+		LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing or wrong RIM Container IE\n", nsei);
+		return -EINVAL;
+	}
+
+	pdu->rim_cont = TLVP_VAL(&tp[0], pdu->rim_cont_iei);
+	pdu->rim_cont_len = TLVP_LEN(&tp[0], pdu->rim_cont_iei);
+
+	/* Make sure the rim container field is not empty */
+	if (pdu->rim_cont_len < 1)
+		return -EINVAL;
+	if (!pdu->rim_cont)
+		return -EINVAL;
+
+	/* Note: It is not an error if we fail to parse the RIM container,
+	 * since there are applications where parsing the RIM container
+	 * is not necessary (routing). It is up to the API user to check
+	 * the results. */
+	switch (pdu->rim_cont_iei) {
+	case BSSGP_IE_RI_REQ_RIM_CONTAINER:
+		rc = bssgp_dec_ran_inf_req_rim_cont(&pdu->decoded.req_rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+		break;
+	case BSSGP_IE_RI_RIM_CONTAINER:
+		rc = bssgp_dec_ran_inf_rim_cont(&pdu->decoded.rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+		break;
+	case BSSGP_IE_RI_APP_ERROR_RIM_CONT:
+		rc = bssgp_dec_ran_inf_app_err_rim_cont(&pdu->decoded.app_err_rim_cont, pdu->rim_cont,
+							pdu->rim_cont_len);
+		break;
+	case BSSGP_IE_RI_ACK_RIM_CONTAINER:
+		rc = bssgp_dec_ran_inf_ack_rim_cont(&pdu->decoded.ack_rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+		break;
+	case BSSGP_IE_RI_ERROR_RIM_COINTAINER:
+		rc = bssgp_dec_ran_inf_err_rim_cont(&pdu->decoded.err_rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+		break;
+	default:
+		LOGP(DLBSSGP, LOGL_DEBUG, "BSSGP RIM (NSEI=%u) cannot parse unknown RIM container.\n", nsei);
+		return 0;
+	}
+	if (rc < 0) {
+		LOGP(DLBSSGP, LOGL_DEBUG, "BSSGP RIM (NSEI=%u) unable to parse RIM container.\n", nsei);
+		return 0;
+	}
+	pdu->decoded_present = true;
+
+	return 0;
+}
+
+/* Encode a given rim-pdu struct into a message buffer */
+struct msgb *bssgp_encode_rim_pdu(const struct bssgp_ran_information_pdu *pdu)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph;
+	uint8_t rim_ri_buf[BSSGP_RIM_ROUTING_INFO_MAXLEN];
+	uint8_t *rim_cont_buf;
+	int rc;
+
+	if (!msg)
+		return NULL;
+	bgph = (struct bssgp_normal_hdr *)msgb_put(msg, sizeof(*bgph));
+
+	/* Set PDU type based on RIM container type */
+	switch (pdu->rim_cont_iei) {
+	case BSSGP_IE_RI_REQ_RIM_CONTAINER:
+		bgph->pdu_type = BSSGP_PDUT_RAN_INFO_REQ;
+		break;
+	case BSSGP_IE_RI_RIM_CONTAINER:
+		bgph->pdu_type = BSSGP_PDUT_RAN_INFO;
+		break;
+	case BSSGP_IE_RI_APP_ERROR_RIM_CONT:
+		bgph->pdu_type = BSSGP_PDUT_RAN_INFO_APP_ERROR;
+		break;
+	case BSSGP_IE_RI_ACK_RIM_CONTAINER:
+		bgph->pdu_type = BSSGP_PDUT_RAN_INFO_ACK;
+		break;
+	case BSSGP_IE_RI_ERROR_RIM_COINTAINER:
+		bgph->pdu_type = BSSGP_PDUT_RAN_INFO_ERROR;
+		break;
+	default:
+		/* The caller must correctly specify the container type! */
+		OSMO_ASSERT(false);
+	}
+
+	/* Put RIM routing information */
+	rc = bssgp_create_rim_ri(rim_ri_buf, &pdu->routing_info_dest);
+	if (rc < 0 || rc > BSSGP_RIM_ROUTING_INFO_MAXLEN)
+		goto error;
+	msgb_tvlv_put(msg, BSSGP_IE_RIM_ROUTING_INFO, rc, rim_ri_buf);
+	rc = bssgp_create_rim_ri(rim_ri_buf, &pdu->routing_info_src);
+	if (rc < 0 || rc > BSSGP_RIM_ROUTING_INFO_MAXLEN)
+		goto error;
+	msgb_tvlv_put(msg, BSSGP_IE_RIM_ROUTING_INFO, rc, rim_ri_buf);
+
+	/* Put RIM container */
+	if (pdu->decoded_present) {
+		rim_cont_buf = talloc_zero_size(msg, msg->data_len);
+		if (!rim_cont_buf)
+			goto error;
+
+		switch (pdu->rim_cont_iei) {
+		case BSSGP_IE_RI_REQ_RIM_CONTAINER:
+			rc = bssgp_enc_ran_inf_req_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.req_rim_cont);
+			break;
+		case BSSGP_IE_RI_RIM_CONTAINER:
+			rc = bssgp_enc_ran_inf_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.rim_cont);
+			break;
+		case BSSGP_IE_RI_APP_ERROR_RIM_CONT:
+			rc = bssgp_enc_ran_inf_app_err_rim_cont(rim_cont_buf, msg->data_len,
+								&pdu->decoded.app_err_rim_cont);
+			break;
+		case BSSGP_IE_RI_ACK_RIM_CONTAINER:
+			rc = bssgp_enc_ran_inf_ack_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.ack_rim_cont);
+			break;
+		case BSSGP_IE_RI_ERROR_RIM_COINTAINER:
+			rc = bssgp_enc_ran_inf_err_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.err_rim_cont);
+			break;
+		default:
+			/* The API user must set the iei properly! */
+			OSMO_ASSERT(false);
+		}
+		if (rc < 0)
+			goto error;
+
+		msgb_tvlv_put(msg, pdu->rim_cont_iei, rc, rim_cont_buf);
+		talloc_free(rim_cont_buf);
+	} else {
+		/* Make sure the RIM container is actually present. */
+		OSMO_ASSERT(pdu->rim_cont_iei != 0 && pdu->rim_cont_len > 0 && pdu->rim_cont);
+		msgb_tvlv_put(msg, pdu->rim_cont_iei, pdu->rim_cont_len, pdu->rim_cont);
+	}
+
+	return msg;
+error:
+	talloc_free(rim_cont_buf);
+	msgb_free(msg);
+	return 0;
+}
diff --git a/src/gb/libosmogb.map b/src/gb/libosmogb.map
index b012184..763ddc6 100644
--- a/src/gb/libosmogb.map
+++ b/src/gb/libosmogb.map
@@ -30,6 +30,7 @@
 bssgp_msgb_tlli_put;
 bssgp_msgb_ra_put;
 bssgp_parse_cell_id;
+bssgp_parse_rim_pdu;
 bssgp_parse_rim_ri;
 bssgp_set_bssgp_callback;
 bssgp_tx_bvc_block;
@@ -48,6 +49,7 @@
 bssgp_tx_resume;
 bssgp_tx_resume_ack;
 bssgp_tx_resume_nack;
+bssgp_tx_rim;
 bssgp_tx_simple_bvci;
 bssgp_tx_status;
 bssgp_tx_suspend;