[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);
/*