blob: 489beda4e29deb29d5089c4bf559c5931145f1ad [file] [log] [blame]
package net.onrc.onos.apps.sdnip;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import com.google.common.net.InetAddresses;
/**
* Represents an IP prefix.
* <p/>
* It is made up of an IP address and a number of significant bits in the
* prefix (i.e. the size of the network part of the address).
* E.g. {@code 192.168.0.0/16}.
* <p/>
* Currently only IPv4 is supported, so a prefix length can be up to 32 bits.
*/
public class Prefix {
/**
* The length of addresses this class can represent prefixes of, in bytes.
*/
public static final int ADDRESS_LENGTH_BYTES = 4;
/**
* The length of addresses this class can represent prefixes of, in bits.
*/
public static final int MAX_PREFIX_LENGTH = Byte.SIZE * ADDRESS_LENGTH_BYTES;
public static final int MIN_PREFIX_LENGTH = 0;
private static final int BINARY_RADIX = 2;
private final int prefixLength;
private final byte[] address;
private final String binaryString;
// For verifying the arguments and pretty printing
private final InetAddress inetAddress;
/**
* Class constructor, taking an byte array representing and IP address and
* a prefix length.
* <p/>
* The valid values for addr and prefixLength are bounded by
* {@link #ADDRESS_LENGTH_BYTES}.
* <p/>
* A valid addr array satisfies
* {@code addr.length == }{@value #ADDRESS_LENGTH_BYTES}.
* <p/>
* A valid prefixLength satisfies
* {@code (prefixLength >= 0 && prefixLength <=} {@link Byte#SIZE}
* {@code * }{@value #ADDRESS_LENGTH_BYTES}{@code )}.
*
* @param addr a byte array representing the address
* @param prefixLength length of the prefix of the specified address
*/
public Prefix(byte[] addr, int prefixLength) {
if (addr == null || addr.length != ADDRESS_LENGTH_BYTES ||
prefixLength < MIN_PREFIX_LENGTH ||
prefixLength > MAX_PREFIX_LENGTH) {
throw new IllegalArgumentException();
}
address = canonicalizeAddress(addr, prefixLength);
this.prefixLength = prefixLength;
binaryString = createBinaryString();
try {
inetAddress = InetAddress.getByAddress(address);
} catch (UnknownHostException e) {
throw new IllegalArgumentException("Couldn't parse IP address", e);
}
}
/**
* Class constructor, taking an address in String format and a prefix
* length. The address must be in dot-notation form (e.g. {@code 0.0.0.0}).
*
* @param strAddress a String representing the address
* @param prefixLength length of the prefix of the specified address
*/
public Prefix(String strAddress, int prefixLength) {
byte[] addr = null;
addr = InetAddresses.forString(strAddress).getAddress();
if (addr == null || addr.length != ADDRESS_LENGTH_BYTES ||
prefixLength < MIN_PREFIX_LENGTH ||
prefixLength > MAX_PREFIX_LENGTH) {
throw new IllegalArgumentException();
}
address = canonicalizeAddress(addr, prefixLength);
this.prefixLength = prefixLength;
binaryString = createBinaryString();
try {
inetAddress = InetAddress.getByAddress(address);
} catch (UnknownHostException e) {
throw new IllegalArgumentException("Couldn't parse IP address", e);
}
}
/**
* This method takes a byte array passed in by the user and ensures it
* conforms to the format we want. The byte array can contain anything,
* but only some of the bits are significant. The bits after
* prefixLengthValue are not significant, and this method will zero them
* out in order to ensure there is a canonical representation for each
* prefix. This simplifies the equals and hashcode methods, because once we
* have a canonical byte array representation we can simply compare byte
* arrays to test equality.
*
* @param addressValue the byte array to canonicalize
* @param prefixLengthValue The length of the prefix. Bits up to and including
* prefixLength are significant, bits following prefixLength are not
* significant and will be zeroed out.
* @return the canonicalized representation of the prefix
*/
private byte[] canonicalizeAddress(byte[] addressValue,
int prefixLengthValue) {
byte[] result = new byte[addressValue.length];
if (prefixLengthValue == 0) {
for (int i = 0; i < ADDRESS_LENGTH_BYTES; i++) {
result[i] = 0;
}
return result;
}
result = Arrays.copyOf(addressValue, addressValue.length);
//Set all bytes after the end of the prefix to 0
int lastByteIndex = (prefixLengthValue - 1) / Byte.SIZE;
for (int i = lastByteIndex; i < ADDRESS_LENGTH_BYTES; i++) {
result[i] = 0;
}
byte lastByte = addressValue[lastByteIndex];
byte mask = 0;
byte msb = (byte) 0x80;
int lastBit = (prefixLengthValue - 1) % Byte.SIZE;
for (int i = 0; i < Byte.SIZE; i++) {
if (i <= lastBit) {
mask |= (msb >> i);
}
}
result[lastByteIndex] = (byte) (lastByte & mask);
return result;
}
/**
* Creates the binary string representation of the prefix.
* The strings length is equal to prefixLength.
*
* @return the binary string representation
*/
private String createBinaryString() {
if (prefixLength == 0) {
return "";
}
StringBuilder result = new StringBuilder(prefixLength);
for (int i = 0; i < address.length; i++) {
byte b = address[i];
for (int j = 0; j < Byte.SIZE; j++) {
byte mask = (byte) (0x80 >>> j);
result.append(((b & mask) == 0) ? "0" : "1");
if (i * Byte.SIZE + j == prefixLength - 1) {
return result.toString();
}
}
}
return result.toString();
}
/**
* Gets the length of the prefix of the address.
*
* @return the prefix length
*/
public int getPrefixLength() {
return prefixLength;
}
/**
* Gets the address.
*
* @return the address as a byte array
*/
public byte[] getAddress() {
return Arrays.copyOf(address, address.length);
}
/**
* Gets the InetAddress.
*
* @return the inetAddress
*/
public InetAddress getInetAddress() {
return inetAddress;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Prefix)) {
return false;
}
Prefix otherPrefix = (Prefix) other;
return (Arrays.equals(address, otherPrefix.address)) &&
(prefixLength == otherPrefix.prefixLength);
}
@Override
public int hashCode() {
int hash = 17;
hash = 31 * hash + prefixLength;
hash = 31 * hash + Arrays.hashCode(address);
return hash;
}
@Override
public String toString() {
return inetAddress.getHostAddress() + "/" + prefixLength;
}
/**
* Gets a binary string representation of the prefix.
*
* @return a binary string representation
*/
public String toBinaryString() {
return binaryString;
}
/**
* Creates a Prefix object from a binary string.
* <p/>
* This is the reverse operation of {@link Prefix#toBinaryString()}. It
* takes a binary string (a String containing only '0' and '1'
* characters) and parses it to create a corresponding Prefix object.
*
* @param binaryString the binary string to convert to Prefix
* @return a Prefix object representing the same prefix as the input string
*/
public static Prefix fromBinaryString(String binaryString) {
if (binaryString.length() > MAX_PREFIX_LENGTH) {
throw new IllegalArgumentException(
"Binary string length must not be greater than "
+ MAX_PREFIX_LENGTH);
}
for (int i = 0; i < binaryString.length(); i++) {
char character = binaryString.charAt(i);
if (character != '0' && character != '1') {
throw new IllegalArgumentException(
"Binary string may only contain the characters "
+ "\'0\' or \'1\'");
}
}
// Pad the binary string out to be MAX_PREFIX_LENGTH bits long
StringBuilder paddedBinaryString = new StringBuilder(MAX_PREFIX_LENGTH);
paddedBinaryString.append(binaryString);
for (int i = binaryString.length(); i < MAX_PREFIX_LENGTH; i++) {
paddedBinaryString.append('0');
}
// BigInteger will parse the binary string to an integer using radix 2
BigInteger bigInteger =
new BigInteger(paddedBinaryString.toString(), BINARY_RADIX);
// Convert the integer to a byte array
ByteBuffer bb = ByteBuffer.allocate(ADDRESS_LENGTH_BYTES);
bb.putInt(bigInteger.intValue());
return new Prefix(bb.array(), binaryString.length());
}
}