Pavlin Radoslavov

Reimplementation of classes Ip4Address/Ip6Address/Ip4Prefix/Ip6Prefix

and the corresponding unit tests.

* Reimplemented classes Ip4Address and Ip6Address by inheriting from
  class IpAddress
* Reimplemented classes Ip4Prefix and Ip6Prefix by inheriting from
  class IpPrefix
* Reimplemented the unit tests Ip4AddressTest and Ip6AddressTest to
  match the corresponding IpAddressTest unit tests
* Reimplemented the unit tests Ip4PrefixTest and Ip6PrefixTest to
  match the corresponding IpPrefixTest unit tests
* Minor refactoring/cleanup of classes IpAddress and IpPrefix
......@@ -15,110 +15,90 @@
*/
package org.onlab.packet;
import java.util.Objects;
/**
* The class representing an IPv4 network address.
* This class is immutable.
*/
public final class Ip4Prefix {
private final Ip4Address address; // The IPv4 address
private final short prefixLen; // The prefix length
public final class Ip4Prefix extends IpPrefix {
public static final IpAddress.Version VERSION = IpAddress.Version.INET;
// Maximum network mask length
public static final int MAX_MASK_LENGTH = IpPrefix.MAX_INET_MASK_LENGTH;
/**
* Default constructor.
*/
public Ip4Prefix() {
this.address = new Ip4Address();
this.prefixLen = 0;
}
/**
* Copy constructor.
* Constructor for given IPv4 address, and a prefix length.
*
* @param other the object to copy from
* @param address the IPv4 address
* @param prefixLength the prefix length
* @throws IllegalArgumentException if the prefix length value is invalid
*/
public Ip4Prefix(Ip4Prefix other) {
this.address = new Ip4Address(other.address);
this.prefixLen = other.prefixLen;
private Ip4Prefix(Ip4Address address, int prefixLength) {
super(address, prefixLength);
}
/**
* Constructor for a given address and prefix length.
* Returns the IPv4 address value of the prefix.
*
* @param address the address to use
* @param prefixLen the prefix length to use
* @return the IPv4 address value of the prefix
*/
public Ip4Prefix(Ip4Address address, short prefixLen) {
this.address = Ip4Address.makeMaskedAddress(address, prefixLen);
this.prefixLen = prefixLen;
public Ip4Address address() {
IpAddress a = super.address();
return (Ip4Address) a;
}
/**
* Constructs an IPv4 prefix from a string representation of the
* prefix.
*<p>
* Example: "1.2.0.0/16"
* Converts an integer and a prefix length into an IPv4 prefix.
*
* @param value the value to use
* @param address an integer representing the IPv4 address
* @param prefixLength the prefix length
* @return an IPv4 prefix
* @throws IllegalArgumentException if the prefix length value is invalid
*/
public Ip4Prefix(String value) {
String[] splits = value.split("/");
if (splits.length != 2) {
throw new IllegalArgumentException("Specified IPv4 prefix must contain an IPv4 " +
"address and a prefix length separated by '/'");
}
this.prefixLen = Short.decode(splits[1]);
this.address = Ip4Address.makeMaskedAddress(new Ip4Address(splits[0]),
this.prefixLen);
public static Ip4Prefix valueOf(int address, int prefixLength) {
return new Ip4Prefix(Ip4Address.valueOf(address), prefixLength);
}
/**
* Gets the address value of the IPv4 prefix.
* Converts a byte array and a prefix length into an IPv4 prefix.
*
* @return the address value of the IPv4 prefix
* @param address the IPv4 address value stored in network byte order
* @param prefixLength the prefix length
* @return an IPv4 prefix
* @throws IllegalArgumentException if the prefix length value is invalid
*/
public Ip4Address getAddress() {
return address;
public static Ip4Prefix valueOf(byte[] address, int prefixLength) {
return new Ip4Prefix(Ip4Address.valueOf(address), prefixLength);
}
/**
* Gets the prefix length value of the IPv4 prefix.
* Converts an IPv4 address and a prefix length into an IPv4 prefix.
*
* @return the prefix length value of the IPv4 prefix
* @param address the IPv4 address
* @param prefixLength the prefix length
* @return an IPv4 prefix
* @throws IllegalArgumentException if the prefix length value is invalid
*/
public short getPrefixLen() {
return prefixLen;
public static Ip4Prefix valueOf(Ip4Address address, int prefixLength) {
return new Ip4Prefix(address, prefixLength);
}
/**
* Converts the IPv4 prefix value to an "address/prefixLen" string.
* Converts a CIDR (slash) notation string (e.g., "10.1.0.0/16")
* into an IPv4 prefix.
*
* @return the IPv4 prefix value as an "address/prefixLen" string
* @param address an IP prefix in string form (e.g., "10.1.0.0/16")
* @return an IPv4 prefix
* @throws IllegalArgumentException if the arguments are invalid
*/
@Override
public String toString() {
return this.address.toString() + "/" + this.prefixLen;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof Ip4Prefix)) {
return false;
public static Ip4Prefix valueOf(String address) {
final String[] parts = address.split("/");
if (parts.length != 2) {
String msg = "Malformed IPv4 prefix string: " + address + "." +
"Address must take form \"x.x.x.x/y\"";
throw new IllegalArgumentException(msg);
}
Ip4Address ipAddress = Ip4Address.valueOf(parts[0]);
int prefixLength = Integer.parseInt(parts[1]);
Ip4Prefix otherIp4Prefix = (Ip4Prefix) other;
return Objects.equals(this.address, otherIp4Prefix.address)
&& this.prefixLen == otherIp4Prefix.prefixLen;
}
@Override
public int hashCode() {
return Objects.hash(address, prefixLen);
return new Ip4Prefix(ipAddress, prefixLength);
}
}
......
......@@ -15,110 +15,79 @@
*/
package org.onlab.packet;
import java.util.Objects;
/**
* The class representing an IPv6 network address.
* This class is immutable.
*/
public final class Ip6Prefix {
private final Ip6Address address; // The IPv6 address
private final short prefixLen; // The prefix length
/**
* Default constructor.
*/
public Ip6Prefix() {
this.address = new Ip6Address();
this.prefixLen = 0;
}
/**
* Copy constructor.
*
* @param other the object to copy from
*/
public Ip6Prefix(Ip6Prefix other) {
this.address = new Ip6Address(other.address);
this.prefixLen = other.prefixLen;
}
public final class Ip6Prefix extends IpPrefix {
public static final IpAddress.Version VERSION = IpAddress.Version.INET6;
// Maximum network mask length
public static final int MAX_MASK_LENGTH = IpPrefix.MAX_INET6_MASK_LENGTH;
/**
* Constructor for a given address and prefix length.
* Constructor for given IPv6 address, and a prefix length.
*
* @param address the address to use
* @param prefixLen the prefix length to use
* @param address the IPv6 address
* @param prefixLength the prefix length
* @throws IllegalArgumentException if the prefix length value is invalid
*/
public Ip6Prefix(Ip6Address address, short prefixLen) {
this.address = Ip6Address.makeMaskedAddress(address, prefixLen);
this.prefixLen = prefixLen;
private Ip6Prefix(Ip6Address address, int prefixLength) {
super(address, prefixLength);
}
/**
* Constructs an IPv6 prefix from a string representation of the
* prefix.
*<p>
* Example: "1111:2222::/32"
* Returns the IPv6 address value of the prefix.
*
* @param value the value to use
* @return the IPv6 address value of the prefix
*/
public Ip6Prefix(String value) {
String[] splits = value.split("/");
if (splits.length != 2) {
throw new IllegalArgumentException("Specified IPv6 prefix must contain an IPv6 " +
"address and a prefix length separated by '/'");
}
this.prefixLen = Short.decode(splits[1]);
this.address = Ip6Address.makeMaskedAddress(new Ip6Address(splits[0]),
this.prefixLen);
public Ip6Address address() {
IpAddress a = super.address();
return (Ip6Address) a;
}
/**
* Gets the address value of the IPv6 prefix.
* Converts a byte array and a prefix length into an IPv6 prefix.
*
* @return the address value of the IPv6 prefix
* @param address the IPv6 address value stored in network byte order
* @param prefixLength the prefix length
* @return an IPv6 prefix
* @throws IllegalArgumentException if the prefix length value is invalid
*/
public Ip6Address getAddress() {
return address;
public static Ip6Prefix valueOf(byte[] address, int prefixLength) {
return new Ip6Prefix(Ip6Address.valueOf(address), prefixLength);
}
/**
* Gets the prefix length value of the IPv6 prefix.
* Converts an IPv6 address and a prefix length into an IPv6 prefix.
*
* @return the prefix length value of the IPv6 prefix
* @param address the IPv6 address
* @param prefixLength the prefix length
* @return an IPv6 prefix
* @throws IllegalArgumentException if the prefix length value is invalid
*/
public short getPrefixLen() {
return prefixLen;
public static Ip6Prefix valueOf(Ip6Address address, int prefixLength) {
return new Ip6Prefix(address, prefixLength);
}
/**
* Converts the IPv6 prefix value to an "address/prefixLen" string.
* Converts a CIDR (slash) notation string (e.g., "1111:2222::/64")
* into an IPv6 prefix.
*
* @return the IPv6 prefix value as an "address/prefixLen" string
* @param address an IP prefix in string form (e.g.,"1111:2222::/64")
* @return an IPv6 prefix
* @throws IllegalArgumentException if the arguments are invalid
*/
@Override
public String toString() {
return this.address.toString() + "/" + this.prefixLen;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
public static Ip6Prefix valueOf(String address) {
final String[] parts = address.split("/");
if (parts.length != 2) {
String msg = "Malformed IPv6 prefix string: " + address + "." +
"Address must take form " +
"\"xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/y\"";
throw new IllegalArgumentException(msg);
}
Ip6Address ipAddress = Ip6Address.valueOf(parts[0]);
int prefixLength = Integer.parseInt(parts[1]);
if (!(other instanceof Ip6Prefix)) {
return false;
}
Ip6Prefix otherIp6Prefix = (Ip6Prefix) other;
return Objects.equals(this.address, otherIp6Prefix.address)
&& this.prefixLen == otherIp6Prefix.prefixLen;
}
@Override
public int hashCode() {
return Objects.hash(address, prefixLen);
return new Ip6Prefix(ipAddress, prefixLength);
}
}
......
......@@ -30,8 +30,9 @@ import static com.google.common.base.Preconditions.checkState;
/**
* A class representing an IP address.
* This class is immutable.
*/
public final class IpAddress implements Comparable<IpAddress> {
public class IpAddress implements Comparable<IpAddress> {
// IP Versions
public enum Version { INET, INET6 };
......@@ -52,7 +53,7 @@ public final class IpAddress implements Comparable<IpAddress> {
* (i.e., the most significant byte first)
* @throws IllegalArgumentException if the arguments are invalid
*/
private IpAddress(Version version, byte[] value) {
protected IpAddress(Version version, byte[] value) {
checkArguments(version, value, 0);
this.version = version;
switch (version) {
......@@ -88,7 +89,7 @@ public final class IpAddress implements Comparable<IpAddress> {
}
/**
* Returns the integral value of this IP address.
* Returns the integer value of this IP address.
* TODO: This method should be moved to Ip4Address.
*
* @return the IP address's value as an integer
......@@ -219,31 +220,7 @@ public final class IpAddress implements Comparable<IpAddress> {
* @throws IllegalArgumentException if the arguments are invalid
*/
public static IpAddress makeMaskPrefix(Version version, int prefixLength) {
int addrByteLength = byteLength(version);
int addrBitLength = addrByteLength * Byte.SIZE;
// Verify the prefix length
if ((prefixLength < 0) || (prefixLength > addrBitLength)) {
final String msg = "Invalid IP prefix length: " + prefixLength +
". Must be in the interval [0, " + addrBitLength + "].";
throw new IllegalArgumentException(msg);
}
// Number of bytes and extra bits that should be all 1s
int maskBytes = prefixLength / Byte.SIZE;
int maskBits = prefixLength % Byte.SIZE;
byte[] mask = new byte[addrByteLength];
// Set the bytes and extra bits to 1s
for (int i = 0; i < maskBytes; i++) {
mask[i] = (byte) 0xff; // Set mask bytes to 1s
}
for (int i = maskBytes; i < addrByteLength; i++) {
mask[i] = 0; // Set remaining bytes to 0s
}
if (maskBits > 0) {
mask[maskBytes] = (byte) (0xff << (Byte.SIZE - maskBits));
}
byte[] mask = makeMaskPrefixArray(version, prefixLength);
return new IpAddress(version, mask);
}
......@@ -251,24 +228,26 @@ public final class IpAddress implements Comparable<IpAddress> {
* Creates an IP address by masking it with a network mask of given
* mask length.
*
* @param addr the address to mask
* @param address the address to mask
* @param prefixLength the length of the mask prefix. Must be in the
* interval [0, 32] for IPv4, or [0, 128] for IPv6
* @return a new IP address that is masked with a mask prefix of the
* specified length
* @throws IllegalArgumentException if the prefix length is invalid
*/
public static IpAddress makeMaskedAddress(final IpAddress addr,
public static IpAddress makeMaskedAddress(final IpAddress address,
int prefixLength) {
IpAddress mask = IpAddress.makeMaskPrefix(addr.version(),
prefixLength);
byte[] net = new byte[mask.octets.length];
// Mask each byte
for (int i = 0; i < net.length; i++) {
net[i] = (byte) (addr.octets[i] & mask.octets[i]);
// TODO: The code below should go away and replaced with generics
if (address instanceof Ip4Address) {
Ip4Address ip4a = (Ip4Address) address;
return Ip4Address.makeMaskedAddress(ip4a, prefixLength);
} else if (address instanceof Ip6Address) {
Ip6Address ip6a = (Ip6Address) address;
return Ip6Address.makeMaskedAddress(ip6a, prefixLength);
} else {
byte[] net = makeMaskedAddressArray(address, prefixLength);
return IpAddress.valueOf(address.version(), net);
}
return IpAddress.valueOf(addr.version(), net);
}
@Override
......@@ -352,8 +331,7 @@ public final class IpAddress implements Comparable<IpAddress> {
* array with the address
* @throws IllegalArgumentException if any of the arguments is invalid
*/
private static void checkArguments(Version version, byte[] value,
int offset) {
static void checkArguments(Version version, byte[] value, int offset) {
// Check the offset and byte array length
int addrByteLength = byteLength(version);
if ((offset < 0) || (offset + addrByteLength > value.length)) {
......@@ -371,4 +349,67 @@ public final class IpAddress implements Comparable<IpAddress> {
throw new IllegalArgumentException(msg);
}
}
/**
* Creates a byte array for IP network mask prefix.
*
* @param version the IP address version
* @param prefixLength the length of the mask prefix. Must be in the
* interval [0, 32] for IPv4, or [0, 128] for IPv6
* @return a byte array that contains a mask prefix of the
* specified length
* @throws IllegalArgumentException if the arguments are invalid
*/
static byte[] makeMaskPrefixArray(Version version, int prefixLength) {
int addrByteLength = byteLength(version);
int addrBitLength = addrByteLength * Byte.SIZE;
// Verify the prefix length
if ((prefixLength < 0) || (prefixLength > addrBitLength)) {
final String msg = "Invalid IP prefix length: " + prefixLength +
". Must be in the interval [0, " + addrBitLength + "].";
throw new IllegalArgumentException(msg);
}
// Number of bytes and extra bits that should be all 1s
int maskBytes = prefixLength / Byte.SIZE;
int maskBits = prefixLength % Byte.SIZE;
byte[] mask = new byte[addrByteLength];
// Set the bytes and extra bits to 1s
for (int i = 0; i < maskBytes; i++) {
mask[i] = (byte) 0xff; // Set mask bytes to 1s
}
for (int i = maskBytes; i < addrByteLength; i++) {
mask[i] = 0; // Set remaining bytes to 0s
}
if (maskBits > 0) {
mask[maskBytes] = (byte) (0xff << (Byte.SIZE - maskBits));
}
return mask;
}
/**
* Creates a byte array that represents an IP address masked with
* a network mask of given mask length.
*
* @param addr the address to mask
* @param prefixLength the length of the mask prefix. Must be in the
* interval [0, 32] for IPv4, or [0, 128] for IPv6
* @return a byte array that represents the IP address masked with
* a mask prefix of the specified length
* @throws IllegalArgumentException if the prefix length is invalid
*/
static byte[] makeMaskedAddressArray(final IpAddress addr,
int prefixLength) {
byte[] mask = IpAddress.makeMaskPrefixArray(addr.version(),
prefixLength);
byte[] net = new byte[mask.length];
// Mask each byte
for (int i = 0; i < net.length; i++) {
net[i] = (byte) (addr.octets[i] & mask[i]);
}
return net;
}
}
......
......@@ -20,12 +20,13 @@ import java.util.Objects;
/**
* A class representing an IP prefix. A prefix consists of an IP address and
* a subnet mask.
* This class is immutable.
* <p>
* NOTE: The stored IP address in the result IP prefix is masked to
* contain zeroes in all bits after the prefix length.
* </p>
*/
public final class IpPrefix {
public class IpPrefix {
// Maximum network mask length
public static final int MAX_INET_MASK_LENGTH = IpAddress.INET_BIT_LENGTH;
public static final int MAX_INET6_MASK_LENGTH = IpAddress.INET6_BIT_LENGTH;
......@@ -40,7 +41,7 @@ public final class IpPrefix {
* @param prefixLength the prefix length
* @throws IllegalArgumentException if the prefix length value is invalid
*/
private IpPrefix(IpAddress address, int prefixLength) {
protected IpPrefix(IpAddress address, int prefixLength) {
checkPrefixLength(address.version(), prefixLength);
this.address = IpAddress.makeMaskedAddress(address, prefixLength);
this.prefixLength = (short) prefixLength;
......@@ -100,7 +101,7 @@ public final class IpPrefix {
}
/**
* Converts an IP address and a prefix length into IP prefix.
* Converts an IP address and a prefix length into an IP prefix.
*
* @param address the IP address
* @param prefixLength the prefix length
......@@ -112,10 +113,11 @@ public final class IpPrefix {
}
/**
* Converts a CIDR (slash) notation string (e.g., "10.1.0.0/16") into an
* IP prefix.
* Converts a CIDR (slash) notation string (e.g., "10.1.0.0/16" or
* "1111:2222::/64") into an IP prefix.
*
* @param address an IP prefix in string form, e.g. "10.1.0.0/16"
* @param address an IP prefix in string form (e.g. "10.1.0.0/16" or
* "1111:2222::/64")
* @return an IP prefix
* @throws IllegalArgumentException if the arguments are invalid
*/
......@@ -123,7 +125,8 @@ public final class IpPrefix {
final String[] parts = address.split("/");
if (parts.length != 2) {
String msg = "Malformed IP prefix string: " + address + "." +
"Address must take form \"x.x.x.x/y\"";
"Address must take form \"x.x.x.x/y\" or " +
"\"xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/y\"";
throw new IllegalArgumentException(msg);
}
IpAddress ipAddress = IpAddress.valueOf(parts[0]);
......
......@@ -16,6 +16,7 @@
package org.onlab.packet;
import com.google.common.testing.EqualsTester;
import org.junit.Ignore;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
......@@ -32,6 +33,7 @@ public class IpPrefixTest {
/**
* Tests the immutability of {@link IpPrefix}.
*/
@Ignore("The class is not pure immutable, because it is not 'final'")
@Test
public void testImmutable() {
assertThatClassIsImmutable(IpPrefix.class);
......