/*
 * This is an implementation of rfc2370.
 * Copyright (C) 2001 KDD R&D Laboratories, Inc.
 * http://www.kddlabs.co.jp/
 *
 * This file is part of GNU Zebra.
 *
 * GNU Zebra 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, or (at your option) any
 * later version.
 * 
 * GNU Zebra is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GNU Zebra; see the file COPYING.  If not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

/***** MTYPE definitions are not reflected to "memory.h" yet. *****/
#define MTYPE_OSPF_OPAQUE_FUNCTAB	MTYPE_TMP
#define MTYPE_OPAQUE_INFO_PER_TYPE	MTYPE_TMP
#define MTYPE_OPAQUE_INFO_PER_ID	MTYPE_TMP

#include <zebra.h>

#include "linklist.h"
#include "prefix.h"
#include "if.h"
#include "table.h"
#include "memory.h"
#include "command.h"
#include "vty.h"
#include "stream.h"
#include "log.h"
#include "thread.h"
#include "hash.h"
#include "sockunion.h"		/* for inet_aton() */

#include "ospfd/ospfd.h"
#include "ospfd/ospf_interface.h"
#include "ospfd/ospf_ism.h"
#include "ospfd/ospf_asbr.h"
#include "ospfd/ospf_lsa.h"
#include "ospfd/ospf_lsdb.h"
#include "ospfd/ospf_neighbor.h"
#include "ospfd/ospf_nsm.h"
#include "ospfd/ospf_flood.h"
#include "ospfd/ospf_packet.h"
#include "ospfd/ospf_spf.h"
#include "ospfd/ospf_dump.h"
#include "ospfd/ospf_route.h"
#include "ospfd/ospf_ase.h"
#include "ospfd/ospf_zebra.h"

/*------------------------------------------------------------------------*
 * Followings are initialize/terminate functions for Opaque-LSAs handling.
 *------------------------------------------------------------------------*/

#include "ospfd/ospf_te.h"
#include "ospfd/ospf_ri.h"

#ifdef SUPPORT_OSPF_API
int ospf_apiserver_init (void);
void ospf_apiserver_term (void); 
/* Init apiserver? It's disabled by default. */
int ospf_apiserver_enable;
#endif /* SUPPORT_OSPF_API */

static void ospf_opaque_register_vty (void);
static void ospf_opaque_funclist_init (void);
static void ospf_opaque_funclist_term (void);
static void free_opaque_info_per_type (void *val);
static void free_opaque_info_per_id (void *val);
static int ospf_opaque_lsa_install_hook (struct ospf_lsa *lsa);
static int ospf_opaque_lsa_delete_hook (struct ospf_lsa *lsa);

void
ospf_opaque_init (void)
{
  ospf_opaque_register_vty ();
  ospf_opaque_funclist_init ();

  if (ospf_mpls_te_init () != 0)
    exit (1);

  if (ospf_router_info_init () != 0)
    exit (1);

#ifdef SUPPORT_OSPF_API
  if ((ospf_apiserver_enable) && (ospf_apiserver_init () != 0))
    exit (1);
#endif /* SUPPORT_OSPF_API */

  return;
}

void
ospf_opaque_term (void)
{
  ospf_mpls_te_term ();

  ospf_router_info_term ();

#ifdef SUPPORT_OSPF_API
  ospf_apiserver_term ();
#endif /* SUPPORT_OSPF_API */

  ospf_opaque_funclist_term ();
  return;
}

int
ospf_opaque_type9_lsa_init (struct ospf_interface *oi)
{
  if (oi->opaque_lsa_self != NULL)
    list_delete (oi->opaque_lsa_self);

  oi->opaque_lsa_self = list_new ();
  oi->opaque_lsa_self->del = free_opaque_info_per_type;
  oi->t_opaque_lsa_self = NULL;
  return 0;
}

void
ospf_opaque_type9_lsa_term (struct ospf_interface *oi)
{
  OSPF_TIMER_OFF (oi->t_opaque_lsa_self);
  if (oi->opaque_lsa_self != NULL)
    list_delete (oi->opaque_lsa_self);
  oi->opaque_lsa_self = NULL;
  return;
}

int
ospf_opaque_type10_lsa_init (struct ospf_area *area)
{
  if (area->opaque_lsa_self != NULL)
    list_delete (area->opaque_lsa_self);

  area->opaque_lsa_self = list_new ();
  area->opaque_lsa_self->del = free_opaque_info_per_type;
  area->t_opaque_lsa_self = NULL;

#ifdef MONITOR_LSDB_CHANGE
  area->lsdb->new_lsa_hook = ospf_opaque_lsa_install_hook;
  area->lsdb->del_lsa_hook = ospf_opaque_lsa_delete_hook;
#endif /* MONITOR_LSDB_CHANGE */
  return 0;
}

void
ospf_opaque_type10_lsa_term (struct ospf_area *area)
{
#ifdef MONITOR_LSDB_CHANGE
  area->lsdb->new_lsa_hook = 
  area->lsdb->del_lsa_hook = NULL;
#endif /* MONITOR_LSDB_CHANGE */

  OSPF_TIMER_OFF (area->t_opaque_lsa_self);
  if (area->opaque_lsa_self != NULL)
    list_delete (area->opaque_lsa_self);
  area->opaque_lsa_self = NULL;
  return;
}

int
ospf_opaque_type11_lsa_init (struct ospf *top)
{
  if (top->opaque_lsa_self != NULL)
    list_delete (top->opaque_lsa_self);

  top->opaque_lsa_self = list_new ();
  top->opaque_lsa_self->del = free_opaque_info_per_type;
  top->t_opaque_lsa_self = NULL;

#ifdef MONITOR_LSDB_CHANGE
  top->lsdb->new_lsa_hook = ospf_opaque_lsa_install_hook;
  top->lsdb->del_lsa_hook = ospf_opaque_lsa_delete_hook;
#endif /* MONITOR_LSDB_CHANGE */
  return 0;
}

void
ospf_opaque_type11_lsa_term (struct ospf *top)
{
#ifdef MONITOR_LSDB_CHANGE
  top->lsdb->new_lsa_hook = 
  top->lsdb->del_lsa_hook = NULL;
#endif /* MONITOR_LSDB_CHANGE */

  OSPF_TIMER_OFF (top->t_opaque_lsa_self);
  if (top->opaque_lsa_self != NULL)
    list_delete (top->opaque_lsa_self);
  top->opaque_lsa_self = NULL;
  return;
}

static const char *
ospf_opaque_type_name (u_char opaque_type)
{
  const char *name = "Unknown";

  switch (opaque_type)
    {
    case OPAQUE_TYPE_WILDCARD: /* This is a special assignment! */
      name = "Wildcard";
      break;
    case OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA:
      name = "Traffic Engineering LSA";
      break;
    case OPAQUE_TYPE_SYCAMORE_OPTICAL_TOPOLOGY_DESC:
      name = "Sycamore optical topology description";
      break;
    case OPAQUE_TYPE_GRACE_LSA:
      name = "Grace-LSA";
      break;
    case OPAQUE_TYPE_INTER_AS_LSA:
      name = "Inter-AS TE-v2 LSA";
      break;
    case OPAQUE_TYPE_ROUTER_INFORMATION_LSA:
      name = "Router Information LSA";
      break;
    default:
      if (OPAQUE_TYPE_RANGE_UNASSIGNED (opaque_type))
        name = "Unassigned";
      else
        {
          u_int32_t bigger_range = opaque_type;
          /*
           * Get around type-limits warning: comparison is always true due to limited range of data type
           */
          if (OPAQUE_TYPE_RANGE_RESERVED (bigger_range))
            name = "Private/Experimental";
        }
      break;
    }
  return name;
}

/*------------------------------------------------------------------------*
 * Followings are management functions to store user specified callbacks.
 *------------------------------------------------------------------------*/

struct opaque_info_per_type; /* Forward declaration. */

struct ospf_opaque_functab
{
  u_char opaque_type;
  struct opaque_info_per_type *oipt;

  int (* new_if_hook)(struct interface *ifp);
  int (* del_if_hook)(struct interface *ifp);
  void (* ism_change_hook)(struct ospf_interface *oi, int old_status);
  void (* nsm_change_hook)(struct ospf_neighbor *nbr, int old_status);
  void (* config_write_router)(struct vty *vty);
  void (* config_write_if    )(struct vty *vty, struct interface *ifp);
  void (* config_write_debug )(struct vty *vty);
  void (* show_opaque_info   )(struct vty *vty, struct ospf_lsa *lsa);
  int  (* lsa_originator)(void *arg);
  struct ospf_lsa *(* lsa_refresher )(struct ospf_lsa *lsa);
  int (* new_lsa_hook)(struct ospf_lsa *lsa);
  int (* del_lsa_hook)(struct ospf_lsa *lsa);
};

/* Handle LSA-9/10/11 altogether. */
static struct list *ospf_opaque_wildcard_funclist;
static struct list *ospf_opaque_type9_funclist;
static struct list *ospf_opaque_type10_funclist;
static struct list *ospf_opaque_type11_funclist;

static void
ospf_opaque_del_functab (void *val)
{
  XFREE (MTYPE_OSPF_OPAQUE_FUNCTAB, val);
  return;
}

static void
ospf_opaque_funclist_init (void)
{
  struct list *funclist;

  funclist = ospf_opaque_wildcard_funclist = list_new ();
  funclist->del = ospf_opaque_del_functab;

  funclist = ospf_opaque_type9_funclist  = list_new ();
  funclist->del = ospf_opaque_del_functab;

  funclist = ospf_opaque_type10_funclist = list_new ();
  funclist->del = ospf_opaque_del_functab;

  funclist = ospf_opaque_type11_funclist = list_new ();
  funclist->del = ospf_opaque_del_functab;
  return;
}

static void
ospf_opaque_funclist_term (void)
{
  struct list *funclist;

  funclist = ospf_opaque_wildcard_funclist;
  list_delete (funclist);

  funclist = ospf_opaque_type9_funclist;
  list_delete (funclist);

  funclist = ospf_opaque_type10_funclist;
  list_delete (funclist);

  funclist = ospf_opaque_type11_funclist;
  list_delete (funclist);
  return;
}

static struct list *
ospf_get_opaque_funclist (u_char lsa_type)
{
  struct list *funclist = NULL;

  switch (lsa_type)
    {
    case OPAQUE_TYPE_WILDCARD:
      /* XXX
       * This is an ugly trick to handle type-9/10/11 LSA altogether.
       * Yes, "OPAQUE_TYPE_WILDCARD (value 0)" is not an LSA-type, nor
       * an officially assigned opaque-type.
       * Though it is possible that the value might be officially used
       * in the future, we use it internally as a special label, for now.
       */
      funclist = ospf_opaque_wildcard_funclist;
      break;
    case OSPF_OPAQUE_LINK_LSA:
      funclist = ospf_opaque_type9_funclist;
      break;
    case OSPF_OPAQUE_AREA_LSA:
      funclist = ospf_opaque_type10_funclist;
      break;
    case OSPF_OPAQUE_AS_LSA:
      funclist = ospf_opaque_type11_funclist;
      break;
    default:
      zlog_warn ("ospf_get_opaque_funclist: Unexpected LSA-type(%u)", lsa_type);
      break;
    }
  return funclist;
}

/* XXX: such a huge argument list can /not/ be healthy... */
int
ospf_register_opaque_functab (
  u_char lsa_type,
  u_char opaque_type,
  int (* new_if_hook)(struct interface *ifp),
  int (* del_if_hook)(struct interface *ifp),
  void (* ism_change_hook)(struct ospf_interface *oi, int old_status),
  void (* nsm_change_hook)(struct ospf_neighbor *nbr, int old_status),
  void (* config_write_router)(struct vty *vty),
  void (* config_write_if    )(struct vty *vty, struct interface *ifp),
  void (* config_write_debug )(struct vty *vty),
  void (* show_opaque_info   )(struct vty *vty, struct ospf_lsa *lsa),
  int  (* lsa_originator)(void *arg),
  struct ospf_lsa *(* lsa_refresher )(struct ospf_lsa *lsa),
  int (* new_lsa_hook)(struct ospf_lsa *lsa),
  int (* del_lsa_hook)(struct ospf_lsa *lsa))
{
  struct list *funclist;
  struct ospf_opaque_functab *new;
  int rc = -1;

  if ((funclist = ospf_get_opaque_funclist (lsa_type)) == NULL)
    {
      zlog_warn ("ospf_register_opaque_functab: Cannot get funclist"
                 " for Type-%u LSAs?",
                 lsa_type);
      goto out;
    }
  else
    {
      struct listnode *node, *nnode;
      struct ospf_opaque_functab *functab;
      
      for (ALL_LIST_ELEMENTS (funclist, node, nnode, functab))
        if (functab->opaque_type == opaque_type)
          {
            zlog_warn ("ospf_register_opaque_functab: Duplicated entry?:"
                       " lsa_type(%u), opaque_type(%u)", 
                       lsa_type, opaque_type);
            goto out;
          }
    }

  if ((new = XCALLOC (MTYPE_OSPF_OPAQUE_FUNCTAB,
		      sizeof (struct ospf_opaque_functab))) == NULL)
    {
      zlog_warn ("ospf_register_opaque_functab: XMALLOC: %s",
                 safe_strerror (errno));
      goto out;
    }

  new->opaque_type    = opaque_type;
  new->oipt           = NULL;
  new->new_if_hook    = new_if_hook;
  new->del_if_hook    = del_if_hook;
  new->ism_change_hook     = ism_change_hook;
  new->nsm_change_hook     = nsm_change_hook;
  new->config_write_router = config_write_router;
  new->config_write_if     = config_write_if;
  new->config_write_debug  = config_write_debug;
  new->show_opaque_info    = show_opaque_info;
  new->lsa_originator = lsa_originator;
  new->lsa_refresher  = lsa_refresher;
  new->new_lsa_hook   = new_lsa_hook;
  new->del_lsa_hook   = del_lsa_hook;

  listnode_add (funclist, new);
  rc = 0;

out:
  return rc;
}

void
ospf_delete_opaque_functab (u_char lsa_type, u_char opaque_type)
{
  struct list *funclist;
  struct listnode *node, *nnode;
  struct ospf_opaque_functab *functab;

  if ((funclist = ospf_get_opaque_funclist (lsa_type)) != NULL)
    for (ALL_LIST_ELEMENTS (funclist, node, nnode, functab))
      {
        if (functab->opaque_type == opaque_type)
          {
            /* Cleanup internal control information, if it still remains. */
            if (functab->oipt != NULL)
              free_opaque_info_per_type (functab->oipt);

            /* Dequeue listnode entry from the list. */
            listnode_delete (funclist, functab);

            /* Avoid misjudgement in the next lookup. */
            if (listcount (funclist) == 0)
              funclist->head = funclist->tail = NULL;

            XFREE (MTYPE_OSPF_OPAQUE_FUNCTAB, functab);
            break;
	  }
      }

  return;
}

static struct ospf_opaque_functab *
ospf_opaque_functab_lookup (struct ospf_lsa *lsa)
{
  struct list *funclist;
  struct listnode *node;
  struct ospf_opaque_functab *functab;
  u_char key = GET_OPAQUE_TYPE (ntohl (lsa->data->id.s_addr));

  if ((funclist = ospf_get_opaque_funclist (lsa->data->type)) != NULL)
    for (ALL_LIST_ELEMENTS_RO (funclist, node, functab))
      if (functab->opaque_type == key)
        return functab;

  return NULL;
}

/*------------------------------------------------------------------------*
 * Followings are management functions for self-originated LSA entries.
 *------------------------------------------------------------------------*/

/*
 * Opaque-LSA control information per opaque-type.
 * Single Opaque-Type may have multiple instances; each of them will be
 * identified by their opaque-id.
 */
struct opaque_info_per_type
{
  u_char lsa_type;
  u_char opaque_type;

  enum { PROC_NORMAL, PROC_SUSPEND } status;

  /*
   * Thread for (re-)origination scheduling for this opaque-type.
   *
   * Initial origination of Opaque-LSAs is controlled by generic
   * Opaque-LSA handling module so that same opaque-type entries are
   * called all at once when certain conditions are met.
   * However, there might be cases that some Opaque-LSA clients need
   * to (re-)originate their own Opaque-LSAs out-of-sync with others.
   * This thread is prepared for that specific purpose.
   */
  struct thread *t_opaque_lsa_self;

  /*
   * Backpointer to an "owner" which is LSA-type dependent.
   *   type-9:  struct ospf_interface
   *   type-10: struct ospf_area
   *   type-11: struct ospf
   */
  void *owner;

  /* Collection of callback functions for this opaque-type. */
  struct ospf_opaque_functab *functab;

  /* List of Opaque-LSA control informations per opaque-id. */
  struct list *id_list;
};

/* Opaque-LSA control information per opaque-id. */
struct opaque_info_per_id
{
  u_int32_t opaque_id;

  /* Thread for refresh/flush scheduling for this opaque-type/id. */
  struct thread *t_opaque_lsa_self;

  /* Backpointer to Opaque-LSA control information per opaque-type. */
  struct opaque_info_per_type *opqctl_type;

  /* Here comes an actual Opaque-LSA entry for this opaque-type/id. */
  struct ospf_lsa *lsa;
};

static struct opaque_info_per_type *register_opaque_info_per_type (struct ospf_opaque_functab *functab, struct ospf_lsa *new);
static struct opaque_info_per_type *lookup_opaque_info_by_type (struct ospf_lsa *lsa);
static struct opaque_info_per_id *register_opaque_info_per_id (struct opaque_info_per_type *oipt, struct ospf_lsa *new);
static struct opaque_info_per_id *lookup_opaque_info_by_id (struct opaque_info_per_type *oipt, struct ospf_lsa *lsa);
static struct opaque_info_per_id *register_opaque_lsa (struct ospf_lsa *new);


static struct opaque_info_per_type *
register_opaque_info_per_type (struct ospf_opaque_functab *functab,
                               struct ospf_lsa *new)
{
  struct ospf *top;
  struct opaque_info_per_type *oipt;

  if ((oipt = XCALLOC (MTYPE_OPAQUE_INFO_PER_TYPE,
		       sizeof (struct opaque_info_per_type))) == NULL)
    {
      zlog_warn ("register_opaque_info_per_type: XMALLOC: %s", safe_strerror (errno));
      goto out;
    }

  switch (new->data->type)
    {
    case OSPF_OPAQUE_LINK_LSA:
      oipt->owner = new->oi;
      listnode_add (new->oi->opaque_lsa_self, oipt);
      break;
    case OSPF_OPAQUE_AREA_LSA:
      oipt->owner = new->area;
      listnode_add (new->area->opaque_lsa_self, oipt);
      break;
    case OSPF_OPAQUE_AS_LSA:
      top = ospf_lookup ();
      if (new->area != NULL && (top = new->area->ospf) == NULL)
        {
          free_opaque_info_per_type ((void *) oipt);
          oipt = NULL;
          goto out; /* This case may not exist. */
        }
      oipt->owner = top;
      listnode_add (top->opaque_lsa_self, oipt);
      break;
    default:
      zlog_warn ("register_opaque_info_per_type: Unexpected LSA-type(%u)", new->data->type);
      free_opaque_info_per_type ((void *) oipt);
      oipt = NULL;
      goto out; /* This case may not exist. */
    }

  oipt->lsa_type = new->data->type;
  oipt->opaque_type = GET_OPAQUE_TYPE (ntohl (new->data->id.s_addr));
  oipt->status = PROC_NORMAL;
  oipt->t_opaque_lsa_self = NULL;
  oipt->functab = functab;
  functab->oipt = oipt;
  oipt->id_list = list_new ();
  oipt->id_list->del = free_opaque_info_per_id;

out:
  return oipt;
}

static void
free_opaque_info_per_type (void *val)
{
  struct opaque_info_per_type *oipt = (struct opaque_info_per_type *) val;
  struct opaque_info_per_id *oipi;
  struct ospf_lsa *lsa;
  struct listnode *node, *nnode;

  /* Control information per opaque-id may still exist. */
  for (ALL_LIST_ELEMENTS (oipt->id_list, node, nnode, oipi))
    {
      if ((lsa = oipi->lsa) == NULL)
        continue;
      if (IS_LSA_MAXAGE (lsa))
        continue;
      ospf_opaque_lsa_flush_schedule (lsa);
    }

  /* Remove "oipt" from its owner's self-originated LSA list. */
  switch (oipt->lsa_type)
    {
    case OSPF_OPAQUE_LINK_LSA:
      {
        struct ospf_interface *oi = (struct ospf_interface *)(oipt->owner);
        listnode_delete (oi->opaque_lsa_self, oipt);
        break;
      }
    case OSPF_OPAQUE_AREA_LSA:
      {
        struct ospf_area *area = (struct ospf_area *)(oipt->owner);
        listnode_delete (area->opaque_lsa_self, oipt);
        break;
      }
    case OSPF_OPAQUE_AS_LSA:
      {
        struct ospf *top = (struct ospf *)(oipt->owner);
        listnode_delete (top->opaque_lsa_self, oipt);
        break;
      }
    default:
      zlog_warn ("free_opaque_info_per_type: Unexpected LSA-type(%u)", oipt->lsa_type);
      break; /* This case may not exist. */
    }

  OSPF_TIMER_OFF (oipt->t_opaque_lsa_self);
  list_delete (oipt->id_list);
  XFREE (MTYPE_OPAQUE_INFO_PER_TYPE, oipt);
  return;
}

static struct opaque_info_per_type *
lookup_opaque_info_by_type (struct ospf_lsa *lsa)
{
  struct ospf *top;
  struct ospf_area *area;
  struct ospf_interface *oi;
  struct list *listtop = NULL;
  struct listnode *node, *nnode;
  struct opaque_info_per_type *oipt = NULL;
  u_char key = GET_OPAQUE_TYPE (ntohl (lsa->data->id.s_addr));

  switch (lsa->data->type)
    {
    case OSPF_OPAQUE_LINK_LSA:
      if ((oi = lsa->oi) != NULL)
        listtop = oi->opaque_lsa_self;
      else
        zlog_warn ("Type-9 Opaque-LSA: Reference to OI is missing?");
      break;
    case OSPF_OPAQUE_AREA_LSA:
      if ((area = lsa->area) != NULL)
        listtop = area->opaque_lsa_self;
      else
        zlog_warn ("Type-10 Opaque-LSA: Reference to AREA is missing?");
      break;
    case OSPF_OPAQUE_AS_LSA:
      top = ospf_lookup ();
      if ((area = lsa->area) != NULL && (top = area->ospf) == NULL)
        {
          zlog_warn ("Type-11 Opaque-LSA: Reference to OSPF is missing?");
          break; /* Unlikely to happen. */
        }
      listtop = top->opaque_lsa_self;
      break;
    default:
      zlog_warn ("lookup_opaque_info_by_type: Unexpected LSA-type(%u)", lsa->data->type);
      break;
    }

  if (listtop != NULL)
    for (ALL_LIST_ELEMENTS (listtop, node, nnode, oipt))
      if (oipt->opaque_type == key)
        return oipt;

  return NULL;
}

static struct opaque_info_per_id *
register_opaque_info_per_id (struct opaque_info_per_type *oipt,
                             struct ospf_lsa *new)
{
  struct opaque_info_per_id *oipi;

  if ((oipi = XCALLOC (MTYPE_OPAQUE_INFO_PER_ID,
		       sizeof (struct opaque_info_per_id))) == NULL)
    {
      zlog_warn ("register_opaque_info_per_id: XMALLOC: %s", safe_strerror (errno));
      goto out;
    }
  oipi->opaque_id = GET_OPAQUE_ID (ntohl (new->data->id.s_addr));
  oipi->t_opaque_lsa_self = NULL;
  oipi->opqctl_type = oipt;
  oipi->lsa = ospf_lsa_lock (new);

  listnode_add (oipt->id_list, oipi);

out:
  return oipi;
}

static void
free_opaque_info_per_id (void *val)
{
  struct opaque_info_per_id *oipi = (struct opaque_info_per_id *) val;

  OSPF_TIMER_OFF (oipi->t_opaque_lsa_self);
  if (oipi->lsa != NULL)
    ospf_lsa_unlock (&oipi->lsa);
  XFREE (MTYPE_OPAQUE_INFO_PER_ID, oipi);
  return;
}

static struct opaque_info_per_id *
lookup_opaque_info_by_id (struct opaque_info_per_type *oipt,
                          struct ospf_lsa *lsa)
{
  struct listnode *node, *nnode;
  struct opaque_info_per_id   *oipi;
  u_int32_t key = GET_OPAQUE_ID (ntohl (lsa->data->id.s_addr));

  for (ALL_LIST_ELEMENTS (oipt->id_list, node, nnode, oipi))
    if (oipi->opaque_id == key)
      return oipi;

  return NULL;
}

static struct opaque_info_per_id *
register_opaque_lsa (struct ospf_lsa *new)
{
  struct ospf_opaque_functab *functab;
  struct opaque_info_per_type *oipt;
  struct opaque_info_per_id *oipi = NULL;

  if ((functab = ospf_opaque_functab_lookup (new)) == NULL)
    goto out;

  if ((oipt = lookup_opaque_info_by_type (new)) == NULL
  &&  (oipt = register_opaque_info_per_type (functab, new)) == NULL)
    goto out;

  if ((oipi = register_opaque_info_per_id (oipt, new)) == NULL)
    goto out;

out:
  return oipi;
}

/*------------------------------------------------------------------------*
 * Followings are (vty) configuration functions for Opaque-LSAs handling.
 *------------------------------------------------------------------------*/

DEFUN (capability_opaque,
       capability_opaque_cmd,
       "capability opaque",
       "Enable specific OSPF feature\n"
       "Opaque LSA\n")
{
  struct ospf *ospf = (struct ospf *) vty->index;

  /* Turn on the "master switch" of opaque-lsa capability. */
  if (!CHECK_FLAG (ospf->config, OSPF_OPAQUE_CAPABLE))
    {
      if (IS_DEBUG_OSPF_EVENT)
        zlog_debug ("Opaque capability: OFF -> ON");

      SET_FLAG (ospf->config, OSPF_OPAQUE_CAPABLE);
      ospf_renegotiate_optional_capabilities (ospf);
    }
  return CMD_SUCCESS;
}

ALIAS (capability_opaque,
       ospf_opaque_capable_cmd,
       "ospf opaque-lsa",
       "OSPF specific commands\n"
       "Enable the Opaque-LSA capability (rfc2370)\n")

DEFUN (no_capability_opaque,
       no_capability_opaque_cmd,
       "no capability opaque",
       NO_STR
       "Enable specific OSPF feature\n"
       "Opaque LSA\n")
{
  struct ospf *ospf = (struct ospf *) vty->index;

  /* Turn off the "master switch" of opaque-lsa capability. */
  if (CHECK_FLAG (ospf->config, OSPF_OPAQUE_CAPABLE))
    {
      if (IS_DEBUG_OSPF_EVENT)
        zlog_debug ("Opaque capability: ON -> OFF");

      UNSET_FLAG (ospf->config, OSPF_OPAQUE_CAPABLE);
      ospf_renegotiate_optional_capabilities (ospf);
    }
  return CMD_SUCCESS;
}

ALIAS (no_capability_opaque,
       no_ospf_opaque_capable_cmd,
       "no ospf opaque-lsa",
       NO_STR
       "OSPF specific commands\n"
       "Disable the Opaque-LSA capability (rfc2370)\n")

static void
ospf_opaque_register_vty (void)
{
  install_element (OSPF_NODE, &capability_opaque_cmd);
  install_element (OSPF_NODE, &no_capability_opaque_cmd);
  install_element (OSPF_NODE, &ospf_opaque_capable_cmd);
  install_element (OSPF_NODE, &no_ospf_opaque_capable_cmd);
  return;
}

/*------------------------------------------------------------------------*
 * Followings are collection of user-registered function callers.
 *------------------------------------------------------------------------*/

static int
opaque_lsa_new_if_callback (struct list *funclist, struct interface *ifp)
{
  struct listnode *node, *nnode;
  struct ospf_opaque_functab *functab;
  int rc = -1;

  for (ALL_LIST_ELEMENTS (funclist, node, nnode, functab))
    if (functab->new_if_hook != NULL)
      if ((* functab->new_if_hook)(ifp) != 0)
        goto out;
  rc = 0;
out:
  return rc;
}

static int
opaque_lsa_del_if_callback (struct list *funclist, struct interface *ifp)
{
  struct listnode *node, *nnode;
  struct ospf_opaque_functab *functab;
  int rc = -1;

  for (ALL_LIST_ELEMENTS (funclist, node, nnode, functab))
    if (functab->del_if_hook != NULL)
      if ((* functab->del_if_hook)(ifp) != 0)
        goto out;
  rc = 0;
out:
  return rc;
}

static void
opaque_lsa_ism_change_callback (struct list *funclist,
                                struct ospf_interface *oi, int old_status)
{
  struct listnode *node, *nnode;
  struct ospf_opaque_functab *functab;

  for (ALL_LIST_ELEMENTS (funclist, node, nnode, functab))
    if (functab->ism_change_hook != NULL)
      (* functab->ism_change_hook)(oi, old_status);

  return;
}

static void
opaque_lsa_nsm_change_callback (struct list *funclist,
                                struct ospf_neighbor *nbr, int old_status)
{
  struct listnode *node, *nnode;
  struct ospf_opaque_functab *functab;

  for (ALL_LIST_ELEMENTS (funclist, node, nnode, functab))
    if (functab->nsm_change_hook != NULL)
      (* functab->nsm_change_hook)(nbr, old_status);
  return;
}

static void
opaque_lsa_config_write_router_callback (struct list *funclist, 
                                         struct vty *vty)
{
  struct listnode *node, *nnode;
  struct ospf_opaque_functab *functab;

  for (ALL_LIST_ELEMENTS (funclist, node, nnode, functab))
    if (functab->config_write_router != NULL)
      (* functab->config_write_router)(vty);
  return;
}

static void
opaque_lsa_config_write_if_callback (struct list *funclist,
                                     struct vty *vty, struct interface *ifp)
{
  struct listnode *node, *nnode;
  struct ospf_opaque_functab *functab;

  for (ALL_LIST_ELEMENTS (funclist, node, nnode, functab))
    if (functab->config_write_if != NULL)
      (* functab->config_write_if)(vty, ifp);
  return;
}

static void
opaque_lsa_config_write_debug_callback (struct list *funclist, struct vty *vty)
{
  struct listnode *node, *nnode;
  struct ospf_opaque_functab *functab;

  for (ALL_LIST_ELEMENTS (funclist, node, nnode, functab))
    if (functab->config_write_debug != NULL)
      (* functab->config_write_debug)(vty);
  return;
}

static int
opaque_lsa_originate_callback (struct list *funclist, void *lsa_type_dependent)
{
  struct listnode *node, *nnode;
  struct ospf_opaque_functab *functab;
  int rc = -1;

  for (ALL_LIST_ELEMENTS (funclist, node, nnode, functab))
    if (functab->lsa_originator != NULL)
      if ((* functab->lsa_originator)(lsa_type_dependent) != 0)
         goto out;
  rc = 0;
out:
  return rc;
}

static int
new_lsa_callback (struct list *funclist, struct ospf_lsa *lsa)
{
  struct listnode *node, *nnode;
  struct ospf_opaque_functab *functab;
  int rc = -1;

  /* This function handles ALL types of LSAs, not only opaque ones. */
  for (ALL_LIST_ELEMENTS (funclist, node, nnode, functab))
    if (functab->new_lsa_hook != NULL)
      if ((* functab->new_lsa_hook)(lsa) != 0)
        goto out;
  rc = 0;
out:
  return rc;
}

static int
del_lsa_callback (struct list *funclist, struct ospf_lsa *lsa)
{
  struct listnode *node, *nnode;
  struct ospf_opaque_functab *functab;
  int rc = -1;

  /* This function handles ALL types of LSAs, not only opaque ones. */
  for (ALL_LIST_ELEMENTS (funclist, node, nnode, functab))
    if (functab->del_lsa_hook != NULL)
      if ((* functab->del_lsa_hook)(lsa) != 0)
        goto out;
  rc = 0;
out:
  return rc;
}

/*------------------------------------------------------------------------*
 * Followings are glue functions to call Opaque-LSA specific processing.
 *------------------------------------------------------------------------*/

int
ospf_opaque_new_if (struct interface *ifp)
{
  struct list *funclist;
  int rc = -1;

  funclist = ospf_opaque_wildcard_funclist;
  if (opaque_lsa_new_if_callback (funclist, ifp) != 0)
    goto out;

  funclist = ospf_opaque_type9_funclist;
  if (opaque_lsa_new_if_callback (funclist, ifp) != 0)
    goto out;

  funclist = ospf_opaque_type10_funclist;
  if (opaque_lsa_new_if_callback (funclist, ifp) != 0)
    goto out;

  funclist = ospf_opaque_type11_funclist;
  if (opaque_lsa_new_if_callback (funclist, ifp) != 0)
    goto out;

  rc = 0;
out:
  return rc;
}

int
ospf_opaque_del_if (struct interface *ifp)
{
  struct list *funclist;
  int rc = -1;

  funclist = ospf_opaque_wildcard_funclist;
  if (opaque_lsa_del_if_callback (funclist, ifp) != 0)
    goto out;

  funclist = ospf_opaque_type9_funclist;
  if (opaque_lsa_del_if_callback (funclist, ifp) != 0)
    goto out;

  funclist = ospf_opaque_type10_funclist;
  if (opaque_lsa_del_if_callback (funclist, ifp) != 0)
    goto out;

  funclist = ospf_opaque_type11_funclist;
  if (opaque_lsa_del_if_callback (funclist, ifp) != 0)
    goto out;

  rc = 0;
out:
  return rc;
}

void
ospf_opaque_ism_change (struct ospf_interface *oi, int old_status)
{
  struct list *funclist;

  funclist = ospf_opaque_wildcard_funclist;
  opaque_lsa_ism_change_callback (funclist, oi, old_status);

  funclist = ospf_opaque_type9_funclist;
  opaque_lsa_ism_change_callback (funclist, oi, old_status);

  funclist = ospf_opaque_type10_funclist;
  opaque_lsa_ism_change_callback (funclist, oi, old_status);

  funclist = ospf_opaque_type11_funclist;
  opaque_lsa_ism_change_callback (funclist, oi, old_status);

  return;
}

void
ospf_opaque_nsm_change (struct ospf_neighbor *nbr, int old_state)
{
  struct ospf *top;
  struct list *funclist;

  if ((top = oi_to_top (nbr->oi)) == NULL)
    goto out;

  if (old_state != NSM_Full && nbr->state == NSM_Full)
    {
      if (CHECK_FLAG (nbr->options, OSPF_OPTION_O))
        {
          if (! CHECK_FLAG (top->opaque, OPAQUE_OPERATION_READY_BIT))
            {
              if (IS_DEBUG_OSPF_EVENT)
                zlog_debug ("Opaque-LSA: Now get operational!");

              SET_FLAG (top->opaque, OPAQUE_OPERATION_READY_BIT);
            }

          ospf_opaque_lsa_originate_schedule (nbr->oi, NULL);
        }
    }
  else
  if (old_state == NSM_Full && nbr->state != NSM_Full)
    {
#ifdef NOTYET
      /*
       * If no more opaque-capable full-state neighbor remains in the
       * flooding scope which corresponds to Opaque-LSA type, periodic
       * LS flooding should be stopped.
       */
#endif /* NOTYET */
      ;
    }

  funclist = ospf_opaque_wildcard_funclist;
  opaque_lsa_nsm_change_callback (funclist, nbr, old_state);

  funclist = ospf_opaque_type9_funclist;
  opaque_lsa_nsm_change_callback (funclist, nbr, old_state);

  funclist = ospf_opaque_type10_funclist;
  opaque_lsa_nsm_change_callback (funclist, nbr, old_state);

  funclist = ospf_opaque_type11_funclist;
  opaque_lsa_nsm_change_callback (funclist, nbr, old_state);

out:
  return;
}

void
ospf_opaque_config_write_router (struct vty *vty, struct ospf *ospf)
{
  struct list *funclist;

  if (CHECK_FLAG (ospf->config, OSPF_OPAQUE_CAPABLE))
    vty_out (vty, " capability opaque%s", VTY_NEWLINE);

  funclist = ospf_opaque_wildcard_funclist;
  opaque_lsa_config_write_router_callback (funclist, vty);

  funclist = ospf_opaque_type9_funclist;
  opaque_lsa_config_write_router_callback (funclist, vty);

  funclist = ospf_opaque_type10_funclist;
  opaque_lsa_config_write_router_callback (funclist, vty);

  funclist = ospf_opaque_type11_funclist;
  opaque_lsa_config_write_router_callback (funclist, vty);

  return;
}

void
ospf_opaque_config_write_if (struct vty *vty, struct interface *ifp)
{
  struct list *funclist;

  funclist = ospf_opaque_wildcard_funclist;
  opaque_lsa_config_write_if_callback (funclist, vty, ifp);

  funclist = ospf_opaque_type9_funclist;
  opaque_lsa_config_write_if_callback (funclist, vty, ifp);

  funclist = ospf_opaque_type10_funclist;
  opaque_lsa_config_write_if_callback (funclist, vty, ifp);

  funclist = ospf_opaque_type11_funclist;
  opaque_lsa_config_write_if_callback (funclist, vty, ifp);

  return;
}

void
ospf_opaque_config_write_debug (struct vty *vty)
{
  struct list *funclist;

  funclist = ospf_opaque_wildcard_funclist;
  opaque_lsa_config_write_debug_callback (funclist, vty);

  funclist = ospf_opaque_type9_funclist;
  opaque_lsa_config_write_debug_callback (funclist, vty);

  funclist = ospf_opaque_type10_funclist;
  opaque_lsa_config_write_debug_callback (funclist, vty);

  funclist = ospf_opaque_type11_funclist;
  opaque_lsa_config_write_debug_callback (funclist, vty);

  return;
}

void
show_opaque_info_detail (struct vty *vty, struct ospf_lsa *lsa)
{
  struct lsa_header *lsah = (struct lsa_header *) lsa->data;
  u_int32_t lsid = ntohl (lsah->id.s_addr);
  u_char    opaque_type = GET_OPAQUE_TYPE (lsid);
  u_int32_t opaque_id = GET_OPAQUE_ID (lsid);
  struct ospf_opaque_functab *functab;

  /* Switch output functionality by vty address. */
  if (vty != NULL)
    {
      vty_out (vty, "  Opaque-Type %u (%s)%s", opaque_type,
	       ospf_opaque_type_name (opaque_type), VTY_NEWLINE);
      vty_out (vty, "  Opaque-ID   0x%x%s", opaque_id, VTY_NEWLINE);

      vty_out (vty, "  Opaque-Info: %u octets of data%s%s",
               ntohs (lsah->length) - OSPF_LSA_HEADER_SIZE,
               VALID_OPAQUE_INFO_LEN(lsah) ? "" : "(Invalid length?)",
               VTY_NEWLINE);
    }
  else
    {
      zlog_debug ("    Opaque-Type %u (%s)", opaque_type,
		 ospf_opaque_type_name (opaque_type));
      zlog_debug ("    Opaque-ID   0x%x", opaque_id);

      zlog_debug ("    Opaque-Info: %u octets of data%s",
               ntohs (lsah->length) - OSPF_LSA_HEADER_SIZE,
               VALID_OPAQUE_INFO_LEN(lsah) ? "" : "(Invalid length?)");
    }

  /* Call individual output functions. */
  if ((functab = ospf_opaque_functab_lookup (lsa)) != NULL)
    if (functab->show_opaque_info != NULL)
      (* functab->show_opaque_info)(vty, lsa);

  return;
}

void
ospf_opaque_lsa_dump (struct stream *s, u_int16_t length)
{
  struct ospf_lsa lsa;

  lsa.data = (struct lsa_header *) STREAM_PNT (s);
  show_opaque_info_detail (NULL, &lsa);
  return;
}

static int
ospf_opaque_lsa_install_hook (struct ospf_lsa *lsa)
{
  struct list *funclist;
  int rc = -1;

  /*
   * Some Opaque-LSA user may want to monitor every LSA installation
   * into the LSDB, regardless with target LSA type.
   */
  funclist = ospf_opaque_wildcard_funclist;
  if (new_lsa_callback (funclist, lsa) != 0)
    goto out;

  funclist = ospf_opaque_type9_funclist;
  if (new_lsa_callback (funclist, lsa) != 0)
    goto out;

  funclist = ospf_opaque_type10_funclist;
  if (new_lsa_callback (funclist, lsa) != 0)
    goto out;

  funclist = ospf_opaque_type11_funclist;
  if (new_lsa_callback (funclist, lsa) != 0)
    goto out;

  rc = 0;
out:
  return rc;
}

static int
ospf_opaque_lsa_delete_hook (struct ospf_lsa *lsa)
{
  struct list *funclist;
  int rc = -1;

  /*
   * Some Opaque-LSA user may want to monitor every LSA deletion
   * from the LSDB, regardless with target LSA type.
   */
  funclist = ospf_opaque_wildcard_funclist;
  if (del_lsa_callback (funclist, lsa) != 0)
    goto out;

  funclist = ospf_opaque_type9_funclist;
  if (del_lsa_callback (funclist, lsa) != 0)
    goto out;

  funclist = ospf_opaque_type10_funclist;
  if (del_lsa_callback (funclist, lsa) != 0)
    goto out;

  funclist = ospf_opaque_type11_funclist;
  if (del_lsa_callback (funclist, lsa) != 0)
    goto out;

  rc = 0;
out:
  return rc;
}

/*------------------------------------------------------------------------*
 * Followings are Opaque-LSA origination/refresh management functions.
 *------------------------------------------------------------------------*/

static int ospf_opaque_type9_lsa_originate (struct thread *t);
static int ospf_opaque_type10_lsa_originate (struct thread *t);
static int ospf_opaque_type11_lsa_originate (struct thread *t);
static void ospf_opaque_lsa_reoriginate_resume (struct list *listtop, void *arg);

void
ospf_opaque_lsa_originate_schedule (struct ospf_interface *oi, int *delay0)
{
  struct ospf *top;
  struct ospf_area *area;
  struct listnode *node, *nnode;
  struct opaque_info_per_type *oipt;
  int delay = 0;

  if ((top = oi_to_top (oi)) == NULL || (area = oi->area) == NULL)
    {
      zlog_warn ("ospf_opaque_lsa_originate_schedule: Invalid argument?");
      goto out;
    }

  /* It may not a right time to schedule origination now. */
  if (! CHECK_FLAG (top->opaque, OPAQUE_OPERATION_READY_BIT))
    {
      if (IS_DEBUG_OSPF_EVENT)
        zlog_debug ("ospf_opaque_lsa_originate_schedule: Not operational.");
      goto out; /* This is not an error. */
    }
  
  if (delay0 != NULL)
    delay = *delay0;

  /*
   * There might be some entries that have been waiting for triggering
   * of per opaque-type re-origination get resumed.
   */
  ospf_opaque_lsa_reoriginate_resume (  oi->opaque_lsa_self, (void *)   oi);
  ospf_opaque_lsa_reoriginate_resume (area->opaque_lsa_self, (void *) area);
  ospf_opaque_lsa_reoriginate_resume ( top->opaque_lsa_self, (void *)  top);

  /*
   * Now, schedule origination of all Opaque-LSAs per opaque-type.
   */
  if (! list_isempty (ospf_opaque_type9_funclist)
  &&    list_isempty (oi->opaque_lsa_self)
  &&    oi->t_opaque_lsa_self == NULL)
    {
      if (IS_DEBUG_OSPF_EVENT)
        zlog_debug ("Schedule Type-9 Opaque-LSA origination in %d ms later.", delay);
      oi->t_opaque_lsa_self =
	thread_add_timer_msec (master, ospf_opaque_type9_lsa_originate, oi, delay);
      delay += top->min_ls_interval;
    }

  if (! list_isempty (ospf_opaque_type10_funclist)
  &&    list_isempty (area->opaque_lsa_self)
  &&    area->t_opaque_lsa_self == NULL)
    {
      /*
       * One AREA may contain multiple OIs, but above 2nd and 3rd
       * conditions prevent from scheduling the originate function
       * again and again.
       */
      if (IS_DEBUG_OSPF_EVENT)
        zlog_debug ("Schedule Type-10 Opaque-LSA origination in %d ms later.", delay);
      area->t_opaque_lsa_self =
        thread_add_timer_msec (master, ospf_opaque_type10_lsa_originate,
                          area, delay);
      delay += top->min_ls_interval;
    }

  if (! list_isempty (ospf_opaque_type11_funclist)
  &&    list_isempty (top->opaque_lsa_self)
  &&    top->t_opaque_lsa_self == NULL)
    {
      /*
       * One OSPF may contain multiple AREAs, but above 2nd and 3rd
       * conditions prevent from scheduling the originate function
       * again and again.
       */
      if (IS_DEBUG_OSPF_EVENT)
        zlog_debug ("Schedule Type-11 Opaque-LSA origination in %d ms later.", delay);
      top->t_opaque_lsa_self =
        thread_add_timer_msec (master, ospf_opaque_type11_lsa_originate,
                          top, delay);
      delay += top->min_ls_interval;
    }

  /*
   * Following section treats a special situation that this node's
   * opaque capability has changed as "ON -> OFF -> ON".
   */
  if (! list_isempty (ospf_opaque_type9_funclist)
  &&  ! list_isempty (oi->opaque_lsa_self))
    {
      for (ALL_LIST_ELEMENTS (oi->opaque_lsa_self, node, nnode, oipt))
        {
	  /* 
	   * removed the test for
	   *   (! list_isempty (oipt->id_list))   * Handler is already active. *
           * because opaque cababilities ON -> OFF -> ON result in list_isempty (oipt->id_list)
	   * not being empty.
	   */
          if (oipt->t_opaque_lsa_self != NULL /* Waiting for a thread call. */
              || oipt->status == PROC_SUSPEND)   /* Cannot originate now. */
              continue;

          ospf_opaque_lsa_reoriginate_schedule ((void *) oi,
            OSPF_OPAQUE_LINK_LSA, oipt->opaque_type);
        }
    }

  if (! list_isempty (ospf_opaque_type10_funclist)
  &&  ! list_isempty (area->opaque_lsa_self))
    {
      for (ALL_LIST_ELEMENTS (area->opaque_lsa_self, node, nnode, oipt))
        {
	  /* 
	   * removed the test for
	   *   (! list_isempty (oipt->id_list))   * Handler is already active. *
           * because opaque cababilities ON -> OFF -> ON result in list_isempty (oipt->id_list)
	   * not being empty.
	   */
          if (oipt->t_opaque_lsa_self != NULL /* Waiting for a thread call. */
              || oipt->status == PROC_SUSPEND)   /* Cannot originate now. */
            continue;

          ospf_opaque_lsa_reoriginate_schedule ((void *) area,
            OSPF_OPAQUE_AREA_LSA, oipt->opaque_type);
        }
    }

  if (! list_isempty (ospf_opaque_type11_funclist)
  &&  ! list_isempty (top->opaque_lsa_self))
    {
      for (ALL_LIST_ELEMENTS (top->opaque_lsa_self, node, nnode, oipt))
        {
	  /* 
	   * removed the test for
	   *   (! list_isempty (oipt->id_list))   * Handler is already active. *
           * because opaque cababilities ON -> OFF -> ON result in list_isempty (oipt->id_list)
	   * not being empty.
	   */
          if (oipt->t_opaque_lsa_self != NULL /* Waiting for a thread call. */
              || oipt->status == PROC_SUSPEND)   /* Cannot originate now. */
            continue;

          ospf_opaque_lsa_reoriginate_schedule ((void *) top,
            OSPF_OPAQUE_AS_LSA, oipt->opaque_type);
        }
    }

  if (delay0 != NULL)
    *delay0 = delay;

out:
  return;
}

static int
ospf_opaque_type9_lsa_originate (struct thread *t)
{
  struct ospf_interface *oi;
  int rc;

  oi = THREAD_ARG (t);
  oi->t_opaque_lsa_self = NULL;

  if (IS_DEBUG_OSPF_EVENT)
    zlog_debug ("Timer[Type9-LSA]: Originate Opaque-LSAs for OI %s",
                IF_NAME (oi));

  rc = opaque_lsa_originate_callback (ospf_opaque_type9_funclist, oi);

  return rc;
}

static int
ospf_opaque_type10_lsa_originate (struct thread *t)
{
  struct ospf_area *area;
  int rc;

  area = THREAD_ARG (t);
  area->t_opaque_lsa_self = NULL;

  if (IS_DEBUG_OSPF_EVENT)
    zlog_debug ("Timer[Type10-LSA]: Originate Opaque-LSAs for Area %s",
                inet_ntoa (area->area_id));

  rc = opaque_lsa_originate_callback (ospf_opaque_type10_funclist, area);

  return rc;
}

static int
ospf_opaque_type11_lsa_originate (struct thread *t)
{
  struct ospf *top;
  int rc;

  top = THREAD_ARG (t);
  top->t_opaque_lsa_self = NULL;

  if (IS_DEBUG_OSPF_EVENT)
    zlog_debug ("Timer[Type11-LSA]: Originate AS-External Opaque-LSAs");

  rc = opaque_lsa_originate_callback (ospf_opaque_type11_funclist, top);

  return rc;
}

static void
ospf_opaque_lsa_reoriginate_resume (struct list *listtop, void *arg)
{
  struct listnode *node, *nnode;
  struct opaque_info_per_type *oipt;
  struct ospf_opaque_functab *functab;

  if (listtop == NULL)
    goto out;

  /*
   * Pickup oipt entries those which in SUSPEND status, and give
   * them a chance to start re-origination now.
   */
  for (ALL_LIST_ELEMENTS (listtop, node, nnode, oipt))
    {
      if (oipt->status != PROC_SUSPEND)
          continue;

      oipt->status = PROC_NORMAL;

      if ((functab = oipt->functab) == NULL
          || functab->lsa_originator  == NULL)
        continue;

      if ((* functab->lsa_originator)(arg) != 0)
        {
          zlog_warn ("ospf_opaque_lsa_reoriginate_resume: Failed (opaque-type=%u)", oipt->opaque_type);
          continue;
        }
    }

out:
  return;
}

struct ospf_lsa *
ospf_opaque_lsa_install (struct ospf_lsa *lsa, int rt_recalc)
{
  struct ospf_lsa *new = NULL;
  struct opaque_info_per_type *oipt;
  struct opaque_info_per_id *oipi;
  struct ospf *top;

  /* Don't take "rt_recalc" into consideration for now. *//* XXX */

  if (! IS_LSA_SELF (lsa))
    {
      new = lsa; /* Don't touch this LSA. */
      goto out;
    }

  if (IS_DEBUG_OSPF (lsa, LSA_INSTALL))
    zlog_debug ("Install Type-%u Opaque-LSA: [opaque-type=%u, opaque-id=%x]", lsa->data->type, GET_OPAQUE_TYPE (ntohl (lsa->data->id.s_addr)), GET_OPAQUE_ID (ntohl (lsa->data->id.s_addr)));

  /* Replace the existing lsa with the new one. */
  if ((oipt = lookup_opaque_info_by_type (lsa)) != NULL
      && (oipi = lookup_opaque_info_by_id (oipt, lsa)) != NULL)
    {
      ospf_lsa_unlock (&oipi->lsa);
      oipi->lsa = ospf_lsa_lock (lsa);
    }
  /* Register the new lsa entry and get its control info. */
  else
  if ((oipi = register_opaque_lsa (lsa)) == NULL)
    {
      zlog_warn ("ospf_opaque_lsa_install: register_opaque_lsa() ?");
      goto out;
    }

  /*
   * Make use of a common mechanism (ospf_lsa_refresh_walker)
   * for periodic refresh of self-originated Opaque-LSAs.
   */
  switch (lsa->data->type)
    {
    case OSPF_OPAQUE_LINK_LSA:
      if ((top = oi_to_top (lsa->oi)) == NULL)
        {
          /* Above conditions must have passed. */
          zlog_warn ("ospf_opaque_lsa_install: Sonmething wrong?");
          goto out;
        }
      break;
    case OSPF_OPAQUE_AREA_LSA:
      if (lsa->area == NULL || (top = lsa->area->ospf) == NULL)
        {
          /* Above conditions must have passed. */
          zlog_warn ("ospf_opaque_lsa_install: Sonmething wrong?");
          goto out;
        }
      break;
    case OSPF_OPAQUE_AS_LSA:
      top = ospf_lookup ();
      if (lsa->area != NULL && (top = lsa->area->ospf) == NULL)
        {
          /* Above conditions must have passed. */
          zlog_warn ("ospf_opaque_lsa_install: Sonmething wrong?");
          goto out;
        }
      break;
    default:
      zlog_warn ("ospf_opaque_lsa_install: Unexpected LSA-type(%u)", lsa->data->type);
      goto out;
    }

  ospf_refresher_register_lsa (top, lsa);
  new = lsa;

out:
  return new;
}

struct ospf_lsa *
ospf_opaque_lsa_refresh (struct ospf_lsa *lsa)
{
  struct ospf *ospf;
  struct ospf_opaque_functab *functab;
  struct ospf_lsa *new = NULL;
  
  ospf = ospf_lookup ();

  if ((functab = ospf_opaque_functab_lookup (lsa)) == NULL
      || functab->lsa_refresher == NULL)
    {
      /*
       * Though this LSA seems to have originated on this node, the
       * handling module for this "lsa-type and opaque-type" was
       * already deleted sometime ago.
       * Anyway, this node still has a responsibility to flush this
       * LSA from the routing domain.
       */
      if (IS_DEBUG_OSPF_EVENT)
        zlog_debug ("LSA[Type%d:%s]: Flush stray Opaque-LSA", lsa->data->type, inet_ntoa (lsa->data->id));

      lsa->data->ls_age = htons (OSPF_LSA_MAXAGE);
      ospf_lsa_flush (ospf, lsa);
    }
  else
    new = (* functab->lsa_refresher)(lsa);

  return new;
}

/*------------------------------------------------------------------------*
 * Followings are re-origination/refresh/flush operations of Opaque-LSAs,
 * triggered by external interventions (vty session, signaling, etc).
 *------------------------------------------------------------------------*/

#define OSPF_OPAQUE_TIMER_ON(T,F,L,V) \
      if (!(T)) \
        (T) = thread_add_timer_msec (master, (F), (L), (V))

static struct ospf_lsa *pseudo_lsa (struct ospf_interface *oi, struct ospf_area *area, u_char lsa_type, u_char opaque_type);
static int ospf_opaque_type9_lsa_reoriginate_timer (struct thread *t);
static int ospf_opaque_type10_lsa_reoriginate_timer (struct thread *t);
static int ospf_opaque_type11_lsa_reoriginate_timer (struct thread *t);
static int ospf_opaque_lsa_refresh_timer (struct thread *t);

void
ospf_opaque_lsa_reoriginate_schedule (void *lsa_type_dependent,
                                      u_char lsa_type, u_char opaque_type)
{
  struct ospf *top;
  struct ospf_area dummy, *area = NULL;
  struct ospf_interface *oi = NULL;

  struct ospf_lsa *lsa;
  struct opaque_info_per_type *oipt;
  int (*func) (struct thread * t) = NULL;
  int delay;

  switch (lsa_type)
    {
    case OSPF_OPAQUE_LINK_LSA:
      if ((oi = (struct ospf_interface *) lsa_type_dependent) == NULL)
        {
          zlog_warn ("ospf_opaque_lsa_reoriginate_schedule:"
                     " Type-9 Opaque-LSA: Invalid parameter?");
          goto out;
        }
      if ((top = oi_to_top (oi)) == NULL)
        {
          zlog_warn ("ospf_opaque_lsa_reoriginate_schedule: OI(%s) -> TOP?",
                     IF_NAME (oi));
          goto out;
        }
      if (!list_isempty (ospf_opaque_type9_funclist)
          && list_isempty (oi->opaque_lsa_self)
          && oi->t_opaque_lsa_self != NULL)
        {
          zlog_warn ("Type-9 Opaque-LSA (opaque_type=%u):"
                     " Common origination for OI(%s) has already started",
                     opaque_type, IF_NAME (oi));
          goto out;
        }
      func = ospf_opaque_type9_lsa_reoriginate_timer;
      break;
    case OSPF_OPAQUE_AREA_LSA:
      if ((area = (struct ospf_area *) lsa_type_dependent) == NULL)
        {
          zlog_warn ("ospf_opaque_lsa_reoriginate_schedule:"
                     " Type-10 Opaque-LSA: Invalid parameter?");
          goto out;
        }
      if ((top = area->ospf) == NULL)
        {
          zlog_warn ("ospf_opaque_lsa_reoriginate_schedule:"
                     " AREA(%s) -> TOP?", inet_ntoa (area->area_id));
          goto out;
        }
      if (!list_isempty (ospf_opaque_type10_funclist)
          && list_isempty (area->opaque_lsa_self)
          && area->t_opaque_lsa_self != NULL)
        {
          zlog_warn ("Type-10 Opaque-LSA (opaque_type=%u):"
                     " Common origination for AREA(%s) has already started",
                     opaque_type, inet_ntoa (area->area_id));
          goto out;
        }
      func = ospf_opaque_type10_lsa_reoriginate_timer;
      break;
    case OSPF_OPAQUE_AS_LSA:
      if ((top = (struct ospf *) lsa_type_dependent) == NULL)
        {
          zlog_warn ("ospf_opaque_lsa_reoriginate_schedule:"
                     " Type-11 Opaque-LSA: Invalid parameter?");
          goto out;
        }
      if (!list_isempty (ospf_opaque_type11_funclist)
          && list_isempty (top->opaque_lsa_self)
          && top->t_opaque_lsa_self != NULL)
        {
          zlog_warn ("Type-11 Opaque-LSA (opaque_type=%u):"
                     " Common origination has already started", opaque_type);
          goto out;
        }

      /* Fake "area" to pass "ospf" to a lookup function later. */
      dummy.ospf = top;
      area = &dummy;

      func = ospf_opaque_type11_lsa_reoriginate_timer;
      break;
    default:
      zlog_warn ("ospf_opaque_lsa_reoriginate_schedule:"
                 " Unexpected LSA-type(%u)",
                 lsa_type);
      goto out;
    }

  /* It may not a right time to schedule reorigination now. */
  if (!CHECK_FLAG (top->opaque, OPAQUE_OPERATION_READY_BIT))
    {
      if (IS_DEBUG_OSPF_EVENT)
        zlog_debug ("ospf_opaque_lsa_reoriginate_schedule: Not operational.");
      goto out;                 /* This is not an error. */
    }
  
  /* Generate a dummy lsa to be passed for a lookup function. */
  lsa = pseudo_lsa (oi, area, lsa_type, opaque_type);

  if ((oipt = lookup_opaque_info_by_type (lsa)) == NULL)
    {
      struct ospf_opaque_functab *functab;
      if ((functab = ospf_opaque_functab_lookup (lsa)) == NULL)
        {
          zlog_warn ("ospf_opaque_lsa_reoriginate_schedule:"
                     " No associated function?: lsa_type(%u),"
                     " opaque_type(%u)",
                     lsa_type, opaque_type);
          goto out;
        }
      if ((oipt = register_opaque_info_per_type (functab, lsa)) == NULL)
        {
          zlog_warn ("ospf_opaque_lsa_reoriginate_schedule:"
                     " Cannot get a control info?: lsa_type(%u),"
                     " opaque_type(%u)",
                     lsa_type, opaque_type);
          goto out;
        }
    }

  if (oipt->t_opaque_lsa_self != NULL)
    {
      if (IS_DEBUG_OSPF_EVENT)
        zlog_debug ("Type-%u Opaque-LSA has already scheduled to"
                   " RE-ORIGINATE: [opaque-type=%u]",
                   lsa_type, GET_OPAQUE_TYPE (ntohl (lsa->data->id.s_addr)));
      goto out;
    }

  /*
   * Different from initial origination time, in which various conditions
   * (opaque capability, neighbor status etc) are assured by caller of
   * the originating function "ospf_opaque_lsa_originate_schedule ()",
   * it is highly possible that these conditions might not be satisfied
   * at the time of re-origination function is to be called.
   */
  delay = top->min_ls_interval; /* XXX */

  if (IS_DEBUG_OSPF_EVENT)
    zlog_debug ("Schedule Type-%u Opaque-LSA to RE-ORIGINATE in %d"
               " ms later: [opaque-type=%u]",
               lsa_type, delay, 
               GET_OPAQUE_TYPE (ntohl (lsa->data->id.s_addr)));

  OSPF_OPAQUE_TIMER_ON (oipt->t_opaque_lsa_self, func, oipt, delay);

out:
  return;
}

static struct ospf_lsa *
pseudo_lsa (struct ospf_interface *oi, struct ospf_area *area,
            u_char lsa_type, u_char opaque_type)
{
  static struct ospf_lsa lsa = { 0 };
  static struct lsa_header lsah = { 0 };
  u_int32_t tmp;

  lsa.oi   = oi;
  lsa.area = area;
  lsa.data = &lsah;

  lsah.type = lsa_type;
  tmp = SET_OPAQUE_LSID (opaque_type, 0); /* Opaque-ID is unused here. */
  lsah.id.s_addr = htonl (tmp);

  return &lsa;
}

static int
ospf_opaque_type9_lsa_reoriginate_timer (struct thread *t)
{
  struct opaque_info_per_type *oipt;
  struct ospf_opaque_functab *functab;
  struct ospf *top;
  struct ospf_interface *oi;
  int rc = -1;

  oipt = THREAD_ARG (t);
  oipt->t_opaque_lsa_self = NULL;

  if ((functab = oipt->functab) == NULL
  ||   functab->lsa_originator == NULL)
    {
      zlog_warn ("ospf_opaque_type9_lsa_reoriginate_timer: No associated function?");
      goto out;
    }

  oi = (struct ospf_interface *) oipt->owner;
  if ((top = oi_to_top (oi)) == NULL)
    {
      zlog_warn ("ospf_opaque_type9_lsa_reoriginate_timer: Something wrong?");
      goto out;
    }

  if (! CHECK_FLAG (top->config, OSPF_OPAQUE_CAPABLE)
  ||  ! ospf_if_is_enable (oi)
  ||    ospf_nbr_count_opaque_capable (oi) == 0)
    {
      if (IS_DEBUG_OSPF_EVENT)
        zlog_debug ("Suspend re-origination of Type-9 Opaque-LSAs (opaque-type=%u) for a while...", oipt->opaque_type);
    
      oipt->status = PROC_SUSPEND;
      rc = 0;
      goto out;
    }

  if (IS_DEBUG_OSPF_EVENT)
    zlog_debug ("Timer[Type9-LSA]: Re-originate Opaque-LSAs (opaque-type=%u) for OI (%s)", oipt->opaque_type, IF_NAME (oi));

  rc = (* functab->lsa_originator)(oi);
out:
  return rc;
}

static int
ospf_opaque_type10_lsa_reoriginate_timer (struct thread *t)
{
  struct opaque_info_per_type *oipt;
  struct ospf_opaque_functab *functab;
  struct listnode *node, *nnode;
  struct ospf *top;
  struct ospf_area *area;
  struct ospf_interface *oi;
  int n, rc = -1;

  oipt = THREAD_ARG (t);
  oipt->t_opaque_lsa_self = NULL;

  if ((functab = oipt->functab) == NULL
  ||   functab->lsa_originator == NULL)
    {
      zlog_warn ("ospf_opaque_type10_lsa_reoriginate_timer: No associated function?");
      goto out;
    }

  area = (struct ospf_area *) oipt->owner;
  if (area == NULL || (top = area->ospf) == NULL)
    {
      zlog_warn ("ospf_opaque_type10_lsa_reoriginate_timer: Something wrong?");
      goto out;
    }

  /* There must be at least one "opaque-capable, full-state" neighbor. */
  n = 0;
  for (ALL_LIST_ELEMENTS (area->oiflist, node, nnode, oi))
    {
      if ((n = ospf_nbr_count_opaque_capable (oi)) > 0)
        break;
    }

  if (n == 0 || ! CHECK_FLAG (top->config, OSPF_OPAQUE_CAPABLE))
    {
      if (IS_DEBUG_OSPF_EVENT)
        zlog_debug ("Suspend re-origination of Type-10 Opaque-LSAs"
                   " (opaque-type=%u) for a while...", 
                   oipt->opaque_type);

      oipt->status = PROC_SUSPEND;
      rc = 0;
      goto out;
    }

  if (IS_DEBUG_OSPF_EVENT)
    zlog_debug ("Timer[Type10-LSA]: Re-originate Opaque-LSAs"
               " (opaque-type=%u) for Area %s", 
               oipt->opaque_type, inet_ntoa (area->area_id));

  rc = (* functab->lsa_originator)(area);
out:
  return rc;
}

static int
ospf_opaque_type11_lsa_reoriginate_timer (struct thread *t)
{
  struct opaque_info_per_type *oipt;
  struct ospf_opaque_functab *functab;
  struct ospf *top;
  int rc = -1;

  oipt = THREAD_ARG (t);
  oipt->t_opaque_lsa_self = NULL;

  if ((functab = oipt->functab) == NULL
      || functab->lsa_originator == NULL)
    {
      zlog_warn ("ospf_opaque_type11_lsa_reoriginate_timer:"
                 " No associated function?");
      goto out;
    }

  if ((top = (struct ospf *) oipt->owner) == NULL)
    {
      zlog_warn ("ospf_opaque_type11_lsa_reoriginate_timer: Something wrong?");
      goto out;
    }

  if (! CHECK_FLAG (top->config, OSPF_OPAQUE_CAPABLE))
    {
      if (IS_DEBUG_OSPF_EVENT)
        zlog_debug ("Suspend re-origination of Type-11 Opaque-LSAs (opaque-type=%u) for a while...", oipt->opaque_type);
    
      oipt->status = PROC_SUSPEND;
      rc = 0;
      goto out;
    }

  if (IS_DEBUG_OSPF_EVENT)
    zlog_debug ("Timer[Type11-LSA]: Re-originate Opaque-LSAs (opaque-type=%u).", oipt->opaque_type);

  rc = (* functab->lsa_originator)(top);
out:
  return rc;
}

void
ospf_opaque_lsa_refresh_schedule (struct ospf_lsa *lsa0)
{
  struct opaque_info_per_type *oipt;
  struct opaque_info_per_id *oipi;
  struct ospf_lsa *lsa;
  struct ospf *top;
  int delay;

  if ((oipt = lookup_opaque_info_by_type (lsa0)) == NULL
  ||  (oipi = lookup_opaque_info_by_id (oipt, lsa0)) == NULL)
    {
      zlog_warn ("ospf_opaque_lsa_refresh_schedule: Invalid parameter?");
      goto out;
    }

  /* Given "lsa0" and current "oipi->lsa" may different, but harmless. */
  if ((lsa = oipi->lsa) == NULL)
    {
      zlog_warn ("ospf_opaque_lsa_refresh_schedule: Something wrong?");
      goto out;
    }

  if (oipi->t_opaque_lsa_self != NULL)
    {
      if (IS_DEBUG_OSPF_EVENT)
        zlog_debug ("Type-%u Opaque-LSA has already scheduled to REFRESH: [opaque-type=%u, opaque-id=%x]", lsa->data->type, GET_OPAQUE_TYPE (ntohl (lsa->data->id.s_addr)), GET_OPAQUE_ID (ntohl (lsa->data->id.s_addr)));
      goto out;
    }

  /* Delete this lsa from neighbor retransmit-list. */
  switch (lsa->data->type)
    {
    case OSPF_OPAQUE_LINK_LSA:
    case OSPF_OPAQUE_AREA_LSA:
      ospf_ls_retransmit_delete_nbr_area (lsa->area, lsa);
      break;
    case OSPF_OPAQUE_AS_LSA:
      top = ospf_lookup ();
      if ((lsa0->area != NULL) && (lsa0->area->ospf != NULL))
        top = lsa0->area->ospf;
      ospf_ls_retransmit_delete_nbr_as (top, lsa);
      break;
    default:
      zlog_warn ("ospf_opaque_lsa_refresh_schedule: Unexpected LSA-type(%u)", lsa->data->type);
      goto out;
    }

  delay = ospf_lsa_refresh_delay (lsa);

  if (IS_DEBUG_OSPF_EVENT)
    zlog_debug ("Schedule Type-%u Opaque-LSA to REFRESH in %d sec later: [opaque-type=%u, opaque-id=%x]", lsa->data->type, delay, GET_OPAQUE_TYPE (ntohl (lsa->data->id.s_addr)), GET_OPAQUE_ID (ntohl (lsa->data->id.s_addr)));

  OSPF_OPAQUE_TIMER_ON (oipi->t_opaque_lsa_self,
                        ospf_opaque_lsa_refresh_timer, oipi, delay * 1000);
out:
  return;
}

static int
ospf_opaque_lsa_refresh_timer (struct thread *t)
{
  struct opaque_info_per_id *oipi;
  struct ospf_opaque_functab *functab;
  struct ospf_lsa *lsa;

  if (IS_DEBUG_OSPF_EVENT)
    zlog_debug ("Timer[Opaque-LSA]: (Opaque-LSA Refresh expire)");

  oipi = THREAD_ARG (t);
  oipi->t_opaque_lsa_self = NULL;

  if ((lsa = oipi->lsa) != NULL)
    if ((functab = oipi->opqctl_type->functab) != NULL)
      if (functab->lsa_refresher != NULL)
        (* functab->lsa_refresher)(lsa);

  return 0;
}

void
ospf_opaque_lsa_flush_schedule (struct ospf_lsa *lsa0)
{
  struct opaque_info_per_type *oipt;
  struct opaque_info_per_id *oipi;
  struct ospf_lsa *lsa;
  struct ospf *top;

  top = ospf_lookup ();

  if ((oipt = lookup_opaque_info_by_type (lsa0)) == NULL
  ||  (oipi = lookup_opaque_info_by_id (oipt, lsa0)) == NULL)
    {
      zlog_warn ("ospf_opaque_lsa_flush_schedule: Invalid parameter?");
      goto out;
    }

  /* Given "lsa0" and current "oipi->lsa" may different, but harmless. */
  if ((lsa = oipi->lsa) == NULL)
    {
      zlog_warn ("ospf_opaque_lsa_flush_schedule: Something wrong?");
      goto out;
    }

  /* Delete this lsa from neighbor retransmit-list. */
  switch (lsa->data->type)
    {
    case OSPF_OPAQUE_LINK_LSA:
    case OSPF_OPAQUE_AREA_LSA:
      ospf_ls_retransmit_delete_nbr_area (lsa->area, lsa);
      break;
    case OSPF_OPAQUE_AS_LSA:
      if ((lsa0->area != NULL) && (lsa0->area->ospf != NULL))
        top = lsa0->area->ospf;
      ospf_ls_retransmit_delete_nbr_as (top, lsa);
      break;
    default:
      zlog_warn ("ospf_opaque_lsa_flush_schedule: Unexpected LSA-type(%u)", lsa->data->type);
      goto out;
    }

  /* Dequeue listnode entry from the list. */
  listnode_delete (oipt->id_list, oipi);

  /* Avoid misjudgement in the next lookup. */
  if (listcount (oipt->id_list) == 0)
    oipt->id_list->head = oipt->id_list->tail = NULL;

  /* Disassociate internal control information with the given lsa. */
  free_opaque_info_per_id ((void *) oipi);

  /* Force given lsa's age to MaxAge. */
  lsa->data->ls_age = htons (OSPF_LSA_MAXAGE);

  if (IS_DEBUG_OSPF_EVENT)
    zlog_debug ("Schedule Type-%u Opaque-LSA to FLUSH: [opaque-type=%u, opaque-id=%x]", lsa->data->type, GET_OPAQUE_TYPE (ntohl (lsa->data->id.s_addr)), GET_OPAQUE_ID (ntohl (lsa->data->id.s_addr)));

  /* This lsa will be flushed and removed eventually. */
  ospf_lsa_flush (top, lsa);

out:
  return;
}

void
ospf_opaque_self_originated_lsa_received (struct ospf_neighbor *nbr, 
                                          struct ospf_lsa *lsa)
{
  struct ospf *top;
  
  if ((top = oi_to_top (nbr->oi)) == NULL)
    return;

  /*
   * Since these LSA entries are not yet installed into corresponding
   * LSDB, just flush them without calling ospf_ls_maxage() afterward.
   */
  lsa->data->ls_age = htons (OSPF_LSA_MAXAGE);
  switch (lsa->data->type)
    {
    case OSPF_OPAQUE_LINK_LSA:
      ospf_flood_through_area (nbr->oi->area, NULL/*inbr*/, lsa);
      break;
    case OSPF_OPAQUE_AREA_LSA:
      ospf_flood_through_area (nbr->oi->area, NULL/*inbr*/, lsa);
      break;
    case OSPF_OPAQUE_AS_LSA:
      ospf_flood_through_as (top, NULL/*inbr*/, lsa);
      break;
    default:
      zlog_warn ("ospf_opaque_self_originated_lsa_received: Unexpected LSA-type(%u)", lsa->data->type);
      return;
    }
  ospf_lsa_discard (lsa); /* List "lsas" will be deleted by caller. */  
}

/*------------------------------------------------------------------------*
 * Followings are util functions; probably be used by Opaque-LSAs only...
 *------------------------------------------------------------------------*/

struct ospf *
oi_to_top (struct ospf_interface *oi)
{
  struct ospf *top = NULL;
  struct ospf_area *area;

  if (oi == NULL || (area = oi->area) == NULL || (top = area->ospf) == NULL)
    zlog_warn ("Broken relationship for \"OI -> AREA -> OSPF\"?");

  return top;
}

