/*
* 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.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package freenet.io;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.StringTokenizer;
/**
* Matcher for IPv4 network addresses. It works like the regex matcher in
* {@link java.util.regex.Matcher}, i.e. you create a new Inet4AddressMatcher
* with the IP address pattern and can then match IP addresses to it. The
* Inet4AddressMatcher can match the following kinds of IP addresses or address
* ranges:
* <ul>
* <li>IP address only (<code>192.168.1.2</code>)</li>
* <li>IP address and network mask (<code>192.168.1.2/255.255.255.0</code>)</li>
* <li>IP address and network mask bits (<code>192.168.1.2/24</code>)</li>
* </ul>
*
* @author David Roden <droden@gmail.com>
* @version $Id$
*/
public class Inet4AddressMatcher implements AddressMatcher {
/** The address of this matcher */
private int address;
/** The network mask of this matcher */
private int networkMask;
/**
* Creates a new address matcher that matches InetAddress objects to the
* address specification given by <code>cidrHostname</code>.
*
* @param cidrHostname
* The address range this matcher matches
*/
public Inet4AddressMatcher(String cidrHostname) {
int slashPosition = cidrHostname.indexOf('/');
if (slashPosition == -1) {
address = convertToBytes(cidrHostname);
networkMask = 0xffffffff;
} else {
address = convertToBytes(cidrHostname.substring(0, slashPosition));
String maskPart = cidrHostname.substring(slashPosition + 1);
if (maskPart.indexOf('.') == -1) {
int bits = Integer.parseInt(maskPart);
if (bits > 32 || bits < 0)
throw new IllegalArgumentException("Mask bits out of range: " + bits + " (" + maskPart + ")");
networkMask = 0xffffffff << (32 - bits);
if (Integer.parseInt(maskPart) == 0) {
networkMask = 0;
}
} else {
networkMask = convertToBytes(maskPart);
}
}
}
/**
* Converts a dotted IP address (a.b.c.d) to a 32-bit value. The first octet
* will be in bits 24 to 31, the second in bits 16 to 23, the third in bits
* 8 to 15, and the fourth in bits 0 to 7.
*
* @param address
* The address to convert
* @return The IP address as 32-bit value
* @throws NumberFormatException
* if a part of the string can not be parsed using
* {@link Integer#parseInt(java.lang.String)}
* @throws java.util.NoSuchElementException
* if <code>address</code> contains less than 3 dots
*/
public static int convertToBytes(String address) {
StringTokenizer addressTokens = new StringTokenizer(address, ".");
int bytes = Integer.parseInt(addressTokens.nextToken()) << 24 | Integer.parseInt(addressTokens.nextToken()) << 16 | Integer.parseInt(addressTokens.nextToken()) << 8 | Integer.parseInt(addressTokens.nextToken());
return bytes;
}
/**
* Checks whether the given address matches this matcher's address.
*
* @param inetAddress
* The address to match to this matcher
* @return <code>true</code> if <code>inetAddress</code> matches the
* specification of this matcher, <code>false</code> otherwise
*/
@Override
public boolean matches(InetAddress inetAddress) {
if (!(inetAddress instanceof Inet4Address)) return false;
int matchAddress = convertToBytes(inetAddress.getHostAddress());
return (matchAddress & networkMask) == (address & networkMask);
}
/**
* Shortcut method for creating a new Inet4AddressMatcher and matching
* <code>address</code> to it.
*
* @param cidrHostname
* The host specification to match
* @param address
* The address to match
* @return <code>true</code> if <code>address</code> matches the
* specification in <code>cidrHostname</code>, <code>false</code>
* otherwise
* @see #Inet4AddressMatcher(String)
* @see #matches(InetAddress)
*/
public static boolean matches(String cidrHostname, InetAddress address) {
return new Inet4AddressMatcher(cidrHostname).matches(address);
}
@Override
public String getHumanRepresentation() {
if(networkMask == -1)
return convertToString(address);
else
return convertToString(address)+'/'+convertToString(networkMask);
}
private String convertToString(int addr) {
StringBuilder sb = new StringBuilder();
for(int i=0;i<4;i++) {
int x = addr >>> 24;
addr = addr << 8;
if(i != 0) sb.append('.');
sb.append(x);
}
return sb.toString();
}
}