[PATCH] concatenate incoming SMS prior to hand over to smsbox

Paul Bagyenda <[email protected]>
Newsgroups gmane.comp.mobile.kannel.devel
Message-ID <[email protected]>
Hi,

  Attached is a proposal for addressing the above issue. Stipe and I  
have had this discussion for a while, and I finally got round to  
doing it. The main driver for this has been the need for Mbuni MMS  
Gateway (www.mbuni.org), which uses Kannel's libs, to be able to  
receive MMS notifications (for those users who have a GPRS/GSM modem,  
rather than a full-blown MMSC connection).  MMS notifications  
typically span more than one SMS.

  The short of it is that with this patch applied:
-  each incoming SMS is examined to determine if it is part of a  
concatenated set (we look for the concatenate udh)
- if it is, we hold onto it and wait for other parts
- when all parts are received, we combine them and pass on the  
message for further processing
- if after a reasonable interval (30 mins), we haven't seen the other  
parts of a message, we throw the received parts away.

All changes are localised to gw/bb_boxc.c so it should be reasonably  
easy to check that nothing is broken.

  I have tested this with a live implementation, using both  
concatenated plain text and a picture message (which has a longer  
UDH), and it works nicely. Kindly review.

Thanks

Paul

Ps. Also included is a previous patch, proposing some improvements to  
my earlier improvements to the mime module. Generally these had to do  
with reducing the amount of copying of objects.
gateway.diff (application/octet-stream, 13.2 KB)
Index: gw/bb_boxc.c
===================================================================
RCS file: /home/cvs/gateway/gw/bb_boxc.c,v
retrieving revision 1.85
diff -u -r1.85 bb_boxc.c
--- gw/bb_boxc.c	23 Mar 2006 18:36:29 -0000	1.85
+++ gw/bb_boxc.c	5 Sep 2006 08:16:41 -0000
@@ -154,6 +154,8 @@
 static void boxc_sent_push(Boxc*, Msg*);
 static void boxc_sent_pop(Boxc*, Msg*);
 
+static void init_concat_handler(void);
+static void shutdown_concat_handler(void);
 
 /*-------------------------------------------------
  *  receiver thingies
@@ -968,6 +970,8 @@
     smsbox_by_receiver = NULL;
     
     gwlist_remove_producer(flow_threads);
+    /* Stop concat handling */
+    shutdown_concat_handler();
 }
 
 
@@ -1131,6 +1135,7 @@
 
     gwlist_add_producer(outgoing_sms);
     gwlist_add_producer(smsbox_list);
+    init_concat_handler();
 
     smsbox_running = 1;
     
@@ -1351,6 +1356,7 @@
     boxid = NULL;
 }
 
+static Msg *check_concatenation(Msg *msg);
 
 /*
  * Route the incoming message to one of the following input queues:
@@ -1371,6 +1377,11 @@
     s = r = NULL;
     gw_assert(msg_type(msg) == sms);
 
+    if (msg->sms.sms_type == mo) {
+	 if ((msg = check_concatenation(msg)) == NULL)  
+	      return 1; /* i.e. message has been handled. */
+    }
+    
     /* msg_dump(msg, 0); */
 
     /* 
@@ -1531,8 +1542,8 @@
 
         /* debug("bb.sms", 0, "sms_boxc_router: handling message (%p vs %p)",
 	          msg, startmsg); */
+	 ret = route_incoming_to_boxc(msg);
 
-        ret = route_incoming_to_boxc(msg);
         if (ret == 1)
             startmsg = newmsg = NULL;
         else if (ret == -1) {
@@ -1550,3 +1561,183 @@
 
     gwlist_remove_producer(flow_threads);
 }
+
+
+/*--------------------------------
+ * incoming concatenated messages handling
+ */
+
+#define CONCATENATED_TIMEOUT 1800
+typedef struct ConcatMsg {
+     int refnum;
+     int total_parts;
+     int num_parts;
+     time_t trecv;
+     Octstr *key; /* in dict. */
+     Msg *parts[1];
+     /* hidden fields! do not put in an array. */
+} ConcatMsg;
+
+static Dict *incoming_concat_msgs;
+static List *incoming_concat_queue;
+static RWLock *concat_rwlock;
+static int concat_inited = 0;
+
+static void destroy_concatMsg(void *msg);
+static void init_concat_handler(void)
+{
+     if (concat_inited)
+	  return;
+     incoming_concat_msgs = dict_create(1024, destroy_concatMsg);
+     incoming_concat_queue = gwlist_create();
+     concat_rwlock = gw_rwlock_create();
+     concat_inited = 1;
+}
+
+static void shutdown_concat_handler(void)
+{
+     if (!concat_inited)
+	  return;
+     dict_destroy(incoming_concat_msgs);
+     gwlist_destroy(incoming_concat_queue,NULL);
+     gw_rwlock_destroy(concat_rwlock);
+     concat_inited = 0;
+}
+
+static void destroy_concatMsg(void *x)
+{
+     int i;
+     ConcatMsg *msg = x;
+
+     gw_assert(msg);
+     for (i = 0; i < msg->total_parts; i++)
+	  if (msg->parts[i])
+	       msg_destroy(msg->parts[i]);
+     octstr_destroy(msg->key);
+     gw_free(msg);     
+}
+
+/* Checks if message is concatenated. Returns:
+ * -  NULL if message is part of concatenated set and we need to wait for other parts
+ * - non-NULL if it is not, or if we have found all parts and are returning the message.
+ */
+static Msg *check_concatenation(Msg *msg)
+{
+     int l, iel, refnum, pos, c, part, totalparts, i, sixteenbit;
+     Octstr *udh = msg->sms.udhdata, *key;
+     ConcatMsg *cmsg, *x;
+     char xudh[1024];
+     
+     if ((l = octstr_len(udh)) == 0)
+	  return msg;
+     
+     for (pos = 1; pos < l - 1; pos += iel + 2) {
+	  iel = octstr_get_char(udh, pos + 1);
+	  if ((c = octstr_get_char(udh,pos)) == 0 || c == 8)
+	       break;
+     }
+     if (pos >= l)  /* no concat UDH found. */
+	  return msg;
+     
+     /* c = 0 means 8 bit, c = 8 means 16 bit concat info */
+     sixteenbit = (c == 8);
+     refnum = (!sixteenbit) ? octstr_get_char(udh, pos + 2) :
+	  (octstr_get_char(udh, pos + 2) << 8) | octstr_get_char(udh, pos + 3);
+     totalparts = octstr_get_char(udh, pos + 3 + sixteenbit);
+     part = octstr_get_char(udh, pos + 4 + sixteenbit);
+     
+     if (part < 1 || part > totalparts) {
+	  warning(0, "Invalid concatenation UDH [ref = %d] in message from %s!",
+		  refnum, octstr_get_cstr(msg->sms.sender));
+	  return msg;
+     }
+
+     debug("concat.sms", 0, "Got part %d [ref %d, total parts %d] of message from %s. Dump follows:",
+	   part, refnum,totalparts, octstr_get_cstr(msg->sms.sender));
+     msg_dump(msg,0);
+
+     key = octstr_format("%S %d", msg->sms.sender, refnum);          
+     gw_rwlock_wrlock(concat_rwlock);
+     if ((cmsg = dict_get(incoming_concat_msgs, key)) == NULL) {
+	  cmsg = gw_malloc(totalparts * sizeof (cmsg->parts[0]) + sizeof *cmsg);
+	  cmsg->refnum = refnum;
+	  cmsg->total_parts = totalparts;
+	  cmsg->num_parts = 0;
+	  cmsg->trecv = time(NULL);
+	  cmsg->key = octstr_duplicate(key);
+	  memset(cmsg->parts, 0, (cmsg->total_parts+1)*sizeof cmsg->parts[0]); /* clear it. */	  
+
+	  dict_put(incoming_concat_msgs, key, cmsg);
+	  gwlist_append(incoming_concat_queue, cmsg);
+     }
+     octstr_destroy(key); 
+     
+     /* check if we have seen message part before... */
+     if (cmsg->parts[part-1] != NULL) {	  
+	  msg_destroy(msg); /* if we have, destroy this one.. */
+	  warning(0, "Duplicate message part %d, ref %d, from %s. Discarding!",
+		  part, refnum, octstr_get_cstr(msg->sms.sender));
+     } else {
+	  cmsg->num_parts++;
+	  cmsg->parts[part-1] = msg;
+     }
+     
+     if (cmsg->num_parts < cmsg->total_parts) {  /* wait for more parts. */
+	  msg = NULL;
+	  gw_rwlock_unlock(concat_rwlock);     
+	  goto done;
+     }
+
+     /* we have all the parts: Put them together, mod UDH, return message. */
+     msg = cmsg->parts[0];
+     cmsg->parts[0] = NULL; /* remove from array. */
+     debug("concat.sms",0,"Received all concatenated message parts from %s, refnum %d",
+	   octstr_get_cstr(msg->sms.sender), refnum);
+     for (i = 1; i<cmsg->total_parts;i++)
+	  octstr_append(msg->sms.msgdata, cmsg->parts[i]->sms.msgdata);
+
+     /* Delete it from the queue and from the Dict. */
+     gwlist_delete_equal(incoming_concat_queue, cmsg);
+     key = octstr_duplicate(cmsg->key); 
+     dict_put(incoming_concat_msgs, key, NULL); /* delete it. */
+     octstr_destroy(key);
+
+     gw_rwlock_unlock(concat_rwlock);     
+     
+     /* fix up UDH */
+     udh = msg->sms.udhdata;
+     l  = octstr_len(udh);
+     for (pos = 1; pos < l - 1; pos += iel + 2) {
+	  iel = octstr_get_char(udh,pos+1);
+	  if ((c = octstr_get_char(udh,pos)) == 0 || c == 8) {
+	       octstr_delete(udh, pos, iel + 2);
+
+	       if (octstr_len(udh) <= 1) /* no other UDH elements. */
+		    octstr_delete(udh, 0, octstr_len(udh));
+	       else
+		    octstr_set_char(udh, 0, octstr_len(udh) - 1);
+	       break;
+	  }
+     }
+     debug("concat.sms", 0, "Got full message [ref %d] of message from %s. Dumping: ",
+	   refnum, octstr_get_cstr(msg->sms.sender));
+     msg_dump(msg,0);
+done:
+
+     /* Remove any pending messages that are too old. */
+     gw_rwlock_wrlock(concat_rwlock);
+     while (gwlist_len(incoming_concat_queue) > 0 && 
+	    (x = gwlist_get(incoming_concat_queue, 0)) != NULL &&
+	    time(NULL)  - x->trecv > CONCATENATED_TIMEOUT) {
+	  Octstr *xkey = octstr_duplicate(x->key);
+
+	  gwlist_delete(incoming_concat_queue, 0,1);
+	  dict_put(incoming_concat_msgs, xkey, NULL); /* delete it. */	  
+	  
+	  info(0, "Time-out waiting for concatenated message '%s'. Dropping message parts", 
+	       octstr_get_cstr(xkey));
+	  octstr_destroy(xkey);
+     }
+     gw_rwlock_unlock(concat_rwlock);
+     return msg;
+}
Index: gwlib/mime.c
===================================================================
RCS file: /home/cvs/gateway/gwlib/mime.c,v
retrieving revision 1.14
diff -u -r1.14 mime.c
--- gwlib/mime.c	14 Mar 2006 19:57:16 -0000	1.14
+++ gwlib/mime.c	5 Sep 2006 08:16:51 -0000
@@ -74,10 +74,11 @@
 #include "gwlib/mime.h"
 
 struct MIMEEntity {
-    List *headers;
-    List *multiparts;
-    Octstr *body;
-    struct MIMEEntity *start;   /* in case multipart/related */
+     List *headers;
+     List *multiparts;
+     Octstr *body;
+     struct MIMEEntity *start;   /* in case multipart/related */
+     struct MIMEEntity *parent; /* Pointer to the MIME container or NULL */
 };
 
 /********************************************************************
@@ -93,19 +94,27 @@
     e->multiparts = gwlist_create();
     e->body = NULL;
     e->start = NULL;
+    e->parent = NULL;
 
     return e;
 }
 
 void static mime_entity_destroy_item(void *e)
 {
-    mime_entity_destroy(e);
+     ((MIMEEntity *)e)->parent = NULL; /* So that func below will destroy it. */
+     mime_entity_destroy(e);
 }
 
+ /* Added: Never actually destroy a mime entity that has a parent,
+  * only its parent may be destroyed (which will recursively destroy children
+  */
 void mime_entity_destroy(MIMEEntity *e) 
 {
     gw_assert(e != NULL);
 
+    if (e->parent) 
+	 return;
+
     if (e->headers != NULL)
         gwlist_destroy(e->headers, octstr_destroy_item);
     if (e->multiparts != NULL)
@@ -508,9 +517,11 @@
      mime_replace_headers(copy, e->headers);
      copy->body = e->body ? octstr_duplicate(e->body) : NULL;
      
-     for (i = 0, n = gwlist_len(e->multiparts); i < n; i++)
-	  gwlist_append(copy->multiparts, 
-			mime_entity_duplicate(gwlist_get(e->multiparts, i)));
+     for (i = 0, n = gwlist_len(e->multiparts); i < n; i++) {
+	  MIMEEntity *ce = mime_entity_duplicate(gwlist_get(e->multiparts, i));   
+	  gwlist_append(copy->multiparts, ce);
+	  ce->parent = copy;
+     }
      return copy;
 }
 
@@ -536,19 +547,20 @@
 }
 
 
-/* Append  a new part to list of body parts. Copy is made
+/* Append  a new part to list of body parts. Copy is NOT made
  * Note that if it was not multipart, this action makes it so!
  */ 
 void mime_entity_add_part(MIMEEntity *e, MIMEEntity *part)
 {
      gw_assert(e != NULL);
      gw_assert(part != NULL);
-     
-     gwlist_append(e->multiparts, mime_entity_duplicate(part));
+
+     gwlist_append(e->multiparts, part);
+     part->parent = e;
 }
 
 
-/* Get part i in list of body parts. Copy is made*/ 
+/* Get part i in list of body parts. Copy is NOT made*/ 
 MIMEEntity *mime_entity_get_part(MIMEEntity *e, int i)
 {
      MIMEEntity *m;
@@ -558,7 +570,7 @@
 
      m = gwlist_get(e->multiparts, i);
      gw_assert(m);
-     return mime_entity_duplicate(m);
+     return m;
 }
 
 
@@ -575,11 +587,11 @@
      m = gwlist_get(e->multiparts, i);
      gwlist_delete(e->multiparts, i, 1);
      if (m == e->start) e->start = NULL;
-
+     m->parent = NULL;
      mime_entity_destroy(m);
 }
 
-/* Replace part i in list of body parts.  Old one will be deleted */ 
+/* Replace part i in list of body parts.  Old one will be deleted. No copy is made */ 
 void mime_entity_replace_part(MIMEEntity *e, int i, MIMEEntity *newpart)
 {
 
@@ -591,9 +603,12 @@
      
      m = gwlist_get(e->multiparts, i);
      gwlist_delete(e->multiparts, i, 1);
-     gwlist_insert(e->multiparts, i, mime_entity_duplicate(newpart));
+     gwlist_insert(e->multiparts, i, newpart);
+     newpart->parent = e;
+
      if (m == e->start) e->start = NULL;
 
+     m->parent = NULL;
      mime_entity_destroy(m);
 }
 
@@ -611,7 +626,7 @@
      e->body = octstr_duplicate(body);
 }
 
-/* Returns (copy of) the 'start' element of a multi-part entity. */
+/* Returns  the 'start' element of a multi-part entity. */
 MIMEEntity *mime_multipart_start_elem(MIMEEntity *e)
 {
      gw_assert(e != NULL);
@@ -651,7 +666,7 @@
 	       octstr_destroy(start);
      }
      
-     return (e->start) ? mime_entity_duplicate(e->start) : NULL;
+     return e->start;
 }
 
 /********************************************************************
Index: gwlib/mime.h
===================================================================
RCS file: /home/cvs/gateway/gwlib/mime.h,v
retrieving revision 1.10
diff -u -r1.10 mime.h
--- gwlib/mime.h	14 Mar 2006 19:57:16 -0000	1.10
+++ gwlib/mime.h	5 Sep 2006 08:16:51 -0000
@@ -104,6 +104,10 @@
 MIMEEntity *mime_entity_duplicate(MIMEEntity *e);
 
 
+/* NOTE: Most of the functions below returning or taking MIMEEntities
+ * as arguments do not make copies of their arguments
+ */
+
 /* Replace top-level MIME headers: Old ones removed completetly */
 void mime_replace_headers(MIMEEntity *e, List *headers);
 
@@ -113,22 +117,22 @@
  */
 int mime_entity_num_parts(MIMEEntity *e);
 
-/* Append  a new part to list of body parts. Copy is made*/ 
+/* Append  a new part to list of body parts. Copy is NOT made*/ 
 void mime_entity_add_part(MIMEEntity *e, MIMEEntity *part);
 
-/* Get part i in list of body parts. Copy is made*/ 
+/* Get part i in list of body parts. Copy is NOT made*/ 
 MIMEEntity *mime_entity_get_part(MIMEEntity *e, int i);
 
 /* Replace part i in list of body parts.  Old one will be deleted */ 
 void mime_entity_replace_part(MIMEEntity *e, int i, MIMEEntity *newpart);
 
-/* Remove part i in list of body parts. */ 
+/* Remove and destroy part i in list of body parts. */ 
 void mime_entity_remove_part(MIMEEntity *e, int i);
 
 /* Change body element of non-multipart entity. */
 void mime_entity_set_body(MIMEEntity *e, Octstr *body);
 
-/* Returns (copy of) the 'start' element of a multi-part entity. */
+/* Returns the 'start' element of a multi-part entity. */
 MIMEEntity *mime_multipart_start_elem(MIMEEntity *e);
 
 /*
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.