GSUP: add inter-MSC handover related msgs and IEs

Based on a draft created by Neels, which is the result of reading a MAP
trace of two MSCs negotiating inter-MSC handovers, and of reading the
TS 29.002, TS 29.010 and related specs:
https://lists.osmocom.org/pipermail/openbsc/2019-January/012653.html

I figured out that the "Handover Number" mentioned in the specifications
is the same as the MSISDN IE that we already have, so we can use that
instead of creating a new IE (example usage in tests/gsup/gsup_test.c).

Create a new OSMO_GSUP_MSGT_E_ROUTING_ERROR message type, which the GSUP
server uses to tell a client that its message could not be forwarded to
the destination (see [1]). MAP has no related message.

[1]: Change-Id: Ia4f345abc877baaf0a8f73b8988e6514d9589bf5 (osmo-hlr.git)

Related: OS#3774
Change-Id: Ic00b0601eacff6d72927cea51767801142ee75db
diff --git a/src/gsm/gsup.c b/src/gsm/gsup.c
index 71dbbe1..a3d9eef 100644
--- a/src/gsm/gsup.c
+++ b/src/gsm/gsup.c
@@ -83,6 +83,26 @@
 	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_CHECK_IMEI_ERROR),
 	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_CHECK_IMEI_RESULT),
 
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_REQUEST),
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_ERROR),
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_RESULT),
+
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_REQUEST),
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_ERROR),
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_RESULT),
+
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_REQUEST),
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_ERROR),
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_RESULT),
+
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_PROCESS_ACCESS_SIGNALLING_REQUEST),
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_FORWARD_ACCESS_SIGNALLING_REQUEST),
+
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_CLOSE),
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_ABORT),
+
+	OSMO_VALUE_STRING(OSMO_GSUP_MSGT_E_ROUTING_ERROR),
+
 	{ 0, NULL }
 };
 
@@ -247,6 +267,26 @@
 	return -1;
 }
 
+/*! Decode AN-apdu (see 3GPP TS 29.002 7.6.9.1).
+ * \param[out] gsup_msg abstract GSUP message structure
+ * \param[in]  data     pointer to the raw IE payload
+ * \param[in]  data_len length of IE pointed by \ref data
+ * \returns 0 in case of success, negative in case of error
+ */
+int osmo_gsup_decode_an_apdu(struct osmo_gsup_message *gsup_msg, const uint8_t *data, size_t data_len)
+{
+	if (data_len < 1) {
+		LOGP(DLGSUP, LOGL_ERROR, "Corrupted an_apdu message (length must be >= 1)\n");
+		return -EINVAL;
+	}
+
+	gsup_msg->an_apdu.access_network_proto = data[0];
+	gsup_msg->an_apdu.data_len = data_len -1;
+	gsup_msg->an_apdu.data = data + 1;
+
+	return 0;
+}
+
 /*! Decode (parse) a GSUP message
  *  \param[in] const_data input data to be parsed
  *  \param[in] data_len length of input (\a const_data)
@@ -481,6 +521,36 @@
 			gsup_msg->message_class = value[0];
 			break;
 
+		case OSMO_GSUP_SOURCE_NAME_IE:
+			gsup_msg->source_name = value;
+			gsup_msg->source_name_len = value_len;
+			break;
+
+		case OSMO_GSUP_DESTINATION_NAME_IE:
+			gsup_msg->destination_name = value;
+			gsup_msg->destination_name_len = value_len;
+			break;
+
+		case OSMO_GSUP_AN_APDU_IE:
+			rc = osmo_gsup_decode_an_apdu(gsup_msg, value, value_len);
+			if (rc)
+				return rc;
+			break;
+
+		case OSMO_GSUP_CAUSE_RR_IE:
+			gsup_msg->cause_rr = value[0];
+			gsup_msg->cause_rr_set = true;
+			break;
+
+		case OSMO_GSUP_CAUSE_BSSAP_IE:
+			gsup_msg->cause_bssap = value[0];
+			gsup_msg->cause_bssap_set = true;
+			break;
+
+		case OSMO_GSUP_CAUSE_SM_IE:
+			gsup_msg->cause_sm = value[0];
+			break;
+
 		default:
 			LOGP(DLGSUP, LOGL_NOTICE,
 			     "GSUP IE type %d unknown\n", iei);
@@ -568,6 +638,35 @@
 	*len_field = msgb_length(msg) - old_len;
 }
 
+/*! Encode AN-apdu (see 3GPP TS 29.002 7.6.9.1).
+ * \param[out] msg      target message buffer (caller-allocated)
+ * \param[in]  gsup_msg abstract GSUP message structure
+ * \returns 0 in case of success, negative in case of error
+ */
+int osmo_gsup_encode_an_apdu(struct msgb *msg, const struct osmo_gsup_message *gsup_msg)
+{
+	const struct osmo_gsup_an_apdu an_apdu = gsup_msg->an_apdu;
+
+	if (msgb_tailroom(msg) < 2 + an_apdu.data_len) {
+		LOGP(DLGSUP, LOGL_ERROR, "Not enough tailroom in msg to encode an_apdu:"
+		     " IE header (2) + an_apdu.data_len (%zu) == %zu, msgb tailroom == %d\n",
+		     an_apdu.data_len, an_apdu.data_len + 2, msgb_tailroom(msg));
+		return -ENOMEM;
+	}
+
+	/* Tag and total length */
+	msgb_tv_put(msg, OSMO_GSUP_AN_APDU_IE, 1 + an_apdu.data_len);
+
+	/* Put access_network_proto */
+	msgb_v_put(msg, an_apdu.access_network_proto);
+
+	/* Put data */
+	uint8_t *buf = msgb_put(msg, an_apdu.data_len);
+	memcpy(buf, an_apdu.data, an_apdu.data_len);
+
+	return 0;
+}
+
 /*! Encode a GSUP message
  *  \param[out] msg message buffer to which encoded message is written
  *  \param[in] gsup_msg \ref osmo_gsup_message data to be encoded
@@ -727,6 +826,34 @@
 		msgb_tlv_put(msg, OSMO_GSUP_MESSAGE_CLASS_IE, sizeof(u8), &u8);
 	}
 
+	if (gsup_msg->source_name)
+		msgb_tlv_put(msg, OSMO_GSUP_SOURCE_NAME_IE, gsup_msg->source_name_len, gsup_msg->source_name);
+
+	if (gsup_msg->destination_name)
+		msgb_tlv_put(msg, OSMO_GSUP_DESTINATION_NAME_IE, gsup_msg->destination_name_len,
+			     gsup_msg->destination_name);
+
+	if (gsup_msg->an_apdu.access_network_proto || gsup_msg->an_apdu.data_len) {
+		rc = osmo_gsup_encode_an_apdu(msg, gsup_msg);
+		if (rc) {
+			LOGP(DLGSUP, LOGL_ERROR, "Failed to encode AN-apdu IE \n");
+			return -EINVAL;
+		}
+	}
+
+	if (gsup_msg->cause_rr_set) {
+		u8 = gsup_msg->cause_rr;
+		msgb_tlv_put(msg, OSMO_GSUP_CAUSE_RR_IE, sizeof(u8), &u8);
+	}
+
+	if (gsup_msg->cause_bssap_set) {
+		u8 = gsup_msg->cause_bssap;
+		msgb_tlv_put(msg, OSMO_GSUP_CAUSE_BSSAP_IE, sizeof(u8), &u8);
+	}
+
+	if ((u8 = gsup_msg->cause_sm))
+		msgb_tlv_put(msg, OSMO_GSUP_CAUSE_SM_IE, sizeof(u8), &u8);
+
 	return 0;
 }