SubnetUtils6Test.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.net.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigInteger;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.apache.commons.net.util.SubnetUtils6.SubnetInfo;
import org.junit.jupiter.api.Test;
/**
* Tests {@link SubnetUtils6}.
*/
class SubnetUtils6Test {
private static final BigInteger TWO = BigInteger.valueOf(2);
@Test
void testBasicCidr64() {
final SubnetUtils6 utils = new SubnetUtils6("2001:db8::1/64");
final SubnetInfo info = utils.getInfo();
assertEquals(64, info.getPrefixLength());
assertEquals("2001:db8:0:0:0:0:0:1", info.getAddress());
assertEquals("2001:db8:0:0:0:0:0:0", info.getNetworkAddress());
assertEquals("2001:db8:0:0:ffff:ffff:ffff:ffff", info.getHighAddress());
// 2^64 addresses
assertEquals(TWO.pow(64), info.getAddressCount());
}
@Test
void testBasicCidr128() {
final SubnetUtils6 utils = new SubnetUtils6("2001:db8::1/128");
final SubnetInfo info = utils.getInfo();
assertEquals(128, info.getPrefixLength());
assertEquals("2001:db8:0:0:0:0:0:1", info.getNetworkAddress());
assertEquals("2001:db8:0:0:0:0:0:1", info.getHighAddress());
assertEquals(BigInteger.ONE, info.getAddressCount());
}
@Test
void testCidr0() {
final SubnetUtils6 utils = new SubnetUtils6("::/0");
final SubnetInfo info = utils.getInfo();
assertEquals(0, info.getPrefixLength());
assertEquals("0:0:0:0:0:0:0:0", info.getNetworkAddress());
// 2^128 addresses
assertEquals(TWO.pow(128), info.getAddressCount());
}
@Test
void testCidr48() {
final SubnetUtils6 utils = new SubnetUtils6("2001:db8:abcd::/48");
final SubnetInfo info = utils.getInfo();
assertEquals(48, info.getPrefixLength());
assertEquals("2001:db8:abcd:0:0:0:0:0", info.getNetworkAddress());
assertEquals("2001:db8:abcd:ffff:ffff:ffff:ffff:ffff", info.getHighAddress());
}
@Test
void testCompressedAddress() {
final SubnetUtils6 utils = new SubnetUtils6("fe80::1/10");
final SubnetInfo info = utils.getInfo();
assertEquals(10, info.getPrefixLength());
assertTrue(info.isInRange("fe80::1"));
assertTrue(info.isInRange("fe80::ffff"));
assertTrue(info.isInRange("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
assertFalse(info.isInRange("fec0::1")); // Outside /10 range
}
@Test
void testConstructorWithSeparateArgs() {
final SubnetUtils6 utils = new SubnetUtils6("2001:db8::1", 64);
final SubnetInfo info = utils.getInfo();
assertEquals(64, info.getPrefixLength());
assertEquals("2001:db8:0:0:0:0:0:0", info.getNetworkAddress());
}
@Test
void testFullAddress() {
final SubnetUtils6 utils = new SubnetUtils6("2001:0db8:0000:0000:0000:0000:0000:0001/64");
final SubnetInfo info = utils.getInfo();
assertEquals(64, info.getPrefixLength());
assertEquals("2001:db8:0:0:0:0:0:0", info.getNetworkAddress());
}
@Test
void testGetCidrSignature() {
final SubnetUtils6 utils = new SubnetUtils6("2001:db8::1/64");
final SubnetInfo info = utils.getInfo();
assertEquals("2001:db8:0:0:0:0:0:1/64", info.getCidrSignature());
}
@Test
void testInvalidCidr() {
assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6(null));
assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1"));
assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1/"));
assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1/129"));
assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1/-1"));
assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1/abc"));
assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("not-an-address/64"));
}
@Test
void testInvalidTwoArgConstructor() {
assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1", 129));
assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("2001:db8::1", -1));
assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("not-an-address", 64));
}
@Test
void testInvalidIPv4Address() {
// IPv4 addresses should be rejected
assertThrows(IllegalArgumentException.class, () -> new SubnetUtils6("192.168.1.1/24"));
}
@Test
void testIsInRange() {
final SubnetUtils6 utils = new SubnetUtils6("2001:db8::/32");
final SubnetInfo info = utils.getInfo();
// Addresses in range
assertTrue(info.isInRange("2001:db8::1"));
assertTrue(info.isInRange("2001:db8::"));
assertTrue(info.isInRange("2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"));
assertTrue(info.isInRange("2001:db8:1234:5678:9abc:def0:1234:5678"));
// Addresses out of range
assertFalse(info.isInRange("2001:db9::1"));
assertFalse(info.isInRange("2001:db7::1"));
assertFalse(info.isInRange("2002:db8::1"));
assertFalse(info.isInRange("::1"));
}
@Test
void testIsInRangeWithInvalidString() {
final SubnetUtils6 utils = new SubnetUtils6("2001:db8::/32");
final SubnetInfo info = utils.getInfo();
assertThrows(IllegalArgumentException.class, () -> info.isInRange("not-an-address"));
assertThrows(IllegalArgumentException.class, () -> info.isInRange("192.168.1.1"));
}
@Test
void testIsInRangeWithBigInteger() throws UnknownHostException {
final SubnetUtils6 utils = new SubnetUtils6("2001:db8::/32");
final SubnetInfo info = utils.getInfo();
// Test with null
assertFalse(info.isInRange((BigInteger) null));
final BigInteger inRange = new BigInteger(1, InetAddress.getByName("2001:db8::1").getAddress());
assertTrue(info.isInRange(inRange));
final BigInteger outOfRange = new BigInteger(1, InetAddress.getByName("2001:db9::1").getAddress());
assertFalse(info.isInRange(outOfRange));
}
@Test
void testIsInRangeWithByteArray() throws UnknownHostException {
final SubnetUtils6 utils = new SubnetUtils6("2001:db8::/32");
final SubnetInfo info = utils.getInfo();
// Test with null
assertFalse(info.isInRange((byte[]) null));
// Test with wrong length
assertFalse(info.isInRange(new byte[4]));
assertFalse(info.isInRange(new byte[15]));
assertFalse(info.isInRange(new byte[17]));
assertTrue(info.isInRange(InetAddress.getByName("2001:db8::1").getAddress()));
assertFalse(info.isInRange(InetAddress.getByName("2001:db9::1").getAddress()));
}
@Test
void testIsInRangeWithInet6Address() throws UnknownHostException {
final SubnetUtils6 utils = new SubnetUtils6("2001:db8::/32");
final SubnetInfo info = utils.getInfo();
// Test with actual Inet6Address
final Inet6Address addr = (Inet6Address) InetAddress.getByName("2001:db8::1");
assertTrue(info.isInRange(addr));
final Inet6Address addrOutside = (Inet6Address) InetAddress.getByName("2001:db9::1");
assertFalse(info.isInRange(addrOutside));
// Test with null
assertFalse(info.isInRange((Inet6Address) null));
}
@Test
void testLinkLocalAddress() {
final SubnetUtils6 utils = new SubnetUtils6("fe80::/10");
final SubnetInfo info = utils.getInfo();
assertTrue(info.isInRange("fe80::1"));
assertTrue(info.isInRange("fe80::1:2:3:4"));
assertFalse(info.isInRange("::1")); // Loopback is not link-local
}
@Test
void testLoopbackAddress() {
final SubnetUtils6 utils = new SubnetUtils6("::1/128");
final SubnetInfo info = utils.getInfo();
assertEquals(128, info.getPrefixLength());
assertTrue(info.isInRange("::1"));
assertFalse(info.isInRange("::2"));
}
@Test
void testToString() {
final SubnetUtils6 utils = new SubnetUtils6("2001:db8::1/64");
final SubnetInfo info = utils.getInfo();
final String str = utils.toString();
assertNotNull(str);
assertTrue(str.contains("CIDR Signature"));
assertTrue(str.contains("Network"));
assertTrue(str.contains("First address"));
assertTrue(str.contains("Last address"));
assertTrue(str.contains("Address Count"));
// note: The CIDR signature from toString can be fed back into the constructor
final String cidr = info.getCidrSignature();
final SubnetUtils6 roundTrip = new SubnetUtils6(cidr);
final SubnetInfo roundTripInfo = roundTrip.getInfo();
assertEquals(info.getPrefixLength(), roundTripInfo.getPrefixLength());
assertEquals(info.getNetworkAddress(), roundTripInfo.getNetworkAddress());
assertEquals(info.getHighAddress(), roundTripInfo.getHighAddress());
assertEquals(info.getAddressCount(), roundTripInfo.getAddressCount());
assertEquals(info.getCidrSignature(), roundTripInfo.getCidrSignature());
}
@Test
void testUniqueLocalAddress() {
// ULA range is fc00::/7
final SubnetUtils6 utils = new SubnetUtils6("fd00::/8");
final SubnetInfo info = utils.getInfo();
assertTrue(info.isInRange("fd00::1"));
assertTrue(info.isInRange("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
assertFalse(info.isInRange("fc00::1")); // fc00::/8 is different from fd00::/8
}
@Test
void testHighBitAddress() {
final SubnetUtils6 utils = new SubnetUtils6("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128");
final SubnetInfo info = utils.getInfo();
assertEquals("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", info.getAddress());
assertEquals("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", info.getNetworkAddress());
assertEquals("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", info.getHighAddress());
assertEquals(BigInteger.ONE, info.getAddressCount());
}
@Test
void testGetLowAddress() {
final SubnetUtils6 utils = new SubnetUtils6("2001:db8::100/120");
final SubnetInfo info = utils.getInfo();
// getLowAddress returns the network address (same as getNetworkAddress)
assertEquals(info.getNetworkAddress(), info.getLowAddress());
assertEquals("2001:db8:0:0:0:0:0:100", info.getLowAddress());
}
// All examples below are from https://datatracker.ietf.org/doc/html/rfc5952 to verify properly
/**
* RFC 5952 Section 1: all representations of the same address must parse identically.
*/
@Test
void testRfc5952Section1EquivalentRepresentations() {
assertEquivalentSubnets(
"2001:db8:0:0:1:0:0:1/128",
"2001:0db8:0:0:1:0:0:1/128",
"2001:db8::1:0:0:1/128",
"2001:db8::0:1:0:0:1/128",
"2001:0db8::1:0:0:1/128",
"2001:db8:0:0:1::1/128",
"2001:db8:0000:0:1::1/128",
"2001:DB8:0:0:1::1/128"
);
}
/**
* RFC 5952 Section 2.1: leading zeros in each 16-bit group must not affect parsing.
*/
@Test
void testRfc5952Section21LeadingZeros() {
assertEquivalentSubnets(
"2001:db8:aaaa:bbbb:cccc:dddd:eeee:0001/128",
"2001:db8:aaaa:bbbb:cccc:dddd:eeee:001/128",
"2001:db8:aaaa:bbbb:cccc:dddd:eeee:01/128",
"2001:db8:aaaa:bbbb:cccc:dddd:eeee:1/128"
);
}
/**
* RFC 5952 Section 2.2: various :: compression positions must resolve to the same address.
*/
@Test
void testRfc5952Section22ZeroCompression() {
assertEquivalentSubnets(
"2001:db8:0:0:0:0:0:1/128",
"2001:db8:0:0:0::1/128",
"2001:db8:0:0::1/128",
"2001:db8:0::1/128",
"2001:db8::1/128"
);
}
/**
* RFC 5952 Section 2.3: uppercase, lowercase, and mixed-case hex digits must parse identically.
*/
@Test
void testRfc5952Section23CaseInsensitivity() {
assertEquivalentSubnets(
"2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa/128",
"2001:db8:aaaa:bbbb:cccc:dddd:eeee:AAAA/128",
"2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa/128",
"2001:DB8:AAAA:BBBB:CCCC:DDDD:EEEE:AAAA/128"
);
}
/**
* RFC 5952 Section 4.1: canonical form suppresses leading zeros.
* Verifies that {@code 2001:0db8::0001} and {@code 2001:db8::1} produce the same output.
*/
@Test
void testRfc5952Section41CanonicalLeadingZeros() {
final SubnetInfo a = new SubnetUtils6("2001:0db8::0001/128").getInfo();
final SubnetInfo b = new SubnetUtils6("2001:db8::1/128").getInfo();
assertEquals(a.getAddress(), b.getAddress());
}
/**
* RFC 5952 Section 4.2.1: :: must compress the longest possible run.
* Both forms represent the same address.
*/
@Test
void testRfc5952Section421MaximumShortening() {
final SubnetInfo a = new SubnetUtils6("2001:db8::0:1/128").getInfo();
final SubnetInfo b = new SubnetUtils6("2001:db8::1/128").getInfo();
assertEquals(a.getAddress(), b.getAddress());
}
/**
* RFC 5952 Section 4.2.3: when two zero runs of equal length exist, the first must be compressed.
* Both input forms must parse to the same address.
*/
@Test
void testRfc5952Section423FirstLongestRunCompressed() {
assertEquivalentSubnets(
"2001:db8:0:0:1:0:0:1/128",
"2001:db8::1:0:0:1/128",
"2001:db8:0:0:1::1/128"
);
}
private static void assertEquivalentSubnets(final String... cidrs) {
final SubnetInfo reference = new SubnetUtils6(cidrs[0]).getInfo();
for (int i = 1; i < cidrs.length; i++) {
final SubnetInfo other = new SubnetUtils6(cidrs[i]).getInfo();
assertEquals(reference.getNetworkAddress(), other.getNetworkAddress(),
cidrs[0] + " vs " + cidrs[i] + " network");
assertEquals(reference.getHighAddress(), other.getHighAddress(),
cidrs[0] + " vs " + cidrs[i] + " high");
assertEquals(reference.getAddress(), other.getAddress(),
cidrs[0] + " vs " + cidrs[i] + " address");
}
}
@Test
void testRfc5952Section5SpecialAddresses() {
final SubnetInfo loopback = new SubnetUtils6("::1/128").getInfo();
assertEquals("0:0:0:0:0:0:0:1", loopback.getAddress());
final SubnetInfo unspecified = new SubnetUtils6("::/128").getInfo();
assertEquals("0:0:0:0:0:0:0:0", unspecified.getAddress());
}
}