Explorar o código

Merge pull request #443 from linuxmaniac/vseva/cfgt

modules/cfgt: debugger config test support
Victor Seva %!s(int64=9) %!d(string=hai) anos
pai
achega
393385a097

+ 17 - 0
modules/cfgt/Makefile

@@ -0,0 +1,17 @@
+#
+# cfgt module makefile
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=cfgt.so
+LIBS=
+
+DEFS+=-DKAMAILIO_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/kcore/kcore
+SER_LIBS+=$(SERLIBPATH)/srutils/srutils
+
+include ../../Makefile.modules

+ 126 - 0
modules/cfgt/README

@@ -0,0 +1,126 @@
+cfgt Module
+
+Victor Seva
+
+   sipwise.com
+
+Edited by
+
+Victor Seva
+
+   <[email protected]>
+
+   Copyright © 2015 Victor Seva (sipwise.com)
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+
+              2.1. Kamailio Modules
+              2.2. External Libraries or Applications
+
+        3. Parameters
+
+              3.1. basedir (string)
+              3.2. mask (int)
+              3.3. callid_prefix (string)
+
+   List of Examples
+
+   1.1. Set cfgtrace parameter
+   1.2. Set mask parameter
+   1.3. Set callid_prefix parameter
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+
+        2.1. Kamailio Modules
+        2.2. External Libraries or Applications
+
+   3. Parameters
+
+        3.1. basedir (string)
+        3.2. mask (int)
+        3.3. callid_prefix (string)
+
+1. Overview
+
+   This module provides a report of the way Kamailio SIP Server Platform
+   configuration has been executed as part of a unit test for different
+   SIP scenarios.
+
+   In order to identify defferent scenarios a prefix string should be used
+   at Call-ID header.
+
+2. Dependencies
+
+   2.1. Kamailio Modules
+   2.2. External Libraries or Applications
+
+2.1. Kamailio Modules
+
+   The following modules must be loaded before this module:
+     * None.
+
+2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
+     * None.
+
+3. Parameters
+
+   3.1. basedir (string)
+   3.2. mask (int)
+   3.3. callid_prefix (string)
+
+3.1. basedir (string)
+
+   Control where the config reports should be stored. The dir must exists
+   and Kamailio SIP Server Platform must have perms to write on it.
+
+   Default value is "/tmp".
+
+   Example 1.1. Set cfgtrace parameter
+...
+modparam("cfgt", "basedir", "/var/run/kamailio/cfgtest")
+...
+
+3.2. mask (int)
+
+   mask - Control the type of vars it should display in the report:
+     * 1 - dump null values
+     * 2 - dump avp vars
+     * 4 - dump script vars
+     * 8 - dump xavp vars
+     * 16 - dump DP_OTHER vars
+     * 32 - dump ALL vars
+
+   Default value is "32" (ALL).
+
+   Example 1.2. Set mask parameter
+...
+# dump xavp(8) and avp(4) vars
+modparam("cfgt", "mask", 12)
+...
+
+3.3. callid_prefix (string)
+
+   Prefix used to indentify test scenario messages. Last char of the
+   string will be used as delimiter.
+
+   Default value is "NGCP%" (using "%" as delimiter).
+
+   Example 1.3. Set callid_prefix parameter
+...
+# using '%' as delimiter
+modparam("cfgt", "callid_prefix", "TEST-ID%")
+...

+ 45 - 0
modules/cfgt/cfgt.c

@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 Victor Seva (sipwise.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ *
+ */
+#include "cfgt_mod.h"
+#include "cfgt.h"
+#include "cfgt_int.h"
+
+/*!
+ * \brief cfgt module API export bind function
+ * \param api cfgt API
+ * \return 0 on success, -1 on failure
+ */
+int bind_cfgt(cfgt_api_t* api)
+{
+	if (!api) {
+		LM_ERR("invalid parameter value\n");
+		return -1;
+	}
+	if (init_flag==0) {
+		LM_ERR("configuration error - trying to bind to cfgt module"
+				" before being initialized\n");
+		return -1;
+	}
+
+	api->cfgt_process_route   = cfgt_process_route;
+	return 0;
+}

+ 40 - 0
modules/cfgt/cfgt.h

@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 Victor Seva (sipwise.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ *
+ */
+
+#ifndef _CFGT_BIND_H
+#define _CFGT_BIND_H
+
+#include "../../sr_module.h"
+
+/* export not usable from scripts */
+#define NO_SCRIPT	-1
+
+typedef int (*cfgt_process_route_f)(struct sip_msg *msg, struct action *a);
+
+typedef struct cfgt_api {
+	cfgt_process_route_f cfgt_process_route;
+} cfgt_api_t;
+
+/*! cfgt API export bind function */
+typedef int (*bind_cfgt_t)(cfgt_api_t* api);
+
+#endif

+ 797 - 0
modules/cfgt/cfgt_int.c

@@ -0,0 +1,797 @@
+/**
+ *
+ * Copyright (C) 2015 Victor Seva (sipwise.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+#include <stdio.h>
+#include <sys/stat.h>
+
+#include "../../events.h"
+#include "../../lib/kcore/cmpapi.h"
+#include "../../pvar.h"
+#include "../../rpc.h"
+#include "../../rpc_lookup.h"
+
+#include "cfgt_int.h"
+#include "cfgt_json.h"
+
+static str _cfgt_route_prefix[] = {
+	str_init("start|"),
+	str_init("exit|"),
+	str_init("drop|"),
+	str_init("return|"),
+	{0, 0}
+};
+cfgt_node_p _cfgt_node = NULL;
+cfgt_hash_p _cfgt_uuid = NULL;
+str cfgt_hdr_prefix = {"NGCP%", 5};
+str cfgt_basedir = {"/tmp", 4};
+int cfgt_mask = CFGT_DP_ALL;
+
+static int shm_str_hash_alloc(struct str_hash_table *ht, int size)
+{
+	ht->table = shm_malloc(sizeof(struct str_hash_head) * size);
+
+	if (!ht->table)
+		return -1;
+
+	ht->size = size;
+	return 0;
+}
+
+static int _cfgt_init_hashtable(struct str_hash_table *ht)
+{
+	if (shm_str_hash_alloc(ht, CFGT_HASH_SIZE) != 0)
+	{
+		LM_ERR("Error allocating shared memory hashtable\n");
+		return -1;
+	}
+
+	str_hash_init(ht);
+
+	return 0;
+}
+
+int _cfgt_pv_parse(str *param, pv_elem_p *elem)
+{
+	if (param->s && param->len > 0)
+	{
+		if (pv_parse_format(param, elem)<0)
+		{
+			LM_ERR("malformed or non AVP %.*s AVP definition\n",
+					param->len, param->s);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+void _cfgt_remove_uuid(const str *uuid)
+{
+	struct str_hash_head *head;
+	struct str_hash_entry *entry, *back;
+	int i;
+
+	if(_cfgt_uuid==NULL) return;
+	if(uuid)
+	{
+		lock_get(&_cfgt_uuid->lock);
+		entry = str_hash_get(&_cfgt_uuid->hash, uuid->s, uuid->len);
+		if(entry)
+		{
+			str_hash_del(entry);
+			shm_free(entry->key.s);
+			shm_free(entry);
+			LM_DBG("uuid[%.*s] removed from hash\n", uuid->len, uuid->s);
+		}
+		else LM_DBG("uuid[%.*s] not found in hash\n", uuid->len, uuid->s);
+		lock_release(&_cfgt_uuid->lock);
+	}
+	else
+	{
+		lock_get(&_cfgt_uuid->lock);
+		for(i=0; i<CFGT_HASH_SIZE; i++)
+		{
+			head = _cfgt_uuid->hash.table+i;
+			clist_foreach_safe(head, entry, back, next)
+			{
+				LM_DBG("uuid[%.*s] removed from hash\n",
+					entry->key.len, entry->key.s);
+				str_hash_del(entry);
+				shm_free(entry->key.s);
+				shm_free(entry);
+			}
+			lock_release(&_cfgt_uuid->lock);
+		}
+		LM_DBG("remove all uuids. done\n");
+	}
+}
+
+int _cfgt_get_uuid_id(cfgt_node_p node)
+{
+	struct str_hash_entry *entry;
+
+	if(_cfgt_uuid==NULL || node==NULL || node->uuid.len == 0) return -1;
+	lock_get(&_cfgt_uuid->lock);
+	entry = str_hash_get(&_cfgt_uuid->hash, node->uuid.s, node->uuid.len);
+	if(entry)
+	{
+		entry->u.n = entry->u.n + 1;
+		node->msgid = entry->u.n;
+	}
+	else
+	{
+		entry = shm_malloc(sizeof(struct str_hash_entry));
+		if(entry==NULL)
+		{
+			lock_release(&_cfgt_uuid->lock);
+			LM_ERR("No shared memory left\n");
+			return -1;
+		}
+		if (shm_str_dup(&entry->key, &node->uuid) != 0)
+		{
+			lock_release(&_cfgt_uuid->lock);
+			shm_free(entry);
+			LM_ERR("No shared memory left\n");
+			return -1;
+		}
+		entry->u.n = 1;
+		node->msgid = 1;
+		LM_DBG("Add new entry[%.*s]\n", node->uuid.len, node->uuid.s);
+		str_hash_add(&_cfgt_uuid->hash, entry);
+	}
+	lock_release(&_cfgt_uuid->lock);
+	LM_DBG("msgid:[%d]\n", node->msgid);
+	return 1;
+}
+
+int _cfgt_get_hdr_helper(struct sip_msg *msg, str *res, int mode)
+{
+	struct hdr_field *hf;
+	char *delimiter, *end;
+	str tmp = STR_NULL;
+
+	if(msg==NULL || (mode==0 && res==NULL))
+		return -1;
+
+	/* we need to be sure we have parsed all headers */
+	if(parse_headers(msg, HDR_EOH_F, 0)<0)
+	{
+		LM_ERR("error parsing headers\n");
+		return -1;
+	}
+
+	hf = msg->callid;
+	if(!hf) return 1;
+
+	if(strncmp(hf->body.s, cfgt_hdr_prefix.s, cfgt_hdr_prefix.len)==0)
+	{
+		tmp.s = hf->body.s+cfgt_hdr_prefix.len;
+		delimiter = tmp.s-1;
+		LM_DBG("Prefix detected. delimiter[%c]\n", *delimiter);
+		if(mode==0)
+		{
+			end = strchr(tmp.s, *delimiter);
+			if(end)
+			{
+				tmp.len = end-tmp.s;
+				if(pkg_str_dup(res, &tmp)<0)
+				{
+					LM_ERR("error copying header\n");
+					return -1;
+				}
+				LM_DBG("cfgtest uuid:[%.*s]\n", res->len, res->s);
+				return 0;
+			}
+		}
+		else
+		{
+			tmp.len = res->len;
+			LM_DBG("tmp[%.*s] res[%.*s]\n", tmp.len, tmp.s, res->len, res->s);
+			return STR_EQ(tmp, *res);
+		}
+	}
+	return 1; /* not found */
+}
+
+int _cfgt_get_hdr(struct sip_msg *msg, str *res)
+{
+	return _cfgt_get_hdr_helper(msg, res, 0);
+}
+
+int _cfgt_cmp_hdr(struct sip_msg *msg, str *res)
+{
+	return _cfgt_get_hdr_helper(msg, res, 1);
+}
+
+cfgt_node_p cfgt_create_node(struct sip_msg *msg)
+{
+	cfgt_node_p node;
+
+	node = (cfgt_node_p) pkg_malloc(sizeof(cfgt_node_t));
+	if(node==NULL)
+	{
+		LM_ERR("cannot allocate cfgtest msgnode\n");
+		return node;
+	}
+	memset(node, 0, sizeof(cfgt_node_t));
+	srjson_InitDoc(&node->jdoc, NULL);
+	if (msg)
+	{
+		node->msgid = msg->id;
+		LM_DBG("msgid:%d\n", node->msgid);
+		if(_cfgt_get_hdr(msg, &node->uuid)!=0 || &node->uuid.len==0)
+		{
+			LM_ERR("cannot get value of cfgtest uuid header!!\n");
+			goto error;
+		}
+	}
+	node->jdoc.root = srjson_CreateObject(&node->jdoc);
+	if(node->jdoc.root==NULL)
+	{
+		LM_ERR("cannot create json root\n");
+		goto error;
+	}
+	node->flow = srjson_CreateArray(&node->jdoc);
+	if(node->flow==NULL)
+	{
+		LM_ERR("cannot create json object\n");
+		goto error;
+	}
+	srjson_AddItemToObject(&node->jdoc, node->jdoc.root, "flow\0", node->flow);
+	node->in = srjson_CreateArray(&node->jdoc);
+	if(node->in==NULL)
+	{
+		LM_ERR("cannot create json object\n");
+		goto error;
+	}
+	srjson_AddItemToObject(&node->jdoc, node->jdoc.root, "sip_in\0", node->in);
+	node->out = srjson_CreateArray(&node->jdoc);
+	if(node->out==NULL)
+	{
+		LM_ERR("cannot create json object\n");
+		goto error;
+	}
+	srjson_AddItemToObject(&node->jdoc, node->jdoc.root, "sip_out\0", node->out);
+	LM_DBG("node created\n");
+	return node;
+
+error:
+	srjson_DestroyDoc(&node->jdoc);
+	pkg_free(node);
+	return NULL;
+}
+
+void _cfgt_remove_node(cfgt_node_p node)
+{
+	if(!node) return;
+	srjson_DestroyDoc(&node->jdoc);
+	if(node->uuid.s) pkg_free(node->uuid.s);
+	while(node->flow_head)
+	{
+		node->route = node->flow_head;
+		node->flow_head = node->route->next;
+		pkg_free(node->route);
+		node->route = NULL;
+	}
+	pkg_free(node);
+}
+
+int _cfgt_get_filename(int msgid, str uuid, str *dest, int *dir)
+{
+	int i, lid;
+	char buff_id[INT2STR_MAX_LEN];
+	char *sid;
+	if(dest==NULL || uuid.len == 0) return -1;
+
+	dest->len = cfgt_basedir.len + uuid.len;
+	if(cfgt_basedir.s[cfgt_basedir.len-1]!='/')
+		dest->len = dest->len + 1;
+	sid = sint2strbuf(msgid, buff_id, INT2STR_MAX_LEN, &lid);
+	dest->len += lid + 6;
+	dest->s = (char *) pkg_malloc((dest->len*sizeof(char)+1));
+	if(dest->s==NULL)
+	{
+		LM_ERR("no more memory.\n");
+		return -1;
+	}
+	strncpy(dest->s, cfgt_basedir.s, cfgt_basedir.len);
+	i = cfgt_basedir.len;
+	if(cfgt_basedir.s[cfgt_basedir.len-1]!='/')
+	{
+		strncpy(dest->s+i, "/", 1);
+		i = i + 1;
+	}
+	strncpy(dest->s+i, uuid.s, uuid.len);
+	i = i + uuid.len; (*dir) = i;
+	strncpy(dest->s+i, "\0", 1);
+	i = i + 1;
+	strncpy(dest->s+i, sid, lid);
+	i = i + lid;
+	strncpy(dest->s+i, ".json\0", 6);
+	return 0;
+}
+
+int _cfgt_node2json(cfgt_node_p node)
+{
+       srjson_t *jobj;
+
+       if(!node) return -1;
+       jobj = srjson_CreateStr(&node->jdoc, node->uuid.s, node->uuid.len);
+       if(jobj==NULL)
+       {
+               LM_ERR("cannot create json object\n");
+               return -1;
+       }
+       srjson_AddItemToObject(&node->jdoc, node->jdoc.root, "uuid\0", jobj);
+
+       jobj = srjson_CreateNumber(&node->jdoc, (double)node->msgid);
+       if(jobj==NULL)
+       {
+               LM_ERR("cannot create json object\n");
+               return -1;
+       }
+       srjson_AddItemToObject(&node->jdoc, node->jdoc.root, "msgid\0", jobj);
+       return 0;
+}
+
+void cfgt_save_node(cfgt_node_p node)
+{
+	FILE *fp;
+	str dest = STR_NULL;
+	int dir = 0;
+	if(_cfgt_get_filename(node->msgid, node->uuid, &dest, &dir)<0)
+	{
+		LM_ERR("can't build filename\n");
+		return;
+	}
+	LM_DBG("dir [%s]\n", dest.s);
+	mkdir(dest.s, S_IRWXO|S_IXGRP|S_IRWXU);
+	dest.s[dir] = '/';
+	fp = fopen(dest.s, "w");
+	LM_DBG("file [%s]\n", dest.s);
+	if(fp) {
+		pkg_free(dest.s);
+		dest.s = srjson_Print(&node->jdoc, node->jdoc.root);
+	    if(dest.s==NULL)
+        {
+           LM_ERR("Cannot get the json string\n");
+           fclose(fp);
+           return;
+        }
+        if(fputs(dest.s, fp)<0){
+        	LM_ERR("failed writing to file\n");
+        }
+        fclose(fp);
+        node->jdoc.free_fn(dest.s);
+	}
+	else {
+		LM_ERR("Can't open file [%s] to write\n", dest.s);
+		pkg_free(dest.s);
+	}
+}
+
+void _cfgt_print_node(cfgt_node_p node, int json)
+{
+	char *buf = NULL;
+	cfgt_str_list_p route;
+
+	if(!node) return;
+	if(node->flow_head)
+	{
+		route = node->flow_head;
+		while(route)
+		{
+			if(route == node->route)
+				LM_DBG("[--[%.*s][%d]--]\n",
+					route->s.len, route->s.s, route->type);
+			else LM_DBG("[%.*s][%d]\n",
+					route->s.len, route->s.s, route->type);
+			route = route->next;
+		}
+	}
+	else LM_DBG("flow:empty\n");
+	if(json) {
+		buf = srjson_PrintUnformatted(&node->jdoc, node->jdoc.root);
+		if(buf==NULL)
+		{
+			LM_ERR("Cannot get the json string\n");
+			return;
+		}
+		LM_DBG("node[%p]: id:[%d] uuid:[%.*s] info:[%s]\n",
+			node, node->msgid, node->uuid.len, node->uuid.s, buf);
+		node->jdoc.free_fn(buf);
+	}
+}
+
+int _cfgt_set_dump(struct sip_msg *msg, cfgt_node_p node, str *flow)
+{
+	srjson_t *f, *vars;
+
+	if(node==NULL || flow == NULL) return -1;
+	vars = srjson_CreateObject(&node->jdoc);
+	if(vars==NULL)
+	{
+		LM_ERR("cannot create json object\n");
+		return -1;
+	}
+	if(cfgt_get_json(msg, 30, &node->jdoc, vars)<0)
+	{
+		LM_ERR("cannot get var info\n");
+		return -1;
+	}
+	f = srjson_CreateObject(&node->jdoc);
+	if(f==NULL)
+	{
+		LM_ERR("cannot create json object\n");
+		srjson_Delete(&node->jdoc, vars);
+		return -1;
+	}
+	srjson_AddStrItemToObject(&node->jdoc, f,
+		flow->s, flow->len, vars);
+	srjson_AddItemToArray(&node->jdoc, node->flow, f);
+	LM_DBG("node[%.*s] flow created\n", flow->len, flow->s);
+	return 0;
+}
+
+void _cfgt_set_type(cfgt_str_list_p route, struct action *a)
+{
+	switch(a->type)
+	{
+		case DROP_T:
+			if(a->val[1].u.number&DROP_R_F) {
+				route->type = CFGT_DROP_D;
+				LM_DBG("set[%.*s]->CFGT_DROP_D\n", route->s.len, route->s.s);
+			}
+			if(a->val[1].u.number&RETURN_R_F){
+				route->type = CFGT_DROP_R;
+				LM_DBG("set[%.*s]->CFGT_DROP_R\n", route->s.len, route->s.s);
+			}
+			else {
+				route->type = CFGT_DROP_E;
+				LM_DBG("set[%.*s]->CFGT_DROP_E\n", route->s.len, route->s.s);
+			}
+			break;
+		case ROUTE_T:
+			route->type = CFGT_ROUTE;
+			LM_DBG("set[%.*s]->CFGT_ROUTE\n", route->s.len, route->s.s);
+			break;
+		default:
+			if(route->type!=CFGT_DROP_E)
+			{
+				route->type = CFGT_DROP_R;
+				LM_DBG("[%.*s] no relevant action: CFGT_DROP_R[%d]\n",
+					route->s.len, route->s.s, a->type);
+			}
+			else
+			{
+				LM_DBG("[%.*s] already set to CFGT_DROP_E[%d]\n",
+					route->s.len, route->s.s, a->type);
+			}
+			break;
+	}
+}
+
+int _cfgt_add_routename(cfgt_node_p node, struct action *a,
+		str *routename)
+{
+	cfgt_str_list_p route;
+	int ret = 0;
+
+	if(!node->route) /* initial */
+	{
+		node->route = pkg_malloc(sizeof(cfgt_str_list_t));
+		if(!node->route)
+		{
+			LM_ERR("No more pkg mem\n");
+			return -1;
+		}
+		memset(node->route, 0, sizeof(cfgt_str_list_t));
+		node->flow_head = node->route;
+		node->route->type = CFGT_ROUTE;
+		ret = 1;
+	}
+	else
+	{
+		LM_DBG("actual routename:[%.*s][%d]\n", node->route->s.len,
+			node->route->s.s, node->route->type);
+		if(node->route->prev)
+			LM_DBG("prev routename:[%.*s][%d]\n", node->route->prev->s.len,
+				node->route->prev->s.s,	node->route->prev->type);
+		if(node->route->next)
+			LM_DBG("next routename:[%.*s][%d]\n", node->route->next->s.len,
+				node->route->next->s.s,	node->route->next->type);
+		if(STR_EQ(*routename, node->route->s))
+		{
+			LM_DBG("same route\n");
+			_cfgt_set_type(node->route, a);
+			return 2;
+		}
+		else if(node->route->prev &&
+				STR_EQ(*routename, node->route->prev->s))
+		{
+			LM_DBG("back to route[%.*s]\n", node->route->prev->s.len,
+				node->route->prev->s.s);
+			_cfgt_set_type(node->route->prev, a);
+			return 3;
+		}
+		route = pkg_malloc(sizeof(cfgt_str_list_t));
+		if(!route)
+		{
+			LM_ERR("No more pkg mem\n");
+			return -1;
+		}
+		memset(route, 0, sizeof(cfgt_str_list_t));
+		route->prev = node->route;
+		node->route->next = route;
+		node->route = route;
+		_cfgt_set_type(node->route, a);
+	}
+	node->route->s.s = routename->s;
+	node->route->s.len = routename->len;
+	LM_DBG("add[%d] route:[%.*s]\n", ret, node->route->s.len, node->route->s.s);
+	_cfgt_print_node(node, 0);
+	return ret;
+}
+
+void _cfgt_del_routename(cfgt_node_p node)
+{
+	if(node->route->next!=NULL) {
+		LM_ERR("wtf!! route->next[%p] not null!!\n", node->route->next);
+		_cfgt_print_node(node, 0);
+	}
+	LM_DBG("del route[%.*s]\n", node->route->s.len, node->route->s.s);
+	node->route = node->route->prev;
+	pkg_free(node->route->next);
+	node->route->next = NULL;
+}
+/* dest has to be freed */
+int _cfgt_node_get_flowname(cfgt_str_list_p route, int *indx, str *dest)
+{
+	int i;
+	if(route==NULL) return -1;
+	LM_DBG("routename:[%.*s][%d]\n", route->s.len, route->s.s,
+		route->type);
+	if(indx) i = *indx;
+	else i = route->type-1;
+	if(str_append(&_cfgt_route_prefix[i],
+		&route->s, dest)<0)
+	{
+		LM_ERR("Cannot create route name\n");
+		return -1;
+	}
+	return 0;
+}
+
+int cfgt_process_route(struct sip_msg *msg, struct action *a)
+{
+	str routename;
+	int ret = -1;
+	int indx = 0;
+	str flowname = STR_NULL;
+	if(!_cfgt_node) {
+		LM_ERR("node empty\n");
+		return -1;
+	}
+	if (a->rname==NULL) {
+		LM_DBG("no routename. type:%d\n", a->type);
+		return 0;
+	}
+	LM_DBG("route from action:[%s]\n", a->rname);
+	routename.s = a->rname; routename.len = strlen(a->rname);
+	switch(_cfgt_add_routename(_cfgt_node, a, &routename))
+	{
+		case 2: /* same name */
+			return 0;
+		case 1: /* initial */
+			LM_DBG("Initial route[%.*s]. dump vars\n",
+				_cfgt_node->route->s.len, _cfgt_node->route->s.s);
+			if(_cfgt_node_get_flowname(_cfgt_node->route, &indx, &flowname)<0)
+			{
+				LM_ERR("cannot create flowname\n");
+				return -1;
+			}
+			ret = _cfgt_set_dump(msg, _cfgt_node, &flowname);
+			break;
+		case 0: /* new */
+			LM_DBG("Change from[%.*s] route to route[%.*s]. dump vars\n",
+				_cfgt_node->route->prev->s.len, _cfgt_node->route->prev->s.s,
+				_cfgt_node->route->s.len, _cfgt_node->route->s.s);
+			if(_cfgt_node_get_flowname(_cfgt_node->route, &indx, &flowname)<0)
+			{
+				LM_ERR("cannot create flowname\n");
+				return -1;
+			}
+			ret = _cfgt_set_dump(msg, _cfgt_node, &flowname);
+			break;
+		case 3: /* back to previous */
+			if(_cfgt_node_get_flowname(_cfgt_node->route, 0, &flowname)<0)
+			{
+				LM_ERR("cannot create flowname\n");
+				return -1;
+			}
+			ret = _cfgt_set_dump(msg, _cfgt_node, &flowname);
+			_cfgt_del_routename(_cfgt_node);
+			break;
+		default:
+			return -1;
+	}
+	if(flowname.s) pkg_free(flowname.s);
+	return ret;
+}
+
+/*
+TODO:
+- parse first line, check if is SIP
+- parse for header cfgtest
+*/
+int cfgt_msgin(void *data)
+{
+	srjson_t *jobj;
+	str *buf = (str *) data;
+	if(buf==NULL) return 0;
+	if(_cfgt_node) {
+		cfgt_save_node(_cfgt_node);
+		_cfgt_remove_node(_cfgt_node);
+		LM_DBG("node removed\n");
+		_cfgt_node = NULL;
+	}
+	LM_DBG("msg in:{%.*s}\n", buf->len, buf->s);
+	_cfgt_node = cfgt_create_node(NULL);
+	if(_cfgt_node)
+	{
+		jobj = srjson_CreateStr(&_cfgt_node->jdoc, buf->s, buf->len);
+		if(jobj==NULL)
+		{
+			LM_ERR("cannot create json object\n");
+			return -1;
+		}
+		srjson_AddItemToArray(&_cfgt_node->jdoc, _cfgt_node->in, jobj);
+		return 0;
+	}
+	LM_ERR("_cfgt_node empty\n");
+	return -1;
+}
+
+int cfgt_pre(struct sip_msg *msg, unsigned int flags, void *bar) {
+	str unknown = {"unknown", 7};
+
+	if(_cfgt_node)
+	{
+		if (_cfgt_node->msgid == 0)
+		{
+			LM_DBG("new node\n");
+			if(_cfgt_get_hdr(msg, &_cfgt_node->uuid)!=0 ||
+				_cfgt_node->uuid.len==0)
+			{
+				LM_ERR("cannot get value of cfgtest uuid header."
+					" Using unknown\n");
+				pkg_str_dup(&_cfgt_node->uuid, &unknown);
+			}
+			return _cfgt_get_uuid_id(_cfgt_node);
+		}
+		else
+		{
+			LM_DBG("_cfgt_node->uuid:[%.*s]\n", _cfgt_node->uuid.len,
+				_cfgt_node->uuid.s);
+			if(_cfgt_cmp_hdr(msg, &_cfgt_node->uuid))
+			{
+				LM_DBG("same uuid\n");
+				return 1;
+			}
+			else {
+				LM_DBG("different uuid\n");
+			}
+		}
+	}
+	else { LM_ERR("node empty??\n"); }
+	_cfgt_node = cfgt_create_node(msg);
+	if(_cfgt_node) {
+		LM_DBG("node created\n");
+		return 1;
+	}
+	return -1;
+}
+int cfgt_post(struct sip_msg *msg, unsigned int flags, void *bar) {
+	str flowname = STR_NULL;
+
+	if(_cfgt_node) {
+		LM_DBG("dump last flow\n");
+		if(_cfgt_node_get_flowname(_cfgt_node->route, 0, &flowname)<0)
+			LM_ERR("cannot create flowname\n");
+		else _cfgt_set_dump(msg, _cfgt_node, &flowname);
+		if(flowname.s) pkg_free(flowname.s);
+		cfgt_save_node(_cfgt_node);
+	}
+	return 1;
+}
+
+int cfgt_msgout(void *data)
+{
+	srjson_t *jobj;
+	str *buf = (str *) data;
+	if(buf==NULL) return 0;
+	LM_DBG("msg out:{%.*s}\n", buf->len, buf->s);
+
+	if(_cfgt_node)
+	{
+		jobj = srjson_CreateStr(&_cfgt_node->jdoc, buf->s, buf->len);
+		if(jobj==NULL)
+		{
+			LM_ERR("cannot create json object\n");
+			return -1;
+		}
+		srjson_AddItemToArray(&_cfgt_node->jdoc, _cfgt_node->out, jobj);
+		return 0;
+	}
+	LM_ERR("node empty\n");
+	return -1;
+}
+
+/**
+ *
+ */
+static const char* cfgt_rpc_mask_doc[2] = {
+	"Specify module mask",
+	0
+};
+
+static void cfgt_rpc_mask(rpc_t* rpc, void* ctx){
+	int mask = CFGT_DP_ALL;
+
+	if (rpc->scan(ctx, "*d", &mask) != 1)
+	{
+		rpc->fault(ctx, 500, "invalid parameters");
+		return;
+	}
+	cfgt_mask = mask;
+	rpc->add(ctx, "s", "200 ok");
+}
+
+rpc_export_t cfgt_rpc[] = {
+	{"dbg.mask", cfgt_rpc_mask, cfgt_rpc_mask_doc, 0},
+	{0, 0, 0, 0}
+};
+
+int cfgt_init(void)
+{
+	if (rpc_register_array(cfgt_rpc)!=0)
+	{
+		LM_ERR("failed to register RPC commands\n");
+		return -1;
+	}
+	_cfgt_uuid = shm_malloc(sizeof(cfgt_hash_t));
+	if(_cfgt_uuid==NULL)
+	{
+		LM_ERR("Cannot allocate shared memory\n");
+		return -1;
+	}
+	if(!lock_init(&_cfgt_uuid->lock))
+	{
+		LM_ERR("cannot init the lock\n");
+		shm_free(_cfgt_uuid);
+		_cfgt_uuid = NULL;
+		return -1;
+	}
+	if(_cfgt_init_hashtable(&_cfgt_uuid->hash)<0)
+		return -1;
+	sr_event_register_cb(SREV_NET_DATA_IN, cfgt_msgin);
+	sr_event_register_cb(SREV_NET_DATA_OUT, cfgt_msgout);
+	return 0;
+}

+ 67 - 0
modules/cfgt/cfgt_int.h

@@ -0,0 +1,67 @@
+/**
+ *
+ * Copyright (C) 2015 Victor Seva (sipwise.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+#ifndef _CFGT_INT_H_
+#define _CFGT_INT_H_
+
+#include "../../lib/srutils/srjson.h"
+#include "../../locking.h"
+#include "../../route_struct.h"
+#include "../../str_hash.h"
+
+#define CFGT_HASH_SIZE 32
+
+enum _cfgt_action_type {
+	CFGT_ROUTE=1,
+	CFGT_DROP_E, CFGT_DROP_D, CFGT_DROP_R
+};
+
+typedef struct _cfgt_hash
+{
+	gen_lock_t lock;
+	struct str_hash_table hash;
+	str save_uuid; /* uuid to be save */
+} cfgt_hash_t, *cfgt_hash_p;
+
+typedef struct _cfgt_str_list
+{
+	str s;
+	enum _cfgt_action_type type;
+	struct _cfgt_str_list *next, *prev;
+} cfgt_str_list_t, *cfgt_str_list_p;
+
+typedef struct _cfgt_node
+{
+	srjson_doc_t jdoc;
+	str uuid;
+	int msgid;
+	cfgt_str_list_p flow_head;
+	cfgt_str_list_p route;
+	srjson_t *in, *out, *flow;
+	struct _cfgt_node *next, *prev;
+} cfgt_node_t, *cfgt_node_p;
+
+int cfgt_init(void);
+cfgt_node_p cfgt_create_node(struct sip_msg *msg);
+int cfgt_process_route(struct sip_msg *msg, struct action *a);
+int cfgt_pre(struct sip_msg *msg, unsigned int flags, void *bar);
+int cfgt_post(struct sip_msg *msg, unsigned int flags, void *bar);
+#endif

+ 389 - 0
modules/cfgt/cfgt_json.c

@@ -0,0 +1,389 @@
+/**
+ *
+ * Copyright (C) 2013-2015 Victor Seva (sipwise.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+#include <stdio.h>
+
+#include "../../pvar.h"
+#include "../../mem/shm_mem.h"
+#include "../../xavp.h"
+#include "../pv/pv_xavp.h"
+
+#include "cfgt_json.h"
+
+int _cfgt_get_array_avp_vals(struct sip_msg *msg,
+		pv_param_t *param, srjson_doc_t *jdoc, srjson_t **jobj,
+		str *item_name)
+{
+	struct usr_avp *avp;
+	unsigned short name_type;
+	int_str avp_name;
+	int_str avp_value;
+	struct search_state state;
+	srjson_t *jobjt;
+	memset(&state, 0, sizeof(struct search_state));
+
+	if(pv_get_avp_name(msg, param, &avp_name, &name_type)!=0)
+	{
+		LM_ERR("invalid name\n");
+		return -1;
+	}
+	*jobj = srjson_CreateArray(jdoc);
+	if(*jobj==NULL)
+	{
+		LM_ERR("cannot create json object\n");
+		return -1;
+	}
+	if ((avp=search_first_avp(name_type, avp_name, &avp_value, &state))==0)
+	{
+		goto ok;
+	}
+	do
+	{
+		if(avp->flags & AVP_VAL_STR)
+		{
+			jobjt = srjson_CreateStr(jdoc, avp_value.s.s, avp_value.s.len);
+			if(jobjt==NULL)
+			{
+				LM_ERR("cannot create json object\n");
+				return -1;
+			}
+		} else {
+			jobjt = srjson_CreateNumber(jdoc, avp_value.n);
+			if(jobjt==NULL)
+			{
+				LM_ERR("cannot create json object\n");
+				return -1;
+			}
+		}
+		srjson_AddItemToArray(jdoc, *jobj, jobjt);
+	} while ((avp=search_next_avp(&state, &avp_value))!=0);
+ok:
+	item_name->s = avp_name.s.s;
+	item_name->len = avp_name.s.len;
+	return 0;
+}
+#define CFGT_XAVP_DUMP_SIZE 32
+static str* _cfgt_xavp_dump[CFGT_XAVP_DUMP_SIZE];
+int _cfgt_xavp_dump_lookup(pv_param_t *param)
+{
+	unsigned int i = 0;
+	pv_xavp_name_t *xname;
+
+	if(param==NULL)
+		return -1;
+
+	xname = (pv_xavp_name_t*)param->pvn.u.dname;
+
+	while(_cfgt_xavp_dump[i]!=NULL&&i<CFGT_XAVP_DUMP_SIZE)
+	{
+		if(_cfgt_xavp_dump[i]->len==xname->name.len)
+		{
+			if(strncmp(_cfgt_xavp_dump[i]->s, xname->name.s, xname->name.len)==0)
+				return 1; /* already dump before */
+		}
+		i++;
+	}
+	if(i==CFGT_XAVP_DUMP_SIZE)
+	{
+		LM_WARN("full _cfgt_xavp_dump cache array\n");
+		return 0; /* end cache names */
+	}
+	_cfgt_xavp_dump[i] = &xname->name;
+	return 0;
+}
+
+void _cfgt_get_obj_xavp_val(sr_xavp_t *avp, srjson_doc_t *jdoc, srjson_t **jobj)
+{
+	static char _pv_xavp_buf[128];
+	int result = 0;
+
+	switch(avp->val.type) {
+		case SR_XTYPE_NULL:
+			*jobj = srjson_CreateNull(jdoc);
+		break;
+		case SR_XTYPE_INT:
+			*jobj = srjson_CreateNumber(jdoc, avp->val.v.i);
+		break;
+		case SR_XTYPE_STR:
+			*jobj = srjson_CreateStr(jdoc, avp->val.v.s.s, avp->val.v.s.len);
+		break;
+		case SR_XTYPE_TIME:
+			result = snprintf(_pv_xavp_buf, 128, "%lu", (long unsigned)avp->val.v.t);
+		break;
+		case SR_XTYPE_LONG:
+			result = snprintf(_pv_xavp_buf, 128, "%ld", (long unsigned)avp->val.v.l);
+		break;
+		case SR_XTYPE_LLONG:
+			result = snprintf(_pv_xavp_buf, 128, "%lld", avp->val.v.ll);
+		break;
+		case SR_XTYPE_XAVP:
+			result = snprintf(_pv_xavp_buf, 128, "<<xavp:%p>>", avp->val.v.xavp);
+		break;
+		case SR_XTYPE_DATA:
+			result = snprintf(_pv_xavp_buf, 128, "<<data:%p>>", avp->val.v.data);
+		break;
+		default:
+			LM_WARN("unknown data type\n");
+			*jobj = srjson_CreateNull(jdoc);
+	}
+	if(result<0)
+	{
+		LM_ERR("cannot convert to str\n");
+		*jobj = srjson_CreateNull(jdoc);
+	}
+	else if(*jobj==NULL)
+	{
+		*jobj = srjson_CreateStr(jdoc, _pv_xavp_buf, 128);
+	}
+}
+
+int _cfgt_get_obj_avp_vals(str name, sr_xavp_t *xavp, srjson_doc_t *jdoc, srjson_t **jobj)
+{
+	sr_xavp_t *avp = NULL;
+	srjson_t *jobjt = NULL;
+
+	*jobj = srjson_CreateArray(jdoc);
+	if(*jobj==NULL)
+	{
+		LM_ERR("cannot create json object\n");
+		return -1;
+	}
+	avp = xavp;
+	while(avp!=NULL&&!STR_EQ(avp->name,name))
+	{
+		avp = avp->next;
+	}
+	while(avp!=NULL)
+	{
+		_cfgt_get_obj_xavp_val(avp, jdoc, &jobjt);
+		srjson_AddItemToArray(jdoc, *jobj, jobjt);
+		jobjt = NULL;
+		avp = xavp_get_next(avp);
+	}
+
+	return 0;
+}
+
+int _cfgt_get_obj_xavp_vals(struct sip_msg *msg,
+		pv_param_t *param, srjson_doc_t *jdoc, srjson_t **jobjr,
+		str *item_name)
+{
+	pv_xavp_name_t *xname = (pv_xavp_name_t*)param->pvn.u.dname;
+	sr_xavp_t *xavp = NULL;
+	sr_xavp_t *avp = NULL;
+	srjson_t *jobj = NULL;
+	srjson_t *jobjt = NULL;
+	struct str_list *keys;
+	struct str_list *k;
+
+	*jobjr = srjson_CreateArray(jdoc);
+	if(*jobjr==NULL)
+	{
+		LM_ERR("cannot create json object\n");
+		return -1;
+	}
+
+	item_name->s = xname->name.s;
+	item_name->len = xname->name.len;
+	xavp = xavp_get_by_index(&xname->name, 0, NULL);
+	if(xavp==NULL)
+	{
+		return 0; /* empty */
+	}
+
+	do
+	{
+		if(xavp->val.type==SR_XTYPE_XAVP)
+		{
+			avp = xavp->val.v.xavp;
+			jobj = srjson_CreateObject(jdoc);
+			if(jobj==NULL)
+			{
+				LM_ERR("cannot create json object\n");
+				return -1;
+			}
+			keys = xavp_get_list_key_names(xavp);
+			if(keys!=NULL)
+			{
+				do
+				{
+					_cfgt_get_obj_avp_vals(keys->s, avp, jdoc, &jobjt);
+					srjson_AddStrItemToObject(jdoc, jobj, keys->s.s,
+						keys->s.len, jobjt);
+					k = keys;
+					keys = keys->next;
+					pkg_free(k);
+					jobjt = NULL;
+				}while(keys!=NULL);
+			}
+		}
+		if(jobj!=NULL)
+		{
+			srjson_AddItemToArray(jdoc, *jobjr, jobj);
+			jobj = NULL;
+		}
+	}while((xavp = xavp_get_next(xavp))!=0);
+
+	return 0;
+}
+
+int cfgt_get_json(struct sip_msg* msg, unsigned int mask, srjson_doc_t *jdoc,
+	srjson_t *head)
+{
+	int i;
+	pv_value_t value;
+	pv_cache_t **_pv_cache = pv_cache_get_table();
+	pv_cache_t *el = NULL;
+	srjson_t *jobj = NULL;
+	str item_name = STR_NULL;
+	static char iname[128];
+
+	if(_pv_cache==NULL)
+	{
+		LM_ERR("cannot access pv_cache\n");
+		return -1;
+	}
+	if(jdoc==NULL){
+		LM_ERR("jdoc is null\n");
+		return -1;
+	}
+	if(head==NULL){
+		LM_ERR("head is null\n");
+		return -1;
+	}
+
+	memset(_cfgt_xavp_dump, 0, sizeof(str*)*CFGT_XAVP_DUMP_SIZE);
+	for(i=0;i<PV_CACHE_SIZE;i++)
+	{
+		el = _pv_cache[i];
+		while(el)
+		{
+			if(!(el->spec.type==PVT_AVP||
+				el->spec.type==PVT_SCRIPTVAR||
+				el->spec.type==PVT_XAVP||
+				el->spec.type==PVT_OTHER)||
+				!((el->spec.type==PVT_AVP&&mask&CFGT_DP_AVP)||
+				(el->spec.type==PVT_XAVP&&mask&CFGT_DP_XAVP)||
+				(el->spec.type==PVT_SCRIPTVAR&&mask&CFGT_DP_SCRIPTVAR)||
+				(el->spec.type==PVT_OTHER&&mask&CFGT_DP_OTHER))||
+				(el->spec.trans!=NULL))
+			{
+				el = el->next;
+				continue;
+			}
+			jobj = NULL;
+			item_name.len = 0;
+			item_name.s = 0;
+			iname[0] = '\0';
+			if(el->spec.type==PVT_AVP)
+			{
+				if(el->spec.pvp.pvi.type==PV_IDX_ALL||
+					(el->spec.pvp.pvi.type==PV_IDX_INT&&el->spec.pvp.pvi.u.ival!=0))
+				{
+					el = el->next;
+					continue;
+				}
+				else
+				{
+					if(_cfgt_get_array_avp_vals(msg, &el->spec.pvp, jdoc, &jobj, &item_name)!=0)
+					{
+						LM_WARN("can't get value[%.*s]\n", el->pvname.len, el->pvname.s);
+						el = el->next;
+						continue;
+					}
+					if(srjson_GetArraySize(jdoc, jobj)==0 && !(mask&CFGT_DP_NULL))
+					{
+						el = el->next;
+						continue;
+					}
+					snprintf(iname, 128, "$avp(%.*s)", item_name.len, item_name.s);
+				}
+			}
+			else if(el->spec.type==PVT_XAVP)
+			{
+				if(_cfgt_xavp_dump_lookup(&el->spec.pvp)!=0)
+				{
+					el = el->next;
+					continue;
+				}
+				if(_cfgt_get_obj_xavp_vals(msg, &el->spec.pvp, jdoc, &jobj, &item_name)!=0)
+				{
+					LM_WARN("can't get value[%.*s]\n", el->pvname.len, el->pvname.s);
+					el = el->next;
+					continue;
+				}
+				if(srjson_GetArraySize(jdoc, jobj)==0 && !(mask&CFGT_DP_NULL))
+				{
+					el = el->next;
+					continue;
+				}
+				snprintf(iname, 128, "$xavp(%.*s)", item_name.len, item_name.s);
+			}
+			else
+			{
+				if(pv_get_spec_value(msg, &el->spec, &value)!=0)
+				{
+					LM_WARN("can't get value[%.*s]\n", el->pvname.len, el->pvname.s);
+					el = el->next;
+					continue;
+				}
+				if(value.flags&(PV_VAL_NULL|PV_VAL_EMPTY|PV_VAL_NONE))
+				{
+					if(mask&CFGT_DP_NULL)
+					{
+						jobj = srjson_CreateNull(jdoc);
+					}
+					else
+					{
+						el = el->next;
+						continue;
+					}
+				}else if(value.flags&(PV_VAL_INT)){
+					jobj = srjson_CreateNumber(jdoc, value.ri);
+				}else if(value.flags&(PV_VAL_STR)){
+					jobj = srjson_CreateStr(jdoc, value.rs.s, value.rs.len);
+				}else {
+					LM_WARN("el->pvname[%.*s] value[%d] unhandled\n", el->pvname.len, el->pvname.s,
+						value.flags);
+					el = el->next;
+					continue;
+				}
+				if(jobj==NULL)
+				{
+					LM_ERR("el->pvname[%.*s] empty json object\n", el->pvname.len,
+						el->pvname.s);
+					goto error;
+				}
+				snprintf(iname, 128, "%.*s", el->pvname.len, el->pvname.s);
+			}
+			if(jobj!=NULL)
+			{
+				srjson_AddItemToObject(jdoc, head, iname, jobj);
+			}
+			el = el->next;
+		}
+	}
+	return 0;
+
+error:
+	srjson_Delete(jdoc, head);
+	return -1;
+}

+ 38 - 0
modules/cfgt/cfgt_json.h

@@ -0,0 +1,38 @@
+/**
+ *
+ * Copyright (C) 2013-2015 Victor Seva (sipwise.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef _CFGT_JSON_H
+#define _CFGT_JSON_H
+
+#include "../../lib/srutils/srjson.h"
+#include "../../route_struct.h"
+
+#define CFGT_DP_NULL       1
+#define CFGT_DP_AVP        2
+#define CFGT_DP_SCRIPTVAR  4
+#define CFGT_DP_XAVP       8
+#define CFGT_DP_OTHER     16
+#define CFGT_DP_ALL       31
+
+int cfgt_get_json(struct sip_msg* msg, unsigned int mask, srjson_doc_t *jdoc,
+	srjson_t *head);
+#endif

+ 112 - 0
modules/cfgt/cfgt_mod.c

@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 Victor Seva (sipwise.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ *
+ */
+
+#include "../../events.h"
+#include "../../script_cb.h"
+#include "../../sr_module.h"
+
+#include "cfgt_mod.h"
+#include "cfgt_int.h"
+#include "cfgt_json.h"
+#include "cfgt.h"
+
+MODULE_VERSION
+
+static int mod_init(void);        /*!< Module initialization function */
+static void destroy(void);        /*!< Module destroy function */
+static int child_init(int rank);  /*!< Per-child init function */
+
+extern int bind_cfgt(cfgt_api_t* api);
+
+/*! flag to protect against wrong initialization */
+unsigned int init_flag = 0;
+extern int cfgt_mask;
+extern str cfgt_basedir;
+extern str cfgt_hdr_prefix;
+
+/*! \brief
+ * Exported functions
+ */
+static cmd_export_t cmds[] = {
+	{"cfgt_bind_cfgt", (cmd_function)bind_cfgt, 1, 0, 0, 0},
+	{0, 0, 0, 0, 0, 0}
+};
+
+/*! \brief
+ * Exported parameters
+ */
+static param_export_t params[] = {
+	{"basedir",  PARAM_STR, &cfgt_basedir},
+	{"mask", INT_PARAM, &cfgt_mask },
+	{"callid_prefix", PARAM_STR, &cfgt_hdr_prefix },
+	{0, 0, 0}
+};
+
+struct module_exports exports = {
+	"cfgt",
+	DEFAULT_DLFLAGS, /*!< dlopen flags */
+	cmds,       /*!< Exported functions */
+	params,     /*!< Export parameters */
+	0,          /*!< exported statistics */
+	0,          /*!< exported MI functions */
+	0,          /*!< exported pseudo-variables */
+	0,          /*!< extra processes */
+	mod_init,   /*!< Module initialization function */
+	0,          /*!< Response function */
+	destroy,    /*!< Destroy function */
+	child_init  /*!< Child initialization function */
+};
+
+/*! \brief
+ * Module initialization function
+ */
+static int mod_init(void)
+{
+	unsigned int ALL = REQUEST_CB+FAILURE_CB+ONREPLY_CB
+		+BRANCH_CB+ONSEND_CB+ERROR_CB+LOCAL_CB+EVENT_CB+BRANCH_FAILURE_CB;
+	if(cfgt_init()<0) return -1;
+	if (register_script_cb(cfgt_pre, PRE_SCRIPT_CB|ALL, 0) != 0)
+	{
+		LM_ERR("could not insert PRE_SCRIPT callback");
+		return -1;
+	}
+	if (register_script_cb(cfgt_post, POST_SCRIPT_CB|ALL, 0) != 0)
+	{
+		LM_ERR("could not insert POST_SCRIPT callback");
+		return -1;
+	}
+
+	init_flag = 1;
+	return 0;
+}
+
+static int child_init(int _rank)
+{
+	return 0;
+}
+
+/*! \brief
+ * Module destroy function
+ */
+static void destroy(void)
+{
+}

+ 29 - 0
modules/cfgt/cfgt_mod.h

@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 Victor Seva (sipwise.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ *
+ */
+
+#ifndef _CFGT_MOD_H
+#define _CFGT_MOD_H
+
+/*! flag to protect against wrong initialization */
+extern unsigned int init_flag;
+
+#endif

+ 4 - 0
modules/cfgt/doc/Makefile

@@ -0,0 +1,4 @@
+docs = cfgt.xml
+
+docbook_dir = ../../../docbook
+include $(docbook_dir)/Makefile.module

+ 36 - 0
modules/cfgt/doc/cfgt.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+	<title>cfgt Module</title>
+	<productname class="trade">&kamailioname;</productname>
+	<authorgroup>
+	    <author>
+		<firstname>Victor</firstname>
+		<surname>Seva</surname>
+		<affiliation><orgname>sipwise.com</orgname></affiliation>
+	    </author>
+	    <editor>
+		<firstname>Victor</firstname>
+		<surname>Seva</surname>
+		    <email>[email protected]</email>
+	    </editor>
+	</authorgroup>
+	<copyright>
+	    <year>2015</year>
+	    <holder>Victor Seva (sipwise.com)</holder>
+	</copyright>
+    </bookinfo>
+    <toc></toc>
+
+	<xi:include href="cfgt_admin.xml"/>
+
+</book>

+ 158 - 0
modules/cfgt/doc/cfgt_admin.xml

@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+
+<!-- Module User's Guide -->
+
+<chapter>
+
+	<title>&adminguide;</title>
+
+	<section>
+	<title>Overview</title>
+	<para>
+		This module provides a report of the way &kamailioname; configuration
+		has been executed as part of a unit test for different
+		SIP scenarios.
+	</para>
+	<para>
+		In order to identify defferent scenarios a prefix string should be
+		used at Call-ID header.
+	</para>
+	</section>
+	<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The following modules must be loaded before this module:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>None</emphasis>.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed before running
+		&kamailio; with this module loaded:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>None</emphasis>.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	</section>
+	<section>
+	<title>Parameters</title>
+	<section id="cfg.p.basedir">
+		<title><varname>basedir</varname> (string)</title>
+		<para>
+			Control where the config reports should be stored. The dir must
+			exists and &kamailioname; must have perms to write on it.
+		</para>
+		<para>
+		<emphasis>
+			Default value is <quote>/tmp</quote>.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>cfgtrace</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("cfgt", "basedir", "/var/run/kamailio/cfgtest")
+...
+</programlisting>
+		</example>
+	</section>
+
+	<section id="cfgt.p.mask">
+		<title><varname>mask</varname> (int)</title>
+		<itemizedlist>
+			<para><emphasis>mask</emphasis> - Control the type of vars it should
+			display in the report:
+			</para>
+			<itemizedlist>
+			<listitem><para>
+			  1 - dump null values
+			</para></listitem>
+			<listitem><para>
+			  2 - dump avp vars
+			</para></listitem>
+			<listitem><para>
+			  4 - dump script vars
+			</para></listitem>
+			<listitem><para>
+			  8 - dump xavp vars
+			</para></listitem>
+			<listitem><para>
+			  16 - dump DP_OTHER vars
+			</para></listitem>
+			<listitem><para>
+			  32 - dump ALL vars
+			</para></listitem>
+			</itemizedlist>
+		</itemizedlist>
+		<para>
+		<emphasis>
+			Default value is <quote>32</quote> (ALL).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>mask</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+# dump xavp(8) and avp(4) vars
+modparam("cfgt", "mask", 12)
+...
+</programlisting>
+		</example>
+	</section>
+
+	<section id="cfgt.p.callid_prefix">
+		<title><varname>callid_prefix</varname> (string)</title>
+		<para>
+			Prefix used to indentify test scenario messages. Last char of the
+			string will be used as delimiter.
+		</para>
+		<para>
+		<emphasis>
+			Default value is <quote>NGCP%</quote>
+			(using <quote>%</quote> as delimiter).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>callid_prefix</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+# using '%' as delimiter
+modparam("cfgt", "callid_prefix", "TEST-ID%")
+...
+</programlisting>
+		</example>
+	</section>
+
+	</section>
+
+<!--
+	<section>
+		<title>Usage</title>
+		<para>
+			TODO: add some more info of how this is been used
+		</para>
+	</section>
+-->
+</chapter>

+ 31 - 14
modules/debugger/README

@@ -41,6 +41,7 @@ Daniel-Constantin Mierla
               3.14. log_assign (int)
               3.15. cfgpkgcheck (int)
               3.16. reset_msgid (int)
+              3.17. cfgtest (int)
 
         4. Functions
 
@@ -76,9 +77,10 @@ Daniel-Constantin Mierla
    1.14. Set log_assign parameter
    1.15. Set cfgpkgcheck parameter
    1.16. Set reset_msgid parameter
-   1.17. dbg_breakpoint usage
-   1.18. dbg_pv_dump usage
-   1.19. dbg_sip_msg usage
+   1.17. Set cfgtest parameter
+   1.18. dbg_breakpoint usage
+   1.19. dbg_pv_dump usage
+   1.20. dbg_sip_msg usage
 
 Chapter 1. Admin Guide
 
@@ -108,6 +110,7 @@ Chapter 1. Admin Guide
         3.14. log_assign (int)
         3.15. cfgpkgcheck (int)
         3.16. reset_msgid (int)
+        3.17. cfgtest (int)
 
    4. Functions
 
@@ -183,6 +186,7 @@ Chapter 1. Admin Guide
    3.14. log_assign (int)
    3.15. cfgpkgcheck (int)
    3.16. reset_msgid (int)
+   3.17. cfgtest (int)
 
 3.1. cfgtrace (int)
 
@@ -389,13 +393,26 @@ modparam("debugger", "cfgpkgcheck", 1)
 modparam("debugger", "reset_msgid", 1)
 ...
 
+3.17. cfgtest (int)
+
+   Control whether the cfgt module is enabled or disabled at startup.
+   Module cfgt needs to be loaded before.
+
+   Default value is "0" (disabled).
+
+   Example 1.17. Set cfgtest parameter
+...
+loadmodule "cfgt.so"
+modparam("debugger", "cfgtest", 1)
+...
+
 4. Functions
 
    4.1. dbg_breakpoint(mode)
    4.2. dbg_pv_dump([mask] [, level])
    4.3. dbg_sip_msg([log_level], [facility])
 
-4.1. dbg_breakpoint(mode)
+4.1.  dbg_breakpoint(mode)
 
    Anchor a breakpoint at the current line of the config (the one on which
    this function is called). The 'mode' specifies whether the breakpoint
@@ -404,13 +421,13 @@ modparam("debugger", "reset_msgid", 1)
    Note that this version of the module does not export this anchors to
    RPC for interactive debugging (temporarily disabled).
 
-   Example 1.17. dbg_breakpoint usage
+   Example 1.18. dbg_breakpoint usage
 ...
 if($si=="10.0.0.10")
         dbg_breakpoint("1");
 ...
 
-4.2. dbg_pv_dump([mask] [, level])
+4.2.  dbg_pv_dump([mask] [, level])
 
    Prints the content of pv_cache on json format. Defaults are mask=31 and
    level = "L_DBG"
@@ -432,7 +449,7 @@ if($si=="10.0.0.10")
      * L_INFO - log level 2
      * L_DBG - log level 3
 
-   Example 1.18. dbg_pv_dump usage
+   Example 1.19. dbg_pv_dump usage
 ...
 $var(temp) = 1;
 $avp(s:more_avp) = 2;
@@ -455,7 +472,7 @@ vp(x)":[{"different":["foo"]},{"other":[2,1],"more":["hi","bye"]}],"$T_branch_id
 x":0,"$var(empty)":0}
  ...
 
-4.3. dbg_sip_msg([log_level], [facility])
+4.3.  dbg_sip_msg([log_level], [facility])
 
    Prints how the sip message would look like if it would be sent out at
    that point in the config(i.e. if the current lump lists would have been
@@ -475,7 +492,7 @@ x":0,"$var(empty)":0}
    force the lump application using msg_apply_changes() function from
    textopsx module.
 
-   Example 1.19. dbg_sip_msg usage
+   Example 1.20. dbg_sip_msg usage
 ...
     dbg_sip_msg();
     dbg_sip_msg("L_ERR");
@@ -512,7 +529,7 @@ P-Hint: My hint
    5.4. dbg.mod_level
    5.5. dbg.reset_msgid
 
-5.1. dbg.ls
+5.1.  dbg.ls
 
    List Kamailio processes with info related to interactive debugging.
 
@@ -526,7 +543,7 @@ P-Hint: My hint
                 dbg.ls
                 dbg.ls 1234
 
-5.2. dbg.trace
+5.2.  dbg.trace
 
    Control config script running trace.
 
@@ -543,7 +560,7 @@ P-Hint: My hint
                 dbg.trace off
                 dbg.trace on 1234
 
-5.3. dbg.bp
+5.3.  dbg.bp
 
    Control breakpoints and config execution.
 
@@ -581,7 +598,7 @@ P-Hint: My hint
                 dbg.bp eval 1234 $fu
                 dbg.bp move 1234
 
-5.4. dbg.mod_level
+5.4.  dbg.mod_level
 
    Specify module log level.
 
@@ -595,7 +612,7 @@ P-Hint: My hint
                 dbg.mod_level core 3
                 dbg.mod_level tm 3
 
-5.5. dbg.reset_msgid
+5.5.  dbg.reset_msgid
 
    Resets the message sequence ($mi). Internally there is no real change.
    This can be useful for unit test cases in order to be able to replicate

+ 19 - 0
modules/debugger/debugger_api.c

@@ -26,6 +26,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 
+#include "../cfgt/cfgt.h"
 #include "../../dprint.h"
 #include "../../events.h"
 #include "../../locking.h"
@@ -70,6 +71,7 @@ str *dbg_get_state_name(int t)
 #define DBG_CFGTRACE_ON	(1<<0)
 #define DBG_ABKPOINT_ON	(1<<1)
 #define DBG_LBKPOINT_ON	(1<<2)
+#define DBG_CFGTEST_ON	(1<<3)
 
 static str _dbg_status_list[] = {
 	str_init("cfgtrace-on"),
@@ -78,6 +80,8 @@ static str _dbg_status_list[] = {
 	str_init("abkpoint-off"),
 	str_init("lbkpoint-on"),
 	str_init("lbkpoint-off"),
+	str_init("cfgtest-on"),
+	str_init("cfgtest-off"),
 	{0, 0}
 };
 
@@ -89,6 +93,8 @@ str *dbg_get_status_name(int t)
 		return &_dbg_status_list[2];
 	if(t&DBG_LBKPOINT_ON)
 		return &_dbg_status_list[4];
+	if(t&DBG_CFGTEST_ON)
+		return &_dbg_status_list[6];
 
 	return &_dbg_state_list[0];
 }
@@ -188,6 +194,12 @@ int _dbg_step_loops = 200;
  */
 int _dbg_reset_msgid = 0;
 
+/**
+ * disabled by default
+ */
+int _dbg_cfgtest = 0;
+cfgt_api_t _dbg_cfgt;
+
 /**
  *
  */
@@ -356,6 +368,11 @@ int dbg_cfg_trace(void *data)
 				);
 		}
 	}
+	if(_dbg_pid_list[process_no].set&DBG_CFGTEST_ON)
+	{
+		if(_dbg_cfgt.cfgt_process_route(msg, a)<0)
+				LM_ERR("Error processing route\n");
+	}
 	if(!(_dbg_pid_list[process_no].set&DBG_ABKPOINT_ON))
 	{
 		/* no breakpoints to be considered */
@@ -590,6 +607,8 @@ int dbg_init_mypid(void)
 		_dbg_pid_list[process_no].set |= DBG_ABKPOINT_ON;
 	if(_dbg_cfgtrace==1)
 		_dbg_pid_list[process_no].set |= DBG_CFGTRACE_ON;
+	if(_dbg_cfgtest==1)
+		_dbg_pid_list[process_no].set |= DBG_CFGTEST_ON;
 	if(_dbg_reset_msgid==1)
 	{
 		LM_DBG("[%d] create locks\n", process_no);

+ 21 - 0
modules/debugger/debugger_mod.c

@@ -27,6 +27,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "../cfgt/cfgt.h"
 #include "../../sr_module.h"
 #include "../../dprint.h"
 #include "../../ut.h"
@@ -71,6 +72,10 @@ extern char *_dbg_cfgtrace_lname;
 extern int _dbg_step_usleep;
 extern int _dbg_step_loops;
 extern int _dbg_reset_msgid;
+extern int _dbg_cfgtest;
+
+/* cfgt api */
+extern cfgt_api_t _dbg_cfgt;
 
 static int _dbg_sip_msg_cline;
 static char * _dbg_cfgtrace_facility_str = 0;
@@ -111,6 +116,7 @@ static param_export_t params[]={
 	{"mod_facility",      PARAM_STRING|USE_FUNC_PARAM, (void*)dbg_mod_facility_param},
 	{"reset_msgid",       INT_PARAM, &_dbg_reset_msgid},
 	{"cfgpkgcheck",       INT_PARAM, &_dbg_cfgpkgcheck},
+	{"cfgtest",           INT_PARAM, &_dbg_cfgtest},
 	{0, 0, 0}
 };
 
@@ -136,6 +142,8 @@ struct module_exports exports = {
 static int mod_init(void)
 {
 	int fl;
+	bind_cfgt_t bind_cfgt;
+
 	if (_dbg_cfgtrace_facility_str!=NULL)
 	{
 		fl = str2facility(_dbg_cfgtrace_facility_str);
@@ -187,6 +195,19 @@ static int mod_init(void)
 			return -1;
 		}
 	}
+	if(_dbg_cfgtest==1)
+	{
+		bind_cfgt = (bind_cfgt_t)find_export("cfgt_bind_cfgt", 1, 0);
+		if (!bind_cfgt) {
+			LM_ERR("can't find cfgt module\n");
+			return -1;
+		}
+
+		if (bind_cfgt(&_dbg_cfgt) < 0) {
+			return -1;
+		}
+		LM_INFO("bind to cfgt module\n");
+	}
 	return dbg_init_bp_list();
 }
 

+ 22 - 0
modules/debugger/doc/debugger_admin.xml

@@ -418,6 +418,28 @@ modparam("debugger", "reset_msgid", 1)
 	    </example>
 	</section>
 
+	<section id="dbg.p.cfgtest">
+	    <title><varname>cfgtest</varname> (int)</title>
+	    <para>
+			Control whether the cfgt module is enabled or disabled
+			at startup. Module cfgt needs to be loaded before.
+	    </para>
+	    <para>
+		<emphasis>
+		    Default value is <quote>0</quote> (disabled).
+		</emphasis>
+	    </para>
+	    <example>
+		<title>Set <varname>cfgtest</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+loadmodule "cfgt.so"
+modparam("debugger", "cfgtest", 1)
+...
+</programlisting>
+	    </example>
+	</section>
+
 	</section>
 
     <section>