Initial revision
diff --git a/isisd/isis_dr.c b/isisd/isis_dr.c
new file mode 100644
index 0000000..5b7d23e
--- /dev/null
+++ b/isisd/isis_dr.c
@@ -0,0 +1,373 @@
+/*
+ * IS-IS Rout(e)ing protocol - isis_dr.c
+ *                             IS-IS designated router related routines   
+ *
+ * Copyright (C) 2001,2002   Sampo Saaristo
+ *                           Tampere University of Technology      
+ *                           Institute of Communications Engineering
+ *
+ * This program is free software; you can redistribute it and/or modify it 
+ * under the terms of the GNU General Public Licenseas published by the Free 
+ * Software Foundation; either version 2 of the License, or (at your option) 
+ * any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc., 
+ * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+
+#include <zebra.h>
+#include <net/ethernet.h>
+
+#include "log.h"
+#include "hash.h"
+#include "thread.h"
+#include "linklist.h"
+#include "vty.h"
+#include "stream.h"
+#include "if.h"
+
+#include "isisd/dict.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_pdu.h"
+#include "isisd/isis_tlv.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_dr.h"
+#include "isisd/isis_events.h"
+
+extern struct isis *isis;
+extern struct thread_master *master;
+
+char *
+isis_disflag2string (int disflag) {
+
+  switch (disflag) {
+    case ISIS_IS_NOT_DIS:
+      return "is not DIS";
+    case ISIS_IS_DIS:
+      return "is DIS";
+    case ISIS_WAS_DIS:
+      return "was DIS";
+  default:
+    return "unknown DIS state";
+  }
+  return NULL; /* not reached */
+}
+
+
+
+int
+isis_run_dr_l1 (struct thread *thread)
+{
+  struct isis_circuit *circuit;
+  
+  circuit = THREAD_ARG (thread);
+  assert (circuit);
+
+  if (circuit->u.bc.run_dr_elect[0])
+    zlog_warn ("isis_run_dr(): run_dr_elect already set for l1");
+  
+  circuit->u.bc.run_dr_elect[0] = 1;
+    
+  return ISIS_OK;
+}
+
+int
+isis_run_dr_l2 (struct thread *thread)
+{
+  struct isis_circuit *circuit;
+  
+  circuit = THREAD_ARG (thread);
+  assert (circuit);
+
+  if (circuit->u.bc.run_dr_elect[1])
+    zlog_warn ("isis_run_dr(): run_dr_elect already set for l2");
+  
+  
+  circuit->u.bc.run_dr_elect[1] = 1;
+    
+  return ISIS_OK;
+}
+
+int
+isis_check_dr_change (struct isis_adjacency *adj, int level)
+{
+  int i;
+
+  if ( adj->dis_record[level-1].dis != 
+       adj->dis_record[(1*ISIS_LEVELS) + level - 1].dis) 
+    /* was there a DIS state transition ? */ 
+    {
+      adj->dischanges[level-1]++;
+      /* ok rotate the history list through */
+      for (i = DIS_RECORDS - 1; i > 0; i--) 
+	{
+	  adj->dis_record[(i * ISIS_LEVELS) + level - 1].dis = 
+            adj->dis_record[((i-1) * ISIS_LEVELS) + level - 1].dis;
+	  adj->dis_record[(i * ISIS_LEVELS) + level - 1].last_dis_change = 
+            adj->dis_record[((i-1) * ISIS_LEVELS) + level - 1].last_dis_change;
+	}
+    }
+  return ISIS_OK;
+}
+
+int 
+isis_dr_elect (struct isis_circuit *circuit, int level)
+{
+  struct list *adjdb;
+  struct listnode *node;
+  struct isis_adjacency *adj, *adj_dr = NULL;
+  struct list *list = list_new ();
+  u_char own_prio;
+  int biggest_prio = -1;
+  int cmp_res, retval = ISIS_OK;
+  
+  own_prio = circuit->u.bc.priority[level - 1];
+  adjdb = circuit->u.bc.adjdb[level - 1];
+
+  if (!adjdb) {
+    zlog_warn ("isis_dr_elect() adjdb == NULL");
+    retval = ISIS_WARNING;
+    list_delete (list);
+    goto out;
+  }
+  isis_adj_build_up_list (adjdb, list);
+
+  /*
+   * Loop the adjacencies and find the one with the biggest priority
+   */
+  for (node = listhead (list); node; nextnode (node)) {
+    adj = getdata (node);
+    /* clear flag for show output */
+    adj->dis_record[level-1].dis = ISIS_IS_NOT_DIS;  
+    adj->dis_record[level-1].last_dis_change = time (NULL);
+
+    if (adj->prio[level-1] > biggest_prio) {
+      biggest_prio = adj->prio[level-1];
+      adj_dr = adj;
+    } else if (adj->prio[level-1] == biggest_prio) {
+      /*
+       * Comparison of MACs breaks a tie
+       */
+      if (adj_dr) {
+        cmp_res = memcmp (adj_dr->snpa, adj->snpa, ETH_ALEN);
+        if (cmp_res < 0) {
+          adj_dr = adj;
+	}
+        if (cmp_res == 0)
+          zlog_warn ("isis_dr_elect(): multiple adjacencies with same SNPA"); 
+      } else {
+        adj_dr = adj;
+      }
+    }
+  }
+  
+  if (!adj_dr) {
+    /*
+     * Could not find the DR - means we are alone and thus the DR
+     */
+    if ( !circuit->u.bc.is_dr[level - 1]) {
+      list_delete (list);
+      list = NULL;
+      return isis_dr_commence (circuit, level);
+    }
+    goto out;
+  }
+
+  /*
+   * Now we have the DR adjacency, compare it to self
+   */
+  if (adj_dr->prio[level-1] < own_prio || (adj_dr->prio[level-1] == own_prio &&
+                                  memcmp (adj_dr->snpa, circuit->u.bc.snpa, 
+                                          ETH_ALEN) < 0)) {
+    if (!circuit->u.bc.is_dr[level - 1]) {
+      /*
+       * We are the DR -> commence
+       */
+      list_delete (list);
+      return isis_dr_commence (circuit, level);
+    }
+  } else {
+
+    /* ok we have found the DIS - lets mark the adjacency */
+    /* set flag for show output */
+    adj_dr->dis_record[level - 1].dis = ISIS_IS_DIS; 
+    adj_dr->dis_record[level - 1].last_dis_change = time(NULL);
+
+    /* now loop through a second time to check if there has been a DIS change
+     * if yes rotate the history log
+     */
+
+    for (node = listhead (list); node; nextnode (node)) {
+      adj = getdata (node);
+      isis_check_dr_change(adj, level);
+    }
+
+    /*
+     * We are not DR - if we were -> resign
+     */
+
+    if (circuit->u.bc.is_dr[level - 1]) {
+      list_delete (list);
+      return isis_dr_resign (circuit, level);
+    }
+  }
+ out:
+  if (list)
+    list_delete (list);
+  return retval;
+}
+
+int 
+isis_dr_resign (struct isis_circuit *circuit, int level)
+{
+  u_char id[ISIS_SYS_ID_LEN + 2];
+      
+  zlog_info ("isis_dr_resign l%d", level);
+
+  circuit->u.bc.is_dr[level - 1] = 0;
+  circuit->u.bc.run_dr_elect[level - 1] = 0; 
+  if (circuit->u.bc.t_run_dr[level - 1]) {
+    thread_cancel (circuit->u.bc.t_run_dr[level - 1]);
+    circuit->u.bc.t_run_dr[level - 1] = NULL;
+  }
+  if (circuit->u.bc.t_refresh_pseudo_lsp[level - 1]) {
+    thread_cancel (circuit->u.bc.t_refresh_pseudo_lsp[level - 1]);
+    circuit->u.bc.t_refresh_pseudo_lsp[level - 1] = NULL;
+  }
+  
+  memcpy (id, isis->sysid, ISIS_SYS_ID_LEN);
+  LSP_PSEUDO_ID(id) = circuit->circuit_id;
+  LSP_FRAGMENT(id) = 0;
+  lsp_purge_dr (id, circuit, level);
+
+  if (level == 1) {
+    memset (circuit->u.bc.l1_desig_is, 0, ISIS_SYS_ID_LEN + 1);
+    
+    if (circuit->t_send_csnp[0])
+      thread_cancel (circuit->t_send_csnp[0]);    
+    
+    circuit->u.bc.t_run_dr[0] =
+      thread_add_timer (master, isis_run_dr_l1, circuit,
+                        2 * circuit->hello_interval[1]);    
+
+    circuit->t_send_psnp[0] = 
+      thread_add_timer (master,
+                        send_l1_psnp,
+                        circuit,
+                        isis_jitter (circuit->psnp_interval[level - 1],
+                                     PSNP_JITTER));
+  } else {
+    memset (circuit->u.bc.l2_desig_is, 0, ISIS_SYS_ID_LEN + 1);
+
+    if (circuit->t_send_csnp[0])
+      thread_cancel (circuit->t_send_csnp[0]);
+
+    circuit->u.bc.t_run_dr[1] =
+      thread_add_timer (master, isis_run_dr_l2, circuit,
+                        2 * circuit->hello_interval[1]);    
+    circuit->t_send_psnp[1] = 
+      thread_add_timer (master,
+                        send_l2_psnp,
+                        circuit,
+                        isis_jitter (circuit->psnp_interval[level - 1],
+                                     PSNP_JITTER));
+  }
+  
+  thread_add_event (master, isis_event_dis_status_change, circuit, 0);
+
+  return ISIS_OK;
+}
+
+int 
+isis_dr_commence (struct isis_circuit *circuit, int level)
+{
+  u_char old_dr[ISIS_SYS_ID_LEN + 2];
+  
+  zlog_info ("isis_dr_commence l%d", level);
+
+  /* Lets keep a pause in DR election */
+  circuit->u.bc.run_dr_elect[level - 1] = 0;
+  if (level == 1) 
+    circuit->u.bc.t_run_dr[0] = 
+      thread_add_timer (master, isis_run_dr_l1, circuit,
+			2 * circuit->hello_multiplier[0] * 
+			circuit->hello_interval[0]); 
+  else 
+    circuit->u.bc.t_run_dr[1] = 
+      thread_add_timer (master, isis_run_dr_l2, circuit,
+			2 * circuit->hello_multiplier[1] * 
+			circuit->hello_interval[1]);	      	
+  circuit->u.bc.is_dr[level - 1] = 1;
+
+  if (level == 1) {
+    memcpy (old_dr, circuit->u.bc.l1_desig_is, ISIS_SYS_ID_LEN + 1);
+    LSP_FRAGMENT (old_dr) = 0;
+    if (LSP_PSEUDO_ID(old_dr)) {
+      /* there was a dr elected, purge its LSPs from the db */
+      lsp_purge_dr (old_dr, circuit, level);
+    }
+    memcpy (circuit->u.bc.l1_desig_is, isis->sysid, ISIS_SYS_ID_LEN);
+    *(circuit->u.bc.l1_desig_is + ISIS_SYS_ID_LEN) = circuit->circuit_id;
+    
+    assert (circuit->circuit_id); /* must be non-zero */
+    /*    if (circuit->t_send_l1_psnp)
+          thread_cancel (circuit->t_send_l1_psnp); */
+    lsp_l1_pseudo_generate (circuit);
+
+    circuit->u.bc.t_run_dr[0] =
+      thread_add_timer (master, isis_run_dr_l1, circuit,
+                        2 * circuit->hello_interval[0]);
+
+    circuit->t_send_csnp[0] = thread_add_timer (master,
+                                                send_l1_csnp,
+                                                circuit, 
+                                                isis_jitter 
+                                              (circuit->csnp_interval[level-1],
+                                               CSNP_JITTER));
+  } else {
+    memcpy (old_dr, circuit->u.bc.l2_desig_is, ISIS_SYS_ID_LEN + 1);
+    LSP_FRAGMENT (old_dr) = 0;
+    if (LSP_PSEUDO_ID(old_dr)) {
+      /* there was a dr elected, purge its LSPs from the db */
+      lsp_purge_dr (old_dr, circuit, level);
+    }
+    memcpy (circuit->u.bc.l2_desig_is, isis->sysid, ISIS_SYS_ID_LEN);
+    *(circuit->u.bc.l2_desig_is + ISIS_SYS_ID_LEN) = circuit->circuit_id;
+    
+    assert (circuit->circuit_id); /* must be non-zero */
+    /*    if (circuit->t_send_l1_psnp)
+          thread_cancel (circuit->t_send_l1_psnp); */
+    lsp_l2_pseudo_generate (circuit);
+
+    circuit->u.bc.t_run_dr[1] =
+      thread_add_timer (master, isis_run_dr_l2, circuit,
+                        2 * circuit->hello_interval[1]);    
+
+    circuit->t_send_csnp[1] = 
+      thread_add_timer (master,
+                        send_l2_csnp,
+                        circuit, 
+                        isis_jitter (circuit->csnp_interval[level-1],
+                                     CSNP_JITTER));
+        
+  } 
+
+  thread_add_event (master, isis_event_dis_status_change, circuit, 0);
+  
+  return ISIS_OK;
+}
+