/*
 * The olsr.org Optimized Link-State Routing daemon(olsrd)
 * Copyright (c) 2004, Thomas Lopatic (thomas@lopatic.de)
 * IPv4 performance optimization (c) 2006, sven-ola(gmx.de)
 * SPF implementation (c) 2007, Hannes Gredler (hannes@gredler.at)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met:
 *
 * * Redistributions of source code must retain the above copyright 
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright 
 *   notice, this list of conditions and the following disclaimer in 
 *   the documentation and/or other materials provided with the 
 *   distribution.
 * * Neither the name of olsr.org, olsrd nor the names of its 
 *   contributors may be used to endorse or promote products derived 
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Visit http://www.olsr.org for more information.
 *
 * If you find this software useful feel free to make a donation
 * to the project. For more information see the website or contact
 * the copyright holders.
 *
 * $Id: lq_route.c,v 1.53 2007/10/16 09:54:43 bernd67 Exp $
 */

#include "defs.h"
#include "olsr.h"
#include "tc_set.h"
#include "neighbor_table.h"
#include "two_hop_neighbor_table.h"
#include "link_set.h"
#include "routing_table.h"
#include "mid_set.h"
#include "hna_set.h"
#include "lq_list.h"
#include "lq_avl.h"
#include "lq_route.h"

/*
 * avl_comp_etx
 *
 * compare two etx metrics.
 * return 0 if there is an exact match and
 * -1 / +1 depending on being smaller or bigger.
 * note that this results in the most optimal code
 * after compiler optimization.
 */
static int
avl_comp_etx (void *etx1, void *etx2)
{       
  if (*(float *)etx1 < *(float *)etx2) {
    return -1;
  }

  if (*(float *)etx1 > *(float *)etx2) {
    return +1;
  }

  return 0;
}

/*
 * olsr_spf_add_cand_tree
 *
 * Key an existing vertex to a candidate tree.
 */
static void
olsr_spf_add_cand_tree (struct avl_tree *tree,
                        struct tc_entry *vert)
{
  vert->cand_tree_node.key = &vert->path_etx;
  vert->cand_tree_node.data = vert;

#ifdef DEBUG
  OLSR_PRINTF(1, "SPF: insert candidate %s, cost %f\n",
              olsr_ip_to_string(&(vert->addr)),
              vert->path_etx);
#endif

  avl_insert(tree, &vert->cand_tree_node, AVL_DUP);
}

/*
 * olsr_spf_del_cand_tree
 *
 * Unkey an existing vertex from a candidate tree.
 */
static void
olsr_spf_del_cand_tree (struct avl_tree *tree,
                        struct tc_entry *vert)
{

#ifdef DEBUG
  OLSR_PRINTF(1, "SPF: delete candidate %s, cost %f\n",
              olsr_ip_to_string(&(vert->addr)),
              vert->path_etx);
#endif

  avl_delete(tree, &vert->cand_tree_node);
}

/*
 * olsr_spf_add_path_list
 *
 * Insert an SPF result at the end of the path list.
 */
static void
olsr_spf_add_path_list (struct list_node *head,
                        int *path_count,
                        struct tc_entry *vert)
{
  vert->path_list_node.data = vert;

#ifdef DEBUG
  OLSR_PRINTF(1, "SPF: append path %s, cost %f, via %s\n",
              olsr_ip_to_string(&(vert->addr)),
              vert->path_etx,
              olsr_ip_to_string(vert->next_hop ? &vert->next_hop->neighbor_iface_addr : NULL));
#endif

  list_add_before(head, &vert->path_list_node);
  *path_count = *path_count + 1;
}

/*
 * olsr_spf_extract_best
 *
 * return the node with the minimum pathcost.
 */
static struct tc_entry *
olsr_spf_extract_best (struct avl_tree *tree)
{
  struct avl_node *node;

  node = avl_walk_first(tree);

  return (node ? node->data :  NULL);
}


char *olsr_etx_to_string(float etx)
{
  static char buff[20];

  if (etx == INFINITE_ETX)
    return "INF";

  snprintf(buff, 20, "%.6f", etx);
  return buff;
}


/*
 * olsr_spf_relax
 *
 * Explore all edges of a node and add the node
 * to the candidate tree if the if the aggregate
 * path cost is better.
 */
static void
olsr_spf_relax (struct avl_tree *cand_tree, struct tc_entry *vert)
{
  struct tc_entry *new_vert;
  struct tc_edge_entry *tc_edge;
  struct avl_node *edge_node;
  float new_etx;

#ifdef DEBUG
  OLSR_PRINTF(1, "SPF: exploring node %s, cost %f\n",
              olsr_ip_to_string(&(vert->addr)),
              vert->path_etx);
#endif

  /*
   * loop through all edges of this vertex.
   */
  for (edge_node = avl_walk_first(&vert->edge_tree);
       edge_node;
       edge_node = avl_walk_next(edge_node)) {

    tc_edge = edge_node->data;

    /*
     * We are not interested in dead-end or dying edges.
     */
    if (!tc_edge->edge_inv || (tc_edge->flags & OLSR_TC_EDGE_DOWN)) {
#ifdef DEBUG
      OLSR_PRINTF(1, "SPF:   ignoring edge %s\n",
                  olsr_ip_to_string(&tc_edge->T_dest_addr));
      if (tc_edge->flags & OLSR_TC_EDGE_DOWN) {
        OLSR_PRINTF(1, "SPF:     edge down\n");
      }
      if (!tc_edge->edge_inv) {
        OLSR_PRINTF(1, "SPF:     no inverse edge\n");
      }
#endif
      continue;
    }

    /*
     * total quality of the path through this vertex
     * to the destination of this edge
     */
    new_etx = vert->path_etx + tc_edge->etx;

#ifdef DEBUG
      OLSR_PRINTF(1, "SPF:   exploring edge %s, cost %s\n",
                  olsr_ip_to_string(&(tc_edge->T_dest_addr)),
                  olsr_etx_to_string(new_vert->path_etx));
#endif

      /* 
       * if it's better than the current path quality of this edge's
       * destination node, then we've found a better path to this node.
       */
    new_vert = tc_edge->edge_inv->tc;
    if (new_etx < new_vert->path_etx) {

      /* if this node has been on the candidate tree delete it */
      if (new_vert->path_etx != INFINITE_ETX) {
        olsr_spf_del_cand_tree(cand_tree, new_vert);
      }

      /* re-insert on candidate tree with the better metric */
      new_vert->path_etx = new_etx;
      olsr_spf_add_cand_tree(cand_tree, new_vert);

      /* pull-up the next-hop and bump the hop count */
      if (vert->next_hop) {
        new_vert->next_hop = vert->next_hop;
      }
      new_vert->hops = vert->hops + 1;

#ifdef DEBUG
      OLSR_PRINTF(1, "SPF:   better path to %s, cost %s -> %s, via %s, hops %u\n",
                  olsr_ip_to_string(&new_vert->addr),
                  olsr_etx_to_string(new_vert->path_etx),
                  olsr_etx_to_string(new_etx),
                  olsr_ip_to_string(vert->next_hop ?
                                    &vert->next_hop->neighbor_iface_addr : NULL),
                  new_vert->->hops);
#endif

    }
  }
}

/*
 * olsr_spf_run_full
 *
 * Run the Dijkstra algorithm.
 * 
 * A node gets added to the candidate tree when one of its edges has
 * an overall better root path cost than the node itself.
 * The node with the shortest metric gets moved from the candidate to
 * the path list every pass.
 * The SPF computation is completed when there are no more nodes
 * on the candidate tree. 
 */ 
static void
olsr_spf_run_full (struct avl_tree *cand_tree, struct list_node *path_list,
                   int *path_count)
{
  struct tc_entry *vert;

  *path_count = 0;

  while ((vert = olsr_spf_extract_best(cand_tree))) {

    olsr_spf_relax(cand_tree, vert);

    /*
     * move the best path from the candidate tree
     * to the path list.
     */
    olsr_spf_del_cand_tree(cand_tree, vert);
    olsr_spf_add_path_list(path_list, path_count, vert);
  }
}

void
olsr_calculate_routing_table (void)
{
  struct avl_tree cand_tree;
  struct list_node path_list;
  int i, plen, path_count = 0;
  struct tc_entry *tc;
  struct tc_edge_entry *tc_edge;
  struct tc_entry *vert;
  struct neighbor_entry *neigh;
  struct mid_address *mid_walker;
  struct hna_entry *hna_gw;
  struct hna_net *hna;
  struct interface *inter;
  struct link_entry *link;

#ifdef SPF_PROFILING
  struct timeval t1, t2, t3, t4, t5, spf_init, spf_run, route, kernel, total;

  gettimeofday(&t1, NULL);
#endif

  /*
   * Prepare the candidate tree and result list.
   */
  avl_init(&cand_tree, avl_comp_etx);
  list_head_init(&path_list);
  olsr_bump_routingtree_version();

  /*
   * Initialize vertices in the lsdb.
   */
  OLSR_FOR_ALL_TC_ENTRIES(tc) {
    tc->next_hop = NULL;
    tc->path_etx = INFINITE_ETX;
    tc->hops = 0;
  } OLSR_FOR_ALL_TC_ENTRIES_END(tc);

  /*
   * zero ourselves and add us to the candidate tree.
   */
  olsr_change_myself_tc();
  tc_myself->path_etx = ZERO_ETX;
  olsr_spf_add_cand_tree(&cand_tree, tc_myself);

  /*
   * add edges to and from our neighbours.
   */
  for (i = 0; i < HASHSIZE; i++)
    for (neigh = neighbortable[i].next; neigh != &neighbortable[i];
         neigh = neigh->next) {

      if (neigh->status == SYM) {

        tc_edge = olsr_lookup_tc_edge(tc_myself, &neigh->neighbor_main_addr);
        link = get_best_link_to_neighbor(&neigh->neighbor_main_addr);
	if (!link) {

          /*
           * If there is no best link to this neighbor
           * and we had an edge before then flush the edge.
           */
          if (tc_edge) {
            olsr_delete_tc_edge_entry(tc_edge);
          }
	  continue;
        }

        /*
         * Set the next-hops of our neighbors. 
         */
        if (!tc_edge) {
          tc_edge = olsr_add_tc_edge_entry(tc_myself, &neigh->neighbor_main_addr,
                                           0, link->last_htime,
                                           link->loss_link_quality2,
                                           link->neigh_link_quality2);
        } else {
          tc_edge->link_quality = link->loss_link_quality2;
          tc_edge->inverse_link_quality = link->neigh_link_quality2;
          olsr_calc_tc_edge_entry_etx(tc_edge);
        }
        if (tc_edge->edge_inv) {
          tc_edge->edge_inv->tc->next_hop = link;
        }
      }
    }

#ifdef SPF_PROFILING
  gettimeofday(&t2, NULL);
#endif

  /*
   * Run the SPF calculation.
   */
  olsr_spf_run_full(&cand_tree, &path_list, &path_count);

  OLSR_PRINTF(2, "\n--- %02d:%02d:%02d.%02d ------------------------------------------------- DIJKSTRA\n\n",
              nowtm->tm_hour,
              nowtm->tm_min,
              nowtm->tm_sec,
              (int)now.tv_usec/10000);

#ifdef SPF_PROFILING
  gettimeofday(&t3, NULL);
#endif

  /*
   * In the path tree we have all the reachable nodes in our topology.
   */
  for (; !list_is_empty(&path_list); list_remove(path_list.next)) {

    vert = path_list.next->data;
    link = vert->next_hop;

    if (!link) {
      OLSR_PRINTF(2, "%s no next-hop\n", olsr_ip_to_string(&vert->addr));
      continue;
    }

    /* find the interface for the found link */
    inter = link->if_name ? if_ifwithname(link->if_name)
      : if_ifwithaddr(&link->local_iface_addr);

    /* interface is up ? */
    if (inter) {

      /* add a route to the main address of the destination node */
      olsr_insert_routing_table(&vert->addr, olsr_cnf->maxplen, &vert->addr,
                                &link->neighbor_iface_addr, inter->if_index,
                                vert->hops, vert->path_etx);

      /* add routes to the remaining interfaces of the destination node */
      for (mid_walker = mid_lookup_aliases(&vert->addr);
           mid_walker != NULL;
           mid_walker = mid_walker->next_alias) {

        olsr_insert_routing_table(&mid_walker->alias, olsr_cnf->maxplen, &vert->addr,
                                  &link->neighbor_iface_addr, inter->if_index,
                                  vert->hops, vert->path_etx);
      }

      /* find the node's HNAs */
      hna_gw = olsr_lookup_hna_gw(&vert->addr);

      /* node doesn't announce any HNAs */
      if (!hna_gw) {
        continue;
      }

      /* loop through the node's HNAs */
      for (hna = hna_gw->networks.next;
           hna != &hna_gw->networks;
           hna = hna->next) {

        plen = olsr_get_hna_prefix_len(hna);
        if (vert->path_etx != INFINITE_ETX)
        olsr_insert_routing_table(&hna->A_network_addr, plen, &vert->addr,
                                  &link->neighbor_iface_addr, inter->if_index,
                                  vert->hops, vert->path_etx);
      }
    }
  }

#ifdef SPF_PROFILING
  gettimeofday(&t4, NULL);
#endif

  /* move the route changes into the kernel */

  olsr_update_kernel_routes();

#ifdef SPF_PROFILING
  gettimeofday(&t5, NULL);
#endif

#ifdef SPF_PROFILING
  timersub(&t2, &t1, &spf_init);
  timersub(&t3, &t2, &spf_run);
  timersub(&t4, &t3, &route);
  timersub(&t5, &t4, &kernel);
  timersub(&t5, &t1, &total);
  olsr_printf(1, "\n--- SPF-stats for %d nodes, %d routes (total/init/run/route/kern): "
              "%d, %d, %d, %d, %d\n",
              path_count, routingtree.count,
              (int)total.tv_usec, (int)spf_init.tv_usec, (int)spf_run.tv_usec,
              (int)route.tv_usec, (int)kernel.tv_usec);
#endif
}

/*
 * Local Variables:
 * c-basic-offset: 2
 * End:
 */


syntax highlighted by Code2HTML, v. 0.9.1