Added PIM neighbors

Change-Id: Ibce9741be02b9e79e53780adc2ce272698a70ee6
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java
index 8bf2333..f54b7b2 100644
--- a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java
@@ -26,8 +26,11 @@
 import org.onosproject.net.host.InterfaceIpAddress;
 import org.slf4j.Logger;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Set;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -52,6 +55,12 @@
     // Our current genid
     private int genid      = PIMHelloOption.DEFAULT_GENID;   // Needs to be assigned.
 
+    // The IP address of the DR
+    IpAddress drIpaddress;
+
+    // A map of all our PIM neighbors keyed on our neighbors IP address
+    private Map<IpAddress, PIMNeighbor> pimNeighbors = new HashMap<>();
+
     /**
      * Create a PIMInterface from an ONOS Interface.
      *
@@ -59,6 +68,17 @@
      */
     public PIMInterface(Interface intf) {
         onosInterface = intf;
+        IpAddress ourIp = getIpAddress();
+        MacAddress mac = intf.mac();
+
+        // Create a PIM Neighbor to represent ourselves for DR election.
+        PIMNeighbor us = new PIMNeighbor(ourIp, mac);
+
+        // Priority and IP address are all we need to DR election.
+        us.setPriority(priority);
+
+        pimNeighbors.put(ourIp, us);
+        drIpaddress = ourIp;
     }
 
     /**
@@ -67,7 +87,8 @@
      * @return ONOS Interface.
      */
     public Interface getInterface() {
-        return this.onosInterface;
+        return onosInterface;
+
     }
 
     /**
@@ -76,7 +97,7 @@
      * @param intf ONOS Interface.
      */
     public PIMInterface setInterface(Interface intf) {
-        this.onosInterface = intf;
+        onosInterface = intf;
         return this;
     }
 
@@ -86,7 +107,7 @@
      * @return a set of Ip Addresses on this interface
      */
     public Set<InterfaceIpAddress> getIpAddresses() {
-        return this.onosInterface.ipAddresses();
+        return onosInterface.ipAddresses();
     }
 
     /**
@@ -113,7 +134,7 @@
      * @return the holdtime
      */
     public short getHoldtime() {
-        return this.holdtime;
+        return holdtime;
     }
 
     /**
@@ -122,7 +143,7 @@
      * @return The prune delay
      */
     public int getPruneDelay() {
-        return this.pruneDelay;
+        return pruneDelay;
     }
 
     /**
@@ -131,7 +152,7 @@
      * @return our priority
      */
     public int getPriority() {
-        return this.priority;
+        return priority;
     }
 
     /**
@@ -140,11 +161,18 @@
      * @return our generation ID
      */
     public int getGenid() {
-        return this.genid;
+        return genid;
     }
 
     /**
-     * Process an incoming PIM Hello message.
+     * Process an incoming PIM Hello message.  There are a few things going on in
+     * this method:
+     * <ul>
+     *     <li>We <em>may</em> have to create a new neighbor if one does not already exist</li>
+     *     <li>We <em>may</em> need to re-elect a new DR if new information is received</li>
+     *     <li>We <em>may</em> need to send an existing neighbor all joins if the genid changed</li>
+     *     <li>We will refresh the neighbors timestamp</li>
+     * </ul>
      *
      * @param ethPkt the Ethernet packet header
      */
@@ -163,32 +191,69 @@
             return;
         }
 
-        // TODO: Maybe a good idea to check the checksum.  Let's jump into the hello options.
+        // get the DR values for later calculation
+        PIMNeighbor dr = pimNeighbors.get(drIpaddress);
+        checkNotNull(dr);
+
+        IpAddress drip = drIpaddress;
+        int drpri = dr.getPriority();
+
+        // Assume we do not need to run a DR election
+        boolean reElectDr = false;
+        boolean genidChanged = false;
+
         PIMHello hello = (PIMHello) pimhdr.getPayload();
 
-        // TODO: this is about where we find or create our PIMNeighbor
-
-        boolean reElectDr = false;
-
-        // Start walking through all the hello options to handle accordingly.
-        for (PIMHelloOption opt : hello.getOptions().values()) {
-
-            // TODO: This is where we handle the options and modify the neighbor accordingly.
-            // We'll need to create the PIMNeighbor class next.  Depending on what happens
-            // we may need to re-elect a DR
+        // Determine if we already have a PIMNeighbor
+        PIMNeighbor nbr = pimNeighbors.getOrDefault(srcip, null);
+        if (nbr == null) {
+            nbr = new PIMNeighbor(srcip, hello.getOptions());
+            checkNotNull(nbr);
+        } else {
+            Integer previousGenid = nbr.getGenid();
+            nbr.addOptions(hello.getOptions());
+            if (previousGenid != nbr.getGenid()) {
+                genidChanged = true;
+            }
         }
 
-        if (reElectDr) {
-            // TODO: create an election() method and call it here with a PIMNeighbor
+        // Refresh this neighbors timestamp
+        nbr.refreshTimestamp();
+
+        /*
+         * the election method will frist determine if an election
+         * needs to be run, if so it will run the election.  The
+         * IP address of the DR will be returned.  If the IP address
+         * of the DR is different from what we already have we know a
+         * new DR has been elected.
+         */
+        IpAddress electedIp = election(nbr, drip, drpri);
+        if (!drip.equals(electedIp)) {
+            // we have a new DR.
+            drIpaddress = electedIp;
         }
     }
 
+    // Run an election if we need to.  Return the elected IP address.
+    private IpAddress election(PIMNeighbor nbr, IpAddress drip, int drpri) {
+
+        IpAddress nbrip = nbr.getIpaddr();
+        if (nbr.getPriority() > drpri) {
+            return nbrip;
+        }
+
+        if (nbrip.compareTo(drip) > 0) {
+            return nbrip;
+        }
+        return drip;
+    }
+
     /**
      * Process an incoming PIM JoinPrune message.
      *
      * @param ethPkt the Ethernet packet header.
      */
     public void processJoinPrune(Ethernet ethPkt) {
-
+        // TODO: add Join/Prune processing code.
     }
 }
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbor.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbor.java
new file mode 100644
index 0000000..a1f9eec
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbor.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.pim.impl;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.pim.PIMHelloOption;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Map;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class PIMNeighbor {
+
+    private final Logger log = getLogger(getClass());
+
+    // IP Address of this neighbor
+    private IpAddress ipAddr;
+
+    // MAC Address of the neighbor (Need for sending J/P)
+    private MacAddress macAddr;
+
+    // Hello Options
+    // Our hello opt holdTime
+    private short holdTime;
+
+    // Our hello opt prune delay
+    private int pruneDelay;
+
+    // Neighbor priority
+    private int priority;
+
+    // Our current genId
+    private int genId;
+
+    // Our timestamp for this neighbor
+    private Date lastRefresh;
+
+    /**
+     * Construct a new PIM Neighbor.
+     *
+     * @param ipAddr the IP Address of our new neighbor
+     */
+    public PIMNeighbor(IpAddress ipAddr, Map<Short, PIMHelloOption> opts) {
+        this.ipAddr = ipAddr;
+        this.addOptions(opts);
+    }
+
+    /**
+     * Construct a new PIM neighbor.
+     *
+     * @param ipAddr the neighbors IP addr
+     */
+    public PIMNeighbor(IpAddress ipAddr, MacAddress macAddr) {
+        this.ipAddr = ipAddr;
+        this.macAddr = macAddr;
+    }
+
+    /**
+     * Get the MAC address of this neighbor.
+     *
+     * @return the mac address
+     */
+    public MacAddress getMacaddr() {
+        return macAddr;
+    }
+
+    /**
+     * Get the IP Address of our neighbor.
+     *
+     * @return the IP address of our neighbor
+     */
+    public IpAddress getIpaddr() {
+        return ipAddr;
+    }
+
+    /**
+     * Set the IP address of our neighbor.
+     *
+     * @param ipAddr our neighbors IP address
+     */
+    public void setIpaddr(IpAddress ipAddr) {
+        this.ipAddr = ipAddr;
+    }
+
+    /**
+     * Get our neighbors holdTime.
+     *
+     * @return the holdTime
+     */
+    public short getHoldtime() {
+        return holdTime;
+    }
+
+    /**
+     * Set our neighbors holdTime.
+     *
+     * @param holdTime the holdTime
+     */
+    public void setHoldtime(short holdTime) {
+        this.holdTime = holdTime;
+    }
+
+    /**
+     * Get our neighbors prune delay.
+     *
+     * @return our neighbors prune delay
+     */
+    public int getPruneDelay() {
+        return pruneDelay;
+    }
+
+    /**
+     * Set our neighbors prune delay.
+     *
+     * @param pruneDelay the prune delay
+     */
+    public void setPruneDelay(int pruneDelay) {
+        this.pruneDelay = pruneDelay;
+    }
+
+    /**
+     * Get our neighbors priority.
+     *
+     * @return our neighbors priority
+     */
+    public int getPriority() {
+        return priority;
+    }
+
+    /**
+     * Set our neighbors priority.
+     *
+     * @param priority our neighbors priority
+     */
+    public void setPriority(int priority) {
+        this.priority = priority;
+    }
+
+    /**
+     * Get our neighbors Genid.
+     *
+     * @return our neighbor Genid
+     */
+    public int getGenid() {
+        return genId;
+    }
+
+    /**
+     * Set our neighbors GenId.
+     *
+     * @param genId our neighbors GenId
+     */
+    public void setGenid(int genId) {
+        this.genId = genId;
+    }
+
+    /**
+     * Add the options for this neighbor if needed.
+     *
+     * @param opts the options to be added/modified
+     * @return true if options changed, false if no option has changed
+     */
+    public boolean addOptions(Map<Short, PIMHelloOption> opts) {
+
+        boolean changed = false;
+
+        for (PIMHelloOption opt : opts.values()) {
+            Short otype = opt.getOptType();
+            ByteBuffer val = ByteBuffer.wrap(opt.getValue());
+
+            if (otype == PIMHelloOption.OPT_ADDRLIST) {
+                // TODO: Will implement someday
+            } else if (otype == PIMHelloOption.OPT_GENID) {
+                int newval = val.getInt();
+                if (newval != genId) {
+                    genId = newval;
+                    changed = true;
+                }
+            } else if (otype == PIMHelloOption.OPT_HOLDTIME) {
+                short newval = val.getShort();
+                if (newval != holdTime) {
+                    holdTime = newval;
+                    changed = true;
+                }
+            } else if (otype == PIMHelloOption.OPT_PRIORITY) {
+                int newval = val.getInt();
+                if (newval != priority) {
+                    priority = newval;
+                    changed = true;
+                }
+            } else if (otype == PIMHelloOption.OPT_PRUNEDELAY) {
+                int newval = val.getInt();
+                if (newval != pruneDelay) {
+                    pruneDelay = newval;
+                    changed = true;
+                }
+            } else {
+                log.warn("received unknown pim hello options" + otype);
+            }
+        }
+        return changed;
+    }
+
+    /**
+     * Refresh this neighbors timestamp.
+     */
+    public void refreshTimestamp() {
+        lastRefresh = Calendar.getInstance().getTime();
+    }
+}