From: Tobias Brunner Date: Tue, 13 Jun 2017 16:41:36 +0000 (+0200) Subject: android: Add class to handle IP ranges and subnets X-Git-Tag: 5.6.0dr1~24^2~28 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=bcba14504a2924ec0f50ea47573c25ff55278989;p=thirdparty%2Fstrongswan.git android: Add class to handle IP ranges and subnets --- diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRange.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRange.java new file mode 100644 index 0000000000..79342fc0c1 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRange.java @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2012-2017 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.utils; + +import android.support.annotation.NonNull; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Class that represents a range of IP addresses. This range could be a proper subnet, but that's + * not necessarily the case (see {@code getPrefix} and {@code toSubnets}). + */ +public class IPRange implements Comparable +{ + private final byte[] mBitmask = { (byte)0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + private byte[] mFrom; + private byte[] mTo; + private Integer mPrefix; + + /** + * Determine if the range is a proper subnet and, if so, what the network prefix is. + */ + private void determinePrefix() + { + boolean matching = true; + + mPrefix = mFrom.length * 8; + for (int i = 0; i < mFrom.length; i++) + { + for (int bit = 0; bit < 8; bit++) + { + if (matching) + { + if ((mFrom[i] & mBitmask[bit]) != (mTo[i] & mBitmask[bit])) + { + mPrefix = (i * 8) + bit; + matching = false; + } + } + else + { + if ((mFrom[i] & mBitmask[bit]) != 0 || (mTo[i] & mBitmask[bit]) == 0) + { + mPrefix = null; + return; + } + } + } + } + } + + private IPRange(byte[] from, byte[] to) + { + mFrom = from; + mTo = to; + determinePrefix(); + } + + public IPRange(String from, String to) throws UnknownHostException + { + this(InetAddress.getByName(from), InetAddress.getByName(to)); + } + + public IPRange(InetAddress from, InetAddress to) + { + byte[] fa = from.getAddress(), ta = to.getAddress(); + if (fa.length != ta.length) + { + throw new IllegalArgumentException("Invalid range"); + } + if (compareAddr(fa, ta) < 0) + { + mFrom = fa; + mTo = ta; + } + else + { + mTo = fa; + mFrom = ta; + } + determinePrefix(); + } + + public IPRange(String base, int prefix) throws UnknownHostException + { + this(InetAddress.getByName(base), prefix); + } + + public IPRange(InetAddress base, int prefix) + { + this(base.getAddress(), prefix); + } + + private IPRange(byte[] from, int prefix) + { + initializeFromCIDR(from, prefix); + } + + private void initializeFromCIDR(byte[] from, int prefix) + { + if (from.length != 4 && from.length != 16) + { + throw new IllegalArgumentException("Invalid address"); + } + if (prefix < 0 || prefix > from.length * 8) + { + throw new IllegalArgumentException("Invalid prefix"); + } + byte[] to = from.clone(); + byte mask = (byte)(0xff << (8 - prefix % 8)); + int i = prefix / 8; + + if (i < from.length) + { + from[i] = (byte)(from[i] & mask); + to[i] = (byte)(to[i] | ~mask); + Arrays.fill(from, i+1, from.length, (byte)0); + Arrays.fill(to, i+1, to.length, (byte)0xff); + } + mFrom = from; + mTo = to; + mPrefix = prefix; + } + + public IPRange(String cidr) throws UnknownHostException + { + /* only verify the basic structure */ + if (!cidr.matches("^(([0-9.]+)|([0-9a-f:]+))(/\\d+)?$")) + { + throw new IllegalArgumentException("Invalid CIDR notation"); + } + String[] parts = cidr.split("/"); + InetAddress addr = InetAddress.getByName(parts[0]); + byte[] base = addr.getAddress(); + int prefix = base.length * 8; + if (parts.length > 1) + { + prefix = Integer.parseInt(parts[1]); + } + initializeFromCIDR(base, prefix); + } + + /** + * Returns the first address of the range. The network ID in case this is a proper subnet. + */ + public InetAddress getFrom() + { + try + { + return InetAddress.getByAddress(mFrom); + } + catch (UnknownHostException ignored) + { + return null; + } + } + + /** + * Returns the last address of the range. + */ + public InetAddress getTo() + { + try + { + return InetAddress.getByAddress(mTo); + } + catch (UnknownHostException ignored) + { + return null; + } + } + + /** + * If this range is a proper subnet returns its prefix, otherwise returns null. + */ + public Integer getPrefix() + { + return mPrefix; + } + + @Override + public int compareTo(@NonNull IPRange other) + { + int cmp = compareAddr(mFrom, other.mFrom); + if (cmp == 0) + { /* smaller ranges first */ + cmp = compareAddr(mTo, other.mTo); + } + return cmp; + } + + @Override + public boolean equals(Object o) + { + if (o == null || !(o instanceof IPRange)) + { + return false; + } + return this == o || compareTo((IPRange)o) == 0; + } + + @Override + public String toString() + { + try + { + if (mPrefix != null) + { + return InetAddress.getByAddress(mFrom).getHostAddress() + "/" + mPrefix; + } + return InetAddress.getByAddress(mFrom).getHostAddress() + ".." + + InetAddress.getByAddress(mTo).getHostAddress(); + } + catch (UnknownHostException ignored) + { + return super.toString(); + } + } + + private int compareAddr(byte a[], byte b[]) + { + if (a.length != b.length) + { + return (a.length < b.length) ? -1 : 1; + } + for (int i = 0; i < a.length; i++) + { + if (a[i] != b[i]) + { + if (((int)a[i] & 0xff) < ((int)b[i] & 0xff)) + { + return -1; + } + else + { + return 1; + } + } + } + return 0; + } + + /** + * Check if this range fully contains the given range. + */ + public boolean contains(IPRange range) + { + return compareAddr(mFrom, range.mFrom) <= 0 && compareAddr(range.mTo, mTo) <= 0; + } + + /** + * Check if this and the given range overlap. + */ + public boolean overlaps(IPRange range) + { + return !(compareAddr(mTo, range.mFrom) < 0 || compareAddr(range.mTo, mFrom) < 0); + } + + private byte[] dec(byte[] addr) + { + for (int i = addr.length - 1; i >= 0; i--) + { + if (--addr[i] != (byte)0xff) + { + break; + } + } + return addr; + } + + private byte[] inc(byte[] addr) + { + for (int i = addr.length - 1; i >= 0; i--) + { + if (++addr[i] != 0) + { + break; + } + } + return addr; + } + + /** + * Remove the given range from the current range. Returns a list of resulting ranges (these are + * not proper subnets). At most two ranges are returned, in case the given range is contained in + * this but does not equal it, which would result in an empty list (which is also the case if + * this range is fully contained in the given range). + */ + public List remove(IPRange range) + { + ArrayList list = new ArrayList<>(); + if (!overlaps(range)) + { /* | this | or | this | + * | range | | range | */ + list.add(this); + } + else if (!range.contains(this)) + { /* we are not completely removed, so none of these cases applies: + * | this | or | this | or | this | + * | range | | range | | range | */ + if (compareAddr(mFrom, range.mFrom) < 0 && compareAddr(range.mTo, mTo) < 0) + { /* the removed range is completely within our boundaries: + * | this | + * | range | */ + list.add(new IPRange(mFrom, dec(range.mFrom.clone()))); + list.add(new IPRange(inc(range.mTo.clone()), mTo)); + } + else + { /* one end is within our boundaries the other at or outside it: + * | this | or | this | or | this | or | this | + * | range | | range | | range | | range | */ + byte[] from = compareAddr(mFrom, range.mFrom) < 0 ? mFrom : inc(range.mTo.clone()); + byte[] to = compareAddr(mTo, range.mTo) > 0 ? mTo : dec(range.mFrom.clone()); + list.add(new IPRange(from, to)); + } + } + return list; + } + + private boolean adjacent(IPRange range) + { + if (compareAddr(mTo, range.mFrom) < 0) + { + byte[] to = inc(mTo.clone()); + return compareAddr(to, range.mFrom) == 0; + } + byte[] from = dec(mFrom.clone()); + return compareAddr(from, range.mTo) == 0; + } + + /** + * Merge two adjacent or overlapping ranges, returns null if it's not possible to merge them. + */ + public IPRange merge(IPRange range) + { + if (overlaps(range)) + { + if (contains(range)) + { + return this; + } + else if (range.contains(this)) + { + return range; + } + } + else if (!adjacent(range)) + { + return null; + } + byte[] from = compareAddr(mFrom, range.mFrom) < 0 ? mFrom : range.mFrom; + byte[] to = compareAddr(mTo, range.mTo) > 0 ? mTo : range.mTo; + return new IPRange(from, to); + } + + /** + * Split the given range into a sorted list of proper subnets. + */ + public List toSubnets() + { + ArrayList list = new ArrayList<>(); + if (mPrefix != null) + { + list.add(this); + } + else + { + int i = 0, bit = 0, prefix, netmask, common_byte, common_bit; + int from_cur, from_prev = 0, to_cur, to_prev = 1; + boolean from_full = true, to_full = true; + + byte[] from = mFrom.clone(); + byte[] to = mTo.clone(); + + /* find a common prefix */ + while (i < from.length && (from[i] & mBitmask[bit]) == (to[i] & mBitmask[bit])) + { + if (++bit == 8) + { + bit = 0; + i++; + } + } + prefix = i * 8 + bit; + + /* at this point we know that the addresses are either equal, or that the + * current bits in the 'from' and 'to' addresses are 0 and 1, respectively. + * we now look at the rest of the bits as two binary trees (0=left, 1=right) + * where 'from' and 'to' are both leaf nodes. all leaf nodes between these + * nodes are addresses contained in the range. to collect them as subnets + * we follow the trees from both leaf nodes to their root node and record + * all complete subtrees (right for from, left for to) we come across as + * subnets. in that process host bits are zeroed out. if both addresses + * are equal we won't enter the loop below. + * 0_____|_____1 for the 'from' address we assume we start on a + * 0__|__ 1 0__|__1 left subtree (0) and follow the left edges until + * _|_ _|_ _|_ _|_ we reach the root of this subtree, which is + * | | | | | | | | either the root of this whole 'from'-subtree + * 0 1 0 1 0 1 0 1 (causing us to leave the loop) or the root node + * of the right subtree (1) of another node (which actually could be the + * leaf node we start from). that whole subtree gets recorded as subnet. + * next we follow the right edges to the root of that subtree which again is + * either the 'from'-root or the root node in the left subtree (0) of + * another node. the complete right subtree of that node is the next subnet + * we record. from there we assume that we are in that right subtree and + * recursively follow right edges to its root. for the 'to' address the + * procedure is exactly the same but with left and right reversed. + */ + if (++bit == 8) + { + bit = 0; + i++; + } + common_byte = i; + common_bit = bit; + netmask = from.length * 8; + for (i = from.length - 1; i >= common_byte; i--) + { + int bit_min = (i == common_byte) ? common_bit : 0; + for (bit = 7; bit >= bit_min; bit--) + { + byte mask = mBitmask[bit]; + + from_cur = from[i] & mask; + if (from_prev == 0 && from_cur != 0) + { /* 0 -> 1: subnet is the whole current (right) subtree */ + list.add(new IPRange(from.clone(), netmask)); + from_full = false; + } + else if (from_prev != 0 && from_cur == 0) + { /* 1 -> 0: invert bit to switch to right subtree and add it */ + from[i] ^= mask; + list.add(new IPRange(from.clone(), netmask)); + from_cur = 1; + } + /* clear the current bit */ + from[i] &= ~mask; + from_prev = from_cur; + + to_cur = to[i] & mask; + if (to_prev != 0 && to_cur == 0) + { /* 1 -> 0: subnet is the whole current (left) subtree */ + list.add(new IPRange(to.clone(), netmask)); + to_full = false; + } + else if (to_prev == 0 && to_cur != 0) + { /* 0 -> 1: invert bit to switch to left subtree and add it */ + to[i] ^= mask; + list.add(new IPRange(to.clone(), netmask)); + to_cur = 0; + } + /* clear the current bit */ + to[i] &= ~mask; + to_prev = to_cur; + netmask--; + } + } + + if (from_full && to_full) + { /* full subnet (from=to or from=0.. and to=1.. after common prefix) - not reachable + * due to the shortcut at the top */ + list.add(new IPRange(from.clone(), prefix)); + } + else if (from_full) + { /* full from subnet (from=0.. after prefix) */ + list.add(new IPRange(from.clone(), prefix + 1)); + } + else if (to_full) + { /* full to subnet (to=1.. after prefix) */ + list.add(new IPRange(to.clone(), prefix + 1)); + } + } + Collections.sort(list); + return list; + } +} diff --git a/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeTest.java b/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeTest.java new file mode 100644 index 0000000000..da6f336e9c --- /dev/null +++ b/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeTest.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2017 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.test; + +import org.junit.Test; +import org.strongswan.android.utils.IPRange; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class IPRangeTest +{ + @Test + public void testRangeReversed() throws UnknownHostException + { + IPRange test = new IPRange("192.168.0.10", "192.168.0.1"); + assertEquals("from", "192.168.0.1", test.getFrom().getHostAddress()); + assertEquals("to", "192.168.0.10", test.getTo().getHostAddress()); + } + + @Test(expected = IllegalArgumentException.class) + public void testRangeInvalid() throws UnknownHostException + { + IPRange test = new IPRange("192.168.0.1", "fec1::1"); + assertEquals("from", "192.168.0.1", test.getFrom().getHostAddress()); + } + + @Test(expected = UnknownHostException.class) + public void testPrefixAddrInvalid() throws UnknownHostException + { + IPRange test = new IPRange("a.b.c.d", 24); + assertEquals("from", "192.168.0.1", test.getFrom().getHostAddress()); + } + + @Test(expected = IllegalArgumentException.class) + public void testPrefixNegative() throws UnknownHostException + { + IPRange test = new IPRange("192.168.0.1", -5); + assertEquals("from", "192.168.0.1", test.getFrom().getHostAddress()); + } + + @Test(expected = IllegalArgumentException.class) + public void testPrefixLarge() throws UnknownHostException + { + IPRange test = new IPRange("192.168.0.1", 42); + assertEquals("from", "192.168.0.1", test.getFrom().getHostAddress()); + } + + private void testPrefix(String from, String to, Integer prefix) throws UnknownHostException + { + IPRange test = new IPRange(from, to); + assertEquals("prefix", prefix, test.getPrefix()); + } + + @Test + public void testPrefix() throws UnknownHostException + { + testPrefix("192.168.0.0", "192.168.0.255", 24); + testPrefix("192.168.0.8", "192.168.0.15", 29); + testPrefix("192.168.0.1", "192.168.0.255", null); + testPrefix("192.168.0.1", "192.168.0.1", 32); + testPrefix("fec1::0", "fec1::ffff", 112); + testPrefix("fec1::1", "fec1::ffff", null); + testPrefix("fec1::1", "fec1::1", 128); + } + + private void testPrefixRange(String base, int prefix, String from, String to) throws UnknownHostException + { + IPRange test = new IPRange(InetAddress.getByName(base), prefix); + assertEquals("from", from, test.getFrom().getHostAddress()); + assertEquals("to", to, test.getTo().getHostAddress()); + } + + @Test + public void testPrefixRange() throws UnknownHostException + { + testPrefixRange("0.0.0.0", 0, "0.0.0.0", "255.255.255.255"); + testPrefixRange("0.0.0.0", 32, "0.0.0.0", "0.0.0.0"); + testPrefixRange("192.168.1.0", 24, "192.168.1.0", "192.168.1.255"); + testPrefixRange("192.168.1.10", 24, "192.168.1.0", "192.168.1.255"); + testPrefixRange("192.168.1.64", 26, "192.168.1.64", "192.168.1.127"); + testPrefixRange("192.168.1.64", 27, "192.168.1.64", "192.168.1.95"); + testPrefixRange("192.168.1.64", 28, "192.168.1.64", "192.168.1.79"); + testPrefixRange("192.168.1.255", 29, "192.168.1.248", "192.168.1.255"); + testPrefixRange("192.168.1.255", 30, "192.168.1.252", "192.168.1.255"); + testPrefixRange("192.168.1.255", 31, "192.168.1.254", "192.168.1.255"); + testPrefixRange("192.168.1.255", 32, "192.168.1.255", "192.168.1.255"); + + testPrefixRange("::", 0, "0:0:0:0:0:0:0:0", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + testPrefixRange("fec1::", 64, "fec1:0:0:0:0:0:0:0", "fec1:0:0:0:ffff:ffff:ffff:ffff"); + testPrefixRange("fec1::1", 128, "fec1:0:0:0:0:0:0:1", "fec1:0:0:0:0:0:0:1"); + testPrefixRange("fec1::10:0", 108, "fec1:0:0:0:0:0:10:0", "fec1:0:0:0:0:0:1f:ffff"); + testPrefixRange("fec1::10:0", 112, "fec1:0:0:0:0:0:10:0", "fec1:0:0:0:0:0:10:ffff"); + testPrefixRange("fec1::10:0", 113, "fec1:0:0:0:0:0:10:0", "fec1:0:0:0:0:0:10:7fff"); + testPrefixRange("fec1::10:0", 116, "fec1:0:0:0:0:0:10:0", "fec1:0:0:0:0:0:10:fff"); + testPrefixRange("fec1::1:ffff", 112, "fec1:0:0:0:0:0:1:0", "fec1:0:0:0:0:0:1:ffff"); + testPrefixRange("fec1::1:ffff", 113, "fec1:0:0:0:0:0:1:8000", "fec1:0:0:0:0:0:1:ffff"); + testPrefixRange("fec1::1:ffff", 114, "fec1:0:0:0:0:0:1:c000", "fec1:0:0:0:0:0:1:ffff"); + testPrefixRange("fec1::1:ffff", 115, "fec1:0:0:0:0:0:1:e000", "fec1:0:0:0:0:0:1:ffff"); + testPrefixRange("fec1::1:ffff", 116, "fec1:0:0:0:0:0:1:f000", "fec1:0:0:0:0:0:1:ffff"); + testPrefixRange("fec1::1:ffff", 117, "fec1:0:0:0:0:0:1:f800", "fec1:0:0:0:0:0:1:ffff"); + } + + @Test(expected = IllegalArgumentException.class) + public void testCIDRAddressInvalid() throws UnknownHostException + { + IPRange test = new IPRange("asdf"); + assertEquals("not reached", null, test); + } + + @Test(expected = IllegalArgumentException.class) + public void testCIDRIncomplete() throws UnknownHostException + { + IPRange test = new IPRange("/32"); + assertEquals("not reached", null, test); + } + + @Test(expected = IllegalArgumentException.class) + public void testCIDRIncompletePrefix() throws UnknownHostException + { + IPRange test = new IPRange("192.168.0.1/"); + assertEquals("not reached", null, test); + } + + @Test(expected = IllegalArgumentException.class) + public void testCIDRPrefixNegative() throws UnknownHostException + { + IPRange test = new IPRange("192.168.0.1/-5"); + assertEquals("not reached", null, test); + } + + @Test(expected = IllegalArgumentException.class) + public void testCIDRPrefixLarge() throws UnknownHostException + { + IPRange test = new IPRange("192.168.0.1/33"); + assertEquals("not reached", null, test); + } + + @Test(expected = IllegalArgumentException.class) + public void testCIDRPrefixLargev6() throws UnknownHostException + { + IPRange test = new IPRange("fec1::1/129"); + assertEquals("not reached", null, test); + } + + private void testCIDR(String cidr, IPRange exp) throws UnknownHostException + { + IPRange test = new IPRange(cidr); + assertEquals("cidr", exp, test); + } + + @Test + public void testCIDR() throws UnknownHostException + { + testCIDR("0.0.0.0/0", new IPRange("0.0.0.0", 0)); + testCIDR("192.168.1.0/24", new IPRange("192.168.1.0", 24)); + testCIDR("192.168.1.10/24", new IPRange("192.168.1.0", 24)); + testCIDR("192.168.1.1/32", new IPRange("192.168.1.1", 32)); + testCIDR("192.168.1.1", new IPRange("192.168.1.1", 32)); + testCIDR("::/0", new IPRange("::", 0)); + testCIDR("fec1::/64", new IPRange("fec1::", 64)); + testCIDR("fec1::10/64", new IPRange("fec1::", 64)); + testCIDR("fec1::1/128", new IPRange("fec1::1", 128)); + testCIDR("fec1::1", new IPRange("fec1::1", 128)); + } + + private void testToString(String f, String t, String exp) throws UnknownHostException + { + IPRange a = new IPRange(f, t); + assertEquals("string", exp, a.toString()); + } + + @Test + public void testToString() throws UnknownHostException + { + testToString("192.168.1.1", "192.168.1.1", "192.168.1.1/32"); + testToString("192.168.1.0", "192.168.1.255", "192.168.1.0/24"); + testToString("192.168.1.1", "192.168.1.255", "192.168.1.1..192.168.1.255"); + testToString("0.0.0.0", "255.255.255.255", "0.0.0.0/0"); + testToString("fec1::1", "fec1::1", "fec1:0:0:0:0:0:0:1/128"); + testToString("fec1::", "fec1::ffff", "fec1:0:0:0:0:0:0:0/112"); + testToString("::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "0:0:0:0:0:0:0:0/0"); + } + + private void compare(String af, String at, String bf, String bt, int exp) throws UnknownHostException + { + IPRange a = new IPRange(af, at); + IPRange b = new IPRange(bf, bt); + assertEquals("compare", exp, a.compareTo(b)); + } + + private void compare(String a, int pa, String b, int pb, int exp) throws UnknownHostException + { + IPRange ar = new IPRange(a, pa); + IPRange br = new IPRange(b, pb); + assertEquals("compare", exp, ar.compareTo(br)); + } + + @Test + public void testCompareTo() throws UnknownHostException + { + compare("192.168.0.0", "192.168.0.10", "192.168.0.0", "192.168.0.10", 0); + compare("192.168.0.1", "192.168.0.10", "192.168.0.0", "192.168.0.10", 1); + compare("192.168.0.0", "192.168.0.9", "192.168.0.0", "192.168.0.10", -1); + + compare("192.168.0.0", 24, "192.168.0.0", 24, 0); + compare("192.168.0.0", 24, "192.168.0.0", 28, 1); + compare("192.168.0.0", 28, "192.168.0.0", 24, -1); + compare("192.168.0.0", 32, "192.168.0.255", 32, -1); + compare("10.0.1.0", 24, "192.168.1.0", 24, -1); + compare("10.0.1.0", 24, "fec1::", 64, -1); + compare("fec1::1", 128, "fec1::2", 128, -1); + compare("fec1::1", 126, "fec1::2", 126, 0); + } + + @Test + public void testEquals() throws UnknownHostException + { + IPRange a = new IPRange("192.168.1.0/24"); + IPRange b = new IPRange("192.168.1.0/24"); + assertEquals("same", true, a.equals(a)); + assertEquals("equals", true, a.equals(b)); + InetAddress c = InetAddress.getByName("192.168.1.0"); + assertEquals("null", false, a.equals(c)); + assertEquals("null", false, a.equals(null)); + } + + private void contains(String af, String at, String bf, String bt, boolean exp) throws UnknownHostException + { + IPRange a = new IPRange(af, at); + IPRange b = new IPRange(bf, bt); + assertEquals("contains", exp, a.contains(b)); + } + + @Test + public void testContains() throws UnknownHostException + { + contains("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.10", true); + contains("192.168.1.0", "192.168.1.10", "192.168.1.1", "192.168.1.10", true); + contains("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.9", true); + contains("192.168.1.0", "192.168.1.10", "192.168.1.1", "192.168.1.9", true); + contains("192.168.1.0", "192.168.1.10", "192.168.1.2", "192.168.1.2", true); + contains("192.168.1.0", "192.168.1.10", "192.168.1.1", "192.168.1.11", false); + contains("192.168.1.0", "192.168.1.10", "192.168.0.255", "192.168.1.10", false); + contains("192.168.1.0", "192.168.1.10", "192.168.0.255", "192.168.1.11", false); + contains("192.168.1.1", "192.168.1.1", "192.168.1.0", "192.168.1.0", false); + contains("192.168.1.1", "192.168.1.1", "192.168.1.2", "192.168.1.2", false); + contains("192.168.1.0", "192.168.1.10", "fec1::", "fec1::10", false); + contains("fec1::", "fec1::10", "192.168.1.0", "192.168.1.10", false); + } + + private void overlaps(String af, String at, String bf, String bt, boolean exp) throws UnknownHostException + { + IPRange a = new IPRange(af, at); + IPRange b = new IPRange(bf, bt); + assertEquals("b overlaps with a", exp, a.overlaps(b)); + assertEquals("a overlaps with b", exp, b.overlaps(a)); + } + + @Test + public void testOverlaps() throws UnknownHostException + { + overlaps("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.10", true); + overlaps("192.168.1.0", "192.168.1.10", "192.168.1.1", "192.168.1.9", true); + overlaps("192.168.1.0", "192.168.1.10", "192.168.1.10", "192.168.1.20", true); + overlaps("192.168.1.0", "192.168.1.10", "192.168.1.9", "192.168.1.20", true); + overlaps("192.168.1.0", "192.168.1.10", "192.168.1.11", "192.168.1.20", false); + overlaps("192.168.1.0", "192.168.1.10", "192.168.0.245", "192.168.1.1", true); + overlaps("192.168.1.0", "192.168.1.10", "192.168.0.245", "192.168.1.0", true); + overlaps("192.168.1.0", "192.168.1.10", "192.168.0.245", "192.168.0.255", false); + overlaps("192.168.1.0", "192.168.1.10", "fec1::", "fec1::10", false); + } + + private void remove(String af, String at, String bf, String bt, IPRange...exp) throws UnknownHostException + { + IPRange a = new IPRange(af, at); + IPRange b = new IPRange(bf, bt); + List l = a.remove(b); + assertEquals("ranges", exp.length, l.size()); + for (int i = 0; i < exp.length; i++) + { + assertEquals("range", exp[i], l.get(i)); + } + } + + @Test + public void testRemove() throws UnknownHostException + { + remove("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.10"); + remove("192.168.1.0", "192.168.1.10", "192.168.0.0", "192.168.1.255"); + remove("192.168.1.0", "192.168.1.10", "10.0.1.0", "10.0.1.10", + new IPRange("192.168.1.0", "192.168.1.10")); + remove("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.5", + new IPRange("192.168.1.6", "192.168.1.10")); + remove("192.168.1.0", "192.168.1.10", "192.168.1.6", "192.168.1.10", + new IPRange("192.168.1.0", "192.168.1.5")); + remove("192.168.1.0", "192.168.1.10", "192.168.0.0", "192.168.1.5", + new IPRange("192.168.1.6", "192.168.1.10")); + remove("192.168.1.0", "192.168.1.10", "192.168.1.6", "192.168.1.255", + new IPRange("192.168.1.0", "192.168.1.5")); + remove("192.168.1.0", "192.168.1.10", "192.168.1.1", "192.168.1.9", + new IPRange("192.168.1.0", "192.168.1.0"), new IPRange("192.168.1.10", "192.168.1.10")); + remove("192.168.1.0", "192.168.1.10", "192.168.1.3", "192.168.1.7", + new IPRange("192.168.1.0", "192.168.1.2"), new IPRange("192.168.1.8", "192.168.1.10")); + remove("192.168.0.0", "192.168.1.255", "192.168.1.0", "192.168.1.255", + new IPRange("192.168.0.0", "192.168.0.255")); + remove("192.168.0.0", "192.168.1.255", "192.168.0.0", "192.168.0.255", + new IPRange("192.168.1.0", "192.168.1.255")); + remove("192.168.1.0", "192.168.1.10", "0.0.0.0", "192.168.1.10"); + remove("192.168.1.0", "192.168.1.10", "192.168.1.0", "255.255.255.255"); + } + + private void merge(String af, String at, String bf, String bt, IPRange exp) throws UnknownHostException + { + IPRange a = new IPRange(af, at); + IPRange b = new IPRange(bf, bt); + IPRange r = a.merge(b); + assertEquals("merge", exp, r); + r = b.merge(a); + assertEquals("reverse", exp, r); + } + + @Test + public void testMerge() throws UnknownHostException + { + merge("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.10", new IPRange("192.168.1.0", "192.168.1.10")); + merge("192.168.1.0", "192.168.1.10", "192.168.0.0", "192.168.1.10", new IPRange("192.168.0.0", "192.168.1.10")); + merge("192.168.1.0", "192.168.1.10", "192.168.0.0", "192.168.0.255", new IPRange("192.168.0.0", "192.168.1.10")); + merge("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.255", new IPRange("192.168.1.0", "192.168.1.255")); + merge("192.168.1.0", "192.168.1.10", "192.168.1.11", "192.168.1.255", new IPRange("192.168.1.0", "192.168.1.255")); + merge("192.168.1.0", "192.168.1.10", "192.168.0.0", "192.168.1.255", new IPRange("192.168.0.0", "192.168.1.255")); + merge("255.255.255.0", "255.255.255.255", "0.0.0.0", "0.0.0.255", null); + merge("0.0.0.1", "255.255.255.255", "0.0.0.0", "0.0.0.0", new IPRange("0.0.0.0", 0)); + merge("0.0.0.0", "255.255.255.254", "255.255.255.255", "255.255.255.255", new IPRange("0.0.0.0", 0)); + merge("192.168.1.0", "192.168.1.10", "192.168.0.0", "192.168.0.254", null); + merge("192.168.1.0", "192.168.1.10", "192.168.1.12", "192.168.1.255", null); + merge("192.168.1.0", "192.168.1.10", "fec1::0", "fec1::10", null); + merge("fec1::1:0", "fec1::1:10", "fec1::1:8", "fec1::1:20", new IPRange("fec1::1:0", "fec1::1:20")); + } + + private void toSubnet(String f, String t, IPRange...exp) throws UnknownHostException + { + IPRange a = new IPRange(f, t); + List l = a.toSubnets(); + assertEquals("ranges", exp.length, l.size()); + for (int i = 0; i < exp.length; i++) + { + assertEquals("range", exp[i], l.get(i)); + } + } + + @Test + public void testToSubnet() throws UnknownHostException + { + toSubnet("0.0.0.0", "255.255.255.255", new IPRange("0.0.0.0", 0)); + toSubnet("192.168.1.1", "192.168.1.1", new IPRange("192.168.1.1", 32)); + toSubnet("192.168.1.0", "192.168.1.255", new IPRange("192.168.1.0", 24)); + toSubnet("192.168.1.0", "192.168.1.10", new IPRange("192.168.1.0", 29), + new IPRange("192.168.1.8", 31), new IPRange("192.168.1.10", 32)); + toSubnet("192.168.1.1", "192.168.1.10", new IPRange("192.168.1.1", 32), + new IPRange("192.168.1.2", 31), new IPRange("192.168.1.4", 30), + new IPRange("192.168.1.8", 31), new IPRange("192.168.1.10", 32)); + toSubnet("192.168.1.241", "192.168.1.255", new IPRange("192.168.1.241", 32), + new IPRange("192.168.1.242", 31), new IPRange("192.168.1.244", 30), + new IPRange("192.168.1.248", 29)); + toSubnet("192.168.254.255", "192.168.255.1", new IPRange("192.168.254.255", 32), + new IPRange("192.168.255.0", 31)); + toSubnet("::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", new IPRange("::", 0)); + toSubnet("fec1::0", "fec1::a", new IPRange("fec1::0", 125), new IPRange("fec1::8", 127), + new IPRange("fec1::a", 128)); + } +}