Răsfoiți Sursa

- initial import of 'asi' module -- an extension to allow sip-router (SR) work
as an Application Server Interface front server.
- documentation will follow; basically, it allows an AS register itself with
SR, process through whitch the AS tells SR:
* what SIP methods it supports;
* what parts of a SIP message it wants to receive.
SR works as:
* networking stack (for SIP)
* SIP parser/assembler (UAS/UAC)
* transactional layer

bpi 15 ani în urmă
părinte
comite
98f09f949c

+ 24 - 0
modules/asi/Makefile

@@ -0,0 +1,24 @@
+# $Id$
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+
+NAME=asi.so
+
+DEFS+=-DSER_MOD_INTERFACE
+
+DEFS+=-DXLL_NULL_FIX
+DEFS+=-DASI_WITH_LOCDGRAM
+DEFS+=-DASI_WITH_RESYNC
+DEFS+=-DASI_FAKE_408
+DEFS+=-DORIG_TID_4CANCEL
+
+DEFS+=-I$(SERLIBPATH)/binrpc2/
+DEFS+=-I../../
+
+
+SER_LIBS+=$(SERLIBPATH)/binrpc2/binrpc2
+
+include ../../Makefile.modules

+ 1176 - 0
modules/asi/appsrv.c

@@ -0,0 +1,1176 @@
+/*
+ * Copyright (c) 2010 IPTEGO GmbH.
+ * All rights reserved.
+ *
+ * This file is part of sip-router, a free SIP server.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ * 
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY IPTEGO GmbH ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL IPTEGO GmbH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * Author: Bogdan Pintea.
+ */
+
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h> /* PF_, SOCK_ */
+#ifdef ASI_WITH_LOCDGRAM
+#	include <stdlib.h> /* dirname */
+#	include <stdio.h> /* tempnam */
+#	include <sys/stat.h> /* chmod, chown */
+#	include <sys/un.h> /* UNIX_PATH_MAX */
+#	include <unistd.h> /* unlink */
+#endif
+#ifdef EXTRA_DEBUG
+#	include <assert.h>
+#endif /* EXTRA_DEBUG */
+#include <binrpc.h>
+#include "mem/mem.h"
+
+#include "strutil.h"
+#include "tid.h"
+#include "binds.h"
+#include "appsrv.h"
+
+
+#define MAX_SIP_METHODS	32
+
+#ifdef ASI_WITH_LOCDGRAM
+#include <ut.h>
+#define DEFAULT_LOCDGRAM_TMPL	"/tmp/ser.asi.usock.XXXXXX"
+
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX sizeof(((struct sockaddr_un *)0)->sun_path)
+#endif /*UNIX_PATH_MAX*/
+#endif /*ASI_WITH_LOCDGRAM*/
+
+
+#define OVER_STREAM(_as_)	(BRPC_ADDR_TYPE(&(_as_)->addr) == SOCK_STREAM)
+#define OVER_LOCDGRAM(_as_)	\
+		((BRPC_ADDR_DOMAIN(&(_as_)->addr) == PF_LOCAL) \
+		&& (BRPC_ADDR_TYPE(&(_as_)->addr) == SOCK_DGRAM))
+
+
+const BRPC_STR_STATIC_INIT(METH_SYNC, "asi.sync");
+const BRPC_STR_STATIC_INIT(METH_METHODS, "methods");
+const BRPC_STR_STATIC_INIT(METH_DIGESTS, "digests");
+
+int ct_timeout = -1;
+int tx_timeout = -1;
+int rx_timeout = -1;
+
+static as_t **app_srvs = NULL; /* list of ASes */
+static size_t as_cnt = 0; /* size of app_srvs array */
+/* cookie counter for each BINRPC message */
+static brpc_id_t brpc_callid;
+
+#define GET_AS_CNT			as_cnt
+#define INC_AS_CNT			(++ as_cnt)
+#define AS_RS_SERIAL(_as_)	(*(_as_)->resync_serial)
+
+#ifdef ASI_WITH_LOCDGRAM
+/* create temporary files using this prefix (dir+file_prefix) */
+char *usock_template = DEFAULT_LOCDGRAM_TMPL;
+int usock_mode = 0;
+int usock_uid = (uid_t)-1;
+int usock_gid = (gid_t)-1;
+#endif
+
+#ifdef ASI_WITH_RESYNC
+/* current process's rank */
+static int my_rank;
+/* proc Bit Mask Block count: how many bytes needed, 1 bit/process */
+static size_t *proc_bmb_cnt;
+#endif /* ASI_WITH_RESYNC */
+
+/* do we wait read what AS responds with to a digest? */
+int expect_reply = 0;
+#ifdef EXTRA_DEBUG
+/* make sure t_get() is called after t_get_trans_ident(), to ensure the right
+ * T is hooked up. */
+static int t_looked_up = 0;
+#endif
+
+
+static inline int dispatch2as(struct sip_msg *sipmsg, as_t *as);
+
+
+#ifdef ASI_WITH_RESYNC
+int setup_bmb(int max_procs)
+{
+	int i;
+
+	if (! (proc_bmb_cnt = (size_t *)shm_malloc(sizeof(size_t)))) {
+		ERR("out of shm mem (proc_bmb_cnt).\n");
+		return -1;
+	}
+
+	for (i = 1; i * 8 < max_procs; i ++)
+		;
+	*proc_bmb_cnt = i;
+	DEBUG("resync bit mask byte size: %zd.\n", *proc_bmb_cnt);
+
+	if (ASI_BMB_MAX * 8 < *proc_bmb_cnt) {
+		BUG("registered processes number (%d) larger than supported by"
+				" ASI (%d): increase ASI_MAX_PROCS and recompile.\n", 
+				max_procs, ASI_BMB_MAX * 8);
+		return -1;
+	}
+	return 0;
+}
+
+static void free_bmb(void)
+{
+	if (proc_bmb_cnt) {
+		shm_free(proc_bmb_cnt);
+		proc_bmb_cnt = NULL;
+	}
+}
+#endif
+
+void init_appsrv_proc(int rank, brpc_id_t callid)
+{
+#ifdef ASI_WITH_RESYNC
+	my_rank = rank;
+#endif /* ASI_WITH_RESYNC */
+	brpc_callid = callid;
+}
+
+size_t appsrvs_count(void)
+{
+	return GET_AS_CNT;
+}
+
+static void free_appsrv(as_t *as)
+{
+	if (! as)
+		return;
+#ifdef ASI_WITH_RESYNC
+	if (as->resync)
+		shm_free(as->resync);
+	if (as->resync_mutex) {
+		lock_destroy(as->resync_mutex);
+		shm_free((void *)as->resync_mutex);
+	}
+	if (as->resync_serial)
+		shm_free(as->resync_serial);
+#endif
+	pkg_free(as);
+}
+
+int new_appsrv(char *name, char *uri)
+{
+	as_t *as, **as_tmp;
+	brpc_addr_t *addr;
+	str name_str;
+	int i;
+
+	name_str.s = name;
+	name_str.len = strlen(name);
+
+	if (! (addr = brpc_parse_uri(uri))) {
+		ERR("failed to parse BINRPC URI `%s': %s [%d].\n", uri, 
+				brpc_strerror(), brpc_errno);
+		return -1;
+	}
+	/* check against name collision or address reuse */
+	for (i = 0; i < GET_AS_CNT; i ++) {
+		if (STR_EQ(name_str, app_srvs[i]->name)) {
+			ERR("AS name `%s' alread in use.\n", name);
+			return -1;
+		}
+		if (brpc_addr_eq(addr, &app_srvs[i]->addr)) {
+			ERR("ASes '%s' and '%s' share same '%s' address.\n", 
+					app_srvs[i]->name.s, name, uri);
+			return -1;
+		}
+	}
+
+	if (! (as = (as_t *)pkg_malloc(sizeof(as_t)))) {
+		ERR("out of pkg mem (as).\n");
+		return -1;
+	}
+	memset(as, 0, sizeof(sizeof(as_t)));
+
+#ifdef ASI_WITH_RESYNC
+	if (! ((as->resync = (uint8_t *)shm_malloc(sizeof(uint8_t) * ASI_BMB_MAX))
+			&& (as->resync_mutex = 
+					(gen_lock_t *)shm_malloc(sizeof(gen_lock_t)))
+			&& (as->resync_serial = (int *)shm_malloc(sizeof(int))) ) ) {
+		ERR("out of shm mem (resyncs).\n");
+		goto error;
+	}
+	memset(as->resync, 0, sizeof(uint8_t) * ASI_BMB_MAX);
+	if (! lock_init(as->resync_mutex)) {
+		ERR("failed to initialize resync_mutex: %s (%d).\n", strerror(errno), 
+				errno);
+		goto error;
+	}
+	AS_RS_SERIAL(as) = -1;
+#endif
+
+	as->name = name_str;
+	as->id = GET_AS_CNT;
+	as->addr = *addr;
+	as->sockfd = -1;
+	as->serial = -1;
+
+	if (! (as_tmp = (as_t **)pkg_realloc(app_srvs, (GET_AS_CNT + 1) * 
+			sizeof(as_t *)))) {
+		ERR("out of pkg memory (realloc AS array).\n");
+		goto error;
+	} else {
+		app_srvs = as_tmp;
+	}
+	app_srvs[GET_AS_CNT] = as;
+	INC_AS_CNT;
+
+	return GET_AS_CNT;
+error:
+	free_appsrv(as);
+	return -1;
+}
+
+void free_appsrvs(void)
+{
+	int i;
+	for (i = 0; i < GET_AS_CNT; i ++)
+		free_appsrv(app_srvs[i]);
+	if (app_srvs)
+		pkg_free(app_srvs);
+#ifdef ASI_WITH_RESYNC
+	free_bmb();
+#endif
+}
+
+as_t *as4id(unsigned int id)
+{
+	if ((id < 0) || (GET_AS_CNT <= id)) {
+		ERR("AS index (%u) out of bounds (0:%zd).\n", id, GET_AS_CNT);
+#ifdef EXTRA_DEBUG
+		abort();
+#endif
+		return NULL;
+	}
+	return app_srvs[id];
+}
+
+
+#ifdef ASI_WITH_LOCDGRAM
+/**
+ * Set modes&permissions for a unix socket file.
+ */
+static int usock_stat(char *path)
+{
+	if (usock_mode) {
+		if (chmod(path, usock_mode)<0){
+			ERR("failed to change the permissions for '%s' to %04o: %s[%d]\n",
+					path, usock_mode, strerror(errno), errno);
+			return -1;
+		}
+	}
+	/* XXX: still needed? */
+	if ((0 <= usock_uid) || (0 <= usock_gid)) {
+		if (chown(path, usock_uid, usock_gid)<0){
+			ERR("failed to change the owner/group for '%s' to %d.%d: %s[%d]\n",
+					path, usock_uid, usock_gid, strerror(errno), errno);
+			return -1;
+		}
+	}
+	return 0;
+}
+/**
+ * Initialize a local domain unix socket address to bind to for AS'es response.
+ */
+static int init_local_dgram(as_t *as)
+{
+	size_t tmp_len;
+	static char tmppath[UNIX_PATH_MAX];
+	int tmpfd;
+
+	tmp_len = strlen(usock_template) + /*0-term*/1;
+	if (sizeof(tmppath) < tmp_len) {
+		ERR("local unix socket template too long: %zd; maximum "
+				"allowed: %zd.\n", tmp_len, sizeof(tmppath));
+		return -1;
+	}
+	memcpy(tmppath, usock_template, tmp_len);
+	if ((tmpfd = mkstemp(tmppath)) < 0) {
+		ERR("failed to create temporary file for local unix "
+				"socket: %s [%d].\n", strerror(errno), errno);
+		return -1;
+	}
+	if (close(tmpfd) < 0)
+		WARN("failed to close temporary file descriptor: %s [%d].\n",
+				strerror(errno), errno);
+
+	as->listen = as->addr; /* copy domain & type */
+	memcpy(BRPC_ADDR_UN(&as->listen)->sun_path, tmppath, tmp_len);
+	BRPC_ADDR_LEN(&as->listen) = SUN_LEN(BRPC_ADDR_UN(&as->listen));
+	DEBUG("init'ed local dgram socket `%.*s'.\n", BRPC_ADDR_LEN(&as->listen),
+			BRPC_ADDR_UN(&as->listen)->sun_path);
+
+	return 0;
+}
+
+static void del_local_dgram(as_t *as)
+{
+	if (! OVER_LOCDGRAM(as))
+		return;
+	if (! BRPC_ADDR_LEN(&as->listen))
+		return;
+	if (unlink(BRPC_ADDR_UN(&as->listen)->sun_path) < 0)
+		WARN("failed to remove LOCAL unix socket `%s': %s [%d].\n", 
+				BRPC_ADDR_UN(&as->listen)->sun_path, strerror(errno), 
+				errno);
+	BRPC_ADDR_LEN(&as->listen) = 0;
+}
+#endif
+
+
+void disconnect_appsrv(as_t *as)
+{
+	if (0 <= as->sockfd) {
+		if (close(as->sockfd) < 0) {
+			WARN("while breaking connection with AS %.*s: %s [%d].\n", 
+					STR_FMT(&as->name), strerror(errno), errno);
+		}
+		as->sockfd = -1;
+	}
+#ifdef ASI_WITH_LOCDGRAM
+	del_local_dgram(as);
+#endif
+	INFO("connection with AS %.*s closed.\n", STR_FMT(&as->name));
+}
+
+static int connect_appsrv(as_t *as)
+{
+	bool named;
+	brpc_addr_t *addr;
+	
+	DEBUG("connecting to AS '%.*s' @ %s.\n", STR_FMT(&as->name), 
+			brpc_print_addr(&as->addr));
+	
+	if (0 <= as->sockfd) {
+		BUG("AS connection socket not closed (%d).\n", as->sockfd);
+#ifdef EXTRA_DEBUG
+		abort();
+#else
+		close(as->sockfd);
+#endif
+	}
+
+#ifdef ASI_WITH_LOCDGRAM
+	if (OVER_LOCDGRAM(as)) {
+		/* create a (unique) socket (file) name. */
+		if (init_local_dgram(as) < 0) {
+			ERR("failed to bind local unix socket.\n");
+			return -1;
+		}
+		addr = &as->listen;
+		named = true;
+	}
+	else
+#endif
+	{
+		addr = &as->addr;
+		named = false;
+	}
+
+	if ((as->sockfd = brpc_socket(addr, /*block.*/false, named)) < 0) {
+		ERR("failed to get new socket: %s [%d].\n", brpc_strerror(), 
+				brpc_errno);
+	}
+#ifdef ASI_WITH_LOCDGRAM
+	if (OVER_LOCDGRAM(as)) {
+		/* change mode&ownership */
+		if (usock_stat(BRPC_ADDR_UN(addr)->sun_path) < 0) {
+			ERR("failed to updated local unix socket stats: %s [%d].\n",
+					strerror(errno), errno);
+			goto discon;
+		}
+	}
+#endif
+	/* init connection */
+	if (! brpc_connect(&as->addr, &as->sockfd, ct_timeout)) {
+		ERR("failed to connect to AS: %s [%d].\n", brpc_strerror(),
+				brpc_errno);
+		goto discon;
+	}
+
+	return 0;
+discon:
+	disconnect_appsrv(as);
+	return -1;
+}
+
+int is_connected(as_t *as)
+{
+#ifdef ASI_WITH_RESYNC
+	int update;
+
+	if (as->resync[my_rank >> 3] & (1 << (my_rank & 0x07))) {
+		DEBUG("resync flag found set for AS '%.*s'.\n", STR_FMT(&as->name));
+		/* prevent racing on resync_serial (update resync [appsrv_set_resync],
+		 * check resync [is_connected], check resync_serial[is_connected],
+		 * clear resync [is_connected], update resync_serial 
+		 * [appsrv_set_resync]) */
+		lock_get(as->resync_mutex);
+		if (as->serial < AS_RS_SERIAL(as)) {
+			DEBUG("SIP worker #%d must resync AS '%.*s' connection.\n", 
+					my_rank, STR_FMT(&as->name));
+			update = 1;
+		} else {
+			update = 0;
+		}
+		as->resync[my_rank >> 3] &= ~ (1 << (my_rank & 0x7));
+		lock_release(as->resync_mutex);
+		if (update) {
+			disconnect_appsrv(as);
+			return handshake_appsrv(as);
+		}
+	}
+#endif
+
+	/* TODO: for streams:
+	 * - implemente a try-reconnect-every-SOMETHING [nr_msgs|time]  
+	 * - try select+read to see if RST (?)
+	 */
+
+	if (0 <= as->sockfd) {
+		DEBUG("AS '%.*s' is connected.\n", STR_FMT(&as->name));
+		return 1;
+	} else {
+		DEBUG("AS '%.*s' not yet connected.\n", STR_FMT(&as->name));
+	}
+	return handshake_appsrv(as);
+}
+
+
+
+static inline int rpc_faulted(brpc_t *rpl)
+{
+	brpc_int_t *code;
+	brpc_str_t *reason;
+
+	if (! brpc_is_fault(rpl))
+		return 0;
+	
+	if (! brpc_fault_status(rpl, &code, &reason)) {
+		ERR("failed to extract RPC failure reason: %s [%d].\n", 
+				brpc_strerror(), brpc_errno);
+		goto end;
+	}
+
+	ERR("RPC call ID#%d faulted.\n", brpc_id(rpl));
+	if (code)
+		ERR("RPC ID#%d failure code: %d.\n", brpc_id(rpl), *code);
+	if (reason)
+		ERR("RPC ID#%d failure reason: %.*s.\n", brpc_id(rpl), 
+				BRPC_STR_FMT(reason));
+end:
+	return 1;
+}
+
+static int query_methods(as_t *as)
+{
+	brpc_t *req = NULL, *rpl = NULL;
+	char meth_desc[MAX_SIP_METHODS + 1];
+	brpc_str_t *meth[MAX_SIP_METHODS + 1];
+	brpc_addr_t from;
+	size_t cnt;
+	int ret = -1;
+
+	DEBUG("quering for ASI version number.\n");
+	
+	if (! (	(req = brpc_req(METH_METHODS, brpc_callid ++)) &&
+			brpc_send(as->sockfd, req, tx_timeout))) {
+		ERR("failed to send request: %s [%d].\n", brpc_strerror(), brpc_errno);
+		goto end;
+	}
+	from = as->addr;
+	if (! (	(rpl = brpc_recvfrom(as->sockfd, &from, rx_timeout)) &&
+			(! rpc_faulted(rpl)))) {
+		ERR("failed to get methods: %s [%d].\n", brpc_strerror(), brpc_errno);
+		goto end;
+	}
+
+	cnt = brpc_val_cnt(rpl);
+	if (MAX_SIP_METHODS + 1 <= cnt) {
+		ERR("not enough space to get SIP methods (need %zd, have %d).\n",
+				cnt + 2, MAX_SIP_METHODS + 1);
+		goto end;
+	} else if (cnt == 0) {
+		WARN("AS '%.*s' supports no SIP method.\n", STR_FMT(&as->name));
+		goto end;
+	} else {
+		DEBUG("# of supported SIP methods: %zd.\n", cnt);
+	}
+	memset(meth_desc + /*first: `!'*/1, 's', cnt);
+	meth_desc[0] = '!';
+	meth_desc[1 + cnt] = 0;
+
+	if (! brpc_dsm(rpl, meth_desc, meth)) {
+		ERR("failed to retrieve SIP methods from reply: %s [%d].\n",
+				brpc_strerror(), brpc_errno);
+		goto end;
+	}
+
+	if (! (as->sipmeth = meth_array_new(meth, cnt)))
+		goto end;
+	as->methcnt = cnt;
+	ret = cnt;
+end:
+	if (req)
+		brpc_finish(req);
+	if (rpl)
+		brpc_finish(rpl);
+	return ret;
+}
+
+
+static int digest_for(int sockfd, brpc_addr_t *from, meth_dig_t *meth)
+{
+	brpc_t *req = NULL, *rpl = NULL;
+	brpc_val_t *val;
+	int i;
+	static const char desc[] = "!{<sL><sL><sL>}";
+	void *vals[6];
+	brpc_str_t *ident;
+	brpc_val_t *array;
+	int ret = -1;
+	
+
+	DEBUG("querying digests for: %.*s.\n", STR_FMT(&meth->name));
+	if (! (	(req = brpc_req(METH_DIGESTS, brpc_callid ++)) &&
+			(val = brpc_str(meth->name.s, meth->name.len)) &&
+			brpc_add_val(req, val) &&
+			brpc_send(sockfd, req, tx_timeout))) {
+		ERR("failed to send query digests: %s [%d].\n", brpc_strerror(),
+				brpc_errno);
+		goto end;
+	}
+	if (! (	(rpl = brpc_recvfrom(sockfd, from, rx_timeout)) &&
+			(! rpc_faulted(rpl)) &&
+			brpc_dsm(rpl, desc, vals))) {
+		ERR("failed to retrieve message digest': %s [%d].\n", brpc_strerror(),
+				brpc_errno);
+		goto end;
+	}
+
+	for (i = 0; i < /*req, fin, prov*/3; i ++) {
+		ident = (brpc_str_t *)vals[2 * i];
+		array = (brpc_val_t *)vals[2 * i + 1];
+		if (meth_add_digest(meth, ident, array) < 0)
+			goto end;
+	}
+
+	ret = 0;
+end:
+	if (req)
+		brpc_finish(req);
+	/* WARN: brpc_add_val() is safe only for single use of `val' var. */
+	if (rpl)
+		brpc_finish(rpl);
+	return ret;
+}
+
+static inline int query_digests(as_t *as)
+{
+	int i, j;
+	brpc_addr_t from;
+
+	for (i = 0; i < as->methcnt; i ++) {
+		from = as->addr;
+		if (digest_for(as->sockfd, &from, &as->sipmeth[i])<0) {
+			ERR("failed to get digests for method `%.*s'.\n", 
+					STR_FMT(&as->sipmeth[i].name));
+			return -1;
+		}
+		/* avoid having one message registered twice */
+		for (j = 0; j < i; j ++) {
+			if (STR_EQ(as->sipmeth[i].name, as->sipmeth[j].name)) {
+				ERR("method '%.*s' apears twice in supported methods list.\n",
+						STR_FMT(&as->sipmeth[i].name));
+				return -1;
+			}
+		}
+	}
+	return 0;
+}
+
+static int asi_sync(as_t *as)
+{
+	brpc_t *req = NULL, *rpl = NULL;
+	int *serial, *proto, ser = -1;
+	brpc_addr_t from;
+
+	if (! ((req = brpc_req(METH_SYNC, brpc_callid ++)) &&
+			brpc_asm(req, "dd", ASI_VERSION, as->id))) {
+		ERR("failed to build BINRPC context: %s (%d).\n", brpc_strerror(),
+				brpc_errno);
+		goto end;
+	}
+
+	from = as->addr;
+	if (! (brpc_send(as->sockfd, req, tx_timeout) &&
+			(rpl = brpc_recvfrom(as->sockfd, &from, rx_timeout)))) {
+		ERR("BINRPC message xchange failed: %s (%d).\n", brpc_strerror(),
+				brpc_errno);
+		goto end;
+	}
+
+	if (rpc_faulted(rpl))
+		goto end;
+
+	if (! (brpc_dsm(rpl, "dd", &proto, &serial) && proto && serial)) {
+		ERR("invlaid reply received (%s [%d]).\n", brpc_strerror(), 
+				brpc_errno);
+		goto end;
+	}
+
+	if (ASI_VERSION < *proto) {
+		ERR("unsupported ASI version: %d.\n", *proto);
+		goto end;
+	} else {
+		INFO("AS '%.*s' speaks ASI#%x; serial: %d.\n", STR_FMT(&as->name), 
+				*proto, *serial);
+	}
+
+	ser = *serial;
+end:
+	if (req)
+		brpc_finish(req);
+	if (rpl)
+		brpc_finish(rpl);
+	return ser;
+}
+
+int handshake_appsrv(as_t *as)
+{
+	int serial;
+
+	DEBUG("handshaking with AS '%.*s'.\n", STR_FMT(&as->name));
+	if (connect_appsrv(as) < 0)
+		return -1;
+
+	if ((serial = asi_sync(as)) < 0) {
+		goto discon;
+	}
+
+	if (as->serial < serial) {
+		DEBUG("AS serial  updated (from %d to current %d); querying "
+				"digests.\n", as->serial, serial);
+		/* free previously obtained digest formats, if any */
+		meth_array_free(as->sipmeth, as->methcnt);
+		as->sipmeth = NULL;
+		as->methcnt = 0;
+		
+		/* get supported SIP methods ... */
+		if (query_methods(as) < 0) {
+			ERR("failed to get supported SIP methods.\n");
+			goto discon;
+		}
+
+		/* ... and digests for each */
+		if (query_digests(as) < 0) {
+			ERR("failed to get digests for SIP methods.\n");
+			goto discon;
+		}
+
+		as->serial = serial;
+	} else {
+		INFO("AS '%.*s' did not restart since last handshake -> reusing"
+				" digest formats.\n", STR_FMT(&as->name));
+	}
+#ifdef ASI_WITH_LOCDGRAM
+	/* if AS is not supposed to sent back confirmations, SER can get rid of the
+	 * named local dgram socket it binds to for replies */
+	if (! expect_reply)
+		del_local_dgram(as);
+#endif
+
+	/* all went fine */
+	DEBUG("handshake with AS '%.*s' completed.\n", STR_FMT(&as->name));
+	return 0;
+
+discon:
+	disconnect_appsrv(as);
+	return -1;
+}
+
+
+int rpc_msg_xchg(brpc_t *rpcreq, as_t *as)
+{
+	brpc_int_t *reply;
+	brpc_t *rpcrpl;
+	brpc_addr_t from;
+	int ret = ASI_EFAILED;
+
+#ifdef EXTRA_DEBUG
+	if (as->sockfd < 0) {
+		BUG("trying to dispatch message to unconnected AS.\n");
+		abort();
+	}
+#endif
+
+	if (! brpc_send(as->sockfd, rpcreq, tx_timeout)) {
+		ERR("failed to dispatch digest to AS '%.*s': %s [%d].\n", 
+				STR_FMT(&as->name), brpc_strerror(), brpc_errno);
+		switch (brpc_errno) {
+			case ETIMEDOUT:
+			case EMSGSIZE:
+				WARN("nothign was sent, so ignoring send error.\n");
+				break;
+			default:
+				ERR("aborting connection to '%.*s'.\n", STR_FMT(&as->name));
+				disconnect_appsrv(as);
+		}
+	} else if (expect_reply) {
+		from = as->addr;
+		while ((rpcrpl = brpc_recvfrom(as->sockfd, &from, rx_timeout))) {
+			if (rpcreq->id == rpcrpl->id)
+				break;
+			ERR("reply's ID (%u) doesn't match request's (%u) - discarded.\n",
+					rpcrpl->id, rpcreq->id);
+			brpc_finish(rpcrpl);
+		}
+		if ((! rpcrpl) || (! rpc_faulted(rpcrpl))) {
+			ERR("failed to read response from AS '%.*s': %s [%d].\n",
+					STR_FMT(&as->name), brpc_strerror(), brpc_errno);
+			disconnect_appsrv(as);
+		} else {
+			if (! brpc_dsm(rpcrpl, "d", &reply)) {
+				ERR("failed to interpret reply: %s [%d].\n", brpc_strerror(),
+						brpc_errno);
+			} else if (! reply) {
+				WARN("NULL integer as reply stat: considering it failure.\n");
+			} else {
+				DEBUG("AS '%.*s' returned %d.\n", STR_FMT(&as->name), *reply);
+				/**
+				 * Temptation is to return *reply. But this would "inspire" AS
+				 * to return a more meaningful code here, which would 
+				 * potentially bring in higher response latency => bad for SER.
+				 */
+				ret = (*reply < 0) ? ASI_EFAILED : ASI_ESUCCESS;
+			}
+			
+			brpc_finish(rpcrpl);
+		}
+	} else {
+		ret = ASI_ESUCCESS;
+	}
+
+	DEBUG("BINRPC message %s AS '%.*s' finished with code %d.\n", 
+			expect_reply ? "exchange with" : "sending to", STR_FMT(&as->name),
+			ret);
+	brpc_finish(rpcreq);
+	return ret;
+}
+
+
+static void tm_callback(struct cell *trans, int type, struct tmcb_params *cbp)
+{
+	as_t *as;
+	enum ASI_STATUS_CODES code;
+
+#ifdef EXTRA_DEBUG
+	assert(trans != T_NULL_CELL);
+	assert(trans != T_UNDEFINED);
+	assert(cbp);
+	/* TODO: deceiving & inaccurate name for the ACK event */
+	assert(((type & TMCB_ACK_NEG_IN) == TMCB_ACK_NEG_IN) || 
+			((type & TMCB_E2ECANCEL_IN) == TMCB_E2ECANCEL_IN));
+#endif
+	DEBUG("ACK/CANCEL callback invoked with type %d.\n", type);
+
+	if (type == TMCB_ACK_NEG_IN)
+		if (300 <= trans->uas.status) {
+			/* TODO: could this be a useful case?! */
+			DEBUG("hbh ACK ignored.\n");
+			return;
+		}
+
+	if (! (as = (as_t *)*cbp->param)) {
+		DEBUG("ACK/CANCEL retransmission in callback.\n");
+		return;
+	}
+	switch ((code = dispatch2as(cbp->req, as))) {
+		default:
+		case ASI_EFAILED:
+			ERR("failed to dispatch TM callback message for request '%.*s' to"
+					" AS '%.*s'.\n", STR_FMT(&REQ_LINE(cbp->req).method), 
+					STR_FMT(&as->name));
+			break;
+		case ASI_ENOMATCH:
+			DEBUG("failed to dispatch TM callback message to AS '%.*s': no"
+					" digests for method '%.*s' registered.\n", 
+					STR_FMT(&as->name), STR_FMT(&REQ_LINE(cbp->req).method));
+			break;
+		case ASI_ESUCCESS:
+			DEBUG("successfully dispatched TM callback message '%.*s' to AS "
+					"'%.*s'.\n", STR_FMT(&REQ_LINE(cbp->req).method), 
+					STR_FMT(&as->name));
+			break;
+	}
+
+	/* Minimize invokation times.
+	 * (In case of simulaneous ACK arrivals,
+	 * the callback could actually be invoked multiple times, quasisimult.) */
+	*cbp->param = NULL; 
+}
+
+inline static str *get_tid(struct sip_msg *sipmsg)
+{
+	unsigned int h_index, h_label;
+#ifdef EXTRA_DEBUG
+	t_looked_up = 0;
+#endif
+#ifdef ORIG_TID_4CANCEL
+	if (sipmsg->REQ_METHOD == METHOD_CANCEL) {
+		if (tmb.t_get_canceled_ident(sipmsg, &h_index, &h_label) < 0) {
+			DEBUG("no transaction for current CANCEL [%u] found.\n",
+					sipmsg->id);
+			return NULL;
+		}
+	}
+	else
+#endif /* ORIG_TID_4CANCEL */
+	if (tmb.t_get_trans_ident(sipmsg, &h_index, &h_label) < 0) {
+		DEBUG("no transaction pending for message %u.\n", sipmsg->id);
+		return NULL;
+	}
+	DEBUG("TID for msg #%d: %u:%u\n", sipmsg->id, h_index, h_label);
+#ifdef EXTRA_DEBUG
+	t_looked_up = 1;
+#endif
+	return tid2str(h_index, h_label);
+}
+
+static inline int tm_hookup(struct sip_msg *sipmsg, as_t *as)
+{
+	if (sipmsg->REQ_METHOD != METHOD_INVITE)
+		/* not applicable */
+		return 0;
+
+#ifdef EXTRA_DEBUG
+	/* FIXME: either abort when no T is found, or allow t_looked_up be 0! */
+	if (! t_looked_up) {
+		BUG("hooking up before checking current message against current "
+				"transaction is unsafe! (tm_hookup() before get_tid()?)\n");
+		abort();
+	}
+#endif
+	
+	if (tmb.register_tmcb(sipmsg, /* avoid new expensive lookup */tmb.t_gett(),
+			TMCB_ACK_NEG_IN | TMCB_E2ECANCEL_IN, tm_callback, as, 
+			/*callback*/0) < 0) {
+		ERR("failed to register ACK&CANCEL hooks.\n");
+		return -1;
+	}
+	DEBUG("registered e2eACK|CANCEL hooks for INVITE transaction.\n");
+	return 1;
+}
+
+inline static brpc_t *new_brpc_req(str *method, enum ASI_DSGT_IDS discr, 
+		str *tid, str *opaque)
+{
+	brpc_t *rpcreq = NULL;
+	brpc_val_t *val;
+	brpc_str_t mname = {method->s, method->len};
+
+	if (! (rpcreq = brpc_req(mname, brpc_callid ++)))
+		goto brpc_err;
+
+	/* method type discriminator */
+	if (! (val = brpc_int(discr))) goto brpc_err;
+	if (! brpc_add_val(rpcreq, val)) goto free_val;
+	/* transaction ID */
+	val = tid ? brpc_str(tid->s, tid->len) : brpc_null(BRPC_VAL_STR);
+	if (! val) goto brpc_err;
+	if (! brpc_add_val(rpcreq, val)) goto free_val;
+	/* opaque */
+	val = opaque ? brpc_str(opaque->s, opaque->len) : brpc_null(BRPC_VAL_STR);
+	if (! val) goto brpc_err;
+	if (! brpc_add_val(rpcreq, val)) goto free_val;
+
+	return rpcreq;
+free_val:
+	brpc_val_free(val);
+brpc_err:
+	ERR("BINRPC operation failed: %s [%d].\n", brpc_strerror(), brpc_errno);
+	if (rpcreq)
+		brpc_finish(rpcreq);
+	return NULL;
+}
+
+static inline int unicast(struct sip_msg *sipmsg, as_t *as)
+{
+	int i;
+	meth_dig_t *meth;
+	tok_dig_t *toks;
+	size_t tokcnt;
+	brpc_t *rpcreq;
+	enum ASI_DSGT_IDS discr; /* ..iminator */
+	int ret;
+
+	DEBUG("dispatching to AS '%.*s'.\n", STR_FMT(&as->name));
+	for (i = 0; i < as->methcnt; i ++) {
+		/* TODO: use msg_parser.h helpers (precalc. rcvd SIP method value) */
+		meth = as->sipmeth + i;
+		if (! STR_EQ(meth->name, REQ_LINE(sipmsg).method))
+			continue;
+		DEBUG("matched registered SIP method `%.*s'.\n", STR_FMT(&meth->name));
+		if (sipmsg->first_line.type == SIP_REQUEST) {
+			toks = meth->req.toks;
+			tokcnt = meth->req.cnt;
+			discr = ASI_DGST_ID_REQ;
+		} else if (sipmsg->REPLY_STATUS < 200) {
+			/* TODO: specify what provisionals are wanted (?) */
+			toks = meth->prov.toks;
+			tokcnt = meth->prov.cnt;
+			discr = ASI_DGST_ID_PRV;
+		} else {
+			toks = meth->fin.toks;
+			tokcnt = meth->fin.cnt;
+			discr = ASI_DGST_ID_FIN;
+		}
+
+		if (tokcnt <= 0)
+			continue;
+		
+		if (! (rpcreq = new_brpc_req(&REQ_LINE(sipmsg).method, discr, 
+				get_tid(sipmsg), NULL))) {
+			ERR("failed to build prepared BINRPC request.\n");
+			return ASI_EFAILED;
+		}
+		if (digest(sipmsg, rpcreq, toks, tokcnt) < 0) {
+			ERR("failed to digest message into BINRPC request.\n");
+			goto free_req_fail;
+		}
+		if (tm_hookup(sipmsg, as) < 0) {
+			ERR("failed to hook up TM.\n");
+			goto free_req_fail;
+		}
+		ret = rpc_msg_xchg(rpcreq, as);
+		if ((0 <= ret) && (discr == ASI_DGST_ID_REQ)) {
+			/* if AS won't reply, SER should */
+			/* TODO: is there any way of handling branches?! */
+			if (tmb.t_addblind() < 0) {
+				ERR("failed to add UAC blind watcher.\n"); // :))
+			}
+		}
+		return ret;
+	}
+	
+	return ASI_ENOMATCH;
+
+free_req_fail:
+	brpc_finish(rpcreq);
+	return ASI_EFAILED;
+}
+
+static inline int dispatch2as(struct sip_msg *sipmsg, as_t *as)
+{
+	if (is_connected(as) < 0) {
+		INFO("AS '%.*s' skipped because not connected.\n", STR_FMT(&as->name));
+		return ASI_EFAILED;
+	}
+	return unicast(sipmsg, as);
+}
+
+int dispatch(struct sip_msg *sipmsg, as_t *as, void *_)
+{
+	if (! as) {
+		/* TODO */
+		ERR("broadcasting not yet implemented.\n");
+		return -1;
+	}
+
+	return dispatch2as(sipmsg, as);
+}
+
+int dispatch_rebuild(struct sip_msg *sipmsg, as_t *as, void *_)
+{
+	int error, ret = -1;
+	struct dest_info dst;
+	struct sip_msg hot_msg;
+	
+	memset(&hot_msg, 0, sizeof(struct sip_msg));
+	init_dest_info(&dst);
+	/* TODO: this would get re-done uselessly, in case no further modifs
+	 * between two successive dispatch_rebuid() are made => some static/var
+	 * or caching? */
+	hot_msg.buf = build_all(sipmsg, /*touch_clen*/1, &hot_msg.len, &error, 
+		&dst);
+	if (error || (! hot_msg.buf)) {
+		ERR("failed to assemble altered SIP msg (%d).\n", error);
+		return -1;
+	}
+
+	DEBUG("parsing locally assembled message.\n");
+	if (parse_msg(hot_msg.buf, hot_msg.len, &hot_msg) != 0) {
+		BUG("failed to parse own assembled message.\n");
+		goto end;
+	} else {
+		/* copy all relevant metadata from orig. msg */
+		hot_msg.id = sipmsg->id;
+		hot_msg.rcv = sipmsg->rcv;
+		hot_msg.hash_index = sipmsg->hash_index;
+		hot_msg.msg_flags = sipmsg->msg_flags;
+		hot_msg.flags = sipmsg->flags;
+		hot_msg.force_send_socket = sipmsg->force_send_socket;
+	}
+	ret = dispatch(&hot_msg, as, _);
+end:
+	free_sip_msg(&hot_msg);
+#ifndef DYN_BUF
+	pkg_free(hot_msg.buf);
+#endif
+	return ret;
+}
+
+int dispatch_tm_reply(int as_id, struct sip_msg *sipmsg, 
+		str *method, enum ASI_DSGT_IDS discr, /*usefull for FAKED_REPLY */
+		str *tid, str *opaque)
+{
+	brpc_t *rpcreq;
+	as_t *as;
+	tok_dig_t *toks;
+	size_t tokcnt;
+	int i;
+	meth_dig_t *meth;
+
+#ifdef EXTRA_DEBUG
+	assert(tid);
+
+	if ((GET_AS_CNT <= as_id) || (as_id < 0)) {
+		BUG("invalid AS ID value: %d; maximum: %zd.\n", as_id, GET_AS_CNT - 1);
+		abort();
+	}
+	else
+#endif
+	{
+		as = app_srvs[as_id];
+	}
+
+	if (is_connected(as) < 0) {
+		INFO("AS '%.*s' skipped because not connected.\n", STR_FMT(&as->name));
+		return ASI_EFAILED;
+	}
+
+	if (! (rpcreq = new_brpc_req(method, discr, tid, opaque))) {
+		ERR("failed to build prepared BINRPC request.\n");
+		return ASI_EFAILED;
+	}
+	if (sipmsg != FAKED_REPLY) {
+		meth = NULL;
+		for (i = 0; i < as->methcnt; i ++) {
+			if (STR_EQ(as->sipmeth[i].name, *method)) {
+				meth = &as->sipmeth[i];
+				break;
+			}
+		}
+		if (! meth) {
+			ERR("AS '%.*s' flaged reply for initiated request `%.*s', but"
+					" no digest format provided.\n", 
+					STR_FMT(&as->name), STR_FMT(method));
+			goto error;
+		}
+		switch (discr) {
+			case ASI_DGST_ID_PRV:
+				toks = meth->prov.toks;
+				tokcnt = meth->prov.cnt;
+				break;
+			case ASI_DGST_ID_FIN:
+				toks = meth->fin.toks;
+				tokcnt = meth->fin.cnt;
+				break;
+			default:
+				BUG("illegal message type discriminator (%d) in TM reply "
+						"dispatcher.\n", discr);
+				abort();
+		}
+		if (digest(sipmsg, rpcreq, toks, tokcnt) < 0) {
+			ERR("failed to digest message into BINRPC request.\n");
+			goto error;
+		}
+	} else {
+		/* TODO: add cfg option to send NULLs (maintain 1 signature) */
+		;
+	}
+
+	return rpc_msg_xchg(rpcreq, as);
+error:
+	brpc_finish(rpcreq);
+	return ASI_EFAILED;
+}
+
+#ifdef ASI_WITH_RESYNC
+/**
+ * Set the resync flag for one AS.
+ * @return:
+ * 	EBADMSG failed to parse URI
+ * 	EADDRNOTAVAIL AS not found
+ * 	EINPROGRESS updating
+ * 	0 updated
+ */
+int appsrv_set_resync(char *as_uri, int serial, int *as_id)
+{
+	brpc_addr_t *as_addr;
+	int i, updated;
+	uint8_t *resync;
+	as_t *as;
+	
+	/* the string can be anbiguous (ex.: brpc4d:// == brpcnd:// or different
+	 * names for same IP) => compare structures */
+	if (! (as_addr = brpc_parse_uri(as_uri))) {
+		ERR("failed to parse BINRPC URI `%s': %s (%d).\n", as_uri, 
+				brpc_strerror(), brpc_errno);
+		return EBADMSG;
+	}
+	
+	for (i = 0; i < GET_AS_CNT; i ++)
+		if (brpc_addr_eq(&app_srvs[i]->addr, as_addr)) {
+			as = app_srvs[i];
+			lock_get(as->resync_mutex);
+			if (AS_RS_SERIAL(as) < serial) {
+				DEBUG("AS '%.*s' asking for resync with updated serial %d.\n",
+						STR_FMT(&as->name), serial);
+				resync = as->resync;
+				for (i = 0; i < *proc_bmb_cnt; i++)
+					resync[i] = 0xFF;
+				AS_RS_SERIAL(as) = serial;
+				updated = 1;
+			} else {
+				DEBUG("AS '%.*s' asking for resync with already learned "
+						"serial %d.\n", STR_FMT(&as->name), serial);
+				updated = 0;
+			}
+			lock_release(as->resync_mutex);
+			*as_id = as->id;
+			return updated ? 0 : EINPROGRESS;
+		}
+	DEBUG("AS@%s not found.\n", as_uri);
+	return EADDRNOTAVAIL;
+}
+#endif /* ASI_WITH_RESYNC */

+ 121 - 0
modules/asi/appsrv.h

@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2010 IPTEGO GmbH.
+ * All rights reserved.
+ *
+ * This file is part of sip-router, a free SIP server.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ * 
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY IPTEGO GmbH ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL IPTEGO GmbH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * Author: Bogdan Pintea.
+ */
+
+#ifndef __ASI_AS_H__
+#define __ASI_AS_H__
+
+#include <stdlib.h> /*size_t*/
+
+#include "str.h"
+#include "locking.h"
+#include "parser/msg_parser.h"
+
+#include "digest.h"
+
+#ifdef ASI_WITH_RESYNC
+/* max Bit Mask Block cells count (x8 = max proc count) */
+#define ASI_BMB_MAX		16
+#endif
+
+typedef struct {
+	str name; /* points to configuration data */
+	int id; /* used to dispatch timeout notifications */
+	brpc_addr_t addr; /* ... where the AS is listening. */
+
+	int sockfd; /* comm. with the AS */
+#ifdef ASI_WITH_LOCDGRAM
+	brpc_addr_t listen; /* if SOCK_DGRAM+PF_LOCAL, listen here from AS rpls */
+#endif
+
+	/**
+	 * Note: the digests related members could be shm'ized as well (maybe the
+	 * whole structure), but AVPs&XLL prepared structures can not be easily
+	 * moved into SHM (some nastier deep copies are needed).
+	 */
+	int serial; /* timestamp to 'sign' digest methods validity */
+	meth_dig_t *sipmeth; /* digests for each supported SIP method */
+	size_t methcnt; /* size of sipmeth array */
+
+	/* shm'ized */
+#ifdef ASI_WITH_RESYNC
+	/* bit mask of flags, 1/proc; if set, a new handshake must be performed */
+	uint8_t *resync;
+	/* mutex to protect rsync access */
+	gen_lock_t *resync_mutex;
+	int *resync_serial;
+#endif
+} as_t;
+
+
+extern int ct_timeout;
+extern int tx_timeout;
+extern int rx_timeout;
+
+extern int expect_reply;
+#ifdef ASI_WITH_LOCDGRAM
+extern char *usock_template;
+extern int usock_mode;
+extern int usock_uid;
+extern int usock_gid;
+#endif
+
+
+void init_appsrv_proc(int rank, brpc_id_t callid);
+size_t appsrvs_count(void);
+int new_appsrv(char *name, char *uri);
+void free_appsrvs(void);
+as_t *as4id(unsigned int id);
+int handshake_appsrv(as_t *as);
+void disconnect_appsrv(as_t *as);
+int dispatch(struct sip_msg *msg, as_t *as, void *_);
+int dispatch_rebuild(struct sip_msg *sipmsg, as_t *as, void *_);
+int dispatch_tm_reply(int as_id, struct sip_msg *sipmsg, 
+		str *method, enum ASI_DSGT_IDS discr, /*usefull for FAKED_REPLY */
+		str *tid, str *opaque);
+#ifdef ASI_WITH_RESYNC
+int setup_bmb(int max_procs);
+int appsrv_set_resync(char *as_uri, int serial, int *as_id);
+#endif
+
+#define ASI_VERSION			0x2
+
+enum ASI_STATUS_CODES {
+	ASI_EFAILED		= -2,
+	ASI_ENOMATCH	= -1,
+	/* 0: reserved (halts script) */
+	ASI_ESUCCESS	= 1,
+};
+
+#define DEFAULT_CT_TIMEOUT		5*1000
+#define DEFAULT_TX_TIMEOUT		30*1000 /*30ms*/
+#define DEFAULT_RX_TIMEOUT		40*1000 /*40ms*/
+
+#endif /* __ASI_AS_H__ */

+ 324 - 0
modules/asi/asi_mod.c

@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2010 IPTEGO GmbH.
+ * All rights reserved.
+ *
+ * This file is part of sip-router, a free SIP server.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ * 
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY IPTEGO GmbH ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL IPTEGO GmbH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * Author: Bogdan Pintea.
+ */
+
+
+#include <stdlib.h>
+
+#include "pt.h"
+#include "sr_module.h"
+#include "ut.h"
+#include "route.h"
+#include "dprint.h"
+#include "mem/wrappers.h"
+#include "select_buf.h"
+
+#include "strutil.h"
+#include "binds.h"
+#include "appsrv.h"
+
+MODULE_VERSION
+
+/* rpc.c */
+extern rpc_export_t mod_rpc[];
+extern select_row_t sel_declaration[];
+
+/* when SHM is available at module parameter setting time, the ASes can simply
+ * be added with the 'constructor' (instead of depositing the URI in this
+ * array). */
+#define MAX_ASES	128
+static char *app_srvs[MAX_ASES];
+static size_t as_cnt = 0;
+
+
+static int mod_init(void);
+static int mod_child(int rank);
+static void mod_destroy(void);
+int dispatch_fixup(void** param, int param_no);
+static int add_app_srv(modparam_t type, void * _param);
+
+extern int onreply_rt_idx; /* rpc.c declaration */
+static char *onreply_route = NULL;
+#ifdef ASI_WITH_LOCDGRAM
+static char *usock_uid_str = NULL;
+static char *usock_gid_str = NULL;
+#endif
+
+#define ALL_ROUTES	\
+	REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE | BRANCH_ROUTE | \
+	ONSEND_ROUTE
+
+static cmd_export_t mod_cmds[] = {
+		{"asi_dispatch", (cmd_function)dispatch, 1, dispatch_fixup, 
+				ALL_ROUTES},
+		{"asi_dispatch_rebuild", (cmd_function)dispatch_rebuild, 1, 
+				dispatch_fixup, ALL_ROUTES},
+		{0, 0, 0, 0, 0}
+};
+
+static param_export_t mod_params[] = {
+	{"app_srv",			PARAM_STRING|PARAM_USE_FUNC,(void *)add_app_srv	 },
+	{"expect_reply",	PARAM_INT,					&expect_reply},
+	{"onreply_route",	PARAM_STRING,				&onreply_route},
+#ifdef ASI_WITH_LOCDGRAM
+	{"usock_template",	PARAM_STRING,				&usock_template},
+	{"usock_mode",		PARAM_INT,					&usock_mode},
+	{"usock_uid",		PARAM_STRING,				&usock_uid_str},
+	{"usock_gid",		PARAM_STRING,				&usock_gid_str},
+#endif
+	{"connect_timeout",		PARAM_INT,				&ct_timeout},
+	{"transmit_timeout",	PARAM_INT,				&tx_timeout},
+	{"receive_timeout",		PARAM_INT,				&rx_timeout},
+	{0, 0, 0} 
+};
+
+struct module_exports exports = {
+	"asi",
+	mod_cmds,
+	mod_rpc,
+	mod_params,
+	mod_init,
+	NULL, /* on-reply callback */
+	mod_destroy,
+	NULL, /* on-cancel callback */
+	mod_child,
+};
+
+
+static void handshake_all()
+{
+	int i;
+	as_t *as;
+	
+	for (i = 0; i < as_cnt; i ++) {
+		as = as4id(i);
+		if (handshake_appsrv(as) < 0) {
+			ERR("handshake with AS '%.*s' failed.\n", STR_FMT(&as->name));
+		} else {
+			INFO("handshake with AS '%.*s' succeeded.\n", 
+					STR_FMT(&as->name));
+		}
+	}
+}
+
+static void disconnect_all()
+{
+	int i;
+	as_t *as;
+	
+	for (i = 0; i < as_cnt; i ++) {
+		as = as4id(i);
+		disconnect_appsrv(as);
+	}
+}
+
+static int mod_init(void)
+{
+	char *name, *uri, *param;
+	int i;
+	extern struct route_list onreply_rt; /* route.c declaration */
+
+	if ((xll_bind() < 0) || (tm_bind() < 0)) {
+		ERR("asi - failed to initialize due to missing dependencies.\n");
+		return -1;
+	}
+
+	if (onreply_route) {
+		i=route_get(&onreply_rt, onreply_route);
+		if ((i < 0) || 
+				/*route_get() actually adds it...*/(! onreply_rt.rlist[i])) {
+			ERR("unknown route `%s'\n", onreply_route);
+			return -1;
+		} else {
+			onreply_rt_idx = i;
+		}
+	}
+	
+	
+	if (register_select_table(sel_declaration) < 0) {
+		ERR("failed to register SELECTs table.\n");
+		return -1;
+	}
+
+	brpc_mem_setup(w_pkg_calloc, w_pkg_malloc, w_pkg_free, w_pkg_realloc);
+#if 0
+	brpc_log_setup(syslog);
+#endif
+
+	/* allow user specify milis (rather than used micros) */
+	ct_timeout = (ct_timeout < 0) ? DEFAULT_CT_TIMEOUT : ct_timeout * 1000;
+	tx_timeout = (tx_timeout < 0) ? DEFAULT_TX_TIMEOUT : tx_timeout * 1000;
+	rx_timeout = (rx_timeout < 0) ? DEFAULT_RX_TIMEOUT : rx_timeout * 1000;
+
+	if (! as_cnt)
+		WARN("ASI: empty AS list.\n");
+	else {
+		/* initialize AS structures */
+		for (i = 0; i < as_cnt; i ++) {
+			/* sems@brpcns://127.0.0.1:5111 */
+			param = app_srvs[i];
+			if (! (uri = strchr(param, '@'))) {
+				ERR("invalid 'app_srv' parameter `%s': missingr `@' "
+						"separator.\n", param);
+				return -1;
+			} else if (uri == param) {
+				ERR("invalid 'app_srv' parameter `%s': empty AP name.\n", 
+						param);
+				return -1;
+			} else {
+				name = param;
+				*uri = 0;
+				uri ++;
+				DEBUG("split into name `%s', uri `%s'.\n", name, uri);
+			}
+
+			if (new_appsrv(name, uri) < 0) {
+				ERR("failed to add AS instance for 'app_srv' `%s'.\n", param);
+				return -1;
+			}
+			INFO("new BINRPC URI `%s' for AS `%s' added.\n", uri, name);
+		}
+
+#ifdef ASI_WITH_LOCDGRAM
+		if (expect_reply) {
+			/* fix UID/GID */
+			if (usock_uid_str)
+				if (user2uid(&usock_uid, NULL, usock_uid_str)<0){
+					ERR("bad user name/uid number %s\n", usock_uid_str);
+					return -1;
+				}
+			if (usock_gid_str)
+				if (group2gid(&usock_uid, usock_gid_str)<0){
+					ERR("bad group name/gid number %s\n", usock_gid_str);
+					return -1;
+				}
+		}
+#endif
+	}
+
+	handshake_all();
+	/*don't need connection in main proc*/
+	disconnect_all();
+	
+	INFO("asi - initialized (parent).\n");
+	return 0;
+}
+
+
+
+static int mod_child(int rank)
+{
+	brpc_id_t callid;
+
+#ifdef ASI_WITH_RESYNC
+	if (rank == PROC_INIT) {
+		if (setup_bmb(get_max_procs()) < 0) {
+			ERR("failed to setup resync process bit mask.\n");
+			return -1;
+		}
+	}
+#endif
+	reset_static_buffer(); /* needed mostly for timer processes */
+	if (rank <= 0)
+		/* TODO: is the limit 0 safe? */
+		/* this is no worker process */
+		return 0;
+
+	INFO("asi - initializing (worker #%d).\n", rank);
+	callid = rand();
+	/* make sure the distance between callid's initial value of all processes
+	 * is large enough */
+	callid &= 0xffffff;
+	callid |= rank << 24;
+	init_appsrv_proc(rank, callid);
+
+	handshake_all();
+
+	return 0;
+}
+
+
+
+static void mod_destroy(void)
+{
+	free_appsrvs();
+	INFO("module ASI destroyed (boom!).\n");
+}
+
+static int add_app_srv(modparam_t type, void * _param)
+{
+	if ((type & PARAM_STRING) == 0) {
+		BUG("invalid parameter type %d (string expected).\n", type);
+		return -1;
+	}
+
+	if (MAX_ASES < as_cnt) {
+		BUG("too many AS configured (%zd); update MAX_ASES definition and "
+				"recompile.\n", as_cnt);
+		return -1;
+	}
+	app_srvs[as_cnt ++] = (char *)_param;
+	DEBUG("new AS mapping `%s' enqueued\n", (char *)_param);
+	return 0;
+
+}
+
+int dispatch_fixup(void** param, int param_no)
+{
+	str name;
+	int i;
+	as_t *as;
+
+	if (param_no != 1) {
+		ERR("invalid number of arguments (%d).\n", param_no);
+		return -1;
+	}
+	name.s = *(char **)param;
+	name.len = strlen(name.s);
+	DEBUG("fixing parameter `%s' [%d].\n", name.s, name.len);
+
+	if ((name.len == 1) && (*name.s == '*')) {
+		DEBUG("asi - broadcasting.\n");
+		*param = NULL;
+		return 0;
+	}
+
+	for (i = 0; i < as_cnt; i ++) {
+		as = as4id(i);
+		if (STR_EQ(name, as->name)) {
+			*param = as;
+			return 0;
+		}
+	}
+	
+	ERR("AS name `%s' not registered (must be specified as module "
+			"parameter.\n", name.s);
+	return -1;
+}

+ 91 - 0
modules/asi/binds.c

@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2010 IPTEGO GmbH.
+ * All rights reserved.
+ *
+ * This file is part of sip-router, a free SIP server.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ * 
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY IPTEGO GmbH ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL IPTEGO GmbH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * Author: Bogdan Pintea.
+ */
+
+
+#include "sr_module.h"
+#include "dprint.h"
+#include "mem/mem.h"
+
+#include "binds.h"
+
+
+xl_parse_format_f *xl_parse;
+xl_print_log_f *xl_print;
+
+struct tm_binds tmb;
+
+#define PARSE_BIND_NAME	"xparse"
+#define PRINT_BIND_NAME	"xprint"
+
+#define LOADTM_BIND_NAME	"load_tm"
+
+#define NO_SCRIPT	-1 /* this is dumb: every module using xlog defines it */
+
+
+int xll_bind(void)
+{
+	xl_parse = (xl_parse_format_f *)find_export(PARSE_BIND_NAME, NO_SCRIPT, 0);
+	xl_print = (xl_print_log_f *)find_export(PRINT_BIND_NAME, NO_SCRIPT, 0);
+
+	if (xl_parse && xl_print)
+		return 0;
+
+	ERR("failed to load 'xl_lib' routines `%s', `%s'; is module XLOG "
+			"loaded?\n", PARSE_BIND_NAME, PRINT_BIND_NAME);
+	return -1;
+}
+
+void xl_elogs_free(xl_elog_t *root)
+{
+	xl_elog_t *next;
+	while (root) {
+		next = root->next;
+		pkg_free(root);
+		root = next;
+	}
+}
+
+int tm_bind(void)
+{
+	load_tm_f tm_load;
+	if (! (tm_load = (load_tm_f)find_export(LOADTM_BIND_NAME, NO_SCRIPT, 0))) {
+		ERR("failed to find '%s' export; is module TM loaded?\n", 
+				LOADTM_BIND_NAME);
+		return -1;
+	}
+
+	if (tm_load(&tmb) < 0) {
+		BUG("TM module loaded but failed to load its exported symbols.\n");
+		return -1;
+	}
+	
+	return 0;
+}

+ 50 - 0
modules/asi/binds.h

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2010 IPTEGO GmbH.
+ * All rights reserved.
+ *
+ * This file is part of sip-router, a free SIP server.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ * 
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY IPTEGO GmbH ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL IPTEGO GmbH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * Author: Bogdan Pintea.
+ */
+
+#ifndef __ASI_BINDS_H__
+#define __ASI_BINDS_H__
+
+#include "modules_s/xlog/xl_lib.h"
+#include "modules/tm/tm_load.h"
+
+extern xl_parse_format_f *xl_parse;
+extern xl_print_log_f *xl_print;
+
+int xll_bind(void);
+void xl_elogs_free(xl_elog_t *root);
+
+
+extern struct tm_binds tmb;
+int tm_bind(void);
+
+
+
+#endif /* __ASI_BINDS_H__ */

+ 383 - 0
modules/asi/digest.c

@@ -0,0 +1,383 @@
+/*
+ * Copyright (c) 2010 IPTEGO GmbH.
+ * All rights reserved.
+ *
+ * This file is part of sip-router, a free SIP server.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ * 
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY IPTEGO GmbH ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL IPTEGO GmbH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * Author: Bogdan Pintea.
+ */
+
+#include <stdlib.h>
+
+#include "strutil.h"
+#include "binds.h"
+#include "digest.h"
+
+enum DIG_TOKEN_TYPE_MARK {
+	DIG_TOKM_SEL	= '@',
+	DIG_TOKM_AVP	= '$',
+	DIG_TOKM_XLL	= '%',
+};
+
+/*TODO: mod param*/
+#define XLL_MAX_BUFF	1024
+#ifdef XLL_NULL_FIX
+#define XLL_NULL		"<null>"
+#endif
+#define TM_SEL_HTID		"@tm.htid"
+
+meth_dig_t *meth_array_new(brpc_str_t **names, size_t cnt)
+{
+	int k;
+	meth_dig_t *array = NULL;
+
+	if (! (array = (meth_dig_t *)pkg_malloc(cnt * sizeof(meth_dig_t)))) {
+		ERR("out of pkg memory (meth dig array).\n");
+		goto end;
+	}
+	/* must: checked later @see meth_free() */
+	memset((char *)array, 0, cnt * sizeof(meth_dig_t));
+
+	for (k = 0; k < cnt; k ++) {
+		DEBUG("supported SIP method: %.*s.\n", (int)names[k]->len, 
+				names[k]->val);
+		if (strclo(names[k]->val, names[k]->len, &array[k].name, 
+				STR_CLONE_PKG|STR_CLONE_TRIM) < 0) {
+			ERR("failed to clone BINRPC to SER string: %s [%d].\n",
+					strerror(errno), errno);
+			goto end;
+		}
+	}
+	return array;
+end:
+	if (array)
+		pkg_free(array);
+	return NULL;
+}
+
+
+
+static inline void tok_free(tok_dig_t *tok)
+{
+	if (tok->spec)
+		brpc_val_free(tok->spec);
+
+	switch (tok->type) {
+		case DIG_TOKT_SEL: free_select(tok->sel); break;
+		case DIG_TOKT_AVP: free_avp_ident(&tok->avp); break;
+		case DIG_TOKT_XLL: xl_elogs_free(tok->xll); break;
+		default:
+			; /*ignore*/
+	}
+}
+
+
+static void meth_free(meth_dig_t *meth)
+{
+	int j;
+
+	pkg_free(meth->name.s);
+	
+	for (j = 0; j < meth->req.cnt; j ++)
+		tok_free(&meth->req.toks[j]);
+	if (j)
+		pkg_free(meth->req.toks);
+
+	for (j = 0; j < meth->fin.cnt; j ++)
+		tok_free(&meth->fin.toks[j]);
+	if (j)
+		pkg_free(meth->fin.toks);
+
+	for (j = 0; j < meth->prov.cnt; j ++)
+		tok_free(&meth->prov.toks[j]);
+	if (j)
+		pkg_free(meth->prov.toks);
+}
+
+
+void meth_array_free(meth_dig_t *array, size_t cnt)
+{
+	int i;
+	for (i = 0; i < cnt; i ++)
+		meth_free(&array[i]);
+	if (i)
+		pkg_free(array);
+}
+
+
+static int tok_fix(tok_dig_t *tok, const brpc_val_t *_spec)
+{
+	bool resolve = true;
+	brpc_str_t spec_brpc;
+	str spec_ser;
+	switch (brpc_val_type(_spec)) {
+		case BRPC_VAL_LIST:
+		case BRPC_VAL_AVP:
+		case BRPC_VAL_MAP:
+		case BRPC_VAL_BIN:
+		case BRPC_VAL_INT:
+		case BRPC_VAL_FLOAT:
+			DEBUG("non-string BINRPC type [%d] as digest token specifier: will"
+					" be passed back as is.\n", brpc_val_type(_spec));
+			resolve = false;
+			/* for rest (=strings), the type can only be determined when 
+			 * resolving */
+			tok->type = DIG_TOKT_IMM;
+			/* no break */
+		case BRPC_VAL_STR:
+			if (! (tok->spec = brpc_val_clone(_spec))) {
+				ERR("failed to clone specification BINRPC value: %s [%d].\n",
+						brpc_strerror(), brpc_errno);
+				return -1;
+			}
+			if (brpc_is_null(tok->spec)) {
+				tok->type = DIG_TOKT_IMM;
+				resolve = false;
+				WARN("received NULL digest string token: will be returned as"
+						" is to requestor.\n");
+			}
+			break;
+		default:
+			BUG("unexpected BINRPC value type %d.\n", brpc_val_type(_spec));
+			return -1;
+	}
+
+	if (! resolve) /* pretty much done */
+		return 0;
+
+	spec_brpc = brpc_str_val(tok->spec);
+	DEBUG("resolving digest token `%.*s'.\n", BRPC_STR_FMT(&spec_brpc));
+
+	/* TODO: check escaping: @@, $$, %% */
+	switch (spec_brpc.val[0]) {
+		case DIG_TOKM_SEL: 
+			tok->type = DIG_TOKT_SEL;
+			/* TODO: shoudln't the shm_* version be better used? (size) */
+			return parse_select(&spec_brpc.val, &tok->sel);
+		case DIG_TOKM_AVP:
+			tok->type = DIG_TOKT_AVP;
+			spec_ser.s = spec_brpc.val + /* skip leading `$' */1;
+			spec_ser.len = spec_brpc.len - /*$*/1 - /*0-term*/1;
+			return parse_avp_ident(&spec_ser, &tok->avp);
+		case DIG_TOKM_XLL: 
+			tok->type = DIG_TOKT_XLL;
+			return xl_parse(spec_brpc.val, &tok->xll);
+		default:
+			/* the value in tok->spec will be simply returned to caller */
+			tok->type = DIG_TOKT_IMM;
+	}
+
+	return 0;
+}
+
+
+int meth_add_digest(meth_dig_t *meth, brpc_str_t *ident, brpc_val_t *array)
+{
+	brpc_dissect_t *diss = NULL;
+	int id;
+	tok_dig_t ** digest;
+	size_t *dlen /*4 GCC*/= NULL;
+	char *tmp;
+	size_t cnt;
+	const brpc_val_t *spec;
+	/* Sip Method Type, Tm Ht Id specifiers */
+	brpc_val_t *smt_spec = NULL, *thi_spec = NULL;
+	int idx = 0;
+	int res = -1;
+	
+	if (! ident) {
+		ERR("NULL identifier provided in digest specification.\n");
+		goto end;
+	}
+	/* identify what current digest is for */
+	if ((id = (int)strtol(ident->val, NULL, /*decimal*/0)) == 0) {
+		ERR("failed to decode \"%s\" as digest type identifier (%s).\n",
+				ident->val, strerror(errno));
+		goto end;
+	}
+	switch (id) {
+		do {
+			case ASI_DGST_ID_REQ: 
+				digest = &meth->req.toks; 
+				dlen = &meth->req.cnt;
+				tmp = "requests";
+				break;
+			case ASI_DGST_ID_FIN:
+				digest = &meth->fin.toks; 
+				dlen = &meth->fin.cnt;
+				tmp = "final replies";
+				break;
+			case ASI_DGST_ID_PRV:
+				digest = &meth->prov.toks; 
+				dlen = &meth->prov.cnt;
+				tmp = "provisional replies";
+				break;
+		} while (0);
+			if ((*dlen) || (*digest)) {
+				ERR("digest format for %s [%d] specified twice.\n", 
+						tmp, id);
+				goto end;
+			}
+			break;
+		default:
+			ERR("invalid digest identifier `%d'.\n", id);
+			goto end;
+	}
+
+	if ((cnt = brpc_val_seqcnt(array)) == 0) {
+		INFO("empty digest format received for %s for SIP method "
+				"\"%.*s\".\n", tmp, STR_FMT(&meth->name));
+		/* don't allow it specified for two times */
+		*digest = (tok_dig_t *)-1;
+		goto ok;
+	} else {
+		if (! (*digest = (tok_dig_t *)pkg_malloc((/*id*/1 + cnt) * 
+				sizeof(tok_dig_t)))) {
+			ERR("out of pkg memory (digest).\n");
+			goto end;
+		}
+	}
+
+	/* unpack array values */
+	if (! (diss = brpc_val_dissector(array))) {
+		ERR("failed to get dissector for digest array: %s [%d].\n",
+				brpc_strerror(), brpc_errno);
+		goto end;
+	}
+	for (; brpc_dissect_next(diss); idx ++) {
+		/* assert(idx < *dlen); */
+		spec = brpc_dissect_fetch(diss);
+		if (tok_fix((*digest) + idx, (brpc_val_t *)spec) < 0) {
+			goto end;
+		}
+	}
+	DEBUG("specified %d digest tokens for %s for %.*s.\n", idx, tmp, 
+			STR_FMT(&meth->name));
+ok:
+	res = 0;
+end:
+	*dlen = idx;
+	if (diss)
+		brpc_dissect_free(diss);
+	/* the values are cloned by tok_fix() */
+	if (smt_spec)
+		brpc_val_free(smt_spec);
+	if (thi_spec)
+		brpc_val_free(thi_spec);
+	return res;
+}
+
+int digest(struct sip_msg *sipmsg, brpc_t *rpcreq, 
+		tok_dig_t *toks, size_t tokcnt)
+{
+	int i;
+	brpc_val_t *rpcval;
+	union {
+		struct {
+			char buff[XLL_MAX_BUFF];
+			int len;
+		} xll;
+		str sel;
+		struct {
+			avp_t *cont;
+			avp_value_t val;
+		} avp;
+	} res;
+
+	DEBUG("adding %zd digest tokens as RPC arguments.\n", tokcnt);
+	for (i = 0; i < tokcnt; i ++) {
+		switch (toks[i].type) {
+			case DIG_TOKT_IMM:
+				rpcval = brpc_val_clone(toks[i].spec);
+				break;
+			case DIG_TOKT_SEL:
+				if (run_select(&res.sel, toks[i].sel, sipmsg) < 0) {
+					DEBUG("digesting token `%.*s' failed.\n", 
+							BRPC_STR_FMT(&brpc_str_val(toks[i].spec)));
+					res.sel.s = NULL;
+				}
+				if (res.sel.s)
+					rpcval = brpc_str(res.sel.s, res.sel.len);
+				else
+					rpcval = brpc_null(BRPC_VAL_STR);
+				break;
+			case DIG_TOKT_AVP:
+				if ((res.avp.cont = search_first_avp(toks[i].avp.flags, 
+						toks[i].avp.name, &res.avp.val, 
+						/* no search state */NULL))) {
+					if (res.avp.cont->flags & AVP_VAL_STR)
+						rpcval = brpc_str(res.avp.val.s.s, res.avp.val.s.len);
+					else
+						rpcval = brpc_int(res.avp.val.n);
+				} else {
+					rpcval = brpc_null(BRPC_VAL_STR);
+				}
+				break;
+			case DIG_TOKT_XLL:
+				res.xll.len = XLL_MAX_BUFF;
+				if (xl_print(sipmsg, toks[i].xll, res.xll.buff, 
+						&res.xll.len) < 0) {
+					ERR("digesting token `%.*s' failed.\n", 
+							BRPC_STR_FMT(&brpc_str_val(toks[i].spec)));
+					goto end;
+				}
+				if (res.xll.len) {
+#ifdef XLL_NULL_FIX
+					if (strncmp(res.xll.buff, XLL_NULL, sizeof(XLL_NULL) - 
+							/*0-term*/1) == 0)
+						rpcval = brpc_null(BRPC_VAL_STR);
+					else
+#endif
+						rpcval = brpc_str(res.xll.buff, res.xll.len);
+				} else {
+					rpcval = brpc_null(BRPC_VAL_STR);
+				}
+				break;
+			default:
+				BUG("unexpected token type %d (idx: %d/%zd).\n", toks[i].type,
+						i, tokcnt);
+				abort();
+		}
+		if (! rpcval) {
+			ERR("failed to build BINRPC digest value: %s [%d].\n",
+					brpc_strerror(), brpc_errno);
+			goto end;
+		}
+		if (! brpc_add_val(rpcreq, rpcval)) {
+			ERR("failed to add BINRPC value to request: %s [%d].\n",
+					brpc_strerror(), brpc_errno);
+#ifdef EXTRA_DEBUG
+			/* most probably a segfault will occur later anyway, but still */
+			abort();
+#endif
+			brpc_val_free(rpcval);
+			goto end;;
+		}
+		DEBUG("digest tok #%i: added new val (%d) for token (%d).\n", i,
+				brpc_val_type(rpcval), toks[i].type);
+	}
+end:
+	return i - (int)tokcnt;
+}

+ 94 - 0
modules/asi/digest.h

@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2010 IPTEGO GmbH.
+ * All rights reserved.
+ *
+ * This file is part of sip-router, a free SIP server.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ * 
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY IPTEGO GmbH ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL IPTEGO GmbH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * Author: Bogdan Pintea.
+ */
+
+#ifndef __ASI_DIGEST_H__
+#define __ASI_DIGEST_H__
+
+#include <binrpc.h>
+
+#include "parser/msg_parser.h"
+#include "select.h"
+#include "usr_avp.h"
+#include "modules_s/xlog/xl_lib.h"
+
+enum DIG_TOK_TYPE {
+	DIG_TOKT_IMM,
+	DIG_TOKT_SEL,
+	DIG_TOKT_AVP,
+	DIG_TOKT_XLL, /* TODO: still needed? */
+};
+
+typedef struct {
+	enum DIG_TOK_TYPE type;
+	/* Cloned received BINRPC value:
+	 * 	- returned for DIG_TOKT_IMM type 
+	 * 	- keeps avp_ident_t's strings
+	 */
+	brpc_val_t *spec;
+	union {
+		select_t *sel;
+		avp_ident_t avp; //TODO: avp_spec_t == avp_ident_t !:?
+		xl_elog_t *xll;
+	};
+} tok_dig_t;
+
+struct digest_format {
+	tok_dig_t *toks;
+	size_t cnt;
+};
+
+typedef struct {
+	str name;
+	struct digest_format req;
+	struct digest_format fin;
+	struct digest_format prov;
+} meth_dig_t;
+
+
+enum ASI_DSGT_IDS {
+	ASI_DGST_ID_REQ	= 1,
+	ASI_DGST_ID_FIN	= 2,
+	ASI_DGST_ID_PRV	= 3,
+};
+
+
+
+meth_dig_t *meth_array_new(brpc_str_t **names, size_t cnt);
+void meth_array_free(meth_dig_t *array, size_t cnt);
+int meth_add_digest(meth_dig_t *meth, brpc_str_t *ident, brpc_val_t *array);
+int digest(struct sip_msg *sipmsg, brpc_t *rpcreq, 
+		tok_dig_t *toks, size_t tokcnt);
+
+#define STR_EQ_BSTR(_str_, _bstr_) \
+	(((_str_)->len == (_bstr_)->len) && \
+	(strncmp((_str_)->s, (_bstr_)->val, (_bstr_)->len) == 0))
+
+#endif /* __ASI_DIGEST_H__ */

+ 1019 - 0
modules/asi/rpc.c

@@ -0,0 +1,1019 @@
+/*
+ * Copyright (c) 2010 IPTEGO GmbH.
+ * All rights reserved.
+ *
+ * This file is part of sip-router, a free SIP server.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ * 
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY IPTEGO GmbH ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL IPTEGO GmbH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * Author: Bogdan Pintea.
+ */
+
+
+#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 "parser/parse_from.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_GEN_ACK_FLG	= 1 << 0, /* wanna generate ACK */
+	ASIREQ_GET_FIN_FLG	= 1 << 1, /* wanna see final reply */
+	ASIREQ_GET_PRV_FLG	= 1 << 2, /* wanna see provisional reply */
+	ASIREQ_RUN_ORR_FLG	= 1 << 3, /* run 'the' on reply route */
+	ASIREQ_DEL_1ST_FLG	= 1 << 4, /* 1st entry in route set, is the dst */
+};
+
+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)
+{
+#ifdef EXTRA_DEBUG
+	if ((! faked) || (! faked->buf) || (! faked->len)) {
+		BUG("freeing NULL faked 408.\n");
+		abort();
+	}
+#endif
+#ifdef DYN_BUF
+/* DYN_BUF is deprecated (until using threads :-) ) */
+#error "dynamic buffer not supported (anymore?) by ASI module"
+#else
+	pkg_free(faked->buf);
+#endif
+	free_sip_msg(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 inline void copy_transport_meta(sip_msg_t *msg, struct dest_info dst)
+{
+	msg->rcv.src_su = dst.to;
+	if (dst.send_sock) {
+		msg->rcv.bind_address = dst.send_sock;
+		msg->rcv.src_ip = dst.send_sock->address;
+		msg->rcv.src_port = dst.send_sock->port_no;
+		/* the 408 is local, generated and consumed locally. */
+		msg->rcv.dst_ip = msg->rcv.src_ip;
+		msg->rcv.dst_port = msg->rcv.src_port;
+	}
+}
+
+#define TOTAG_MAXLEN	128
+#define TOTAG_PREFIX	"408."
+#define TOTAG_PREFIX_LEN	(sizeof(TOTAG_PREFIX) - /*0-term*/1)
+
+static struct sip_msg *fake_408(struct cell *trans)
+{
+	char *buff_408;
+	unsigned len_408;
+	sip_msg_t orig;
+	static sip_msg_t faked;
+	struct bookmark dummy_bm;
+	char totag_buff[TOTAG_MAXLEN + TOTAG_PREFIX_LEN];
+	str *_to_tag, to_tag;
+
+	DEBUG("faking 408 SIP message for UAC#0/T@%p.\n", trans);
+
+	memset(&orig, 0, sizeof(struct sip_msg));
+	orig.buf = trans->uac[0].request.buffer;
+	orig.len = trans->uac[0].request.buffer_len;
+#ifdef EXTRA_DEBUG
+	assert(0 < orig.len);
+#endif
+	/* needed for parse_msg to work */
+	copy_transport_meta(&orig, trans->uac[0].request.dst);
+	DEBUG("re-parsing original request.\n");
+	if (parse_msg(orig.buf,orig.len, &orig)!=0) {
+		ERR("failed to parse original request that 408'ed.\n");
+		return NULL;
+	}
+
+	/* if don't have To tag, build it by prefixing the From tag with 408.*/
+	if (	(parse_headers(&orig, HDR_TO_F, 0) == 0) && 
+			orig.to && (get_to(&orig)->tag_value.s == NULL) && 
+			(parse_headers(&orig, HDR_FROM_F, 0) == 0) &&
+			/* From must be explicitly parsed (but implicitly freed X-() ) */
+			orig.from && (parse_from_header(&orig) == 0) && 
+			(get_from(&orig)->tag_value.s != NULL)) {
+		int totag_len = get_from(&orig)->tag_value.len < TOTAG_MAXLEN ?
+				get_from(&orig)->tag_value.len : TOTAG_MAXLEN;
+		memcpy(totag_buff, TOTAG_PREFIX, TOTAG_PREFIX_LEN);
+		memcpy(totag_buff + TOTAG_PREFIX_LEN, get_from(&orig)->tag_value.s,
+				totag_len);
+		to_tag.s = totag_buff;
+		to_tag.len = TOTAG_PREFIX_LEN + totag_len;
+		_to_tag = &to_tag;
+	} else {
+		/* copy original (if exists) */
+		_to_tag = NULL;
+	}
+
+	buff_408 = build_res_buf_from_sip_req(408, "Request Timedout", _to_tag, 
+			&orig, &len_408, &dummy_bm);
+	free_sip_msg(&orig);
+	if (! buff_408) {
+		ERR("failed to build 408 reply to timed out request.\n");
+		return NULL;
+	}
+#ifdef EXTRA_DEBUG
+	assert(len_408);
+#endif
+
+	memset(&faked, 0, sizeof(struct sip_msg));
+	faked.buf = buff_408;
+	faked.len = len_408;
+	
+	/* AS might want some info about message's IP level, so we copy/make up
+	 * some metadata here */
+	/* TODO: is this enough/safe? */
+	copy_transport_meta(&faked, trans->uac[0].request.dst);
+
+	DEBUG("parsing faked 408 reply.\n");
+	if (parse_msg(faked.buf,faked.len, &faked) != 0) {
+		ERR("failed to parse faked 408.\n");
+		pkg_free(faked.buf);
+		return NULL;
+	}
+
+	return &faked;
+}
+
+#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_IN:
+#ifdef EXTRA_DEBUG
+			assert(mycbp->flags & ASIREQ_GET_PRV_FLG);
+			assert(cbp->rpl != FAKED_REPLY);
+#endif
+			if (200 <= cbp->rpl->REPLY_STATUS)
+				/* B/c the CB will be later invoked with T._L._COMPLETED 
+				 * TLRI won't filter retransmissions, while TLC will. */
+				return;
+			sipmsg = cbp->rpl;
+			discr = ASI_DGST_ID_PRV;
+			break;
+		case TMCB_LOCAL_COMPLETED:
+#ifdef EXTRA_DEBUG
+			assert(mycbp->flags & ASIREQ_GET_FIN_FLG);
+#endif
+			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;
+	}
+
+	/* this could be done when creating the transaction (by setting the 
+	 * 'on_reply' field), but there is no clean way to do that safely:
+	 * if T obtained from TM HT ID (slot:index), T gets ref'ed (and there is
+	 * no way to unref it w/o a message); if set_t() is used when creating and
+	 * get_t() afterwards, the obtained T might already be terminated/free'd.
+	 */
+	if (mycbp->flags & ASIREQ_RUN_ORR_FLG) {
+		if (onreply_rt_idx < 0) {
+			WARN("reply route flag set, but no route specified as "
+					"module parameter (\"onreply_route\").\n");
+		} else {
+			struct run_act_ctx ra_ctx;
+			int ret;
+			
+			init_run_actions_ctx(&ra_ctx);
+			ret = run_actions(&ra_ctx, onreply_rt.rlist[onreply_rt_idx], 
+					sipmsg);
+			if (ret < 0)
+				ERR("failed to execute reply route for local request "
+								"(failed with %d).\n", ret);
+			else
+				DEBUG("reply route of local request returned with: %d.\n",ret);
+		}
+	}
+	
+	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
+}
+
+
+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
+ * 	route set (string) : SIP URIs, comma&/space separated (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>[])
+ * 	? routes  ( - || - )
+ *
+ * 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, rtset, 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;
+
+
+#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, &rtset, &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_GEN_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_GET_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(rtset, 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;rtset:%.*s;opaque:%.*s):"
+			" %.*s %.*s "
+			"<%.*s,%.*s,%u,%.*s>"
+			" [%.*s] {%.*s}.\n", 
+			as_id, flags, STR_FMT(&rtset), 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;
+
+	if (rtset.len) {
+		if (parse_rr_body(rtset.s, rtset.len, &route_set) != 0) {
+			rpc->fault(ctx, 493, "Failed to parse route set.");
+			DEBUG("failed to parse route set '%.*s'.\n", STR_FMT(&rtset));
+			return;
+		}
+		if (flags & ASIREQ_DEL_1ST_FLG) {
+			dialog.dst_uri = route_set->nameaddr.uri;
+			dialog.route_set = route_set->next;
+		} else {
+			dialog.route_set = route_set;
+		}
+	} else {
+		route_set = 0;
+	}
+
+	if (/*0-term*/1 < headers.len) {
+		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");
+			goto end;
+		}
+		if (flags & ASIREQ_GET_FIN_FLG)
+			cb_flags |= TMCB_LOCAL_COMPLETED;
+		if (flags & ASIREQ_GET_PRV_FLG)
+			cb_flags |= TMCB_LOCAL_RESPONSE_IN;
+		if (flags & ASIREQ_GEN_ACK_FLG)
+			cb_flags |= TMCB_DONT_ACK;
+	} 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 {
+		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(dialog.hooks.next_hop), 
+				STR_FMT(&ruri));
+	}
+
+end:
+	if (route_set)
+		free_rr(&route_set);
+#undef STRIP_0TERM_C
+}
+
+/**
+ * Note: if returns non-0, the returned T is ref'ed!
+ */
+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));
+
+#ifndef E2E_CANCEL_HOP_BY_HOP
+#error "ASI module can only cancel local UACs in E2E_CANCEL_HOP_BY_HOP tm mode"
+#endif
+	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_all_uacs(trans, F_CANCEL_UNREF) < 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;
+	}
+
+	if (is_local(trans)) {
+		rpc->add(ctx, "ds", 400, "Can not reply to a local transaction.");
+		DEBUG("Trying to reply to a locally initiated transaction with "
+				"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, "Replying failed.") < 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}
+};
+

+ 83 - 0
modules/asi/select.c

@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2010 IPTEGO GmbH.
+ * All rights reserved.
+ *
+ * This file is part of sip-router, a free SIP server.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ * 
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY IPTEGO GmbH ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL IPTEGO GmbH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * Author: Bogdan Pintea.
+ */
+
+#include "select.h"
+#include "ut.h"
+//#include "modules/tm/t_cancel.h"
+
+#include "tid.h"
+#include "binds.h"
+
+static int canceled_tid(str* res, select_t* s, struct sip_msg* msg)
+{
+	const static str EMPTY = STR_STATIC_INIT("");
+	unsigned int index, label;
+
+	if (msg->REQ_METHOD != METHOD_CANCEL) {
+		WARN("invoked with non CANCEL method (%d).\n", msg->REQ_METHOD);
+		*res = EMPTY;
+		return 0;
+	}
+	if (tmb.t_get_canceled_ident(msg, &index, &label) < 0) {
+		ERR("failed to lookup CANCEL's original transaction.\n");
+		return -1;
+	} else {
+		*res = *tid2str(index, label);
+	}
+	return 0;
+}
+
+static int current_tid(str* res, select_t* s, struct sip_msg* msg)
+{
+	unsigned int index, label;
+
+	if (tmb.t_get_trans_ident(msg, &index, &label) < 0) {
+		ERR("failed to lookup transaction.\n");
+		return -1;
+	} else {
+		*res = *tid2str(index, label);
+	}
+	return 0;
+}
+
+static int dummy(str* res, select_t* s, struct sip_msg* msg) {return 0;}
+
+select_row_t sel_declaration[] = {
+	{ NULL, SEL_PARAM_STR, STR_STATIC_INIT("asi"), dummy, 
+			SEL_PARAM_EXPECTED},
+	{dummy, SEL_PARAM_STR, STR_STATIC_INIT("tid"), dummy, 
+			SEL_PARAM_EXPECTED},
+	{dummy, SEL_PARAM_STR, STR_STATIC_INIT("canceled"), canceled_tid, 0},
+	{dummy, SEL_PARAM_STR, STR_STATIC_INIT("current"), current_tid, 0},
+
+	/* marks end of table */
+	{ NULL, SEL_PARAM_INT, STR_NULL, NULL, 0}
+};

+ 170 - 0
modules/asi/strutil.h

@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2010 IPTEGO GmbH.
+ * All rights reserved.
+ *
+ * This file is part of sip-router, a free SIP server.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ * 
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY IPTEGO GmbH ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL IPTEGO GmbH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * Author: Bogdan Pintea.
+ */
+
+#ifndef __ASI_STRUTIL_H__
+#define __ASI_STRUTIL_H__
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "mem/mem.h"
+#include "mem/shm_mem.h"
+#include "str.h"
+
+//strclone_shm
+enum STR_CLONE_FL {
+	STR_CLONE_PKG	= 1 << 0, /* alloc in private mem */
+	STR_CLONE_SHM	= 1 << 1, /* alloc in private mem */
+	STR_CLONE_TERM	= 1 << 2, /* add 0-terminator to clone */
+	STR_CLONE_TRIM	= 1 << 3, /* strip 0-terminator from clone */
+};
+
+#define _chr_clone(FUNC, _src_, _dst_, _alloc, _copy) \
+	do { \
+		_dst_ = (char *)FUNC(_alloc * sizeof(char)); \
+		if (! (_dst_)) { \
+			errno = ENOMEM; \
+			_dst_ = NULL; \
+		} else { \
+			memcpy(_dst_, _src_, _copy); \
+		} \
+	} while (0)
+
+static inline char *_chr_clone_pkg(char *orig, size_t alloc, size_t copy)
+{
+	char *dup;
+	_chr_clone(pkg_malloc, orig, dup, alloc, copy);
+	return dup;
+}
+
+static inline char *_chr_clone_shm(char *orig, size_t alloc, size_t copy)
+{
+	char *dup;
+	_chr_clone(shm_malloc, orig, dup, alloc, copy);
+	return dup;
+}
+
+static inline int strclo(char *orig, size_t olen, str *dup, unsigned flags)
+{
+	size_t alloc, copy;
+
+#define init_alloc_copy(_val)	copy = alloc = _val
+#define init_alloc(_val)		alloc = _val
+#define clone_str(FUNC) \
+	do { \
+		init_alloc(olen); \
+		if (! (dup->s = FUNC(orig, alloc, alloc))) {  \
+			errno = ENOMEM; \
+			return -1; \
+		} \
+	} while (0)
+#define clone_term(FUNC) \
+	do { \
+		init_alloc_copy(olen); \
+		alloc ++; \
+		if (! (dup->s = FUNC(orig, alloc, copy))) { \
+			errno = ENOMEM; \
+			return -1; \
+		} \
+		dup->s[copy] = 0; \
+	} while (0)
+#define clone_trim(FUNC) \
+	do { \
+		init_alloc(olen - 1); \
+		if (! (dup->s = FUNC(orig, alloc, alloc))) { \
+			errno = ENOMEM; \
+			return -1; \
+		} \
+	} while (0)
+
+	switch (flags) {
+		case STR_CLONE_PKG: clone_str(_chr_clone_pkg); break;
+		case STR_CLONE_PKG | STR_CLONE_TERM: clone_term(_chr_clone_pkg); break;
+		case STR_CLONE_PKG | STR_CLONE_TRIM: clone_trim(_chr_clone_pkg); break;
+
+#if defined(SHM_MEM) && defined(USE_SHM_MEM)
+		case STR_CLONE_SHM: clone_str(_chr_clone_shm); break;
+		case STR_CLONE_SHM | STR_CLONE_TERM: clone_term(_chr_clone_shm); break;
+		case STR_CLONE_SHM | STR_CLONE_TRIM: clone_trim(_chr_clone_shm); break;
+#endif
+
+		default:
+			errno = EINVAL;
+			return -1;
+	}
+	dup->len = alloc;
+	return 0;
+#undef init_alloc_copy
+#undef init_alloc
+#undef clone_str
+#undef clone_term
+#undef clone_trim
+}
+
+static inline int strdclo(char *orig, size_t olen, str **dup, unsigned flags)
+{
+	switch (flags) {
+		case STR_CLONE_PKG:
+		case STR_CLONE_PKG | STR_CLONE_TERM:
+		case STR_CLONE_PKG | STR_CLONE_TRIM:
+			if (! (dup = (str **)pkg_malloc(sizeof(str *)))) {
+				errno = ENOMEM;
+				return -1;
+			}
+			if (strclo(orig, olen, *dup, flags) < 0) {
+				pkg_free(dup);
+				return -1;
+			}
+			break;
+
+#if defined(SHM_MEM) && defined(USE_SHM_MEM)
+		case STR_CLONE_SHM:
+		case STR_CLONE_SHM | STR_CLONE_TERM:
+		case STR_CLONE_SHM | STR_CLONE_TRIM:
+			if (! (dup = (str **)shm_malloc(sizeof(str *)))) {
+				errno = ENOMEM;
+				return -1;
+			}
+			if (strclo(orig, olen, *dup, flags) < 0) {
+				shm_free(dup);
+				return -1;
+			}
+			break;
+#endif
+		default:
+			errno = EINVAL;
+			return -1;
+	}
+	return 0;
+}
+
+#endif /*__ASI_STRUTIL_H__ */

+ 15 - 0
modules/asi/test/Makefile

@@ -0,0 +1,15 @@
+
+include ../../../Makefile.defs
+
+CFLAGS += -g -I../../../lib/binrpc/binrpc/include/ -L../../../lib/binrpc/binrpc/src/
+CFLAGS += -DNODEBUG
+
+LDLIBS += -lbinrpc -lpthread
+
+all: srv
+
+re: clean all
+
+clean:
+	rm -rf srv
+

+ 211 - 0
modules/asi/test/srv.c

@@ -0,0 +1,211 @@
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <pthread.h>
+#include <time.h>
+
+#include <binrpc.h>
+
+#define ERREXIT(msg, args...) \
+	do { \
+		fprintf(stderr, "ERROR (%d): " msg " (%s [%d]).\n", __LINE__, \
+				strerror(errno), errno, ##args); \
+		exit (-1); \
+	} while (0)
+
+const BRPC_STR_STATIC_INIT(ASIVer, "ASI_Version");
+const BRPC_STR_STATIC_INIT(SetASId, "Set_AS_ID");
+const BRPC_STR_STATIC_INIT(SIPMeth, "SIP_Methods");
+const BRPC_STR_STATIC_INIT(DGSTFor, "Digest_For");
+const BRPC_STR_STATIC_INIT(INVITE, "INVITE");
+const BRPC_STR_STATIC_INIT(ACK, "ACK");
+
+static int as_id = -1;
+static int id_age;
+
+brpc_t *asi_version(brpc_t *req)
+{
+	brpc_int_t *ver = NULL;
+	brpc_t *rpl;
+	brpc_val_t *val;
+
+	assert(brpc_dsm(req, "d", &ver));
+	assert(ver);
+	assert(*ver == 1);
+
+	assert(	(rpl = brpc_rpl(req)) &&
+			(val = brpc_int(1)) &&
+			brpc_add_val(rpl, val));
+	return rpl;
+}
+
+brpc_t *set_as_id(brpc_t *req)
+{
+	int *loc_as_id;
+	brpc_t *rpl;
+	brpc_val_t *val;
+	int old_age;
+
+	assert(	brpc_dsm(req, "d", &loc_as_id) &&
+			loc_as_id);
+	assert(loc_as_id);
+	if (as_id < 0) {
+		as_id = *loc_as_id;
+		id_age = (int)time(NULL);
+		old_age = id_age;
+	} else {
+		old_age = id_age;
+		if (as_id != *loc_as_id) {
+			id_age = (int)time(NULL);
+			printf("updating ID: previous=%d, new=%d; ts=%d.\n", 
+					as_id, *loc_as_id, id_age);
+			as_id = *loc_as_id;
+		}
+	}
+
+	assert(	(rpl = brpc_rpl(req)) &&
+			(val = brpc_int(old_age)) &&
+			brpc_add_val(rpl, val));
+	return rpl;
+}
+
+brpc_t *sip_methods(brpc_t *req)
+{
+	brpc_t *rpl = NULL;
+	char *methods[] = {"INVITE", "ACK", "BYE", "OPTIONS", NULL};
+	int i;
+
+	assert(! brpc_val_cnt(req));
+	assert((rpl = brpc_rpl(req)));
+	for (i = 0; methods[i]; i ++)
+		assert(brpc_asm(rpl, "c", methods[i]));
+	return rpl;
+}
+
+brpc_t *digest_for(brpc_t *req)
+{
+	brpc_t *rpl = NULL;
+	brpc_str_t *mname = NULL;
+
+	assert(brpc_dsm(req, "s", &mname));
+	assert(mname);
+
+	assert((rpl = brpc_rpl(req)));
+	assert(brpc_asm(rpl, "{ <c:[cccc dss]>, <c:[ccc]>, <c:[cc]> }", 
+			"1", "%ru", "%ci", "$cucu", "@hf_value.contact[\"*\"]", -1, mname, NULL,
+			"2", "%rs", "%rr", "%tt",
+			"3", "%rs", "%rr"));
+	return rpl;
+}
+
+brpc_t *invite(brpc_t *req)
+{
+	brpc_t *rpl;
+	char buff[1024];
+	brpc_print(req, buff, sizeof(buff));
+	printf("request: %s.\n", buff);
+	assert((rpl = brpc_rpl(req)));
+	assert(brpc_asm(rpl, "dc", 100, "OK"));
+	return rpl;
+}
+
+brpc_t *(*ack)(brpc_t *req) = invite;
+
+static void logbrpc(int level, const char *fmt, ...)
+{
+	va_list ap;
+
+#ifdef NODEBUG //NDEBUG cuts assert()'s out
+	if (LOG_INFO < (level & 0x7))
+		return;
+#endif
+
+	va_start(ap, fmt);
+	vprintf(fmt, ap);
+	va_end(ap);
+}
+
+void *worker(void *param)
+{
+	pid_t tid;
+	int cltfd;
+	brpc_t *req, *rpl;
+	
+	cltfd = (int)param;
+	tid = pthread_self();
+	while ((req = brpc_recv(cltfd, 0))) {
+		printf("[%u]: new request (%.*s) received.\n", tid,
+				BRPC_STR_FMT(brpc_method(req)));
+		assert((rpl = brpc_cb_run(req)));
+		assert(brpc_send(cltfd, rpl, 30*1000));
+		brpc_finish(req);
+		brpc_finish(rpl);
+	}
+	printf("[%u]: connection to client closed.\n", tid);
+	close(cltfd);
+	return NULL;
+}
+
+int main(int argc, char **argv)
+{
+	char *uri;
+	brpc_addr_t *addr, from;
+	int sockfd, cltfd;
+	pthread_t tid;
+
+	if (argc < 2) {
+		printf("USAGE: %s <uri>\n", argv[0]);
+		return 1;
+	} else {
+		uri = argv[1];
+		printf("Using URI: <%s>.\n", uri);
+	}
+
+	brpc_log_setup(logbrpc);
+
+	if (! (addr = brpc_parse_uri(uri)))
+		ERREXIT("failed to parse URI: %s [%d]", brpc_strerror(), brpc_errno);
+	if (addr->socktype == SOCK_DGRAM)
+		ERREXIT("will not use dgram sockets.\n");
+
+	if (! brpc_cb_init(10, /*no req*/0))
+		ERREXIT("failed to init callbacks: %s [%d]", brpc_strerror(), 
+				brpc_errno);
+
+	if (! (	brpc_cb_req(ASIVer.val, NULL, asi_version, NULL) &&
+			brpc_cb_req(SetASId.val, NULL, set_as_id, NULL) &&
+			brpc_cb_req(SIPMeth.val, NULL, sip_methods, NULL) &&
+			brpc_cb_req(DGSTFor.val, NULL, digest_for, NULL) &&
+			brpc_cb_req(INVITE.val, NULL, invite, NULL) &&
+			brpc_cb_req(ACK.val, NULL, ack, NULL)))
+		ERREXIT("failed to register REQ callbacks: %s [%d]", brpc_strerror(),
+				brpc_errno);
+
+	as_id = -1;
+	id_age = (int)time(NULL);
+	if ((sockfd = brpc_socket(addr, /*block*/true, /*bind*/true)) < 0)
+		ERREXIT("failed to get listening socket: %s [%d]", brpc_strerror(), 
+				brpc_errno);
+	if ((listen(sockfd, 10)) < 0)
+		ERREXIT("failed to listening on socket: %s [%d]", strerror(errno), 
+				errno);
+
+	while (true) {
+		from.addrlen = sizeof(from.sockaddr);
+		cltfd = accept(sockfd, (struct sockaddr *)&from.sockaddr, 
+				&from.addrlen);
+		if (cltfd < 0)
+			ERREXIT("failed to accept client");
+		assert(! pthread_create(&tid, NULL, &worker, (void *)cltfd));
+		printf("[%lu]: new connection.\n", (unsigned long)tid);
+	}
+
+	close(sockfd);
+	brpc_cb_close();
+	return 0;
+}

+ 56 - 0
modules/asi/tid.h

@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2010 IPTEGO GmbH.
+ * All rights reserved.
+ *
+ * This file is part of sip-router, a free SIP server.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ * 
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY IPTEGO GmbH ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL IPTEGO GmbH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * Author: Bogdan Pintea.
+ */
+
+#ifndef __ASI_TID_H__
+#define __ASI_TID_H__
+
+#include "ut.h"
+
+inline static str *tid2str(unsigned int h_index, unsigned int h_label)
+{
+	static char buff[2 * INT2STR_MAX_LEN + /* `:' */1 + /* 0-term */1];
+	static str tid_str = {buff, sizeof(buff)};
+	char *cursor, *intstr;
+	int num_len;
+
+	cursor = buff;
+	intstr = int2str(h_index, &num_len);
+	memcpy(cursor, intstr, num_len); cursor += num_len;
+	cursor[0] = ':'; cursor += 1;
+	intstr = int2str(h_label, &num_len);
+	memcpy(cursor, intstr, num_len); cursor += num_len;
+	cursor[0] = 0; cursor += 1;
+
+	tid_str.len = cursor - buff;
+	return &tid_str;
+}
+
+#endif /* __ASI_TID_H__ */