/*
 * $Id: bul.c 1.114 06/05/15 13:45:42+03:00 vnuorval@tcs.hut.fi $
 *
 * This file is part of the MIPL Mobile IPv6 for Linux.
 *
 * Author: Henrik Petander <petander@tcs.hut.fi>
 *
 * Copyright 2003-2005 Go-Core Project
 * Copyright 2003-2006 Helsinki University of Technology
 *
 * MIPL Mobile IPv6 for Linux 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; version 2 of
 * the License.
 *
 * MIPL Mobile IPv6 for Linux 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 MIPL Mobile IPv6 for Linux; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307 USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <time.h>
#include <errno.h>
#include <netinet/ip6mh.h>
#include <syslog.h>

#include "bul.h"
#include "mn.h"
#include "util.h"
#include "conf.h"
#include "xfrm.h"
#include "debug.h"
#include "retrout.h"
#ifdef ENABLE_VT
#include "vt.h"
#endif

#define BUL_DEBUG_LEVEL 1

#if BUL_DEBUG_LEVEL >= 1
#define BDBG dbg
#else
#define BDBG(...)
#endif

#define BUL_BUCKETS 32

static struct hash bul_hash;

struct bulentry *create_bule(const struct in6_addr *hoa,
			     const struct in6_addr *cn_addr)
{
	struct bulentry *bule;
	if ((bule = malloc(sizeof(*bule))) != NULL) {
		memset(bule, 0, sizeof(*bule));
		bule->hoa = *hoa;
		bule->last_coa = *hoa;
		bule->peer_addr = *cn_addr;
		INIT_LIST_HEAD(&bule->tqe.list);
		bule->seq = random();
	}
	return bule;
}

void free_bule(struct bulentry *bule)
{
	assert(bule != NULL);
	free(bule);
}

void dump_bule(void *bule, void *os)
{
	struct bulentry *e = (struct bulentry *)bule;
	FILE *out = (FILE *)os;

	if (e->type == BUL_ENTRY)
		fprintf(out, "== BUL_ENTRY ==\n");
	else if (e->type == NON_MIP_CN_ENTRY)
		fprintf(out, "== NON_MIP_CN_ENTRY ==\n");
	else if (e->type == UNREACH_ENTRY)
		fprintf(out, "== UNREACH_ENTRY ==\n");
	else 
		fprintf(out, "== Unknown BUL entry ==\n");

	fprintf(out, "Home address    %x:%x:%x:%x:%x:%x:%x:%x\n",
		NIP6ADDR(&e->hoa));
	fprintf(out, "Care-of address %x:%x:%x:%x:%x:%x:%x:%x\n",
		NIP6ADDR(&e->coa));
	fprintf(out, "CN address      %x:%x:%x:%x:%x:%x:%x:%x\n",
		NIP6ADDR(&e->peer_addr));
	fprintf(out, " lifetime = %ld, ", e->lifetime.tv_sec);
	fprintf(out, " delay = %ld\n", tstomsec(e->delay));
	fprintf(out, " flags: ");
	if (e->flags & IP6_MH_BU_HOME)
		fprintf(out, "IP6_MH_BU_HOME ");
	if (e->flags & IP6_MH_BU_ACK)
		fprintf(out, "IP6_MH_BU_ACK ");
	if (e->flags & IP6_MH_BU_LLOCAL)
		fprintf(out, "IP6_MH_BU_LLOCAL ");
	if (e->flags & IP6_MH_BU_KEYM)
		fprintf(out, "IP6_MH_BU_KEYM ");
	if (e->flags & IP6_MH_BU_MR)
		fprintf(out, "IP6_MH_BU_MR");

	fprintf(out, "\n");
	fflush(out);
}

/**
 * bul_get - returns a binding update list entry
 * @hinfo: home address info, optional if our_addr is present
 * @our_addr: local address (home address)
 * @peer_addr: address of CN
 *
 * Returns non-null entry on success and null on failure. Caller must
 * call del_task and add_task, if lifetime of the entry is changed.
 **/
struct bulentry *bul_get(struct home_addr_info *hinfo,
			 const struct in6_addr *our_addr,
			 const struct in6_addr *peer_addr)
{
	struct bulentry *bule;

	assert(hinfo || our_addr);

	if (hinfo)
		bule = hash_get(&hinfo->bul, NULL, peer_addr);
	else bule = hash_get(&bul_hash, our_addr, peer_addr);
	return bule;
}

/*
 * need to be separated into two phase:
 * phase 1: before sending BU
 * 		add policy/state for BU
 * phase 2: after sending BU
 * 		add policy/state for RO
 */
void bul_update_timer(struct bulentry *bule)
{
	struct timespec timer_expire;
	tsadd(bule->delay, bule->lastsent, timer_expire);
	dbg("Updating timer\n");
	dbg_func(bule, dump_bule);
	add_task_abs(&timer_expire, &bule->tqe, bule->callback);
}

void bul_update_expire(struct bulentry *bule)
{

	if (bule->type != BUL_ENTRY)
		bule->expires = bule->lastsent;
	else if (tsisset(bule->lifetime))
		tsadd(bule->lastsent, bule->lifetime, bule->expires);
	else {
		/* Deregistration entry, expires after 420 seconds...*/
		tsadd(DEREG_BU_LIFETIME_TS, bule->lastsent, bule->expires);
	}
}

/* Adds bul entry to both hashes and adds a timer for expiry / resend. 
   Caller must fill all non-private fields of bule */
int bul_add(struct bulentry *bule)
{
	int ret = 0;
	struct timespec timer_expire;
	struct home_addr_info *hai = bule->home;

	assert(bule && tsisset(bule->lifetime) && hai);
	
	if ((ret = hash_add(&bul_hash, bule, &bule->hoa, &bule->peer_addr)) < 0)
		return ret;
	if ((ret = hash_add(&hai->bul, bule, NULL, &bule->peer_addr)) < 0)
		goto bul_free;

	clock_gettime(CLOCK_REALTIME, &bule->lastsent);
	if (bule->type == BUL_ENTRY) {
		if ((ret = pre_bu_bul_update(bule)) < 0)
			goto home_bul_free;
	} else if (bule->type == NON_MIP_CN_ENTRY) {
		if (bule->flags & IP6_MH_BU_HOME) {
			if (xfrm_block_hoa(hai) < 0 ||
			    (hai->mob_rtr && xfrm_block_ra(hai) < 0))
				goto home_bul_free;
		}
	}
	tsadd(bule->delay, bule->lastsent, timer_expire);
	dbg("Adding bule\n");
	dbg_func(bule, dump_bule);
	add_task_abs(&timer_expire, &bule->tqe, bule->callback);
	return 0;
home_bul_free:
	hash_delete(&hai->bul, &bule->hoa, &bule->peer_addr);
bul_free:
	hash_delete(&bul_hash, &bule->hoa, &bule->peer_addr);
	return ret; 
}

/* bul_delete - deletes a bul entry */
void bul_delete(struct bulentry *bule)
{
	struct home_addr_info *hai = bule->home;

	del_task(&bule->tqe);
	hash_delete(&bul_hash, &bule->hoa, &bule->peer_addr);
	hash_delete(&hai->bul, NULL, &bule->peer_addr);

	if (!IN6_ARE_ADDR_EQUAL(&bule->hoa, &bule->coa)) {
		bule->last_coa = bule->coa;
		bule->coa = bule->hoa;
		bule->coa_changed = 1;
	}
	if (bule->type == BUL_ENTRY) {
		xfrm_del_bule(bule);
		if (!(bule->flags & IP6_MH_BU_HOME))
			mn_rr_delete_bule(bule);
	}
	if (bule->flags & IP6_MH_BU_HOME) {
		if (bule->type == UNREACH_ENTRY) {
			pthread_mutex_lock(&hai->ha_list.c_lock);
			if (IN6_ARE_ADDR_EQUAL(&bule->peer_addr, 
					       &hai->ha_list.last_ha))
				hai->ha_list.last_ha = in6addr_any;
			pthread_mutex_unlock(&hai->ha_list.c_lock);
		} else {
			if (hai->home_block & HOME_LINK_BLOCK)
				xfrm_unblock_link(hai);
			if (hai->home_block & HOME_ADDR_BLOCK)
				xfrm_unblock_hoa(hai);
			if (hai->home_block & NEMO_RA_BLOCK)
				xfrm_unblock_ra(hai);
			if (hai->home_block & NEMO_FWD_BLOCK)
				xfrm_unblock_fwd(hai);
			if (conf.MnResetDhaadAtHome && hai->use_dhaad)
				hai->ha_addr = in6addr_any;
		}
	}
	while (bule->ext_cleanup)
		bule->ext_cleanup(bule);
	dbg("Deleting bule\n");
	dbg_func(bule, dump_bule);
	free_bule(bule);
}

/* bul_init - initializes global bul */
int bul_init(void)
{
	int ret;

	ret = hash_init(&bul_hash, DOUBLE_ADDR, BUL_BUCKETS);

#ifdef ENABLE_VT
	if (ret < 0)
		return ret;

	ret = vt_bul_init();
#endif

	return ret;
}

/* bul_home_init - initializes a bul */
int bul_home_init(struct home_addr_info *home)
{
	return hash_init(&home->bul, SINGLE_ADDR, BUL_BUCKETS);
}

/* bule_cleanup - cleans up a bulentry */
static int bule_cleanup(void *vbule, void *vbul)
{
	if (vbul == NULL)
		BUG("bul_hash should be empty!\n");
	bul_delete(vbule);
	return 0;
}

/* bul_home_cleanup - cleans up a bul 
 * @bul: binding update list to clean up
 */
void bul_home_cleanup(struct hash *bul)
{
	hash_iterate(bul, bule_cleanup, bul);	
	hash_cleanup(bul);
}

void bul_flush(void)
{
	hash_iterate(&bul_hash, bule_cleanup, NULL);	
}

/* bul_cleanup - cleans up global bul */
void bul_cleanup(void)
{
	hash_cleanup(&bul_hash);
}

/* bul_iterate - iterates through binding update list calling func for
 * every entry. 
 * @func: function to be called for every entry, @func
 * takes a void cast bulentry as its first argument and @arg as its
 * second.  
 * @arg: second argument with which @func is called for every
 * bul entry 
 */
int bul_iterate(struct hash *h, int (* func)(void *, void *), void *arg)
{
	struct hash *tmp = h ? h : &bul_hash;
	return hash_iterate(tmp, func, arg);
}
