#include "dsr_route.h"
#include "dsr_debug.h"
#include "dsr_output.h"
#include "dsr_crypt.h"

#include "graph.h"
#include "spf.h"

extern __u32 IPADDR;
extern __u32 NETMASK;
extern __u32 NETWORK;
extern struct sk_buff_head send_q;
#ifdef DSR_USE_LINK_CACHE
extern struct graph *lc_graph;
extern struct node *my_node;
/* we don't need a route cache, but a single return value 
 * TODO: better */
static struct rt_entry ret_route;
static int graph_recalc_spf = -1;
#else
extern struct rt_entry rt_cache[];
#endif
extern struct rt_req_entry rt_req_table[];
extern int rt_cache_size;
extern int rt_req_table_size;
extern unsigned int stat_add_f_rt;
extern unsigned int stat_add_r_rt;
extern unsigned int stat_remove_rt;
#ifdef DSR_CRYPT
extern struct timer_list refresh_timer;
#endif


/* FIXME: change in oif may mean change in hh_len.  Check and realloc --RR */
/* modified from ipv4/netfilter/iptable_mangle.c */
int reroute_output(struct sk_buff *skb, __u32 src, __u32 dst) {
  struct iphdr *iph = skb->nh.iph;
  struct rtable *rt;
  struct rt_key key = { dst:dst,
			src:src,
			oif:skb->sk ? skb->sk->bound_dev_if : 0,
			tos:RT_TOS(iph->tos)|RTO_CONN,
  };
  
  if (ip_route_output_key(&rt, &key) != 0) {
    printk("dsr reroute_output: No more route.\n");
    return -EINVAL;
  }
  
  /* Drop old route. */
  dst_release(skb->dst);

  skb->dst = &rt->u.dst;
  return 0;
}


void remove_rt_req_id(__u32 daddr) {
  int i, count;

  /* clear entry in rt req table */
  for (i = 0, count = 0;count < rt_req_table_size;i++) {
    DSR_ASSERT(i < REQ_TABLE_SIZE);
    if (rt_req_table[i].taddr != 0) {
      count++;
      if (rt_req_table[i].taddr == daddr) {
	del_timer(&(rt_req_table[i].timer));
	rt_req_table[i].taddr = 0;
	rt_req_table_size--;
	return;
      }
    }
  }
  return;
}



#ifdef DSR_USE_LINK_CACHE
/*
 * we use "link cache" as described in dsr-draft 
 */




#ifdef DSR_CRYPT

/*
 * cleanup route, refresh qos_trust values 
 */
void route_refresh_timeout(unsigned long data) {

#ifndef DSR_DEBUG_NO_REFRESH_TIMER
	/* refresh the routes */
	/* this code is quite inefficient. please fix! */

	struct listnode *ln;
	struct rt_entry *rt;

	//spin_lock_bh(&(lc_graph->lock));

	DEBUGP("*ROUTE_REFRESH_TIMEOUT*\n");
	/* check all nodes if qos value has expired */
//    read_lock(&(lc_graph->lock));
	for (ln = listhead(lc_graph->nodes); ln; nextnode(ln)) {
	    n = getdata(ln);
	    if(n != my_node) {
		if(n->state.expires < CURRENT_TIME) {
		    /* value has expired */
		    /* refresh qos values: send qos_req packets to nodes which could form alternate route */
		    rt = lookup_route(lc_graph, n->id);
		    
		    send_qos_crypt_req(n->id, rt->addr, rt->segs_left);
		}
	    }
	}
	//spin_unlock_bh(&(lc_graph->lock));
//    read_unlock(&(lc_graph->lock));
	
	/* reset timeout */
	lc_graph->refresh = 0;
	refresh_timer.expires = jiffies + QOS_CRYPT_REFRESH_INTERVAL * HZ;
	add_timer(&(refresh_timer));
#endif

    /* refresh itself is done later in lookup_route() to avoid this lot of 
     * calculation to be done in an interrupt context.
     * think i should use bottom halves?
     */
}




/*
 * update qos and trust values of a node
 */
void route_set_qos_trust(__u32 addr, unsigned int qos, unsigned int trust, unsigned int sig_type) {
    struct node * node;
//    write_lock(&(lc_graph->lock));
    //spin_lock_bh(&(lc_graph->lock));

    node = node_get(lc_graph, addr);
    if(node) {
	DEBUGP("  route_set_qos_trust: setting qos and trust value for [%u.%u.%u.%u] to {%i  %i}\n", NIPQUAD(addr), qos, trust);
	node->qos = qos;
	node->trust = trust;
	node->sig_type = sig_type;
	node->time = CURRENT_TIME;
	node->state.expires = CURRENT_TIME + QOS_CRYPT_VALIDITY_TIMEOUT;
	node->state.req_sent = CRYPT_STATE_NOT_SENT;
	graph_recalc_spf = -1;
    } else {
	DEBUGP("ERROR: route_set_qos_trust: node not found!\n");
    }
//    write_unlock(&(lc_graph->lock));
    //spin_unlock_bh(&(lc_graph->lock));

}

#endif



void check_send_q() {
  int i;
  struct rt_entry * rt;
  int q_len = skb_queue_len(&send_q);
  struct sk_buff * skbptr;
  struct send_info * sendinfo;

  /* see if we can send anything in the send queue */
  for (i = 0;i < q_len;i++) {
    skbptr = skb_dequeue(&send_q);
    sendinfo = (struct send_info *) skbptr->cb;
    DSR_ASSERT(skbptr != NULL);
    //spin_lock_bh(&(lc_graph->lock));
    rt = lookup_route(lc_graph, sendinfo->fwdaddr);
    //spin_unlock_bh(&(lc_graph->lock));
    if (rt != NULL) {
      del_timer(&(sendinfo->timer));
      /* dsr_send should free the skb */
      dsr_send(&skbptr, rt, sendinfo->output);
    } else {
      skb_queue_tail(&send_q, skbptr);
    }
  }
}


void remove_link(struct graph * lc_graph, __u32 srchop, __u32 dsthop) {
    struct node * srcnode; 
    struct link * link;

//    write_lock(&(lc_graph->lock));
    //spin_lock_bh(&(lc_graph->lock));
    srcnode = node_get(lc_graph, srchop);
    if(!srcnode) {
	DEBUGP("  remove_route: src node [%u.%u.%u.%u] not found in graph\n", 
	       NIPQUAD(srchop));
    } else {
	link = link_get_addr(srcnode, dsthop);
	if(!link) {
	    DEBUGP("  remove_route: link [%u.%u.%u.%u]-->[%u.%u.%u.%u] not found in graph\n", 
		   NIPQUAD(srchop), NIPQUAD(dsthop));
	} else {
	    DEBUGP("  remove_route: removing link [%u.%u.%u.%u]-->[%u.%u.%u.%u]\n", 
		   NIPQUAD(srchop), NIPQUAD(dsthop));
	    listnode_delete(srcnode->links, link);
	    link_free(link);

//	    link->metric = INT_MAX;


	    graph_recalc_spf = -1;
	}
    }
    //spin_unlock_bh(&(lc_graph->lock));
//    write_unlock(&(lc_graph->lock));
}

void remove_route(struct graph * lc_graph, __u32 srchop, __u32 dsthop) {
#if 0
    remove_link(lc_graph, srchop, dsthop);
#ifndef DSR_UNIDIRECTIONAL  /* bidirectional links */
    remove_link(lc_graph, dsthop, srchop);
#endif
#endif

#if 1
    struct node * n = node_get(lc_graph, dsthop);

    remove_link(lc_graph, srchop, dsthop);
    /* remove node from list */
    listnode_delete(lc_graph->nodes, n);
    node_free(n);
#endif
}


static inline void add_new_link(struct node *src, struct node *dst) {
    struct link * link = NULL;

    /* check if link already exists */
    link = link_get_node(src, dst);
    if(link) {
//	DEBUGP("  add_new_link: link [%u.%u.%u.%u]-->[%u.%u.%u.%u] (metric=%i) already in graph\n", 
//	       NIPQUAD(src->id), NIPQUAD(dst->id), link->metric);
    } else {
	link = link_new(src, dst, 0);
	if(!link) {
	    printk("ERROR: could not get memory for new link\n");
	    return;
	} else {
	    /* calc metric */
	    link->metric = dsrcrypt_calc_metric(src->qos, 
						src->trust, 
						dst->qos, 
						dst->trust);
	    graph_recalc_spf = -1;
	}
    }
    link->time = CURRENT_TIME;

    DEBUGP("  link [%u.%u.%u.%u]-->[%u.%u.%u.%u] (metric=%i) updated\n", 
	   NIPQUAD(src->id), NIPQUAD(dst->id), link->metric);
}

/*
 * add dst node and a link src-->dst to graph.
 * returns dst node;
 * NOTE: src must already be present in graph!
 */
#define FORWARD_CONNECTION 1
#define REVERSE_CONNECTION 0
static inline struct node * add_connection(struct node *src, __u32 daddr, int type) {
    struct node * dst = NULL;
//    write_lock(&(lc_graph->lock));

    /* check if node already exists */
    dst = node_get(lc_graph, daddr);
    if(dst) {
//	DEBUGP("  add_connection: node [%u.%u.%u.%u] already in graph\n", NIPQUAD(daddr));
    } else {
	/* add node to graph */
	dst = node_new(daddr);
	if(!dst) {
	    printk("add_connection: could not generate new node for [%u.%u.%u.%u]!\n", 
		   NIPQUAD(daddr));
//	    write_unlock(&(lc_graph->lock));
	    return NULL;
	}
	graph_add_node_nocheck(lc_graph, dst);
	graph_recalc_spf = -1;
    }
    dst->time = CURRENT_TIME;
    

    /* add the links */
    /* TODO: use unidirectional links? */
#ifdef DSR_UNIDIRECTIONAL  /* unidirectional links */
    if(src) {
	switch(type) {
	case FORWARD_CONNECTION:
	    add_new_link(src, dst);
	    break;
	case REVERSE_CONNECTION:
	    add_new_link(dst, src);
	    break;
	}
    }
#else  /* bidirectional links */
    add_new_link(src, dst);
    add_new_link(dst, src);
#endif

//    write_unlock(&(lc_graph->lock));

    return dst;
}


/* addr can be null */
void add_forward_route(__u32 daddr, __u32 * addr, int n_addr) {
  struct node * prevnode = NULL;
  int i;

  stat_add_f_rt++;

//  DEBUGP("  add_forward_route: daddr [%u.%u.%u.%u]\n", NIPQUAD(daddr));
  /* TODO: table size */
  //spin_lock_bh(&(lc_graph->lock));
  prevnode = my_node;
  for(i = 0; i < n_addr ; i++) {
//      DEBUGP("  add_forward_route: addr  [%u.%u.%u.%u]\n", NIPQUAD(addr[i]));
      prevnode = add_connection(prevnode, addr[i], FORWARD_CONNECTION);
      remove_rt_req_id(addr[i]);
  }  
  

  /* add daddr to graph */
  if (DSR_SUBNET(daddr)) {
      prevnode = add_connection(prevnode, daddr, FORWARD_CONNECTION);
      remove_rt_req_id(daddr);
  }
  //spin_unlock_bh(&(lc_graph->lock));

  check_send_q();
}

/* addr can be null */
void add_reverse_route(__u32 daddr, __u32 * addr, int n_addr) {
  struct node * prevnode = NULL;
  int i;

  stat_add_r_rt++;

//  DEBUGP("  add_reverse_route: daddr [%u.%u.%u.%u]\n", NIPQUAD(daddr));
  /* TODO: table size */
  //spin_lock_bh(&(lc_graph->lock));
  prevnode = my_node;
  for(i = n_addr - 1; i >=0 ; i--) {
//      DEBUGP("  add_reverse_route: addr [%u.%u.%u.%u]\n", NIPQUAD(addr[i]));
      prevnode = add_connection(prevnode, addr[i], REVERSE_CONNECTION);
//      remove_rt_req_id(addr[i]);
  }  
  

  /* add daddr to graph */
  if (DSR_SUBNET(daddr)) {
      prevnode = add_connection(prevnode, daddr, REVERSE_CONNECTION);
//      remove_rt_req_id(daddr);
  }
  //spin_unlock_bh(&(lc_graph->lock));

  check_send_q();
}



struct rt_entry * lookup_route(struct graph * lc_graph, __u32 dst) {
    struct node *n;
    struct node *endnode;
    int i = MAX_ROUTE_LEN - 1;
    int j;
    

    /* recalculate shortest path if necessary */
    if(graph_recalc_spf) {	
	DEBUGP("lookup_route: performing dijkstra spf algorithm on graph\n");
//	    write_lock(&(lc_graph->lock));
	spf_calculate(lc_graph, my_node);
//	    write_unlock(&(lc_graph->lock));
	graph_recalc_spf = 0;
	DEBUGP("lookup_route: performing dijkstra spf algorithm on graph *** finished ***\n");
    }


//    write_lock_irqsave(&(lc_graph->lock), lc_graph->lock_flags);

//    read_lock(&(lc_graph->lock));
    endnode = node_get(lc_graph, dst);
//    read_unlock(&(lc_graph->lock));
    if(endnode) {
//	read_lock(&(lc_graph->lock));

	if(endnode->distance == INT_MAX) {
	    /* no route to node available */
	    DEBUGP("lookup_route: no route to host [%u.%u.%u.%u]\n", NIPQUAD(dst));
//	    read_unlock(&(lc_graph->lock));
//	    write_unlock_irqrestore(&(lc_graph->lock), lc_graph->lock_flags);
	    return NULL;
	} else {
	    /* fit the route into a rt_entry struct */

	    /* we get the route in reversed order */
	    n = endnode->pred;
	    while (n && (i >= 0)) {
		ret_route.addr[i--] = n->id;
		n = n->pred;
	    }
	    /* copy the values down */
	    j = 0;
	    i += 2; /* skip first node (it's my_node) */
	    while(i < MAX_ROUTE_LEN) {
		DEBUGP("*** %i, %i\n", i, j);
		ret_route.addr[j++] = ret_route.addr[i++];
		DEBUGP("*** [%u.%u.%u.%u], [%u.%u.%u.%u]\n", NIPQUAD(ret_route.addr[j-1]), NIPQUAD( ret_route.addr[i-1]));
	    }

	    ret_route.segs_left = j;
	    ret_route.time = CURRENT_TIME;
	    ret_route.dst = dst;
	
#ifdef DSRCRYPT_DEBUG       
	    DEBUGP("lookup_route: [%u.%u.%u.%u]-->[%u.%u.%u.%u] distance=%i\n", 
		   NIPQUAD(my_node->id), NIPQUAD(dst), endnode->distance);
	    for(i = 0; i < ret_route.segs_left; i++) {
		DEBUGP("  hop #%i: [%u.%u.%u.%u]\n", i, NIPQUAD(ret_route.addr[i]));
	    }
#endif
//	    read_unlock(&(lc_graph->lock));
//	    write_unlock_irqrestore(&(lc_graph->lock), lc_graph->lock_flags);

	    return &ret_route;
	}
    } else {
	DEBUGP("lookup_route: dst node [%u.%u.%u.%u] not in graph\n", NIPQUAD(dst));
    }
//    read_unlock(&(lc_graph->lock));

//    write_unlock_irqrestore(&(lc_graph->lock), lc_graph->lock_flags);
    return NULL;
}


#else /* ROUTE_CACHE */
/*
 * old "route cache" code
 */


void check_send_q() {

  int i;
  struct rt_entry * rt;
  int q_len = skb_queue_len(&send_q);
  struct sk_buff * skbptr;
  struct send_info * sendinfo;

  /* see if we can send anything in the send queue */
  for (i = 0;i < q_len;i++) {
    skbptr = skb_dequeue(&send_q);
    sendinfo = (struct send_info *) skbptr->cb;
    DSR_ASSERT(skbptr != NULL);
    rt = lookup_route(rt_cache, sendinfo->fwdaddr);
    if (rt != NULL) {
      del_timer(&(sendinfo->timer));
      /* dsr_send should free the skb */
      dsr_send(&skbptr, rt, sendinfo->output);
    } else {
      skb_queue_tail(&send_q, skbptr);
    }
  }
}


void remove_route(struct rt_entry * rt_cache, __u32 srchop, __u32 dsthop) {

  int i, j;
  stat_remove_rt++;
#ifdef DSR_DEBUG
  printk("rmv rt, srchop:%u.%u.%u.%u dsthop:%u.%u.%u.%u\n", NIPQUAD(srchop),
	 NIPQUAD(dsthop));
#endif
  /*
   * brute force search for now, hash table later
   * remove every entry with srchop and dsthop
   */
  for (i = 0;i < ROUTE_CACHE_SIZE;i++) {
    if (rt_cache[i].dst != 0) {
      if (CURRENT_TIME - rt_cache[i].time < ROUTE_CACHE_TIMEOUT) {
	int finished;
	/* start - end - middle */
	if (rt_cache[i].segs_left == 0) {
	  if(IPADDR == srchop && rt_cache[i].dst == dsthop) {
	    rt_cache[i].dst = 0;
	    rt_cache_size--;
	  }
	} else if (IPADDR == srchop && rt_cache[i].addr[0] == dsthop) {
	  rt_cache[i].dst = 0;
	  rt_cache_size--;
	} else if (rt_cache[i].addr[rt_cache[i].segs_left - 1] == srchop
		   && rt_cache[i].dst == dsthop) {
	  rt_cache[i].dst = 0;
	  rt_cache_size--;
	} else {
	  finished = 0;
	  for (j = 0;j < rt_cache[i].segs_left && finished == 0;j++) {
	    if (rt_cache[i].addr[j] == srchop &&
		rt_cache[i].addr[j+1] == dsthop) {
	      rt_cache[i].dst = 0;
	      rt_cache_size--;
	      finished = 1;
	    }
	  }
	}
      } else {
	/* timed out entry, clean it */
	rt_cache[i].dst = 0;
	rt_cache_size--;
      }
    }
  }
}


void add_new_forward_route(__u32 daddr, __u32 * addr, int n_addr) {

  struct rt_entry * rt;
  int i;

  if (rt_cache_size < ROUTE_CACHE_SIZE) {
    int finished = 0;
    rt = rt_cache;
    /* create new entry */
    for (i = 0;i < ROUTE_CACHE_SIZE && finished == 0;i++) {
      if (rt_cache[i].dst == 0) {
	finished = 1;
	rt = rt_cache + i;
      }
    }
    DSR_ASSERT(finished == 1);
    rt_cache_size++;
  } else {
    int oldest = 0;
    /* replace oldest entry */
    for (i = 1;i < ROUTE_CACHE_SIZE;i++) {
      if (rt_cache[i].dst != 0) {
	if (rt_cache[i].time < rt_cache[oldest].time) {
	  oldest = i;
	}
      }
    }
    rt = rt_cache + oldest;
  }

  rt->dst = daddr;
  rt->time = CURRENT_TIME;
  rt->segs_left = n_addr;

  for (i = 0;i < n_addr;i++)
    rt->addr[i] = addr[i];
}

void add_new_reverse_route(__u32 daddr, __u32 * addr, int n_addr) {

  struct rt_entry * rt;
  int i;

  if (rt_cache_size < ROUTE_CACHE_SIZE) {
    int finished = 0;
    rt = rt_cache;
    /* create new entry */
    for (i = 0;i < ROUTE_CACHE_SIZE && finished == 0;i++) {
      if (rt_cache[i].dst == 0) {
	finished = 1;
	rt = rt_cache + i;
      }
    }
    DSR_ASSERT(finished == 1);
    rt_cache_size++;
  } else {
    int oldest = 0;
    /* replace oldest entry */
    for (i = 1;i < ROUTE_CACHE_SIZE;i++) {
      if (rt_cache[i].dst != 0) {
	if (rt_cache[i].time < rt_cache[oldest].time) {
	  oldest = i;
	}
      }
    }
    rt = rt_cache + oldest;
  }

  rt->dst = daddr;
  rt->time = CURRENT_TIME;
  rt->segs_left = n_addr;

  for (i = 0;i < n_addr;i++)
    rt->addr[i] = addr[n_addr - i - 1];
}

int have_route(__u32 daddr, __u32 * addr, int n_addr) {

  int i, j;

  /* look through all the routes to see if we already have the SAME route */
  for (i = 0;i < ROUTE_CACHE_SIZE;i++) {
    if (rt_cache[i].dst != 0) {
      if (CURRENT_TIME - rt_cache[i].time < ROUTE_CACHE_TIMEOUT) {
	if (rt_cache[i].dst == daddr && rt_cache[i].segs_left == n_addr) {
	  int diff = 0;
	  for (j = 0;j < n_addr && diff == 0;j++) {
	    /* route is different */
	    if (rt_cache[i].addr[j] != addr[j])
	      diff = 1;
	  }
	  /* if the route is the same then we return */
	  if (diff == 0) {
	    rt_cache[i].time = CURRENT_TIME;
	    return 1;
	  }
	}
      } else {
	/* timed out entry, clean it */
	rt_cache[i].dst = 0;
	rt_cache_size--;
      }
    }
  }
  return 0;
}

/* addr can be null */
void add_reverse_route(__u32 daddr, __u32 * addr, int n_addr) {

  int i;
  stat_add_r_rt++;

  for (i = 0;i < n_addr;i++) {
    if (have_route(addr[n_addr - i - 1], addr + n_addr - i, i) == 0) {
      add_new_reverse_route(addr[n_addr - i - 1], addr + n_addr - i, i);
      remove_rt_req_id(addr[n_addr - i - 1]);
    }
  }

  if (DSR_SUBNET(daddr) && have_route(daddr, addr, n_addr) == 0) {
    add_new_reverse_route(daddr, addr, n_addr);
    remove_rt_req_id(daddr);
  }
  check_send_q();
}

/* addr can be null */
void add_forward_route(__u32 daddr, __u32 * addr, int n_addr) {

  int i;
  stat_add_f_rt++;

  for (i = 0;i < n_addr;i++) {
    if (have_route(addr[i], addr + i - 1, i) == 0) {
      add_new_forward_route(addr[i], addr + i - 1, i);
      remove_rt_req_id(addr[i]);
    }
  }

  if (DSR_SUBNET(daddr) && have_route(daddr, addr, n_addr) == 0) {
    add_new_forward_route(daddr, addr, n_addr);
    remove_rt_req_id(daddr);
  }
  check_send_q();
}

struct rt_entry * lookup_route(struct rt_entry * rt_cache, __u32 dst) {

  int i;
  struct rt_entry * rt_ptr = NULL;

  /*
   * brute force search for now, hash table later
   * the most current entry with the smallest hop count will be used
   */
  for (i = 0;i < ROUTE_CACHE_SIZE;i++) {
    if (rt_cache[i].dst != 0) {
      if (CURRENT_TIME - rt_cache[i].time < ROUTE_CACHE_TIMEOUT) {
	if (rt_cache[i].dst == dst) {
	  /* found an entry */
	  if (rt_ptr == NULL)
	    rt_ptr = rt_cache + i;
	  else if (rt_cache[i].segs_left < rt_ptr->segs_left)
	    rt_ptr = rt_cache + i;
	  else if (rt_cache[i].segs_left == rt_ptr->segs_left)
	    if (time_after(rt_cache[i].time, rt_ptr->time))
	      rt_ptr = rt_cache + i;
	}
      } else {
	/* timed out entry, clean it */
	rt_cache[i].dst = 0;
	rt_cache_size--;
      }
    }
  }

  /* refresh the route we found */
  if (rt_ptr != NULL)
    rt_ptr->time = CURRENT_TIME;

  return rt_ptr;
}
#endif
