DHCP util refactor

Move options to packet.dhcp package
Deprecated DHCPPacketType, add MsgType to DHCP class

Change-Id: I85ce7fa5e6f3fdc916fbbeba9a4e10e75064a054
diff --git a/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java b/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
index dd21838..7190aea 100644
--- a/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
+++ b/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
@@ -28,8 +28,7 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.packet.ARP;
 import org.onlab.packet.DHCP;
-import org.onlab.packet.DHCPOption;
-import org.onlab.packet.DHCPPacketType;
+import org.onlab.packet.dhcp.DhcpOption;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
@@ -89,9 +88,6 @@
 import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_DHCPServerIp;
 import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
 import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_RequestedIP;
-import static org.onlab.packet.DHCPPacketType.DHCPACK;
-import static org.onlab.packet.DHCPPacketType.DHCPNAK;
-import static org.onlab.packet.DHCPPacketType.DHCPOFFER;
 import static org.onlab.packet.MacAddress.valueOf;
 import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_RangeNotEnforced;
 import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_Requested;
@@ -344,7 +340,7 @@
             dhcpReply.setClientHardwareAddress(dhcpPacket.getClientHardwareAddress());
             dhcpReply.setTransactionId(dhcpPacket.getTransactionId());
 
-            if (outgoingMessageType != DHCPPacketType.DHCPNAK.getValue()) {
+            if (outgoingMessageType != DHCP.MsgType.DHCPNAK.getValue()) {
                 dhcpReply.setYourIPAddress(ipOffered.toInt());
                 dhcpReply.setServerIPAddress(dhcpServerReply.toInt());
                 if (dhcpPacket.getGatewayIPAddress() == 0) {
@@ -355,8 +351,8 @@
             dhcpReply.setHardwareAddressLength((byte) 6);
 
             // DHCP Options.
-            DHCPOption option = new DHCPOption();
-            List<DHCPOption> optionList = new ArrayList<>();
+            DhcpOption option = new DhcpOption();
+            List<DhcpOption> optionList = new ArrayList<>();
 
             // DHCP Message Type.
             option.setCode(OptionCode_MessageType.getValue());
@@ -366,15 +362,15 @@
             optionList.add(option);
 
             // DHCP Server Identifier.
-            option = new DHCPOption();
+            option = new DhcpOption();
             option.setCode(OptionCode_DHCPServerIp.getValue());
             option.setLength((byte) 4);
             option.setData(dhcpServerReply.toOctets());
             optionList.add(option);
 
-            if (outgoingMessageType != DHCPPacketType.DHCPNAK.getValue()) {
+            if (outgoingMessageType != DHCP.MsgType.DHCPNAK.getValue()) {
                 // IP Address Lease Time.
-                option = new DHCPOption();
+                option = new DhcpOption();
                 option.setCode(DHCP.DHCPOptionCode.OptionCode_LeaseTime.getValue());
                 option.setLength((byte) 4);
                 option.setData(ByteBuffer.allocate(4)
@@ -382,28 +378,28 @@
                 optionList.add(option);
 
                 // IP Address Renewal Time.
-                option = new DHCPOption();
+                option = new DhcpOption();
                 option.setCode(DHCP.DHCPOptionCode.OptionCode_RenewalTime.getValue());
                 option.setLength((byte) 4);
                 option.setData(ByteBuffer.allocate(4).putInt(renewalTime).array());
                 optionList.add(option);
 
                 // IP Address Rebinding Time.
-                option = new DHCPOption();
+                option = new DhcpOption();
                 option.setCode(DHCP.DHCPOptionCode.OPtionCode_RebindingTime.getValue());
                 option.setLength((byte) 4);
                 option.setData(ByteBuffer.allocate(4).putInt(rebindingTime).array());
                 optionList.add(option);
 
                 // Subnet Mask.
-                option = new DHCPOption();
+                option = new DhcpOption();
                 option.setCode(DHCP.DHCPOptionCode.OptionCode_SubnetMask.getValue());
                 option.setLength((byte) 4);
                 option.setData(subnetMaskReply.toOctets());
                 optionList.add(option);
 
                 // Broadcast Address.
-                option = new DHCPOption();
+                option = new DhcpOption();
                 option.setCode(DHCP.DHCPOptionCode.OptionCode_BroadcastAddress.getValue());
                 option.setLength((byte) 4);
                 option.setData(broadcastReply.toOctets());
@@ -411,7 +407,7 @@
 
                 // Router Address.
                 if (routerAddressReply.isPresent()) {
-                    option = new DHCPOption();
+                    option = new DhcpOption();
                     option.setCode(DHCP.DHCPOptionCode.OptionCode_RouterAddress.getValue());
                     option.setLength((byte) 4);
                     option.setData(routerAddressReply.get().toOctets());
@@ -420,7 +416,7 @@
 
                 // DNS Server Address.
                 if (domainServerReply.isPresent()) {
-                    option = new DHCPOption();
+                    option = new DhcpOption();
                     option.setCode(DHCP.DHCPOptionCode.OptionCode_DomainServer.getValue());
                     option.setLength((byte) 4);
                     option.setData(domainServerReply.get().toOctets());
@@ -429,7 +425,7 @@
             }
 
             // End Option.
-            option = new DHCPOption();
+            option = new DhcpOption();
             option.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
             option.setLength((byte) 1);
             optionList.add(option);
@@ -472,16 +468,16 @@
             }
 
             Ethernet packet = context.inPacket().parsed();
-            DHCPPacketType incomingPacketType = null;
+            DHCP.MsgType incomingPacketType = null;
             boolean flagIfRequestedIP = false;
             boolean flagIfServerIP = false;
             Ip4Address requestedIP = Ip4Address.valueOf("0.0.0.0");
             Ip4Address serverIP = Ip4Address.valueOf("0.0.0.0");
 
-            for (DHCPOption option : dhcpPayload.getOptions()) {
+            for (DhcpOption option : dhcpPayload.getOptions()) {
                 if (option.getCode() == OptionCode_MessageType.getValue()) {
                     byte[] data = option.getData();
-                    incomingPacketType = DHCPPacketType.getType(data[0]);
+                    incomingPacketType = DHCP.MsgType.getType(data[0]);
                 }
                 if (option.getCode() == OptionCode_RequestedIP.getValue()) {
                     byte[] data = option.getData();
@@ -500,7 +496,7 @@
                 return;
             }
 
-            DHCPPacketType outgoingPacketType;
+            DHCP.MsgType outgoingPacketType;
             MacAddress clientMac = new MacAddress(dhcpPayload.getClientHardwareAddress());
             VlanId vlanId = VlanId.vlanId(packet.getVlanID());
             HostId hostId = HostId.hostId(clientMac, vlanId);
@@ -513,7 +509,7 @@
                         Ethernet ethReply = buildReply(
                                 packet,
                                 ipOffered,
-                                (byte) DHCPOFFER.getValue());
+                                (byte) DHCP.MsgType.DHCPOFFER.getValue());
                         sendReply(context, ethReply);
                     }
                     break;
@@ -536,10 +532,10 @@
                             .assignmentStatus(Option_Requested).build();
 
                     if (dhcpStore.assignIP(hostId, ipAssignment)) {
-                        outgoingPacketType = DHCPACK;
+                        outgoingPacketType = DHCP.MsgType.DHCPACK;
                         discoverHost(context, requestedIP);
                     } else {
-                        outgoingPacketType = DHCPNAK;
+                        outgoingPacketType = DHCP.MsgType.DHCPNAK;
                     }
 
                     Ethernet ethReply = buildReply(packet, requestedIP, (byte) outgoingPacketType.getValue());
diff --git a/apps/dhcp/app/src/test/java/org/onosproject/dhcp/impl/DhcpManagerTest.java b/apps/dhcp/app/src/test/java/org/onosproject/dhcp/impl/DhcpManagerTest.java
index 934e601..104d931 100644
--- a/apps/dhcp/app/src/test/java/org/onosproject/dhcp/impl/DhcpManagerTest.java
+++ b/apps/dhcp/app/src/test/java/org/onosproject/dhcp/impl/DhcpManagerTest.java
@@ -20,8 +20,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.packet.DHCP;
-import org.onlab.packet.DHCPOption;
-import org.onlab.packet.DHCPPacketType;
+import org.onlab.packet.dhcp.DhcpOption;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
@@ -109,7 +108,7 @@
      */
     @Test
     public void testDiscover() {
-        Ethernet reply = constructDhcpPacket(DHCPPacketType.DHCPDISCOVER);
+        Ethernet reply = constructDhcpPacket(DHCP.MsgType.DHCPDISCOVER);
         sendPacket(reply);
     }
 
@@ -118,7 +117,7 @@
      */
     @Test
     public void testRequest() {
-        Ethernet reply = constructDhcpPacket(DHCPPacketType.DHCPREQUEST);
+        Ethernet reply = constructDhcpPacket(DHCP.MsgType.DHCPREQUEST);
         sendPacket(reply);
     }
 
@@ -141,7 +140,7 @@
      * @param packetType DHCP Message Type
      * @return Ethernet packet
      */
-    private Ethernet constructDhcpPacket(DHCPPacketType packetType) {
+    private Ethernet constructDhcpPacket(DHCP.MsgType packetType) {
 
         // Ethernet Frame.
         Ethernet ethReply = new Ethernet();
@@ -174,8 +173,8 @@
         dhcpReply.setHardwareAddressLength((byte) 6);
 
         // DHCP Options.
-        DHCPOption option = new DHCPOption();
-        List<DHCPOption> optionList = new ArrayList<>();
+        DhcpOption option = new DhcpOption();
+        List<DhcpOption> optionList = new ArrayList<>();
 
         // DHCP Message Type.
         option.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
@@ -185,7 +184,7 @@
         optionList.add(option);
 
         // DHCP Requested IP.
-        option = new DHCPOption();
+        option = new DhcpOption();
         option.setCode(DHCP.DHCPOptionCode.OptionCode_RequestedIP.getValue());
         option.setLength((byte) 4);
         optionData = Ip4Address.valueOf(EXPECTED_IP).toOctets();
@@ -193,7 +192,7 @@
         optionList.add(option);
 
         // End Option.
-        option = new DHCPOption();
+        option = new DhcpOption();
         option.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
         option.setLength((byte) 1);
         optionList.add(option);
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelay.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelay.java
index 40003eb..778f778 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelay.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelay.java
@@ -28,8 +28,7 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.packet.ARP;
 import org.onlab.packet.DHCP;
-import org.onlab.packet.DHCPOption;
-import org.onlab.packet.DHCPPacketType;
+import org.onlab.packet.dhcp.DhcpOption;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
@@ -421,11 +420,11 @@
             }
 
             Ethernet packet = context.inPacket().parsed();
-            DHCPPacketType incomingPacketType = null;
-            for (DHCPOption option : dhcpPayload.getOptions()) {
+            DHCP.MsgType incomingPacketType = null;
+            for (DhcpOption option : dhcpPayload.getOptions()) {
                 if (option.getCode() == OptionCode_MessageType.getValue()) {
                     byte[] data = option.getData();
-                    incomingPacketType = DHCPPacketType.getType(data[0]);
+                    incomingPacketType = DHCP.MsgType.getType(data[0]);
                 }
             }
 
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java
index d87326d8..8271644 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java
@@ -25,8 +25,7 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.packet.DHCP;
-import org.onlab.packet.DHCPOption;
-import org.onlab.packet.DHCPPacketType;
+import org.onlab.packet.dhcp.DhcpOption;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
@@ -63,8 +62,8 @@
 import java.util.List;
 
 import static org.onlab.packet.DHCP.DHCPOptionCode.*;
-import static org.onlab.packet.DHCPPacketType.DHCPACK;
-import static org.onlab.packet.DHCPPacketType.DHCPOFFER;
+import static org.onlab.packet.DHCP.MsgType.DHCPACK;
+import static org.onlab.packet.DHCP.MsgType.DHCPOFFER;
 import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -192,7 +191,7 @@
                 return;
             }
 
-            DHCPPacketType inPacketType = getPacketType(dhcpPacket);
+            DHCP.MsgType inPacketType = getPacketType(dhcpPacket);
             if (inPacketType == null || dhcpPacket.getClientHardwareAddress() == null) {
                 log.trace("Malformed DHCP packet received, ignore it");
                 return;
@@ -235,14 +234,14 @@
             }
         }
 
-        private DHCPPacketType getPacketType(DHCP dhcpPacket) {
-            DHCPOption optType = dhcpPacket.getOption(OptionCode_MessageType);
+        private DHCP.MsgType getPacketType(DHCP dhcpPacket) {
+            DhcpOption optType = dhcpPacket.getOption(OptionCode_MessageType);
             if (optType == null) {
                 log.trace("DHCP packet with no message type, ignore it");
                 return null;
             }
 
-            DHCPPacketType inPacketType = DHCPPacketType.getType(optType.getData()[0]);
+            DHCP.MsgType inPacketType = DHCP.MsgType.getType(optType.getData()[0]);
             if (inPacketType == null) {
                 log.trace("DHCP packet with no packet type, ignore it");
             }
@@ -318,9 +317,9 @@
             dhcpReply.setServerIPAddress(gatewayIp.toInt());
             dhcpReply.setClientHardwareAddress(request.getClientHardwareAddress());
 
-            List<DHCPOption> options = Lists.newArrayList();
+            List<DhcpOption> options = Lists.newArrayList();
             // message type
-            DHCPOption option = new DHCPOption();
+            DhcpOption option = new DhcpOption();
             option.setCode(OptionCode_MessageType.getValue());
             option.setLength((byte) 1);
             byte[] optionData = {msgType};
@@ -328,14 +327,14 @@
             options.add(option);
 
             // server identifier
-            option = new DHCPOption();
+            option = new DhcpOption();
             option.setCode(OptionCode_DHCPServerIp.getValue());
             option.setLength((byte) 4);
             option.setData(gatewayIp.toOctets());
             options.add(option);
 
             // lease time
-            option = new DHCPOption();
+            option = new DhcpOption();
             option.setCode(OptionCode_LeaseTime.getValue());
             option.setLength((byte) 4);
             option.setData(DHCP_DATA_LEASE_INFINITE);
@@ -343,7 +342,7 @@
 
             // subnet mask
             Ip4Address subnetMask = Ip4Address.makeMaskPrefix(subnetPrefixLen);
-            option = new DHCPOption();
+            option = new DhcpOption();
             option.setCode(OptionCode_SubnetMask.getValue());
             option.setLength((byte) 4);
             option.setData(subnetMask.toOctets());
@@ -351,35 +350,35 @@
 
             // broadcast address
             Ip4Address broadcast = Ip4Address.makeMaskedAddress(yourIp, subnetPrefixLen);
-            option = new DHCPOption();
+            option = new DhcpOption();
             option.setCode(OptionCode_BroadcastAddress.getValue());
             option.setLength((byte) 4);
             option.setData(broadcast.toOctets());
             options.add(option);
 
             // domain server
-            option = new DHCPOption();
+            option = new DhcpOption();
             option.setCode(OptionCode_DomainServer.getValue());
             option.setLength((byte) 4);
             option.setData(DEFAULT_DNS.toOctets());
             options.add(option);
 
             // TODO fix MTU value to be configurable
-            option = new DHCPOption();
+            option = new DhcpOption();
             option.setCode(DHCP_OPTION_MTU);
             option.setLength((byte) 2);
             option.setData(DHCP_DATA_MTU_DEFAULT);
             options.add(option);
 
             // router address
-            option = new DHCPOption();
+            option = new DhcpOption();
             option.setCode(OptionCode_RouterAddress.getValue());
             option.setLength((byte) 4);
             option.setData(gatewayIp.toOctets());
             options.add(option);
 
             // end option
-            option = new DHCPOption();
+            option = new DhcpOption();
             option.setCode(OptionCode_END.getValue());
             option.setLength((byte) 1);
             options.add(option);
diff --git a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
index 97b49a4..996bd43 100644
--- a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
+++ b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
@@ -24,7 +24,6 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.packet.ARP;
 import org.onlab.packet.DHCP;
-import org.onlab.packet.DHCPPacketType;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.ICMP6;
 import org.onlab.packet.IPacket;
@@ -489,7 +488,7 @@
                                     .anyMatch(dhcpOption -> dhcpOption.getCode() ==
                                             DHCP.DHCPOptionCode.OptionCode_MessageType.getValue() &&
                                             dhcpOption.getLength() == 1 &&
-                                            dhcpOption.getData()[0] == DHCPPacketType.DHCPACK.getValue())) {
+                                            dhcpOption.getData()[0] == DHCP.MsgType.DHCPACK.getValue())) {
                                 MacAddress hostMac = MacAddress.valueOf(dhcp.getClientHardwareAddress());
                                 VlanId hostVlan = VlanId.vlanId(eth.getVlanID());
                                 HostId hostId = HostId.hostId(hostMac, hostVlan);
diff --git a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
index b642458..b28ec17 100644
--- a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
+++ b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
@@ -25,8 +25,7 @@
 import org.onlab.packet.ARP;
 import org.onlab.packet.ChassisId;
 import org.onlab.packet.DHCP;
-import org.onlab.packet.DHCPOption;
-import org.onlab.packet.DHCPPacketType;
+import org.onlab.packet.dhcp.DhcpOption;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.ICMP6;
 import org.onlab.packet.IPv4;
@@ -614,9 +613,9 @@
         @Override
         public InboundPacket inPacket() {
             byte[] dhcpMsgType = new byte[1];
-            dhcpMsgType[0] = (byte) DHCPPacketType.DHCPREQUEST.getValue();
+            dhcpMsgType[0] = (byte) DHCP.MsgType.DHCPREQUEST.getValue();
 
-            DHCPOption dhcpOption = new DHCPOption();
+            DhcpOption dhcpOption = new DhcpOption();
             dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
             dhcpOption.setData(dhcpMsgType);
             dhcpOption.setLength((byte) 1);
@@ -658,9 +657,9 @@
         @Override
         public InboundPacket inPacket() {
             byte[] dhcpMsgType = new byte[1];
-            dhcpMsgType[0] = (byte) DHCPPacketType.DHCPACK.getValue();
+            dhcpMsgType[0] = (byte) DHCP.MsgType.DHCPACK.getValue();
 
-            DHCPOption dhcpOption = new DHCPOption();
+            DhcpOption dhcpOption = new DhcpOption();
             dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
             dhcpOption.setData(dhcpMsgType);
             dhcpOption.setLength((byte) 1);
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 012443e..ed1741e 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/DHCP.java
@@ -18,12 +18,16 @@
 
 package org.onlab.packet;
 
-import java.io.UnsupportedEncodingException;
+import com.google.common.collect.ImmutableMap;
+import org.onlab.packet.dhcp.DhcpOption;
+import org.onlab.packet.dhcp.DhcpRelayAgentOption;
+
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.ListIterator;
+import java.util.Map;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static org.onlab.packet.PacketUtils.checkInput;
@@ -53,20 +57,33 @@
     public static final int MIN_HEADER_LENGTH = 240;
     public static final byte OPCODE_REQUEST = 0x1;
     public static final byte OPCODE_REPLY = 0x2;
-
     public static final byte HWTYPE_ETHERNET = 0x1;
 
+    private static final Map<Byte, Deserializer<? extends DhcpOption>> OPTION_DESERIALIZERS =
+            ImmutableMap.of(DHCPOptionCode.OptionCode_CircuitID.value, DhcpRelayAgentOption.deserializer());
+    private static final int UNSIGNED_BYTE_MASK = 0xff;
+    private static final int BASE_OPTION_LEN = 60;
+    private static final int MIN_DHCP_LEN = 240;
+    private static final int BASE_HW_ADDR_LEN = 16;
+    private static final byte PAD_BYTE = 0;
+    private static final int BASE_SERVER_NAME_LEN = 64;
+    private static final int BASE_BOOT_FILE_NAME_LEN = 128;
+    private static final int MAGIC_COOKIE = 0x63825363;
+
     public enum DHCPOptionCode {
-        OptionCode_SubnetMask((byte) 1), OptionCode_RouterAddress((byte) 3), OptionCode_DomainServer((byte) 6),
-        OptionCode_HostName((byte) 12), OptionCode_DomainName((byte) 15), OptionCode_BroadcastAddress((byte) 28),
-        OptionCode_RequestedIP((byte) 50), OptionCode_LeaseTime((byte) 51), OptionCode_MessageType((byte) 53),
+        OptionCode_Pad((byte) 0), OptionCode_SubnetMask((byte) 1),
+        OptionCode_RouterAddress((byte) 3), OptionCode_DomainServer((byte) 6),
+        OptionCode_HostName((byte) 12), OptionCode_DomainName((byte) 15),
+        OptionCode_BroadcastAddress((byte) 28), OptionCode_RequestedIP((byte) 50),
+        OptionCode_LeaseTime((byte) 51), OptionCode_MessageType((byte) 53),
         OptionCode_DHCPServerIp((byte) 54), OptionCode_RequestedParameters((byte) 55),
-        OptionCode_RenewalTime((byte) 58), OPtionCode_RebindingTime((byte) 59), OptionCode_ClientID((byte) 61),
-        OptionCode_CircuitID((byte) 82), OptionCode_END((byte) 255);
+        OptionCode_RenewalTime((byte) 58), OPtionCode_RebindingTime((byte) 59),
+        OptionCode_ClientID((byte) 61), OptionCode_CircuitID((byte) 82),
+        OptionCode_END((byte) 255);
 
         protected byte value;
 
-        private DHCPOptionCode(final byte value) {
+        DHCPOptionCode(final byte value) {
             this.value = value;
         }
 
@@ -75,6 +92,65 @@
         }
     }
 
+    public enum MsgType {
+        // From RFC 1533
+        DHCPDISCOVER(1), DHCPOFFER(2), DHCPREQUEST(3), DHCPDECLINE(4), DHCPACK(5),
+        DHCPNAK(6), DHCPRELEASE(7),
+
+        // From RFC2132
+        DHCPINFORM(8),
+
+        // From RFC3203
+        DHCPFORCERENEW(9),
+
+        // From RFC4388
+        DHCPLEASEQUERY(10), DHCPLEASEUNASSIGNED(11), DHCPLEASEUNKNOWN(12),
+        DHCPLEASEACTIVE(13);
+
+        protected int value;
+
+        MsgType(final int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return this.value;
+        }
+
+        public static MsgType getType(final int value) {
+            switch (value) {
+                case 1:
+                    return DHCPDISCOVER;
+                case 2:
+                    return DHCPOFFER;
+                case 3:
+                    return DHCPREQUEST;
+                case 4:
+                    return DHCPDECLINE;
+                case 5:
+                    return DHCPACK;
+                case 6:
+                    return DHCPNAK;
+                case 7:
+                    return DHCPRELEASE;
+                case 8:
+                    return DHCPINFORM;
+                case 9:
+                    return DHCPFORCERENEW;
+                case 10:
+                    return DHCPLEASEQUERY;
+                case 11:
+                    return DHCPLEASEUNASSIGNED;
+                case 12:
+                    return DHCPLEASEUNKNOWN;
+                case 13:
+                    return DHCPLEASEACTIVE;
+                default:
+                    return null;
+            }
+        }
+    }
+
     protected byte opCode;
     protected byte hardwareType;
     protected byte hardwareAddressLength;
@@ -89,7 +165,7 @@
     protected byte[] clientHardwareAddress;
     protected String serverName;
     protected String bootFileName;
-    protected List<DHCPOption> options = new ArrayList<DHCPOption>();
+    protected List<DhcpOption> options = new ArrayList<DhcpOption>();
 
     /**
      * @return the opCode
@@ -302,9 +378,9 @@
      *            The option code to get
      * @return The value of the option if it exists, null otherwise
      */
-    public DHCPOption getOption(final DHCPOptionCode optionCode) {
-        for (final DHCPOption opt : this.options) {
-            if (opt.code == optionCode.value) {
+    public DhcpOption getOption(final DHCPOptionCode optionCode) {
+        for (final DhcpOption opt : this.options) {
+            if (opt.getCode() == optionCode.getValue()) {
                 return opt;
             }
         }
@@ -314,7 +390,7 @@
     /**
      * @return the options
      */
-    public List<DHCPOption> getOptions() {
+    public List<DhcpOption> getOptions() {
         return this.options;
     }
 
@@ -323,7 +399,7 @@
      *            the options to set
      * @return this
      */
-    public DHCP setOptions(final List<DHCPOption> options) {
+    public DHCP setOptions(final List<DhcpOption> options) {
         this.options = options;
         return this;
     }
@@ -331,16 +407,15 @@
     /**
      * @return the packetType base on option 53
      */
-    public DHCPPacketType getPacketType() {
-        final ListIterator<DHCPOption> lit = this.options.listIterator();
-        while (lit.hasNext()) {
-            final DHCPOption option = lit.next();
-            // only care option 53
-            if (option.getCode() == 53) {
-                return DHCPPacketType.getType(option.getData()[0]);
-            }
-        }
-        return null;
+    public MsgType getPacketType() {
+        return this.options.parallelStream()
+                .filter(op -> op.getCode() == DHCPOptionCode.OptionCode_MessageType.getValue())
+                .map(DhcpOption::getData)
+                .filter(data -> data.length != 0)
+                .map(data -> data[0])
+                .map(MsgType::getType)
+                .findFirst()
+                .orElse(null);
     }
 
     /**
@@ -385,19 +460,20 @@
         // minimum size 240 including magic cookie, options generally padded to
         // 300
         int optionsLength = 0;
-        for (final DHCPOption option : this.options) {
-            if (option.getCode() == 0 || option.getCode() == ((byte) 255)) {
+        for (final DhcpOption option : this.options) {
+            if (option.getCode() == DHCPOptionCode.OptionCode_Pad.getValue() ||
+                    option.getCode() == DHCPOptionCode.OptionCode_END.getValue()) {
                 optionsLength += 1;
             } else {
-                optionsLength += 2 + (0xff & option.getLength());
+                optionsLength += 2 + (UNSIGNED_BYTE_MASK & option.getLength());
             }
         }
         int optionsPadLength = 0;
-        if (optionsLength < 60) {
-            optionsPadLength = 60 - optionsLength;
+        if (optionsLength < BASE_OPTION_LEN) {
+            optionsPadLength = BASE_OPTION_LEN - optionsLength;
         }
 
-        final byte[] data = new byte[240 + optionsLength + optionsPadLength];
+        final byte[] data = new byte[MIN_DHCP_LEN + optionsLength + optionsPadLength];
         final ByteBuffer bb = ByteBuffer.wrap(data);
         bb.put(this.opCode);
         bb.put(this.hardwareType);
@@ -410,127 +486,44 @@
         bb.putInt(this.yourIPAddress);
         bb.putInt(this.serverIPAddress);
         bb.putInt(this.gatewayIPAddress);
-        checkArgument(this.clientHardwareAddress.length <= 16,
+        checkArgument(this.clientHardwareAddress.length <= BASE_HW_ADDR_LEN,
                 "Hardware address is too long (%s bytes)", this.clientHardwareAddress.length);
         bb.put(this.clientHardwareAddress);
-        if (this.clientHardwareAddress.length < 16) {
-            for (int i = 0; i < 16 - this.clientHardwareAddress.length; ++i) {
-                bb.put((byte) 0x0);
+        if (this.clientHardwareAddress.length < BASE_HW_ADDR_LEN) {
+            for (int i = 0; i < BASE_HW_ADDR_LEN - this.clientHardwareAddress.length; ++i) {
+                bb.put(PAD_BYTE);
             }
         }
-        this.writeString(this.serverName, bb, 64);
-        this.writeString(this.bootFileName, bb, 128);
+        this.writeString(this.serverName, bb, BASE_SERVER_NAME_LEN);
+        this.writeString(this.bootFileName, bb, BASE_BOOT_FILE_NAME_LEN);
         // magic cookie
-        bb.put((byte) 0x63);
-        bb.put((byte) 0x82);
-        bb.put((byte) 0x53);
-        bb.put((byte) 0x63);
-        for (final DHCPOption option : this.options) {
-            dhcpOptionToByteArray(option, bb);
+        bb.putInt(MAGIC_COOKIE);
+        for (final DhcpOption option : this.options) {
+            bb.put(option.serialize());
         }
         // assume the rest is padded out with zeroes
         return data;
     }
 
-    public static ByteBuffer dhcpOptionToByteArray(DHCPOption option, ByteBuffer bb) {
-        final int code = option.getCode() & 0xff;
-        bb.put((byte) code);
-        if (code != 0 && code != 255) {
-            bb.put(option.getLength());
-            bb.put(option.getData());
-        }
-        return bb;
-    }
-
     @Override
     public IPacket deserialize(final byte[] data, final int offset,
                                final int length) {
-        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
-        if (bb.remaining() < DHCP.MIN_HEADER_LENGTH) {
-            return this;
+        try {
+            return deserializer().deserialize(data, offset, length);
+        } catch (DeserializationException e) {
+            return null;
         }
-
-        this.opCode = bb.get();
-        this.hardwareType = bb.get();
-        this.hardwareAddressLength = bb.get();
-        this.hops = bb.get();
-        this.transactionId = bb.getInt();
-        this.seconds = bb.getShort();
-        this.flags = bb.getShort();
-        this.clientIPAddress = bb.getInt();
-        this.yourIPAddress = bb.getInt();
-        this.serverIPAddress = bb.getInt();
-        this.gatewayIPAddress = bb.getInt();
-        final int hardwareAddressLength = 0xff & this.hardwareAddressLength;
-        this.clientHardwareAddress = new byte[hardwareAddressLength];
-
-        bb.get(this.clientHardwareAddress);
-        for (int i = hardwareAddressLength; i < 16; ++i) {
-            bb.get();
-        }
-        this.serverName = this.readString(bb, 64);
-        this.bootFileName = this.readString(bb, 128);
-        // read the magic cookie
-        // magic cookie
-        bb.get();
-        bb.get();
-        bb.get();
-        bb.get();
-        // read options
-        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);
-                    } else {
-                        // Skip the invalid option and set the END option
-                        code = 0xff;
-                        option.setCode((byte) code);
-                        option.setLength((byte) 0);
-                    }
-                } else {
-                    // Skip the invalid option and set the END option
-                    code = 0xff;
-                    option.setCode((byte) code);
-                    option.setLength((byte) 0);
-                }
-            }
-            this.options.add(option);
-            if (code == 255) {
-                // remaining bytes are supposed to be 0, but ignore them just in
-                // case
-                break;
-            }
-        }
-
-        return this;
     }
 
     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);
+                bb.put(PAD_BYTE);
             }
         } else {
-            byte[] bytes = null;
-            try {
-                bytes = string.getBytes("ascii");
-            } catch (final UnsupportedEncodingException e) {
-                throw new RuntimeException("Failure encoding server name", e);
-            }
+            byte[] bytes;
+            bytes = string.getBytes(StandardCharsets.US_ASCII);
             int writeLength = bytes.length;
             if (writeLength > maxLength) {
                 writeLength = maxLength;
@@ -545,12 +538,8 @@
     private static String readString(final ByteBuffer bb, final int maxLength) {
         final byte[] bytes = new byte[maxLength];
         bb.get(bytes);
-        String result = null;
-        try {
-            result = new String(bytes, "ascii").trim();
-        } catch (final UnsupportedEncodingException e) {
-            throw new RuntimeException("Failure decoding string", e);
-        }
+        String result;
+        result = new String(bytes, StandardCharsets.US_ASCII).trim();
         return result;
     }
 
@@ -577,56 +566,64 @@
             dhcp.yourIPAddress = bb.getInt();
             dhcp.serverIPAddress = bb.getInt();
             dhcp.gatewayIPAddress = bb.getInt();
-            final int hardwareAddressLength = 0xff & dhcp.hardwareAddressLength;
+            final int hardwareAddressLength = UNSIGNED_BYTE_MASK & dhcp.hardwareAddressLength;
             dhcp.clientHardwareAddress = new byte[hardwareAddressLength];
 
             bb.get(dhcp.clientHardwareAddress);
-            for (int i = hardwareAddressLength; i < 16; ++i) {
+            for (int i = hardwareAddressLength; i < BASE_HW_ADDR_LEN; ++i) {
                 bb.get();
             }
-            dhcp.serverName = readString(bb, 64);
-            dhcp.bootFileName = readString(bb, 128);
+            dhcp.serverName = readString(bb, BASE_SERVER_NAME_LEN);
+            dhcp.bootFileName = readString(bb, BASE_BOOT_FILE_NAME_LEN);
             // read the magic cookie
             // magic cookie
-            bb.get();
-            bb.get();
-            bb.get();
-            bb.get();
+            bb.getInt();
 
             // 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
+                DhcpOption option;
+
+                int pos = bb.position();
+                int optCode = UNSIGNED_BYTE_MASK & bb.array()[pos]; // to unsigned integer
+                int optLen;
+                byte[] optData;
+
+                if (optCode == DHCPOptionCode.OptionCode_Pad.value) {
+                    // pad, skip
+                    // read option code
+                    bb.get();
                     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) {
-                    DHCPOption end = new DHCPOption();
-                    end.setCode((byte) 255);
-                    dhcp.options.add(end);
-                    // remaining bytes are supposed to be 0, but ignore them just in
-                    // case
+                }
+                if (optCode == (UNSIGNED_BYTE_MASK & DHCPOptionCode.OptionCode_END.value)) {
+                    // end of dhcp options or invalid option and set the END option
+                    option = new DhcpOption();
+                    option.setCode((byte) optCode);
+                    dhcp.options.add(option);
                     foundEndOptionsMarker = true;
                     break;
                 }
+
+                if (bb.remaining() < 2) {
+                    // No option length
+                    throw new DeserializationException("Buffer underflow while reading DHCP option");
+                }
+
+                optLen = UNSIGNED_BYTE_MASK & bb.array()[pos + 1];
+                if (bb.remaining() < DhcpOption.DEFAULT_LEN + optLen) {
+                    // Invalid option length
+                    throw new DeserializationException("Buffer underflow while reading DHCP option");
+                }
+                optData = new byte[DhcpOption.DEFAULT_LEN + optLen];
+                bb.get(optData);
+                if (OPTION_DESERIALIZERS.containsKey((byte) optCode)) {
+                    option = OPTION_DESERIALIZERS.get((byte) optCode).deserialize(optData, 0, optData.length);
+                    dhcp.options.add(option);
+                } else {
+                    // default option
+                    option = DhcpOption.deserializer().deserialize(optData, 0, optData.length);
+                    dhcp.options.add(option);
+                }
             }
 
             if (!foundEndOptionsMarker) {
diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCP6.java b/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
index b6aa691..6310ec5 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
@@ -18,6 +18,7 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
+import org.onlab.packet.dhcp.Dhcp6Option;
 
 import java.nio.ByteBuffer;
 import java.util.List;
@@ -91,7 +92,7 @@
 
     // general field
     private byte msgType; // 1 byte
-    private List<DHCP6Option> options;
+    private List<Dhcp6Option> options;
 
     // non-relay field
     private int transactionId; // 3 bytes
@@ -111,7 +112,7 @@
     @Override
     public byte[] serialize() {
         int payloadLength = options.stream()
-                .mapToInt(DHCP6Option::getLength)
+                .mapToInt(Dhcp6Option::getLength)
                 .sum();
 
         // 2 bytes code and 2 bytes length
@@ -186,7 +187,7 @@
 
             dhcp6.options = Lists.newArrayList();
             while (bb.remaining() >= OPT_CODE_SIZE) {
-                DHCP6Option option = new DHCP6Option();
+                Dhcp6Option option = new Dhcp6Option();
                 short code = bb.getShort();
                 if (bb.remaining() < OPT_LEN_SIZE) {
                     throw new DeserializationException(
@@ -235,7 +236,7 @@
      *
      * @return DHCPv6 options
      */
-    public List<DHCP6Option> getOptions() {
+    public List<Dhcp6Option> getOptions() {
         return options;
     }
 
@@ -289,7 +290,7 @@
      *
      * @param options the options
      */
-    public void setOptions(List<DHCP6Option> options) {
+    public void setOptions(List<Dhcp6Option> options) {
         this.options = options;
     }
 
diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCPOption.java b/utils/misc/src/main/java/org/onlab/packet/DHCPOption.java
deleted file mode 100644
index fa41753..0000000
--- a/utils/misc/src/main/java/org/onlab/packet/DHCPOption.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2014-present Open Networking Laboratory
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.onlab.packet;
-
-import java.util.Arrays;
-
-/**
- * Representation of DHCPOption field.
- */
-public class DHCPOption {
-    protected byte code;
-    protected byte length;
-    protected byte[] data;
-
-    /**
-     * @return the code
-     */
-    public byte getCode() {
-        return this.code;
-    }
-
-    /**
-     * @param code the code to set
-     * @return this
-     */
-    public DHCPOption setCode(final byte code) {
-        this.code = code;
-        return this;
-    }
-
-    /**
-     * @return the length
-     */
-    public byte getLength() {
-        return this.length;
-    }
-
-    /**
-     * @param length the length to set
-     * @return this
-     */
-    public DHCPOption setLength(final byte length) {
-        this.length = length;
-        return this;
-    }
-
-    /**
-     * @return the data
-     */
-    public byte[] getData() {
-        return this.data;
-    }
-
-    /**
-     * @param data the data to set
-     * @return this
-     */
-    public DHCPOption setData(final byte[] data) {
-        this.data = data;
-        return this;
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see java.lang.Object#hashCode()
-     */
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + this.code;
-        result = prime * result + Arrays.hashCode(this.data);
-        result = prime * result + this.length;
-        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 (obj == null) {
-            return false;
-        }
-        if (!(obj instanceof DHCPOption)) {
-            return false;
-        }
-        final DHCPOption other = (DHCPOption) obj;
-        if (this.code != other.code) {
-            return false;
-        }
-        if (!Arrays.equals(this.data, other.data)) {
-            return false;
-        }
-        if (this.length != other.length) {
-            return false;
-        }
-        return true;
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see java.lang.Object#toString()
-     */
-    @Override
-    public String toString() {
-        return "DHCPOption [code=" + this.code + ", length=" + this.length
-                + ", data=" + Arrays.toString(this.data) + "]";
-    }
-}
diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCPPacketType.java b/utils/misc/src/main/java/org/onlab/packet/DHCPPacketType.java
index 0447feb..ea78872 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCPPacketType.java
+++ b/utils/misc/src/main/java/org/onlab/packet/DHCPPacketType.java
@@ -18,6 +18,10 @@
 
 package org.onlab.packet;
 
+/**
+ * @deprecated 1.11 Loon, move into DHCP class.
+ */
+@Deprecated
 public enum DHCPPacketType {
     // From RFC 1533
     DHCPDISCOVER(1), DHCPOFFER(2), DHCPREQUEST(3), DHCPDECLINE(4), DHCPACK(5), DHCPNAK(
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/CircuitId.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/CircuitId.java
new file mode 100644
index 0000000..afa28ec
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/CircuitId.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2017-present 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.dhcp;
+
+import com.google.common.collect.Lists;
+import org.onlab.packet.VlanId;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Representation of DHCP option 82 Circuit id.
+ */
+public class CircuitId {
+    private static final String SEPARATOR = ":";
+    private static final String CIRCUIT_ID_FORMAT = "%s" + SEPARATOR + "%s";
+    private String connectPoint;
+    private VlanId vlanId;
+
+    /**
+     * Creates a circuit id by given information.
+     *
+     * @param connectPoint the connect point of circuit id
+     * @param vlanId the vlan id of circuit id
+     */
+    public CircuitId(String connectPoint, VlanId vlanId) {
+        this.connectPoint = connectPoint;
+        this.vlanId = vlanId;
+    }
+
+    /**
+     * Combines connect point with vlan id with separator ':' as circuit id.
+     * e.g. of:0000000000000204/1:100
+     *
+     * @return serialized circuit id for connect point and vlan ID
+     */
+    public byte[] serialize() {
+        return String
+                .format(CIRCUIT_ID_FORMAT, connectPoint, vlanId.toString())
+                .getBytes(StandardCharsets.US_ASCII);
+    }
+
+    /**
+     * Deserialize circuit id from byte string.
+     *
+     * @param circuitId the circuit id byte string
+     * @return a Circuit Id
+     */
+    public static CircuitId deserialize(byte[] circuitId) {
+        String cIdString = new String(circuitId, StandardCharsets.US_ASCII);
+        List<String> split = Lists.newArrayList(cIdString.split(SEPARATOR));
+        checkArgument(split.size() > 1, "Illegal circuit id.");
+        // remove last element (vlan id)
+        String vlanId = split.remove(split.size() - 1);
+        String connectPoint = String.join(SEPARATOR, split);
+        return new CircuitId(connectPoint, VlanId.vlanId(vlanId));
+    }
+
+    /**
+     * Gets the connect point of circuit id.
+     *
+     * @return the connect point
+     */
+    public String connectPoint() {
+        return connectPoint;
+    }
+
+    /**
+     * Gets the vlan id of circuit id.
+     *
+     * @return the vlan id
+     */
+    public VlanId vlanId() {
+        return vlanId;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof CircuitId)) {
+            return false;
+        }
+        CircuitId that = (CircuitId) obj;
+        return Objects.equals(this.connectPoint, that.connectPoint) &&
+                Objects.equals(this.vlanId, that.vlanId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(connectPoint, vlanId);
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCP6Option.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Option.java
similarity index 96%
rename from utils/misc/src/main/java/org/onlab/packet/DHCP6Option.java
rename to utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Option.java
index f70a1c5..dc34075 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCP6Option.java
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Option.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package org.onlab.packet;
+package org.onlab.packet.dhcp;
 
 /**
  * Representation of an DHCPv6 Option.
  * Base on RFC-3315.
  */
-public class DHCP6Option {
+public class Dhcp6Option {
     private short code;
     private short length;
     private byte[] data;
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpOption.java
new file mode 100644
index 0000000..a1b5923
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpOption.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2017-present 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.dhcp;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.IPacket;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Objects;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Default DHCP option.
+ */
+public class DhcpOption extends BasePacket {
+    public static final int OPT_CODE_LEN = 1;
+    public static final int DEFAULT_LEN = 2;
+    private final Logger log = getLogger(getClass());
+    protected byte code;
+    protected byte length;
+    protected byte[] data;
+
+    @Override
+    public byte[] serialize() {
+        ByteBuffer byteBuffer;
+        if (data != null) {
+            byteBuffer = ByteBuffer.allocate(DEFAULT_LEN + data.length);
+            byteBuffer.put(code);
+            byteBuffer.put(length);
+            byteBuffer.put(data);
+        } else {
+            byteBuffer = ByteBuffer.allocate(OPT_CODE_LEN);
+            byteBuffer.put(code);
+        }
+        return byteBuffer.array();
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        try {
+            return deserializer().deserialize(data, offset, length);
+        } catch (DeserializationException e) {
+            log.warn("Can't deserialize DhcpOption {}", e);
+            return null;
+        }
+    }
+
+    /**
+     * Deserializer function for DHCP option.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<DhcpOption> deserializer() {
+        return (data, offset, length) -> {
+            DhcpOption dhcpOption = new DhcpOption();
+            ByteBuffer byteBuffer = ByteBuffer.wrap(data, offset, length);
+            dhcpOption.code = byteBuffer.get();
+            if (byteBuffer.hasRemaining()) {
+                dhcpOption.length = byteBuffer.get();
+                dhcpOption.data = new byte[dhcpOption.length];
+                byteBuffer.get(dhcpOption.data);
+            } else {
+                dhcpOption.length = 0;
+                dhcpOption.data = null;
+            }
+            return dhcpOption;
+        };
+    }
+
+    /**
+     * @return the code
+     */
+    public byte getCode() {
+        return this.code;
+    }
+
+    /**
+     * @param code the code to set
+     * @return this
+     */
+    public DhcpOption setCode(final byte code) {
+        this.code = code;
+        return this;
+    }
+
+    /**
+     * @return the length
+     */
+    public byte getLength() {
+        return this.length;
+    }
+
+    /**
+     * @param length the length to set
+     * @return this
+     */
+    public DhcpOption setLength(final byte length) {
+        this.length = length;
+        return this;
+    }
+
+    /**
+     * @return the data
+     */
+    public byte[] getData() {
+        return this.data;
+    }
+
+    /**
+     * @param data the data to set
+     * @return this
+     */
+    public DhcpOption setData(final byte[] data) {
+        this.data = data;
+        return this;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(code, length, data);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof DhcpOption)) {
+            return false;
+        }
+        final DhcpOption other = (DhcpOption) obj;
+        return Objects.equals(this.code, other.code) &&
+                Objects.equals(this.length, other.length) &&
+                Arrays.equals(this.data, other.data);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "DhcpOption [code=" + this.code + ", length=" + this.length
+                + ", data=" + Arrays.toString(this.data) + "]";
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpRelayAgentOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpRelayAgentOption.java
new file mode 100644
index 0000000..498704d
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpRelayAgentOption.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2017-present 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.dhcp;
+
+import com.google.common.collect.Maps;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.IPacket;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Representation of DHCP relay agent option (option 82).
+ */
+public class DhcpRelayAgentOption extends DhcpOption {
+    private static final int SUB_OPT_DEFAULT_LEN = 2;
+    private final Logger log = getLogger(getClass());
+    private final Map<Byte, DhcpOption> subOptions = Maps.newHashMap();
+
+    // Sub-option codes for option 82
+    public enum RelayAgentInfoOptions {
+        CIRCUIT_ID((byte) 1),
+        REMOTE_ID((byte) 2),
+        DOCSIS((byte) 4),
+        LINK_SELECTION((byte) 5),
+        SUBSCRIBER_ID((byte) 6),
+        RADIUS((byte) 7),
+        AUTH((byte) 8),
+        VENDOR_SPECIFIC((byte) 9),
+        RELAY_AGENT_FLAGS((byte) 10),
+        SERVER_ID_OVERRIDE((byte) 11),
+        VIRTUAL_SUBNET_SELECTION((byte) 151),
+        VIRTUAL_SUBNET_SELECTION_CTRL((byte) 152);
+
+        private byte value;
+        public byte getValue() {
+            return value;
+        }
+        RelayAgentInfoOptions(byte value) {
+            this.value = value;
+        }
+    }
+
+    @Override
+    public byte[] serialize() {
+        int totalLen = 0;
+        totalLen += subOptions.size() * SUB_OPT_DEFAULT_LEN;
+        totalLen += subOptions.values().stream().mapToInt(DhcpOption::getLength).sum();
+        totalLen += DEFAULT_LEN;
+        ByteBuffer byteBuffer = ByteBuffer.allocate(totalLen);
+        byteBuffer.put(code);
+        byteBuffer.put(length);
+        subOptions.values().forEach(subOpt -> {
+            byteBuffer.put(subOpt.code);
+            byteBuffer.put(subOpt.length);
+            byteBuffer.put(subOpt.data);
+        });
+        return byteBuffer.array();
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        try {
+            return deserializer().deserialize(data, offset, length);
+        } catch (DeserializationException e) {
+            log.warn("can't deserialize DHCP relay agent information option {}", e);
+            return null;
+        }
+    }
+
+    /**
+     * Deserializer function for DHCP relay agent option.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<DhcpOption> deserializer() {
+        return (data, offset, length) -> {
+            DhcpRelayAgentOption relayOption = new DhcpRelayAgentOption();
+            ByteBuffer byteBuffer = ByteBuffer.wrap(data, offset, length);
+            relayOption.code = byteBuffer.get();
+            relayOption.length = byteBuffer.get();
+
+            while (byteBuffer.remaining() >= DEFAULT_LEN) {
+                byte subOptCode = byteBuffer.get();
+                byte subOptLen = byteBuffer.get();
+                byte[] subOptData = new byte[subOptLen];
+                byteBuffer.get(subOptData);
+
+                DhcpOption subOption = new DhcpOption();
+                subOption.code = subOptCode;
+                subOption.length = subOptLen;
+                subOption.data = subOptData;
+                relayOption.subOptions.put(subOptCode, subOption);
+            }
+
+            return relayOption;
+        };
+    }
+
+    /**
+     * Gets sub-option from this option by given option code.
+     *
+     * @param code the option code
+     * @return sub-option of given code; null if there is no sub-option for given
+     * code
+     */
+    public DhcpOption getSubOption(byte code) {
+        return subOptions.get(code);
+    }
+
+    /**
+     * Adds a sub-option for this option.
+     *
+     * @param subOption the sub-option
+     */
+    public void addSubOption(DhcpOption subOption) {
+        this.length += SUB_OPT_DEFAULT_LEN + subOption.length;
+        this.subOptions.put(subOption.getCode(), subOption);
+    }
+
+    /**
+     * Removes a sub-option by given sub-option code.
+     *
+     * @param code the code for sub-option
+     * @return sub-option removed; null of sub-option not exists
+     */
+    public DhcpOption removeSubOption(byte code) {
+        DhcpOption subOption = subOptions.remove(code);
+        if (subOption != null) {
+            this.length -= SUB_OPT_DEFAULT_LEN + subOption.length;
+        }
+        return subOption;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof DhcpRelayAgentOption)) {
+            return false;
+        }
+        DhcpRelayAgentOption that = (DhcpRelayAgentOption) obj;
+        return Objects.equals(this.subOptions, that.subOptions);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), subOptions);
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/package-info.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/package-info.java
new file mode 100644
index 0000000..1033b5d
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017-present 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 DHCP options.
+ */
+package org.onlab.packet.dhcp;
\ No newline at end of file
diff --git a/utils/misc/src/test/java/org/onlab/packet/Dhcp6Test.java b/utils/misc/src/test/java/org/onlab/packet/Dhcp6Test.java
index 927266d..7b83c00 100644
--- a/utils/misc/src/test/java/org/onlab/packet/Dhcp6Test.java
+++ b/utils/misc/src/test/java/org/onlab/packet/Dhcp6Test.java
@@ -18,6 +18,7 @@
 
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
+import org.onlab.packet.dhcp.Dhcp6Option;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -80,7 +81,7 @@
         assertEquals(dhcp6.getTransactionId(), TRANSACTION_ID);
         assertEquals(dhcp6.getOptions().size(), 1);
 
-        DHCP6Option clientIdOption = dhcp6.getOptions().get(0);
+        Dhcp6Option clientIdOption = dhcp6.getOptions().get(0);
         assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
         assertArrayEquals(clientIdOption.getData(), OPT_CLIENT_ID_BYTE_ARR);
     }
@@ -110,7 +111,7 @@
         assertArrayEquals(dhcp6.getPeerAddress(), PEER_ADDRESS.toOctets());
         assertEquals(dhcp6.getOptions().size(), 1);
 
-        DHCP6Option clientIdOption = dhcp6.getOptions().get(0);
+        Dhcp6Option clientIdOption = dhcp6.getOptions().get(0);
         assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
         assertArrayEquals(clientIdOption.getData(), OPT_CLIENT_ID_BYTE_ARR);
     }
@@ -124,13 +125,13 @@
         dhcp6.setMsgType(DHCP6.MsgType.REQUEST.value());
         dhcp6.setTransactionId(TRANSACTION_ID);
 
-        DHCP6Option opt1 = new DHCP6Option();
+        Dhcp6Option opt1 = new Dhcp6Option();
         opt1.setCode(DHCP6.OptionCode.CLIENTID.value());
         opt1.setLength(OPT_CLIENT_ID_SIZE);
         opt1.setData(OPT_CLIENT_ID_BYTE_ARR);
 
 
-        DHCP6Option opt2 = new DHCP6Option();
+        Dhcp6Option opt2 = new Dhcp6Option();
         opt2.setCode(DHCP6.OptionCode.AUTH.value());
         opt2.setLength(OPT_AUTH_SIZE);
         opt2.setData(OPT_AUTH_BYTE_AR);
@@ -162,13 +163,13 @@
         dhcp6.setLinkAddress(LINK_ADDRESS.toOctets());
         dhcp6.setPeerAddress(PEER_ADDRESS.toOctets());
 
-        DHCP6Option opt1 = new DHCP6Option();
+        Dhcp6Option opt1 = new Dhcp6Option();
         opt1.setCode(DHCP6.OptionCode.CLIENTID.value());
         opt1.setLength(OPT_CLIENT_ID_SIZE);
         opt1.setData(OPT_CLIENT_ID_BYTE_ARR);
 
 
-        DHCP6Option opt2 = new DHCP6Option();
+        Dhcp6Option opt2 = new Dhcp6Option();
         opt2.setCode(DHCP6.OptionCode.AUTH.value());
         opt2.setLength(OPT_AUTH_SIZE);
         opt2.setData(OPT_AUTH_BYTE_AR);
diff --git a/utils/misc/src/test/java/org/onlab/packet/DhcpTest.java b/utils/misc/src/test/java/org/onlab/packet/DhcpTest.java
index b13f78b..d8f5f6b 100644
--- a/utils/misc/src/test/java/org/onlab/packet/DhcpTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/DhcpTest.java
@@ -20,10 +20,14 @@
 import org.apache.commons.lang3.StringUtils;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.packet.dhcp.DhcpOption;
 
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -32,6 +36,33 @@
  */
 public class DhcpTest {
 
+    // For serialize test
+    private static final int TRANSACTION_ID = 1000;
+    private static final MacAddress CLIENT1_HOST_MAC = MacAddress.valueOf("1a:1a:1a:1a:1a:1a");
+    private static final Ip4Address REQ_IP = Ip4Address.valueOf("10.2.0.2");
+    private static final byte[] EXPECTED_SERIALIZED = ByteBuffer.allocate(300)
+            .put((byte) 0x01) // op code
+            .put((byte) 0x01) // hardware type
+            .put((byte) 0x06) // hardware address len
+            .put((byte) 0x00) // hops
+            .putInt(0x3e8) // transaction id
+            .putShort((short) 0x0) // seconds
+            .putShort((short) 0x0) // flags
+            .putInt(0) // client ip
+            .putInt(0) // your ip
+            .putInt(0) // server ip
+            .putInt(0) // gateway ip
+            .put(CLIENT1_HOST_MAC.toBytes()) // client hardware address
+            .put(new byte[10]) // pad
+            .put(new byte[64]) // server name
+            .put(new byte[128]) // boot file name
+            .putInt(0x63825363) // magic cookie
+            .put(new byte[]{0x35, 0x1, 0x3}) // msg type
+            .put(new byte[]{0x32, 0x4, 0xa, 0x2, 0x0, 0x2}) // requested ip
+            .put((byte) 0xff) // end of options
+            .put(new byte[50]) // pad
+            .array();
+
     private Deserializer<DHCP> deserializer = DHCP.deserializer();
 
     private byte opCode = 1;
@@ -50,7 +81,7 @@
     private String bootFileName = "test-file";
 
     private String hostName = "test-host";
-    private DHCPOption hostNameOption = new DHCPOption();
+    private DhcpOption hostNameOption = new DhcpOption();
 
     private byte[] byteHeader;
 
@@ -97,7 +128,7 @@
         bb.put(hostNameOption.getData());
 
         // End of options marker
-        bb.put((byte) (0xff & 255));
+        bb.put((DHCP.DHCPOptionCode.OptionCode_END.getValue()));
 
         byteHeader = bb.array();
     }
@@ -162,4 +193,49 @@
         assertTrue(StringUtils.contains(str, "bootFileName=" + bootFileName));
         // TODO: add option unit test
     }
+
+
+
+    @Test
+    public void testSerialize() throws Exception {
+        DHCP dhcpReply = new DHCP();
+        dhcpReply.setOpCode(DHCP.OPCODE_REQUEST);
+
+        dhcpReply.setYourIPAddress(0);
+        dhcpReply.setServerIPAddress(0);
+
+        dhcpReply.setTransactionId(TRANSACTION_ID);
+        dhcpReply.setClientHardwareAddress(CLIENT1_HOST_MAC.toBytes());
+        dhcpReply.setHardwareType(DHCP.HWTYPE_ETHERNET);
+        dhcpReply.setHardwareAddressLength((byte) 6);
+
+        // DHCP Options.
+        DhcpOption option = new DhcpOption();
+        List<DhcpOption> optionList = new ArrayList<>();
+
+        // DHCP Message Type.
+        option.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+        option.setLength((byte) 1);
+        byte[] optionData = {(byte) DHCP.MsgType.DHCPREQUEST.getValue()};
+        option.setData(optionData);
+        optionList.add(option);
+
+        // DHCP Requested IP.
+        option = new DhcpOption();
+        option.setCode(DHCP.DHCPOptionCode.OptionCode_RequestedIP.getValue());
+        option.setLength((byte) 4);
+        optionData = REQ_IP.toOctets();
+        option.setData(optionData);
+        optionList.add(option);
+
+        // End Option.
+        option = new DhcpOption();
+        option.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
+        option.setLength((byte) 1);
+        optionList.add(option);
+
+        dhcpReply.setOptions(optionList);
+
+        assertArrayEquals(EXPECTED_SERIALIZED, dhcpReply.serialize());
+    }
 }