android: Add class to manage a set of IP address ranges/subnets

This commit is contained in:
Tobias Brunner 2017-06-21 14:35:06 +02:00
parent bcba14504a
commit d852a02717
2 changed files with 339 additions and 0 deletions

View File

@ -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();
}
}

View File

@ -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);
}
}