8272215: Add InetAddress methods for parsing IP address literals

Reviewed-by: dfuchs, michaelm
This commit is contained in:
Aleksei Efimov 2023-10-26 18:24:42 +00:00
parent a9b31b587c
commit 77fe0fd9e6
6 changed files with 606 additions and 82 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,7 +25,6 @@
package java.net;
import java.net.*;
import java.util.Formatter;
import java.util.Locale;
import sun.net.util.IPAddressUtil;
@ -137,7 +136,7 @@ class HostPortrange {
}
this.ipv4 = this.literal = ipv4;
if (ipv4) {
byte[] ip = IPAddressUtil.validateNumericFormatV4(hoststr);
byte[] ip = IPAddressUtil.validateNumericFormatV4(hoststr, false);
if (ip == null) {
throw new IllegalArgumentException("illegal IPv4 address");
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,7 +25,10 @@
package java.net;
import sun.net.util.IPAddressUtil;
import java.io.ObjectStreamException;
import java.util.Objects;
/**
* This class represents an Internet Protocol version 4 (IPv4) address.
@ -36,7 +39,7 @@ import java.io.ObjectStreamException;
* and <a href="http://www.ietf.org/rfc/rfc2365.txt"><i>RFC&nbsp;2365:
* Administratively Scoped IP Multicast</i></a>
*
* <h2> <a id="format">Textual representation of IP addresses</a> </h2>
* <h2> <a id="format">Textual representation of IPv4 addresses</a> </h2>
*
* Textual representation of IPv4 address used as input to methods
* takes one of the following forms:
@ -55,7 +58,7 @@ import java.io.ObjectStreamException;
* <p> When a three part address is specified, the last part is
* interpreted as a 16-bit quantity and placed in the right most two
* bytes of the network address. This makes the three part address
* format convenient for specifying Class B net- work addresses as
* format convenient for specifying Class B network addresses as
* 128.net.host.
*
* <p> When a two part address is supplied, the last part is
@ -67,6 +70,29 @@ import java.io.ObjectStreamException;
* <p> When only one part is given, the value is stored directly in
* the network address without any byte rearrangement.
*
* <p> These forms support parts specified in decimal format only.
* For example, the following forms are supported by methods capable
* of parsing textual representations of IPv4 addresses:
* {@snippet :
* // Dotted-decimal 'd.d.d.d' form with four part address literal
* InetAddress.getByName("007.008.009.010"); // ==> /7.8.9.10
* InetAddress.getByName("127.0.1.1"); // ==> /127.0.1.1
*
* // Dotted-decimal 'd.d.d' form with three part address literal,
* // the last part is placed in the right most two bytes
* // of the constructed address
* InetAddress.getByName("127.0.257"); // ==> /127.0.1.1
*
* // Dotted-decimal 'd.d' form with two part address literal,
* // the last part is placed in the right most three bytes
* // of the constructed address
* Inet4Address.ofLiteral("127.257"); // ==> /127.0.1.1
*
* // 'd' form with one decimal value that is stored directly in
* // the constructed address bytes without any rearrangement
* Inet4Address.ofLiteral("02130706689"); // ==> /127.0.1.1
* }
*
* <p> For methods that return a textual representation as output
* value, the first form, i.e. a dotted-quad string, is used.
*
@ -134,6 +160,57 @@ class Inet4Address extends InetAddress {
holder().originalHostName = hostName;
}
/**
* Creates an {@code Inet4Address} based on the provided {@linkplain
* Inet4Address##format textual representation} of an IPv4 address.
* <p> If the provided IPv4 address literal cannot represent a {@linkplain
* Inet4Address##format valid IPv4 address} an {@code IllegalArgumentException} is thrown.
* <p> This method doesn't block, i.e. no reverse lookup is performed.
*
* @param ipv4AddressLiteral the textual representation of an IPv4 address.
* @return an {@link Inet4Address} object with no hostname set, and constructed
* from the provided IPv4 address literal.
* @throws IllegalArgumentException if the {@code ipv4AddressLiteral} cannot be
* parsed as an IPv4 address literal.
* @throws NullPointerException if the {@code ipv4AddressLiteral} is {@code null}.
*/
public static Inet4Address ofLiteral(String ipv4AddressLiteral) {
Objects.requireNonNull(ipv4AddressLiteral);
return parseAddressString(ipv4AddressLiteral, true);
}
/**
* Parses the given string as an IPv4 address literal.
* If the given {@code addressLiteral} string cannot be parsed as an IPv4 address literal
* and {@code throwIAE} is {@code false}, {@code null} is returned.
* If the given {@code addressLiteral} string cannot be parsed as an IPv4 address literal
* and {@code throwIAE} is {@code true}, an {@code IllegalArgumentException} is thrown.
* Otherwise, if it can be considered as {@linkplain IPAddressUtil#validateNumericFormatV4(String,
* boolean) an ambiguous literal} - {@code IllegalArgumentException} is thrown irrelevant to
* {@code throwIAE} value.
*
* @apiNote
* The given {@code addressLiteral} string is considered ambiguous if it cannot be parsed as
* a valid IPv4 address literal using decimal notation, but could be
* interpreted as an IPv4 address in some other representation (octal, hexadecimal, or mixed).
* @param addressLiteral IPv4 address literal to parse
* @param throwIAE whether to throw {@code IllegalArgumentException} if the
* given {@code addressLiteral} string cannot be parsed as
* an IPv4 address literal.
* @return {@code Inet4Address} object constructed from the address literal;
* or {@code null} if the literal cannot be parsed as an IPv4 address
* @throws IllegalArgumentException if the given {@code addressLiteral} string
* cannot be parsed as an IPv4 address literal and {@code throwIAE} is {@code true},
* or if it is considered ambiguous, regardless of the value of {@code throwIAE}.
*/
static Inet4Address parseAddressString(String addressLiteral, boolean throwIAE) {
byte [] addrBytes= IPAddressUtil.validateNumericFormatV4(addressLiteral, throwIAE);
if (addrBytes == null) {
return null;
}
return new Inet4Address(null, addrBytes);
}
/**
* Replaces the object to be serialized with an InetAddress object.
*

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,6 +25,8 @@
package java.net;
import sun.net.util.IPAddressUtil;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
@ -32,13 +34,14 @@ import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.util.Enumeration;
import java.util.Arrays;
import java.util.Objects;
/**
* This class represents an Internet Protocol version 6 (IPv6) address.
* Defined by <a href="http://www.ietf.org/rfc/rfc2373.txt">
* <i>RFC&nbsp;2373: IP Version 6 Addressing Architecture</i></a>.
*
* <h2> <a id="format">Textual representation of IP addresses</a> </h2>
* <h2> <a id="format">Textual representation of IPv6 addresses</a> </h2>
*
* Textual representation of IPv6 address used as input to methods
* takes one of the following forms:
@ -116,7 +119,7 @@ import java.util.Arrays;
* form because it is unambiguous when used in combination with other
* textual data.
*
* <h3> Special IPv6 address </h3>
* <h3><a id="special-ipv6-address">Special IPv6 address</a></h3>
*
* <blockquote>
* <dl>
@ -139,7 +142,8 @@ import java.util.Arrays;
*
* <p>The textual representation of IPv6 addresses as described above can be
* extended to specify IPv6 scoped addresses. This extension to the basic
* addressing architecture is described in [draft-ietf-ipngwg-scoping-arch-04.txt].
* addressing architecture is described in <a href="https://www.rfc-editor.org/info/rfc4007">
* <i>RFC&nbsp;4007: IPv6 Scoped Address Architecture</i></a>.
*
* <p> Because link-local and site-local addresses are non-global, it is possible
* that different hosts may have the same destination address and may be
@ -170,8 +174,24 @@ import java.util.Arrays;
* Inet6Address instances returned from the NetworkInterface class. This can be
* used to find out the current scope ids configured on the system.
*
* <h3><a id="input">Textual representation of IPv6 addresses as method inputs</a></h3>
*
* <p> Methods of {@code InetAddress} and {@code Inet6Address} that accept a
* textual representation of an IPv6 address allow for that representation
* to be enclosed in square brackets. For example,
* {@snippet :
* // The full IPv6 form
* InetAddress.getByName("1080:0:0:0:8:800:200C:417A"); // ==> /1080:0:0:0:8:800:200c:417a
* InetAddress.getByName("[1080:0:0:0:8:800:200C:417A]"); // ==> /1080:0:0:0:8:800:200c:417a
*
* // IPv6 scoped address with scope-id as string
* Inet6Address.ofLiteral("fe80::1%en0"); // ==> /fe80:0:0:0:0:0:0:1%en0
* Inet6Address.ofLiteral("[fe80::1%en0]"); // ==> /fe80:0:0:0:0:0:0:1%en0
* }
* @spec https://www.rfc-editor.org/info/rfc2373
* RFC 2373: IP Version 6 Addressing Architecture
* @spec https://www.rfc-editor.org/info/rfc4007
* RFC 4007: IPv6 Scoped Address Architecture
* @since 1.4
*/
@ -477,6 +497,128 @@ class Inet6Address extends InetAddress {
throw new UnknownHostException("addr is of illegal length");
}
/**
* Creates an {@code InetAddress} based on the provided {@linkplain
* Inet6Address##format textual representation} of an IPv6 address.
* <p> If the provided address literal cannot represent {@linkplain Inet6Address##format
* a valid IPv6 address} an {@code IllegalArgumentException} is thrown.
* An {@code IllegalArgumentException} is also thrown if an IPv6 scoped address literal
* contains a scope-id that doesn't map to any network interface on the system, or
* if a scope-id is present in an IPv4-mapped IPv6 address literal.
* <p> This method doesn't block, i.e. no reverse lookup is performed.
* <p> Note that IPv6 address literal forms are also supported when enclosed in
* square brackets.
* Note also that if the supplied literal represents an {@linkplain
* Inet6Address##special-ipv6-address IPv4-mapped IPv6 address} an
* instance of {@code Inet4Address} is returned.
*
* @param ipv6AddressLiteral the textual representation of an IPv6 address.
* @return an {@link InetAddress} object with no hostname set, and constructed
* from the provided IPv6 address literal.
* @throws IllegalArgumentException if the {@code ipv6AddressLiteral} cannot be
* parsed as an IPv6 address literal.
* @throws NullPointerException if the {@code ipv6AddressLiteral} is {@code null}.
*/
public static InetAddress ofLiteral(String ipv6AddressLiteral) {
Objects.requireNonNull(ipv6AddressLiteral);
try {
InetAddress parsedAddress = parseAddressString(ipv6AddressLiteral, true);
if (parsedAddress != null) {
return parsedAddress;
}
} catch (UnknownHostException uhe) {
// Error constructing Inet6Address from address literal containing
// a network interface name
}
throw IPAddressUtil.invalidIpAddressLiteral(ipv6AddressLiteral);
}
/**
* Method tries to parse supplied IP address literal as IPv6, IPv4-compatible IPv6 or
* IPv4-mapped IPv6 address.
* If address part of the literal string doesn't contain address in valid IPv6 form
* - {@code null} is returned.
* {@code UnknownHostException} is thrown if {@link InetAddress} cannot be constructed
* from parsed string due to:
* - incorrect zone-id specified in IPv6-scoped address literal that references
* non-existing interface name.
* - unexpected zone-id in IPv4-mapped address literal.
*
* @param addressLiteral literal IP address
* @param removeSqBrackets if {@code true} remove outer square brackets
* @return {@link Inet6Address} or {@link Inet4Address} object constructed from
* literal IP address string.
* @throws UnknownHostException if literal IP address string cannot be parsed
* as IPv6, IPv4-mapped IPv6 or IPv4-compatible IPv6 address literals.
*/
static InetAddress parseAddressString(String addressLiteral, boolean removeSqBrackets)
throws UnknownHostException {
// Remove trailing and leading square brackets if requested
if (removeSqBrackets && addressLiteral.charAt(0) == '[' &&
addressLiteral.length() > 2 &&
addressLiteral.charAt(addressLiteral.length() - 1) == ']') {
addressLiteral = addressLiteral.substring(1, addressLiteral.length() - 1);
}
int pos, numericZone = -1;
String ifname = null;
if ((pos = addressLiteral.indexOf('%')) != -1) {
numericZone = checkNumericZone(addressLiteral);
if (numericZone == -1) {
/* remainder of string must be an ifname */
ifname = addressLiteral.substring(pos + 1);
}
}
byte[] addrBytes = IPAddressUtil.textToNumericFormatV6(addressLiteral);
if (addrBytes == null) {
return null;
}
// IPv4-mapped IPv6 address
if (addrBytes.length == Inet4Address.INADDRSZ) {
if (numericZone != -1 || ifname != null) {
// IPv4-mapped address must not contain zone-id
throw new UnknownHostException(addressLiteral + ": invalid IPv4-mapped address");
}
return new Inet4Address(null, addrBytes);
}
if (ifname != null) {
return new Inet6Address(null, addrBytes, ifname);
} else {
return new Inet6Address(null, addrBytes, numericZone);
}
}
/**
* Check if the literal address string has %nn appended
* returns -1 if not, or the numeric value otherwise.
* <p>
* %nn may also be a string that represents the displayName of
* a currently available NetworkInterface.
*/
private static int checkNumericZone(String s) {
int percent = s.indexOf('%');
int slen = s.length();
int digit, zone = 0;
int multmax = Integer.MAX_VALUE / 10; // for int overflow detection
if (percent == -1) {
return -1;
}
for (int i = percent + 1; i < slen; i++) {
char c = s.charAt(i);
if ((digit = IPAddressUtil.parseAsciiDigit(c, 10)) < 0) {
return -1;
}
if (zone > multmax) {
return -1;
}
zone = (zone * 10) + digit;
if (zone < 0) {
return -1;
}
}
return zone;
}
private void initstr(String hostName, byte[] addr, String ifname)
throws UnknownHostException
{

View File

@ -136,7 +136,7 @@ import static java.net.spi.InetAddressResolver.LookupPolicy.IPV6_FIRST;
*
* <p> <i>Global</i> addresses are unique across the internet.
*
* <h3> Textual representation of IP addresses </h3>
* <h3> <a id="format">Textual representation of IP addresses</a> </h3>
*
* The textual representation of an IP address is address family specific.
*
@ -1417,7 +1417,7 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
byte[] addrArray;
// check if IPV4 address - most likely
try {
addrArray = IPAddressUtil.validateNumericFormatV4(addrStr);
addrArray = IPAddressUtil.validateNumericFormatV4(addrStr, false);
} catch (IllegalArgumentException iae) {
return null;
}
@ -1644,51 +1644,29 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
// Check and try to parse host string as an IP address literal
if (IPAddressUtil.digit(host.charAt(0), 16) != -1
|| (host.charAt(0) == ':')) {
byte[] addr = null;
int numericZone = -1;
String ifname = null;
InetAddress inetAddress = null;
if (!ipv6Expected) {
// check if it is IPv4 address only if host is not wrapped in '[]'
try {
addr = IPAddressUtil.validateNumericFormatV4(host);
// Here we check the address string for ambiguity only
inetAddress = Inet4Address.parseAddressString(host, false);
} catch (IllegalArgumentException iae) {
var uhe = new UnknownHostException(host);
uhe.initCause(iae);
throw uhe;
}
}
if (addr == null) {
// Try to parse host string as an IPv6 literal
// Check if a numeric or string zone id is present first
int pos;
if ((pos = host.indexOf('%')) != -1) {
numericZone = checkNumericZone(host);
if (numericZone == -1) { /* remainder of string must be an ifname */
ifname = host.substring(pos + 1);
}
}
if ((addr = IPAddressUtil.textToNumericFormatV6(host)) == null &&
if (inetAddress == null) {
// This is supposed to be an IPv6 literal
// Check for presence of a numeric or string zone id
// is done in Inet6Address.parseAddressString
if ((inetAddress = Inet6Address.parseAddressString(host, false)) == null &&
(host.contains(":") || ipv6Expected)) {
throw invalidIPv6LiteralException(host, ipv6Expected);
}
}
if(addr != null) {
InetAddress[] ret = new InetAddress[1];
if (addr.length == Inet4Address.INADDRSZ) {
if (numericZone != -1 || ifname != null) {
// IPv4-mapped address must not contain zone-id
throw new UnknownHostException(host + ": invalid IPv4-mapped address");
}
ret[0] = new Inet4Address(null, addr);
} else {
if (ifname != null) {
ret[0] = new Inet6Address(null, addr, ifname);
} else {
ret[0] = new Inet6Address(null, addr, numericZone);
}
}
return ret;
if (inetAddress != null) {
return new InetAddress[]{inetAddress};
}
} else if (ipv6Expected) {
// We were expecting an IPv6 Literal since host string starts
@ -1718,39 +1696,6 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
return impl.loopbackAddress();
}
/**
* check if the literal address string has %nn appended
* returns -1 if not, or the numeric value otherwise.
*
* %nn may also be a string that represents the displayName of
* a currently available NetworkInterface.
*/
private static int checkNumericZone (String s) throws UnknownHostException {
int percent = s.indexOf ('%');
int slen = s.length();
int digit, zone=0;
int multmax = Integer.MAX_VALUE / 10; // for int overflow detection
if (percent == -1) {
return -1;
}
for (int i=percent+1; i<slen; i++) {
char c = s.charAt(i);
if ((digit = IPAddressUtil.parseAsciiDigit(c, 10)) < 0) {
return -1;
}
if (zone > multmax) {
return -1;
}
zone = (zone * 10) + digit;
if (zone < 0) {
return -1;
}
}
return zone;
}
/**
* package private so SocketPermission can call it
*/
@ -1759,6 +1704,38 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
return getAllByName0(host, check, true);
}
/**
* Creates an {@code InetAddress} based on the provided {@linkplain InetAddress##format
* textual representation} of an IP address.
* <p> The provided IP address literal is parsed as
* {@linkplain Inet4Address#ofLiteral(String) an IPv4 address literal} first.
* If it cannot be parsed as an IPv4 address literal, then the method attempts
* to parse it as {@linkplain Inet6Address#ofLiteral(String) an IPv6 address literal}.
* If neither attempts succeed an {@code IllegalArgumentException} is thrown.
* <p> This method doesn't block, i.e. no reverse lookup is performed.
*
* @param ipAddressLiteral the textual representation of an IP address.
* @return an {@link InetAddress} object with no hostname set, and constructed
* from the provided IP address literal.
* @throws IllegalArgumentException if the {@code ipAddressLiteral} cannot be parsed
* as an IPv4 or IPv6 address literal.
* @throws NullPointerException if the {@code ipAddressLiteral} is {@code null}.
* @see Inet4Address#ofLiteral(String)
* @see Inet6Address#ofLiteral(String)
*/
public static InetAddress ofLiteral(String ipAddressLiteral) {
Objects.requireNonNull(ipAddressLiteral);
InetAddress inetAddress;
try {
// First try to parse the input as an IPv4 address literal
inetAddress = Inet4Address.ofLiteral(ipAddressLiteral);
} catch (IllegalArgumentException iae) {
// If it fails try to parse the input as an IPv6 address literal
inetAddress = Inet6Address.ofLiteral(ipAddressLiteral);
}
return inetAddress;
}
/**
* Designated lookup method.
*

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2004, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -134,22 +134,41 @@ public class IPAddressUtil {
* If string can't be parsed by following IETF IPv4 address string literals
* formatting style rules (default one), but can be parsed by following BSD formatting
* style rules, the IPv4 address string content is treated as ambiguous and
* {@code IllegalArgumentException} is thrown.
* either {@code IllegalArgumentException} is thrown, or {@code null} is returned.
*
* @param src input string
* @param throwIAE {@code true} - throw {@code IllegalArgumentException} when cannot be parsed
* as IPv4 address string;
* {@code false} - throw {@code IllegalArgumentException} only when IPv4 address
* string is ambiguous.
* @return bytes array if string is a valid IPv4 address string
* @throws IllegalArgumentException if "jdk.net.allowAmbiguousIPAddressLiterals" SP is set to
* "false" and IPv4 address string {@code "src"} is ambiguous
* {@code false}, IPv4 address string {@code src} is ambiguous,
* or when address string cannot be parsed as an IPv4 address
* string and {@code throwIAE} is set to {@code true}.
*/
public static byte[] validateNumericFormatV4(String src) {
public static byte[] validateNumericFormatV4(String src, boolean throwIAE) {
byte[] parsedBytes = textToNumericFormatV4(src);
if (!ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP_VALUE
&& parsedBytes == null && isBsdParsableV4(src)) {
throw new IllegalArgumentException("Invalid IP address literal: " + src);
throw invalidIpAddressLiteral(src);
}
if (parsedBytes == null && throwIAE) {
throw invalidIpAddressLiteral(src);
}
return parsedBytes;
}
/**
* Creates {@code IllegalArgumentException} with invalid IP address literal message.
*
* @param src address literal string to include to the exception message
* @return an {@code IllegalArgumentException} instance
*/
public static IllegalArgumentException invalidIpAddressLiteral(String src) {
return new IllegalArgumentException("Invalid IP address literal: " + src);
}
/*
* Convert IPv6 presentation level address to network order binary form.
* credit:

View File

@ -0,0 +1,310 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/* @test
* @bug 8272215
* @summary Test for ofLiteral API in InetAddress classes
* @run junit/othervm -Djdk.net.hosts.file=nonExistingHostsFile.txt
* OfLiteralTest
* @run junit/othervm -Djdk.net.hosts.file=nonExistingHostsFile.txt
* -Djava.net.preferIPv4Stack=true
* OfLiteralTest
* @run junit/othervm -Djdk.net.hosts.file=nonExistingHostsFile.txt
* -Djava.net.preferIPv6Addresses=true
* OfLiteralTest
* @run junit/othervm -Djdk.net.hosts.file=nonExistingHostsFile.txt
* -Djava.net.preferIPv6Addresses=false
* OfLiteralTest
*/
import org.junit.Assert;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertThrows;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class OfLiteralTest {
@ParameterizedTest
@MethodSource("validLiteralArguments")
public void validLiteral(InetAddressClass inetAddressClass,
String addressLiteral,
byte[] expectedAddressBytes) throws Exception {
InetAddress ofLiteralResult = switch (inetAddressClass) {
case INET_ADDRESS -> InetAddress.ofLiteral(addressLiteral);
case INET4_ADDRESS -> Inet4Address.ofLiteral(addressLiteral);
case INET6_ADDRESS -> Inet6Address.ofLiteral(addressLiteral);
};
InetAddress getByNameResult = InetAddress.getByName(addressLiteral);
Assert.assertArrayEquals(expectedAddressBytes, ofLiteralResult.getAddress());
Assert.assertEquals(getByNameResult, ofLiteralResult);
}
private static Stream<Arguments> validLiteralArguments() throws Exception {
// 1080:0:0:0:8:800:200C:417A address bytes
byte[] ipv6AddressExpBytes = new byte[]{16, -128, 0, 0, 0, 0, 0, 0, 0,
8, 8, 0, 32, 12, 65, 122};
// ::129.144.52.38 address bytes
byte[] ipv4CompIpv6ExpBytes = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, -127, -112, 52, 38};
// 222.173.190.239 address bytes
byte[] ipv4ExpBytes = new byte[]{(byte) 222, (byte) 173,
(byte) 190, (byte) 239};
// 222.173.190.39 address bytes
byte[] ipv4ExpBytes2 = new byte[]{(byte) 222, (byte) 173,
(byte) 190, 39};
// 1.2.3.4 address bytes
byte[] oneToFourAddressExpBytes = new byte[]{1, 2, 3, 4};
// 6.7.8.9 address bytes
byte[] sixtoNineAddressExpBytes = new byte[]{6, 7, 8, 9};
// ::FFFF:129.144.52.38 address bytes
byte[] ipv6Ipv4MappedAddressExpBytes = new byte[]{
(byte) 129, (byte) 144, 52, 38};
Stream<Arguments> validLiterals = Stream.of(
// IPv6 address literals are parsable by Inet6Address.ofLiteral
// and InetAddress.ofLiteral methods
Arguments.of(InetAddressClass.INET6_ADDRESS,
"1080:0:0:0:8:800:200C:417A", ipv6AddressExpBytes),
Arguments.of(InetAddressClass.INET_ADDRESS,
"[1080:0:0:0:8:800:200C:417A]", ipv6AddressExpBytes),
// Compressed series of zeros with square brackets
Arguments.of(InetAddressClass.INET6_ADDRESS,
"[1080::8:800:200C:417A]", ipv6AddressExpBytes),
// Compressed series of zeros without square brackets
Arguments.of(InetAddressClass.INET_ADDRESS,
"1080::8:800:200C:417A", ipv6AddressExpBytes),
// IPv4-mapped IPv6 address literals are parsable by
// InetAddress.ofLiteral and Inet6Address.ofLiteral methods
Arguments.of(InetAddressClass.INET_ADDRESS,
"::FFFF:129.144.52.38", ipv6Ipv4MappedAddressExpBytes),
Arguments.of(InetAddressClass.INET_ADDRESS,
"[::ffff:1.2.3.4]", oneToFourAddressExpBytes),
Arguments.of(InetAddressClass.INET6_ADDRESS,
"::FFFF:129.144.52.38", ipv6Ipv4MappedAddressExpBytes),
Arguments.of(InetAddressClass.INET6_ADDRESS,
"[::ffff:1.2.3.4]", oneToFourAddressExpBytes),
// IPv4-compatible IPv6 address literals are parsable by
// Inet6Address.ofLiteral and InetAddress.ofLiteral methods
Arguments.of(InetAddressClass.INET6_ADDRESS,
"::129.144.52.38", ipv4CompIpv6ExpBytes),
Arguments.of(InetAddressClass.INET6_ADDRESS,
"[::129.144.52.38]", ipv4CompIpv6ExpBytes),
Arguments.of(InetAddressClass.INET_ADDRESS,
"::129.144.52.38", ipv4CompIpv6ExpBytes),
Arguments.of(InetAddressClass.INET_ADDRESS,
"[::129.144.52.38]", ipv4CompIpv6ExpBytes),
// Tests for IPv4 address literal forms
// form:'d.d.d.d' method:Inet4Address.ofLiteral
Arguments.of(InetAddressClass.INET4_ADDRESS,
"222.173.190.239", ipv4ExpBytes),
// form:'d.d.d.d' with decimal octet with leading zero
Arguments.of(InetAddressClass.INET4_ADDRESS,
"222.173.190.039", ipv4ExpBytes2),
Arguments.of(InetAddressClass.INET4_ADDRESS,
"06.07.08.09", sixtoNineAddressExpBytes),
// form:'d.d.d.d' method:InetAddress.ofLiteral
Arguments.of(InetAddressClass.INET_ADDRESS,
"222.173.190.239", ipv4ExpBytes),
// form:'d.d.d' method:Inet4Address.ofLiteral
Arguments.of(InetAddressClass.INET4_ADDRESS,
"222.173.48879", ipv4ExpBytes),
// form:'d.d.d' method:InetAddress.ofLiteral
Arguments.of(InetAddressClass.INET_ADDRESS,
"222.173.48879", ipv4ExpBytes),
// form:'d.d' method:Inet4Address.ofLiteral
Arguments.of(InetAddressClass.INET4_ADDRESS,
"222.11386607", ipv4ExpBytes),
// form:'d.d' method:InetAddress.ofLiteral
Arguments.of(InetAddressClass.INET_ADDRESS,
"222.11386607", ipv4ExpBytes),
// form:'d' method:Inet4Address.ofLiteral
Arguments.of(InetAddressClass.INET4_ADDRESS,
"3735928559", ipv4ExpBytes),
// form:'d' method:InetAddress.ofLiteral
Arguments.of(InetAddressClass.INET_ADDRESS,
"3735928559", ipv4ExpBytes),
// form:'d' method:InetAddress.ofLiteral -
// with leading 0 that is discarded and address
// parsed as decimal
Arguments.of(InetAddressClass.INET_ADDRESS,
"03735928559", ipv4ExpBytes)
);
// Generate addresses for loopback and wildcard address test cases
var loopbackAndWildcardAddresses = List.of(
// Loopback address
InetAddress.getLoopbackAddress(),
// IPv6 wildcard address
InetAddress.getByName("::"),
// IPv4 wildcard address
InetAddress.getByName("0.0.0.0"));
// Get addresses for all network interfaces available,
// and construct a test case for each. And then combine
// them with loopback/wildcard test cases.
Stream<Arguments> hostAddressArguments = Stream.concat(
NetworkInterface.networkInterfaces()
.flatMap(NetworkInterface::inetAddresses),
loopbackAndWildcardAddresses.stream())
.flatMap(OfLiteralTest::addressToValidTestCases);
return Stream.concat(validLiterals, hostAddressArguments);
}
@ParameterizedTest
@MethodSource("invalidLiteralArguments")
public void invalidLiteral(InetAddressClass inetAddressClass,
String addressLiteral) {
var executable = constructExecutable(inetAddressClass, addressLiteral);
var exception = assertThrows(IllegalArgumentException.class, executable);
System.err.println("Expected exception observed: " + exception);
}
@ParameterizedTest
@EnumSource(InetAddressClass.class)
public void nullLiteral(InetAddressClass inetAddressClass) {
var executable = constructExecutable(inetAddressClass, null);
assertThrows(NullPointerException.class, executable);
}
private static Stream<Arguments> invalidLiteralArguments() {
Stream<Arguments> argumentsStream = Stream.of(
// IPv4 address wrapped in square brackets
Arguments.of(InetAddressClass.INET_ADDRESS, "[1.2.3.4]"),
Arguments.of(InetAddressClass.INET4_ADDRESS, "[1.2.3.4]"),
Arguments.of(InetAddressClass.INET6_ADDRESS, "[1.2.3.4]"),
// IPv4 address literal with BSD-formatting
Arguments.of(InetAddressClass.INET_ADDRESS, "1.2.3.0256"),
Arguments.of(InetAddressClass.INET4_ADDRESS, "1.2.3.0256"),
// Invalid IPv4-mapped IPv6 address forms
// ::FFFF:d.d.d
Arguments.of(InetAddressClass.INET_ADDRESS, "::FFFF:1.2.3"),
Arguments.of(InetAddressClass.INET6_ADDRESS, "::FFFF:1.2.3"),
// ::FFFF:d.d
Arguments.of(InetAddressClass.INET_ADDRESS, "::FFFF:1.2"),
Arguments.of(InetAddressClass.INET6_ADDRESS, "::FFFF:1.2"),
// ::d.d.d
Arguments.of(InetAddressClass.INET_ADDRESS, "::1.2.3"),
Arguments.of(InetAddressClass.INET6_ADDRESS, "::1.2.3"),
// ::d.d
Arguments.of(InetAddressClass.INET_ADDRESS, "::1.2"),
Arguments.of(InetAddressClass.INET6_ADDRESS, "::1.2"),
// IPv4-mapped IPv6 address with scope-id
Arguments.of(InetAddressClass.INET6_ADDRESS,
"::FFFF:129.144.52.38%1"),
// IPv4-mapped IPv6 addresses cannot be parsed by Inet4Address.ofLiteral
Arguments.of(InetAddressClass.INET4_ADDRESS,
"::FFFF:129.144.52.38"),
Arguments.of(InetAddressClass.INET4_ADDRESS,
"[::ffff:1.2.3.4]"),
// IPv4 literals in BSD form
Arguments.of(InetAddressClass.INET_ADDRESS, "0256.1.2.3"),
Arguments.of(InetAddressClass.INET4_ADDRESS, "1.2.0256.3"),
Arguments.of(InetAddressClass.INET_ADDRESS, "0x1.2.3.4"),
Arguments.of(InetAddressClass.INET4_ADDRESS, "1.2.0x3.4"),
Arguments.of(InetAddressClass.INET_ADDRESS, "0xFFFFFFFF"),
Arguments.of(InetAddressClass.INET4_ADDRESS, "0xFFFFFFFF")
);
// Construct arguments for a test case with IPv6-scoped address with scope-id
// specified as a string with non-existing network interface name
String ifName = generateNonExistingIfName();
Stream<Arguments> nonExistingIFinScope = ifName.isBlank() ? Stream.empty() :
Stream.of(Arguments.of(InetAddressClass.INET6_ADDRESS,
"2001:db8:a0b:12f0::1%" + ifName));
return Stream.concat(argumentsStream, nonExistingIFinScope);
}
private static Stream<Arguments> addressToValidTestCases(InetAddress inetAddress) {
String addressLiteral = inetAddress.getHostAddress();
byte[] expectedAddressBytes = inetAddress.getAddress();
InetAddressClass addressClass = switch (inetAddress) {
case Inet4Address i4 -> InetAddressClass.INET4_ADDRESS;
case Inet6Address i6 -> InetAddressClass.INET6_ADDRESS;
case InetAddress ia -> InetAddressClass.INET_ADDRESS;
};
return Stream.of(
Arguments.of(InetAddressClass.INET_ADDRESS, addressLiteral, expectedAddressBytes),
Arguments.of(addressClass, addressLiteral, expectedAddressBytes));
}
private static String generateNonExistingIfName() {
try {
// At least two network interfaces are required to generate
// a non-existing interface name
if (NetworkInterface.networkInterfaces().count() < 2) {
return "";
}
return NetworkInterface
.networkInterfaces()
.map(NetworkInterface::getName)
.collect(Collectors.joining())
.strip();
} catch (SocketException e) {
return "";
}
}
private static Executable constructExecutable(InetAddressClass inetAddressClass, String input) {
return switch (inetAddressClass) {
case INET_ADDRESS -> () -> InetAddress.ofLiteral(input);
case INET4_ADDRESS -> () -> Inet4Address.ofLiteral(input);
case INET6_ADDRESS -> () -> Inet6Address.ofLiteral(input);
};
}
enum InetAddressClass {
INET_ADDRESS,
INET4_ADDRESS,
INET6_ADDRESS
}
}