SMPP: Implement SMPP Osmocom Estensions on MO-SMS

An ESME can now be configured in the VTY to enable osmocom-extensions,
which will add vendor-specific SMPP TLVs for RxLev/RxQual/ARFCN/IMEI and
transmit power to the SMPP DELIVER-SM message type.
diff --git a/openbsc/configure.ac b/openbsc/configure.ac
index 7004f96..cdefcaf 100644
--- a/openbsc/configure.ac
+++ b/openbsc/configure.ac
@@ -21,7 +21,7 @@
 
 PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.5.3.60)
 PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.3.0)
-PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.3.0)
+PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.6.0)
 PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.1.0)
 PKG_CHECK_MODULES(LIBOSMOGB, libosmogb >= 0.5.2)
 
diff --git a/openbsc/src/libmsc/gsm_04_11.c b/openbsc/src/libmsc/gsm_04_11.c
index 9e00bda..2fc250b 100644
--- a/openbsc/src/libmsc/gsm_04_11.c
+++ b/openbsc/src/libmsc/gsm_04_11.c
@@ -57,7 +57,8 @@
 
 #ifdef BUILD_SMPP
 #include "smpp_smsc.h"
-extern int smpp_try_deliver(struct gsm_sms *sms);
+extern int smpp_try_deliver(struct gsm_sms *sms,
+			    struct gsm_subscriber_connection *conn);
 #endif
 
 void *tall_gsms_ctx;
@@ -395,7 +396,7 @@
 	gsms->receiver = subscr_get_by_extension(conn->bts->network, gsms->dst.addr);
 	if (!gsms->receiver) {
 #ifdef BUILD_SMPP
-		rc = smpp_try_deliver(gsms);
+		rc = smpp_try_deliver(gsms, conn);
 		if (rc == 1) {
 			rc = 1; /* cause 1: unknown subscriber */
 			osmo_counter_inc(conn->bts->network->stats.sms.no_receiver);
diff --git a/openbsc/src/libmsc/smpp_openbsc.c b/openbsc/src/libmsc/smpp_openbsc.c
index 585f939..2f410e9 100644
--- a/openbsc/src/libmsc/smpp_openbsc.c
+++ b/openbsc/src/libmsc/smpp_openbsc.c
@@ -1,6 +1,6 @@
 /* OpenBSC SMPP 3.4 interface, SMSC-side implementation */
 
-/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -33,6 +33,7 @@
 #include <osmocom/core/logging.h>
 #include <osmocom/core/talloc.h>
 #include <osmocom/gsm/protocol/gsm_04_11.h>
+#include <osmocom/gsm/protocol/smpp34_osmocom.h>
 
 #include <openbsc/gsm_subscriber.h>
 #include <openbsc/debug.h>
@@ -335,7 +336,80 @@
 }
 
 
-static int deliver_to_esme(struct osmo_esme *esme, struct gsm_sms *sms)
+/* FIXME: libsmpp34 helpers, they should  be part of libsmpp34! */
+void append_tlv(tlv_t **req_tlv, uint16_t tag,
+	        const uint8_t *data, uint16_t len)
+{
+	tlv_t tlv;
+
+	memset(&tlv, 0, sizeof(tlv));
+	tlv.tag = tag;
+	tlv.length = len;
+	memcpy(tlv.value.octet, data, tlv.length);
+	build_tlv(req_tlv, &tlv);
+}
+void append_tlv_u8(tlv_t **req_tlv, uint16_t tag, uint8_t val)
+{
+	tlv_t tlv;
+
+	memset(&tlv, 0, sizeof(tlv));
+	tlv.tag = tag;
+	tlv.length = 1;
+	tlv.value.val08 = val;
+	build_tlv(req_tlv, &tlv);
+}
+void append_tlv_u16(tlv_t **req_tlv, uint16_t tag, uint16_t val)
+{
+	tlv_t tlv;
+
+	memset(&tlv, 0, sizeof(tlv));
+	tlv.tag = tag;
+	tlv.length = 2;
+	tlv.value.val16 = htons(val);
+	build_tlv(req_tlv, &tlv);
+}
+
+/* Append the Osmocom vendor-specific additional TLVs to a SMPP msg */
+static void append_osmo_tlvs(tlv_t **req_tlv, const struct gsm_lchan *lchan)
+{
+	int idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+				   lchan->meas_rep_idx, 1);
+	const struct gsm_meas_rep *mr = &lchan->meas_rep[idx];
+	const struct gsm_meas_rep_unidir *ul_meas = &mr->ul;
+	const struct gsm_meas_rep_unidir *dl_meas = &mr->dl;
+
+	/* Osmocom vendor-specific SMPP34 extensions */
+	append_tlv_u16(req_tlv, TLVID_osmo_arfcn, lchan->ts->trx->arfcn);
+	if (mr->flags & MEAS_REP_F_MS_L1) {
+		uint8_t ms_dbm;
+		append_tlv_u8(req_tlv, TLVID_osmo_ta, mr->ms_l1.ta);
+		ms_dbm = ms_pwr_dbm(lchan->ts->trx->bts->band, mr->ms_l1.pwr);
+		append_tlv_u8(req_tlv, TLVID_osmo_ms_l1_txpwr, ms_dbm);
+	} else if (mr->flags & MEAS_REP_F_MS_TO)
+		append_tlv_u8(req_tlv, TLVID_osmo_ta, mr->ms_timing_offset);
+
+	append_tlv_u16(req_tlv, TLVID_osmo_rxlev_ul,
+		       rxlev2dbm(ul_meas->full.rx_lev));
+	append_tlv_u8(req_tlv, TLVID_osmo_rxqual_ul, ul_meas->full.rx_qual);
+
+	if (mr->flags & MEAS_REP_F_DL_VALID) {
+		append_tlv_u16(req_tlv, TLVID_osmo_rxlev_dl,
+			       rxlev2dbm(dl_meas->full.rx_lev));
+		append_tlv_u8(req_tlv, TLVID_osmo_rxqual_dl,
+			      dl_meas->full.rx_qual);
+	}
+
+	if (lchan->conn && lchan->conn->subscr) {
+		struct gsm_subscriber *subscr = lchan->conn->subscr;
+		size_t imei_len = strlen(subscr->equipment.imei);
+		if (imei_len)
+			append_tlv(req_tlv, TLVID_osmo_imei,
+				   (uint8_t *)subscr->equipment.imei, imei_len+1);
+	}
+}
+
+static int deliver_to_esme(struct osmo_esme *esme, struct gsm_sms *sms,
+			   struct gsm_subscriber_connection *conn)
 {
 	struct deliver_sm_t deliver;
 	uint8_t dcs;
@@ -405,12 +479,15 @@
 		memcpy(deliver.short_message, sms->user_data, deliver.sm_length);
 	}
 
+	if (esme->acl->osmocom_ext && conn && conn->lchan)
+		append_osmo_tlvs(&deliver.tlv, conn->lchan);
+
 	return smpp_tx_deliver(esme, &deliver);
 }
 
 static struct smsc *g_smsc;
 
-int smpp_try_deliver(struct gsm_sms *sms)
+int smpp_try_deliver(struct gsm_sms *sms, struct gsm_subscriber_connection *conn)
 {
 	struct osmo_esme *esme;
 	struct osmo_smpp_addr dst;
@@ -424,7 +501,7 @@
 	if (!esme)
 		return 1; /* unknown subscriber */
 
-	return deliver_to_esme(esme, sms);
+	return deliver_to_esme(esme, sms, conn);
 }
 
 struct smsc *smsc_from_vty(struct vty *v)
diff --git a/openbsc/src/libmsc/smpp_smsc.h b/openbsc/src/libmsc/smpp_smsc.h
index be72a0c..e72f81f 100644
--- a/openbsc/src/libmsc/smpp_smsc.h
+++ b/openbsc/src/libmsc/smpp_smsc.h
@@ -60,6 +60,7 @@
 	char passwd[SMPP_PASSWD_LEN+1];
 	int default_route;
 	int deliver_src_imsi;
+	int osmocom_ext;
 	struct llist_head route_list;
 };
 
diff --git a/openbsc/src/libmsc/smpp_vty.c b/openbsc/src/libmsc/smpp_vty.c
index 96447bf..4c64a3c 100644
--- a/openbsc/src/libmsc/smpp_vty.c
+++ b/openbsc/src/libmsc/smpp_vty.c
@@ -339,6 +339,28 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_esme_osmo_ext, cfg_esme_osmo_ext_cmd,
+	"osmocom-extensions",
+	"Enable the use of Osmocom SMPP Extensions for this ESME")
+{
+	struct osmo_smpp_acl *acl = vty->index;
+
+	acl->osmocom_ext = 1;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_osmo_ext, cfg_esme_no_osmo_ext_cmd,
+	"no osmocom-extensions", NO_STR
+	"Disable the use of Osmocom SMPP Extensions for this ESME")
+{
+	struct osmo_smpp_acl *acl = vty->index;
+
+	acl->osmocom_ext = 0;
+
+	return CMD_SUCCESS;
+}
+
 static void dump_one_esme(struct vty *vty, struct osmo_esme *esme)
 {
 	char host[128], serv[128];
@@ -395,6 +417,8 @@
 		vty_out(vty, "  default-route%s", VTY_NEWLINE);
 	if (acl->deliver_src_imsi)
 		vty_out(vty, "  deliver-src-imsi%s", VTY_NEWLINE);
+	if (acl->osmocom_ext)
+		vty_out(vty, "  osmocom-extensions%s", VTY_NEWLINE);
 
 	llist_for_each_entry(r, &acl->route_list, list)
 		write_esme_route_single(vty, r);
@@ -431,6 +455,8 @@
 	install_element(SMPP_ESME_NODE, &cfg_esme_no_route_pfx_cmd);
 	install_element(SMPP_ESME_NODE, &cfg_esme_defaultroute_cmd);
 	install_element(SMPP_ESME_NODE, &cfg_esme_no_defaultroute_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_osmo_ext_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_no_osmo_ext_cmd);
 	install_element(SMPP_ESME_NODE, &ournode_exit_cmd);
 
 	install_element_ve(&show_esme_cmd);