SMS over GSUP: implement forwarding of MO SMS

MO-forwardSM.req messages are now forwarded to a connected SMSC
based on the SMSC address (SM-RP-DA) in the MO SM and the vty-defined
mapping from SMSC numeric addresses to IPA names.

Related: OS#6135
Change-Id: Iaad4531922c41583d261c79f42561a1bdbe03521
diff --git a/include/osmocom/hlr/hlr_sms.h b/include/osmocom/hlr/hlr_sms.h
index e6e0d8a..6e68a85 100644
--- a/include/osmocom/hlr/hlr_sms.h
+++ b/include/osmocom/hlr/hlr_sms.h
@@ -27,3 +27,5 @@
 struct hlr_smsc_route *smsc_route_alloc(struct hlr *hlr, const char *num_addr,
 					struct hlr_smsc *smsc);
 void smsc_route_del(struct hlr_smsc_route *rt);
+
+void forward_mo_sms(struct osmo_gsup_req *req);
diff --git a/src/hlr.c b/src/hlr.c
index a7bebf8..9aabada 100644
--- a/src/hlr.c
+++ b/src/hlr.c
@@ -49,6 +49,7 @@
 #include <osmocom/hlr/rand.h>
 #include <osmocom/hlr/hlr_vty.h>
 #include <osmocom/hlr/hlr_ussd.h>
+#include <osmocom/hlr/hlr_sms.h>
 #include <osmocom/hlr/dgsm.h>
 #include <osmocom/hlr/proxy.h>
 #include <osmocom/hlr/lu_fsm.h>
@@ -555,6 +556,9 @@
 	case OSMO_GSUP_MSGT_CHECK_IMEI_REQUEST:
 		rx_check_imei_req(req);
 		break;
+	case OSMO_GSUP_MSGT_MO_FORWARD_SM_REQUEST:
+		forward_mo_sms(req);
+		break;
 	default:
 		LOGP(DMAIN, LOGL_DEBUG, "Unhandled GSUP message type %s\n",
 		     osmo_gsup_message_type_name(req->gsup.message_type));
diff --git a/src/hlr_sms.c b/src/hlr_sms.c
index bd5c85b..ebf4099 100644
--- a/src/hlr_sms.c
+++ b/src/hlr_sms.c
@@ -26,6 +26,8 @@
 
 #include <osmocom/core/talloc.h>
 #include <osmocom/gsm/gsup.h>
+#include <osmocom/gsm/gsm48_ie.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
 
 #include <osmocom/hlr/hlr.h>
 #include <osmocom/hlr/hlr_sms.h>
@@ -101,3 +103,91 @@
 	llist_del(&rt->list);
 	talloc_free(rt);
 }
+
+/***********************************************************************
+ * forwarding of MO SMS to SMSCs based on SM-RP-DA
+ ***********************************************************************/
+
+static const struct hlr_smsc *find_smsc_route(const char *smsc_addr)
+{
+	const struct hlr_smsc_route *rt;
+
+	rt = smsc_route_find(g_hlr, smsc_addr);
+	if (rt)
+		return rt->smsc;
+	return g_hlr->smsc_default;
+}
+
+static void respond_with_sm_rp_cause(struct osmo_gsup_req *req,
+				     uint8_t sm_rp_cause)
+{
+	struct osmo_gsup_message rsp_msg = { };
+
+	rsp_msg.sm_rp_cause = &sm_rp_cause;
+	osmo_gsup_req_respond(req, &rsp_msg, true, true);
+}
+
+/* Short Message from MSC/VLR towards SMSC */
+void forward_mo_sms(struct osmo_gsup_req *req)
+{
+	/* The length limit on the SMSC address is 20 digits, stated
+	 * indirectly in GSM 04.11 section 8.2.5.2. */
+	uint8_t gsm48_decode_buffer[11];
+	char smsc_addr[21];
+	const struct hlr_smsc *smsc;
+	struct osmo_cni_peer_id dest_peer;
+
+	/* Make sure SM-RP-DA (SMSC address) is present */
+	if (req->gsup.sm_rp_da == NULL || !req->gsup.sm_rp_da_len) {
+		osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
+					  "missing SM-RP-DA");
+		return;
+	}
+
+	if (req->gsup.sm_rp_da_type != OSMO_GSUP_SMS_SM_RP_ODA_SMSC_ADDR) {
+		osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
+					  "SM-RP-DA type is not SMSC");
+		return;
+	}
+
+	/* Enforce the length constrainst on SM-RP-DA, as specified in
+	 * GSM 04.11 section 8.2.5.2.  Also enforce absence of ToN/NPI
+	 * extension octets at the same time. */
+	if (req->gsup.sm_rp_da_len < 2 || req->gsup.sm_rp_da_len > 11 ||
+	    !(req->gsup.sm_rp_da[0] & 0x80)) {
+		/* This form of bogosity originates from the MS,
+		 * not from OsmoMSC or any other Osmocom network elements! */
+		LOGP(DLSMS, LOGL_NOTICE,
+		     "Rx '%s' (IMSI-%s) contains invalid SM-RP-DA from MS\n",
+		     osmo_gsup_message_type_name(req->gsup.message_type),
+		     req->gsup.imsi);
+		respond_with_sm_rp_cause(req, GSM411_RP_CAUSE_SEMANT_INC_MSG);
+		return;
+	}
+
+	/* Decode SMSC address from SM-RP-DA */
+	gsm48_decode_buffer[0] = req->gsup.sm_rp_da_len - 1;
+	memcpy(gsm48_decode_buffer + 1, req->gsup.sm_rp_da + 1,
+		req->gsup.sm_rp_da_len - 1);
+	gsm48_decode_bcd_number2(smsc_addr, sizeof(smsc_addr),
+				 gsm48_decode_buffer,
+				 req->gsup.sm_rp_da_len, 0);
+
+	/* Look for a route to this SMSC */
+	smsc = find_smsc_route(smsc_addr);
+	if (smsc == NULL) {
+		LOGP(DLSMS, LOGL_NOTICE,
+		     "Failed to find a route for '%s' (IMSI-%s, SMSC-Addr-%s)\n",
+		     osmo_gsup_message_type_name(req->gsup.message_type),
+		     req->gsup.imsi, smsc_addr);
+		respond_with_sm_rp_cause(req,
+					 GSM411_RP_CAUSE_MO_NUM_UNASSIGNED);
+		return;
+	}
+
+	/* We got the IPA name of our SMSC - forward the message */
+	osmo_cni_peer_id_set(&dest_peer, OSMO_CNI_PEER_ID_IPA_NAME,
+			     (const uint8_t *) smsc->name,
+			     strlen(smsc->name) + 1);
+	osmo_gsup_forward_to_local_peer(req->cb_data, &dest_peer, req, NULL);
+}