Improvided PIM Hello option handling by creating the
PIMHelloOption class.  Also fixed a typo for pim regsiter
stop message type.

Change-Id: Iff06ce7d2746ebc34811205f4c4a4d4784e2740c
diff --git a/utils/misc/src/main/java/org/onlab/packet/PIM.java b/utils/misc/src/main/java/org/onlab/packet/PIM.java
index d306263..d9a5e83 100755
--- a/utils/misc/src/main/java/org/onlab/packet/PIM.java
+++ b/utils/misc/src/main/java/org/onlab/packet/PIM.java
@@ -33,7 +33,7 @@
 
     public static final byte TYPE_HELLO = 0x00;
     public static final byte TYPE_REGISTER = 0x01;
-    public static final byte TYPE_REQUEST_STOP = 0x02;
+    public static final byte TYPE_REGISTER_STOP = 0x02;
     public static final byte TYPE_JOIN_PRUNE_REQUEST = 0x03;
     public static final byte TYPE_BOOTSTRAP = 0x04;
     public static final byte TYPE_ASSERT = 0x05;
diff --git a/utils/misc/src/main/java/org/onlab/packet/pim/PIMHello.java b/utils/misc/src/main/java/org/onlab/packet/pim/PIMHello.java
index d454f1a..9ad3fdb 100644
--- a/utils/misc/src/main/java/org/onlab/packet/pim/PIMHello.java
+++ b/utils/misc/src/main/java/org/onlab/packet/pim/PIMHello.java
@@ -19,88 +19,41 @@
 import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPacket;
 import org.onlab.packet.IpAddress;
-
 import java.nio.ByteBuffer;
-import java.util.Random;
+import java.util.HashMap;
+import java.util.Map;
 
 import static org.onlab.packet.PacketUtils.checkInput;
 
 public class PIMHello extends BasePacket {
 
     private IpAddress nbrIpAddress;
-
-    private int holdtime = 105;
-    private int genid = 0;
-    private int priority = 1;
     private boolean priorityPresent = false;
 
-    public static final int MINIMUM_OPTION_LEN_BYTES = 4;
+    private Map<Short, PIMHelloOption> options = new HashMap<>();
 
     /**
-     * PIM Option types.
+     * Create a PIM Hello packet with the most common hello options and default
+     * values.  The values of any options can be easily changed by modifying the value of
+     * the option with the desired change.
      */
-    public enum Option {
-        HOLDTIME  (1, 2),
-        PRUNEDELAY(2, 4),
-        PRIORITY  (19, 4),
-        GENID     (20, 4),
-        ADDRLIST  (24, 0);
-
-        private final int optType;
-        private final int optLen;
-
-        Option(int ot, int ol) {
-            this.optType = ot;
-            this.optLen = ol;
-        }
-
-        public int optType() {
-            return this.optType;
-        }
-
-        public int optLen() {
-            return this.optLen;
-        }
+    public void createDefaultOptions() {
+        options.put(PIMHelloOption.OPT_HOLDTIME, new PIMHelloOption(PIMHelloOption.OPT_HOLDTIME));
+        options.put(PIMHelloOption.OPT_PRIORITY, new PIMHelloOption(PIMHelloOption.OPT_PRIORITY));
+        options.put(PIMHelloOption.OPT_GENID, new PIMHelloOption(PIMHelloOption.OPT_GENID));
     }
 
     /**
-     * Add the holdtime to the packet.
+     * Add a PIM Hello option to this hello message.  Note
      *
-     * @param holdtime the holdtime in seconds
+     * @param opt the PIM Hello option we are adding
      */
-    public void addHoldtime(int holdtime) {
-        this.holdtime = holdtime;
+    public void addOption(PIMHelloOption opt) {
+        this.options.put(opt.getOptType(), opt);
     }
 
-    /**
-     * Add the hello priority.
-     *
-     * @param priority default is 1, the higher the better
-     */
-    public void addPriority(int priority) {
-        this.priority = priority;
-        this.priorityPresent = true;
-    }
-
-    /**
-     * Add a Gen ID.
-     *
-     * @param genid a random generated number, changes only after reset.
-     */
-    public void addGenId(int genid) {
-        if (genid == 0) {
-            this.addGenId();
-        } else {
-            this.genid = genid;
-        }
-    }
-
-    /**
-     * Add the genid.  Let this function figure out the number.
-     */
-    public void addGenId() {
-        Random rand = new Random();
-        this.genid = rand.nextInt();
+    public Map<Short, PIMHelloOption> getOptions() {
+        return this.options;
     }
 
     /**
@@ -111,68 +64,54 @@
      */
     @Override
     public byte[] serialize() {
+        int totalLen = 0;
 
-        // TODO: Figure out a better way to calculate buffer size
-        int size = Option.PRIORITY.optLen() + 4 +
-                Option.GENID.optLen() + 4 +
-                Option.HOLDTIME.optLen() + 4;
 
-        byte[] data = new byte[size];      // Come up with something better
+         // Since we are likely to only have 3-4 options, go head and walk the
+         // hashmap twice, once to calculate the space needed to allocate a
+         // buffer, the second time serialize the options into the buffer.  This
+         // saves us from allocating an over sized buffer the re-allocating and
+         // copying.
+        for (Short optType : options.keySet()) {
+            PIMHelloOption opt = options.get(optType);
+            totalLen += PIMHelloOption.MINIMUM_OPTION_LEN_BYTES + opt.getOptLength();
+        }
+
+        byte[] data = new byte[totalLen];
         ByteBuffer bb = ByteBuffer.wrap(data);
 
-        // Add the priority
-        bb.putShort((short) Option.PRIORITY.optType);
-        bb.putShort((short) Option.PRIORITY.optLen);
-        bb.putInt(this.priority);
-
-        // Add the genid
-        bb.putShort((short) Option.GENID.optType);
-        bb.putShort((short) Option.GENID.optLen);
-        bb.putInt(this.genid);
-
-        // Add the holdtime
-        bb.putShort((short) Option.HOLDTIME.optType);
-        bb.putShort((short) Option.HOLDTIME.optLen);
-        bb.putShort((short) this.holdtime);
+        // Now serialize the data.
+        for (Short optType : options.keySet()) {
+            PIMHelloOption opt = options.get(optType);
+            bb.put(opt.serialize());
+        }
         return data;
     }
 
     /**
      * XXX: This is deprecated, DO NOT USE, use the deserializer() function instead.
      */
-    // @Override
     public IPacket deserialize(final byte[] data, final int offset,
                                final int length) {
-        //
+        // TODO: throw an expection?
         return null;
     }
 
     /**
      * Deserialize this hello message.
      *
-     * @return a deserialized hello message.
+     * @return a deserialized hello message
      */
     public static Deserializer<PIMHello> deserializer() {
         return (data, offset, length) -> {
-            checkInput(data, offset, length, MINIMUM_OPTION_LEN_BYTES);
+            checkInput(data, offset, length, PIMHelloOption.MINIMUM_OPTION_LEN_BYTES);
             final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
 
             PIMHello hello = new PIMHello();
             while (bb.hasRemaining()) {
-                int optType = bb.getShort();
-                int optLen  = bb.getShort();
-
-                // Check that we have enough buffer for the next option.
-                checkInput(data, bb.position(), bb.limit() - bb.position(), optLen);
-                if (optType == Option.GENID.optType) {
-                    hello.addGenId(bb.getInt());
-                } else if (optType == Option.PRIORITY.optType) {
-                    hello.addPriority(bb.getInt());
-                } else if (optType == Option.HOLDTIME.optType) {
-                    hello.addHoldtime((int) bb.getShort());
-                }
+                PIMHelloOption opt = PIMHelloOption.deserialize(bb);
+                hello.addOption(opt);
             }
-
             return hello;
         };
     }
diff --git a/utils/misc/src/main/java/org/onlab/packet/pim/PIMHelloOption.java b/utils/misc/src/main/java/org/onlab/packet/pim/PIMHelloOption.java
new file mode 100644
index 0000000..bf021fb
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/pim/PIMHelloOption.java
@@ -0,0 +1,147 @@
+/*
+ * 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.onlab.packet.pim;
+
+import org.onlab.packet.DeserializationException;
+
+import java.nio.ByteBuffer;
+import java.text.MessageFormat;
+
+import static org.onlab.packet.PacketUtils.checkBufferLength;
+import static org.onlab.packet.PacketUtils.checkInput;
+
+public class PIMHelloOption {
+
+    /**
+     * PIM Option types.
+     */
+    public static final short OPT_HOLDTIME = 1;
+    public static final short OPT_PRUNEDELAY = 2;
+    public static final short OPT_PRIORITY = 19;
+    public static final short OPT_GENID = 20;
+    public static final short OPT_ADDRLIST = 24;
+
+    public static final short DEFAULT_HOLDTIME = 105;
+    public static final int DEFAULT_PRUNEDELAY = 2000; // 2,000 ms
+    public static final int DEFAULT_PRIORITY = 1;
+    public static final int DEFAULT_GENID = 0;
+
+    public static final int MINIMUM_OPTION_LEN_BYTES = 4;
+
+    // Values for this particular hello option.
+    private short optType;
+    private short optLength;
+    private byte[] optValue;
+
+    public PIMHelloOption() {
+    }
+
+    /**
+     * Set a PIM Hello option by type. The length and default value of the
+     * type will be auto filled in by default.
+     *
+     * @param type hello option type
+     */
+    public PIMHelloOption(short type) {
+        this.optType = type;
+        switch (type) {
+            case OPT_HOLDTIME:
+                this.optLength = 2;
+                this.optValue = new byte[optLength];
+                ByteBuffer.wrap(this.optValue).putShort(PIMHelloOption.DEFAULT_HOLDTIME);
+                break;
+
+            case OPT_PRUNEDELAY:
+                this.optLength = 4;
+                this.optValue = new byte[this.optLength];
+                ByteBuffer.wrap(this.optValue).putInt(PIMHelloOption.DEFAULT_PRUNEDELAY);
+                break;
+
+            case OPT_PRIORITY:
+                this.optLength = 4;
+                this.optValue = new byte[this.optLength];
+                ByteBuffer.wrap(this.optValue).putInt(PIMHelloOption.DEFAULT_PRIORITY);
+                break;
+
+            case OPT_GENID:
+                this.optLength = 4;
+                this.optValue = new byte[this.optLength];
+                ByteBuffer.wrap(this.optValue).putInt(PIMHelloOption.DEFAULT_GENID);
+                break;
+
+            case OPT_ADDRLIST:
+                this.optLength = 0;   // We don't know what the length will be yet.
+                this.optValue = null;
+
+            default:
+                //log.error("Unkown option type: " + type + "\n" );
+                return;
+        }
+    }
+
+    public void setOptType(short type) {
+        this.optType = type;
+    }
+
+    public short getOptType() {
+        return this.optType;
+    }
+
+    public void setOptLength(short len) {
+        this.optLength = len;
+    }
+
+    public short getOptLength() {
+        return this.optLength;
+    }
+
+    public void setValue(ByteBuffer bb) throws DeserializationException {
+        this.optValue = new byte[this.optLength];
+        bb.get(this.optValue, 0, this.optLength);
+    }
+
+    public byte[] getValue() {
+        return this.optValue;
+    }
+
+    public static PIMHelloOption deserialize(ByteBuffer bb) throws DeserializationException {
+        checkInput(bb.array(), bb.position(), bb.limit() - bb.position(), 4);
+
+        PIMHelloOption opt = new PIMHelloOption();
+        opt.setOptType(bb.getShort());
+        opt.setOptLength(bb.getShort());
+
+        checkBufferLength(bb.limit(), bb.position(), opt.getOptLength());
+        opt.setValue(bb);
+
+        return opt;
+    }
+
+    public byte [] serialize() {
+        int len = 4 + this.optLength;
+        ByteBuffer bb = ByteBuffer.allocate(len);
+        bb.putShort(this.optType);
+        bb.putShort(this.optLength);
+        bb.put(this.optValue);
+        return bb.array();
+    }
+
+    public String toString() {
+        return MessageFormat.format("Type: {0}, len: {1} value: {2}", this.optType, this.optLength,
+                (this.optValue == null) ? "null" : this.optValue.toString());
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/PIMTest.java b/utils/misc/src/test/java/org/onlab/packet/PIMTest.java
index bed4733..7fba3cd 100644
--- a/utils/misc/src/test/java/org/onlab/packet/PIMTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/PIMTest.java
@@ -46,6 +46,11 @@
     private PIM pimJoinPrune;
     private PIMJoinPrune joinPrune;
 
+    /**
+     * Create PIM Hello and Join/Prune packets to be used in testing.
+     *
+     * @throws Exception if packet creation fails
+     */
     @Before
     public void setUp() throws Exception {
 
@@ -56,9 +61,7 @@
         pimHello.setChecksum((short) 0);
 
         hello = new PIMHello();
-        hello.addHoldtime(0xd2);
-        hello.addPriority(44);
-        hello.addGenId(0xf00d);
+        hello.createDefaultOptions();
         pimHello.setPayload(hello);
         hello.setParent(pimHello);
 
@@ -81,20 +84,32 @@
         deserializer = PIM.deserializer();
     }
 
+    /**
+     * Make sure our deserializer throws an exception if we recieve bad input.
+     *
+     * @throws Exception if we are given bad input.
+     */
     @Test
-    public void testDerserializeBadInput() throws Exception {
+    public void testDeserializeBadInput() throws Exception {
         PacketTestUtils.testDeserializeBadInput(deserializer);
     }
 
+    /**
+     * Verify we throw an exception if we receive a truncated Join/Prune message.
+     *
+     * @throws Exception if we receive a truncated Join/Prune message.
+     */
     @Test
     public void testDeserializeTruncated() throws Exception {
-        //byte [] bits = pimHello.serialize();
-        //PacketTestUtils.testDeserializeTruncated(deserializer, bits);
-
         byte [] bits = pimJoinPrune.serialize();
         PacketTestUtils.testDeserializeTruncated(deserializer, bits);
     }
 
+    /**
+     * Verify that we correctly deserialize hello messages.
+     *
+     * @throws Exception if our input is bad or truncated.
+     */
     @Test
     public void testDeserializeHello() throws Exception {
         byte [] data = pimHello.serialize();
@@ -102,6 +117,11 @@
         assertTrue(pim.equals(pimHello));
     }
 
+    /**
+     * Verify that we correctly deserialize Join/Prune messages.
+     *
+     * @throws Exception if our input is bad or truncated.
+     */
     @Test
     public void testDeserializeJoinPrune() throws Exception {
         byte [] data = pimJoinPrune.serialize();