/*
 * Copyright (C) 2007 Tobias Brunner
 *
 * Copyright (C) secunet Security Networks AG
 *
 * This program 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.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program 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.
 */

#include "endpoint_notify.h"

#include <math.h>

#include <daemon.h>

typedef struct private_endpoint_notify_t private_endpoint_notify_t;

/**
 * Private data of an notify_payload_t object.
 */
struct private_endpoint_notify_t {
	/**
	 * Public endpoint_notify_t interface.
	 */
	endpoint_notify_t public;

	/**
	 * Priority
	 */
	uint32_t priority;

	/**
	 * Family
	 */
	me_endpoint_family_t family;

	/**
	 * Endpoint type
	 */
	me_endpoint_type_t type;

	/**
	 * Endpoint
	 */
	host_t *endpoint;

	/**
	 * Base (used for server reflexive endpoints)
	 */
	host_t *base;
};

/*    Notification data:
                           1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      !                           Priority                            !
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      !     Family    !      Type     !              Port             !
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      !                       IP Address (variable)                   !
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/

ENUM(me_endpoint_type_names, HOST, RELAYED,
	"HOST",
	"PEER_REFLEXIVE",
	"SERVER_REFLEXIVE",
	"RELAYED"
);

/**
 * Forward declaration
 */
static private_endpoint_notify_t *endpoint_notify_create();

/**
 * Helper functions to parse integer values
 */
static status_t parse_uint8(uint8_t **cur, uint8_t *top, uint8_t *val)
{
	if (*cur + sizeof(uint8_t) > top)
	{
		return FAILED;
	}
	*val =  *(uint8_t*)*cur;
	*cur += sizeof(uint8_t);
	return SUCCESS;
}

static status_t parse_uint16(uint8_t **cur, uint8_t *top, uint16_t *val)
{
	if (*cur + sizeof(uint16_t) > top)
	{
		return FAILED;
	}
	*val =  ntohs(*(uint16_t*)*cur);
	*cur += sizeof(uint16_t);
	return SUCCESS;
}

static status_t parse_uint32(uint8_t **cur, uint8_t *top, uint32_t *val)
{
	if (*cur + sizeof(uint32_t) > top)
	{
		return FAILED;
	}
	*val =  ntohl(*(uint32_t*)*cur);
	*cur += sizeof(uint32_t);
	return SUCCESS;
}

/**
 * Parses the notification data of a ME_ENDPOINT notify
 */
static status_t parse_notification_data(private_endpoint_notify_t *this, chunk_t data)
{
	uint8_t family, type, addr_family;
	uint16_t port;
	chunk_t addr;
	uint8_t *cur = data.ptr;
	uint8_t *top = data.ptr + data.len;

	DBG3(DBG_IKE, "me_endpoint_data %B", &data);

	if (parse_uint32(&cur, top, &this->priority) != SUCCESS)
	{
		DBG1(DBG_IKE, "failed to parse ME_ENDPOINT: invalid priority");
		return FAILED;
	}

	if (parse_uint8(&cur, top, &family) != SUCCESS || family >= MAX_FAMILY)
	{
		DBG1(DBG_IKE, "failed to parse ME_ENDPOINT: invalid family");
		return FAILED;
	}
	this->family = (me_endpoint_family_t)family;

	if (parse_uint8(&cur, top, &type) != SUCCESS ||
		type == NO_TYPE || type >= MAX_TYPE)
	{
		DBG1(DBG_IKE, "failed to parse ME_ENDPOINT: invalid type");
		return FAILED;
	}
	this->type = (me_endpoint_type_t)type;

	addr_family = AF_INET;
	addr.len = 4;

	switch(this->family)
	{
		case IPv6:
			addr_family = AF_INET6;
			addr.len = 16;
			/* fall-through */
		case IPv4:
			if (parse_uint16(&cur, top, &port) != SUCCESS)
			{
				DBG1(DBG_IKE, "failed to parse ME_ENDPOINT: invalid port");
				return FAILED;
			}

			if (cur + addr.len > top)
			{
				DBG1(DBG_IKE, "failed to parse ME_ENDPOINT: invalid IP address");
				return FAILED;
			}

			addr.ptr = cur;
			this->endpoint = host_create_from_chunk(addr_family, addr, port);
			break;
		case NO_FAMILY:
		default:
			this->endpoint = NULL;
			break;
	}
	return SUCCESS;
}


/**
 * Generates the notification data of a ME_ENDPOINT notify
 */
static chunk_t build_notification_data(private_endpoint_notify_t *this)
{
	chunk_t prio_chunk, family_chunk, type_chunk, port_chunk, addr_chunk;
	chunk_t data;
	uint32_t prio;
	uint16_t port;
	uint8_t family, type;

	prio = htonl(this->priority);
	prio_chunk = chunk_from_thing(prio);
	family = this->family;
	family_chunk = chunk_from_thing(family);
	type = this->type;
	type_chunk = chunk_from_thing(type);

	if (this->endpoint)
	{
		port = htons(this->endpoint->get_port(this->endpoint));
		addr_chunk = this->endpoint->get_address(this->endpoint);
	}
	else
	{
		port = 0;
		addr_chunk = chunk_empty;
	}
	port_chunk = chunk_from_thing(port);

	/* data = prio | family | type | port | addr */
	data = chunk_cat("ccccc", prio_chunk, family_chunk, type_chunk,
					 port_chunk, addr_chunk);
	DBG3(DBG_IKE, "me_endpoint_data %B", &data);
	return data;
}

METHOD(endpoint_notify_t, build_notify, notify_payload_t*,
	private_endpoint_notify_t *this)
{
	chunk_t data;
	notify_payload_t *notify;

	notify = notify_payload_create(PLV2_NOTIFY);
	notify->set_notify_type(notify, ME_ENDPOINT);
	data = build_notification_data(this);
	notify->set_notification_data(notify, data);
	chunk_free(&data);

	return notify;
}


METHOD(endpoint_notify_t, get_priority, uint32_t,
	private_endpoint_notify_t *this)
{
	return this->priority;
}

METHOD(endpoint_notify_t, set_priority, void,
	private_endpoint_notify_t *this, uint32_t priority)
{
	this->priority = priority;
}

METHOD(endpoint_notify_t, get_type,  me_endpoint_type_t,
	private_endpoint_notify_t *this)
{
	return this->type;
}

METHOD(endpoint_notify_t, get_family, me_endpoint_family_t,
	private_endpoint_notify_t *this)
{
	return this->family;
}

METHOD(endpoint_notify_t, get_host, host_t*,
	private_endpoint_notify_t *this)
{
	return this->endpoint;
}

METHOD(endpoint_notify_t, get_base, host_t*,
	private_endpoint_notify_t *this)
{
	return (!this->base) ? this->endpoint : this->base;
}

METHOD(endpoint_notify_t, clone_, endpoint_notify_t*,
	private_endpoint_notify_t *this)
{
	private_endpoint_notify_t *clone;

	clone = endpoint_notify_create();
	clone->priority = this->priority;
	clone->type = this->type;
	clone->family = this->family;

	if (this->endpoint)
	{
		clone->endpoint = this->endpoint->clone(this->endpoint);
	}

	if (this->base)
	{
		clone->base = this->base->clone(this->base);
	}

	return &clone->public;
}

METHOD(endpoint_notify_t, destroy, void,
	private_endpoint_notify_t *this)
{
	DESTROY_IF(this->endpoint);
	DESTROY_IF(this->base);
	free(this);
}

/**
 * Creates an empty endpoint notify
 */
static private_endpoint_notify_t *endpoint_notify_create()
{
	private_endpoint_notify_t *this;

	INIT(this,
		.public = {
			.get_priority = _get_priority,
			.set_priority = _set_priority,
			.get_type = _get_type,
			.get_family = _get_family,
			.get_host = _get_host,
			.get_base = _get_base,
			.build_notify = _build_notify,
			.clone = _clone_,
			.destroy = _destroy,
		},
		.family = NO_FAMILY,
		.type = NO_TYPE,
	);

	return this;
}

/**
 * Described in header
 */
endpoint_notify_t *endpoint_notify_create_from_host(me_endpoint_type_t type,
													host_t *host, host_t *base)
{
	private_endpoint_notify_t *this = endpoint_notify_create();
	this->type = type;

	switch(type)
	{
		case HOST:
			this->priority = pow(2, 16) * ME_PRIO_HOST;
			break;
		case PEER_REFLEXIVE:
			this->priority = pow(2, 16) * ME_PRIO_PEER;
			break;
		case SERVER_REFLEXIVE:
			this->priority = pow(2, 16) * ME_PRIO_SERVER;
			break;
		case RELAYED:
		default:
			this->priority = pow(2, 16) * ME_PRIO_RELAY;
			break;
	}

	/* FIXME: if there is more than one ip address we should vary this priority */
	this->priority += 65535;

	if (!host)
	{
		return &this->public;
	}

	switch(host->get_family(host))
	{
		case AF_INET:
			this->family = IPv4;
			break;
		case AF_INET6:
			this->family = IPv6;
			break;
		default:
			/* unsupported family type, we do not set the host
			 * (family is set to NO_FAMILY) */
			return &this->public;
	}

	this->endpoint = host->clone(host);

	if (base)
	{
		this->base = base->clone(base);
	}

	return &this->public;
}

/**
 * Described in header
 */
endpoint_notify_t *endpoint_notify_create_from_payload(notify_payload_t *notify)
{
	private_endpoint_notify_t *this;
	chunk_t data;

	if (notify->get_notify_type(notify) != ME_ENDPOINT)
	{
		return NULL;
	}

	this = endpoint_notify_create();
	data = notify->get_notification_data(notify);

	if (parse_notification_data(this, data) != SUCCESS)
	{
		destroy(this);
		return NULL;
	}
	return &this->public;
}
