[ONOS-6634] Add method to create NDP replay for ICMP6

Change-Id: Ibc24470072cc6810a428ac9caf1d3343310df80c
diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java
index 96e2fd7..6ca76ed 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java
@@ -29,6 +29,8 @@
 import java.util.List;
 
 import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onlab.packet.PacketUtils.checkInput;
 
 /**
@@ -36,6 +38,11 @@
  */
 public class NeighborAdvertisement extends BasePacket {
     public static final byte HEADER_LENGTH = 20; // bytes
+    // Constants for NDP reply
+    protected static final byte NDP_HOP_LIMIT = (byte) 0x255;
+    protected static final byte NDP_SOLICITED_FLAG = (byte) 0x1;
+    protected static final byte NDP_OVERRIDE_FLAG = (byte) 0x1;
+    protected static final byte RESERVED_CODE = (byte) 0x0;
 
     protected byte routerFlag;
     protected byte solicitedFlag;
@@ -298,7 +305,9 @@
      * @param srcMac  the MAC address to use as the reply source
      * @param request the Neighbor Solicitation request we got
      * @return an Ethernet frame containing the Neighbor Advertisement reply
+     * @deprecated since 1.11.0
      */
+    @Deprecated
     public static Ethernet buildNdpAdv(byte[] srcIp,
                                    byte[] srcMac,
                                    Ethernet request) {
@@ -352,4 +361,61 @@
         return eth;
     }
 
+    /**
+     * Builds an NDP reply based on a request.
+     *
+     * @param srcIp   the IP address to use as the reply source
+     * @param srcMac  the MAC address to use as the reply source
+     * @param request the Neighbor Solicitation request we got
+     * @return an Ethernet frame containing the Neighbor Advertisement reply
+     */
+    public static Ethernet buildNdpAdv(Ip6Address srcIp,
+                                       MacAddress srcMac,
+                                       Ethernet request) {
+
+        checkNotNull(srcIp, "IP address cannot be null");
+        checkNotNull(srcMac, "MAC address cannot be null");
+        checkNotNull(request, "Request cannot be null");
+        checkArgument(request.getEtherType() == Ethernet.TYPE_IPV6,
+                "EtherType must be IPv6");
+
+        final IPv6 ipv6Request = (IPv6) request.getPayload();
+
+        checkArgument(ipv6Request.getNextHeader() == IPv6.PROTOCOL_ICMP6,
+                "Protocol must be ICMP6");
+
+        final ICMP6 icmpv6 = (ICMP6) ipv6Request.getPayload();
+
+        checkArgument(icmpv6.getIcmpType() == ICMP6.NEIGHBOR_SOLICITATION,
+                "ICMP6 type must be NEIGHBOR_SOLICITATION");
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(request.getSourceMAC());
+        eth.setSourceMACAddress(srcMac);
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setVlanID(request.getVlanID());
+
+        IPv6 ipv6 = new IPv6();
+        ipv6.setSourceAddress(srcIp.toOctets());
+        ipv6.setDestinationAddress(ipv6Request.getSourceAddress());
+        ipv6.setHopLimit(NDP_HOP_LIMIT);
+        ipv6.setNextHeader(IPv6.PROTOCOL_ICMP6);
+
+        ICMP6 icmp6 = new ICMP6();
+        icmp6.setIcmpType(ICMP6.NEIGHBOR_ADVERTISEMENT);
+        icmp6.setIcmpCode(RESERVED_CODE);
+
+        NeighborAdvertisement nadv = new NeighborAdvertisement();
+        nadv.setTargetAddress(srcIp.toOctets());
+        nadv.setSolicitedFlag(NDP_SOLICITED_FLAG);
+        nadv.setOverrideFlag(NDP_OVERRIDE_FLAG);
+        nadv.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS,
+                srcMac.toBytes());
+
+        icmp6.setPayload(nadv);
+        ipv6.setPayload(icmp6);
+        eth.setPayload(ipv6);
+        return eth;
+    }
+
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java
index 82071da..05fd414 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java
@@ -30,6 +30,7 @@
 import java.util.List;
 
 import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onlab.packet.PacketUtils.checkInput;
 
 /**
@@ -37,6 +38,9 @@
  */
 public class NeighborSolicitation extends BasePacket {
     public static final byte HEADER_LENGTH = 20; // bytes
+    // Constants for NDP reply
+    protected static final byte NDP_HOP_LIMIT = (byte) 0x255;
+    protected static final byte RESERVED_CODE = (byte) 0x0;
 
     protected byte[] targetAddress = new byte[Ip6Address.BYTE_LENGTH];
 
@@ -214,7 +218,9 @@
      * @param destinationMac the destination mac address
      * @param vlan the vlan id
      * @return the ethernet packet containing the ndp solicitation
+     * @deprecated since 1.11.0
      */
+    @Deprecated
     public static Ethernet buildNdpSolicit(byte[] targetIp,
                                            byte[] sourceIp,
                                            byte[] destinationIp,
@@ -259,4 +265,59 @@
 
         return ethernet;
     }
+
+    /**
+     * Builds a NDP solicitation using the supplied parameters.
+     *
+     * @param targetIp the target ip
+     * @param sourceIp the source ip
+     * @param destinationIp the destination ip
+     * @param sourceMac the source mac address
+     * @param destinationMac the destination mac address
+     * @param vlan the vlan id
+     * @return the ethernet packet containing the ndp solicitation
+     */
+    public static Ethernet buildNdpSolicit(Ip6Address targetIp,
+                                           Ip6Address sourceIp,
+                                           Ip6Address destinationIp,
+                                           MacAddress sourceMac,
+                                           MacAddress destinationMac,
+                                           VlanId vlan) {
+
+        checkNotNull(targetIp, "Target IP address cannot be null");
+        checkNotNull(sourceIp, "Source IP address cannot be null");
+        checkNotNull(destinationIp, "Destination IP address cannot be null");
+        checkNotNull(sourceMac, "Source MAC address cannot be null");
+        checkNotNull(destinationMac, "Destination MAC address cannot be null");
+        checkNotNull(vlan, "Vlan cannot be null");
+
+        // Here we craft the Ethernet packet.
+        Ethernet ethernet = new Ethernet();
+        ethernet.setEtherType(Ethernet.TYPE_IPV6)
+                .setDestinationMACAddress(destinationMac)
+                .setSourceMACAddress(sourceMac);
+        ethernet.setVlanID(vlan.id());
+        // IPv6 packet is created.
+        IPv6 ipv6 = new IPv6();
+        ipv6.setSourceAddress(sourceIp.toOctets());
+        ipv6.setDestinationAddress(destinationIp.toOctets());
+        ipv6.setHopLimit(NDP_HOP_LIMIT);
+        // Create the ICMPv6 packet.
+        ICMP6 icmp6 = new ICMP6();
+        icmp6.setIcmpType(ICMP6.NEIGHBOR_SOLICITATION);
+        icmp6.setIcmpCode(RESERVED_CODE);
+        // Create the Neighbor Solicitation packet.
+        NeighborSolicitation ns = new NeighborSolicitation();
+        ns.setTargetAddress(targetIp.toOctets());
+        // DAD packets should not contain SRC_LL_ADDR option
+        if (!Arrays.equals(sourceIp.toOctets(), Ip6Address.ZERO.toOctets())) {
+            ns.addOption(NeighborDiscoveryOptions.TYPE_SOURCE_LL_ADDRESS, sourceMac.toBytes());
+        }
+        // Set the payloads
+        icmp6.setPayload(ns);
+        ipv6.setPayload(icmp6);
+        ethernet.setPayload(ipv6);
+
+        return ethernet;
+    }
 }
diff --git a/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java b/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java
index ac00096..db34514 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java
@@ -16,16 +16,24 @@
 package org.onlab.packet.ndp;
 
 import org.apache.commons.lang3.StringUtils;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.onlab.packet.DeserializationException;
 import org.onlab.packet.Deserializer;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP6;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.Ip6Address;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.PacketTestUtils;
 
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.core.IsCollectionContaining.hasItem;
 import static org.junit.Assert.*;
 
 /**
@@ -39,7 +47,18 @@
         (byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce
     };
     private static final MacAddress MAC_ADDRESS =
-        MacAddress.valueOf("11:22:33:44:55:66");
+            MacAddress.valueOf("11:22:33:44:55:66");
+    private static final MacAddress MAC_ADDRESS2 =
+            MacAddress.valueOf("10:20:30:40:50:60");
+    private static final byte[] IPV6_SOURCE_ADDRESS = {
+            (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01
+    };
+    private static final byte[] IPV6_DESTINATION_ADDRESS = {
+            (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02
+    };
+    private static final Ip6Address IP_6_ADDRESS = Ip6Address.valueOf(IPV6_DESTINATION_ADDRESS);
 
     private static byte[] bytePacket;
 
@@ -149,4 +168,86 @@
         assertTrue(StringUtils.contains(str, "overrideFlag=" + (byte) 1));
         // TODO: need to handle TARGET_ADDRESS
     }
+
+    /**
+     * Test Neighbor Advertisement reply build.
+     */
+    @Test
+    public void testBuildNdpAdv() {
+        Ethernet eth = new Ethernet();
+        eth.setSourceMACAddress(MAC_ADDRESS);
+        eth.setDestinationMACAddress(MAC_ADDRESS2);
+
+        IPv6 ipv6 = new IPv6();
+        ipv6.setSourceAddress(IPV6_SOURCE_ADDRESS);
+        ipv6.setDestinationAddress(IPV6_DESTINATION_ADDRESS);
+        ipv6.setNextHeader(IPv6.PROTOCOL_ICMP6);
+
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setPayload(ipv6);
+
+        ICMP6 icmp6 = new ICMP6();
+        icmp6.setIcmpType(ICMP6.NEIGHBOR_SOLICITATION);
+        icmp6.setIcmpCode(NeighborAdvertisement.RESERVED_CODE);
+        ipv6.setPayload(icmp6);
+
+        final Ethernet ethResponse = NeighborAdvertisement.buildNdpAdv(IP_6_ADDRESS, MAC_ADDRESS2, eth);
+
+        assertTrue(ethResponse.getDestinationMAC().equals(MAC_ADDRESS));
+        assertTrue(ethResponse.getSourceMAC().equals(MAC_ADDRESS2));
+        assertTrue(ethResponse.getEtherType() == Ethernet.TYPE_IPV6);
+
+        final IPv6 responseIpv6 = (IPv6) ethResponse.getPayload();
+
+        assertArrayEquals(responseIpv6.getSourceAddress(), ipv6.getDestinationAddress());
+        assertArrayEquals(responseIpv6.getDestinationAddress(), ipv6.getSourceAddress());
+        assertTrue(responseIpv6.getNextHeader() == IPv6.PROTOCOL_ICMP6);
+
+        final ICMP6 responseIcmp6 = (ICMP6) responseIpv6.getPayload();
+
+        assertTrue(responseIcmp6.getIcmpType() == ICMP6.NEIGHBOR_ADVERTISEMENT);
+        assertTrue(responseIcmp6.getIcmpCode() == NeighborAdvertisement.RESERVED_CODE);
+
+        final NeighborAdvertisement responseNadv = (NeighborAdvertisement) responseIcmp6.getPayload();
+
+        assertArrayEquals(responseNadv.getTargetAddress(), IPV6_DESTINATION_ADDRESS);
+        assertTrue(responseNadv.getSolicitedFlag() == NeighborAdvertisement.NDP_SOLICITED_FLAG);
+        assertTrue(responseNadv.getOverrideFlag() == NeighborAdvertisement.NDP_OVERRIDE_FLAG);
+        assertThat(responseNadv.getOptions(),
+                hasItem(hasOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, MAC_ADDRESS2.toBytes())));
+    }
+
+    private NeighborDiscoveryOptionMatcher hasOption(byte type, byte[] data) {
+        return new NeighborDiscoveryOptionMatcher(type, data);
+    }
+
+    private static class NeighborDiscoveryOptionMatcher extends TypeSafeMatcher<NeighborDiscoveryOptions.Option> {
+
+        private final byte type;
+        private final byte[] data;
+        private String reason = "";
+
+        NeighborDiscoveryOptionMatcher(byte type, byte[] data) {
+            this.type = type;
+            this.data = data;
+        }
+
+        @Override
+        protected boolean matchesSafely(NeighborDiscoveryOptions.Option option) {
+            if (type != option.type()) {
+                reason = "Wrong Option type";
+                return false;
+            }
+            if (!Arrays.equals(data, option.data())) {
+                reason = "Wrong Option data";
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(reason);
+        }
+    }
 }
diff --git a/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java b/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java
index e01db27..abcd20e 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java
@@ -158,12 +158,23 @@
      */
     @Test
     public void testBuildNdpSolicit() throws Exception {
-        Ethernet ethPacket = NeighborSolicitation.buildNdpSolicit(TARGET_IP.toOctets(),
-                SRC_IP.toOctets(), DST_IP.toOctets(),
-                SRC_MAC.toBytes(), DST_MAC.toBytes(), VLAN_ID);
-        IPv6 ipPacket = (IPv6) ethPacket.getPayload();
-        ICMP6 icmp6Packet = (ICMP6) ipPacket.getPayload();
-        NeighborSolicitation nsPacket = (NeighborSolicitation) icmp6Packet.getPayload();
+        final Ethernet ethPacket = NeighborSolicitation.buildNdpSolicit(TARGET_IP,
+                SRC_IP, DST_IP, SRC_MAC, DST_MAC, VLAN_ID);
+
+        assertTrue(ethPacket.getDestinationMAC().equals(DST_MAC));
+        assertTrue(ethPacket.getSourceMAC().equals(SRC_MAC));
+        assertTrue(ethPacket.getEtherType() == Ethernet.TYPE_IPV6);
+        assertTrue(ethPacket.getVlanID() == VLAN_ID.id());
+
+        final IPv6 ipPacket = (IPv6) ethPacket.getPayload();
+
+        assertArrayEquals(ipPacket.getSourceAddress(), SRC_IP.toOctets());
+        assertArrayEquals(ipPacket.getDestinationAddress(), DST_IP.toOctets());
+
+        final ICMP6 icmp6Packet = (ICMP6) ipPacket.getPayload();
+        final NeighborSolicitation nsPacket = (NeighborSolicitation) icmp6Packet.getPayload();
+
+        assertArrayEquals(nsPacket.getTargetAddress(), TARGET_IP.toOctets());
 
         assertEquals("Non-DAD NS should have 1 option", 1, nsPacket.getOptions().size());
         assertEquals("The option should be SRC_LL_ADDR type", TYPE_SOURCE_LL_ADDRESS,
@@ -176,9 +187,8 @@
      */
     @Test
     public void testBuildNdpSolicitDad() throws Exception {
-        Ethernet ethPacket = NeighborSolicitation.buildNdpSolicit(TARGET_IP.toOctets(),
-                Ip6Address.ZERO.toOctets(), DST_IP.toOctets(),
-                SRC_MAC.toBytes(), DST_MAC.toBytes(), VLAN_ID);
+        Ethernet ethPacket = NeighborSolicitation.buildNdpSolicit(TARGET_IP,
+                Ip6Address.ZERO, DST_IP, SRC_MAC, DST_MAC, VLAN_ID);
         IPv6 ipPacket = (IPv6) ethPacket.getPayload();
         ICMP6 icmp6Packet = (ICMP6) ipPacket.getPayload();
         NeighborSolicitation nsPacket = (NeighborSolicitation) icmp6Packet.getPayload();