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/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;
+        };
+    }
 }