android: Add class to handle IP ranges and subnets
authorTobias Brunner <tobias@strongswan.org>
Tue, 13 Jun 2017 16:41:36 +0000 (18:41 +0200)
committerTobias Brunner <tobias@strongswan.org>
Mon, 3 Jul 2017 08:27:51 +0000 (10:27 +0200)
src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRange.java [new file with mode: 0644]
src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeTest.java [new file with mode: 0644]

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 (file)
index 0000000..79342fc
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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<IPRange>
+{
+       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<IPRange> remove(IPRange range)
+       {
+               ArrayList<IPRange> 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<IPRange> toSubnets()
+       {
+               ArrayList<IPRange> 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 (file)
index 0000000..da6f336
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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<IPRange> 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<IPRange> 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));
+       }
+}