/*
 *	BIRD -- OSPF Topological Database
 *
 *	(c) 1999       Martin Mares <mj@ucw.cz>
 *	(c) 1999--2004 Ondrej Filip <feela@network.cz>
 *
 *	Can be freely distributed and used under the terms of the GNU GPL.
 */

#include "nest/bird.h"
#include "lib/string.h"

#include "ospf.h"

#define HASH_DEF_ORDER 6
#define HASH_HI_MARK *4
#define HASH_HI_STEP 2
#define HASH_HI_MAX 16
#define HASH_LO_MARK /5
#define HASH_LO_STEP 2
#define HASH_LO_MIN 8

static void *
originate_rt_lsa_body(struct ospf_area *oa, u16 * length)
{
  struct proto_ospf *po = oa->po;
  struct ospf_iface *ifa;
  int j = 0, k = 0;
  u16 i = 0;
  struct ospf_lsa_rt *rt;
  struct ospf_lsa_rt_link *ln;
  struct ospf_neighbor *neigh;

  DBG("%s: Originating RT_lsa body for area \"%I\".\n", po->proto.name,
      oa->areaid);

  WALK_LIST(ifa, po->iface_list)
  {
    if ((ifa->oa == oa) && (ifa->state != OSPF_IS_DOWN))
    {
      i++;
    }
  }
  rt = mb_allocz(po->proto.pool, sizeof(struct ospf_lsa_rt) +
		 i * sizeof(struct ospf_lsa_rt_link));
  if (po->areano > 1)
    rt->veb.bit.b = 1;
  if ((po->ebit) && (!oa->stub))
    rt->veb.bit.e = 1;
  ln = (struct ospf_lsa_rt_link *) (rt + 1);

  WALK_LIST(ifa, po->iface_list)
  {
    if ((ifa->type == OSPF_IT_VLINK) && (ifa->voa == oa) && (!EMPTY_LIST(ifa->neigh_list)))
    {
      neigh = (struct ospf_neighbor *) HEAD(ifa->neigh_list);
      if ((neigh->state == NEIGHBOR_FULL) && (ifa->cost <= 0xffff))
        rt->veb.bit.v = 1;
    }

    if ((ifa->oa != oa) || (ifa->state == OSPF_IS_DOWN))
      continue;

    if (ifa->state == OSPF_IS_LOOP)
    {
      ln->type = 3;
      ln->id = ipa_to_u32(ifa->iface->addr->ip);
      ln->data = 0xffffffff;
      ln->metric = 0;
      ln->notos = 0;
    }
    else
    {
      switch (ifa->type)
      {
      case OSPF_IT_PTP:	/* rfc2328 - pg126 */
	neigh = (struct ospf_neighbor *) HEAD(ifa->neigh_list);
	if ((!EMPTY_LIST(ifa->neigh_list)) && (neigh->state == NEIGHBOR_FULL))
	{
	  ln->type = LSART_PTP;
	  ln->id = neigh->rid;
	  ln->metric = ifa->cost;
	  ln->notos = 0;
	  if (ifa->iface->flags && IA_UNNUMBERED)
	  {
	    ln->data = ifa->iface->index;
	  }
	  else
	  {
	    ln->data = ipa_to_u32(ifa->iface->addr->ip);
	  }
	}
	else
	{
	  if (ifa->state == OSPF_IS_PTP)
	  {
	    ln->type = LSART_STUB;
	    ln->id = ln->id = ipa_to_u32(ifa->iface->addr->opposite);
	    ln->metric = ifa->cost;
	    ln->notos = 0;
	    ln->data = 0xffffffff;
	  }
	  else
          {
            ln--;
	    i--;		/* No link added */
          }
	}
	break;
      case OSPF_IT_BCAST:
      case OSPF_IT_NBMA:
	if (ifa->state == OSPF_IS_WAITING)
	{
	  ln->type = LSART_STUB;
	  ln->data = ipa_to_u32(ipa_mkmask(ifa->iface->addr->pxlen));
	  ln->id = ipa_to_u32(ifa->iface->addr->prefix) & ln->data;
	  ln->metric = ifa->cost;
	  ln->notos = 0;
	}
	else
	{
	  j = 0, k = 0;
	  WALK_LIST(neigh, ifa->neigh_list)
	  {
	    if ((neigh->rid == ifa->drid) && (neigh->state == NEIGHBOR_FULL))
	      k = 1;
	    if (neigh->state == NEIGHBOR_FULL)
	      j = 1;
	  }
	  if (((ifa->state == OSPF_IS_DR) && (j == 1)) || (k == 1))
	  {
	    ln->type = LSART_NET;
	    ln->id = ipa_to_u32(ifa->drip);
	    ln->data = ipa_to_u32(ifa->iface->addr->ip);
	    ln->metric = ifa->cost;
	    ln->notos = 0;
	  }
	  else
	  {
	    ln->type = LSART_STUB;
	    ln->data = ipa_to_u32(ipa_mkmask(ifa->iface->addr->pxlen));
	    ln->id = ipa_to_u32(ifa->iface->addr->prefix) & ln->data;
	    ln->metric = ifa->cost;
	    ln->notos = 0;
	  }
	}
	break;
      case OSPF_IT_VLINK:
	neigh = (struct ospf_neighbor *) HEAD(ifa->neigh_list);
	if ((!EMPTY_LIST(ifa->neigh_list)) && (neigh->state == NEIGHBOR_FULL) && (ifa->cost <= 0xffff))
	{
	  ln->type = LSART_VLNK;
	  ln->id = neigh->rid;
	  ln->metric = ifa->cost;
	  ln->notos = 0;
        }
	else
        {
          ln--;
	  i--;		/* No link added */
        }
        break;
      default:
        ln--;
	i--;		/* No link added */
        log("Unknown interface type %s", ifa->iface->name);
        break;
      }
    }
    ln++;
  }
  rt->links = i;
  *length = i * sizeof(struct ospf_lsa_rt_link) + sizeof(struct ospf_lsa_rt) +
    sizeof(struct ospf_lsa_header);
  return rt;
}

/**
 * originate_rt_lsa - build new instance of router LSA
 * @oa: ospf_area which is LSA built to
 *
 * It builds router LSA walking through all OSPF interfaces in
 * specified OSPF area. This function is mostly called from
 * area_disp(). Builds new LSA, increases sequence number (if old
 * instance exists) and sets age of LSA to zero.
 */
void
originate_rt_lsa(struct ospf_area *oa)
{
  struct ospf_lsa_header lsa;
  struct proto_ospf *po = oa->po;
  struct proto *p = &po->proto;
  u32 rtid = po->proto.cf->global->router_id;
  struct top_hash_entry *en;
  void *body;

  if ((oa->rt) && ((oa->rt->inst_t + MINLSINTERVAL)) > now)
    return;
  /*
   * Tick is probably set to very low value. We cannot
   * originate new LSA before MINLSINTERVAL. We will
   * try to do it next tick.
   */

  OSPF_TRACE(D_EVENTS, "Originating RT_lsa for area \"%I\".", oa->areaid);

  lsa.age = 0;
  lsa.id = rtid;
  lsa.type = LSA_T_RT;
  lsa.rt = rtid;
  lsa.options = oa->opt.byte;
  if (oa->rt == NULL)
  {
    lsa.sn = LSA_INITSEQNO;
  }
  else
  {
    lsa.sn = oa->rt->lsa.sn + 1;
  }
  body = originate_rt_lsa_body(oa, &lsa.length);
  lsasum_calculate(&lsa, body);
  en = lsa_install_new(&lsa, body, oa);
  oa->rt = en;
  ospf_lsupd_flood(NULL, NULL, &oa->rt->lsa, NULL, oa, 1);
  schedule_rtcalc(po);
  oa->origrt = 0;
}

static void *
originate_net_lsa_body(struct ospf_iface *ifa, u16 * length,
		       struct proto_ospf *po)
{
  u16 i = 1;
  struct ospf_neighbor *n;
  u32 *body;
  struct ospf_lsa_net *net;

  net = mb_alloc(po->proto.pool, sizeof(u32) * (ifa->fadj + 1) +
		 sizeof(struct ospf_lsa_net));
  net->netmask = ipa_mkmask(ifa->iface->addr->pxlen);

  body = (u32 *) (net + 1);
  i = 1;
  *body = po->proto.cf->global->router_id;
  WALK_LIST(n, ifa->neigh_list)
  {
    if (n->state == NEIGHBOR_FULL)
    {
      *(body + i) = n->rid;
      i++;
    }
  }
  *length = i * sizeof(u32) + sizeof(struct ospf_lsa_header) +
    sizeof(struct ospf_lsa_net);
  return net;
}

/**
 * originate_net_lsa - originates of deletes network LSA
 * @ifa: interface which is LSA originated for
 *
 * Interface counts number of adjacent neighbors. If this number is
 * lower than one or interface is not in state %OSPF_IS_DR it deletes
 * and premature ages instance of network LSA for specified interface.
 * In other case, new instance of network LSA is originated.
 */
void
originate_net_lsa(struct ospf_iface *ifa)
{
  struct proto_ospf *po = ifa->oa->po;
  struct ospf_lsa_header lsa;
  u32 rtid = po->proto.cf->global->router_id;
  struct proto *p = &po->proto;
  void *body;

  if (ifa->nlsa && ((ifa->nlsa->inst_t + MINLSINTERVAL) > now))
    return;
  /*
   * It's too early to originate new network LSA. We will
   * try to do it next tick
   */

  if ((ifa->state != OSPF_IS_DR) || (ifa->fadj == 0))
  {
    if (ifa->nlsa == NULL)
      return;

    OSPF_TRACE(D_EVENTS, "Deleting Net lsa for iface \"%s\".",
	       ifa->iface->name);
    ifa->nlsa->lsa.sn += 1;
    ifa->nlsa->lsa.age = LSA_MAXAGE;
    ospf_lsupd_flood(NULL, NULL, &ifa->nlsa->lsa, NULL, ifa->oa, 0);
    s_rem_node(SNODE ifa->nlsa);
    if (ifa->nlsa->lsa_body != NULL)
      mb_free(ifa->nlsa->lsa_body);
    ifa->nlsa->lsa_body = NULL;
    ospf_hash_delete(po->gr, ifa->nlsa);
    schedule_rtcalc(po);
    ifa->nlsa = NULL;
    return;
  }

  OSPF_TRACE(D_EVENTS, "Originating Net lsa for iface \"%s\".",
	     ifa->iface->name);

  lsa.age = 0;
  lsa.id = ipa_to_u32(ifa->iface->addr->ip);
  lsa.type = LSA_T_NET;
  lsa.rt = rtid;
  lsa.options = ifa->oa->opt.byte;
  if (ifa->nlsa == NULL)
  {
    lsa.sn = LSA_INITSEQNO;
  }
  else
  {
    lsa.sn = ifa->nlsa->lsa.sn + 1;
  }

  body = originate_net_lsa_body(ifa, &lsa.length, po);
  lsasum_calculate(&lsa, body);
  ifa->nlsa = lsa_install_new(&lsa, body, ifa->oa);
  ospf_lsupd_flood(NULL, NULL, &ifa->nlsa->lsa, NULL, ifa->oa, 1);
  ifa->orignet = 0;
}


static void *
originate_ext_lsa_body(net * n, rte * e, struct proto_ospf *po,
		       struct ea_list *attrs)
{
  struct proto *p = &po->proto;
  struct ospf_lsa_ext *ext;
  struct ospf_lsa_ext_tos *et;
  u32 m1 = ea_get_int(attrs, EA_OSPF_METRIC1, LSINFINITY);
  u32 m2 = ea_get_int(attrs, EA_OSPF_METRIC2, 10000);
  u32 tag = ea_get_int(attrs, EA_OSPF_TAG, 0);
  int inas = 0;

  ext = mb_alloc(p->pool, sizeof(struct ospf_lsa_ext) +
		 sizeof(struct ospf_lsa_ext_tos));
  ext->netmask = ipa_mkmask(n->n.pxlen);

  et = (struct ospf_lsa_ext_tos *) (ext + 1);

  if (m1 != LSINFINITY)
  {
    et->etm.metric = m1;
    et->etm.etos.tos = 0;
    et->etm.etos.ebit = 0;
  }
  else
  {
    et->etm.metric = m2;
    et->etm.etos.tos = 0;
    et->etm.etos.ebit = 1;
  }
  et->tag = tag;
  if (!ipa_equal(e->attrs->gw, IPA_NONE))
  {
    if (ospf_iface_find((struct proto_ospf *) p, e->attrs->iface) != NULL)
      inas = 1;
  }

  if (!inas)
    et->fwaddr = IPA_NONE;
  else
    et->fwaddr = e->attrs->gw;
  return ext;
}

/**
 * max_ext_lsa - calculate the maximum amount of external networks
 * possible for the given prefix length.
 * @pxlen: network prefix length
 *
 * This is a fix for the previous static use of MAXNETS which did
 * only work correct if MAXNETS < possible IPs for given prefix.
 * This solution is kind of a hack as there can now only be one
 * route for /32 type entries but this is better than the crashes
 * I did experience whith close together /32 routes originating
 * on different hosts.
 */

int
max_ext_lsa(unsigned pxlen)
{
  int i;
  for (i = 1; pxlen < BITS_PER_IP_ADDRESS; pxlen++, i <<= 1)
    if (i >= MAXNETS)
      return MAXNETS;
  return i;
}

void
flush_sum_lsa(struct ospf_area *oa, struct fib_node *fn, int type)
{
  struct proto_ospf *po = oa->po;
  struct proto *p = &po->proto;
  struct top_hash_entry *en;
  u32 rtid = po->proto.cf->global->router_id;
  struct ospf_lsa_header lsa;
  int max, i;
  struct ospf_lsa_sum *sum = NULL;

  lsa.rt = rtid;
  lsa.type = LSA_T_SUM_NET;
  if (type == ORT_ROUTER)
    lsa.type = LSA_T_SUM_RT;

  max = max_ext_lsa(fn->pxlen);

  for (i = 0; i < max; i++)
  {
    lsa.id = ipa_to_u32(fn->prefix) + i;
    if ((en = ospf_hash_find_header(po->gr, oa->areaid, &lsa)) != NULL)
    {
      sum = en->lsa_body;
      if ((type == ORT_ROUTER) || (fn->pxlen == ipa_mklen(sum->netmask)))
      {
        en->lsa.age = LSA_MAXAGE;
        en->lsa.sn = LSA_MAXSEQNO;
        lsasum_calculate(&en->lsa, sum);
        OSPF_TRACE(D_EVENTS, "Flushing summary lsa. (id=%I, type=%d)", en->lsa.id, en->lsa.type);
        ospf_lsupd_flood(NULL, NULL, &en->lsa, NULL, oa, 1);
        if (can_flush_lsa(po)) flush_lsa(en, po);
        break;
      }
    }
  }
}

void
originate_sum_lsa(struct ospf_area *oa, struct fib_node *fn, int type, int metric)
{
  struct proto_ospf *po = oa->po;
  struct proto *p = &po->proto;
  struct top_hash_entry *en;
  u32 rtid = po->proto.cf->global->router_id;
  struct ospf_lsa_header lsa;
  int i, max, mlen = fn->pxlen, free = 0;
  u32 freeid = 0xFFFF;
  struct ospf_lsa_sum *sum = NULL;
  union ospf_lsa_sum_tm *tm;
  lsa.type = LSA_T_SUM_NET;

  if (type == ORT_ROUTER)
  {
    lsa.type = LSA_T_SUM_RT;
    mlen = 0;
  }

  lsa.age = 0;
  lsa.rt = rtid;
  lsa.sn = LSA_INITSEQNO;
  lsa.length = sizeof(struct ospf_lsa_sum) + sizeof(union ospf_lsa_sum_tm) +
    sizeof(struct ospf_lsa_header);
  lsa.options = oa->opt.byte;

  max = max_ext_lsa(fn->pxlen);
  for (i = 0; i < max; i++)
  {
    lsa.id = ipa_to_u32(fn->prefix) + i;
    if ((en = ospf_hash_find_header(po->gr, oa->areaid, &lsa)) == NULL)
    {
      if (!free)
      {
        freeid = lsa.id;
	free = 1;
      }
    }
    else
    {
      sum = en->lsa_body;
      if (mlen == ipa_mklen(sum->netmask))
      {
        tm = (union ospf_lsa_sum_tm *) (sum + 1);
        if (tm->metric == (unsigned) metric) return;	/* No reason for origination */
        lsa.sn = en->lsa.sn + 1;
        freeid = en->lsa.id;
	free = 1;
        break;
      }
    }
  }

  if(!free)
  {
    log("%s: got more routes for one /%d network then %d, ignoring", p->name,
        fn->pxlen, max);
    return;
  }
  lsa.id = freeid;
  
  OSPF_TRACE(D_EVENTS, "Originating summary (type %d) lsa for %I/%d (met %d).", lsa.type, fn->prefix,
             fn->pxlen, metric);

  sum = mb_alloc(p->pool, sizeof(struct ospf_lsa_sum) + sizeof(union ospf_lsa_sum_tm));
  sum->netmask = ipa_mkmask(mlen);
  tm = (union ospf_lsa_sum_tm *) (sum + 1);
  tm->metric = metric;
  tm->tos.tos = 0;

  lsasum_calculate(&lsa, sum);
  en = lsa_install_new(&lsa, sum, oa);
  ospf_lsupd_flood(NULL, NULL, &en->lsa, NULL, oa, 1);
}

void
check_sum_lsa(struct proto_ospf *po, ort *nf, int dest)
{
  struct ospf_area *oa;
  int flush, mlen;
  ip_addr ip;

  if (po->areano < 2) return;

  if ((nf->n.type > RTS_OSPF_IA) && (nf->o.type > RTS_OSPF_IA)) return;

#ifdef LOCAL_DEBUG
  DBG("Checking...dest = %d, %I/%d", dest, nf->fn.prefix, nf->fn.pxlen);
  if (nf->n.oa) DBG("New: met=%d, oa=%d", nf->n.metric1, nf->n.oa->areaid);
  if (nf->o.oa) DBG("Old: met=%d, oa=%d", nf->o.metric1, nf->o.oa->areaid);
#endif

  WALK_LIST(oa, po->area_list)
  {
    flush = 0;
    if ((nf->n.metric1 >= LSINFINITY) || (nf->n.type > RTS_OSPF_IA))
      flush = 1;
    if ((dest == ORT_ROUTER) && (!(nf->n.capa & ORTA_ASBR)))
      flush = 1;
    if ((!nf->n.oa) || (nf->n.oa->areaid == oa->areaid))
      flush = 1;

    if (nf->n.ifa) {
      if (nf->n.ifa->oa->areaid == oa->areaid)
        flush = 1;
      }
    else flush = 1;

    /* Don't send summary into stub areas
     * We send just default route (and later) */
    if (oa->stub)
      flush = 1;
    
    mlen = nf->fn.pxlen;
    ip = ipa_and(nf->fn.prefix, ipa_mkmask(mlen));

    if ((oa == po->backbone) && (nf->n.type == RTS_OSPF_IA)) flush = 1;	/* Only intra-area can go to the backbone */

    if ((!flush) && (dest == ORT_NET) && fib_route(&nf->n.oa->net_fib, ip, mlen))	/* The route fits into area networks */
    {
      flush = 1;
      if ((nf->n.oa == po->backbone) && (oa->trcap)) flush = 0;
    }

    if(flush)
      flush_sum_lsa(oa, &nf->fn, dest);
    else
      originate_sum_lsa(oa, &nf->fn, dest, nf->n.metric1);
  }
}

/**
 * originate_ext_lsa - new route received from nest and filters
 * @n: network prefix and mask
 * @e: rte
 * @po: current instance of OSPF
 * @attrs: list of extended attributes
 *
 * If I receive a message that new route is installed, I try to originate an
 * external LSA. The LSA header of such LSA does not contain information about
 * prefix length, so if I have to originate multiple LSAs for route with
 * different prefixes I try to increment prefix id to find a "free" one.
 *
 * The function also sets flag ebit. If it's the first time, the new router lsa
 * origination is necessary.
 */
void
originate_ext_lsa(net * n, rte * e, struct proto_ospf *po,
		  struct ea_list *attrs)
{
  struct ospf_lsa_header lsa;
  u32 rtid = po->proto.cf->global->router_id;
  struct top_hash_entry *en = NULL;
  void *body = NULL;
  struct proto *p = &po->proto;
  struct ospf_area *oa;
  struct ospf_lsa_ext *ext1, *ext2;
  int i, max;

  OSPF_TRACE(D_EVENTS, "Originating Ext lsa for %I/%d.", n->n.prefix,
	     n->n.pxlen);

  lsa.age = 0;
  lsa.id = ipa_to_u32(n->n.prefix);
  lsa.type = LSA_T_EXT;
  lsa.rt = rtid;
  lsa.sn = LSA_INITSEQNO;
  lsa.options = 0;

  body = originate_ext_lsa_body(n, e, po, attrs);
  lsa.length = sizeof(struct ospf_lsa_ext) + sizeof(struct ospf_lsa_ext_tos) +
    sizeof(struct ospf_lsa_header);
  ext1 = body;
  max = max_ext_lsa(n->n.pxlen);

  for (i = 0; i < max; i++)
  {
    if ((en = ospf_hash_find_header(po->gr, 0 , &lsa)) != NULL)
    {
      ext2 = en->lsa_body;
      if (ipa_compare(ext1->netmask, ext2->netmask) != 0)
	lsa.id++;
      else
	break;
    }
    else
      break;
  }

  if (i == max)
  {
    log("%s: got more routes for one /%d network then %d, ignoring", p->name,
	n->n.pxlen, max);
    mb_free(body);
    return;
  }
  lsasum_calculate(&lsa, body);
  WALK_LIST(oa, po->area_list)
  {
    if (!oa->stub)
    {
      en = lsa_install_new(&lsa, body, oa);
      ospf_lsupd_flood(NULL, NULL, &en->lsa, NULL, oa, 1);
      body = originate_ext_lsa_body(n, e, po, attrs);
    }
  }
  mb_free(body);

  if (po->ebit == 0)
  {
    po->ebit = 1;
    WALK_LIST(oa, po->area_list)
    {
      schedule_rt_lsa(oa);
    }
  }
}


static void
ospf_top_ht_alloc(struct top_graph *f)
{
  f->hash_size = 1 << f->hash_order;
  f->hash_mask = f->hash_size - 1;
  if (f->hash_order > HASH_HI_MAX - HASH_HI_STEP)
    f->hash_entries_max = ~0;
  else
    f->hash_entries_max = f->hash_size HASH_HI_MARK;
  if (f->hash_order < HASH_LO_MIN + HASH_LO_STEP)
    f->hash_entries_min = 0;
  else
    f->hash_entries_min = f->hash_size HASH_LO_MARK;
  DBG("Allocating OSPF hash of order %d: %d hash_entries, %d low, %d high\n",
      f->hash_order, f->hash_size, f->hash_entries_min, f->hash_entries_max);
  f->hash_table =
    mb_alloc(f->pool, f->hash_size * sizeof(struct top_hash_entry *));
  bzero(f->hash_table, f->hash_size * sizeof(struct top_hash_entry *));
}

static inline void
ospf_top_ht_free(struct top_hash_entry **h)
{
  mb_free(h);
}

static inline u32
ospf_top_hash_u32(u32 a)
{
  /* Shamelessly stolen from IP address hashing in ipv4.h */
  a ^= a >> 16;
  a ^= a << 10;
  return a;
}

static inline unsigned
ospf_top_hash(struct top_graph *f, u32 areaid, u32 lsaid, u32 rtrid, u32 type)
{
#if 1				/* Dirty patch to make rt table calculation work. */
  return (ospf_top_hash_u32(lsaid) +
	  ospf_top_hash_u32((type ==
			     LSA_T_NET) ? lsaid : rtrid) + type +
          (type == LSA_T_EXT ? 0 : areaid)) & f->hash_mask;
#else
  return (ospf_top_hash_u32(lsaid) + ospf_top_hash_u32(rtrid) +
	  type + areaid) & f->hash_mask;
#endif
}

/**
 * ospf_top_new - allocated new topology database
 * @p: current instance of OSPF
 *
 * This dynamically hashed structure is often used for keeping LSAs. Mainly
 * its used in @ospf_area structure.
 */
struct top_graph *
ospf_top_new(pool *pool)
{
  struct top_graph *f;

  f = mb_allocz(pool, sizeof(struct top_graph));
  f->pool = pool;
  f->hash_slab = sl_new(f->pool, sizeof(struct top_hash_entry));
  f->hash_order = HASH_DEF_ORDER;
  ospf_top_ht_alloc(f);
  f->hash_entries = 0;
  f->hash_entries_min = 0;
  return f;
}

void
ospf_top_free(struct top_graph *f)
{
  rfree(f->hash_slab);
  ospf_top_ht_free(f->hash_table);
  mb_free(f);
}

static void
ospf_top_rehash(struct top_graph *f, int step)
{
  unsigned int oldn, oldh;
  struct top_hash_entry **n, **oldt, **newt, *e, *x;

  oldn = f->hash_size;
  oldt = f->hash_table;
  DBG("Re-hashing topology hash from order %d to %d\n", f->hash_order,
      f->hash_order + step);
  f->hash_order += step;
  ospf_top_ht_alloc(f);
  newt = f->hash_table;

  for (oldh = 0; oldh < oldn; oldh++)
  {
    e = oldt[oldh];
    while (e)
    {
      x = e->next;
      n = newt + ospf_top_hash(f, e->oa->areaid, e->lsa.id, e->lsa.rt, e->lsa.type);
      e->next = *n;
      *n = e;
      e = x;
    }
  }
  ospf_top_ht_free(oldt);
}

struct top_hash_entry *
ospf_hash_find_header(struct top_graph *f, u32 areaid, struct ospf_lsa_header *h)
{
  return ospf_hash_find(f, areaid, h->id, h->rt, h->type);
}

struct top_hash_entry *
ospf_hash_get_header(struct top_graph *f, struct ospf_area *oa, struct ospf_lsa_header *h)
{
  return ospf_hash_get(f, oa, h->id, h->rt, h->type);
}

struct top_hash_entry *
ospf_hash_find(struct top_graph *f, u32 areaid, u32 lsa, u32 rtr, u32 type)
{
  struct top_hash_entry *e;

  e = f->hash_table[ospf_top_hash(f, areaid, lsa, rtr, type)];

  /* Dirty patch to make rt table calculation work. */
  if (type == LSA_T_NET)
  {
    while (e && (e->lsa.id != lsa || e->lsa.type != LSA_T_NET || e->oa->areaid != areaid))
      e = e->next;
  }
  else if (type == LSA_T_EXT)
  {
    while (e && (e->lsa.id != lsa || e->lsa.type != type || e->lsa.rt != rtr))
      e = e->next;
  }
  else
  {
    while (e && (e->lsa.id != lsa || e->lsa.type != type || e->lsa.rt != rtr || e->oa->areaid != areaid))
      e = e->next;
  }

  return e;
}

struct top_hash_entry *
ospf_hash_get(struct top_graph *f, struct ospf_area *oa, u32 lsa, u32 rtr, u32 type)
{
  struct top_hash_entry **ee;
  struct top_hash_entry *e;
  u32 nareaid = (type == LSA_T_EXT ? 0 : oa->areaid);

  ee = f->hash_table + ospf_top_hash(f, nareaid, lsa, rtr, type);
  e = *ee;

  if (type == LSA_T_EXT)
  {
    while (e && (e->lsa.id != lsa || e->lsa.rt != rtr || e->lsa.type != type))
      e = e->next;
  }
  else
  {
    while (e && (e->lsa.id != lsa || e->lsa.rt != rtr || e->lsa.type != type || e->oa->areaid != nareaid))
      e = e->next;
  }

  if (e)
    return e;

  e = sl_alloc(f->hash_slab);
  e->color = OUTSPF;
  e->dist = LSINFINITY;
  e->nhi = NULL;
  e->nh = IPA_NONE;
  e->lb = IPA_NONE;
  e->lsa.id = lsa;
  e->lsa.rt = rtr;
  e->lsa.type = type;
  e->lsa_body = NULL;
  e->nhi = NULL;
  e->oa = oa;
  e->next = *ee;
  *ee = e;
  if (f->hash_entries++ > f->hash_entries_max)
    ospf_top_rehash(f, HASH_HI_STEP);
  return e;
}

void
ospf_hash_delete(struct top_graph *f, struct top_hash_entry *e)
{
  struct top_hash_entry **ee = f->hash_table + 
    ospf_top_hash(f, e->oa->areaid, e->lsa.id, e->lsa.rt, e->lsa.type);

  while (*ee)
  {
    if (*ee == e)
    {
      *ee = e->next;
      sl_free(f->hash_slab, e);
      if (f->hash_entries-- < f->hash_entries_min)
	ospf_top_rehash(f, -HASH_LO_STEP);
      return;
    }
    ee = &((*ee)->next);
  }
  bug("ospf_hash_delete() called for invalid node");
}

void
ospf_top_dump(struct top_graph *f, struct proto *p)
{
  unsigned int i;
  OSPF_TRACE(D_EVENTS, "Hash entries: %d", f->hash_entries);

  for (i = 0; i < f->hash_size; i++)
  {
    struct top_hash_entry *e = f->hash_table[i];
    while (e)
    {
      OSPF_TRACE(D_EVENTS, "- %1x %-1I %-1I %4u 0x%08x 0x%04x %-1I",
		 e->lsa.type, e->lsa.id, e->lsa.rt, e->lsa.age,
		 e->lsa.sn, e->lsa.checksum, e->oa ? e->oa->areaid : 0 );
      e = e->next;
    }
  }
}

/* This is very inefficient, please don't call it often */

/* I should also test for every LSA if it's in some link state
 * retransmission list for every neighbor. I will not test it.
 * It could happen that I'll receive some strange ls ack's.
 */

int
can_flush_lsa(struct proto_ospf *po)
{
  struct ospf_iface *ifa;
  struct ospf_neighbor *n;

  WALK_LIST(ifa, po->iface_list)
  {
    WALK_LIST(n, ifa->neigh_list)
    {
      if ((n->state == NEIGHBOR_EXCHANGE) || (n->state == NEIGHBOR_LOADING))
        return 0;

      break;
    }
  }

  return 1;
}
