SMPP: add small utility program 'smpp_mirror'

This program binds as ESME transceiver to a SMSC and simply mirrors back
all SMS that it receives.
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
index 3b0f248..ea9f601 100644
--- a/openbsc/include/openbsc/gsm_data.h
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -293,7 +293,7 @@
 	unsigned long long id;
 	struct gsm_subscriber *sender;
 	struct gsm_subscriber *receiver;
-	struct gsm_sms_addr destination;
+	struct gsm_sms_addr src, dst;
 	enum gsm_sms_source_id source;
 
 	struct {
@@ -310,8 +310,6 @@
 	uint8_t protocol_id;
 	uint8_t data_coding_scheme;
 	uint8_t msg_ref;
-	char dest_addr[20+1];	/* DA LV is 12 bytes max, i.e. 10 bytes
-				 * BCD == 20 bytes string */
 	uint8_t user_data_len;
 	uint8_t user_data[SMS_TEXT_SIZE];
 
diff --git a/openbsc/src/libmsc/db.c b/openbsc/src/libmsc/db.c
index 790cde3..9a5f18d 100644
--- a/openbsc/src/libmsc/db.c
+++ b/openbsc/src/libmsc/db.c
@@ -993,7 +993,7 @@
 	/* FIXME: generate validity timestamp based on validity_minutes */
 
 	dbi_conn_quote_string_copy(conn, (char *)sms->text, &q_text);
-	dbi_conn_quote_string_copy(conn, (char *)sms->dest_addr, &q_daddr);
+	dbi_conn_quote_string_copy(conn, (char *)sms->dst.addr, &q_daddr);
 	dbi_conn_quote_binary_copy(conn, sms->user_data, sms->user_data_len,
 				   &q_udata);
 	/* FIXME: correct validity period */
@@ -1035,6 +1035,7 @@
 
 	sender_id = dbi_result_get_ulonglong(result, "sender_id");
 	sms->sender = subscr_get_by_id(net, sender_id);
+	strncpy(sms->src.addr, sms->sender->extension, sizeof(sms->src.addr)-1);
 
 	receiver_id = dbi_result_get_ulonglong(result, "receiver_id");
 	sms->receiver = subscr_get_by_id(net, receiver_id);
@@ -1051,8 +1052,8 @@
 
 	daddr = dbi_result_get_string(result, "dest_addr");
 	if (daddr) {
-		strncpy(sms->dest_addr, daddr, sizeof(sms->dest_addr));
-		sms->dest_addr[sizeof(sms->dest_addr)-1] = '\0';
+		strncpy(sms->dst.addr, daddr, sizeof(sms->dst.addr));
+		sms->dst.addr[sizeof(sms->dst.addr)-1] = '\0';
 	}
 
 	sms->user_data_len = dbi_result_get_field_length(result, "user_data");
diff --git a/openbsc/src/libmsc/gsm_04_11.c b/openbsc/src/libmsc/gsm_04_11.c
index 5beae53..be6b5b0 100644
--- a/openbsc/src/libmsc/gsm_04_11.c
+++ b/openbsc/src/libmsc/gsm_04_11.c
@@ -143,12 +143,13 @@
 
 	/* FIXME: don't use ID 1 static */
 	sms->sender = subscr_get_by_id(receiver->net, 1);
+	strncpy(sms->src.addr, sms->sender->extension, sizeof(sms->src.addr)-1);
 	sms->reply_path_req = 0;
 	sms->status_rep_req = 0;
 	sms->ud_hdr_ind = 0;
 	sms->protocol_id = 0; /* implicit */
 	sms->data_coding_scheme = dcs;
-	strncpy(sms->dest_addr, receiver->extension, sizeof(sms->dest_addr)-1);
+	strncpy(sms->dst.addr, receiver->extension, sizeof(sms->dst.addr)-1);
 	/* Generate user_data */
 	sms->user_data_len = gsm_7bit_encode(sms->user_data, sms->text);
 
@@ -270,10 +271,10 @@
 
 /* generate a TPDU address field compliant with 03.40 sec. 9.1.2.5 */
 static int gsm340_gen_oa_sub(uint8_t *oa, unsigned int oa_len,
-			 struct gsm_subscriber *subscr)
+			 const struct gsm_sms_addr *src)
 {
 	/* network specific, private numbering plan */
-	return gsm340_gen_oa(oa, oa_len, 0x3, 0x9, subscr->extension);
+	return gsm340_gen_oa(oa, oa_len, src->ton, src->npi, src->addr);
 }
 
 /* generate a msgb containing a TPDU derived from struct gsm_sms,
@@ -299,9 +300,9 @@
 	/* TP-UDHI (indicating TP-UD contains a header) */
 	if (sms->ud_hdr_ind)
 		*smsp |= 0x40;
-	
+
 	/* generate originator address */
-	oa_len = gsm340_gen_oa_sub(oa, sizeof(oa), sms->sender);
+	oa_len = gsm340_gen_oa_sub(oa, sizeof(oa), &sms->src);
 	smsp = msgb_put(msg, oa_len);
 	memcpy(smsp, oa, oa_len);
 
@@ -392,11 +393,11 @@
 	/* mangle first byte to reflect length in bytes, not digits */
 	address_lv[0] = da_len_bytes - 1;
 
-	gsms->destination.ton = (address_lv[1] >> 4) & 7;
-	gsms->destination.npi = address_lv[1] & 0xF;
+	gsms->dst.ton = (address_lv[1] >> 4) & 7;
+	gsms->dst.npi = address_lv[1] & 0xF;
 	/* convert to real number */
-	gsm48_decode_bcd_number(gsms->destination.addr,
-				sizeof(gsms->destination.addr), address_lv, 1);
+	gsm48_decode_bcd_number(gsms->dst.addr,
+				sizeof(gsms->dst.addr), address_lv, 1);
 	smsp += da_len_bytes;
 
 	gsms->protocol_id = *smsp++;
@@ -449,7 +450,7 @@
 	     "MR: 0x%02x PID: 0x%02x, DCS: 0x%02x, DA: %s, "
 	     "UserDataLength: 0x%02x, UserData: \"%s\"\n",
 	     subscr_name(gsms->sender), sms_mti, sms_vpf, gsms->msg_ref,
-	     gsms->protocol_id, gsms->data_coding_scheme, gsms->dest_addr,
+	     gsms->protocol_id, gsms->data_coding_scheme, gsms->dst.addr,
 	     gsms->user_data_len,
 			sms_alphabet == DCS_7BIT_DEFAULT ? gsms->text :
 				osmo_hexdump(gsms->user_data, gsms->user_data_len));
@@ -460,7 +461,7 @@
 	send_signal(0, NULL, gsms, 0);
 
 	/* determine gsms->receiver based on dialled number */
-	gsms->receiver = subscr_get_by_extension(conn->bts->network, gsms->dest_addr);
+	gsms->receiver = subscr_get_by_extension(conn->bts->network, gsms->dst.addr);
 	if (!gsms->receiver) {
 #ifdef BUILD_SMPP
 		rc = smpp_try_deliver(gsms);
diff --git a/openbsc/src/libmsc/smpp_openbsc.c b/openbsc/src/libmsc/smpp_openbsc.c
index b3b8d36..378cbf6 100644
--- a/openbsc/src/libmsc/smpp_openbsc.c
+++ b/openbsc/src/libmsc/smpp_openbsc.c
@@ -118,9 +118,18 @@
 	sms = sms_alloc();
 	sms->source = SMS_SOURCE_SMPP;
 	sms->smpp.sequence_nr = submit->sequence_number;
+
+	/* fill in the destination address */
 	sms->receiver = dest;
-	strncpy(sms->dest_addr, dest->extension, sizeof(sms->dest_addr)-1);
+	sms->dst.ton = submit->dest_addr_ton;
+	sms->dst.npi = submit->dest_addr_npi;
+	strncpy(sms->dst.addr, dest->extension, sizeof(sms->dst.addr)-1);
+
+	/* fill in the source address */
 	sms->sender = subscr_get_by_id(net, 1);
+	sms->src.ton = submit->source_addr_ton;
+	sms->src.npi = submit->source_addr_npi;
+	strncpy(sms->src.addr, (char *)submit->source_addr, sizeof(sms->src.addr)-1);
 
 	if (submit->esm_class & 0x40)
 		sms->ud_hdr_ind = 1;
@@ -312,9 +321,9 @@
 			 sms->sender->extension);
 	}
 
-	deliver.dest_addr_ton	= sms->destination.ton;
-	deliver.dest_addr_npi	= sms->destination.npi;
-	memcpy(deliver.destination_addr, sms->destination.addr,
+	deliver.dest_addr_ton	= sms->dst.ton;
+	deliver.dest_addr_npi	= sms->dst.npi;
+	memcpy(deliver.destination_addr, sms->dst.addr,
 		sizeof(deliver.destination_addr));
 
 	deliver.esm_class	= 1;	/* datagram mode */
@@ -368,9 +377,9 @@
 	struct osmo_smpp_addr dst;
 
 	memset(&dst, 0, sizeof(dst));
-	dst.ton = sms->destination.ton;
-	dst.npi = sms->destination.npi;
-	memcpy(dst.addr, sms->destination.addr, sizeof(dst.addr));
+	dst.ton = sms->dst.ton;
+	dst.npi = sms->dst.npi;
+	memcpy(dst.addr, sms->dst.addr, sizeof(dst.addr));
 
 	esme = smpp_route(g_smsc, &dst);
 	if (!esme)
diff --git a/openbsc/src/utils/Makefile.am b/openbsc/src/utils/Makefile.am
index c5b6a7a..701e9b8 100644
--- a/openbsc/src/utils/Makefile.am
+++ b/openbsc/src/utils/Makefile.am
@@ -4,6 +4,10 @@
 
 bin_PROGRAMS = bs11_config isdnsync
 
+if BUILD_SMPP
+noinst_PROGRAMS = smpp_mirror
+endif
+
 bs11_config_SOURCES = bs11_config.c
 bs11_config_LDADD = $(top_builddir)/src/libcommon/libcommon.a \
 		    $(top_builddir)/src/libbsc/libbsc.a \
@@ -11,3 +15,7 @@
 		    $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOABIS_LIBS)
 
 isdnsync_SOURCES = isdnsync.c
+
+smpp_mirror_SOURCES = smpp_mirror.c
+smpp_mirror_LDADD = $(top_builddir)/src/libcommon/libcommon.a \
+		    $(LIBOSMOCORE_LIBS) $(LIBSMPP34_LIBS)
diff --git a/openbsc/src/utils/smpp_mirror.c b/openbsc/src/utils/smpp_mirror.c
new file mode 100644
index 0000000..213c681
--- /dev/null
+++ b/openbsc/src/utils/smpp_mirror.c
@@ -0,0 +1,316 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <string.h>
+
+#include <netinet/in.h>
+
+#include <smpp34.h>
+#include <smpp34_structs.h>
+#include <smpp34_params.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/write_queue.h>
+
+#include <openbsc/debug.h>
+
+/* FIXME: merge with smpp_smsc.c */
+#define SMPP_SYS_ID_LEN	16
+enum esme_read_state {
+	READ_ST_IN_LEN = 0,
+	READ_ST_IN_MSG = 1,
+};
+/* FIXME: merge with smpp_smsc.c */
+
+struct esme {
+	struct osmo_fd ofd;
+
+	uint32_t own_seq_nr;
+
+	struct osmo_wqueue wqueue;
+	enum esme_read_state read_state;
+	uint32_t read_len;
+	uint32_t read_idx;
+	struct msgb *read_msg;
+
+	uint8_t smpp_version;
+	char system_id[SMPP_SYS_ID_LEN+1];
+	char password[SMPP_SYS_ID_LEN+1];
+};
+
+/* FIXME: merge with smpp_smsc.c */
+#define SMPP34_UNPACK(rc, type, str, data, len)		\
+	memset(str, 0, sizeof(*str));			\
+	rc = smpp34_unpack(type, str, data, len)
+#define INIT_RESP(type, resp, req) 		{ \
+	memset((resp), 0, sizeof(*(resp)));	  \
+	(resp)->command_length	= 0;		  \
+	(resp)->command_id	= type;		  \
+	(resp)->command_status	= ESME_ROK;	  \
+	(resp)->sequence_number	= (req)->sequence_number;	\
+}
+#define PACK_AND_SEND(esme, ptr)	pack_and_send(esme, (ptr)->command_id, ptr)
+static inline uint32_t smpp_msgb_cmdid(struct msgb *msg)
+{
+	uint8_t *tmp = msgb_data(msg) + 4;
+	return ntohl(*(uint32_t *)tmp);
+}
+static uint32_t esme_inc_seq_nr(struct esme *esme)
+{
+	esme->own_seq_nr++;
+	if (esme->own_seq_nr > 0x7fffffff)
+		esme->own_seq_nr = 1;
+
+	return esme->own_seq_nr;
+}
+static int pack_and_send(struct esme *esme, uint32_t type, void *ptr)
+{
+	struct msgb *msg = msgb_alloc(4096, "SMPP_Tx");
+	int rc, rlen;
+	if (!msg)
+		return -ENOMEM;
+
+	rc = smpp34_pack(type, msg->tail, msgb_tailroom(msg), &rlen, ptr);
+	if (rc != 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Error during smpp34_pack(): %s\n",
+		     esme->system_id, smpp34_strerror);
+		msgb_free(msg);
+		return -EINVAL;
+	}
+	msgb_put(msg, rlen);
+
+	return osmo_wqueue_enqueue(&esme->wqueue, msg);
+}
+/* FIXME: merge with smpp_smsc.c */
+
+
+static int smpp_handle_deliver(struct esme *esme, struct msgb *msg)
+{
+	struct deliver_sm_t deliver;
+	struct deliver_sm_resp_t deliver_r;
+	struct submit_sm_t submit;
+	int rc;
+
+	memset(&deliver, 0, sizeof(deliver));
+	SMPP34_UNPACK(rc, DELIVER_SM, &deliver, msgb_data(msg), msgb_length(msg));
+	if (rc < 0)
+		return rc;
+
+	INIT_RESP(DELIVER_SM_RESP, &deliver_r, &deliver);
+
+	PACK_AND_SEND(esme, &deliver_r);
+
+	memset(&submit, 0, sizeof(submit));
+	submit.command_id = SUBMIT_SM;
+	submit.command_status = ESME_ROK;
+	submit.sequence_number = esme_inc_seq_nr(esme);
+
+	submit.dest_addr_ton =  deliver.source_addr_ton;
+	submit.dest_addr_npi =  deliver.source_addr_npi;
+	memcpy(submit.destination_addr, deliver.source_addr,
+		OSMO_MIN(sizeof(submit.destination_addr),
+			 sizeof(deliver.source_addr)));
+
+	submit.source_addr_ton = deliver.dest_addr_ton;
+	submit.source_addr_npi = deliver.dest_addr_npi;
+	memcpy(submit.source_addr, deliver.destination_addr,
+		OSMO_MIN(sizeof(submit.source_addr),
+			 sizeof(deliver.destination_addr)));
+
+	submit.esm_class = deliver.esm_class;
+	submit.protocol_id = deliver.protocol_id;
+	submit.priority_flag = deliver.priority_flag;
+	memcpy(submit.schedule_delivery_time, deliver.schedule_delivery_time,
+	       OSMO_MIN(sizeof(submit.schedule_delivery_time),
+		        sizeof(deliver.schedule_delivery_time)));
+	memcpy(submit.validity_period, deliver.validity_period,
+		OSMO_MIN(sizeof(submit.validity_period),
+			 sizeof(deliver.validity_period)));
+	submit.registered_delivery = deliver.registered_delivery;
+	submit.replace_if_present_flag = deliver.replace_if_present_flag;
+	submit.data_coding = deliver.data_coding;
+	submit.sm_default_msg_id = deliver.sm_default_msg_id;
+	submit.sm_length = deliver.sm_length;
+	memcpy(submit.short_message, deliver.short_message,
+		OSMO_MIN(sizeof(submit.short_message),
+			 sizeof(deliver.short_message)));
+	/* FIXME: TLV? */
+
+	return PACK_AND_SEND(esme, &submit);
+}
+
+static int bind_transceiver(struct esme *esme)
+{
+	struct bind_transceiver_t bind;
+
+	memset(&bind, 0, sizeof(bind));
+	bind.command_id = BIND_TRANSCEIVER;
+	bind.sequence_number = esme_inc_seq_nr(esme);
+	snprintf((char *)bind.system_id, sizeof(bind.system_id), "%s", esme->system_id);
+	snprintf((char *)bind.password, sizeof(bind.password), "%s", esme->password);
+	snprintf((char *)bind.system_type, sizeof(bind.system_type), "mirror");
+	bind.interface_version = esme->smpp_version;
+
+	return PACK_AND_SEND(esme, &bind);
+}
+
+static int smpp_pdu_rx(struct esme *esme, struct msgb *msg)
+{
+	uint32_t cmd_id = smpp_msgb_cmdid(msg);
+	int rc;
+
+	switch (cmd_id) {
+	case DELIVER_SM:
+		rc = smpp_handle_deliver(esme, msg);
+		break;
+	default:
+		break;
+	}
+
+	return rc;
+}
+
+/* FIXME: merge with smpp_smsc.c */
+static int esme_read_cb(struct osmo_fd *ofd)
+{
+	struct esme *esme = ofd->data;
+	uint32_t len;
+	uint8_t *lenptr = (uint8_t *) &len;
+	uint8_t *cur;
+	struct msgb *msg;
+	int rdlen;
+	int rc;
+
+	switch (esme->read_state) {
+	case READ_ST_IN_LEN:
+		rdlen = sizeof(uint32_t) - esme->read_idx;
+		rc = read(ofd->fd, lenptr + esme->read_idx, rdlen);
+		if (rc < 0) {
+			LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %d\n",
+			     esme->system_id, rc);
+		} else if (rc == 0) {
+			goto dead_socket;
+		} else
+			esme->read_idx += rc;
+		if (esme->read_idx >= sizeof(uint32_t)) {
+			esme->read_len = ntohl(len);
+			msg = msgb_alloc(esme->read_len, "SMPP Rx");
+			if (!msg)
+				return -ENOMEM;
+			esme->read_msg = msg;
+			cur = msgb_put(msg, sizeof(uint32_t));
+			memcpy(cur, lenptr, sizeof(uint32_t));
+			esme->read_state = READ_ST_IN_MSG;
+			esme->read_idx = sizeof(uint32_t);
+		}
+		break;
+	case READ_ST_IN_MSG:
+		msg = esme->read_msg;
+		rdlen = esme->read_len - esme->read_idx;
+		rc = read(ofd->fd, msg->tail, OSMO_MIN(rdlen, msgb_tailroom(msg)));
+		if (rc < 0) {
+			LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %d\n",
+				esme->system_id, rc);
+		} else if (rc == 0) {
+			goto dead_socket;
+		} else {
+			esme->read_idx += rc;
+			msgb_put(msg, rc);
+		}
+
+		if (esme->read_idx >= esme->read_len) {
+			rc = smpp_pdu_rx(esme, esme->read_msg);
+			esme->read_msg = NULL;
+			esme->read_idx = 0;
+			esme->read_len = 0;
+			esme->read_state = READ_ST_IN_LEN;
+		}
+		break;
+	}
+
+	return 0;
+dead_socket:
+	msgb_free(esme->read_msg);
+	osmo_fd_unregister(&esme->wqueue.bfd);
+	close(esme->wqueue.bfd.fd);
+	esme->wqueue.bfd.fd = -1;
+	exit(2342);
+
+	return 0;
+}
+
+static void esme_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+	struct esme *esme = ofd->data;
+	int rc;
+
+	rc = write(ofd->fd, msgb_data(msg), msgb_length(msg));
+	if (rc == 0) {
+		osmo_fd_unregister(&esme->wqueue.bfd);
+		close(esme->wqueue.bfd.fd);
+		esme->wqueue.bfd.fd = -1;
+		exit(99);
+	} else if (rc < msgb_length(msg)) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Short write\n", esme->system_id);
+		return;
+	}
+}
+
+static int smpp_esme_init(struct esme *esme, const char *host, uint16_t port)
+{
+	int rc;
+
+	if (port == 0)
+		port = 2775;
+
+	esme->own_seq_nr = rand();
+	esme_inc_seq_nr(esme);
+	osmo_wqueue_init(&esme->wqueue, 10);
+	esme->wqueue.bfd.data = esme;
+	esme->wqueue.read_cb = esme_read_cb;
+	esme->wqueue.write_cb = esme_write_cb;
+
+	rc = osmo_sock_init_ofd(&esme->wqueue.bfd, AF_UNSPEC, SOCK_STREAM,
+				IPPROTO_TCP, host, port, OSMO_SOCK_F_CONNECT);
+	if (rc < 0)
+		return rc;
+
+	return bind_transceiver(esme);
+}
+
+
+int main(int argc, char **argv)
+{
+	struct esme esme;
+	char *host = "localhost";
+	int port = 0;
+	int rc;
+
+	memset(&esme, 0, sizeof(esme));
+
+	osmo_init_logging(&log_info);
+
+	snprintf((char *) esme.system_id, sizeof(esme.system_id), "mirror");
+	snprintf((char *) esme.password, sizeof(esme.password), "mirror");
+	esme.smpp_version = 0x34;
+
+	if (argc >= 2)
+		host = argv[1];
+	if (argc >= 3)
+		port = atoi(argv[2]);
+
+	rc = smpp_esme_init(&esme, host, port);
+	if (rc < 0)
+		exit(1);
+
+	while (1) {
+		osmo_select_main(0);
+	}
+
+	exit(0);
+}