/*
 * $Id: tm.c 1827 2007-03-12 15:22:53Z bogdan_iancu $
 *
 * Copyright (C) 2001-2003 FhG Fokus
 *
 * This file is part of openser, a free SIP server.
 *
 * openser 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
 *
 * openser 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * History:
 * --------
 *  2003-02-18  added t_forward_nonack_{udp, tcp}, t_relay_to_{udp,tcp},
 *               t_replicate_{udp, tcp} (andrei)
 *  2003-02-19  added t_rely_{udp, tcp} (andrei)
 *  2003-03-06  voicemail changes accepted (jiri)
 *  2003-03-10  module export interface updated to the new format (andrei)
 *  2003-03-16  flags export parameter added (janakj)
 *  2003-03-19  replaced all mallocs/frees w/ pkg_malloc/pkg_free (andrei)
 *  2003-03-30  set_kr for requests only (jiri)
 *  2003-04-05  s/reply_route/failure_route, onreply_route introduced (jiri)
 *  2003-04-14  use protocol from uri (jiri)
 *  2003-07-07  added t_relay_to_tls, t_replicate_tls, t_forward_nonack_tls
 *              added #ifdef USE_TCP, USE_TLS
 *              removed t_relay_{udp,tcp,tls} (andrei)
 *  2003-09-26  added t_forward_nonack_uri() - same as t_forward_nonack() but
 *              takes no parameters -> forwards to uri (bogdan)
 *  2004-02-11  FIFO/CANCEL + alignments (hash=f(callid,cseq)) (uli+jiri)
 *  2004-02-18  t_reply exported via FIFO - imported from VM (bogdan)
 *  2004-10-01  added a new param.: restart_fr_on_each_reply (andrei)
 *  2005-05-30  light version of tm_load - find_export dropped -> module 
 *              interface dosen't need to export internal functions (bogdan)
 *  2006-01-15  merged functions which diff only via proto (like t_relay,
 *              t_replicate and t_forward_nonack) (bogdan)
 *  2007-01-25  t_forward_nonack removed as it merged into t_relay,
 *              t_replicate also accepts flags for controlling DNS failover
 *              (bogdan)
 */


#include <stdio.h>
#include <string.h>
#include <netdb.h>

#include "../../sr_module.h"
#include "../../dprint.h"
#include "../../error.h"
#include "../../ut.h"
#include "../../script_cb.h"
#include "../../mi/mi.h"
#include "../../usr_avp.h"
#include "../../mem/mem.h"
#include "../../unixsock_server.h"
#include "../../items.h"

#include "sip_msg.h"
#include "h_table.h"
#include "ut.h"
#include "t_reply.h"
#include "uac_unixsock.h"
#include "t_fwd.h"
#include "t_lookup.h"
#include "callid.h"
#include "t_cancel.h"
#include "t_fifo.h"
#include "mi.h"
#include "tm_load.h"

MODULE_VERSION

/* item functions */
static int it_get_tm_branch_idx(struct sip_msg *msg, xl_value_t *res,
		xl_param_t *param, int flags);
static int it_get_tm_reply_code(struct sip_msg *msg, xl_value_t *res,
		xl_param_t *param, int flags);

/* fixup functions */
static int fixup_t_send_reply(void** param, int param_no);
static int fixup_str2int( void** param, int param_no);
static int fixup_str2regexp(void** param, int param_no);
static int fixup_local_replied(void** param, int param_no);
static int fixup_t_relay1(void** param, int param_no);
static int fixup_t_relay2(void** param, int param_no);
static int fixup_t_replicate(void** param, int param_no);


/* init functions */
static int mod_init(void);
static int child_init(int rank);


/* exported functions */
inline static int w_t_release(struct sip_msg* msg, char* , char* );
inline static int w_t_newtran(struct sip_msg* p_msg, char* , char* );
inline static int w_t_reply(struct sip_msg *msg, char* code, char* text);
inline static int w_t_relay(struct sip_msg *p_msg , char *proxy, char* flags);
inline static int w_t_replicate(struct sip_msg *p_msg, char *dst,char* );
inline static int w_t_on_negative(struct sip_msg* msg, char *go_to, char* );
inline static int w_t_on_reply(struct sip_msg* msg, char *go_to, char* );
inline static int w_t_on_branch(struct sip_msg* msg, char *go_to, char* );
inline static int t_check_status(struct sip_msg* msg, char *regexp, char* );
inline static int t_flush_flags(struct sip_msg* msg, char*, char* );
inline static int t_local_replied(struct sip_msg* msg, char *type, char* );
inline static int t_check_trans(struct sip_msg* msg, char* , char* );
inline static int t_was_cancelled(struct sip_msg* msg, char* , char* );


/* strings with avp definition */
static char *fr_timer_param = NULL;
static char *fr_inv_timer_param = NULL;

/* module parameteres */
int tm_enable_stats = 1;

/* statistic variables */
stat_var *tm_rcv_rpls;
stat_var *tm_rld_rpls;
stat_var *tm_loc_rpls;
stat_var *tm_uas_trans;
stat_var *tm_uac_trans;
stat_var *tm_trans_2xx;
stat_var *tm_trans_3xx;
stat_var *tm_trans_4xx;
stat_var *tm_trans_5xx;
stat_var *tm_trans_6xx;
stat_var *tm_trans_inuse;


static cmd_export_t cmds[]={
	{"t_newtran",            w_t_newtran,             0, 0,
			REQUEST_ROUTE},
	{"t_reply",              w_t_reply,               2, fixup_t_send_reply,
			REQUEST_ROUTE | FAILURE_ROUTE },
	{"t_release",            w_t_release,             0, 0,
			REQUEST_ROUTE},
	{"t_replicate",          w_t_replicate,           1, fixup_t_replicate,
			REQUEST_ROUTE},
	{"t_replicate",          w_t_replicate,           2, fixup_t_replicate,
			REQUEST_ROUTE},
	{"t_relay",              w_t_relay,               0, 0,
			REQUEST_ROUTE | FAILURE_ROUTE },
	{"t_relay",              w_t_relay,               1, fixup_t_relay1,
			REQUEST_ROUTE | FAILURE_ROUTE },
	{"t_relay",              w_t_relay,               2, fixup_t_relay2,
			REQUEST_ROUTE | FAILURE_ROUTE },
	{"t_on_failure",         w_t_on_negative,         1, fixup_str2int,
			REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE | BRANCH_ROUTE },
	{"t_on_reply",           w_t_on_reply,            1, fixup_str2int,
			REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE | BRANCH_ROUTE },
	{"t_on_branch",          w_t_on_branch,           1, fixup_str2int,
			REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE | BRANCH_ROUTE },
	{"t_check_status",       t_check_status,          1, fixup_str2regexp,
			REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE | BRANCH_ROUTE },
	{"t_write_req",         t_write_req,              2, fixup_t_write,
			REQUEST_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE },
	{"t_write_unix",        t_write_unix,             2, fixup_t_write,
			REQUEST_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE },
	{"t_flush_flags",       t_flush_flags,            0, 0,
			REQUEST_ROUTE | BRANCH_ROUTE  },
	{"t_local_replied",     t_local_replied,          1, fixup_local_replied,
			REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE | BRANCH_ROUTE },
	{"t_check_trans",       t_check_trans,            0, 0,
			REQUEST_ROUTE | BRANCH_ROUTE },
	{"t_was_cancelled",     t_was_cancelled,          0, 0,
			FAILURE_ROUTE | ONREPLY_ROUTE },
	{"load_tm",             (cmd_function)load_tm,    0, 0,
			0},
	{0,0,0,0,0}
};


static param_export_t params[]={
	{"ruri_matching",             INT_PARAM,
		&ruri_matching},
	{"via1_matching",             INT_PARAM,
		&via1_matching},
	{"fr_timer",                  INT_PARAM,
		&(timer_id2timeout[FR_TIMER_LIST])},
	{"fr_inv_timer",              INT_PARAM,
		&(timer_id2timeout[FR_INV_TIMER_LIST])},
	{"wt_timer",                  INT_PARAM,
		&(timer_id2timeout[WT_TIMER_LIST])},
	{"delete_timer",              INT_PARAM,
		&(timer_id2timeout[DELETE_LIST])},
	{"T1_timer",                  INT_PARAM,
		&(timer_id2timeout[RT_T1_TO_1])},
	{"T2_timer",                  INT_PARAM,
		&(timer_id2timeout[RT_T2])},
	{"noisy_ctimer",              INT_PARAM,
		&noisy_ctimer},
	{"unix_tx_timeout",           INT_PARAM,
		&tm_unix_tx_timeout},
	{"restart_fr_on_each_reply",  INT_PARAM,
		&restart_fr_on_each_reply},
	{"fr_timer_avp",              STR_PARAM,
		&fr_timer_param},
	{"fr_inv_timer_avp",          STR_PARAM,
		&fr_inv_timer_param},
	{"tw_append",                 STR_PARAM|USE_FUNC_PARAM,
		(void*)parse_tw_append },
	{ "enable_stats",             INT_PARAM,
		&tm_enable_stats },
	{ "pass_provisional_replies", INT_PARAM,
		&pass_provisional_replies },
	{ "syn_branch",               INT_PARAM,
		&syn_branch },
	{ "onreply_avp_mode",         INT_PARAM,
		&onreply_avp_mode },
	{0,0,0}
};


static stat_export_t mod_stats[] = {
	{"received_replies" ,    0,              &tm_rcv_rpls    },
	{"relayed_replies" ,     0,              &tm_rld_rpls    },
	{"local_replies" ,       0,              &tm_loc_rpls    },
	{"UAS_transactions" ,    0,              &tm_uas_trans   },
	{"UAC_transactions" ,    0,              &tm_uac_trans   },
	{"2xx_transactions" ,    0,              &tm_trans_2xx   },
	{"3xx_transactions" ,    0,              &tm_trans_3xx   },
	{"4xx_transactions" ,    0,              &tm_trans_4xx   },
	{"5xx_transactions" ,    0,              &tm_trans_5xx   },
	{"6xx_transactions" ,    0,              &tm_trans_6xx   },
	{"inuse_transactions" ,  STAT_NO_RESET,  &tm_trans_inuse },
	{0,0,0}
};


/**
 * pseudo-variables exported by TM module
 *   { name, function, type, xl_param_t}
 *   type: can be 0 if you do not want to use it in identifying the PV
 *   xl_param_t: is given as parameter to 'function'
 */
static item_export_t mod_items[] = {
	{ "T_branch_idx", it_get_tm_branch_idx, 100, {{0, 0}, 0, 0} },
	{ "T_reply_code", it_get_tm_reply_code, 100, {{0, 0}, 0, 0} },
	{ 0, 0, 0, {{0, 0}, 0, 0} }
};


static mi_export_t mi_cmds [] = {
	{MI_TM_UAC,     mi_tm_uac_dlg,   MI_ASYNC_RPL_FLAG,  0,  0 },
	{MI_TM_CANCEL,  mi_tm_cancel,    0,                  0,  0 },
	{MI_TM_HASH,    mi_tm_hash,      MI_NO_INPUT_FLAG,   0,  0 },
	{MI_TM_REPLY,   mi_tm_reply,     0,                  0,  0 },
	{0,0,0,0,0}
};


#ifdef STATIC_TM
struct module_exports tm_exports = {
#else
struct module_exports exports= {
#endif
	"tm",      /* module name*/
	DEFAULT_DLFLAGS, /* dlopen flags */
	cmds,      /* exported functions */
	params,    /* exported variables */
	mod_stats, /* exported statistics */
	mi_cmds,   /* exported MI functions */
	mod_items, /* exported pseudo-variables */
	mod_init,  /* module initialization function */
	(response_function) reply_received,
	(destroy_function) tm_shutdown,
	child_init /* per-child init function */
};



/**************************** fixup functions ******************************/
static int flag_fixup(void** param, int param_no)
{
	unsigned int flags;
	str s;

	if (param_no == 1) {
		s.s = (char*)*param;
		s.len = strlen(s.s);
		if ( strno2int(&s, &flags )<0 ) {
			return -1;
		}
		pkg_free(*param);
		*param = (void*)(unsigned long int)(flags<<1);
	}
	return 0;
}


static int fixup_t_replicate(void** param, int param_no)
{
	str *s;

	if (param_no == 1) {
		/* string */
		s = (str*)pkg_malloc( sizeof(str) );
		if (s==0) {
			LOG(L_ERR,"ERROR:tm:fixup_str: no more pkg mem\n");
			return E_OUT_OF_MEM;
		}
		s->s = (char*)*param;
		s->len = strlen(s->s);
		*param = (void*)s;
	} else {
		/* flags */
		if (flag_fixup( param, 1)!=0) {
			LOG(L_ERR, "ERROR:TM:fixup_t_relay2: bad flags <%s>\n",
				(char *)(*param));
			return E_CFG;
		}
	}
	return 0;
}


static int fixup_str2int( void** param, int param_no)
{
	unsigned long go_to;
	int err;

	if (param_no==1) {
		go_to=str2s(*param, strlen(*param), &err );
		if (err==0) {
			pkg_free(*param);
			*param=(void *)go_to;
			return 0;
		} else {
			LOG(L_ERR, "ERROR:tm:fixup_str2int: bad number <%s>\n",
				(char *)(*param));
			return E_CFG;
		}
	}
	return 0;
}


static int fixup_phostport2proxy(void** param, int param_no)
{
	struct proxy_l *proxy;
	char *s;
	int port;
	int proto;
	str host;

	if (param_no!=1) {
		LOG(L_CRIT,"BUG:tm:fixup_phostport2proxy: called with more than "
			" one parameter\n");
		return E_BUG;
	}

	s = (char *) (*param);
	if (s==0 || *s==0) {
		LOG(L_CRIT,"ERROR:tm:fixup_phostport2proxy: empty parameter\n");
		return E_UNSPEC;
	}

	if (parse_phostport( s, strlen(s), &host.s, &host.len, &port, &proto)!=0){
		LOG(L_CRIT,"ERROR:tm:fixup_phostport2proxy: invalid parameter "
			"<%s>\n",s);
		return E_UNSPEC;
	}

	proxy = mk_proxy( &host, port, proto, 0);
	if (proxy==0) {
		LOG(L_ERR, "ERROR:tm:fixup_phostport2proxy: failed to resolve "
			"<%.*s>\n", host.len, host.s );
		return ser_error;
	}
	*(param)=proxy;
	return 0;
}


static int fixup_t_relay1(void** param, int param_no)
{
	if (flag_fixup( param, 1)==0) {
		/* param is flag -> move it as second param */
		*((void**)(((char*)param)+sizeof(action_elem_t))) = *param;
		*param = 0;
		return 0;
	} else if (fixup_phostport2proxy( param, 1)==0 ) {
		/* param is OBP -> nothing else to do */
		return 0;
	} else {
		LOG(L_ERR,"ERROR:TM:fixup_t_relay1: param is neither flag, nor OBP "
			"<%s>\n",(char *)(*param));
		return E_CFG;
	}
}


static int fixup_t_relay2(void** param, int param_no)
{
	if (param_no==1) {
		return fixup_phostport2proxy( param, param_no);
	} else if (param_no==2) {
		if (flag_fixup( param, 1)!=0) {
			LOG(L_ERR, "ERROR:TM:fixup_t_relay2: bad flags <%s>\n",
				(char *)(*param));
			return E_CFG;
		}
	}
	return 0;
}


/* (char *code, char *reason_phrase)==>(int code, r_p as is) */
static int fixup_t_send_reply(void** param, int param_no)
{
	unsigned long code;
	int err;
	str *s;

	if (param_no==1){
		code=str2s(*param, strlen(*param), &err);
		if (err==0){
			pkg_free(*param);
			*param=(void*)code;
			return 0;
		}else{
			LOG(L_ERR, "ERROR:tm:fixup_t_send_reply: bad  number <%s>\n",
					(char*)(*param));
			return E_UNSPEC;
		}
	} else if (param_no==2) {
		s = (str*)pkg_malloc(sizeof(str));
		if (s==0) {
			LOG(L_ERR, "ERROR:sl:fixup_t_send_reply: no more pkg mem\n");
			return E_OUT_OF_MEM;
		}
		s->s = (char*)*param;
		s->len = strlen(s->s);
		*param = (void*)s;
	}

	return 0;
}


static int fixup_str2regexp(void** param, int param_no)
{
	regex_t* re;

	if (param_no==1) {
		if ((re=pkg_malloc(sizeof(regex_t)))==0)
			return E_OUT_OF_MEM;
		if (regcomp(re, *param, REG_EXTENDED|REG_ICASE|REG_NEWLINE) ) {
			pkg_free(re);
			LOG(L_ERR,"ERROR: %s : bad re %s\n", exports.name, (char*)*param);
			return E_BAD_RE;
		}
		/* free string */
		pkg_free(*param);
		/* replace it with the compiled re */
		*param=re;
	} else {
		LOG(L_ERR,"ERROR: fixup_str2regexp called with parameter != 1\n");
		return E_BUG;
	}
	return 0;
}


static int fixup_local_replied(void** param, int param_no)
{
	char *val;
	int n = 0;

	if (param_no==1) {
		val = (char*)*param;
		if (strcasecmp(val,"all")==0) {
			n = 0;
		} else if (strcasecmp(val,"branch")==0) {
			n = 1;
		} else if (strcasecmp(val,"last")==0) {
			n = 2;
		} else {
			LOG(L_ERR,"ERROR:tm:fixup_local_replied: invalid param \"%s\"\n",
				val);
			return E_CFG;
		}
		/* free string */
		pkg_free(*param);
		/* replace it with the compiled re */
		*param=(void*)(long)n;
	} else {
		LOG(L_ERR,"ERROR:fixup_local_replied: called with parameter != 1\n");
		return E_BUG;
	}
	return 0;
}



/***************************** init functions *****************************/
int load_tm( struct tm_binds *tmb)
{
	tmb->register_tmcb = register_tmcb;

	/* relay function */
	tmb->t_relay = w_t_relay;
	/* reply functions */
	tmb->t_reply = (treply_f)w_t_reply;
	tmb->t_reply_with_body = t_reply_with_body;

	/* transaction location/status functions */
	tmb->t_newtran = t_newtran;
	tmb->t_is_local = t_is_local;
	tmb->t_get_trans_ident = t_get_trans_ident;
	tmb->t_lookup_ident = t_lookup_ident;
	tmb->t_gett = get_t;
	tmb->t_get_picked = t_get_picked_branch;

	tmb->t_lookup_original_t = t_lookupOriginalT;
	tmb->t_cancel_uac = t_uac_cancel;
	tmb->unref_cell = t_unref_cell;
	tmb->t_setkr = set_kr;

	/* tm uac functions */
	tmb->t_addblind = add_blind_uac;
	tmb->t_request_within = req_within;
	tmb->t_request_outside = req_outside;
	tmb->t_request = request;
	tmb->new_dlg_uac = new_dlg_uac;
	tmb->dlg_response_uac = dlg_response_uac;
	tmb->new_dlg_uas = new_dlg_uas;
	tmb->dlg_request_uas = dlg_request_uas;
	tmb->free_dlg = free_dlg;
	tmb->print_dlg = print_dlg;


	return 1;
}


static int do_t_unref( struct sip_msg *foo, void *bar)
{
	struct cell *t;

	t = get_cancelled_t();
	if (t!=NULL && t!=T_UNDEFINED)
		t_unref_cell(t);

	t = get_e2eack_t();
	if (t!=NULL && t!=T_UNDEFINED)
		t_unref_cell(t);

	return t_unref(foo);
}


static int script_init( struct sip_msg *foo, void *bar)
{
	/* we primarily reset all private memory here to make sure
	 * private values left over from previous message will
	 * not be used again */

	/* make sure the new message will not inherit previous
	 * message's t_on_negative value
	 */
	t_on_negative( 0 );
	t_on_reply(0);
	t_on_branch(0);
	set_t(T_UNDEFINED);
	reset_cancelled_t();
	reset_e2eack_t();
	/* reset the kr status */
	set_kr(0);
	return 1;
}


static int mod_init(void)
{
	LOG(L_INFO,"TM - initializing...\n");

	/* checking if we have sufficient bitmap capacity for given
	   maximum number of  branches */
	if (MAX_BRANCHES+1>31) {
		LOG(L_CRIT, "Too many max UACs for UAC branch_bm_t bitmap: %d\n",
			MAX_BRANCHES );
		return -1;
	}

	/* if statistics are disabled, prevent their registration to core */
	if (tm_enable_stats==0)
#ifdef STATIC_TM
		tm_exports.stats = 0;
#else
		exports.stats = 0;
#endif

	if (init_callid() < 0) {
		LOG(L_CRIT, "Error while initializing Call-ID generator\n");
		return -1;
	}

	if (unixsock_register_cmd("t_uac_dlg", unixsock_uac) < 0) {
		LOG(L_CRIT, "cannot register t_uac with the unix server\n");
		return -1;
	}

	if (unixsock_register_cmd("t_uac_cancel", unixsock_uac_cancel) < 0) {
		LOG(L_CRIT, "cannot register t_uac_cancel with the unix server\n");
		return -1;
	}

	if (unixsock_register_cmd("t_hash", unixsock_hash) < 0) {
		LOG(L_CRIT, "cannot register t_hash with the unix server\n");
		return -1;
	}

	if (unixsock_register_cmd("t_reply", unixsock_t_reply) < 0) {
		LOG(L_CRIT, "cannot register t_reply with the unix server\n");
		return -1;
	}

	/* building the hash table*/
	if (!init_hash_table()) {
		LOG(L_ERR, "ERROR:tm:mod_init: initializing hash_table failed\n");
		return -1;
	}

	/* init static hidden values */
	init_t();

	if (!tm_init_timers()) {
		LOG(L_ERR, "ERROR:tm:mod_init: timer init failed\n");
		return -1;
	}

	/* register the timer functions */
	if (register_timer( timer_routine , 0, 1 )<0) {
		LOG(L_ERR, "ERROR:tm:mod_init: failed to register timer\n");
		return -1;
	}
	if (register_utimer( utimer_routine , 0, 100*1000 )<0) {
		LOG(L_ERR, "ERROR:tm:mod_init: failed to register utimer\n");
		return -1;
	}

	if (uac_init()==-1) {
		LOG(L_ERR, "ERROR:tm:mod_init: uac_init failed\n");
		return -1;
	}

	if (init_tmcb_lists()!=1) {
		LOG(L_CRIT, "ERROR:tm:mod_init: failed to init tmcb lists\n");
		return -1;
	}

	tm_init_tags();
	init_twrite_lines();
	if (init_twrite_sock() < 0) {
		LOG(L_ERR, "ERROR:tm:mod_init: Unable to create socket\n");
		return -1;
	}

	/* register post-script clean-up function */
	if (register_script_cb( do_t_unref, POST_SCRIPT_CB|REQ_TYPE_CB, 0)<0 ) {
		LOG(L_ERR,"ERROR:tm:mod_init: failed to register POST request "
			"callback\n");
		return -1;
	}
	if (register_script_cb( script_init, PRE_SCRIPT_CB|REQ_TYPE_CB , 0)<0 ) {
		LOG(L_ERR,"ERROR:tm:mod_init: failed to register PRE request "
			"callback\n");
		return -1;
	}

	if ( init_avp_params( fr_timer_param, fr_inv_timer_param)<0 ){
		LOG(L_ERR,"ERROR:tm:mod_init: failed to process timer AVPs\n");
		return -1;
	}

	return 0;
}


static int child_init(int rank)
{
	if (child_init_callid(rank) < 0) {
		LOG(L_ERR, "ERROR:tm:child_init: Error while initializing "
			"Call-ID generator\n");
		return -2;
	}

	return 0;
}




/**************************** wrapper functions ***************************/
static int t_check_status(struct sip_msg* msg, char *regexp, char *foo)
{
	regmatch_t pmatch;
	struct cell *t;
	char *status;
	char backup;
	int branch;
	int n;

	/* first get the transaction */
	t = get_t();
	if ( t==0 || t==T_UNDEFINED ) {
		LOG(L_ERR, "ERROR: t_check_status: cannot check status for a reply "
			"which has no T-state established\n");
		return -1;
	}
	backup = 0;

	switch (route_type) {
		case REQUEST_ROUTE:
			/* use the status of the last sent reply */
			status = int2str( t->uas.status, 0);
			break;
		case ONREPLY_ROUTE:
			/* use the status of the current reply */
			status = msg->first_line.u.reply.status.s;
			backup = status[msg->first_line.u.reply.status.len];
			status[msg->first_line.u.reply.status.len] = 0;
			break;
		case FAILURE_ROUTE:
			/* use the status of the winning reply */
			if ( (branch=t_get_picked_branch())<0 ) {
				LOG(L_CRIT,"BUG:t_check_status: no picked branch (%d) for"
					" a final response in MODE_ONFAILURE\n", branch);
				return -1;
			}
			status = int2str( t->uac[branch].last_received , 0);
			break;
		default:
			LOG(L_ERR,"ERROR:t_check_status: unsupported route_type %d\n",
					route_type);
			return -1;
	}

	DBG("DEBUG:t_check_status: checked status is <%s>\n",status);
	/* do the checking */
	n = regexec((regex_t*)regexp, status, 1, &pmatch, 0);

	if (backup) status[msg->first_line.u.reply.status.len] = backup;
	if (n!=0) return -1;
	return 1;
}


inline static int t_check_trans(struct sip_msg* msg, char *foo, char *bar)
{
	struct cell *trans;

	if (msg->REQ_METHOD==METHOD_CANCEL) {
		/* parse needed hdrs*/
		if (check_transaction_quadruple(msg)==0) {
			LOG(L_ERR, "ERROR:tm:t_check_trans: too few headers\n");
			return 0; /*drop request!*/
		}
		if (!msg->hash_index)
			msg->hash_index = tm_hash(msg->callid->body,get_cseq(msg)->number);
		/* performe lookup */
		trans = t_lookupOriginalT(  msg );
		return trans?1:-1;
	} else {
		trans = get_t();
		if (trans==NULL)
			return -1;
		if (trans!=T_UNDEFINED)
			return 1;
		switch ( t_lookup_request( msg , 0) ) {
			case 1:
				/* transaction found -> is it local ACK? */
				if (msg->REQ_METHOD==METHOD_ACK)
					return 1;
				/* .... else -> retransmission */
				trans = get_t();
				t_retransmit_reply(trans);
				UNREF(trans);
				set_t(0);
				return 0;
			case -2:
				/* e2e ACK found */
				return 1;
			default:
				/* notfound */
				return -1;
		}
	}
}


static int t_flush_flags(struct sip_msg* msg, char *foo, char *bar)
{
	struct cell *t;

	/* first get the transaction */
	t = get_t();
	if ( t==0 || t==T_UNDEFINED) {
		LOG(L_ERR, "ERROR: t_flush_flags: cannot flush flags for a message "
			"which has no T-state established\n");
		return -1;
	}

	/* do the flush */
	t->uas.request->flags = msg->flags;
	return 1;
}


inline static int t_local_replied(struct sip_msg* msg, char *type, char *bar)
{
	struct cell *t;
	int branch;
	int i;

	t = get_t();
	if (t==0 || t==T_UNDEFINED) {
		LOG(L_ERR,"ERROR:t_local_replied: no trasaction created\n");
		return -1;
	}

	switch ( (int)(long)type ) {
		/* check all */
		case 0:
			for( i=t->first_branch ; i<t->nr_of_outgoings ; i++ ) {
				if (t->uac[i].flags&T_UAC_HAS_RECV_REPLY)
					return -1;
			}
			return 1;
		/* check branch */
		case 1:
			if (route_type==FAILURE_ROUTE) {
				/* use the the winning reply */
				if ( (branch=t_get_picked_branch())<0 ) {
					LOG(L_CRIT,"BUG:t_local_replied: no picked branch (%d) for"
						" a final response in MODE_ONFAILURE\n", branch);
					return -1;
				}
				if (t->uac[branch].flags&T_UAC_HAS_RECV_REPLY)
					return -1;
				return 1;
			}
			return -1;
		/* check last */
		case 2:
			if (route_type==FAILURE_ROUTE) {
				/* use the the winning reply */
				if ( (branch=t_get_picked_branch())<0 ) {
					LOG(L_CRIT,"BUG:t_local_replied: no picked branch (%d) for"
						" a final response in MODE_ONFAILURE\n", branch);
					return -1;
				}
				if (t->uac[branch].reply==FAKED_REPLY)
					return 1;
				return -1;
			}
			return (t->relaied_reply_branch==-2)?1:-1;
		default:
			return -1;
	}
}


static int t_was_cancelled(struct sip_msg* msg, char *foo, char *bar)
{
	struct cell *t;

	/* first get the transaction */
	t = get_t();
	if ( t==0 || t==T_UNDEFINED ) {
		LOG(L_ERR, "ERROR:tm:t_was_cancelled: cannot check cancel flag for "
			"a reply without a transaction\n");
		return -1;
	}
	return (t->flags&T_WAS_CANCELLED_FLAG)?1:-1;
}


inline static int w_t_reply(struct sip_msg* msg, char* str1, char* str2)
{
	struct cell *t;

	if (msg->REQ_METHOD==METHOD_ACK) {
		LOG(L_WARN, "WARNING: t_reply: ACKs are not replied\n");
		return -1;
	}
	t=get_t();
	if ( t==0 || t==T_UNDEFINED ) {
		LOG(L_ERR, "ERROR: t_reply: cannot send a t_reply to a message "
			"for which no T-state has been established\n");
		return -1;
	}
	/* if called from reply_route, make sure that unsafe version
	 * is called; we are already in a mutex and another mutex in
	 * the safe version would lead to a deadlock
	 */
	switch (route_type) {
		case FAILURE_ROUTE:
			DBG("DEBUG: t_reply_unsafe called from w_t_reply\n");
			return t_reply_unsafe(t, msg, (unsigned int)(long)str1,(str*)str2);
		case REQUEST_ROUTE:
			return t_reply( t, msg, (unsigned int)(long) str1, (str*)str2);
		default:
			LOG(L_CRIT, "BUG:tm:w_t_reply: unsupported route_type (%d)\n",
				route_type);
			return -1;
	}
}


inline static int w_t_release(struct sip_msg* msg, char* str, char* str2)
{
	struct cell *t;

	t=get_t();
	if ( t && t!=T_UNDEFINED ) 
		return t_release_transaction( t );
	return 1;
}


inline static int w_t_newtran( struct sip_msg* p_msg, char* foo, char* bar ) 
{
	/* t_newtran returns 0 on error (negative value means
	   'transaction exists' */
	return t_newtran( p_msg );
}


inline static int w_t_on_negative( struct sip_msg* msg, char *go_to, char *foo)
{
	t_on_negative( (unsigned int )(long) go_to );
	return 1;
}


inline static int w_t_on_reply( struct sip_msg* msg, char *go_to, char *foo )
{
	t_on_reply( (unsigned int )(long) go_to );
	return 1;
}


inline static int w_t_on_branch( struct sip_msg* msg, char *go_to, char *foo )
{
	t_on_branch( (unsigned int )(long) go_to );
	return 1;
}


inline static int w_t_replicate(struct sip_msg *p_msg, char *dst, char *flags)
{
	return t_replicate( p_msg, (str*)dst, (int)(long)flags);
}


inline static int w_t_relay( struct sip_msg  *p_msg , char *proxy, char *flags)
{
	struct cell *t;
	int ret;

	t=get_t();

	if (!t || t==T_UNDEFINED) {
		/* no transaction yet */
		if (route_type==FAILURE_ROUTE) {
			LOG(L_CRIT, "BUG:tm:w_t_relay: undefined T\n");
			return -1;
		}
		return t_relay_to( p_msg, (struct proxy_l *)proxy, (int)(long)flags );
	} else {
		/* transaction already created */

		if ( route_type!=REQUEST_ROUTE && route_type!=FAILURE_ROUTE )
			goto route_err;

		if (p_msg->REQ_METHOD==METHOD_ACK) {
			/* local ACK*/
			t_release_transaction(t);
			return 1;
		}

		if (((int)(long)flags)&TM_T_REPLY_nodnsfo_FLAG)
			t->flags|=T_NO_DNS_FAILOVER_FLAG;

		ret = t_forward_nonack( t, p_msg, (struct proxy_l *)proxy);
		if (ret<=0 )
			LOG(L_ERR, "ERROR:tm:w_t_relay: t_forward_nonack failed\n");
		return ret;
	}

route_err:
	LOG(L_CRIT, "ERROR:tm:w_t_relay: unsupported route type: %d\n",
		route_type);
	return 0;
}



/* item functions */
static int it_get_tm_branch_idx(struct sip_msg *msg, xl_value_t *res,
		xl_param_t *param, int flags)
{
	extern int _tm_branch_index;
	int l = 0;
	char *ch = NULL;

	if(msg==NULL || res==NULL)
		return -1;
	
	ch = int2str(_tm_branch_index, &l);

	res->rs.s = ch;
	res->rs.len = l;

	res->ri = _tm_branch_index;
	res->flags = XL_VAL_STR|XL_VAL_INT|XL_TYPE_INT;

	return 0;
}

static int it_get_tm_reply_code(struct sip_msg *msg, xl_value_t *res,
		xl_param_t *param, int flags)
{
	struct cell *t;
	int code;
	int branch;

	if(msg==NULL || res==NULL)
		return -1;

	/* first get the transaction */
	if (t_check( msg , 0 )==-1) return -1;
	if ( (t=get_t())==0) {
		/* no T */
		code = 0;
	} else {
		switch (route_type) {
			case REQUEST_ROUTE:
				/* use the status of the last sent reply */
				code = t->uas.status;
				break;
			case ONREPLY_ROUTE:
				/* use the status of the current reply */
				code = msg->first_line.u.reply.statuscode;
				break;
			case FAILURE_ROUTE:
				/* use the status of the winning reply */
				if ( (branch=t_get_picked_branch())<0 ) {
					LOG(L_CRIT,"BUG:tm:it_get_tm_reply_code: no picked "
						"branch (%d) for a final response in MODE_ONFAILURE\n",
						branch);
					code = 0;
				} else {
					code = t->uac[branch].last_received;
				}
				break;
			default:
				LOG(L_ERR,"ERROR:tm:it_get_tm_reply_code: unsupported "
					"route_type %d\n", route_type);
				code = 0;
		}
	}

	DBG("DEBUG:it_get_tm_reply_code: reply code is <%d>\n",code);

	res->rs.s = int2str( code, &res->rs.len);

	res->ri = code;
	res->flags = XL_VAL_STR|XL_VAL_INT|XL_TYPE_INT;
	return 0;
}

