Supports classless static route option in SONA

Change-Id: I8e45176c13218c0aa81d934832f8c9d2dc5839ca
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java
index 2cc9671..27e8031 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java
@@ -16,6 +16,7 @@
 package org.onosproject.openstacknetworking.impl;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -58,6 +59,7 @@
 import org.onosproject.openstacknode.api.OpenstackNodeEvent;
 import org.onosproject.openstacknode.api.OpenstackNodeListener;
 import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.HostRoute;
 import org.openstack4j.model.network.IP;
 import org.openstack4j.model.network.Port;
 import org.openstack4j.model.network.Subnet;
@@ -70,6 +72,7 @@
 import java.util.Objects;
 
 import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_BroadcastAddress;
+import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_Classless_Static_Route;
 import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_DHCPServerIp;
 import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_DomainServer;
 import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
@@ -102,6 +105,11 @@
             ByteBuffer.allocate(4).putInt(-1).array();
     // we are using 1450 as a default DHCP MTU value
     private static final int DHCP_DATA_MTU_DEFAULT = 1450;
+    private static final int OCTET_BIT_LENGTH = 8;
+    private static final int V4_BYTE_SIZE = 4;
+    private static final int V4_CIDR_LOWER_BOUND = 0;
+    private static final int V4_CIDR_UPPER_BOUND = 33;
+    private static final int PADDING_SIZE = 4;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
@@ -397,6 +405,36 @@
             option.setData(ByteBuffer.allocate(2).putShort((short) dhcpDataMtu).array());
             options.add(option);
 
+            // classless static route
+            if (!osSubnet.getHostRoutes().isEmpty()) {
+                option = new DhcpOption();
+                option.setCode(OptionCode_Classless_Static_Route.getValue());
+
+                int hostRoutesSize = hostRoutesSize(ImmutableList.copyOf(osSubnet.getHostRoutes()));
+                if (hostRoutesSize == 0) {
+                    throw new IllegalArgumentException("Illegal CIDR hostRoutesSize value!");
+                }
+
+                log.trace("hostRouteSize: {}", hostRoutesSize);
+
+                option.setLength((byte) hostRoutesSize);
+                ByteBuffer hostRouteByteBuf = ByteBuffer.allocate(hostRoutesSize);
+
+                osSubnet.getHostRoutes().forEach(h -> {
+                    log.debug("processing host route information: {}", h.toString());
+
+                    IpPrefix ipPrefix = IpPrefix.valueOf(h.getDestination());
+
+                    hostRouteByteBuf.put(bytesDestinationDescriptor(ipPrefix));
+
+                    hostRouteByteBuf.put(Ip4Address.valueOf(h.getNexthop()).toOctets());
+                });
+
+                option.setData(hostRouteByteBuf.array());
+
+                options.add(option);
+            }
+
             // router address
             option = new DhcpOption();
             option.setCode(OptionCode_RouterAddress.getValue());
@@ -413,6 +451,58 @@
             dhcpReply.setOptions(options);
             return dhcpReply;
         }
+
+        private int hostRoutesSize(List<HostRoute> hostRoutes) {
+            int size = 0;
+            int preFixLen;
+
+            for (HostRoute h : hostRoutes) {
+                preFixLen = IpPrefix.valueOf(h.getDestination()).prefixLength();
+                if (Math.max(V4_CIDR_LOWER_BOUND, preFixLen) == V4_CIDR_LOWER_BOUND ||
+                        Math.min(preFixLen, V4_CIDR_UPPER_BOUND) == V4_CIDR_UPPER_BOUND) {
+                    throw new IllegalArgumentException("Illegal CIDR length value!");
+                }
+
+                for (int i = 1; i <= V4_BYTE_SIZE; i++) {
+
+                    if (preFixLen == Math.min(preFixLen, i * OCTET_BIT_LENGTH)) {
+                        size = size + i + 1 + PADDING_SIZE;
+                        break;
+                    }
+                }
+            }
+            return size;
+        }
+
+        private byte[] bytesDestinationDescriptor(IpPrefix ipPrefix) {
+            ByteBuffer byteBuffer;
+            int prefixLen = ipPrefix.prefixLength();
+
+            // retrieve ipPrefix to the destination descriptor format
+            // ex) 10.1.1.0/24 -> [10,1,1,0]
+            String[] ipPrefixString = ipPrefix.getIp4Prefix().toString()
+                    .split("/")[0]
+                    .split("\\.");
+
+            // retrieve destination descriptor and put this to bytebuffer according to 3442
+            // ex) 10.0.0.0/8 -> 8.10
+            // ex) 10.17.0.0/16 -> 16.10.17
+            // ex) 10.27.129.0/24 -> 24.10.27.129
+            // ex) 10.229.0.128/25 -> 25.10.229.0.128
+            for (int i = 1; i <= V4_BYTE_SIZE; i++) {
+                if (prefixLen == Math.min(prefixLen, i * OCTET_BIT_LENGTH)) {
+                    byteBuffer = ByteBuffer.allocate(i + 1);
+                    byteBuffer.put((byte) prefixLen);
+
+                    for (int j = 0; j < i; j++) {
+                        byteBuffer.put((byte) Integer.parseInt(ipPrefixString[j]));
+                    }
+                    return byteBuffer.array();
+                }
+            }
+
+            return null;
+        }
     }
 
     private class InternalNodeEventListener implements OpenstackNodeListener {
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandlerTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandlerTest.java
index 57491da..8737cdc 100644
--- a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandlerTest.java
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandlerTest.java
@@ -16,6 +16,7 @@
 package org.onosproject.openstacknetworking.impl;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -435,7 +436,7 @@
 
             @Override
             public List<? extends HostRoute> getHostRoutes() {
-                return null;
+                return Lists.newArrayList();
             }
 
             @Override