Skip to content

Commit

Permalink
Move the big integer conversion code into InetAddresses
Browse files Browse the repository at this point in the history
RELNOTES=Add toBigInteger and fromIpv4BigInteger/fromIpv6BigInteger to InetAddresses for manipulating InetAddresses as BigIntegers

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=280282088
  • Loading branch information
moodysalem authored and cpovirk committed Nov 14, 2019
1 parent cc6cff2 commit d7a0b3d
Show file tree
Hide file tree
Showing 4 changed files with 312 additions and 0 deletions.
Expand Up @@ -20,6 +20,7 @@

import com.google.common.collect.ImmutableSet;
import com.google.common.testing.NullPointerTester;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
Expand Down Expand Up @@ -712,4 +713,80 @@ public void testDecrementIPv6() throws UnknownHostException {
} catch (IllegalArgumentException expected) {
}
}

public void testFromIpv4BigIntegerThrowsLessThanZero() {
try {
InetAddresses.fromIpv4BigInteger(BigInteger.valueOf(-1L));
fail();
} catch (IllegalArgumentException expected) {
assertEquals("BigInteger must be greater than or equal to 0", expected.getMessage());
}
}

public void testFromIpv6BigIntegerThrowsLessThanZero() {
try {
InetAddresses.fromIpv6BigInteger(BigInteger.valueOf(-1L));
fail();
} catch (IllegalArgumentException expected) {
assertEquals("BigInteger must be greater than or equal to 0", expected.getMessage());
}
}

public void testFromIpv4BigIntegerValid() {
checkBigIntegerConversion("0.0.0.0", BigInteger.ZERO);
checkBigIntegerConversion("0.0.0.1", BigInteger.ONE);
checkBigIntegerConversion("127.255.255.255", BigInteger.valueOf(Integer.MAX_VALUE));
checkBigIntegerConversion(
"255.255.255.254", BigInteger.valueOf(Integer.MAX_VALUE).multiply(BigInteger.valueOf(2)));
checkBigIntegerConversion(
"255.255.255.255", BigInteger.ONE.shiftLeft(32).subtract(BigInteger.ONE));
}

public void testFromIpv6BigIntegerValid() {
checkBigIntegerConversion("::", BigInteger.ZERO);
checkBigIntegerConversion("::1", BigInteger.ONE);
checkBigIntegerConversion("::7fff:ffff", BigInteger.valueOf(Integer.MAX_VALUE));
checkBigIntegerConversion("::7fff:ffff:ffff:ffff", BigInteger.valueOf(Long.MAX_VALUE));
checkBigIntegerConversion(
"::ffff:ffff:ffff:ffff", BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE));
checkBigIntegerConversion(
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
BigInteger.ONE.shiftLeft(128).subtract(BigInteger.ONE));
}

public void testFromIpv4BigIntegerInputTooLarge() {
try {
InetAddresses.fromIpv4BigInteger(BigInteger.ONE.shiftLeft(32).add(BigInteger.ONE));
fail();
} catch (IllegalArgumentException expected) {
assertEquals(
"BigInteger cannot be converted to InetAddress because it has more than 4 bytes:"
+ " 4294967297",
expected.getMessage());
}
}

public void testFromIpv6BigIntegerInputTooLarge() {
try {
InetAddresses.fromIpv6BigInteger(BigInteger.ONE.shiftLeft(128).add(BigInteger.ONE));
fail();
} catch (IllegalArgumentException expected) {
assertEquals(
"BigInteger cannot be converted to InetAddress because it has more than 16 bytes:"
+ " 340282366920938463463374607431768211457",
expected.getMessage());
}
}

/** Checks that the IP converts to the big integer and the big integer converts to the IP. */
private static void checkBigIntegerConversion(String ip, BigInteger bigIntegerIp) {
InetAddress address = InetAddresses.forString(ip);
boolean isIpv6 = address instanceof Inet6Address;
assertEquals(bigIntegerIp, InetAddresses.toBigInteger(address));
assertEquals(
address,
isIpv6
? InetAddresses.fromIpv6BigInteger(bigIntegerIp)
: InetAddresses.fromIpv4BigInteger(bigIntegerIp));
}
}
79 changes: 79 additions & 0 deletions android/guava/src/com/google/common/net/InetAddresses.java
Expand Up @@ -25,6 +25,7 @@
import com.google.common.hash.Hashing;
import com.google.common.io.ByteStreams;
import com.google.common.primitives.Ints;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
Expand Down Expand Up @@ -895,6 +896,19 @@ public static int coerceToInteger(InetAddress ip) {
return ByteStreams.newDataInput(getCoercedIPv4Address(ip).getAddress()).readInt();
}

/**
* Returns a BigInteger representing the address.
*
* <p>Unlike {@code coerceToInteger}, IPv6 addresses are not coerced to IPv4 addresses.
*
* @param address {@link InetAddress} to convert
* @return {@code BigInteger} representation of the address
* @since NEXT
*/
public static BigInteger toBigInteger(InetAddress address) {
return new BigInteger(1, address.getAddress());
}

/**
* Returns an Inet4Address having the integer value specified by the argument.
*
Expand All @@ -905,6 +919,71 @@ public static Inet4Address fromInteger(int address) {
return getInet4Address(Ints.toByteArray(address));
}

/**
* Returns the {@code Inet4Address} corresponding to a given {@code BigInteger}.
*
* @param address BigInteger representing the IPv4 address
* @return Inet4Address representation of the given BigInteger
* @throws IllegalArgumentException if the BigInteger is not between 0 and 2^32-1
* @since NEXT
*/
public static Inet4Address fromIpv4BigInteger(BigInteger address) {
return (Inet4Address) fromBigInteger(address, false);
}
/**
* Returns the {@code Inet6Address} corresponding to a given {@code BigInteger}.
*
* @param address BigInteger representing the IPv6 address
* @return Inet6Address representation of the given BigInteger
* @throws IllegalArgumentException if the BigInteger is not between 0 and 2^128-1
* @since NEXT
*/
public static Inet6Address fromIpv6BigInteger(BigInteger address) {
return (Inet6Address) fromBigInteger(address, true);
}

/**
* Converts a BigInteger to either an IPv4 or IPv6 address. If the IP is IPv4, it must be
* constrainted to 32 bits, otherwise it is constrained to 128 bits.
*
* @param address the address represented as a big integer
* @param isIpv6 whether the created address should be IPv4 or IPv6
* @return the BigInteger converted to an address
* @throws IllegalArgumentException if the BigInteger is not between 0 and maximum value for IPv4
* or IPv6 respectively
*/
private static InetAddress fromBigInteger(BigInteger address, boolean isIpv6) {
checkArgument(address.signum() >= 0, "BigInteger must be greater than or equal to 0");

int numBytes = isIpv6 ? 16 : 4;

byte[] addressBytes = address.toByteArray();
byte[] targetCopyArray = new byte[numBytes];

int srcPos = Math.max(0, addressBytes.length - numBytes);
int copyLength = addressBytes.length - srcPos;
int destPos = numBytes - copyLength;

// Check the extra bytes in the BigInteger are all zero.
for (int i = 0; i < srcPos; i++) {
checkArgument(
addressBytes[i] == 0x00,
String.format(
"BigInteger cannot be converted to InetAddress because it has more than %d"
+ " bytes: %s",
numBytes, address));
}

// Copy the bytes into the least significant positions.
System.arraycopy(addressBytes, srcPos, targetCopyArray, destPos, copyLength);

try {
return InetAddress.getByAddress(targetCopyArray);
} catch (UnknownHostException impossible) {
throw new AssertionError(impossible);
}
}

/**
* Returns an address from a <b>little-endian ordered</b> byte array (the opposite of what {@link
* InetAddress#getByAddress} expects).
Expand Down
77 changes: 77 additions & 0 deletions guava-tests/test/com/google/common/net/InetAddressesTest.java
Expand Up @@ -20,6 +20,7 @@

import com.google.common.collect.ImmutableSet;
import com.google.common.testing.NullPointerTester;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
Expand Down Expand Up @@ -712,4 +713,80 @@ public void testDecrementIPv6() throws UnknownHostException {
} catch (IllegalArgumentException expected) {
}
}

public void testFromIpv4BigIntegerThrowsLessThanZero() {
try {
InetAddresses.fromIpv4BigInteger(BigInteger.valueOf(-1L));
fail();
} catch (IllegalArgumentException expected) {
assertEquals("BigInteger must be greater than or equal to 0", expected.getMessage());
}
}

public void testFromIpv6BigIntegerThrowsLessThanZero() {
try {
InetAddresses.fromIpv6BigInteger(BigInteger.valueOf(-1L));
fail();
} catch (IllegalArgumentException expected) {
assertEquals("BigInteger must be greater than or equal to 0", expected.getMessage());
}
}

public void testFromIpv4BigIntegerValid() {
checkBigIntegerConversion("0.0.0.0", BigInteger.ZERO);
checkBigIntegerConversion("0.0.0.1", BigInteger.ONE);
checkBigIntegerConversion("127.255.255.255", BigInteger.valueOf(Integer.MAX_VALUE));
checkBigIntegerConversion(
"255.255.255.254", BigInteger.valueOf(Integer.MAX_VALUE).multiply(BigInteger.valueOf(2)));
checkBigIntegerConversion(
"255.255.255.255", BigInteger.ONE.shiftLeft(32).subtract(BigInteger.ONE));
}

public void testFromIpv6BigIntegerValid() {
checkBigIntegerConversion("::", BigInteger.ZERO);
checkBigIntegerConversion("::1", BigInteger.ONE);
checkBigIntegerConversion("::7fff:ffff", BigInteger.valueOf(Integer.MAX_VALUE));
checkBigIntegerConversion("::7fff:ffff:ffff:ffff", BigInteger.valueOf(Long.MAX_VALUE));
checkBigIntegerConversion(
"::ffff:ffff:ffff:ffff", BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE));
checkBigIntegerConversion(
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
BigInteger.ONE.shiftLeft(128).subtract(BigInteger.ONE));
}

public void testFromIpv4BigIntegerInputTooLarge() {
try {
InetAddresses.fromIpv4BigInteger(BigInteger.ONE.shiftLeft(32).add(BigInteger.ONE));
fail();
} catch (IllegalArgumentException expected) {
assertEquals(
"BigInteger cannot be converted to InetAddress because it has more than 4 bytes:"
+ " 4294967297",
expected.getMessage());
}
}

public void testFromIpv6BigIntegerInputTooLarge() {
try {
InetAddresses.fromIpv6BigInteger(BigInteger.ONE.shiftLeft(128).add(BigInteger.ONE));
fail();
} catch (IllegalArgumentException expected) {
assertEquals(
"BigInteger cannot be converted to InetAddress because it has more than 16 bytes:"
+ " 340282366920938463463374607431768211457",
expected.getMessage());
}
}

/** Checks that the IP converts to the big integer and the big integer converts to the IP. */
private static void checkBigIntegerConversion(String ip, BigInteger bigIntegerIp) {
InetAddress address = InetAddresses.forString(ip);
boolean isIpv6 = address instanceof Inet6Address;
assertEquals(bigIntegerIp, InetAddresses.toBigInteger(address));
assertEquals(
address,
isIpv6
? InetAddresses.fromIpv6BigInteger(bigIntegerIp)
: InetAddresses.fromIpv4BigInteger(bigIntegerIp));
}
}
79 changes: 79 additions & 0 deletions guava/src/com/google/common/net/InetAddresses.java
Expand Up @@ -25,6 +25,7 @@
import com.google.common.hash.Hashing;
import com.google.common.io.ByteStreams;
import com.google.common.primitives.Ints;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
Expand Down Expand Up @@ -890,6 +891,19 @@ public static int coerceToInteger(InetAddress ip) {
return ByteStreams.newDataInput(getCoercedIPv4Address(ip).getAddress()).readInt();
}

/**
* Returns a BigInteger representing the address.
*
* <p>Unlike {@code coerceToInteger}, IPv6 addresses are not coerced to IPv4 addresses.
*
* @param address {@link InetAddress} to convert
* @return {@code BigInteger} representation of the address
* @since NEXT
*/
public static BigInteger toBigInteger(InetAddress address) {
return new BigInteger(1, address.getAddress());
}

/**
* Returns an Inet4Address having the integer value specified by the argument.
*
Expand All @@ -900,6 +914,71 @@ public static Inet4Address fromInteger(int address) {
return getInet4Address(Ints.toByteArray(address));
}

/**
* Returns the {@code Inet4Address} corresponding to a given {@code BigInteger}.
*
* @param address BigInteger representing the IPv4 address
* @return Inet4Address representation of the given BigInteger
* @throws IllegalArgumentException if the BigInteger is not between 0 and 2^32-1
* @since NEXT
*/
public static Inet4Address fromIpv4BigInteger(BigInteger address) {
return (Inet4Address) fromBigInteger(address, false);
}
/**
* Returns the {@code Inet6Address} corresponding to a given {@code BigInteger}.
*
* @param address BigInteger representing the IPv6 address
* @return Inet6Address representation of the given BigInteger
* @throws IllegalArgumentException if the BigInteger is not between 0 and 2^128-1
* @since NEXT
*/
public static Inet6Address fromIpv6BigInteger(BigInteger address) {
return (Inet6Address) fromBigInteger(address, true);
}

/**
* Converts a BigInteger to either an IPv4 or IPv6 address. If the IP is IPv4, it must be
* constrainted to 32 bits, otherwise it is constrained to 128 bits.
*
* @param address the address represented as a big integer
* @param isIpv6 whether the created address should be IPv4 or IPv6
* @return the BigInteger converted to an address
* @throws IllegalArgumentException if the BigInteger is not between 0 and maximum value for IPv4
* or IPv6 respectively
*/
private static InetAddress fromBigInteger(BigInteger address, boolean isIpv6) {
checkArgument(address.signum() >= 0, "BigInteger must be greater than or equal to 0");

int numBytes = isIpv6 ? 16 : 4;

byte[] addressBytes = address.toByteArray();
byte[] targetCopyArray = new byte[numBytes];

int srcPos = Math.max(0, addressBytes.length - numBytes);
int copyLength = addressBytes.length - srcPos;
int destPos = numBytes - copyLength;

// Check the extra bytes in the BigInteger are all zero.
for (int i = 0; i < srcPos; i++) {
checkArgument(
addressBytes[i] == 0x00,
String.format(
"BigInteger cannot be converted to InetAddress because it has more than %d"
+ " bytes: %s",
numBytes, address));
}

// Copy the bytes into the least significant positions.
System.arraycopy(addressBytes, srcPos, targetCopyArray, destPos, copyLength);

try {
return InetAddress.getByAddress(targetCopyArray);
} catch (UnknownHostException impossible) {
throw new AssertionError(impossible);
}
}

/**
* Returns an address from a <b>little-endian ordered</b> byte array (the opposite of what {@link
* InetAddress#getByAddress} expects).
Expand Down

0 comments on commit d7a0b3d

Please sign in to comment.