[PATCH] SMPP box multipart messages

"Rene Kluwen" <[email protected]>
Newsgroups gmane.comp.mobile.kannel.devel
Message-ID <[email protected]>
Okay, to comply with Kannel's common practices, hereby I send in a patch for
open smppbox that allows for all parts of multipart messages to be sent via
the same smsc (letting bearerbox split the message up).

The patch applies to current svn trunk of smppbox.

Known issue: This patch submits a deliver_sm message for all parts of the
original message. Kannel complains about it, because it can only find the
first part in the dlr table.
Since this seems to be a Kannel issue and not smppbox, I am letting it this
way. This doesn't affect any practical, operational issues.

Special thanks to Tomasz Konopka for letting me test in his production
environment.

Can I have any votes on this?

== Rene
multipart13.patch (application/octet-stream, 12.1 KB)
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 4)
+++ ChangeLog	(working copy)
@@ -1,3 +1,8 @@
+2010/06/16
+    Added support for long messages being sent via de same SMSC (by re-assembling
+    the message and sending it to bearerbox as a whole.
+    Thanks to Tomasz Konopka for testing patiently.
+
 2010/06/15
     Added HAVE_SHUTDOWN_CONNECTION #define, so we can use gwlib.a without
     patching.
Index: gw/smppbox.c
===================================================================
--- gw/smppbox.c	(revision 4)
+++ gw/smppbox.c	(working copy)
@@ -97,6 +97,7 @@
 static Counter *boxid;
 static int restart = 0;
 static List *all_boxes;
+static Dict *list_dict;
 
 Octstr *smppbox_id;
 Octstr *our_system_id;
@@ -274,6 +275,42 @@
     return msg;
 }
 
+Msg *catenate_msg(List *list, int total)
+{
+	int current = 1, partno = 1, thismsg, max = 0;
+	Msg *current_msg;
+	Msg *ret = msg_duplicate(gwlist_get(list, 0));
+
+	octstr_destroy(ret->sms.udhdata);
+	ret->sms.udhdata = NULL;
+	octstr_delete(ret->sms.msgdata, 0, octstr_len(ret->sms.msgdata));
+	for (;;) {
+		current_msg = gwlist_get(list, current - 1);
+		if (current_msg) {
+			thismsg = octstr_get_char(current_msg->sms.udhdata, 5);
+			if (thismsg == partno) {
+				octstr_append(ret->sms.msgdata, current_msg->sms.msgdata);
+				max = 0;
+				if (++partno > total) {
+					return ret;
+				}
+			}
+		}
+		if (current >= total) {
+			current = 0;
+		}
+		current++;
+		if (++max > total) {
+			/* test if all parts are in place. If not, fail. */
+			debug("smppbox", 0, "re-assembling message failed.");
+			msg_destroy(ret);
+			return NULL;
+		}
+	}
+	/* unreachable code */
+	return NULL;
+}
+
 static long convert_addr_from_pdu(Octstr *id, Octstr *addr, long ton, long npi)
 {
     long reason = SMPP_ESME_ROK;
@@ -414,13 +451,30 @@
     return 1;
 }
 
-static SMPP_PDU *msg_to_pdu(Boxc *box, Msg *msg)
+Octstr *extract_first_msgid(Octstr *dlr_url)
 {
-    SMPP_PDU *pdu;
+	Octstr *ret;
+	List *parts = octstr_split(dlr_url, octstr_imm(";"));
+
+	if (gwlist_len(parts) > 0) {
+		ret = octstr_duplicate(gwlist_get(parts, 0));
+	}
+	else {
+		/* this should never occur */
+		ret = octstr_create("");
+	}
+	gwlist_destroy(parts, octstr_destroy_item);
+	return ret;
+}
+
+static List *msg_to_pdu(Boxc *box, Msg *msg)
+{
+    SMPP_PDU *pdu, *pdu2;
+    List *pdulist = gwlist_create(), *parts;
     int validity, dlrtype;
     Msg *dlr;
     char *text;
-    Octstr *msgid, *dlr_status, *dlvrd;
+    Octstr *msgid, *msgid2, *dlr_status, *dlvrd;
     
     pdu = smpp_pdu_create(deliver_sm,
     	    	    	  counter_increase(box->smpp_pdu_counter));
@@ -490,6 +544,7 @@
     if (octstr_len(pdu->u.deliver_sm.destination_addr) > 20 ||
         octstr_len(pdu->u.deliver_sm.source_addr) > 20) {
         smpp_pdu_destroy(pdu);
+	gwlist_destroy(pdulist, NULL);
         return NULL;
     }
 
@@ -525,20 +580,17 @@
         pdu->u.deliver_sm.esm_class = pdu->u.deliver_sm.esm_class |
             ESM_CLASS_SUBMIT_RPI;
 
-    /*
-     * set data segments and length
-     */
-
-    pdu->u.deliver_sm.short_message = octstr_duplicate(msg->sms.msgdata);
-
     /* Is this a delivery report? */
     if (msg->sms.sms_type == report_mo) {
 	pdu->u.deliver_sm.esm_class |= (0x04 | 0x08);
 	dlrtype = msg->sms.dlr_mask;
-	dlr = dlr_find(box->boxc_id, msg->sms.dlr_url, msg->sms.receiver, dlrtype);
+	parts = octstr_split(msg->sms.dlr_url, octstr_imm(";"));
+	msgid = gwlist_extract_first(parts);
+	dlr = dlr_find(msg->sms.boxc_id, msgid, msg->sms.receiver, dlrtype);
 	if (dlr == NULL) {
 		/* we could not find a corresponding dlr; nothing to send */
 		smpp_pdu_destroy(pdu);
+		gwlist_destroy(pdulist, NULL);
 		return NULL;
 	}
 	dlvrd = octstr_imm("000");
@@ -566,7 +618,31 @@
 	if (strstr(text, "text:") != NULL) {
 		text = strstr(text, "text:") + (5 * sizeof(char));
 	}
-	pdu->u.deliver_sm.short_message = octstr_format("id:%S sub:001 dlvrd:%S submit date:%ld done date:%ld stat:%S err:000 text:%12s", msg->sms.dlr_url, dlvrd, msg->sms.time, dlr->sms.time, dlr_status, text);
+	if (gwlist_len(parts) > 0) {
+		while ((msgid2 = gwlist_extract_first(parts)) != NULL) {
+			debug("smppbox", 0, "DLR for multipart message: sending %s.", octstr_get_cstr(msgid2));
+			pdu2 = smpp_pdu_create(deliver_sm, counter_increase(box->smpp_pdu_counter));
+			pdu2->u.deliver_sm.esm_class = pdu->u.deliver_sm.esm_class;
+			pdu2->u.deliver_sm.source_addr_ton = pdu->u.deliver_sm.source_addr_ton;
+			pdu2->u.deliver_sm.source_addr_npi = pdu->u.deliver_sm.source_addr_npi;
+			pdu2->u.deliver_sm.dest_addr_ton = pdu->u.deliver_sm.dest_addr_ton;
+			pdu2->u.deliver_sm.dest_addr_npi = pdu->u.deliver_sm.dest_addr_npi;
+			pdu2->u.deliver_sm.data_coding = pdu->u.deliver_sm.data_coding;
+			pdu2->u.deliver_sm.protocol_id = pdu->u.deliver_sm.protocol_id;
+			pdu2->u.deliver_sm.source_addr = octstr_duplicate(pdu->u.deliver_sm.source_addr);
+			pdu2->u.deliver_sm.destination_addr = octstr_duplicate(pdu->u.deliver_sm.destination_addr);
+			pdu2->u.deliver_sm.service_type = octstr_duplicate(pdu->u.deliver_sm.service_type);
+			pdu2->u.deliver_sm.short_message = octstr_format("id:%S sub:001 dlvrd:%S submit date:%ld done date:%ld stat:%S err:000 text:%12s", msgid2, dlvrd, msg->sms.time, dlr->sms.time, dlr_status, text);
+			octstr_destroy(msgid2);
+			gwlist_append(pdulist, pdu2);
+		}
+		msg_destroy(dlr);
+		return pdulist;
+	}
+	else {
+		pdu->u.deliver_sm.short_message = octstr_format("id:%S sub:001 dlvrd:%S submit date:%ld done date:%ld stat:%S err:000 text:%12s", msgid, dlvrd, msg->sms.time, dlr->sms.time, dlr_status, text);
+	}
+	octstr_destroy(msgid);
 	msg_destroy(dlr);
     }
     else {
@@ -575,6 +651,12 @@
 		pdu->u.deliver_sm.registered_delivery = 1;
 	else if (DLR_IS_FAIL(msg->sms.dlr_mask) && !DLR_IS_SUCCESS(msg->sms.dlr_mask))
 		pdu->u.deliver_sm.registered_delivery = 2;
+    	/*
+     	* set data segments and length
+     	*/
+
+    	pdu->u.deliver_sm.short_message = octstr_duplicate(msg->sms.msgdata);
+
     }
 
 
@@ -642,7 +724,8 @@
         pdu->u.deliver_sm.more_messages_to_send = 1;
 */
 
-    return pdu;
+    gwlist_append(pdulist, pdu);
+    return pdulist;
 }
 
 /*
@@ -966,11 +1049,72 @@
     return NULL;
 }
 
+Octstr *concat_msgids(Octstr *msgid, List *list)
+{
+	Octstr *ret = octstr_duplicate(msgid);
+	int i;
+	Msg *msg;
+
+	for (i = 0; i < gwlist_len(list); i++) {
+		msg = gwlist_get(list, i);
+		octstr_append(ret, octstr_imm(";"));
+		octstr_append(ret, msg->sms.dlr_url);
+	}
+	return ret;
+}
+
+void check_multipart(Boxc *box, Msg *msg, int *msg_to_send, Msg **msg2, List **parts_list)
+{
+	int reference, total;
+	Octstr *key;
+
+	if (msg->sms.udhdata && octstr_len(msg->sms.udhdata) == 6 && octstr_get_char(msg->sms.udhdata, 1) == 0) {
+		/* We collect long messages as one and send them to bearerbox as a whole, so they can be sent
+		   from the same smsc. */
+		(*msg_to_send) = 0;
+		debug("smppbox", 0, "assemble multi-part message.");
+		reference = octstr_get_char(msg->sms.udhdata, 3);
+		total = octstr_get_char(msg->sms.udhdata, 4);
+		key = octstr_format("%S-%i", msg->sms.receiver, reference);
+		(*parts_list) = dict_get(list_dict, key);
+		if (NULL == (*parts_list)) {
+			(*parts_list) = gwlist_create();
+			dict_put(list_dict, key, (*parts_list));
+		}
+		debug("smppbox", 0, "received %d of %d.", gwlist_len((*parts_list)) + 1, total);
+		if ((gwlist_len((*parts_list)) + 1) == total) {
+			debug("smppbox", 0, "received all parts of multi-part message.");
+			gwlist_append((*parts_list), msg);
+			/* assemble message */
+			(*msg2) = catenate_msg((*parts_list), total);
+			dict_put(list_dict, key, NULL);
+			octstr_destroy(key);
+			if (NULL == (*msg2)) {
+				/* we could not assemble an appropiate message */
+				debug("smppbox", 0, "Invalid multi-part message.");
+				
+			}
+			else {
+				(*msg2)->sms.smsc_id = box->route_to_smsc ? octstr_duplicate(box->route_to_smsc) : NULL;
+				(*msg2)->sms.boxc_id = octstr_duplicate(box->boxc_id);
+				debug("smppbox", 0, "multi-part message, length: %d.", octstr_len((*msg2)->sms.msgdata));
+				(*msg_to_send) = 1;
+			}
+		}
+		else {
+			gwlist_append((*parts_list), msg);
+			octstr_destroy(key);
+		}
+	}
+}
+
 static void handle_pdu(Connection *conn, Boxc *box, SMPP_PDU *pdu) {
 	SMPP_PDU *resp = NULL;
-	Msg *msg;
+	Msg *msg, *msg2;
 	long reason;
-	Octstr *msgid;
+	Octstr *msgid = NULL;
+	int msg_to_send = 1;
+	List *parts_list = NULL;
 
 	dump_pdu("Got PDU:", box->boxc_id, pdu);
 	switch (pdu->type) {
@@ -1040,46 +1184,73 @@
 		break;
 	case data_sm:
 		msg = data_sm_to_msg(box, pdu, &reason);
+		msg2 = msg;
 		if (msg == NULL) {
 			resp = smpp_pdu_create(generic_nack, pdu->u.submit_sm.sequence_number);
 			resp->u.generic_nack.command_status = SMPP_ESME_RUNKNOWNERR;
 		}
 		else {
+			check_multipart(box, msg, &msg_to_send, &msg2, &parts_list);
 			msg->sms.smsc_id = box->route_to_smsc ? octstr_duplicate(box->route_to_smsc) : NULL;
 			msg->sms.boxc_id = octstr_duplicate(box->boxc_id);
 			msg_dump(msg, 0);
 			resp = smpp_pdu_create(data_sm_resp, pdu->u.data_sm.sequence_number);
 			msgid = generate_smppid(msg);
+			msg->sms.dlr_url = octstr_duplicate(msgid);
 			resp->u.data_sm_resp.message_id = msgid;
-			if (DLR_IS_ENABLED(msg->sms.dlr_mask)) {
-				msg->sms.dlr_url = octstr_duplicate(msgid);
-				dlr_add(box->boxc_id, msgid, msg);
+			msg->sms.dlr_url = octstr_duplicate(msgid);
+			if (msg_to_send) {
+				if (DLR_IS_ENABLED(msg2->sms.dlr_mask)) {
+					msgid = generate_smppid(msg2);
+					if (parts_list) {
+						msg2->sms.dlr_url = concat_msgids(msgid, parts_list);
+					}
+					dlr_add(box->boxc_id, msgid, msg2);
+					octstr_destroy(msgid);
+				}
+				send_msg(box->bearerbox_connection, box, msg2);
+				if (parts_list) {
+					/* destroy values */
+					gwlist_destroy(parts_list, msg_destroy_item);
+				}
 			}
-			send_msg(box->bearerbox_connection, box, msg);
 		}
 		break;
 	case submit_sm:
 		msg = pdu_to_msg(box, pdu, &reason);
+		msg2 = msg;
 		if (msg == NULL) {
 			resp = smpp_pdu_create(generic_nack, pdu->u.submit_sm.sequence_number);
 			resp->u.generic_nack.command_status = SMPP_ESME_RUNKNOWNERR;
 		}
 		else {
+			check_multipart(box, msg, &msg_to_send, &msg2, &parts_list);
 			msg->sms.smsc_id = box->route_to_smsc ? octstr_duplicate(box->route_to_smsc) : NULL;
 			msg->sms.boxc_id = octstr_duplicate(box->boxc_id);
 			msg_dump(msg, 0);
 			resp = smpp_pdu_create(submit_sm_resp, pdu->u.submit_sm.sequence_number);
 			msgid = generate_smppid(msg);
+			msg->sms.dlr_url = octstr_duplicate(msgid);
 			resp->u.submit_sm_resp.message_id = msgid;
-			if (DLR_IS_ENABLED(msg->sms.dlr_mask)) {
-				msg->sms.dlr_url = octstr_duplicate(msgid);
-				dlr_add(box->boxc_id, msgid, msg);
+			if (msg_to_send) {
+				if (DLR_IS_ENABLED(msg2->sms.dlr_mask)) {
+					msgid = generate_smppid(msg2);
+					if (parts_list) {
+						msg2->sms.dlr_url = concat_msgids(msgid, parts_list);
+					}
+					dlr_add(box->boxc_id, msgid, msg2);
+					octstr_destroy(msgid);
+				}
+				send_msg(box->bearerbox_connection, box, msg2);
+				if (parts_list) {
+					/* destroy values */
+					gwlist_destroy(parts_list, msg_destroy_item);
+				}
 			}
-			send_msg(box->bearerbox_connection, box, msg);
 		}
 		break;
 	case deliver_sm_resp:
-		// thank you
+		/* thank you */
 		break;
 	case unbind_resp:
 		box->logged_in = 0;
@@ -1269,6 +1440,7 @@
     Msg *msg, *mack;
     Boxc *box = arg;
     SMPP_PDU *pdu;
+    List *pdulist;
     int dreport;
     Connection *receiver_connection;
 
@@ -1378,11 +1550,14 @@
 		mack->ack.time = msg->sms.time;
 		uuid_copy(mack->ack.id, msg->sms.id);
 
-		pdu = msg_to_pdu(box, msg);
-		if (pdu != NULL) {
+		pdulist = msg_to_pdu(box, msg);
+		if (pdulist != NULL) {
 			receiver_connection = find_receiver_connection(box);
-			send_pdu(receiver_connection, box->boxc_id, pdu);
-			smpp_pdu_destroy(pdu);
+			while ((pdu = gwlist_extract_first(pdulist)) != NULL) {
+				send_pdu(receiver_connection, box->boxc_id, pdu);
+				smpp_pdu_destroy(pdu);
+			}
+			gwlist_destroy(pdulist, NULL);
 		}
 		send_msg(box->bearerbox_connection, box, mack);
 	}
@@ -1695,6 +1870,7 @@
 
 	gwlib_init();
 	all_boxes = gwlist_create();
+	list_dict = dict_create(1, NULL);
 
 	cf_index = get_and_set_debugs(argc, argv, check_args);
 	setup_signal_handlers();
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.