/*
 *
 *  oFono - Open Source Telephony
 *
 *  Copyright (C) 2009-2011  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  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.
 *
 *  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 St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <errno.h>
#include <string.h>

#include <glib.h>

#include "gatppp.h"
#include "ppp.h"
#include "missing.h"

#define IPV6CP_SUPPORTED_CODES	((1 << PPPCP_CODE_TYPE_CONFIGURE_REQUEST) | \
				(1 << PPPCP_CODE_TYPE_CONFIGURE_ACK) | \
				(1 << PPPCP_CODE_TYPE_CONFIGURE_NAK) | \
				(1 << PPPCP_CODE_TYPE_CONFIGURE_REJECT) | \
				(1 << PPPCP_CODE_TYPE_TERMINATE_REQUEST) | \
				(1 << PPPCP_CODE_TYPE_TERMINATE_ACK) | \
				(1 << PPPCP_CODE_TYPE_CODE_REJECT))

#define OPTION_COPY(_options, _len, _req, _type, _var, _opt_len)	\
	if (_req) {							\
		_options[_len] = _type;					\
		_options[_len + 1] = _opt_len + 2;			\
		memcpy(_options + _len + 2, _var, _opt_len);		\
		_len += _opt_len + 2;					\
	}

/* We request only IPv6 Interface Id */
#define IPV6CP_MAX_CONFIG_OPTION_SIZE	10
#define IPV6CP_MAX_FAILURE		3
#define IPV6CP_ERROR ipv6cp_error_quark()

enum ipv6cp_option_types {
	IPV6CP_INTERFACE_ID = 1,
};

struct ipv6cp_data {
	guint8 options[IPV6CP_MAX_CONFIG_OPTION_SIZE];
	guint16 options_len;
	guint8 req_options;
	guint64 local_addr;
	guint64 peer_addr;
	gboolean is_server;
};

static GQuark ipv6cp_error_quark(void)
{
	return g_quark_from_static_string("ipv6cp");
}

static void ipv6cp_generate_config_options(struct ipv6cp_data *ipv6cp)
{
	guint16 len = 0;

	OPTION_COPY(ipv6cp->options, len,
			ipv6cp->req_options & IPV6CP_INTERFACE_ID,
			IPV6CP_INTERFACE_ID, &ipv6cp->local_addr,
			sizeof(ipv6cp->local_addr));

	ipv6cp->options_len = len;
}

static void ipv6cp_reset_config_options(struct ipv6cp_data *ipv6cp)
{
	ipv6cp->req_options = IPV6CP_INTERFACE_ID;

	ipv6cp_generate_config_options(ipv6cp);
}

static void ipv6cp_up(struct pppcp_data *pppcp)
{

}

static void ipv6cp_down(struct pppcp_data *pppcp)
{
	struct ipv6cp_data *ipv6cp = pppcp_get_data(pppcp);

	ipv6cp_reset_config_options(ipv6cp);

	pppcp_set_local_options(pppcp, ipv6cp->options, ipv6cp->options_len);
}

static void ipv6cp_finished(struct pppcp_data *pppcp)
{

}

static enum rcr_result ipv6cp_server_rcr(struct ipv6cp_data *ipv6cp,
					const struct pppcp_packet *packet,
					guint8 **new_options, guint16 *new_len)
{
	struct ppp_option_iter iter;
	guint8 nak_options[IPV6CP_MAX_CONFIG_OPTION_SIZE];
	guint16 len = 0;
	guint8 *rej_options = NULL;
	guint16 rej_len = 0;
	guint64 addr;

	ppp_option_iter_init(&iter, packet);

	while (ppp_option_iter_next(&iter) == TRUE) {
		guint8 type = ppp_option_iter_get_type(&iter);
		const void *data = ppp_option_iter_get_data(&iter);

		switch (type) {
		case IPV6CP_INTERFACE_ID:
			memcpy(&addr, data, sizeof(addr));

			OPTION_COPY(nak_options, len,
					addr != ipv6cp->peer_addr || addr == 0,
					type, &ipv6cp->peer_addr,
					ppp_option_iter_get_length(&iter));
			break;
		default:
			if (rej_options == NULL) {
				guint16 max_len = ntohs(packet->length) - 4;
				rej_options = g_new0(guint8, max_len);
			}

			OPTION_COPY(rej_options, rej_len, rej_options != NULL,
					type, data,
					ppp_option_iter_get_length(&iter));
			break;
		}
	}

	if (rej_len > 0) {
		*new_len = rej_len;
		*new_options = rej_options;

		return RCR_REJECT;
	}

	if (len > 0) {
		*new_len = len;
		*new_options = g_memdup2(nak_options, len);

		return RCR_NAK;
	}

	return RCR_ACCEPT;
}

static enum rcr_result ipv6cp_client_rcr(struct ipv6cp_data *ipv6cp,
					const struct pppcp_packet *packet,
					guint8 **new_options, guint16 *new_len)
{
	struct ppp_option_iter iter;
	guint8 *options = NULL;
	guint8 len = 0;

	ppp_option_iter_init(&iter, packet);

	while (ppp_option_iter_next(&iter) == TRUE) {
		guint8 type = ppp_option_iter_get_type(&iter);
		const void *data = ppp_option_iter_get_data(&iter);

		switch (type) {
		case IPV6CP_INTERFACE_ID:
			memcpy(&ipv6cp->peer_addr, data,
					sizeof(ipv6cp->peer_addr));

			if (ipv6cp->peer_addr != 0)
				break;

			/*
			 * Reject zero Interface ID
			 */

			/* fall through */
		default:
			if (options == NULL) {
				guint16 max_len = ntohs(packet->length) - 4;
				options = g_new0(guint8, max_len);
			}

			OPTION_COPY(options, len, options != NULL,
					type, data,
					ppp_option_iter_get_length(&iter));
			break;
		}
	}

	if (len > 0) {
		*new_len = len;
		*new_options = options;

		return RCR_REJECT;
	}

	return RCR_ACCEPT;
}

static enum rcr_result ipv6cp_rcr(struct pppcp_data *pppcp,
					const struct pppcp_packet *packet,
					guint8 **new_options, guint16 *new_len)
{
	struct ipv6cp_data *ipv6cp = pppcp_get_data(pppcp);

	if (ipv6cp->is_server)
		return ipv6cp_server_rcr(ipv6cp, packet, new_options, new_len);
	else
		return ipv6cp_client_rcr(ipv6cp, packet, new_options, new_len);
}

static void ipv6cp_rca(struct pppcp_data *pppcp,
					const struct pppcp_packet *packet)
{
	struct ipv6cp_data *ipv6cp = pppcp_get_data(pppcp);
	struct ppp_option_iter iter;

	if (ipv6cp->is_server)
		return;

	ppp_option_iter_init(&iter, packet);

	while (ppp_option_iter_next(&iter) == TRUE) {
		const guint8 *data = ppp_option_iter_get_data(&iter);

		switch (ppp_option_iter_get_type(&iter)) {
		case IPV6CP_INTERFACE_ID:
			memcpy(&ipv6cp->local_addr, data,
					sizeof(ipv6cp->local_addr));
			break;
		default:
			break;
		}
	}
}

static void ipv6cp_rcn_nak(struct pppcp_data *pppcp,
				const struct pppcp_packet *packet)
{
	struct ipv6cp_data *ipv6cp = pppcp_get_data(pppcp);
	struct ppp_option_iter iter;

	if (ipv6cp->is_server)
		return;

	ppp_option_iter_init(&iter, packet);

	while (ppp_option_iter_next(&iter) == TRUE) {
		const guint8 *data = ppp_option_iter_get_data(&iter);

		switch (ppp_option_iter_get_type(&iter)) {
		case IPV6CP_INTERFACE_ID:
			ipv6cp->req_options |= IPV6CP_INTERFACE_ID;
			memcpy(&ipv6cp->local_addr, data,
				sizeof(ipv6cp->local_addr));
			break;
		default:
			break;
		}
	}

	ipv6cp_generate_config_options(ipv6cp);
	pppcp_set_local_options(pppcp, ipv6cp->options, ipv6cp->options_len);
}

static void ipv6cp_rcn_rej(struct pppcp_data *pppcp,
				const struct pppcp_packet *packet)
{
	struct ipv6cp_data *ipv6cp = pppcp_get_data(pppcp);
	struct ppp_option_iter iter;

	ppp_option_iter_init(&iter, packet);

	while (ppp_option_iter_next(&iter) == TRUE) {
		switch (ppp_option_iter_get_type(&iter)) {
		case IPV6CP_INTERFACE_ID:
			ipv6cp->req_options &= ~IPV6CP_INTERFACE_ID;
			break;
		default:
			break;
		}
	}

	ipv6cp_generate_config_options(ipv6cp);
	pppcp_set_local_options(pppcp, ipv6cp->options, ipv6cp->options_len);
}

struct pppcp_proto ipv6cp_proto = {
	.proto			= IPV6CP_PROTO,
	.name			= "ipv6cp",
	.supported_codes	= IPV6CP_SUPPORTED_CODES,
	.this_layer_up		= ipv6cp_up,
	.this_layer_down	= ipv6cp_down,
	.this_layer_finished	= ipv6cp_finished,
	.rca			= ipv6cp_rca,
	.rcn_nak		= ipv6cp_rcn_nak,
	.rcn_rej		= ipv6cp_rcn_rej,
	.rcr			= ipv6cp_rcr,
};

struct pppcp_data *ipv6cp_new(GAtPPP *ppp, gboolean is_server,
					const char *local, const char *peer,
					GError **error)
{
	struct ipv6cp_data *ipv6cp;
	struct pppcp_data *pppcp;
	struct in6_addr local_addr;
	struct in6_addr peer_addr;

	if (local == NULL)
		memset(&local_addr, 0, sizeof(local_addr));
	else if (inet_pton(AF_INET6, local, &local_addr) != 1) {
		g_set_error(error, IPV6CP_ERROR, errno,
				"Unable to set local Interface ID: %s",
				strerror(errno));
		return NULL;
	}

	if (peer == NULL)
		memset(&peer_addr, 0, sizeof(peer_addr));
	else if (inet_pton(AF_INET6, peer, &peer_addr) != 1) {
		g_set_error(error, IPV6CP_ERROR, errno,
				"Unable to set peer Interface ID: %s",
				g_strerror(errno));
		return NULL;
	}

	ipv6cp = g_try_new0(struct ipv6cp_data, 1);
	if (ipv6cp == NULL)
		return NULL;

	pppcp = pppcp_new(ppp, &ipv6cp_proto, FALSE, IPV6CP_MAX_FAILURE);
	if (pppcp == NULL) {
		g_free(ipv6cp);
		return NULL;
	}

	memcpy(&ipv6cp->local_addr, &local_addr.s6_addr[8],
						sizeof(ipv6cp->local_addr));
	memcpy(&ipv6cp->peer_addr, &peer_addr.s6_addr[8],
						sizeof(ipv6cp->peer_addr));
	ipv6cp->is_server = is_server;

	pppcp_set_data(pppcp, ipv6cp);

	ipv6cp_reset_config_options(ipv6cp);

	pppcp_set_local_options(pppcp, ipv6cp->options, ipv6cp->options_len);

	return pppcp;
}

void ipv6cp_free(struct pppcp_data *data)
{
	struct ipv6cp_data *ipv6cp = pppcp_get_data(data);

	g_free(ipv6cp);
	pppcp_free(data);
}
