/**
 * Copyright (C) 2011 Daniel-Constantin Mierla (asipto.com)
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

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

#include "../../core/dprint.h"
#include "../../core/dset.h"
#include "../../core/forward.h"
#include "../../core/parser/parse_uri.h"
#include "../../core/resolve.h"

#include "corex_lib.h"

/**
 * append new branches with generic parameters
 */
int corex_append_branch(sip_msg_t *msg, str *uri, str *qv)
{
	int ret = 0;

	qvalue_t q = Q_UNSPECIFIED;
	flag_t branch_flags = 0;

	if(qv != NULL && qv->len > 0 && str2q(&q, qv->s, qv->len) < 0) {
		LM_ERR("cannot parse the Q parameter\n");
		return -1;
	}

	getbflagsval(0, &branch_flags);
	ret = append_branch(msg, (uri && uri->len > 0) ? uri : 0, &msg->dst_uri,
			&msg->path_vec, q, branch_flags, msg->force_send_socket, 0, 0, 0,
			0);


	if(uri == NULL || uri->len <= 0) {
		/* reset all branch attributes if r-uri was shifted to branch */
		reset_force_socket(msg);
		setbflagsval(0, 0);
		if(msg->dst_uri.s != 0)
			pkg_free(msg->dst_uri.s);
		msg->dst_uri.s = 0;
		msg->dst_uri.len = 0;

		/* if this is a cloned message, don't free the path vector as it was copied into shm memory and will be freed as contiguous block*/
		if(!shm_address_in(msg->path_vec.s)) {
			if(msg->path_vec.s)
				pkg_free(msg->path_vec.s);
			msg->path_vec.s = 0;
			msg->path_vec.len = 0;
		} else {
			LM_WARN("Found path_vec that is not in pkg mem!\n");
		}
	}

	return ret;
}

/**
 * append new branches with generic parameters
 */
int w_corex_append_branch(sip_msg_t *msg, gparam_t *pu, gparam_t *pq)
{
	str uri = {0};
	str qv = {0};
	if(pu != NULL) {
		if(fixup_get_svalue(msg, pu, &uri) != 0) {
			LM_ERR("cannot get the URI parameter\n");
			return -1;
		}
	}
	if(pq != NULL) {
		if(fixup_get_svalue(msg, pq, &qv) != 0) {
			LM_ERR("cannot get the Q parameter\n");
			return -1;
		}
	}
	return corex_append_branch(
			msg, (pu != NULL) ? &uri : NULL, (pq != NULL) ? &qv : NULL);
}

typedef struct corex_alias
{
	str alias;
	unsigned short port;
	unsigned short proto;
	int flags;
	struct corex_alias *next;
} corex_alias_t;

static corex_alias_t *_corex_alias_list = NULL;

int corex_add_alias_subdomains(char *aliasval)
{
	char *p = NULL;
	corex_alias_t ta;
	corex_alias_t *na;

	LM_DBG("try to add alias: %s\n", aliasval);
	memset(&ta, 0, sizeof(corex_alias_t));

	p = strchr(aliasval, ':');
	if(p == NULL) {
		/* only hostname */
		ta.alias.s = aliasval;
		ta.alias.len = strlen(aliasval);
		goto done;
	}
	if((p - aliasval) == 3 || (p - aliasval) == 4) {
		/* check if it is protocol */
		if((p - aliasval) == 3 && strncasecmp(aliasval, "udp", 3) == 0) {
			ta.proto = PROTO_UDP;
		} else if((p - aliasval) == 3 && strncasecmp(aliasval, "tcp", 3) == 0) {
			ta.proto = PROTO_TCP;
		} else if((p - aliasval) == 3 && strncasecmp(aliasval, "tls", 3) == 0) {
			ta.proto = PROTO_TLS;
		} else if((p - aliasval) == 4
				  && strncasecmp(aliasval, "sctp", 4) == 0) {
			ta.proto = PROTO_SCTP;
		} else {
			/* invalid protocol */
			LM_ERR("invalid protocol %.*s", (int)(p - aliasval), aliasval);
			goto error;
		}
		/* hostname */
		p++;
		ta.alias.s = aliasval + (p - aliasval);

		if(p >= aliasval + strlen(aliasval)) {
			goto error;
		}
		/* port */
		p = strchr(ta.alias.s, ':');
		if(p == NULL) {
			ta.alias.len = strlen(ta.alias.s);
			goto done;
		}
		ta.alias.len = p - ta.alias.s;
		p++;
		ta.port = str2s(p, strlen(p), NULL);

	} else {
		/* hostname */
		ta.alias.s = aliasval;
		ta.alias.len = p - ta.alias.s;
		/* port */
		p++;
		if(p >= aliasval + strlen(aliasval)) {
			goto error;
		}
		ta.port = str2s(p, strlen(p), NULL);
	}

done:
	if(ta.alias.len == 0)
		goto error;

	na = (corex_alias_t *)pkg_malloc(sizeof(corex_alias_t));
	if(na == NULL) {
		LM_ERR("no memory for adding alias subdomains: %s\n", aliasval);
		return -1;
	}
	memcpy(na, &ta, sizeof(corex_alias_t));
	na->next = _corex_alias_list;
	_corex_alias_list = na;

	LM_DBG("alias: %d:%.*s:%d from value: %s added\n", ta.proto, ta.alias.len,
			ta.alias.s, ta.port, aliasval);
	return 0;

error:
	LM_ERR("error adding alias subdomains: %s\n", aliasval);
	return -1;
}


int corex_check_self(str *host, unsigned short port, unsigned short proto)
{
	corex_alias_t *ta;

	LM_DBG("check self for: %d:%.*s:%d\n", (int)proto, host->len, host->s,
			(int)port);
	for(ta = _corex_alias_list; ta; ta = ta->next) {
		if(host->len < ta->alias.len)
			continue;
		if(ta->port != 0 && port != 0 && ta->port != port)
			continue;
		if(ta->proto != 0 && proto != 0 && ta->proto != proto)
			continue;
		if(host->len == ta->alias.len
				&& strncasecmp(host->s, ta->alias.s, host->len) == 0) {
			/* match domain */
			LM_DBG("check self domain match: %d:%.*s:%d\n", (int)ta->proto,
					ta->alias.len, ta->alias.s, (int)ta->port);
			return 1;
		}
		if(strncasecmp(ta->alias.s, host->s + host->len - ta->alias.len,
				   ta->alias.len)
				== 0) {
			if(host->s[host->len - ta->alias.len - 1] == '.') {
				/* match sub-domain */
				LM_DBG("check self sub-domain match: %d:%.*s:%d\n",
						(int)ta->proto, ta->alias.len, ta->alias.s,
						(int)ta->port);
				return 1;
			}
		}
	}
	LM_DBG("no match found\n");
	return 0; /* no match */
}

int corex_register_check_self(void)
{
	if(_corex_alias_list == NULL)
		return 0;
	if(register_check_self_func(corex_check_self) < 0) {
		LM_ERR("failed to register check self function\n");
		return -1;
	}
	return 0;
}

int corex_send(sip_msg_t *msg, gparam_t *pu, enum sip_protos proto)
{
	str dest = {0};
	int ret = 0;
	struct sip_uri next_hop, *u;
	struct dest_info dst;
	char *p;

	if(pu) {
		if(fixup_get_svalue(msg, pu, &dest)) {
			LM_ERR("cannot get the destination parameter\n");
			return -1;
		}
	}

	init_dest_info(&dst);

	if(dest.len <= 0) {
		/*get next hop uri uri*/
		if(msg->dst_uri.len) {
			ret = parse_uri(msg->dst_uri.s, msg->dst_uri.len, &next_hop);
			u = &next_hop;
		} else {
			ret = parse_sip_msg_uri(msg);
			u = &msg->parsed_uri;
		}

		if(ret < 0) {
			LM_ERR("send() - bad_uri dropping packet\n");
			ret = E_BUG;
			goto error;
		}
	} else {
		u = &next_hop;
		u->port_no = 5060;
		u->host = dest;
		/* detect ipv6 */
		p = memchr(dest.s, ']', dest.len);
		if(p) {
			p++;
			p = memchr(p, ':', dest.s + dest.len - p);
		} else {
			p = memchr(dest.s, ':', dest.len);
		}
		if(p) {
			u->host.len = p - dest.s;
			p++;
			u->port_no = str2s(p, dest.len - (p - dest.s), NULL);
		}
	}

	ret = sip_hostport2su(&dst.to, &u->host, u->port_no, &dst.proto);
	if(ret != 0) {
		LM_ERR("failed to resolve [%.*s]\n", u->host.len, ZSW(u->host.s));
		ret = E_BUG;
		goto error;
	}

	dst.proto = proto;
	if(proto == PROTO_UDP) {
		dst.send_sock = get_send_socket(msg, &dst.to, PROTO_UDP);
		if(dst.send_sock != 0) {
			ret = udp_send(&dst, msg->buf, msg->len);
		} else {
			ret = -1;
		}
	}
#ifdef USE_TCP
	else {
		/*tcp*/
		dst.id = 0;
		ret = tcp_send(&dst, 0, msg->buf, msg->len);
	}
#endif

	if(ret >= 0)
		ret = 1;


error:
	return ret;
}

/**
 *
 */
int corex_send_data(str *puri, str *psock, str *pdata)
{
	struct dest_info dst;
	sip_uri_t next_hop;
	int ret = 0;
	char proto;
	socket_info_t *si = NULL;
	int sport, sproto;
	str shost;

	if(parse_uri(puri->s, puri->len, &next_hop) < 0) {
		LM_ERR("bad dst sip uri <%.*s>\n", puri->len, puri->s);
		return -1;
	}

	init_dest_info(&dst);
	LM_DBG("sending data to sip uri <%.*s>\n", puri->len, puri->s);
	proto = next_hop.proto;
	if(sip_hostport2su(&dst.to, &next_hop.host, next_hop.port_no, &proto)
			!= 0) {
		LM_ERR("failed to resolve [%.*s]\n", next_hop.host.len,
				ZSW(next_hop.host.s));
		return -1;
	}

	if(psock && psock->s && psock->len > 0) {
		if(parse_phostport(psock->s, &shost.s, &shost.len, &sport, &sproto)
				< 0) {
			LM_ERR("invalid socket specification\n");
			return -1;
		}
		si = grep_sock_info(
				&shost, (unsigned short)sport, (unsigned short)sproto);
		if(si == NULL) {
			LM_WARN("local socket not found: %.*s\n", psock->len, psock->s);
		}
	}

	dst.proto = proto;
	if(dst.proto == PROTO_NONE)
		dst.proto = PROTO_UDP;

	if(dst.proto == PROTO_UDP) {
		if(si != NULL) {
			dst.send_sock = si;
		} else {
			dst.send_sock = get_send_socket(0, &dst.to, PROTO_UDP);
		}
		if(dst.send_sock != 0) {
			ret = udp_send(&dst, pdata->s, pdata->len);
		} else {
			LM_ERR("no socket for dst sip uri <%.*s>\n", puri->len, puri->s);
			ret = -1;
		}
	}
#ifdef USE_TCP
	else if(dst.proto == PROTO_TCP) {
		if(si != NULL) {
			dst.send_sock = si;
		}
		/*tcp*/
		dst.id = 0;
		ret = tcp_send(&dst, 0, pdata->s, pdata->len);
	}
#endif
#ifdef USE_TLS
	else if(dst.proto == PROTO_TLS) {
		if(si != NULL) {
			dst.send_sock = si;
		}
		/*tls*/
		dst.id = 0;
		ret = tcp_send(&dst, 0, pdata->s, pdata->len);
	}
#endif
#ifdef USE_SCTP
	else if(dst.proto == PROTO_SCTP) {
		/*sctp*/
		if(si != NULL) {
			dst.send_sock = si;
		} else {
			dst.send_sock = get_send_socket(0, &dst.to, PROTO_SCTP);
		}
		if(dst.send_sock != 0) {
			ret = sctp_core_msg_send(&dst, pdata->s, pdata->len);
		} else {
			LM_ERR("no socket for dst sip uri <%.*s>\n", puri->len, puri->s);
			ret = -1;
		}
	}
#endif
	else {
		LM_ERR("unknown proto [%d] for dst sip uri <%.*s>\n", dst.proto,
				puri->len, puri->s);
		ret = -1;
	}

	if(ret >= 0)
		ret = 1;

	return ret;
}
