/**
 *
 * Copyright (C) 2001-2003 FhG Fokus
 *
 * 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
 *
 */

/*
 * Prefix-Domains Translation - ser module
 * Ramona Modroiu
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#include "../../lib/srdb1/db_op.h"
#include "../../core/sr_module.h"
#include "../../lib/srdb1/db.h"
#include "../../core/mem/shm_mem.h"
#include "../../core/mem/mem.h"
#include "../../core/dprint.h"
#include "../../core/parser/parse_uri.h"
#include "../../core/timer.h"
#include "../../core/ut.h"
#include "../../core/locking.h"
#include "../../core/action.h"
#include "../../core/mod_fix.h"
#include "../../core/parser/parse_from.h"
#include "../../core/rpc.h"
#include "../../core/rpc_lookup.h"
#include "../../core/kemi.h"

#include "pdtree.h"

MODULE_VERSION


#define NR_KEYS			3

int pdt_fetch_rows = 1000;

/** structures containing prefix-domain pairs */
pdt_tree_t **_ptree = NULL;

/** database connection */
static db1_con_t *db_con = NULL;
static db_func_t pdt_dbf;


/** parameters */
static str db_url = str_init(DEFAULT_DB_URL);
static str db_table = str_init("pdt");
static str sdomain_column = str_init("sdomain");
static str prefix_column  = str_init("prefix");
static str domain_column  = str_init("domain");
static int pdt_check_domain  = 1;

/** translation prefix */
str pdt_prefix = {"", 0};
/* List of allowed chars for a prefix*/
str pdt_char_list = {"0123456789", 10};

/* lock, ref counter and flag used for reloading the date */
static gen_lock_t *pdt_lock = 0;
static volatile int pdt_tree_refcnt = 0;
static volatile int pdt_reload_flag = 0;

static int  w_prefix2domain(struct sip_msg* msg, char* str1, char* str2);
static int  w_prefix2domain_1(struct sip_msg* msg, char* mode, char* str2);
static int  w_prefix2domain_2(struct sip_msg* msg, char* mode, char* sd_en);
static int  mod_init(void);
static void mod_destroy(void);
static int  child_init(int rank);
static int  pd_translate(sip_msg_t *msg, str *sdomain, int rmode, int fmode);

static int w_pd_translate(struct sip_msg* msg, char* str1, char* str2);
static int fixup_translate(void** param, int param_no);

static int update_new_uri(struct sip_msg *msg, int plen, str *d, int mode);
static int pdt_init_rpc(void);

static cmd_export_t cmds[]={
	{"prefix2domain", (cmd_function)w_prefix2domain,   0, 0,
		0, REQUEST_ROUTE|FAILURE_ROUTE},
	{"prefix2domain", (cmd_function)w_prefix2domain_1, 1, fixup_igp_null,
		0, REQUEST_ROUTE|FAILURE_ROUTE},
	{"prefix2domain", (cmd_function)w_prefix2domain_2, 2, fixup_igp_igp,
		0, REQUEST_ROUTE|FAILURE_ROUTE},
	{"pd_translate", (cmd_function)w_pd_translate,     2, fixup_translate,
		0, REQUEST_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
	{0, 0, 0, 0, 0, 0}
};

static param_export_t params[]={
	{"db_url",         PARAM_STR, &db_url},
	{"db_table",       PARAM_STR, &db_table},
	{"sdomain_column", PARAM_STR, &sdomain_column},
	{"prefix_column",  PARAM_STR, &prefix_column},
	{"domain_column",  PARAM_STR, &domain_column},
	{"prefix",         PARAM_STR, &pdt_prefix},
	{"char_list",      PARAM_STR, &pdt_char_list},
	{"fetch_rows",     INT_PARAM, &pdt_fetch_rows},
	{"check_domain",   INT_PARAM, &pdt_check_domain},
	{0, 0, 0}
};


struct module_exports exports = {
	"pdt",           /* module name */
	DEFAULT_DLFLAGS, /* dlopen flags */
	cmds,            /* cmd exports */
	params,          /* param exports */
	0,               /* RPC method exports */
	0,               /* exported pseudo-variables */
	0,               /* response function */
	mod_init,        /* module initialization function */
	child_init,      /* per child init function */
	mod_destroy      /* destroy function */
};




/**
 * init module function
 */
static int mod_init(void)
{
	if(pdt_init_rpc()<0)
	{
		LM_ERR("failed to register RPC commands\n");
		return -1;
	}

	if(pdt_fetch_rows<=0)
		pdt_fetch_rows = 1000;

	pdt_char_list.len = strlen(pdt_char_list.s);
	if(pdt_char_list.len<=0)
	{
		LM_ERR("invalid pdt char list\n");
		return -1;
	}
	LM_INFO("pdt_char_list=%s \n",pdt_char_list.s);

	/* binding to mysql module */
	if(db_bind_mod(&db_url, &pdt_dbf))
	{
		LM_ERR("database module not found\n");
		return -1;
	}

	if (!DB_CAPABILITY(pdt_dbf, DB_CAP_ALL))
	{
		LM_ERR("database module does not "
		    "implement all functions needed by the module\n");
		return -1;
	}

	/* open a connection with the database */
	db_con = pdt_dbf.init(&db_url);
	if(db_con==NULL)
	{
		LM_ERR("failed to connect to the database\n");        
		return -1;
	}
	
	if (pdt_dbf.use_table(db_con, &db_table) < 0)
	{
		LM_ERR("failed to use_table\n");
		goto error1;
	}
	LM_DBG("database connection opened successfully\n");
	
	if ( (pdt_lock=lock_alloc())==0) {
		LM_CRIT("failed to alloc lock\n");
		goto error1;
	}
	if (lock_init(pdt_lock)==0 ) {
		LM_CRIT("failed to init lock\n");
		goto error1;
	}
	
	/* tree pointer in shm */
	_ptree = (pdt_tree_t**)shm_malloc( sizeof(pdt_tree_t*) );
	if (_ptree==0) {
		LM_ERR("out of shm mem for pdtree\n");
		goto error1;
	}
	*_ptree=0;

	/* loading all information from database */
	if(pdt_load_db()!=0)
	{
		LM_ERR("cannot load info from database\n");	
		goto error1;
	}
		
	pdt_dbf.close(db_con);
	db_con = 0;

#if 0
	pdt_print_tree(*_ptree);
#endif

	/* success code */
	return 0;

error1:
	if (pdt_lock)
	{
		lock_destroy( pdt_lock );
		lock_dealloc( pdt_lock );
		pdt_lock = 0;
	}
	if(_ptree!=0)
	{
		shm_free(_ptree);
		_ptree = 0;
	}

	if(db_con!=NULL)
	{
		pdt_dbf.close(db_con);
		db_con = 0;
	}
	return -1;
}

/* each child get a new connection to the database */
static int child_init(int rank)
{
	if (rank==PROC_INIT || rank==PROC_MAIN || rank==PROC_TCP_MAIN)
		return 0; /* do nothing for the main process */

	if(pdt_init_db()<0)
	{
		LM_ERR("cannot initialize database connection\n");
		return -1;
	}
	LM_DBG("#%d: database connection opened successfully\n", rank);
	return 0;
}


static void mod_destroy(void)
{
	LM_DBG("cleaning up\n");
	if (_ptree!=NULL)
	{
		if (*_ptree!=NULL)
			pdt_free_tree(*_ptree);
		shm_free(_ptree);
	}
	if (db_con!=NULL && pdt_dbf.close!=NULL)
		pdt_dbf.close(db_con);
		/* destroy lock */
	if (pdt_lock)
	{
		lock_destroy( pdt_lock );
		lock_dealloc( pdt_lock );
		pdt_lock = 0;
	}

}


static int w_prefix2domain(struct sip_msg* msg, char* str1, char* str2)
{
	str sdall={"*",1};
	return pd_translate(msg, &sdall, 0, 0);
}

static int w_prefix2domain_1(struct sip_msg* msg, char* mode, char* str2)
{
	str sdall={"*",1};
	int md;

	if(fixup_get_ivalue(msg, (gparam_p)mode, &md)!=0)
	{
		LM_ERR("no mode value\n");
		return -1;
	}

	if(md!=1 && md!=2)
		md = 0;

	return pd_translate(msg, &sdall, md, 0);
}

static int ki_prefix2domain(sip_msg_t* msg, int m, int s)
{
	int f;
	str sdomain={"*",1};
	sip_uri_t *furi;

	if(m!=1 && m!=2)
		m = 0;

	if(s!=1 && s!=2)
		s = 0;

	f = 0;
	if(s==1 || s==2) {
		/* take the domain from  FROM uri as sdomain */
		if((furi = parse_from_uri(msg))==NULL) {
			LM_ERR("cannot parse FROM header URI\n");
			return -1;
		}
		sdomain = furi->host;
		if(s==2)
			f = 1;
	}
	return pd_translate(msg, &sdomain, m, f);
}

static int w_prefix2domain_2(struct sip_msg* msg, char* mode, char* sdm)
{
	int m, s;

	if(fixup_get_ivalue(msg, (gparam_p)mode, &m)!=0)
	{
		LM_ERR("no mode value\n");
		return -1;
	}

	if(fixup_get_ivalue(msg, (gparam_p)sdm, &s)!=0)
	{
		LM_ERR("no multi-domain mode value\n");
		return -1;
	}
	return ki_prefix2domain(msg, m, s);
}

/**
 * @brief change the r-uri domain based on source domain and prefix
 *
 * @param msg the sip message structure
 * @param sdomain the source domain
 * @param rmode the r-uri rewrite mode
 * @param fmode the source domain fallback mode
 * @return 1 if translation is done; -1 otherwise
 */
static int pd_translate(sip_msg_t *msg, str *sdomain, int rmode, int fmode)
{
	str *d, p;
	str sdall={"*",1};
	int plen;
	
	if(msg==NULL)
	{
		LM_ERR("received null msg\n");
		return -1;
	}
	
	if(parse_sip_msg_uri(msg)<0)
	{
		LM_ERR("failed to parse the R-URI\n");
		return -1;
	}

    /* if the user part begin with the prefix, extract the code*/
	if (msg->parsed_uri.user.len<=0)
	{
		LM_DBG("user part of the message is empty\n");
		return -1;
	}   
    
	if(pdt_prefix.len>0)
	{
		if (msg->parsed_uri.user.len<=pdt_prefix.len)
		{
			LM_DBG("user part is less than prefix parameter\n");
			return -1;
		}   
		if(strncasecmp(pdt_prefix.s, msg->parsed_uri.user.s,
					pdt_prefix.len)!=0)
		{
			LM_DBG("prefix parameter did not matched\n");
			return -1;
		}
	}   
	
	p.s   = msg->parsed_uri.user.s + pdt_prefix.len;
	p.len = msg->parsed_uri.user.len - pdt_prefix.len;

again:
	lock_get( pdt_lock );
	if (pdt_reload_flag) {
		lock_release( pdt_lock );
		sleep_us(5);
		goto again;
	}
	pdt_tree_refcnt++;
	lock_release( pdt_lock );


	if((d=pdt_get_domain(*_ptree, sdomain, &p, &plen))==NULL)
	{
		plen = 0;
		if((fmode==0) || (d=pdt_get_domain(*_ptree, &sdall, &p, &plen))==NULL)
		{
			LM_INFO("no prefix PDT prefix matched [%.*s]\n", p.len, p.s);
			goto error;
		}
	}

	
	/* update the new uri */
	if(update_new_uri(msg, plen, d, rmode)<0)
	{
		LM_ERR("new_uri cannot be updated\n");
		goto error;
	}

	lock_get( pdt_lock );
	pdt_tree_refcnt--;
	lock_release( pdt_lock );
	return 1;

error:
	lock_get( pdt_lock );
	pdt_tree_refcnt--;
	lock_release( pdt_lock );
	return -1;
}

/**
 *
 */
static int fixup_translate(void** param, int param_no)
{
	if(param_no==1)
		return fixup_spve_null(param, 1);
	if(param_no==2)
		return fixup_igp_null(param, 1);
	return 0;
}

/**
 *
 */
static int w_pd_translate(sip_msg_t* msg, char* sdomain, char* mode)
{
	int md;
	str sd;

	if(fixup_get_svalue(msg, (gparam_p)sdomain, &sd)!=0)
	{
		LM_ERR("no source domain value\n");
		return -1;
	}


	if(fixup_get_ivalue(msg, (gparam_p)mode, &md)!=0)
	{
		LM_ERR("no multi-domain mode value\n");
		return -1;
	}

	if(md!=1 && md!=2)
		md = 0;

	return pd_translate(msg, &sd, md, 0);
}

/**
 *
 */
static int ki_pd_translate(sip_msg_t* msg, str* sd, int md)
{
	if(md!=1 && md!=2)
		return pd_translate(msg, sd, 0, 0);;

	return pd_translate(msg, sd, md, 0);
}

/**
 * change the uri according to update mode
 */
static int update_new_uri(struct sip_msg *msg, int plen, str *d, int mode)
{
	struct action act;
	struct run_act_ctx ra_ctx;
	if(msg==NULL || d==NULL)
	{
		LM_ERR("bad parameters\n");
		return -1;
	}
	
	if(mode==0 || (mode==1 && pdt_prefix.len>0))
	{
		memset(&act, '\0', sizeof(act));
		act.type = STRIP_T;
		act.val[0].type = NUMBER_ST;
		if(mode==0)
			act.val[0].u.number = plen + pdt_prefix.len;
		else
			act.val[0].u.number = pdt_prefix.len;

		init_run_actions_ctx(&ra_ctx);
		if (do_action(&ra_ctx, &act, msg) < 0)
		{
			LM_ERR("failed to remove prefix parameter\n");
			return -1;
		}
	}
	
	memset(&act, '\0', sizeof(act));
	act.type = SET_HOSTALL_T;
	act.val[0].type = STRING_ST;
	act.val[0].u.string = d->s;
	init_run_actions_ctx(&ra_ctx);
	if (do_action(&ra_ctx, &act, msg) < 0)
	{
		LM_ERR("failed to change domain\n");
		return -1;
	}

	LM_DBG("len=%d uri=%.*s\n", msg->new_uri.len, 
			msg->new_uri.len, msg->new_uri.s);
	
	return 0;
}

int pdt_init_db(void)
{
	/* db handler initialization */
	db_con = pdt_dbf.init(&db_url);
	if(db_con==NULL)
	{
		LM_ERR("failed to connect to database\n");
		return -1;
	}

	if (pdt_dbf.use_table(db_con, &db_table) < 0)
	{
		LM_ERR("use_table failed\n");
		return -1;
	}
	return 0;
}

int pdt_load_db(void)
{
	db_key_t db_cols[3] = {&sdomain_column, &prefix_column, &domain_column};
	str p, d, sdomain;
	db1_res_t* db_res = NULL;
	int i, ret;
	pdt_tree_t *_ptree_new = NULL; 
	pdt_tree_t *old_tree = NULL; 

	if(db_con==NULL)
	{
		LM_ERR("no db connection\n");
		return -1;
	}
		
	if (pdt_dbf.use_table(db_con, &db_table) < 0)
	{
		LM_ERR("failed to use_table\n");
		return -1;
	}

	if (DB_CAPABILITY(pdt_dbf, DB_CAP_FETCH)) {
		if(pdt_dbf.query(db_con,0,0,0,db_cols,0,3,&sdomain_column,0) < 0)
		{
			LM_ERR("Error while querying db\n");
			return -1;
		}
		if(pdt_dbf.fetch_result(db_con, &db_res, pdt_fetch_rows)<0)
		{
			LM_ERR("Error while fetching result\n");
			if (db_res)
				pdt_dbf.free_result(db_con, db_res);
			goto error;
		} else {
			if(RES_ROW_N(db_res)==0)
			{
				return 0;
			}
		}
	} else {
		if((ret=pdt_dbf.query(db_con, NULL, NULL, NULL, db_cols,
				0, 3, &sdomain_column, &db_res))!=0
			|| RES_ROW_N(db_res)<=0 )
		{
			pdt_dbf.free_result(db_con, db_res);
			if( ret==0)
			{
				return 0;
			} else {
				goto error;
			}
		}
	}

	do {
		for(i=0; i<RES_ROW_N(db_res); i++)
		{
			/* check for NULL values ?!?! */
			sdomain.s = (char*)(RES_ROWS(db_res)[i].values[0].val.string_val);
			p.s = (char*)(RES_ROWS(db_res)[i].values[1].val.string_val);
			d.s = (char*)(RES_ROWS(db_res)[i].values[2].val.string_val);

			if(p.s==NULL || d.s==NULL || sdomain.s==NULL)
			{
				LM_ERR("Error - bad values in db\n");
				continue;
			}

			sdomain.len = strlen(sdomain.s);
			p.len = strlen(p.s);
			d.len = strlen(d.s);

			if(p.len<=0 || d.len<=0 || sdomain.len<=0)
			{
				LM_ERR("Error - bad values in db\n");
				continue;
			}

			if(pdt_check_domain!=0 && _ptree_new!=NULL
					&& pdt_check_pd(_ptree_new, &sdomain, &p, &d)==1)
			{
				LM_ERR("sdomain [%.*s]: prefix [%.*s] or domain <%.*s> "
					"duplicated\n", sdomain.len, sdomain.s, p.len, p.s,
					d.len, d.s);
				continue;
			}

			if(pdt_add_to_tree(&_ptree_new, &sdomain, &p, &d)<0)
			{
				LM_ERR("Error adding info to tree\n");
				goto error;
			}
	 	}
		if (DB_CAPABILITY(pdt_dbf, DB_CAP_FETCH)) {
			if(pdt_dbf.fetch_result(db_con, &db_res, pdt_fetch_rows)<0) {
				LM_ERR("Error while fetching!\n");
				if (db_res)
					pdt_dbf.free_result(db_con, db_res);
				goto error;
			}
		} else {
			break;
		}
	}  while(RES_ROW_N(db_res)>0);
	pdt_dbf.free_result(db_con, db_res);


	/* block all readers */
	lock_get( pdt_lock );
	pdt_reload_flag = 1;
	lock_release( pdt_lock );

	while (pdt_tree_refcnt) {
		sleep_us(10);
	}

	old_tree = *_ptree;
	*_ptree = _ptree_new;

	pdt_reload_flag = 0;

	/* free old data */
	if (old_tree!=NULL)
		pdt_free_tree(old_tree);

	return 0;

error:
	pdt_dbf.free_result(db_con, db_res);
	if (_ptree_new!=NULL)
		pdt_free_tree(_ptree_new);
	return -1;
}


/* return the pointer to char list */
str* pdt_get_char_list(void)
{
	return &pdt_char_list;
}

/* return head of pdt trees */
pdt_tree_t **pdt_get_ptree(void)
{
	return _ptree;
}


/*** RPC commands implementation ***/

static const char* pdt_rpc_reload_doc[2] = {
	"Reload PDT database records",
	0
};


/*
 * RPC command to reload pdt db records
 */
static void pdt_rpc_reload(rpc_t* rpc, void* ctx)
{
	if(pdt_load_db()<0) {
		LM_ERR("cannot re-load pdt records from database\n");	
		rpc->fault(ctx, 500, "Reload Failed");
		return;
	}
	return;
}


static const char* pdt_rpc_list_doc[2] = {
	"List PDT memory records",
	0
};


int pdt_rpc_print_node(rpc_t* rpc, void* ctx, void *ih, pdt_node_t *pt, char *code,
		int len, str *sdomain, str *tdomain, str *tprefix)
{
	int i;
	str *cl;
	str prefix;
	void* vh;

	if(pt==NULL || len>=PDT_MAX_DEPTH)
		return 0;
	
	cl = pdt_get_char_list();

	for(i=0; i<cl->len; i++)
	{
		code[len]=cl->s[i];
		if(pt[i].domain.s!=NULL)
		{
			if((tprefix->s==NULL && tdomain->s==NULL)
				|| (tprefix->s==NULL && (tdomain->s!=NULL && pt[i].domain.len==tdomain->len
						&& strncasecmp(pt[i].domain.s, tdomain->s, tdomain->len)==0))
				|| (tdomain->s==NULL && (len+1>=tprefix->len
						&& strncmp(code, tprefix->s, tprefix->len)==0))
				|| ((tprefix->s!=NULL && len+1>=tprefix->len
						&& strncmp(code, tprefix->s, tprefix->len)==0)
						&& (tdomain->s!=NULL && pt[i].domain.len>=tdomain->len
						&& strncasecmp(pt[i].domain.s, tdomain->s, tdomain->len)==0)))
			{
				if(rpc->struct_add(ih, "{",
						"ENTRY", &vh)<0)
				{
					LM_ERR("Internal error creating entry\n");
					return -1;
				}
				prefix.s = code;
				prefix.len = len + 1;
				if(rpc->struct_add(vh, "SS",
						"DOMAIN", &pt[i].domain,
						"PREFIX", &prefix)<0)
				{
					LM_ERR("Internal error filling entry struct\n");
					return -1;
				}
			}
		}
		if(pdt_rpc_print_node(rpc, ctx, ih, pt[i].child, code, len+1, sdomain,
					tdomain, tprefix)<0)
			goto error;
	}
	return 0;
error:
	return -1;
}

/*
 * RPC command to list pdt memory records
 */
/**
 * "pdt.list" parameters:
 *    sdomain
 *    prefix
 *    domain
 *
 * 	- '.' (dot) means NULL value and will match anything
 * 	- the comparison operation is 'START WITH' -- if domain is 'a' then
 * 	  all domains starting with 'a' are listed
 *
 * 	  Examples
 * 	  pdt_list o 2 .    - lists the entries where sdomain is starting with 'o', 
 * 	                      prefix is starting with '2' and domain is anything
 * 	  
 * 	  pdt_list . 2 open - lists the entries where sdomain is anything, prefix 
 * 	                      starts with '2' and domain starts with 'open'
 */
static void pdt_rpc_list(rpc_t* rpc, void* ctx)
{
	str sdomain = {0};
	str tprefix = {0};
	str tdomain = {0};
	pdt_tree_t *pt;
	unsigned int i;
	static char code_buf[PDT_MAX_DEPTH+1];
	int len;
	str *cl;
	pdt_tree_t **ptree;
	void* th;
	void* ih;

	ptree = pdt_get_ptree();

	if(ptree==NULL || *ptree==NULL)
	{
		LM_ERR("empty domain list\n");
		rpc->fault(ctx, 404, "No records");
		return;
	}

	len = rpc->scan(ctx, "*S.SS", &sdomain, &tprefix, &tdomain);
	if(len<0)
	{
		rpc->fault(ctx, 500, "Error Reading Parameters");
		return;
	}
	if(len<1 || sdomain.len==0 || (sdomain.len==1 && sdomain.s[0]=='.')) {
		sdomain.len = 0;
		sdomain.s = 0;
	}
	cl = pdt_get_char_list();
	if(len<2 || tprefix.len==0 || (tprefix.len==1 && tprefix.s[0]=='.')) {
		tprefix.len = 0;
		tprefix.s = 0;
	} else if(tprefix.len>0) {
		/* validate prefix */
		i = 0;
		while(tprefix.s!=NULL && i!=tprefix.len)
		{
			if(strpos(cl->s, tprefix.s[i]) < 0)
			{
				LM_ERR("bad prefix [%.*s]\n", tprefix.len, tprefix.s);
				rpc->fault(ctx, 400, "Bad Prefix");
				return;
			}
			i++;
		}
	}
	if(len<3 || tdomain.len==0 || (tdomain.len==1 && tdomain.s[0]=='.')) {
		tdomain.len = 0;
		tdomain.s = 0;
	}

	pt = *ptree;
	
	if (rpc->add(ctx, "{", &th) < 0)
	{
		rpc->fault(ctx, 500, "Internal error root reply");
		return;
	}
	while(pt!=NULL)
	{
		LM_ERR("---- 1 (%d [%.*s])\n", sdomain.len, sdomain.len, sdomain.s);
		if(sdomain.s==NULL || 
			(sdomain.s!=NULL && pt->sdomain.len>=sdomain.len && 
			 strncmp(pt->sdomain.s, sdomain.s, sdomain.len)==0))
		{
		LM_ERR("---- 2\n");
			len = 0;
			if(rpc->struct_add(th, "S{",
					"SDOMAIN", &pt->sdomain,
					"RECORDS",  &ih)<0)
			{
				rpc->fault(ctx, 500, "Internal error creating sdomain structure");
				return;
			}
			if(pdt_rpc_print_node(rpc, ctx, ih, pt->head, code_buf, len, &pt->sdomain,
						&tdomain, &tprefix)<0)
				goto error;
		}
		pt = pt->next;
	}
	return;
error:
	rpc->fault(ctx, 500, "Internal error printing records");
	return;
}


rpc_export_t pdt_rpc_cmds[] = {
	{"pdt.reload", pdt_rpc_reload,
		pdt_rpc_reload_doc, 0},
	{"pdt.list", pdt_rpc_list,
		pdt_rpc_list_doc, 0},
	{0, 0, 0, 0}
};


/**
 * register RPC commands
 */
static int pdt_init_rpc(void)
{
	if (rpc_register_array(pdt_rpc_cmds)!=0)
	{
		LM_ERR("failed to register RPC commands\n");
		return -1;
	}
	return 0;
}

/**
 *
 */
/* clang-format off */
static sr_kemi_t sr_kemi_pdt_exports[] = {
	{ str_init("pdt"), str_init("pd_translate"),
		SR_KEMIP_INT, ki_pd_translate,
		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("pdt"), str_init("pprefix2domain"),
		SR_KEMIP_INT, ki_prefix2domain,
		{ SR_KEMIP_INT, SR_KEMIP_INT, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},

	{ {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
};
/* clang-format on */

/**
 *
 */
int mod_register(char *path, int *dlflags, void *p1, void *p2)
{
	sr_kemi_modules_add(sr_kemi_pdt_exports);
	return 0;
}