Added packet handling functions for PIM, Specifically
PIM Hello and PIM Join/Prune messages along with
respective PIM encoded address types

Change-Id: Iaef2e3581e27fa910ad355043bcb3e175238706a
diff --git a/utils/misc/src/main/java/org/onlab/packet/pim/PIMAddrGroup.java b/utils/misc/src/main/java/org/onlab/packet/pim/PIMAddrGroup.java
new file mode 100644
index 0000000..195dc92
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/pim/PIMAddrGroup.java
@@ -0,0 +1,256 @@
+/*
+ * 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 org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip6Address;
+
+import java.nio.ByteBuffer;
+
+import static org.onlab.packet.PacketUtils.checkInput;
+
+public class PIMAddrGroup {
+    private byte family;
+    private byte encType;
+    private byte reserved;
+    private boolean bBit;
+    private boolean zBit;
+    private byte masklen;
+    IpAddress addr;
+
+    public static final int ENC_GROUP_IPV4_BYTE_LENGTH = 4 + Ip4Address.BYTE_LENGTH;
+    public static final int ENC_GROUP_IPV6_BYTE_LENGTH = 4 + Ip6Address.BYTE_LENGTH;
+
+    /**
+     * PIM Encoded Group Address.
+     */
+    public PIMAddrGroup() {
+        this.family = 4;
+        this.encType = 0;
+        this.reserved = 0;
+        this.bBit = false;
+        this.zBit = false;
+    }
+
+    /**
+     * PIM Encoded Source Address.
+     *
+     * @param addr IPv4 or IPv6
+     */
+    public PIMAddrGroup(String addr) {
+        this.setAddr(addr);
+    }
+
+    /**
+     * PIM Encoded Group Address.
+     *
+     * @param gpfx PIM encoded group address.
+     */
+    public PIMAddrGroup(IpPrefix gpfx) {
+        this.setAddr(gpfx);
+    }
+
+    /**
+     * PIM encoded source address.
+     *
+     * @param addr IPv4 or IPv6
+     */
+    public void setAddr(String addr) {
+        setAddr(IpPrefix.valueOf(addr));
+    }
+
+    /**
+     * Set the encoded source address.
+     *
+     * @param pfx
+     */
+    public void setAddr(IpPrefix pfx) {
+        this.addr = pfx.address();
+        this.masklen = (byte) pfx.prefixLength();
+        this.family = (byte) ((this.addr.isIp4()) ? 4 : 6);
+    }
+
+    /**
+     * Get the IP family of this address: 4 or 6.
+     *
+     * @return the IP address family
+     */
+    public int getFamily() {
+        return this.family;
+    }
+
+    /**
+     * Get the address of this encoded address.
+     *
+     * @return source address
+     */
+    public IpAddress getAddr() {
+        return this.addr;
+    }
+
+    /**
+     * Get the masklen of the group address.
+     *
+     * @return the masklen
+     */
+    public int getMasklen() {
+        return this.masklen;
+    }
+
+    /**
+     * Return the z bit for admin scoping. Only used for the Bootstrap router.
+     *
+     * @return true or false
+     */
+    public boolean getZBit() {
+        return this.zBit;
+    }
+
+    /**
+     * Return the bBit. Used to indicate this is a bidir
+     *
+     * @return return true or false.
+     */
+    public boolean getBBit() {
+        return this.bBit;
+    }
+
+    /**
+     * The size in bytes of a serialized address.
+     *
+     * @return the number of bytes when serialized
+     */
+    public int getByteSize() {
+        int size = 4;
+        size += addr.isIp4() ? 4 : 16;
+        return size;
+    }
+
+    /**
+     * Serialize this group address.
+     *
+     * @return the serialized address in a buffer.
+     */
+    public byte[] serialize() {
+        int len = getByteSize();
+
+        final byte[] data = new byte[len];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.put(this.family);
+        bb.put(this.encType);
+
+        // Todo: technically we should be setting the B and Z bits, but we'll never use them.
+        bb.put(reserved);
+
+        bb.put(this.masklen);
+        bb.put(this.addr.toOctets());
+        return data;
+    }
+
+    /**
+     * Deserialze from a ByteBuffer.
+     *
+     * @param bb the ByteBuffer
+     * @return an encoded PIM group address.
+     */
+    public PIMAddrGroup deserialize(ByteBuffer bb) throws DeserializationException {
+
+        /*
+         * We need to verify that we have enough buffer space.  First we'll assume that
+         * we are decoding an IPv4 address.  After we read the first by (address family),
+         * we'll determine if we actually need more buffer space for an IPv6 address.
+         */
+        checkInput(bb.array(), bb.position(), bb.limit() - bb.position(), ENC_GROUP_IPV4_BYTE_LENGTH);
+
+        this.family = bb.get();
+        if (family != 4 && family != 6) {
+            throw new DeserializationException("Illegal IP version number: " + family + "\n");
+        } else if (family == 6) {
+
+            // Check for one less by since we have already read the first byte of the packet.
+            checkInput(bb.array(), bb.position(), bb.limit() - bb.position(), ENC_GROUP_IPV6_BYTE_LENGTH - 1);
+        }
+
+        this.encType = bb.get();
+        this.reserved = bb.get();
+        if ((this.reserved & 0x80) != 0) {
+            this.bBit = true;
+        }
+        if ((this.reserved & 0x01) != 0) {
+            this.zBit = true;
+        }
+        // Remove the z and b bits from reserved
+        this.reserved |= 0x7d;
+
+        this.masklen = bb.get();
+        if (this.family == 4) {
+            this.addr = IpAddress.valueOf(bb.getInt());
+        } else if (this.family == 6) {
+            this.addr = Ip6Address.valueOf(bb.array(), 2);
+        }
+        return this;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 2521;
+        int result = super.hashCode();
+        result = prime * result + this.family;
+        result = prime * result + this.encType;
+        result = prime * result + this.reserved;
+        result = prime * result + this.masklen;
+        result = prime * result + this.addr.hashCode();
+        return result;
+    }
+
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals()
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof PIMAddrGroup)) {
+            return false;
+        }
+        final PIMAddrGroup other = (PIMAddrGroup) obj;
+        if (this.family != this.family) {
+            return false;
+        }
+
+        if (this.encType != other.encType) {
+            return false;
+        }
+
+        if (!this.addr.equals(other.addr)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/pim/PIMAddrSource.java b/utils/misc/src/main/java/org/onlab/packet/pim/PIMAddrSource.java
new file mode 100644
index 0000000..2d4a781
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/pim/PIMAddrSource.java
@@ -0,0 +1,281 @@
+/*
+ * 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 org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip6Address;
+
+import java.nio.ByteBuffer;
+
+import static org.onlab.packet.PacketUtils.checkInput;
+
+public class PIMAddrSource {
+    private byte family;
+    private byte encType;
+    private byte reserved;
+    private boolean sBit;
+    private boolean wBit;
+    private boolean rBit;
+    private byte masklen;
+    IpAddress addr;
+
+    public static final int ENC_SOURCE_IPV4_BYTE_LENGTH = 4 + Ip4Address.BYTE_LENGTH;
+    public static final int ENC_SOURCE_IPV6_BYTE_LENGTH = 4 + Ip6Address.BYTE_LENGTH;
+
+    /**
+     * PIM Encoded Source Address.
+     *
+     * @param addr IPv4 or IPv6
+     */
+    public PIMAddrSource(String addr) {
+        this.init();
+        this.setAddr(addr);
+    }
+
+    /**
+     * PIM Encoded Source Address.
+     *
+     * @param spfx IPv4 or IPv6
+     */
+    public PIMAddrSource(IpPrefix spfx) {
+        this.init();
+        this.setAddr(spfx);
+    }
+
+    /**
+     * PIM Encoded Group Address.
+     */
+    public PIMAddrSource() {
+        this.init();
+    }
+
+    private void init() {
+        this.family = 4;
+        this.encType = 0;
+        this.reserved = 0;
+        this.sBit = true;
+        this.wBit = false;
+        this.rBit = false;
+    }
+
+    /**
+     * PIM Encoded Source Address.
+     *
+     * @param addr IPv4 or IPv6
+     */
+    public void setAddr(String addr) {
+        IpPrefix spfx = IpPrefix.valueOf(addr);
+        setAddr(spfx);
+    }
+
+    /**
+     * PIM Encoded Source Address.
+     *
+     * @param spfx IPv4 or IPv6 address prefix
+     */
+    public void setAddr(IpPrefix spfx) {
+        this.addr = spfx.address();
+        this.masklen = (byte) spfx.prefixLength();
+        this.family = (byte) ((this.addr.isIp4()) ? 4 : 6);
+    }
+
+    /**
+     * Get the IP family of this address: 4 or 6.
+     *
+     * @return the IP address family
+     */
+    public byte getFamily() {
+        return this.family;
+    }
+
+    /**
+     * Get the address of this encoded address.
+     *
+     * @return source address
+     */
+    public IpAddress getAddr() {
+        return this.addr;
+    }
+
+    /**
+     * Get the masklen of the group address.
+     *
+     * @return the masklen
+     */
+    public int getMasklen() {
+        return this.masklen;
+    }
+
+    /**
+     * Return the sparse bit.
+     *
+     * @return true or false
+     */
+    public boolean getSBit() {
+        return this.sBit;
+    }
+
+    /**
+     * Return the wBit, used in Join/Prune messages.
+     *
+     * @return return true or false.
+     */
+    public boolean getWBit() {
+        return this.wBit;
+    }
+
+    /**
+     * Return the rBit. Used by Rendezvous Point.
+     *
+     * @return the rBit.
+     */
+    public boolean getRBit() {
+        return this.rBit;
+    }
+
+    /**
+     * The size in bytes of a serialized address.
+     *
+     * @return the number of bytes when serialized
+     */
+    public int getByteSize() {
+        int size = 4;
+        size += addr.isIp4() ? 4 : 16;
+        return size;
+    }
+
+    public byte[] serialize() {
+        int len = addr.isIp4() ? ENC_SOURCE_IPV4_BYTE_LENGTH : ENC_SOURCE_IPV6_BYTE_LENGTH;
+
+        final byte[] data = new byte[len];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.put(this.family);
+        bb.put(this.encType);
+
+        // Todo: technically we should be setting the B and Z bits, but we'll never use them.
+        byte mask = 0x0;
+        if (this.sBit) {
+            this.reserved |= 0x4;
+        }
+        if (this.wBit) {
+            this.reserved |= 0x2;
+        }
+        if (this.rBit) {
+            this.reserved |= 0x1;
+        }
+        bb.put(reserved);
+
+        bb.put(this.masklen);
+        bb.put(this.addr.toOctets());
+        return data;
+    }
+
+    public PIMAddrSource deserialize(byte[] data, int offset, int length) throws DeserializationException {
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        return deserialize(bb);
+    }
+
+    public PIMAddrSource deserialize(ByteBuffer bb) throws DeserializationException {
+
+        /*
+         * We need to verify that we have enough buffer space.  First we'll assume that
+         * we are decoding an IPv4 address.  After we read the first by (address family),
+         * we'll determine if we actually need more buffer space for an IPv6 address.
+         */
+        checkInput(bb.array(), bb.position(), bb.limit() - bb.position(), ENC_SOURCE_IPV4_BYTE_LENGTH);
+
+        this.family = bb.get();
+        if (family != 4 && family != 6) {
+            throw new DeserializationException("Illegal IP version number: " + family + "\n");
+        } else if (family == 6) {
+
+            // Check for one less by since we have already read the first byte of the packet.
+            checkInput(bb.array(), bb.position(), bb.limit() - bb.position(), ENC_SOURCE_IPV6_BYTE_LENGTH - 1);
+        }
+
+        this.encType = bb.get();
+        this.reserved = bb.get();
+        if ((this.reserved & 0x01) != 0) {
+            this.rBit = true;
+        }
+        if ((this.reserved & 0x02) != 0) {
+            this.wBit = true;
+        }
+        if ((this.reserved & 0x4) != 0) {
+            this.sBit = true;
+        }
+
+        // Remove the s, reserved
+        this.reserved &= 0xf8;
+
+        this.masklen = bb.get();
+        if (this.family == 4) {
+            this.addr = IpAddress.valueOf(bb.getInt());
+        } else if (this.family == 6) {
+            this.addr = Ip6Address.valueOf(bb.array(), 2);
+        }
+        return this;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 2521;
+        int result = super.hashCode();
+        result = prime * result + this.family;
+        result = prime * result + this.encType;
+        result = prime * result + this.reserved;
+        result = prime * result + this.masklen;
+        result = prime * result + this.addr.hashCode();
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof PIMAddrSource)) {
+            return false;
+        }
+        final PIMAddrSource other = (PIMAddrSource) obj;
+        if (this.family != this.family) {
+            return false;
+        }
+
+        if (this.encType != other.encType) {
+            return false;
+        }
+
+        if (!this.addr.equals(other.addr)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/pim/PIMAddrUnicast.java b/utils/misc/src/main/java/org/onlab/packet/pim/PIMAddrUnicast.java
new file mode 100644
index 0000000..0c2d676
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/pim/PIMAddrUnicast.java
@@ -0,0 +1,182 @@
+/*
+ * 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 org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip6Address;
+
+import java.nio.ByteBuffer;
+
+import static org.onlab.packet.PacketUtils.checkInput;
+
+public class PIMAddrUnicast {
+    private byte family;
+    private byte encType;
+    IpAddress addr;
+
+    public static final int ENC_UNICAST_IPV4_BYTE_LENGTH = 2 + Ip4Address.BYTE_LENGTH;
+    public static final int ENC_UNICAST_IPV6_BYTE_LENGTH = 2 + Ip6Address.BYTE_LENGTH;
+
+    /**
+     * PIM Encoded Source Address.
+     */
+    public PIMAddrUnicast() {
+        this.family = 4;
+        this.encType = 0;
+    }
+
+    /**
+     * PIM Encoded Source Address.
+     *
+     * @param addr IPv4 or IPv6
+     */
+    public PIMAddrUnicast(String addr) {
+        this.addr = IpAddress.valueOf(addr);
+        if (this.addr.isIp4()) {
+            this.family = 4;
+        } else {
+            this.family = 6;
+        }
+        this.encType = 0;
+    }
+
+    /**
+     * PIM Encoded Source Address.
+     *
+     * @param addr IPv4 or IPv6
+     */
+    public void setAddr(IpAddress addr) {
+        this.addr = addr;
+        if (this.addr.isIp4()) {
+            this.family = 4;
+        } else {
+            this.family = 6;
+        }
+    }
+
+    /**
+     * Get the address of this encoded address.
+     *
+     * @return source address
+     */
+    public IpAddress getAddr() {
+        return this.addr;
+    }
+
+    /**
+     * Get the IP family of this address: 4 or 6.
+     *
+     * @return the IP address family
+     */
+    public int getFamily() {
+        return this.family;
+    }
+
+    /**
+     * The size in bytes of a serialized address.
+     *
+     * @return the number of bytes when serialized
+     */
+    public int getByteSize() {
+        int size = 2;
+        if (addr != null) {
+            size += addr.isIp4() ? 4 : 16;
+        } else {
+            size += 4;
+        }
+        return size;
+    }
+
+    public byte[] serialize() {
+        int len = getByteSize();
+
+        final byte[] data = new byte[len];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.put(family);
+        bb.put(encType);
+        bb.put(addr.toOctets());
+        return data;
+    }
+
+    public PIMAddrUnicast deserialize(ByteBuffer bb) throws DeserializationException {
+
+        // Assume IPv4 for check length until we read the encoded family.
+        checkInput(bb.array(), bb.position(), bb.limit() - bb.position(), ENC_UNICAST_IPV4_BYTE_LENGTH);
+        this.family = bb.get();
+
+        // If we have IPv6 we need to ensure we have adequate buffer space.
+        if (this.family != 4 && this.family != 6) {
+            throw new DeserializationException("Invalid address family: " + this.family);
+        } else if (this.family == 6) {
+            // Subtract -1 from ENC_UNICAST_IPv6 BYTE_LENGTH because we read one byte for family previously.
+            checkInput(bb.array(), bb.position(), bb.limit() - bb.position(), ENC_UNICAST_IPV6_BYTE_LENGTH - 1);
+        }
+
+        this.encType = bb.get();
+        if (this.family == 4) {
+            this.addr = IpAddress.valueOf(bb.getInt());
+        } else if (this.family == 6) {
+            this.addr = Ip6Address.valueOf(bb.array(), 2);
+        }
+        return this;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 2521;
+        int result = super.hashCode();
+        result = prime * result + this.family;
+        result = prime * result + this.encType;
+        result = prime * result + this.addr.hashCode();
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof PIMAddrUnicast)) {
+            return false;
+        }
+        final PIMAddrUnicast other = (PIMAddrUnicast) obj;
+        if (this.family != this.family) {
+            return false;
+        }
+
+        if (this.encType != other.encType) {
+            return false;
+        }
+
+        if (!this.addr.equals(other.addr)) {
+            return false;
+        }
+        return true;
+    }
+}
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
new file mode 100644
index 0000000..d454f1a
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/pim/PIMHello.java
@@ -0,0 +1,179 @@
+/*
+ * 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.BasePacket;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.IpAddress;
+
+import java.nio.ByteBuffer;
+import java.util.Random;
+
+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;
+
+    /**
+     * PIM Option types.
+     */
+    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;
+        }
+    }
+
+    /**
+     * Add the holdtime to the packet.
+     *
+     * @param holdtime the holdtime in seconds
+     */
+    public void addHoldtime(int holdtime) {
+        this.holdtime = holdtime;
+    }
+
+    /**
+     * 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();
+    }
+
+    /**
+     * Sets all payloads parent packet if applicable, then serializes this
+     * packet and all payloads.
+     *
+     * @return a byte[] containing this packet and payloads
+     */
+    @Override
+    public byte[] serialize() {
+
+        // 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
+        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);
+        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) {
+        //
+        return null;
+    }
+
+    /**
+     * Deserialize this hello message.
+     *
+     * @return a deserialized hello message.
+     */
+    public static Deserializer<PIMHello> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, 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());
+                }
+            }
+
+            return hello;
+        };
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/pim/PIMJoinPrune.java b/utils/misc/src/main/java/org/onlab/packet/pim/PIMJoinPrune.java
new file mode 100644
index 0000000..9653115
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/pim/PIMJoinPrune.java
@@ -0,0 +1,271 @@
+/*
+ * 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.BasePacket;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.IpPrefix;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+
+import static org.onlab.packet.PacketUtils.checkInput;
+
+public class PIMJoinPrune extends BasePacket {
+
+    private PIMAddrUnicast upstreamAddr = new PIMAddrUnicast();
+    private short holdTime = (short) 0xffff;
+
+    private class JoinPruneGroup {
+        protected IpPrefix group;
+        protected HashMap<IpPrefix, IpPrefix> joins = new HashMap<>();
+        protected HashMap<IpPrefix, IpPrefix> prunes = new HashMap<>();
+
+        public JoinPruneGroup(IpPrefix grp) {
+            group = grp;
+        }
+    }
+    private HashMap<IpPrefix, JoinPruneGroup> joinPrunes = new HashMap<>();
+
+    /**
+     * Get the J/P hold time.
+     *
+     * @return specified in seconds.
+     */
+    public short getHoldTime() {
+        return holdTime;
+    }
+
+    /**
+     * Set the J/P holdtime in seconds.
+     *
+     * @param holdTime return the holdtime.
+     */
+    public void setHoldTime(short holdTime) {
+        this.holdTime = holdTime;
+    }
+
+    /**
+     * Get the upstreamAddr for this J/P request.
+     *
+     * @return the upstream address.
+     */
+    public PIMAddrUnicast getUpstreamAddr() {
+        return upstreamAddr;
+    }
+
+    /**
+     * Set the upstream address of this PIM J/P request.
+     *
+     * @param upstr the PIM Upstream unicast address
+     */
+    public void setUpstreamAddr(PIMAddrUnicast upstr) {
+        this.upstreamAddr = upstr;
+    }
+
+    /**
+     * Add the specified s,g to join field.
+     *
+     * @param saddr the source address of the route
+     * @param gaddr the group address of the route
+     * @param join true for a join, false for a prune.
+     */
+    public void addJoinPrune(String saddr, String gaddr, boolean join) {
+        IpPrefix gpfx = IpPrefix.valueOf(gaddr);
+        IpPrefix spfx = IpPrefix.valueOf(saddr);
+        addJoinPrune(spfx, gpfx, join);
+    }
+
+    /**
+     * Add the specified S, G to the join field.
+     *
+     * @param spfx the source prefix of the route
+     * @param gpfx the group prefix of the route
+     * @param join true for join, false for prune
+     */
+    public void addJoinPrune(IpPrefix spfx, IpPrefix gpfx, boolean join) {
+        JoinPruneGroup jpg = joinPrunes.get(gpfx);
+        if (jpg == null) {
+            jpg = new JoinPruneGroup(gpfx);
+            joinPrunes.put(gpfx, jpg);
+        }
+
+        HashMap<IpPrefix, IpPrefix> members = (join) ? jpg.joins : jpg.prunes;
+        if (members.get(spfx) == null) {
+            members.put(spfx, spfx);
+        }
+    }
+
+    /**
+     * Add a join given strings represending the source and group addresses.
+     *
+     * @param saddr source address
+     * @param gaddr group address
+     */
+    public void addJoin(String saddr, String gaddr) {
+        this.addJoinPrune(saddr, gaddr, true);
+    }
+
+    /**
+     * Add a prune given strings represending the source and group addresses.
+     *
+     * @param saddr source address
+     * @param gaddr group address
+     */
+    public void addPrune(String saddr, String gaddr) {
+        this.addJoinPrune(saddr, gaddr, false);
+    }
+
+    /**
+     * Sets all payloads parent packet if applicable, then serializes this
+     * packet and all payloads.
+     *
+     * @return a byte[] containing this packet and payloads
+     */
+    @Override
+    public byte[] serialize() {
+
+        byte[] data = new byte[8096];      // Come up with something better
+        ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.put(upstreamAddr.serialize());
+        bb.put((byte) 0);    // reserved
+
+        int ngrps = joinPrunes.size();
+        bb.put((byte) ngrps);
+        bb.putShort(this.holdTime);
+
+        // Walk the group list and input all groups
+        for (JoinPruneGroup jpg : joinPrunes.values()) {
+            PIMAddrGroup grp = new PIMAddrGroup(jpg.group);
+            bb.put(grp.serialize());
+
+            // put the number of joins and prunes
+            bb.putShort((short) jpg.joins.size());
+            bb.putShort((short) jpg.prunes.size());
+
+            // Set all of the joins
+            for (IpPrefix spfx : jpg.joins.values()) {
+                PIMAddrSource src = new PIMAddrSource(spfx);
+                bb.put(src.serialize());
+            }
+
+            // Set all of the prunes
+            for (IpPrefix spfx : jpg.prunes.values()) {
+                PIMAddrSource src = new PIMAddrSource(spfx);
+                bb.put(src.serialize());
+            }
+        }
+
+        int len = bb.position();
+        byte[] data2 = new byte[len];
+        bb = ByteBuffer.wrap(data2, 0, len);
+        bb.put(data, 0, len);
+        return data2;
+    }
+
+    // TODO: I suppose I really need to implement this?
+    @Override
+    public IPacket deserialize(final byte[] data, final int offset,
+                               final int length) {
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        return this;
+    }
+
+    /**
+     * Return the J/P deserializer function.
+     *
+     * @return a function that will deserialize a J/P message.
+     */
+    public static Deserializer<PIMJoinPrune> deserializer() {
+        return (data, offset, length) -> {
+
+            /*
+             * Delay buffer checks until we read enough of the packet to know how
+             * much data we will require.  Each encoded address deserializer function
+             * will ensure there is enough data for that address.
+             */
+            PIMJoinPrune jp = new PIMJoinPrune();
+            final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+            // We must get a PIM encoded unicast address
+            PIMAddrUnicast upstream = new PIMAddrUnicast();
+            upstream.deserialize(bb);
+            jp.setUpstreamAddr(upstream);
+
+            // Use this boolean to determine the buffer space we need according to address sizes
+            boolean ipv4 = upstream.getAddr().isIp4();
+
+            // We need at minimum 4 bytes for reserved(1), ngroups(1) & holdtime(2)
+            checkInput(bb.array(), bb.position(), bb.limit() - bb.position(), 4);
+
+            // get and skip the reserved byte
+            bb.get();
+
+            // Get the number of groups.
+            int ngroups = bb.get();
+
+            // Save the holdtime.
+            jp.setHoldTime(bb.getShort());
+
+
+            for (int i = 0; i < ngroups; i++) {
+                PIMAddrGroup grp = new PIMAddrGroup();
+
+                /*
+                 * grp.deserialize will ensure the buffer has enough data to read the group address.
+                 */
+                grp.deserialize(bb);
+
+                checkInput(bb.array(), bb.position(), bb.limit() - bb.position(), 4);
+                int njoins = bb.getShort();
+                int nprunes = bb.getShort();
+
+                /*
+                 * Now we'll verify we have enough buffer to read the next
+                 * group of join and prune addresses for this group.
+                 */
+                int required = (njoins + nprunes) *
+                        (ipv4 ? PIMAddrSource.ENC_SOURCE_IPV4_BYTE_LENGTH : PIMAddrSource.ENC_SOURCE_IPV6_BYTE_LENGTH);
+                checkInput(bb.array(), bb.position(), bb.limit() - bb.position(), required);
+
+                // Now iterate through the joins for this group
+                for (; njoins > 0; njoins--) {
+
+                    PIMAddrSource src = new PIMAddrSource();
+                    src.deserialize(bb);
+
+                    jp.addJoinPrune(
+                            src.getAddr().toIpPrefix(),
+                            grp.getAddr().toIpPrefix(), true);
+                }
+
+                // Now iterate through the prunes for this group
+                for (; nprunes > 0; nprunes--) {
+
+                    PIMAddrSource src = new PIMAddrSource();
+                    src.deserialize(bb);
+                    jp.addJoinPrune(
+                            src.getAddr().toIpPrefix(),
+                            grp.getAddr().toIpPrefix(), false);
+                }
+            }
+
+            return jp;
+        };
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/pim/package-info.java b/utils/misc/src/main/java/org/onlab/packet/pim/package-info.java
new file mode 100755
index 0000000..88a1ad5
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/pim/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2014-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.
+ */
+
+/**
+ * Utilities for managing PIM packets.
+ */
+package org.onlab.packet.pim;
+