/*
 * Copyright (C) 2007 Voice System SRL
 *
 * 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
 *
*/


/*!
 * \file
 * \brief Requests
 * \ingroup dialog
 * Module: \ref dialog
 */

#include <stdlib.h>
#include <string.h>

#include "../../core/dprint.h"
#include "../../core/ut.h"
#include "../../lib/srdb1/db.h"
#include "../../core/dprint.h"
#include "../../core/config.h"
#include "../../core/socket_info.h"
#include "../../core/dset.h"
#include "../../modules/tm/dlg.h"
#include "../../modules/tm/tm_load.h"
#include "../../core/counters.h"
#include "../../core/parser/contact/parse_contact.h"
#include "dlg_timer.h"
#include "dlg_hash.h"
#include "dlg_handlers.h"
#include "dlg_req_within.h"
#include "dlg_db_handler.h"


#define MAX_FWD_HDR        "Max-Forwards: " MAX_FWD CRLF
#define MAX_FWD_HDR_LEN    (sizeof(MAX_FWD_HDR) - 1)

extern str dlg_extra_hdrs;
extern str dlg_lreq_callee_headers;
extern int dlg_ka_failed_limit;
extern int dlg_filter_mode;

extern int bye_early_code;
extern str bye_early_reason;

/**
 *
 */
int free_tm_dlg(dlg_t *td)
{
	if(td)
	{
		if(td->route_set)
			free_rr(&td->route_set);
		pkg_free(td);
	}
	return 0;
}



dlg_t * build_dlg_t(struct dlg_cell * cell, int dir){

	dlg_t* td = NULL;
	str cseq;
	unsigned int loc_seq;
	char nbuf[MAX_URI_SIZE];
	char dbuf[80];
	str nuri = STR_NULL;
	str duri = STR_NULL;
	size_t sz;
	char *p;

	/*remote target--- Request URI*/
	if(cell->contact[dir].s==0 || cell->contact[dir].len==0){
		LM_ERR("no contact available\n");
		goto error;
	}
	if(cell->route_set[dir].s==NULL || cell->route_set[dir].len<=0){
		/*try to restore alias parameter if no route set */
		nuri.s = nbuf;
		nuri.len = MAX_URI_SIZE;
		duri.s = dbuf;
		duri.len = 80;
		if(uri_restore_rcv_alias(&cell->contact[dir], &nuri, &duri)<0) {
			nuri.len = 0;
			duri.len = 0;
		}
	}
	if(nuri.len>0 && duri.len>0) {
		sz = sizeof(dlg_t) + (nuri.len+duri.len+2)*sizeof(char);
	} else {
		sz = sizeof(dlg_t);
	}
	td = (dlg_t*)pkg_malloc(sz);
	if(!td){
		LM_ERR("out of pkg memory\n");
		return NULL;
	}
	memset(td, 0, sz);

	/*local sequence number*/
	cseq = (dir == DLG_CALLER_LEG) ?	cell->cseq[DLG_CALLEE_LEG]:
										cell->cseq[DLG_CALLER_LEG];
	if(str2int(&cseq, &loc_seq) != 0){
		LM_ERR("invalid cseq\n");
		goto error;
	}
	/*we don not increase here the cseq as this will be done by TM*/
	td->loc_seq.value = loc_seq;
	td->loc_seq.is_set = 1;

	/*route set*/
	if( cell->route_set[dir].s && cell->route_set[dir].len){
		if( parse_rr_body(cell->route_set[dir].s, cell->route_set[dir].len,
						&td->route_set) !=0){
		 	LM_ERR("failed to parse route set\n");
			goto error;
		}
	}

	if(nuri.len>0 && duri.len>0) {
		/* req uri */
		p = (char*)td + sizeof(dlg_t);
		strncpy(p, nuri.s, nuri.len);
		p[nuri.len] = '\0';
		td->rem_target.s = p;
		td->rem_target.len = nuri.len;
		/* dst uri */
		p += nuri.len + 1;
		strncpy(p, duri.s, duri.len);
		p[duri.len] = '\0';
		td->dst_uri.s = p;
		td->dst_uri.len = duri.len;
	} else {
		td->rem_target = cell->contact[dir];
	}

	td->rem_uri	=   (dir == DLG_CALLER_LEG)?	cell->from_uri: cell->to_uri;
	td->loc_uri	=	(dir == DLG_CALLER_LEG)?	cell->to_uri: cell->from_uri;
	td->id.call_id = cell->callid;
	td->id.rem_tag = cell->tag[dir];
	td->id.loc_tag = (dir == DLG_CALLER_LEG) ? 	cell->tag[DLG_CALLEE_LEG]:
												cell->tag[DLG_CALLER_LEG];

	td->state= DLG_CONFIRMED;
	td->send_sock = cell->bind_addr[dir];

	return td;

error:
	free_tm_dlg(td);
	return NULL;
}



/* callback function to handle responses to the BYE request */
void bye_reply_cb(struct cell* t, int type, struct tmcb_params* ps){

	struct dlg_cell* dlg;
	int event, old_state, new_state, unref, ret;
	dlg_iuid_t *iuid = NULL;

	if(ps->param == NULL || *ps->param == NULL){
		LM_ERR("invalid parameter\n");
		return;
	}

	if(ps->code < 200){
		LM_DBG("receiving a provisional reply\n");
		return;
	}

	LM_DBG("receiving a final reply %d\n",ps->code);

	iuid = (dlg_iuid_t*)(*ps->param);
	dlg = dlg_get_by_iuid(iuid);
	if(dlg==0)
		return;

	event = DLG_EVENT_REQBYE;
	next_state_dlg(dlg, event, &old_state, &new_state, &unref);

	if(new_state == DLG_STATE_DELETED && old_state != DLG_STATE_DELETED){

		LM_DBG("removing dialog with h_entry %u and h_id %u\n", 
			dlg->h_entry, dlg->h_id);

		/* remove from timer */
		ret = remove_dialog_timer(&dlg->tl);
		if (ret < 0) {
			LM_CRIT("unable to unlink the timer on dlg %p [%u:%u] "
				"with clid '%.*s' and tags '%.*s' '%.*s'\n",
				dlg, dlg->h_entry, dlg->h_id,
				dlg->callid.len, dlg->callid.s,
				dlg->tag[DLG_CALLER_LEG].len, dlg->tag[DLG_CALLER_LEG].s,
				dlg->tag[DLG_CALLEE_LEG].len, dlg->tag[DLG_CALLEE_LEG].s);
		} else if (ret > 0) {
			LM_WARN("inconsistent dlg timer data on dlg %p [%u:%u] "
				"with clid '%.*s' and tags '%.*s' '%.*s'\n",
				dlg, dlg->h_entry, dlg->h_id,
				dlg->callid.len, dlg->callid.s,
				dlg->tag[DLG_CALLER_LEG].len, dlg->tag[DLG_CALLER_LEG].s,
				dlg->tag[DLG_CALLEE_LEG].len, dlg->tag[DLG_CALLEE_LEG].s);
		} else {
			unref++;
		}
		/* dialog terminated (BYE) */
		run_dlg_callbacks( DLGCB_TERMINATED_CONFIRMED, dlg, ps->req, ps->rpl, DLG_DIR_NONE, 0);

		LM_DBG("first final reply\n");
		/* derefering the dialog */
		dlg_unref(dlg, unref+1);

		if_update_stat( dlg_enable_stats, active_dlgs, -1);
	}

	if(new_state == DLG_STATE_DELETED && old_state == DLG_STATE_DELETED ) {
		/* trash the dialog from DB and memory */
		LM_DBG("second final reply\n");
		/* delete the dialog from DB */
		if (dlg_db_mode)
			remove_dialog_from_db(dlg);
		/* force delete from mem */
		dlg_unref(dlg, 1);
	}
	dlg_iuid_sfree(iuid);
}


/* callback function to handle responses to the keep-alive request */
void dlg_ka_cb_all(struct cell* t, int type, struct tmcb_params* ps, int dir)
{
	int tend;
	dlg_cell_t* dlg;
	dlg_iuid_t *iuid = NULL;

	if(ps->param == NULL || *ps->param == NULL) {
		LM_ERR("invalid parameter\n");
		return;
	}

	if(ps->code < 200) {
		LM_DBG("receiving a provisional reply\n");
		return;
	}

	LM_DBG("receiving a final reply %d\n",ps->code);

	iuid = (dlg_iuid_t*)(*ps->param);
	dlg = dlg_get_by_iuid(iuid);
	if(dlg==0) {
		dlg_iuid_sfree(iuid);
		return;
	}

	if(ps->code==408 || ps->code==481) {
		if (dlg->state != DLG_STATE_CONFIRMED) {
			LM_DBG("skip updating non-confirmed dialogs\n");
			goto done;
		}
		if(ps->code==408 && (dlg->cseq[dir].len==0
					|| (dlg->cseq[dir].len==1 && dlg->cseq[dir].s[0]=='\0'))) {
			LM_DBG("ignore 408 for %s cseq 0\n",
					((dir==DLG_CALLER_LEG)?"caller":"callee"));
			goto done;
		}
		tend = 0;
		if(dir==DLG_CALLER_LEG) {
			dlg->ka_src_counter++;
			if(dlg->ka_src_counter>=dlg_ka_failed_limit) {
				tend = 1;
			}
		} else {
			dlg->ka_dst_counter++;
			if(dlg->ka_dst_counter>=dlg_ka_failed_limit) {
				tend = 1;
			}
		}
		if(tend) {
			if(update_dlg_timer(&dlg->tl, 10)<0) {
				LM_ERR("failed to update dialog lifetime\n");
				goto done;
			}
			dlg->lifetime = 10;
			dlg->dflags |= DLG_FLAG_CHANGED;
		}
	} else {
		if (dlg->state == DLG_STATE_CONFIRMED) {
			if(dir==DLG_CALLER_LEG) {
				dlg->ka_src_counter = 0;
			} else {
				dlg->ka_dst_counter = 0;
			}
		}
	}

done:
	dlg_unref(dlg, 1);
	dlg_iuid_sfree(iuid);
}

/* callback function to handle responses to the keep-alive request to src */
void dlg_ka_cb_src(struct cell* t, int type, struct tmcb_params* ps)
{
	dlg_ka_cb_all(t, type, ps, DLG_CALLER_LEG);
}

/* callback function to handle responses to the keep-alive request to dst */
void dlg_ka_cb_dst(struct cell* t, int type, struct tmcb_params* ps)
{
	dlg_ka_cb_all(t, type, ps, DLG_CALLEE_LEG);
}

static inline int build_extra_hdr(struct dlg_cell * cell, str *extra_hdrs,
		str *str_hdr)
{
	char *p;
	int blen;

	str_hdr->len = MAX_FWD_HDR_LEN + dlg_extra_hdrs.len;
	if(extra_hdrs && extra_hdrs->len>0)
		str_hdr->len += extra_hdrs->len;

	blen = str_hdr->len + 3 /* '\r\n\0' */;

	/* reserve space for callee headers in local requests */
	if(dlg_lreq_callee_headers.len>0)
		blen += dlg_lreq_callee_headers.len + 2 /* '\r\n' */;

	str_hdr->s = (char*)pkg_malloc( blen * sizeof(char) );
	if(!str_hdr->s){
		LM_ERR("out of pkg memory\n");
		goto error;
	}

	memcpy(str_hdr->s , MAX_FWD_HDR, MAX_FWD_HDR_LEN );
	p = str_hdr->s + MAX_FWD_HDR_LEN;
	if (dlg_extra_hdrs.len) {
		memcpy( p, dlg_extra_hdrs.s, dlg_extra_hdrs.len);
		p += dlg_extra_hdrs.len;
	}
	if (extra_hdrs && extra_hdrs->len>0)
		memcpy( p, extra_hdrs->s, extra_hdrs->len);

	return 0;

error: 
	return -1;
}



/* cell- pointer to a struct dlg_cell
 * dir- direction: the request will be sent to:
 * 		DLG_CALLER_LEG (0): caller
 * 		DLG_CALLEE_LEG (1): callee
 */
static inline int send_bye(struct dlg_cell * cell, int dir, str *hdrs)
{
	uac_req_t uac_r;
	dlg_t* dialog_info;
	str met = {"BYE", 3};
	int result;
	dlg_iuid_t *iuid = NULL;
	str lhdrs;

	/* Send Cancel or final response for non-confirmed dialogs */
	if (cell->state != DLG_STATE_CONFIRMED_NA && cell->state != DLG_STATE_CONFIRMED) {
		if (cell->t) {
			if (dir == DLG_CALLER_LEG) {
				if(d_tmb.t_reply(cell->t->uas.request, bye_early_code, bye_early_reason.s)< 0) {
					LM_ERR("Failed to send reply to caller\n");
					return -1;
				}
				LM_DBG("\"%d %.*s\" sent to caller\n", bye_early_code, bye_early_reason.len, bye_early_reason.s);
			} else {
				d_tmb.cancel_all_uacs(cell->t, 0);
				LM_DBG("CANCEL sent to callee(s)\n");
			}
			return 0;
		} else {
			LM_ERR("terminating non-confirmed dialog not possible, transaction not longer available.\n");
			return -1;
		}
	}

	/*verify direction*/

	if ((dialog_info = build_dlg_t(cell, dir)) == 0){
		LM_ERR("failed to create dlg_t\n");
		goto err;
	}

	/* safety bump of cseq if prack was involved in call setup */
	if(cell->iflags & DLG_IFLAG_PRACK) {
		dialog_info->loc_seq.value += 80;
	}

	LM_DBG("sending BYE to %s\n", (dir==DLG_CALLER_LEG)?"caller":"callee");

	iuid = dlg_get_iuid_shm_clone(cell);
	if(iuid==NULL)
	{
		LM_ERR("failed to create dialog unique id clone\n");
		goto err;
	}

	lhdrs = *hdrs;

	if(dir==DLG_CALLEE_LEG && dlg_lreq_callee_headers.len>0) {
		/* space allocated in hdrs->s by build_extra_hdrs() */
		memcpy(lhdrs.s+lhdrs.len, dlg_lreq_callee_headers.s,
				dlg_lreq_callee_headers.len);
		lhdrs.len += dlg_lreq_callee_headers.len;
		if(dlg_lreq_callee_headers.s[dlg_lreq_callee_headers.len-1]!='\n') {
			memcpy(lhdrs.s+lhdrs.len, CRLF, CRLF_LEN);
			lhdrs.len += CRLF_LEN;
		}
	}

	set_uac_req(&uac_r, &met, &lhdrs, NULL, dialog_info, TMCB_LOCAL_COMPLETED,
				bye_reply_cb, (void*)iuid);
	result = d_tmb.t_request_within(&uac_r);

	if(result < 0){
		LM_ERR("failed to send the BYE request\n");
		goto err;
	}

	free_tm_dlg(dialog_info);

	LM_DBG("BYE sent to %s\n", (dir==0)?"caller":"callee");
	return 0;

err:
	if(dialog_info)
		free_tm_dlg(dialog_info);
	return -1;
}

dlg_t * build_dlg_t_early(struct sip_msg *msg, struct dlg_cell * cell,
		int branch_id, str * rr_set)
{

	dlg_t* td = NULL;
	str cseq;
	unsigned int loc_seq;
	char nbuf[MAX_URI_SIZE];
	char dbuf[80];
	str nuri = STR_NULL;
	str duri = STR_NULL;
	size_t sz;
	char *p;
	unsigned int own_rr = 0, skip_recs = 0;

	if (cell->state != DLG_STATE_UNCONFIRMED && cell->state != DLG_STATE_EARLY) {
		LM_ERR("invalid state for build_dlg_state: %d"
				" (only working for unconfirmed or early dialogs)\n", cell->state);
		goto error;
	}

	if (msg == NULL || msg->first_line.type != SIP_REPLY) {
		if (!cell->t) {
			LM_ERR("no transaction associated\n");
			goto error;
		}

		if (branch_id <= 0 || branch_id > cell->t->nr_of_outgoings) {
			LM_ERR("invalid branch %d (%d branches in transaction)\n",
					branch_id, cell->t->nr_of_outgoings);
			goto error;
		}
		msg = msg;
	}

	if (!msg->contact && (parse_headers(msg,HDR_CONTACT_F,0)<0
				|| !msg->contact)) {
		LM_ERR("bad sip message or missing Contact hdr\n");
		goto error;
	}

	if ( parse_contact(msg->contact)<0 ||
			((contact_body_t *)msg->contact->parsed)->contacts==NULL) {
		LM_ERR("bad Contact HDR\n");
		goto error;
	}

	/*try to restore alias parameter if no route set */
	nuri.s = nbuf;
	nuri.len = MAX_URI_SIZE;
	duri.s = dbuf;
	duri.len = 80;
	if(uri_restore_rcv_alias(&((contact_body_t *)msg->contact->parsed)->contacts->uri,
				&nuri, &duri)<0) {
		nuri.len = 0;
		duri.len = 0;
	}

	if(nuri.len>0 && duri.len>0) {
		sz = sizeof(dlg_t) + (nuri.len+duri.len+2)*sizeof(char);
	} else {
		sz = sizeof(dlg_t);
	}

	td = (dlg_t*)pkg_malloc(sz);
	if(!td){
		LM_ERR("out of pkg memory\n");
		return NULL;
	}
	memset(td, 0, sz);

	/*route set*/
	if (msg->record_route) {
		if (cell->t) {
			LM_DBG("transaction exists\n");
			own_rr = (cell->t->flags&TM_UAC_FLAG_R2)?2:
				(cell->t->flags&TM_UAC_FLAG_RR)?1:0;
		} else {
			own_rr = (msg->flags&TM_UAC_FLAG_R2)?2:
				(msg->flags&TM_UAC_FLAG_RR)?1:0;
		}
		skip_recs = cell->from_rr_nb + own_rr;

		LM_DBG("skipping %u records, %u of myself\n", skip_recs, own_rr);

		if( print_rr_body(msg->record_route, rr_set, DLG_CALLEE_LEG,
					&skip_recs) != 0 ){
			LM_ERR("failed to print route records \n");
			goto error;
		}
		LM_DBG("new route set: %.*s\n", STR_FMT(rr_set));

		if( parse_rr_body(rr_set->s, rr_set->len,
					&td->route_set) !=0){
			LM_ERR("failed to parse route set\n");
			goto error;
		}
	}

	/*local sequence number*/
	cseq = cell->cseq[DLG_CALLER_LEG];

	if (cseq.len > 0) {
		LM_DBG("CSeq is %.*s\n", cseq.len, cseq.s);
		if(str2int(&cseq, &loc_seq) != 0){
			LM_ERR("invalid cseq\n");
			goto error;
		}
	} else {
		LM_DBG("CSeq not set yet, assuming 1\n");
		loc_seq = 1;
	}

	/*we don not increase here the cseq as this will be done by TM*/
	td->loc_seq.value = loc_seq;
	td->loc_seq.is_set = 1;

	LM_DBG("nuri: %.*s\n", STR_FMT(&nuri));
	LM_DBG("duri: %.*s\n", STR_FMT(&duri));

	if(nuri.len>0 && duri.len>0) {
		/* req uri */
		p = (char*)td + sizeof(dlg_t);
		strncpy(p, nuri.s, nuri.len);
		p[nuri.len] = '\0';
		td->rem_target.s = p;
		td->rem_target.len = nuri.len;
		/* dst uri */
		p += nuri.len + 1;
		strncpy(p, duri.s, duri.len);
		p[duri.len] = '\0';
		td->dst_uri.s = p;
		td->dst_uri.len = duri.len;
	} else {
		td->rem_target = ((contact_body_t *)msg->contact->parsed)->contacts->uri;
	}

	td->rem_uri	= cell->from_uri;
	td->loc_uri	= cell->to_uri;
	LM_DBG("rem_uri: %.*s\n", STR_FMT(&td->rem_uri));
	LM_DBG("loc_uri: %.*s\n", STR_FMT(&td->loc_uri));

	LM_DBG("rem_target: %.*s\n", STR_FMT(&td->rem_target));
	LM_DBG("dst_uri: %.*s\n", STR_FMT(&td->dst_uri));

	td->id.call_id = cell->callid;
	td->id.rem_tag = cell->tag[DLG_CALLER_LEG];
	td->id.loc_tag = cell->tag[DLG_CALLEE_LEG];

	td->state= DLG_EARLY;
	td->send_sock = cell->bind_addr[DLG_CALLER_LEG];

	return td;

error:
	LM_ERR("Error occured creating early dialog\n");
	free_tm_dlg(td);
	return NULL;
}

int dlg_request_within(struct sip_msg *msg, struct dlg_cell *dlg, int side,
		str * method, str * hdrs, str * content_type, str * content)
{
	uac_req_t uac_r;
	dlg_t* dialog_info;
	int result;
	dlg_iuid_t *iuid = NULL;
	char rr_set_s[MAX_URI_SIZE];
	str rr_set = {rr_set_s, 0};
	str allheaders = {0, 0};
	str content_type_hdr = {"Content-Type: ", 14};
	int idx = 0;
	memset(rr_set_s, 0, 500);

	/* Special treatment for callee in early state*/
	if (dlg->state != DLG_STATE_CONFIRMED_NA
			&& dlg->state != DLG_STATE_CONFIRMED && side == DLG_CALLEE_LEG) {
		LM_DBG("Send request to callee in early state...\n");

		if (dlg->t == NULL && d_tmb.t_gett) {
			dlg->t = d_tmb.t_gett();
			if (dlg->t && dlg->t != T_UNDEFINED)
				idx = dlg->t->nr_of_outgoings;
		}
		LM_DBG("Branch %i\n", idx);

		/*verify direction*/
		if ((dialog_info = build_dlg_t_early(msg, dlg, idx, &rr_set)) == 0){
			LM_ERR("failed to create dlg_t\n");
			goto err;
		}
	} else {
		LM_DBG("Send request to caller or in confirmed state...\n");
		/*verify direction*/
		if ((dialog_info = build_dlg_t(dlg, side)) == 0){
			LM_ERR("failed to create dlg_t\n");
			goto err;
		}
	}

	LM_DBG("sending %.*s to %s\n", method->len, method->s,
			(side==DLG_CALLER_LEG)?"caller":"callee");

	iuid = dlg_get_iuid_shm_clone(dlg);
	if(iuid==NULL)
	{
		LM_ERR("failed to create dialog unique id clone\n");
		goto err;
	}

	if (hdrs && hdrs->len > 0) {
		LM_DBG("Extra headers: %.*s\n", STR_FMT(hdrs));
		allheaders.len += hdrs->len;
	}

	if (content_type && content_type->s && content && content->s) {
		LM_DBG("Content-Type: %.*s\n", STR_FMT(content_type));
		allheaders.len += content_type_hdr.len + content_type->len + 2;
	}
	if (allheaders.len > 0) {
		allheaders.s = (char*)pkg_malloc(allheaders.len);
		if (allheaders.s == NULL) {
			PKG_MEM_ERROR;
			goto err;
		}
		allheaders.len = 0;
		if (hdrs && hdrs->len > 0) {
			memcpy(allheaders.s, hdrs->s, hdrs->len);
			allheaders.len += hdrs->len;
		}
		if (content_type && content_type->s && content && content->s) {
			memcpy(allheaders.s + allheaders.len, content_type_hdr.s, content_type_hdr.len);
			allheaders.len += content_type_hdr.len;
			memcpy(allheaders.s + allheaders.len, content_type->s, content_type->len);
			allheaders.len += content_type->len;
			memcpy(allheaders.s + allheaders.len, "\r\n", 2);
			allheaders.len += 2;
		}
		LM_DBG("All headers: %.*s\n", STR_FMT(&allheaders));
	}

	set_uac_req(&uac_r, method, allheaders.len?&allheaders:NULL,
			(content && content->len)?content:NULL, dialog_info, TMCB_LOCAL_COMPLETED,
				bye_reply_cb, (void*)iuid);

	result = d_tmb.t_request_within(&uac_r);

	if (allheaders.s)
		pkg_free(allheaders.s);

	if(result < 0){
		LM_ERR("failed to send request\n");
		goto err;
	}

	free_tm_dlg(dialog_info);

	LM_DBG("%.*s sent to %s\n", method->len, method->s,
			(side==DLG_CALLER_LEG)?"caller":"callee");

	return 0;
err:
	if(dialog_info)
		free_tm_dlg(dialog_info);
	return -1;
}

/* send keep-alive
 * dlg - pointer to a struct dlg_cell
 * dir - direction: the request will be sent to:
 * 		DLG_CALLER_LEG (0): caller
 * 		DLG_CALLEE_LEG (1): callee
 */
int dlg_send_ka(dlg_cell_t *dlg, int dir)
{
	uac_req_t uac_r;
	dlg_t* di;
	str met = {"OPTIONS", 7};
	int result;
	dlg_iuid_t *iuid = NULL;

	if (dlg_filter_mode & DLG_FILTER_LOCALONLY) {
		if (dlg->bind_addr[dir] == NULL) {
			LM_DBG("skipping dialog without bind address\n");
			return 0;
		}

		if (lookup_local_socket(&(dlg->bind_addr[dir]->sock_str)) == NULL) {
			LM_DBG("skipping non local dialog\n");
			return 0;
		}
	}

	/* do not send KA request for non-confirmed dialogs (not supported) */
	if (dlg->state != DLG_STATE_CONFIRMED) {
		LM_DBG("skipping non-confirmed dialogs\n");
		return 0;
	}

	/* build tm dlg by direction */
	if ((di = build_dlg_t(dlg, dir)) == 0){
		LM_ERR("failed to create dlg_t\n");
		goto err;
	}

	/* tm increases cseq value, decrease it no to make it invalid
	 * - dialog is ended on timeout (408) or C/L does not exist (481) */
	if(di->loc_seq.value>1)
		di->loc_seq.value -= 2;
	else
		di->loc_seq.value -= 1;

	LM_DBG("sending OPTIONS to %s\n", (dir==DLG_CALLER_LEG)?"caller":"callee");

	iuid = dlg_get_iuid_shm_clone(dlg);
	if(iuid==NULL)
	{
		LM_ERR("failed to create dialog unique id clone\n");
		goto err;
	}

	if(dir==DLG_CALLEE_LEG && dlg_lreq_callee_headers.len>0) {
		set_uac_req(&uac_r, &met, &dlg_lreq_callee_headers, NULL, di,
				TMCB_LOCAL_COMPLETED, dlg_ka_cb_dst, (void*)iuid);
	} else {
		set_uac_req(&uac_r, &met, NULL, NULL, di, TMCB_LOCAL_COMPLETED,
				(dir==DLG_CALLEE_LEG)?dlg_ka_cb_dst:dlg_ka_cb_src, (void*)iuid);
	}
	result = d_tmb.t_request_within(&uac_r);

	if(result < 0){
		LM_ERR("failed to send the OPTIONS request\n");
		goto err;
	}

	free_tm_dlg(di);

	LM_DBG("keep-alive sent to %s\n", (dir==0)?"caller":"callee");
	return 0;

err:
	if(di)
		free_tm_dlg(di);
	return -1;
}


int dlg_bye(struct dlg_cell *dlg, str *hdrs, int side)
{
	str all_hdrs = { 0, 0 };
	int ret;

	if(side==DLG_CALLER_LEG)
	{
		if(dlg->dflags&DLG_FLAG_CALLERBYE)
			return -1;
		dlg->dflags |= DLG_FLAG_CALLERBYE;
	} else {
		if(dlg->dflags&DLG_FLAG_CALLEEBYE)
			return -1;
		dlg->dflags |= DLG_FLAG_CALLEEBYE;
	}
	if ((build_extra_hdr(dlg, hdrs, &all_hdrs)) != 0)
	{
		LM_ERR("failed to build dlg headers\n");
		return -1;
	}
	ret = send_bye(dlg, side, &all_hdrs);
	pkg_free(all_hdrs.s);

	dlg_run_event_route(dlg, NULL, dlg->state, DLG_STATE_DELETED);

	return ret;
}

int dlg_bye_all(struct dlg_cell *dlg, str *hdrs)
{
	str all_hdrs = { 0, 0 };
	int ret;

	/* run dialog terminated callbacks */
	run_dlg_callbacks( DLGCB_TERMINATED, dlg, NULL, NULL, DLG_DIR_NONE, 0);

	if ((build_extra_hdr(dlg, hdrs, &all_hdrs)) != 0)
	{
		LM_ERR("failed to build dlg headers\n");
		return -1;
	}

	ret = send_bye(dlg, DLG_CALLER_LEG, &all_hdrs);
	ret |= send_bye(dlg, DLG_CALLEE_LEG, &all_hdrs);
	
	pkg_free(all_hdrs.s);

	dlg_run_event_route(dlg, NULL, dlg->state, DLG_STATE_DELETED);

	return ret;

}