/**
 * Copyright (C) 2017 kamailio.org
 * Copyright (C) 2017 flowroute.com
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 *
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*!
 * \file
 * \brief Kamailio topos ::
 * \ingroup topos
 * Module: \ref topos
 */

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

#include "../../core/dprint.h"
#include "../../core/ut.h"

#include "../ndb_redis/api.h"
#include "../topos/api.h"

#include "topos_redis_storage.h"


extern str _topos_redis_serverid;
extern ndb_redis_api_t _tps_redis_api;
extern topos_api_t _tps_api;

static str _tps_redis_bprefix = str_init("b:x:");
static str _tps_redis_dprefix = str_init("d:z:");

// void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);

#define TPS_REDIS_NR_KEYS 48
#define TPS_REDIS_DATA_SIZE 8192

static char _tps_redis_cbuf[TPS_REDIS_DATA_SIZE];

static str _tps_redis_empty = str_init("");

#define TPS_REDIS_STRZ(_s) ((_s).s) ? (_s) : (_tps_redis_empty)


/**
 * storage keys
 */
str td_key_rectime = str_init("rectime");
str td_key_a_callid = str_init("a_callid");
str td_key_a_uuid = str_init("a_uuid");
str td_key_b_uuid = str_init("b_uuid");
str td_key_a_contact = str_init("a_contact");
str td_key_b_contact = str_init("b_contact");
str td_key_as_contact = str_init("as_contact");
str td_key_bs_contact = str_init("bs_contact");
str td_key_a_tag = str_init("a_tag");
str td_key_b_tag = str_init("b_tag");
str td_key_a_rr = str_init("a_rr");
str td_key_b_rr = str_init("b_rr");
str td_key_s_rr = str_init("s_rr");
str td_key_iflags = str_init("iflags");
str td_key_a_uri = str_init("a_uri");
str td_key_b_uri = str_init("b_uri");
str td_key_r_uri = str_init("r_uri");
str td_key_a_srcaddr = str_init("a_srcaddr");
str td_key_b_srcaddr = str_init("b_srcaddr");
str td_key_s_method = str_init("s_method");
str td_key_s_cseq = str_init("s_cseq");
str td_key_x_context = str_init("x_context");

str tt_key_rectime = str_init("rectime");
str tt_key_a_callid = str_init("a_callid");
str tt_key_a_uuid = str_init("a_uuid");
str tt_key_b_uuid = str_init("b_uuid");
str tt_key_direction = str_init("direction");
str tt_key_x_via = str_init("x_via");
str tt_key_x_vbranch = str_init("x_vbranch");
str tt_key_x_rr = str_init("x_rr");
str tt_key_y_rr = str_init("y_rr");
str tt_key_s_rr = str_init("s_rr");
str tt_key_x_uri = str_init("x_uri");
str tt_key_a_contact = str_init("a_contact");
str tt_key_b_contact = str_init("b_contact");
str tt_key_as_contact = str_init("as_contact");
str tt_key_bs_contact = str_init("bs_contact");
str tt_key_x_tag = str_init("x_tag");
str tt_key_a_tag = str_init("a_tag");
str tt_key_b_tag = str_init("b_tag");
str tt_key_s_method = str_init("s_method");
str tt_key_s_cseq = str_init("s_cseq");
str tt_key_x_context = str_init("x_context");

#define TPS_REDIS_SET_ARGSV(sval, argc, argv, argvlen) \
	do {                                               \
		if((sval)->s != NULL && (sval)->len > 0) {     \
			argv[argc] = (sval)->s;                    \
			argvlen[argc] = (sval)->len;               \
			argc++;                                    \
		}                                              \
	} while(0)

#define TPS_REDIS_SET_ARGS(sval, argc, akey, argv, argvlen) \
	do {                                                    \
		if((sval)->s != NULL && (sval)->len > 0) {          \
			argv[argc] = (akey)->s;                         \
			argvlen[argc] = (akey)->len;                    \
			argc++;                                         \
			argv[argc] = (sval)->s;                         \
			argvlen[argc] = (sval)->len;                    \
			argc++;                                         \
		}                                                   \
	} while(0)

#define TPS_REDIS_SET_ARGSX(sval, argc, akey, argv, argvlen)     \
	do {                                                         \
		if((sval) != NULL) {                                     \
			TPS_REDIS_SET_ARGS(sval, argc, akey, argv, argvlen); \
		}                                                        \
	} while(0)

#define TPS_REDIS_SET_ARGN(nval, rp, sval, argc, akey, argv, argvlen) \
	do {                                                              \
		(sval)->s = int2bstr((unsigned long)nval, rp, &(sval)->len);  \
		rp = (sval)->s + (sval)->len + 1;                             \
		TPS_REDIS_SET_ARGS((sval), argc, akey, argv, argvlen);        \
	} while(0)

#define TPS_REDIS_SET_ARGNV(nval, rp, sval, argc, argv, argvlen)     \
	do {                                                             \
		(sval)->s = int2bstr((unsigned long)nval, rp, &(sval)->len); \
		rp = (sval)->s + (sval)->len + 1;                            \
		TPS_REDIS_SET_ARGSV((sval), argc, argv, argvlen);            \
	} while(0)


/**
 *
 */
int tps_redis_insert_dialog(tps_data_t *td)
{
	char *argv[TPS_REDIS_NR_KEYS];
	size_t argvlen[TPS_REDIS_NR_KEYS];
	int argc = 0;
	str rcmd = str_init("HMSET");
	str rkey = STR_NULL;
	char *rp;
	str rval = STR_NULL;
	redisc_server_t *rsrv = NULL;
	redisReply *rrpl = NULL;
	unsigned long lval = 0;

	if(td->a_uuid.len <= 0 && td->b_uuid.len <= 0) {
		LM_INFO("no uuid for this message\n");
		return -1;
	}

	rsrv = _tps_redis_api.get_server(&_topos_redis_serverid);
	if(rsrv == NULL) {
		LM_ERR("cannot find redis server [%.*s]\n", _topos_redis_serverid.len,
				_topos_redis_serverid.s);
		return -1;
	}

	memset(argv, 0, TPS_REDIS_NR_KEYS * sizeof(char *));
	memset(argvlen, 0, TPS_REDIS_NR_KEYS * sizeof(size_t));
	argc = 0;

	rp = _tps_redis_cbuf;
	memcpy(rp, _tps_redis_dprefix.s, _tps_redis_dprefix.len);

	if(td->a_uuid.len > 0) {
		memcpy(rp + _tps_redis_dprefix.len, td->a_uuid.s, td->a_uuid.len);
		if(td->a_uuid.s[0] == 'b') {
			rp[_tps_redis_dprefix.len] = 'a';
		}
		rp[_tps_redis_dprefix.len + td->a_uuid.len] = '\0';
		rkey.s = rp;
		rkey.len = _tps_redis_dprefix.len + td->a_uuid.len;
		rp += _tps_redis_dprefix.len + td->a_uuid.len + 1;
	} else {
		memcpy(rp + _tps_redis_dprefix.len, td->b_uuid.s, td->b_uuid.len);
		if(td->b_uuid.s[0] == 'b') {
			rp[_tps_redis_dprefix.len] = 'a';
		}
		rp[_tps_redis_dprefix.len + td->b_uuid.len] = '\0';
		rkey.s = rp;
		rkey.len = _tps_redis_dprefix.len + td->b_uuid.len;
		rp += _tps_redis_dprefix.len + td->b_uuid.len + 1;
	}

	argv[argc] = rcmd.s;
	argvlen[argc] = rcmd.len;
	argc++;

	argv[argc] = rkey.s;
	argvlen[argc] = rkey.len;
	argc++;

	lval = (unsigned long)time(NULL);
	TPS_REDIS_SET_ARGN(lval, rp, &rval, argc, &td_key_rectime, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->a_callid, argc, &td_key_a_callid, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->a_uuid, argc, &td_key_a_uuid, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->b_uuid, argc, &td_key_b_uuid, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->a_contact, argc, &td_key_a_contact, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->b_contact, argc, &td_key_b_contact, argv, argvlen);

	TPS_REDIS_SET_ARGS(
			&td->as_contact, argc, &td_key_as_contact, argv, argvlen);
	TPS_REDIS_SET_ARGS(
			&td->bs_contact, argc, &td_key_bs_contact, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->a_tag, argc, &td_key_a_tag, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->b_tag, argc, &td_key_b_tag, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->a_rr, argc, &td_key_a_rr, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->b_rr, argc, &td_key_b_rr, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->s_rr, argc, &td_key_s_rr, argv, argvlen);

	TPS_REDIS_SET_ARGN(
			td->iflags, rp, &rval, argc, &td_key_iflags, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->a_uri, argc, &td_key_a_uri, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->b_uri, argc, &td_key_b_uri, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->r_uri, argc, &td_key_r_uri, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->a_srcaddr, argc, &td_key_a_srcaddr, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->b_srcaddr, argc, &td_key_b_srcaddr, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->s_method, argc, &td_key_s_method, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->s_cseq, argc, &td_key_s_cseq, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->x_context, argc, &td_key_x_context, argv, argvlen);

	rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
	if(rrpl == NULL) {
		LM_ERR("failed to execute redis command\n");
		if(rsrv->ctxRedis->err) {
			LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
		}
		return -1;
	}
	LM_DBG("inserted dialog record for [%.*s] with argc %d\n", rkey.len, rkey.s,
			argc);
	freeReplyObject(rrpl);

	/* set expire for the key */
	argc = 0;

	argv[argc] = "EXPIRE";
	argvlen[argc] = 6;
	argc++;

	argv[argc] = rkey.s;
	argvlen[argc] = rkey.len;
	argc++;

	if(td->s_method.len == 9 && strncmp(td->s_method.s, "SUBSCRIBE", 9) == 0) {
		lval = (unsigned long)td->expires;
	} else {
		lval = (unsigned long)_tps_api.get_dialog_expire();
	}

	if(lval == 0) {
		return 0;
	}
	TPS_REDIS_SET_ARGNV(lval, rp, &rval, argc, argv, argvlen);

	rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
	if(rrpl == NULL) {
		LM_ERR("failed to execute expire redis command\n");
		if(rsrv->ctxRedis->err) {
			LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
		}
		return -1;
	}
	LM_DBG("expire %lu set on dialog record for [%.*s] with argc %d\n", lval,
			rkey.len, rkey.s, argc);
	freeReplyObject(rrpl);

	return 0;
}

/**
 *
 */
int tps_redis_clean_dialogs(void)
{
	return 0;
}

/**
 *
 */
int tps_redis_insert_initial_method_branch(tps_data_t *md, tps_data_t *sd)
{
	char *argv[TPS_REDIS_NR_KEYS];
	size_t argvlen[TPS_REDIS_NR_KEYS];
	int argc = 0;
	str rcmd = str_init("HMSET");
	str rkey = STR_NULL;
	char *rp;
	str rval = STR_NULL;
	redisc_server_t *rsrv = NULL;
	redisReply *rrpl = NULL;
	unsigned long lval = 0;
	str xuuid = str_init("");

	if(md->x_vbranch1.len <= 0) {
		LM_INFO("no via branch for this message\n");
		return -1;
	}

	rsrv = _tps_redis_api.get_server(&_topos_redis_serverid);
	if(rsrv == NULL) {
		LM_ERR("cannot find redis server [%.*s]\n", _topos_redis_serverid.len,
				_topos_redis_serverid.s);
		return -1;
	}

	memset(argv, 0, TPS_REDIS_NR_KEYS * sizeof(char *));
	memset(argvlen, 0, TPS_REDIS_NR_KEYS * sizeof(size_t));
	argc = 0;

	if(md->a_uuid.len > 1) {
		xuuid.s = md->a_uuid.s + 1;
		xuuid.len = md->a_uuid.len - 1;
	} else if(md->b_uuid.len > 1) {
		xuuid.s = md->b_uuid.s + 1;
		xuuid.len = md->b_uuid.len - 1;
	} else if(sd->a_uuid.len > 1) {
		xuuid.s = sd->a_uuid.s + 1;
		xuuid.len = sd->a_uuid.len - 1;
	} else if(sd->b_uuid.len > 1) {
		xuuid.s = sd->b_uuid.s + 1;
		xuuid.len = sd->b_uuid.len - 1;
	}

	rp = _tps_redis_cbuf;
	rkey.len = snprintf(rp, TPS_REDIS_DATA_SIZE - 128,
			"%.*s%.*s:%.*s:%.*s:x%.*s", _tps_redis_bprefix.len,
			_tps_redis_bprefix.s, md->s_method.len, md->s_method.s,
			md->a_callid.len, md->a_callid.s, md->b_tag.len, md->b_tag.s,
			xuuid.len, xuuid.s);
	if(rkey.len < 0 || rkey.len >= TPS_REDIS_DATA_SIZE - 128) {
		LM_ERR("error or insufficient buffer size: %d\n", rkey.len);
		return -1;
	}
	rkey.s = rp;
	rp += rkey.len + 1;

	argv[argc] = rcmd.s;
	argvlen[argc] = rcmd.len;
	argc++;

	argv[argc] = rkey.s;
	argvlen[argc] = rkey.len;
	argc++;

	lval = (unsigned long)time(NULL);
	TPS_REDIS_SET_ARGN(lval, rp, &rval, argc, &tt_key_rectime, argv, argvlen);
	TPS_REDIS_SET_ARGS(&md->x_vbranch1, argc, &tt_key_x_vbranch, argv, argvlen);

	rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
	if(rrpl == NULL) {
		LM_ERR("failed to execute redis command\n");
		if(rsrv->ctxRedis->err) {
			LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
		}
		return -1;
	}
	LM_DBG("inserting initial %.*s branch record for [%.*s] with argc %d\n",
			md->s_method.len, md->s_method.s, rkey.len, rkey.s, argc);

	freeReplyObject(rrpl);

	/* set expire for the key */
	argc = 0;

	argv[argc] = "EXPIRE";
	argvlen[argc] = 6;
	argc++;

	argv[argc] = rkey.s;
	argvlen[argc] = rkey.len;
	argc++;

	lval = (unsigned long)_tps_api.get_branch_expire();
	if(lval == 0) {
		return 0;
	}
	TPS_REDIS_SET_ARGNV(lval, rp, &rval, argc, argv, argvlen);

	rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
	if(rrpl == NULL) {
		LM_ERR("failed to execute expire redis command\n");
		if(rsrv->ctxRedis->err) {
			LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
		}
		return -1;
	}
	LM_DBG("expire %lu set on branch record for [%.*s] with argc %d\n", lval,
			rkey.len, rkey.s, argc);
	freeReplyObject(rrpl);

	return 0;
}


/**
 *
 */
int tps_redis_insert_branch(tps_data_t *td)
{
	char *argv[TPS_REDIS_NR_KEYS];
	size_t argvlen[TPS_REDIS_NR_KEYS];
	int argc = 0;
	str rcmd = str_init("HMSET");
	str rkey = STR_NULL;
	char *rp;
	str rval = STR_NULL;
	redisc_server_t *rsrv = NULL;
	redisReply *rrpl = NULL;
	unsigned long lval = 0;

	if(td->x_vbranch1.len <= 0) {
		LM_INFO("no via branch for this message\n");
		return -1;
	}

	rsrv = _tps_redis_api.get_server(&_topos_redis_serverid);
	if(rsrv == NULL) {
		LM_ERR("cannot find redis server [%.*s]\n", _topos_redis_serverid.len,
				_topos_redis_serverid.s);
		return -1;
	}

	memset(argv, 0, TPS_REDIS_NR_KEYS * sizeof(char *));
	memset(argvlen, 0, TPS_REDIS_NR_KEYS * sizeof(size_t));
	argc = 0;

	rp = _tps_redis_cbuf;
	memcpy(rp, _tps_redis_bprefix.s, _tps_redis_bprefix.len);
	memcpy(rp + _tps_redis_bprefix.len, td->x_vbranch1.s, td->x_vbranch1.len);
	rp[_tps_redis_bprefix.len + td->x_vbranch1.len] = '\0';
	rkey.s = rp;
	rkey.len = _tps_redis_bprefix.len + td->x_vbranch1.len;
	rp += _tps_redis_bprefix.len + td->x_vbranch1.len + 1;

	argv[argc] = rcmd.s;
	argvlen[argc] = rcmd.len;
	argc++;

	argv[argc] = rkey.s;
	argvlen[argc] = rkey.len;
	argc++;

	lval = (unsigned long)time(NULL);
	TPS_REDIS_SET_ARGN(lval, rp, &rval, argc, &tt_key_rectime, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->a_callid, argc, &tt_key_a_callid, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->a_uuid, argc, &tt_key_a_uuid, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->b_uuid, argc, &tt_key_b_uuid, argv, argvlen);

	TPS_REDIS_SET_ARGN(
			td->direction, rp, &rval, argc, &tt_key_direction, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->x_via, argc, &tt_key_x_via, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->x_vbranch1, argc, &tt_key_x_vbranch, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->x_rr, argc, &tt_key_x_rr, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->y_rr, argc, &tt_key_y_rr, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->s_rr, argc, &tt_key_s_rr, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->x_uri, argc, &tt_key_x_uri, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->x_tag, argc, &tt_key_x_tag, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->s_method, argc, &tt_key_s_method, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->s_cseq, argc, &tt_key_s_cseq, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->a_contact, argc, &tt_key_a_contact, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->b_contact, argc, &tt_key_b_contact, argv, argvlen);
	TPS_REDIS_SET_ARGS(
			&td->as_contact, argc, &tt_key_as_contact, argv, argvlen);
	TPS_REDIS_SET_ARGS(
			&td->bs_contact, argc, &tt_key_bs_contact, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->a_tag, argc, &tt_key_a_tag, argv, argvlen);
	TPS_REDIS_SET_ARGS(&td->b_tag, argc, &tt_key_b_tag, argv, argvlen);

	TPS_REDIS_SET_ARGS(&td->x_context, argc, &tt_key_x_context, argv, argvlen);

	rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
	if(rrpl == NULL) {
		LM_ERR("failed to execute redis command\n");
		if(rsrv->ctxRedis->err) {
			LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
		}
		return -1;
	}
	LM_DBG("inserting branch record for [%.*s] with argc %d\n", rkey.len,
			rkey.s, argc);

	freeReplyObject(rrpl);

	/* set expire for the key */
	argc = 0;

	argv[argc] = "EXPIRE";
	argvlen[argc] = 6;
	argc++;

	argv[argc] = rkey.s;
	argvlen[argc] = rkey.len;
	argc++;

	lval = (unsigned long)_tps_api.get_branch_expire();
	if(lval == 0) {
		return 0;
	}
	TPS_REDIS_SET_ARGNV(lval, rp, &rval, argc, argv, argvlen);

	rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
	if(rrpl == NULL) {
		LM_ERR("failed to execute expire redis command\n");
		if(rsrv->ctxRedis->err) {
			LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
		}
		return -1;
	}
	LM_DBG("expire %lu set on branch record for [%.*s] with argc %d\n", lval,
			rkey.len, rkey.s, argc);
	freeReplyObject(rrpl);

	return 0;
}

/**
 *
 */
int tps_redis_clean_branches(void)
{
	return 0;
}

#define TPS_REDIS_DATA_APPEND(_sd, _k, _v, _r)                         \
	do {                                                               \
		if((_sd)->cp + (_v)->len >= (_sd)->cbuf + TPS_DATA_SIZE) {     \
			LM_ERR("not enough space for %.*s\n", (_k)->len, (_k)->s); \
			goto error;                                                \
		}                                                              \
		if((_v)->len > 0) {                                            \
			(_r)->s = (_sd)->cp;                                       \
			(_r)->len = (_v)->len;                                     \
			memcpy((_sd)->cp, (_v)->s, (_v)->len);                     \
			(_sd)->cp += (_v)->len;                                    \
			(_sd)->cp[0] = '\0';                                       \
			(_sd)->cp++;                                               \
		}                                                              \
	} while(0)

/**
 *
 */
int tps_redis_load_initial_method_branch(
		sip_msg_t *msg, tps_data_t *md, tps_data_t *sd)
{
	char *argv[TPS_REDIS_NR_KEYS];
	size_t argvlen[TPS_REDIS_NR_KEYS];
	int argc = 0;
	str rcmd = str_init("HGETALL");
	str rkey = STR_NULL;
	char *rp;
	int i;
	redisc_server_t *rsrv = NULL;
	redisReply *rrpl = NULL;
	str skey = STR_NULL;
	str sval = STR_NULL;
	str xuuid = str_init("");
	str xtag = str_init("");
	str smethod = str_init("INVITE");

	if(msg == NULL || md == NULL || sd == NULL)
		return -1;

	if(md->a_callid.len <= 0 || md->b_tag.len <= 0) {
		LM_INFO("no call-id or to-tag for this message\n");
		return -1;
	}

	rsrv = _tps_redis_api.get_server(&_topos_redis_serverid);
	if(rsrv == NULL) {
		LM_ERR("cannot find redis server [%.*s]\n", _topos_redis_serverid.len,
				_topos_redis_serverid.s);
		return -1;
	}

	memset(argv, 0, TPS_REDIS_NR_KEYS * sizeof(char *));
	memset(argvlen, 0, TPS_REDIS_NR_KEYS * sizeof(size_t));
	argc = 0;

	if(md->direction == TPS_DIR_DOWNSTREAM) {
		xtag = TPS_REDIS_STRZ(md->b_tag);
	} else {
		xtag = TPS_REDIS_STRZ(md->a_tag);
	}
	if(md->a_uuid.len > 1) {
		xuuid.s = md->a_uuid.s + 1;
		xuuid.len = md->a_uuid.len - 1;
	} else if(md->b_uuid.len > 1) {
		xuuid.s = md->b_uuid.s + 1;
		xuuid.len = md->b_uuid.len - 1;
	} else if(sd->a_uuid.len > 1) {
		xuuid.s = sd->a_uuid.s + 1;
		xuuid.len = sd->a_uuid.len - 1;
	} else if(sd->b_uuid.len > 1) {
		xuuid.s = sd->b_uuid.s + 1;
		xuuid.len = sd->b_uuid.len - 1;
	}

	if(md->s_method_id & (METHOD_SUBSCRIBE | METHOD_NOTIFY)) {
		smethod.s = "SUBSCRIBE";
		smethod.len = 9;
	}

	rp = _tps_redis_cbuf;

	rkey.len = snprintf(rp, TPS_REDIS_DATA_SIZE, "%.*s%.*s:%.*s:%.*s:x%.*s",
			_tps_redis_bprefix.len, _tps_redis_bprefix.s, smethod.len,
			smethod.s, md->a_callid.len, md->a_callid.s, xtag.len, xtag.s,
			xuuid.len, xuuid.s);
	if(rkey.len < 0 || rkey.len >= TPS_REDIS_DATA_SIZE) {
		LM_ERR("error or insufficient buffer size: %d\n", rkey.len);
		return -1;
	}
	rkey.s = rp;
	rp += rkey.len + 1;

	argv[argc] = rcmd.s;
	argvlen[argc] = rcmd.len;
	argc++;

	argv[argc] = rkey.s;
	argvlen[argc] = rkey.len;
	argc++;

	LM_DBG("loading initial %.*s branch record for [%.*s]\n", md->s_method.len,
			md->s_method.s, rkey.len, rkey.s);

	rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
	if(rrpl == NULL) {
		LM_ERR("failed to execute redis command\n");
		if(rsrv->ctxRedis->err) {
			LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
		}
		return -1;
	}

	if(rrpl->type != REDIS_REPLY_ARRAY) {
		LM_WARN("invalid redis result type: %d\n", rrpl->type);
		if(rrpl->type == REDIS_REPLY_ERROR) {
			LM_ERR("Redis error:%s\n", rrpl->str);
		}
		freeReplyObject(rrpl);
		return -1;
	}

	if(rrpl->elements <= 0) {
		LM_DBG("hmap with key [%.*s] not found\n", rkey.len, rkey.s);
		freeReplyObject(rrpl);
		return 1;
	}
	if(rrpl->elements % 2) {
		LM_DBG("hmap with key [%.*s] has invalid result\n", rkey.len, rkey.s);
		freeReplyObject(rrpl);
		return -1;
	}

	memset(sd, 0, sizeof(tps_data_t));
	sd->cp = sd->cbuf;

	for(i = 0; i < rrpl->elements; i++) {
		if(rrpl->element[i]->type != REDIS_REPLY_STRING) {
			LM_ERR("invalid type for hmap[%.*s] key pos[%d]\n", rkey.len,
					rkey.s, i);
			freeReplyObject(rrpl);
			return -1;
		}
		skey.s = rrpl->element[i]->str;
		skey.len = rrpl->element[i]->len;
		i++;
		if(rrpl->element[i] == NULL) {
			continue;
		}
		sval.s = NULL;
		switch(rrpl->element[i]->type) {
			case REDIS_REPLY_STRING:
				LM_DBG("r[%d]: s[%.*s]\n", i, (int)rrpl->element[i]->len,
						rrpl->element[i]->str);
				sval.s = rrpl->element[i]->str;
				sval.len = rrpl->element[i]->len;
				break;
			case REDIS_REPLY_INTEGER:
				LM_DBG("r[%d]: n[%lld]\n", i, rrpl->element[i]->integer);
				break;
			default:
				LM_WARN("unexpected type [%d] at pos [%d]\n",
						rrpl->element[i]->type, i);
		}
		if(sval.s == NULL) {
			continue;
		}

		if(skey.len == tt_key_rectime.len
				&& strncmp(skey.s, tt_key_rectime.s, skey.len) == 0) {
			/* skip - not needed */
		} else if(skey.len == tt_key_x_vbranch.len
				  && strncmp(skey.s, tt_key_x_vbranch.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->x_vbranch1);
		} else {
			LM_INFO("useless key[%.*s]\n", skey.len, skey.s);
		}
	}

	freeReplyObject(rrpl);
	return 0;

error:
	if(rrpl)
		freeReplyObject(rrpl);
	return -1;
}

/**
 *
 */
int tps_redis_load_branch(
		sip_msg_t *msg, tps_data_t *md, tps_data_t *sd, uint32_t mode)
{
	char *argv[TPS_REDIS_NR_KEYS];
	size_t argvlen[TPS_REDIS_NR_KEYS];
	int argc = 0;
	str rcmd = str_init("HGETALL");
	str rkey = STR_NULL;
	char *rp;
	int i;
	redisc_server_t *rsrv = NULL;
	redisReply *rrpl = NULL;
	str skey = STR_NULL;
	str sval = STR_NULL;
	str *xvbranch1 = NULL;
	tps_data_t id;

	if(msg == NULL || md == NULL || sd == NULL)
		return -1;

	if(mode == 0 && md->x_vbranch1.len <= 0) {
		LM_INFO("no via branch for this message\n");
		return -1;
	}

	rsrv = _tps_redis_api.get_server(&_topos_redis_serverid);
	if(rsrv == NULL) {
		LM_ERR("cannot find redis server [%.*s]\n", _topos_redis_serverid.len,
				_topos_redis_serverid.s);
		return -1;
	}

	memset(argv, 0, TPS_REDIS_NR_KEYS * sizeof(char *));
	memset(argvlen, 0, TPS_REDIS_NR_KEYS * sizeof(size_t));
	argc = 0;
	memset(&id, 0, sizeof(tps_data_t));

	if(mode == 0) {
		/* load same transaction using Via branch */
		xvbranch1 = &md->x_vbranch1;
	} else {
		/* load corresponding INVITE or SUBSCRIBE transaction using call-id + to-tag */
		if(tps_redis_load_initial_method_branch(msg, md, &id) < 0) {
			LM_ERR("failed to load the %.*s branch value\n", md->s_method.len,
					md->s_method.s);
			return -1;
		}
		xvbranch1 = &id.x_vbranch1;
	}
	if(xvbranch1->len <= 0 || xvbranch1->s == NULL) {
		LM_DBG("branch value not found (mode: %u)\n", mode);
		return 1;
	}
	rp = _tps_redis_cbuf;
	memcpy(rp, _tps_redis_bprefix.s, _tps_redis_bprefix.len);
	memcpy(rp + _tps_redis_bprefix.len, xvbranch1->s, xvbranch1->len);
	rp[_tps_redis_bprefix.len + xvbranch1->len] = '\0';
	rkey.s = rp;
	rkey.len = _tps_redis_bprefix.len + xvbranch1->len;
	rp += _tps_redis_bprefix.len + xvbranch1->len + 1;

	argv[argc] = rcmd.s;
	argvlen[argc] = rcmd.len;
	argc++;

	argv[argc] = rkey.s;
	argvlen[argc] = rkey.len;
	argc++;

	LM_DBG("loading branch record for [%.*s]\n", rkey.len, rkey.s);

	rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
	if(rrpl == NULL) {
		LM_ERR("failed to execute redis command\n");
		if(rsrv->ctxRedis->err) {
			LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
		}
		return -1;
	}

	if(rrpl->type != REDIS_REPLY_ARRAY) {
		LM_WARN("invalid redis result type: %d\n", rrpl->type);
		if(rrpl->type == REDIS_REPLY_ERROR) {
			LM_ERR("Redis error:%s\n", rrpl->str);
		}
		freeReplyObject(rrpl);
		return -1;
	}

	if(rrpl->elements <= 0) {
		LM_DBG("hmap with key [%.*s] not found\n", rkey.len, rkey.s);
		freeReplyObject(rrpl);
		return 1;
	}
	if(rrpl->elements % 2) {
		LM_DBG("hmap with key [%.*s] has invalid result\n", rkey.len, rkey.s);
		freeReplyObject(rrpl);
		return -1;
	}

	sd->cp = sd->cbuf;

	for(i = 0; i < rrpl->elements; i++) {
		if(rrpl->element[i]->type != REDIS_REPLY_STRING) {
			LM_ERR("invalid type for hmap[%.*s] key pos[%d]\n", rkey.len,
					rkey.s, i);
			freeReplyObject(rrpl);
			return -1;
		}
		skey.s = rrpl->element[i]->str;
		skey.len = rrpl->element[i]->len;
		i++;
		if(rrpl->element[i] == NULL) {
			continue;
		}
		sval.s = NULL;
		switch(rrpl->element[i]->type) {
			case REDIS_REPLY_STRING:
				LM_DBG("r[%d]: s[%.*s]\n", i, (int)rrpl->element[i]->len,
						rrpl->element[i]->str);
				sval.s = rrpl->element[i]->str;
				sval.len = rrpl->element[i]->len;
				break;
			case REDIS_REPLY_INTEGER:
				LM_DBG("r[%d]: n[%lld]\n", i, rrpl->element[i]->integer);
				break;
			default:
				LM_WARN("unexpected type [%d] at pos [%d]\n",
						rrpl->element[i]->type, i);
		}
		if(sval.s == NULL) {
			continue;
		}

		if(skey.len == tt_key_rectime.len
				&& strncmp(skey.s, tt_key_rectime.s, skey.len) == 0) {
			/* skip - not needed */
		} else if(skey.len == tt_key_a_callid.len
				  && strncmp(skey.s, tt_key_a_callid.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->a_callid);
		} else if(skey.len == tt_key_a_uuid.len
				  && strncmp(skey.s, tt_key_a_uuid.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->a_uuid);
		} else if(skey.len == tt_key_b_uuid.len
				  && strncmp(skey.s, tt_key_b_uuid.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->b_uuid);
		} else if(skey.len == tt_key_direction.len
				  && strncmp(skey.s, tt_key_direction.s, skey.len) == 0) {
			/* skip - not needed */
		} else if(skey.len == tt_key_x_via.len
				  && strncmp(skey.s, tt_key_x_via.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->x_via);
		} else if(skey.len == tt_key_x_vbranch.len
				  && strncmp(skey.s, tt_key_x_vbranch.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->x_vbranch1);
		} else if(skey.len == tt_key_x_rr.len
				  && strncmp(skey.s, tt_key_x_rr.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->x_rr);
		} else if(skey.len == tt_key_y_rr.len
				  && strncmp(skey.s, tt_key_y_rr.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->y_rr);
		} else if(skey.len == tt_key_s_rr.len
				  && strncmp(skey.s, tt_key_s_rr.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->s_rr);
		} else if(skey.len == tt_key_x_uri.len
				  && strncmp(skey.s, tt_key_x_uri.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->x_uri);
		} else if(skey.len == tt_key_x_tag.len
				  && strncmp(skey.s, tt_key_x_tag.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->x_tag);
		} else if(skey.len == tt_key_s_method.len
				  && strncmp(skey.s, tt_key_s_method.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->s_method);
		} else if(skey.len == tt_key_s_cseq.len
				  && strncmp(skey.s, tt_key_s_cseq.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->s_cseq);
		} else if(skey.len == tt_key_a_contact.len
				  && strncmp(skey.s, tt_key_a_contact.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->a_contact);
		} else if(skey.len == tt_key_b_contact.len
				  && strncmp(skey.s, tt_key_b_contact.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->b_contact);
		} else if(skey.len == tt_key_as_contact.len
				  && strncmp(skey.s, tt_key_as_contact.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->as_contact);
		} else if(skey.len == tt_key_bs_contact.len
				  && strncmp(skey.s, tt_key_bs_contact.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->bs_contact);
		} else if(skey.len == tt_key_a_tag.len
				  && strncmp(skey.s, tt_key_a_tag.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->a_tag);
		} else if(skey.len == tt_key_b_tag.len
				  && strncmp(skey.s, tt_key_b_tag.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->b_tag);
		} else if(skey.len == tt_key_x_context.len
				  && strncmp(skey.s, tt_key_x_context.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->x_context);
		} else {
			LM_WARN("unknown key[%.*s]\n", skey.len, skey.s);
		}
	}

	freeReplyObject(rrpl);
	return 0;

error:
	if(rrpl)
		freeReplyObject(rrpl);
	return -1;
}

/**
 *
 */
int tps_redis_load_dialog(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd)
{
	char *argv[TPS_REDIS_NR_KEYS];
	size_t argvlen[TPS_REDIS_NR_KEYS];
	int argc = 0;
	str rcmd = str_init("HGETALL");
	str rkey = STR_NULL;
	char *rp;
	int i;
	redisc_server_t *rsrv = NULL;
	redisReply *rrpl = NULL;
	str skey = STR_NULL;
	str sval = STR_NULL;

	if(msg == NULL || md == NULL || sd == NULL)
		return -1;

	if(md->a_uuid.len <= 0 && md->b_uuid.len <= 0) {
		LM_DBG("no dlg uuid provided\n");
		return -1;
	}
	rsrv = _tps_redis_api.get_server(&_topos_redis_serverid);
	if(rsrv == NULL) {
		LM_ERR("cannot find redis server [%.*s]\n", _topos_redis_serverid.len,
				_topos_redis_serverid.s);
		return -1;
	}

	memset(argv, 0, TPS_REDIS_NR_KEYS * sizeof(char *));
	memset(argvlen, 0, TPS_REDIS_NR_KEYS * sizeof(size_t));
	argc = 0;

	rp = _tps_redis_cbuf;
	memcpy(rp, _tps_redis_dprefix.s, _tps_redis_dprefix.len);

	if(md->a_uuid.len > 0) {
		memcpy(rp + _tps_redis_dprefix.len, md->a_uuid.s, md->a_uuid.len);
		if(md->a_uuid.s[0] == 'b') {
			rp[_tps_redis_dprefix.len] = 'a';
		}
		rp[_tps_redis_dprefix.len + md->a_uuid.len] = '\0';
		rkey.s = rp;
		rkey.len = _tps_redis_dprefix.len + md->a_uuid.len;
		rp += _tps_redis_dprefix.len + md->a_uuid.len + 1;
	} else {
		memcpy(rp + _tps_redis_dprefix.len, md->b_uuid.s, md->b_uuid.len);
		if(md->b_uuid.s[0] == 'b') {
			rp[_tps_redis_dprefix.len] = 'a';
		}
		rp[_tps_redis_dprefix.len + md->b_uuid.len] = '\0';
		rkey.s = rp;
		rkey.len = _tps_redis_dprefix.len + md->b_uuid.len;
		rp += _tps_redis_dprefix.len + md->b_uuid.len + 1;
	}

	argv[argc] = rcmd.s;
	argvlen[argc] = rcmd.len;
	argc++;

	argv[argc] = rkey.s;
	argvlen[argc] = rkey.len;
	argc++;

	LM_DBG("loading dialog record for [%.*s]\n", rkey.len, rkey.s);

	rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
	if(rrpl == NULL) {
		LM_ERR("failed to execute redis command\n");
		if(rsrv->ctxRedis->err) {
			LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
		}
		return -1;
	}

	if(rrpl->type != REDIS_REPLY_ARRAY) {
		LM_WARN("invalid redis result type: %d\n", rrpl->type);
		if(rrpl->type == REDIS_REPLY_ERROR) {
			LM_ERR("Redis error:%s\n", rrpl->str);
		}
		freeReplyObject(rrpl);
		return -1;
	}

	if(rrpl->elements <= 0) {
		LM_DBG("hmap with key [%.*s] not found\n", rkey.len, rkey.s);
		freeReplyObject(rrpl);
		return 1;
	}
	if(rrpl->elements % 2) {
		LM_DBG("hmap with key [%.*s] has invalid result\n", rkey.len, rkey.s);
		freeReplyObject(rrpl);
		return -1;
	}

	sd->cp = sd->cbuf;

	for(i = 0; i < rrpl->elements; i++) {
		if(rrpl->element[i]->type != REDIS_REPLY_STRING) {
			LM_ERR("invalid type for hmap[%.*s] key pos[%d]\n", rkey.len,
					rkey.s, i);
			freeReplyObject(rrpl);
			return -1;
		}
		skey.s = rrpl->element[i]->str;
		skey.len = rrpl->element[i]->len;
		i++;
		if(rrpl->element[i] == NULL) {
			continue;
		}
		sval.s = NULL;
		switch(rrpl->element[i]->type) {
			case REDIS_REPLY_STRING:
				LM_DBG("r[%d]: s[%.*s]\n", i, (int)rrpl->element[i]->len,
						rrpl->element[i]->str);
				sval.s = rrpl->element[i]->str;
				sval.len = rrpl->element[i]->len;
				break;
			case REDIS_REPLY_INTEGER:
				LM_DBG("r[%d]: n[%lld]\n", i, rrpl->element[i]->integer);
				break;
			default:
				LM_WARN("unexpected type [%d] at pos [%d]\n",
						rrpl->element[i]->type, i);
		}
		if(sval.s == NULL) {
			continue;
		}
		if(skey.len == td_key_rectime.len
				&& strncmp(skey.s, td_key_rectime.s, skey.len) == 0) {
			/* skip - not needed */
		} else if(skey.len == td_key_a_callid.len
				  && strncmp(skey.s, td_key_a_callid.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->a_callid);
		} else if(skey.len == td_key_a_uuid.len
				  && strncmp(skey.s, td_key_a_uuid.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->a_uuid);
		} else if(skey.len == td_key_b_uuid.len
				  && strncmp(skey.s, td_key_b_uuid.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->b_uuid);
		} else if(skey.len == td_key_a_contact.len
				  && strncmp(skey.s, td_key_a_contact.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->a_contact);
		} else if(skey.len == td_key_b_contact.len
				  && strncmp(skey.s, td_key_b_contact.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->b_contact);
		} else if(skey.len == td_key_as_contact.len
				  && strncmp(skey.s, td_key_as_contact.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->as_contact);
		} else if(skey.len == td_key_bs_contact.len
				  && strncmp(skey.s, td_key_bs_contact.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->bs_contact);
		} else if(skey.len == td_key_a_tag.len
				  && strncmp(skey.s, td_key_a_tag.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->a_tag);
		} else if(skey.len == td_key_b_tag.len
				  && strncmp(skey.s, td_key_b_tag.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->b_tag);
		} else if(skey.len == td_key_a_rr.len
				  && strncmp(skey.s, td_key_a_rr.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->a_rr);
		} else if(skey.len == td_key_b_rr.len
				  && strncmp(skey.s, td_key_b_rr.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->b_rr);
		} else if(skey.len == td_key_s_rr.len
				  && strncmp(skey.s, td_key_s_rr.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->s_rr);
		} else if(skey.len == td_key_iflags.len
				  && strncmp(skey.s, td_key_iflags.s, skey.len) == 0) {
			/* skip - not needed */
		} else if(skey.len == td_key_a_uri.len
				  && strncmp(skey.s, td_key_a_uri.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->a_uri);
		} else if(skey.len == td_key_b_uri.len
				  && strncmp(skey.s, td_key_b_uri.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->b_uri);
		} else if(skey.len == td_key_r_uri.len
				  && strncmp(skey.s, td_key_r_uri.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->r_uri);
		} else if(skey.len == td_key_a_srcaddr.len
				  && strncmp(skey.s, td_key_a_srcaddr.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->a_srcaddr);
		} else if(skey.len == td_key_b_srcaddr.len
				  && strncmp(skey.s, td_key_b_srcaddr.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->b_srcaddr);
		} else if(skey.len == td_key_s_method.len
				  && strncmp(skey.s, td_key_s_method.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->s_method);
		} else if(skey.len == td_key_s_cseq.len
				  && strncmp(skey.s, td_key_s_cseq.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->s_cseq);
		} else if(skey.len == td_key_x_context.len
				  && strncmp(skey.s, td_key_x_context.s, skey.len) == 0) {
			TPS_REDIS_DATA_APPEND(sd, &skey, &sval, &sd->x_context);
		} else {
			LM_WARN("unknown key[%.*s]\n", skey.len, skey.s);
		}
	}

	freeReplyObject(rrpl);
	return 0;

error:
	if(rrpl)
		freeReplyObject(rrpl);
	return -1;
}

/**
 *
 */
int tps_redis_update_branch(
		sip_msg_t *msg, tps_data_t *md, tps_data_t *sd, uint32_t mode)
{
	char *argv[TPS_REDIS_NR_KEYS];
	size_t argvlen[TPS_REDIS_NR_KEYS];
	int argc = 0;
	str rcmd = str_init("HMSET");
	str rkey = STR_NULL;
	char *rp;
	redisc_server_t *rsrv = NULL;
	redisReply *rrpl = NULL;

	if(sd->a_uuid.len <= 0 && sd->b_uuid.len <= 0) {
		LM_INFO("no uuid for this message\n");
		return -1;
	}

	if(md->s_method_id == METHOD_INVITE
			|| md->s_method_id == METHOD_SUBSCRIBE) {
		if(tps_redis_insert_initial_method_branch(md, sd) < 0) {
			LM_ERR("failed to insert %.*s extra initial branch data\n",
					md->s_method.len, md->s_method.s);
			return -1;
		}
	}

	rsrv = _tps_redis_api.get_server(&_topos_redis_serverid);
	if(rsrv == NULL) {
		LM_ERR("cannot find redis server [%.*s]\n", _topos_redis_serverid.len,
				_topos_redis_serverid.s);
		return -1;
	}

	memset(argv, 0, TPS_REDIS_NR_KEYS * sizeof(char *));
	memset(argvlen, 0, TPS_REDIS_NR_KEYS * sizeof(size_t));
	argc = 0;

	rp = _tps_redis_cbuf;
	memcpy(rp, _tps_redis_bprefix.s, _tps_redis_bprefix.len);

	memcpy(rp + _tps_redis_bprefix.len, sd->x_vbranch1.s, sd->x_vbranch1.len);
	rp[_tps_redis_bprefix.len + sd->x_vbranch1.len] = '\0';
	rkey.s = rp;
	rkey.len = _tps_redis_bprefix.len + sd->x_vbranch1.len;
	rp += _tps_redis_bprefix.len + sd->x_vbranch1.len + 1;

	argv[argc] = rcmd.s;
	argvlen[argc] = rcmd.len;
	argc++;

	argv[argc] = rkey.s;
	argvlen[argc] = rkey.len;
	argc++;

	if(mode & TPS_DBU_CONTACT) {
		TPS_REDIS_SET_ARGS(
				&md->a_contact, argc, &tt_key_a_contact, argv, argvlen);
		TPS_REDIS_SET_ARGS(
				&md->b_contact, argc, &tt_key_b_contact, argv, argvlen);
	}

	if((mode & TPS_DBU_RPLATTRS) && msg->first_line.type == SIP_REPLY) {
		if(msg->first_line.u.reply.statuscode >= 180
				&& msg->first_line.u.reply.statuscode < 200) {

			TPS_REDIS_SET_ARGS(&md->b_rr, argc, &tt_key_y_rr, argv, argvlen);

			TPS_REDIS_SET_ARGS(&md->b_tag, argc, &tt_key_b_tag, argv, argvlen);
		}
	}

	if(argc <= 2) {
		return 0;
	}

	rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
	if(rrpl == NULL) {
		LM_ERR("failed to execute redis command for branch update\n");
		if(rsrv->ctxRedis->err) {
			LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
		}
		return -1;
	}
	LM_DBG("updated branch record for [%.*s] with argc %d\n", rkey.len, rkey.s,
			argc);
	freeReplyObject(rrpl);

	return 0;
}

/**
 *
 */
int tps_redis_update_dialog(
		sip_msg_t *msg, tps_data_t *md, tps_data_t *sd, uint32_t mode)
{
	char *argv[TPS_REDIS_NR_KEYS];
	size_t argvlen[TPS_REDIS_NR_KEYS];
	int argc = 0;
	str rcmd = str_init("HMSET");
	str rkey = STR_NULL;
	char *rp;
	str rval = STR_NULL;
	redisc_server_t *rsrv = NULL;
	redisReply *rrpl = NULL;
	int32_t liflags;
	unsigned long lval = 0;

	if(sd->a_uuid.len <= 0 && sd->b_uuid.len <= 0) {
		LM_INFO("no uuid for this message\n");
		return -1;
	}

	rsrv = _tps_redis_api.get_server(&_topos_redis_serverid);
	if(rsrv == NULL) {
		LM_ERR("cannot find redis server [%.*s]\n", _topos_redis_serverid.len,
				_topos_redis_serverid.s);
		return -1;
	}

	memset(argv, 0, TPS_REDIS_NR_KEYS * sizeof(char *));
	memset(argvlen, 0, TPS_REDIS_NR_KEYS * sizeof(size_t));
	argc = 0;

	rp = _tps_redis_cbuf;
	memcpy(rp, _tps_redis_dprefix.s, _tps_redis_dprefix.len);

	if(sd->a_uuid.len > 0) {
		memcpy(rp + _tps_redis_dprefix.len, sd->a_uuid.s, sd->a_uuid.len);
		if(sd->a_uuid.s[0] == 'b') {
			rp[_tps_redis_dprefix.len] = 'a';
		}
		rp[_tps_redis_dprefix.len + sd->a_uuid.len] = '\0';
		rkey.s = rp;
		rkey.len = _tps_redis_dprefix.len + sd->a_uuid.len;
		rp += _tps_redis_dprefix.len + sd->a_uuid.len + 1;
	} else {
		memcpy(rp + _tps_redis_dprefix.len, sd->b_uuid.s, sd->b_uuid.len);
		if(sd->b_uuid.s[0] == 'b') {
			rp[_tps_redis_dprefix.len] = 'a';
		}
		rp[_tps_redis_dprefix.len + sd->b_uuid.len] = '\0';
		rkey.s = rp;
		rkey.len = _tps_redis_dprefix.len + sd->b_uuid.len;
		rp += _tps_redis_dprefix.len + sd->b_uuid.len + 1;
	}

	argv[argc] = rcmd.s;
	argvlen[argc] = rcmd.len;
	argc++;

	argv[argc] = rkey.s;
	argvlen[argc] = rkey.len;
	argc++;

	if(mode & TPS_DBU_CONTACT) {
		TPS_REDIS_SET_ARGS(
				&md->a_contact, argc, &td_key_a_contact, argv, argvlen);
		TPS_REDIS_SET_ARGS(
				&md->b_contact, argc, &td_key_b_contact, argv, argvlen);
	}

	if((mode & TPS_DBU_RPLATTRS) && msg->first_line.type == SIP_REPLY) {
		if(sd->b_tag.len <= 0 && msg->first_line.u.reply.statuscode >= 200
				&& msg->first_line.u.reply.statuscode < 300) {

			if((sd->iflags & TPS_IFLAG_DLGON) == 0) {
				TPS_REDIS_SET_ARGS(
						&md->b_rr, argc, &td_key_b_rr, argv, argvlen);
			}

			TPS_REDIS_SET_ARGS(&md->b_tag, argc, &td_key_b_tag, argv, argvlen);

			liflags = sd->iflags | TPS_IFLAG_DLGON;
			TPS_REDIS_SET_ARGN(
					liflags, rp, &rval, argc, &td_key_iflags, argv, argvlen);
		}
	}

	if(sd->b_tag.len > 0 && ((mode & TPS_DBU_BRR) || (mode & TPS_DBU_ARR))) {
		if(((md->direction == TPS_DIR_DOWNSTREAM)
				   && (msg->first_line.type == SIP_REPLY))
				|| ((md->direction == TPS_DIR_UPSTREAM)
						&& (msg->first_line.type == SIP_REQUEST))) {
			if(((sd->iflags & TPS_IFLAG_DLGON) == 0) && (mode & TPS_DBU_BRR)) {
				TPS_REDIS_SET_ARGS(
						&md->b_rr, argc, &td_key_b_rr, argv, argvlen);
			}
		} else {
			if(((sd->iflags & TPS_IFLAG_DLGON) == 0) && (mode & TPS_DBU_ARR)) {
				TPS_REDIS_SET_ARGS(
						&md->a_rr, argc, &td_key_a_rr, argv, argvlen);
				TPS_REDIS_SET_ARGS(
						&md->s_rr, argc, &td_key_s_rr, argv, argvlen);
			}
		}
	}

	if(mode & TPS_DBU_TIME) {
		lval = (unsigned long)time(NULL);
		TPS_REDIS_SET_ARGN(
				lval, rp, &rval, argc, &td_key_rectime, argv, argvlen);
	}

	if(argc <= 2) {
		return 0;
	}

	rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
	if(rrpl == NULL) {
		LM_ERR("failed to execute redis command for dialog update\n");
		if(rsrv->ctxRedis->err) {
			LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
		}
		return -1;
	}
	LM_DBG("updated dialog record for [%.*s] with argc %d\n", rkey.len, rkey.s,
			argc);
	freeReplyObject(rrpl);

	if(mode & TPS_DBU_TIME) {
		/* reset expire for the key */
		argc = 0;

		argv[argc] = "EXPIRE";
		argvlen[argc] = 6;
		argc++;

		argv[argc] = rkey.s;
		argvlen[argc] = rkey.len;
		argc++;

		lval = (unsigned long)md->expires;
		if(lval == 0) {
			return 0;
		}
		TPS_REDIS_SET_ARGNV(lval, rp, &rval, argc, argv, argvlen);

		rrpl = _tps_redis_api.exec_argv(
				rsrv, argc, (const char **)argv, argvlen);
		if(rrpl == NULL) {
			LM_ERR("failed to execute expire redis command\n");
			if(rsrv->ctxRedis->err) {
				LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
			}
			return -1;
		}
		LM_DBG("expire %lu set on dialog record for [%.*s] with argc %d\n",
				lval, rkey.len, rkey.s, argc);
		freeReplyObject(rrpl);
	}

	return 0;
}

/**
 *
 */
int tps_redis_end_dialog(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd)
{
	char *argv[TPS_REDIS_NR_KEYS];
	size_t argvlen[TPS_REDIS_NR_KEYS];
	int argc = 0;
	str rcmd = str_init("HMSET");
	str rkey = STR_NULL;
	char *rp;
	str rval = STR_NULL;
	redisc_server_t *rsrv = NULL;
	redisReply *rrpl = NULL;
	int32_t liflags;
	unsigned long lval = 0;

	if((md->s_method_id != METHOD_BYE)
			&& !((md->s_method_id == METHOD_SUBSCRIBE) && (md->expires == 0))) {
		return 0;
	}

	if(sd->a_uuid.len <= 0 && sd->b_uuid.len <= 0) {
		LM_INFO("no uuid for this message\n");
		return -1;
	}

	rsrv = _tps_redis_api.get_server(&_topos_redis_serverid);
	if(rsrv == NULL) {
		LM_ERR("cannot find redis server [%.*s]\n", _topos_redis_serverid.len,
				_topos_redis_serverid.s);
		return -1;
	}

	memset(argv, 0, TPS_REDIS_NR_KEYS * sizeof(char *));
	memset(argvlen, 0, TPS_REDIS_NR_KEYS * sizeof(size_t));
	argc = 0;

	rp = _tps_redis_cbuf;
	memcpy(rp, _tps_redis_dprefix.s, _tps_redis_dprefix.len);

	if(sd->a_uuid.len > 0) {
		memcpy(rp + _tps_redis_dprefix.len, sd->a_uuid.s, sd->a_uuid.len);
		if(sd->a_uuid.s[0] == 'b') {
			rp[_tps_redis_dprefix.len] = 'a';
		}
		rp[_tps_redis_dprefix.len + sd->a_uuid.len] = '\0';
		rkey.s = rp;
		rkey.len = _tps_redis_dprefix.len + sd->a_uuid.len;
		rp += _tps_redis_dprefix.len + sd->a_uuid.len + 1;
	} else {
		memcpy(rp + _tps_redis_dprefix.len, sd->b_uuid.s, sd->b_uuid.len);
		if(sd->b_uuid.s[0] == 'b') {
			rp[_tps_redis_dprefix.len] = 'a';
		}
		rp[_tps_redis_dprefix.len + sd->b_uuid.len] = '\0';
		rkey.s = rp;
		rkey.len = _tps_redis_dprefix.len + sd->b_uuid.len;
		rp += _tps_redis_dprefix.len + sd->b_uuid.len + 1;
	}

	argv[argc] = rcmd.s;
	argvlen[argc] = rcmd.len;
	argc++;

	argv[argc] = rkey.s;
	argvlen[argc] = rkey.len;
	argc++;

	lval = (unsigned long)time(NULL);
	TPS_REDIS_SET_ARGN(lval, rp, &rval, argc, &td_key_rectime, argv, argvlen);
	liflags = 0;
	TPS_REDIS_SET_ARGN(liflags, rp, &rval, argc, &td_key_iflags, argv, argvlen);

	rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
	if(rrpl == NULL) {
		LM_ERR("failed to execute redis command\n");
		if(rsrv->ctxRedis->err) {
			LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
		}
		return -1;
	}
	LM_DBG("updated on end the dialog record for [%.*s] with argc %d\n",
			rkey.len, rkey.s, argc);

	freeReplyObject(rrpl);

	/* set expire for the key */
	argc = 0;

	argv[argc] = "EXPIRE";
	argvlen[argc] = 6;
	argc++;

	argv[argc] = rkey.s;
	argvlen[argc] = rkey.len;
	argc++;

	/* dialog ended -- keep it for branch lifetime only */
	lval = (unsigned long)_tps_api.get_branch_expire();
	if(lval == 0) {
		return 0;
	}
	TPS_REDIS_SET_ARGNV(lval, rp, &rval, argc, argv, argvlen);

	rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
	if(rrpl == NULL) {
		LM_ERR("failed to execute expire redis command\n");
		if(rsrv->ctxRedis->err) {
			LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
		}
		return -1;
	}
	LM_DBG("expire %lu set on dialog record for [%.*s] with argc %d\n", lval,
			rkey.len, rkey.s, argc);
	freeReplyObject(rrpl);

	return 0;
}
