android: Add class to manage a set of IP address ranges/subnets
authorTobias Brunner <tobias@strongswan.org>
Wed, 21 Jun 2017 12:35:06 +0000 (14:35 +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/IPRangeSet.java [new file with mode: 0644]
src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeSetTest.java [new file with mode: 0644]

diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRangeSet.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRangeSet.java
new file mode 100644 (file)
index 0000000..60e468b
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeSet;
+
+/**
+ * Class that represents a set of IP address ranges (not necessarily proper subnets) and allows
+ * modifying the set and enumerating the resulting subnets.
+ */
+public class IPRangeSet
+{
+       private TreeSet<IPRange> mRanges = new TreeSet<>();
+
+       /**
+        * Parse the given string (space separated subnets in CIDR notation) and return the resulting
+        * set or {@code null} if the string was invalid. And empty set is returned if the given string
+        * is {@code null}.
+        */
+       public static IPRangeSet fromString(String ranges)
+       {
+               IPRangeSet set = new IPRangeSet();
+               if (ranges != null)
+               {
+                       for (String range : ranges.split("\\s+"))
+                       {
+                               try
+                               {
+                                       set.add(new IPRange(range));
+                               }
+                               catch (Exception unused)
+                               {       /* besides due to invalid strings exceptions might get thrown if the string
+                                        * contains a hostname (NetworkOnMainThreadException) */
+                                       return null;
+                               }
+                       }
+               }
+               return set;
+       }
+
+       /**
+        * Add a range to this set. Automatically gets merged with existing ranges.
+        */
+       public void add(IPRange range)
+       {
+               if (mRanges.contains(range))
+               {
+                       return;
+               }
+               reinsert:
+               while (true)
+               {
+                       Iterator<IPRange> iterator = mRanges.iterator();
+                       while (iterator.hasNext())
+                       {
+                               IPRange existing = iterator.next();
+                               IPRange replacement = existing.merge(range);
+                               if (replacement != null)
+                               {
+                                       iterator.remove();
+                                       range = replacement;
+                                       continue reinsert;
+                               }
+                       }
+                       mRanges.add(range);
+                       break;
+               }
+       }
+
+       /**
+        * Remove the given range from this set. Existing ranges are automatically adjusted.
+        */
+       public void remove(IPRange range)
+       {
+               ArrayList <IPRange> additions = new ArrayList<>();
+               Iterator<IPRange> iterator = mRanges.iterator();
+               while (iterator.hasNext())
+               {
+                       IPRange existing = iterator.next();
+                       List<IPRange> result = existing.remove(range);
+                       if (result.size() == 0)
+                       {
+                               iterator.remove();
+                       }
+                       else if (!result.get(0).equals(existing))
+                       {
+                               iterator.remove();
+                               additions.addAll(result);
+                       }
+               }
+               mRanges.addAll(additions);
+       }
+
+       /**
+        * Returns the subnets derived from all the ranges in this set.
+        */
+       public Enumeration<IPRange> getSubnets()
+       {
+               return new Enumeration<IPRange>()
+               {
+                       private Iterator<IPRange> mIterator = mRanges.iterator();
+                       private List<IPRange> mSubnets;
+
+                       @Override
+                       public boolean hasMoreElements()
+                       {
+                               return (mSubnets != null && mSubnets.size() > 0) || mIterator.hasNext();
+                       }
+
+                       @Override
+                       public IPRange nextElement()
+                       {
+                               if (mSubnets == null || mSubnets.size() == 0)
+                               {
+                                       IPRange range = mIterator.next();
+                                       mSubnets = range.toSubnets();
+                               }
+                               return mSubnets.remove(0);
+                       }
+               };
+       }
+
+       /**
+        * Returns the number of ranges, not subnets.
+        */
+       public int size()
+       {
+               return mRanges.size();
+       }
+}
diff --git a/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeSetTest.java b/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeSetTest.java
new file mode 100644 (file)
index 0000000..d53d127
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * 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 org.strongswan.android.utils.IPRangeSet;
+
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+
+import static org.junit.Assert.assertEquals;
+
+public class IPRangeSetTest
+{
+       private void assertSubnets(Enumeration<IPRange> subnets, IPRange...exp)
+       {
+               if (exp.length == 0)
+               {
+                       assertEquals("no subnets", false, subnets.hasMoreElements());
+                       return;
+               }
+               for (int i = 0; i < exp.length; i++)
+               {
+                       assertEquals("has subnet", true, subnets.hasMoreElements());
+                       assertEquals("range", exp[i], subnets.nextElement());
+               }
+               assertEquals("done", false, subnets.hasMoreElements());
+       }
+
+       @Test
+       public void testEmpty()
+       {
+               IPRangeSet set = new IPRangeSet();
+               assertEquals("size", 0, set.size());
+               assertSubnets(set.getSubnets());
+       }
+
+       @Test
+       public void testAddDistinct() throws UnknownHostException
+       {
+               IPRangeSet set = new IPRangeSet();
+               set.add(new IPRange("192.168.1.0/24"));
+               assertEquals("size", 1, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24"));
+               set.add(new IPRange("10.0.1.0/24"));
+               assertEquals("size", 2, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("10.0.1.0/24"), new IPRange("192.168.1.0/24"));
+       }
+
+       @Test
+       public void testAddAdjacent() throws UnknownHostException
+       {
+               IPRangeSet set = new IPRangeSet();
+               set.add(new IPRange("192.168.1.0/24"));
+               assertEquals("size", 1, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24"));
+               set.add(new IPRange("192.168.2.0/24"));
+               assertEquals("size", 1, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24"), new IPRange("192.168.2.0/24"));
+       }
+
+       @Test
+       public void testAddAdjacentJoin() throws UnknownHostException
+       {
+               IPRangeSet set = new IPRangeSet();
+               set.add(new IPRange("192.168.1.0/24"));
+               set.add(new IPRange("192.168.3.0/24"));
+               assertEquals("size", 2, set.size());
+               set.add(new IPRange("192.168.2.0/24"));
+               assertEquals("size", 1, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24"), new IPRange("192.168.2.0/23"));
+       }
+
+       @Test
+       public void testAddSame() throws UnknownHostException
+       {
+               IPRangeSet set = new IPRangeSet();
+               set.add(new IPRange("192.168.1.0/24"));
+               assertEquals("size", 1, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24"));
+               set.add(new IPRange("192.168.1.0/24"));
+               assertEquals("size", 1, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24"));
+       }
+
+       @Test
+       public void testAddLarger() throws UnknownHostException
+       {
+               IPRangeSet set = new IPRangeSet();
+               set.add(new IPRange("192.168.1.0/24"));
+               assertEquals("size", 1, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24"));
+               set.add(new IPRange("192.168.0.0/16"));
+               assertEquals("size", 1, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("192.168.0.0/16"));
+               set.add(new IPRange("0.0.0.0/0"));
+               assertEquals("size", 1, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("0.0.0.0/0"));
+       }
+
+       @Test
+       public void testAddLargerMulti() throws UnknownHostException
+       {
+               IPRangeSet set = new IPRangeSet();
+               set.add(new IPRange("192.168.1.0/24"));
+               set.add(new IPRange("10.0.1.0/24"));
+               set.add(new IPRange("255.255.255.255/32"));
+               assertEquals("size", 3, set.size());
+               set.add(new IPRange("0.0.0.0/0"));
+               assertEquals("size", 1, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("0.0.0.0/0"));
+       }
+
+       @Test
+       public void testRemoveNothing() throws UnknownHostException
+       {
+               IPRangeSet set = new IPRangeSet();
+               set.add(new IPRange("192.168.1.0/24"));
+               set.remove(new IPRange("192.168.2.0/24"));
+               assertEquals("size", 1, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24"));
+               set.remove(new IPRange("10.0.1.0/24"));
+               assertEquals("size", 1, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24"));
+       }
+
+       @Test
+       public void testRemoveAll() throws UnknownHostException
+       {
+               IPRangeSet set = new IPRangeSet();
+               set.add(new IPRange("192.168.1.0/24"));
+               set.remove(new IPRange("192.168.0.0/16"));
+               assertEquals("size", 0, set.size());
+               assertSubnets(set.getSubnets());
+               set.add(new IPRange("192.168.1.0/24"));
+               set.add(new IPRange("10.0.1.0/24"));
+               set.add(new IPRange("255.255.255.255/32"));
+               assertEquals("size", 3, set.size());
+               set.remove(new IPRange("0.0.0.0/0"));
+               assertEquals("size", 0, set.size());
+               assertSubnets(set.getSubnets());
+       }
+
+       @Test
+       public void testRemoveOverlap() throws UnknownHostException
+       {
+               IPRangeSet set = new IPRangeSet();
+               set.add(new IPRange("192.168.1.0/24"));
+               set.add(new IPRange("192.168.2.0/24"));
+               set.remove(new IPRange("192.168.1.128", "192.168.2.127"));
+               assertEquals("size", 2, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/25"), new IPRange("192.168.2.128/25"));
+       }
+
+       @Test
+       public void testFromStringSingle() throws UnknownHostException
+       {
+               IPRangeSet set = IPRangeSet.fromString("192.168.1.0/24");
+               assertEquals("size", 1, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24"));
+       }
+
+       @Test
+       public void testFromString() throws UnknownHostException
+       {
+               IPRangeSet set = IPRangeSet.fromString("192.168.1.0/24 fec1::/64        10.0.1.0/24 255.255.255.255");
+               assertEquals("size", 4, set.size());
+               assertSubnets(set.getSubnets(), new IPRange("10.0.1.0/24"), new IPRange("192.168.1.0/24"),
+                                         new IPRange("255.255.255.255/32"), new IPRange("fec1::/64"));
+       }
+
+       @Test
+       public void testFromStringInvalidRange() throws UnknownHostException
+       {
+               IPRangeSet set = IPRangeSet.fromString("192.168.1.0/65");
+               assertEquals("failed", null, set);
+       }
+}