/*
 *      Home-agent header file
 *
 *      Authors:
 *      Sami Kivisaari          <skivisaa@cc.hut.fi>
 *
 *      $Id: s.ha.h 1.10 02/12/19 13:57:09+02:00 vnuorval@dsl-hkigw1d8c.dial.inet.fi $
 *
 *      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.
 *
 */

#ifndef _HA_H
#define _HA_H

#include <linux/config.h>
#include <net/mipv6.h>
#include "prefix.h"
#include "bcache.h"
#include "mobhdr.h"

#ifdef CONFIG_IPV6_MOBILITY_HA

#include "tunnel.h"
#include "mipv6_icmp.h"
#include "sortedlist.h"
#include "debug.h"
#include "util.h"

int mipv6_ha_init(void);
void mipv6_ha_exit(void);

int mipv6_dad_start(struct inet6_ifaddr *ifp, int ifindex,
		    struct in6_addr *saddr, struct in6_addr *daddr,
		    struct in6_addr *haddr, struct in6_addr *coa,
		    __u32 ba_lifetime, int plength, __u16 sequence,
		    int single);

int mipv6_proxy_nd(
	struct in6_addr *home_addr, 
	int ifindex,
	int prefix_length,
	int router);

int mipv6_proxy_nd_rem(
	struct in6_addr *home_addr,
	int ifindex,
	int prefix_length,
	int router);

static __inline__ void mipv6_generate_ll_addr(struct in6_addr *ll_addr,
					      struct in6_addr *addr)
{
	ll_addr->s6_addr32[0] = htonl(0xfe800000);
	ll_addr->s6_addr32[1] = 0;
	ll_addr->s6_addr32[2] = addr->s6_addr32[2];
	ll_addr->s6_addr32[3] = addr->s6_addr32[3];
}

void mipv6_bu_add_home(
	int ifindex, struct in6_addr *saddr, struct in6_addr *daddr, 
	struct in6_addr *haddr, struct in6_addr *coa, __u32 lifetime, 
	int plength, __u16 sequence, int single, int ack, 
	__u32 ba_lifetime, __u32 ba_refresh, int dad, int lladdr,__u8 *k_bu);

/*
 * Stop listening to the multicast value of the Home Address of a
 * MN when it's binding cache entry is being expired or freed up.
 */
static inline void bcache_proxy_nd_rem(struct mipv6_bcache_entry *entry)
{
	if (entry->type == HOME_REGISTRATION) {
		if (mipv6_proxy_nd_rem(&entry->home_addr, entry->ifindex,
				       entry->prefix, entry->single) == 0) {
			DEBUG(DBG_INFO, "proxy_nd succ");
		} else {
			DEBUG(DBG_INFO, "proxy_nd fail");
		}
	}
}

static __inline__ void del_proxy(struct in6_addr *home_addr,
				 struct mipv6_bcache_entry *entry)
{
	bcache_proxy_nd_rem(entry);	
	mipv6_del_tnl_to_mn(&entry->coa, &entry->our_addr, home_addr);
}


static __inline__ void mipv6_check_entry(struct mipv6_bcache_entry *entry,
		int create_tunnel, int type, int single, int *ret)
{
	if (entry->type == HOME_REGISTRATION) {
		if (create_tunnel || 
		    type != HOME_REGISTRATION) {
			del_proxy(&entry->home_addr, entry);
		} else if (type == HOME_REGISTRATION && 
			   entry->single != single) {
			/* proxy nd for link-local address
			   should either be added or removed */
			bcache_proxy_nd_rem(entry);
			*ret = 1;
		}
	}
}

static __inline__ int mipv6_create_tnl(int type, int single, int create_tunnel,
		int ifindex, __u8 prefix,
		struct mipv6_bcache_entry *entry,
		struct in6_addr *coa, struct in6_addr *our_addr,
		struct in6_addr *home_addr, int update_pneigh_tbl)
{
	int ret;

	if (type == HOME_REGISTRATION) {
		entry->router = 0;
		entry->single = single;
		if (create_tunnel && 
		    ((ret = mipv6_add_tnl_to_mn(coa, our_addr, home_addr))
		     <= 0)) {
			if (ret != -ENOMEDIUM) {
				DEBUG(DBG_ERROR,
				      "unable to configure tunnel to MN!");
			}
			return 1;
		}
		if ((create_tunnel || update_pneigh_tbl) &&
		    mipv6_proxy_nd(home_addr, ifindex,
				   prefix, single) != 0) {
			DEBUG(DBG_ERROR, "mipv6_proxy_nd failed!");
			return 2;
		}
	}
	return 0;
}

/**
 * set_ha_pfx_list - manipulate pfx_list for HA when timer goes off
 * @entry: pfx_list_entry that is due
 */
static __inline__ void set_ha_pfx_list(struct pfx_list_entry *entry)
{
	int count;
	struct prefix_info *plist;
	__u16 id;
	extern struct list_head pfx_list;

	id = -1;
	if ((count = ipv6_get_prefix_entries(&plist, entry->ifindex, 0)) > 0) {
		mipv6_icmpv6_send(&entry->daddr, &entry->saddr, 
				  MIPV6_PREFIX_ADV, 0, &id, plist, 
				  count * sizeof(struct prefix_info));
		kfree(plist);
	}

	if (++entry->retries <= PREFIX_ADV_RETRIES)
		mipv6_slist_push_first(&pfx_list, jiffies +
				(PREFIX_ADV_TIMEOUT << entry->retries) * HZ);
	else
		kfree(mipv6_slist_del_first(&pfx_list));
}

static __inline__ void mipv6_bcache_delete_wrapper(struct in6_addr *haddr,
		struct in6_addr *daddr, struct in6_addr *coa, int *ba_lifetime,
		int *ba_refresh, int *need_ack, __u8 *ba_status, int lifetime,
		int plength, int sequence, int home)
{
	if (home) {
		/* Primary Care-of Address Deregistration */

		/* ack SHOULD be set (says draft) */
		*need_ack = 1;

		if (mipv6_bcache_exists(haddr, daddr) == HOME_REGISTRATION) {
			if (mipv6_bcache_delete(haddr, daddr,
						HOME_REGISTRATION) != 0)
				DEBUG(DBG_ERROR, "delete failed.");
			else
				DEBUG(DBG_INFO, "delete succ.");

			*ba_lifetime = 0;
			*ba_refresh = 0;

			DEBUG(DBG_DATADUMP, "home_addr: %x:%x:%x:%x:%x:%x:%x:%x",
			      NIPV6ADDR(haddr));
			DEBUG(DBG_DATADUMP, "coa: %x:%x:%x:%x:%x:%x:%x:%x",
			      NIPV6ADDR(coa));
			DEBUG(DBG_DATADUMP, "lifet:%d,plen:%d,seq:%d",
			      lifetime, plength, sequence);

		} else {
			*ba_status = NOT_HA_FOR_MN;
		}
	}
}

static __inline__ void mipv6_adjust_lifetime(__u32 *lifetime)
{ return; }

#else	/* CONFIG_IPV6_MOBILITY_HA */

static inline void bcache_proxy_nd_rem(struct mipv6_bcache_entry *entry)
{
	DEBUG(DBG_ERROR, "HA function called in non-HA config");
}

static __inline__ void del_proxy(struct in6_addr *home_addr,
				 struct mipv6_bcache_entry *entry)
{
	DEBUG(DBG_ERROR, "HA function called in non-HA config");
}

static __inline__ void mipv6_check_entry(struct mipv6_bcache_entry *entry,
		int create_tunnel, int type, int single, int *ret)
{ return; }

static __inline__ int mipv6_create_tnl(int type, int single, int create_tunnel,
		int ifindex, __u8 prefix,
		struct mipv6_bcache_entry *entry,
		struct in6_addr *coa, struct in6_addr *our_addr,
		struct in6_addr *home_addr, int update_pneigh_tbl)
{
	return 0;		/* always ok for CN's */
}

static __inline__ void set_ha_pfx_list(struct pfx_list_entry *entry)
{ return; }

static __inline__ void mipv6_bcache_delete_wrapper(struct in6_addr *haddr, 
		struct in6_addr *daddr, struct in6_addr *coa, int *ba_lifetime,
		int *ba_refresh, int *need_ack, __u8 *ba_status, int lifetime,
		int plength, int sequence, int home)
{
	/* Node is not a Home Agent, but the sender believes
	 * so.  The draft doesn't tell if, when _deleting_,
	 * sender should be informed with a code 132 (Home Reg
	 * not supported).  At this time we return
	 * REASON_UNSPECIFIED. */
	if (home)
		*ba_status = REASON_UNSPECIFIED;
}

static __inline__ void  mipv6_bu_add_home(
	int ifindex, struct in6_addr *saddr, struct in6_addr *daddr, 
	struct in6_addr *haddr, struct in6_addr *coa, __u32 lifetime, 
	int plength, __u16 sequence, int single, int ack, 
	__u32 ba_lifetime, __u32 ba_refresh, int dad, int lladdr, __u8 *k_bu)
{
	mipv6_send_ba(daddr, haddr, coa, 0, 
		      HOME_REGISTRATION_NOT_SUPPORTED,
		      sequence, ba_lifetime, ba_refresh, k_bu);
}

static __inline__ void mipv6_adjust_lifetime(__u32 *lifetime)
{
	if (*lifetime > MAX_RR_BINDING_LIFE)
		*lifetime = MAX_RR_BINDING_LIFE;
}
#endif	/* CONFIG_IPV6_MOBILITY_HA */

#endif
