NetUtil IPv6 bugs related to IPv4 and compression
Motivation: NetUtil#getByName and NetUtil#isValidIpV6Address do not strictly enforce the format of IPv4 addresses that are allowed to be embedded in IPv6 addresses as specified in https://tools.ietf.org/html/rfc4291#section-2.5.5. This may lead to invalid addresses being parsed, or invalid addresses being considered valid. Compression of a single IPv6 word was also not handled correctly if there are 7 : characters. Modifications: - NetUtil#isValidIpV6Address should enforce the IPv4-Compatible and IPv4-Mapped are the only valid formats for including IPv4 addresses as specified in https://tools.ietf.org/html/rfc4291#section-2.5.5 - NetUtil#getByName should more stritcly parse IPv6 addresses which contain IPv4 addresses as specified in https://tools.ietf.org/html/rfc4291#section-2.5.5 - NetUtil should allow compression even if the number of : characters is 7. - NetUtil#createByteArrayFromIpAddressString should use the same IP string to byte[] translation which is used in NetUtil#getByName Result: NetUtil#getByName and NetUtil#isValidIpV6Address respect the IPv6 RFC which defines the valid formats for embedding IPv4 addresses.
This commit is contained in:
parent
a45f9d7939
commit
9cb858fcf6
@ -314,8 +314,7 @@ public final class NetUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an byte[] based on an ipAddressString. No error handling is
|
* Creates an byte[] based on an ipAddressString. No error handling is performed here.
|
||||||
* performed here.
|
|
||||||
*/
|
*/
|
||||||
public static byte[] createByteArrayFromIpAddressString(String ipAddressString) {
|
public static byte[] createByteArrayFromIpAddressString(String ipAddressString) {
|
||||||
|
|
||||||
@ -343,112 +342,11 @@ public final class NetUtil {
|
|||||||
ipAddressString = ipAddressString.substring(0, percentPos);
|
ipAddressString = ipAddressString.substring(0, percentPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
StringTokenizer tokenizer = new StringTokenizer(ipAddressString, ":.", true);
|
return getIPv6ByName(ipAddressString, true);
|
||||||
ArrayList<String> hexStrings = new ArrayList<String>();
|
|
||||||
ArrayList<String> decStrings = new ArrayList<String>();
|
|
||||||
String token = "";
|
|
||||||
String prevToken = "";
|
|
||||||
int doubleColonIndex = -1; // If a double colon exists, we need to
|
|
||||||
// insert 0s.
|
|
||||||
|
|
||||||
// Go through the tokens, including the seperators ':' and '.'
|
|
||||||
// When we hit a : or . the previous token will be added to either
|
|
||||||
// the hex list or decimal list. In the case where we hit a ::
|
|
||||||
// we will save the index of the hexStrings so we can add zeros
|
|
||||||
// in to fill out the string
|
|
||||||
while (tokenizer.hasMoreTokens()) {
|
|
||||||
prevToken = token;
|
|
||||||
token = tokenizer.nextToken();
|
|
||||||
|
|
||||||
if (":".equals(token)) {
|
|
||||||
if (":".equals(prevToken)) {
|
|
||||||
doubleColonIndex = hexStrings.size();
|
|
||||||
} else if (!prevToken.isEmpty()) {
|
|
||||||
hexStrings.add(prevToken);
|
|
||||||
}
|
|
||||||
} else if (".".equals(token)) {
|
|
||||||
decStrings.add(prevToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (":".equals(prevToken)) {
|
|
||||||
if (":".equals(token)) {
|
|
||||||
doubleColonIndex = hexStrings.size();
|
|
||||||
} else {
|
|
||||||
hexStrings.add(token);
|
|
||||||
}
|
|
||||||
} else if (".".equals(prevToken)) {
|
|
||||||
decStrings.add(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// figure out how many hexStrings we should have
|
|
||||||
// also check if it is a IPv4 address
|
|
||||||
int hexStringsLength = 8;
|
|
||||||
|
|
||||||
// If we have an IPv4 address tagged on at the end, subtract
|
|
||||||
// 4 bytes, or 2 hex words from the total
|
|
||||||
if (!decStrings.isEmpty()) {
|
|
||||||
hexStringsLength -= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we hit a double Colon add the appropriate hex strings
|
|
||||||
if (doubleColonIndex != -1) {
|
|
||||||
int numberToInsert = hexStringsLength - hexStrings.size();
|
|
||||||
for (int i = 0; i < numberToInsert; i ++) {
|
|
||||||
hexStrings.add(doubleColonIndex, "0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] ipByteArray = new byte[IPV6_BYTE_COUNT];
|
|
||||||
|
|
||||||
// Finally convert these strings to bytes...
|
|
||||||
for (int i = 0; i < hexStrings.size(); i ++) {
|
|
||||||
convertToBytes(hexStrings.get(i), ipByteArray, i << 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now if there are any decimal values, we know where they go...
|
|
||||||
for (int i = 0; i < decStrings.size(); i ++) {
|
|
||||||
ipByteArray[i + 12] = (byte) (Integer.parseInt(decStrings.get(i)) & 255);
|
|
||||||
}
|
|
||||||
return ipByteArray;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a 4 character hex word into a 2 byte word equivalent
|
|
||||||
*/
|
|
||||||
private static void convertToBytes(String hexWord, byte[] ipByteArray, int byteIndex) {
|
|
||||||
|
|
||||||
int hexWordLength = hexWord.length();
|
|
||||||
int hexWordIndex = 0;
|
|
||||||
ipByteArray[byteIndex] = 0;
|
|
||||||
ipByteArray[byteIndex + 1] = 0;
|
|
||||||
int charValue;
|
|
||||||
|
|
||||||
// high order 4 bits of first byte
|
|
||||||
if (hexWordLength > 3) {
|
|
||||||
charValue = getIntValue(hexWord.charAt(hexWordIndex ++));
|
|
||||||
ipByteArray[byteIndex] |= charValue << 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// low order 4 bits of the first byte
|
|
||||||
if (hexWordLength > 2) {
|
|
||||||
charValue = getIntValue(hexWord.charAt(hexWordIndex ++));
|
|
||||||
ipByteArray[byteIndex] |= charValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// high order 4 bits of second byte
|
|
||||||
if (hexWordLength > 1) {
|
|
||||||
charValue = getIntValue(hexWord.charAt(hexWordIndex ++));
|
|
||||||
ipByteArray[byteIndex + 1] |= charValue << 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// low order 4 bits of the first byte
|
|
||||||
charValue = getIntValue(hexWord.charAt(hexWordIndex));
|
|
||||||
ipByteArray[byteIndex + 1] |= charValue & 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getIntValue(char c) {
|
private static int getIntValue(char c) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '0':
|
case '0':
|
||||||
@ -581,6 +479,35 @@ public final class NetUtil {
|
|||||||
numberOfPeriods ++;
|
numberOfPeriods ++;
|
||||||
if (numberOfPeriods > 3) {
|
if (numberOfPeriods > 3) {
|
||||||
return false;
|
return false;
|
||||||
|
} else if (numberOfPeriods == 1) {
|
||||||
|
// Verify this address is of the correct structure to contain an IPv4 address.
|
||||||
|
// It must be IPv4-Mapped or IPv4-Compatible
|
||||||
|
// (see https://tools.ietf.org/html/rfc4291#section-2.5.5).
|
||||||
|
int j = i - word.length() - 2; // index of character before the previous ':'.
|
||||||
|
final int beginColonIndex = ipAddress.lastIndexOf(':', j);
|
||||||
|
if (beginColonIndex == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char tmpChar = ipAddress.charAt(j);
|
||||||
|
if (isValidIPv4MappedChar(tmpChar)) {
|
||||||
|
if (j - beginColonIndex != 4 ||
|
||||||
|
!isValidIPv4MappedChar(ipAddress.charAt(j - 1)) ||
|
||||||
|
!isValidIPv4MappedChar(ipAddress.charAt(j - 2)) ||
|
||||||
|
!isValidIPv4MappedChar(ipAddress.charAt(j - 3))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
j -= 5;
|
||||||
|
} else if (tmpChar == '0' || tmpChar == ':') {
|
||||||
|
--j;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (; j >= startOffset; --j) {
|
||||||
|
tmpChar = ipAddress.charAt(j);
|
||||||
|
if (tmpChar != '0' && tmpChar != ':') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!isValidIp4Word(word.toString())) {
|
if (!isValidIp4Word(word.toString())) {
|
||||||
return false;
|
return false;
|
||||||
@ -588,12 +515,6 @@ public final class NetUtil {
|
|||||||
if (numberOfColons != 6 && !doubleColon) {
|
if (numberOfColons != 6 && !doubleColon) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// a special case ::1:2:3:4:5:d.d.d.d allows 7 colons with an
|
|
||||||
// IPv4 ending, otherwise 7 :'s is bad
|
|
||||||
if (numberOfColons == 7 && ipAddress.charAt(startOffset) != ':' &&
|
|
||||||
ipAddress.charAt(1 + startOffset) != ':') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
word.delete(0, word.length());
|
word.delete(0, word.length());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -635,7 +556,7 @@ public final class NetUtil {
|
|||||||
// Check if we have an IPv4 ending
|
// Check if we have an IPv4 ending
|
||||||
if (numberOfPeriods > 0) {
|
if (numberOfPeriods > 0) {
|
||||||
// There is a test case with 7 colons and valid ipv4 this should resolve it
|
// There is a test case with 7 colons and valid ipv4 this should resolve it
|
||||||
if (numberOfPeriods != 3 || !(isValidIp4Word(word.toString()) && numberOfColons < 7)) {
|
if (numberOfPeriods != 3 || !(isValidIp4Word(word.toString()) && (numberOfColons < 7 || doubleColon))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -679,6 +600,23 @@ public final class NetUtil {
|
|||||||
return c >= '0' && c <= '9';
|
return c >= '0' && c <= '9';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isValidIPv4MappedChar(char c) {
|
||||||
|
return c == 'f' || c == 'F';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isValidIPv4MappedSeparators(byte b0, byte b1, boolean mustBeZero) {
|
||||||
|
// We allow IPv4 Mapped (https://tools.ietf.org/html/rfc4291#section-2.5.5.1)
|
||||||
|
// and IPv4 compatible (https://tools.ietf.org/html/rfc4291#section-2.5.5.1).
|
||||||
|
// The IPv4 compatible is deprecated, but it allows parsing of plain IPv4 addressed into IPv6-Mapped addresses.
|
||||||
|
return b0 == b1 && (b0 == 0 || !mustBeZero && b1 == -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isValidIPv4Mapped(byte[] bytes, int currentIndex, int compressBegin, int compressLength) {
|
||||||
|
return currentIndex <= 12 && currentIndex >= 2 &&
|
||||||
|
isValidIPv4MappedSeparators(bytes[currentIndex - 1], bytes[currentIndex - 2],
|
||||||
|
(compressBegin + compressLength) >= 14) && PlatformDependent.isZero(bytes, 0, currentIndex - 3);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a string and parses it to see if it is a valid IPV4 address.
|
* Takes a string and parses it to see if it is a valid IPV4 address.
|
||||||
*
|
*
|
||||||
@ -748,11 +686,37 @@ public final class NetUtil {
|
|||||||
* @param ipv4Mapped
|
* @param ipv4Mapped
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@code true} To allow IPv4 mapped inputs to be translated into {@link Inet6Address}</li>
|
* <li>{@code true} To allow IPv4 mapped inputs to be translated into {@link Inet6Address}</li>
|
||||||
* <li>{@code false} Don't turn IPv4 addressed to mapped addresses</li>
|
* <li>{@code false} Consider IPv4 mapped addresses as invalid.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* @return {@link Inet6Address} representation of the {@code ip} or {@code null} if not a valid IP address.
|
* @return {@link Inet6Address} representation of the {@code ip} or {@code null} if not a valid IP address.
|
||||||
*/
|
*/
|
||||||
public static Inet6Address getByName(CharSequence ip, boolean ipv4Mapped) {
|
public static Inet6Address getByName(CharSequence ip, boolean ipv4Mapped) {
|
||||||
|
byte[] bytes = getIPv6ByName(ip, ipv4Mapped);
|
||||||
|
if (bytes == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Inet6Address.getByAddress(null, bytes, -1);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new RuntimeException(e); // Should never happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the byte array representation of a {@link CharSequence} IP address.
|
||||||
|
* <p>
|
||||||
|
* The {@code ipv4Mapped} parameter specifies how IPv4 addresses should be treated.
|
||||||
|
* "IPv4 mapped" format as
|
||||||
|
* defined in <a href="http://tools.ietf.org/html/rfc4291#section-2.5.5">rfc 4291 section 2</a> is supported.
|
||||||
|
* @param ip {@link CharSequence} IP address to be converted to a {@link Inet6Address}
|
||||||
|
* @param ipv4Mapped
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code true} To allow IPv4 mapped inputs to be translated into {@link Inet6Address}</li>
|
||||||
|
* <li>{@code false} Consider IPv4 mapped addresses as invalid.</li>
|
||||||
|
* </ul>
|
||||||
|
* @return byte array representation of the {@code ip} or {@code null} if not a valid IP address.
|
||||||
|
*/
|
||||||
|
private static byte[] getIPv6ByName(CharSequence ip, boolean ipv4Mapped) {
|
||||||
final byte[] bytes = new byte[IPV6_BYTE_COUNT];
|
final byte[] bytes = new byte[IPV6_BYTE_COUNT];
|
||||||
final int ipLength = ip.length();
|
final int ipLength = ip.length();
|
||||||
int compressBegin = 0;
|
int compressBegin = 0;
|
||||||
@ -761,17 +725,17 @@ public final class NetUtil {
|
|||||||
int value = 0;
|
int value = 0;
|
||||||
int begin = -1;
|
int begin = -1;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int ipv6Seperators = 0;
|
int ipv6Separators = 0;
|
||||||
int ipv4Seperators = 0;
|
int ipv4Separators = 0;
|
||||||
int tmp;
|
int tmp;
|
||||||
boolean needsShift = false;
|
boolean needsShift = false;
|
||||||
for (; i < ipLength; ++i) {
|
for (; i < ipLength; ++i) {
|
||||||
final char c = ip.charAt(i);
|
final char c = ip.charAt(i);
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case ':':
|
case ':':
|
||||||
++ipv6Seperators;
|
++ipv6Separators;
|
||||||
if (i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR ||
|
if (i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR ||
|
||||||
ipv4Seperators > 0 || ipv6Seperators > IPV6_MAX_SEPARATORS ||
|
ipv4Separators > 0 || ipv6Separators > IPV6_MAX_SEPARATORS ||
|
||||||
currentIndex + 1 >= bytes.length) {
|
currentIndex + 1 >= bytes.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -792,8 +756,8 @@ public final class NetUtil {
|
|||||||
if (compressBegin != 0 || (tmp < ipLength && ip.charAt(tmp) == ':')) {
|
if (compressBegin != 0 || (tmp < ipLength && ip.charAt(tmp) == ':')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
++ipv6Seperators;
|
++ipv6Separators;
|
||||||
needsShift = ipv6Seperators == 2 && value == 0;
|
needsShift = ipv6Separators == 2 && value == 0;
|
||||||
compressBegin = currentIndex;
|
compressBegin = currentIndex;
|
||||||
compressLength = bytes.length - compressBegin - 2;
|
compressLength = bytes.length - compressBegin - 2;
|
||||||
++i;
|
++i;
|
||||||
@ -802,22 +766,27 @@ public final class NetUtil {
|
|||||||
begin = -1;
|
begin = -1;
|
||||||
break;
|
break;
|
||||||
case '.':
|
case '.':
|
||||||
++ipv4Seperators;
|
++ipv4Separators;
|
||||||
if (i - begin > IPV4_MAX_CHAR_BETWEEN_SEPARATOR
|
tmp = i - begin; // tmp is the length of the current segment.
|
||||||
|| ipv4Seperators > IPV4_SEPARATORS
|
if (tmp > IPV4_MAX_CHAR_BETWEEN_SEPARATOR
|
||||||
|| (ipv6Seperators > 0 && (currentIndex + compressLength < 12))
|
|| begin < 0
|
||||||
|
|| ipv4Separators > IPV4_SEPARATORS
|
||||||
|
|| (ipv6Separators > 0 && (currentIndex + compressLength < 12))
|
||||||
|| i + 1 >= ipLength
|
|| i + 1 >= ipLength
|
||||||
|| currentIndex >= bytes.length
|
|| currentIndex >= bytes.length
|
||||||
|| begin < 0
|
|| ipv4Separators == 1 &&
|
||||||
|| (begin == 0 && (i == 3 && (!isValidNumericChar(ip.charAt(2)) ||
|
// We also parse pure IPv4 addresses as IPv4-Mapped for ease of use.
|
||||||
!isValidNumericChar(ip.charAt(1)) ||
|
((!ipv4Mapped || currentIndex != 0 && !isValidIPv4Mapped(bytes, currentIndex,
|
||||||
!isValidNumericChar(ip.charAt(0))) ||
|
compressBegin, compressLength)) ||
|
||||||
i == 2 && (!isValidNumericChar(ip.charAt(1)) ||
|
(tmp == 3 && (!isValidNumericChar(ip.charAt(i - 1)) ||
|
||||||
!isValidNumericChar(ip.charAt(0))) ||
|
!isValidNumericChar(ip.charAt(i - 2)) ||
|
||||||
i == 1 && !isValidNumericChar(ip.charAt(0))))) {
|
!isValidNumericChar(ip.charAt(i - 3))) ||
|
||||||
|
tmp == 2 && (!isValidNumericChar(ip.charAt(i - 1)) ||
|
||||||
|
!isValidNumericChar(ip.charAt(i - 2))) ||
|
||||||
|
tmp == 1 && !isValidNumericChar(ip.charAt(i - 1))))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
value <<= (IPV4_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2;
|
value <<= (IPV4_MAX_CHAR_BETWEEN_SEPARATOR - tmp) << 2;
|
||||||
|
|
||||||
// The value integer holds at most 3 bytes from right (most significant) to left (least significant).
|
// The value integer holds at most 3 bytes from right (most significant) to left (least significant).
|
||||||
// The following bit shifting is to restructure the bytes to be left (most significant) to
|
// The following bit shifting is to restructure the bytes to be left (most significant) to
|
||||||
@ -831,7 +800,7 @@ public final class NetUtil {
|
|||||||
begin = -1;
|
begin = -1;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (!isValidHexChar(c) || (ipv4Seperators > 0 && !isValidNumericChar(c))) {
|
if (!isValidHexChar(c) || (ipv4Separators > 0 && !isValidNumericChar(c))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (begin < 0) {
|
if (begin < 0) {
|
||||||
@ -850,18 +819,17 @@ public final class NetUtil {
|
|||||||
|
|
||||||
final boolean isCompressed = compressBegin > 0;
|
final boolean isCompressed = compressBegin > 0;
|
||||||
// Finish up last set of data that was accumulated in the loop (or before the loop)
|
// Finish up last set of data that was accumulated in the loop (or before the loop)
|
||||||
if (ipv4Seperators > 0) {
|
if (ipv4Separators > 0) {
|
||||||
if (begin > 0 && i - begin > IPV4_MAX_CHAR_BETWEEN_SEPARATOR ||
|
if (begin > 0 && i - begin > IPV4_MAX_CHAR_BETWEEN_SEPARATOR ||
|
||||||
ipv4Seperators != IPV4_SEPARATORS ||
|
ipv4Separators != IPV4_SEPARATORS ||
|
||||||
currentIndex >= bytes.length) {
|
currentIndex >= bytes.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (ipv6Seperators == 0) {
|
if (ipv6Separators == 0) {
|
||||||
compressLength = 12;
|
compressLength = 12;
|
||||||
} else if (ipv6Seperators >= IPV6_MIN_SEPARATORS &&
|
} else if (ipv6Separators >= IPV6_MIN_SEPARATORS &&
|
||||||
ip.charAt(ipLength - 1) != ':' &&
|
(!isCompressed && (ipv6Separators == 6 && ip.charAt(0) != ':') ||
|
||||||
(!isCompressed && (ipv6Seperators == 6 && ip.charAt(0) != ':') ||
|
isCompressed && (ipv6Separators < IPV6_MAX_SEPARATORS &&
|
||||||
isCompressed && (ipv6Seperators + 1 < IPV6_MAX_SEPARATORS &&
|
|
||||||
(ip.charAt(0) != ':' || compressBegin <= 2)))) {
|
(ip.charAt(0) != ':' || compressBegin <= 2)))) {
|
||||||
compressLength -= 2;
|
compressLength -= 2;
|
||||||
} else {
|
} else {
|
||||||
@ -880,11 +848,11 @@ public final class NetUtil {
|
|||||||
} else {
|
} else {
|
||||||
tmp = ipLength - 1;
|
tmp = ipLength - 1;
|
||||||
if (begin > 0 && i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR ||
|
if (begin > 0 && i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR ||
|
||||||
ipv6Seperators < IPV6_MIN_SEPARATORS ||
|
ipv6Separators < IPV6_MIN_SEPARATORS ||
|
||||||
!isCompressed && (ipv6Seperators + 1 != IPV6_MAX_SEPARATORS ||
|
!isCompressed && (ipv6Separators + 1 != IPV6_MAX_SEPARATORS ||
|
||||||
ip.charAt(0) == ':' || ip.charAt(tmp) == ':') ||
|
ip.charAt(0) == ':' || ip.charAt(tmp) == ':') ||
|
||||||
isCompressed && (ipv6Seperators > IPV6_MAX_SEPARATORS ||
|
isCompressed && (ipv6Separators > IPV6_MAX_SEPARATORS ||
|
||||||
(ipv6Seperators == IPV6_MAX_SEPARATORS &&
|
(ipv6Separators == IPV6_MAX_SEPARATORS &&
|
||||||
(compressBegin <= 2 && ip.charAt(0) != ':' ||
|
(compressBegin <= 2 && ip.charAt(0) != ':' ||
|
||||||
compressBegin >= 14 && ip.charAt(tmp) != ':'))) ||
|
compressBegin >= 14 && ip.charAt(tmp) != ':'))) ||
|
||||||
currentIndex + 1 >= bytes.length) {
|
currentIndex + 1 >= bytes.length) {
|
||||||
@ -927,17 +895,14 @@ public final class NetUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ipv4Mapped && ipv4Seperators > 0 &&
|
if (ipv4Separators > 0) {
|
||||||
bytes[0] == 0 && bytes[1] == 0 && bytes[2] == 0 && bytes[3] == 0 && bytes[4] == 0 &&
|
// We only support IPv4-Mapped addresses [1] because IPv4-Compatible addresses are deprecated [2].
|
||||||
bytes[5] == 0 && bytes[6] == 0 && bytes[7] == 0 && bytes[8] == 0 && bytes[9] == 0) {
|
// [1] https://tools.ietf.org/html/rfc4291#section-2.5.5.2
|
||||||
|
// [2] https://tools.ietf.org/html/rfc4291#section-2.5.5.1
|
||||||
bytes[10] = bytes[11] = (byte) 0xff;
|
bytes[10] = bytes[11] = (byte) 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return bytes;
|
||||||
return Inet6Address.getByAddress(null, bytes, -1);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
throw new RuntimeException(e); // Should never happen
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,8 +95,8 @@ public final class ConstantTimeUtils {
|
|||||||
// Benchmarking demonstrates that using an int to accumulate is faster than other data types.
|
// Benchmarking demonstrates that using an int to accumulate is faster than other data types.
|
||||||
int b = 0;
|
int b = 0;
|
||||||
final int end = startPos1 + length;
|
final int end = startPos1 + length;
|
||||||
for (int i = startPos1, j = startPos2; i < end; ++i, ++j) {
|
for (; startPos1 < end; ++startPos1, ++startPos2) {
|
||||||
b |= bytes1[i] ^ bytes2[j];
|
b |= bytes1[startPos1] ^ bytes2[startPos2];
|
||||||
}
|
}
|
||||||
return equalsConstantTime(b, 0);
|
return equalsConstantTime(b, 0);
|
||||||
}
|
}
|
||||||
|
@ -645,6 +645,19 @@ public final class PlatformDependent {
|
|||||||
PlatformDependent0.equals(bytes1, startPos1, bytes2, startPos2, length);
|
PlatformDependent0.equals(bytes1, startPos1, bytes2, startPos2, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a subsection of an array is zero.
|
||||||
|
* @param bytes The byte array.
|
||||||
|
* @param startPos The starting index (inclusive) in {@code bytes}.
|
||||||
|
* @param length The amount of bytes to check for zero.
|
||||||
|
* @return {@code false} if {@code bytes[startPos:startsPos+length)} contains a value other than zero.
|
||||||
|
*/
|
||||||
|
public static boolean isZero(byte[] bytes, int startPos, int length) {
|
||||||
|
return !hasUnsafe() || !unalignedAccess() ?
|
||||||
|
isZeroSafe(bytes, startPos, length) :
|
||||||
|
PlatformDependent0.isZero(bytes, startPos, length);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare two {@code byte} arrays for equality without leaking timing information.
|
* Compare two {@code byte} arrays for equality without leaking timing information.
|
||||||
* For performance reasons no bounds checking on the parameters is performed.
|
* For performance reasons no bounds checking on the parameters is performed.
|
||||||
@ -1179,8 +1192,18 @@ public final class PlatformDependent {
|
|||||||
|
|
||||||
private static boolean equalsSafe(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
|
private static boolean equalsSafe(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
|
||||||
final int end = startPos1 + length;
|
final int end = startPos1 + length;
|
||||||
for (int i = startPos1, j = startPos2; i < end; ++i, ++j) {
|
for (; startPos1 < end; ++startPos1, ++startPos2) {
|
||||||
if (bytes1[i] != bytes2[j]) {
|
if (bytes1[startPos1] != bytes2[startPos2]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isZeroSafe(byte[] bytes, int startPos, int length) {
|
||||||
|
final int end = startPos + length;
|
||||||
|
for (; startPos < end; ++startPos) {
|
||||||
|
if (bytes[startPos] != 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -552,7 +552,7 @@ final class PlatformDependent0 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
|
static boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
|
||||||
if (length == 0) {
|
if (length <= 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
final long baseOffset1 = BYTE_ARRAY_BASE_OFFSET + startPos1;
|
final long baseOffset1 = BYTE_ARRAY_BASE_OFFSET + startPos1;
|
||||||
@ -620,6 +620,32 @@ final class PlatformDependent0 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean isZero(byte[] bytes, int startPos, int length) {
|
||||||
|
if (length <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final long baseOffset = BYTE_ARRAY_BASE_OFFSET + startPos;
|
||||||
|
int remainingBytes = length & 7;
|
||||||
|
final long end = baseOffset + remainingBytes;
|
||||||
|
for (long i = baseOffset - 8 + length; i >= end; i -= 8) {
|
||||||
|
if (UNSAFE.getLong(bytes, i) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingBytes >= 4) {
|
||||||
|
remainingBytes -= 4;
|
||||||
|
if (UNSAFE.getInt(bytes, baseOffset + remainingBytes) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (remainingBytes >= 2) {
|
||||||
|
return UNSAFE.getChar(bytes, baseOffset) == 0 &&
|
||||||
|
(remainingBytes == 2 || bytes[startPos + 2] == 0);
|
||||||
|
}
|
||||||
|
return bytes[startPos] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int hashCodeAscii(byte[] bytes, int startPos, int length) {
|
static int hashCodeAscii(byte[] bytes, int startPos, int length) {
|
||||||
int hash = HASH_CODE_ASCII_SEED;
|
int hash = HASH_CODE_ASCII_SEED;
|
||||||
final long baseOffset = BYTE_ARRAY_BASE_OFFSET + startPos;
|
final long baseOffset = BYTE_ARRAY_BASE_OFFSET + startPos;
|
||||||
|
@ -17,6 +17,7 @@ package io.netty.util;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
@ -29,7 +30,11 @@ import static io.netty.util.NetUtil.createByteArrayFromIpAddressString;
|
|||||||
import static io.netty.util.NetUtil.getByName;
|
import static io.netty.util.NetUtil.getByName;
|
||||||
import static io.netty.util.NetUtil.toAddressString;
|
import static io.netty.util.NetUtil.toAddressString;
|
||||||
import static io.netty.util.NetUtil.toSocketAddressString;
|
import static io.netty.util.NetUtil.toSocketAddressString;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class NetUtilTest {
|
public class NetUtilTest {
|
||||||
|
|
||||||
@ -87,7 +92,11 @@ public class NetUtilTest {
|
|||||||
"[2001:0000:4136:e378:8000:63bf:3fff:fdd2]", "200100004136e378800063bf3ffffdd2",
|
"[2001:0000:4136:e378:8000:63bf:3fff:fdd2]", "200100004136e378800063bf3ffffdd2",
|
||||||
"0:1:2:3:4:5:6:789a", "0000000100020003000400050006789a",
|
"0:1:2:3:4:5:6:789a", "0000000100020003000400050006789a",
|
||||||
"0:1:2:3::f", "0000000100020003000000000000000f",
|
"0:1:2:3::f", "0000000100020003000000000000000f",
|
||||||
"0:0:0:0:0:0:10.0.0.1", "0000000000000000000000000a000001",
|
"0:0:0:0:0:0:10.0.0.1", "00000000000000000000ffff0a000001",
|
||||||
|
"0:0:0:0:0::10.0.0.1", "00000000000000000000ffff0a000001",
|
||||||
|
"::0:0:0:0:0:10.0.0.1", "00000000000000000000ffff0a000001",
|
||||||
|
"0:0:0:0:0:0::10.0.0.1", "00000000000000000000ffff0a000001",
|
||||||
|
"0:0:0:0:0:ffff:10.0.0.1", "00000000000000000000ffff0a000001",
|
||||||
"::ffff:192.168.0.1", "00000000000000000000ffffc0a80001",
|
"::ffff:192.168.0.1", "00000000000000000000ffffc0a80001",
|
||||||
// Test if various interface names after the percent sign are recognized.
|
// Test if various interface names after the percent sign are recognized.
|
||||||
"[::1%1]", "00000000000000000000000000000001",
|
"[::1%1]", "00000000000000000000000000000001",
|
||||||
@ -134,8 +143,6 @@ public class NetUtilTest {
|
|||||||
"0:0:0:0:0:x:10.0.0.1", null,
|
"0:0:0:0:0:x:10.0.0.1", null,
|
||||||
// Test method with ipv4 style, bad ipv4 digits.
|
// Test method with ipv4 style, bad ipv4 digits.
|
||||||
"0:0:0:0:0:0:10.0.0.x", null,
|
"0:0:0:0:0:0:10.0.0.x", null,
|
||||||
// Test method with ipv4 style, adjacent :
|
|
||||||
"0:0:0:0:0::0:10.0.0.1", null,
|
|
||||||
// Test method with ipv4 style, too many ipv6 digits.
|
// Test method with ipv4 style, too many ipv6 digits.
|
||||||
"0:0:0:0:0:00000:10.0.0.1", null,
|
"0:0:0:0:0:00000:10.0.0.1", null,
|
||||||
// Test method with ipv4 style, too many :
|
// Test method with ipv4 style, too many :
|
||||||
@ -204,6 +211,8 @@ public class NetUtilTest {
|
|||||||
"0:0:0:0:0:0:10.0.0..1", null,
|
"0:0:0:0:0:0:10.0.0..1", null,
|
||||||
// Double compression symbol
|
// Double compression symbol
|
||||||
"::0::", null,
|
"::0::", null,
|
||||||
|
// Double compression symbol
|
||||||
|
"12::0::12", null,
|
||||||
// Empty contents
|
// Empty contents
|
||||||
"", null,
|
"", null,
|
||||||
// Trailing : (max number of : = 8)
|
// Trailing : (max number of : = 8)
|
||||||
@ -220,6 +229,14 @@ public class NetUtilTest {
|
|||||||
"::ffff:0.0..0", null,
|
"::ffff:0.0..0", null,
|
||||||
// Not enough IPv4 entries trailing .
|
// Not enough IPv4 entries trailing .
|
||||||
"::ffff:127.0.0.", null,
|
"::ffff:127.0.0.", null,
|
||||||
|
// Invalid trailing IPv4 character
|
||||||
|
"::ffff:127.0.0.a", null,
|
||||||
|
// Invalid leading IPv4 character
|
||||||
|
"::ffff:a.0.0.1", null,
|
||||||
|
// Invalid middle IPv4 character
|
||||||
|
"::ffff:127.a.0.1", null,
|
||||||
|
// Invalid middle IPv4 character
|
||||||
|
"::ffff:127.0.a.1", null,
|
||||||
// Not enough IPv4 entries no trailing .
|
// Not enough IPv4 entries no trailing .
|
||||||
"::ffff:1.2.4", null,
|
"::ffff:1.2.4", null,
|
||||||
// Extra IPv4 entry
|
// Extra IPv4 entry
|
||||||
@ -227,7 +244,43 @@ public class NetUtilTest {
|
|||||||
// Not enough IPv6 content
|
// Not enough IPv6 content
|
||||||
":ffff:192.168.0.1.255", null,
|
":ffff:192.168.0.1.255", null,
|
||||||
// Intermixed IPv4 and IPv6 symbols
|
// Intermixed IPv4 and IPv6 symbols
|
||||||
"::ffff:255.255:255.255.", null);
|
"::ffff:255.255:255.255.", null,
|
||||||
|
// Invalid IPv4 mapped address - invalid ipv4 separator
|
||||||
|
"0:0:0::0:0:00f.0.0.1", null,
|
||||||
|
// Invalid IPv4 mapped address - not enough f's
|
||||||
|
"0:0:0:0:0:fff:1.0.0.1", null,
|
||||||
|
// Invalid IPv4 mapped address - not IPv4 mapped, not IPv4 compatible
|
||||||
|
"0:0:0:0:0:ff00:1.0.0.1", null,
|
||||||
|
// Invalid IPv4 mapped address - not IPv4 mapped, not IPv4 compatible
|
||||||
|
"0:0:0:0:0:ff:1.0.0.1", null,
|
||||||
|
// Invalid IPv4 mapped address - too many f's
|
||||||
|
"0:0:0:0:0:fffff:1.0.0.1", null,
|
||||||
|
// Invalid IPv4 mapped address - too many bytes (too many 0's)
|
||||||
|
"0:0:0:0:0:0:ffff:1.0.0.1", null,
|
||||||
|
// Invalid IPv4 mapped address - too few bytes (not enough 0's)
|
||||||
|
"0:0:0:0:ffff:1.0.0.1", null,
|
||||||
|
// Invalid IPv4 mapped address - 0's after the mapped ffff indicator
|
||||||
|
"0:0:0:0:0:ffff::10.0.0.1", null,
|
||||||
|
// Invalid IPv4 mapped address - 0's after the mapped ffff indicator
|
||||||
|
"0:0:0:0:ffff::10.0.0.1", null,
|
||||||
|
// Invalid IPv4 mapped address - 0's after the mapped ffff indicator
|
||||||
|
"0:0:0:ffff::10.0.0.1", null,
|
||||||
|
// Invalid IPv4 mapped address - 0's after the mapped ffff indicator
|
||||||
|
"0:0:ffff::10.0.0.1", null,
|
||||||
|
// Invalid IPv4 mapped address - 0's after the mapped ffff indicator
|
||||||
|
"0:ffff::10.0.0.1", null,
|
||||||
|
// Invalid IPv4 mapped address - 0's after the mapped ffff indicator
|
||||||
|
"ffff::10.0.0.1", null,
|
||||||
|
// Invalid IPv4 mapped address - not all 0's before the mapped separator
|
||||||
|
"1:0:0:0:0:ffff:10.0.0.1", null,
|
||||||
|
// Address that is similar to IPv4 mapped, but is invalid
|
||||||
|
"0:0:0:0:ffff:ffff:1.0.0.1", null,
|
||||||
|
// Valid number of separators, but invalid IPv4 format
|
||||||
|
"::1:2:3:4:5:6.7.8.9", null,
|
||||||
|
// Too many digits
|
||||||
|
"0:0:0:0:0:0:ffff:10.0.0.1", null,
|
||||||
|
// Invalid IPv4 format
|
||||||
|
":1.2.3.4", null);
|
||||||
|
|
||||||
private static final Map<byte[], String> ipv6ToAddressStrings = new HashMap<byte[], String>() {
|
private static final Map<byte[], String> ipv6ToAddressStrings = new HashMap<byte[], String>() {
|
||||||
private static final long serialVersionUID = 2999763170377573184L;
|
private static final long serialVersionUID = 2999763170377573184L;
|
||||||
@ -344,6 +397,24 @@ public class NetUtilTest {
|
|||||||
"1.2.3.4", "::ffff:1.2.3.4",
|
"1.2.3.4", "::ffff:1.2.3.4",
|
||||||
"192.168.0.1", "::ffff:192.168.0.1",
|
"192.168.0.1", "::ffff:192.168.0.1",
|
||||||
|
|
||||||
|
// IPv4 compatible addresses are deprecated [1], so we don't support outputting them, but we do support
|
||||||
|
// parsing them into IPv4 mapped addresses. These values are treated the same as a plain IPv4 address above.
|
||||||
|
// [1] https://tools.ietf.org/html/rfc4291#section-2.5.5.1
|
||||||
|
"0:0:0:0:0:0:255.254.253.252", "::ffff:255.254.253.252",
|
||||||
|
"0:0:0:0:0::1.2.3.4", "::ffff:1.2.3.4",
|
||||||
|
"0:0:0:0::1.2.3.4", "::ffff:1.2.3.4",
|
||||||
|
"0:0:0:0:0:0::1.2.3.4", "::ffff:1.2.3.4",
|
||||||
|
"::0:0:0:0:0:1.2.3.4", "::ffff:1.2.3.4",
|
||||||
|
"::0:0:0:0:1.2.3.4", "::ffff:1.2.3.4",
|
||||||
|
"::0:0:0:0:1.2.3.4", "::ffff:1.2.3.4",
|
||||||
|
"::0:0:0:1.2.3.4", "::ffff:1.2.3.4",
|
||||||
|
"::0:0:1.2.3.4", "::ffff:1.2.3.4",
|
||||||
|
"::0:1.2.3.4", "::ffff:1.2.3.4",
|
||||||
|
"::1.2.3.4", "::ffff:1.2.3.4",
|
||||||
|
|
||||||
|
// IPv4 mapped (fully specified)
|
||||||
|
"0:0:0:0:0:ffff:1.2.3.4", "::ffff:1.2.3.4",
|
||||||
|
|
||||||
// IPv6 addresses
|
// IPv6 addresses
|
||||||
// Fully specified
|
// Fully specified
|
||||||
"2001:0:4136:e378:8000:63bf:3fff:fdd2", "2001:0:4136:e378:8000:63bf:3fff:fdd2",
|
"2001:0:4136:e378:8000:63bf:3fff:fdd2", "2001:0:4136:e378:8000:63bf:3fff:fdd2",
|
||||||
@ -435,10 +506,10 @@ public class NetUtilTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testIsValidIpV6Address() {
|
public void testIsValidIpV6Address() {
|
||||||
for (String host : validIpV6Hosts.keySet()) {
|
for (String host : validIpV6Hosts.keySet()) {
|
||||||
assertTrue(NetUtil.isValidIpV6Address(host));
|
assertTrue(host, NetUtil.isValidIpV6Address(host));
|
||||||
}
|
}
|
||||||
for (String host : invalidIpV6Hosts.keySet()) {
|
for (String host : invalidIpV6Hosts.keySet()) {
|
||||||
assertFalse(NetUtil.isValidIpV6Address(host));
|
assertFalse(host, NetUtil.isValidIpV6Address(host));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,20 +556,20 @@ public class NetUtilTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testIpv4MappedIp6GetByName() {
|
public void testIpv4MappedIp6GetByName() {
|
||||||
for (Entry<String, String> testEntry : ipv4MappedToIPv6AddressStrings.entrySet()) {
|
for (Entry<String, String> testEntry : ipv4MappedToIPv6AddressStrings.entrySet()) {
|
||||||
assertEquals(
|
Inet6Address inet6Address = getByName(testEntry.getKey(), true);
|
||||||
testEntry.getValue(),
|
assertNotNull(testEntry.getKey() + ", " + testEntry.getValue(), inet6Address);
|
||||||
toAddressString(getByName(testEntry.getKey(), true), true));
|
assertEquals(testEntry.getKey(), testEntry.getValue(), toAddressString(inet6Address, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testinvalidIpv4MappedIp6GetByName() {
|
public void testinvalidIpv4MappedIp6GetByName() {
|
||||||
for (String testEntry : invalidIpV4Hosts.keySet()) {
|
for (String testEntry : invalidIpV4Hosts.keySet()) {
|
||||||
assertNull(getByName(testEntry, true));
|
assertNull(testEntry, getByName(testEntry, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String testEntry : invalidIpV6Hosts.keySet()) {
|
for (String testEntry : invalidIpV6Hosts.keySet()) {
|
||||||
assertNull(getByName(testEntry, true));
|
assertNull(testEntry, getByName(testEntry, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,20 @@ public class PlatformDependentTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsZero() {
|
||||||
|
byte[] bytes = new byte[100];
|
||||||
|
assertTrue(PlatformDependent.isZero(bytes, 0, 0));
|
||||||
|
assertTrue(PlatformDependent.isZero(bytes, 0, -1));
|
||||||
|
assertTrue(PlatformDependent.isZero(bytes, 0, 100));
|
||||||
|
assertTrue(PlatformDependent.isZero(bytes, 10, 90));
|
||||||
|
bytes[10] = 1;
|
||||||
|
assertTrue(PlatformDependent.isZero(bytes, 0, 10));
|
||||||
|
assertFalse(PlatformDependent.isZero(bytes, 0, 11));
|
||||||
|
assertFalse(PlatformDependent.isZero(bytes, 10, 1));
|
||||||
|
assertTrue(PlatformDependent.isZero(bytes, 11, 89));
|
||||||
|
}
|
||||||
|
|
||||||
private interface EqualityChecker {
|
private interface EqualityChecker {
|
||||||
boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length);
|
boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length);
|
||||||
}
|
}
|
||||||
@ -105,6 +119,9 @@ public class PlatformDependentTest {
|
|||||||
bytes2 = bytes1.clone();
|
bytes2 = bytes1.clone();
|
||||||
assertTrue(equalsChecker.equals(bytes1, 0, bytes2, 0, bytes1.length));
|
assertTrue(equalsChecker.equals(bytes1, 0, bytes2, 0, bytes1.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertTrue(equalsChecker.equals(bytes1, 0, bytes2, 0, 0));
|
||||||
|
assertTrue(equalsChecker.equals(bytes1, 0, bytes2, 0, -1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static char randomCharInByteRange() {
|
private static char randomCharInByteRange() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user