[GitHub] [commons-net] Stzx commented on a change in pull request #41: [NET-405] Support for IPv6 in SubnetUtils

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[GitHub] [commons-net] Stzx commented on a change in pull request #41: [NET-405] Support for IPv6 in SubnetUtils

GitBox
Stzx commented on a change in pull request #41: [NET-405] Support for IPv6 in SubnetUtils
URL: https://github.com/apache/commons-net/pull/41#discussion_r324953247
 
 

 ##########
 File path: src/main/java/org/apache/commons/net/util/SubnetUtils.java
 ##########
 @@ -16,348 +16,344 @@
  */
 package org.apache.commons.net.util;
 
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
- * A class that performs some subnet calculations given a network address and a subnet mask.
- * @see "http://www.faqs.org/rfcs/rfc1519.html"
+ * This class that performs some subnet calculations given IP address in CIDR-notation.
+ * <p>For IPv4 address subnet, especially Classless Inter-Domain Routing (CIDR),
+ * refer to <a href="https://tools.ietf.org/html/rfc4632">RFC4632</a>.</p>
+ * <p>For IPv6 address subnet, refer to <a href="https://tools.ietf.org/html/rfc4291#section-2.3">
+ * Section 2.3 of RFC 4291</a>.</p>
+ *
  * @since 2.0
  */
-public class SubnetUtils {
-
-    private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})";
-    private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,2})"; // 0 -> 32
-    private static final Pattern addressPattern = Pattern.compile(IP_ADDRESS);
-    private static final Pattern cidrPattern = Pattern.compile(SLASH_FORMAT);
-    private static final int NBITS = 32;
-
-    private final int netmask;
-    private final int address;
-    private final int network;
-    private final int broadcast;
-
-    /** Whether the broadcast/network address are included in host count */
-    private boolean inclusiveHostCount = false;
-
+public class SubnetUtils
+{
+
+    private static final String IPV4_ADDRESS = "(\\d{1,3}\\.){3}\\d{1,3}/\\d{1,2}";
+    private static final String IPV6_ADDRESS = "(([0-9a-f]{1,4}:){7}[0-9a-f]{1,4}|"
+                                               + "([0-9a-f]{1,4}:){1,7}:|"
+                                               + "([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}|"
+                                               + "([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}|"
+                                               + "([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}|"
+                                               + "([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}|"
+                                               + "([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}|"
+                                               + "[0-9a-f]{1,4}:((:[0-9a-f]{1,4}){1,6})|"
+                                               + ":((:[0-9a-f]{1,4}){1,7}|:))/\\d{1,3}";
+
+    private final SubnetInfo subnetInfo;
 
     /**
-     * Constructor that takes a CIDR-notation string, e.g. "192.168.0.1/16"
-     * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16"
+     * Constructor that creates subnet summary information based on the provided IPv4 or IPv6 address in CIDR-notation,
+     * e.g. "192.168.0.1/16" or "2001:db8:0:0:0:ff00:42:8329/46"
+     * <p>
+     * NOTE: IPv6 address does NOT allow to omit consecutive sections of zeros in the current version.
+     *
+     * @param cidrNotation IPv4 or IPv6 address, e.g. "192.168.0.1/16" or "2001:db8:0:0:0:ff00:42:8329/46"
      * @throws IllegalArgumentException if the parameter is invalid,
-     * i.e. does not match n.n.n.n/m where n=1-3 decimal digits, m = 1-2 decimal digits in range 0-32
+     * e.g. does not match either n.n.n.n/m where n = 1-3 decimal digits, m = 1-2 decimal digits in range 0-32; or
+     * n:n:n:n:n:n:n:n/m n = 1-4 hexadecimal digits, m = 1-3 decimal digits in range 0-128.
      */
-    public SubnetUtils(String cidrNotation) {
-      Matcher matcher = cidrPattern.matcher(cidrNotation);
-
-      if (matcher.matches()) {
-          this.address = matchAddress(matcher);
-
-          /* Create a binary netmask from the number of bits specification /x */
-
-          int trailingZeroes = NBITS - rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS);
-          /*
-           * An IPv4 netmask consists of 32 bits, a contiguous sequence
-           * of the specified number of ones followed by all zeros.
-           * So, it can be obtained by shifting an unsigned integer (32 bits) to the left by
-           * the number of trailing zeros which is (32 - the # bits specification).
-           * Note that there is no unsigned left shift operator, so we have to use
-           * a long to ensure that the left-most bit is shifted out correctly.
-           */
-          this.netmask = (int) (0x0FFFFFFFFL << trailingZeroes );
-
-          /* Calculate base network address */
-          this.network = (address & netmask);
-
-          /* Calculate broadcast address */
-          this.broadcast = network | ~(netmask);
-      } else {
-          throw new IllegalArgumentException("Could not parse [" + cidrNotation + "]");
-      }
+    public SubnetUtils(String cidrNotation)
+    {
+        subnetInfo = getByCIDRNotation(cidrNotation);
     }
 
     /**
-     * Constructor that takes a dotted decimal address and a dotted decimal mask.
-     * @param address An IP address, e.g. "192.168.0.1"
-     * @param mask A dotted decimal netmask e.g. "255.255.0.0"
+     * Constructor that creates IPv4 subnet summary information, given a dotted decimal address and mask.
+     *
+     * @param address an IP address, e.g. "192.168.0.1"
+     * @param mask a dotted decimal netmask e.g. "255.255.0.0"
      * @throws IllegalArgumentException if the address or mask is invalid,
-     * i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros
+     * e.g. the address does not match n.n.n.n where n=1-3 decimal digits, or
+     * the mask does not match n.n.n.n which n={0, 128, 192, 224, 240, 248, 252, 254, 255} and after the 0-field, it is all zeros.
      */
-    public SubnetUtils(String address, String mask) {
-        this.address = toInteger(address);
-        this.netmask = toInteger(mask);
-
-        if ((this.netmask & -this.netmask) - 1 != ~this.netmask) {
-            throw new IllegalArgumentException("Could not parse [" + mask + "]");
-        }
-
-        /* Calculate base network address */
-        this.network = (this.address & this.netmask);
-
-        /* Calculate broadcast address */
-        this.broadcast = this.network | ~(this.netmask);
+    public SubnetUtils(String address, String mask)
+    {
+        subnetInfo = new IP4Subnet(address, mask);
     }
 
-
     /**
-     * Returns <code>true</code> if the return value of {@link SubnetInfo#getAddressCount()}
+     * Returns {@code true} if the return value of {@link SubnetInfo#getAddressCountLong() getAddressCountLong}
      * includes the network and broadcast addresses.
+     *
+     * @return {@code true} if the host count includes the network and broadcast addresses
      * @since 2.2
-     * @return true if the host count includes the network and broadcast addresses
      */
-    public boolean isInclusiveHostCount() {
-        return inclusiveHostCount;
+    public boolean isInclusiveHostCount()
+    {
+        return subnetInfo.isInclusiveHostCount();
     }
 
     /**
-     * Set to <code>true</code> if you want the return value of {@link SubnetInfo#getAddressCount()}
+     * Set to {@code true} if you want the return value of {@link SubnetInfo#getAddressCountLong() getAddressCountLong}
      * to include the network and broadcast addresses.
-     * @param inclusiveHostCount true if network and broadcast addresses are to be included
+     *
+     * @param inclusiveHostCount {@code true} if network and broadcast addresses are to be included
      * @since 2.2
      */
-    public void setInclusiveHostCount(boolean inclusiveHostCount) {
-        this.inclusiveHostCount = inclusiveHostCount;
+    public void setInclusiveHostCount(boolean inclusiveHostCount)
+    {
+        subnetInfo.setInclusiveHostCount(inclusiveHostCount);
     }
 
-
+    /**
+     * Creates subnet summary information based on the provided IPv4 or IPv6 address in CIDR-notation,
+     * e.g. "192.168.0.1/16" or "2001:db8:0:0:0:ff00:42:8329/46"
+     * <p>
+     * NOTE: IPv6 address does NOT allow to omit consecutive sections of zeros in the current version.
+     *
+     * @param cidrNotation IPv4 or IPv6 address
+     * @return a {@link SubnetInfo SubnetInfo} object created from the IP address.
+     * @since 3.7
+     */
+    public static SubnetInfo getByCIDRNotation(String cidrNotation)
+    {
+        if (Pattern.matches(IPV4_ADDRESS, cidrNotation))
+        {
+            return new IP4Subnet(cidrNotation);
+        } else if (Pattern.matches(IPV6_ADDRESS, cidrNotation))
+        {
+            return new IP6Subnet(cidrNotation);
+        } else
+        {
+            throw new IllegalArgumentException("Could not parse [" + cidrNotation + "]");
+        }
+    }
 
     /**
-     * Convenience container for subnet summary information.
+     * Creates IPv4 subnet summary information, given a dotted decimal address and mask.
      *
+     * @param address an IP address, e.g. "192.168.0.1"
+     * @param mask a dotted decimal netmask e.g. "255.255.0.0"
+     * @return an {@link IP4Subnet} object generated based on {@code address} and {@code mask}.
+     * @throws IllegalArgumentException if the address or mask is invalid,
+     * e.g. the address does not match n.n.n.n where n=1-3 decimal digits, or
+     * the mask does not match n.n.n.n which n={0, 128, 192, 224, 240, 248, 252, 254, 255} and after the 0-field, it is all zeros.
+     * @since 3.7
      */
-    public final class SubnetInfo {
-        /* Mask to convert unsigned int to a long (i.e. keep 32 bits) */
-        private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL;
+    public static IP4Subnet getByMask(String address, String mask)
+    {
+        return new IP4Subnet(address, mask);
+    }
 
-        private SubnetInfo() {}
+    /**
+     * Convenience container for subnet summary information.
+     */
+    public static class SubnetInfo
+    {
 
-        // long versions of the values (as unsigned int) which are more suitable for range checking
-        private long networkLong()  { return network &  UNSIGNED_INT_MASK; }
-        private long broadcastLong(){ return broadcast &  UNSIGNED_INT_MASK; }
+        /*
+         * Convenience function to check integer boundaries.
+         * Checks if a value x is in the range [begin,end].
+         * Returns x if it is in range, throws an exception otherwise.
+         */
+        static int rangeCheck(int value, int begin, int end)
+        {
+            if (value < begin || value > end)
+            {
+                throw new IllegalArgumentException("Value [" + value + "] not in range [" + begin + "," + end + "]");
+            }
 
-        private int low() {
-            return (isInclusiveHostCount() ? network :
-                broadcastLong() - networkLong() > 1 ? network + 1 : 0);
+            return value;
         }
 
-        private int high() {
-            return (isInclusiveHostCount() ? broadcast :
-                broadcastLong() - networkLong() > 1 ? broadcast -1  : 0);
+        /*
+         * Count the number of 1-bits in a 32-bit integer using a divide-and-conquer strategy see Hacker's Delight section 5.1
+         */
+        static int pop(int x)
+        {
+            x = x - ((x >>> 1) & 0x55555555);
+            x = (x & 0x33333333) + ((x >>> 2) & 0x33333333);
+            x = (x + (x >>> 4)) & 0x0F0F0F0F;
+            x = x + (x >>> 8);
+            x = x + (x >>> 16);
+            return x & 0x3F;
         }
 
         /**
-         * Returns true if the parameter <code>address</code> is in the
-         * range of usable endpoint addresses for this subnet. This excludes the
-         * network and broadcast addresses.
-         * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1"
-         * @return True if in range, false otherwise
+         * Converts a dotted decimal format address to a packed integer format. (ONLY USE in IPv4)
+         *
+         * @param address a dotted decimal format address
+         * @return a packed integer of a dotted decimal format address
          */
-        public boolean isInRange(String address) {
-            return isInRange(toInteger(address));
-        }
+        public int asInteger(String address) { return 0; }
 
         /**
-         * Returns true if the parameter <code>address</code> is in the
-         * range of usable endpoint addresses for this subnet. This excludes the
-         * network and broadcast addresses.
+         * Returns {@code true} if the return value of {@link #getAddressCountLong() getAddressCountLong}
+         * includes the network and broadcast addresses. (ONLY USE in IPv4)
+         *
+         * @return {@code true} if the host count includes the network and broadcast addresses
+         */
+        public boolean isInclusiveHostCount() { return false; }
+
+        /**
+         * Sets to {@code true} if you want the return value of {@link #getAddressCountLong() getAddressCountLong}
+         * to include the network and broadcast addresses. (ONLY USE in IPv4)
+         *
+         * @param inclusiveHostCount {@code true} if network and broadcast addresses are to be included
+         */
+        public void setInclusiveHostCount(boolean inclusiveHostCount) {}
+
+        /**
+         * Returns {@code true} if the parameter {@code address} is in the range of usable endpoint addresses for this subnet.
+         * This excludes the network and broadcast addresses if the address is IPv4 address.
+         *
+         * @param address a dot-delimited IPv4 address, e.g. "192.168.0.1", or
+         * a colon-hexadecimal IPv6 address, e.g. "2001:db8::ff00:42:8329"
+         * @return {@code true} if in range, {@code false} otherwise
+         */
+        public boolean isInRange(String address) { return false; }
+
+        /**
+         * Returns {@code true} if the parameter {@code address} is in the range of usable endpoint addresses for this subnet.
+         * This excludes the network and broadcast addresses if the address is IPv4 address.
+         *
          * @param address the address to check
-         * @return true if it is in range
-         * @since 3.4 (made public)
+         * @return {@code true} if it is in range
          */
-        public boolean isInRange(int address) {
-            if (address == 0) { // cannot ever be in range; rejecting now avoids problems with CIDR/31,32
-                return false;
-            }
-            long addLong = address & UNSIGNED_INT_MASK;
-            long lowLong = low() & UNSIGNED_INT_MASK;
-            long highLong = high() & UNSIGNED_INT_MASK;
-            return addLong >= lowLong && addLong <= highLong;
-        }
+        public boolean isInRange(int address) { return false; }
 
-        public String getBroadcastAddress() {
-            return format(toArray(broadcast));
-        }
+        /**
+         * Returns {@code true} if the parameter {@code address} is in the range of usable endpoint addresses for this subnet.
+         *
+         * @param address the address to check
+         * @return {@code true} if it is in range
+         */
+        public boolean isInRange(int[] address) { return false; }
 
-        public String getNetworkAddress() {
-            return format(toArray(network));
-        }
+        /**
+         * Returns the IP address.
+         * <ul style="list-style-type: none">
+         * <li>IPv4 format: a dot-decimal format, e.g. "192.168.0.1"</li>
+         * <li>IPv6 format: a colon-hexadecimal format, e.g. "2001:db8::ff00:42:8329"</li>
+         * </ul>
+         *
+         * @return a string of the IP address
+         */
+        public String getAddress() { return null; }
 
-        public String getNetmask() {
-            return format(toArray(netmask));
-        }
+        /**
+         * Returns the CIDR suffixes, the count of consecutive 1 bits in the subnet mask.
+         * The range in IPv4 is 0-32, and in IPv6 is 0-128, actually 64 or less.
+         *
+         * @return the CIDR suffixes of the address in an integer.
+         */
+        public int getCIDR() { return 0; }
 
-        public String getAddress() {
-            return format(toArray(address));
-        }
+        /**
+         * Returns a netmask in the address. (ONLY USE IPv4)
+         *
+         * @return a string of netmask in a dot-decimal format.
+         */
+        public String getNetmask() { return null; }
 
         /**
-         * Return the low address as a dotted IP address.
-         * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
+         * Returns a network address in the address. (ONLY USE IPv4)
          *
-         * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address
+         * @return a string of a network address in a dot-decimal format.
          */
-        public String getLowAddress() {
-            return format(toArray(low()));
-        }
+        public String getNetworkAddress() { return null; }
+
+        /**
+         * Returns a broadcast address in the address. (ONLY USE IPv4)
+         *
+         * @return a string of a broadcast address in a dot-decimal format.
+         */
+        public String getBroadcastAddress() { return null; }
+
+        /**
+         * Returns a CIDR notation, in which the address is followed by slash and
+         * the count of counting the 1-bit population in the subnet mask.
+         * <ul style="list-style-type: none">
+         * <li>IPv4 format: a dot-decimal format, e.g. "192.168.0.1"</li>
+         * <li>IPv6 format: a colon-hexadecimal format, e.g. "2001:db8::ff00:42:8329"</li>
+         * </ul>
+         *
+         * @return the CIDR notation of the address
+         */
+        public String getCIDRNotation() { return null; }
 
         /**
-         * Return the high address as a dotted IP address.
-         * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
+         * Returns a CIDR notation, in which the address is followed by slash and
+         * the count of counting the 1-bit population in the subnet mask.
+         * <ul style="list-style-type: none">
+         * <li>IPv4 format: a dot-decimal format, e.g. "192.168.0.1"</li>
+         * <li>IPv6 format: a colon-hexadecimal format, e.g. "2001:db8::ff00:42:8329"</li>
+         * </ul>
          *
-         * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address
+         * @return the CIDR notation of the address
          */
-        public String getHighAddress() {
-            return format(toArray(high()));
+        public String getCidrSignature()
+        {
+            return getCIDRNotation();
         }
 
+        /**
+         * Returns the lowest address as a dotted decimal or the colon-separated hexadecimal IP address.
+         * Will be zero for CIDR/31 and CIDR/32 if the address is IPv4 address and the {@code inclusiveHostCount} flag is {@code false}.
+         *
+         * @return the IP address in dotted or colon 16-bit delimited format, may be "0.0.0.0" or "::" if there is no valid address
+         */
+        public String getLowAddress() { return null; }
+
+        /**
+         * Returns the highest address as the dotted decimal or the colon-separated hexadecimal IP address.
+         * Will be zero for CIDR/31 and CIDR/32 if the address is IPv4 address and the {@code inclusiveHostCount} flag is {@code false}.
+         *
+         * @return the IP address in dotted or colon 16-bit delimited format, may be "0.0.0.0" or "::" if there is no valid address
+         */
+        public String getHighAddress() { return null; }
+
         /**
          * Get the count of available addresses.
-         * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
+         * Will be zero for CIDR/31 and CIDR/32 if the {@code inclusiveHostCount} flag is {@code false}.
+         *
          * @return the count of addresses, may be zero.
          * @throws RuntimeException if the correct count is greater than {@code Integer.MAX_VALUE}
          * @deprecated (3.4) use {@link #getAddressCountLong()} instead
          */
         @Deprecated
-        public int getAddressCount() {
+        public int getAddressCount()
+        {
             long countLong = getAddressCountLong();
-            if (countLong > Integer.MAX_VALUE) {
+            if (countLong > Integer.MAX_VALUE)
+            {
                 throw new RuntimeException("Count is larger than an integer: " + countLong);
             }
             // N.B. cannot be negative
-            return (int)countLong;
+            return (int) countLong;
         }
 
         /**
-         * Get the count of available addresses.
-         * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
-         * @return the count of addresses, may be zero.
-         * @since 3.4
+         * Returns the count of available addresses.
+         * Will be zero for CIDR/31 and CIDR/32 if the address is IPv4 address and the {@code inclusiveHostCount} flag is {@code false}.
+         *
+         * @return the count of addresses, may be zero
          */
-        public long getAddressCountLong() {
-            long b = broadcastLong();
-            long n = networkLong();
-            long count = b - n + (isInclusiveHostCount() ? 1 : -1);
-            return count < 0 ? 0 : count;
-        }
-
-        public int asInteger(String address) {
-            return toInteger(address);
-        }
+        public long getAddressCountLong() { return 0; }
 
-        public String getCidrSignature() {
-            return format(toArray(address)) + "/" + pop(netmask);
-        }
-
-        public String[] getAllAddresses() {
-            int ct = getAddressCount();
-            String[] addresses = new String[ct];
-            if (ct == 0) {
-                return addresses;
-            }
-            for (int add = low(), j=0; add <= high(); ++add, ++j) {
-                addresses[j] = format(toArray(add));
-            }
-            return addresses;
-        }
+        /**
+         * Returns the count of available addresses.
+         * Will be zero for CIDR/31 and CIDR/32 if the address is IPv4 address and the {@code inclusiveHostCount} flag is {@code false}.
+         *
+         * @return the count of addresses in a string, may be zero
+         */
+        public String getAddressCountString() { return null; }
 
         /**
-         * {@inheritDoc}
-         * @since 2.2
+         * Returns a list of the available addresses.
+         *
+         * @return an array of the available addresses
          */
-        @Override
-        public String toString() {
-            final StringBuilder buf = new StringBuilder();
-            buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]")
-                .append(" Netmask: [").append(getNetmask()).append("]\n")
-                .append("Network:\t[").append(getNetworkAddress()).append("]\n")
-                .append("Broadcast:\t[").append(getBroadcastAddress()).append("]\n")
-                 .append("First Address:\t[").append(getLowAddress()).append("]\n")
-                 .append("Last Address:\t[").append(getHighAddress()).append("]\n")
-                 .append("# Addresses:\t[").append(getAddressCount()).append("]\n");
-            return buf.toString();
-        }
+        public String[] getAllAddresses() { return new String[0]; }
+
     }
 
     /**
-     * Return a {@link SubnetInfo} instance that contains subnet-specific statistics
+     * Return a {@link SubnetInfo SubnetInfo} instance that contains subnet-specific statistics
+     *
      * @return new instance
 
 Review comment:
   Does not correspond to method implementation.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[hidden email]


With regards,
Apache Git Services