[ONOS-6469] Allow to set port range when creating security group rules.

Change-Id: Icaab223303478873ea6764964efb96acdada3789
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java
index 7664429..5909faf 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java
@@ -17,6 +17,8 @@
 package org.onosproject.openstacknetworking.impl;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -38,7 +40,6 @@
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficSelector;
-import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.openstacknetworking.api.InstancePort;
 import org.onosproject.openstacknetworking.api.InstancePortEvent;
 import org.onosproject.openstacknetworking.api.InstancePortListener;
@@ -59,6 +60,7 @@
 
 import java.util.Collections;
 import java.util.Dictionary;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -67,7 +69,6 @@
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.openstacknetworking.api.Constants.ACL_TABLE;
-import static org.onosproject.openstacknetworking.api.Constants.JUMP_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ACL_RULE;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -177,6 +178,7 @@
     }
 
     private void updateSecurityGroupRule(InstancePort instPort, Port port, SecurityGroupRule sgRule, boolean install) {
+
         if (sgRule.getRemoteGroupId() != null && !sgRule.getRemoteGroupId().isEmpty()) {
             getRemoteInstPorts(port.getTenantId(), sgRule.getRemoteGroupId())
                 .forEach(rInstPort -> {
@@ -197,24 +199,21 @@
 
     private void populateSecurityGroupRule(SecurityGroupRule sgRule, InstancePort instPort,
                                            IpPrefix remoteIp, boolean install) {
-        Ip4Address vmIp = Ip4Address.valueOf(instPort.ipAddress().toInetAddress());
-        if (remoteIp != null && remoteIp.equals(IpPrefix.valueOf(vmIp, 32))) {
-            // do nothing if the remote IP is my IP
+        Set<TrafficSelector> selectors = buildSelectors(sgRule,
+                Ip4Address.valueOf(instPort.ipAddress().toInetAddress()), remoteIp);
+        if (selectors == null || selectors.isEmpty()) {
             return;
         }
 
-        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-        buildMatchs(sBuilder, sgRule, vmIp, remoteIp);
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder().transition(JUMP_TABLE).build();
-
-        osFlowRuleService.setRule(appId,
-                instPort.deviceId(),
-                sBuilder.build(),
-                treatment,
-                PRIORITY_ACL_RULE,
-                ACL_TABLE,
-                install);
+        selectors.forEach(selector -> {
+            osFlowRuleService.setRule(appId,
+                    instPort.deviceId(),
+                    selector,
+                    DefaultTrafficTreatment.builder().build(),
+                    PRIORITY_ACL_RULE,
+                    ACL_TABLE,
+                    install);
+        });
     }
 
     /**
@@ -238,14 +237,57 @@
         return Collections.unmodifiableSet(remoteInstPorts);
     }
 
+    private Set<TrafficSelector> buildSelectors(SecurityGroupRule sgRule,
+                                                Ip4Address vmIp,
+                                                IpPrefix remoteIp) {
+        if (remoteIp != null && remoteIp.equals(IpPrefix.valueOf(vmIp, 32))) {
+            // do nothing if the remote IP is my IP
+            return null;
+        }
+
+        Set<TrafficSelector> selectorSet = Sets.newHashSet();
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        buildMatchs(sBuilder, sgRule, vmIp, remoteIp);
+
+        if (sgRule.getPortRangeMax() != null && sgRule.getPortRangeMin() != null &&
+                sgRule.getPortRangeMin() < sgRule.getPortRangeMax()) {
+            Map<TpPort, TpPort> portRangeMatchMap = buildPortRangeMatches(sgRule.getPortRangeMin(),
+                    sgRule.getPortRangeMax());
+            portRangeMatchMap.entrySet().forEach(entry -> {
+
+                if (sgRule.getProtocol().toUpperCase().equals(PROTO_TCP)) {
+                    if (sgRule.getDirection().toUpperCase().equals(EGRESS)) {
+                        sBuilder.matchTcpSrcMasked(entry.getKey(), entry.getValue());
+                    } else {
+                        sBuilder.matchTcpDstMasked(entry.getKey(), entry.getValue());
+                    }
+                } else if (sgRule.getProtocol().toUpperCase().equals(PROTO_UDP)) {
+                    if (sgRule.getDirection().toUpperCase().equals(EGRESS)) {
+                        sBuilder.matchUdpSrcMasked(entry.getKey(), entry.getValue());
+                    } else {
+                        sBuilder.matchUdpDstMasked(entry.getKey(), entry.getValue());
+                    }
+                }
+
+                selectorSet.add(sBuilder.build());
+                }
+            );
+        } else {
+            selectorSet.add(sBuilder.build());
+        }
+
+        return selectorSet;
+    }
+
     private void buildMatchs(TrafficSelector.Builder sBuilder, SecurityGroupRule sgRule,
                              Ip4Address vmIp, IpPrefix remoteIp) {
         buildMatchEthType(sBuilder, sgRule.getEtherType());
         buildMatchDirection(sBuilder, sgRule.getDirection(), vmIp);
         buildMatchProto(sBuilder, sgRule.getProtocol());
         buildMatchPort(sBuilder, sgRule.getProtocol(), sgRule.getDirection(),
-                sgRule.getPortRangeMax() == null ? 0 : sgRule.getPortRangeMax(),
-                sgRule.getPortRangeMin() == null ? 0 : sgRule.getPortRangeMin());
+                sgRule.getPortRangeMin() == null ? 0 : sgRule.getPortRangeMin(),
+                sgRule.getPortRangeMax() == null ? 0 : sgRule.getPortRangeMax());
         buildMatchRemoteIp(sBuilder, remoteIp, sgRule.getDirection());
         if (sgRule.getRemoteGroupId() != null && sgRule.getRemoteGroupId().isEmpty()) {
             buildMatchRemoteIp(sBuilder, remoteIp, sgRule.getDirection());
@@ -354,6 +396,85 @@
                 });
     }
 
+    private int binLower(String binStr, int bits) {
+        String outBin = binStr.substring(0, 16 - bits);
+        for (int i = 0; i < bits; i++) {
+            outBin += "0";
+        }
+
+        return Integer.parseInt(outBin, 2);
+    }
+
+    private int binHigher(String binStr, int bits) {
+        String outBin = binStr.substring(0, 16 - bits);
+        for (int i = 0; i < bits; i++) {
+            outBin += "1";
+        }
+
+        return Integer.parseInt(outBin, 2);
+    }
+
+    private int testMasks(String binStr, int start, int end) {
+        int mask = 0;
+        for (; mask <= 16; mask++) {
+            int maskStart = binLower(binStr, mask);
+            int maskEnd = binHigher(binStr, mask);
+            if (maskStart < start || maskEnd > end) {
+                return mask - 1;
+            }
+        }
+
+        return mask;
+    }
+
+    private String getMask(int bits) {
+        switch (bits) {
+            case 0:  return "ffff";
+            case 1:  return "fffe";
+            case 2:  return "fffc";
+            case 3:  return "fff8";
+            case 4:  return "fff0";
+            case 5:  return "ffe0";
+            case 6:  return "ffc0";
+            case 7:  return "ff80";
+            case 8:  return "ff00";
+            case 9:  return "fe00";
+            case 10: return "fc00";
+            case 11: return "f800";
+            case 12: return "f000";
+            case 13: return "e000";
+            case 14: return "c000";
+            case 15: return "8000";
+            case 16: return "0000";
+            default: return null;
+        }
+    }
+
+    private Map<TpPort, TpPort> buildPortRangeMatches(int portMin, int portMax) {
+
+        boolean processing = true;
+        int start = portMin;
+        Map<TpPort, TpPort> portMaskMap = Maps.newHashMap();
+        while (processing) {
+            String minStr = Integer.toBinaryString(start);
+            String binStrMinPadded = "0000000000000000".substring(minStr.length()) + minStr;
+
+            int mask = testMasks(binStrMinPadded, start, portMax);
+            int maskStart = binLower(binStrMinPadded, mask);
+            int maskEnd = binHigher(binStrMinPadded, mask);
+
+            log.debug("start : {} port/mask = {} / {} ", start, getMask(mask), maskStart);
+            portMaskMap.put(TpPort.tpPort(maskStart), TpPort.tpPort(Integer.parseInt(getMask(mask), 16)));
+
+            start = maskEnd + 1;
+            if (start > portMax) {
+                processing = false;
+            }
+        }
+
+        return portMaskMap;
+    }
+
     private class InternalInstancePortListener implements InstancePortListener {
 
         @Override