CORD-1430 Dhcpv6 Relay APP

Change-Id: Ib913b5d53305acfa47c13676c6d6bbd9fd0023f4
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 924d181..f807000 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
@@ -23,8 +23,10 @@
 import org.onlab.packet.dhcp.Dhcp6IaAddressOption;
 import org.onlab.packet.dhcp.Dhcp6IaNaOption;
 import org.onlab.packet.dhcp.Dhcp6IaTaOption;
+import org.onlab.packet.dhcp.Dhcp6IaPdOption;
 import org.onlab.packet.dhcp.Dhcp6Option;
 import org.onlab.packet.dhcp.Dhcp6RelayOption;
+import org.onlab.packet.dhcp.Dhcp6InterfaceIdOption;
 
 import java.nio.ByteBuffer;
 import java.util.List;
@@ -88,7 +90,8 @@
         RELAY_MSG((short) 9), AUTH((short) 11), UNICAST((short) 12),
         STATUS_CODE((short) 13), RAPID_COMMIT((short) 14), USER_CLASS((short) 15),
         VENDOR_CLASS((short) 16), VENDOR_OPTS((short) 17), INTERFACE_ID((short) 18),
-        RECONF_MSG((short) 19), RECONF_ACCEPT((short) 20), SUBSCRIBER_ID((short) 38);
+        RECONF_MSG((short) 19), RECONF_ACCEPT((short) 20), IA_PD((short) 25), IAPREFIX((short) 26),
+        SUBSCRIBER_ID((short) 38);
 
         protected short value;
         OptionCode(final short value) {
@@ -100,11 +103,15 @@
     }
 
     private static final Map<Short, Deserializer<Dhcp6Option>> OPT_DESERIALIZERS =
-            ImmutableMap.of(OptionCode.IA_NA.value, Dhcp6IaNaOption.deserializer(),
-                            OptionCode.IA_TA.value, Dhcp6IaTaOption.deserializer(),
-                            OptionCode.IAADDR.value, Dhcp6IaAddressOption.deserializer(),
-                            OptionCode.RELAY_MSG.value, Dhcp6RelayOption.deserializer(),
-                            OptionCode.CLIENTID.value, Dhcp6ClientIdOption.deserializer());
+            ImmutableMap.<Short, Deserializer<Dhcp6Option>>builder()
+                            .put(OptionCode.IA_NA.value, Dhcp6IaNaOption.deserializer())
+                            .put(OptionCode.IA_TA.value, Dhcp6IaTaOption.deserializer())
+                            .put(OptionCode.IAADDR.value, Dhcp6IaAddressOption.deserializer())
+                            .put(OptionCode.RELAY_MSG.value, Dhcp6RelayOption.deserializer())
+                            .put(OptionCode.CLIENTID.value, Dhcp6ClientIdOption.deserializer())
+                            .put(OptionCode.IA_PD.value, Dhcp6IaPdOption.deserializer())
+                            .put(OptionCode.INTERFACE_ID.value, Dhcp6InterfaceIdOption.deserializer())
+                    .build();
 
     // general field
     private byte msgType; // 1 byte
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaPdOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaPdOption.java
new file mode 100644
index 0000000..e76f055
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaPdOption.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.DHCP6;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * DHCPv6 Identity Association for Prefix Delegation Option.
+ * Based on RFC-3633
+ */
+public final class Dhcp6IaPdOption extends Dhcp6Option {
+    public static final int DEFAULT_LEN = 12;
+    private int iaId;
+    private int t1;
+    private int t2;
+    private List<Dhcp6Option> options;
+
+    @Override
+    public short getCode() {
+        return DHCP6.OptionCode.IA_PD.value();
+    }
+
+    @Override
+    public short getLength() {
+        return (short) (DEFAULT_LEN + options.stream()
+                        .mapToInt(opt -> (int) opt.getLength() + Dhcp6Option.DEFAULT_LEN)
+                        .sum());
+
+    }
+
+    /**
+     * Gets Identity Association ID.
+     * The unique identifier for this IA_PD; the IAID must
+     * be unique among the identifiers for all of this
+     * requesting router's IA_PDs.
+     *
+     * @return the Identity Association ID
+     */
+    public int getIaId() {
+        return iaId;
+    }
+
+    /**
+     * Sets Identity Association ID.
+     *
+     * @param iaId the Identity Association ID.
+     */
+    public void setIaId(int iaId) {
+        this.iaId = iaId;
+    }
+
+    /**
+     * Gets time 1.
+     * The time at which the requesting router should
+     * contact the delegating router from which the
+     * prefixes in the IA_PD were obtained to extend the
+     * lifetimes of the prefixes delegated to the IA_PD;
+     * T1 is a time duration relative to the current time
+     * expressed in units of seconds.
+     *
+     * @return the value of time 1
+     */
+    public int getT1() {
+        return t1;
+    }
+
+    /**
+     * Sets time 1.
+     *
+     * @param t1 the value of time 1
+     */
+    public void setT1(int t1) {
+        this.t1 = t1;
+    }
+
+    /**
+     * Gets time 2.
+     * The time at which the requesting router should
+     * contact any available delegating router to extend
+     * the lifetimes of the prefixes assigned to the
+     * IA_PD; T2 is a time duration relative to the
+     * current time expressed in units of seconds.
+     *
+     * @return the value of time 2
+     */
+    public int getT2() {
+        return t2;
+    }
+
+    /**
+     * Sets time 2.
+     *
+     * @param t2 the value of time 2
+     */
+    public void setT2(int t2) {
+        this.t2 = t2;
+    }
+
+    /**
+     * Gets sub-options.
+     *
+     * @return sub-options of this option
+     */
+    public List<Dhcp6Option> getOptions() {
+        return options;
+    }
+
+    /**
+     * Sets sub-options.
+     *
+     * @param options the sub-options of this option
+     */
+    public void setOptions(List<Dhcp6Option> options) {
+        this.options = options;
+    }
+
+    /**
+     * Default constructor.
+     */
+    public Dhcp6IaPdOption() {
+    }
+
+    /**
+     * Constructs a DHCPv6 IA PD option with DHCPv6 option.
+     *
+     * @param dhcp6Option the DHCPv6 option
+     */
+    public Dhcp6IaPdOption(Dhcp6Option dhcp6Option) {
+        super(dhcp6Option);
+    }
+
+    /**
+     * Gets deserializer.
+     *
+     * @return the deserializer
+     */
+    public static Deserializer<Dhcp6Option> deserializer() {
+        return (data, offset, length) -> {
+            Dhcp6Option dhcp6Option =
+                    Dhcp6Option.deserializer().deserialize(data, offset, length);
+            if (dhcp6Option.getLength() < DEFAULT_LEN) {
+                throw new DeserializationException("Invalid IA PD option data");
+            }
+            Dhcp6IaPdOption iaPdOption = new Dhcp6IaPdOption(dhcp6Option);
+            byte[] optionData = iaPdOption.getData();
+            ByteBuffer bb = ByteBuffer.wrap(optionData);
+            iaPdOption.iaId = bb.getInt();
+            iaPdOption.t1 = bb.getInt();
+            iaPdOption.t2 = bb.getInt();
+
+            iaPdOption.options = Lists.newArrayList();
+            while (bb.remaining() >= Dhcp6Option.DEFAULT_LEN) {
+                Dhcp6Option option;
+                ByteBuffer optByteBuffer = ByteBuffer.wrap(optionData,
+                                                           bb.position(),
+                                                           optionData.length - bb.position());
+                short code = optByteBuffer.getShort();
+                short len = optByteBuffer.getShort();
+                int optLen = UNSIGNED_SHORT_MASK & len;
+                byte[] subOptData = new byte[Dhcp6Option.DEFAULT_LEN + optLen];
+                bb.get(subOptData);
+
+                // TODO: put more sub-options?
+                if (code == DHCP6.OptionCode.IAPREFIX.value()) {
+                    option = Dhcp6IaPrefixOption.deserializer()
+                            .deserialize(subOptData, 0, subOptData.length);
+                } else {
+                    option = Dhcp6Option.deserializer()
+                            .deserialize(subOptData, 0, subOptData.length);
+                }
+                iaPdOption.options.add(option);
+            }
+            return iaPdOption;
+        };
+    }
+
+    @Override
+    public byte[] serialize() {
+        int payloadLen = DEFAULT_LEN + options.stream()
+                .mapToInt(opt -> (int) opt.getLength() + Dhcp6Option.DEFAULT_LEN)
+                .sum();
+        int len = Dhcp6Option.DEFAULT_LEN + payloadLen;
+        ByteBuffer bb = ByteBuffer.allocate(len);
+        bb.putShort(DHCP6.OptionCode.IA_PD.value());
+        bb.putShort((short) payloadLen);
+        bb.putInt(iaId);
+        bb.putInt(t1);
+        bb.putInt(t2);
+
+        options.stream().map(Dhcp6Option::serialize).forEach(bb::put);
+        return bb.array();
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + Objects.hash(iaId, t1, t2, options);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        final Dhcp6IaPdOption other = (Dhcp6IaPdOption) obj;
+        return Objects.equals(this.iaId, other.iaId)
+                && Objects.equals(this.t1, other.t1)
+                && Objects.equals(this.t2, other.t2)
+                && Objects.equals(this.options, other.options);
+    }
+
+    @Override
+    public String toString() {
+        return getToStringHelper()
+                .add("iaId", iaId)
+                .add("t1", t1)
+                .add("t2", t2)
+                .add("options", options)
+                .toString();
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaPrefixOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaPrefixOption.java
new file mode 100644
index 0000000..997455d
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaPrefixOption.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.DHCP6;
+import org.onlab.packet.Data;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.Ip6Address;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * IA Address option for DHCPv6.
+ * Based on RFC-3633.
+ */
+public final class Dhcp6IaPrefixOption extends Dhcp6Option {
+    public static final int DEFAULT_LEN = 25;
+
+    private Ip6Address ip6Prefix;
+    private byte prefixLength;
+    private int preferredLifetime;
+    private int validLifetime;
+    private IPacket options;
+
+    @Override
+    public short getCode() {
+        return DHCP6.OptionCode.IAPREFIX.value();
+    }
+
+    @Override
+    public short getLength() {
+        return (short) (options == null ? DEFAULT_LEN : DEFAULT_LEN + options.serialize().length);
+    }
+
+    /**
+     * Sets IPv6 prefix.
+     *
+     * @param ip6Prefix the IPv6 prefix
+     */
+    public void setIp6Prefix(Ip6Address ip6Prefix) {
+        this.ip6Prefix = ip6Prefix;
+    }
+
+    /**
+     * Sets prefix length.
+     *
+     * @param prefixLength the prefix length
+     */
+    public void setPrefixLength(byte prefixLength) {
+        this.prefixLength = prefixLength;
+    }
+
+    /**
+     * Sets preferred lifetime.
+     *
+     * @param preferredLifetime the preferred lifetime
+     */
+    public void setPreferredLifetime(int preferredLifetime) {
+        this.preferredLifetime = preferredLifetime;
+    }
+
+    /**
+     * Sets valid lifetime.
+     *
+     * @param validLifetime the valid lifetime
+     */
+    public void setValidLifetime(int validLifetime) {
+        this.validLifetime = validLifetime;
+    }
+
+    /**
+     * Sets options data.
+     *
+     * @param options the options data
+     */
+    public void setOptions(IPacket options) {
+        this.options = options;
+    }
+
+    /**
+     * Gets IPv6 address.
+     *
+     * @return the IPv6 address
+     */
+    public Ip6Address getIp6Prefix() {
+        return ip6Prefix;
+    }
+
+    /**
+     * Gets prefix length.
+     *
+     * @return the prefix length
+     */
+    public byte getPrefixLength() {
+        return prefixLength;
+    }
+
+    /**
+     * Gets preferred lifetime.
+     *
+     * @return the preferred lifetime
+     */
+    public int getPreferredLifetime() {
+        return preferredLifetime;
+    }
+
+    /**
+     * Gets valid lifetime.
+     *
+     * @return the valid lifetime
+     */
+    public int getValidLifetime() {
+        return validLifetime;
+    }
+
+    /**
+     * Gets options of IA Address option.
+     *
+     * @return the options data
+     */
+    public IPacket getOptions() {
+        return options;
+    }
+
+    public static Deserializer<Dhcp6Option> deserializer() {
+        return (data, offset, length) -> {
+            Dhcp6IaPrefixOption iaPrefixOption = new Dhcp6IaPrefixOption();
+            Dhcp6Option dhcp6Option =
+                    Dhcp6Option.deserializer().deserialize(data, offset, length);
+            iaPrefixOption.setPayload(dhcp6Option.getPayload());
+            if (dhcp6Option.getLength() < DEFAULT_LEN) {
+                throw new DeserializationException("Invalid length of IA prefix option");
+            }
+            ByteBuffer bb = ByteBuffer.wrap(dhcp6Option.getData());
+            iaPrefixOption.preferredLifetime = bb.getInt();
+            iaPrefixOption.validLifetime = bb.getInt();
+            iaPrefixOption.prefixLength = bb.get();
+            byte[] ipv6Pref = new byte[Ip6Address.BYTE_LENGTH];
+            bb.get(ipv6Pref);
+            iaPrefixOption.ip6Prefix = Ip6Address.valueOf(ipv6Pref);
+
+            // options length of IA Address option
+            int optionsLen = dhcp6Option.getLength() - DEFAULT_LEN;
+            if (optionsLen > 0) {
+                byte[] optionsData = new byte[optionsLen];
+                bb.get(optionsData);
+                iaPrefixOption.options =
+                        Data.deserializer().deserialize(optionsData, 0, optionsLen);
+            }
+            return iaPrefixOption;
+        };
+    }
+
+    @Override
+    public byte[] serialize() {
+        int payloadLen = options == null ? DEFAULT_LEN : DEFAULT_LEN + options.serialize().length;
+        ByteBuffer bb = ByteBuffer.allocate(payloadLen + Dhcp6Option.DEFAULT_LEN);
+        bb.putShort(DHCP6.OptionCode.IAPREFIX.value());
+        bb.putShort((short) payloadLen);
+        bb.putInt(preferredLifetime);
+        bb.putInt(validLifetime);
+        bb.put(prefixLength);
+        bb.put(ip6Prefix.toOctets());
+        if (options != null) {
+            bb.put(options.serialize());
+        }
+        return bb.array();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), ip6Prefix, prefixLength, preferredLifetime,
+                            validLifetime, options);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof Dhcp6IaAddressOption)) {
+            return false;
+        }
+        final Dhcp6IaPrefixOption other = (Dhcp6IaPrefixOption) obj;
+
+        return Objects.equals(getCode(), other.getCode()) &&
+                Objects.equals(getLength(), other.getLength()) &&
+                Objects.equals(preferredLifetime, other.preferredLifetime) &&
+                Objects.equals(validLifetime, other.validLifetime) &&
+                Objects.equals(prefixLength, other.prefixLength) &&
+                Objects.equals(ip6Prefix, other.ip6Prefix) &&
+                Objects.equals(options, other.options);
+    }
+
+    @Override
+    public String toString() {
+        return getToStringHelper()
+                .add("preferredLifetime", preferredLifetime)
+                .add("validLifetime", validLifetime)
+                .add("prefixLength", prefixLength)
+                .add("ip6Address", ip6Prefix)
+                .add("options", options)
+                .toString();
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6InterfaceIdOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6InterfaceIdOption.java
new file mode 100644
index 0000000..abb8257
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6InterfaceIdOption.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.MacAddress;
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.DeserializationException;
+
+
+import java.nio.ByteBuffer;
+
+/**
+ * Relay option for DHCPv6.
+ * Based on RFC-3315.
+ */
+public final class Dhcp6InterfaceIdOption extends Dhcp6Option {
+    private MacAddress peerMacAddr;
+    private byte[] inPort;
+    @Override
+    public short getCode() {
+        return DHCP6.OptionCode.INTERFACE_ID.value();
+    }
+
+    @Override
+    public short getLength() {
+        return (short) payload.serialize().length;
+    }
+
+    @Override
+    public byte[] getData() {
+        return this.payload.serialize();
+    }
+
+    /**
+     * Default constructor.
+     */
+    public Dhcp6InterfaceIdOption() {
+    }
+
+    /**
+     * Constructs a DHCPv6 relay option with DHCPv6 option.
+     *
+     * @param dhcp6Option the DHCPv6 option
+     */
+    public Dhcp6InterfaceIdOption(Dhcp6Option dhcp6Option) {
+        super(dhcp6Option);
+    }
+
+    /**
+     * Sets MacAddress address.
+     *
+     * @param macAddress the client peer MacAddress
+     */
+    public void setMacAddress(MacAddress macAddress) {
+        this.peerMacAddr = macAddress;
+    }
+
+    /**
+     * Gets Mac address.
+     *
+     * @return the client peer mac address
+     */
+    public MacAddress getMacAddress() {
+        return peerMacAddr;
+    }
+
+    /**
+     * Sets inPort string.
+     *
+     * @param port the port from which client packet is received
+     */
+    public void setInPort(byte[] port) {
+        this.inPort = port;
+    }
+
+    /**
+     * Gets inPort string.
+     *
+     * @return the port from which client packet is received
+     */
+    public byte[] getInPort() {
+        return inPort;
+    }
+
+    /**
+     * Gets deserializer for DHCPv6 relay option.
+     *
+     * @return the deserializer
+     */
+    public static Deserializer<Dhcp6Option> deserializer() {
+        return (data, offset, len) -> {
+            Dhcp6Option dhcp6Option = Dhcp6Option.deserializer().deserialize(data, offset, len);
+            if (dhcp6Option.getLength() < DEFAULT_LEN) {
+                throw new DeserializationException("Invalid InterfaceIoption data");
+            }
+            Dhcp6InterfaceIdOption interfaceIdOption = new Dhcp6InterfaceIdOption(dhcp6Option);
+            byte[] optionData = interfaceIdOption.getData();
+            if (optionData.length >= 28) {
+                ByteBuffer bb = ByteBuffer.wrap(optionData);
+
+                byte[] macAddr = new byte[MacAddress.MAC_ADDRESS_LENGTH];
+                byte[] port = new byte[21];
+                bb.get(macAddr);
+                bb.get();  // separator
+                bb.get(port);
+                interfaceIdOption.setMacAddress(MacAddress.valueOf(macAddr));
+                interfaceIdOption.setInPort(port);
+            }
+            return interfaceIdOption;
+        };
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("code", getCode())
+                .add("length", getLength())
+                .add("data", payload.toString())
+                .toString();
+    }
+}