Improve the resiliency of the packet deserialization code.

Packet deserializers now check for malformed input while reading the byte
stream. Deserializers are re-implemented as functions that take a byte array
and return a packet object. The old IPacket.deserialize(...) methods have been
deprecated with the goal of eventually moving to immutable packet objects.
Unit tests have been implemented for all Deserializer functions.

ONOS-1589

Change-Id: I9073d5e6e7991e15d43830cfd810989256b71c56
diff --git a/utils/misc/pom.xml b/utils/misc/pom.xml
index e3635f4..1795121 100644
--- a/utils/misc/pom.xml
+++ b/utils/misc/pom.xml
@@ -42,6 +42,11 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>io.netty</groupId>
             <artifactId>netty</artifactId>
         </dependency>
diff --git a/utils/misc/src/main/java/org/onlab/packet/ARP.java b/utils/misc/src/main/java/org/onlab/packet/ARP.java
index 88e1d4f..dc3c07f 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ARP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ARP.java
@@ -21,6 +21,8 @@
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 
+import static org.onlab.packet.PacketUtils.*;
+
 /**
  *
  *
@@ -35,6 +37,8 @@
     public static final short OP_RARP_REQUEST = 0x3;
     public static final short OP_RARP_REPLY = 0x4;
 
+    public static final short INITIAL_HEADER_LENGTH = 8;
+
     protected short hardwareType;
     protected short protocolType;
     protected byte hardwareAddressLength;
@@ -247,7 +251,7 @@
 
     @Override
     public IPacket deserialize(final byte[] data, final int offset,
-            final int length) {
+                               final int length) {
         final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
         this.hardwareType = bb.getShort();
         this.protocolType = bb.getShort();
@@ -386,10 +390,50 @@
         arp.setTargetHardwareAddress(request.getSourceMACAddress());
 
         arp.setTargetProtocolAddress(((ARP) request.getPayload())
-                .getSenderProtocolAddress());
+                                             .getSenderProtocolAddress());
         arp.setSenderProtocolAddress(srcIp.toInt());
 
         eth.setPayload(arp);
         return eth;
     }
+
+    /**
+     * Deserializer function for ARP packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<ARP> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, INITIAL_HEADER_LENGTH);
+
+            ARP arp = new ARP();
+            final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            arp.setHardwareType(bb.getShort());
+            arp.setProtocolType(bb.getShort());
+
+            byte hwAddressLength = bb.get();
+            arp.setHardwareAddressLength(hwAddressLength);
+
+            byte protocolAddressLength = bb.get();
+            arp.setProtocolAddressLength(protocolAddressLength);
+            arp.setOpCode(bb.getShort());
+
+            // Check we have enough space for the addresses
+            checkHeaderLength(length, INITIAL_HEADER_LENGTH +
+                    2 * hwAddressLength +
+                    2 * protocolAddressLength);
+
+            arp.senderHardwareAddress = new byte[0xff & hwAddressLength];
+            bb.get(arp.senderHardwareAddress, 0, arp.senderHardwareAddress.length);
+            arp.senderProtocolAddress = new byte[0xff & protocolAddressLength];
+            bb.get(arp.senderProtocolAddress, 0, arp.senderProtocolAddress.length);
+            arp.targetHardwareAddress = new byte[0xff & hwAddressLength];
+            bb.get(arp.targetHardwareAddress, 0, arp.targetHardwareAddress.length);
+            arp.targetProtocolAddress = new byte[0xff & protocolAddressLength];
+            bb.get(arp.targetProtocolAddress, 0, arp.targetProtocolAddress.length);
+
+            return arp;
+        };
+    }
+
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCP.java b/utils/misc/src/main/java/org/onlab/packet/DHCP.java
index 8dba13c..40a7745 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/DHCP.java
@@ -24,6 +24,8 @@
 import java.util.List;
 import java.util.ListIterator;
 
+import static org.onlab.packet.PacketUtils.*;
+
 /**
  *
  */
@@ -429,33 +431,9 @@
         return data;
     }
 
-    protected void writeString(final String string, final ByteBuffer bb,
-            final int maxLength) {
-        if (string == null) {
-            for (int i = 0; i < maxLength; ++i) {
-                bb.put((byte) 0x0);
-            }
-        } else {
-            byte[] bytes = null;
-            try {
-                bytes = string.getBytes("ascii");
-            } catch (final UnsupportedEncodingException e) {
-                throw new RuntimeException("Failure encoding server name", e);
-            }
-            int writeLength = bytes.length;
-            if (writeLength > maxLength) {
-                writeLength = maxLength;
-            }
-            bb.put(bytes, 0, writeLength);
-            for (int i = writeLength; i < maxLength; ++i) {
-                bb.put((byte) 0x0);
-            }
-        }
-    }
-
     @Override
     public IPacket deserialize(final byte[] data, final int offset,
-            final int length) {
+                               final int length) {
         final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
         if (bb.remaining() < DHCP.MIN_HEADER_LENGTH) {
             return this;
@@ -529,7 +507,31 @@
         return this;
     }
 
-    protected String readString(final ByteBuffer bb, final int maxLength) {
+    protected void writeString(final String string, final ByteBuffer bb,
+            final int maxLength) {
+        if (string == null) {
+            for (int i = 0; i < maxLength; ++i) {
+                bb.put((byte) 0x0);
+            }
+        } else {
+            byte[] bytes = null;
+            try {
+                bytes = string.getBytes("ascii");
+            } catch (final UnsupportedEncodingException e) {
+                throw new RuntimeException("Failure encoding server name", e);
+            }
+            int writeLength = bytes.length;
+            if (writeLength > maxLength) {
+                writeLength = maxLength;
+            }
+            bb.put(bytes, 0, writeLength);
+            for (int i = writeLength; i < maxLength; ++i) {
+                bb.put((byte) 0x0);
+            }
+        }
+    }
+
+    private static String readString(final ByteBuffer bb, final int maxLength) {
         final byte[] bytes = new byte[maxLength];
         bb.get(bytes);
         String result = null;
@@ -540,4 +542,84 @@
         }
         return result;
     }
+
+    /**
+     * Deserializer function for DHCP packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<DHCP> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, MIN_HEADER_LENGTH);
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            DHCP dhcp = new DHCP();
+
+            dhcp.opCode = bb.get();
+            dhcp.hardwareType = bb.get();
+            dhcp.hardwareAddressLength = bb.get();
+            dhcp.hops = bb.get();
+            dhcp.transactionId = bb.getInt();
+            dhcp.seconds = bb.getShort();
+            dhcp.flags = bb.getShort();
+            dhcp.clientIPAddress = bb.getInt();
+            dhcp.yourIPAddress = bb.getInt();
+            dhcp.serverIPAddress = bb.getInt();
+            dhcp.gatewayIPAddress = bb.getInt();
+            final int hardwareAddressLength = 0xff & dhcp.hardwareAddressLength;
+            dhcp.clientHardwareAddress = new byte[hardwareAddressLength];
+
+            bb.get(dhcp.clientHardwareAddress);
+            for (int i = hardwareAddressLength; i < 16; ++i) {
+                bb.get();
+            }
+            dhcp.serverName = readString(bb, 64);
+            dhcp.bootFileName = readString(bb, 128);
+            // read the magic cookie
+            // magic cookie
+            bb.get();
+            bb.get();
+            bb.get();
+            bb.get();
+
+            // read options
+            boolean foundEndOptionsMarker = false;
+            while (bb.hasRemaining()) {
+                final DHCPOption option = new DHCPOption();
+                int code = 0xff & bb.get(); // convert signed byte to int in range
+                // [0,255]
+                option.setCode((byte) code);
+                if (code == 0) {
+                    // skip these
+                    continue;
+                } else if (code != 255) {
+                    if (bb.hasRemaining()) {
+                        final int l = 0xff & bb.get(); // convert signed byte to
+                        // int in range [0,255]
+                        option.setLength((byte) l);
+                        if (bb.remaining() >= l) {
+                            final byte[] optionData = new byte[l];
+                            bb.get(optionData);
+                            option.setData(optionData);
+                            dhcp.options.add(option);
+                        } else {
+                            throw new DeserializationException(
+                                    "Buffer underflow while reading DHCP option");
+                        }
+                    }
+                } else if (code == 255) {
+                    // remaining bytes are supposed to be 0, but ignore them just in
+                    // case
+                    foundEndOptionsMarker = true;
+                    break;
+                }
+            }
+
+            if (!foundEndOptionsMarker) {
+                throw new DeserializationException("DHCP End options marker was missing");
+            }
+
+            return dhcp;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/Data.java b/utils/misc/src/main/java/org/onlab/packet/Data.java
index f3a1092..79abcba 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Data.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Data.java
@@ -20,6 +20,8 @@
 
 import java.util.Arrays;
 
+import static org.onlab.packet.PacketUtils.*;
+
 /**
  *
  */
@@ -30,6 +32,7 @@
      *
      */
     public Data() {
+        data = new byte[0];
     }
 
     /**
@@ -63,7 +66,7 @@
 
     @Override
     public IPacket deserialize(final byte[] data, final int offset,
-            final int length) {
+                               final int length) {
         this.data = Arrays.copyOfRange(data, offset, data.length);
         return this;
     }
@@ -103,4 +106,27 @@
         }
         return true;
     }
+
+    /**
+     * Deserializer function for generic payload data.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<Data> deserializer() {
+        return (data, offset, length) -> {
+            // Allow zero-length data for now
+            if (length == 0) {
+                return new Data();
+            }
+
+            checkInput(data, offset, length, 1);
+
+            Data dataObject = new Data();
+
+            dataObject.data = Arrays.copyOfRange(data, offset, data.length);
+
+            return dataObject;
+        };
+    }
+
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/DeserializationException.java b/utils/misc/src/main/java/org/onlab/packet/DeserializationException.java
new file mode 100644
index 0000000..03a8aa3
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/DeserializationException.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * Signals that an error occurred during deserialization of a packet.
+ */
+public class DeserializationException extends Exception {
+
+    /**
+     * Creates a new deserialization exception with the given message.
+     *
+     * @param message exception message
+     */
+    public DeserializationException(String message) {
+        super(message);
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/Deserializer.java b/utils/misc/src/main/java/org/onlab/packet/Deserializer.java
new file mode 100644
index 0000000..e0ef381
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/Deserializer.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Function to deserialize a packet from a byte-based input stream.
+ */
+@FunctionalInterface
+public interface Deserializer<U extends IPacket> {
+
+    /**
+     * Deserialize a packet object from a byte array.
+     *
+     * @param data input array to take packet bytes from
+     * @param offset index where this packet header begins in the byte array
+     * @param length length of the packet header
+     * @return a deserialized packet object
+     * @throws DeserializationException if the packet cannot be deserialized
+     * from the input
+     */
+    U deserialize(byte[] data, int offset, int length) throws DeserializationException;
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/EthType.java b/utils/misc/src/main/java/org/onlab/packet/EthType.java
index 4f52b7d..7573ae8 100644
--- a/utils/misc/src/main/java/org/onlab/packet/EthType.java
+++ b/utils/misc/src/main/java/org/onlab/packet/EthType.java
@@ -93,25 +93,27 @@
 
     public static enum EtherType {
 
-        ARP(0x806, "arp", ARP.class),
-        RARP(0x8035, "rarp", null),
-        IPV4(0x800, "ipv4", IPv4.class),
-        IPV6(0x86dd, "ipv6", IPv6.class),
-        LLDP(0x88cc, "lldp", LLDP.class),
-        VLAN(0x8100, "vlan", null),
-        BDDP(0x8942, "bddp", LLDP.class),
-        MPLS_UNICAST(0x8847, "mpls_unicast", null),
-        MPLS_MULTICAST(0x8848, "mpls_unicast", null);
+        ARP(0x806, "arp", ARP.class, org.onlab.packet.ARP.deserializer()),
+        RARP(0x8035, "rarp", null, org.onlab.packet.ARP.deserializer()),
+        IPV4(0x800, "ipv4", IPv4.class, org.onlab.packet.IPv4.deserializer()),
+        IPV6(0x86dd, "ipv6", IPv6.class, org.onlab.packet.IPv6.deserializer()),
+        LLDP(0x88cc, "lldp", LLDP.class, org.onlab.packet.LLDP.deserializer()),
+        VLAN(0x8100, "vlan", null, null),
+        BDDP(0x8942, "bddp", LLDP.class, org.onlab.packet.LLDP.deserializer()),
+        MPLS_UNICAST(0x8847, "mpls_unicast", null, org.onlab.packet.MPLS.deserializer()),
+        MPLS_MULTICAST(0x8848, "mpls_unicast", null, org.onlab.packet.MPLS.deserializer());
 
 
         private final Class clazz;
         private EthType ethType;
         private String type;
+        private Deserializer<?> deserializer;
 
-        EtherType(int ethType, String type, Class clazz) {
+        EtherType(int ethType, String type, Class clazz, Deserializer deserializer) {
             this.ethType = new EthType(ethType);
             this.type = type;
             this.clazz = clazz;
+            this.deserializer = deserializer;
         }
 
         public EthType ethType() {
@@ -127,6 +129,8 @@
             return clazz;
         }
 
-
+        public Deserializer<?> deserializer() {
+            return deserializer;
+        }
     }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
index 779d96c..700bdfc 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
@@ -18,13 +18,14 @@
 
 package org.onlab.packet;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.packet.PacketUtils.checkHeaderLength;
+import static org.onlab.packet.PacketUtils.checkInput;
 
 /**
  *
@@ -42,15 +43,21 @@
     public static final short MPLS_UNICAST = EthType.MPLS_UNICAST;
     public static final short MPLS_MULTICAST = EthType.MPLS_MULTICAST;
 
+
     public static final short VLAN_UNTAGGED = (short) 0xffff;
+
+    public static final short ETHERNET_HEADER_LENGTH = 14; // bytes
+    public static final short VLAN_HEADER_LENGTH = 4; // bytes
+
     public static final short DATALAYER_ADDRESS_LENGTH = 6; // bytes
-    public static final Map<Short, Class<? extends IPacket>> ETHER_TYPE_CLASS_MAP =
-        new HashMap<>();
+
+    private static final Map<Short, Deserializer<? extends IPacket>> ETHERTYPE_DESERIALIZER_MAP =
+            new HashMap<>();
 
     static {
        for (EthType.EtherType ethType : EthType.EtherType.values()) {
            if (ethType.clazz() != null) {
-               ETHER_TYPE_CLASS_MAP.put(ethType.ethType().toShort(), ethType.clazz());
+               ETHERTYPE_DESERIALIZER_MAP.put(ethType.ethType().toShort(), ethType.deserializer());
            }
        }
     }
@@ -300,7 +307,7 @@
 
     @Override
     public IPacket deserialize(final byte[] data, final int offset,
-            final int length) {
+                               final int length) {
         if (length <= 0) {
             return null;
         }
@@ -331,21 +338,19 @@
         this.etherType = ethType;
 
         IPacket payload;
-        if (Ethernet.ETHER_TYPE_CLASS_MAP.containsKey(this.etherType)) {
-            final Class<? extends IPacket> clazz = Ethernet.ETHER_TYPE_CLASS_MAP
-                    .get(this.etherType);
-            try {
-                payload = clazz.newInstance();
-            } catch (final Exception e) {
-                throw new RuntimeException(
-                        "Error parsing payload for Ethernet packet", e);
-            }
+        Deserializer<? extends IPacket> deserializer;
+        if (Ethernet.ETHERTYPE_DESERIALIZER_MAP.containsKey(ethType)) {
+            deserializer = Ethernet.ETHERTYPE_DESERIALIZER_MAP.get(ethType);
         } else {
-            payload = new Data();
+            deserializer = Data.deserializer();
         }
-        this.payload = payload.deserialize(data, bb.position(),
-                bb.limit() - bb.position());
-        this.payload.setParent(this);
+        try {
+            this.payload = deserializer.deserialize(data, bb.position(),
+                                                    bb.limit() - bb.position());
+            this.payload.setParent(this);
+        } catch (DeserializationException e) {
+            return this;
+        }
         return this;
     }
 
@@ -567,4 +572,53 @@
         return builder.toString();
     }
 
+    /**
+     * Deserializer function for Ethernet packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<Ethernet> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, ETHERNET_HEADER_LENGTH);
+
+            byte[] addressBuffer = new byte[DATALAYER_ADDRESS_LENGTH];
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            Ethernet eth = new Ethernet();
+            // Read destination MAC address into buffer
+            bb.get(addressBuffer);
+            eth.setDestinationMACAddress(addressBuffer);
+
+            // Read source MAC address into buffer
+            bb.get(addressBuffer);
+            eth.setSourceMACAddress(addressBuffer);
+
+            short ethType = bb.getShort();
+            if (ethType == TYPE_VLAN) {
+                checkHeaderLength(length, ETHERNET_HEADER_LENGTH + VLAN_HEADER_LENGTH);
+                final short tci = bb.getShort();
+                eth.setPriorityCode((byte) (tci >> 13 & 0x07));
+                eth.setVlanID((short) (tci & 0x0fff));
+                ethType = bb.getShort();
+            } else {
+                eth.setVlanID(Ethernet.VLAN_UNTAGGED);
+            }
+            eth.setEtherType(ethType);
+
+            IPacket payload;
+            Deserializer<? extends IPacket> deserializer;
+            if (Ethernet.ETHERTYPE_DESERIALIZER_MAP.containsKey(ethType)) {
+                deserializer = Ethernet.ETHERTYPE_DESERIALIZER_MAP.get(ethType);
+            } else {
+                deserializer = Data.deserializer();
+            }
+            payload = deserializer.deserialize(data, bb.position(),
+                                               bb.limit() - bb.position());
+            payload.setParent(eth);
+            eth.setPayload(payload);
+
+            return eth;
+        };
+    }
+
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ICMP.java b/utils/misc/src/main/java/org/onlab/packet/ICMP.java
index 46814cd..d07a9ba 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ICMP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ICMP.java
@@ -20,6 +20,8 @@
 
 import java.nio.ByteBuffer;
 
+import static org.onlab.packet.PacketUtils.*;
+
 /**
  * Implements ICMP packet format.
  *
@@ -33,6 +35,8 @@
     public static final byte TYPE_ECHO_REPLY = 0x00;
     public static final byte SUBTYPE_ECHO_REPLY = 0x00;
 
+    public static final short ICMP_HEADER_LENGTH = 4;
+
     /**
      * @return the icmpType
      */
@@ -134,6 +138,21 @@
         return data;
     }
 
+    @Override
+    public IPacket deserialize(final byte[] data, final int offset,
+                               final int length) {
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.icmpType = bb.get();
+        this.icmpCode = 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;
+    }
+
     /*
      * (non-Javadoc)
      *
@@ -178,18 +197,27 @@
         return true;
     }
 
-    @Override
-    public IPacket deserialize(final byte[] data, final int offset,
-            final int length) {
-        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
-        this.icmpType = bb.get();
-        this.icmpCode = bb.get();
-        this.checksum = bb.getShort();
+    /**
+     * Deserializer function for ICMP packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<ICMP> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, ICMP_HEADER_LENGTH);
 
-        this.payload = new Data();
-        this.payload = this.payload.deserialize(data, bb.position(), bb.limit()
-                - bb.position());
-        this.payload.setParent(this);
-        return this;
+            ICMP icmp = new ICMP();
+
+            final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            icmp.icmpType = bb.get();
+            icmp.icmpCode = bb.get();
+            icmp.checksum = bb.getShort();
+
+            icmp.payload = Data.deserializer()
+                    .deserialize(data, bb.position(), bb.limit()
+                            - bb.position());
+            icmp.payload.setParent(icmp);
+            return icmp;
+        };
     }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ICMP6.java b/utils/misc/src/main/java/org/onlab/packet/ICMP6.java
index 7c9b4b9..c898130 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ICMP6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ICMP6.java
@@ -24,10 +24,13 @@
 import org.onlab.packet.ndp.Redirect;
 import org.onlab.packet.ndp.RouterAdvertisement;
 import org.onlab.packet.ndp.RouterSolicitation;
+
 import java.nio.ByteBuffer;
 import java.util.HashMap;
 import java.util.Map;
 
+import static org.onlab.packet.PacketUtils.checkInput;
+
 /**
  * Implements ICMPv6 packet format. (RFC 4443)
  */
@@ -96,15 +99,15 @@
     /** Unrecognized IPv6 option encountered. */
     public static final byte IPV6_OPT_ERR = (byte) 0x01;
 
-    public static final Map<Byte, Class<? extends IPacket>> PROTOCOL_CLASS_MAP =
+    public static final Map<Byte, Deserializer<? extends IPacket>> TYPE_DESERIALIZER_MAP =
             new HashMap<>();
 
     static {
-        ICMP6.PROTOCOL_CLASS_MAP.put(ICMP6.ROUTER_SOLICITATION, RouterSolicitation.class);
-        ICMP6.PROTOCOL_CLASS_MAP.put(ICMP6.ROUTER_ADVERTISEMENT, RouterAdvertisement.class);
-        ICMP6.PROTOCOL_CLASS_MAP.put(ICMP6.NEIGHBOR_SOLICITATION, NeighborSolicitation.class);
-        ICMP6.PROTOCOL_CLASS_MAP.put(ICMP6.NEIGHBOR_ADVERTISEMENT, NeighborAdvertisement.class);
-        ICMP6.PROTOCOL_CLASS_MAP.put(ICMP6.REDIRECT, Redirect.class);
+        ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.ROUTER_SOLICITATION, RouterSolicitation.deserializer());
+        ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.ROUTER_ADVERTISEMENT, RouterAdvertisement.deserializer());
+        ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.NEIGHBOR_SOLICITATION, NeighborSolicitation.deserializer());
+        ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.NEIGHBOR_ADVERTISEMENT, NeighborAdvertisement.deserializer());
+        ICMP6.TYPE_DESERIALIZER_MAP.put(ICMP6.REDIRECT, Redirect.deserializer());
     }
 
     protected byte icmpType;
@@ -261,6 +264,31 @@
         return data;
     }
 
+    @Override
+    public IPacket deserialize(final byte[] data, final int offset,
+                               final int length) {
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.icmpType = bb.get();
+        this.icmpCode = bb.get();
+        this.checksum = bb.getShort();
+
+        Deserializer<? extends IPacket> deserializer;
+        if (ICMP6.TYPE_DESERIALIZER_MAP.containsKey(icmpType)) {
+            deserializer = TYPE_DESERIALIZER_MAP.get(icmpType);
+        } else {
+            deserializer = Data.deserializer();
+        }
+        try {
+            this.payload = deserializer.deserialize(data, bb.position(),
+                                                     bb.limit() - bb.position());
+            this.payload.setParent(this);
+        } catch (DeserializationException e) {
+            return this;
+        }
+
+        return this;
+    }
+
     /*
      * (non-Javadoc)
      *
@@ -305,31 +333,34 @@
         return true;
     }
 
-    @Override
-    public IPacket deserialize(final byte[] data, final int offset,
-                               final int length) {
-        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
-        this.icmpType = bb.get();
-        this.icmpCode = bb.get();
-        this.checksum = bb.getShort();
+    /**
+     * Deserializer function for ICMPv6 packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<ICMP6> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, HEADER_LENGTH);
 
-        IPacket payload;
-        if (ICMP6.PROTOCOL_CLASS_MAP.containsKey(this.icmpType)) {
-            final Class<? extends IPacket> clazz = ICMP6.PROTOCOL_CLASS_MAP
-                    .get(this.icmpType);
-            try {
-                payload = clazz.newInstance();
-            } catch (final Exception e) {
-                throw new RuntimeException(
-                        "Error parsing payload for ICMP6 packet", e);
+            ICMP6 icmp6 = new ICMP6();
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+            icmp6.icmpType = bb.get();
+            icmp6.icmpCode = bb.get();
+            icmp6.checksum = bb.getShort();
+
+            Deserializer<? extends IPacket> deserializer;
+            if (ICMP6.TYPE_DESERIALIZER_MAP.containsKey(icmp6.icmpType)) {
+                deserializer = TYPE_DESERIALIZER_MAP.get(icmp6.icmpType);
+            } else {
+                deserializer = Data.deserializer();
             }
-        } else {
-            payload = new Data();
-        }
-        this.payload = payload.deserialize(data, bb.position(),
-                bb.limit() - bb.position());
-        this.payload.setParent(this);
+            icmp6.payload = deserializer.deserialize(data, bb.position(),
+                                                bb.limit() - bb.position());
+            icmp6.payload.setParent(icmp6);
 
-        return this;
+            return icmp6;
+        };
     }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/IPacket.java b/utils/misc/src/main/java/org/onlab/packet/IPacket.java
index ac6ae60..38684eb 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IPacket.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IPacket.java
@@ -64,6 +64,11 @@
     /**
      * Deserializes this packet layer and all possible payloads.
      *
+     * NOTE: This method has been deprecated and will be removed in a future
+     * release. It is now recommended to use the Deserializer function provided
+     * by the deserialize() method on each packet to deserialize them. The
+     * Deserializer functions are robust to malformed input.
+     *
      * @param data bytes to deserialize
      * @param offset
      *            offset to start deserializing from
@@ -71,6 +76,7 @@
      *            length of the data to deserialize
      * @return the deserialized data
      */
+    @Deprecated
     IPacket deserialize(byte[] data, int offset, int length);
 
     /**
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 d1563eb..0d75245 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IPv4.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IPv4.java
@@ -25,6 +25,8 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import static org.onlab.packet.PacketUtils.*;
+
 /**
  *
  */
@@ -32,19 +34,21 @@
     public static final byte PROTOCOL_ICMP = 0x1;
     public static final byte PROTOCOL_TCP = 0x6;
     public static final byte PROTOCOL_UDP = 0x11;
-    public static final Map<Byte, Class<? extends IPacket>> PROTOCOL_CLASS_MAP =
+    public static final Map<Byte, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP =
             new HashMap<>();
 
     static {
-        IPv4.PROTOCOL_CLASS_MAP.put(IPv4.PROTOCOL_ICMP, ICMP.class);
-        IPv4.PROTOCOL_CLASS_MAP.put(IPv4.PROTOCOL_TCP, TCP.class);
-        IPv4.PROTOCOL_CLASS_MAP.put(IPv4.PROTOCOL_UDP, UDP.class);
+        IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_ICMP, ICMP.deserializer());
+        IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_TCP, TCP.deserializer());
+        IPv4.PROTOCOL_DESERIALIZER_MAP.put(IPv4.PROTOCOL_UDP, UDP.deserializer());
     }
 
     private static final byte DSCP_MASK = 0x3f;
     private static final byte DSCP_OFFSET = 2;
     private static final byte ECN_MASK = 0x3;
 
+    private static final short HEADER_LENGTH = 20;
+
     protected byte version;
     protected byte headerLength;
     protected byte diffServ;
@@ -414,7 +418,7 @@
 
     @Override
     public IPacket deserialize(final byte[] data, final int offset,
-            final int length) {
+                               final int length) {
         final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
         short sscratch;
 
@@ -439,29 +443,26 @@
             bb.get(this.options);
         }
 
-        IPacket payload;
-        if (IPv4.PROTOCOL_CLASS_MAP.containsKey(this.protocol)) {
-            final Class<? extends IPacket> clazz = IPv4.PROTOCOL_CLASS_MAP
-                    .get(this.protocol);
-            try {
-                payload = clazz.newInstance();
-            } catch (final Exception e) {
-                throw new RuntimeException(
-                        "Error parsing payload for IPv4 packet", e);
-            }
-        } else {
-            payload = new Data();
-        }
-        this.payload = payload.deserialize(data, bb.position(),
-                bb.limit() - bb.position());
-        this.payload.setParent(this);
-
         if (this.totalLength != length) {
             this.isTruncated = true;
         } else {
             this.isTruncated = false;
         }
 
+        Deserializer<? extends IPacket> deserializer;
+        if (IPv4.PROTOCOL_DESERIALIZER_MAP.containsKey(this.protocol)) {
+            deserializer = IPv4.PROTOCOL_DESERIALIZER_MAP.get(this.protocol);
+        } else {
+            deserializer = Data.deserializer();
+        }
+        try {
+            this.payload = deserializer.deserialize(data, bb.position(),
+                                                    bb.limit() - bb.position());
+            this.payload.setParent(this);
+        } catch (DeserializationException e) {
+            return this;
+        }
+
         return this;
     }
 
@@ -669,4 +670,60 @@
         }
         return true;
     }
+
+    /**
+     * Deserializer function for IPv4 packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<IPv4> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, HEADER_LENGTH);
+
+            IPv4 ipv4 = new IPv4();
+
+            final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+            byte versionByte = bb.get();
+            ipv4.headerLength = (byte) (versionByte & 0xf);
+            ipv4.setVersion((byte) (versionByte >> 4 & 0xf));
+            ipv4.setDiffServ(bb.get());
+            ipv4.totalLength = bb.getShort();
+            ipv4.identification = bb.getShort();
+            short flagsFragment = bb.getShort();
+            ipv4.flags = (byte) (flagsFragment >> 13 & 0x7);
+            ipv4.fragmentOffset = (short) (flagsFragment & 0x1fff);
+            ipv4.ttl = bb.get();
+            ipv4.protocol = bb.get();
+            ipv4.checksum = bb.getShort();
+            ipv4.sourceAddress = bb.getInt();
+            ipv4.destinationAddress = bb.getInt();
+
+            if (ipv4.headerLength > 5) {
+                checkHeaderLength(length, ipv4.headerLength * 4);
+
+                int optionsLength = (ipv4.headerLength - 5) * 4;
+                ipv4.options = new byte[optionsLength];
+                bb.get(ipv4.options);
+            }
+
+            Deserializer<? extends IPacket> deserializer;
+            if (IPv4.PROTOCOL_DESERIALIZER_MAP.containsKey(ipv4.protocol)) {
+                deserializer = IPv4.PROTOCOL_DESERIALIZER_MAP.get(ipv4.protocol);
+            } else {
+                deserializer = Data.deserializer();
+            }
+            ipv4.payload = deserializer.deserialize(data, bb.position(),
+                                                    bb.limit() - bb.position());
+            ipv4.payload.setParent(ipv4);
+
+            if (ipv4.totalLength != length) {
+                ipv4.isTruncated = true;
+            } else {
+                ipv4.isTruncated = false;
+            }
+
+            return ipv4;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/IPv6.java b/utils/misc/src/main/java/org/onlab/packet/IPv6.java
index 3bb35c5..2e59632 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IPv6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IPv6.java
@@ -22,14 +22,17 @@
 import org.onlab.packet.ipv6.DestinationOptions;
 import org.onlab.packet.ipv6.EncapSecurityPayload;
 import org.onlab.packet.ipv6.Fragment;
-import org.onlab.packet.ipv6.IExtensionHeader;
 import org.onlab.packet.ipv6.HopByHopOptions;
+import org.onlab.packet.ipv6.IExtensionHeader;
 import org.onlab.packet.ipv6.Routing;
+
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
+import static org.onlab.packet.PacketUtils.checkInput;
+
 /**
  * Implements IPv6 packet format. (RFC 2460)
  */
@@ -47,19 +50,19 @@
     public static final byte PROTOCOL_DSTOPT = 0x3C;
 
 
-    public static final Map<Byte, Class<? extends IPacket>> PROTOCOL_CLASS_MAP =
+    public static final Map<Byte, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP =
             new HashMap<>();
 
     static {
-        IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_ICMP6, ICMP6.class);
-        IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_TCP, TCP.class);
-        IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_UDP, UDP.class);
-        IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_HOPOPT, HopByHopOptions.class);
-        IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_ROUTING, Routing.class);
-        IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_FRAG, Fragment.class);
-        IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_ESP, EncapSecurityPayload.class);
-        IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_AH, Authentication.class);
-        IPv6.PROTOCOL_CLASS_MAP.put(IPv6.PROTOCOL_DSTOPT, DestinationOptions.class);
+        IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_ICMP6, ICMP6.deserializer());
+        IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_TCP, TCP.deserializer());
+        IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_UDP, UDP.deserializer());
+        IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_HOPOPT, HopByHopOptions.deserializer());
+        IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_ROUTING, Routing.deserializer());
+        IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_FRAG, Fragment.deserializer());
+        IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_ESP, EncapSecurityPayload.deserializer());
+        IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_AH, Authentication.deserializer());
+        IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_DSTOPT, DestinationOptions.deserializer());
     }
 
     protected byte version;
@@ -256,22 +259,19 @@
         bb.get(this.sourceAddress, 0, Ip6Address.BYTE_LENGTH);
         bb.get(this.destinationAddress, 0, Ip6Address.BYTE_LENGTH);
 
-        IPacket payload;
-        if (IPv6.PROTOCOL_CLASS_MAP.containsKey(this.nextHeader)) {
-            final Class<? extends IPacket> clazz = IPv6.PROTOCOL_CLASS_MAP
-                    .get(this.nextHeader);
-            try {
-                payload = clazz.newInstance();
-            } catch (final Exception e) {
-                throw new RuntimeException(
-                        "Error parsing payload for IPv6 packet", e);
-            }
+        Deserializer<? extends IPacket> deserializer;
+        if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) {
+            deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader);
         } else {
-            payload = new Data();
+            deserializer = Data.deserializer();
         }
-        this.payload = payload.deserialize(data, bb.position(),
-                bb.limit() - bb.position());
-        this.payload.setParent(this);
+        try {
+            this.payload = deserializer.deserialize(data, bb.position(),
+                                                    bb.limit() - bb.position());
+            this.payload.setParent(this);
+        } catch (DeserializationException e) {
+            return this;
+        }
 
         return this;
     }
@@ -343,4 +343,42 @@
         }
         return true;
     }
+
+    /**
+     * Deserializer function for IPv6 packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<IPv6> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, FIXED_HEADER_LENGTH);
+
+            IPv6 ipv6 = new IPv6();
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+            int iscratch = bb.getInt();
+
+            ipv6.version = (byte) (iscratch >> 28 & 0xf);
+            ipv6.trafficClass = (byte) (iscratch >> 20 & 0xff);
+            ipv6.flowLabel = iscratch & 0xfffff;
+            ipv6.payloadLength = bb.getShort();
+            ipv6.nextHeader = bb.get();
+            ipv6.hopLimit = bb.get();
+            bb.get(ipv6.sourceAddress, 0, Ip6Address.BYTE_LENGTH);
+            bb.get(ipv6.destinationAddress, 0, Ip6Address.BYTE_LENGTH);
+
+            Deserializer<? extends IPacket> deserializer;
+            if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(ipv6.nextHeader)) {
+                deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(ipv6.nextHeader);
+            } else {
+                deserializer = Data.deserializer();
+            }
+            ipv6.payload = deserializer.deserialize(data, bb.position(),
+                                               bb.limit() - bb.position());
+            ipv6.payload.setParent(ipv6);
+
+            return ipv6;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/LLC.java b/utils/misc/src/main/java/org/onlab/packet/LLC.java
index 035f396..78b4f3f 100644
--- a/utils/misc/src/main/java/org/onlab/packet/LLC.java
+++ b/utils/misc/src/main/java/org/onlab/packet/LLC.java
@@ -20,6 +20,8 @@
 
 import java.nio.ByteBuffer;
 
+import static org.onlab.packet.PacketUtils.*;
+
 /**
  * This class represents an Link Local Control header that is used in Ethernet
  * 802.3.
@@ -27,6 +29,9 @@
  *
  */
 public class LLC extends BasePacket {
+
+    public static final byte LLC_HEADER_LENGTH = 3;
+
     private byte dsap = 0;
     private byte ssap = 0;
     private byte ctrl = 0;
@@ -67,11 +72,31 @@
 
     @Override
     public IPacket deserialize(final byte[] data, final int offset,
-            final int length) {
+                               final int length) {
         final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
         this.dsap = bb.get();
         this.ssap = bb.get();
         this.ctrl = bb.get();
         return this;
     }
+
+    /**
+     * Deserializer function for LLC packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<LLC> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, LLC_HEADER_LENGTH);
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            LLC llc = new LLC();
+
+            llc.dsap = bb.get();
+            llc.ssap = bb.get();
+            llc.ctrl = bb.get();
+
+            return llc;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/LLDP.java b/utils/misc/src/main/java/org/onlab/packet/LLDP.java
index 339bc3a..ae9d717 100644
--- a/utils/misc/src/main/java/org/onlab/packet/LLDP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/LLDP.java
@@ -20,13 +20,26 @@
 package org.onlab.packet;
 
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
 
+import static org.onlab.packet.PacketUtils.*;
+
 /**
  *
  */
 public class LLDP extends BasePacket {
+    public static final byte CHASSIS_TLV_TYPE = 1;
+    public static final short CHASSIS_TLV_SIZE = 7;
+    public static final byte CHASSIS_TLV_SUBTYPE = 4;
+
+    public static final byte PORT_TLV_TYPE = 2;
+    public static final short PORT_TLV_SIZE = 5;
+    public static final byte PORT_TLV_SUBTYPE = 2;
+
+    public static final byte TTL_TLV_TYPE = 3;
+    public static final short TTL_TLV_SIZE = 2;
+
     protected LLDPTLV chassisId;
     protected LLDPTLV portId;
     protected LLDPTLV ttl;
@@ -34,7 +47,7 @@
     protected short ethType;
 
     public LLDP() {
-        this.optionalTLVList = new ArrayList<LLDPTLV>();
+        this.optionalTLVList = new LinkedList<>();
         this.ethType = Ethernet.TYPE_LLDP;
     }
 
@@ -134,11 +147,15 @@
 
     @Override
     public IPacket deserialize(final byte[] data, final int offset,
-            final int length) {
+                               final int length) {
         final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
         LLDPTLV tlv;
         do {
-            tlv = new LLDPOrganizationalTLV().deserialize(bb);
+            try {
+                tlv = new LLDPOrganizationalTLV().deserialize(bb);
+            } catch (DeserializationException e) {
+                break;
+            }
 
             // if there was a failure to deserialize stop processing TLVs
             if (tlv == null) {
@@ -227,4 +244,57 @@
         }
         return true;
     }
+
+    /**
+     * Deserializer function for LLDP packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<LLDP> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, 0);
+
+            LLDP lldp = new LLDP();
+
+            int currentIndex = 0;
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            LLDPTLV tlv;
+            do {
+                // Each new TLV must be a minimum of 2 bytes
+                // (containing the type and length fields).
+                currentIndex += 2;
+                checkHeaderLength(length, currentIndex);
+
+                tlv = new LLDPOrganizationalTLV().deserialize(bb);
+
+                // if there was a failure to deserialize stop processing TLVs
+                if (tlv == null) {
+                    break;
+                }
+                switch (tlv.getType()) {
+                case 0x0:
+                    // can throw this one away, it's just an end delimiter
+                    break;
+                case 0x1:
+                    lldp.chassisId = tlv;
+                    break;
+                case 0x2:
+                    lldp.portId = tlv;
+                    break;
+                case 0x3:
+                    lldp.ttl = tlv;
+                    break;
+                default:
+                    lldp.optionalTLVList.add(tlv);
+                    break;
+                }
+
+                currentIndex += tlv.getLength();
+            } while (tlv.getType() != 0);
+
+            return lldp;
+        };
+    }
+
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java b/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java
index 497e0ad..bedf439 100644
--- a/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java
+++ b/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java
@@ -154,10 +154,15 @@
     }
 
     @Override
-    public LLDPTLV deserialize(final ByteBuffer bb) {
-        LLDPTLV tlv = super.deserialize(bb);
-        if (tlv.getType() != LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) {
-            return tlv;
+    public LLDPTLV deserialize(final ByteBuffer bb) throws DeserializationException {
+        super.deserialize(bb);
+        if (this.getType() != LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) {
+            return this;
+        }
+
+        if (this.getLength() <= OUI_LENGTH + SUBTYPE_LENGTH) {
+            throw new DeserializationException(
+                    "TLV length is less than required for organizational TLV");
         }
 
         final ByteBuffer optionalField = ByteBuffer.wrap(this.value);
diff --git a/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java b/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java
index b5fe833..77efe1b 100644
--- a/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java
+++ b/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java
@@ -95,18 +95,23 @@
         return data;
     }
 
-    public LLDPTLV deserialize(final ByteBuffer bb) {
-        short sscratch;
-        sscratch = bb.getShort();
-        this.type = (byte) (sscratch >> 9 & 0x7f);
-        this.length = (short) (sscratch & 0x1ff);
+    public LLDPTLV deserialize(final ByteBuffer bb) throws DeserializationException {
+        if (bb.remaining() < 2) {
+            throw new DeserializationException(
+                    "Not enough bytes to deserialize TLV type and length");
+        }
+        short typeLength;
+        typeLength = bb.getShort();
+        this.type = (byte) (typeLength >> 9 & 0x7f);
+        this.length = (short) (typeLength & 0x1ff);
 
         if (this.length > 0) {
             this.value = new byte[this.length];
 
             // if there is an underrun just toss the TLV
             if (bb.remaining() < this.length) {
-                return null;
+                throw new DeserializationException(
+                        "Remaining bytes are less then the length of the TLV");
             }
             bb.get(this.value);
         }
diff --git a/utils/misc/src/main/java/org/onlab/packet/MPLS.java b/utils/misc/src/main/java/org/onlab/packet/MPLS.java
index 4ba6a0c..47dbeed 100644
--- a/utils/misc/src/main/java/org/onlab/packet/MPLS.java
+++ b/utils/misc/src/main/java/org/onlab/packet/MPLS.java
@@ -4,16 +4,19 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import static org.onlab.packet.PacketUtils.checkInput;
+
 public class MPLS extends BasePacket {
-    public static final int ADDRESS_LENGTH = 4;
+    public static final int HEADER_LENGTH = 4;
+
     public static final byte PROTOCOL_IPV4 = 0x1;
     public static final byte PROTOCOL_MPLS = 0x6;
-    public static final Map<Byte, Class<? extends IPacket>> PROTOCOL_CLASS_MAP;
+    static Map<Byte, Deserializer<? extends IPacket>> protocolDeserializerMap
+            = new HashMap<>();
 
     static {
-        PROTOCOL_CLASS_MAP = new HashMap<Byte, Class<? extends IPacket>>();
-        PROTOCOL_CLASS_MAP.put(PROTOCOL_IPV4, IPv4.class);
-        PROTOCOL_CLASS_MAP.put(PROTOCOL_MPLS, MPLS.class);
+        protocolDeserializerMap.put(PROTOCOL_IPV4, IPv4.deserializer());
+        protocolDeserializerMap.put(PROTOCOL_MPLS, MPLS.deserializer());
     }
 
     protected int label; //20bits
@@ -59,19 +62,18 @@
         this.bos = (byte) (mplsheader & 0x000000ff);
         this.protocol = (this.bos == 1) ? PROTOCOL_IPV4 : PROTOCOL_MPLS;
 
-        IPacket payload;
-        if (IPv4.PROTOCOL_CLASS_MAP.containsKey(this.protocol)) {
-            Class<? extends IPacket> clazz = IPv4.PROTOCOL_CLASS_MAP.get(this.protocol);
-            try {
-                payload = clazz.newInstance();
-            } catch (Exception e) {
-                throw new RuntimeException("Error parsing payload for MPLS packet", e);
-            }
+        Deserializer<? extends IPacket> deserializer;
+        if (protocolDeserializerMap.containsKey(this.protocol)) {
+            deserializer = protocolDeserializerMap.get(this.protocol);
         } else {
-            payload = new Data();
+            deserializer = Data.deserializer();
         }
-        this.payload = payload.deserialize(data, bb.position(), bb.limit() - bb.position());
-        this.payload.setParent(this);
+        try {
+            this.payload = deserializer.deserialize(data, bb.position(), bb.limit() - bb.position());
+            this.payload.setParent(this);
+        } catch (DeserializationException e) {
+            return this;
+        }
 
         return this;
     }
@@ -112,4 +114,34 @@
         this.ttl = ttl;
     }
 
+    /**
+     * Deserializer function for MPLS packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<MPLS> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, HEADER_LENGTH);
+
+            MPLS mpls = new MPLS();
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+            int mplsheader = bb.getInt();
+            mpls.label = ((mplsheader & 0xfffff000) >>> 12);
+            mpls.bos = (byte) ((mplsheader & 0x00000100) >> 8);
+            mpls.ttl = (byte) (mplsheader & 0x000000ff);
+            mpls.protocol = (mpls.bos == 1) ? PROTOCOL_IPV4 : PROTOCOL_MPLS;
+
+            Deserializer<? extends IPacket> deserializer;
+            if (protocolDeserializerMap.containsKey(mpls.protocol)) {
+                deserializer = protocolDeserializerMap.get(mpls.protocol);
+            } else {
+                deserializer = Data.deserializer();
+            }
+            mpls.payload = deserializer.deserialize(data, bb.position(), bb.limit() - bb.position());
+            mpls.payload.setParent(mpls);
+
+            return mpls;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/PacketUtils.java b/utils/misc/src/main/java/org/onlab/packet/PacketUtils.java
new file mode 100644
index 0000000..c3bede2
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/PacketUtils.java
@@ -0,0 +1,84 @@
+/*
+ * 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 static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Utilities for working with packet headers.
+ */
+public final class PacketUtils {
+
+    private PacketUtils() {
+    }
+
+    /**
+     * Check the length of the input buffer is appropriate given the offset and
+     * length parameters.
+     *
+     * @param byteLength length of the input buffer array
+     * @param offset offset given to begin reading bytes from
+     * @param length length given to read up until
+     * @throws DeserializationException if the input parameters don't match up (i.e
+     * we can't read that many bytes from the buffer at the given offest)
+     */
+    public static void checkBufferLength(int byteLength, int offset, int length)
+            throws DeserializationException {
+        boolean ok = (offset >= 0 && offset < byteLength);
+        ok = ok & (length >= 0 && offset + length <= byteLength);
+
+        if (!ok) {
+            throw new DeserializationException("Unable to read " + length + " bytes from a "
+                + byteLength + " byte array starting at offset " + offset);
+        }
+    }
+
+    /**
+     * Check that there are enough bytes in the buffer to read some number of
+     * bytes that we need to read a full header.
+     *
+     * @param givenLength given size of the buffer
+     * @param requiredLength number of bytes we need to read some header fully
+     * @throws DeserializationException if there aren't enough bytes
+     */
+    public static void checkHeaderLength(int givenLength, int requiredLength)
+            throws DeserializationException {
+        if (requiredLength > givenLength) {
+            throw new DeserializationException(requiredLength
+                + " bytes are needed to continue deserialization, however only "
+                + givenLength + " remain in buffer");
+        }
+    }
+
+    /**
+     * Check the input parameters are sane and there's enough bytes to read
+     * the required length.
+     *
+     * @param data input byte buffer
+     * @param offset offset of the start of the header
+     * @param length length given to deserialize the header
+     * @param requiredLength length needed to deserialize header
+     * @throws DeserializationException if we're unable to deserialize the
+     * packet based on the input parameters
+     */
+    public static void checkInput(byte[] data, int offset, int length, int requiredLength)
+            throws DeserializationException {
+        checkNotNull(data);
+        checkBufferLength(data.length, offset, length);
+        checkHeaderLength(length, requiredLength);
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/TCP.java b/utils/misc/src/main/java/org/onlab/packet/TCP.java
index 3b92c83..b13b53c 100644
--- a/utils/misc/src/main/java/org/onlab/packet/TCP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/TCP.java
@@ -20,11 +20,16 @@
 
 import java.nio.ByteBuffer;
 
+import static org.onlab.packet.PacketUtils.*;
+
 /**
  * Implements TCP packet format.
  */
 
 public class TCP extends BasePacket {
+
+    private static final short TCP_HEADER_LENGTH = 20;
+
     protected short sourcePort;
     protected short destinationPort;
     protected int sequence;
@@ -339,6 +344,40 @@
         return data;
     }
 
+    @Override
+    public IPacket deserialize(final byte[] data, final int offset,
+                               final int length) {
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.sourcePort = bb.getShort();
+        this.destinationPort = bb.getShort();
+        this.sequence = bb.getInt();
+        this.acknowledge = bb.getInt();
+        this.flags = bb.getShort();
+        this.dataOffset = (byte) (this.flags >> 12 & 0xf);
+        this.flags = (short) (this.flags & 0x1ff);
+        this.windowSize = bb.getShort();
+        this.checksum = bb.getShort();
+        this.urgentPointer = bb.getShort();
+        if (this.dataOffset > 5) {
+            int optLength = (this.dataOffset << 2) - 20;
+            if (bb.limit() < bb.position() + optLength) {
+                optLength = bb.limit() - bb.position();
+            }
+            try {
+                this.options = new byte[optLength];
+                bb.get(this.options, 0, optLength);
+            } catch (final IndexOutOfBoundsException e) {
+                this.options = null;
+            }
+        }
+
+        this.payload = new Data();
+        this.payload = this.payload.deserialize(data, bb.position(), bb.limit()
+                - bb.position());
+        this.payload.setParent(this);
+        return this;
+    }
+
     /*
      * (non-Javadoc)
      *
@@ -384,37 +423,39 @@
                 && (this.dataOffset == 5 || this.options.equals(other.options));
     }
 
-    @Override
-    public IPacket deserialize(final byte[] data, final int offset,
-            final int length) {
-        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
-        this.sourcePort = bb.getShort();
-        this.destinationPort = bb.getShort();
-        this.sequence = bb.getInt();
-        this.acknowledge = bb.getInt();
-        this.flags = bb.getShort();
-        this.dataOffset = (byte) (this.flags >> 12 & 0xf);
-        this.flags = (short) (this.flags & 0x1ff);
-        this.windowSize = bb.getShort();
-        this.checksum = bb.getShort();
-        this.urgentPointer = bb.getShort();
-        if (this.dataOffset > 5) {
-            int optLength = (this.dataOffset << 2) - 20;
-            if (bb.limit() < bb.position() + optLength) {
-                optLength = bb.limit() - bb.position();
-            }
-            try {
-                this.options = new byte[optLength];
-                bb.get(this.options, 0, optLength);
-            } catch (final IndexOutOfBoundsException e) {
-                this.options = null;
-            }
-        }
+    /**
+     * Deserializer function for TCP packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<TCP> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, TCP_HEADER_LENGTH);
 
-        this.payload = new Data();
-        this.payload = this.payload.deserialize(data, bb.position(), bb.limit()
-                - bb.position());
-        this.payload.setParent(this);
-        return this;
+            TCP tcp = new TCP();
+
+            final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            tcp.sourcePort = bb.getShort();
+            tcp.destinationPort = bb.getShort();
+            tcp.sequence = bb.getInt();
+            tcp.acknowledge = bb.getInt();
+            tcp.flags = bb.getShort();
+            tcp.dataOffset = (byte) (tcp.flags >> 12 & 0xf);
+            tcp.flags = (short) (tcp.flags & 0x1ff);
+            tcp.windowSize = bb.getShort();
+            tcp.checksum = bb.getShort();
+            tcp.urgentPointer = bb.getShort();
+            if (tcp.dataOffset > 5) {
+                int optLength = (tcp.dataOffset << 2) - 20;
+                checkHeaderLength(length, TCP_HEADER_LENGTH + tcp.dataOffset);
+                tcp.options = new byte[optLength];
+                bb.get(tcp.options, 0, optLength);
+            }
+
+            tcp.payload = Data.deserializer()
+                    .deserialize(data, bb.position(), bb.limit() - bb.position());
+            tcp.payload.setParent(tcp);
+            return tcp;
+        };
     }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/UDP.java b/utils/misc/src/main/java/org/onlab/packet/UDP.java
index c743f09..32432e6 100644
--- a/utils/misc/src/main/java/org/onlab/packet/UDP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/UDP.java
@@ -22,23 +22,27 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import static org.onlab.packet.PacketUtils.*;
+
 /**
  *
  */
 
 public class UDP extends BasePacket {
-    public static final Map<Short, Class<? extends IPacket>> DECODE_MAP =
+    public static final Map<Short, Deserializer<? extends IPacket>> PORT_DESERIALIZER_MAP =
             new HashMap<>();
     public static final short DHCP_SERVER_PORT = (short) 67;
     public static final short DHCP_CLIENT_PORT = (short) 68;
 
+    private static final short UDP_HEADER_LENGTH = 8;
+
     static {
         /*
          * Disable DHCP until the deserialize code is hardened to deal with
          * garbage input
          */
-        UDP.DECODE_MAP.put(UDP.DHCP_SERVER_PORT, DHCP.class);
-        UDP.DECODE_MAP.put(UDP.DHCP_CLIENT_PORT, DHCP.class);
+        UDP.PORT_DESERIALIZER_MAP.put(UDP.DHCP_SERVER_PORT, DHCP.deserializer());
+        UDP.PORT_DESERIALIZER_MAP.put(UDP.DHCP_CLIENT_PORT, DHCP.deserializer());
 
     }
 
@@ -192,6 +196,34 @@
         return data;
     }
 
+    @Override
+    public IPacket deserialize(final byte[] data, final int offset,
+                               final int length) {
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.sourcePort = bb.getShort();
+        this.destinationPort = bb.getShort();
+        this.length = bb.getShort();
+        this.checksum = bb.getShort();
+
+        Deserializer<? extends IPacket> deserializer;
+        if (UDP.PORT_DESERIALIZER_MAP.containsKey(this.destinationPort)) {
+            deserializer = UDP.PORT_DESERIALIZER_MAP.get(this.destinationPort);
+        } else if (UDP.PORT_DESERIALIZER_MAP.containsKey(this.sourcePort)) {
+            deserializer = UDP.PORT_DESERIALIZER_MAP.get(this.sourcePort);
+        } else {
+            deserializer = Data.deserializer();
+        }
+
+        try {
+            this.payload = deserializer.deserialize(data, bb.position(),
+                                                   bb.limit() - bb.position());
+            this.payload.setParent(this);
+        } catch (DeserializationException e) {
+            return this;
+        }
+        return this;
+    }
+
     /*
      * (non-Javadoc)
      *
@@ -240,35 +272,36 @@
         return true;
     }
 
-    @Override
-    public IPacket deserialize(final byte[] data, final int offset,
-            final int length) {
-        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
-        this.sourcePort = bb.getShort();
-        this.destinationPort = bb.getShort();
-        this.length = bb.getShort();
-        this.checksum = bb.getShort();
+    /**
+     * Deserializer function for UDP packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<UDP> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, UDP_HEADER_LENGTH);
 
-        if (UDP.DECODE_MAP.containsKey(this.destinationPort)) {
-            try {
-                this.payload = UDP.DECODE_MAP.get(this.destinationPort)
-                        .getConstructor().newInstance();
-            } catch (final Exception e) {
-                throw new RuntimeException("Failure instantiating class", e);
+            UDP udp = new UDP();
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            udp.sourcePort = bb.getShort();
+            udp.destinationPort = bb.getShort();
+            udp.length = bb.getShort();
+            udp.checksum = bb.getShort();
+
+            Deserializer<? extends IPacket> deserializer;
+            if (UDP.PORT_DESERIALIZER_MAP.containsKey(udp.destinationPort)) {
+                deserializer = UDP.PORT_DESERIALIZER_MAP.get(udp.destinationPort);
+            } else if (UDP.PORT_DESERIALIZER_MAP.containsKey(udp.sourcePort)) {
+                deserializer = UDP.PORT_DESERIALIZER_MAP.get(udp.sourcePort);
+            } else {
+                deserializer = Data.deserializer();
             }
-        } else if (UDP.DECODE_MAP.containsKey(this.sourcePort)) {
-            try {
-                this.payload = UDP.DECODE_MAP.get(this.sourcePort)
-                        .getConstructor().newInstance();
-            } catch (final Exception e) {
-                throw new RuntimeException("Failure instantiating class", e);
-            }
-        } else {
-            this.payload = new Data();
-        }
-        this.payload = this.payload.deserialize(data, bb.position(), bb.limit()
-                - bb.position());
-        this.payload.setParent(this);
-        return this;
+
+            udp.payload = deserializer.deserialize(data, bb.position(),
+                                                   bb.limit() - bb.position());
+            udp.payload.setParent(udp);
+            return udp;
+        };
     }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/Authentication.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/Authentication.java
index d4c741f..ec04a81 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ipv6/Authentication.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/Authentication.java
@@ -18,11 +18,16 @@
 
 import org.onlab.packet.BasePacket;
 import org.onlab.packet.Data;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPacket;
 import org.onlab.packet.IPv6;
+
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 
+import static org.onlab.packet.PacketUtils.checkInput;
+
 /**
  * Implements IPv6 authentication extension header format. (RFC 4302)
  */
@@ -186,22 +191,20 @@
         this.integrityCheck = new byte[icvLength];
         bb.get(this.integrityCheck, 0, icvLength);
 
-        IPacket payload;
-        if (IPv6.PROTOCOL_CLASS_MAP.containsKey(this.nextHeader)) {
-            final Class<? extends IPacket> clazz = IPv6.PROTOCOL_CLASS_MAP
-                    .get(this.nextHeader);
-            try {
-                payload = clazz.newInstance();
-            } catch (final Exception e) {
-                throw new RuntimeException(
-                        "Error parsing payload for Authentication packet", e);
-            }
+        Deserializer<? extends IPacket> deserializer;
+        if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) {
+            deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader);
         } else {
-            payload = new Data();
+            deserializer = Data.deserializer();
         }
-        this.payload = payload.deserialize(data, bb.position(),
-                bb.limit() - bb.position());
-        this.payload.setParent(this);
+
+        try {
+            this.payload = deserializer.deserialize(data, bb.position(),
+                                                              bb.limit() - bb.position());
+            this.payload.setParent(this);
+        } catch (DeserializationException e) {
+            return this;
+        }
 
         return this;
     }
@@ -259,4 +262,39 @@
         }
         return true;
     }
+
+    /**
+     * Deserializer function for authentication headers.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<Authentication> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, FIXED_HEADER_LENGTH);
+
+            Authentication authentication = new Authentication();
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            authentication.nextHeader = bb.get();
+            authentication.payloadLength = bb.get();
+            bb.getShort();
+            authentication.securityParamIndex = bb.getInt();
+            authentication.sequence = bb.getInt();
+            int icvLength = (authentication.payloadLength + MINUS) * LENGTH_UNIT - FIXED_HEADER_LENGTH;
+            authentication.integrityCheck = new byte[icvLength];
+            bb.get(authentication.integrityCheck, 0, icvLength);
+
+            Deserializer<? extends IPacket> deserializer;
+            if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(authentication.nextHeader)) {
+                deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(authentication.nextHeader);
+            } else {
+                deserializer = Data.deserializer();
+            }
+            authentication.payload = deserializer.deserialize(data, bb.position(),
+                                               bb.limit() - bb.position());
+            authentication.payload.setParent(authentication);
+
+            return authentication;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/BaseOptions.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/BaseOptions.java
index 6fd81cf..f57b756 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ipv6/BaseOptions.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/BaseOptions.java
@@ -18,12 +18,17 @@
 
 import org.onlab.packet.BasePacket;
 import org.onlab.packet.Data;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPacket;
 import org.onlab.packet.IPv6;
 
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 
+import static org.onlab.packet.PacketUtils.checkHeaderLength;
+import static org.onlab.packet.PacketUtils.checkInput;
+
 /**
  * Base class for hop-by-hop options and destination options.
  */
@@ -151,22 +156,19 @@
         this.options = new byte[optionLength];
         bb.get(this.options, 0, optionLength);
 
-        IPacket payload;
-        if (IPv6.PROTOCOL_CLASS_MAP.containsKey(this.nextHeader)) {
-            final Class<? extends IPacket> clazz = IPv6.PROTOCOL_CLASS_MAP
-                    .get(this.nextHeader);
-            try {
-                payload = clazz.newInstance();
-            } catch (final Exception e) {
-                throw new RuntimeException(
-                        "Error parsing payload for BaseOptions packet", e);
-            }
+        Deserializer<? extends IPacket> deserializer;
+        if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) {
+            deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader);
         } else {
-            payload = new Data();
+            deserializer = Data.deserializer();
         }
-        this.payload = payload.deserialize(data, bb.position(),
-                bb.limit() - bb.position());
-        this.payload.setParent(this);
+        try {
+            this.payload = deserializer.deserialize(data, bb.position(),
+                                                    bb.limit() - bb.position());
+            this.payload.setParent(this);
+        } catch (DeserializationException e) {
+            return this;
+        }
 
         return this;
     }
@@ -219,4 +221,40 @@
         }
         return true;
     }
+
+    /**
+     * Deserializer function for IPv6 base options.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<BaseOptions> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, FIXED_HEADER_LENGTH);
+
+            BaseOptions baseOptions = new BaseOptions();
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            baseOptions.nextHeader = bb.get();
+            baseOptions.headerExtLength = bb.get();
+            int optionLength =
+                    FIXED_OPTIONS_LENGTH + LENGTH_UNIT * baseOptions.headerExtLength;
+
+            checkHeaderLength(bb.remaining(), optionLength);
+
+            baseOptions.options = new byte[optionLength];
+            bb.get(baseOptions.options, 0, optionLength);
+
+            Deserializer<? extends IPacket> deserializer;
+            if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(baseOptions.nextHeader)) {
+                deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(baseOptions.nextHeader);
+            } else {
+                deserializer = Data.deserializer();
+            }
+            baseOptions.payload = deserializer.deserialize(data, bb.position(),
+                                               bb.limit() - bb.position());
+            baseOptions.payload.setParent(baseOptions);
+
+            return baseOptions;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/EncapSecurityPayload.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/EncapSecurityPayload.java
index ffe552f..e46a126 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ipv6/EncapSecurityPayload.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/EncapSecurityPayload.java
@@ -18,10 +18,14 @@
 
 import org.onlab.packet.BasePacket;
 import org.onlab.packet.Data;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPacket;
 import org.onlab.packet.IPv6;
+
 import java.nio.ByteBuffer;
 
+import static org.onlab.packet.PacketUtils.checkInput;
+
 /**
  * Implements IPv6 Encapsulating Security Payload (ESP) extension header format.
  * (RFC 4303)
@@ -113,7 +117,7 @@
 
         this.payload = new Data();
         this.payload.deserialize(data, bb.position(),
-                bb.limit() - bb.position());
+                                 bb.limit() - bb.position());
         this.payload.setParent(this);
 
         return this;
@@ -158,4 +162,27 @@
         }
         return true;
     }
+
+    /**
+     * Deserializer function for encapsulated security payload headers.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<EncapSecurityPayload> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, HEADER_LENGTH);
+
+            EncapSecurityPayload encapSecurityPayload = new EncapSecurityPayload();
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            encapSecurityPayload.securityParamIndex = bb.getInt();
+            encapSecurityPayload.sequence = bb.getInt();
+
+            encapSecurityPayload.payload = Data.deserializer().deserialize(
+                    data, bb.position(), bb.limit() - bb.position());
+            encapSecurityPayload.payload.setParent(encapSecurityPayload);
+
+            return encapSecurityPayload;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/Fragment.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/Fragment.java
index 5a0d4b4..68015d3 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ipv6/Fragment.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/Fragment.java
@@ -18,11 +18,15 @@
 
 import org.onlab.packet.BasePacket;
 import org.onlab.packet.Data;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPacket;
 import org.onlab.packet.IPv6;
 
 import java.nio.ByteBuffer;
 
+import static org.onlab.packet.PacketUtils.checkInput;
+
 /**
  * Implements IPv6 fragment extension header format. (RFC 2460)
  */
@@ -149,22 +153,19 @@
         this.moreFragment = (byte) (sscratch & 0x1);
         this.identification = bb.getInt();
 
-        IPacket payload;
-        if (IPv6.PROTOCOL_CLASS_MAP.containsKey(this.nextHeader)) {
-            final Class<? extends IPacket> clazz = IPv6.PROTOCOL_CLASS_MAP
-                    .get(this.nextHeader);
-            try {
-                payload = clazz.newInstance();
-            } catch (final Exception e) {
-                throw new RuntimeException(
-                        "Error parsing payload for Fragment packet", e);
-            }
+        Deserializer<? extends IPacket> deserializer;
+        if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) {
+            deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader);
         } else {
-            payload = new Data();
+            deserializer = Data.deserializer();
         }
-        this.payload = payload.deserialize(data, bb.position(),
-                bb.limit() - bb.position());
-        this.payload.setParent(this);
+        try {
+            this.payload = deserializer.deserialize(data, bb.position(),
+                                                    bb.limit() - bb.position());
+            this.payload.setParent(this);
+        } catch (DeserializationException e) {
+            return this;
+        }
 
         return this;
     }
@@ -216,4 +217,37 @@
         }
         return true;
     }
+
+    /**
+     * Deserializer function for fragment headers.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<Fragment> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, HEADER_LENGTH);
+
+            Fragment fragment = new Fragment();
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            fragment.nextHeader = bb.get();
+            bb.get();
+            short sscratch = bb.getShort();
+            fragment.fragmentOffset = (short) (sscratch >> 3 & 0x1fff);
+            fragment.moreFragment = (byte) (sscratch & 0x1);
+            fragment.identification = bb.getInt();
+
+            Deserializer<? extends IPacket> deserializer;
+            if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(fragment.nextHeader)) {
+                deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(fragment.nextHeader);
+            } else {
+                deserializer = Data.deserializer();
+            }
+            fragment.payload = deserializer.deserialize(data, bb.position(),
+                                               bb.limit() - bb.position());
+            fragment.payload.setParent(fragment);
+
+            return fragment;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/Routing.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/Routing.java
index 808d2ec..d7d204a 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ipv6/Routing.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/Routing.java
@@ -18,12 +18,17 @@
 
 import org.onlab.packet.BasePacket;
 import org.onlab.packet.Data;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPacket;
 import org.onlab.packet.IPv6;
 
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 
+import static org.onlab.packet.PacketUtils.checkHeaderLength;
+import static org.onlab.packet.PacketUtils.checkInput;
+
 /**
  * Implements IPv6 routing extension header format. (RFC 2460)
  */
@@ -175,22 +180,19 @@
         this.routingData = new byte[dataLength];
         bb.get(this.routingData, 0, dataLength);
 
-        IPacket payload;
-        if (IPv6.PROTOCOL_CLASS_MAP.containsKey(this.nextHeader)) {
-            final Class<? extends IPacket> clazz = IPv6.PROTOCOL_CLASS_MAP
-                    .get(this.nextHeader);
-            try {
-                payload = clazz.newInstance();
-            } catch (final Exception e) {
-                throw new RuntimeException(
-                        "Error parsing payload for Routing packet", e);
-            }
+        Deserializer<? extends IPacket> deserializer;
+        if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) {
+            deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader);
         } else {
-            payload = new Data();
+            deserializer = new Data().deserializer();
         }
-        this.payload = payload.deserialize(data, bb.position(),
-                bb.limit() - bb.position());
-        this.payload.setParent(this);
+        try {
+            this.payload = deserializer.deserialize(data, bb.position(),
+                                                    bb.limit() - bb.position());
+            this.payload.setParent(this);
+        } catch (DeserializationException e) {
+            return this;
+        }
 
         return this;
     }
@@ -248,4 +250,42 @@
         }
         return true;
     }
-}
\ No newline at end of file
+
+    /**
+     * Deserializer function for routing headers.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<Routing> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, FIXED_HEADER_LENGTH);
+
+            Routing routing = new Routing();
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            routing.nextHeader = bb.get();
+            routing.headerExtLength = bb.get();
+            routing.routingType = bb.get();
+            routing.segmentsLeft = bb.get();
+            int dataLength =
+                    FIXED_ROUTING_DATA_LENGTH + LENGTH_UNIT * routing.headerExtLength;
+
+            checkHeaderLength(bb.remaining(), dataLength);
+
+            routing.routingData = new byte[dataLength];
+            bb.get(routing.routingData, 0, dataLength);
+
+            Deserializer<? extends IPacket> deserializer;
+            if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(routing.nextHeader)) {
+                deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(routing.nextHeader);
+            } else {
+                deserializer = new Data().deserializer();
+            }
+            routing.payload = deserializer.deserialize(data, bb.position(),
+                                               bb.limit() - bb.position());
+            routing.payload.setParent(routing);
+
+            return routing;
+        };
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java
index ccb87e3..08c749a 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java
@@ -16,6 +16,7 @@
 package org.onlab.packet.ndp;
 
 import org.onlab.packet.BasePacket;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPacket;
 import org.onlab.packet.Ip6Address;
 
@@ -23,6 +24,8 @@
 import java.util.Arrays;
 import java.util.List;
 
+import static org.onlab.packet.PacketUtils.checkInput;
+
 /**
  * Implements ICMPv6 Neighbor Advertisement packet format (RFC 4861).
  */
@@ -238,4 +241,36 @@
         }
         return true;
     }
+
+    /**
+     * Deserializer function for neighbor advertisement packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<NeighborAdvertisement> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, HEADER_LENGTH);
+
+            NeighborAdvertisement neighborAdvertisement = new NeighborAdvertisement();
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+            int iscratch;
+
+            iscratch = bb.getInt();
+            neighborAdvertisement.routerFlag = (byte) (iscratch >> 31 & 0x1);
+            neighborAdvertisement.solicitedFlag = (byte) (iscratch >> 30 & 0x1);
+            neighborAdvertisement.overrideFlag = (byte) (iscratch >> 29 & 0x1);
+            bb.get(neighborAdvertisement.targetAddress, 0, Ip6Address.BYTE_LENGTH);
+
+            NeighborDiscoveryOptions options = NeighborDiscoveryOptions.deserializer()
+                    .deserialize(data, bb.position(), bb.limit() - bb.position());
+
+            for (NeighborDiscoveryOptions.Option option : options.options()) {
+                neighborAdvertisement.addOption(option.type(), option.data());
+            }
+
+            return neighborAdvertisement;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java
index b8561b1..00a2606 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborDiscoveryOptions.java
@@ -15,13 +15,17 @@
  */
 package org.onlab.packet.ndp;
 
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.IPacket;
+
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-import org.onlab.packet.BasePacket;
-import org.onlab.packet.IPacket;
+import static org.onlab.packet.PacketUtils.checkInput;
 
 /**
  * Neighbor Discovery Protocol packet options.
@@ -33,6 +37,11 @@
     public static final byte TYPE_REDIRECTED_HEADER = 4;
     public static final byte TYPE_MTU = 5;
 
+    public static final byte INITIAL_HEADER_REQUIRED = 2;
+
+    private static final String BUFFER_UNDERFLOW_ERROR =
+            "Not enough bytes in buffer to read option";
+
     private final List<Option> options = new ArrayList<>();
 
     /**
@@ -187,7 +196,7 @@
             }
             byte lengthField = bb.get();
             int dataLength = lengthField * 8;   // The data length field is in
-                                                // unit of 8 octets
+            // unit of 8 octets
 
             // Exclude the type and length fields
             if (dataLength < 2) {
@@ -229,4 +238,44 @@
         }
         return false;
     }
+
+    public static Deserializer<NeighborDiscoveryOptions> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, INITIAL_HEADER_REQUIRED);
+
+            final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+            NeighborDiscoveryOptions ndo = new NeighborDiscoveryOptions();
+
+            ndo.options.clear();
+
+            //
+            // Deserialize all options
+            //
+            while (bb.hasRemaining()) {
+                byte type = bb.get();
+                if (!bb.hasRemaining()) {
+                    throw new DeserializationException(BUFFER_UNDERFLOW_ERROR);
+                }
+                byte lengthField = bb.get();
+                int dataLength = lengthField * 8;   // The data length field is in
+                // unit of 8 octets
+
+                // Exclude the type and length fields
+                if (dataLength < 2) {
+                    break;
+                }
+                dataLength -= 2;
+
+                if (bb.remaining() < dataLength) {
+                    throw new DeserializationException(BUFFER_UNDERFLOW_ERROR);
+                }
+                byte[] optionData = new byte[dataLength];
+                bb.get(optionData, 0, optionData.length);
+                ndo.addOption(type, optionData);
+            }
+
+            return ndo;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java
index 25df3b4..33a6eb2b6 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java
@@ -16,6 +16,7 @@
 package org.onlab.packet.ndp;
 
 import org.onlab.packet.BasePacket;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPacket;
 import org.onlab.packet.Ip6Address;
 
@@ -23,6 +24,8 @@
 import java.util.Arrays;
 import java.util.List;
 
+import static org.onlab.packet.PacketUtils.checkInput;
+
 /**
  * Implements ICMPv6 Neighbor Solicitation packet format. (RFC 4861)
  */
@@ -157,4 +160,31 @@
         }
         return true;
     }
+
+    /**
+     * Deserializer function for neighbor solicitation packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<NeighborSolicitation> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, HEADER_LENGTH);
+
+            NeighborSolicitation neighborSolicitation = new NeighborSolicitation();
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+            bb.getInt();
+            bb.get(neighborSolicitation.targetAddress, 0, Ip6Address.BYTE_LENGTH);
+
+            NeighborDiscoveryOptions options = NeighborDiscoveryOptions.deserializer()
+                    .deserialize(data, bb.position(), bb.limit() - bb.position());
+
+            for (NeighborDiscoveryOptions.Option option : options.options()) {
+                neighborSolicitation.addOption(option.type(), option.data());
+            }
+
+            return neighborSolicitation;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/Redirect.java b/utils/misc/src/main/java/org/onlab/packet/ndp/Redirect.java
index 0515869..c3f46dd 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ndp/Redirect.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ndp/Redirect.java
@@ -16,6 +16,7 @@
 package org.onlab.packet.ndp;
 
 import org.onlab.packet.BasePacket;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPacket;
 import org.onlab.packet.Ip6Address;
 
@@ -23,6 +24,8 @@
 import java.util.Arrays;
 import java.util.List;
 
+import static org.onlab.packet.PacketUtils.checkInput;
+
 /**
  * Implements ICMPv6 Redirect packet format. (RFC 4861)
  */
@@ -188,4 +191,33 @@
         }
         return true;
     }
+
+    /**
+     * Deserializer function for redirect packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<Redirect> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, HEADER_LENGTH);
+
+            Redirect redirect = new Redirect();
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+            bb.getInt();
+
+            bb.get(redirect.targetAddress, 0, Ip6Address.BYTE_LENGTH);
+            bb.get(redirect.destinationAddress, 0, Ip6Address.BYTE_LENGTH);
+
+            NeighborDiscoveryOptions options = NeighborDiscoveryOptions.deserializer()
+                    .deserialize(data, bb.position(), bb.limit() - bb.position());
+
+            for (NeighborDiscoveryOptions.Option option : options.options()) {
+                redirect.addOption(option.type(), option.data());
+            }
+
+            return redirect;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/RouterAdvertisement.java b/utils/misc/src/main/java/org/onlab/packet/ndp/RouterAdvertisement.java
index 1c2f928..e4ef2c6 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ndp/RouterAdvertisement.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ndp/RouterAdvertisement.java
@@ -16,11 +16,14 @@
 package org.onlab.packet.ndp;
 
 import org.onlab.packet.BasePacket;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPacket;
 
 import java.nio.ByteBuffer;
 import java.util.List;
 
+import static org.onlab.packet.PacketUtils.checkInput;
+
 /**
  * Implements ICMPv6 Router Advertisement packet format. (RFC 4861)
  */
@@ -284,4 +287,37 @@
         }
         return true;
     }
+
+    /**
+     * Deserializer function for router advertisement packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<RouterAdvertisement> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, HEADER_LENGTH);
+
+            RouterAdvertisement routerAdvertisement = new RouterAdvertisement();
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            int bscratch;
+
+            routerAdvertisement.currentHopLimit = bb.get();
+            bscratch = bb.get();
+            routerAdvertisement.mFlag = (byte) ((bscratch >> 7) & 0x1);
+            routerAdvertisement.oFlag = (byte) ((bscratch >> 6) & 0x1);
+            routerAdvertisement.routerLifetime = bb.getShort();
+            routerAdvertisement.reachableTime = bb.getInt();
+            routerAdvertisement.retransmitTimer = bb.getInt();
+
+            NeighborDiscoveryOptions options = NeighborDiscoveryOptions.deserializer()
+                    .deserialize(data, bb.position(), bb.limit() - bb.position());
+
+            for (NeighborDiscoveryOptions.Option option : options.options()) {
+                routerAdvertisement.addOption(option.type(), option.data());
+            }
+
+            return routerAdvertisement;
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/RouterSolicitation.java b/utils/misc/src/main/java/org/onlab/packet/ndp/RouterSolicitation.java
index 3fddc5e..07729df 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ndp/RouterSolicitation.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ndp/RouterSolicitation.java
@@ -16,19 +16,21 @@
 package org.onlab.packet.ndp;
 
 import org.onlab.packet.BasePacket;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPacket;
 
 import java.nio.ByteBuffer;
 import java.util.List;
 
+import static org.onlab.packet.PacketUtils.checkInput;
+
 /**
  * Implements ICMPv6 Router Solicitation packet format. (RFC 4861)
  */
 public class RouterSolicitation extends BasePacket {
     public static final byte HEADER_LENGTH = 4; // bytes
 
-    private final NeighborDiscoveryOptions options =
-        new NeighborDiscoveryOptions();
+    private final NeighborDiscoveryOptions options = new NeighborDiscoveryOptions();
 
     /**
      * Gets the Neighbor Discovery Protocol packet options.
@@ -122,4 +124,30 @@
         }
         return true;
     }
+
+    /**
+     * Deserializer function for router solicitation packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<RouterSolicitation> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, HEADER_LENGTH);
+
+            RouterSolicitation routerSolicitation = new RouterSolicitation();
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+            bb.getInt();
+
+            NeighborDiscoveryOptions options = NeighborDiscoveryOptions.deserializer()
+                    .deserialize(data, bb.position(), bb.limit() - bb.position());
+
+            for (NeighborDiscoveryOptions.Option option : options.options()) {
+                routerSolicitation.addOption(option.type(), option.data());
+            }
+
+            return routerSolicitation;
+        };
+    }
 }
diff --git a/utils/misc/src/test/java/org/onlab/packet/ArpTest.java b/utils/misc/src/test/java/org/onlab/packet/ArpTest.java
new file mode 100644
index 0000000..6224624
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/ArpTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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 java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for the ARP class.
+ */
+public class ArpTest {
+
+    private Deserializer<ARP> deserializer = ARP.deserializer();
+
+    private final byte hwAddressLength = 6;
+    private final byte protoAddressLength = 4;
+
+    private MacAddress srcMac = MacAddress.valueOf(1);
+    private MacAddress targetMac = MacAddress.valueOf(2);
+    private Ip4Address srcIp = Ip4Address.valueOf(1);
+    private Ip4Address targetIp = Ip4Address.valueOf(2);
+
+    private byte[] byteHeader;
+
+    @Before
+    public void setUp() {
+        ByteBuffer bb = ByteBuffer.allocate(ARP.INITIAL_HEADER_LENGTH +
+                        2 * hwAddressLength + 2 * protoAddressLength);
+        bb.putShort(ARP.HW_TYPE_ETHERNET);
+        bb.putShort(ARP.PROTO_TYPE_IP);
+        bb.put(hwAddressLength);
+        bb.put(protoAddressLength);
+        bb.putShort(ARP.OP_REPLY);
+
+        bb.put(srcMac.toBytes());
+        bb.put(srcIp.toOctets());
+        bb.put(targetMac.toBytes());
+        bb.put(targetIp.toOctets());
+
+        byteHeader = bb.array();
+    }
+
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(deserializer);
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        PacketTestUtils.testDeserializeTruncated(deserializer, byteHeader);
+    }
+
+    @Test
+    public void testDeserialize() throws Exception {
+        ARP arp = deserializer.deserialize(byteHeader, 0, byteHeader.length);
+
+        assertEquals(ARP.HW_TYPE_ETHERNET, arp.getHardwareType());
+        assertEquals(ARP.PROTO_TYPE_IP, arp.getProtocolType());
+        assertEquals(hwAddressLength, arp.getHardwareAddressLength());
+        assertEquals(protoAddressLength, arp.getProtocolAddressLength());
+        assertEquals(ARP.OP_REPLY, arp.getOpCode());
+
+        assertTrue(Arrays.equals(srcMac.toBytes(), arp.getSenderHardwareAddress()));
+        assertTrue(Arrays.equals(srcIp.toOctets(), arp.getSenderProtocolAddress()));
+        assertTrue(Arrays.equals(targetMac.toBytes(), arp.getTargetHardwareAddress()));
+        assertTrue(Arrays.equals(targetIp.toOctets(), arp.getTargetProtocolAddress()));
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/DhcpTest.java b/utils/misc/src/test/java/org/onlab/packet/DhcpTest.java
new file mode 100644
index 0000000..ff48331
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/DhcpTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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 com.google.common.base.Charsets;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for DHCP class.
+ */
+public class DhcpTest {
+
+    private Deserializer<DHCP> deserializer = DHCP.deserializer();
+
+    private byte opCode = 1;
+    private byte hardwareType = 1;
+    private byte hardwareAddressLength = Ethernet.DATALAYER_ADDRESS_LENGTH;
+    private byte hops = 0;
+    private int transactionId = 0x2ed4eb50;
+    private short seconds = 0;
+    private short flags = 0;
+    private int clientIpAddress = 1;
+    private int yourIpAddress = 2;
+    private int serverIpAddress = 3;
+    private int gatewayIpAddress = 4;
+    private byte[] clientHardwareAddress = MacAddress.valueOf(500).toBytes();
+    private String serverName = "test-server";
+    private String bootFileName = "test-file";
+
+    private String hostName = "test-host";
+    private DHCPOption hostNameOption = new DHCPOption();
+
+    private byte[] byteHeader;
+
+    @Before
+    public void setUp() {
+        hostNameOption.setCode((byte) 55);
+        hostNameOption.setLength((byte) hostName.length());
+        hostNameOption.setData(hostName.getBytes(Charsets.US_ASCII));
+
+        // Packet length is the fixed DHCP header plus option length plus an
+        // extra byte to indicate 'end of options'.
+        ByteBuffer bb = ByteBuffer.allocate(DHCP.MIN_HEADER_LENGTH +
+                                                    2 + hostNameOption.getLength()  + 1);
+
+        bb.put(opCode);
+        bb.put(hardwareType);
+        bb.put(hardwareAddressLength);
+        bb.put(hops);
+        bb.putInt(transactionId);
+        bb.putShort(seconds);
+        bb.putShort(flags);
+        bb.putInt(clientIpAddress);
+        bb.putInt(yourIpAddress);
+        bb.putInt(serverIpAddress);
+        bb.putInt(gatewayIpAddress);
+        bb.put(clientHardwareAddress);
+
+        // need 16 bytes of zeros to pad out the client hardware address field
+        bb.put(new byte[16 - hardwareAddressLength]);
+
+        // Put server name and pad out to 64 bytes
+        bb.put(serverName.getBytes(Charsets.US_ASCII));
+        bb.put(new byte[64 - serverName.length()]);
+
+        // Put boot file name and pad out to 128 bytes
+        bb.put(bootFileName.getBytes(Charsets.US_ASCII));
+        bb.put(new byte[128 - bootFileName.length()]);
+
+        // Magic cookie
+        bb.put("DHCP".getBytes(Charsets.US_ASCII));
+
+        bb.put(hostNameOption.getCode());
+        bb.put(hostNameOption.getLength());
+        bb.put(hostNameOption.getData());
+
+        // End of options marker
+        bb.put((byte) (0xff & 255));
+
+        byteHeader = bb.array();
+    }
+
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(deserializer);
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        PacketTestUtils.testDeserializeTruncated(deserializer, byteHeader);
+    }
+
+    @Test
+    public void testDeserialize() throws Exception {
+        DHCP dhcp = deserializer.deserialize(byteHeader, 0, byteHeader.length);
+
+        assertEquals(opCode, dhcp.opCode);
+        assertEquals(hardwareType, dhcp.hardwareType);
+        assertEquals(hardwareAddressLength, dhcp.hardwareAddressLength);
+        assertEquals(hops, dhcp.hops);
+        assertEquals(transactionId, dhcp.transactionId);
+        assertEquals(seconds, dhcp.seconds);
+        assertEquals(flags, dhcp.flags);
+        assertEquals(clientIpAddress, dhcp.clientIPAddress);
+        assertEquals(yourIpAddress, dhcp.yourIPAddress);
+        assertEquals(serverIpAddress, dhcp.serverIPAddress);
+        assertEquals(gatewayIpAddress, dhcp.gatewayIPAddress);
+        assertTrue(Arrays.equals(clientHardwareAddress, dhcp.clientHardwareAddress));
+
+        assertEquals(serverName, dhcp.serverName);
+        assertEquals(bootFileName, dhcp.bootFileName);
+        assertEquals(1, dhcp.options.size());
+        assertEquals(hostNameOption, dhcp.options.get(0));
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java b/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java
new file mode 100644
index 0000000..15a01fc
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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 java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for the Ethernet class.
+ */
+public class EthernetTest {
+
+    private MacAddress dstMac;
+    private MacAddress srcMac;
+    private short ethertype = 6;
+    private short vlan = 5;
+
+    private Deserializer<Ethernet> deserializer;
+
+    private byte[] byteHeader;
+    private byte[] vlanByteHeader;
+
+    @Before
+    public void setUp() {
+        deserializer = Ethernet.deserializer();
+
+        byte[] dstMacBytes = {
+                (byte) 0x88, (byte) 0x88, (byte) 0x88, (byte) 0x88, (byte) 0x88,
+                (byte) 0x88 };
+        dstMac = MacAddress.valueOf(dstMacBytes);
+        byte[] srcMacBytes = {
+                (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa,
+                (byte) 0xaa };
+        srcMac = MacAddress.valueOf(srcMacBytes);
+
+        // Create Ethernet byte array with no VLAN header
+        ByteBuffer bb = ByteBuffer.allocate(Ethernet.ETHERNET_HEADER_LENGTH);
+        bb.put(dstMacBytes);
+        bb.put(srcMacBytes);
+        bb.putShort(ethertype);
+
+        byteHeader = bb.array();
+
+        // Create Ethernet byte array with a VLAN header
+        bb = ByteBuffer.allocate(Ethernet.ETHERNET_HEADER_LENGTH + Ethernet.VLAN_HEADER_LENGTH);
+        bb.put(dstMacBytes);
+        bb.put(srcMacBytes);
+        bb.putShort(Ethernet.TYPE_VLAN);
+        bb.putShort(vlan);
+        bb.putShort(ethertype);
+
+        vlanByteHeader = bb.array();
+    }
+
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(deserializer);
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws DeserializationException {
+        PacketTestUtils.testDeserializeTruncated(deserializer, vlanByteHeader);
+    }
+
+    @Test
+    public void testDeserializeNoVlan() throws Exception {
+        Ethernet eth = deserializer.deserialize(byteHeader, 0, byteHeader.length);
+
+        assertEquals(dstMac, eth.getDestinationMAC());
+        assertEquals(srcMac, eth.getSourceMAC());
+        assertEquals(Ethernet.VLAN_UNTAGGED, eth.getVlanID());
+        assertEquals(ethertype, eth.getEtherType());
+    }
+
+    @Test
+    public void testDeserializeWithVlan() throws Exception {
+        Ethernet eth = deserializer.deserialize(vlanByteHeader, 0, vlanByteHeader.length);
+
+        assertEquals(dstMac, eth.getDestinationMAC());
+        assertEquals(srcMac, eth.getSourceMAC());
+        assertEquals(vlan, eth.getVlanID());
+        assertEquals(ethertype, eth.getEtherType());
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/ICMP6Test.java b/utils/misc/src/test/java/org/onlab/packet/ICMP6Test.java
index 1a6308e..39ddc24 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ICMP6Test.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ICMP6Test.java
@@ -67,13 +67,22 @@
         assertArrayEquals(bytePacket, icmp6.serialize());
     }
 
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(ICMP6.deserializer());
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        PacketTestUtils.testDeserializeTruncated(ICMP6.deserializer(), bytePacket);
+    }
+
     /**
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        ICMP6 icmp6 = new ICMP6();
-        icmp6.deserialize(bytePacket, 0, bytePacket.length);
+    public void testDeserialize() throws Exception {
+        ICMP6 icmp6 = ICMP6.deserializer().deserialize(bytePacket, 0, bytePacket.length);
 
         assertThat(icmp6.getIcmpType(), is(ICMP6.ECHO_REQUEST));
         assertThat(icmp6.getIcmpCode(), is((byte) 0x00));
diff --git a/utils/misc/src/test/java/org/onlab/packet/ICMPTest.java b/utils/misc/src/test/java/org/onlab/packet/ICMPTest.java
new file mode 100644
index 0000000..8514aa5
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/ICMPTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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 java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for the ICMP class.
+ */
+public class ICMPTest {
+
+    private Deserializer<ICMP> deserializer;
+
+    private byte icmpType = ICMP.TYPE_ECHO_REQUEST;
+    private byte icmpCode = 4;
+    private short checksum = 870;
+
+    private byte[] headerBytes;
+
+    @Before
+    public void setUp() throws Exception {
+        deserializer = ICMP.deserializer();
+
+        ByteBuffer bb = ByteBuffer.allocate(ICMP.ICMP_HEADER_LENGTH);
+
+        bb.put(icmpType);
+        bb.put(icmpCode);
+        bb.putShort(checksum);
+
+        headerBytes = bb.array();
+    }
+
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(deserializer);
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        PacketTestUtils.testDeserializeTruncated(deserializer, headerBytes);
+    }
+
+    @Test
+    public void testDeserialize() throws Exception {
+        ICMP icmp = deserializer.deserialize(headerBytes, 0, headerBytes.length);
+
+        assertEquals(icmpType, icmp.getIcmpType());
+        assertEquals(icmpCode, icmp.getIcmpCode());
+        assertEquals(checksum, icmp.getChecksum());
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/IPv4Test.java b/utils/misc/src/test/java/org/onlab/packet/IPv4Test.java
new file mode 100644
index 0000000..1bacf2a
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/IPv4Test.java
@@ -0,0 +1,99 @@
+/*
+ * 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 java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for IPv4 class.
+ */
+public class IPv4Test {
+
+    private Deserializer<IPv4> deserializer;
+
+    private byte version = 4;
+    private byte headerLength = 6;
+    private byte diffServ = 2;
+    private short totalLength = 20;
+    private short identification = 1;
+    private byte flags = 1;
+    private short fragmentOffset = 1;
+    private byte ttl = 60;
+    private byte protocol = 4;
+    private short checksum = 4;
+    private int sourceAddress = 1;
+    private int destinationAddress = 2;
+    private byte[] options = new byte[] {0x1, 0x2, 0x3, 0x4};
+
+    private byte[] headerBytes;
+
+    @Before
+    public void setUp() throws Exception {
+        deserializer = IPv4.deserializer();
+
+        ByteBuffer bb = ByteBuffer.allocate(headerLength * 4);
+
+        bb.put((byte) ((version & 0xf) << 4 | headerLength & 0xf));
+        bb.put(diffServ);
+        bb.putShort(totalLength);
+        bb.putShort(identification);
+        bb.putShort((short) ((flags & 0x7) << 13 | fragmentOffset & 0x1fff));
+        bb.put(ttl);
+        bb.put(protocol);
+        bb.putShort(checksum);
+        bb.putInt(sourceAddress);
+        bb.putInt(destinationAddress);
+        bb.put(options);
+
+        headerBytes = bb.array();
+    }
+
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(deserializer);
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        PacketTestUtils.testDeserializeTruncated(deserializer, headerBytes);
+    }
+
+    @Test
+    public void testDeserialize() throws Exception {
+        IPv4 ipv4 = deserializer.deserialize(headerBytes, 0, headerBytes.length);
+
+        assertEquals(version, ipv4.getVersion());
+        assertEquals(headerLength, ipv4.getHeaderLength());
+        assertEquals(diffServ, ipv4.getDiffServ());
+        assertEquals(totalLength, ipv4.getTotalLength());
+        assertEquals(identification, ipv4.getIdentification());
+        assertEquals(flags, ipv4.getFlags());
+        assertEquals(fragmentOffset, ipv4.getFragmentOffset());
+        assertEquals(ttl, ipv4.getTtl());
+        assertEquals(protocol, ipv4.getProtocol());
+        assertEquals(checksum, ipv4.getChecksum());
+        assertEquals(sourceAddress, ipv4.getSourceAddress());
+        assertEquals(destinationAddress, ipv4.getDestinationAddress());
+        assertTrue(ipv4.isTruncated());
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java b/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java
index 2e12d5b..720a4d2 100644
--- a/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java
+++ b/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java
@@ -18,14 +18,17 @@
 
 package org.onlab.packet;
 
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.nio.ByteBuffer;
+
 import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 /**
  * Tests for class {@link IPv6}.
@@ -43,6 +46,8 @@
     private static UDP udp;
     private static byte[] bytePacket;
 
+    private Deserializer<IPv6> deserializer;
+
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
         data = new Data();
@@ -65,6 +70,11 @@
         System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length);
     }
 
+    @Before
+    public void setUp() {
+        deserializer = IPv6.deserializer();
+    }
+
     /**
      * Tests serialize and setters.
      */
@@ -83,13 +93,26 @@
         assertArrayEquals(ipv6.serialize(), bytePacket);
     }
 
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(deserializer);
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        // Run the truncation test only on the IPv6 header
+        byte[] ipv6Header = new byte[IPv6.FIXED_HEADER_LENGTH];
+        ByteBuffer.wrap(bytePacket).get(ipv6Header);
+
+        PacketTestUtils.testDeserializeTruncated(deserializer, ipv6Header);
+    }
+
     /**
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        IPv6 ipv6 = new IPv6();
-        ipv6.deserialize(bytePacket, 0, bytePacket.length);
+    public void testDeserialize() throws DeserializationException {
+        IPv6 ipv6 = deserializer.deserialize(bytePacket, 0, bytePacket.length);
 
         assertThat(ipv6.getVersion(), is((byte) 6));
         assertThat(ipv6.getTrafficClass(), is((byte) 0x93));
diff --git a/utils/misc/src/test/java/org/onlab/packet/LLCTest.java b/utils/misc/src/test/java/org/onlab/packet/LLCTest.java
new file mode 100644
index 0000000..39bb72f
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/LLCTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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 java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for LLC class.
+ */
+public class LLCTest {
+
+    private Deserializer<LLC> deserializer;
+
+    private byte dsap = 10;
+    private byte ssap = 20;
+    private byte ctrl = 30;
+
+    private byte[] bytes;
+
+    @Before
+    public void setUp() throws Exception {
+        deserializer = LLC.deserializer();
+
+        ByteBuffer bb = ByteBuffer.allocate(LLC.LLC_HEADER_LENGTH);
+
+        bb.put(dsap);
+        bb.put(ssap);
+        bb.put(ctrl);
+
+        bytes = bb.array();
+    }
+
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(deserializer);
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        PacketTestUtils.testDeserializeTruncated(deserializer, bytes);
+    }
+
+    @Test
+    public void testDeserialize() throws Exception {
+        LLC llc = deserializer.deserialize(bytes, 0, bytes.length);
+
+        assertEquals(dsap, llc.getDsap());
+        assertEquals(ssap, llc.getSsap());
+        assertEquals(ctrl, llc.getCtrl());
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/LLDPTest.java b/utils/misc/src/test/java/org/onlab/packet/LLDPTest.java
new file mode 100644
index 0000000..95b0b04
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/LLDPTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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 java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for the LLDP class.
+ */
+public class LLDPTest {
+
+    private Deserializer<LLDP> deserializer;
+
+    private byte[] chassisValue = new byte[] {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7};
+    private byte[] portValue = new byte[] {0x1, 0x2, 0x3, 0x4, 0x5};
+    private byte[] ttlValue = new byte[] {0x0, 0x20};
+
+    private short optionalTlvSize = 6;
+    private byte[] optionalTlvValue = new byte[] {0x6, 0x5, 0x4, 0x3, 0x2, 0x1};
+
+    private byte[] bytes;
+
+    @Before
+    public void setUp() throws Exception {
+        deserializer = LLDP.deserializer();
+
+        // Each TLV is 2 bytes for the type+length, plus the size of the value
+        // There are 2 zero-bytes at the end
+        ByteBuffer bb = ByteBuffer.allocate(2 + LLDP.CHASSIS_TLV_SIZE +
+                                            2 + LLDP.PORT_TLV_SIZE +
+                                            2 + LLDP.TTL_TLV_SIZE +
+                                            2 + optionalTlvSize +
+                                            2);
+
+        // Chassis TLV
+        bb.putShort(getTypeLength(LLDP.CHASSIS_TLV_TYPE, LLDP.CHASSIS_TLV_SIZE));
+        bb.put(chassisValue);
+
+        // Port TLV
+        bb.putShort(getTypeLength(LLDP.PORT_TLV_TYPE, LLDP.PORT_TLV_SIZE));
+        bb.put(portValue);
+
+        // TTL TLV
+        bb.putShort(getTypeLength(LLDP.TTL_TLV_TYPE, LLDP.TTL_TLV_SIZE));
+        bb.put(ttlValue);
+
+        // Optional TLV
+        bb.putShort(getTypeLength(LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE, optionalTlvSize));
+        bb.put(optionalTlvValue);
+
+        bb.putShort((short) 0);
+
+        bytes = bb.array();
+
+    }
+
+    private short getTypeLength(byte type, short length) {
+        return (short) ((0x7f & type) << 9 | 0x1ff & length);
+    }
+
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(deserializer);
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        PacketTestUtils.testDeserializeTruncated(deserializer, bytes);
+    }
+
+    @Test
+    public void testDeserialize() throws Exception {
+        LLDP lldp = deserializer.deserialize(bytes, 0, bytes.length);
+
+        assertEquals(LLDP.CHASSIS_TLV_TYPE, lldp.getChassisId().getType());
+        assertEquals(LLDP.CHASSIS_TLV_SIZE, lldp.getChassisId().getLength());
+        assertTrue(Arrays.equals(chassisValue, lldp.getChassisId().getValue()));
+
+        assertEquals(LLDP.PORT_TLV_TYPE, lldp.getPortId().getType());
+        assertEquals(LLDP.PORT_TLV_SIZE, lldp.getPortId().getLength());
+        assertTrue(Arrays.equals(portValue, lldp.getPortId().getValue()));
+
+        assertEquals(LLDP.TTL_TLV_TYPE, lldp.getTtl().getType());
+        assertEquals(LLDP.TTL_TLV_SIZE, lldp.getTtl().getLength());
+        assertTrue(Arrays.equals(ttlValue, lldp.getTtl().getValue()));
+
+        assertEquals(1, lldp.getOptionalTLVList().size());
+        LLDPTLV optionalTlv = lldp.getOptionalTLVList().get(0);
+
+        assertEquals(LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE, optionalTlv.getType());
+        assertEquals(optionalTlvSize, optionalTlv.getLength());
+        assertTrue(Arrays.equals(optionalTlvValue, optionalTlv.getValue()));
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/MplsTest.java b/utils/misc/src/test/java/org/onlab/packet/MplsTest.java
new file mode 100644
index 0000000..2ab8ff9
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/MplsTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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 java.nio.ByteBuffer;
+import java.util.HashMap;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for MPLS class.
+ */
+public class MplsTest {
+
+    private Deserializer<MPLS> deserializer;
+
+    private int label = 1048575;
+    private byte bos = 1;
+    private byte ttl = 20;
+    private byte protocol = MPLS.PROTOCOL_IPV4;
+
+    private byte[] bytes;
+
+    @Before
+    public void setUp() throws Exception {
+        // Replace normal deserializer map with an empty map. This will cause
+        // the DataDeserializer to be used which will silently handle 0-byte input.
+        MPLS.protocolDeserializerMap = new HashMap<>();
+
+        deserializer = MPLS.deserializer();
+
+        ByteBuffer bb = ByteBuffer.allocate(MPLS.HEADER_LENGTH);
+        bb.putInt(((label & 0x000fffff) << 12) | ((bos & 0x1) << 8 | (ttl & 0xff)));
+
+        bytes = bb.array();
+    }
+
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(deserializer);
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        PacketTestUtils.testDeserializeTruncated(deserializer, bytes);
+    }
+
+    @Test
+    public void testDeserialize() throws Exception {
+        MPLS mpls = deserializer.deserialize(bytes, 0, bytes.length);
+
+        assertEquals(label, mpls.label);
+        assertEquals(bos, mpls.bos);
+        assertEquals(ttl, mpls.ttl);
+        assertEquals(protocol, mpls.protocol);
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/PacketTestUtils.java b/utils/misc/src/test/java/org/onlab/packet/PacketTestUtils.java
new file mode 100644
index 0000000..209b1d2
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/PacketTestUtils.java
@@ -0,0 +1,98 @@
+/*
+ * 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 java.nio.ByteBuffer;
+
+import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Utilities for testing packet methods.
+ */
+public final class PacketTestUtils {
+
+    private PacketTestUtils() {
+    }
+
+    /**
+     * Tests that the Deserializer function is resilient to bad input parameters
+     * such as null input, negative offset and length, etc.
+     *
+     * @param deserializer deserializer function to test
+     */
+    public static void testDeserializeBadInput(Deserializer deserializer) {
+        byte[] bytes = ByteBuffer.allocate(4).array();
+
+        try {
+            deserializer.deserialize(null, 0, 4);
+            fail("NullPointerException was not thrown");
+        } catch (NullPointerException e) {
+            assertTrue(true);
+        } catch (DeserializationException e) {
+            fail("NullPointerException was not thrown");
+        }
+
+        // input byte array length, offset and length don't make sense
+        expectDeserializationException(deserializer, bytes, -1, 0);
+        expectDeserializationException(deserializer, bytes, 0, -1);
+        expectDeserializationException(deserializer, bytes, 0, 5);
+        expectDeserializationException(deserializer, bytes, 2, 3);
+        expectDeserializationException(deserializer, bytes, 5, 0);
+    }
+
+    /**
+     * Tests that the Deserializer function is resilient to truncated input, or
+     * cases where the input byte array does not contain enough bytes to
+     * deserialize the packet.
+     *
+     * @param deserializer deserializer function to test
+     * @param header byte array of a full-size packet
+     */
+    public static void testDeserializeTruncated(Deserializer deserializer,
+                                                byte[] header) {
+        byte[] truncated;
+
+        for (int i = 0; i < header.length; i++) {
+            truncated = new byte[i];
+
+            ByteBuffer.wrap(header).get(truncated);
+
+            expectDeserializationException(deserializer, truncated, 0, truncated.length);
+        }
+    }
+
+    /**
+     * Run the given deserializer function against the given inputs and verify
+     * that a DeserializationException is thrown. The the test will fail if a
+     * DeserializationException is not thrown by the deserializer function.
+     *
+     * @param deserializer deserializer function to test
+     * @param bytes input byte array
+     * @param offset input offset
+     * @param length input length
+     */
+    public static void expectDeserializationException(Deserializer deserializer,
+                                                      byte[] bytes, int offset, int length) {
+        try {
+            deserializer.deserialize(bytes, offset, length);
+            fail("DeserializationException was not thrown");
+        } catch (DeserializationException e) {
+            assertTrue(true);
+        }
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/TCPTest.java b/utils/misc/src/test/java/org/onlab/packet/TCPTest.java
index 8bc3212..18f532e 100644
--- a/utils/misc/src/test/java/org/onlab/packet/TCPTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/TCPTest.java
@@ -67,8 +67,12 @@
             (byte) 0x00, (byte) 0x01  // urgent
     };
 
+    private static Deserializer<TCP> deserializer;
+
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
+        deserializer = TCP.deserializer();
+
         ipv4.setSourceAddress(IPv4.toIPv4Address(IPV4_SOURCE_ADDRESS));
         ipv4.setDestinationAddress(IPv4.toIPv4Address(IPV4_DESTINATION_ADDRESS));
         ipv4.setProtocol(IPv4.PROTOCOL_TCP);
@@ -100,13 +104,22 @@
         assertArrayEquals(bytePacketTCP6, tcp.serialize());
     }
 
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(deserializer);
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        PacketTestUtils.testDeserializeTruncated(deserializer, bytePacketTCP4);
+    }
+
     /**
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        TCP tcp = new TCP();
-        tcp.deserialize(bytePacketTCP4, 0, bytePacketTCP4.length);
+    public void testDeserialize() throws Exception {
+        TCP tcp = deserializer.deserialize(bytePacketTCP4, 0, bytePacketTCP4.length);
 
         assertThat(tcp.getSourcePort(), is((short) 0x50));
         assertThat(tcp.getDestinationPort(), is((short) 0x60));
diff --git a/utils/misc/src/test/java/org/onlab/packet/UDPTest.java b/utils/misc/src/test/java/org/onlab/packet/UDPTest.java
index 974fa75..86363fa 100644
--- a/utils/misc/src/test/java/org/onlab/packet/UDPTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/UDPTest.java
@@ -61,8 +61,12 @@
             (byte) 0x02, (byte) 0x2a, // checksum
     };
 
+    private static Deserializer<UDP> deserializer;
+
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
+        deserializer = UDP.deserializer();
+
         ipv4.setSourceAddress(IPv4.toIPv4Address(IPV4_SOURCE_ADDRESS));
         ipv4.setDestinationAddress(IPv4.toIPv4Address(IPV4_DESTINATION_ADDRESS));
         ipv4.setProtocol(IPv4.PROTOCOL_UDP);
@@ -88,13 +92,22 @@
         assertArrayEquals(bytePacketUDP6, udp.serialize());
     }
 
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(deserializer);
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        PacketTestUtils.testDeserializeTruncated(deserializer, bytePacketUDP4);
+    }
+
     /**
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        UDP udp = new UDP();
-        udp.deserialize(bytePacketUDP4, 0, bytePacketUDP4.length);
+    public void testDeserialize() throws Exception {
+        UDP udp = deserializer.deserialize(bytePacketUDP4, 0, bytePacketUDP4.length);
 
         assertThat(udp.getSourcePort(), is((short) 0x50));
         assertThat(udp.getDestinationPort(), is((short) 0x60));
diff --git a/utils/misc/src/test/java/org/onlab/packet/ipv6/AuthenticationTest.java b/utils/misc/src/test/java/org/onlab/packet/ipv6/AuthenticationTest.java
index a6edc5d..39ab910 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ipv6/AuthenticationTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ipv6/AuthenticationTest.java
@@ -16,9 +16,11 @@
 
 package org.onlab.packet.ipv6;
 
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.onlab.packet.Data;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.UDP;
 
 import static org.hamcrest.Matchers.is;
@@ -38,6 +40,8 @@
     };
     private static byte[] bytePacket;
 
+    private Deserializer<Authentication> deserializer;
+
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
         data = new Data();
@@ -57,6 +61,11 @@
         System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length);
     }
 
+    @Before
+    public void setUp() {
+        deserializer = Authentication.deserializer();
+    }
+
     /**
      * Tests serialize and setters.
      */
@@ -77,9 +86,8 @@
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        Authentication auth = new Authentication();
-        auth.deserialize(bytePacket, 0, bytePacket.length);
+    public void testDeserialize() throws Exception {
+        Authentication auth = deserializer.deserialize(bytePacket, 0, bytePacket.length);
 
         assertThat(auth.getNextHeader(), is((byte) 0x11));
         assertThat(auth.getPayloadLength(), is((byte) 0x02));
diff --git a/utils/misc/src/test/java/org/onlab/packet/ipv6/BaseOptionsTest.java b/utils/misc/src/test/java/org/onlab/packet/ipv6/BaseOptionsTest.java
index 885b4af..bb91e9e 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ipv6/BaseOptionsTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ipv6/BaseOptionsTest.java
@@ -16,9 +16,11 @@
 
 package org.onlab.packet.ipv6;
 
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.onlab.packet.Data;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPv6;
 import org.onlab.packet.UDP;
 
@@ -40,6 +42,8 @@
     };
     private static byte[] bytePacket;
 
+    private Deserializer<BaseOptions> deserializer;
+
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
         data = new Data();
@@ -57,6 +61,11 @@
         System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length);
     }
 
+    @Before
+    public void setUp() {
+        deserializer = BaseOptions.deserializer();
+    }
+
     /**
      * Tests serialize and setters.
      */
@@ -75,9 +84,8 @@
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        BaseOptions baseopt = new BaseOptions();
-        baseopt.deserialize(bytePacket, 0, bytePacket.length);
+    public void testDeserialize() throws Exception {
+        BaseOptions baseopt = deserializer.deserialize(bytePacket, 0, bytePacket.length);
 
         assertThat(baseopt.getNextHeader(), is((byte) 0x11));
         assertThat(baseopt.getHeaderExtLength(), is((byte) 0x00));
diff --git a/utils/misc/src/test/java/org/onlab/packet/ipv6/EncapSecurityPayloadTest.java b/utils/misc/src/test/java/org/onlab/packet/ipv6/EncapSecurityPayloadTest.java
index 294dffb..e0e9919 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ipv6/EncapSecurityPayloadTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ipv6/EncapSecurityPayloadTest.java
@@ -16,9 +16,13 @@
 
 package org.onlab.packet.ipv6;
 
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.onlab.packet.Data;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+
 import java.util.Arrays;
 
 import static org.hamcrest.Matchers.is;
@@ -35,6 +39,8 @@
     private static byte[] dataByte = new byte[32];
     private static byte[] bytePacket;
 
+    private Deserializer<EncapSecurityPayload> deserializer;
+
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
         Arrays.fill(dataByte, (byte) 0xff);
@@ -50,6 +56,11 @@
         System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length);
     }
 
+    @Before
+    public void setUp() {
+        deserializer = EncapSecurityPayload.deserializer();
+    }
+
     /**
      * Tests serialize and setters.
      */
@@ -67,9 +78,8 @@
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        EncapSecurityPayload esp = new EncapSecurityPayload();
-        esp.deserialize(bytePacket, 0, bytePacket.length);
+    public void testDeserialize() throws DeserializationException {
+        EncapSecurityPayload esp = deserializer.deserialize(bytePacket, 0, bytePacket.length);
 
         assertThat(esp.getSecurityParamIndex(), is(0x13572468));
         assertThat(esp.getSequence(), is(0xffff00));
diff --git a/utils/misc/src/test/java/org/onlab/packet/ipv6/FragmentTest.java b/utils/misc/src/test/java/org/onlab/packet/ipv6/FragmentTest.java
index a7c2492..f2d5e48 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ipv6/FragmentTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ipv6/FragmentTest.java
@@ -16,9 +16,12 @@
 
 package org.onlab.packet.ipv6;
 
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.onlab.packet.Data;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.UDP;
 
 import static org.hamcrest.Matchers.is;
@@ -35,6 +38,8 @@
     private static UDP udp;
     private static byte[] bytePacket;
 
+    private Deserializer<Fragment> deserializer;
+
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
         data = new Data();
@@ -52,6 +57,11 @@
         System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length);
     }
 
+    @Before
+    public void setUp() {
+        deserializer = Fragment.deserializer();
+    }
+
     /**
      * Tests serialize and setters.
      */
@@ -71,9 +81,8 @@
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        Fragment frag = new Fragment();
-        frag.deserialize(bytePacket, 0, bytePacket.length);
+    public void testDeserialize() throws DeserializationException {
+        Fragment frag = deserializer.deserialize(bytePacket, 0, bytePacket.length);
 
         assertThat(frag.getNextHeader(), is((byte) 0x11));
         assertThat(frag.getFragmentOffset(), is((short) 0x1f));
diff --git a/utils/misc/src/test/java/org/onlab/packet/ipv6/RoutingTest.java b/utils/misc/src/test/java/org/onlab/packet/ipv6/RoutingTest.java
index 2dc71c8..a03bacc 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ipv6/RoutingTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ipv6/RoutingTest.java
@@ -16,9 +16,12 @@
 
 package org.onlab.packet.ipv6;
 
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.onlab.packet.Data;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.UDP;
 
 import static org.hamcrest.Matchers.is;
@@ -42,6 +45,8 @@
     };
     private static byte[] bytePacket;
 
+    private Deserializer<Routing> deserializer;
+
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
         data = new Data();
@@ -63,6 +68,11 @@
         System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length);
     }
 
+    @Before
+    public void setUp() {
+        deserializer = Routing.deserializer();
+    }
+
     /**
      * Tests serialize and setters.
      */
@@ -83,9 +93,8 @@
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        Routing routing = new Routing();
-        routing.deserialize(bytePacket, 0, bytePacket.length);
+    public void testDeserialize() throws DeserializationException {
+        Routing routing = deserializer.deserialize(bytePacket, 0, bytePacket.length);
 
         assertThat(routing.getNextHeader(), is((byte) 0x11));
         assertThat(routing.getHeaderExtLength(), is((byte) 0x02));
diff --git a/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java b/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java
index 0c95c6f..303db07 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java
@@ -17,6 +17,8 @@
 
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.MacAddress;
 
 import static org.hamcrest.Matchers.is;
@@ -40,6 +42,9 @@
 
     private static byte[] bytePacket;
 
+    private Deserializer<NeighborAdvertisement> deserializer
+            = NeighborAdvertisement.deserializer();
+
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
         byte[] byteHeader = {
@@ -75,9 +80,8 @@
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        NeighborAdvertisement na = new NeighborAdvertisement();
-        na.deserialize(bytePacket, 0, bytePacket.length);
+    public void testDeserialize() throws DeserializationException {
+        NeighborAdvertisement na = deserializer.deserialize(bytePacket, 0, bytePacket.length);
 
         assertThat(na.getRouterFlag(), is((byte) 1));
         assertThat(na.getSolicitedFlag(), is((byte) 1));
diff --git a/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java b/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java
index 09e0117..2571b7a 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java
@@ -17,6 +17,8 @@
 
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.MacAddress;
 
 import static org.hamcrest.Matchers.is;
@@ -46,6 +48,9 @@
 
     private static byte[] bytePacket;
 
+    private Deserializer<NeighborSolicitation> deserializer
+            = NeighborSolicitation.deserializer();
+
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
         byte[] byteHeader = {
@@ -78,9 +83,8 @@
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        NeighborSolicitation ns = new NeighborSolicitation();
-        ns.deserialize(bytePacket, 0, bytePacket.length);
+    public void testDeserialize() throws DeserializationException {
+        NeighborSolicitation ns = deserializer.deserialize(bytePacket, 0, bytePacket.length);
 
         assertArrayEquals(ns.getTargetAddress(), TARGET_ADDRESS);
 
diff --git a/utils/misc/src/test/java/org/onlab/packet/ndp/RedirectTest.java b/utils/misc/src/test/java/org/onlab/packet/ndp/RedirectTest.java
index 865f03a..7d0d56c 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ndp/RedirectTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ndp/RedirectTest.java
@@ -17,6 +17,8 @@
 
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.MacAddress;
 
 import static org.hamcrest.Matchers.is;
@@ -52,6 +54,8 @@
 
     private static byte[] bytePacket;
 
+    private Deserializer<Redirect> deserializer = Redirect.deserializer();
+
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
         byte[] byteHeader = {
@@ -89,9 +93,8 @@
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        Redirect rd = new Redirect();
-        rd.deserialize(bytePacket, 0, bytePacket.length);
+    public void testDeserialize() throws DeserializationException {
+        Redirect rd = deserializer.deserialize(bytePacket, 0, bytePacket.length);
 
         assertArrayEquals(rd.getTargetAddress(), TARGET_ADDRESS);
         assertArrayEquals(rd.getDestinationAddress(), DESTINATION_ADDRESS);
diff --git a/utils/misc/src/test/java/org/onlab/packet/ndp/RouterAdvertisementTest.java b/utils/misc/src/test/java/org/onlab/packet/ndp/RouterAdvertisementTest.java
index b69d142..d65dd51 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ndp/RouterAdvertisementTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ndp/RouterAdvertisementTest.java
@@ -17,6 +17,8 @@
 
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.MacAddress;
 
 import static org.hamcrest.Matchers.is;
@@ -34,6 +36,9 @@
 
     private static byte[] bytePacket;
 
+    private Deserializer<RouterAdvertisement> deserializer
+            = RouterAdvertisement.deserializer();
+
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
         byte[] byteHeader = {
@@ -69,9 +74,8 @@
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        RouterAdvertisement ra = new RouterAdvertisement();
-        ra.deserialize(bytePacket, 0, bytePacket.length);
+    public void testDeserialize() throws DeserializationException {
+        RouterAdvertisement ra = deserializer.deserialize(bytePacket, 0, bytePacket.length);
 
         assertThat(ra.getCurrentHopLimit(), is((byte) 3));
         assertThat(ra.getMFlag(), is((byte) 1));
diff --git a/utils/misc/src/test/java/org/onlab/packet/ndp/RouterSolicitationTest.java b/utils/misc/src/test/java/org/onlab/packet/ndp/RouterSolicitationTest.java
index 9c087e3..173baaa 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ndp/RouterSolicitationTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ndp/RouterSolicitationTest.java
@@ -17,7 +17,9 @@
 
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.onlab.packet.Deserializer;
 import org.onlab.packet.MacAddress;
+import org.onlab.packet.PacketTestUtils;
 
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertArrayEquals;
@@ -36,6 +38,9 @@
 
     private static byte[] bytePacket;
 
+    private Deserializer<RouterSolicitation> deserializer
+            = RouterSolicitation.deserializer();
+
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
         byte[] byteHeader = {
@@ -59,13 +64,22 @@
         assertArrayEquals(rs.serialize(), bytePacket);
     }
 
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(RouterSolicitation.deserializer());
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        PacketTestUtils.testDeserializeTruncated(RouterSolicitation.deserializer(), bytePacket);
+    }
+
     /**
      * Tests deserialize and getters.
      */
     @Test
-    public void testDeserialize() {
-        RouterSolicitation rs = new RouterSolicitation();
-        rs.deserialize(bytePacket, 0, bytePacket.length);
+    public void testDeserialize() throws Exception {
+        RouterSolicitation rs = deserializer.deserialize(bytePacket, 0, bytePacket.length);
 
         // Check the option(s)
         assertThat(rs.getOptions().size(), is(1));