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