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


/*!
 * \file
 * \brief Profile related functions for the dialog module
 * \ingroup dialog
 * Module: \ref dialog
 */


#include "../../core/mem/shm_mem.h"
#include "../../core/hashes.h"
#include "../../core/trim.h"
#include "../../core/dprint.h"
#include "../../core/ut.h"
#include "../../core/route.h"
#include "../../core/dset.h"
#include "../../modules/tm/tm_load.h"
#include "../../core/utils/sruid.h"
#include "dlg_hash.h"
#include "dlg_var.h"
#include "dlg_handlers.h"
#include "dlg_profile.h"


/*! size of dialog profile hash */
#define PROFILE_HASH_SIZE 16

/*! tm bindings */
extern struct tm_binds d_tmb;

/*! global dialog message id */
static unsigned int current_dlg_msg_id = 0;
static unsigned int current_dlg_msg_pid = 0;

/*! pending dialog links */
static dlg_profile_link_t *current_pending_linkers = NULL;

/*! global dialog profile list */
static dlg_profile_table_t *profiles = NULL;


static dlg_profile_table_t *new_dlg_profile(
		str *name, unsigned int size, unsigned int has_value);

extern int update_dlg_timeout(dlg_cell_t *, int);

static sruid_t _dlg_profile_sruid;

/*!
 * \brief Add profile definitions to the global list
 * \see new_dlg_profile
 * \param profiles profile name
 * \param has_value set to 0 for a profile without value, otherwise it has a value
 * \return 0 on success, -1 on failure
 */
int add_profile_definitions(char *profiles, unsigned int has_value)
{
	char *p;
	char *d;
	str name;
	unsigned int i;

	if(profiles == NULL || strlen(profiles) == 0)
		return 0;

	p = profiles;
	do {
		/* locate name of profile */
		name.s = p;
		d = strchr(p, ';');
		if(d) {
			name.len = d - p;
			d++;
		} else {
			name.len = strlen(p);
		}

		/* we have the name -> trim it for spaces */
		trim_spaces_lr(name);

		/* check len name */
		if(name.len == 0)
			/* ignore */
			continue;

		/* check the name format */
		for(i = 0; i < name.len; i++) {
			if(!isalnum(name.s[i]) && name.s[i] != '_') {
				LM_ERR("bad profile name <%.*s>, char %c - use only "
					   "alphanumerical characters or '_'\n",
						name.len, name.s, name.s[i]);
				return -1;
			}
		}

		/* name ok -> create the profile */
		LM_DBG("creating profile <%.*s>\n", name.len, name.s);

		if(new_dlg_profile(&name, PROFILE_HASH_SIZE, has_value) == NULL) {
			LM_ERR("failed to create new profile <%.*s>\n", name.len, name.s);
			return -1;
		}

	} while((p = d) != NULL);

	return 0;
}


/*!
 * \brief Search a dialog profile in the global list
 * \note Linear search, this won't have the best performance for huge profile lists
 * \param name searched dialog profile
 * \return pointer to the profile on success, NULL otherwise
 */
struct dlg_profile_table *search_dlg_profile(str *name)
{
	struct dlg_profile_table *profile;

	for(profile = profiles; profile; profile = profile->next) {
		if(name->len == profile->name.len
				&& memcmp(name->s, profile->name.s, name->len) == 0)
			return profile;
	}
	return NULL;
}


/*!
 * \brief Creates a new dialog profile
 * \see add_profile_definitions
 * \param name profile name
 * \param size profile size
 * \param has_value set to 0 for a profile without value, otherwise it has a value
 * \return pointer to the created dialog on success, NULL otherwise
 */
static struct dlg_profile_table *new_dlg_profile(
		str *name, unsigned int size, unsigned int has_value)
{
	struct dlg_profile_table *profile;
	struct dlg_profile_table *ptmp;
	unsigned int len;
	unsigned int i;

	if(name->s == NULL || name->len == 0 || size == 0) {
		LM_ERR("invalid parameters\n");
		return NULL;
	}

	for(len = 0, i = 0; i < 8 * sizeof(size); i++) {
		if(size & (1 << i))
			len++;
	}
	if(len != 1) {
		LM_ERR(" size %u is not power of 2!\n", size);
		return NULL;
	}

	profile = search_dlg_profile(name);
	if(profile != NULL) {
		LM_ERR("duplicate dialog profile registered <%.*s>\n", name->len,
				name->s);
		return NULL;
	}

	len = sizeof(struct dlg_profile_table)
		  + size * sizeof(struct dlg_profile_entry) + name->len + 1;
	profile = (struct dlg_profile_table *)shm_malloc(len);
	if(profile == NULL) {
		LM_ERR("no more shm mem\n");
		return NULL;
	}

	memset(profile, 0, len);
	profile->size = size;
	profile->has_value = (has_value == 0) ? 0 : 1;

	/* init lock */
	if(lock_init(&profile->lock) == NULL) {
		LM_ERR("failed to init lock\n");
		shm_free(profile);
		return NULL;
	}

	/* set inner pointers */
	profile->entries = (struct dlg_profile_entry *)(profile + 1);
	profile->name.s = ((char *)profile->entries)
					  + size * sizeof(struct dlg_profile_entry);

	/* copy the name of the profile */
	memcpy(profile->name.s, name->s, name->len);
	profile->name.len = name->len;
	profile->name.s[profile->name.len] = 0;

	/* link profile */
	for(ptmp = profiles; ptmp && ptmp->next; ptmp = ptmp->next)
		;
	if(ptmp == NULL) {
		profiles = profile;
		sruid_init(&_dlg_profile_sruid, '-', "dlgp", SRUID_INC);
	} else {
		ptmp->next = profile;
	}

	return profile;
}


/*!
 * \brief Destroy a dialog profile list
 * \param profile dialog profile
 */
static void destroy_dlg_profile(struct dlg_profile_table *profile)
{
	if(profile == NULL)
		return;

	lock_destroy(&profile->lock);
	shm_free(profile);
	return;
}


/*!
 * \brief Destroy the global dialog profile list
 */
void destroy_dlg_profiles(void)
{
	struct dlg_profile_table *profile;

	while(profiles) {
		profile = profiles;
		profiles = profiles->next;
		destroy_dlg_profile(profile);
	}
	return;
}


/*!
 * \brief Destroy dialog linkers
 * \param linker dialog linker
 */
void destroy_linkers(struct dlg_profile_link *linker)
{
	struct dlg_profile_entry *p_entry;
	struct dlg_profile_link *l;
	struct dlg_profile_hash *lh;

	while(linker) {
		l = linker;
		linker = linker->next;
		/* unlink from profile table */
		if(l->hash_linker.next) {
			p_entry = &l->profile->entries[l->hash_linker.hash];
			lock_get(&l->profile->lock);
			lh = &l->hash_linker;
			/* last element on the list? */
			if(lh == lh->next) {
				p_entry->first = NULL;
			} else {
				if(p_entry->first == lh)
					p_entry->first = lh->next;
				lh->next->prev = lh->prev;
				lh->prev->next = lh->next;
			}
			lh->next = lh->prev = NULL;
			p_entry->content--;
			lock_release(&l->profile->lock);
		}
		/* free memory */
		shm_free(l);
	}
}


/*!
 * \brief Calculate the hash profile from a dialog
 * \see core_hash
 * \param value hash source
 * \param dlg dialog cell
 * \param profile dialog profile table (for hash size)
 * \return value hash if the value has a value, hash over dialog otherwise
 */
inline static unsigned int calc_hash_profile(
		str *value1, str *value2, dlg_profile_table_t *profile)
{
	if(profile->has_value) {
		/* do hash over the value1 */
		return core_hash(value1, NULL, profile->size);
	} else {
		/* do hash over the value2 */
		if(value2)
			return core_hash(value2, NULL, profile->size);
		return 0;
	}
}


/*!
 * \brief Remove remote profile items that are expired
 * \param te expiration time
 */
void remove_expired_remote_profiles(time_t te)
{
	struct dlg_profile_table *profile;
	struct dlg_profile_entry *p_entry;
	struct dlg_profile_hash *lh;
	struct dlg_profile_hash *kh;
	int i;

	for(profile = profiles; profile; profile = profile->next) {
		if(profile->flags & FLAG_PROFILE_REMOTE) {
			for(i = 0; i < profile->size; i++) {
				/* space for optimization */
				lock_get(&profile->lock);
				p_entry = &profile->entries[i];
				lh = p_entry->first;
				while(lh) {
					kh = lh->next;
					if(lh->dlg == NULL && lh->expires > 0 && lh->expires < te) {
						/* last element on the list? */
						if(lh == lh->next) {
							p_entry->first = NULL;
						} else {
							if(p_entry->first == lh)
								p_entry->first = lh->next;
							lh->next->prev = lh->prev;
							lh->prev->next = lh->next;
						}
						lh->next = lh->prev = NULL;
						if(lh->linker)
							shm_free(lh->linker);
						p_entry->content--;
						lock_release(&profile->lock);
						return;
					}
					lh = kh;
				}
				lock_release(&profile->lock);
			}
		}
	}
}

/*!
 * \brief Remove profile
 * \param profile pointer to profile
 * \param value profile value
 * \param puid profile unique id
 */
int remove_profile(dlg_profile_table_t *profile, str *value, str *puid)
{
	unsigned int hash;
	struct dlg_profile_entry *p_entry;
	struct dlg_profile_hash *lh;

	hash = calc_hash_profile(value, puid, profile);
	lock_get(&profile->lock);
	p_entry = &profile->entries[hash];
	lh = p_entry->first;
	if(lh) {
		do {
			if(lh->dlg == NULL && lh->puid_len == puid->len
					&& lh->value.len == value->len
					&& strncmp(lh->puid, puid->s, puid->len) == 0
					&& strncmp(lh->value.s, value->s, value->len) == 0) {
				/* last element on the list? */
				if(lh == lh->next) {
					p_entry->first = NULL;
				} else {
					if(p_entry->first == lh)
						p_entry->first = lh->next;
					lh->next->prev = lh->prev;
					lh->prev->next = lh->next;
				}
				lh->next = lh->prev = NULL;
				if(lh->linker)
					shm_free(lh->linker);
				p_entry->content--;
				lock_release(&profile->lock);
				return 1;
			}
			lh = lh->next;
		} while(lh != p_entry->first);
	}
	lock_release(&profile->lock);
	return 0;
}


/*!
 * \brief Callback for cleanup of profile local vars
 * \param msg SIP message
 * \param flags unused
 * \param param unused
 * \return 1
 */
int cb_profile_reset(struct sip_msg *msg, unsigned int flags, void *param)
{
	current_dlg_msg_id = 0;
	current_dlg_msg_pid = 0;
	if(current_pending_linkers) {
		destroy_linkers(current_pending_linkers);
		current_pending_linkers = NULL;
	}

	/* need to return non-zero - 0 will break the exec of the request */
	return 1;
}


/*!
 * \brief Cleanup a profile
 * \param msg SIP message
 * \param flags unused
 * \param param unused
 * \return 1
 */
int profile_cleanup(struct sip_msg *msg, unsigned int flags, void *param)
{
	dlg_cell_t *dlg;

	if(get_route_type() == LOCAL_ROUTE) {
		return 1;
	}

	current_dlg_msg_id = 0;
	current_dlg_msg_pid = 0;
	dlg = dlg_get_ctx_dialog();
	if(dlg != NULL) {
		if(dlg->dflags & DLG_FLAG_TM) {
			dlg_unref(dlg, 1);
		} else {
			/* dialog didn't make it to tm */
			dlg_unref(dlg, 2);
		}
	}
	if(current_pending_linkers) {
		destroy_linkers(current_pending_linkers);
		current_pending_linkers = NULL;
	}

	/* need to return non-zero - 0 will break the exec of the request */
	return 1;
}


/*!
 * \brief Link a dialog profile
 * \param linker dialog linker
 * \param vkey key for profile hash table
 */
static void link_profile(struct dlg_profile_link *linker, str *vkey)
{
	unsigned int hash;
	struct dlg_profile_entry *p_entry;

	/* calculate the hash position */
	hash = calc_hash_profile(&linker->hash_linker.value, vkey, linker->profile);
	linker->hash_linker.hash = hash;

	/* insert into profile hash table */
	p_entry = &linker->profile->entries[hash];
	lock_get(&linker->profile->lock);
	if(p_entry->first) {
		linker->hash_linker.prev = p_entry->first->prev;
		linker->hash_linker.next = p_entry->first;
		p_entry->first->prev->next = &linker->hash_linker;
		p_entry->first->prev = &linker->hash_linker;
	} else {
		p_entry->first = linker->hash_linker.next = linker->hash_linker.prev =
				&linker->hash_linker;
	}
	p_entry->content++;
	lock_release(&linker->profile->lock);
}

/*!
 * \brief Link a dialog profile
 * \param linker dialog linker
 * \param dlg dialog cell
 */
static void link_dlg_profile(
		struct dlg_profile_link *linker, struct dlg_cell *dlg)
{
	struct dlg_entry *d_entry;

	/* add the linker to the dialog */
	/* FIXME zero h_id is not 100% for testing if the dialog is inserted
	 * into the hash table -> we need circular lists  -bogdan */
	if(dlg->h_id) {
		d_entry = &d_table->entries[dlg->h_entry];
		dlg_lock(d_table, d_entry);
		linker->next = dlg->profile_links;
		dlg->profile_links = linker;
		linker->hash_linker.dlg = dlg;
		dlg_unlock(d_table, d_entry);
	} else {
		linker->next = dlg->profile_links;
		dlg->profile_links = linker;
		linker->hash_linker.dlg = dlg;
	}

	atomic_or_int((volatile int *)&dlg->dflags, DLG_FLAG_CHANGED_PROF);
	link_profile(linker, &dlg->callid);
}


/*!
 * \brief Set the global variables to the current dialog
 * \param msg SIP message
 * \param dlg dialog cell
 */
void set_current_dialog(sip_msg_t *msg, dlg_cell_t *dlg)
{
	struct dlg_profile_link *linker;
	struct dlg_profile_link *tlinker;

	LM_DBG("setting current dialog [%u:%u]\n", dlg->h_entry, dlg->h_id);
	/* if linkers are not from current request, just discard them */
	if(msg->id != current_dlg_msg_id || msg->pid != current_dlg_msg_pid) {
		current_dlg_msg_id = msg->id;
		current_dlg_msg_pid = msg->pid;
		destroy_linkers(current_pending_linkers);
	} else {
		/* add the linker, one by one, to the dialog */
		linker = current_pending_linkers;
		while(linker) {
			tlinker = linker;
			linker = linker->next;
			/* process tlinker */
			tlinker->next = NULL;
			link_dlg_profile(tlinker, dlg);
		}
	}
	current_pending_linkers = NULL;
}


/*!
 * \brief Set a dialog profile
 * \param msg SIP message
 * \param value value
 * \param profile dialog profile table
 * \return 0 on success, -1 on failure
 */
int set_dlg_profile(
		struct sip_msg *msg, str *value, struct dlg_profile_table *profile)
{
	dlg_cell_t *dlg = NULL;
	dlg_profile_link_t *linker;

	/* get current dialog */
	dlg = dlg_get_msg_dialog(msg);

	if(dlg == NULL && !is_route_type(REQUEST_ROUTE)) {
		LM_CRIT("BUG - dialog not found in a non REQUEST route (%d)\n",
				REQUEST_ROUTE);
		return -1;
	}

	/* build new linker */
	linker = (struct dlg_profile_link *)shm_malloc(
			sizeof(struct dlg_profile_link)
			+ (profile->has_value ? value->len : 0));
	if(linker == NULL) {
		LM_ERR("no more shm memory\n");
		goto error;
	}
	memset(linker, 0, sizeof(struct dlg_profile_link));

	/* set backpointers to profile and linker (itself) */
	linker->profile = profile;
	linker->hash_linker.linker = linker;

	/* set the value */
	if(profile->has_value) {
		linker->hash_linker.value.s = (char *)(linker + 1);
		memcpy(linker->hash_linker.value.s, value->s, value->len);
		linker->hash_linker.value.len = value->len;
	}
	sruid_next_safe(&_dlg_profile_sruid);
	if(_dlg_profile_sruid.uid.len < SRUID_SIZE) {
		strcpy(linker->hash_linker.puid, _dlg_profile_sruid.uid.s);
		linker->hash_linker.puid_len = _dlg_profile_sruid.uid.len;
	} else {
		LM_ERR("sruid size is too large\n");
		shm_free(linker);
		goto error;
	}

	if(dlg != NULL) {
		/* add linker directly to the dialog and profile */
		link_dlg_profile(linker, dlg);
	} else {
		/* if existing linkers are not from current request, just discard them */
		if(msg->id != current_dlg_msg_id || msg->pid != current_dlg_msg_pid) {
			current_dlg_msg_id = msg->id;
			current_dlg_msg_pid = msg->pid;
			destroy_linkers(current_pending_linkers);
			current_pending_linkers = NULL;
		}
		/* no dialog yet -> set linker as pending */
		if(msg->id != current_dlg_msg_id || msg->pid != current_dlg_msg_pid) {
			current_dlg_msg_id = msg->id;
			current_dlg_msg_pid = msg->pid;
			destroy_linkers(current_pending_linkers);
		}

		linker->next = current_pending_linkers;
		current_pending_linkers = linker;
	}

	dlg_release(dlg);
	return 0;
error:
	dlg_release(dlg);
	return -1;
}

/*!
 * \brief Add dialog to a profile
 * \param dlg dialog
 * \param value value
 * \param profile dialog profile table
 * \return 0 on success, -1 on failure
 */
int dlg_add_profile(dlg_cell_t *dlg, str *value,
		struct dlg_profile_table *profile, str *puid, time_t expires, int flags)
{
	dlg_profile_link_t *linker;
	str vkey;

	/* build new linker */
	linker = (struct dlg_profile_link *)shm_malloc(
			sizeof(struct dlg_profile_link)
			+ (profile->has_value ? (value->len + 1) : 0));
	if(linker == NULL) {
		LM_ERR("no more shm memory\n");
		goto error;
	}
	memset(linker, 0, sizeof(struct dlg_profile_link));

	/* set backpointers to profile and linker (itself) */
	linker->profile = profile;
	linker->hash_linker.linker = linker;

	/* set the value */
	if(profile->has_value) {
		linker->hash_linker.value.s = (char *)(linker + 1);
		memcpy(linker->hash_linker.value.s, value->s, value->len);
		linker->hash_linker.value.len = value->len;
		linker->hash_linker.value.s[value->len] = '\0';
	}
	if(puid && puid->s && puid->len > 0) {
		if(puid->len < SRUID_SIZE) {
			memcpy(linker->hash_linker.puid, puid->s, puid->len);
			linker->hash_linker.puid_len = puid->len;
		} else {
			LM_ERR("puid size is too large\n");
			shm_free(linker);
			goto error;
		}
	} else {
		sruid_next_safe(&_dlg_profile_sruid);
		if(_dlg_profile_sruid.uid.len < SRUID_SIZE) {
			memcpy(linker->hash_linker.puid, _dlg_profile_sruid.uid.s,
					_dlg_profile_sruid.uid.len);
			linker->hash_linker.puid_len = _dlg_profile_sruid.uid.len;
		} else {
			LM_ERR("sruid size is too large\n");
			shm_free(linker);
			goto error;
		}
	}
	linker->hash_linker.expires = expires;
	linker->hash_linker.flags = flags;

	/* add linker directly to the dialog and profile */
	if(dlg != NULL) {
		link_dlg_profile(linker, dlg);
	} else {
		vkey.s = linker->hash_linker.puid;
		vkey.len = linker->hash_linker.puid_len;
		profile->flags |= FLAG_PROFILE_REMOTE;
		link_profile(linker, &vkey);
	}
	return 0;
error:
	return -1;
}

/*!
 * \brief Unset a dialog profile
 * \param msg SIP message
 * \param value value
 * \param profile dialog profile table
 * \return 1 on success, -1 on failure
 */
int unset_dlg_profile(sip_msg_t *msg, str *value, dlg_profile_table_t *profile)
{
	dlg_cell_t *dlg;
	dlg_profile_link_t *linker;
	dlg_profile_link_t *linker_prev;
	dlg_entry_t *d_entry;

	if(is_route_type(REQUEST_ROUTE)) {
		LM_ERR("dialog delete profile cannot be used in request route\n");
		return -1;
	}

	/* get current dialog */
	dlg = dlg_get_msg_dialog(msg);

	if(dlg == NULL) {
		LM_WARN("dialog is NULL for delete profile\n");
		return -1;
	}

	/* check the dialog linkers */
	d_entry = &d_table->entries[dlg->h_entry];
	dlg_lock(d_table, d_entry);
	linker = dlg->profile_links;
	linker_prev = NULL;
	for(; linker; linker_prev = linker, linker = linker->next) {
		if(linker->profile == profile) {
			if(profile->has_value == 0) {
				goto found;
			} else if(value && value->len == linker->hash_linker.value.len
					  && memcmp(value->s, linker->hash_linker.value.s,
								 value->len)
								 == 0) {
				goto found;
			}
			/* allow further search - maybe the dialog is inserted twice in
			 * the same profile, but with different values -bogdan
			 */
		}
	}
	atomic_or_int((volatile int *)&dlg->dflags, DLG_FLAG_CHANGED_PROF);
	dlg_unlock(d_table, d_entry);
	dlg_release(dlg);
	return -1;

found:
	/* table still locked */
	/* remove the linker element from dialog */
	if(linker_prev == NULL) {
		dlg->profile_links = linker->next;
	} else {
		linker_prev->next = linker->next;
	}
	linker->next = NULL;
	dlg_unlock(d_table, d_entry);
	/* remove linker from profile table and free it */
	destroy_linkers(linker);
	dlg_release(dlg);
	return 1;
}


/*!
 * \brief Check if a dialog belongs to a profile
 * \param msg SIP message
 * \param profile dialog profile table
 * \param value value
 * \return 1 on success, -1 on failure
 */
int is_dlg_in_profile(
		struct sip_msg *msg, struct dlg_profile_table *profile, str *value)
{
	struct dlg_cell *dlg;
	struct dlg_profile_link *linker;
	struct dlg_entry *d_entry;
	int ret;

	/* get current dialog */
	dlg = dlg_get_msg_dialog(msg);

	if(dlg == NULL)
		return -1;

	ret = -1;
	/* check the dialog linkers */
	d_entry = &d_table->entries[dlg->h_entry];
	dlg_lock(d_table, d_entry);
	for(linker = dlg->profile_links; linker; linker = linker->next) {
		if(linker->profile == profile) {
			if(profile->has_value == 0) {
				dlg_unlock(d_table, d_entry);
				ret = 1;
				goto done;
			} else if(value && value->len == linker->hash_linker.value.len
					  && memcmp(value->s, linker->hash_linker.value.s,
								 value->len)
								 == 0) {
				dlg_unlock(d_table, d_entry);
				ret = 1;
				goto done;
			}
			/* allow further search - maybe the dialog is inserted twice in
			 * the same profile, but with different values -bogdan
			 */
		}
	}
	dlg_unlock(d_table, d_entry);

done:
	dlg_release(dlg);
	return ret;
}


/*!
 * \brief Get the size of a profile
 * \param profile evaluated profile
 * \param value value
 * \return the profile size
 */
unsigned int get_profile_size(struct dlg_profile_table *profile, str *value)
{
	unsigned int n, i;
	struct dlg_profile_hash *ph;

	if(profile->has_value == 0 || value == NULL) {
		/* iterate through the hash and count all records */
		lock_get(&profile->lock);
		for(i = 0, n = 0; i < profile->size; i++)
			n += profile->entries[i].content;
		lock_release(&profile->lock);
		return n;
	} else {
		/* iterate through the hash entry and count only matching */
		/* calculate the hash position */
		i = calc_hash_profile(value, NULL, profile);
		n = 0;
		lock_get(&profile->lock);
		ph = profile->entries[i].first;
		if(ph) {
			do {
				/* compare */
				if(value->len == ph->value.len
						&& memcmp(value->s, ph->value.s, value->len) == 0) {
					/* found */
					n++;
				}
				/* next */
				ph = ph->next;
			} while(ph != profile->entries[i].first);
		}
		lock_release(&profile->lock);
		return n;
	}
}

/*
 * Determine if message is in a dialog currently being tracked
 */
int is_known_dlg(struct sip_msg *msg)
{
	dlg_cell_t *dlg;

	dlg = dlg_get_msg_dialog(msg);

	if(dlg == NULL)
		return -1;

	dlg_release(dlg);

	return 1;
}

/**
 *
 */
int dlg_set_ruri(sip_msg_t *msg)
{
	dlg_cell_t *dlg;
	unsigned int dir;
	int leg;

	dlg = dlg_lookup_msg_dialog(msg, &dir);
	if(dlg == NULL) {
		LM_DBG("no dialog found\n");
		return -1;
	}

	if(dir == DLG_DIR_DOWNSTREAM) {
		leg = DLG_CALLEE_LEG;
	} else {
		leg = DLG_CALLER_LEG;
	}
	if(dlg->contact[leg].s == 0 || dlg->contact[leg].len == 0) {
		LM_NOTICE("no contact uri (leg: %d)\n", leg);
		dlg_release(dlg);
		return -1;
	}
	if(rewrite_uri(msg, &dlg->contact[leg]) < 0) {
		LM_ERR("failed to rewrite uri (leg: %d)\n", leg);
		dlg_release(dlg);
		return -1;
	}
	dlg_release(dlg);

	return 1;
}

/*
 * \brief Set the timeout of all the dialogs in a given profile, by value.
 * \param profile The evaluated profile name.
 * \param value The value constraint.
 * \param timeout The dialog timeout to apply.
 */

int dlg_set_timeout_by_profile(
		struct dlg_profile_table *profile, str *value, int timeout)
{
	unsigned int i = 0;
	dlg_cell_t *this_dlg = NULL;
	struct dlg_profile_hash *ph = NULL;

	/* Private structure necessary for manipulating dialog
         * timeouts outside of profile locks.  Admittedly, an
         * ugly hack, but avoids some concurrency issues.
         */

	struct dlg_map_list
	{
		unsigned int h_id;
		unsigned int h_entry;
		struct dlg_map_list *next;
	} * map_head, *map_scan, *map_scan_next;

	map_head = NULL;

	/* If the profile has no value, iterate through every
	 * node and set its timeout.
	 */

	if(profile->has_value == 0 || value == NULL) {
		lock_get(&profile->lock);

		for(i = 0; i < profile->size; i++) {
			ph = profile->entries[i].first;

			if(!ph)
				continue;

			do {
				struct dlg_map_list *d = malloc(sizeof(struct dlg_map_list));

				if(!d)
					goto error;

				memset(d, 0, sizeof(struct dlg_map_list));

				d->h_id = ph->dlg->h_id;
				d->h_entry = ph->dlg->h_entry;

				if(map_head == NULL)
					map_head = d;
				else {
					d->next = map_head;
					map_head = d;
				}

				ph = ph->next;
			} while(ph != profile->entries[i].first);
		}

		lock_release(&profile->lock);
	} else {
		i = calc_hash_profile(value, NULL, profile);

		lock_get(&profile->lock);

		ph = profile->entries[i].first;

		if(ph) {
			do {
				if(ph && value->len == ph->value.len
						&& memcmp(value->s, ph->value.s, value->len) == 0) {
					struct dlg_map_list *d =
							malloc(sizeof(struct dlg_map_list));

					if(!d)
						goto error;

					memset(d, 0, sizeof(struct dlg_map_list));

					d->h_id = ph->dlg->h_id;
					d->h_entry = ph->dlg->h_entry;

					if(map_head == NULL)
						map_head = d;
					else {
						d->next = map_head;
						map_head = d;
					}
				}

				ph = ph->next;
			} while(ph && ph != profile->entries[i].first);
		}

		lock_release(&profile->lock);
	}

	/* Walk the list and bulk-set the timeout */

	for(map_scan = map_head; map_scan != NULL; map_scan = map_scan_next) {
		map_scan_next = map_scan->next;

		this_dlg = dlg_lookup(map_scan->h_entry, map_scan->h_id);

		if(!this_dlg) {
			LM_CRIT("Unable to find dialog %d:%d\n", map_scan->h_entry,
					map_scan->h_id);
		} else if(this_dlg->state >= DLG_STATE_EARLY) {
			if(update_dlg_timeout(this_dlg, timeout) < 0) {
				LM_ERR("Unable to set timeout on %d:%d\n", map_scan->h_entry,
						map_scan->h_id);
			}
			dlg_release(this_dlg);
		}
		free(map_scan);
	}

	return 0;
error:
	for(map_scan = map_head; map_scan != NULL; map_scan = map_scan_next) {
		map_scan_next = map_scan->next;
		free(map_scan);
	}
	return -1;
}


/**
 * json serialization of dialog profiles
 */
int dlg_profiles_to_json(dlg_cell_t *dlg, srjson_doc_t *jdoc)
{
	dlg_profile_link_t *l;
	srjson_t *aj = NULL;
	srjson_t *pj = NULL;

	LM_DBG("serializing profiles for dlg[%u:%u]\n", dlg->h_entry, dlg->h_id);
	if(dlg == NULL || dlg->profile_links == NULL)
		return -1;
	LM_DBG("start of serializing profiles for dlg[%u:%u]\n", dlg->h_entry,
			dlg->h_id);

	for(l = dlg->profile_links; l; l = l->next) {
		if(aj == NULL) {
			aj = srjson_CreateArray(jdoc);
			if(aj == NULL) {
				LM_ERR("cannot create json profiles array object\n");
				goto error;
			}
		}
		pj = srjson_CreateObject(jdoc);
		if(pj == NULL) {
			LM_ERR("cannot create json dynamic profiles obj\n");
			goto error;
		}

		srjson_AddStrStrToObject(
				jdoc, pj, "name", 4, l->profile->name.s, l->profile->name.len);
		if(l->profile->has_value) {
			srjson_AddStrStrToObject(jdoc, pj, "value", 5,
					l->hash_linker.value.s, l->hash_linker.value.len);
		}
		if(l->hash_linker.puid[0] != '\0')
			srjson_AddStringToObject(jdoc, pj, "puid", l->hash_linker.puid);
		if(l->hash_linker.expires != 0)
			srjson_AddNumberToObject(
					jdoc, pj, "expires", l->hash_linker.expires);
		if(l->hash_linker.flags != 0)
			srjson_AddNumberToObject(jdoc, pj, "flags", l->hash_linker.flags);
		srjson_AddItemToArray(jdoc, aj, pj);
	}

	if(jdoc->root == NULL) {
		jdoc->root = srjson_CreateObject(jdoc);
		if(jdoc->root == NULL) {
			LM_ERR("cannot create json root\n");
			goto error;
		}
	}
	if(aj != NULL)
		srjson_AddItemToObject(jdoc, jdoc->root, "profiles", aj);
	if(jdoc->buf.s != NULL) {
		jdoc->free_fn(jdoc->buf.s);
		jdoc->buf.s = NULL;
		jdoc->buf.len = 0;
	}
	jdoc->buf.s = srjson_PrintUnformatted(jdoc, jdoc->root);
	if(jdoc->buf.s != NULL) {
		jdoc->buf.len = strlen(jdoc->buf.s);
		LM_DBG("serialized profiles for dlg[%u:%u] = [[%.*s]]\n", dlg->h_entry,
				dlg->h_id, jdoc->buf.len, jdoc->buf.s);
		return 0;
	}
	return -1;

error:
	srjson_Delete(jdoc, aj);
	return -1;
}


/**
 * json de-serialization of dialog profiles
 */
int dlg_json_to_profiles(dlg_cell_t *dlg, srjson_doc_t *jdoc)
{
	srjson_t *aj = NULL;
	srjson_t *it = NULL;
	srjson_t *jt = NULL;
	dlg_profile_table_t *profile;
	str name;
	str val;
	str puid;
	time_t expires;
	int flags;

	if(dlg == NULL || jdoc == NULL || jdoc->buf.s == NULL)
		return -1;

	if(jdoc->root == NULL) {
		jdoc->root = srjson_Parse(jdoc, jdoc->buf.s);
		if(jdoc->root == NULL) {
			LM_ERR("invalid json doc [[%s]]\n", jdoc->buf.s);
			return -1;
		}
	}
	aj = srjson_GetObjectItem(jdoc, jdoc->root, "profiles");
	if(aj != NULL) {
		for(it = aj->child; it; it = it->next) {
			name.s = val.s = puid.s = NULL;
			expires = 0;
			flags = 0;
			for(jt = it->child; jt; jt = jt->next) {
				if(strcmp(jt->string, "name") == 0) {
					name.s = jt->valuestring;
					name.len = strlen(name.s);
				} else if(strcmp(jt->string, "value") == 0) {
					val.s = jt->valuestring;
					val.len = strlen(val.s);
				} else if(strcmp(jt->string, "puid") == 0) {
					puid.s = jt->valuestring;
					puid.len = strlen(puid.s);
				} else if(strcmp(jt->string, "expires") == 0) {
					expires = (time_t)SRJSON_GET_ULONG(jt);
				} else if(strcmp(jt->string, "flags") == 0) {
					flags = SRJSON_GET_UINT(jt);
				}
			}
			if(name.s == NULL)
				continue;
			profile = search_dlg_profile(&name);
			if(profile == NULL) {
				LM_ERR("profile [%.*s] not found\n", name.len, name.s);
				continue;
			}
			if(val.s != NULL) {
				if(profile->has_value) {
					if(dlg_add_profile(
							   dlg, &val, profile, &puid, expires, flags)
							< 0)
						LM_ERR("dynamic profile cannot be added, ignore!\n");
					else
						LM_DBG("dynamic profile added [%s : %s]\n", name.s,
								val.s);
				}
			} else {
				if(!profile->has_value) {
					if(dlg_add_profile(
							   dlg, NULL, profile, &puid, expires, flags)
							< 0)
						LM_ERR("static profile cannot be added, ignore!\n");
					else
						LM_DBG("static profile added [%s]\n", name.s);
				}
			}
		}
	}
	return 0;
}

/*!
 *
 */
int dlg_cmd_remote_profile(
		str *cmd, str *pname, str *value, str *puid, time_t expires, int flags)
{
	dlg_profile_table_t *dprofile;
	int ret;

	if(cmd == NULL || cmd->s == NULL || cmd->len <= 0 || pname == NULL
			|| pname->s == NULL || pname->len <= 0 || puid == NULL
			|| puid->s == NULL || puid->len <= 0) {
		LM_ERR("invalid parameters\n");
		return -1;
	}
	dprofile = search_dlg_profile(pname);
	if(dprofile == NULL) {
		LM_ERR("profile [%.*s] not found\n", pname->len, pname->s);
		return -1;
	}
	if(dprofile->has_value) {
		if(value == NULL || value->s == NULL || value->len <= 0) {
			LM_ERR("profile [%.*s] requires a value\n", pname->len, pname->s);
			return -1;
		}
	}

	if(cmd->len == 3 && strncmp(cmd->s, "add", 3) == 0) {
		if(value && value->s && value->len > 0) {
			ret = dlg_add_profile(NULL, value, dprofile, puid, expires, flags);
		} else {
			ret = dlg_add_profile(NULL, NULL, dprofile, puid, expires, flags);
		}
		if(ret < 0) {
			LM_ERR("failed to add to profile [%.*s]\n", pname->len, pname->s);
			return -1;
		}
	} else if(cmd->len == 2 && strncmp(cmd->s, "rm", 2) == 0) {
		ret = remove_profile(dprofile, value, puid);
		return ret;
	} else {
		LM_ERR("unknown command [%.*s]\n", cmd->len, cmd->s);
		return -1;
	}
	return 0;
}
