/*
 * Copyright (C) 2012 Reto Guadagnini
 *
 * 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 "ipseckey.h"

#include <library.h>
#include <utils/debug.h>
#include <bio/bio_reader.h>

typedef struct private_ipseckey_t private_ipseckey_t;

/**
* private data of the ipseckey
*/
struct private_ipseckey_t {

	/**
	 * public functions
	 */
	ipseckey_t public;

	/**
	 * Precedence
	 */
	uint8_t precedence;

	/**
	 * Gateway type
	 */
	uint8_t gateway_type;

	/**
	 * Algorithm
	 */
	uint8_t algorithm;

	/**
	 * Gateway
	 */
	chunk_t gateway;

	/**
	 * Public key
	 */
	chunk_t public_key;
};

METHOD(ipseckey_t, get_precedence, uint8_t,
	private_ipseckey_t *this)
{
	return this->precedence;
}

METHOD(ipseckey_t, get_gateway_type, ipseckey_gw_type_t,
	private_ipseckey_t *this)
{
	return this->gateway_type;
}

METHOD(ipseckey_t, get_algorithm, ipseckey_algorithm_t,
	private_ipseckey_t *this)
{
	return this->algorithm;
}

METHOD(ipseckey_t, get_gateway, chunk_t,
	private_ipseckey_t *this)
{
	return this->gateway;
}

METHOD(ipseckey_t, get_public_key, chunk_t,
	private_ipseckey_t *this)
{
	return this->public_key;
}

METHOD(ipseckey_t, destroy, void,
	private_ipseckey_t *this)
{
	chunk_free(&this->gateway);
	chunk_free(&this->public_key);
	free(this);
}

/*
 * See header
 */
ipseckey_t *ipseckey_create_frm_rr(rr_t *rr)
{
	private_ipseckey_t *this;
	bio_reader_t *reader = NULL;
	uint8_t label;
	chunk_t tmp;

	INIT(this,
			.public = {
				.get_precedence = _get_precedence,
				.get_gateway_type = _get_gateway_type,
				.get_algorithm = _get_algorithm,
				.get_gateway = _get_gateway,
				.get_public_key = _get_public_key,
				.destroy = _destroy,
			},
	);

	if (rr->get_type(rr) != RR_TYPE_IPSECKEY)
	{
		DBG1(DBG_CFG, "unable to create an ipseckey out of an RR "
					  "whose type is not IPSECKEY");
		free(this);
		return NULL;
	}

	/** Parse the content (RDATA field) of the RR */
	reader = bio_reader_create(rr->get_rdata(rr));
	if (!reader->read_uint8(reader, &this->precedence) ||
		!reader->read_uint8(reader, &this->gateway_type) ||
		!reader->read_uint8(reader, &this->algorithm))
	{
		DBG1(DBG_CFG, "ipseckey RR has a wrong format");
		reader->destroy(reader);
		free(this);
		return NULL;
	}

	switch (this->gateway_type)
	{
		case IPSECKEY_GW_TP_NOT_PRESENT:
			break;

		case IPSECKEY_GW_TP_IPV4:
			if (!reader->read_data(reader, 4, &this->gateway))
			{
				DBG1(DBG_CFG, "ipseckey gateway field does not contain an "
							  "IPv4 address as expected");
				reader->destroy(reader);
				free(this);
				return NULL;
			}
			this->gateway = chunk_clone(this->gateway);
			break;

		case IPSECKEY_GW_TP_IPV6:
			if (!reader->read_data(reader, 16, &this->gateway))
			{
				DBG1(DBG_CFG, "ipseckey gateway field does not contain an "
							  "IPv6 address as expected");
				reader->destroy(reader);
				free(this);
				return NULL;
			}
			this->gateway = chunk_clone(this->gateway);
			break;

		case IPSECKEY_GW_TP_WR_ENC_DNAME:
			/**
			 * Uncompressed domain name as defined in RFC 1035 chapter 3.
			 *
			 * TODO: Currently we ignore wire encoded domain names.
			 *
			 */
			while (reader->read_uint8(reader, &label) &&
				   label != 0 && label < 192)
			{
				if (!reader->read_data(reader, label, &tmp))
				{
					DBG1(DBG_CFG, "wrong wire encoded domain name format "
								  "in ipseckey gateway field");
					reader->destroy(reader);
					free(this);
					return NULL;
				}
			}
			break;

		default:
			DBG1(DBG_CFG, "unable to parse ipseckey gateway field");
			reader->destroy(reader);
			free(this);
			return NULL;
	}

	if (!reader->read_data(reader, reader->remaining(reader),
						   &this->public_key))
	{
		DBG1(DBG_CFG, "failed to read ipseckey public key field");
		reader->destroy(reader);
		chunk_free(&this->gateway);
		free(this);
		return NULL;
	}
	this->public_key = chunk_clone(this->public_key);
	reader->destroy(reader);
	return &this->public;
}
