/**
 * @file sip/msg.c  SIP Message decode
 *
 * Copyright (C) 2010 Creytiv.com
 */
#include <re_types.h>
#include <ctype.h>
#include <re_mem.h>
#include <re_sys.h>
#include <re_mbuf.h>
#include <re_sa.h>
#include <re_list.h>
#include <re_hash.h>
#include <re_fmt.h>
#include <re_uri.h>
#include <re_udp.h>
#include <re_sip.h>
#include <re_dbg.h>
#include "sip.h"


enum {
	HDR_HASH_SIZE = 32,
	STARTLINE_MAX = 8192,
};


static void hdr_destructor(void *arg)
{
	struct sip_hdr *hdr = arg;

	list_unlink(&hdr->le);
	hash_unlink(&hdr->he);
}


static void destructor(void *arg)
{
	struct sip_msg *msg = arg;

	list_flush(&msg->hdrl);
	hash_flush(msg->hdrht);
	mem_deref(msg->hdrht);
	mem_deref(msg->sock);
	mem_deref(msg->mb);
}


static enum sip_hdrid hdr_hash(const struct pl *name)
{
	if (!name->l)
		return SIP_HDR_NONE;

	if (name->l > 1) {
		switch (name->p[0]) {

		case 'x':
		case 'X':
			if (name->p[1] == '-')
				return SIP_HDR_NONE;

			/*@fallthrough@*/

		default:
			return (enum sip_hdrid)
				(hash_joaat_ci(name->p, name->l) & 0xfff);
		}
	}

	/* compact headers */
	switch (tolower(name->p[0])) {

	case 'a': return SIP_HDR_ACCEPT_CONTACT;
	case 'b': return SIP_HDR_REFERRED_BY;
	case 'c': return SIP_HDR_CONTENT_TYPE;
	case 'd': return SIP_HDR_REQUEST_DISPOSITION;
	case 'e': return SIP_HDR_CONTENT_ENCODING;
	case 'f': return SIP_HDR_FROM;
	case 'i': return SIP_HDR_CALL_ID;
	case 'j': return SIP_HDR_REJECT_CONTACT;
	case 'k': return SIP_HDR_SUPPORTED;
	case 'l': return SIP_HDR_CONTENT_LENGTH;
	case 'm': return SIP_HDR_CONTACT;
	case 'n': return SIP_HDR_IDENTITY_INFO;
	case 'o': return SIP_HDR_EVENT;
	case 'r': return SIP_HDR_REFER_TO;
	case 's': return SIP_HDR_SUBJECT;
	case 't': return SIP_HDR_TO;
	case 'u': return SIP_HDR_ALLOW_EVENTS;
	case 'v': return SIP_HDR_VIA;
	case 'x': return SIP_HDR_SESSION_EXPIRES;
	case 'y': return SIP_HDR_IDENTITY;
	default:  return SIP_HDR_NONE;
	}
}


static inline bool hdr_comma_separated(enum sip_hdrid id)
{
	switch (id) {

	case SIP_HDR_ACCEPT:
	case SIP_HDR_ACCEPT_CONTACT:
	case SIP_HDR_ACCEPT_ENCODING:
	case SIP_HDR_ACCEPT_LANGUAGE:
	case SIP_HDR_ACCEPT_RESOURCE_PRIORITY:
	case SIP_HDR_ALERT_INFO:
	case SIP_HDR_ALLOW:
	case SIP_HDR_ALLOW_EVENTS:
	case SIP_HDR_AUTHENTICATION_INFO:
	case SIP_HDR_CALL_INFO:
	case SIP_HDR_CONTACT:
	case SIP_HDR_CONTENT_ENCODING:
	case SIP_HDR_CONTENT_LANGUAGE:
	case SIP_HDR_ERROR_INFO:
	case SIP_HDR_HISTORY_INFO:
	case SIP_HDR_IN_REPLY_TO:
	case SIP_HDR_P_ASSERTED_IDENTITY:
	case SIP_HDR_P_ASSOCIATED_URI:
	case SIP_HDR_P_EARLY_MEDIA:
	case SIP_HDR_P_MEDIA_AUTHORIZATION:
	case SIP_HDR_P_PREFERRED_IDENTITY:
	case SIP_HDR_P_REFUSED_URI_LIST:
	case SIP_HDR_P_VISITED_NETWORK_ID:
	case SIP_HDR_PATH:
	case SIP_HDR_PERMISSION_MISSING:
	case SIP_HDR_PROXY_REQUIRE:
	case SIP_HDR_REASON:
	case SIP_HDR_RECORD_ROUTE:
	case SIP_HDR_REJECT_CONTACT:
	case SIP_HDR_REQUEST_DISPOSITION:
	case SIP_HDR_REQUIRE:
	case SIP_HDR_RESOURCE_PRIORITY:
	case SIP_HDR_ROUTE:
	case SIP_HDR_SECURITY_CLIENT:
	case SIP_HDR_SECURITY_SERVER:
	case SIP_HDR_SECURITY_VERIFY:
	case SIP_HDR_SERVICE_ROUTE:
	case SIP_HDR_SUPPORTED:
	case SIP_HDR_TRIGGER_CONSENT:
	case SIP_HDR_UNSUPPORTED:
	case SIP_HDR_VIA:
	case SIP_HDR_WARNING:
		return true;

	default:
		return false;
	}
}


static inline int hdr_add(struct sip_msg *msg, const struct pl *name,
			  enum sip_hdrid id, const char *p, ssize_t l,
			  bool atomic, bool line)
{
	struct sip_hdr *hdr;
	int err = 0;

	hdr = mem_zalloc(sizeof(*hdr), hdr_destructor);
	if (!hdr)
		return ENOMEM;

	hdr->name  = *name;
	hdr->val.p = p;
	hdr->val.l = MAX(l, 0);
	hdr->id    = id;

	switch (id) {

	case SIP_HDR_VIA:
	case SIP_HDR_ROUTE:
		if (!atomic)
			break;

		hash_append(msg->hdrht, id, &hdr->he, mem_ref(hdr));
		list_append(&msg->hdrl, &hdr->le, mem_ref(hdr));
		break;

	default:
		if (atomic)
			hash_append(msg->hdrht, id, &hdr->he, mem_ref(hdr));
		if (line)
			list_append(&msg->hdrl, &hdr->le, mem_ref(hdr));
		break;
	}

	/* parse common headers */
	switch (id) {

	case SIP_HDR_VIA:
		if (!atomic || pl_isset(&msg->via.sentby))
			break;

		err = sip_via_decode(&msg->via, &hdr->val);
		break;

	case SIP_HDR_TO:
		err = sip_addr_decode((struct sip_addr *)&msg->to, &hdr->val);
		if (err)
			break;

		(void)sip_param_decode(&msg->to.params, "tag", &msg->to.tag);
		msg->to.val = hdr->val;
		break;

	case SIP_HDR_FROM:
		err = sip_addr_decode((struct sip_addr *)&msg->from,
					  &hdr->val);
		if (err)
			break;

		(void)sip_param_decode(&msg->from.params, "tag",
					   &msg->from.tag);
		msg->from.val = hdr->val;
		break;

	case SIP_HDR_CALL_ID:
		msg->callid = hdr->val;
		break;

	case SIP_HDR_CSEQ:
		err = sip_cseq_decode(&msg->cseq, &hdr->val);
		break;

	case SIP_HDR_MAX_FORWARDS:
		msg->maxfwd = hdr->val;
		break;

	case SIP_HDR_CONTENT_TYPE:
		msg->ctype = hdr->val;
		break;

	case SIP_HDR_CONTENT_LENGTH:
		msg->clen = hdr->val;
		break;

	case SIP_HDR_EXPIRES:
		msg->expires = hdr->val;
		break;

	case SIP_HDR_CALL_INFO:
		err = sip_call_info_decode(&msg->call_info, &hdr->val);
		break;

	case SIP_HDR_ALERT_INFO:
		err = sip_alert_info_decode(&msg->alert_info, &hdr->val);
		break;

	case SIP_HDR_ACCESS_URL:
		err = sip_access_url_decode(name, &msg->access_url, &hdr->val);
		break;

	case SIP_HDR_P_ASSERTED_IDENTITY:
		err = sip_addr_decode((struct sip_addr *)&msg->p_asserted_identity, &hdr->val);
		if (err == 0) {
			msg->p_asserted_identity_present = true;
			msg->p_asserted_identity.val = hdr->val;
		} else {
			DEBUG_WARNING("Failed to decode P-Asserted-Identity value (ignored)\n");
		}
		break;

	default:
		/* re_printf("%r = %u\n", &hdr->name, id); */
		break;
	}

	mem_deref(hdr);

	if (err == ENOENT) {
		/* this would look better than ENOENT returned by regexp search */
		err = EBADMSG;
    }
	return err;
}


/**
 * Decode a SIP message
 *
 * @param msgp Pointer to allocated SIP Message
 * @param mb   Buffer containing SIP Message
 *
 * @return 0 if success, otherwise errorcode
 */
int sip_msg_decode(struct sip_msg **msgp, struct mbuf *mb)
{
	struct pl x, y, z, e, name;
	const char *p, *v, *cv;
	struct sip_msg *msg;
	bool comsep, quote;
	enum sip_hdrid id = SIP_HDR_NONE;
	uint32_t ws, lf;
	size_t l;
	int err;

	if (!msgp || !mb)
		return EINVAL;

	p = (const char *)mbuf_buf(mb);
	l = mbuf_get_left(mb);

	if (re_regex(p, l, "[^ \t\r\n]+ [^ \t\r\n]+ [^\r\n]*[\r]*[\n]1",
		     &x, &y, &z, NULL, &e) || x.p != (char *)mbuf_buf(mb))
		return (l > STARTLINE_MAX) ? EBADMSG : ENODATA;

	msg = mem_zalloc(sizeof(*msg), destructor);
	if (!msg)
		return ENOMEM;

	msg->mb_msg = *mb;

	err = hash_alloc(&msg->hdrht, HDR_HASH_SIZE);
	if (err)
		goto out;

	msg->tag = rand_u64();
	msg->mb  = mem_ref(mb);
	msg->req = (0 == pl_strcmp(&z, "SIP/2.0"));
	msg->call_info.answer_after = -1;

	if (msg->req) {

		msg->met = x;
		msg->ruri = y;
		msg->ver = z;

		if (uri_decode(&msg->uri, &y)) {
			err = EBADMSG;
			goto out;
		}
	}
	else {
		msg->ver    = x;
		msg->scode  = pl_u32(&y);
		msg->reason = z;

		if (!msg->scode) {
			err = EBADMSG;
			goto out;
		}
	}

	l -= e.p + e.l - p;
	p = e.p + e.l;

	name.p = v = cv = NULL;
	name.l = ws = lf = 0;
	comsep = false;
	quote = false;

	for (; l > 0; p++, l--) {

		switch (*p) {

		case ' ':
		case '\t':
			lf = 0; /* folding */
			++ws;
			break;

		case '\r':
			++ws;
			break;

		case '\n':
			++ws;

			if (!lf++)
				break;

			++p; --l; /* eoh */

			/*@fallthrough@*/

		default:
			if (lf || (*p == ',' && comsep && !quote)) {

				if (!name.l) {
					err = EBADMSG;
					goto out;
				}

				err = hdr_add(msg, &name, id, cv ? cv : p,
					      cv ? p - cv - ws : 0,
					      true, cv == v && lf);
				if (err)
					goto out;

				if (!lf) { /* comma separated */
					cv = NULL;
					break;
				}

				if (cv != v) {
					err = hdr_add(msg, &name, id,
						      v ? v : p,
						      v ? p - v - ws : 0,
						      false, true);
					if (err)
						goto out;
				}

				if (lf > 1) { /* eoh */
					err = 0;
					goto out;
				}

				comsep = false;
				name.p = NULL;
				cv = v = NULL;
				lf = 0;
			}

			if (!name.p) {
				name.p = p;
				name.l = 0;
				ws = 0;
			}

			if (!name.l) {
				if (*p != ':') {
					ws = 0;
					break;
				}

				name.l = MAX((int)(p - name.p - ws), 0);
				if (!name.l) {
					err = EBADMSG;
					goto out;
				}

				id = hdr_hash(&name);
				comsep = hdr_comma_separated(id);
				break;
			}

			if (!cv) {
				quote = false;
				cv = p;
			}

			if (!v) {
				v = p;
			}

			if (*p == '"')
				quote = !quote;

			ws = 0;
			break;
		}
	}

	err = ENODATA;

 out:
	if (err)
		mem_deref(msg);
	else {
		*msgp = msg;
		mb->pos = mb->end - l;
		msg->mb_msg.end = mb->pos;
	}

	return err;
}


/**
 * Get a SIP Header from a SIP Message
 *
 * @param msg SIP Message
 * @param id  SIP Header ID
 *
 * @return SIP Header if found, NULL if not found
 */
const struct sip_hdr *sip_msg_hdr(const struct sip_msg *msg, enum sip_hdrid id)
{
	return sip_msg_hdr_apply(msg, true, id, NULL, NULL);
}


/**
 * Apply a function handler to certain SIP Headers
 *
 * @param msg SIP Message
 * @param fwd True to traverse forwards, false to traverse backwards
 * @param id  SIP Header ID
 * @param h   Function handler
 * @param arg Handler argument
 *
 * @return SIP Header if handler returns true, otherwise NULL
 */
const struct sip_hdr *sip_msg_hdr_apply(const struct sip_msg *msg,
					bool fwd, enum sip_hdrid id,
					sip_hdr_h *h, void *arg)
{
	struct list *lst;
	struct le *le;

	if (!msg)
		return NULL;

	lst = hash_list(msg->hdrht, id);

	le = fwd ? list_head(lst) : list_tail(lst);

	while (le) {
		const struct sip_hdr *hdr = le->data;

		le = fwd ? le->next : le->prev;

		if (hdr->id != id)
			continue;

		if (!h || h(hdr, msg, arg))
			return hdr;
	}

	return NULL;
}


/**
 * Get an unknown SIP Header from a SIP Message
 *
 * @param msg  SIP Message
 * @param name Header name
 *
 * @return SIP Header if found, NULL if not found
 */
const struct sip_hdr *sip_msg_xhdr(const struct sip_msg *msg, const char *name)
{
	return sip_msg_xhdr_apply(msg, true, name, NULL, NULL);
}


/**
 * Apply a function handler to certain unknown SIP Headers
 *
 * @param msg  SIP Message
 * @param fwd  True to traverse forwards, false to traverse backwards
 * @param name SIP Header name
 * @param h    Function handler
 * @param arg  Handler argument
 *
 * @return SIP Header if handler returns true, otherwise NULL
 */
const struct sip_hdr *sip_msg_xhdr_apply(const struct sip_msg *msg,
					 bool fwd, const char *name,
					 sip_hdr_h *h, void *arg)
{
	struct list *lst;
	struct le *le;
	struct pl pl;

	if (!msg || !name)
		return NULL;

	pl_set_str(&pl, name);

	lst = hash_list(msg->hdrht, hdr_hash(&pl));

	le = fwd ? list_head(lst) : list_tail(lst);

	while (le) {
		const struct sip_hdr *hdr = le->data;

		le = fwd ? le->next : le->prev;

		if (pl_casecmp(&hdr->name, &pl))
			continue;

		if (!h || h(hdr, msg, arg))
			return hdr;
	}

	return NULL;
}


static bool count_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
			  void *arg)
{
	uint32_t *n = arg;
	(void)hdr;
	(void)msg;

	++(*n);

	return false;
}


/**
 * Count the number of SIP Headers
 *
 * @param msg SIP Message
 * @param id  SIP Header ID
 *
 * @return Number of SIP Headers
 */
uint32_t sip_msg_hdr_count(const struct sip_msg *msg, enum sip_hdrid id)
{
	uint32_t n = 0;

	sip_msg_hdr_apply(msg, true, id, count_handler, &n);

	return n;
}


/**
 * Count the number of unknown SIP Headers
 *
 * @param msg  SIP Message
 * @param name SIP Header name
 *
 * @return Number of SIP Headers
 */
uint32_t sip_msg_xhdr_count(const struct sip_msg *msg, const char *name)
{
	uint32_t n = 0;

	sip_msg_xhdr_apply(msg, true, name, count_handler, &n);

	return n;
}


static bool value_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
			  void *arg)
{
	(void)msg;

	return 0 == pl_strcasecmp(&hdr->val, (const char *)arg);
}


/**
 * Check if a SIP Header matches a certain value
 *
 * @param msg   SIP Message
 * @param id    SIP Header ID
 * @param value Header value to check
 *
 * @return True if value matches, false if not
 */
bool sip_msg_hdr_has_value(const struct sip_msg *msg, enum sip_hdrid id,
			   const char *value)
{
	return NULL != sip_msg_hdr_apply(msg, true, id, value_handler,
					 (void *)value);
}


/**
 * Check if an unknown SIP Header matches a certain value
 *
 * @param msg   SIP Message
 * @param name  SIP Header name
 * @param value Header value to check
 *
 * @return True if value matches, false if not
 */
bool sip_msg_xhdr_has_value(const struct sip_msg *msg, const char *name,
			    const char *value)
{
	return NULL != sip_msg_xhdr_apply(msg, true, name, value_handler,
					  (void *)value);
}


/**
 * Print a SIP Message to stdout
 *
 * @param msg SIP Message
 */
void sip_msg_dump(const struct sip_msg *msg)
{
	struct le *le;
	uint32_t i;

	if (!msg)
		return;

	for (i=0; i<HDR_HASH_SIZE; i++) {

		le = list_head(hash_list(msg->hdrht, i));

		while (le) {
			const struct sip_hdr *hdr = le->data;

			le = le->next;

			(void)re_printf("%02u '%r'='%r'\n", i, &hdr->name,
					&hdr->val);
		}
	}

	le = list_head(&msg->hdrl);

	while (le) {
		const struct sip_hdr *hdr = le->data;

		le = le->next;

		(void)re_printf("%02u '%r'='%r'\n", hdr->id, &hdr->name,
				&hdr->val);
	}
}
