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/IPv4.java b/utils/misc/src/main/java/org/onlab/packet/IPv4.java
index d75b50a..a5c5f4f 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IPv4.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IPv4.java
@@ -35,6 +35,7 @@
     public static final byte PROTOCOL_IGMP = 0x2;
     public static final byte PROTOCOL_TCP = 0x6;
     public static final byte PROTOCOL_UDP = 0x11;
+    public static final byte PROTOCOL_PIM = 0x67;
     public static final Map<Byte, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP =
             new HashMap<>();
 
@@ -43,6 +44,7 @@
         IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_IGMP, IGMP.deserializer());
         IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_TCP, TCP.deserializer());
         IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_UDP, UDP.deserializer());
+        IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_PIM, PIM.deserializer());
     }
 
     private static final byte DSCP_MASK = 0x3f;
diff --git a/utils/misc/src/main/java/org/onlab/packet/PIM.java b/utils/misc/src/main/java/org/onlab/packet/PIM.java
new file mode 100755
index 0000000..cab9514
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/PIM.java
@@ -0,0 +1,296 @@
+/*
+ * 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;
+
+import org.onlab.packet.pim.PIMHello;
+import org.onlab.packet.pim.PIMJoinPrune;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.onlab.packet.PacketUtils.checkInput;
+
+/**
+ * Implements PIM control packet format.
+ */
+public class PIM extends BasePacket {
+
+    public static final IpAddress PIM_ADDRESS = IpAddress.valueOf("224.0.0.13");
+
+    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_JOIN_PRUNE_REQUEST = 0x03;
+    public static final byte TYPE_BOOTSTRAP = 0x04;
+    public static final byte TYPE_ASSERT = 0x05;
+    public static final byte TYPE_GRAFT = 0x06;
+    public static final byte TYPE_GRAFT_ACK = 0x07;
+    public static final byte TYPE_CANDIDATE_RP_ADV = 0x08;
+
+    public static final int PIM_HEADER_LEN = 4;
+
+    public static final Map<Byte, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP =
+            new HashMap<>();
+
+    static {
+        PIM.PROTOCOL_DESERIALIZER_MAP.put(PIM.TYPE_HELLO, PIMHello.deserializer());
+        PIM.PROTOCOL_DESERIALIZER_MAP.put(PIM.TYPE_JOIN_PRUNE_REQUEST, PIMJoinPrune.deserializer());
+    }
+
+    /*
+     * PIM Header fields
+     */
+    protected byte version;
+    protected byte type;
+    protected byte reserved;
+    protected short checksum;
+
+    /**
+     * Default constructor.
+     */
+    public PIM() {
+        super();
+        this.version = 2;
+        this.reserved = 0;
+    }
+
+    /**
+     * Return the PIM message type.
+     *
+     * @return the pimMsgType
+     */
+    public byte getPimMsgType() {
+        return this.type;
+    }
+
+    /**
+     * Set the PIM message type. Currently PIMJoinPrune and PIMHello are
+     * supported.
+     *
+     * @param type PIM message type
+     * @return PIM Header
+     */
+     public PIM setPIMType(final byte type) {
+            this.type = type;
+            return this;
+     }
+
+    /**
+     * Get the version of PIM.
+     *
+     * @return the PIM version.   Must be 2.
+     */
+    public byte getVersion() {
+         return version;
+    }
+
+    /**
+     * Set the PIM version type. Should not change from 2.
+     *
+     * @param version
+     */
+    public void setVersion(byte version) {
+         this.version = version;
+    }
+
+    /**
+     * Get the reserved field.
+     *
+     * @return the reserved field.  Must be ignored.
+     */
+    public byte getReserved() {
+        return reserved;
+    }
+
+    /**
+     * Set the reserved field.
+     *
+     * @param reserved should be 0
+     */
+    public void setReserved(byte reserved) {
+        this.reserved = reserved;
+    }
+
+    /**
+     * Get the checksum of this packet.
+     *
+     * @return the checksum
+     */
+    public short getChecksum() {
+        return checksum;
+    }
+
+    /**
+     * Set the checksum.
+     *
+     * @param checksum the checksum
+     */
+    public void setChecksum(short checksum) {
+        this.checksum = checksum;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 5807;
+        int result = super.hashCode();
+        result = prime * result + this.type;
+        result = prime * result + this.version;
+        result = prime * result + this.checksum;
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof PIM)) {
+            return false;
+        }
+        final PIM other = (PIM) obj;
+        if (this.type != other.type) {
+            return false;
+        }
+        if (this.version != other.version) {
+            return false;
+        }
+        if (this.checksum != other.checksum) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Serializes the packet. Will compute and set the following fields if they
+     * are set to specific values at the time serialize is called: -checksum : 0
+     * -length : 0
+     *
+     * @return will return the serialized packet
+     */
+    @Override
+    public byte[] serialize() {
+        int length = 4;
+        byte[] payloadData = null;
+        if (this.payload != null) {
+            this.payload.setParent(this);
+            payloadData = this.payload.serialize();
+            length += payloadData.length;
+        }
+
+        final byte[] data = new byte[length];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.put((byte) ((this.version & 0xf) << 4 | this.type & 0xf));
+        bb.put(this.reserved);
+        bb.putShort(this.checksum);
+        if (payloadData != null) {
+            bb.put(payloadData);
+        }
+
+        if (this.parent != null && this.parent instanceof PIM) {
+            ((PIM) this.parent).setPIMType(TYPE_JOIN_PRUNE_REQUEST);
+        }
+
+        // compute checksum if needed
+        if (this.checksum == 0) {
+            bb.rewind();
+            int accumulation = 0;
+
+            for (int i = 0; i < length / 2; ++i) {
+                accumulation += 0xffff & bb.getShort();
+            }
+            // pad to an even number of shorts
+            if (length % 2 > 0) {
+                accumulation += (bb.get() & 0xff) << 8;
+            }
+
+            accumulation = (accumulation >> 16 & 0xffff)
+                    + (accumulation & 0xffff);
+            this.checksum = (short) (~accumulation & 0xffff);
+            bb.putShort(2, this.checksum);
+        }
+        return data;
+    }
+
+    /**
+     * Deserialize the PIM packet.
+     *
+     * @param data bytes to deserialize.
+     * @param offset offset to start deserializing from
+     * @param length length of the data to deserialize
+     *
+     * @return the deserialized PIM packet.
+     */
+    @Override
+    public IPacket deserialize(final byte[] data, final int offset,
+            final int length) {
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.type = bb.get();
+        this.version = bb.get();
+        this.checksum = bb.getShort();
+
+        //this.payload = new Data();
+        this.payload = this.payload.deserialize(data, bb.position(), bb.limit() - bb.position());
+        this.payload.setParent(this);
+        return this;
+    }
+    /**
+     * Deserializer function for IPv4 packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<PIM> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, PIM_HEADER_LEN);
+
+            PIM pim = new PIM();
+
+            final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+            byte versionByte = bb.get();
+            pim.version = (byte) (versionByte >> 4 & 0xf);
+            pim.setPIMType((byte) (versionByte & 0xf));
+            pim.reserved = bb.get();
+            pim.checksum = bb.getShort();
+
+            Deserializer<? extends IPacket> deserializer;
+            if (PIM.PROTOCOL_DESERIALIZER_MAP.containsKey(pim.getPimMsgType())) {
+                deserializer = PIM.PROTOCOL_DESERIALIZER_MAP.get(pim.getPimMsgType());
+            } else {
+                deserializer = Data.deserializer();
+            }
+
+            pim.payload = deserializer.deserialize(data, bb.position(), bb.limit() - bb.position());
+            pim.payload.setParent(pim);
+
+            return pim;
+        };
+    }
+}
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;
+
diff --git a/utils/misc/src/test/java/org/onlab/packet/PIMTest.java b/utils/misc/src/test/java/org/onlab/packet/PIMTest.java
new file mode 100644
index 0000000..bed4733
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/PIMTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.pim.PIMAddrUnicast;
+import org.onlab.packet.pim.PIMHello;
+import org.onlab.packet.pim.PIMJoinPrune;
+
+import static junit.framework.Assert.assertTrue;
+
+public final class PIMTest {
+
+    public static final String SADDR = "10.2.1.2";
+    public static final String PIMADDR = "224.0.0.13";
+    public static final String PIMUADDR = "10.23.3.5";
+
+    public static final String SADDR1 = "10.1.1.1/32";
+    public static final String SADDR2 = "10.1.2.1/32";
+    public static final String GADDR1 = "232.1.1.1/32";
+    public static final String GADDR2 = "232.1.2.1/32";
+
+    public static final String CPSTR1 = "of:deadbeefball/8";
+    public static final String CPSTR2 = "of:deadbeefcafe/3";
+    public static final String CPSTR3 = "of:2badcafef00d/3";
+
+    private Deserializer<PIM> deserializer;
+
+    private PIM pimHello;
+    private PIMHello hello;
+
+    private PIM pimJoinPrune;
+    private PIMJoinPrune joinPrune;
+
+    @Before
+    public void setUp() throws Exception {
+
+        // Create a PIM Hello
+        pimHello = new PIM();
+        pimHello.setVersion((byte) 2);
+        pimHello.setPIMType((byte) PIM.TYPE_HELLO);
+        pimHello.setChecksum((short) 0);
+
+        hello = new PIMHello();
+        hello.addHoldtime(0xd2);
+        hello.addPriority(44);
+        hello.addGenId(0xf00d);
+        pimHello.setPayload(hello);
+        hello.setParent(pimHello);
+
+        // Create PIM Join Prune
+        pimJoinPrune = new PIM();
+        pimJoinPrune.setVersion((byte) 2);
+        pimJoinPrune.setPIMType((byte) PIM.TYPE_JOIN_PRUNE_REQUEST);
+        pimJoinPrune.setChecksum((short) 0);
+
+        joinPrune = new PIMJoinPrune();
+        joinPrune.setUpstreamAddr(new PIMAddrUnicast(SADDR));
+        joinPrune.addJoin(GADDR1, SADDR1);
+        joinPrune.addJoin(GADDR2, SADDR2);
+        joinPrune.addPrune(GADDR1, SADDR2);
+        joinPrune.addPrune(GADDR2, SADDR1);
+
+        pimJoinPrune.setPayload(joinPrune);
+        joinPrune.setParent(pimJoinPrune);
+
+        deserializer = PIM.deserializer();
+    }
+
+    @Test
+    public void testDerserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(deserializer);
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        //byte [] bits = pimHello.serialize();
+        //PacketTestUtils.testDeserializeTruncated(deserializer, bits);
+
+        byte [] bits = pimJoinPrune.serialize();
+        PacketTestUtils.testDeserializeTruncated(deserializer, bits);
+    }
+
+    @Test
+    public void testDeserializeHello() throws Exception {
+        byte [] data = pimHello.serialize();
+        PIM pim = deserializer.deserialize(data, 0, data.length);
+        assertTrue(pim.equals(pimHello));
+    }
+
+    @Test
+    public void testDeserializeJoinPrune() throws Exception {
+        byte [] data = pimJoinPrune.serialize();
+        PIM pim = deserializer.deserialize(data, 0, data.length);
+        assertTrue(pim.equals(pimJoinPrune));
+    }
+
+}
\ No newline at end of file