/**
 * Copyright (C) 2007 iptego GmbH
 *
 * This file is part of ser, a free SIP server.
 *
 * ser 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
 *
 * ser 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
 */


#include <stdlib.h>
#ifdef EXTRA_DEBUG
#include <assert.h>
#endif

#include "ut.h"
#include "config.h"
#include "sr_module.h"
#include "parser/parse_rr.h"
#include "rpc.h"
#include "modules/tm/dlg.h" /* dlg_t */
/* define TM's branch_bm_t used in t_cancel.h (FIXME!!!) */
#include "modules/tm/t_reply.h"
#include "modules/tm/t_cancel.h" /* F_CANCEL_B_FAKE_REPLY */

#include "tid.h"
#include "binds.h" /* for tmb.t_uac_with_ids() */
#include "appsrv.h" /* for dispatch_tm_reply() */
#include "digest.h" /* for ASI_DSGT_IDS */

#define STX	0x02
#define ETX	0x03
#define SUB	0x1A

enum ASI_REQ_FLAGS {
	ASIREQ_ACK_FLG	= 1 << 0, /* wanna generate ACK */
	ASIREQ_FIN_FLG	= 1 << 1, /* wanna see final reply */
	ASIREQ_PRV_FLG	= 1 << 2, /* wanna see provisional reply */
	ASIREQ_RUN_ORR	= 1 << 3, /* run 'the' on reply route */
};

typedef struct {
	unsigned int flags;
	int as_id;
	str method;
	str opaque;
} tmcbprm_t;


int onreply_rt_idx = -1;


	/* RPC strings are 0-terminated */
#define STRIP_0TERM(_str, may0, _call) \
	do { \
		if (_str.len) \
			_str.len --; \
		if ((! _str.len) && (! may0)) { \
			rpc->add(ctx, "ds", 484, "Mandatory parameter empty."); \
			DEBUG("faulty %s() RPC: empty parameter (%s).\n", _call, #_str); \
			return; \
		} \
		DEBUG("%s(%s: `%.*s' [%d]).\n", _call, #_str, \
				_str.len, _str.s, _str.len); \
	} while (0)



/* TODO: WTF?! no such generic function[=print a URI] in all SER?!?!? */
/**
 * turn outbound socket into SIP URI.
 * @return Offset past <<scheme COLUMN>> (so that a <<user@>> can be
 * inserted) OR, on error, negative.
 */
static inline int outbound_as_uri(str *dst_uri, str *outbound_uri)
{
	struct dest_info dstinfo;
	struct socket_info* send_sock;
	const static str trans_tcp = STR_STATIC_INIT(TRANSPORT_PARAM "tcp");
	const static str sch_sip = STR_STATIC_INIT("sip:");
	const static str sch_sips = STR_STATIC_INIT("sips:");
	const str *param, *scheme;
	char *cursor;

/* TODO: having this conditional is IMO a bad ideea; why not only one
 * signature and NULL for the !USE_DNS_FAILOVER ? */
#ifdef USE_DNS_FAILOVER
	if (! uri2dst(NULL, &dstinfo, /*msg*/NULL, dst_uri, PROTO_NONE))
#else
	if (! uri2dst(&dstinfo, /*msg*/NULL, dst_uri, PROTO_NONE))
#endif
	{
		ERR("failed to resolve URI `%.*s' into socket info.\n", 
				STR_FMT(dst_uri));
		return -1;
	} else {
		send_sock = dstinfo.send_sock;
	}
	switch (send_sock->proto) {
		case PROTO_UDP:
			scheme = &sch_sip;
			param = NULL;
			break;
		case PROTO_TCP:
			scheme = &sch_sip;
			param = &trans_tcp;
			break;
		case PROTO_TLS:
			scheme = &sch_sips;
			/* TODO: does transport=tls make sense if already sips: ? */
			param = NULL;

		do {
		case PROTO_NONE: BUG("unknown protocol for outbound socket.\n"); break;
		/* TODO: sip/sips? */
		case PROTO_SCTP: BUG("unimplemented protocol (SCTP).\n"); break;
		default: BUG("invalid protocol %d.\n", send_sock->proto); break;
		} while (0);
			return -1;
	}

	if (outbound_uri->len < (scheme->len + send_sock->address_str.len + 
			/*`:'*/1 + send_sock->port_no_str.len + 
			(param ? param->len : 0))) {
		ERR("insufficient buffer to print outbound URI for destination URI"
				" `%.*s'.\n", dst_uri->len, dst_uri->s);
		return -1;
	}
	cursor = outbound_uri->s;
	memcpy(cursor, scheme->s, scheme->len);
	cursor += scheme->len;
	memcpy(cursor, send_sock->address_str.s, send_sock->address_str.len);
	cursor += send_sock->address_str.len;
	/* TODO: if  UDP/TCP & 5060 || TLS & 5071, skip port */
	*cursor = ':'; cursor ++;
	memcpy(cursor, send_sock->port_no_str.s, send_sock->port_no_str.len);
	cursor += send_sock->port_no_str.len;
	if (param) {
		memcpy(cursor, param->s, param->len);
		cursor += param->len;
	}

	outbound_uri->len = cursor - outbound_uri->s;
	return scheme->len;
}

/**
 * Replace SUB or <<STX user_name ETX>> with SIP URI of outbound socket
 * (through which the message will be dispatched).
 */
int fix_local_uris(str *uri, str old_hdrs, str *new_hdrs)
{
	char *old_end, *new_end;
	char *old_curs, *new_curs;
	char *marker, *tmp;
	char buff[MAX_URI_SIZE];
	str local_uri = {buff, sizeof(buff)};
	off_t scheme_len = 0; /* if 0, local_uri had not been resolved */
	enum {
		PARSE_SKIP,	/* cursor in 'casual' header text */
		PARSE_TEXT,	/* cursor between STX and ETX */
	} parse_state;


#define GET_LOCAL_URI \
	do { \
		if (scheme_len != 0) \
			break; \
		if ((scheme_len = outbound_as_uri(uri, &local_uri)) < 0) \
			return -1; \
	} while (0)

#define WRITE_TO_HDRS(_src_, _len) \
	do { \
		size_t _len_ = _len; \
		if (new_end < new_curs + _len_) { \
			ERR("insufficient buffer space (%d !) to fix local URIs.\n", \
					new_hdrs->len); \
			return -1; \
		} \
		memcpy(new_curs, _src_, _len_); \
		new_curs += _len_; \
	} while (0)

	for (	old_curs = old_hdrs.s, old_end = old_hdrs.s + old_hdrs.len,
			new_curs = new_hdrs->s, new_end = new_hdrs->s + new_hdrs->len,
			marker = old_hdrs.s, parse_state = PARSE_SKIP;
			old_curs < old_end; old_curs ++) {
		switch (parse_state) {
			case PARSE_SKIP:
				switch (*old_curs) {
					case STX:
						WRITE_TO_HDRS(marker, old_curs - marker);
						marker = old_curs + /*past current position*/1;
						parse_state = PARSE_TEXT;
						break;
					case ETX:
						ERR("unexpected ETX marker (after `%.*s'): no leading "
								"STX.\n", (int)(old_curs - old_hdrs.s), 
								old_hdrs.s);
						return -1;
					case SUB:
						GET_LOCAL_URI;
						WRITE_TO_HDRS(marker, old_curs - marker);
						marker = old_curs + /*past current position*/1;
						WRITE_TO_HDRS(local_uri.s, local_uri.len);
						break;
					case 0:
						WRITE_TO_HDRS(marker, old_curs - marker);
						new_hdrs->len = new_curs - new_hdrs->s;
						break; /* formal */
				}
				break;

			case PARSE_TEXT:
				switch (*old_curs) {
					do {
						case SUB: tmp = "SUB"; break;
						case STX: tmp = "STX"; break;
						case 0: tmp = "0-terminator"; break;
					} while (0);
						ERR("unexpected %s marker (after `%.*s'): in text "\
								"mode.\n", tmp, (int)(old_curs - old_hdrs.s), 
								old_hdrs.s);
						return -1;

					case ETX:
						GET_LOCAL_URI;
						WRITE_TO_HDRS(local_uri.s, scheme_len);
						WRITE_TO_HDRS(marker, old_curs - marker);
						WRITE_TO_HDRS("@", 1);
						WRITE_TO_HDRS(local_uri.s + scheme_len, 
								local_uri.len - scheme_len);
						marker = old_curs + /*past current position*/1;
						parse_state = PARSE_SKIP;
						break; /* formal */
				}
				break;

			default:
				BUG("invalid parsing state %d.\n", parse_state);
				abort();
		}
	}
	return 0;
#undef WRITE_TO_HDRS
#undef GET_LOCAL_URI
}

#ifdef ASI_FAKE_408
static inline void free_faked_408(struct sip_msg *faked)
{
	if (! faked)
		return;
#ifdef DYN_BUF
/* DYN_BUF is deprecated (until using threads :-) ) */
#error "dynamic buffer not supported (anymore?) by ASI module"
#endif
	free_sip_msg(faked);
	pkg_free(faked);
	DEBUG("faked 408 free'd.\n");
}

/**
 * Fake a 408 SIP reply.
 *
 * NOTE:
 * Scenario: A->B call, through B2B. B doesn't reply (at all or only with
 * 100).
 * Problem: there is a race between the first inbound (A->SER->B2B) transaction
 * and the outbound (B2B->SER->B) one: as the inbound one is started first, it
 * will also timeout first and will reply itself to A with a 408. 
 * When the second one times out, B2B will try to reply to the first one with
 * its own 408, but will get back and error, since a 408 was already generated
 * in this (first) transaction.
 *
 * 1 Fix: make the first transaction time out slower, relative to second one
 * (in script; see t_set_fr()).
 */
static struct sip_msg *fake_408(struct cell *trans)
{
	const static str FIRST_LINE = 
			/* hope they won't change it too soon */
			STR_STATIC_INIT("SIP/2.0 408 Request Timedout\r\n");
	const char *orig_buff;
	int orig_len, i, found_at;
	struct sip_msg *faked;

	DEBUG("faking 408 SIP message for UAC#0/T@%p.\n", trans);

	orig_buff = trans->uac[0].request.buffer;
	orig_len = trans->uac[0].request.buffer_len;
#ifdef EXTRA_DEBUG
	assert(0 < orig_len);
#endif

	found_at = 0;
	for (i = 0; i < orig_len; i ++)
		if (orig_buff[i] == '\n') {
#ifdef EXTRA_DEBUG
			assert(0 < i);
			assert(orig_buff[i-1] == '\r');
#endif
			found_at = i + 1; /* indicate begining of rest of message */
			break;
		}
	
	if (! found_at) {
		BUG("invalid req retransmission buffer for UAC#0 of T@%p: missing "
				"CRLF.\n", trans);
		return NULL;
	}

	faked = (struct sip_msg *)pkg_malloc(sizeof(struct sip_msg) + 
			FIRST_LINE.len + orig_len - found_at);
	memset(faked, 0, sizeof(struct sip_msg));
	faked->buf = (char *)&faked[1];
	faked->len = FIRST_LINE.len + orig_len - found_at;
	
	/* TODO: To tag? */
	memcpy(faked->buf, FIRST_LINE.s, FIRST_LINE.len);
	memcpy(faked->buf + FIRST_LINE.len, orig_buff + found_at, 
			orig_len - found_at);

	/* AS might want some info about message's IP level, so we copy/make up
	 * some metadata here */
	/* TODO: is this enough/safe? */
	faked->rcv.src_su = trans->uac[0].request.dst.to;
	/* TODO: can actually 'send_sock' not be set?! */
	if (trans->uac[0].request.dst.send_sock) {
		faked->rcv.bind_address = trans->uac[0].request.dst.send_sock;
		faked->rcv.src_ip = trans->uac[0].request.dst.send_sock->address;
		faked->rcv.src_port = trans->uac[0].request.dst.send_sock->port_no;
		/* the 408 is local, generated and consumed locally. */
		faked->rcv.dst_ip = faked->rcv.src_ip;
		faked->rcv.dst_port = faked->rcv.src_port;
	}

	DEBUG("parsing faked 408 reply.\n");
	if (parse_msg(faked->buf,faked->len, faked)!=0){
		ERR("failed to parse faked 408.\n");
		goto error;
	}

	return faked;
error:
	free_faked_408(faked);
	return NULL;
}
#endif

inline static tmcbprm_t *tmcbprm_new(int as_id, unsigned int flags, 
		str method, str opaque)
{
	tmcbprm_t *cbp;
	char *pos;
	size_t len;

	/* aggregate strings with structure */
	len = sizeof(tmcbprm_t);
	len += method.len;
	len += opaque.len;

	if (! (cbp = (tmcbprm_t *)shm_malloc(len))) {
		ERR("out of shm memroy.\n");
		return NULL;
	}
	memset(cbp, 0, sizeof(tmcbprm_t));
	cbp->flags = flags;
	cbp->as_id = as_id;

	pos = (char *)cbp + sizeof(tmcbprm_t);
	cbp->method.s = pos;
	memcpy(pos, method.s, method.len);
	cbp->method.len = method.len;

	if (opaque.len) {
		pos += method.len;
		cbp->opaque.s = pos;
		memcpy(pos, opaque.s, opaque.len);
		cbp->opaque.len = opaque.len;
	}

	return cbp;
}

inline static void tmcbprm_free(tmcbprm_t *cbp)
{
	shm_free(cbp);
}

static void tm_callback(struct cell *trans, int type, struct tmcb_params *cbp)
{
	enum ASI_DSGT_IDS discr;
	tmcbprm_t *mycbp;
	struct sip_msg *sipmsg;
	int ret;

#ifdef EXTRA_DEBUG
	assert(trans != T_NULL_CELL);
	assert(trans != T_UNDEFINED);
	assert(cbp);
#endif

	if (! (mycbp = (tmcbprm_t *)*cbp->param)) {
		/* TODO: this is kind of dumb: the transaction moves to terminated
		 * only once => review if really needed this guard. */
		BUG("TM callback invoked after TMCB_DESTROY.\n");
#ifdef EXTRA_DEBUG
		abort();
#endif
		return;
	} else {
		DEBUG("running ASI TM CB; T@%p [%d:%d], type=%d; flags=0x%x.\n", trans,
				trans->hash_index, trans->label, type, mycbp->flags);
	}
	switch (type) {
		case TMCB_LOCAL_RESPONSE_OUT:
			if ((mycbp->flags & ASIREQ_PRV_FLG) == 0) {
				DEBUG("provisional for local transaction (@0x%p) skipped - "
						"no interest.\n", trans);
				return;
			}
			if (cbp->rpl == FAKED_REPLY) {
				WARN("provisional faked reply (T@0x%p) skipped.\n", trans);
				return;
			} else {
				sipmsg = cbp->rpl;
			}
			discr = ASI_DGST_ID_PRV;
			break;
		case TMCB_LOCAL_COMPLETED:
			if ((mycbp->flags & ASIREQ_FIN_FLG) == 0) {
				DEBUG("final for local transaction (@0x%p) skipped - "
						"no interest.\n", trans);
				return;
			}
			if (cbp->rpl == FAKED_REPLY) {
#ifdef ASI_FAKE_408
				if (! (sipmsg = fake_408(trans))) {
					ERR("failed to build a faked 408 SIP message.\n");
					return;
				}
#else
				sipmsg = cbp->rpl;
#endif
			} else {
				sipmsg = cbp->rpl;
			}
			discr = ASI_DGST_ID_FIN;
			break;
		case TMCB_DESTROY:
			tmcbprm_free(mycbp);
			DEBUG("ASI TM CB param freed for T@%p.\n", mycbp);
			*cbp->param = NULL; /* retransmission guard */
			return;
		default:
			BUG("unexpected TM callback (type:%d) handed by ASI (T@0x%p) - "
					"ignoring.\n", type, trans);
#ifdef EXTRA_DEBUG
			abort();
#endif
			return;
	}

	if ((ret = dispatch_tm_reply(mycbp->as_id, sipmsg, &mycbp->method,
			discr, tid2str(trans->hash_index, trans->label),
			mycbp->opaque.len ? &mycbp->opaque : NULL)) < 0)
		ERR("running TM reply callback failed with %d.\n", ret);

#if ASI_FAKE_408
	if (cbp->rpl == FAKED_REPLY)
		free_faked_408(sipmsg);
#endif
}

/**
 * Determine 1st route in route set.
 * @return: negative on error, 0 when empty route set, stricly positive
 * otherwise.
 */
static inline int get_1st_route(char *hdrs, int hdrs_len, rr_t *route_set)
{
	struct sip_msg faked_msg; /* msg contains only headers */
	int ret = -1;

	/* FIXME: this is ugly and done repeatedly => some API? */
	memset(&faked_msg, 0, sizeof(struct sip_msg));
	faked_msg.buf = hdrs;
	faked_msg.unparsed = hdrs;
	faked_msg.len = hdrs_len;

	if (parse_headers(&faked_msg, HDR_ROUTE_T, 0) < 0) {
		ERR("failed to parse headers as faked SIP message.\n");
		return -1;
	}
	if (! faked_msg.route) {
		DEBUG("no Route in headers.\n");
		ret = 0;
		goto end;
	}
	if (parse_rr(faked_msg.route) < 0) {
		ERR("failed to parse Route header.\n");
		goto end;
	}
	*route_set = *(rr_t *)faked_msg.route->parsed;
	ret = 1;
end:
	free_sip_msg(&faked_msg);
	return ret;
}

static const char* request_doc[] = { "Initiate a UAC transaction.", NULL };

/**
 * Expect RPC parameters:
 * 	AS ID (integer)
 * 	flags (integer)
 * 	method (string) : SIP method name
 * 	RURI (string) : SIP URI
 * 	From header value (string) : SIP From; must contain the tag
 * 	To header value (string) : SIP To
 * 	cseq (u integer) : numeric part only
 * 	Call-ID header value (string) : SIP Call-ID
 * 	next hop (string) : SIP URI (may be empty/NULL)
 * 	headers (string) : aditional fully built headers (maybe be empty/NULL)
 * 	body (string) : SIP message body (may be empty/NULL)
 * 	opaque (string) : to be returned in replies (may be empty/NULL)
 * 	TODO:
 * 	? headers (<name_string:values_string[]>[] or <name_string:value_string>[])
 *
 * Return:
 * 	code (integer) [2xx for sucees]
 * 	SER's opaque or failure reason (string)
 */
static void rpc_uac_request(rpc_t* rpc, void* ctx)
{
	const static str invite = STR_STATIC_INIT("INVITE");
	str method, ruri, nhop, to, from, callid, headers, body, opaque;
	int as_id, flags, scseq;
	uint32_t cseq;
	dlg_t dialog;
	char hdrs_buff[BUF_SIZE];
	str hdrs = {hdrs_buff, sizeof(hdrs_buff)};
	str *headers_p, *body_p;
	rr_t route_set;
	uac_req_t cb_req;
	int cb_flags;
	tmcbprm_t *cb_param;
	int uacret;
	unsigned int h_index, h_label;
	char err_buff[MAX_REASON_LEN], *err_p;
	int sip_err, have_hdrs;


#define STRIP_0TERM_C(_str, may0)	STRIP_0TERM(_str,may0,"asi.uac.request()")

	if (rpc->scan(ctx, "ddSSSSdSSSSS", &as_id, &flags,
			&method, &ruri, &from, &to, 
			&scseq,
			&callid, &nhop, &headers, &body, &opaque) != 12) {
		rpc->fault(ctx, 493, "Failed to extract request parameters.");
		DEBUG("faulty asi.uac.request() RPC: failed to read params.\n");
		return;
	}
	
	/* sanity checks */

	if ((as_id < 0) || (appsrvs_count() <= as_id)) {
		rpc->add(ctx, "ds", 488, "Invalid AS ID.");
		DEBUG("faulty asi.uac.request() RPC: invalid AS ID: %d.\n", as_id);
		return;
	}

	STRIP_0TERM_C(method, false);
	STRIP_0TERM_C(ruri, false);
	STRIP_0TERM_C(from, false);
	STRIP_0TERM_C(to, false);
	STRIP_0TERM_C(callid, false);

	/* SIP's CSeq is an unsigned 32bits integer, while BINRPC streams signed */
	cseq = (uint32_t)scseq;
	/* ACK flag only makes sens for INVITE */
	if (flags & ASIREQ_ACK_FLG) {
		if (! ((method.len == invite.len) && 
				(strncmp(method.s, invite.s, invite.len) == 0))) {
			rpc->add(ctx, "ds", 606, "ACK flg can only be used with INVITEs.");
			DEBUG("faulty asi.uac.request() RPC (from AS#%d): ACK flag used "
					"with `%.*s' method.\n", as_id, STR_FMT(&method));
			return;
		}
		if ((flags & ASIREQ_FIN_FLG) == 0) {
			/* enforce AS to set it, so that it is aware that it has to handle
			 * the final reply, also. */
			rpc->add(ctx, "ds", 606, "The ACK flag must be accompanied by the "
					"FINAL flag.");
			DEBUG("faulty asi.uac.request() RPC (from AS#%d): ACK without "
					"FINAL.\n", as_id);
			return;
		}
	}

	/* seems sane */

	STRIP_0TERM_C(nhop, true);
	/* headers not trimmed */
	STRIP_0TERM_C(body, true);
	STRIP_0TERM_C(opaque, true);
	
	DEBUG("new asi.uac.request() RPC call "
			"(AS#%d;flags:%d;nhop:%.*s;opaque:%.*s):"
			" %.*s %.*s "
			"<%.*s,%.*s,%u,%.*s>"
			" [%.*s] {%.*s}.\n", 
			as_id, flags, STR_FMT(&nhop), STR_FMT(&opaque),
			STR_FMT(&method), STR_FMT(&ruri), 
			STR_FMT(&from), STR_FMT(&to), cseq, STR_FMT(&callid),
			STR_FMT(&headers), STR_FMT(&body));

	/* TODO: the dialog is built manaually (no API used) b/c it's only used 
	 * for retransmission buffer building => add some API for the case 
	 * The new_dlg_uac() nearly fits the purpose BUT:
	 * 	- it allocates (<> stack variable enough)
	 * 	- it expects From tag (the From value as received from AS contains it
	 * 	and extracting it is not needed: it would only be used to print it)
	 * */
	memset(&dialog, 0, sizeof(dlg_t));
	dialog.loc_seq = (dlg_seq_t){cseq, 1};
	dialog.loc_uri = from;
	dialog.rem_uri = to;
	dialog.id.call_id = callid;
	dialog.rem_target = ruri;

	have_hdrs = /*0-term*/1 < headers.len;
	/**
	 * If AS wants to force a next hop, sent the message there.
	 * Otherwise, evaluate the route set and take 1st entry as next hop.
	 *
	 * Note: how is the target where to send the request computed:
	 * dialog.next_hop holds this info; d.next_hop is evaluated to:
	 * - if have route set, first R in set
	 * - else d.dst_uri, if it is set
	 * - else d.remote_target, if it is set
	 * - else d.rem_uri
	 */
	if (nhop.len) {
		dialog.dst_uri = nhop;
	} else if (have_hdrs) {
		if ((sip_err = get_1st_route(headers.s, headers.len - /*0-term*/1, 
				&route_set)) < 0) {
			rpc->fault(ctx, 493, "Failed to check 1st route in headers.");
			DEBUG("faulty asi.uac.request() RPC: failed to check 1st route "
					"in headers.\n");
			return;
		} else if (sip_err) {
			DEBUG("1st route in set: `%.*s'\n", 
					STR_FMT(&route_set.nameaddr.uri));
			dialog.dst_uri = route_set.nameaddr.uri;
		}
	}
	
	if (have_hdrs) {
		str dst_uri = dialog.dst_uri.len ? dialog.dst_uri : dialog.rem_target;
		if (fix_local_uris(&dst_uri, headers, &hdrs) < 0) {
			rpc->fault(ctx, 500, "Failed to fix local URIs.\n");
			return;
		}
		headers_p = &hdrs;
	} else {
		headers_p = NULL;
	}
	body_p = body.len ? &body : NULL;

	cb_flags = TMCB_DESTROY;
	if (flags) {
		if (! (cb_param = (void *)tmcbprm_new(as_id, flags, method, opaque))) {
			ERR("RPC dropped due to callback param aggregating failure.\n");
			return;
		}
		if (flags & ASIREQ_FIN_FLG)
			cb_flags |= TMCB_LOCAL_COMPLETED;
		if (flags & ASIREQ_PRV_FLG)
			cb_flags |= TMCB_LOCAL_RESPONSE_OUT;
		if (flags & ASIREQ_ACK_FLG)
			cb_flags |= TMCB_E2EACK_IN;
	} else {
		cb_param = NULL;
	}
	set_uac_req(&cb_req, &method, headers_p, body_p, &dialog, 
			cb_flags, tm_callback, cb_param);

	/* have everything set: start T & dispatch away */

	/* TODO: collect error from SEND_BUFFER & retr arming; OR call TERMINATED
	 * callback; OTHERWISE the shm param remains leaking */
	if ((uacret = tmb.t_uac_with_ids(&cb_req, &h_index, &h_label)) < 0) {
		if (cb_param)
			tmcbprm_free(cb_param);
		if (err2reason_phrase(uacret, &sip_err, err_buff, sizeof(err_buff), 
				"ASI/UAC") <= 0) {
			sip_err = 500;
			err_p = "Failed to create ASI/UAC";
		} else {
			err_p = err_buff;
		}
		rpc->fault(ctx, sip_err, err_p);
	} else {
		if (flags & ASIREQ_RUN_ORR) {
			if (onreply_rt_idx < 0) {
				WARN("reply route flag set, but no route specified as "
						"module parameter (\"onreply_route\").\n");
			} else {
				struct cell *trans;
				/* t_gett() might not return 'correctlly' */
				if (tmb.t_lookup_ident(&trans, h_index, h_label) <= 0)
					ERR("failed to find T by TID [%u:%u] -> ORR not set.\n", 
							h_index, h_label);
				else
					trans->on_reply = onreply_rt_idx;
			}
		}
		rpc->add(ctx, "dS", 200, tid2str(h_index, h_label));
		DEBUG("successfully created new ASI/UAC (%.*s to %.*s (%.*s)).\n",
					STR_FMT(&method), STR_FMT(&nhop), STR_FMT(&ruri));
	}
#undef STRIP_0TERM_C
}

inline static struct cell *tmhtid2trans(const str *tmhtid)
{
	str idx_str, label_str;
	char *column;
	unsigned int h_index, h_label;
	struct cell *trans;

	if (! (column = q_memchr(tmhtid->s, ':', tmhtid->len))) {
		DEBUG("no column found in TMHTID `%.*s'.\n", STR_FMT(tmhtid));
		return NULL;
	}
	
	idx_str.s = tmhtid->s;
	idx_str.len = column - idx_str.s;
	label_str.s = column + 1;
	label_str.len = tmhtid->len - /*0-term*/1 - idx_str.len - /*`:'*/1;
	if ((str2int(&idx_str, &h_index) < 0) || 
			(str2int(&label_str, &h_label) < 0)) {
		DEBUG("failed to convert identifiers from TMHTID `%.*s' into unsigned"
				" integers.\n", STR_FMT(tmhtid));
		return NULL;
	}

	if (tmb.t_lookup_ident(&trans, h_index, h_label) < 0) {
		DEBUG("no transaction found for identifiers %u:%u.\n", h_index, h_label);
		return NULL;
	}
	
	return trans;
}


static const char* cancel_doc[] = {"Cancel an ongoing UAC transaction.", NULL};

/**
 * Expected RPC parameters:
 * 	TM HT ID (string)
 *
 * Return:
 * 	code (integer) [2xx for sucess]
 * 	reason (string)

 */
static void rpc_uac_cancel(rpc_t* rpc, void* ctx)
{
	str tmhtid;
	struct cell *trans;

	if (rpc->scan(ctx, "S", &tmhtid) != 1) {
		rpc->fault(ctx, 493, "Failed to extract request parameters.");
		DEBUG("faulty asi.uac.cancel() RPC: failed to read params.\n");
		return;
	}
	DEBUG("asi.uac.cancel(TMHTID: `%.*s').\n", STR_FMT(&tmhtid));

	if (! (trans = tmhtid2trans(&tmhtid))) {
		rpc->add(ctx, "ds", 404, "No transaction found for identifiers.");
		DEBUG("No transaction found for identifiers `%.*s'.\n",STR_FMT(&tmhtid));
		return;
	}

	/* TODO: if SER rcvd no reply for REQ, the final CB will be fired only
	 * after initial T expires (<>as fast as canceling) */

	/* TODO: cancel with F_CANCEL_B_FAKE_REPLY crashes SER if no 
	 * reply received */
	if (tmb.cancel_uac(trans, 0) < 0) {
		rpc->fault(ctx, 500, "Internal Server Error");
		BUG("cancel_uac returned negative.\n");
		/* TODO: can this return negative?
		 * FIXME: if yes, unref the T.
		 */
	} else {
		rpc->add(ctx, "ds", 200, "Transaction successfully canceled.\n");
		DEBUG("Successfully canceled transaction.\n");
	}
}


static const char* ack_doc[] = { "ACK a received reply.", NULL };

/**
 * Expected RPC parameters:
 * 	TM HT ID (string)
 * 	headers (string)
 * 	body (string)
 *
 * Return:
 * 	code (integer) [2xx for sucess]
 * 	reason (string)
 */
static void rpc_uac_ack(rpc_t* rpc, void* ctx)
{
	str tmhtid, body, hdrs;
	struct cell *trans;
	int ret;

	if (rpc->scan(ctx, "SSS", &tmhtid, &hdrs, &body) != 3) {
		rpc->fault(ctx, 493, "Failed to extract requst parameters.");
		DEBUG("faulty asi.uac.ack() RPC: failed to read params.\n");
		return;
	}

	if (! (trans = tmhtid2trans(&tmhtid))) {
		rpc->add(ctx, "ds", 404, "No transaction found for identifiers.");
		DEBUG("No transaction found for identifiers `%.*s'.\n",STR_FMT(&tmhtid));
		return;
	}

	STRIP_0TERM(hdrs, true, "asi.uac.ack()");
	STRIP_0TERM(body, true, "asi.uac.ack()");

#ifndef WITH_AS_SUPPORT
#error "no AS support enabled (WITH_AS_SUPPORT missing)"
#endif
	if ((ret = tmb.ack_local_uac(trans, &hdrs, &body)) < 0) {
		if (ret == -2)
			rpc->add(ctx, "ds", 400, "Invalid call (illegal state)");
		else
			rpc->add(ctx, "ds", 500, "Internal Server Error");
		ERR("ack_local_uac failed (with %d).\n", ret);
	} else {
		rpc->add(ctx, "ds", 200, "Transaction successfully ACKed.\n");
		DEBUG("Successfully ACKed transaction.\n");
	}
}


static const char* reply_doc[] = { "Reply to a received request.", NULL };

/**
 * Expect RPC params:
 * 	TM HT ID (string)
 * 	code (int)
 * 	reason (string)
 * 	totag (string)
 * 	headers (string)
 * 	body (string)
 *
 * Return:
 * 	code (integer) [2xx for sucess]
 * 	reason (string)
 */
static void rpc_uas_reply(rpc_t* rpc, void* ctx)
{
	int code, ret;
	str tmhtid, reason, totag, headers, body;
	struct cell *trans;

	if (rpc->scan(ctx, "SdSSSS", &tmhtid, &code, &reason, &totag, &headers, 
			&body) != 6) {
		rpc->fault(ctx, 493, "Failed to extract request parameters.");
		DEBUG("faulty asi.uas.reply(): failed to read params.\n");
		return;
	}
#ifdef EXTRA_DEBUG
	DEBUG("asi.uas.reply(tmhtid:%.*s,code:%d,reason:%.*s,totag:%.*s,"
			"headers:%.*s,body:%.*s) invoked.\n", STR_FMT(&tmhtid), code, 
			STR_FMT(&reason), STR_FMT(&totag), STR_FMT(&headers), 
			STR_FMT(&body));
#endif

	/* sanity checks */
	if ((code < 100) || (700 <= code)) {
		rpc->add(ctx, "ds", 606, "Illegal SIP code.");
		DEBUG("faulty asi.uas.reply() RPC: illegal SIP code %d.\n", code);
		return;
	}

	if (! (trans = tmhtid2trans(&tmhtid))) {
		rpc->add(ctx, "ds", 404, "No transaction found for identifiers.");
		DEBUG("No transaction found for identifiers `%.*s'.\n",STR_FMT(&tmhtid));
		return;
	}
	/* looks sane */
	
	/* TODO: check if makes sens to fix any local URIs in outgoing replies */

	ret = tmb.t_reply_with_body(trans, code, reason.s, body.s, headers.s,
			totag.s);
	if (rpc->add(ctx, "ds", (ret < 0) ? 500 : 200, "Replied processed.") < 0) {
		ERR("failed to build RPC reply.\n");
	} else {
		DEBUG("successfully replied.\n");
	}
}


#ifdef ASI_WITH_RESYNC
static const char* resync_doc[] = { "Set resync flag forcing AS reconnecting", 
		NULL };

static void rpc_resync(rpc_t *rpc, void *ctx)
{
	str as_uri;
	int ret, as_id;
	int serial, proto;
	char *as_id_str;
	int as_id_len;

	if (rpc->scan(ctx, "dSd", &proto, &as_uri, &serial) != 3) {
		rpc->fault(ctx, 493, "Failed to extract call parameters "
				"(proto, URI, serial).\n");
		DEBUG("faulty as.resync(): failed to read params.\n");
		return;
	}
	DEBUG("Requesting connection renewing with AS@%s, serial:%d; proto:%d.\n", 
			as_uri.s, serial, proto);
	if (proto != ASI_VERSION) {
		ERR("unsupported SASI version %d.\n", proto);
		ret = 505;
		rpc->add(ctx, "ds", ret, "SASI version not supported.");
	} else {
		switch ((ret = appsrv_set_resync(as_uri.s, serial, &as_id))) {
			case EBADMSG:
				ret = 493;
				rpc->add(ctx, "ds", ret, "failed to parse URI.");
				break;
			case EADDRNOTAVAIL:
				ret = 404;
				rpc->add(ctx, "ds", ret, "no AS for URI.");
				break;
			case EINPROGRESS:
			case 0:
				as_id_str = int2str(as_id, &as_id_len);
#ifdef EXTRA_DEBUG
				/* INT2STR_MAX_LEN must be large enough to hold a 0-term */
				assert(as_id_len < INT2STR_MAX_LEN); 
#endif
				as_id_str[as_id_len] = 0;
				/* 202 can occur when multiple AS threads push a resync */
				ret = 200 + (ret ? 2 : 0);
				rpc->add(ctx, "ds", ret, as_id_str);
				break;
			default:
				BUG("unexpected ret val %d.\n", ret);
#ifdef EXTRA_DEBUG
				abort();
#endif
				ret = 500;
				rpc->add(ctx, "ds", ret, "server interal error.");
		}
	}
	DEBUG("as.resync(%s, %d) -> %d.\n", as_uri.s, serial, ret);
}
#endif /* ASI_WITH_RESYNC */

rpc_export_t mod_rpc[] = {
	{"asi.uac.request",	rpc_uac_request,	request_doc,	/*flags*/0},
	{"asi.uac.cancel",	rpc_uac_cancel	,	cancel_doc,		/*flags*/0},
	{"asi.uac.ack",		rpc_uac_ack	,		ack_doc,		/*flags*/0},
	{"asi.uas.reply",	rpc_uas_reply,		reply_doc,		/*flags*/0},
#ifdef ASI_WITH_RESYNC
	{"asi.resync",		rpc_resync,			resync_doc,		/*flags*/0},
#endif
	{0, 0, 0, 0}
};

