ONOS-512: Implement IPv6 Extension Headers

* Create IExtensionHeader interface
    - setNextHeader, getNextHeader interface for all extension header classes
        - Except EncapSecurityPayload, in which the nextHeader field is encrypted

* Create BaseOptions class
    - Super class of HopByHopOptions and DestinationOptions, since these two are very similar

* Implement following classes with unit test
    - HopByHopOptions
    - DestinationOptions
    - Fragment
    - Routing
    - Authentication
    - EncapSecurityPayload

Change-Id: If65894eccf20ac90f04bc2b0cb42aac6dd5a9674
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 53a1ace..16c6687 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IPv6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IPv6.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Open Networking Laboratory
+ * Copyright 2014-2015 Open Networking Laboratory
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,6 +18,13 @@
 
 package org.onlab.packet;
 
+import org.onlab.packet.ipv6.Authentication;
+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.Routing;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -26,13 +33,20 @@
 /**
  * Implements IPv6 packet format. (RFC 2460)
  */
-public class IPv6 extends BasePacket {
+public class IPv6 extends BasePacket implements IExtensionHeader {
     public static final byte FIXED_HEADER_LENGTH = 40; // bytes
 
-    // TODO: Implement extension header.
     public static final byte PROTOCOL_TCP = 0x6;
     public static final byte PROTOCOL_UDP = 0x11;
     public static final byte PROTOCOL_ICMP6 = 0x3A;
+    public static final byte PROTOCOL_HOPOPT = 0x00;
+    public static final byte PROTOCOL_ROUTING = 0x2B;
+    public static final byte PROTOCOL_FRAG = 0x2C;
+    public static final byte PROTOCOL_ESP = 0x32;
+    public static final byte PROTOCOL_AH = 0x33;
+    public static final byte PROTOCOL_DSTOPT = 0x3C;
+
+
     public static final Map<Byte, Class<? extends IPacket>> PROTOCOL_CLASS_MAP =
             new HashMap<>();
 
@@ -40,6 +54,12 @@
         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);
     }
 
     protected byte version;
@@ -119,21 +139,12 @@
         return this;
     }
 
-    /**
-     * Gets next header.
-     *
-     * @return the next header
-     */
+    @Override
     public byte getNextHeader() {
         return this.nextHeader;
     }
 
-    /**
-     * Sets next header.
-     *
-     * @param nextHeader the next header to set
-     * @return this
-     */
+    @Override
     public IPv6 setNextHeader(final byte nextHeader) {
         this.nextHeader = nextHeader;
         return this;
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
new file mode 100644
index 0000000..d4c741f
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/Authentication.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.Data;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv6;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * Implements IPv6 authentication extension header format. (RFC 4302)
+ */
+public class Authentication extends BasePacket implements IExtensionHeader {
+    public static final byte FIXED_HEADER_LENGTH = 12; // bytes
+    public static final byte LENGTH_UNIT = 4; // bytes per unit
+    public static final byte MINUS = 2;
+
+    protected byte nextHeader;
+    protected byte payloadLength;
+    protected int securityParamIndex;
+    protected int sequence;
+    protected byte[] integrityCheck;
+
+    @Override
+    public byte getNextHeader() {
+        return this.nextHeader;
+    }
+
+    @Override
+    public Authentication setNextHeader(final byte nextHeader) {
+        this.nextHeader = nextHeader;
+        return this;
+    }
+
+    /**
+     * Gets the payload length of this header.
+     *
+     * @return the payload length
+     */
+    public byte getPayloadLength() {
+        return this.payloadLength;
+    }
+
+    /**
+     * Sets the payload length of this header.
+     *
+     * @param payloadLength the payload length to set
+     * @return this
+     */
+    public Authentication setPayloadLength(final byte payloadLength) {
+        this.payloadLength = payloadLength;
+        return this;
+    }
+
+    /**
+     * Gets the security parameter index of this header.
+     *
+     * @return the security parameter index
+     */
+    public int getSecurityParamIndex() {
+        return this.securityParamIndex;
+    }
+
+    /**
+     * Sets the security parameter index of this header.
+     *
+     * @param securityParamIndex the security parameter index to set
+     * @return this
+     */
+    public Authentication setSecurityParamIndex(final int securityParamIndex) {
+        this.securityParamIndex = securityParamIndex;
+        return this;
+    }
+
+    /**
+     * Gets the sequence number of this header.
+     *
+     * @return the sequence number
+     */
+    public int getSequence() {
+        return this.sequence;
+    }
+
+    /**
+     * Sets the sequence number of this header.
+     *
+     * @param sequence the sequence number to set
+     * @return this
+     */
+    public Authentication setSequence(final int sequence) {
+        this.sequence = sequence;
+        return this;
+    }
+
+    /**
+     * Gets the integrity check value of this header.
+     *
+     * @return the integrity check value
+     */
+    public byte[] getIntegrityCheck() {
+        return this.integrityCheck;
+    }
+
+    /**
+     * Sets the integrity check value of this header.
+     *
+     * @param integrityCheck the integrity check value to set
+     * @return this
+     */
+    public Authentication setIngegrityCheck(final byte[] integrityCheck) {
+        this.integrityCheck =
+                Arrays.copyOfRange(integrityCheck, 0, integrityCheck.length);
+        return this;
+    }
+
+    /**
+     * Gets the total length of this header.
+     * According to spec, payload length should be the total length of this AH
+     * in 4-octet unit, minus 2
+     *
+     * @return the total length
+     */
+    public int getTotalLength() {
+        return (this.payloadLength + MINUS) * LENGTH_UNIT;
+    }
+
+    @Override
+    public byte[] serialize() {
+        byte[] payloadData = null;
+        if (this.payload != null) {
+            this.payload.setParent(this);
+            payloadData = this.payload.serialize();
+        }
+
+        int headerLength = FIXED_HEADER_LENGTH + integrityCheck.length;
+        int payloadLength = 0;
+        if (payloadData != null) {
+            payloadLength = payloadData.length;
+        }
+
+        final byte[] data = new byte[headerLength + payloadLength];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.put(this.nextHeader);
+        bb.put(this.payloadLength);
+        bb.putShort((short) 0);
+        bb.putInt(this.securityParamIndex);
+        bb.putInt(this.sequence);
+        bb.put(this.integrityCheck, 0, integrityCheck.length);
+
+        if (payloadData != null) {
+            bb.put(payloadData);
+        }
+
+        if (this.parent != null && this.parent instanceof IExtensionHeader) {
+            ((IExtensionHeader) this.parent).setNextHeader(IPv6.PROTOCOL_AH);
+        }
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.nextHeader = bb.get();
+        this.payloadLength = bb.get();
+        bb.getShort();
+        this.securityParamIndex = bb.getInt();
+        this.sequence = bb.getInt();
+        int icvLength = getTotalLength() - FIXED_HEADER_LENGTH;
+        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);
+            }
+        } else {
+            payload = new Data();
+        }
+        this.payload = payload.deserialize(data, bb.position(),
+                bb.limit() - bb.position());
+        this.payload.setParent(this);
+
+        return this;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 5807;
+        int result = super.hashCode();
+        result = prime * result + this.nextHeader;
+        result = prime * result + this.payloadLength;
+        result = prime * result + this.securityParamIndex;
+        result = prime * result + this.sequence;
+        for (byte b : this.integrityCheck) {
+            result = prime * result + b;
+        }
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof Authentication)) {
+            return false;
+        }
+        final Authentication other = (Authentication) obj;
+        if (this.nextHeader != other.nextHeader) {
+            return false;
+        }
+        if (this.payloadLength != other.payloadLength) {
+            return false;
+        }
+        if (this.securityParamIndex != other.securityParamIndex) {
+            return false;
+        }
+        if (this.sequence != other.sequence) {
+            return false;
+        }
+        if (!Arrays.equals(this.integrityCheck, other.integrityCheck)) {
+            return false;
+        }
+        return true;
+    }
+}
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
new file mode 100644
index 0000000..6fd81cf
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/BaseOptions.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.Data;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv6;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * Base class for hop-by-hop options and destination options.
+ */
+public class BaseOptions extends BasePacket implements IExtensionHeader {
+    public static final byte FIXED_HEADER_LENGTH = 2; // bytes
+    public static final byte FIXED_OPTIONS_LENGTH = 6; // bytes
+    public static final byte LENGTH_UNIT = 8; // bytes per unit
+
+    protected byte nextHeader;
+    protected byte headerExtLength;
+    protected byte[] options;
+    protected byte type;
+
+    @Override
+    public byte getNextHeader() {
+        return this.nextHeader;
+    }
+
+    @Override
+    public BaseOptions setNextHeader(final byte nextHeader) {
+        this.nextHeader = nextHeader;
+        return this;
+    }
+
+    /**
+     * Gets the extension length of this header.
+     *
+     * @return header length
+     */
+    public byte getHeaderExtLength() {
+        return this.headerExtLength;
+    }
+
+    /**
+     * Sets the extension length of this header.
+     *
+     * @param headerExtLength the header length to set
+     * @return this
+     */
+    public BaseOptions setHeaderExtLength(final byte headerExtLength) {
+        this.headerExtLength = headerExtLength;
+        return this;
+    }
+
+    /**
+     * Gets the options.
+     *
+     * @return the options
+     */
+    public byte[] getOptions() {
+        return this.options;
+    }
+
+    /**
+     * Sets the options.
+     *
+     * @param options the options to set
+     * @return this
+     */
+    public BaseOptions setOptions(final byte[] options) {
+        this.options =
+                Arrays.copyOfRange(options, 0, options.length);
+        return this;
+    }
+
+    /**
+     * Gets the type of this option.
+     *
+     * @return the type
+     */
+    protected byte getType() {
+        return this.type;
+    }
+
+    /**
+     * Sets the type of this option.
+     * Must be either IPv6.PROTOCOL_HOPOPT or IPv6.PROTOCOL_DSTOPT
+     *
+     * @param type the type to set
+     * @return this
+     */
+    protected BaseOptions setType(final byte type) {
+        this.type = type;
+        return this;
+    }
+
+    @Override
+    public byte[] serialize() {
+        byte[] payloadData = null;
+        if (this.payload != null) {
+            this.payload.setParent(this);
+            payloadData = this.payload.serialize();
+        }
+
+        int headerLength = FIXED_HEADER_LENGTH + options.length;
+        int payloadLength = 0;
+        if (payloadData != null) {
+            payloadLength = payloadData.length;
+        }
+
+        final byte[] data = new byte[headerLength + payloadLength];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.put(this.nextHeader);
+        bb.put(this.headerExtLength);
+        bb.put(this.options, 0, options.length);
+
+        if (payloadData != null) {
+            bb.put(payloadData);
+        }
+
+        if (this.parent != null && this.parent instanceof IExtensionHeader) {
+            ((IExtensionHeader) this.parent).setNextHeader(this.type);
+        }
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.nextHeader = bb.get();
+        this.headerExtLength = bb.get();
+        int optionLength =
+                FIXED_OPTIONS_LENGTH + LENGTH_UNIT * this.headerExtLength;
+        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);
+            }
+        } else {
+            payload = new Data();
+        }
+        this.payload = payload.deserialize(data, bb.position(),
+                bb.limit() - bb.position());
+        this.payload.setParent(this);
+
+        return this;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 5807;
+        int result = super.hashCode();
+        result = prime * result + this.nextHeader;
+        result = prime * result + this.headerExtLength;
+        for (byte b : this.options) {
+            result = prime * result + b;
+        }
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof BaseOptions)) {
+            return false;
+        }
+        final BaseOptions other = (BaseOptions) obj;
+        if (this.nextHeader != other.nextHeader) {
+            return false;
+        }
+        if (this.headerExtLength != other.headerExtLength) {
+            return false;
+        }
+        if (!Arrays.equals(this.options, other.options)) {
+            return false;
+        }
+        if (this.type != other.type) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/DestinationOptions.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/DestinationOptions.java
new file mode 100644
index 0000000..208bdd7
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/DestinationOptions.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.onlab.packet.IPv6;
+
+/**
+ * Implements IPv6 Destination Options extension header format. (RFC 2460)
+ */
+public class DestinationOptions extends BaseOptions {
+    public DestinationOptions() {
+        super();
+        this.setType(IPv6.PROTOCOL_DSTOPT);
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..ffe552f
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/EncapSecurityPayload.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.Data;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv6;
+import java.nio.ByteBuffer;
+
+/**
+ * Implements IPv6 Encapsulating Security Payload (ESP) extension header format.
+ * (RFC 4303)
+ */
+public class EncapSecurityPayload extends BasePacket {
+    public static final byte HEADER_LENGTH = 8; // bytes
+
+    protected int securityParamIndex;
+    protected int sequence;
+    //
+    // NOTE: The remaining fields including payload data, padding length and
+    //       next header are encrypted and all considered as a payload of ESP.
+    //
+
+    /**
+     * Gets the security parameter index of this header.
+     *
+     * @return the security parameter index
+     */
+    public int getSecurityParamIndex() {
+        return this.securityParamIndex;
+    }
+
+    /**
+     * Sets the security parameter index of this header.
+     *
+     * @param securityParamIndex the security parameter index to set
+     * @return this
+     */
+    public EncapSecurityPayload setSecurityParamIndex(final int securityParamIndex) {
+        this.securityParamIndex = securityParamIndex;
+        return this;
+    }
+
+    /**
+     * Gets the sequence number of this header.
+     *
+     * @return the sequence number
+     */
+    public int getSequence() {
+        return this.sequence;
+    }
+
+    /**
+     * Sets the sequence number of this header.
+     *
+     * @param sequence the sequence number to set
+     * @return this
+     */
+    public EncapSecurityPayload setSequence(final int sequence) {
+        this.sequence = sequence;
+        return this;
+    }
+
+    @Override
+    public byte[] serialize() {
+        byte[] payloadData = null;
+        if (this.payload != null) {
+            this.payload.setParent(this);
+            payloadData = this.payload.serialize();
+        }
+
+        int payloadLength = 0;
+        if (payloadData != null) {
+            payloadLength = payloadData.length;
+        }
+
+        final byte[] data = new byte[HEADER_LENGTH + payloadLength];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.putInt(this.securityParamIndex);
+        bb.putInt(this.sequence);
+
+        if (payloadData != null) {
+            bb.put(payloadData);
+        }
+
+        if (this.parent != null && this.parent instanceof IExtensionHeader) {
+            ((IExtensionHeader) this.parent).setNextHeader(IPv6.PROTOCOL_ESP);
+        }
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.securityParamIndex = bb.getInt();
+        this.sequence = bb.getInt();
+
+        this.payload = new Data();
+        this.payload.deserialize(data, bb.position(),
+                bb.limit() - bb.position());
+        this.payload.setParent(this);
+
+        return this;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 5807;
+        int result = super.hashCode();
+        result = prime * result + this.securityParamIndex;
+        result = prime * result + this.sequence;
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof EncapSecurityPayload)) {
+            return false;
+        }
+        final EncapSecurityPayload other = (EncapSecurityPayload) obj;
+        if (this.securityParamIndex != other.securityParamIndex) {
+            return false;
+        }
+        if (this.sequence != other.sequence) {
+            return false;
+        }
+        return true;
+    }
+}
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
new file mode 100644
index 0000000..5a0d4b4
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/Fragment.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.Data;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv6;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Implements IPv6 fragment extension header format. (RFC 2460)
+ */
+public class Fragment extends BasePacket implements IExtensionHeader {
+    public static final byte HEADER_LENGTH = 8; // bytes
+
+    protected byte nextHeader;
+    protected short fragmentOffset;
+    protected byte moreFragment;
+    protected int identification;
+
+    @Override
+    public byte getNextHeader() {
+        return this.nextHeader;
+    }
+
+    @Override
+    public Fragment setNextHeader(final byte nextHeader) {
+        this.nextHeader = nextHeader;
+        return this;
+    }
+
+    /**
+     * Gets the fragment offset of this header.
+     *
+     * @return fragment offset
+     */
+    public short getFragmentOffset() {
+        return this.fragmentOffset;
+    }
+
+    /**
+     * Sets the fragment offset of this header.
+     *
+     * @param fragmentOffset the fragment offset to set
+     * @return this
+     */
+    public Fragment setFragmentOffset(final short fragmentOffset) {
+        this.fragmentOffset = fragmentOffset;
+        return this;
+    }
+
+    /**
+     * Gets the more fragment flag of this header.
+     *
+     * @return more fragment flag
+     */
+    public byte getMoreFragment() {
+        return this.moreFragment;
+    }
+
+    /**
+     * Sets the more fragment flag of this header.
+     *
+     * @param moreFragment the more fragment flag to set
+     * @return this
+     */
+    public Fragment setMoreFragment(final byte moreFragment) {
+        this.moreFragment = moreFragment;
+        return this;
+    }
+
+    /**
+     * Gets the identification of this header.
+     *
+     * @return identification
+     */
+    public int getIdentification() {
+        return this.identification;
+    }
+
+    /**
+     * Sets the identification of this header.
+     *
+     * @param identification the identification to set
+     * @return this
+     */
+    public Fragment setIdentification(final int identification) {
+        this.identification = identification;
+        return this;
+    }
+
+    @Override
+    public byte[] serialize() {
+        byte[] payloadData = null;
+        if (this.payload != null) {
+            this.payload.setParent(this);
+            payloadData = this.payload.serialize();
+        }
+
+        int payloadLength = 0;
+        if (payloadData != null) {
+            payloadLength = payloadData.length;
+        }
+
+        final byte[] data = new byte[HEADER_LENGTH + payloadLength];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.put(this.nextHeader);
+        bb.put((byte) 0);
+        bb.putShort((short) (
+                (this.fragmentOffset & 0x1fff) << 3 |
+                this.moreFragment & 0x1
+        ));
+        bb.putInt(this.identification);
+
+        if (payloadData != null) {
+            bb.put(payloadData);
+        }
+
+        if (this.parent != null && this.parent instanceof IExtensionHeader) {
+            ((IExtensionHeader) this.parent).setNextHeader(IPv6.PROTOCOL_FRAG);
+        }
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.nextHeader = bb.get();
+        bb.get();
+        short sscratch = bb.getShort();
+        this.fragmentOffset = (short) (sscratch >> 3 & 0x1fff);
+        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);
+            }
+        } else {
+            payload = new Data();
+        }
+        this.payload = payload.deserialize(data, bb.position(),
+                bb.limit() - bb.position());
+        this.payload.setParent(this);
+
+        return this;
+    }
+
+    /*
+    * (non-Javadoc)
+    *
+    * @see java.lang.Object#hashCode()
+    */
+    @Override
+    public int hashCode() {
+        final int prime = 5807;
+        int result = super.hashCode();
+        result = prime * result + this.nextHeader;
+        result = prime * result + this.fragmentOffset;
+        result = prime * result + this.moreFragment;
+        result = prime * result + this.identification;
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof Fragment)) {
+            return false;
+        }
+        final Fragment other = (Fragment) obj;
+        if (this.nextHeader != other.nextHeader) {
+            return false;
+        }
+        if (this.fragmentOffset != other.fragmentOffset) {
+            return false;
+        }
+        if (this.moreFragment != other.moreFragment) {
+            return false;
+        }
+        if (this.identification != other.identification) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/HopByHopOptions.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/HopByHopOptions.java
new file mode 100644
index 0000000..cd8c141
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/HopByHopOptions.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.onlab.packet.IPv6;
+
+/**
+ * Implements IPv6 Hop-by-hop Options extension header format. (RFC 2460)
+ */
+public class HopByHopOptions extends BaseOptions {
+    public HopByHopOptions() {
+        super();
+        this.setType(IPv6.PROTOCOL_HOPOPT);
+    }
+}
\ No newline at end of file
diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/IExtensionHeader.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/IExtensionHeader.java
new file mode 100644
index 0000000..bbe5b7c
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/IExtensionHeader.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+/**
+ * Interface for IPv6 extension header.
+ */
+public interface IExtensionHeader {
+    /**
+     * Gets the type of next header.
+     *
+     * @return next header
+     */
+    public byte getNextHeader();
+
+    /**
+     * Sets the type of next header.
+     *
+     * @param nextHeader the next header to set
+     * @return this
+     */
+    public IExtensionHeader setNextHeader(final byte nextHeader);
+}
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
new file mode 100644
index 0000000..808d2ec
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/Routing.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.Data;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv6;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * Implements IPv6 routing extension header format. (RFC 2460)
+ */
+public class Routing extends BasePacket implements IExtensionHeader {
+    public static final byte FIXED_HEADER_LENGTH = 4; // bytes
+    public static final byte FIXED_ROUTING_DATA_LENGTH = 4; // bytes
+    public static final byte LENGTH_UNIT = 8; // bytes per unit
+
+    protected byte nextHeader;
+    protected byte headerExtLength;
+    protected byte routingType;
+    protected byte segmentsLeft;
+    protected byte[] routingData;
+
+    @Override
+    public byte getNextHeader() {
+        return this.nextHeader;
+    }
+
+    @Override
+    public Routing setNextHeader(final byte nextHeader) {
+        this.nextHeader = nextHeader;
+        return this;
+    }
+
+    /**
+     * Gets the extension length of this header.
+     *
+     * @return header length
+     */
+    public byte getHeaderExtLength() {
+        return this.headerExtLength;
+    }
+
+    /**
+     * Sets the extension length of this header.
+     *
+     * @param headerExtLength the header length to set
+     * @return this
+     */
+    public Routing setHeaderExtLength(final byte headerExtLength) {
+        this.headerExtLength = headerExtLength;
+        return this;
+    }
+
+    /**
+     * Gets the routing type of this header.
+     *
+     * @return routing type
+     */
+    public byte getRoutingType() {
+        return this.routingType;
+    }
+
+    /**
+     * Sets the routing type of this header.
+     *
+     * @param routingType the routing type to set
+     * @return this
+     */
+    public Routing setRoutingType(final byte routingType) {
+        this.routingType = routingType;
+        return this;
+    }
+
+    /**
+     * Gets the number of remaining route segments of this header.
+     *
+     * @return number of remaining route segments
+     */
+    public byte getSegmentsLeft() {
+        return this.segmentsLeft;
+    }
+
+    /**
+     * Sets the number of remaining route segments of this header.
+     *
+     * @param segmentsLeft the number of remaining route segments to set
+     * @return this
+     */
+    public Routing setSegmntsLeft(final byte segmentsLeft) {
+        this.segmentsLeft = segmentsLeft;
+        return this;
+    }
+
+    /**
+     * Gets the routing data.
+     *
+     * @return the routing data
+     */
+    public byte[] getRoutingData() {
+        return this.routingData;
+    }
+
+    /**
+     * Sets the routing data.
+     *
+     * @param routingData the routing data to set
+     * @return this
+     */
+    public Routing setRoutingData(final byte[] routingData) {
+        this.routingData =
+                Arrays.copyOfRange(routingData, 0, routingData.length);
+        return this;
+    }
+
+    @Override
+    public byte[] serialize() {
+        byte[] payloadData = null;
+        if (this.payload != null) {
+            this.payload.setParent(this);
+            payloadData = this.payload.serialize();
+        }
+
+        int headerLength = FIXED_HEADER_LENGTH + routingData.length;
+        int payloadLength = 0;
+        if (payloadData != null) {
+            payloadLength = payloadData.length;
+        }
+
+        final byte[] data = new byte[headerLength + payloadLength];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.put(this.nextHeader);
+        bb.put(this.headerExtLength);
+        bb.put(this.routingType);
+        bb.put(this.segmentsLeft);
+        bb.put(this.routingData, 0, routingData.length);
+
+        if (payloadData != null) {
+            bb.put(payloadData);
+        }
+
+        if (this.parent != null && this.parent instanceof IExtensionHeader) {
+            ((IExtensionHeader) this.parent).setNextHeader(IPv6.PROTOCOL_ROUTING);
+        }
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.nextHeader = bb.get();
+        this.headerExtLength = bb.get();
+        this.routingType = bb.get();
+        this.segmentsLeft = bb.get();
+        int dataLength =
+                FIXED_ROUTING_DATA_LENGTH + LENGTH_UNIT * this.headerExtLength;
+        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);
+            }
+        } else {
+            payload = new Data();
+        }
+        this.payload = payload.deserialize(data, bb.position(),
+                bb.limit() - bb.position());
+        this.payload.setParent(this);
+
+        return this;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 5807;
+        int result = super.hashCode();
+        result = prime * result + this.nextHeader;
+        result = prime * result + this.headerExtLength;
+        result = prime * result + this.routingType;
+        result = prime * result + this.segmentsLeft;
+        for (byte b : this.routingData) {
+            result = prime * result + b;
+        }
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof Routing)) {
+            return false;
+        }
+        final Routing other = (Routing) obj;
+        if (this.nextHeader != other.nextHeader) {
+            return false;
+        }
+        if (this.headerExtLength != other.headerExtLength) {
+            return false;
+        }
+        if (this.routingType != other.routingType) {
+            return false;
+        }
+        if (this.segmentsLeft != other.segmentsLeft) {
+            return false;
+        }
+        if (!Arrays.equals(this.routingData, other.routingData)) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/utils/misc/src/main/java/org/onlab/packet/ipv6/package-info.java b/utils/misc/src/main/java/org/onlab/packet/ipv6/package-info.java
new file mode 100644
index 0000000..e4391d5
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/ipv6/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Utilities for decoding and encoding IPv6 extension headers.
+ */
+package org.onlab.packet.ipv6;
diff --git a/utils/misc/src/test/java/org/onlab/packet/ipv6/AuthenticationTest.java b/utils/misc/src/test/java/org/onlab/packet/ipv6/AuthenticationTest.java
new file mode 100644
index 0000000..a6edc5d
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/ipv6/AuthenticationTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onlab.packet.Data;
+import org.onlab.packet.UDP;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for class {@link Authentication}.
+ */
+public class AuthenticationTest {
+    private static Data data;
+    private static UDP udp;
+    private static byte[] icv = {
+            (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44
+    };
+    private static byte[] bytePacket;
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        data = new Data();
+        data.setData("testSerialize".getBytes());
+        udp = new UDP();
+        udp.setPayload(data);
+
+        byte[] bytePayload = udp.serialize();
+        byte[] byteHeader = {
+                (byte) 0x11, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+                (byte) 0x13, (byte) 0x57, (byte) 0x24, (byte) 0x68,
+                (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0x00,
+                (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44
+        };
+        bytePacket = new byte[byteHeader.length + bytePayload.length];
+        System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length);
+        System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length);
+    }
+
+    /**
+     * Tests serialize and setters.
+     */
+    @Test
+    public void testSerialize() {
+        Authentication auth = new Authentication();
+        auth.setNextHeader((byte) 0x11);
+        auth.setPayloadLength((byte) 0x02);
+        auth.setSecurityParamIndex(0x13572468);
+        auth.setSequence(0xffff00);
+        auth.setIngegrityCheck(icv);
+        auth.setPayload(udp);
+
+        assertArrayEquals(auth.serialize(), bytePacket);
+    }
+
+    /**
+     * Tests deserialize and getters.
+     */
+    @Test
+    public void testDeserialize() {
+        Authentication auth = new Authentication();
+        auth.deserialize(bytePacket, 0, bytePacket.length);
+
+        assertThat(auth.getNextHeader(), is((byte) 0x11));
+        assertThat(auth.getPayloadLength(), is((byte) 0x02));
+        assertThat(auth.getSecurityParamIndex(), is(0x13572468));
+        assertThat(auth.getSequence(), is(0xffff00));
+        assertArrayEquals(auth.getIntegrityCheck(), icv);
+    }
+
+    /**
+     * Tests comparator.
+     */
+    @Test
+    public void testEqual() {
+        Authentication auth1 = new Authentication();
+        auth1.setNextHeader((byte) 0x11);
+        auth1.setPayloadLength((byte) 0x02);
+        auth1.setSecurityParamIndex(0x13572468);
+        auth1.setSequence(0xffff00);
+        auth1.setIngegrityCheck(icv);
+
+        Authentication auth2 = new Authentication();
+        auth2.setNextHeader((byte) 0x11);
+        auth2.setPayloadLength((byte) 0x02);
+        auth2.setSecurityParamIndex(0x13572467);
+        auth2.setSequence(0xffff00);
+        auth2.setIngegrityCheck(icv);
+
+        assertTrue(auth1.equals(auth1));
+        assertFalse(auth1.equals(auth2));
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/ipv6/BaseOptionsTest.java b/utils/misc/src/test/java/org/onlab/packet/ipv6/BaseOptionsTest.java
new file mode 100644
index 0000000..885b4af
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/ipv6/BaseOptionsTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onlab.packet.Data;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.UDP;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for class {@link BaseOptions}.
+ */
+public class BaseOptionsTest {
+    private static Data data;
+    private static UDP udp;
+    private static byte[] options = {
+            (byte) 0x00, (byte) 0x03,
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00
+    };
+    private static byte[] bytePacket;
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        data = new Data();
+        data.setData("testSerialize".getBytes());
+        udp = new UDP();
+        udp.setPayload(data);
+
+        byte[] bytePayload = udp.serialize();
+        byte[] byteHeader = {
+                (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x03,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00
+        };
+        bytePacket = new byte[byteHeader.length + bytePayload.length];
+        System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length);
+        System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length);
+    }
+
+    /**
+     * Tests serialize and setters.
+     */
+    @Test
+    public void testSerialize() {
+        BaseOptions baseopt = new BaseOptions();
+        baseopt.setNextHeader((byte) 0x11);
+        baseopt.setHeaderExtLength((byte) 0x00);
+        baseopt.setOptions(options);
+        baseopt.setPayload(udp);
+
+        assertArrayEquals(baseopt.serialize(), bytePacket);
+    }
+
+    /**
+     * Tests deserialize and getters.
+     */
+    @Test
+    public void testDeserialize() {
+        BaseOptions baseopt = new BaseOptions();
+        baseopt.deserialize(bytePacket, 0, bytePacket.length);
+
+        assertThat(baseopt.getNextHeader(), is((byte) 0x11));
+        assertThat(baseopt.getHeaderExtLength(), is((byte) 0x00));
+        assertArrayEquals(baseopt.getOptions(), options);
+    }
+
+    /**
+     * Tests comparator.
+     */
+    @Test
+    public void testEqual() {
+        BaseOptions baseopt1 = new BaseOptions();
+        baseopt1.setNextHeader((byte) 0x11);
+        baseopt1.setHeaderExtLength((byte) 0x00);
+        baseopt1.setOptions(options);
+        baseopt1.setType(IPv6.PROTOCOL_HOPOPT);
+
+        BaseOptions baseopt2 = new BaseOptions();
+        baseopt2.setNextHeader((byte) 0x11);
+        baseopt2.setHeaderExtLength((byte) 0x00);
+        baseopt2.setOptions(options);
+        baseopt1.setType(IPv6.PROTOCOL_DSTOPT);
+
+        assertTrue(baseopt1.equals(baseopt1));
+        assertFalse(baseopt1.equals(baseopt2));
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/ipv6/DestinationOptionsTest.java b/utils/misc/src/test/java/org/onlab/packet/ipv6/DestinationOptionsTest.java
new file mode 100644
index 0000000..29dd126
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/ipv6/DestinationOptionsTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.junit.Test;
+import org.onlab.packet.IPv6;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Tests for class {@link DestinationOptions}.
+ */
+public class DestinationOptionsTest {
+    /**
+     * Tests constructor.
+     */
+    @Test
+    public void testConstructor() {
+        DestinationOptions dstopt = new DestinationOptions();
+        assertThat(dstopt.getType(), is(IPv6.PROTOCOL_DSTOPT));
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/ipv6/EncapSecurityPayloadTest.java b/utils/misc/src/test/java/org/onlab/packet/ipv6/EncapSecurityPayloadTest.java
new file mode 100644
index 0000000..294dffb
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/ipv6/EncapSecurityPayloadTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onlab.packet.Data;
+import java.util.Arrays;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for class {@link EncapSecurityPayload}.
+ */
+public class EncapSecurityPayloadTest {
+    private static Data data;
+    private static byte[] dataByte = new byte[32];
+    private static byte[] bytePacket;
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        Arrays.fill(dataByte, (byte) 0xff);
+        data = new Data().setData(dataByte);
+
+        byte[] bytePayload = data.serialize();
+        byte[] byteHeader = {
+                (byte) 0x13, (byte) 0x57, (byte) 0x24, (byte) 0x68,
+                (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0x00
+        };
+        bytePacket = new byte[byteHeader.length + bytePayload.length];
+        System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length);
+        System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length);
+    }
+
+    /**
+     * Tests serialize and setters.
+     */
+    @Test
+    public void testSerialize() {
+        EncapSecurityPayload esp = new EncapSecurityPayload();
+        esp.setSecurityParamIndex(0x13572468);
+        esp.setSequence(0xffff00);
+        esp.setPayload(data);
+
+        assertArrayEquals(esp.serialize(), bytePacket);
+    }
+
+    /**
+     * Tests deserialize and getters.
+     */
+    @Test
+    public void testDeserialize() {
+        EncapSecurityPayload esp = new EncapSecurityPayload();
+        esp.deserialize(bytePacket, 0, bytePacket.length);
+
+        assertThat(esp.getSecurityParamIndex(), is(0x13572468));
+        assertThat(esp.getSequence(), is(0xffff00));
+    }
+
+    /**
+     * Tests comparator.
+     */
+    @Test
+    public void testEqual() {
+        EncapSecurityPayload esp1 = new EncapSecurityPayload();
+        esp1.setSecurityParamIndex(0x13572468);
+        esp1.setSequence(0xffff00);
+
+        EncapSecurityPayload esp2 = new EncapSecurityPayload();
+        esp2.setSecurityParamIndex(0x13572468);
+        esp2.setSequence(0xfffff0);
+
+        assertTrue(esp1.equals(esp1));
+        assertFalse(esp1.equals(esp2));
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/ipv6/FragmentTest.java b/utils/misc/src/test/java/org/onlab/packet/ipv6/FragmentTest.java
new file mode 100644
index 0000000..a7c2492
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/ipv6/FragmentTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onlab.packet.Data;
+import org.onlab.packet.UDP;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for class {@link Fragment}.
+ */
+public class FragmentTest {
+    private static Data data;
+    private static UDP udp;
+    private static byte[] bytePacket;
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        data = new Data();
+        data.setData("testSerialize".getBytes());
+        udp = new UDP();
+        udp.setPayload(data);
+
+        byte[] bytePayload = udp.serialize();
+        byte[] byteHeader = {
+                (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0xf9,
+                (byte) 0x00, (byte) 0x00, (byte) 0x13, (byte) 0x57
+        };
+        bytePacket = new byte[byteHeader.length + bytePayload.length];
+        System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length);
+        System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length);
+    }
+
+    /**
+     * Tests serialize and setters.
+     */
+    @Test
+    public void testSerialize() {
+        Fragment frag = new Fragment();
+        frag.setNextHeader((byte) 0x11);
+        frag.setFragmentOffset((short) 0x1f);
+        frag.setMoreFragment((byte) 1);
+        frag.setIdentification(0x1357);
+        frag.setPayload(udp);
+
+        assertArrayEquals(frag.serialize(), bytePacket);
+    }
+
+    /**
+     * Tests deserialize and getters.
+     */
+    @Test
+    public void testDeserialize() {
+        Fragment frag = new Fragment();
+        frag.deserialize(bytePacket, 0, bytePacket.length);
+
+        assertThat(frag.getNextHeader(), is((byte) 0x11));
+        assertThat(frag.getFragmentOffset(), is((short) 0x1f));
+        assertThat(frag.getMoreFragment(), is((byte) 1));
+        assertThat(frag.getIdentification(), is(0x1357));
+    }
+
+    /**
+     * Tests comparator.
+     */
+    @Test
+    public void testEqual() {
+        Fragment frag1 = new Fragment();
+        frag1.setNextHeader((byte) 0x11);
+        frag1.setFragmentOffset((short) 0x1f);
+        frag1.setMoreFragment((byte) 1);
+        frag1.setIdentification(0x1357);
+
+        Fragment frag2 = new Fragment();
+        frag2.setNextHeader((byte) 0x11);
+        frag2.setFragmentOffset((short) 0x1f);
+        frag2.setMoreFragment((byte) 1);
+        frag2.setIdentification(0x1358);
+
+        assertTrue(frag1.equals(frag1));
+        assertFalse(frag1.equals(frag2));
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/ipv6/HopByHopOptionsTest.java b/utils/misc/src/test/java/org/onlab/packet/ipv6/HopByHopOptionsTest.java
new file mode 100644
index 0000000..1e9be2f
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/ipv6/HopByHopOptionsTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.junit.Test;
+import org.onlab.packet.IPv6;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Tests for class {@link HopByHopOptions}.
+ */
+public class HopByHopOptionsTest {
+    /**
+     * Tests constructor.
+     */
+    @Test
+    public void testConstructor() {
+        HopByHopOptions hopopt = new HopByHopOptions();
+        assertThat(hopopt.getType(), is(IPv6.PROTOCOL_HOPOPT));
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/ipv6/RoutingTest.java b/utils/misc/src/test/java/org/onlab/packet/ipv6/RoutingTest.java
new file mode 100644
index 0000000..2dc71c8
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/ipv6/RoutingTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.packet.ipv6;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onlab.packet.Data;
+import org.onlab.packet.UDP;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for class {@link Routing}.
+ */
+public class RoutingTest {
+    private static Data data;
+    private static UDP udp;
+    private static byte[] routingData = {
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18,
+            (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15,
+            (byte) 0xca, (byte) 0x2a, (byte) 0x14, (byte) 0xff,
+            (byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce
+    };
+    private static byte[] bytePacket;
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        data = new Data();
+        data.setData("testSerialize".getBytes());
+        udp = new UDP();
+        udp.setPayload(data);
+
+        byte[] bytePayload = udp.serialize();
+        byte[] byteHeader = {
+                (byte) 0x11, (byte) 0x02, (byte) 0x00, (byte) 0x03,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18,
+                (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15,
+                (byte) 0xca, (byte) 0x2a, (byte) 0x14, (byte) 0xff,
+                (byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce
+        };
+        bytePacket = new byte[byteHeader.length + bytePayload.length];
+        System.arraycopy(byteHeader, 0, bytePacket, 0, byteHeader.length);
+        System.arraycopy(bytePayload, 0, bytePacket, byteHeader.length, bytePayload.length);
+    }
+
+    /**
+     * Tests serialize and setters.
+     */
+    @Test
+    public void testSerialize() {
+        Routing routing = new Routing();
+        routing.setNextHeader((byte) 0x11);
+        routing.setHeaderExtLength((byte) 0x02);
+        routing.setRoutingType((byte) 0x00);
+        routing.setSegmntsLeft((byte) 0x03);
+        routing.setRoutingData(routingData);
+        routing.setPayload(udp);
+
+        assertArrayEquals(routing.serialize(), bytePacket);
+    }
+
+    /**
+     * Tests deserialize and getters.
+     */
+    @Test
+    public void testDeserialize() {
+        Routing routing = new Routing();
+        routing.deserialize(bytePacket, 0, bytePacket.length);
+
+        assertThat(routing.getNextHeader(), is((byte) 0x11));
+        assertThat(routing.getHeaderExtLength(), is((byte) 0x02));
+        assertThat(routing.getRoutingType(), is((byte) 0x00));
+        assertThat(routing.getSegmentsLeft(), is((byte) 0x03));
+        assertArrayEquals(routing.getRoutingData(), routingData);
+    }
+
+    /**
+     * Tests comparator.
+     */
+    @Test
+    public void testEqual() {
+        Routing routing1 = new Routing();
+        routing1.setNextHeader((byte) 0x11);
+        routing1.setHeaderExtLength((byte) 0x02);
+        routing1.setRoutingType((byte) 0x00);
+        routing1.setSegmntsLeft((byte) 0x03);
+        routing1.setRoutingData(routingData);
+
+        Routing routing2 = new Routing();
+        routing2.setNextHeader((byte) 0x11);
+        routing2.setHeaderExtLength((byte) 0x02);
+        routing2.setRoutingType((byte) 0x00);
+        routing2.setSegmntsLeft((byte) 0x02);
+        routing2.setRoutingData(routingData);
+
+        assertTrue(routing1.equals(routing1));
+        assertFalse(routing1.equals(routing2));
+    }
+}