Java tutorial
/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.github.veqryn.net; import static com.github.veqryn.net.Cidrs.NBITS; import static com.github.veqryn.net.Cidrs.cidrPattern; import static com.github.veqryn.net.Cidrs.getDifferenceNetmask; import static com.github.veqryn.net.Cidrs.getHighestBinaryWithNetmask; import static com.github.veqryn.net.Cidrs.getLowestBinaryWithNetmask; import static com.github.veqryn.net.Cidrs.getLowestContainingCidrForRange; import static com.github.veqryn.net.Cidrs.getMaskBitCount; import static com.github.veqryn.net.Cidrs.getNetMask; import static com.github.veqryn.net.Cidrs.toCidrNotation; import static com.github.veqryn.net.Ips.format; import static com.github.veqryn.net.Ips.matchAddress; import static com.github.veqryn.net.Ips.rangeCheck; import static com.github.veqryn.net.Ips.toArray; import static com.github.veqryn.net.Ips.toInteger; import java.io.Serializable; import java.util.regex.Matcher; /** * Light weight immutable CIDR IPv4 type, which implements hashCode, equals, and Comparable. * Some methods and method signatures influenced by org.apache.commons.net.util.SubnetUtils * * <pre> * // Example usage: * // Various ways to construct: * Cidr4 myCIDR1 = new Cidr4("192.168.1.96/29"); * // 192.168.1.98/32 (true = append /32 if missing) * Cidr4 myCIDR2 = new Cidr4("192.168.1.98", true); * Cidr4 myCIDR3 = new Cidr4("192.168.1.0", "255.255.255.0"); // 192.168.1.0/24 * Cidr4 myCIDR4 = new Cidr4(192, 168, 1, 104, 30); // 192.168.1.104/30 * Cidr4 myCIDR5 = new Cidr4(-1062731414, 31); // 192.168.1.106/31 * Cidr4 myCIDR6 = new Cidr4(myCIDR1); // 192.168.1.96/29 * Cidr4 myCIDR7 = new Cidr4(myIP1); // 192.168.1.104/32 * Cidr4 myCIDR8 = new Cidr4(myIP2, myIP3); // 192.168.1.96/28 * * System.out.println(myCIDR1.equals(myCIDR6)); // true * * // [192.168.1.0/24, 192.168.1.96/29, 192.168.1.98/32, * // 192.168.1.104/30, 192.168.1.104/32, 192.168.1.106/31] * SortedSet<Cidr4> sorted = new TreeSet<Cidr4>( * Arrays.asList(myCIDR1, myCIDR2, myCIDR3, myCIDR4, myCIDR5, myCIDR6, myCIDR7)); * * // 192.168.1.96/29 creates a range of "[192.168.1.96--192.168.1.103]" * System.out.println(myCIDR1.getAddressRange()); * * // true = include network and broadcast address * System.out.println(myCIDR1.getAddressCount(true)); // 8 * * System.out.println(myCIDR1.getLowAddress(true)); // 192.168.1.96 * * Ip4 highIP = myCIDR1.getHighIp(true); // 192.168.1.103 * * System.out.println(myCIDR1.getNetmask()); // 255.255.255.248 * * Ip4[] allIPs = myCIDR5.getAllIps(true); // [192.168.1.106, 192.168.1.107] * * System.out.println(myCIDR1.isInRange(myIP2, true)); // true * System.out.println(myCIDR1.isInRange(myCIDR7, true)); // true * </pre> * * @author Chris Duncan */ public final class Cidr4 implements Comparable<Cidr4>, Serializable { private static final long serialVersionUID = -5580964083176413038L; private final int low; private final int high; /** * 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" * @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-3 decimal digits in range 1-32 */ public Cidr4(final String cidrNotation) { final Matcher matcher = cidrPattern.matcher(cidrNotation); if (matcher.matches()) { final int address = toInteger(matchAddress(matcher), true); final int netmask = getNetMask(rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS)); final int network = getLowestBinaryWithNetmask(address, netmask); this.low = network ^ Integer.MIN_VALUE; this.high = getHighestBinaryWithNetmask(network, netmask) ^ Integer.MIN_VALUE; } else { throw new IllegalArgumentException("Could not parse [" + cidrNotation + "]"); } } /** * Constructor that takes a CIDR-notation string, e.g. "192.168.0.1/16" * Or an IP dotted decimal format address, e.g. "192.168.0.1" * * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16" * ("192.168.0.1" accepted as if it was /32, if acceptAddressWithoutRange true) * @param acceptAddressWithoutRange true if n.n.n.n should be accepted * @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-3 decimal digits in range 1-32 * (if acceptAddressWithoutRange then n.n.n.n is also accepted) */ public Cidr4(final String cidrNotation, final boolean acceptAddressWithoutRange) { this((acceptAddressWithoutRange && !cidrNotation.contains("/")) ? cidrNotation + "/32" : 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" * @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 */ public Cidr4(final String address, final String mask) { this(toCidrNotation(address, mask)); } /** * Constructor that takes a integer value for address and the * number of mask bits for the cidr range * * @param address Low value of CIDR range, * where Integer.MIN_VALUE = 128.0.0.0 * and 0 = 0.0.0.0 * and Integer.MAX_VALUE = 127.255.255.255 * and -1 = 255.255.255.255 * @param maskBits e.g. 32 */ public Cidr4(final int address, final int maskBits) { this(address, true, maskBits); } /** * Constructor that takes a integer value for address and the * number of mask bits for the cidr range * * @param address Low value of CIDR range * @param binary false if using a sortable packed integer, * where Integer.MIN_VALUE = 0.0.0.0 * and 0 = 128.0.0.0 * and Integer.MAX_VALUE = 255.255.255.255<br> * true if using a binary integer, * where Integer.MIN_VALUE = 128.0.0.0 * and 0 = 0.0.0.0 * and Integer.MAX_VALUE = 127.255.255.255 * and -1 = 255.255.255.255 * @param maskBits e.g. 32 */ protected Cidr4(int address, final boolean binary, final int maskBits) { address = binary ? address : address ^ Integer.MIN_VALUE; final int netmask = getNetMask(rangeCheck(maskBits, 0, NBITS)); final int network = getLowestBinaryWithNetmask(address, netmask); this.low = network ^ Integer.MIN_VALUE; this.high = getHighestBinaryWithNetmask(network, netmask) ^ Integer.MIN_VALUE; } /** * Constructor that makes a copy of the provided Cidr * * @param cidr Cidr */ public Cidr4(final Cidr4 cidr) { this.low = cidr.low; this.high = cidr.high; } /** * Constructor that takes a single Ip value for a CIDR with range /32 * * @param address Ip of the single address this CIDR represents */ public Cidr4(final Ip4 address) { this.low = address.getSortableInteger(); this.high = this.low; } /** * Constructor that takes the low and high Ip values of the CIDR range. * * @param lowIp Low Ip of CIDR range * @param highIp High Ip of CIDR range */ public Cidr4(final Ip4 lowIp, final Ip4 highIp) { this(lowIp.getSortableInteger(), highIp.getSortableInteger(), false); } /** * Constructor that takes the low and high integer values of the CIDR range. * Where Integer.MIN_VALUE = 0.0.0.0 * and 0 = 128.0.0.0 * and Integer.MAX_VALUE = 255.255.255.255 * * @param lowIp Low value of CIDR range * @param highIp High value of CIDR range * @param binary false if using a sortable packed integer, * where Integer.MIN_VALUE = 0.0.0.0 * and 0 = 128.0.0.0 * and Integer.MAX_VALUE = 255.255.255.255<br> * true if using a binary integer, * where Integer.MIN_VALUE = 128.0.0.0 * and 0 = 0.0.0.0 * and Integer.MAX_VALUE = 127.255.255.255 * and -1 = 255.255.255.255 */ protected Cidr4(final int lowIp, final int highIp, final boolean binary) { int network = binary ? lowIp : lowIp ^ Integer.MIN_VALUE; final int broadcast = binary ? highIp : highIp ^ Integer.MIN_VALUE; // if low and high do not actually match with the netmask that would contains them // we must manually figure out and re-apply the netmask // example: 192.168.211.245--192.168.211.247 should become 192.168.211.244--192.168.211.247 final int netmask = getDifferenceNetmask(network, broadcast); network = getLowestBinaryWithNetmask(network, netmask); this.low = network ^ Integer.MIN_VALUE; this.high = getHighestBinaryWithNetmask(network, netmask) ^ Integer.MIN_VALUE; if (this.low > this.high) { throw new IllegalArgumentException("Low IP value must be <= High IP value"); } } /** * Constructor that takes individual unsigned int octets, and the cidr range / mask bit count * * @param octet1 e.g. 192 * @param octet2 e.g. 168 * @param octet3 e.g. 0 * @param octet4 e.g. 1 * @param maskBits e.g. 32 */ public Cidr4(final int octet1, final int octet2, final int octet3, final int octet4, final int maskBits) { final int address = toInteger(new int[] { octet1, octet2, octet3, octet4 }, true); final int netmask = getNetMask(rangeCheck(maskBits, 0, NBITS)); final int network = getLowestBinaryWithNetmask(address, netmask); this.low = network ^ Integer.MIN_VALUE; this.high = getHighestBinaryWithNetmask(network, netmask) ^ Integer.MIN_VALUE; } /** * @return String with the high to low addresses, e.g. [192.168.0.0--192.168.0.255] */ public final String getAddressRange() { final StringBuilder buf = new StringBuilder(); buf.append('[').append(getLowAddress(true)).append("--").append(getHighAddress(true)).append(']'); return buf.toString(); } /** * @param hostCountInclusive whether to include the network and broadcast addresses * @return the Lowest IP address in dotted format, may be "0.0.0.0" if there is no valid address */ public final String getLowAddress(final boolean hostCountInclusive) { return format(toArray(getLowSortableInteger(hostCountInclusive), false)); } /** * @param hostCountInclusive whether to include the network and broadcast addresses * @return a packed integer equal to the lowest ip in the CIDR range, * where Integer.MIN_VALUE = 0.0.0.0 (returned if there is no valid address) * and 0 = 128.0.0.0 * and Integer.MAX_VALUE = 255.255.255.255 */ protected final int getLowSortableInteger(final boolean hostCountInclusive) { if (hostCountInclusive) { return low; } if (this.high > this.low + 1) { return low + 1; } return Integer.MIN_VALUE; } /** * @param hostCountInclusive whether to include the network and broadcast addresses * @return a binary integer equal to the lowest ip in the CIDR range, * where Integer.MIN_VALUE = 128.0.0.0 * and 0 = 0.0.0.0 (returned if there is no valid address) * and Integer.MAX_VALUE = 127.255.255.255 * and -1 = 255.255.255.255 */ public final int getLowBinaryInteger(final boolean hostCountInclusive) { if (hostCountInclusive) { return low ^ Integer.MIN_VALUE; } if (this.high > this.low + 1) { return (low + 1) ^ Integer.MIN_VALUE; } return 0; } /** * @param hostCountInclusive whether to include the network and broadcast addresses * @return a Ip equal to the lowest IP value in the CIDR range, * may be "0.0.0.0" if there is no valid address */ public final Ip4 getLowIp(final boolean hostCountInclusive) { return new Ip4(getLowSortableInteger(hostCountInclusive), false); } /** * @param hostCountInclusive whether to include the network and broadcast addresses * @return a Cidr equal to the lowest IP value in the CIDR range, * may be "0.0.0.0/32" if there is no valid address */ public final Cidr4 getLowCidr(final boolean hostCountInclusive) { final int lowest = getLowSortableInteger(hostCountInclusive); return new Cidr4(lowest, lowest, false); } /** * @param maskBits the maximum number of bits in the netmask (e.g. 32 - 1) * @return A new Cidr representing the lowest cidr that has * no more than this many maskBits and contains our cidr range * e.g. 192.168.10.10/31 -> mask 24 -> 192.168.0.0/24 */ public final Cidr4 getLowestContainingCidr(final int maskBits) { return getLowestContainingCidrForRange(this.low, this.high, maskBits, false); } /** * @param hostCountInclusive whether to include the network and broadcast addresses * @return the Highest IP address in dotted format, may be "0.0.0.0" if there is no valid address */ public final String getHighAddress(final boolean hostCountInclusive) { return format(toArray(getHighSortableInteger(hostCountInclusive), false)); } /** * @param hostCountInclusive whether to include the network and broadcast addresses * @return a packed integer equal to the highest ip in the CIDR range, * where Integer.MIN_VALUE = 0.0.0.0 (returned if there is no valid address) * and 0 = 128.0.0.0 * and Integer.MAX_VALUE = 255.255.255.255 */ protected final int getHighSortableInteger(final boolean hostCountInclusive) { if (hostCountInclusive) { return high; } if (this.high > this.low + 1) { return high - 1; } return Integer.MIN_VALUE; } /** * @param hostCountInclusive whether to include the network and broadcast addresses * @return a binary integer equal to the highest ip in the CIDR range, * where Integer.MIN_VALUE = 128.0.0.0 * and 0 = 0.0.0.0 (returned if there is no valid address) * and Integer.MAX_VALUE = 127.255.255.255 * and -1 = 255.255.255.255 */ public final int getHighBinaryInteger(final boolean hostCountInclusive) { if (hostCountInclusive) { return high ^ Integer.MIN_VALUE; } if (this.high > this.low + 1) { return (high - 1) ^ Integer.MIN_VALUE; } return 0; } /** * @param hostCountInclusive whether to include the network and broadcast addresses * @return a Ip equal to the highest IP value in the CIDR range, * may be "0.0.0.0" if there is no valid address */ public final Ip4 getHighIp(final boolean hostCountInclusive) { return new Ip4(getHighSortableInteger(hostCountInclusive), false); } /** * @param hostCountInclusive whether to include the network and broadcast addresses * @return a Cidr equal to the highest IP value in the CIDR range, * may be "0.0.0.0/32" if there is no valid address */ public final Cidr4 getHighCidr(final boolean hostCountInclusive) { final int highest = getHighSortableInteger(hostCountInclusive); return new Cidr4(highest, highest, false); } /** * Get the netmask used for this Cidr in binary integer format * * @return binary integer netmask */ public final int getBinaryNetmask() { // Low and High are already the lowest and highest allowed by our netmask // So the quick version of the function is OK to use, instead of Cidrs.getDifferenceNetmask() return ~(low ^ high); } /** * Get the netmask used for this Cidr in dotted decimal format * * @return netmask in dotted decimal format (e.g. "255.255.255.0") */ public final String getNetmask() { return format(toArray(getBinaryNetmask(), true)); } /** * Get the number of mask bits for the Cidr range * * @return mask bit count (e.g. "192.168.0.0/16" would return 16) */ public final int getMaskBits() { return getMaskBitCount(getBinaryNetmask()); } /** * @return CIDR string (e.g. "192.168.0.1/32") */ public final String getCidrSignature() { return getLowAddress(true) + '/' + getMaskBits(); } /** * Get the number of addresses included in this Cidr range * * @param hostCountInclusive whether to include the network and broadcast addresses * @return the number of addresses in this range. could be zero if hostCountInclusive is false */ public final long getAddressCount(final boolean hostCountInclusive) { return Math.max(0, Ips.integerToUnsignedLong(high, false) - Ips.integerToUnsignedLong(low, false) + (hostCountInclusive ? 1 : -1)); } /** * Get all IP addresses in this range * * @param hostCountInclusive whether to include the network and broadcast addresses * @return an array of all IP addresses in this Cidr range in dot-delimited IPv4 format, * could be empty if hostCountInclusive is false, * or if the number of addresses exceeds Integer.MAX_VALUE */ public final String[] getAllAddresses(final boolean hostCountInclusive) { final int count = Math.max(0, (int) getAddressCount(hostCountInclusive)); final String[] addresses = new String[count]; if (count == 0) { return addresses; } final int max = getHighSortableInteger(hostCountInclusive); for (int j = 0, add = getLowSortableInteger(hostCountInclusive); add <= max; ++j, ++add) { addresses[j] = format(toArray(add, false)); } return addresses; } /** * Get all IP addresses in this range * * @param hostCountInclusive whether to include the network and broadcast addresses * @return a Ip array of all IP addresses in this Cidr range, * could be empty if hostCountInclusive is false, * or if the number of addresses exceeds Integer.MAX_VALUE */ public final Ip4[] getAllIps(final boolean hostCountInclusive) { final int count = Math.max(0, (int) getAddressCount(hostCountInclusive)); final Ip4[] addresses = new Ip4[count]; if (count == 0) { return addresses; } final int max = getHighSortableInteger(hostCountInclusive); for (int j = 0, add = getLowSortableInteger(hostCountInclusive); add <= max; ++j, ++add) { addresses[j] = new Ip4(add, false); } return addresses; } /** * Check if the parameter <code>address</code> is within * the range of our CIDR, inclusive * * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1" * @param hostCountInclusive whether to include the network and broadcast addresses * @return true if the passed address is contained within our range */ public final boolean isInRange(final String address, final boolean hostCountInclusive) { return isInRange(toInteger(address, false), hostCountInclusive); } /** * Check if the parameter <code>address</code> is within * the range of our CIDR, inclusive * * @param address Ip * @param hostCountInclusive whether to include the network and broadcast addresses * @return true if the passed address is contained within of our range */ public final boolean isInRange(final Ip4 address, final boolean hostCountInclusive) { return isInRange(address.getSortableInteger(), hostCountInclusive); } /** * Check if the parameter <code>address</code> is within * the range of our CIDR, inclusive * * @param address sortable integer IPv4 value, * where Integer.MIN_VALUE = 0.0.0.0 * and 0 = 128.0.0.0 * and Integer.MAX_VALUE = 255.255.255.255 * @param hostCountInclusive whether to include the network and broadcast addresses * @return true if the passed address is contained within of our range */ protected final boolean isInRange(final int address, final boolean hostCountInclusive) { if (hostCountInclusive) { return address >= low && address <= high; } return address >= low + 1 && address <= high - 1; } /** * Check if the parameter <code>address</code> is within * the range of our CIDR, inclusive * * @param cidr Cidr * @param hostCountInclusive whether to include the network and broadcast addresses * @return true if the passed cidr is equal to or contained within of our range */ public final boolean isInRange(final Cidr4 cidr, final boolean hostCountInclusive) { if (hostCountInclusive) { return cidr.low >= this.low && cidr.high <= this.high; } return cidr.low >= this.low + 1 && cidr.high <= this.high - 1; } @Override public final int hashCode() { return (31 + high) * 31 + low; } @Override public final boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Cidr4 other = (Cidr4) obj; if (high != other.high) { return false; } if (low != other.low) { return false; } return true; } /** * Given a cidr range, sort from lowest to highest starting IP, * then from widest (highest) to narrowest (lowest) ending IP. * This format is important to be able to generate subsets and submaps * from a NavigableSet or NavigableMap. */ @Override public final int compareTo(final Cidr4 other) { final int lowDiff = Integer.compare(this.low, other.low); if (lowDiff != 0) { return lowDiff; } final int highDiff = Integer.compare(this.high, other.high); if (highDiff != 0) { return -highDiff; // negative = widest first } return 0; } @Override public final String toString() { return this.getCidrSignature(); } }