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

/*! \file
 *  \brief USRLOC - Usrloc contact handling functions
 *  \ingroup usrloc
 *
 * - Module: \ref usrloc
 */

#include <string.h>
#include <time.h>
#include "../../core/mem/shm_mem.h"
#include "../../core/ut.h"
#include "../../core/ip_addr.h"
#include "../../core/socket_info.h"
#include "../../core/dprint.h"
#include "../../lib/srdb1/db.h"
#include "usrloc_mod.h"
#include "ul_callback.h"
#include "usrloc.h"
#include "urecord.h"
#include "ucontact.h"

extern int ul_db_insert_null;

static int ul_xavp_contact_clone = 1;

void ul_set_xavp_contact_clone(int v)
{
	ul_xavp_contact_clone = v;
}

/*!
 * \brief Store xavp list per contact from core, update existing contact as well
 * \param _c contact structure
 */
static void ucontact_xavp_store(ucontact_t *_c)
{
	sr_xavp_t *xavp;
	if(_c == NULL)
		return;
	if(ul_xavp_contact_clone == 0)
		return;
	if(ul_xavp_contact_name.s == NULL)
		return;
	/* XAVP from core */
	xavp = xavp_get(&ul_xavp_contact_name, NULL);
	if(xavp == NULL) {
		return;
	}
	/* remove old xavp if it is set -- update case */
	if(_c->xavp) {
		xavp_destroy_list(&_c->xavp);
	}
	/* clone the xavp found in core */
	LM_DBG("trying to clone per contact xavps\n");
	_c->xavp = xavp_clone_level_nodata(xavp);
	return;
}

int uldb_delete_attrs_ruid(str *_dname, str *_ruid);

/*!
 * \brief Create a new contact structure
 * \param _dom domain
 * \param _aor address of record
 * \param _contact contact string
 * \param _ci contact informations
 * \return new created contact on success, 0 on failure
 */
ucontact_t *new_ucontact(
		str *_dom, str *_aor, str *_contact, ucontact_info_t *_ci)
{
	ucontact_t *c;

	if(unlikely(_ci->ruid.len <= 0)) {
		LM_ERR("no ruid for aor: %.*s\n", _aor->len, ZSW(_aor->s));
		return 0;
	}

	c = (ucontact_t *)shm_malloc(sizeof(ucontact_t));
	if(!c) {
		SHM_MEM_ERROR;
		return 0;
	}
	memset(c, 0, sizeof(ucontact_t));

	if(shm_str_dup(&c->c, _contact) < 0)
		goto error;
	if(shm_str_dup(&c->callid, _ci->callid) < 0)
		goto error;
	if(shm_str_dup(&c->user_agent, _ci->user_agent) < 0)
		goto error;

	if(_ci->received.s && _ci->received.len) {
		if(shm_str_dup(&c->received, &_ci->received) < 0)
			goto error;
	}
	if(_ci->path && _ci->path->len) {
		if(shm_str_dup(&c->path, _ci->path) < 0)
			goto error;
	}
	if(_ci->ruid.s && _ci->ruid.len) {
		if(shm_str_dup(&c->ruid, &_ci->ruid) < 0)
			goto error;
	}
	if(_ci->instance.s && _ci->instance.len) {
		if(shm_str_dup(&c->instance, &_ci->instance) < 0)
			goto error;
	}
	/* we got a xavp from e.g. DMQ */
	if(_ci->xavp) {
		/* destroy the existing contact xavp */
		if(c->xavp) {
			xavp_destroy_list(&c->xavp);
		}
		/* clone the DMQ xavp because it will be destroyed */
		c->xavp = xavp_clone_level_nodata(_ci->xavp);
	}

	c->domain = _dom;
	c->aor = _aor;
	c->expires = _ci->expires;
	c->q = _ci->q;
	c->sock = _ci->sock;
	c->cseq = _ci->cseq;
	c->state = CS_NEW;
	c->flags = _ci->flags;
	c->cflags = _ci->cflags;
	c->methods = _ci->methods;
	c->reg_id = _ci->reg_id;
	c->last_modified = _ci->last_modified;
	c->last_keepalive = time(NULL);
	c->tcpconn_id = _ci->tcpconn_id;
	c->server_id = _ci->server_id;
	c->keepalive = (_ci->cflags & ul_nat_bflag) ? 1 : 0;

	/* Use core xavp if set, otherwise does nothing */
	ucontact_xavp_store(c);


	return c;
error:
	SHM_MEM_ERROR;
	if(c->path.s)
		shm_free(c->path.s);
	if(c->received.s)
		shm_free(c->received.s);
	if(c->user_agent.s)
		shm_free(c->user_agent.s);
	if(c->callid.s)
		shm_free(c->callid.s);
	if(c->c.s)
		shm_free(c->c.s);
	if(c->ruid.s)
		shm_free(c->ruid.s);
	if(c->instance.s)
		shm_free(c->instance.s);
	if(c->xavp)
		xavp_destroy_list(&c->xavp);
	shm_free(c);
	return 0;
}


/*!
 * \brief Free all memory associated with given contact structure
 * \param _c freed contact
 */
void free_ucontact(ucontact_t *_c)
{
	if(!_c)
		return;
	if(_c->path.s)
		shm_free(_c->path.s);
	if(_c->received.s)
		shm_free(_c->received.s);
	if(_c->user_agent.s)
		shm_free(_c->user_agent.s);
	if(_c->callid.s)
		shm_free(_c->callid.s);
	if(_c->c.s)
		shm_free(_c->c.s);
	if(_c->ruid.s)
		shm_free(_c->ruid.s);
	if(_c->instance.s)
		shm_free(_c->instance.s);
	if(_c->xavp)
		xavp_destroy_list(&_c->xavp);
	shm_free(_c);
}


/*!
 * \brief Print contact, for debugging purposes only
 * \param _f output file
 * \param _c printed contact
 */
void print_ucontact(FILE *_f, ucontact_t *_c)
{
	time_t t = time(0);
	char *st;

	switch(_c->state) {
		case CS_NEW:
			st = "CS_NEW";
			break;
		case CS_SYNC:
			st = "CS_SYNC";
			break;
		case CS_DIRTY:
			st = "CS_DIRTY";
			break;
		default:
			st = "CS_UNKNOWN";
			break;
	}

	fprintf(_f, "~~~Contact(%p)~~~\n", _c);
	fprintf(_f, "domain    : '%.*s'\n", _c->domain->len, ZSW(_c->domain->s));
	fprintf(_f, "aor       : '%.*s'\n", _c->aor->len, ZSW(_c->aor->s));
	fprintf(_f, "Contact   : '%.*s'\n", _c->c.len, ZSW(_c->c.s));
	fprintf(_f, "Expires   : ");
	if(_c->expires == 0) {
		fprintf(_f, "Permanent\n");
	} else if(_c->expires == UL_EXPIRED_TIME) {
		fprintf(_f, "Deleted\n");
	} else if(t > _c->expires) {
		fprintf(_f, "Expired\n");
	} else {
		fprintf(_f, "%u\n", (unsigned int)(_c->expires - t));
	}
	fprintf(_f, "q         : %s\n", q2str(_c->q, 0));
	fprintf(_f, "Call-ID   : '%.*s'\n", _c->callid.len, ZSW(_c->callid.s));
	fprintf(_f, "CSeq      : %d\n", _c->cseq);
	fprintf(_f, "User-Agent: '%.*s'\n", _c->user_agent.len,
			ZSW(_c->user_agent.s));
	fprintf(_f, "received  : '%.*s'\n", _c->received.len, ZSW(_c->received.s));
	fprintf(_f, "Path      : '%.*s'\n", _c->path.len, ZSW(_c->path.s));
	fprintf(_f, "State     : %s\n", st);
	fprintf(_f, "Flags     : %u\n", _c->flags);
	if(_c->sock) {
		fprintf(_f, "Sock      : %.*s (%p)\n", _c->sock->sock_str.len,
				_c->sock->sock_str.s, _c->sock);
	} else {
		fprintf(_f, "Sock      : none (null)\n");
	}
	fprintf(_f, "Methods   : %u\n", _c->methods);
	fprintf(_f, "ruid      : '%.*s'\n", _c->ruid.len, ZSW(_c->ruid.s));
	fprintf(_f, "instance  : '%.*s'\n", _c->instance.len, ZSW(_c->instance.s));
	fprintf(_f, "reg-id    : %u\n", _c->reg_id);
	fprintf(_f, "next      : %p\n", _c->next);
	fprintf(_f, "prev      : %p\n", _c->prev);
	fprintf(_f, "~~~/Contact~~~~\n");
}


/*!
 * \brief Update existing contact in memory with new values
 * \param _c contact
 * \param _ci contact informations
 * \return 0 on success, -1 on failure
 */
int mem_update_ucontact(ucontact_t *_c, ucontact_info_t *_ci)
{
#define update_str(_old, _new)                         \
	do {                                               \
		if((_old)->len < (_new)->len) {                \
			ptr = (char *)shm_malloc((_new)->len);     \
			if(ptr == 0) {                             \
				SHM_MEM_ERROR;                         \
				return -1;                             \
			}                                          \
			memcpy(ptr, (_new)->s, (_new)->len);       \
			if((_old)->s)                              \
				shm_free((_old)->s);                   \
			(_old)->s = ptr;                           \
		} else {                                       \
			memcpy((_old)->s, (_new)->s, (_new)->len); \
		}                                              \
		(_old)->len = (_new)->len;                     \
	} while(0)

	char *ptr;

	if(_ci->instance.s != NULL && _ci->instance.len > 0) {
		/* when we have instance set, update contact address */
		if(_ci->c != NULL && _ci->c->s != NULL && _ci->c->len > 0)
			update_str(&_c->c, _ci->c);
	}

	/* refresh call-id */
	if(_ci->callid != NULL && _ci->callid->s != NULL && _ci->callid->len > 0)
		update_str(&_c->callid, _ci->callid);
	update_str(&_c->user_agent, _ci->user_agent);

	if(_ci->received.s && _ci->received.len) {
		update_str(&_c->received, &_ci->received);
	} else {
		if(_c->received.s)
			shm_free(_c->received.s);
		_c->received.s = 0;
		_c->received.len = 0;
	}

	if(_ci->path) {
		update_str(&_c->path, _ci->path);
	} else {
		if(_c->path.s)
			shm_free(_c->path.s);
		_c->path.s = 0;
		_c->path.len = 0;
	}

	/* we got a xavp from e.g. DMQ */
	if(_ci->xavp) {
		/* destroy the existing contact xavp */
		if(_c->xavp) {
			xavp_destroy_list(&_c->xavp);
		}
		/* clone the DMQ xavp because it will be destroyed */
		_c->xavp = xavp_clone_level_nodata(_ci->xavp);
	}
	/* Use core xavp if set, otherwise does nothing */
	ucontact_xavp_store(_c);

	_c->sock = _ci->sock;
	_c->expires = _ci->expires;
	_c->q = _ci->q;
	_c->cseq = _ci->cseq;
	_c->methods = _ci->methods;
	_c->last_modified = _ci->last_modified;
	_c->last_keepalive = time(NULL);
	_c->flags = _ci->flags;
	_c->cflags = _ci->cflags;
	_c->server_id = _ci->server_id;
	_c->tcpconn_id = _ci->tcpconn_id;

	return 0;
}


/* ================ State related functions =============== */

/*!
 * \brief Update state of the contact if we are using write-back scheme
 * \param _c updated contact
 */
void st_update_ucontact(ucontact_t *_c)
{
	switch(_c->state) {
		case CS_NEW:
			/* Contact is new and is not in the database yet,
			 * we remain in the same state here because the
			 * contact must be inserted later in the timer
			 */
			break;

		case CS_SYNC:
			/* For db mode 1 & 2 a modified contact needs to be
			 * updated also in the database, so transit into
			 * CS_DIRTY and let the timer to do the update
			 * again. For db mode 1 we try to update right
			 * now and if fails, let the timer to do the job
			 */
			if(ul_db_mode == WRITE_BACK || ul_db_mode == WRITE_THROUGH) {
				_c->state = CS_DIRTY;
			}
			break;

		case CS_DIRTY:
			/* Modification of dirty contact results in
			 * dirty contact again, don't change anything
			 */
			break;
	}
}


/*!
 * \brief Update state of the contact
 * \param _c updated contact
 * \return 1 if the contact should be deleted from memory immediately, 0 otherwise
 */
int st_delete_ucontact(ucontact_t *_c)
{
	switch(_c->state) {
		case CS_NEW:
			/* Contact is new and isn't in the database
			 * yet, we can delete it from the memory
			 * safely.
			 */
			return 1;

		case CS_SYNC:
		case CS_DIRTY:
			/* Contact is in the database,
			 * we cannot remove it from the memory
			 * directly, but we can set expires to zero
			 * and the timer will take care of deleting
			 * the contact from the memory as well as
			 * from the database
			 */
			if(ul_db_mode == WRITE_BACK) {
				_c->expires = UL_EXPIRED_TIME;
				return 0;
			} else {
				/* WRITE_THROUGH or NO_DB -- we can
			 * remove it from memory immediately and
			 * the calling function would also remove
			 * it from the database if needed
			 */
				return 1;
			}
	}

	return 0; /* Makes gcc happy */
}


/*!
 * \brief Called when the timer is about to delete an expired contact
 * \param _c expired contact
 * \return 1 if the contact should be removed from the database and 0 otherwise
 */
int st_expired_ucontact(ucontact_t *_c)
{
	/* There is no need to change contact
	 * state, because the contact will
	 * be deleted anyway
	 */

	switch(_c->state) {
		case CS_NEW:
			/* Contact is not in the database
		 * yet, remove it from memory only
		 */
			return 0;

		case CS_SYNC:
		case CS_DIRTY:
			/* Remove from database here */
			return 1;
	}

	return 0; /* Makes gcc happy */
}


/*!
 * \brief Called when the timer is about flushing the contact, updates contact state
 * \param _c flushed contact
 * \return 1 if the contact should be inserted, 2 if update and 0 otherwise
 */
int st_flush_ucontact(ucontact_t *_c)
{
	switch(_c->state) {
		case CS_NEW:
			/* Contact is new and is not in
		 * the database yet so we have
		 * to insert it
		 */
			_c->state = CS_SYNC;
			return 1;

		case CS_SYNC:
			/* Contact is synchronized, do
		 * nothing
		 */
			return 0;

		case CS_DIRTY:
			/* Contact has been modified and
		 * is in the db already so we
		 * have to update it
		 */
			_c->state = CS_SYNC;
			return 2;
	}

	return 0; /* Makes gcc happy */
}


/* ============== Database related functions ================ */

extern unsigned int _ul_max_partition;
static unsigned int _ul_partition_counter = 0;

/*!
 * \brief Insert contact into the database
 * \param _c inserted contact
 * \return 0 on success, -1 on failure
 */
int db_insert_ucontact(ucontact_t *_c)
{
	char *dom;
	db_key_t keys[22];
	db_val_t vals[22];
	int nr_cols;

	if(_c->flags & FL_MEM) {
		return 0;
	}
	if(unlikely(_c->ruid.len <= 0)) {
		LM_ERR("invalid ruid for aor: %.*s\n", _c->aor->len, ZSW(_c->aor->s));
		return -1;
	}


	keys[0] = &ul_user_col;
	vals[0].type = DB1_STR;
	vals[0].nul = 0;
	vals[0].val.str_val.s = _c->aor->s;
	vals[0].val.str_val.len = _c->aor->len;

	keys[1] = &ul_contact_col;
	vals[1].type = DB1_STR;
	vals[1].nul = 0;
	vals[1].val.str_val.s = _c->c.s;
	vals[1].val.str_val.len = _c->c.len;

	keys[2] = &ul_expires_col;
	vals[2].nul = 0;
	UL_DB_EXPIRES_SET(&vals[2], _c->expires);

	keys[3] = &ul_q_col;
	vals[3].type = DB1_DOUBLE;
	vals[3].nul = 0;
	vals[3].val.double_val = q2double(_c->q);

	keys[4] = &ul_callid_col;
	vals[4].type = DB1_STR;
	vals[4].nul = 0;
	vals[4].val.str_val.s = _c->callid.s;
	vals[4].val.str_val.len = _c->callid.len;

	keys[5] = &ul_cseq_col;
	vals[5].type = DB1_INT;
	vals[5].nul = 0;
	vals[5].val.int_val = _c->cseq;

	keys[6] = &ul_flags_col;
	vals[6].type = DB1_INT;
	vals[6].nul = 0;
	vals[6].val.bitmap_val = _c->flags;

	keys[7] = &ul_cflags_col;
	vals[7].type = DB1_INT;
	vals[7].nul = 0;
	vals[7].val.bitmap_val = _c->cflags;

	keys[8] = &ul_user_agent_col;
	vals[8].type = DB1_STR;
	vals[8].nul = 0;
	vals[8].val.str_val.s = _c->user_agent.s;
	vals[8].val.str_val.len = _c->user_agent.len;

	nr_cols = 9;

	if(_c->received.s) {
		keys[nr_cols] = &ul_received_col;
		vals[nr_cols].type = DB1_STR;
		vals[nr_cols].nul = 0;
		vals[nr_cols].val.str_val.s = _c->received.s;
		vals[nr_cols].val.str_val.len = _c->received.len;
		nr_cols++;
	} else if(ul_db_insert_null != 0) {
		keys[nr_cols] = &ul_received_col;
		vals[nr_cols].type = DB1_STR;
		vals[nr_cols].nul = 1;
		nr_cols++;
	}

	if(_c->path.s) {
		keys[nr_cols] = &ul_path_col;
		vals[nr_cols].type = DB1_STR;
		vals[nr_cols].nul = 0;
		vals[nr_cols].val.str_val.s = _c->path.s;
		vals[nr_cols].val.str_val.len = _c->path.len;
		nr_cols++;
	} else if(ul_db_insert_null != 0) {
		keys[nr_cols] = &ul_path_col;
		vals[nr_cols].type = DB1_STR;
		vals[nr_cols].nul = 1;
		nr_cols++;
	}

	if(_c->sock) {
		keys[nr_cols] = &ul_sock_col;
		vals[nr_cols].type = DB1_STR;
		vals[nr_cols].val.str_val = _c->sock->sock_str;
		vals[nr_cols].nul = 0;
		nr_cols++;
	} else if(ul_db_insert_null != 0) {
		keys[nr_cols] = &ul_sock_col;
		vals[nr_cols].type = DB1_STR;
		vals[nr_cols].nul = 1;
		nr_cols++;
	}

	if(_c->methods != 0xFFFFFFFF) {
		keys[nr_cols] = &ul_methods_col;
		vals[nr_cols].type = DB1_BITMAP;
		vals[nr_cols].val.bitmap_val = _c->methods;
		vals[nr_cols].nul = 0;
		nr_cols++;
	} else if(ul_db_insert_null != 0) {
		keys[nr_cols] = &ul_methods_col;
		vals[nr_cols].type = DB1_BITMAP;
		vals[nr_cols].nul = 1;
		nr_cols++;
	}

	keys[nr_cols] = &ul_last_mod_col;
	vals[nr_cols].nul = 0;
	UL_DB_EXPIRES_SET(&vals[nr_cols], _c->last_modified);
	nr_cols++;


	if(_c->ruid.len > 0) {
		keys[nr_cols] = &ul_ruid_col;
		vals[nr_cols].type = DB1_STR;
		vals[nr_cols].nul = 0;
		vals[nr_cols].val.str_val = _c->ruid;
		nr_cols++;
	} else if(ul_db_insert_null != 0) {
		keys[nr_cols] = &ul_ruid_col;
		vals[nr_cols].type = DB1_STR;
		vals[nr_cols].nul = 1;
		nr_cols++;
	}

	if(_c->instance.len > 0) {
		keys[nr_cols] = &ul_instance_col;
		vals[nr_cols].type = DB1_STR;
		vals[nr_cols].nul = 0;
		vals[nr_cols].val.str_val = _c->instance;
		nr_cols++;
	} else if(ul_db_insert_null != 0) {
		keys[nr_cols] = &ul_instance_col;
		vals[nr_cols].type = DB1_STR;
		vals[nr_cols].nul = 1;
		nr_cols++;
	}

	keys[nr_cols] = &ul_reg_id_col;
	vals[nr_cols].type = DB1_INT;
	vals[nr_cols].nul = 0;
	vals[nr_cols].val.int_val = (int)_c->reg_id;
	nr_cols++;

	keys[nr_cols] = &ul_srv_id_col;
	vals[nr_cols].type = DB1_INT;
	vals[nr_cols].nul = 0;
	vals[nr_cols].val.int_val = (int)_c->server_id;
	nr_cols++;

	keys[nr_cols] = &ul_con_id_col;
	vals[nr_cols].type = DB1_INT;
	vals[nr_cols].nul = 0;
	vals[nr_cols].val.int_val = (int)_c->tcpconn_id;
	nr_cols++;

	keys[nr_cols] = &ul_keepalive_col;
	vals[nr_cols].type = DB1_INT;
	vals[nr_cols].nul = 0;
	vals[nr_cols].val.int_val = (int)_c->keepalive;
	nr_cols++;

	keys[nr_cols] = &ul_partition_col;
	vals[nr_cols].type = DB1_INT;
	vals[nr_cols].nul = 0;
	if(_ul_max_partition > 0) {
		vals[nr_cols].val.int_val =
				((_ul_partition_counter++) + my_pid()) % _ul_max_partition;
	} else {
		vals[nr_cols].val.int_val = 0;
	}
	nr_cols++;


	if(ul_use_domain) {
		keys[nr_cols] = &ul_domain_col;
		vals[nr_cols].type = DB1_STR;
		vals[nr_cols].nul = 0;

		dom = memchr(_c->aor->s, '@', _c->aor->len);
		if(dom == 0) {
			vals[0].val.str_val.len = 0;
			vals[nr_cols].val.str_val = *_c->aor;
		} else {
			vals[0].val.str_val.len = dom - _c->aor->s;
			vals[nr_cols].val.str_val.s = dom + 1;
			vals[nr_cols].val.str_val.len = _c->aor->s + _c->aor->len - dom - 1;
		}
		nr_cols++;
	}

	if(ul_dbf.use_table(ul_dbh, _c->domain) < 0) {
		if(_c->domain) {
			LM_ERR("sql use_table failed for: %.*s\n",
					(_c->domain->s) ? _c->domain->len : 0,
					(_c->domain->s) ? _c->domain->s : "");
		} else {
			LM_ERR("sql use_table failed - null\n");
		}
		return -1;
	}

	if(ul_db_insert_update && ul_dbf.insert_update) {
		if(ul_dbf.insert_update(ul_dbh, keys, vals, nr_cols) < 0) {
			LM_ERR("inserting with update contact in db failed %.*s (%.*s)\n",
					_c->aor->len, ZSW(_c->aor->s), _c->ruid.len,
					ZSW(_c->ruid.s));
			return -1;
		}
	} else {
		if(ul_dbf.insert(ul_dbh, keys, vals, nr_cols) < 0) {
			LM_ERR("inserting contact in db failed %.*s (%.*s)\n", _c->aor->len,
					ZSW(_c->aor->s), _c->ruid.len, ZSW(_c->ruid.s));
			return -1;
		}
	}

	if(ul_xavp_contact_name.s) {
		uldb_insert_attrs(_c->domain, &vals[0].val.str_val,
				&vals[nr_cols - 1].val.str_val, &_c->ruid, _c->xavp);
	}

	return 0;
}


/*!
 * \brief Update contact in the database by address
 * \param _c updated contact
 * \return 0 on success, -1 on failure
 */
int db_update_ucontact_addr(ucontact_t *_c)
{
	char *dom;
	db_key_t keys1[4];
	db_val_t vals1[4];
	int n1 = 0;

	db_key_t keys2[19];
	db_val_t vals2[19];
	int nr_cols2 = 0;


	if(_c->flags & FL_MEM) {
		return 0;
	}

	keys1[n1] = &ul_user_col;
	vals1[n1].type = DB1_STR;
	vals1[n1].nul = 0;
	vals1[n1].val.str_val = *_c->aor;
	LM_DBG("aor:%.*s\n", vals1[n1].val.str_val.len, vals1[n1].val.str_val.s);
	n1++;

	keys1[n1] = &ul_contact_col;
	vals1[n1].type = DB1_STR;
	vals1[n1].nul = 0;
	vals1[n1].val.str_val = _c->c;
	LM_DBG("contact:%.*s\n", vals1[n1].val.str_val.len,
			vals1[n1].val.str_val.s);
	n1++;

	switch(ul_matching_mode) {
		case CONTACT_ONLY:
			/* update call-id */
			keys2[nr_cols2] = &ul_callid_col;
			vals2[nr_cols2].type = DB1_STR;
			vals2[nr_cols2].nul = 0;
			vals2[nr_cols2].val.str_val = _c->callid;
			nr_cols2++;
			/* update path */
			keys2[nr_cols2] = &ul_path_col;
			vals2[nr_cols2].type = DB1_STR;
			if(_c->path.s == 0) {
				vals2[nr_cols2].nul = 1;
			} else {
				vals2[nr_cols2].nul = 0;
				vals2[nr_cols2].val.str_val = _c->path;
			}
			nr_cols2++;
			break;
		case CONTACT_CALLID:
			keys1[n1] = &ul_callid_col;
			vals1[n1].type = DB1_STR;
			vals1[n1].nul = 0;
			vals1[n1].val.str_val = _c->callid;
			LM_DBG("callid:%.*s\n", vals1[n1].val.str_val.len,
					vals1[n1].val.str_val.s);
			n1++;
			/* update path */
			keys2[nr_cols2] = &ul_path_col;
			vals2[nr_cols2].type = DB1_STR;
			if(_c->path.s == 0) {
				vals2[nr_cols2].nul = 1;
			} else {
				vals2[nr_cols2].nul = 0;
				vals2[nr_cols2].val.str_val = _c->path;
			}
			nr_cols2++;
			break;
		case CONTACT_PATH:
			keys1[n1] = &ul_path_col;
			vals1[n1].type = DB1_STR;
			if(_c->path.s == 0) {
				vals1[n1].nul = 1;
				LM_DBG("path: NULL\n");
			} else {
				vals1[n1].nul = 0;
				vals1[n1].val.str_val = _c->path;
				LM_DBG("path:%.*s\n", vals1[n1].val.str_val.len,
						vals1[n1].val.str_val.s);
			}
			n1++;
			/* update call-id */
			keys2[nr_cols2] = &ul_callid_col;
			vals2[nr_cols2].type = DB1_STR;
			vals2[nr_cols2].nul = 0;
			vals2[nr_cols2].val.str_val = _c->callid;
			nr_cols2++;
			break;
		default:
			LM_CRIT("unknown matching_mode %d\n", ul_matching_mode);
			return -1;
	}

	keys2[nr_cols2] = &ul_expires_col;
	vals2[nr_cols2].nul = 0;
	UL_DB_EXPIRES_SET(&vals2[nr_cols2], _c->expires);
	nr_cols2++;

	keys2[nr_cols2] = &ul_q_col;
	vals2[nr_cols2].type = DB1_DOUBLE;
	vals2[nr_cols2].nul = 0;
	vals2[nr_cols2].val.double_val = q2double(_c->q);
	nr_cols2++;

	keys2[nr_cols2] = &ul_cseq_col;
	vals2[nr_cols2].type = DB1_INT;
	vals2[nr_cols2].nul = 0;
	vals2[nr_cols2].val.int_val = _c->cseq;
	nr_cols2++;

	keys2[nr_cols2] = &ul_flags_col;
	vals2[nr_cols2].type = DB1_INT;
	vals2[nr_cols2].nul = 0;
	vals2[nr_cols2].val.bitmap_val = _c->flags;
	nr_cols2++;

	keys2[nr_cols2] = &ul_cflags_col;
	vals2[nr_cols2].type = DB1_INT;
	vals2[nr_cols2].nul = 0;
	vals2[nr_cols2].val.bitmap_val = _c->cflags;
	nr_cols2++;

	keys2[nr_cols2] = &ul_user_agent_col;
	vals2[nr_cols2].type = DB1_STR;
	vals2[nr_cols2].nul = 0;
	vals2[nr_cols2].val.str_val = _c->user_agent;
	nr_cols2++;

	keys2[nr_cols2] = &ul_received_col;
	vals2[nr_cols2].type = DB1_STR;
	if(_c->received.s == 0) {
		vals2[nr_cols2].nul = 1;
	} else {
		vals2[nr_cols2].nul = 0;
		vals2[nr_cols2].val.str_val = _c->received;
	}
	nr_cols2++;

	keys2[nr_cols2] = &ul_sock_col;
	vals2[nr_cols2].type = DB1_STR;
	if(_c->sock) {
		vals2[nr_cols2].val.str_val = _c->sock->sock_str;
		vals2[nr_cols2].nul = 0;
	} else {
		vals2[nr_cols2].nul = 1;
	}
	nr_cols2++;

	keys2[nr_cols2] = &ul_methods_col;
	vals2[nr_cols2].type = DB1_BITMAP;
	if(_c->methods == 0xFFFFFFFF) {
		vals2[nr_cols2].nul = 1;
	} else {
		vals2[nr_cols2].val.bitmap_val = _c->methods;
		vals2[nr_cols2].nul = 0;
	}
	nr_cols2++;

	keys2[nr_cols2] = &ul_last_mod_col;
	vals2[nr_cols2].nul = 0;
	UL_DB_EXPIRES_SET(&vals2[nr_cols2], _c->last_modified);
	nr_cols2++;

	keys2[nr_cols2] = &ul_ruid_col;
	vals2[nr_cols2].type = DB1_STR;
	if(_c->ruid.len > 0) {
		vals2[nr_cols2].nul = 0;
		vals2[nr_cols2].val.str_val = _c->ruid;
	} else {
		vals2[nr_cols2].nul = 1;
	}
	nr_cols2++;

	keys2[nr_cols2] = &ul_instance_col;
	vals2[nr_cols2].type = DB1_STR;
	if(_c->instance.len > 0) {
		vals2[nr_cols2].nul = 0;
		vals2[nr_cols2].val.str_val = _c->instance;
	} else {
		vals2[nr_cols2].nul = 1;
	}
	nr_cols2++;

	keys2[nr_cols2] = &ul_reg_id_col;
	vals2[nr_cols2].type = DB1_INT;
	vals2[nr_cols2].nul = 0;
	vals2[nr_cols2].val.int_val = (int)_c->reg_id;
	nr_cols2++;

	keys2[nr_cols2] = &ul_srv_id_col;
	vals2[nr_cols2].type = DB1_INT;
	vals2[nr_cols2].nul = 0;
	vals2[nr_cols2].val.int_val = (int)_c->server_id;
	nr_cols2++;

	keys2[nr_cols2] = &ul_con_id_col;
	vals2[nr_cols2].type = DB1_INT;
	vals2[nr_cols2].nul = 0;
	vals2[nr_cols2].val.int_val = (int)_c->tcpconn_id;
	nr_cols2++;

	keys2[nr_cols2] = &ul_keepalive_col;
	vals2[nr_cols2].type = DB1_INT;
	vals2[nr_cols2].nul = 0;
	vals2[nr_cols2].val.int_val = (int)_c->keepalive;
	nr_cols2++;

	keys2[nr_cols2] = &ul_contact_col;
	vals2[nr_cols2].type = DB1_STR;
	vals2[nr_cols2].nul = 0;
	vals2[nr_cols2].val.str_val = _c->c;
	LM_DBG("contact:%.*s\n", vals2[nr_cols2].val.str_val.len,
			vals2[nr_cols2].val.str_val.s);
	nr_cols2++;

	if(ul_use_domain) {
		keys1[n1] = &ul_domain_col;
		vals1[n1].type = DB1_STR;
		vals1[n1].nul = 0;
		dom = memchr(_c->aor->s, '@', _c->aor->len);
		if(dom == 0) {
			vals1[0].val.str_val.len = 0;
			vals1[n1].val.str_val = *_c->aor;
		} else {
			vals1[0].val.str_val.len = dom - _c->aor->s;
			vals1[n1].val.str_val.s = dom + 1;
			vals1[n1].val.str_val.len = _c->aor->s + _c->aor->len - dom - 1;
		}
		n1++;
	}

	if(ul_dbf.use_table(ul_dbh, _c->domain) < 0) {
		LM_ERR("sql use_table failed\n");
		return -1;
	}

	if(ul_dbf.update(ul_dbh, keys1, 0, vals1, keys2, vals2, n1, nr_cols2) < 0) {
		LM_ERR("updating database failed\n");
		return -1;
	}

	if(ul_db_check_update == 1 && ul_dbf.affected_rows) {
		/* supposed to be an UPDATE, but if affected rows is 0, then try
		 * to do an INSERT */
		if(ul_dbf.affected_rows(ul_dbh) == 0) {
			LM_DBG("affected rows by UPDATE was 0, doing an INSERT\n");
			if(db_insert_ucontact(_c) < 0)
				return -1;
		}
	}
	/* delete old db attrs and add the current list */
	if(ul_xavp_contact_name.s) {
		if(ul_use_domain) {
			uldb_delete_attrs(_c->domain, &vals1[0].val.str_val,
					&vals1[n1 - 1].val.str_val, &_c->ruid);
			uldb_insert_attrs(_c->domain, &vals1[0].val.str_val,
					&vals1[n1 - 1].val.str_val, &_c->ruid, _c->xavp);
		} else {
			uldb_delete_attrs(
					_c->domain, &vals1[0].val.str_val, NULL, &_c->ruid);
			uldb_insert_attrs(_c->domain, &vals1[0].val.str_val, NULL,
					&_c->ruid, _c->xavp);
		}
	}

	return 0;
}

/*!
 * \brief Update contact in the database by ruid
 * \param _c updated contact
 * \return 0 on success, -1 on failure
 */
int db_update_ucontact_ruid(ucontact_t *_c)
{
	str auser;
	str adomain;
	db_key_t keys1[1];
	db_val_t vals1[1];
	int n1;

	db_key_t keys2[18];
	db_val_t vals2[18];
	int n2;


	if(_c->flags & FL_MEM) {
		return 0;
	}

	if(_c->ruid.len <= 0) {
		LM_ERR("updating record in database failed - empty ruid\n");
		return -1;
	}

	if(ul_dbh == NULL) {
		LM_ERR("database connection handle not initialized\n");
		return -1;
	}

	n1 = 0;
	keys1[n1] = &ul_ruid_col;
	vals1[n1].type = DB1_STR;
	vals1[n1].nul = 0;
	vals1[n1].val.str_val = _c->ruid;
	LM_DBG("ruid:%.*s\n", vals1[n1].val.str_val.len, vals1[n1].val.str_val.s);
	n1++;

	n2 = 0;
	keys2[n2] = &ul_expires_col;
	vals2[n2].nul = 0;
	UL_DB_EXPIRES_SET(&vals2[n2], _c->expires);
	n2++;

	keys2[n2] = &ul_q_col;
	vals2[n2].type = DB1_DOUBLE;
	vals2[n2].nul = 0;
	vals2[n2].val.double_val = q2double(_c->q);
	n2++;

	keys2[n2] = &ul_cseq_col;
	vals2[n2].type = DB1_INT;
	vals2[n2].nul = 0;
	vals2[n2].val.int_val = _c->cseq;
	n2++;

	keys2[n2] = &ul_flags_col;
	vals2[n2].type = DB1_INT;
	vals2[n2].nul = 0;
	vals2[n2].val.bitmap_val = _c->flags;
	n2++;

	keys2[n2] = &ul_cflags_col;
	vals2[n2].type = DB1_INT;
	vals2[n2].nul = 0;
	vals2[n2].val.bitmap_val = _c->cflags;
	n2++;

	keys2[n2] = &ul_user_agent_col;
	vals2[n2].type = DB1_STR;
	vals2[n2].nul = 0;
	vals2[n2].val.str_val = _c->user_agent;
	n2++;

	keys2[n2] = &ul_received_col;
	vals2[n2].type = DB1_STR;
	if(_c->received.s == 0) {
		vals2[n2].nul = 1;
	} else {
		vals2[n2].nul = 0;
		vals2[n2].val.str_val = _c->received;
	}
	n2++;

	keys2[n2] = &ul_path_col;
	vals2[n2].type = DB1_STR;
	if(_c->path.s == 0) {
		vals2[n2].nul = 1;
	} else {
		vals2[n2].nul = 0;
		vals2[n2].val.str_val = _c->path;
	}
	n2++;

	keys2[n2] = &ul_sock_col;
	vals2[n2].type = DB1_STR;
	if(_c->sock) {
		vals2[n2].val.str_val = _c->sock->sock_str;
		vals2[n2].nul = 0;
	} else {
		vals2[n2].nul = 1;
	}
	n2++;

	keys2[n2] = &ul_methods_col;
	vals2[n2].type = DB1_BITMAP;
	if(_c->methods == 0xFFFFFFFF) {
		vals2[n2].nul = 1;
	} else {
		vals2[n2].val.bitmap_val = _c->methods;
		vals2[n2].nul = 0;
	}
	n2++;

	keys2[n2] = &ul_last_mod_col;
	vals2[n2].nul = 0;
	UL_DB_EXPIRES_SET(&vals2[n2], _c->last_modified);
	n2++;

	keys2[n2] = &ul_callid_col;
	vals2[n2].type = DB1_STR;
	vals2[n2].nul = 0;
	vals2[n2].val.str_val = _c->callid;
	n2++;

	keys2[n2] = &ul_instance_col;
	vals2[n2].type = DB1_STR;
	if(_c->instance.len > 0) {
		vals2[n2].nul = 0;
		vals2[n2].val.str_val = _c->instance;
	} else {
		vals2[n2].nul = 1;
	}
	n2++;

	keys2[n2] = &ul_reg_id_col;
	vals2[n2].type = DB1_INT;
	vals2[n2].nul = 0;
	vals2[n2].val.int_val = (int)_c->reg_id;
	n2++;

	keys2[n2] = &ul_srv_id_col;
	vals2[n2].type = DB1_INT;
	vals2[n2].nul = 0;
	vals2[n2].val.int_val = (int)_c->server_id;
	n2++;

	keys2[n2] = &ul_con_id_col;
	vals2[n2].type = DB1_INT;
	vals2[n2].nul = 0;
	vals2[n2].val.int_val = (int)_c->tcpconn_id;
	n2++;

	keys2[n2] = &ul_keepalive_col;
	vals2[n2].type = DB1_INT;
	vals2[n2].nul = 0;
	vals2[n2].val.int_val = (int)_c->keepalive;
	n2++;

	keys2[n2] = &ul_contact_col;
	vals2[n2].type = DB1_STR;
	vals2[n2].nul = 0;
	vals2[n2].val.str_val = _c->c;
	LM_DBG("contact:%.*s\n", vals2[n2].val.str_val.len,
			vals2[n2].val.str_val.s);
	n2++;

	if(ul_dbf.use_table(ul_dbh, _c->domain) < 0) {
		LM_ERR("sql use_table failed\n");
		return -1;
	}

	if(ul_dbf.update(ul_dbh, keys1, 0, vals1, keys2, vals2, n1, n2) < 0) {
		LM_ERR("updating database failed\n");
		return -1;
	}

	if(ul_db_check_update == 1 && ul_dbf.affected_rows) {
		/* supposed to be an UPDATE, but if affected rows is 0, then try
		 * to do an INSERT */
		if(ul_dbf.affected_rows(ul_dbh) == 0) {
			LM_DBG("affected rows by UPDATE was 0, doing an INSERT\n");
			if(db_insert_ucontact(_c) < 0)
				return -1;
		}
	}

	/* delete old db attrs and add the current list */
	if(ul_xavp_contact_name.s) {
		auser = *_c->aor;
		if(ul_use_domain) {
			adomain.s = memchr(_c->aor->s, '@', _c->aor->len);
			if(adomain.s == 0) {
				auser.len = 0;
				adomain = *_c->aor;
			} else {
				auser.len = adomain.s - _c->aor->s;
				adomain.s++;
				adomain.len = _c->aor->s + _c->aor->len - adomain.s;
			}

			uldb_delete_attrs(_c->domain, &auser, &adomain, &_c->ruid);
			uldb_insert_attrs(
					_c->domain, &auser, &adomain, &_c->ruid, _c->xavp);
		} else {
			uldb_delete_attrs(_c->domain, &auser, NULL, &_c->ruid);
			uldb_insert_attrs(_c->domain, &auser, NULL, &_c->ruid, _c->xavp);
		}
	}

	return 0;
}

/*!
 * \brief Update contact in the database by instance reg_id
 * \param _c updated contact
 * \return 0 on success, -1 on failure
 */
int db_update_ucontact_instance(ucontact_t *_c)
{
	str auser;
	str adomain;
	db_key_t keys1[4];
	db_val_t vals1[4];
	int n1;

	db_key_t keys2[16];
	db_val_t vals2[16];
	int n2;


	if(_c->flags & FL_MEM) {
		return 0;
	}

	if(_c->instance.len <= 0) {
		LM_ERR("updating record in database failed - empty instance\n");
		return -1;
	}

	n1 = 0;
	keys1[n1] = &ul_user_col;
	vals1[n1].type = DB1_STR;
	vals1[n1].nul = 0;
	vals1[n1].val.str_val = *_c->aor;
	LM_DBG("aor:%.*s\n", vals1[n1].val.str_val.len, vals1[n1].val.str_val.s);
	n1++;

	keys1[n1] = &ul_instance_col;
	vals1[n1].type = DB1_STR;
	vals1[n1].nul = 0;
	vals1[n1].val.str_val = _c->instance;
	LM_DBG("instance:%.*s\n", vals1[n1].val.str_val.len,
			vals1[n1].val.str_val.s);
	n1++;

	keys1[n1] = &ul_reg_id_col;
	vals1[n1].type = DB1_INT;
	vals1[n1].nul = 0;
	vals1[n1].val.int_val = (int)_c->reg_id;
	LM_DBG("reg-id:%d\n", vals1[n1].val.int_val);
	n1++;

	n2 = 0;
	keys2[n2] = &ul_expires_col;
	vals2[n2].nul = 0;
	UL_DB_EXPIRES_SET(&vals2[n2], _c->expires);
	n2++;

	keys2[n2] = &ul_q_col;
	vals2[n2].type = DB1_DOUBLE;
	vals2[n2].nul = 0;
	vals2[n2].val.double_val = q2double(_c->q);
	n2++;

	keys2[n2] = &ul_cseq_col;
	vals2[n2].type = DB1_INT;
	vals2[n2].nul = 0;
	vals2[n2].val.int_val = _c->cseq;
	n2++;

	keys2[n2] = &ul_flags_col;
	vals2[n2].type = DB1_INT;
	vals2[n2].nul = 0;
	vals2[n2].val.bitmap_val = _c->flags;
	n2++;

	keys2[n2] = &ul_cflags_col;
	vals2[n2].type = DB1_INT;
	vals2[n2].nul = 0;
	vals2[n2].val.bitmap_val = _c->cflags;
	n2++;

	keys2[n2] = &ul_user_agent_col;
	vals2[n2].type = DB1_STR;
	vals2[n2].nul = 0;
	vals2[n2].val.str_val = _c->user_agent;
	n2++;

	keys2[n2] = &ul_received_col;
	vals2[n2].type = DB1_STR;
	if(_c->received.s == 0) {
		vals2[n2].nul = 1;
	} else {
		vals2[n2].nul = 0;
		vals2[n2].val.str_val = _c->received;
	}
	n2++;

	keys2[n2] = &ul_path_col;
	vals2[n2].type = DB1_STR;
	if(_c->path.s == 0) {
		vals2[n2].nul = 1;
	} else {
		vals2[n2].nul = 0;
		vals2[n2].val.str_val = _c->path;
	}
	n2++;

	keys2[n2] = &ul_sock_col;
	vals2[n2].type = DB1_STR;
	if(_c->sock) {
		vals2[n2].val.str_val = _c->sock->sock_str;
		vals2[n2].nul = 0;
	} else {
		vals2[n2].nul = 1;
	}
	n2++;

	keys2[n2] = &ul_methods_col;
	vals2[n2].type = DB1_BITMAP;
	if(_c->methods == 0xFFFFFFFF) {
		vals2[n2].nul = 1;
	} else {
		vals2[n2].val.bitmap_val = _c->methods;
		vals2[n2].nul = 0;
	}
	n2++;

	keys2[n2] = &ul_last_mod_col;
	vals2[n2].nul = 0;
	UL_DB_EXPIRES_SET(&vals2[n2], _c->last_modified);
	n2++;

	keys2[n2] = &ul_callid_col;
	vals2[n2].type = DB1_STR;
	vals2[n2].nul = 0;
	vals2[n2].val.str_val = _c->callid;
	n2++;

	keys2[n2] = &ul_srv_id_col;
	vals2[n2].type = DB1_INT;
	vals2[n2].nul = 0;
	vals2[n2].val.bitmap_val = _c->server_id;
	n2++;

	keys2[n2] = &ul_con_id_col;
	vals2[n2].type = DB1_INT;
	vals2[n2].nul = 0;
	vals2[n2].val.bitmap_val = _c->tcpconn_id;
	n2++;

	keys2[n2] = &ul_keepalive_col;
	vals2[n2].type = DB1_INT;
	vals2[n2].nul = 0;
	vals2[n2].val.bitmap_val = _c->keepalive;
	n2++;

	keys2[n2] = &ul_contact_col;
	vals2[n2].type = DB1_STR;
	vals2[n2].nul = 0;
	vals2[n2].val.str_val.s = _c->c.s;
	vals2[n2].val.str_val.len = _c->c.len;
	LM_DBG("contact:%.*s\n", vals2[n2].val.str_val.len,
			vals2[n2].val.str_val.s);
	n2++;

	auser = *_c->aor;
	if(ul_use_domain) {
		keys1[n1] = &ul_domain_col;
		vals1[n1].type = DB1_STR;
		vals1[n1].nul = 0;
		adomain.s = memchr(_c->aor->s, '@', _c->aor->len);
		if(adomain.s == 0) {
			vals1[0].val.str_val.len = 0;
			vals1[n1].val.str_val = *_c->aor;
			auser.len = 0;
			adomain = *_c->aor;
		} else {
			vals1[0].val.str_val.len = adomain.s - _c->aor->s;
			vals1[n1].val.str_val.s = adomain.s + 1;
			vals1[n1].val.str_val.len =
					_c->aor->s + _c->aor->len - adomain.s - 1;
			auser.len = adomain.s - _c->aor->s;
			adomain.s++;
			adomain.len = _c->aor->s + _c->aor->len - adomain.s;
		}
		n1++;
	}

	if(ul_dbf.use_table(ul_dbh, _c->domain) < 0) {
		LM_ERR("sql use_table failed\n");
		return -1;
	}

	if(ul_dbf.update(ul_dbh, keys1, 0, vals1, keys2, vals2, n1, n2) < 0) {
		LM_ERR("updating database failed\n");
		return -1;
	}

	if(ul_db_check_update == 1 && ul_dbf.affected_rows) {
		LM_DBG("update affected_rows 0\n");
		/* supposed to be an UPDATE, but if affected rows is 0, then try
		 * to do an INSERT */
		if(ul_dbf.affected_rows(ul_dbh) == 0) {
			LM_DBG("affected rows by UPDATE was 0, doing an INSERT\n");
			if(db_insert_ucontact(_c) < 0)
				return -1;
		}
	}

	/* delete old db attrs and add the current list */
	if(ul_xavp_contact_name.s) {
		if(ul_use_domain) {
			uldb_delete_attrs(_c->domain, &auser, &adomain, &_c->ruid);
			uldb_insert_attrs(
					_c->domain, &auser, &adomain, &_c->ruid, _c->xavp);
		} else {
			uldb_delete_attrs(_c->domain, &auser, NULL, &_c->ruid);
			uldb_insert_attrs(_c->domain, &auser, NULL, &_c->ruid, _c->xavp);
		}
	}

	return 0;
}

/*!
 * \brief Update contact in the database
 * \param _c updated contact
 * \return 0 on success, -1 on failure
 */
int db_update_ucontact(ucontact_t *_c)
{
	if(ul_db_ops_ruid == 0)
		if(_c->instance.len <= 0) {
			return db_update_ucontact_addr(_c);
		} else {
			return db_update_ucontact_instance(_c);
		}
	else
		return db_update_ucontact_ruid(_c);
}

/*!
 * \brief Delete contact from the database by address
 * \param _c deleted contact
 * \return 0 on success, -1 on failure
 */
int db_delete_ucontact_addr(ucontact_t *_c)
{
	char *dom;
	db_key_t keys[4];
	db_val_t vals[4];
	int n;

	if(_c->flags & FL_MEM) {
		return 0;
	}


	n = 0;
	keys[n] = &ul_user_col;
	vals[n].type = DB1_STR;
	vals[n].nul = 0;
	vals[n].val.str_val = *_c->aor;
	n++;

	keys[n] = &ul_contact_col;
	vals[n].type = DB1_STR;
	vals[n].nul = 0;
	vals[n].val.str_val = _c->c;
	n++;

	switch(ul_matching_mode) {
		case CONTACT_ONLY:
			break;
		case CONTACT_CALLID:
			keys[n] = &ul_callid_col;
			vals[n].type = DB1_STR;
			vals[n].nul = 0;
			vals[n].val.str_val = _c->callid;
			n++;
			break;
		case CONTACT_PATH:
			keys[n] = &ul_path_col;
			vals[n].type = DB1_STR;
			if(_c->path.s == 0) {
				vals[n].nul = 1;
			} else {
				vals[n].nul = 0;
				vals[n].val.str_val = _c->path;
			}
			n++;
			break;
		default:
			LM_CRIT("unknown matching_mode %d\n", ul_matching_mode);
			return -1;
	}

	if(ul_use_domain) {
		keys[n] = &ul_domain_col;
		vals[n].type = DB1_STR;
		vals[n].nul = 0;
		dom = memchr(_c->aor->s, '@', _c->aor->len);
		if(dom == 0) {
			vals[0].val.str_val.len = 0;
			vals[n].val.str_val = *_c->aor;
		} else {
			vals[0].val.str_val.len = dom - _c->aor->s;
			vals[n].val.str_val.s = dom + 1;
			vals[n].val.str_val.len = _c->aor->s + _c->aor->len - dom - 1;
		}
		uldb_delete_attrs(_c->domain, &vals[0].val.str_val,
				&vals[n].val.str_val, &_c->ruid);
		n++;
	} else {
		uldb_delete_attrs(_c->domain, &vals[0].val.str_val, NULL, &_c->ruid);
	}

	if(ul_dbf.use_table(ul_dbh, _c->domain) < 0) {
		LM_ERR("sql use_table failed\n");
		return -1;
	}

	if(ul_dbf.delete(ul_dbh, keys, 0, vals, n) < 0) {
		LM_ERR("deleting from database failed\n");
		return -1;
	}

	return 0;
}

/*!
 * \brief Delete contact from the database by ruid
 * \param _c deleted contact
 * \return 0 on success, -1 on failure
 */
int db_delete_ucontact_ruid(ucontact_t *_c)
{
	db_key_t keys[1];
	db_val_t vals[1];
	int n;

	if(_c->flags & FL_MEM) {
		return 0;
	}

	if(_c->ruid.len <= 0) {
		LM_ERR("deleting from database failed - empty ruid\n");
		return -1;
	}

	n = 0;
	keys[n] = &ul_ruid_col;
	vals[n].type = DB1_STR;
	vals[n].nul = 0;
	vals[n].val.str_val = _c->ruid;
	n++;

	uldb_delete_attrs_ruid(_c->domain, &_c->ruid);

	if(ul_dbf.use_table(ul_dbh, _c->domain) < 0) {
		LM_ERR("sql use_table failed\n");
		return -1;
	}

	if(ul_dbf.delete(ul_dbh, keys, 0, vals, n) < 0) {
		LM_ERR("deleting from database failed\n");
		return -1;
	}

	return 0;
}

/*!
 * \brief Delete contact from the database
 * \param _c deleted contact
 * \return 0 on success, -1 on failure
 */
int db_delete_ucontact(ucontact_t *_c)
{
	if(ul_db_ops_ruid == 0)
		return db_delete_ucontact_addr(_c);
	else
		return db_delete_ucontact_ruid(_c);
}

/*!
 * \brief Remove a contact from list belonging to a certain record
 * \param _r record the contact belongs
 * \param _c removed contact
 */
static inline void unlink_contact(struct urecord *_r, ucontact_t *_c)
{
	if(_c->prev) {
		_c->prev->next = _c->next;
		if(_c->next) {
			_c->next->prev = _c->prev;
		}
	} else {
		_r->contacts = _c->next;
		if(_c->next) {
			_c->next->prev = 0;
		}
	}
}


/*!
 * \brief Insert a new contact into the list at the correct position
 * \param _r record that holds the sorted contacts
 * \param _c new contact
 */
static inline void update_contact_pos(struct urecord *_r, ucontact_t *_c)
{
	ucontact_t *pos, *ppos;

	if(ul_desc_time_order) {
		/* order by time - first the newest */
		if(_c->prev == 0)
			return;
		unlink_contact(_r, _c);
		/* insert it at the beginning */
		_c->next = _r->contacts;
		_c->prev = 0;
		_r->contacts->prev = _c;
		_r->contacts = _c;
	} else {
		/* order by q - first the smaller q */
		if((_c->prev == 0 || _c->q <= _c->prev->q)
				&& (_c->next == 0 || _c->q >= _c->next->q))
			return;
		/* need to move , but where? */
		unlink_contact(_r, _c);
		_c->next = _c->prev = 0;
		for(pos = _r->contacts, ppos = 0; pos && pos->q < _c->q;
				ppos = pos, pos = pos->next)
			;
		if(pos) {
			if(!pos->prev) {
				pos->prev = _c;
				_c->next = pos;
				_r->contacts = _c;
			} else {
				_c->next = pos;
				_c->prev = pos->prev;
				pos->prev->next = _c;
				pos->prev = _c;
			}
		} else if(ppos) {
			ppos->next = _c;
			_c->prev = ppos;
		} else {
			_r->contacts = _c;
		}
	}
}

/*!
 * \brief helper function for update_ucontact
 * \param _c contact
 * \return 0 on success, -1 on failure
 */
static inline int update_contact_db(ucontact_t *_c)
{
	int res;

	if(ul_db_update_as_insert)
		res = db_insert_ucontact(_c);
	else
		res = db_update_ucontact(_c);

	if(res < 0) {
		LM_ERR("failed to update database\n");
		return -1;
	} else {
		_c->state = CS_SYNC;
	}
	return 0;
}

/*!
 * \brief Update ucontact with new values
 * \param _r record the contact belongs to
 * \param _c updated contact
 * \param _ci new contact informations
 * \return 0 on success, -1 on failure
 */
int update_ucontact(struct urecord *_r, ucontact_t *_c, ucontact_info_t *_ci)
{
	struct urecord _ur;
	/* we have to update memory in any case, but database directly
	 * only in db_mode 1 */
	if(mem_update_ucontact(_c, _ci) < 0) {
		LM_ERR("failed to update memory\n");
		return -1;
	}

	if(ul_db_mode == DB_ONLY) {
		/* urecord is static generate a copy for later */
		if(_r)
			memcpy(&_ur, _r, sizeof(struct urecord));
		if(update_contact_db(_c) < 0)
			return -1;
	}

	/* run callbacks for UPDATE event */
	if(exists_ulcb_type(UL_CONTACT_UPDATE)) {
		LM_DBG("exists callback for type= UL_CONTACT_UPDATE\n");
		run_ul_callbacks(UL_CONTACT_UPDATE, _c);
	}

	if(_r) {
		if(ul_db_mode != DB_ONLY) {
			update_contact_pos(_r, _c);
		} else {
			/* urecord was static restore copy */
			memcpy(_r, &_ur, sizeof(struct urecord));
		}
	}

	st_update_ucontact(_c);

	if(ul_db_mode == WRITE_THROUGH) {
		if(update_contact_db(_c) < 0)
			return -1;
	}
	return 0;
}

/*!
 * \brief Load all location attributes from a udomain
 *
 * Load all location attributes from a udomain, useful to populate the
 * memory cache on startup.
 * \param _dname loaded domain name
 * \param _user sip username
 * \param _domain sip domain
 * \param _ruid usrloc record unique id
 * \return 0 on success, -1 on failure
 */
int uldb_delete_attrs(str *_dname, str *_user, str *_domain, str *_ruid)
{
	char tname_buf[64];
	str tname;
	db_key_t keys[3];
	db_val_t vals[3];
	int n = 0;

	if(ul_db_ops_ruid == 1 && _ruid) {
		return uldb_delete_attrs_ruid(_dname, _ruid);
	}

	LM_DBG("trying to delete location attributes\n");

	if(ul_xavp_contact_name.s == NULL) {
		/* feature disabled by mod param */
		return 0;
	}

	if(_dname->len + 6 >= 64) {
		LM_ERR("attributes table name is too big\n");
		return -1;
	}
	strncpy(tname_buf, _dname->s, _dname->len);
	tname_buf[_dname->len] = '\0';
	strcat(tname_buf, "_attrs");
	tname.s = tname_buf;
	tname.len = _dname->len + 6;

	keys[n] = &ulattrs_user_col;
	vals[n].type = DB1_STR;
	vals[n].nul = 0;
	vals[n].val.str_val = *_user;
	n++;

	if(_ruid) {
		keys[n] = &ulattrs_ruid_col;
		vals[n].type = DB1_STR;
		vals[n].nul = 0;
		vals[n].val.str_val = *_ruid;
		n++;
	}

	if(ul_use_domain) {
		keys[n] = &ulattrs_domain_col;
		vals[n].type = DB1_STR;
		vals[n].nul = 0;
		vals[n].val.str_val = *_domain;
		n++;
	}

	if(ul_dbf.use_table(ul_dbh, &tname) < 0) {
		LM_ERR("sql use_table failed\n");
		return -1;
	}

	if(ul_dbf.delete(ul_dbh, keys, 0, vals, n) < 0) {
		LM_ERR("deleting from database failed\n");
		return -1;
	}

	return 0;
}

/*!
 * \brief Delete all location attributes from a udomain by ruid
 *
 * \param _dname loaded domain name
 * \param _ruid usrloc record unique id
 * \return 0 on success, -1 on failure
 */
int uldb_delete_attrs_ruid(str *_dname, str *_ruid)
{
	char tname_buf[64];
	str tname;
	db_key_t keys[1];
	db_val_t vals[1];

	LM_DBG("trying to delete location attributes\n");

	if(ul_xavp_contact_name.s == NULL) {
		/* feature disabled by mod param */
		return 0;
	}

	if(_dname->len + 6 >= 64) {
		LM_ERR("attributes table name is too big\n");
		return -1;
	}
	strncpy(tname_buf, _dname->s, _dname->len);
	tname_buf[_dname->len] = '\0';
	strcat(tname_buf, "_attrs");
	tname.s = tname_buf;
	tname.len = _dname->len + 6;

	keys[0] = &ulattrs_ruid_col;

	vals[0].type = DB1_STR;
	vals[0].nul = 0;
	vals[0].val.str_val = *_ruid;

	if(ul_dbf.use_table(ul_dbh, &tname) < 0) {
		LM_ERR("sql use_table failed\n");
		return -1;
	}

	if(ul_dbf.delete(ul_dbh, keys, 0, vals, 1) < 0) {
		LM_ERR("deleting from database failed\n");
		return -1;
	}

	return 0;
}

/*!
 * \brief Insert contact attributes into the database
 * \param _dname loaded domain name
 * \param _user sip username
 * \param _domain sip domain
 * \param _ruid record unique id
 * \param _xhead head of xavp list
 * \return 0 on success, -1 on failure
 */
int uldb_insert_attrs(
		str *_dname, str *_user, str *_domain, str *_ruid, sr_xavp_t *_xhead)
{
	char tname_buf[64];
	str tname;
	str avalue;
	sr_xavp_t *xavp;
	db_key_t keys[7];
	db_val_t vals[7];
	int nr_cols;

	LM_DBG("trying to insert location attributes\n");

	if(ul_xavp_contact_name.s == NULL) {
		/* feature disabled by mod param */
		LM_DBG("location attributes disabled\n");
		return 0;
	}

	if(_xhead == NULL || _xhead->val.type != SR_XTYPE_XAVP
			|| _xhead->val.v.xavp == NULL) {
		/* nothing to write */
		LM_DBG("no location attributes\n");
		return 0;
	}

	if(_dname->len + 6 >= 64) {
		LM_ERR("attributes table name is too big\n");
		return -1;
	}
	strncpy(tname_buf, _dname->s, _dname->len);
	tname_buf[_dname->len] = '\0';
	strcat(tname_buf, "_attrs");
	tname.s = tname_buf;
	tname.len = _dname->len + 6;

	if(ul_dbf.use_table(ul_dbh, &tname) < 0) {
		LM_ERR("sql use_table failed for %.*s\n", tname.len, tname.s);
		return -1;
	}

	keys[0] = &ulattrs_user_col;
	keys[1] = &ulattrs_ruid_col;
	keys[2] = &ulattrs_last_mod_col;
	keys[3] = &ulattrs_aname_col;
	keys[4] = &ulattrs_atype_col;
	keys[5] = &ulattrs_avalue_col;
	keys[6] = &ulattrs_domain_col;

	vals[0].type = DB1_STR;
	vals[0].nul = 0;
	vals[0].val.str_val = *_user;

	vals[1].type = DB1_STR;
	vals[1].nul = 0;
	vals[1].val.str_val = *_ruid;

	vals[2].nul = 0;
	UL_DB_EXPIRES_SET(&vals[2], time(NULL));

	if(ul_use_domain && _domain != NULL && _domain->s != NULL) {
		nr_cols = 7;
		vals[6].type = DB1_STR;
		vals[6].nul = 0;
		vals[6].val.str_val = *_domain;

	} else {
		nr_cols = 6;
	}

	for(xavp = _xhead->val.v.xavp; xavp; xavp = xavp->next) {
		vals[3].type = DB1_STR;
		vals[3].nul = 0;
		vals[3].val.str_val = xavp->name;

		vals[4].type = DB1_INT;
		vals[4].nul = 0;
		if(xavp->val.type == SR_XTYPE_STR) {
			vals[4].val.int_val = 0;
			avalue = xavp->val.v.s;
		} else if(xavp->val.type == SR_XTYPE_LONG) {
			vals[4].val.int_val = 1;
			avalue.s = sint2str(xavp->val.v.l, &avalue.len);
		} else {
			continue;
		}

		vals[5].type = DB1_STR;
		vals[5].nul = 0;
		vals[5].val.str_val = avalue;

		if(ul_dbf.insert(ul_dbh, keys, vals, nr_cols) < 0) {
			LM_ERR("inserting contact in db failed\n");
			return -1;
		}
	}
	return 0;
}
