/**
 * Copyright (C) 2013 Daniel-Constantin Mierla (asipto.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
 *
 */


#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "../../core/dprint.h"
#include "../../core/rand/fastrand.h"
#include "../../core/hashes.h"
#include "../../core/resolve.h"
#include "../../core/pvar.h"


#define PV_DNS_ADDR 64
#define PV_DNS_RECS 32

typedef struct _sr_dns_record
{
	int type;
	char addr[PV_DNS_ADDR];
} sr_dns_record_t;

typedef struct _sr_dns_item
{
	str name;
	unsigned int hashid;
	char hostname[256];
	int count;
	int ipv4;
	int ipv6;
	sr_dns_record_t r[PV_DNS_RECS];
	struct _sr_dns_item *next;
} sr_dns_item_t;

#define SR_DNS_PVIDX 1

typedef struct _dns_pv
{
	sr_dns_item_t *item;
	int type;
	int flags;
	pv_spec_t *pidx;
	int nidx;
} dns_pv_t;

static sr_dns_item_t *_sr_dns_list = NULL;

/**
 *
 */
sr_dns_item_t *sr_dns_get_item(str *name)
{
	sr_dns_item_t *it = NULL;
	unsigned int hashid = 0;

	hashid = get_hash1_raw(name->s, name->len);

	it = _sr_dns_list;
	while(it != NULL) {
		if(it->hashid == hashid && it->name.len == name->len
				&& strncmp(it->name.s, name->s, name->len) == 0)
			return it;
		it = it->next;
	}
	return NULL;
}

/**
 *
 */
sr_dns_item_t *sr_dns_add_item(str *name)
{
	sr_dns_item_t *it = NULL;
	unsigned int hashid = 0;
	int n = 0;

	hashid = get_hash1_raw(name->s, name->len);

	it = _sr_dns_list;
	while(it != NULL) {
		if(it->hashid == hashid && it->name.len == name->len
				&& strncmp(it->name.s, name->s, name->len) == 0)
			return it;
		it = it->next;
		n++;
	}
	if(n > 20) {
		LM_WARN("too many dns containers - adding number %d - can fill "
				"memory\n",
				n);
	}

	/* add new */
	it = (sr_dns_item_t *)pkg_malloc(sizeof(sr_dns_item_t));
	if(it == NULL) {
		PKG_MEM_ERROR;
		return NULL;
	}
	memset(it, 0, sizeof(sr_dns_item_t));
	it->name.s = (char *)pkg_malloc(name->len + 1);
	if(it->name.s == NULL) {
		PKG_MEM_ERROR;
		pkg_free(it);
		return NULL;
	}
	memcpy(it->name.s, name->s, name->len);
	it->name.s[name->len] = '\0';
	it->name.len = name->len;
	it->hashid = hashid;
	it->next = _sr_dns_list;
	_sr_dns_list = it;
	return it;
}

/**
 *
 */
int pv_parse_dns_name(pv_spec_t *sp, str *in)
{
	dns_pv_t *dpv = NULL;
	char *p;
	str pvc;
	str pvs;
	str pvi;
	int sign;

	if(sp == NULL || in == NULL || in->len <= 0)
		return -1;

	dpv = (dns_pv_t *)pkg_malloc(sizeof(dns_pv_t));
	if(dpv == NULL) {
		PKG_MEM_ERROR;
		return -1;
	}
	memset(dpv, 0, sizeof(dns_pv_t));

	p = in->s;

	while(p < in->s + in->len
			&& (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r'))
		p++;
	if(p > in->s + in->len || *p == '\0')
		goto error;
	pvc.s = p;
	while(p < in->s + in->len) {
		if(*p == '=' || *p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
			break;
		p++;
	}
	if(p > in->s + in->len || *p == '\0')
		goto error;
	pvc.len = p - pvc.s;
	if(*p != '=') {
		while(p < in->s + in->len
				&& (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r'))
			p++;
		if(p > in->s + in->len || *p == '\0' || *p != '=')
			goto error;
	}
	p++;
	if(*p != '>')
		goto error;
	p++;

	pvs.len = in->len - (int)(p - in->s);
	pvs.s = p;
	pvi.s = 0;
	pvi.len = 0;
	if(pvs.s[pvs.len - 1] == ']') {
		/* index */
		p = memchr(pvs.s, '[', pvs.len - 1);
		if(p == NULL) {
			goto error;
		}
		pvi.s = p + 1;
		pvi.len = pvs.s + pvs.len - 1 - pvi.s;
		pvs.len = p - pvs.s;
	}
	LM_DBG("dns [%.*s] - key [%.*s] index [%.*s]\n", pvc.len, pvc.s, pvs.len,
			pvs.s, (pvi.len > 0) ? pvi.len : 0, (pvi.s != NULL) ? pvi.s : 0);

	dpv->item = sr_dns_add_item(&pvc);
	if(dpv->item == NULL)
		goto error;

	switch(pvs.len) {
		case 4:
			if(strncmp(pvs.s, "addr", 4) == 0)
				dpv->type = 0;
			else if(strncmp(pvs.s, "type", 4) == 0)
				dpv->type = 1;
			else if(strncmp(pvs.s, "ipv4", 4) == 0)
				dpv->type = 2;
			else if(strncmp(pvs.s, "ipv6", 4) == 0)
				dpv->type = 3;
			else
				goto error;
			break;
		case 5:
			if(strncmp(pvs.s, "count", 5) == 0)
				dpv->type = 4;
			else
				goto error;
			break;
		default:
			goto error;
	}

	if(pvi.len > 0) {
		if(pvi.s[0] == PV_MARKER) {
			dpv->pidx = pv_cache_get(&pvi);
			if(dpv->pidx == NULL)
				goto error;
			dpv->flags |= SR_DNS_PVIDX;
		} else {
			sign = 1;
			p = pvi.s;
			if(*p == '-') {
				sign = -1;
				p++;
			}
			dpv->nidx = 0;
			while(p < pvi.s + pvi.len && *p >= '0' && *p <= '9') {
				dpv->nidx = dpv->nidx * 10 + *p - '0';
				p++;
			}
			if(p != pvi.s + pvi.len) {
				LM_ERR("invalid index [%.*s] in [%.*s]\n", pvi.len, pvi.s,
						in->len, in->s);
				goto error;
			}
			dpv->nidx *= sign;
		}
	}
	sp->pvp.pvn.u.dname = (void *)dpv;
	sp->pvp.pvn.type = PV_NAME_OTHER;

	return 0;

error:
	LM_ERR("error at PV dns name: %.*s\n", in->len, in->s);
	if(dpv)
		pkg_free(dpv);
	return -1;
}

/**
 *
 */
int pv_get_dns(sip_msg_t *msg, pv_param_t *param, pv_value_t *res)
{
	dns_pv_t *dpv;
	pv_value_t val;

	if(msg == NULL || param == NULL)
		return -1;

	dpv = (dns_pv_t *)param->pvn.u.dname;
	if(dpv == NULL || dpv->item == NULL)
		return -1;

	if(dpv->pidx != NULL) {
		if(pv_get_spec_value(msg, dpv->pidx, &val) < 0
				|| (!(val.flags & PV_VAL_INT))) {
			LM_ERR("failed to evaluate index variable\n");
			return pv_get_null(msg, param, res);
		}
	} else {
		val.ri = dpv->nidx;
	}
	if(val.ri < 0) {
		if(dpv->item->count + val.ri < 0) {
			return pv_get_null(msg, param, res);
		}
		val.ri = dpv->item->count + val.ri;
	}
	if(val.ri >= dpv->item->count) {
		return pv_get_null(msg, param, res);
	}
	switch(dpv->type) {
		case 0: /* address */
			return pv_get_strzval(msg, param, res, dpv->item->r[val.ri].addr);
		case 1: /* type */
			return pv_get_sintval(msg, param, res, dpv->item->r[val.ri].type);
		case 2: /* ipv4 */
			return pv_get_sintval(msg, param, res, dpv->item->ipv4);
		case 3: /* ipv6 */
			return pv_get_sintval(msg, param, res, dpv->item->ipv6);
		case 4: /* count */
			return pv_get_sintval(msg, param, res, dpv->item->count);
		default: /* else */
			return pv_get_null(msg, param, res);
	}
}

/**
 *
 */
int dns_init_pv(char *path)
{
	return 0;
}

/**
 *
 */
void dns_destroy_list(void)
{
	return;
}

/**
 *
 */
void dns_destroy_pv(void)
{
	return;
}

/**
 *
 */
int dns_update_pv(str *hostname, str *name)
{
	sr_dns_item_t *dr = NULL;
	struct addrinfo hints, *res, *p;
	struct sockaddr_in *ipv4;
	struct sockaddr_in6 *ipv6;
	void *addr;
	int status;
	int i;

	if(hostname->len > 255) {
		LM_DBG("target hostname too long (max 255): %s\n", hostname->s);
		return -2;
	}

	dr = sr_dns_add_item(name);
	if(dr == NULL) {
		LM_DBG("container not found: %s\n", name->s);
		return -3;
	}

	/* reset the counter */
	dr->count = 0;
	dr->ipv4 = 0;
	dr->ipv6 = 0;

	strncpy(dr->hostname, hostname->s, hostname->len);
	dr->hostname[hostname->len] = '\0';
	LM_DBG("attempting to resolve: %s\n", dr->hostname);

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC; /* allow any of AF_INET or AF_INET6 */
	// hints.ai_socktype = SOCK_STREAM;
	hints.ai_socktype = SOCK_DGRAM;

	if((status = getaddrinfo(dr->hostname, NULL, &hints, &res)) != 0) {
		LM_ERR("unable to resolve %s - getaddrinfo: %s\n", dr->hostname,
				gai_strerror(status));
		return -4;
	}

	i = 0;
	for(p = res; p != NULL; p = p->ai_next) {
		if(p->ai_family == AF_INET) {
			dr->ipv4 = 1;
			dr->r[i].type = 4;
			ipv4 = (struct sockaddr_in *)p->ai_addr;
			addr = &(ipv4->sin_addr);
		} else {
			dr->ipv6 = 1;
			dr->r[i].type = 6;
			ipv6 = (struct sockaddr_in6 *)p->ai_addr;
			addr = &(ipv6->sin6_addr);
		}
		inet_ntop(p->ai_family, addr, dr->r[i].addr, PV_DNS_ADDR);
		LM_DBG("#%d - type %d addr: %s (%d)\n", i, dr->r[i].type, dr->r[i].addr,
				p->ai_socktype);
		i++;
		if(i == PV_DNS_RECS) {
			LM_WARN("more than %d addresses for %s - truncating\n", PV_DNS_RECS,
					dr->hostname);
			break;
		}
	}
	freeaddrinfo(res);

	dr->count = i;

	LM_DBG("dns PV updated for: %s (%d)\n", dr->hostname, i);

	return 1;
}

struct _hn_pv_data
{
	str data;
	str fullname;
	str hostname;
	str domain;
	str ipaddr;
};

static struct _hn_pv_data *_hn_data = NULL;

/**
 *
 */
int hn_pv_data_init(void)
{
	char hbuf[512];
	int hlen;
	char *d;
	struct hostent *he;
	int i;

	if(_hn_data != NULL)
		return 0;

	if(gethostname(hbuf, 512) < 0) {
		LM_WARN("gethostname failed - host pvs will be null\n");
		return -1;
	}

	hlen = strlen(hbuf);
	if(hlen <= 0) {
		LM_WARN("empty hostname result - host pvs will be null\n");
		return -1;
	}

	_hn_data = (struct _hn_pv_data *)pkg_malloc(
			sizeof(struct _hn_pv_data) + 46 + 2 * (hlen + 1));
	if(_hn_data == NULL) {
		PKG_MEM_ERROR;
		return -1;
	}
	memset(_hn_data, 0, sizeof(struct _hn_pv_data) + 46 + 2 * (hlen + 1));

	_hn_data->data.len = hlen;
	_hn_data->data.s = (char *)_hn_data + sizeof(struct _hn_pv_data);
	_hn_data->fullname.len = hlen;
	_hn_data->fullname.s = _hn_data->data.s + hlen + 1;

	strcpy(_hn_data->data.s, hbuf);
	strcpy(_hn_data->fullname.s, hbuf);

	d = strchr(_hn_data->data.s, '.');
	if(d) {
		_hn_data->hostname.len = d - _hn_data->data.s;
		_hn_data->hostname.s = _hn_data->data.s;
		_hn_data->domain.len =
				_hn_data->fullname.len - _hn_data->hostname.len - 1;
		_hn_data->domain.s = d + 1;
	} else {
		_hn_data->hostname = _hn_data->fullname;
	}

	he = resolvehost(_hn_data->fullname.s);
	if(he) {
		if((strlen(he->h_name) != _hn_data->fullname.len)
				|| strncmp(he->h_name, _hn_data->fullname.s,
						_hn_data->fullname.len)) {
			LM_WARN("hostname '%.*s' different than gethostbyname '%s'\n",
					_hn_data->fullname.len, _hn_data->fullname.s, he->h_name);
		}

		if(he->h_addr_list) {
			for(i = 0; he->h_addr_list[i]; i++) {
				if(inet_ntop(he->h_addrtype, he->h_addr_list[i], hbuf, 46)) {
					if(_hn_data->ipaddr.len == 0) {
						_hn_data->ipaddr.len = strlen(hbuf);
						_hn_data->ipaddr.s = _hn_data->fullname.s + hlen + 1;
						strcpy(_hn_data->ipaddr.s, hbuf);
					} else if(strncmp(_hn_data->ipaddr.s, hbuf,
									  _hn_data->ipaddr.len)
							  != 0) {
						LM_WARN("many IPs to hostname: %s not used\n", hbuf);
					}
				}
			}
		} else {
			LM_WARN(" can't resolve hostname's address: %s\n",
					_hn_data->fullname.s);
		}
	}

	DBG("Hostname: %.*s\n", _hn_data->hostname.len, ZSW(_hn_data->hostname.s));
	DBG("Domain:   %.*s\n", _hn_data->domain.len, ZSW(_hn_data->domain.s));
	DBG("Fullname: %.*s\n", _hn_data->fullname.len, ZSW(_hn_data->fullname.s));
	DBG("IPaddr:   %.*s\n", _hn_data->ipaddr.len, ZSW(_hn_data->ipaddr.s));

	return 0;
}

/**
 *
 */
int pv_parse_hn_name(pv_spec_p sp, str *in)
{
	if(sp == NULL || in == NULL || in->len <= 0)
		return -1;

	switch(in->len) {
		case 1:
			if(strncmp(in->s, "n", 1) == 0)
				sp->pvp.pvn.u.isname.name.n = 0;
			else if(strncmp(in->s, "f", 1) == 0)
				sp->pvp.pvn.u.isname.name.n = 1;
			else if(strncmp(in->s, "d", 1) == 0)
				sp->pvp.pvn.u.isname.name.n = 2;
			else if(strncmp(in->s, "i", 1) == 0)
				sp->pvp.pvn.u.isname.name.n = 3;
			else
				goto error;
			break;
		default:
			goto error;
	}
	sp->pvp.pvn.type = PV_NAME_INTSTR;
	sp->pvp.pvn.u.isname.type = 0;

	hn_pv_data_init();

	return 0;

error:
	LM_ERR("unknown host PV name %.*s\n", in->len, in->s);
	return -1;
}

/**
 *
 */
int pv_get_hn(struct sip_msg *msg, pv_param_t *param, pv_value_t *res)
{
	if(param == NULL)
		return -1;
	if(_hn_data == NULL)
		return pv_get_null(msg, param, res);
	;
	switch(param->pvn.u.isname.name.n) {
		case 1:
			if(_hn_data->fullname.len == 0)
				return pv_get_null(msg, param, res);
			;
			return pv_get_strval(msg, param, res, &_hn_data->fullname);
		case 2:
			if(_hn_data->domain.len == 0)
				return pv_get_null(msg, param, res);
			;
			return pv_get_strval(msg, param, res, &_hn_data->domain);
		case 3:
			if(_hn_data->ipaddr.len == 0)
				return pv_get_null(msg, param, res);
			;
			return pv_get_strval(msg, param, res, &_hn_data->ipaddr);
		default:
			if(_hn_data->hostname.len == 0)
				return pv_get_null(msg, param, res);
			;
			return pv_get_strval(msg, param, res, &_hn_data->hostname);
	}
}

/**********
 * srvquery PV
 **********/

static char *srvqrylst[] = {
		"count", "port", "priority", "target", "weight", NULL};

#define PV_SRV_MAXSTR 64
#define PV_SRV_MAXRECS 32

typedef struct _sr_srv_record
{
	unsigned short priority;
	unsigned short weight;
	unsigned short port;
	char target[PV_SRV_MAXSTR + 1];
} sr_srv_record_t;

typedef struct _sr_srv_item
{
	str pvid;
	unsigned int hashid;
	int count;
	sr_srv_record_t rr[PV_SRV_MAXRECS];
	struct _sr_srv_item *next;
} sr_srv_item_t;

typedef struct _srv_pv
{
	sr_srv_item_t *item;
	int type;
	int flags;
	pv_spec_t *pidx;
	int nidx;
} srv_pv_t;

static sr_srv_item_t *_sr_srv_list = NULL;

/**********
 * Add srvquery Item
 *
 * INPUT:
 *   Arg (1) = pvid string pointer
 *   Arg (2) = find flag; 0 - add if not found; 1 - search only
 * OUTPUT: srv record pointer; NULL=not found
 **********/

sr_srv_item_t *sr_srv_add_item(str *pvid, int findflg)

{
	sr_srv_item_t *pitem;
	unsigned int hashid;

	/**********
	 * o get hash
	 * o already exists?
	 **********/

	hashid = get_hash1_raw(pvid->s, pvid->len);
	for(pitem = _sr_srv_list; pitem; pitem = pitem->next) {
		if(pitem->hashid == hashid && pitem->pvid.len == pvid->len
				&& !strncmp(pitem->pvid.s, pvid->s, pvid->len))
			return pitem;
	}
	if(findflg)
		return NULL;

	/**********
	 * o alloc/init item structure
	 * o link in new item
	 **********/

	pitem = (sr_srv_item_t *)pkg_malloc(sizeof(sr_srv_item_t));
	if(!pitem) {
		PKG_MEM_ERROR;
		return NULL;
	}
	memset(pitem, 0, sizeof(sr_srv_item_t));
	pitem->pvid.s = (char *)pkg_malloc(pvid->len + 1);
	if(!pitem->pvid.s) {
		PKG_MEM_ERROR;
		pkg_free(pitem);
		return NULL;
	}
	memcpy(pitem->pvid.s, pvid->s, pvid->len);
	pitem->pvid.len = pvid->len;
	pitem->hashid = hashid;
	pitem->next = _sr_srv_list;
	_sr_srv_list = pitem;
	return pitem;
}

/**********
 * Skip Over
 *
 * INPUT:
 *   Arg (1) = string pointer
 *   Arg (2) = starting position
 *   Arg (3) = whitespace flag
 * OUTPUT: position past skipped
 **********/

int skip_over(str *pstr, int pos, int bWS)

{
	char *pchar;

	/**********
	 * o string exists?
	 * o skip over
	 **********/

	if(pos >= pstr->len)
		return pstr->len;
	for(pchar = &pstr->s[pos]; pos < pstr->len; pchar++, pos++) {
		if(*pchar == ' ' || *pchar == '\t' || *pchar == '\n'
				|| *pchar == '\r') {
			if(bWS)
				continue;
		}
		if((*pchar >= 'A' && *pchar <= 'Z') || (*pchar >= 'a' && *pchar <= 'z')
				|| (*pchar >= '0' && *pchar <= '9')) {
			if(!bWS)
				continue;
		}
		break;
	}
	return pos;
}

/**********
 * Sort SRV Records by Weight (RFC 2782)
 *
 * INPUT:
 *   Arg (1) = pointer to array of SRV records
 *   Arg (2) = first record in range
 *   Arg (3) = last record in range
 * OUTPUT: position past skipped
 **********/

void sort_weights(struct srv_rdata **plist, int pos1, int pos2)

{
	int idx1, idx2, lastfound;
	struct srv_rdata *wlist[PV_SRV_MAXRECS];
	unsigned int rand, sum, sums[PV_SRV_MAXRECS];

	/**********
	 * place zero weights in the unordered list and then non-zero
	 **********/

	idx2 = 0;
	for(idx1 = pos1; idx1 <= pos2; idx1++) {
		if(!plist[idx1]->weight) {
			wlist[idx2++] = plist[idx1];
		}
	}
	for(idx1 = pos1; idx1 <= pos2; idx1++) {
		if(plist[idx1]->weight) {
			wlist[idx2++] = plist[idx1];
		}
	}

	/**********
	 * generate running sum list
	 **********/

	sum = 0;
	for(idx1 = 0; idx1 < idx2; idx1++) {
		sum += wlist[idx1]->weight;
		sums[idx1] = sum;
	}

	/**********
	 * resort randomly
	 **********/

	lastfound = 0;
	for(idx1 = pos1; idx1 <= pos2; idx1++) {
		/**********
		 * o calculate a random number in range
		 * o find first unsorted
		 **********/

		rand = fastrand_max(sum);
		for(idx2 = 0; idx2 <= pos2 - pos1; idx2++) {
			if(!wlist[idx2]) {
				continue;
			}
			if(sums[idx2] >= rand) {
				plist[idx1] = wlist[idx2];
				wlist[idx2] = 0;
				break;
			}
			lastfound = idx2;
		}
		if(idx2 > pos2 - pos1) {
			plist[idx1] = wlist[lastfound];
			wlist[lastfound] = 0;
		}
	}
	return;
}

/**********
 * Sort SRV Records by Priority/Weight
 *
 * INPUT:
 *   Arg (1) = pointer to array of SRV records
 *   Arg (2) = record count
 * OUTPUT: position past skipped
 **********/

void sort_srv(struct srv_rdata **plist, int rcount)

{
	int idx1, idx2;
	struct srv_rdata *pswap;

	/**********
	 * sort by priority
	 **********/

	for(idx1 = 1; idx1 < rcount; idx1++) {
		pswap = plist[idx1];
		for(idx2 = idx1; idx2 && (plist[idx2 - 1]->priority > pswap->priority);
				--idx2) {
			plist[idx2] = plist[idx2 - 1];
		}
		plist[idx2] = pswap;
	}

	/**********
	 * check for multiple priority
	 **********/

	idx2 = 0;
	pswap = plist[0];
	for(idx1 = 1; idx1 < rcount; idx1++) {
		if((idx1 == rcount) || (pswap->priority != plist[idx1]->priority)) {
			/**********
			 * o range has more than one element?
			 * o restart range
			 **********/

			if(idx1 - idx2 - 1) {
				sort_weights(plist, idx2, idx1 - 1);
			}
			idx2 = idx1;
			pswap = plist[idx2];
		}
	}
	return;
}

/**********
 * Parse srvquery Name
 *
 * INPUT:
 *   Arg (1) = pv spec pointer
 *   Arg (2) = input string pointer
 * OUTPUT: 0=success
 **********/

int pv_parse_srv_name(pv_spec_t *sp, str *in)

{
	char *pstr;
	int i, pos, sign;
	srv_pv_t *dpv;
	str pvi = {0}, pvk = {0}, pvn = {0};

	/**********
	 * o alloc/init pvid structure
	 * o extract pvid name
	 * o check separator
	 **********/

	if(!sp || !in || in->len <= 0)
		return -1;
	dpv = (srv_pv_t *)pkg_malloc(sizeof(srv_pv_t));
	if(!dpv) {
		PKG_MEM_ERROR;
		return -1;
	}
	memset(dpv, 0, sizeof(srv_pv_t));
	pos = skip_over(in, 0, 1);
	if(pos == in->len)
		goto error;
	pvn.s = &in->s[pos];
	pvn.len = pos;
	pos = skip_over(in, pos, 0);
	pvn.len = pos - pvn.len;
	if(!pvn.len)
		goto error;
	pos = skip_over(in, pos, 1);
	if((pos + 2) > in->len)
		goto error;
	if(strncmp(&in->s[pos], "=>", 2))
		goto error;

	/**********
	 * o extract key name
	 * o check key name
	 * o count?
	 **********/

	pos = skip_over(in, pos + 2, 1);
	pvk.s = &in->s[pos];
	pvk.len = pos;
	pos = skip_over(in, pos, 0);
	pvk.len = pos - pvk.len;
	if(!pvk.len)
		goto error;
	for(i = 0; srvqrylst[i]; i++) {
		if(strlen(srvqrylst[i]) != pvk.len)
			continue;
		if(!strncmp(pvk.s, srvqrylst[i], pvk.len)) {
			dpv->type = i;
			break;
		}
	}
	if(!srvqrylst[i])
		goto error;
	if(!i)
		goto noindex;

	/**********
	 * o check for array
	 * o extract array index and check
	 **********/

	pos = skip_over(in, pos, 1);
	if((pos + 3) > in->len)
		goto error;
	if(in->s[pos] != '[')
		goto error;
	pos = skip_over(in, pos + 1, 1);
	if((pos + 2) > in->len)
		goto error;
	pvi.s = &in->s[pos];
	pvi.len = pos;
	if(in->s[pos] == PV_MARKER) {
		/**********
		 * o search from the end back to array close
		 * o get PV value
		 **********/

		for(i = in->len - 1; i != pos; --i) {
			if(in->s[i] == ']')
				break;
		}
		if(i == pos)
			goto error;
		pvi.len = i - pvi.len;
		pos = i + 1;
		dpv->pidx = pv_cache_get(&pvi);
		if(!dpv->pidx)
			goto error;
		dpv->flags |= SR_DNS_PVIDX;
	} else {
		/**********
		 * o get index value
		 * o check for reverse index
		 * o convert string to number
		 **********/

		pos = skip_over(in, pos, 0);
		pvi.len = pos - pvi.len;
		sign = 1;
		i = 0;
		pstr = pvi.s;
		if(*pstr == '-') {
			sign = -1;
			i++;
			pstr++;
		}
		for(dpv->nidx = 0; i < pvi.len; i++) {
			if(*pstr >= '0' && *pstr <= '9')
				dpv->nidx = (dpv->nidx * 10) + *pstr++ - '0';
		}
		if(i != pvi.len)
			goto error;
		dpv->nidx *= sign;
		pos = skip_over(in, pos, 1);
		if(pos == in->len)
			goto error;
		if(in->s[pos++] != ']')
			goto error;
	}

	/**********
	 * o check for trailing whitespace
	 * o add data to PV
	 **********/

noindex:
	if(skip_over(in, pos, 1) != in->len)
		goto error;
	LM_DBG("srvquery (%.*s => %.*s [%.*s])\n", pvn.len, ZSW(pvn.s), pvk.len,
			ZSW(pvk.s), pvi.len, ZSW(pvi.s));
	dpv->item = sr_srv_add_item(&pvn, 0);
	if(!dpv->item)
		goto error;
	sp->pvp.pvn.u.dname = (void *)dpv;
	sp->pvp.pvn.type = PV_NAME_OTHER;
	return 0;

error:
	LM_ERR("error at PV srvquery: %.*s@%d\n", in->len, in->s, pos);
	pkg_free(dpv);
	return -1;
}

int srv_update_pv(str *srvcname, str *pvid)

{
	int idx1, idx2, rcount;
	struct rdata *phead, *psrv;
	struct srv_rdata *plist[PV_SRV_MAXRECS];
	sr_srv_item_t *pitem;
	sr_srv_record_t *prec;

	/**********
	 * o service name missing?
	 * o find pvid
	 **********/

	if(!srvcname->len) {
		LM_DBG("service name missing: %.*s\n", srvcname->len, srvcname->s);
		return -2;
	}
	pitem = sr_srv_add_item(pvid, 0);
	if(!pitem) {
		LM_DBG("pvid not found: %.*s\n", pvid->len, pvid->s);
		return -3;
	}

	/**********
	 * o get records
	 * o sort by priority/weight
	 * o save to PV
	 **********/

	LM_DBG("attempting to query: %.*s\n", srvcname->len, srvcname->s);
	phead = get_record(srvcname->s, T_SRV, RES_ONLY_TYPE);
	rcount = 0;
	for(psrv = phead; psrv; psrv = psrv->next) {
		if(rcount < PV_SRV_MAXRECS) {
			plist[rcount++] = (struct srv_rdata *)psrv->rdata;
		} else {
			LM_WARN("truncating srv_query list to %d records!", PV_SRV_MAXRECS);
			break;
		}
	}
	pitem->count = rcount;
	if(rcount)
		sort_srv(plist, rcount);
	for(idx1 = 0; idx1 < rcount; idx1++) {
		prec = &pitem->rr[idx1];
		prec->priority = plist[idx1]->priority;
		prec->weight = plist[idx1]->weight;
		prec->port = plist[idx1]->port;
		idx2 = plist[idx1]->name_len;
		if(idx2 > PV_SRV_MAXSTR) {
			LM_WARN("truncating srv_query target (%.*s)!", idx2,
					plist[idx1]->name);
			idx2 = PV_SRV_MAXSTR;
		}
		strncpy(prec->target, plist[idx1]->name, idx2);
		prec->target[idx2] = '\0';
	}
	if(phead)
		free_rdata_list(phead);
	LM_DBG("srvquery PV updated for: %.*s (%d)\n", srvcname->len, srvcname->s,
			rcount);
	return 1;
}

/**********
 * Get srvquery Values
 *
 * INPUT:
 *   Arg (1) = SIP message pointer
 *   Arg (2) = parameter pointer
 *   Arg (3) = PV value pointer
 * OUTPUT: 0=success
 **********/

int pv_get_srv(sip_msg_t *pmsg, pv_param_t *param, pv_value_t *res)

{
	pv_value_t val;
	srv_pv_t *dpv;

	/**********
	 * o sipmsg and param exist?
	 * o PV name exists?
	 * o count?
	 **********/

	if(!pmsg || !param)
		return -1;
	dpv = (srv_pv_t *)param->pvn.u.dname;
	if(!dpv || !dpv->item)
		return -1;
	if(!dpv->type)
		return pv_get_sintval(pmsg, param, res, dpv->item->count);

	/**********
	 * o get index value
	 * o reverse index?
	 * o extract data
	 **********/

	if(!dpv->pidx) {
		val.ri = dpv->nidx;
	} else {
		if(pv_get_spec_value(pmsg, dpv->pidx, &val) < 0
				|| !(val.flags & PV_VAL_INT)) {
			LM_ERR("failed to evaluate index variable!\n");
			return pv_get_null(pmsg, param, res);
		}
	}
	if(val.ri < 0) {
		if((dpv->item->count + val.ri) < 0)
			return pv_get_null(pmsg, param, res);
		val.ri = dpv->item->count + val.ri;
	}
	if(val.ri >= dpv->item->count)
		return pv_get_null(pmsg, param, res);
	switch(dpv->type) {
		case 1: /* port */
			return pv_get_sintval(pmsg, param, res, dpv->item->rr[val.ri].port);
		case 2: /* priority */
			return pv_get_sintval(
					pmsg, param, res, dpv->item->rr[val.ri].priority);
		case 3: /* target */
			return pv_get_strzval(
					pmsg, param, res, dpv->item->rr[val.ri].target);
		case 4: /* weight */
			return pv_get_sintval(
					pmsg, param, res, dpv->item->rr[val.ri].weight);
	}
	return pv_get_null(pmsg, param, res);
}

/**********
 * naptrquery PV
 **********/

static char *naptrqrylst[] = {"count", "order", "pref", "flags", "services",
		"regex", "replace", NULL};

#define PV_NAPTR_MAXSTR 64
#define PV_NAPTR_MAXRECS 32

typedef struct _sr_naptr_record
{
	unsigned short count;
	unsigned short order;
	unsigned short pref;
	char flags[PV_NAPTR_MAXSTR + 1];
	char services[PV_NAPTR_MAXSTR + 1];
	char regex[PV_NAPTR_MAXSTR + 1];
	char replace[PV_NAPTR_MAXSTR + 1];
} sr_naptr_record_t;

typedef struct _sr_naptr_item
{
	str pvid;
	unsigned int hashid;
	int count;
	sr_naptr_record_t rr[PV_NAPTR_MAXRECS];
	struct _sr_naptr_item *next;
} sr_naptr_item_t;

typedef struct _naptr_pv
{
	sr_naptr_item_t *item;
	int type;
	int flags;
	pv_spec_t *pidx;
	int nidx;
} naptr_pv_t;

static sr_naptr_item_t *_sr_naptr_list = NULL;

/**********
 * Add naptrquery Item
 *
 * INPUT:
 *   Arg (1) = pvid string pointer
 *   Arg (2) = find flag; 0 - add if not found, 1 - search only
 * OUTPUT: naptr record pointer; NULL=not found
 **********/

sr_naptr_item_t *sr_naptr_add_item(str *pvid, int findflg)
{
	sr_naptr_item_t *pitem;
	unsigned int hashid;

	LM_DBG("%s:%d %s - called: pvid => [%.*s] findflg => [%d]\n", __FILE__,
			__LINE__, __PRETTY_FUNCTION__, STR_FMT(pvid), findflg);

	/**********
	 * o get hash
	 * o already exists?
	 **********/

	hashid = get_hash1_raw(pvid->s, pvid->len);
	for(pitem = _sr_naptr_list; pitem; pitem = pitem->next) {
		if(pitem->hashid == hashid && pitem->pvid.len == pvid->len
				&& !strncmp(pitem->pvid.s, pvid->s, pvid->len))
			return pitem;
	}
	if(findflg)
		return NULL;

	/**********
	 * o alloc/init item structure
	 * o link in new item
	 **********/

	pitem = (sr_naptr_item_t *)pkg_malloc(sizeof(sr_naptr_item_t));
	if(!pitem) {
		PKG_MEM_ERROR;
		return NULL;
	}
	memset(pitem, 0, sizeof(sr_naptr_item_t));

	pitem->pvid.s = (char *)pkg_malloc(pvid->len + 1);
	if(!pitem->pvid.s) {
		PKG_MEM_ERROR;
		pkg_free(pitem);
		return NULL;
	}
	memcpy(pitem->pvid.s, pvid->s, pvid->len);
	pitem->pvid.len = pvid->len;
	pitem->hashid = hashid;
	pitem->next = _sr_naptr_list;

	_sr_naptr_list = pitem;

	LM_DBG("New item [%.*s]", STR_FMT(pvid));

	return pitem;
}

/**********
 * Sort NAPTR Records by Order/Pref
 *
 * INPUT:
 *   Arg (1) = pointer to array of NAPTR records
 *   Arg (2) = record count
 * OUTPUT: position past skipped
 **********/

void sort_naptr(struct naptr_rdata **plist, int rcount)

{
	int idx1, idx2;
	struct naptr_rdata *pswap;

	for(idx1 = 1; idx1 < rcount; idx1++) {
		pswap = plist[idx1];
		for(idx2 = idx1;
				idx2
				&& ((plist[idx2 - 1]->order > pswap->order)
						|| (plist[idx2 - 1]->order == pswap->order
								&& plist[idx2 - 1]->pref > pswap->pref));
				--idx2) {
			plist[idx2] = plist[idx2 - 1];
		}
		plist[idx2] = pswap;
	}

	return;
}

/**********
 * Parse naptrquery Name
 *
 * INPUT:
 *   Arg (1) = pv spec pointer
 *   Arg (2) = input string pointer
 * OUTPUT: 0=success
 **********/
int pv_parse_naptr_name(pv_spec_t *sp, str *in)
{
	LM_DBG("%s:%d %s - called: sp => [%p] in => [%.*s]\n", __FILE__, __LINE__,
			__PRETTY_FUNCTION__, sp, STR_FMT(in));

	char *pstr;
	int i, pos, sign;
	naptr_pv_t *dpv;
	str pvn = {0}, pvk = {0}, pvi = {0};

	/**********
	 * o alloc/init pvid structure
	 * o extract pvid name
	 * o check separator
	 **********/

	if(!sp || !in || in->len <= 0)
		return -1;

	/* alloc/init pvid structure */
	dpv = (naptr_pv_t *)pkg_malloc(sizeof(naptr_pv_t));
	if(!dpv) {
		PKG_MEM_ERROR;
		return -1;
	}
	memset(dpv, 0, sizeof(naptr_pv_t));

	/* skip blank chars and init pvn.s */
	pos = skip_over(in, 0, 1);
	if(pos == in->len)
		goto error;
	pvn.s = &in->s[pos];
	pvn.len = pos;

	/* skip [a-zA-Z0-9] and find pvn.len */
	pos = skip_over(in, pos, 0);
	pvn.len = pos - pvn.len;
	if(!pvn.len)
		goto error;

	/* skip blank chars */
	pos = skip_over(in, pos, 1);
	if((pos + 2) > in->len)
		goto error;

	if(strncmp(&in->s[pos], "=>", 2))
		goto error;

	/**********
	 * o extract key name
	 * o check key name
	 * o count?
	 **********/

	/* skip blank chars and init pvk.s */
	pos = skip_over(in, pos + 2, 1);
	pvk.s = &in->s[pos];
	pvk.len = pos;

	/* skip [a-zA-Z0-9] and find pvk.len */
	pos = skip_over(in, pos, 0);
	pvk.len = pos - pvk.len;
	if(!pvk.len)
		goto error;


	for(i = 0; naptrqrylst[i]; i++) {
		if(strlen(naptrqrylst[i]) != pvk.len)
			continue;
		if(!strncmp(pvk.s, naptrqrylst[i], pvk.len)) {
			dpv->type = i;
			break;
		}
	}

	if(!naptrqrylst[i])
		goto error;

	/* "=>count" doesn't have any index */
	if(!i)
		goto noindex;

	/**********
	 * o check for array
	 * o extract array index and check
	 **********/

	/* skip blank between "=>order" and array index "["  */
	pos = skip_over(in, pos, 1);
	if((pos + 3) > in->len)
		goto error;

	if(in->s[pos] != '[')
		goto error;

	/* skip blank between index sign "[" and a numeric value */
	pos = skip_over(in, pos + 1, 1);
	if((pos + 2) > in->len)
		goto error;

	pvi.s = &in->s[pos];
	pvi.len = pos;
	if(in->s[pos] == PV_MARKER) {
		/**********
		 * o search from the end back to array close
		 * o get PV value
		 **********/

		for(i = in->len - 1; i != pos; --i) {
			if(in->s[i] == ']')
				break;
		}
		/* empty idx */
		if(i == pos)
			goto error;

		pvi.len = i - pvi.len;
		pos = i + 1;
		dpv->pidx = pv_cache_get(&pvi);
		if(!dpv->pidx)
			goto error;
		dpv->flags |= SR_DNS_PVIDX;
	} else {
		/**********
		 * o get index value
		 * o check for reverse index
		 * o convert string to number
		 **********/

		/* find the end of the index and its length */
		pos = skip_over(in, pos, 0);
		pvi.len = pos - pvi.len;
		sign = 1;
		i = 0;
		pstr = pvi.s;
		if(*pstr == '-') {
			sign = -1;
			i++;
			pstr++;
		}
		/* homemade atoi() */
		for(dpv->nidx = 0; i < pvi.len; i++) {
			if(*pstr >= '0' && *pstr <= '9')
				dpv->nidx = (dpv->nidx * 10) + *pstr++ - '0';
		}
		if(i != pvi.len)
			goto error;
		dpv->nidx *= sign;

		/* skip blanks between index and the final "]" */
		pos = skip_over(in, pos, 1);
		if(pos == in->len)
			goto error;

		/* check the final "]" */
		if(in->s[pos++] != ']')
			goto error;
	}

	/**********
	 * o check for trailing whitespace
	 * o add data to PV
	 **********/

noindex:

	if(skip_over(in, pos, 1) != in->len)
		goto error;

	LM_DBG("naptrquery (%.*s => %.*s [%.*s])\n", pvn.len, ZSW(pvn.s), pvk.len,
			ZSW(pvk.s), pvi.len, ZSW(pvi.s));

	dpv->item = sr_naptr_add_item(&pvn, 0);
	if(!dpv->item)
		goto error;

	sp->pvp.pvn.u.dname = (void *)dpv;
	sp->pvp.pvn.type = PV_NAME_OTHER;
	return 0;

error:
	LM_ERR("error at PV naptrquery: %.*s@%d\n", in->len, in->s, pos);
	pkg_free(dpv);
	return -1;
}

int naptr_update_pv(str *naptrname, str *pvid)
{
	LM_DBG("%s:%d %s - called: naptrname => [%.*s], pvid => [%.*s]\n", __FILE__,
			__LINE__, __PRETTY_FUNCTION__, STR_FMT(naptrname), STR_FMT(pvid));

	int idx1, idx2, rcount;
	struct rdata *phead, *pnaptr;
	struct naptr_rdata *plist[PV_SRV_MAXRECS];
	sr_naptr_item_t *pitem;
	sr_naptr_record_t *prec;

	/**********
	 * o service name missing?
	 * o find pvid
	 **********/

	if(!naptrname->len) {
		LM_DBG("naptr name missing: %.*s\n", naptrname->len, naptrname->s);
		return -2;
	}
	pitem = sr_naptr_add_item(pvid, 0);
	if(!pitem) {
		LM_DBG("pvid not found: %.*s\n", pvid->len, pvid->s);
		return -3;
	}

	/**********
	 * o get records
	 * o sort by order/pref
	 * o save to PV
	 **********/

	LM_DBG("attempting to query: %.*s\n", naptrname->len, naptrname->s);
	phead = get_record(naptrname->s, T_NAPTR, RES_ONLY_TYPE);
	rcount = 0;
	for(pnaptr = phead; pnaptr; pnaptr = pnaptr->next) {
		if(rcount < PV_NAPTR_MAXRECS) {
			plist[rcount++] = (struct naptr_rdata *)pnaptr->rdata;
		} else {
			LM_WARN("truncating naptr_query list to %d records!",
					PV_NAPTR_MAXRECS);
			break;
		}
	}
	pitem->count = rcount;
	if(rcount)
		sort_naptr(plist, rcount);
	for(idx1 = 0; idx1 < rcount; idx1++) {
		prec = &pitem->rr[idx1];

		prec->order = plist[idx1]->order;
		prec->pref = plist[idx1]->pref;

		idx2 = plist[idx1]->flags_len;
		if(idx2 > PV_NAPTR_MAXSTR) {
			LM_WARN("truncating naptr_query flags (%.*s)!", idx2,
					plist[idx1]->flags);
			idx2 = PV_NAPTR_MAXSTR;
		}
		strncpy(prec->flags, plist[idx1]->flags, idx2);
		prec->flags[idx2] = '\0';

		idx2 = plist[idx1]->services_len;
		if(idx2 > PV_NAPTR_MAXSTR) {
			LM_WARN("truncating naptr query services (%.*s)!", idx2,
					plist[idx1]->services);
			idx2 = PV_NAPTR_MAXSTR;
		}
		strncpy(prec->services, plist[idx1]->services, idx2);
		prec->services[idx2] = '\0';

		idx2 = plist[idx1]->regexp_len;
		if(idx2 > PV_NAPTR_MAXSTR) {
			LM_WARN("truncating naptr query regexp (%.*s)!", idx2,
					plist[idx1]->regexp);
			idx2 = PV_NAPTR_MAXSTR;
		}
		strncpy(prec->regex, plist[idx1]->regexp, idx2);
		prec->regex[idx2] = '\0';

		idx2 = plist[idx1]->repl_len;
		if(idx2 > PV_NAPTR_MAXSTR) {
			LM_WARN("truncating naptr query replace (%.*s)!", idx2,
					plist[idx1]->repl);
			idx2 = PV_NAPTR_MAXSTR;
		}
		strncpy(prec->replace, plist[idx1]->repl, idx2);
		prec->replace[idx2] = '\0';
	}
	if(phead)
		free_rdata_list(phead);
	LM_DBG("naptrquery PV updated for: %.*s (%d)\n", naptrname->len,
			naptrname->s, rcount);

	return 1;
}

/**********
 * Get naptrquery Values
 *
 * INPUT:
 *   Arg (1) = SIP message pointer
 *   Arg (2) = parameter pointer
 *   Arg (3) = PV value pointer
 * OUTPUT: 0=success
 **********/

int pv_get_naptr(sip_msg_t *pmsg, pv_param_t *param, pv_value_t *res)
{
	pv_value_t val;
	naptr_pv_t *dpv;

	LM_DBG("%s:%d %s - called: param => [%p], res => [%p]\n", __FILE__,
			__LINE__, __PRETTY_FUNCTION__, param, res);

	/**********
	 * o sipmsg and param exist?
	 * o PV name exists?
	 * o count?
	 **********/

	if(!pmsg || !param)
		return -1;
	dpv = (naptr_pv_t *)param->pvn.u.dname;
	if(!dpv || !dpv->item)
		return -1;
	if(!dpv->type)
		return pv_get_sintval(pmsg, param, res, dpv->item->count);

	/**********
	 * o get index value
	 * o reverse index?
	 * o extract data
	 **********/

	if(!dpv->pidx) {
		val.ri = dpv->nidx;
	} else {
		if(pv_get_spec_value(pmsg, dpv->pidx, &val) < 0
				|| !(val.flags & PV_VAL_INT)) {
			LM_ERR("failed to evaluate index variable!\n");
			return pv_get_null(pmsg, param, res);
		}
	}
	if(val.ri < 0) {
		if((dpv->item->count + val.ri) < 0)
			return pv_get_null(pmsg, param, res);
		val.ri = dpv->item->count + val.ri;
	}
	if(val.ri >= dpv->item->count)
		return pv_get_null(pmsg, param, res);

	switch(dpv->type) {
		case 1: /* order */
			return pv_get_sintval(
					pmsg, param, res, dpv->item->rr[val.ri].order);
		case 2: /* pref */
			return pv_get_sintval(pmsg, param, res, dpv->item->rr[val.ri].pref);
		case 3: /* flags */
			return pv_get_strzval(
					pmsg, param, res, dpv->item->rr[val.ri].flags);
		case 4: /* services */
			return pv_get_strzval(
					pmsg, param, res, dpv->item->rr[val.ri].services);
		case 5: /* regex */
			return pv_get_strzval(
					pmsg, param, res, dpv->item->rr[val.ri].regex);
		case 6: /* replace */
			return pv_get_strzval(
					pmsg, param, res, dpv->item->rr[val.ri].replace);
	}
	return pv_get_null(pmsg, param, res);
}
