[PATCH] meta-data branch vs. CVS HEAD

Stipe Tolj <[email protected]>
Newsgroups gmane.comp.mobile.kannel.devel
Organization tolj.org system architecture
Message-ID <[email protected]>
Hi list,

attached is a revised version of Alex's meta-data support, i.e. for vendor
specific optional TLVs in SMPP v3.4.

This patch is against CVS HEAD, which makes it more current then the version we
used when we spin-off the 'meta-data' branch in CVS, also fixing minor segfault
possibilities.

Please review and vote for committing to mainline HEAD tree.

Stipe

-------------------------------------------------------------------
Kölner Landstrasse 419
40589 DÃŒsseldorf, NRW, Germany

tolj.org system architecture      Kannel Software Foundation (KSF)
http://www.tolj.org/              http://www.kannel.org/

mailto:st_{at}_tolj.org           mailto:stolj_{at}_kannel.org
-------------------------------------------------------------------
meta_data_cvs_head.diff (text/plain, 45.1 KB)
### Eclipse Workspace Patch 1.0
#P gateway-cvs-head
Index: gw/urltrans.c
===================================================================
RCS file: /home/cvs/gateway/gw/urltrans.c,v
retrieving revision 1.108
diff -u -r1.108 urltrans.c
--- gw/urltrans.c	24 Jun 2008 11:28:41 -0000	1.108
+++ gw/urltrans.c	8 Sep 2008 17:19:48 -0000
@@ -367,6 +367,15 @@
         }
         break;
 
+    case 'D': /* meta_data */
+        if (octstr_len(request->sms.meta_data)) {
+            enc = octstr_duplicate(request->sms.meta_data);
+            octstr_url_encode(enc);
+            octstr_append(result, enc);
+            octstr_destroy(enc);
+        }
+        break;
+
     case 'A':
         if (request->sms.msgdata) {
         enc = octstr_duplicate(request->sms.msgdata);
Index: gw/bb_smscconn.c
===================================================================
RCS file: /home/cvs/gateway/gw/bb_smscconn.c,v
retrieving revision 1.96
diff -u -r1.96 bb_smscconn.c
--- gw/bb_smscconn.c	9 Jan 2008 20:06:56 -0000	1.96
+++ gw/bb_smscconn.c	8 Sep 2008 17:19:44 -0000
@@ -91,6 +91,8 @@
 #include "bb_smscconn_cb.h"    /* callback functions for connections */
 #include "smscconn_p.h"        /* to access counters */
 
+#include "smsc/smpp_pdu.h"     /* access smpp_pdu_init/smpp_pdu_shutdown */
+
 /* passed from bearerbox core */
 
 extern volatile sig_atomic_t bb_status;
@@ -639,6 +641,10 @@
     if (handle_concatenated_mo)
         init_concat_handler();
 
+    /* initialize low level PDUs */
+    if (smpp_pdu_init(cfg) == -1)
+        panic(0, "Connot start with PDU init failed.");
+
     smsc_groups = cfg_get_multi_group(cfg, octstr_imm("smsc"));
     gwlist_add_producer(smsc_list);
     for (i = 0; i < gwlist_len(smsc_groups) && 
@@ -839,6 +845,10 @@
      * to shutdown before calling these?
      */
     gwlist_remove_producer(incoming_sms);
+
+    /* shutdown low levele PDU things */
+    smpp_pdu_shutdown();
+
     return 0;
 }
 
Index: gw/msg-decl.h
===================================================================
RCS file: /home/cvs/gateway/gw/msg-decl.h,v
retrieving revision 1.34
diff -u -r1.34 msg-decl.h
--- gw/msg-decl.h	9 Jan 2008 20:06:57 -0000	1.34
+++ gw/msg-decl.h	8 Sep 2008 17:19:44 -0000
@@ -109,6 +109,7 @@
 		INTEGER(priority);
                 INTEGER(resend_try);
                 INTEGER(resend_time);
+                OCTSTR(meta_data);
 	})
 
 MSG(ack,
Index: gw/smsbox.c
===================================================================
RCS file: /home/cvs/gateway/gw/smsbox.c,v
retrieving revision 1.278
diff -u -r1.278 smsbox.c
--- gw/smsbox.c	24 Jun 2008 11:28:41 -0000	1.278
+++ gw/smsbox.c	8 Sep 2008 17:19:47 -0000
@@ -506,7 +506,7 @@
 				      int *validity, int *deferred,
 				      int *dlr_mask, Octstr **dlr_url, 
 				      Octstr **account, int *pid, int *alt_dcs, 
-				      int *rpi, Octstr **binfo, int *priority)
+				      int *rpi, Octstr **binfo, int *priority, Octstr **meta_data)
 {
     Octstr *name, *val;
     long l;
@@ -596,6 +596,10 @@
 	else if (octstr_case_compare(name, octstr_imm("X-Kannel-Priority")) == 0) {
     	    sscanf(octstr_get_cstr(val),"%d", priority);
 	}
+        else if (octstr_case_compare(name, octstr_imm("X-Kannel-Meta-Data")) == 0) {
+            *meta_data = octstr_duplicate(val);
+            octstr_strip_blanks(*meta_data);
+        }
 	octstr_destroy(name);
 	octstr_destroy(val);
     }
@@ -652,7 +656,7 @@
                                   int *dlr_mask, Octstr **dlr_url,
                                   Octstr **account, int *pid, int *alt_dcs,
                                   int *rpi, List **tolist, Octstr **charset,
-                                  Octstr **binfo, int *priority)
+                                  Octstr **binfo, int *priority, Octstr **meta_data)
 {                                    
 
     Octstr *text, *tmp, *tmp2;
@@ -834,6 +838,13 @@
 	    *priority = tmplong;
 	O_DESTROY(tmp);
     }
+
+    /* meta_data */
+    get_tag(*body, octstr_imm("meta-data"), &tmp, 0, 0);
+    if (tmp) {
+        *meta_data = octstr_duplicate(tmp);
+        O_DESTROY(tmp);
+    }
     
     /* charset from <?xml...encoding=?> */
     tmp = find_charset_encoding(*body);
@@ -873,7 +884,7 @@
 			 int validity, int deferred,
 			 Octstr *dlr_url, int dlr_mask, int pid, int alt_dcs,
 			 int rpi, Octstr *smsc, Octstr *account,
-			 Octstr *charset, Octstr *binfo, int priority)
+			 Octstr *charset, Octstr *binfo, int priority, Octstr *meta_data)
 {
     msg->sms.msgdata = replytext;
     msg->sms.time = time(NULL);
@@ -1031,6 +1042,16 @@
         else
             warning(0, "Tried to change priority to '%d', denied.", priority);
     }
+
+    if (meta_data != NULL) {
+        if (urltrans_accept_x_kannel_headers(trans)) {
+            octstr_destroy(msg->sms.meta_data);
+            msg->sms.meta_data = meta_data;
+        } else {
+            warning(0, "Tried to set Meta-Data field, denied.");
+            O_DESTROY(meta_data);
+        }
+    }
 }
 
 
@@ -1106,7 +1127,7 @@
     unsigned int queued; /* indicate if processes reply is requeued */
 
     Octstr *reply_body, *charset;
-    Octstr *udh, *from, *to, *dlr_url, *account, *smsc, *binfo;
+    Octstr *udh, *from, *to, *dlr_url, *account, *smsc, *binfo, *meta_data;
     int dlr_mask, mclass, mwi, coding, compress, pid, alt_dcs, rpi;
     int validity, deferred, priority;
 
@@ -1125,7 +1146,7 @@
             break;
 
 	octets = 0;
-        from = to = udh = smsc = dlr_url = account = binfo = charset = NULL;
+        from = to = udh = smsc = dlr_url = account = binfo = charset = meta_data = NULL;
         mclass = mwi = coding = compress = pid = alt_dcs = rpi = dlr_mask 
             = validity = deferred = priority = SMS_PARAM_UNDEFINED;
 
@@ -1145,7 +1166,7 @@
 					  &coding, &compress, &validity,
 					  &deferred, &dlr_mask, &dlr_url,
 					  &account, &pid, &alt_dcs, &rpi,
-					  &binfo, &priority);
+					  &binfo, &priority, &meta_data);
             } else if (octstr_case_compare(type, text_plain) == 0) {
                 replytext = octstr_duplicate(reply_body);
                 octstr_destroy(reply_body);
@@ -1155,7 +1176,7 @@
 					  &coding, &compress, &validity,
 					  &deferred, &dlr_mask, &dlr_url,
 					  &account, &pid, &alt_dcs, &rpi,
-					  &binfo, &priority);
+					  &binfo, &priority, &meta_data);
             } else if (octstr_case_compare(type, text_xml) == 0) {
                 replytext = octstr_duplicate(reply_body);
                 octstr_destroy(reply_body);
@@ -1164,7 +1185,7 @@
                         &from, &to, &udh, NULL, NULL, &smsc, &mclass, &mwi,
                         &coding, &compress, &validity, &deferred, &dlr_mask,
                         &dlr_url, &account, &pid, &alt_dcs, &rpi, NULL, &charset,
-                        &binfo, &priority);
+                        &binfo, &priority, &meta_data);
             } else if (octstr_case_compare(type, octet_stream) == 0) {
                 replytext = octstr_duplicate(reply_body);
                 octstr_destroy(reply_body);
@@ -1175,7 +1196,7 @@
 					  &coding, &compress, &validity,
 					  &deferred, &dlr_mask, &dlr_url,
 					  &account, &pid, &alt_dcs, &rpi,
-					  &binfo, &priority);
+					  &binfo, &priority, &meta_data);
             } else {
                 replytext = octstr_duplicate(reply_couldnotrepresent);
             }
@@ -1195,7 +1216,7 @@
         fill_message(msg, trans, replytext, octets, from, to, udh, mclass,
             mwi, coding, compress, validity, deferred, dlr_url,
             dlr_mask, pid, alt_dcs, rpi, smsc, account, charset,
-            binfo, priority);
+            binfo, priority, meta_data);
 
         if (final_url == NULL)
             final_url = octstr_imm("");
@@ -1458,6 +1479,8 @@
 	    	octstr_get_cstr(os));
 	    octstr_destroy(os);
 	}
+        if (octstr_len(msg->sms.meta_data))
+            http_header_add(request_headers, "X-Kannel-Meta-Data", octstr_get_cstr(msg->sms.meta_data));
 	id = remember_receiver(msg, trans, HTTP_METHOD_POST, pattern, request_headers, msg->sms.msgdata, 0);
         semaphore_down(max_pending_requests);
 	http_start_request(caller, HTTP_METHOD_POST, pattern, request_headers,
@@ -1615,6 +1638,11 @@
 	    O_DESTROY(tmp);
 	}
 
+        /* meta_data */
+        if (octstr_len(msg->sms.meta_data)) {
+            OCTSTR_APPEND_XML(xml, "meta-data", msg->sms.meta_data);
+        }
+
 	/* End XML */
 	octstr_append(xml, octstr_imm("\t</submit>\n"));
 	octstr_append(xml, octstr_imm("</message>\n"));
@@ -1955,7 +1983,7 @@
 				 int validity, int deferred,
 				 int *status, int dlr_mask, Octstr *dlr_url, 
 				 Octstr *account, int pid, int alt_dcs, int rpi,
-				 List *receiver, Octstr *binfo, int priority)
+				 List *receiver, Octstr *binfo, int priority, Octstr *meta_data)
 {				     
     Msg *msg = NULL;
     Octstr *newfrom = NULL;
@@ -2283,6 +2311,8 @@
 	goto field_error;
     }
 
+    msg->sms.meta_data = meta_data;
+
     msg->sms.receiver = NULL;
 
     /* 
@@ -2465,11 +2495,11 @@
     URLTranslation *t = NULL;
     Octstr *tmp_string;
     Octstr *from, *to, *charset, *text, *udh, *smsc, *dlr_url, *account;
-    Octstr *binfo;
+    Octstr *binfo, *meta_data;
     int	dlr_mask, mclass, mwi, coding, compress, validity, deferred, pid;
     int alt_dcs, rpi, priority;
 
-    from = to = udh = text = smsc = account = dlr_url = charset = binfo = NULL;
+    from = to = udh = text = smsc = account = dlr_url = charset = binfo = meta_data = NULL;
     mclass = mwi = coding = compress = validity = deferred = dlr_mask = 
         pid = alt_dcs = rpi = priority = SMS_PARAM_UNDEFINED;
  
@@ -2543,6 +2573,8 @@
     if(tmp_string != NULL)
         sscanf(octstr_get_cstr(tmp_string),"%d", &priority);
     
+    meta_data = octstr_duplicate(http_cgi_variable(args, "meta-data"));
+
     /*
      * we required "to" to be defined
      */
@@ -2561,7 +2593,7 @@
     return smsbox_req_handle(t, client_ip, client, from, to, text, charset, udh,
 			     smsc, mclass, mwi, coding, compress, validity, 
 			     deferred, status, dlr_mask, dlr_url, account,
-			     pid, alt_dcs, rpi, NULL, binfo, priority);
+			     pid, alt_dcs, rpi, NULL, binfo, priority, meta_data);
     
 }
 
@@ -2579,7 +2611,7 @@
     List *tolist;
     Octstr *text_html, *text_plain, *text_wml, *text_xml, *octet_stream;
     Octstr *text;
-    Octstr *from, *to, *udh, *smsc, *charset, *dlr_url, *account, *binfo;
+    Octstr *from, *to, *udh, *smsc, *charset, *dlr_url, *account, *binfo, *meta_data;
     int dlr_mask, mclass, mwi, coding, compress, validity, deferred;
     int pid, alt_dcs, rpi, priority;
  
@@ -2591,7 +2623,7 @@
 
     user = pass = ret = type = NULL;
     tolist = NULL;
-    from = to = udh = smsc = account = dlr_url = charset = binfo = NULL;
+    from = to = udh = smsc = account = dlr_url = charset = binfo = meta_data = NULL;
     mclass = mwi = coding = compress = validity = deferred = dlr_mask = 
         pid = alt_dcs = rpi = priority = SMS_PARAM_UNDEFINED;
  
@@ -2607,7 +2639,7 @@
 				  &coding, &compress, &validity, 
 				  &deferred, &dlr_mask, &dlr_url, 
 				  &account, &pid, &alt_dcs, &rpi,
-				  &binfo, &priority);
+				  &binfo, &priority, &meta_data);
     } else if (octstr_case_compare(type, text_plain) == 0 ||
                octstr_case_compare(type, octet_stream) == 0) {
 	get_x_kannel_from_headers(headers, &from, &to, &udh,
@@ -2615,13 +2647,13 @@
 				  &coding, &compress, &validity, 
 				  &deferred, &dlr_mask, &dlr_url, 
 				  &account, &pid, &alt_dcs, &rpi,
-				  &binfo, &priority);
+				  &binfo, &priority, &meta_data);
     } else if (octstr_case_compare(type, text_xml) == 0) {
 	get_x_kannel_from_xml(mt_push, &type, &body, headers, 
                               &from, &to, &udh, &user, &pass, &smsc, &mclass, 
 			      &mwi, &coding, &compress, &validity, &deferred,
 			      &dlr_mask, &dlr_url, &account, &pid, &alt_dcs,
-			      &rpi, &tolist, &charset, &binfo, &priority);
+			      &rpi, &tolist, &charset, &binfo, &priority, &meta_data);
     } else {
 	*status = HTTP_BAD_REQUEST;
 	ret = octstr_create("Invalid content-type");
@@ -2672,7 +2704,7 @@
 				    udh, smsc, mclass, mwi, coding, compress, 
 				    validity, deferred, status, dlr_mask, 
 				    dlr_url, account, pid, alt_dcs, rpi, tolist,
-				    binfo, priority);
+				    binfo, priority, meta_data);
 
     }
 error2:
@@ -2685,6 +2717,7 @@
     octstr_destroy(dlr_url);
     octstr_destroy(account);
     octstr_destroy(binfo);
+    octstr_destroy(meta_data);
 error:
     octstr_destroy(type);
     octstr_destroy(charset);
@@ -2707,7 +2740,7 @@
                                   Octstr *client_ip, int *status)
 {
     Octstr *ret, *type, *user, *pass;
-    Octstr *from, *to, *udh, *smsc, *charset, *dlr_url, *account, *binfo;
+    Octstr *from, *to, *udh, *smsc, *charset, *dlr_url, *account, *binfo, *meta_data;
     Octstr *output;
     Octstr *method_name;
     XMLRPCDocument *msg;
@@ -2715,7 +2748,7 @@
     int	dlr_mask, mclass, mwi, coding, compress, validity, 
 	deferred, pid, alt_dcs, rpi;
 
-    from = to = udh = smsc = account = dlr_url = charset = binfo = NULL;
+    from = to = udh = smsc = account = dlr_url = charset = binfo = meta_data = NULL;
     mclass = mwi = coding = compress = validity = deferred = dlr_mask = 
         pid = alt_dcs = rpi = -1;
  
Index: gw/smsc/smpp_pdu.h
===================================================================
RCS file: /home/cvs/gateway/gw/smsc/smpp_pdu.h,v
retrieving revision 1.14
diff -u -r1.14 smpp_pdu.h
--- gw/smsc/smpp_pdu.h	9 Jan 2008 20:06:53 -0000	1.14
+++ gw/smsc/smpp_pdu.h	8 Sep 2008 17:19:49 -0000
@@ -95,7 +95,7 @@
         #define TLV_INTEGER(name, octets) unsigned long name;
         #define TLV_NULTERMINATED(name, max_len) Octstr *name;
         #define TLV_OCTETS(name, min_len, max_len) Octstr *name;
-        #define OPTIONAL_END
+        #define OPTIONAL_END Dict *tlv;
         #define INTEGER(name, octets) unsigned long name;
         #define NULTERMINATED(name, max_octets) Octstr *name;
         #define OCTETS(name, field_giving_octets) Octstr *name;
@@ -224,6 +224,11 @@
     SMPP_ESME_RINVBCASTCHANIND = 0x00000112,
 };
 
+/* initialize SMPP PDU */
+int smpp_pdu_init(Cfg *cfg);
+/* shutdown SMPP PDU */
+int smpp_pdu_shutdown(void);
+
 SMPP_PDU *smpp_pdu_create(unsigned long type, unsigned long seq_no);
 void smpp_pdu_destroy(SMPP_PDU *pdu);
 int smpp_pdu_is_valid(SMPP_PDU *pdu); /* XXX */
Index: gw/smsc/smpp_pdu.c
===================================================================
RCS file: /home/cvs/gateway/gw/smsc/smpp_pdu.c,v
retrieving revision 1.29
diff -u -r1.29 smpp_pdu.c
--- gw/smsc/smpp_pdu.c	9 Jan 2008 20:06:52 -0000	1.29
+++ gw/smsc/smpp_pdu.c	8 Sep 2008 17:19:49 -0000
@@ -71,6 +71,118 @@
 #define MAX_SMPP_PDU_LEN    (7424)
 
 
+struct smpp_tlv {
+    Octstr *name;
+    long tag;
+    long length;
+    enum { SMPP_TLV_OCTETS = 0, SMPP_TLV_NULTERMINATED = 1, SMPP_TLV_INTEGER = 2 } type;
+};
+
+
+static Dict *tlv_by_tag;
+static Dict *tlv_by_name;
+static int initialized;
+
+
+static void smpp_tlv_destroy(struct smpp_tlv *tlv)
+{
+    if (tlv == NULL)
+        return;
+    octstr_destroy(tlv->name);
+    gw_free(tlv);
+}
+
+
+int smpp_pdu_init(Cfg *cfg)
+{
+    CfgGroup *grp;
+    List *l;
+
+    if (initialized)
+        return 0;
+
+
+    l = cfg_get_multi_group(cfg, octstr_imm("smpp-tlv"));
+    tlv_by_tag = dict_create(gwlist_len(l) > 0 ? gwlist_len(l) : 1, (void(*)(void*))smpp_tlv_destroy);
+    tlv_by_name = dict_create(gwlist_len(l) > 0 ? gwlist_len(l) : 1, NULL);
+    while (l != NULL && (grp = gwlist_extract_first(l)) != NULL) {
+        struct smpp_tlv *tlv;
+        Octstr *tmp;
+
+        tlv = gw_malloc(sizeof(*tlv));
+        if ((tlv->name = cfg_get(grp, octstr_imm("name"))) == NULL) {
+            error(0, "SMPP: Unable to get name for smpp-tlv.");
+            smpp_tlv_destroy(tlv);
+            goto failed;
+        }
+        if (cfg_get_integer(&tlv->tag, grp, octstr_imm("tag")) == -1) {
+            error(0, "SMPP: Unable to get tag for smpp-tlv.");
+            smpp_tlv_destroy(tlv);
+            goto failed;
+        }
+        if (cfg_get_integer(&tlv->length, grp, octstr_imm("length")) == -1) {
+            error(0, "SMPP: Unable to get length for smpp-tlv.");
+            smpp_tlv_destroy(tlv);
+            goto failed;
+        }
+        if ((tmp = cfg_get(grp, octstr_imm("type"))) == NULL) {
+            error(0, "SMPP: Unable to get type for smpp-tlv.");
+            smpp_tlv_destroy(tlv);
+            goto failed;
+        }
+        if (octstr_str_case_compare(tmp, "octetstring") == 0)
+            tlv->type = SMPP_TLV_OCTETS;
+        else if (octstr_str_case_compare(tmp, "nulterminated") == 0)
+            tlv->type = SMPP_TLV_NULTERMINATED;
+        else if (octstr_str_case_compare(tmp, "integer") == 0)
+            tlv->type = SMPP_TLV_INTEGER;
+        else {
+            error(0, "SMPP: Unknown type for smpp-tlv: `%s'", octstr_get_cstr(tmp));
+            octstr_destroy(tmp);
+            smpp_tlv_destroy(tlv);
+            goto failed;
+        }
+        octstr_destroy(tmp);
+        /* put into dict */
+        if (!dict_put_once(tlv_by_name, tlv->name, tlv)) {
+            error(0, "SMPP: Double TLV name %s found.", octstr_get_cstr(tlv->name));
+            smpp_tlv_destroy(tlv);
+            goto failed;
+        }
+        tmp = octstr_format("%ld", tlv->tag);
+        if (!dict_put_once(tlv_by_tag, tmp, tlv)) {
+            error(0, "SMPP: Double TLV tag %s found.", octstr_get_cstr(tmp));
+            octstr_destroy(tmp);
+            goto failed;
+        }
+        octstr_destroy(tmp);
+    }
+    gwlist_destroy(l, NULL);
+
+    initialized = 1;
+    return 0;
+
+failed:
+    dict_destroy(tlv_by_tag);
+    dict_destroy(tlv_by_name);
+    return -1;
+}
+
+
+int smpp_pdu_shutdown(void)
+{
+    if (initialized == 0)
+        return 0;
+
+    initialized = 0;
+    dict_destroy(tlv_by_tag);
+    dict_destroy(tlv_by_name);
+    tlv_by_tag = tlv_by_name = NULL;
+
+    return 0;
+}
+
+
 static long decode_integer(Octstr *os, long pos, int octets)
 {
     unsigned long u;
@@ -101,7 +213,7 @@
     long nul;
 
     *data = NULL;
-
+    
     nul = octstr_search_char(os, '\0', *pos);
     if (nul == -1) {
         warning(0, "SMPP: PDU NULL terminated string (%s) has no NULL.", field_name);
@@ -129,7 +241,7 @@
     #define TLV_INTEGER(name, octets) p->name = -1;
     #define TLV_NULTERMINATED(name, max_len) p->name = NULL;
     #define TLV_OCTETS(name, min_len, max_len) p->name = NULL;
-    #define OPTIONAL_END
+    #define OPTIONAL_END p->tlv = dict_create(1024, octstr_destroy_item);
     #define INTEGER(name, octets) p->name = 0;
     #define NULTERMINATED(name, max_octets) p->name = NULL;
     #define OCTETS(name, field_giving_octetst) p->name = NULL;
@@ -161,7 +273,7 @@
     #define TLV_INTEGER(name, octets) p->name = -1;
     #define TLV_NULTERMINATED(name, max_octets) octstr_destroy(p->name);
     #define TLV_OCTETS(name, min_len, max_len) octstr_destroy(p->name);
-    #define OPTIONAL_END
+    #define OPTIONAL_END dict_destroy(p->tlv);
     #define INTEGER(name, octets) p->name = 0; /* Make sure "p" is used */
     #define NULTERMINATED(name, max_octets) octstr_destroy(p->name);
     #define OCTETS(name, field_giving_octets) octstr_destroy(p->name);
@@ -230,7 +342,55 @@
                 octstr_append(os, p->name); \
             } \
         }
-    #define OPTIONAL_END
+    #define OPTIONAL_END \
+        if (p->tlv != NULL) { \
+            Octstr *key; \
+            List *keys; \
+            struct smpp_tlv *tlv; \
+            keys = dict_keys(p->tlv); \
+            while(keys != NULL && (key = gwlist_extract_first(keys)) != NULL) { \
+                tlv = dict_get(tlv_by_name, key); \
+                if (tlv == NULL) { \
+                    error(0, "SMPP: Unknown TLV `%s', don't send.", octstr_get_cstr(key)); \
+                    octstr_destroy(key); \
+                    continue; \
+                } \
+                switch(tlv->type) { \
+                case SMPP_TLV_INTEGER: { \
+                    long val = atol(octstr_get_cstr(dict_get(p->tlv, key))); \
+                    append_encoded_integer(os, tlv->tag, 2); \
+                    append_encoded_integer(os, tlv->length, 2); \
+                    append_encoded_integer(os, val, tlv->length); \
+                    break; \
+                } \
+                case SMPP_TLV_OCTETS: \
+                case SMPP_TLV_NULTERMINATED: { \
+                    Octstr *val = dict_get(p->tlv, key); \
+                    unsigned long len = octstr_len(val); \
+                    if (len > tlv->length) { \
+                        error(0, "SMPP: Optional field (%s) with invalid length (%ld) (should be %ld) dropped.", \
+                              octstr_get_cstr(key), len, tlv->length);\
+                        octstr_destroy(key); \
+                        continue; \
+                    } \
+                    append_encoded_integer(os, tlv->tag, 2); \
+                    if (tlv->type == SMPP_TLV_NULTERMINATED) \
+                        append_encoded_integer(os, len + 1, 2); \
+                    else \
+                        append_encoded_integer(os, len, 2); \
+                    octstr_append(os, val); \
+                    if (tlv->type == SMPP_TLV_NULTERMINATED) \
+                        octstr_append_char(os, '\0'); \
+                    break; \
+                } \
+                default: \
+                    panic(0, "SMPP: Internal error, unknown configured TLV type %d.", tlv->type); \
+                    break; \
+                } \
+                octstr_destroy(key); \
+            } \
+            gwlist_destroy(keys, octstr_destroy_item); \
+        }
     #define INTEGER(name, octets) \
         append_encoded_integer(os, p->name, octets);
     #define NULTERMINATED(name, max_octets) \
@@ -332,13 +492,57 @@
                 } else
     #define OPTIONAL_END \
                 { \
-                    Octstr *val = octstr_copy(data_without_len, pos, opt_len); \
-                    if (val) octstr_binary_to_hex(val, 0); \
-                    else val = octstr_create(""); \
-                    warning(0, "SMPP: Unknown TLV(0x%04lx,0x%04lx,%s) for PDU type (%s) received!", \
-                            opt_tag, opt_len, octstr_get_cstr(val), pdu->type_name); \
-                    pos += opt_len; \
-                    octstr_destroy(val); \
+                    Octstr *val = NULL; \
+                    struct smpp_tlv *tlv; \
+                    Octstr *tmp = octstr_format("%ld", opt_tag); \
+                    /* check configured TLVs */ \
+                    tlv = dict_get(tlv_by_tag, tmp); \
+                    octstr_destroy(tmp); \
+                    if (tlv != NULL) { \
+                        /* found configured tlv */ \
+                        /* check length */ \
+                        if (opt_len > tlv->length) { \
+                            error(0, "SMPP: Optional field (%s) with invalid length (%ld) (should be %ld) dropped.", \
+                                  octstr_get_cstr(tlv->name), opt_len, tlv->length); \
+                            pos += opt_len; \
+                            continue; \
+                        } \
+                        switch (tlv->type) { \
+                        case SMPP_TLV_INTEGER: { \
+                            long val_i; \
+                            if ((val_i = decode_integer(data_without_len, pos, opt_len)) == -1) \
+                                goto err; \
+                            val = octstr_format("%ld", val_i); \
+                            dict_put(p->tlv, tlv->name, val); \
+                            pos += opt_len; \
+                            break; \
+                        } \
+                        case SMPP_TLV_OCTETS: { \
+                            val = octstr_copy(data_without_len, pos, opt_len); \
+                            dict_put(p->tlv, tlv->name, val); \
+                            pos += opt_len; \
+                            break; \
+                        } \
+                        case SMPP_TLV_NULTERMINATED: { \
+                            if (copy_until_nul(octstr_get_cstr(tlv->name), data_without_len, &pos, opt_len, &val) == 0) \
+                                dict_put(p->tlv, tlv->name, val); \
+                            break; \
+                        } \
+                        default: \
+                            panic(0, "SMPP: Internal error, unknown configured TLV type %d.", tlv->type); \
+                            break; \
+                        } \
+                    } else { \
+                        val = octstr_copy(data_without_len, pos, opt_len); \
+                        if (val) \
+                            octstr_binary_to_hex(val, 0); \
+                        else \
+                            val = octstr_create(""); \
+                        warning(0, "SMPP: Unknown TLV(0x%04lx,0x%04lx,%s) for PDU type (%s) received!", \
+                              opt_tag, opt_len, octstr_get_cstr(val), pdu->type_name); \
+                        octstr_destroy(val); \
+                        pos += opt_len; \
+                    } \
                 } \
             } \
         }
@@ -394,7 +598,17 @@
         if (p->name != NULL) { \
             OCTETS(name, max_len) \
         }
-    #define OPTIONAL_END
+    #define OPTIONAL_END \
+        if (p->tlv != NULL) { \
+            List *keys; \
+            Octstr *key; \
+            keys = dict_keys(p->tlv); \
+            while(keys != NULL && (key = gwlist_extract_first(keys)) != NULL) { \
+                octstr_dump_short(dict_get(p->tlv, key), 2, octstr_get_cstr(key)); \
+                octstr_destroy(key); \
+            } \
+            gwlist_destroy(keys, octstr_destroy_item); \
+        }
     #define INTEGER(name, octets) \
         debug("sms.smpp", 0, "  %s: %lu = 0x%08lx", #name, p->name, p->name);
     #define NULTERMINATED(name, max_octets) \
Index: gw/smsc/smsc_smpp.c
===================================================================
RCS file: /home/cvs/gateway/gw/smsc/smsc_smpp.c,v
retrieving revision 1.104
diff -u -r1.104 smsc_smpp.c
--- gw/smsc/smsc_smpp.c	24 Aug 2008 15:44:00 -0000	1.104
+++ gw/smsc/smsc_smpp.c	8 Sep 2008 17:19:52 -0000
@@ -78,6 +78,7 @@
 #include "sms.h"
 #include "dlr.h"
 #include "bearerbox.h"
+#include "meta_data.h"
 
 #define SMPP_DEFAULT_CHARSET "UTF-8"
 
@@ -559,6 +560,10 @@
     /* set priority flag */
     msg->sms.priority = pdu->u.deliver_sm.priority_flag;
 
+    if (msg->sms.meta_data == NULL)
+        msg->sms.meta_data = octstr_create("");
+    meta_data_set_values(msg->sms.meta_data, pdu->u.deliver_sm.tlv, "smpp");
+
     return msg;
 
 error:
@@ -705,6 +710,10 @@
             }
     }
 
+    if (msg->sms.meta_data == NULL)
+        msg->sms.meta_data = octstr_create("");
+    meta_data_set_values(msg->sms.meta_data, pdu->u.data_sm.tlv, "smpp");
+
     return msg;
 
 error:
@@ -929,6 +938,9 @@
     if (smpp->version > 0x33 && msg->sms.msg_left > 0)
         pdu->u.submit_sm.more_messages_to_send = 1;
 
+    dict_destroy(pdu->u.submit_sm.tlv);
+    pdu->u.submit_sm.tlv = meta_data_get_values(msg->sms.meta_data, "smpp");
+
     return pdu;
 }
 
@@ -1352,7 +1364,12 @@
                       octstr_get_cstr(smpp->conn->id));
                 dlrmsg = handle_dlr(smpp, pdu->u.data_sm.source_addr, NULL, pdu->u.data_sm.message_payload,
                                     pdu->u.data_sm.receipted_message_id, pdu->u.data_sm.message_state);
+                
                 if (dlrmsg != NULL) {
+                    if (dlrmsg->sms.meta_data == NULL)
+                        dlrmsg->sms.meta_data = octstr_create("");
+                    meta_data_set_values(dlrmsg->sms.meta_data, pdu->u.data_sm.tlv, "smpp");
+
                     /* passing DLR to upper layer */
                     reason = bb_smscconn_receive(smpp->conn, dlrmsg);
                 } else {
@@ -1407,12 +1424,18 @@
 
                 dlrmsg = handle_dlr(smpp, pdu->u.deliver_sm.source_addr, pdu->u.deliver_sm.short_message, pdu->u.deliver_sm.message_payload,
                                     pdu->u.deliver_sm.receipted_message_id, pdu->u.deliver_sm.message_state);
-                resp = smpp_pdu_create(deliver_sm_resp,
-                            pdu->u.deliver_sm.sequence_number);
-                if (dlrmsg != NULL)
+                
+                resp = smpp_pdu_create(deliver_sm_resp, pdu->u.deliver_sm.sequence_number);
+                
+                if (dlrmsg != NULL) {
+                    if (dlrmsg->sms.meta_data == NULL)
+                        dlrmsg->sms.meta_data = octstr_create("");
+                    meta_data_set_values(dlrmsg->sms.meta_data, pdu->u.deliver_sm.tlv, "smpp");
+                
                     reason = bb_smscconn_receive(smpp->conn, dlrmsg);
-                else
+                } else {
                     reason = SMSCCONN_SUCCESS;
+                }
                 resp->u.deliver_sm_resp.command_status = smscconn_failure_reason_to_smpp_status(reason);
             } else {/* MO-SMS */
                 resp = smpp_pdu_create(deliver_sm_resp,
Index: doc/userguide/userguide.xml
===================================================================
RCS file: /home/cvs/gateway/doc/userguide/userguide.xml,v
retrieving revision 1.337
diff -u -r1.337 userguide.xml
--- doc/userguide/userguide.xml	28 Jul 2008 20:19:37 -0000	1.337
+++ doc/userguide/userguide.xml	8 Sep 2008 17:19:42 -0000
@@ -6130,6 +6130,10 @@
 the delivery report value
 </entry></row>
 
+<row><entry><literal>%D</literal></entry><entry>
+the Meta-Data value
+</entry></row>
+
 <row><entry><literal>%A</literal></entry><entry>
 the delivery report SMSC reply, if any
 </entry></row>
Index: gwlib/cfg.def
===================================================================
RCS file: /home/cvs/gateway/gwlib/cfg.def,v
retrieving revision 1.134
diff -u -r1.134 cfg.def
--- gwlib/cfg.def	26 Jul 2008 09:21:17 -0000	1.134
+++ gwlib/cfg.def	8 Sep 2008 17:19:52 -0000
@@ -582,6 +582,16 @@
     OCTSTR(unified-prefix)
 )
 
+
+MULTI_GROUP(smpp-tlv,
+    OCTSTR(name)
+    OCTSTR(tag)
+    OCTSTR(length)
+    OCTSTR(type)
+    OCTSTR(smsc-id)
+)
+
+
 #undef OCTSTR
 #undef SINGLE_GROUP
 #undef MULTI_GROUP
Index: gw/meta_data.c
===================================================================
RCS file: gw/meta_data.c
diff -N gw/meta_data.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gw/meta_data.c	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,376 @@
+/* ====================================================================
+ * The Kannel Software License, Version 1.0
+ *
+ * Copyright (c) 2001-2004 Kannel Group
+ * Copyright (c) 1998-2001 WapIT Ltd.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        Kannel Group (http://www.kannel.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Kannel" and "Kannel Group" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact [email protected].
+ *
+ * 5. Products derived from this software may not be called "Kannel",
+ *    nor may "Kannel" appear in their name, without prior written
+ *    permission of the Kannel Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Kannel Group.  For more information on
+ * the Kannel Group, please see <http://www.kannel.org/>.
+ *
+ * Portions of this software are based upon software originally written at
+ * WapIT Ltd., Helsinki, Finland for the Kannel project.
+ */
+
+/*
+ * meta_data.h
+ *
+ * Meta Data manupilation.
+ * 
+ * Alexander Malysh <amalysh kannel.org>, 2007
+ */
+
+#include "gwlib/gwlib.h"
+#include "meta_data.h"
+
+
+struct meta_data {
+    Octstr *group;
+    Dict *values;
+    struct meta_data *next;
+};
+
+
+static struct meta_data *meta_data_create(void)
+{
+    struct meta_data *ret;
+
+    ret = gw_malloc(sizeof(*ret));
+    ret->group = NULL;
+    ret->values = NULL;
+    ret->next = NULL;
+
+    return ret;
+}
+
+
+static void meta_data_destroy(struct meta_data *meta)
+{
+    struct meta_data *next;
+
+    if (meta == NULL)
+        return;
+
+    do {
+        next = meta->next;
+        octstr_destroy(meta->group);
+        dict_destroy(meta->values);
+        gw_free(meta);
+        meta = next;
+    } while(meta != NULL);
+}
+
+
+/* format: ?group-name?key=value&key=value?group?... group, key, value are urlencoded */
+static struct meta_data *meta_data_unpack(const Octstr *data)
+{
+    struct meta_data *ret = NULL, *curr = NULL;
+    const char *str;
+    long pos;
+    Octstr *key = NULL;
+    int type, next_type;
+    long start, end;
+
+    start = end = -1;
+    type = next_type = -1;
+    for (pos = 0, str = octstr_get_cstr(data); pos <= octstr_len(data); str++, pos++) {
+        switch(*str) {
+        case '?':
+            if (start == -1) { /* start of str */
+                start = pos;
+                type = 0;
+            } else if (type == 0) { /* end of group */
+                end = pos;
+                next_type = 1;
+            } else if (type == 2 && key != NULL) { /* end of value */
+                end = pos;
+                next_type = 0;
+            } else if (key == NULL) { /* start of next group without key and value */
+                start = pos;
+                type = 0;
+            } else {
+                /* FAILED */
+                error(0, "MDATA: Found '?' but not expected it end=%ld start=%ld type=%d.", end, start, type);
+                meta_data_destroy(ret);
+                octstr_destroy(key);
+                return NULL;
+            }
+            break;
+        case '=':
+            if (type == 1 && curr != NULL && key == NULL) { /* end of key */
+                end = pos;
+                next_type = 2;
+            } else {
+                /* FAILED */
+                error(0, "MDATA: Found '=' but not expected it end=%ld start=%ld type=%d.", end, start, type);
+                meta_data_destroy(ret);
+                octstr_destroy(key);
+                return NULL;
+            }
+            break;
+        case '&':
+            if (type == 2 && curr != NULL && key != NULL) { /* end of value */
+                end = pos;
+                next_type = 1;
+            } else if (type == 1 && key == NULL) { /* just & skip it */
+                start = pos;
+            } else {
+                /* FAILED */
+                error(0, "MDATA: Found '&' but not expected it end=%ld start=%ld type=%d.", end, start, type);
+                meta_data_destroy(ret);
+                octstr_destroy(key);
+                return NULL;
+            }
+            break;
+        case '\0':
+            if (type == 2) /* end of value */
+                end = pos;
+            break;
+        }
+        if (start >= 0 && end >= 0) {
+            Octstr *tmp;
+
+            if (end - start - 1 == 0)
+                tmp = octstr_create("");
+            else
+                tmp = octstr_create_from_data(str - end + start + 1, end - start - 1);
+
+            octstr_url_decode(tmp);
+
+            switch(type) {
+            case 0: /* group */
+                if (curr == NULL) {
+                    curr = gw_malloc(sizeof(*curr));
+                } else {
+                    curr->next = gw_malloc(sizeof(*curr));
+                    curr = curr->next;
+                }
+                curr->group = tmp;
+                tmp = NULL;
+                curr->values = dict_create(1024, octstr_destroy_item);
+                curr->next = NULL;
+                if (ret == NULL)
+                    ret = curr;
+                debug("meta_data", 0, "new group created `%s'", octstr_get_cstr(curr->group));
+                break;
+            case 1: /* key */
+                key = tmp;
+                tmp = NULL;
+                break;
+            case 2: /* value */
+                debug("meta_data", 0, "group=`%s' key=`%s' value=`%s'", octstr_get_cstr(curr->group),
+                      octstr_get_cstr(key), octstr_get_cstr(tmp));
+                dict_put(curr->values, key, tmp);
+                tmp = NULL;
+                octstr_destroy(key);
+                key = NULL;
+                break;
+            }
+            octstr_destroy(tmp);
+            type = next_type;
+            next_type = -1;
+            start = end;
+            end = -1;
+        }
+    }
+    octstr_destroy(key);
+
+    return ret;
+}
+
+
+static int meta_data_pack(struct meta_data *mdata, Octstr *data)
+{
+    List *l;
+    Octstr *tmp;
+
+    if (mdata == NULL || data == NULL)
+        return -1;
+    /* clear data */
+    octstr_delete(data, 0, octstr_len(data));
+    do {
+        octstr_format_append(data, "?%E?", mdata->group);
+        l = dict_keys(mdata->values);
+        while(l != NULL && (tmp = gwlist_extract_first(l)) != NULL) {
+            octstr_format_append(data, "%E=%E&", tmp, dict_get(mdata->values, tmp));
+            octstr_destroy(tmp);
+        }
+        gwlist_destroy(l, octstr_destroy_item);
+        mdata = mdata->next;
+    } while(mdata != NULL);
+
+    return 0;
+}
+
+
+Dict *meta_data_get_values(const Octstr *data, const char *group)
+{
+    struct meta_data *mdata, *curr;
+    Dict *ret = NULL;
+
+    if (data == NULL || group == NULL)
+        return NULL;
+
+    mdata = meta_data_unpack(data);
+    if (mdata == NULL)
+        return NULL;
+    for (curr = mdata; curr != NULL; curr = curr->next) {
+        if (octstr_str_case_compare(curr->group, group) == 0) {
+            ret = curr->values;
+            curr->values = NULL;
+            break;
+        }
+    }
+
+    meta_data_destroy(mdata);
+
+    return ret;
+}
+
+
+int meta_data_set_values(Octstr *data, const Dict *dict, const char *group)
+{
+    struct meta_data *mdata, *curr;
+    int i;
+
+    if (data == NULL || group == NULL)
+        return -1;
+
+    mdata = meta_data_unpack(data);
+    for (curr = mdata; curr != NULL; curr = curr->next) {
+        if (octstr_str_case_compare(curr->group, group) == 0) {
+            dict_destroy(curr->values);
+            curr->values = (Dict*)dict;
+            break;
+        }
+    }
+
+    if (curr == NULL) {
+        curr = meta_data_create();
+        curr->group = octstr_create(group);
+        curr->values = (Dict*)dict;
+        curr->next = NULL;
+        if (mdata == NULL) {
+            mdata = curr;
+        } else {
+            curr->next = mdata->next;
+            mdata->next = curr;
+        }
+    }
+    i = meta_data_pack(mdata, data);
+    curr->values = NULL;
+
+    meta_data_destroy(mdata);
+
+    return i;
+}
+
+
+int meta_data_set_value(Octstr *data, const char *group, const Octstr *key, const Octstr *value, int replace)
+{
+    struct meta_data *mdata, *curr;
+    int ret = 0;
+
+    if (data == NULL || group == NULL || value == NULL)
+        return -1;
+
+    mdata = meta_data_unpack(data);
+    for (curr = mdata; curr != NULL; curr = curr->next) {
+        if (octstr_str_case_compare(curr->group, group) == 0)
+            break;
+    }
+    if (curr == NULL) {
+        /* group doesn't exists */
+        curr = meta_data_create();
+        curr->group = octstr_create(group);
+        curr->values = dict_create(10, octstr_destroy_item);
+        if (mdata != NULL) {
+            curr->next = mdata->next;
+            mdata->next = curr;
+        } else {
+            mdata = curr;
+        }
+    }
+    if (replace) {
+        /* delete old value if any */
+        dict_put(curr->values, key, NULL);
+        /* put new value */
+        dict_put(curr->values, key, octstr_duplicate(value));
+    } else if (dict_get(curr->values, key) == NULL) {
+        /* put new value */
+        dict_put(curr->values, key, octstr_duplicate(value));
+    }
+
+    /* pack it */
+    ret = meta_data_pack(mdata, data);
+
+    meta_data_destroy(mdata);
+
+    return ret;
+}
+
+
+Octstr *meta_data_get_value(Octstr *data, const char *group, const Octstr *key)
+{
+    struct meta_data *mdata, *curr;
+    Octstr *ret = NULL;
+
+    if (data == NULL || group == NULL || key == NULL)
+        return NULL;
+
+    mdata = meta_data_unpack(data);
+    if (mdata == NULL)
+        return NULL;
+    for (curr = mdata; curr != NULL; curr = curr->next) {
+        if (octstr_str_case_compare(curr->group, group) == 0) {
+            ret = dict_remove(curr->values, key);
+            break;
+        }
+    }
+
+    meta_data_destroy(mdata);
+
+    return ret;
+}
Index: gw/meta_data.h
===================================================================
RCS file: gw/meta_data.h
diff -N gw/meta_data.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gw/meta_data.h	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,85 @@
+/* ====================================================================
+ * The Kannel Software License, Version 1.0
+ *
+ * Copyright (c) 2001-2004 Kannel Group
+ * Copyright (c) 1998-2001 WapIT Ltd.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        Kannel Group (http://www.kannel.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Kannel" and "Kannel Group" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact [email protected].
+ *
+ * 5. Products derived from this software may not be called "Kannel",
+ *    nor may "Kannel" appear in their name, without prior written
+ *    permission of the Kannel Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Kannel Group.  For more information on
+ * the Kannel Group, please see <http://www.kannel.org/>.
+ *
+ * Portions of this software are based upon software originally written at
+ * WapIT Ltd., Helsinki, Finland for the Kannel project.
+ */
+
+/*
+ * meta_data.h
+ *
+ * Meta Data manupilation.
+ * 
+ * Alexander Malysh <amalysh kannel.org>, 2007
+ */
+
+#ifndef META_DATA_H
+#define META_DATA_H
+
+/**
+ * Get Dictionary with all values for this group.
+ */
+Dict *meta_data_get_values(const Octstr *data, const char *group);
+/**
+ * Replace Dictionary for the given group.
+ */
+int meta_data_set_values(Octstr *data, const Dict *dict, const char *group);
+/**
+ * Set or replace value for a given group and key.
+ */
+int meta_data_set_value(Octstr *data, const char *group, const Octstr *key, const Octstr *value, int replace);
+/**
+ * Get value for a given group and key.
+ */
+Octstr *meta_data_get_value(Octstr *data, const char *group, const Octstr *key);
+
+#endif
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.