DHCP util refactor

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

Change-Id: I85ce7fa5e6f3fdc916fbbeba9a4e10e75064a054
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/dhcp/Dhcp6Option.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Option.java
new file mode 100644
index 0000000..dc34075
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Option.java
@@ -0,0 +1,81 @@
+/*
+ * 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;
+
+/**
+ * Representation of an DHCPv6 Option.
+ * Base on RFC-3315.
+ */
+public class Dhcp6Option {
+    private short code;
+    private short length;
+    private byte[] data;
+
+    /**
+     * Sets the code of this option.
+     *
+     * @param code the code to set
+     */
+    public void setCode(short code) {
+        this.code = code;
+    }
+
+    /**
+     * Sets the data and length of this option.
+     *
+     * @param data the data to set
+     */
+    public void setData(byte[] data) {
+        this.data = data;
+    }
+
+    /**
+     * Sets length of this option.
+     *
+     * @param length the length to set
+     */
+    public void setLength(short length) {
+        this.length = length;
+    }
+
+    /**
+     * Gets the code of this option.
+     *
+     * @return the code
+     */
+    public short getCode() {
+        return code;
+    }
+
+    /**
+     * Gets the length of this option.
+     *
+     * @return the length of this option
+     */
+    public short getLength() {
+        return length;
+    }
+
+    /**
+     * Gets the data of this option.
+     *
+     * @return the data of this option
+     */
+    public byte[] getData() {
+        return 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