/*
 *      Binding Cache
 *
 *      Authors:
 *      Juha Mynttinen            <jmynttin@cc.hut.fi>
 *
 *      $Id: bcache.c,v 1.21 2001/02/23 18:25:37 antti Exp $
 *
 *      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.
 */

/*
 *	Changes:
 *
 *	Nanno Langstraat	:	Timer code cleaned up, active socket
 *					test rewritten
 */

#include <linux/autoconf.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/in6.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#endif
#include <net/ipv6.h>
#include <net/tcp.h>
#include <net/udp.h>

#include "bcache.h"
#include "mempool.h"
#include "hashlist.h"
#include "debug.h"
#include "sendopts.h"
#include "mipv6.h"

struct mipv6_bcache {
	struct hashlist *entries;    /* hashkey home_addr, sortkey callback_time */
	struct mipv6_allocation_pool *entry_pool;
	__u32 size;
	struct timer_list callback_timer;
	struct tq_struct  callback_task;
	rwlock_t lock;
};

static struct mipv6_bcache *bcache;

#ifdef CONFIG_PROC_FS
static int bcache_proc_info(char *buffer, char **start, off_t offset,
			    int length);
#endif

static void set_timer(void);


#define MIPV6_BCACHE_HASHSIZE  32

/* Moment of transmission of a BR, in seconds before bcache entry expiry */
#define BCACHE_BR_SEND_LEAD  3

/* No BR is sent if the bcache entry hasn't been used in the last
 * BCACHE_BR_SEND_THRESHOLD seconds before expiry */
#define BCACHE_BR_SEND_THRESHOLD  10



/* 
 * Internal functions.
 *
 * Assume that synchronization is taken care by the callers of these
 * functions, in the top level of the module. This is to avoid
 * deadlocks, when called function tries to get the same lock with the
 * caller.
 */

/*
 * Callback for hashlist_iterate
 */

struct cache_entry_iterator_args {
	struct mipv6_bcache_entry **entry;
};

static int find_first_cache_entry_iterator(
	void *data, void *args,
	struct in6_addr *home_addr,
	unsigned long *lifetime)
{
	struct mipv6_bcache_entry *entry =
		(struct mipv6_bcache_entry *)data;
	struct cache_entry_iterator_args *state =
		(struct cache_entry_iterator_args *)args;

	if (entry == NULL) {
		DEBUG((DBG_ERROR, "iterator called with NULL argument"));
		return ITERATOR_ERR; /* continue iteration ?? */
	}

	if (entry->type == CACHE_ENTRY) {
		*(state->entry) = entry;
		return ITERATOR_STOP; /* stop iteration */
	} else {
		return ITERATOR_CONT; /* continue iteration */
	}
}


/* 
 * Get memory for a new bcache entry.  If bcache if full, a cache
 * entry may deleted to get space for a home registration, but not
 * vice versa.
 */
static struct mipv6_bcache_entry *mipv6_bcache_get_entry(__u8 type) 
{
	struct mipv6_bcache_entry *entry;
	struct cache_entry_iterator_args args;

	DEBUG_FUNC();
  
	entry = (struct mipv6_bcache_entry *)
		mipv6_allocate_element(bcache->entry_pool);

	if (entry == NULL && type == HOME_REGISTRATION) {
                /* cache full, but need space for a home registration */
		args.entry = &entry;
		hashlist_iterate(bcache->entries, &args,
				 find_first_cache_entry_iterator);
		if (entry != NULL) {
			DEBUG((DBG_INFO, "cache entry: %x", entry));
			if (hashlist_delete(bcache->entries, &entry->home_addr) < 0)
				DEBUG((DBG_ERROR, "bcache entry delete failed"));
			else
				entry = (struct mipv6_bcache_entry *)
					mipv6_allocate_element(bcache->entry_pool);
		}
	}

	return entry;
}

/*
 * Frees entry's memory allocated with mipv6_bcache_get_entry
 */
static void mipv6_bcache_entry_free(struct mipv6_bcache_entry *entry)
{
	mipv6_free_element(bcache->entry_pool, (void *)entry);
}

static int is_valid_type(__u8 type)
{
	if (type != CACHE_ENTRY && 
	    type != HOME_REGISTRATION && 
	    type != ANY_ENTRY)
		return 0;
	else
		return 1;
}

/*
 * Removes all expired entries 
 */
static void expire(void)
{
	struct mipv6_bcache_entry *entry;
	unsigned long now = jiffies;
	unsigned long flags;
	struct br_addrs {
		struct in6_addr daddr;
		struct in6_addr saddr;
		struct br_addrs *next;
	};
	struct br_addrs *br_info = NULL;

	DEBUG_FUNC();

	write_lock_irqsave(&bcache->lock, flags);

	while ((entry = (struct mipv6_bcache_entry *)
		hashlist_get_first(bcache->entries)) != NULL) {
		if (entry->callback_time <= now) {
			DEBUG((DBG_INFO, "expire(): an entry expired"));
			hashlist_delete(bcache->entries, &entry->home_addr);
			mipv6_bcache_entry_free(entry);
		} else if (entry->br_callback_time != 0 &&
			   entry->br_callback_time <= now &&
			   entry->type != HOME_REGISTRATION) {
			if (now - entry->last_used < BCACHE_BR_SEND_THRESHOLD * HZ) {
				struct br_addrs *tmp;
				tmp = br_info;
				DEBUG((DBG_INFO, "bcache entry recently used. Sending BR."));
				/* queue for sending */
				br_info = kmalloc(sizeof(struct br_addrs), GFP_ATOMIC);
				if (br_info) {
					ipv6_addr_copy(&br_info->saddr, &entry->our_addr);
					ipv6_addr_copy(&br_info->daddr, &entry->home_addr);
					br_info->next = tmp;
					entry->last_br = now;
				} else {
					br_info = tmp;
					DEBUG((DBG_ERROR, "Out of memory"));
				}
			}
			entry->br_callback_time = 0;
		} else {
			break;
		}
	}
	write_unlock_irqrestore(&bcache->lock, flags);

	while (br_info) {
		struct br_addrs *tmp = br_info->next;
		if (mipv6_send_rq_option(&br_info->saddr, &br_info->daddr, 0, NULL) < 0)
			DEBUG((DBG_WARNING, "BR send for %x:%x:%x:%x:%x:%x:%x:%x failed",
			       NIPV6ADDR(&br_info->daddr)));
		kfree(br_info);
		br_info = tmp;
	}

	return;
}

/* 
 * The function that is scheduled to do the callback functions. May be
 * modified e.g to allow Binding Requests, now only calls expire() and
 * schedules a new timer.
 *
 * Important: This function is a 'top-level' function in this module,
 * it is not called from any other function inside the module although
 * it is static. So it must take care of the syncronization, like the
 * other static functions need not.  
 */
static void task_handler(void *dummy)
{
	unsigned long flags;

	expire(); 

	write_lock_irqsave(&bcache->lock, flags);
	set_timer();
	write_unlock_irqrestore(&bcache->lock, flags);
}


/*
 * Schedule a task to call the callback, this is to make avoiding
 * synchronization problems easier.  The scheduled task may use rwlock
 * as usual - using it from the timer function might cause problems.
 */
void timer_handler(unsigned long dummy)
{
	INIT_LIST_HEAD(&bcache->callback_task.list);
	bcache->callback_task.sync    = 0;
	bcache->callback_task.routine = task_handler;
	bcache->callback_task.data    = NULL;
	queue_task(&bcache->callback_task, &tq_timer);

	return;
}

static void set_timer(void)
{
	struct mipv6_bcache_entry *entry;
	unsigned long callback_time;

	DEBUG_FUNC();

	entry = (struct mipv6_bcache_entry *) hashlist_get_first(bcache->entries);
	if (entry != NULL) {
		if (entry->callback_time == EXPIRE_INFINITE) {
			DEBUG((DBG_WARNING, "bcache.c: set_timer: expire at infinity, not setting "
			                    "a new timer"));
		} else {
			if (entry->br_callback_time > 0)
				callback_time = entry->br_callback_time;
			else if (entry->callback_time > jiffies)
				callback_time = entry->callback_time;
			else {
				DEBUG((DBG_WARNING, "bcache.c: set_timer: bcache timer attempted "
				                    "to schedule for a historical jiffies count!"));
				callback_time = jiffies+TIMERDELAY;
			}

			DEBUG((DBG_INFO, "bcache.c: set_timer: setting timer to now"));
			mod_timer(&bcache->callback_timer, callback_time);
		}
	} else {
		del_timer(&bcache->callback_timer);
		DEBUG((DBG_INFO, "bcache.c: set_timer: BC empty, not setting a new timer"));
	}

	return;
}


int modGT65536(__u16 x, __u16 y)
{
	__u16 z;
	
	z = y + (__u16)0x8000;           /* Forward window from y */
	if (z > y)                       /* Overflow(z) = False   */
		return ((x>y) && (x<=z));
	else                             /* Overflow(z) = True    */
		return ((x>y) || (x<=z));
}



/*
 * Interface functions visible to other modules
 */
int mipv6_bcache_add(
	int ifindex,
	struct in6_addr *our_addr,
	struct in6_addr *home_addr,
	struct in6_addr *coa,
	__u32 lifetime,
	__u8 prefix,
	__u16 seq,
	__u8 router,
	__u8 type) 
{
	unsigned long flags;
	struct mipv6_bcache_entry *entry;
	int update = 0;
	unsigned long now = jiffies;
	struct cache_entry_iterator_args args;

	write_lock_irqsave(&bcache->lock, flags);

	if ((entry = (struct mipv6_bcache_entry *)
	     hashlist_get(bcache->entries, home_addr)) != NULL) {
                /* if an entry for this home_addr exists (with smaller
		 * seq than the new seq), update it by removing it
		 * first
		 */
		if (modGT65536(seq, entry->seq)) {
			DEBUG((DBG_INFO, "mipv6_bcache_add: updating an "
			       "existing entry"));
			update = 1;
		} else {
			DEBUG((DBG_INFO, "mipv6_bcache_add: smaller seq "
			       "than existing, not updating"));
			write_unlock_irqrestore(&bcache->lock, flags);
			return 0;
		}
	} else {
		/* no entry for this home_addr, try to create a new entry */
		DEBUG((DBG_INFO, "mipv6_bcache_add: creating a new entry"));
		entry = mipv6_bcache_get_entry(type);

		if (entry == NULL) {
			/* delete next expiring entry of type CACHE_ENTRY */
			args.entry = &entry;
			hashlist_iterate(bcache->entries, &args,
			                 find_first_cache_entry_iterator);

			if (entry == NULL) {
				DEBUG((DBG_INFO, "mipv6_bcache_add: cache full"));
				write_unlock_irqrestore(&bcache->lock, flags);
				return -1;
			} else {
				hashlist_delete(bcache->entries,
						&entry->home_addr);
			}
		}
	}

	ipv6_addr_copy(&(entry->our_addr), our_addr);
	ipv6_addr_copy(&(entry->home_addr), home_addr);
	ipv6_addr_copy(&(entry->coa), coa);
	entry->ifindex = ifindex;
	entry->prefix = prefix;
	entry->seq = seq;
	entry->type = type;
	entry->last_used = 0;

	if (type == HOME_REGISTRATION) {
		entry->router = router;
	} else if (router != 0) {
		DEBUG((DBG_WARNING, "Router bit set for non-home registration! Ignoring."));
	}
	entry->last_br = 0;
	if (lifetime == EXPIRE_INFINITE) {
		entry->callback_time = EXPIRE_INFINITE; 
	} else {
		entry->callback_time = now + lifetime * HZ;
		if (entry->type == HOME_REGISTRATION)
			entry->br_callback_time = 0;
		else
			entry->br_callback_time = now + 
				(lifetime - BCACHE_BR_SEND_LEAD) * HZ;
	}

	if (update) {
		DEBUG((DBG_INFO, "updating entry : %x", entry));
		hashlist_reschedule(bcache->entries,
		                    home_addr,
		                    entry->callback_time);
	} else {
		DEBUG((DBG_INFO, "adding entry: %x", entry));

		if ((hashlist_add(bcache->entries,
				  home_addr,
				  entry->callback_time,
				  entry)) < 0) {
			mipv6_bcache_entry_free(entry);
			DEBUG((DBG_ERROR, "Hash add failed"));
			write_unlock_irqrestore(&bcache->lock, flags);
			return -1;
		}
	}

	set_timer();

	write_unlock_irqrestore(&bcache->lock, flags);
	return 0;
}

int mipv6_bcache_delete(struct in6_addr *home_addr, __u8 type) 
{
	unsigned long flags;
	struct mipv6_bcache_entry *entry;
  
	DEBUG_FUNC();

	if (home_addr == NULL || !is_valid_type(type)) {
		DEBUG((DBG_INFO, "error in arguments"));
		return -1;
	}

	write_lock_irqsave(&bcache->lock, flags);
	entry = (struct mipv6_bcache_entry *) 
		hashlist_get(bcache->entries, home_addr);
	if (entry == NULL) {
		DEBUG((DBG_INFO, "mipv6_bcache_delete: No such entry"));
		write_unlock_irqrestore(&bcache->lock, flags);
		return -1;
	}
  	if (!(entry->type & type)) {
		DEBUG((DBG_INFO, "mipv6_bcache_delete: No entry with "
		       "type correct type"));
		write_unlock_irqrestore(&bcache->lock, flags);
		return -1;
	}

	hashlist_delete(bcache->entries, &entry->home_addr);
	mipv6_bcache_entry_free(entry);

	set_timer();
	write_unlock_irqrestore(&bcache->lock, flags);

	return 0;
} 

int mipv6_bcache_exists(struct in6_addr *home_addr)
{
	unsigned long flags;
	struct mipv6_bcache_entry *entry;

	DEBUG_FUNC();

	if (home_addr == NULL) return -1;

	read_lock_irqsave(&bcache->lock, flags);
	entry = (struct mipv6_bcache_entry *)
		hashlist_get(bcache->entries, home_addr);
	read_unlock_irqrestore(&bcache->lock, flags);

	if(entry == NULL) return -1;

	return entry->type;
}

/*
 * Updates the ->last_used field of the found bcache entry.
 */
int mipv6_bcache_get(
	struct in6_addr *home_addr, 
	struct mipv6_bcache_entry *entry)
{
	unsigned long flags;
	struct mipv6_bcache_entry *entry2;

	DEBUG_FUNC();
  
	if (home_addr == NULL || entry == NULL) 
		return -1;

	read_lock_irqsave(&bcache->lock, flags);

	entry2 = (struct mipv6_bcache_entry *) 
		hashlist_get(bcache->entries, home_addr);
	if (entry2 != NULL) {
		entry2->last_used = jiffies;
		memcpy(entry, entry2, sizeof(struct mipv6_bcache_entry));
	}

	read_unlock_irqrestore(&bcache->lock, flags);
	return (entry2 == NULL)? -1 : 0;
}



/*
 * Proc-filesystem functions
 */

#ifdef CONFIG_PROC_FS

struct procinfo_iterator_args {
	int *len;
	char *buffer;
};

static int procinfo_iterator(void *data, void *args,
			     struct in6_addr *addr, 
			     unsigned long *pref)
{
	struct procinfo_iterator_args *state =
		(struct procinfo_iterator_args *)args;
	struct mipv6_bcache_entry *entry =
		(struct mipv6_bcache_entry *)data;
	char *buffer = (char *)state->buffer;
	int *len = state->len;

	DEBUG_FUNC();

	if (entry == NULL) return ITERATOR_ERR;

	*len += sprintf(buffer + *len, 
			"h=%04x%04x%04x%04x%04x%04x%04x%04x "
			"c=%04x%04x%04x%04x%04x%04x%04x%04x "
			"(e=%ld,t=%d)\n",
			NIPV6ADDR(&entry->home_addr),
			NIPV6ADDR(&entry->coa),
			((entry->callback_time) - jiffies) / HZ,
			(int)entry->type);

	return ITERATOR_CONT;
}

/*
 * Callback function for proc filesystem.
 */
static int bcache_proc_info(char *buffer, char **start, off_t offset,
			    int length)
{
	int len = 0;
	unsigned long flags; 
	struct procinfo_iterator_args args;

	args.len = &len;
	args.buffer = buffer;

	read_lock_irqsave(&bcache->lock, flags);
	hashlist_iterate(bcache->entries, &args, procinfo_iterator);
	read_unlock_irqrestore(&bcache->lock, flags);

	*start = buffer + offset;
	len -= offset;
	if (len > length) len = length;
	
	return len;
}

#endif /* CONFIG_PROC_FS */




/* 
 * Initialization and shutdown functions
 */

int __init mipv6_initialize_bcache(__u32 size) 
{
	DEBUG_FUNC();

	if (size < 1) {
		DEBUG((DBG_ERROR, "Binding cache size must be at least 1"));
		return -1;
	}
		
	bcache = (struct mipv6_bcache *) 
		kmalloc(sizeof(struct mipv6_bcache), GFP_KERNEL);
	if (bcache == NULL) {
		DEBUG((DBG_ERROR, "Couldn't allocate memory for binding cache"));
		return -1;
	}

	init_timer(&bcache->callback_timer);
	bcache->callback_timer.data     = 0;
	bcache->callback_timer.function = timer_handler;

	bcache->size = size;
	bcache->lock = RW_LOCK_UNLOCKED;

	if ((bcache->entry_pool = mipv6_create_allocation_pool(
		size, sizeof(struct mipv6_bcache_entry), GFP_KERNEL)) == NULL)
	{
		DEBUG((DBG_ERROR, "mipv6_bcache_init(): Allocation pool creation failed"));
		kfree(bcache);
		return -1;
	}

	if ((bcache->entries = 
	     hashlist_create(size, MIPV6_BCACHE_HASHSIZE)) == NULL)
	{
		DEBUG((DBG_ERROR, "Failed to initialize hashlist"));
		mipv6_free_allocation_pool(bcache->entry_pool);
		kfree(bcache);
		return -1;
	}

#ifdef CONFIG_PROC_FS
	proc_net_create("mip6_bcache", 0, bcache_proc_info); 
#endif

	DEBUG((DBG_INFO, "Binding cache initialized"));
	return 0;
}


int __exit mipv6_shutdown_bcache()
{
	unsigned long flags;
	struct mipv6_bcache_entry *entry;

	DEBUG_FUNC();

	write_lock_irqsave(&bcache->lock, flags);
	DEBUG((DBG_INFO, "mipv6_shutdown_bcache: Stopping the timer"));
	del_timer(&bcache->callback_timer);

	while ((entry = (struct mipv6_bcache_entry *) 
		hashlist_get_first(bcache->entries)) != NULL)
	{
		DEBUG_FUNC();
	/*	hashlist_delete_first(bcache->entries); */
		hashlist_delete(bcache->entries, &entry->home_addr);
		mipv6_bcache_entry_free(entry);
		DEBUG_FUNC();
	}

	hashlist_destroy(bcache->entries);
	mipv6_free_allocation_pool(bcache->entry_pool);
#ifdef CONFIG_PROC_FS
	proc_net_remove("mip6_bcache");
#endif
	/* Lock must be released before freeing the memory. */
	write_unlock_irqrestore(&bcache->lock, flags);

	kfree(bcache);

	return 0;
}
