diff --git a/IPAddress/build.xml b/IPAddress/build.xml index 0c305d1f..9cb7d542 100644 --- a/IPAddress/build.xml +++ b/IPAddress/build.xml @@ -1,6 +1,6 @@ Create IP address jar - + diff --git a/IPAddress/dist/IPAddress.jar b/IPAddress/dist/IPAddress.jar index 9d32dbad..3d1d29ff 100644 Binary files a/IPAddress/dist/IPAddress.jar and b/IPAddress/dist/IPAddress.jar differ diff --git a/IPAddress/maintenance_instructions.txt b/IPAddress/maintenance_instructions.txt index 65e2284f..7133af55 100644 --- a/IPAddress/maintenance_instructions.txt +++ b/IPAddress/maintenance_instructions.txt @@ -17,7 +17,12 @@ https://github.com/seancfoley/Functional-Doclet-for-Javadoc 4. In eclipse you switch the IPAddress project to the local gh-pages branch. Do not commit the javadoc files before doing so. Since those files do not belong to any branch, they will remain in your IPAddress project as you switch branches. -5. Once in gh-pages, you cut and paste the new javadoc files from the uncommitted javadoc folder to the apidocs folder, overwriting the old ones +5. Once in gh-pages, you cut and paste the new javadoc files from the uncommitted javadoc folder to the apidocs folder, overwriting the old ones. +From command line: +cd ~/git/IPAddress/IPAddress/javadoc +cp -r * ../apidocs + + 6. You commit and push the new contents in the apidocs folder. @@ -69,6 +74,8 @@ Then create the dist jar with java 9 in the same JVM as Eclipse, otherwise ant w 3. Use build.xml to "create dist jar" running in jdk >= 9, maybe the same as used by the workspace. +Note: last time, I could NOT run in the same JVM as the workspace. But it worked when running in separate JRE. So, who knows. + 4. Replace the checked-in file IPAddress.jar with the dist jar created. 5. Commit and push the changes (build.xml and IPAddress.jar and any source file changes not yet pushed). @@ -78,7 +85,7 @@ Then create the dist jar with java 9 in the same JVM as Eclipse, otherwise ant w 7. Create the release using the github releases button on the releases page, which allows you to select the branch. This creates the tag as well (but does not create a branch). The tags have the format vx.x.x -Add the dist jar from (2) to the release. +Add the dist jar from (3) to the release. The source code zip and tar files are automatically created by github. If something goes wrong, move the tag: https://stackoverflow.com/questions/8044583/how-can-i-move-a-tag-on-a-git-branch-to-a-different-commit diff --git a/IPAddress/release_notes.txt b/IPAddress/release_notes.txt index e1d3be72..292e3d0a 100644 --- a/IPAddress/release_notes.txt +++ b/IPAddress/release_notes.txt @@ -1,50 +1,65 @@ +Version 5.5: + +-added a collection type for dual IPv4/v6 tries and another for dual IPv4/v6 associative tries, issue #103 +-trie performance improvements for all trie operations +-added shortestPrefixMatch trie methods +-added enumerate methods, the inverse of the increment methods, to find the position of an address in a subnet, or to find the distance between two addresses +-added an increment method accepting a BigInteger argument to IPv6Address and IPv6AddressSection +-added the ability to construct an IPv6Address from two longs +-added replace methods to address classes that take address sections +-added overlaps methods to check for overlapping subnets, and for checking sequential range overlap with a subnet +-added floor/lower/ceiling/higher methods to the address trie types, these methods were previously accessible only from trie sets +-added the extraneous digits IPv4 parsing option allow_inet_aton_extraneous_digits, issue #105. +-includes the fix to invalid radix argument infinite loop, issue #118 + + Version 5.4: -added PrefixBlockAllocator for automatic CIDR prefix block allocation -added AddedTree and AssociativeAddedTree classes for expanded constructAddedNodesTree methods -added getBlockSize and getBitsForCount in AddressItem -added matchUnordered and matchOrdered in Address -eliminated invalid AddressValueException when joining a range with the IPv4 max value to an IPv6 range, issue #86 -fix to generation of strings from parsed address data, issue #87 -fix to IPv4 address primitive int upper value generation, issue #96 +-added PrefixBlockAllocator for automatic CIDR prefix block allocation +-added AddedTree and AssociativeAddedTree classes for expanded constructAddedNodesTree methods +-added getBlockSize and getBitsForCount in AddressItem +-added matchUnordered and matchOrdered in Address +-eliminated invalid AddressValueException when joining a range with the IPv4 max value to an IPv6 range, issue #86 +-fix to generation of strings from parsed address data, issue #87 +-fix to IPv4 address primitive int upper value generation, issue #96 Version 5.3: This version introduces address tries, associative address tries, address sets backed by address tries, and maps backed by associative address tries -added AddressTrie and its subclasses for IPv4, IPv6 and MAC -added AssociativeAddressTrie and its subclasses for IPv4, IPv6 and MAC -tries can be used as Java collections framework navigable set -associative tries can be used as Java collections framework navigable map -added testBit and isOneBit methods to all series and segments +-added AddressTrie and its subclasses for IPv4, IPv6 and MAC +-added AssociativeAddressTrie and its subclasses for IPv4, IPv6 and MAC +-tries can be used as Java collections framework navigable set +-associative tries can be used as Java collections framework navigable map +-added testBit and isOneBit methods to all series and segments Version 5.2: This version introduces methods for Java 8 functional-style operations. -added stream methods for addresses, address sections, address segments, and ip address sequential ranges: stream, prefixStream, prefixStream(int prefixLength), prefixBlockStream, prefixBlockStream(int prefixLength), blockStream(int segmentCount), sequentialBlockStream, segmentsStream -added corresponding spliterator methods: spliterator, prefixSpliterator, prefixSpliterator(int prefixLength), prefixBlockSpliterator, prefixBlockSpliterator(int prefixLength), blockSpliterator(int segmentCount), sequentialBlockSpliterator, segmentsSpliterator -added functions to create a single stream from multiple spliterators in AddressComponentRange: - Stream stream(Function> addrStreamFunc, T ...components) - Stream stream(Function> addrStreamFunc, Collection components) -added coverWithPrefixBlock method to find single covering prefix block, the smallest prefix block covering two subnets or addresses -added IPAddressString and HostName parsed mask access through getMask method -made sub-typing of address classes easier by loosening restrictions on using multiple network objects -altered network mask with prefix length so that it is single host +-added stream methods for addresses, address sections, address segments, and ip address sequential ranges: stream, prefixStream, prefixStream(int prefixLength), prefixBlockStream, prefixBlockStream(int prefixLength), blockStream(int segmentCount), sequentialBlockStream, segmentsStream +-added corresponding spliterator methods: spliterator, prefixSpliterator, prefixSpliterator(int prefixLength), prefixBlockSpliterator, prefixBlockSpliterator(int prefixLength), blockSpliterator(int segmentCount), sequentialBlockSpliterator, segmentsSpliterator +-added functions to create a single stream from multiple spliterators in AddressComponentRange: +- Stream stream(Function> addrStreamFunc, T ...components) +- Stream stream(Function> addrStreamFunc, Collection components) +-added coverWithPrefixBlock method to find single covering prefix block, the smallest prefix block covering two subnets or addresses +-added IPAddressString and HostName parsed mask access through getMask method +-made sub-typing of address classes easier by loosening restrictions on using multiple network objects +-altered network mask with prefix length so that it is single host Version 5.1: Mostly parsing and masking improvements. -getSequentialRange() method added to IPAddressString for direct access to sequential range -improved handling of masking and bitwise-oring subnets. isMaskCompatibleWithRange replaced by maskRange, same with bitwise-or. -getDivisionGrouping() method added to IPAddressString for "as-is" parsing -toString() for division strings adjusted, no longer using '*' due to varying bit lengths for divisions and potentially no segment separator to indicate bit length, also using radix matching parsed string -reverse ranges allowed in parsed strings -improved control/support of inferred range boundaries +-getSequentialRange() method added to IPAddressString for direct access to sequential range +-improved handling of masking and bitwise-oring subnets. isMaskCompatibleWithRange replaced by maskRange, same with bitwise-or. +-getDivisionGrouping() method added to IPAddressString for "as-is" parsing +-toString() for division strings adjusted, no longer using '*' due to varying bit lengths for divisions and potentially no segment separator to indicate bit length, also using radix matching parsed string +-reverse ranges allowed in parsed strings +-improved control/support of inferred range boundaries Version 5 release: diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/Address.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/Address.java index d3b8df87..0fe0049f 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/Address.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/Address.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -233,7 +233,29 @@ public void getSegments(int start, int end, AddressSegment segs[], int index) { @Override public abstract Address getUpper(); - + + /** + * Returns whether this address represents more than a single individual address, whether it is a subnet. + * + * Such addresses include CIDR/IP addresses (eg 1.2.3.0/25) or wildcard addresses (eg 1.2.*.4) or range addresses (eg 1.2.3-4.5) + * + * @return whether this address represents more than one address. + */ + @Override + public boolean isMultiple() { + return getSection().isMultiple(); + } + + /** + * Returns whether this address has an associated prefix length + * + * @return whether this address has an associated prefix length + */ + @Override + public boolean isPrefixed() { + return getSection().isPrefixed(); + } + /** * Returns whether this address is an IP address * @@ -242,7 +264,7 @@ public void getSegments(int start, int end, AddressSegment segs[], int index) { public boolean isIPAddress() { return false; } - + /** * Returns whether this address is a MAC address * @@ -251,7 +273,7 @@ public boolean isIPAddress() { public boolean isMACAddress() { return false; } - + /** * If this address is an IP address, returns that {@link IPAddress}. Otherwise, returns null. * @@ -260,7 +282,7 @@ public boolean isMACAddress() { public IPAddress toIPAddress() { return null; } - + /** * If this address is a MAC address, returns that {@link MACAddress}. Otherwise, returns null. * @@ -269,29 +291,7 @@ public IPAddress toIPAddress() { public MACAddress toMACAddress() { return null; } - - /** - * Returns whether this address represents more than a single individual address, whether it is a subnet. - * - * Such addresses include CIDR/IP addresses (eg 1.2.3.0/25) or wildcard addresses (eg 1.2.*.4) or range addresses (eg 1.2.3-4.5) - * - * @return whether this address represents more than one address. - */ - @Override - public boolean isMultiple() { - return getSection().isMultiple(); - } - /** - * Returns whether this address has an associated prefix length - * - * @return whether this address has an associated prefix length - */ - @Override - public boolean isPrefixed() { - return getSection().isPrefixed(); - } - /** * the largest number of high bits for which this address represents all addresses with the same set of high bits */ @@ -299,7 +299,7 @@ public boolean isPrefixed() { public Integer getPrefixLength() { return getSection().getPrefixLength(); } - + /** * Returns the smallest prefix length possible such that this includes the block of addresses for that prefix. *

@@ -474,7 +474,7 @@ public int hashCode() { public boolean isSameAddress(Address other) { return other == this || getSection().equals(other.getSection()); } - + /** * Two Address objects are equal if they represent the same set of addresses. */ @@ -492,16 +492,31 @@ public boolean equals(Object o) { } return false; } - + public boolean prefixEquals(Address other) { if(other == this) { return true; } return getSection().prefixEquals(other.getSection()); } - + /** - * Returns whether this is same type and version of the given address and whether it contains all values in the given address or subnet + * Returns whether this is same type and version of the given address and whether it overlaps with the individual addresses in the given address or subnet, + * containing at least one individual address common to both. + * + * + * @param other + * @return + */ + public boolean overlaps(Address other) { + if(other == this) { + return true; + } + return getSection().overlaps(other.getSection()); + } + + /** + * Returns whether this is same type and version of the given address and whether it contains all individual addresses in the given address or subnet * * @param other * @return @@ -513,6 +528,27 @@ public boolean contains(Address other) { return getSection().contains(other.getSection()); } + /** + * Indicates where an address sits relative to the subnet ordering. + *

+ * Determines how many address elements of a subnet precede the given address element, if the address is in the subnet. + * If above the subnet range, it is the distance to the upper boundary added to the subnet address count less one, and if below the subnet range, the distance to the lower boundary. + *

+ * In other words, if the given address is not in the subnet but above it, returns the number of addresses preceding the address from the upper subnet boundary, + * added to one less than the total number of subnet addresses. If the given address is not in the subnet but below it, returns the number of addresses following the address to the lower subnet boundary. + *

+ * enumerate returns null when the argument is a multi-valued subnet. The argument must be an individual address. + *

+ * When this address is also single-valued, the returned value is the distance (difference) between this address and the argument address. + *

+ * enumerate is the inverse of the increment method: + *

  • subnet.enumerate(subnet.increment(inc)) = inc
  • + *
  • subnet.increment(subnet.enumerate(newAddr)) = newAddr
+ *

+ * If the given address does not have the same version or type as this subnet or address, then null is returned. + */ + public abstract BigInteger enumerate(Address other); + @Override public boolean isSequential() { return getSection().isSequential(); @@ -718,7 +754,7 @@ public static boolean matchOrdered(Address addrs1[], Address addrs2[]) { if(len1 != len2) { return false; } - for(int i = 0; i < addrs1.length; i++) { + for(int i = 0; i < len1; i++) { if(!addrs1[i].equals(addrs2[i])) { return false; } diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressComparator.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressComparator.java index 33441bd0..ac41777a 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressComparator.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressComparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -383,19 +383,12 @@ protected int compareParts(AddressSection one, AddressSection two) { } boolean compareHigh = compareHighValue; do { - int segCount = one.getSegmentCount(); - for(int i = 0; i < segCount; i++) { - AddressSegment segOne = one.getSegment(i); - AddressSegment segTwo = two.getSegment(i); - int result = compareHigh ? - (segOne.getUpperSegmentValue() - segTwo.getUpperSegmentValue()) : - (segOne.getSegmentValue() - segTwo.getSegmentValue()); - if(result != 0) { - if(flipSecond && compareHigh != compareHighValue) { - return -result; - } - return result; + int result = compareSegmentValues(compareHigh, one, two); + if(result != 0) { + if(flipSecond && compareHigh != compareHighValue) { + return -result; } + return result; } compareHigh = !compareHigh; } while(compareHigh != compareHighValue); @@ -861,4 +854,27 @@ protected int compareValues(BigInteger oneUpper, BigInteger oneLower, BigInteger return result; } } + + static int compareSegmentValues(boolean compareUpper, AddressSection one, AddressSection two) { + int segCount = one.getSegmentCount(); + for(int i = 0; i < segCount; i++) { + AddressSegment segOne = one.getSegment(i); + AddressSegment segTwo = two.getSegment(i); + int s1, s2; + if(compareUpper) { + s1 = segOne.getUpperSegmentValue(); + s2 = segTwo.getUpperSegmentValue(); + } else { + s1 = segOne.getSegmentValue(); + s2 = segTwo.getSegmentValue(); + } + if(s1 != s2) { + if(s1 > s2) { + return 1; + } + return -1; + } + } + return 0; + } } diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressComponent.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressComponent.java index 817584b8..299a9234 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressComponent.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressNetwork.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressNetwork.java index f1968194..ac917e77 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressNetwork.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressNetwork.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2019 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressSection.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressSection.java index b8bb6572..4dc1d5d0 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressSection.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressSection.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package inet.ipaddr; +import java.math.BigInteger; import java.util.Iterator; import java.util.stream.Stream; @@ -28,7 +29,7 @@ * */ public interface AddressSection extends AddressSegmentSeries { - + /** * Determines if one section contains another. *

@@ -40,7 +41,41 @@ public interface AddressSection extends AddressSegmentSeries { * @return whether this section contains the given address section */ boolean contains(AddressSection other); - + + /** + * Determines if one section overlaps another. + *

+ * Sections must have the same number of segments to be comparable. + *

+ * For sections which are aware of their position in an address (IPv6 and MAC), their respective positions must match to be comparable. + * + * @param other + * @return whether this section overlaps the given address section + */ + boolean overlaps(AddressSection other); + + /** + * Indicates where an address section sits relative to the ordering of individual address sections within this section. + *

+ * Determines how many address section elements precede the given address section element, if the given address section is within this address section. + * If above the range, it is the distance to the upper boundary added to the address section count less one, and if below the range, the distance to the lower boundary. + *

+ * In other words, if the given address section is not in this section but above it, returns the number of individual address sections preceding the given address section from the upper section boundary, + * added to one less than the total number of individual address sections within. If the given address section is not in this section but below it, returns the number of individual address sections following the given address section to the lower section boundary. + *

+ * enumerate returns null when the argument is a multi-valued section. The argument must be an individual address section. + *

+ * When this address section is also single-valued, the returned value is the distance (difference) between this address section and the argument address section. + *

+ * enumerate is the inverse of the increment method: + *

  • section.enumerate(section.increment(inc)) = inc
  • + *
  • section.increment(section.enumerate(individualSection)) = individualSection
+ * + * If the given address section does not have the same version or type as this address section, then null is returned. + * If the given address section is the same version and type, but has a different segment count, then SizeMismatchException is thrown. + */ + BigInteger enumerate(AddressSection other); + /** * Determines if the argument section matches this section up to the prefix length of this section. *

@@ -54,40 +89,40 @@ public interface AddressSection extends AddressSegmentSeries { * @return whether the argument section has the same address section prefix as this */ boolean prefixEquals(AddressSection other); - + @Override AddressSection getLower(); - + @Override AddressSection getUpper(); - + @Override AddressSection reverseSegments(); - + @Override AddressSection reverseBits(boolean perByte); - + @Override AddressSection reverseBytes(); - + @Override AddressSection reverseBytesPerSegment(); - + @Override AddressSection toPrefixBlock(); @Override @Deprecated AddressSection removePrefixLength(); - + @Override AddressSection withoutPrefixLength(); - + @Override @Deprecated AddressSection removePrefixLength(boolean zeroed); @Override AddressSection adjustPrefixBySegment(boolean nextSegment); - + @Override AddressSection adjustPrefixBySegment(boolean nextSegment, boolean zeroed); @@ -112,7 +147,7 @@ public interface AddressSection extends AddressSegmentSeries { @Override Iterator iterator(); - + @Override AddressComponentSpliterator spliterator(); @@ -121,7 +156,7 @@ public interface AddressSection extends AddressSegmentSeries { @Override Iterator prefixIterator(); - + @Override AddressComponentSpliterator prefixSpliterator(); @@ -130,7 +165,7 @@ public interface AddressSection extends AddressSegmentSeries { @Override Iterator prefixBlockIterator(); - + @Override AddressComponentSpliterator prefixBlockSpliterator(); diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressSegment.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressSegment.java index 7a2d3336..6a4c0649 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressSegment.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressSegment.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -112,6 +112,8 @@ public interface AddressSegment extends AddressComponent, AddressGenericDivision boolean matchesWithMask(int lowerValue, int upperValue, int mask); + boolean overlaps(AddressSegment other); + boolean contains(AddressSegment other); @Override diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressSegmentSeries.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressSegmentSeries.java index f0ebdf40..c4bd7fbe 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressSegmentSeries.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressSegmentSeries.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -145,7 +145,7 @@ public interface AddressSegmentSeries extends AddressDivisionSeries, AddressComp AddressSegmentSeries getLower(); /** - * If this represents a series with ranging values, returns a series representing the upper values of the range + * If this represents a series with ranging values, returns a series representing the upper values of the range. * If this represents a series with a single value in each segment, returns this. * * @return diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressStringParameters.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressStringParameters.java index bb646bd5..22a6e74b 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressStringParameters.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressStringParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2020 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressValueException.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressValueException.java index 6a433981..6ea17059 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressValueException.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/AddressValueException.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,11 +66,11 @@ public AddressValueException(AddressItem one, String key) { super(one + ", " + errorMessage + " " + getMessage(key)); } - AddressValueException(String message) { + public AddressValueException(String message) { super(message); } - AddressValueException(String message, Throwable cause) { + public AddressValueException(String message, Throwable cause) { super(message, cause); } } diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/HostIdentifierString.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/HostIdentifierString.java index 68dd0b2c..1f0552d9 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/HostIdentifierString.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/HostIdentifierString.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2019 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/HostName.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/HostName.java index d6bab247..b75269cc 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/HostName.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/HostName.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddress.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddress.java index 6e409529..c1151ed3 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddress.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddress.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -658,8 +658,32 @@ protected boolean isFromSameString(HostIdentifierString other) { return false; } + /** + * Returns true if this address overlaps the given address or subnet + * + * @param other + * @return + */ + @Override + public boolean overlaps(IPAddress other) { + return super.overlaps(other); + } + + /** + * Returns true if this address overlaps the given sequential range + * + * @param other + * @return + */ + @Override + public boolean overlaps(IPAddressSeqRange other) { + return other.overlaps(this); + } + /** * Returns whether this contains all values of the given address or subnet + *

+ * Implements the same method in {@link IPAddressRange}. * * @param other * @return @@ -682,6 +706,16 @@ public boolean containsNonZeroHosts(IPAddress other) { return getSection().containsNonZeroHosts(other.getSection()); } + /** + * Indicates where an address sits relative to the subnet ordering. + *

+ * For more details, see the equivalent method {@link #enumerate(Address)}. + * This method satisfies the implementation of {@link IPAddressRange}. + * + */ + @Override + public abstract BigInteger enumerate(IPAddress other); + /** * Returns whether the prefix of this address contains all values of the same bits in the given address or subnet * @@ -718,24 +752,7 @@ public boolean isZeroHost(int networkPrefixLength) { @Override public boolean contains(IPAddressSeqRange otherRange) { - if(compareLowValues(otherRange.getLower(), getLower()) >= 0 && - compareLowValues(otherRange.getUpper(), getUpper()) <= 0) { - if(isSequential()) { - return true; - } - Iterator iterator = sequentialBlockIterator(); - while(iterator.hasNext()) { - IPAddress sequential = iterator.next(); - if(sequential.contains(otherRange)) { - return true; - } - } - } - return false; - } - - static int compareLowValues(IPAddress one, IPAddress two) { - return Address.ADDRESS_LOW_VALUE_COMPARATOR.compare(one, two); + return otherRange.isContainedBy(this); } /** @@ -1570,11 +1587,96 @@ static List spanWithBlocks(IPAddressSegmentSer * @return */ public abstract IPAddress[] mergeToPrefixBlocks(IPAddress ...addresses) throws AddressConversionException; - - protected static List getMergedPrefixBlocks(IPAddressSegmentSeries sections[]) { + + protected static List getMergedPrefixBlocks(IPAddressSegmentSeries sections[]) { return IPAddressSection.getMergedPrefixBlocks(sections); } + private static final IPv6Address EMPTY_IPV6_ADDRESS[] = {}; + private static final IPv4Address EMPTY_IPV4_ADDRESS[] = {}; + + public static class DualIPv4Pv6Arrays { + public final IPv4Address addressesIPv4[]; + public final IPv6Address addressesIPv6[]; + + DualIPv4Pv6Arrays(IPv4Address addressesIPv4[], IPv6Address addressesIPv6[]) { + this.addressesIPv4 = addressesIPv4; + this.addressesIPv6 = addressesIPv6; + } + } + + /** + * merges the given set of IP addresses and subnets into a minimal number of prefix blocks. + * + * This function complements the MergeToPrefixBlock methods of each IP address type. + * Those instance methods attempt to convert arguments that do not match the IP version of the method receiver, while this function does not. + * This static method merges every non-null argument into one of the two returned slices. + * + * @param addresses + * @return + */ + public static DualIPv4Pv6Arrays mergeToDualSequentialBlocks(IPAddress ...addresses) { + Function> merger = (series) -> { + SeriesCreator seriesCreator = ((IPAddress) series[0]).getSequentialSeriesCreator(); + return IPAddressSection.getMergedSequentialBlocks(series, seriesCreator); + }; + return mergeToBlocks(addresses, merger); + } + + protected abstract SeriesCreator getSequentialSeriesCreator(); + + /** + * merges the given set of IP addresses and subnets into a minimal number of prefix blocks. + * + * This function complements the MergeToPrefixBlock methods of each IP address type. + * Those instance methods attempt to convert arguments that do not match the IP version of the method receiver, while this function does not. + * This static method merges every non-null argument into one of the two returned slices. + * + * @param addresses + * @return + */ + public static DualIPv4Pv6Arrays mergeToDualPrefixBlocks(IPAddress ...addresses) { + return mergeToBlocks(addresses, IPAddressSection::getMergedPrefixBlocks); + } + + private static DualIPv4Pv6Arrays mergeToBlocks( + IPAddress addresses[], + Function> merger) { + ArrayList ipv4List = null; + ArrayList ipv6List = null; + for(int i = 0; i < addresses.length; i++) { + IPAddress addr = addresses[i]; + if(addr != null) { + if(addr.isIPv4()) { + if(ipv4List == null) { + ipv4List = new ArrayList(addresses.length); + } + ipv4List.add(addr); + } else if(addr.isIPv6()) { + if(ipv6List == null) { + ipv6List = new ArrayList(addresses.length); + } + ipv6List.add(addr); + } + } + } + IPv4Address addressesIPv4[]; + if(ipv4List != null){ + List blocks = merger.apply(ipv4List.toArray(new IPAddressSegmentSeries[ipv4List.size()])); + addressesIPv4 = blocks.toArray(new IPv4Address[blocks.size()]); + } else { + addressesIPv4 = EMPTY_IPV4_ADDRESS; + } + IPv6Address addressesIPv6[]; + if(ipv6List != null){ + List blocks = merger.apply(ipv6List.toArray(new IPAddressSegmentSeries[ipv6List.size()])); + addressesIPv6 = blocks.toArray(new IPv6Address[blocks.size()]); + } else { + addressesIPv6 = EMPTY_IPV6_ADDRESS; + } + return new DualIPv4Pv6Arrays(addressesIPv4, addressesIPv6); + } + /** * Merges this with the list of subnets to produce the smallest list of block subnets that are sequential. *

diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressConverter.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressConverter.java index 2cf41573..58851c23 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressConverter.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressNetwork.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressNetwork.java index 5ad70e08..0c596da5 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressNetwork.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressNetwork.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressResources.properties b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressResources.properties index 91246109..de68e0df 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressResources.properties +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressResources.properties @@ -1,5 +1,5 @@ # -# Copyright 2016-2018 Sean C Foley +# Copyright 2016-2024 Sean C Foley # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -91,11 +91,8 @@ ipaddress.host.error.invalid.service.hyphen.end=service name cannot end in a hyp ipaddress.host.error.invalid.service.hyphen.start=service name cannot start with a hyphen ipaddress.host.error.invalid.service.hyphen.consecutive=service name cannot have consecutive hyphens ipaddress.host.error.invalid.port.service=invalid port or service name character at index: -ipaddress.error.zoneAndCIDRPrefix=zone and prefix combined -ipaddress.error.zone=IPv6 zone not allowed ipaddress.error.CIDRNotAllowed=CIDR prefix or mask not allowed for this address -ipaddress.error.wildcardOrRangeIPv6=Wildcards and ranges are not supported for IPv6 addresses -ipaddress.error.mixedVersions=Please specify either IPv4 or IPv6 addresses, but not both +ipaddress.error.mixedVersions=please specify either IPv4 or IPv6 addresses, but not both ipaddress.error.mixedNetworks=Address components have different networks ipaddress.error.nullNetwork=network is null ipaddress.error.version.mismatch=Unable to convert version of argument address @@ -123,11 +120,11 @@ ipaddress.error.invalid.zone.encoding=invalid encoding in zone at index: ipaddress.error.mask.single.segment=mask with single segment not allowed by validation options ipaddress.error.exceeds.size=exceeds address size ipaddress.error.index.exceeds.prefix.length=index exceeds prefix length -ipaddress.error.null.segment=Section or grouping array contains a null value -ipaddress.error.inconsistent.prefixes=Segments invalid due to inconsistent prefix values -ipaddress.error.invalid.position=Invalid index into address -ipaddress.error.address.not.block=Address is neither a CIDR prefix block nor an individual address -ipaddress.error.address.out.of.range=Address not within the assigned range +ipaddress.error.null.segment=section or grouping array contains a null value +ipaddress.error.inconsistent.prefixes=segments invalid due to inconsistent prefix values +ipaddress.error.invalid.position=invalid index into address +ipaddress.error.address.not.block=address is neither a CIDR prefix block nor an individual address +ipaddress.error.address.out.of.range=address not within the assigned range ipaddress.error.address.lower.exceeds.upper=invalid address range, lower bound exceeds upper: ipaddress.error.lower.below.range=below range: ipaddress.error.lower.above.range=above range: diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressSection.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressSection.java index 7e6e8b29..2f8b078f 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressSection.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressSection.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -979,7 +979,7 @@ protected static segProducer, IntFunction otherSegProducer) { //check if they are comparable first. We only check segment count, we do not care about start index. - first.checkSectionCount(other); + first.checkSegmentCount(other); //larger prefix length should prevail? hmmmmm... I would say that is true, choose the larger prefix Integer pref = first.getNetworkPrefixLength(); @@ -1185,7 +1185,7 @@ protected static R[] getSpanningPrefixBlocks( UnaryOperator prefixAdder, UnaryOperator prefixRemover, IntFunction arrayProducer) { - first.checkSectionCount(other); + first.checkSegmentCount(other); R result = checkPrefixBlockContainment(first, other, prefixAdder); if(result != null) { R resultArray[] = arrayProducer.apply(1); @@ -1885,7 +1885,7 @@ protected static segProducer, SegFunction prefixApplier) { //check if they are comparable first - first.checkSectionCount(other); + first.checkSegmentCount(other); if(!first.isMultiple()) { if(other.contains(first)) { return null; @@ -2039,13 +2039,7 @@ private static = getSegmentValue() && other.getUpperSegmentValue() <= getUpperSegmentValue(); } + /** + * + * @param other + * @return whether this subnet segment overlaps the given address segment + */ + protected boolean overlapsSeg(AddressSegment other) { + return other.getSegmentValue() <= getUpperSegmentValue() && other.getUpperSegmentValue() >= getSegmentValue(); + } + @Override public boolean includesZero() { return getSegmentValue() == 0; diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressSegmentSeries.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressSegmentSeries.java index 81e02e35..5c11a0dc 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressSegmentSeries.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressSegmentSeries.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2020 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressSeqRange.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressSeqRange.java index 6a9e63d3..4897e48f 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressSeqRange.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressSeqRange.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Sean C Foley + * Copyright 2018-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,8 +32,8 @@ import java.util.stream.Stream; import inet.ipaddr.AddressNetwork.AddressSegmentCreator; -import inet.ipaddr.IPAddressSection.IPAddressSeqRangeSpliterator; import inet.ipaddr.IPAddressSection.IPAddressSeqRangePrefixSpliterator; +import inet.ipaddr.IPAddressSection.IPAddressSeqRangeSpliterator; import inet.ipaddr.IPAddressSection.SegFunction; import inet.ipaddr.IPAddressSection.SeqRangeIteratorProvider; import inet.ipaddr.format.AddressComponentRange; @@ -42,6 +42,8 @@ import inet.ipaddr.format.util.AddressComponentRangeSpliterator; import inet.ipaddr.format.util.AddressComponentSpliterator; import inet.ipaddr.format.validate.ParsedAddressGrouping; +import inet.ipaddr.ipv4.IPv4Address; +import inet.ipaddr.ipv6.IPv6Address; /** * This class can be used to represent an arbitrary range of consecutive IP addresses. @@ -106,10 +108,24 @@ protected IPAddressSeqRange( upper = second; } - private static int compareLowValues(IPAddress one, IPAddress two) { - return IPAddress.compareLowValues(one, two); + private static boolean versionsMatch(IPAddress one, IPAddress two) { + if(one.getClass().equals(two.getClass())) { + return true; + } + // here we use type checks and not: if(!lower.getIPVersion().equals(other.getIPVersion())) + // This is because we construct ranges based on type, + // all we need is both to be the same type IPvxAddress to be able to construct an IPvxAddressSeqRange, + // so we might as well stick with that principle here. + if(one instanceof IPv4Address) { + return two instanceof IPv4Address; + } + return two instanceof IPv6Address; } + private static int compareLowValues(IPAddress one, IPAddress two) { + return AddressComparator.compareSegmentValues(false, one.getSection(), two.getSection()); + } + @Override public BigInteger getCount() { BigInteger result = count; @@ -118,16 +134,16 @@ public BigInteger getCount() { } return result; } - + @Override public boolean isMultiple() { BigInteger count = this.count; if(count == null) { - return !getLower().equals(getUpper()); + return IPAddressRange.super.isMultiple(); } - return IPAddressRange.super.isMultiple(); + return !count.equals(BigInteger.ONE); } - + /** * * @param other the range to compare, which does not need to range across the same address space @@ -136,22 +152,22 @@ public boolean isMultiple() { public boolean isMore(IPAddressSeqRange other) { return getCount().compareTo(other.getCount()) > 0; } - + protected BigInteger getCountImpl() { return IPAddressRange.super.getCount(); } - + @Override public abstract Iterable getIterable(); - + protected static int getNetworkSegmentIndex(int networkPrefixLength, int bytesPerSegment, int bitsPerSegment) { return ParsedAddressGrouping.getNetworkSegmentIndex(networkPrefixLength, bytesPerSegment, bitsPerSegment); } - + protected static int getHostSegmentIndex(int networkPrefixLength, int bytesPerSegment, int bitsPerSegment) { return ParsedAddressGrouping.getHostSegmentIndex(networkPrefixLength, bytesPerSegment, bitsPerSegment); } - + /** * Iterates through the range of prefix blocks in this range instance using the given prefix length. * @@ -422,7 +438,7 @@ protected Iterator iterator(UnaryOperator incrementor) { @Override public boolean hasNext() { - return !count.equals(BigInteger.ZERO); + return count.signum() != 0; } @Override @@ -684,8 +700,11 @@ public static IPAddressSeqRange[] join(IPAddressSeqRange... ranges) { for(; j < len; j++) { IPAddressSeqRange range2 = ranges[j]; IPAddress nextLower = range2.getLower(); + if(!versionsMatch(nextLower, currentUpper)) { + break; + } if(compareLowValues(currentUpper, nextLower) >= 0 - || (currentUpper.getIPVersion().equals(nextLower.getIPVersion()) && currentUpper.increment(1).equals(nextLower))) { + || currentUpper.increment(1).equals(nextLower)) { // join them joinedCount++; IPAddress nextUpper = range2.getUpper(); @@ -722,15 +741,113 @@ public static IPAddressSeqRange[] join(IPAddressSeqRange... ranges) { } return joined; } + + boolean isContainedBy(IPAddress other) { + IPAddress lower = getLower(), upper = getUpper(); + if(!versionsMatch(lower, other)) { + return false; + } + int segCount = lower.getSegmentCount(); + for(int i = 0; i < segCount; i++) { + IPAddressSegment lowerSeg = lower.getSegment(i); + IPAddressSegment upperSeg = upper.getSegment(i); + int lowerSegValue = lowerSeg.getSegmentValue(); + int upperSegValue = upperSeg.getSegmentValue(); + IPAddressSegment otherSeg = other.getSegment(i); + int otherSegLowerValue = otherSeg.getSegmentValue(); + int otherSegUpperValue = otherSeg.getUpperSegmentValue(); + if(lowerSegValue < otherSegLowerValue || upperSegValue > otherSegUpperValue) { + return false; + } + if(lowerSegValue != upperSegValue) { + for(int j = i + 1; j < segCount; j++) { + otherSeg = other.getSegment(j); + if(!otherSeg.isFullRange()) { + return false; + } + } + break; + } + } + return true; + } + + /** + * Returns true if this sequential range overlaps the given address or subnet. + * + * @param other + * @return + */ + @Override + public boolean overlaps(IPAddress other) { + IPAddress lower = getLower(), upper = getUpper(); + if(!versionsMatch(lower, other)) { + return false; + } + int segCount = lower.getSegmentCount(); + for(int i = 0; i < segCount; i++) { + IPAddressSegment lowerSeg = lower.getSegment(i); + IPAddressSegment upperSeg = upper.getSegment(i); + int lowerSegValue = lowerSeg.getSegmentValue(); + int upperSegValue = upperSeg.getSegmentValue(); + IPAddressSegment otherSeg = other.getSegment(i); + int otherSegLowerValue = otherSeg.getSegmentValue(); + int otherSegUpperValue = otherSeg.getUpperSegmentValue(); + if(lowerSegValue == upperSegValue) { + if(lowerSegValue < otherSegLowerValue || lowerSegValue > otherSegUpperValue) { + return false; + } + } else { + if(otherSegLowerValue < upperSegValue && otherSegUpperValue > lowerSegValue) { + return true; + } else if(otherSegLowerValue == upperSegValue) { + for(int j = i + 1; j < segCount; j++) { + otherSeg = other.getSegment(j); + upperSeg = upper.getSegment(j); + upperSegValue = upperSeg.getSegmentValue(); + otherSegLowerValue = otherSeg.getSegmentValue(); + if(otherSegLowerValue < upperSegValue) { + return true; + } else if(otherSegLowerValue > upperSegValue) { + return false; + } + } + break; + } else if(otherSegUpperValue == lowerSegValue) { + for(int j = i + 1; j < segCount; j++) { + otherSeg = other.getSegment(j); + lowerSeg = lower.getSegment(j); + lowerSegValue = lowerSeg.getSegmentValue(); + otherSegUpperValue = otherSeg.getUpperSegmentValue(); + if(otherSegUpperValue > lowerSegValue) { + return true; + } else if(otherSegUpperValue < lowerSegValue) { + return false; + } + } + break; + } else { + return false; + } + } + } + return true; + } /** - * Returns true if this sequential range overlaps with the given sequential range. + * Returns true if this sequential range overlaps the given sequential range. * * @param other * @return */ + @Override public boolean overlaps(IPAddressSeqRange other) { - return compareLowValues(other.getLower(), getUpper()) <= 0 && compareLowValues(other.getUpper(), getLower()) >= 0; + IPAddress otherLower = other.getLower(); + IPAddress upper = getUpper(); + if(!versionsMatch(upper, otherLower)) { + return false; + } + return compareLowValues(otherLower, upper) <= 0 && compareLowValues(other.getUpper(), getLower()) >= 0; } // we choose to not make this public @@ -743,7 +860,12 @@ public boolean overlaps(IPAddressSeqRange other) { // in IPAddress you need to either go through the segments, or you need to go through the sequential blocks, // and there is no general way to do it for any implementation of IPAddressRange. private boolean containsRange(IPAddressRange other) { - return compareLowValues(other.getLower(), getLower()) >= 0 && compareLowValues(other.getUpper(), getUpper()) <= 0; + IPAddress otherLower = other.getLower(); + IPAddress lower = getLower(); + if(!versionsMatch(lower, otherLower)) { + return false; + } + return compareLowValues(otherLower, lower) >= 0 && compareLowValues(other.getUpper(), getUpper()) <= 0; } @Override @@ -756,6 +878,29 @@ public boolean contains(IPAddressSeqRange other) { return containsRange(other); } + /** + * Returns the distance of the given address from the initial value of this range. Indicates where an address sits relative to the range ordering. + *

+ * If within or above the range, it is the distance to the lower boundary of the sequential range. If below the, returns the number of addresses following the address to the lower range boundary. + *

+ * The method does not return null if this range does not contain the address. You can call {@link #contains(IPAddress)} or you can compare with {@link #getCount()} to check for containment. + * An address is in the range if 0 <= {@link #enumerate(IPAddress)} < {@link #getCount()}. + *

+ * Returns null when the argument is a multi-valued subnet. The argument must be an individual address. + *

+ * If the given address does not have the same version or type as the addresses in this range, then null is returned. + */ + @Override + public BigInteger enumerate(IPAddress other) { + IPAddress lower = getLower(); + if(other == lower) { + return BigInteger.ZERO; + } else if(other == getUpper()) { + return getCount().subtract(BigInteger.ONE); + } + return lower.enumerate(other); + } + /** * Returns whether the address or subnet represents a range of values that are sequential. *

@@ -797,10 +942,13 @@ public boolean equals(Object o) { * @return */ public IPAddressSeqRange intersect(IPAddressSeqRange other) { - IPAddress otherLower = other.getLower(); - IPAddress otherUpper = other.getUpper(); IPAddress lower = getLower(); IPAddress upper = getUpper(); + IPAddress otherLower = other.getLower(); + IPAddress otherUpper = other.getUpper(); + if(!versionsMatch(lower, otherLower)) { + return null; + } if(compareLowValues(lower, otherLower) <= 0) { if(compareLowValues(upper, otherUpper) >= 0) { return other; @@ -819,7 +967,7 @@ public IPAddressSeqRange intersect(IPAddressSeqRange other) { /** * Joins two ranges if they are contiguous ranges. * - * If this range overlaps with the given range, + * If this range overlaps the given range, * or if the highest value of the lower range is one below the lowest value of the higher range, * then the two are joined into a new larger range that is returned. *

@@ -829,21 +977,22 @@ public IPAddressSeqRange intersect(IPAddressSeqRange other) { * @return */ public IPAddressSeqRange join(IPAddressSeqRange other) { - IPAddress otherLower = other.getLower(); - IPAddress otherUpper = other.getUpper(); IPAddress lower = getLower(); IPAddress upper = getUpper(); + IPAddress otherLower = other.getLower(); + IPAddress otherUpper = other.getUpper(); + if(!versionsMatch(lower, otherLower)) { + return null; + } int lowerComp = compareLowValues(lower, otherLower); if(!overlaps(other)) { - if(lower.getIPVersion().equals(otherLower.getIPVersion())) { - if(lowerComp >= 0) { - if(otherUpper.increment(1).equals(lower)) { - return create(otherLower, upper); - } - } else { - if(upper.increment(1).equals(otherLower)) { - return create(lower, otherUpper); - } + if(lowerComp >= 0) { + if(otherUpper.increment(1).equals(lower)) { + return create(otherLower, upper); + } + } else { + if(upper.increment(1).equals(otherLower)) { + return create(lower, otherUpper); } } return null; @@ -871,19 +1020,19 @@ public IPAddressSeqRange join(IPAddressSeqRange other) { * @return */ public IPAddressSeqRange extend(IPAddressRange other) { - IPAddress otherLower = other.getLower(); - IPAddress otherUpper = other.getUpper(); IPAddress lower = getLower(); IPAddress upper = getUpper(); + IPAddress otherLower = other.getLower(); + IPAddress otherUpper = other.getUpper(); + if(!versionsMatch(lower, otherLower)) { + return null; + } int lowerComp = compareLowValues(lower, otherLower); int upperComp = compareLowValues(upper, otherUpper); if(lowerComp > 0) { // if(upperComp <= 0) { // ol l u ou return other.toSequentialRange(); } - if(!upper.getIPVersion().equals(otherLower.getIPVersion())) { - return null; - } // ol l ou u or ol ou l u return create(otherLower, upper); } @@ -891,9 +1040,6 @@ public IPAddressSeqRange extend(IPAddressRange other) { if(upperComp >= 0) { // l ol ou u return this; } - if(!lower.getIPVersion().equals(otherUpper.getIPVersion())) { - return null; - } return create(lower, otherUpper);// l ol u ou or l u ol ou } @@ -905,10 +1051,13 @@ public IPAddressSeqRange extend(IPAddressRange other) { * @return */ public IPAddressSeqRange[] subtract(IPAddressSeqRange other) { - IPAddress otherLower = other.getLower(); - IPAddress otherUpper = other.getUpper(); IPAddress lower = getLower(); IPAddress upper = getUpper(); + IPAddress otherLower = other.getLower(); + IPAddress otherUpper = other.getUpper(); + if(!versionsMatch(lower, otherLower)) { + return createSingle(); + } if(compareLowValues(lower, otherLower) < 0) { if(compareLowValues(upper, otherUpper) > 0) { // l ol ou u return createPair(lower, otherLower.increment(-1), otherUpper.increment(1), upper); diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressString.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressString.java index 2325dac1..daff0c54 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressString.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressString.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressStringParameters.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressStringParameters.java index b22ab359..1818b6bb 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressStringParameters.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/IPAddressStringParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -188,6 +188,15 @@ public Builder setRangeOptions(RangeParameters rangeOptions) { return this; } + /** + * Allows joined IPv4 segments, resulting in just 2, 3 or 4 segments. Allows IPv4 octal or hex segments. + * Allows an unlimited number of leading zeros in such segments. + * To allow just a single segment, use {@link IPAddressStringParameters.Builder#allowSingleSegment(boolean)} + * This does not affect whether extraneous IPv4 digits are allowed, which can be allowed with {@link IPv4AddressStringParameters.Builder#allow_inet_aton_extraneous_digits(boolean)} + * + * @param allow + * @return + */ public Builder allow_inet_aton(boolean allow) { getIPv4AddressParametersBuilder().allow_inet_aton(allow); getIPv6AddressParametersBuilder().allow_mixed_inet_aton(allow); diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/IncompatibleAddressException.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/IncompatibleAddressException.java index aadb24f7..0cbc4bb3 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/IncompatibleAddressException.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/IncompatibleAddressException.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,4 +83,12 @@ public IncompatibleAddressException(AddressItem one, int oneIndex, AddressItem t public IncompatibleAddressException(AddressItem one, AddressItem two, String key) { super(one + ", " + two + ", " + errorMessage + " " + getMessage(key)); } + + public IncompatibleAddressException(String message) { + super(message); + } + + public IncompatibleAddressException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/MACAddressString.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/MACAddressString.java index ad7ceb6b..334d4b09 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/MACAddressString.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/MACAddressString.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2021 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/MACAddressStringParameters.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/MACAddressStringParameters.java index 07a48607..f1df6870 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/MACAddressStringParameters.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/MACAddressStringParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2020 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/NetworkMismatchException.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/NetworkMismatchException.java index 2bcaa4fe..1df54f7d 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/NetworkMismatchException.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/NetworkMismatchException.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2019 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/PrefixBlockAllocator.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/PrefixBlockAllocator.java index f0a6c374..82b447e2 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/PrefixBlockAllocator.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/PrefixBlockAllocator.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Sean C Foley + * Copyright 2022-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,13 +66,10 @@ public IPVersion getVersion() { * which is the total number of individual addresses in all the blocks. */ public BigInteger getTotalCount() { - if(getBlockCount() == 0) { + if(getBlockCount() == 0 || blocks == null) { return BigInteger.ZERO; } BigInteger result = BigInteger.ZERO; - if(blocks == null) { - return result; - } IPVersion version = this.version; for(int i = blocks.length - 1; i >= 0; i--) { ArrayDeque rowBlocks = blocks[i]; diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressComponentRange.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressComponentRange.java index 6f1e0ae7..8ed2f214 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressComponentRange.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressComponentRange.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2020 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressDivisionBase.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressDivisionBase.java index fd3f357a..e59fad52 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressDivisionBase.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressDivisionBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -185,7 +185,7 @@ public int hashCode() { lower = lower.shiftRight(longBits); upper = upper.shiftRight(longBits); res = adjustHashCode(res, low, up); - } while(!upper.equals(BigInteger.ZERO)); + } while(upper.signum() != 0); hashCode = res; } return res; @@ -298,7 +298,18 @@ public byte[] getUpperBytes(byte bytes[]) { */ protected abstract int getMaxDigitCount(); + + // Returns the maximum number of digits required for the given bit-count and radix. + // The supplied maximum value can be null, in which case it will be calculated if needed, + // otherwise it must correspond to the largest unsigned integer corresponding to the given bit-count. + // So this means you should call this method for bit-counts 63 bits and larger, for which maxValue cannot be stored in a long. + // The bit-count and radix are validated. + // Callers must ensure maxValue is either null, or it is non-negative and corresponds to the given bit-count, otherwise the result will be incorrect. protected static int getMaxDigitCount(int radix, int bitCount, BigInteger maxValue) { + int result = getDigitCount(radix, bitCount); // validates both radix and bitCount + if(result > 0) { + return result; + } long key = (((long) radix) << 32) | bitCount; Integer digs = maxDigitMap.get(key); if(digs == null) { @@ -337,38 +348,115 @@ protected static BigInteger getMaxValue(int bitCount) { public static int getDigitCount(BigInteger val, BigInteger radix) { if(radix.compareTo(BIG_MIN_RADIX) < 0 || radix.compareTo(BIG_MAX_RADIX) > 0) { throw new IllegalArgumentException(); - } else if(val.equals(BigInteger.ZERO) || val.equals(BigInteger.ONE)) { + } else if(val.signum() == 0 || val.equals(BigInteger.ONE)) { return 1; } + if(val.signum() > 0) { + int smallRadix = radix.intValue(); + if(smallRadix == 16) { + return getDigitCount(16, val.bitLength()); + } else if(smallRadix == 8) { + // could have used getDigitCount like hex, but chose to avoid the division + int result = 1; + while(true) { + val = val.shiftRight(3); + if(val.signum() == 0) { + break; + } + result++; + } + return result; + } else if(smallRadix == 4) { + return getDigitCount(4, val.bitLength()); + } else if(smallRadix == 2) { + return val.bitLength(); + } + } int result = 1; while(true) { val = val.divide(radix); - if(val.equals(BigInteger.ZERO)) { + if(val.signum() == 0) { break; } result++; } return result; } - + + // Returns the maximum number of digits required for the given bit-count and radix. + // The supplied maximum value must correspond to the largest unsigned integer corresponding to the given bit-count. + // So this means you should avoid calling this method for bit-counts larger than 63. + // The bit-count and radix are validated. + // Callers must ensure maxValue is non-negative and corresponds to the given bit-count, otherwise the result will be incorrect. protected static int getMaxDigitCount(int radix, int bitCount, long maxValue) { - long key = (((long) radix) << 32) | bitCount; - Integer digs = maxDigitMap.get(key); - if(digs == null) { - int digitCount = getDigitCount(bitCount, radix); - if(digitCount < 0) { - digitCount = getDigitCount(maxValue, radix); + int result = getDigitCount(radix, bitCount); // validates both radix and bitCount + if(result > 0) { + return result; + } + if(radix == 10) { + if(maxValue < 10) { + return 1; + } else if(maxValue < 100) { + return 2; + } else if(maxValue < 1000) { + return 3; + } else if(maxValue < 10000) { + return 4; + } else if(maxValue < 100000) { + return 5; + } else if(maxValue < 1000000) { + return 6; + } else if(maxValue < 10000000) { + return 7; + } else if(maxValue < 100000000) { + return 8; + } else if(maxValue < 1000000000) { + return 9; + } else if(maxValue < 10000000000L) { + return 10; + } else if(maxValue < 100000000000L) { + return 11; + } else if(maxValue < 1000000000000L) { + return 12; + } else if(maxValue < 10000000000000L) { + return 13; + } else if(maxValue < 100000000000000L) { + return 14; + } else if(maxValue < 1000000000000000L) { + return 15; + } else if(maxValue < 10000000000000000L) { + return 16; + } else if(maxValue < 100000000000000000L) { + return 17; + } else if(maxValue < 1000000000000000000L) { + return 18; } - digs = AddressDivisionGroupingBase.cacheBits(digitCount); - @SuppressWarnings("unchecked") - TreeMap newMaxDigitMap = (TreeMap) maxDigitMap.clone(); - newMaxDigitMap.put(key, digs); - maxDigitMap = newMaxDigitMap; + return 19; + } else { + long key = (((long) radix) << 32) | bitCount; + Integer digs = maxDigitMap.get(key); + if(digs == null) { + result = 1; + while(true) { + maxValue /= radix; + if(maxValue == 0) { + break; + } + result++; + } + + @SuppressWarnings("unchecked") + TreeMap newMaxDigitMap = (TreeMap) maxDigitMap.clone(); + newMaxDigitMap.put(key, result); + maxDigitMap = newMaxDigitMap; + } else { + result = digs; + } + return result; } - return digs; } - - private static int getDigitCount(int bitCount, int radix) { + + private static int getDigitCount(int radix, int bitCount) { if(bitCount <= 0) { if(bitCount == 0 && radix >= MIN_RADIX && radix <= MAX_RADIX) { return 1; @@ -397,13 +485,7 @@ private static int getDigitCount(int bitCount, int radix) { public static int getDigitCount(long value, int radix) { int result = 1; if(radix == 16 && value >= 0) { - while(true) { - value >>>= 4; - if(value == 0) { - return result; - } - result++; - } + return getDigitCount(16, Long.SIZE - Long.numberOfLeadingZeros(value)); } else if(radix == 10 && value > -10) { if(value < 10) { return 1; @@ -415,6 +497,7 @@ public static int getDigitCount(long value, int radix) { value /= 1000; result = 3; //we start with 3 in the loop below } else if(radix == 8 && value >= 0) { + // could have used getDigitCount like hex, but chose to avoid the division while(true) { value >>>= 3; if(value == 0) { @@ -423,6 +506,8 @@ public static int getDigitCount(long value, int radix) { result++; } return result; + } else if(radix == 4 && value >= 0) { + return getDigitCount(4, Long.SIZE - Long.numberOfLeadingZeros(value)); } else if(radix == 2 && value > 0) { return Long.SIZE - Long.numberOfLeadingZeros(value); } else if(radix < MIN_RADIX || radix > MAX_RADIX) { diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressDivisionGroupingBase.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressDivisionGroupingBase.java index 4a04d280..02ef0d73 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressDivisionGroupingBase.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressDivisionGroupingBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -532,6 +532,11 @@ public boolean isMultiple() { return result; } + @Override + public boolean isSequential() { + return !isMultiple() || AddressDivisionSeries.super.isSequential(); + } + protected static int adjustHashCode(int currentHash, long lowerValue, long upperValue) { return AddressDivisionBase.adjustHashCode(currentHash, lowerValue, upperValue); } @@ -552,7 +557,7 @@ public int hashCode() {//designed so that this hashcode matches the same in Addr lower = lower.shiftRight(longBits); upper = upper.shiftRight(longBits); res = adjustHashCode(res, low, up); - } while(!upper.equals(BigInteger.ZERO)); + } while(upper.signum() != 0); } hashCode = res; } diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressDivisionSeries.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressDivisionSeries.java index 89f72232..0fd71678 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressDivisionSeries.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressDivisionSeries.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2021 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressItem.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressItem.java index 29c6826c..0d0e8c21 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressItem.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressItem.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -107,7 +107,7 @@ default int getByteCount() { * Whether this represents multiple potential values (eg a prefixed address or a segment representing a range of values) */ default boolean isMultiple() { - return !getCount().equals(BigInteger.ONE); + return !getUpperValue().equals(getValue()); } /** @@ -281,7 +281,7 @@ default int getMinPrefixLengthForBlock() { } lower = lower.shiftRight(longBits); upper = upper.shiftRight(longBits); - } while(!upper.equals(BigInteger.ZERO)); + } while(upper.signum() != 0); } return result; } @@ -347,3 +347,4 @@ public static Integer getBitsForCount(long count) { return logBase2; } } + diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressItemRange.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressItemRange.java index 81d76cc1..04d5cd58 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressItemRange.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressItemRange.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2019 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressItemSpliteratorBase.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressItemSpliteratorBase.java index dc4764d1..17c39db9 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressItemSpliteratorBase.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressItemSpliteratorBase.java @@ -1,3 +1,21 @@ +/* + * Copyright 2019 Sean C Foley + * + * Licensed 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * or at + * https://github.com/seancfoley/IPAddress/blob/master/LICENSE + * + * 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 inet.ipaddr.format; import java.math.BigInteger; diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressSegmentSpliterator.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressSegmentSpliterator.java index e3df0f7f..3ff2ea71 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressSegmentSpliterator.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressSegmentSpliterator.java @@ -1,3 +1,21 @@ +/* + * Copyright 2019 Sean C Foley + * + * Licensed 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * or at + * https://github.com/seancfoley/IPAddress/blob/master/LICENSE + * + * 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 inet.ipaddr.format; import java.math.BigInteger; diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressSeriesSpliterator.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressSeriesSpliterator.java index 12df88ca..93243022 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressSeriesSpliterator.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/AddressSeriesSpliterator.java @@ -1,3 +1,21 @@ +/* + * Copyright 2019 Sean C Foley + * + * Licensed 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * or at + * https://github.com/seancfoley/IPAddress/blob/master/LICENSE + * + * 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 inet.ipaddr.format; import java.math.BigInteger; diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/IPAddressGenericDivision.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/IPAddressGenericDivision.java index f6ff7871..6d1259b9 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/IPAddressGenericDivision.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/IPAddressGenericDivision.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Sean C Foley + * Copyright 2018-2020 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/IPAddressRange.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/IPAddressRange.java index 429cd55a..c6f53ef6 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/IPAddressRange.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/IPAddressRange.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package inet.ipaddr.format; +import java.math.BigInteger; import java.util.Iterator; import java.util.stream.Stream; @@ -50,6 +51,42 @@ public interface IPAddressRange extends AddressComponentRange { */ boolean contains(IPAddress other); + /** + * Returns whether this range overlaps the given sequential range + * + * @param other + * @return + */ + boolean overlaps(IPAddressSeqRange other); + + /** + * Returns whether this range overlaps the addresses in the given address or subnet + * + * @param other + * @return + */ + boolean overlaps(IPAddress other); + + /** + * Indicates where an address sits relative to the range ordering. + *

+ * Determines how many address elements of a range precede the given address element, if the address is in the range. + * If above the range, it is the distance to the upper boundary added to the range count less one, and if below the range, the distance to the lower boundary. + *

+ * In other words, if the given address is not in the range but above it, returns the number of addresses preceding the address from the upper range boundary, + * added to one less than the total number of range addresses. If the given address is not in the subnet but below it, returns the number of addresses following the address to the lower subnet boundary. + *

+ * Returns null when the argument is multi-valued. The argument must be an individual address. + *

+ * When this is also an individual address, the returned value is the distance (difference) between the two address values. + *

+ * If the given address does not have the same version or type, then null is returned. + * + * @param other + * @return + */ + BigInteger enumerate(IPAddress other); + /** * Returns the address in the range with the lowest numeric value. * diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/SpliteratorBase.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/SpliteratorBase.java index dfa11bff..f3183d04 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/SpliteratorBase.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/SpliteratorBase.java @@ -1,3 +1,21 @@ +/* + * Copyright 2019 Sean C Foley + * + * Licensed 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * or at + * https://github.com/seancfoley/IPAddress/blob/master/LICENSE + * + * 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 inet.ipaddr.format; import java.util.Iterator; diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/large/IPAddressLargeDivision.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/large/IPAddressLargeDivision.java index e46d52a9..62c14982 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/large/IPAddressLargeDivision.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/large/IPAddressLargeDivision.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -334,7 +334,7 @@ public boolean isMultiple() { @Override public boolean includesZero() { - return getValue().equals(BigInteger.ZERO); + return getValue().signum() == 0; } @Override @@ -420,7 +420,7 @@ private static void toDefaultStringRecursive(BigInteger val, BigInteger radix, b BigInteger highLow[] = val.divideAndRemainder(radixPower); BigInteger high = highLow[0]; BigInteger low = highLow[1]; - if(highest && high.equals(BigInteger.ZERO)) { + if(highest && high.signum() == 0) { // only do low toDefaultStringRecursive(low, radix, uppercase, choppedDigits, halfCount, dig, true, builder); } else { @@ -493,7 +493,7 @@ private static String toDefaultString(BigInteger val, BigInteger radix, boolean } builder.append(dig[remainder.intValue()]); val = quotient; - } while(!val.equals(BigInteger.ZERO)); + } while(val.signum() != 0); if(builder == null) { return ""; } @@ -757,7 +757,7 @@ protected int getRangeDigitCount(int radix) { BigInteger highLow[] = val.divideAndRemainder(bigRadix); BigInteger quotient = highLow[0]; BigInteger remainder = highLow[1]; - if(remainder.equals(BigInteger.ZERO)) { + if(remainder.signum() == 0) { highLow = upperVal.divideAndRemainder(bigRadix); BigInteger upperQuotient = highLow[0]; remainder = highLow[1]; diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressBitsDivision.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressBitsDivision.java index 538db933..ad74c23d 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressBitsDivision.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressBitsDivision.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressCreator.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressCreator.java index 347eece6..4deb8ead 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressCreator.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2019 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressDivision.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressDivision.java index a473cf2e..98e87525 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressDivision.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressDivision.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressDivisionGrouping.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressDivisionGrouping.java index 8b2f8e88..83cbdcb0 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressDivisionGrouping.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/AddressDivisionGrouping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.function.BiFunction; +import java.util.function.BooleanSupplier; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.IntUnaryOperator; @@ -40,14 +41,19 @@ import inet.ipaddr.AddressSegment; import inet.ipaddr.AddressSegmentSeries; import inet.ipaddr.AddressValueException; +import inet.ipaddr.IPAddress; import inet.ipaddr.IPAddressNetwork; import inet.ipaddr.IPAddressSegment; +import inet.ipaddr.IPAddressSeqRange; import inet.ipaddr.IncompatibleAddressException; import inet.ipaddr.NetworkMismatchException; +import inet.ipaddr.SizeMismatchException; import inet.ipaddr.format.AddressDivisionBase; import inet.ipaddr.format.AddressDivisionGroupingBase; import inet.ipaddr.format.string.AddressStringDivisionSeries; import inet.ipaddr.format.validate.ParsedAddressGrouping; +import inet.ipaddr.ipv4.IPv4Address; +import inet.ipaddr.ipv6.IPv6Address; /** * AddressDivisionGrouping objects consist of a series of AddressDivision objects, each division containing one or more segments. @@ -131,6 +137,21 @@ protected static Integer cacheBits(int i) { return ParsedAddressGrouping.cache(i); } + protected static boolean overlaps(R original, R other) { + //check if they are comparable first + int count = original.getSegmentCount(); + if(count != other.getSegmentCount()) { + return false; + } + for(int i = count - 1; i >= 0; i--) { + AddressSegment seg = original.getSegment(i); + if(!seg.overlaps(other.getSegment(i))) { + return false; + } + } + return true; + } + /** * Returns whether the values of this division grouping contain the prefix block for the given prefix length * @@ -1187,20 +1208,22 @@ public void remove() { protected static void checkOverflow( long increment, - long lowerValue, - long upperValue, - long count, - LongSupplier maxValue - ) { + LongSupplier lower, + LongSupplier upper, + LongSupplier counter, + BooleanSupplier isSequential, + LongSupplier maxValue) { if(increment < 0) { - if(lowerValue < -increment) { + if(lower.getAsLong() < -increment) { throw new AddressValueException(increment); } - } else { - if(count > 1) { - increment -= count - 1; + } else if(isSequential.getAsBoolean()) { + if(increment > maxValue.getAsLong() - lower.getAsLong()) { + throw new AddressValueException(increment); } - if(increment > maxValue.getAsLong() - upperValue) { + } else { + long count = counter.getAsLong(); + if(increment >= count && increment - (count - 1) > maxValue.getAsLong() - upper.getAsLong()) { throw new AddressValueException(increment); } } @@ -1209,22 +1232,44 @@ protected static void checkOverflow( protected static void checkOverflow( long increment, BigInteger bigIncrement, - BigInteger lowerValue, - BigInteger upperValue, - BigInteger count, - Supplier maxValue - ) { - boolean isMultiple = count.compareTo(BigInteger.ONE) > 0; - if(increment < 0) { - if(lowerValue.compareTo(bigIncrement.negate()) < 0) { - throw new AddressValueException(increment); + Supplier lower, + Supplier upper, + Supplier counter, + BooleanSupplier isSequential, + Supplier maxValue) { + checkOverflow(increment < 0, bigIncrement, lower, upper, counter, isSequential, maxValue); + } + + protected static void checkOverflow( + BigInteger bigIncrement, + Supplier lower, + Supplier upper, + Supplier counter, + BooleanSupplier isSequential, + Supplier maxValue) { + checkOverflow(bigIncrement.signum() < 0, bigIncrement, lower, upper, counter, isSequential, maxValue); + } + + private static void checkOverflow( + boolean incrementIsNegative, + BigInteger bigIncrement, + Supplier lower, + Supplier upper, + Supplier counter, + BooleanSupplier isSequential, + Supplier maxValue) { + if(incrementIsNegative) { + if(lower.get().compareTo(bigIncrement.negate()) < 0) { + throw new AddressValueException(bigIncrement); + } + } else if(isSequential.getAsBoolean()) { + if(bigIncrement.compareTo(maxValue.get().subtract(lower.get())) > 0) { + throw new AddressValueException(bigIncrement); } } else { - if(isMultiple) { - bigIncrement = bigIncrement.subtract(count.subtract(BigInteger.ONE)); - } - if(bigIncrement.compareTo(maxValue.get().subtract(upperValue)) > 0) { - throw new AddressValueException(increment); + BigInteger count = counter.get(); + if(bigIncrement.compareTo(count) >= 0 && bigIncrement.subtract(count.subtract(BigInteger.ONE)).compareTo(maxValue.get().subtract(upper.get())) > 0) { + throw new AddressValueException(bigIncrement); } } } @@ -1257,16 +1302,15 @@ protected static R fastIncr } return incrementRange(section, increment, addrCreator, lowerProducer, prefixLength); } - BigInteger value = section.getValue(); - BigInteger upperValue; - if(value.compareTo(LONG_MAX) <= 0 && (upperValue = section.getUpperValue()).compareTo(LONG_MAX) <= 0) { + BigInteger upperValue = section.getUpperValue(); + if(upperValue.compareTo(LONG_MAX) <= 0) { return increment( section, increment, addrCreator, - count.longValue(), - value.longValue(), - upperValue.longValue(), + count::longValue, + () -> section.getValue().longValue(), + upperValue::longValue, lowerProducer, upperProducer, prefixLength); @@ -1286,26 +1330,28 @@ protected static R incremen R section, long increment, AddressCreator addrCreator, - long count, - long lowerValue, - long upperValue, + LongSupplier counter, + LongSupplier lower, + LongSupplier upper, Supplier lowerProducer, Supplier upperProducer, Integer prefixLength) { if(!section.isMultiple()) { - return add(section, lowerValue, increment, addrCreator, prefixLength); + return add(section, lower.getAsLong(), increment, addrCreator, prefixLength); } boolean isDecrement = increment <= 0; if(isDecrement) { //we know lowerValue + increment >= 0 because we already did an overflow check - return add(lowerProducer.get(), lowerValue, increment, addrCreator, prefixLength); + return add(lowerProducer.get(), lower.getAsLong(), increment, addrCreator, prefixLength); } + long count = counter.getAsLong(); if(count > increment) { if(count == increment + 1) { return upperProducer.get(); } return incrementRange(section, increment, addrCreator, lowerProducer, prefixLength); } + long upperValue = upper.getAsLong(); if(increment <= Long.MAX_VALUE - upperValue) { return add(upperProducer.get(), upperValue, increment - (count - 1), addrCreator, prefixLength); } @@ -1340,6 +1386,33 @@ protected static R incremen return incrementRange(section, increment, addrCreator, lowerProducer, prefixLength); } + //this does not handle overflow, overflow should be checked before calling this + protected static R increment( + R section, + BigInteger bigIncrement, + AddressCreator addrCreator, + Supplier lowerProducer, + Supplier upperProducer, + Integer prefixLength) { + if(!section.isMultiple()) { + return add(section, bigIncrement, addrCreator, prefixLength); + } + boolean isDecrement = bigIncrement.signum() <= 0; + if(isDecrement) { + return add(lowerProducer.get(), bigIncrement, addrCreator, prefixLength); + } + BigInteger count = section.getCount(); + BigInteger incrementPlus1 = bigIncrement.add(BigInteger.ONE); + int countCompare = count.compareTo(incrementPlus1); + if(countCompare <= 0) { + if(countCompare == 0) { + return upperProducer.get(); + } + return add(upperProducer.get(), incrementPlus1.subtract(count), addrCreator, prefixLength); + } + return incrementRange(section, bigIncrement, addrCreator, lowerProducer, prefixLength); + } + /** * * @param section @@ -1352,6 +1425,7 @@ protected static R incremen * @param prefixLength * @return */ + @SuppressWarnings("unchecked") protected static R incrementRange( R section, long increment, @@ -1362,22 +1436,82 @@ protected static R incremen return lowerProducer.get(); } int segCount = section.getSegmentCount(); + S newSegments[]; + if(segCount > 0) { + int i = segCount - 1; + AddressSegment seg = section.getSegment(i); + int bitCount = seg.getBitCount(); + newSegments = addrCreator.createSegmentArray(segCount); + while(true) { + int segValue = seg.getSegmentValue(); + long revolutions; + int remainder; + S newSegment; + long segRange = seg.getValueCount(); + if(bitCount == IPv6Address.BITS_PER_SEGMENT && segRange == IPv6Address.MAX_VALUE_PER_SEGMENT + 1) { + revolutions = increment >>> IPv6Address.BITS_PER_SEGMENT; + remainder = (int) (increment & IPv6Address.MAX_VALUE_PER_SEGMENT); + newSegment = addrCreator.createSegment(remainder); + } else if(bitCount == IPv4Address.BITS_PER_SEGMENT && segRange == IPv4Address.MAX_VALUE_PER_SEGMENT + 1) { + revolutions = increment >>> IPv4Address.BITS_PER_SEGMENT; + remainder = (int) (increment & IPv4Address.MAX_VALUE_PER_SEGMENT); + newSegment = addrCreator.createSegment(remainder); + } else if(segRange == 1) { + revolutions = increment; + newSegment = addrCreator.createSegment(segValue); + } else { + revolutions = increment / segRange; + remainder = (int) (increment % segRange); + newSegment = addrCreator.createSegment(segValue + remainder); + } + newSegments[i] = newSegment; + if(revolutions == 0) { + for(i--; i >= 0; i--) { + AddressSegment original = section.getSegment(i); + newSegments[i] = addrCreator.createSegment(original.getSegmentValue()); + } + break; + } + if(--i < 0) { + break; + } + increment = revolutions; + seg = section.getSegment(i); + } + } else { + newSegments = (S[]) section.getSegments(); + } + return createIteratedSection(newSegments, addrCreator, prefixLength); + } + + private static R incrementRange( + R section, + BigInteger bigIncrement, + AddressCreator addrCreator, + Supplier lowerProducer, + Integer prefixLength) { + if(bigIncrement.signum() == 0) { + return lowerProducer.get(); + } + int segCount = section.getSegmentCount(); S newSegments[] = addrCreator.createSegmentArray(segCount); for(int i = segCount - 1; i >= 0; i--) { AddressSegment seg = section.getSegment(i); - long segRange = (seg.getUpperSegmentValue() - seg.getSegmentValue()) + 1L; - long revolutions = increment / segRange; - int remainder = (int) (increment % segRange); - S newSegment = addrCreator.createSegment(seg.getSegmentValue() + remainder); + int segValue = seg.getSegmentValue(); + long segRange = (seg.getUpperSegmentValue() - segValue) + 1L; + BigInteger divs[] = bigIncrement.divideAndRemainder(BigInteger.valueOf(segRange)); + BigInteger revolutions = divs[0]; + int remainder = divs[1].intValue(); + S newSegment = addrCreator.createSegment(segValue + remainder); newSegments[i] = newSegment; - if(revolutions == 0) { + if(revolutions.signum() == 0) { for(i--; i >= 0; i--) { AddressSegment original = section.getSegment(i); newSegments[i] = addrCreator.createSegment(original.getSegmentValue()); } break; } else { - increment = revolutions; + bigIncrement = revolutions; } } return createIteratedSection(newSegments, addrCreator, prefixLength); @@ -1413,6 +1547,124 @@ protected static R add( return createIteratedSection(newSegs, addrCreator, prefixLength); } + + // Use this for IPv4 and also for when IPv6 has the first 5 segments with no diff (seg equal to getLower), + // and if MAC is 6 segments or the first segment of 8 has no diff. + // Callers should have checked for matching segment counts. + protected static Long enumerateSmall(AddressSegmentSeries series, AddressSegmentSeries otherSeries) { + if(otherSeries.isMultiple()) { + return null; + } else if(otherSeries == series) { // both the same individual address + return 0L; + } + return enumerateSmallImpl(series, otherSeries); + } + + protected static Long enumerateSmallImpl(AddressSegmentSeries series, AddressSegmentSeries otherSeries) { + if(series.isMultiple()) { + if(!series.isSequential()) { + if(series.getUpper().compareTo(otherSeries) < 0) { + return (otherSeries.getValue().longValue() - series.getUpperValue().longValue()) + (series.getCount().longValue() - 1L); + } else if(series.getLower().compareTo(otherSeries) <= 0) { + long total = 0; + long cumulativeSize = 1; + for(int i = series.getSegmentCount() - 1; ; i--) { + AddressSegment segment = series.getSegment(i), otherSegment = otherSeries.getSegment(i); + int otherValue = otherSegment.getSegmentValue(); + int segValue = segment.getSegmentValue(); + if(otherValue < segValue || otherValue > segment.getUpperSegmentValue()) { + return null; + } + total += cumulativeSize * (otherValue - segValue); + if(i == 0) { + return total; + } + cumulativeSize *= segment.getValueCount(); + } + } + } + } + return otherSeries.getValue().longValue() - series.getValue().longValue(); + } + + protected static BigInteger enumerateBig(AddressSegmentSeries series, AddressSegmentSeries otherSeries) { + int segmentCount = series.getSegmentCount(); + if(segmentCount != otherSeries.getSegmentCount()) { + throw new SizeMismatchException(series, otherSeries); + } else if(otherSeries.isMultiple()) { + return null; + } else if(otherSeries == series) { // both the same individual address + return BigInteger.ZERO; + } + + // If the initial segments beyond 63 bits match, which is probably the case for most subnets, + // then we can just use long values to calculate + boolean initialSegsMatch = true; + int bitsPerSegment = series.getBitsPerSegment(); + int totalBits = ParsedAddressGrouping.getTotalBits(segmentCount, series.getBytesPerSegment(), bitsPerSegment); + int i = 0; + while(totalBits > Long.SIZE) { + AddressSegment seg = series.getSegment(i); + AddressSegment otherSeg = otherSeries.getSegment(i); + if(!seg.matches(otherSeg.getSegmentValue())) { + initialSegsMatch = false; + break; + } + totalBits -= bitsPerSegment; + i++; + } + if(initialSegsMatch) { + // if there are exactly 64 bits left, we also need the top bit to match too, the sign bit + if(totalBits == Long.SIZE) { + AddressSegment seg = series.getSegment(i); + AddressSegment otherSeg = otherSeries.getSegment(i); + int mask = (1 << bitsPerSegment) >>> 1; + if(seg.matchesWithMask(otherSeg.getSegmentValue() & mask, mask)) { + // we can use longs to calculate + Long result = enumerateSmallImpl(series, otherSeries); + if(result == null) { + return null; + } + return BigInteger.valueOf(result); + } + } else { + // we can use longs to calculate + Long result = enumerateSmallImpl(series, otherSeries); + if(result == null) { + return null; + } + return BigInteger.valueOf(result); + } + } + + // we use BigIntegers to calculate + + if(series.isMultiple()) { + if(!series.isSequential()) { + if(series.getUpper().compareTo(otherSeries) < 0) { + return otherSeries.getValue().subtract(series.getUpperValue()).add(series.getCount().subtract(BigInteger.ONE)); + } else if(series.getLower().compareTo(otherSeries) <= 0) { + BigInteger total = BigInteger.ZERO; + BigInteger cumulativeSize = BigInteger.ONE; + for(int j = series.getSegmentCount() - 1; ; j--) { + AddressSegment segment = series.getSegment(j), otherSegment = otherSeries.getSegment(j); + int otherValue = otherSegment.getSegmentValue(); + int segValue = segment.getSegmentValue(); + if(otherValue < segValue || otherValue > segment.getUpperSegmentValue()) { + return null; + } + total = total.add(cumulativeSize.multiply(BigInteger.valueOf(otherValue - segValue))); + if(j == 0) { + return total; + } + cumulativeSize = cumulativeSize.multiply(segment.getCount()); + } + } + } + } + return otherSeries.getValue().subtract(series.getValue()); + } + protected static BigInteger count(IntUnaryOperator segmentValueCountProvider, int segCount, int safeMultiplies, long safeLimit) { int i = 0; BigInteger result = BigInteger.ONE; @@ -1580,6 +1832,12 @@ protected boolean isDualString() throws IncompatibleAddressException { return builder.toString(); } + protected void checkSegmentCount(AddressSection sec) throws SizeMismatchException { + if(sec.getDivisionCount() != getDivisionCount()) { + throw new SizeMismatchException(this, sec); + } + } + /** * Represents a clear way to create a specific type of string. * diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressBitsDivision.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressBitsDivision.java index 12af5640..4b0d7bcf 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressBitsDivision.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressBitsDivision.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressDivision.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressDivision.java index 36b4f8c5..d2317b0d 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressDivision.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressDivision.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,9 +25,9 @@ /** * A division of an IP address. - * - * May be associated with a prefix length, in which case that number of bits in the upper-most - * portion of the object represent a prefix, while the remaining bits can assume all possible values. + *

+ * May be associated with a prefix length, in which case that number of bits + * in the upper-most portion of the object represent a prefix. * * @author sfoley * diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressDivisionGrouping.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressDivisionGrouping.java index 3f9f089a..a74bf9ea 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressDivisionGrouping.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressDivisionGrouping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2020 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressJoinedSegments.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressJoinedSegments.java index 129a0189..685dfa95 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressJoinedSegments.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/standard/IPAddressJoinedSegments.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AbstractTree.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AbstractTree.java index b8c037e5..39e33aa8 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AbstractTree.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AbstractTree.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -152,18 +152,17 @@ public int hashCode() { return hashCode; } - @SuppressWarnings("unchecked") @Override public boolean equals(Object o) { if (o == this) { return true; } if(o instanceof AbstractTree) { - AbstractTree other = (AbstractTree) o; + AbstractTree other = (AbstractTree) o; if(other.size() != size()) { return false; } - Iterator> these = nodeIterator(true), + Iterator> these = nodeIterator(true), others = other.nodeIterator(true); while(these.hasNext()) { BinaryTreeNode node = these.next(), otherNode = others.next(); diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddedTree.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddedTree.java index 2971c758..c99cc26f 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddedTree.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddedTree.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Sean C Foley + * Copyright 2022-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddedTreeBase.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddedTreeBase.java index be15f7c8..83a429db 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddedTreeBase.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddedTreeBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Sean C Foley + * Copyright 2022-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressComponentRangeSpliterator.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressComponentRangeSpliterator.java index 2f67a9d1..d277dc47 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressComponentRangeSpliterator.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressComponentRangeSpliterator.java @@ -1,3 +1,21 @@ +/* + * Copyright 2019 Sean C Foley + * + * Licensed 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * or at + * https://github.com/seancfoley/IPAddress/blob/master/LICENSE + * + * 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 inet.ipaddr.format.util; import java.math.BigInteger; diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressComponentSpliterator.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressComponentSpliterator.java index ed794b96..7fbf98f1 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressComponentSpliterator.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressComponentSpliterator.java @@ -1,3 +1,21 @@ +/* + * Copyright 2019 Sean C Foley + * + * Licensed 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * or at + * https://github.com/seancfoley/IPAddress/blob/master/LICENSE + * + * 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 inet.ipaddr.format.util; import inet.ipaddr.format.AddressComponentRange; diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrie.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrie.java index 94f992bb..a5c5da20 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrie.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrie.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package inet.ipaddr.format.util; import java.io.Serializable; @@ -24,7 +25,6 @@ import java.util.Comparator; import java.util.Deque; import java.util.Iterator; -import java.util.Objects; import java.util.Spliterator; import java.util.function.Function; @@ -33,6 +33,8 @@ import inet.ipaddr.AddressSegmentSeries; import inet.ipaddr.IPAddress; import inet.ipaddr.IPAddressSegment; +import inet.ipaddr.format.util.AddressTrie.TrieNode.FollowingBits; +import inet.ipaddr.format.util.AddressTrie.TrieNode.KeyCompareResult; import inet.ipaddr.format.util.AssociativeAddressTrie.AssociativeTrieNode; import inet.ipaddr.format.util.BinaryTreeNode.BlockSizeNodeIterator; import inet.ipaddr.format.util.BinaryTreeNode.Bounds; @@ -45,6 +47,7 @@ import inet.ipaddr.format.util.BinaryTreeNode.NodeSpliterator; import inet.ipaddr.format.util.BinaryTreeNode.PostOrderNodeIterator; import inet.ipaddr.format.util.BinaryTreeNode.PreOrderNodeIterator; +import inet.ipaddr.format.validate.ParsedAddressGrouping; import inet.ipaddr.ipv4.IPv4Address; import inet.ipaddr.ipv6.IPv6Address; @@ -224,7 +227,6 @@ AddressBounds intersect(E lowerBound, boolean lowerInclusive, E upperBound, b boolean isAdjacentAboveUpperBound(E addr) { E res = oneAboveUpperBound; if(res == null) { - //res = (E) upperBound.increment(1); res = increment(upperBound); oneAboveUpperBound = res; } @@ -236,31 +238,29 @@ boolean isAdjacentAboveUpperBound(E addr) { boolean isAdjacentBelowLowerBound(E addr) { E res = oneBelowLowerBound; if(res == null) { - //res = (E) lowerBound.increment(-1); res = decrement(lowerBound); oneBelowLowerBound = res; } return res != null && res.equals(addr); } + // matches the value just below the upper bound (only applies to discrete quantities) @Override boolean isAdjacentBelowUpperBound(E addr) { E res = oneBelowUpperBound; if(res == null) { res = decrement(upperBound); - //res = (E) upperBound.increment(-1); oneBelowUpperBound = res; } return res != null && res.equals(addr); } - // matches the value just below the lower bound (only applies to discrete quantities) + // matches the value just above the lower bound (only applies to discrete quantities) @Override boolean isAdjacentAboveLowerBound(E addr) { E res = oneAboveLowerBound; if(res == null) { res = increment(lowerBound); - //res = (E) lowerBound.increment(1); oneAboveLowerBound = res; } return res != null && res.equals(addr); @@ -291,24 +291,30 @@ protected static enum Operation { NEAR, // closest match, going down trie to get element considered closest. // Whether one thing is closer than another is determined by the sorted order. // For example, for subnet 1.2.0.0/16, 1.2.128.0 is closest address on the high side, 1.2.127.255 is closest address on the low side - CONTAINING, // list the nodes whose keys contain E + CONTAINING, // find a single node whose key contains E + ALL_CONTAINING, // list the nodes whose keys contain E INSERTED_DELETE, // remove node for E - SUBNET_DELETE // remove nodes whose keys are contained by E + SUBTREE_DELETE // remove nodes whose keys are contained by E } // not optimized for size, since only temporary, to be used for a single operation - protected static class OpResult { + protected static class OpResult implements KeyCompareResult, FollowingBits, Serializable { + + private static final long serialVersionUID = 1L; + E addr; // whether near is searching for a floor or ceiling // a floor is greatest element below addr // a ceiling is lowest element above addr - final boolean nearestFloor; + boolean nearestFloor; // whether near cannot be an exact match - final boolean nearExclusive; + boolean nearExclusive; + + Operation op; - final Operation op; + OpResult() {} OpResult(E addr, Operation op) { this(addr, op, false, false); @@ -325,6 +331,46 @@ private OpResult(E addr, Operation op, boolean floor, boolean exclusive) { this.nearExclusive = exclusive; } + // do not use with Operation.NEAR, INSERT, REMAP, INSERTED_DELETE, SUBTREE_DELETE + OpResult reset(E addr, Operation op) { + this.addr = addr; + this.op = op; + return this; + } + + OpResult resetNear(E addr, boolean floor, boolean exclusive) { + this.nearestFloor = floor; + this.nearExclusive = exclusive; + return reset(addr, Operation.NEAR); + } + + // Do not use with Operation.NEAR, INSERT, REMAP, INSERTED_DELETE, SUBTREE_DELETE, + // We'd need to do more cleaning if we did. + void clean() { + addr = null; + op = null; + + // contains and lookups + exists = false; + existingNode = containing = containingEnd = + smallestContaining = largestContaining = + containedBy = null; + + // near + nearestFloor = nearExclusive = false; + nearestNode = backtrackNode = null; + + // deletions + deleted = null; + + // adds and puts + newValue = existingValue = null; + inserted = added = addedAlready = null; + + // remaps + remapper = null; + } + // lookups: // an inserted tree element matches the supplied argument @@ -349,8 +395,9 @@ private OpResult(E addr, Operation op, boolean floor, boolean exclusive) { // that contain the supplied argument, and the end of the list TrieNode containing, containingEnd; - // The tree node with the smallest subnet or address containing the supplied argument - TrieNode smallestContaining; + // Of the tree nodes with elements containing the subnet or address, + // those with the smallest or largest subnet or address + TrieNode smallestContaining, largestContaining; // contained by: @@ -436,6 +483,70 @@ void addContaining(TrieNode containingSub) { } containingEnd = cloned; } + + // + // + // + // for searching + + long followingBits; + + @Override + public void setFollowingBits(long bits) { + followingBits = bits; + } + + TrieNode node; + + @Override + public void bitsMatch() { + E existingAddr = node.getKey(); + Integer existingPref = existingAddr.getPrefixLength(); + Integer newPrefixLen = addr.getPrefixLength(); + containedBy = node; + if(existingPref == null) { + if(newPrefixLen == null) { + // note that "added" is already true here, + // we can only be here if explicitly inserted already + // since it is a non-prefixed full address + node.handleMatch(this); + } else if(newPrefixLen == existingAddr.getBitCount()) { + node.handleMatch(this); + } else { + node.handleContained(this, newPrefixLen); + } + } else { + // we know newPrefixLen != null since we know all of the bits of newAddr match, + // which is impossible if newPrefixLen is null and existingPref not null + if(newPrefixLen.intValue() == existingPref.intValue()) { + if(node.isAdded()) { + node.handleMatch(this); + } else { + node.handleNodeMatch(this); + } + } else if(existingPref == existingAddr.getBitCount()) { + node.handleMatch(this); + } else { // existing prefix > newPrefixLen + node.handleContained(this, newPrefixLen); + } + } + } + + @Override + public void bitsDoNotMatch(int matchedBits) { + node.handleSplitNode(this, matchedBits); + } + + @Override + public FollowingBits bitsMatchPartially() { + if(node.isAdded()) { + node.handleContains(this); + if(op == Operation.CONTAINING) { + return null; + } + } + return this; + } } /** @@ -480,32 +591,31 @@ public int compare(E o1, E o2) { } int segmentCount = o1.getSegmentCount(); int bitsPerSegment = o1.getBitsPerSegment(); + Integer o1Pref = o1.getPrefixLength(); + Integer o2Pref = o2.getPrefixLength(); int bitsMatchedSoFar = 0; - int extraBits = Integer.SIZE - bitsPerSegment; int i = 0; while(true) { AddressSegment segment1 = o1.getSegment(i); AddressSegment segment2 = o2.getSegment(i); - Integer pref1 = getSegmentPrefLen(o1, bitsMatchedSoFar, segment1); - Integer pref2 = getSegmentPrefLen(o2, bitsMatchedSoFar, segment2); + Integer pref1 = getSegmentPrefLen(o1, o1Pref, bitsPerSegment, bitsMatchedSoFar, segment1); + Integer pref2 = getSegmentPrefLen(o2, o2Pref, bitsPerSegment, bitsMatchedSoFar, segment2); int segmentPref2; if(pref1 != null) { int segmentPref1 = pref1; if(pref2 != null && (segmentPref2 = pref2) <= segmentPref1) { - int matchingBits = getMatchingBits(segment1, segment2, segmentPref2, extraBits); + int matchingBits = getMatchingBits(segment1, segment2, segmentPref2, bitsPerSegment); if(matchingBits >= segmentPref2) { if(segmentPref2 == segmentPref1) { // same prefix block return 0; - } else { - // segmentPref2 is shorter prefix, prefix bits match, so depends on bit at index segmentPref2 - return segment1.isOneBit(segmentPref2) ? 1 : -1; } - } else { - return segment1.getSegmentValue() - segment2.getSegmentValue(); + // segmentPref2 is shorter prefix, prefix bits match, so depends on bit at index segmentPref2 + return segment1.isOneBit(segmentPref2) ? 1 : -1; } + return segment1.getSegmentValue() - segment2.getSegmentValue(); } else { - int matchingBits = getMatchingBits(segment1, segment2, segmentPref1, extraBits); + int matchingBits = getMatchingBits(segment1, segment2, segmentPref1, bitsPerSegment); if(matchingBits >= segmentPref1) { if(segmentPref1 < bitsPerSegment) { return segment2.isOneBit(segmentPref1) ? -1 : 1; @@ -518,7 +628,7 @@ public int compare(E o1, E o2) { } } else if(pref2 != null) { segmentPref2 = pref2; - int matchingBits = getMatchingBits(segment1, segment2, segmentPref2, extraBits); + int matchingBits = getMatchingBits(segment1, segment2, segmentPref2, bitsPerSegment); if(matchingBits >= pref2) { if(segmentPref2 < bitsPerSegment) { return segment1.isOneBit(segmentPref2) ? 1 : -1; @@ -529,7 +639,7 @@ public int compare(E o1, E o2) { return segment1.getSegmentValue() - segment2.getSegmentValue(); } } else { - int matchingBits = getMatchingBits(segment1, segment2, bitsPerSegment, extraBits); + int matchingBits = getMatchingBits(segment1, segment2, bitsPerSegment, bitsPerSegment); if(matchingBits < bitsPerSegment) { // no match - the current subnet/address is not here return segment1.getSegmentValue() - segment2.getSegmentValue(); } else if(++i == segmentCount) { @@ -752,6 +862,11 @@ TrieNode lowerNodeNoCheck(E addr) { return findNodeNearNoCheck(addr, true, true); } + @Override + public E lower(E addr) { + return getNodeKey(lowerAddedNode(addr)); + } + @Override public TrieNode floorAddedNode(E addr) { return findNodeNear(addr, true, false); @@ -761,6 +876,11 @@ TrieNode floorNodeNoCheck(E addr) { return findNodeNearNoCheck(addr, true, false); } + @Override + public E floor(E addr) { + return getNodeKey(floorAddedNode(addr)); + } + @Override public TrieNode higherAddedNode(E addr) { return findNodeNear(addr, false, true); @@ -770,6 +890,11 @@ TrieNode higherNodeNoCheck(E addr) { return findNodeNearNoCheck(addr, false, true); } + @Override + public E higher(E addr) { + return getNodeKey(higherAddedNode(addr)); + } + @Override public TrieNode ceilingAddedNode(E addr) { return findNodeNear(addr, false, false); @@ -779,6 +904,11 @@ TrieNode ceilingNodeNoCheck(E addr) { return findNodeNearNoCheck(addr, false, false); } + @Override + public E ceiling(E addr) { + return getNodeKey(ceilingAddedNode(addr)); + } + @SuppressWarnings("unchecked") @Override public Iterator> nodeIterator(boolean forward) { @@ -833,8 +963,8 @@ public CachingIterator, E, C> blockSizeCachingAllNodeI @SuppressWarnings("unchecked") @Override - public CachingIterator, E, C> containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator, E, C>) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator> containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator>) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") @@ -892,8 +1022,7 @@ public Spliterator descendingSpliterator() { @Override public boolean contains(E addr) { - OpResult result = doLookup(addr); - return result.exists; + return doLookup(addr).exists; } @Override @@ -906,30 +1035,27 @@ public boolean remove(E addr) { @Override public TrieNode getNode(E addr) { - OpResult result = doLookup(addr); - TrieNode ret = result.existingNode; - return ret; + return doLookup(addr).existingNode; } @Override public TrieNode removeElementsContainedBy(E addr) { addr = checkBlockOrAddress(addr, true); - OpResult result = new OpResult<>(addr, Operation.SUBNET_DELETE); + OpResult result = new OpResult<>(addr, Operation.SUBTREE_DELETE); matchBits(result); return result.deleted; } @Override public TrieNode elementsContainedBy(E addr) { - OpResult result = doLookup(addr); - return result.containedBy; + return doLookup(addr).containedBy; } // only added nodes are added to the linked list @Override public TrieNode elementsContaining(E addr) { addr = checkBlockOrAddress(addr, true); - OpResult result = new OpResult<>(addr, Operation.CONTAINING); + OpResult result = new OpResult<>(addr, Operation.ALL_CONTAINING); matchBits(result); return result.getContaining(); } @@ -940,177 +1066,470 @@ public E longestPrefixMatch(E addr) { return node == null ? null : node.getKey(); } - // only added nodes are added to the linked list @Override public TrieNode longestPrefixMatchNode(E addr) { return doLookup(addr).smallestContaining; } + @Override + public E shortestPrefixMatch(E addr) { + TrieNode node = shortestPrefixMatchNode(addr); + return node == null ? null : node.getKey(); + } + + @Override + public TrieNode shortestPrefixMatchNode(E addr) { + return doElementContains(addr); + } + @Override public boolean elementContains(E addr) { - return longestPrefixMatch(addr) != null; + return doElementContains(addr) != null; + } + + private TrieNode doElementContains(E addr) { + addr = checkBlockOrAddress(addr, true); + OpResult result = new OpResult<>(addr, Operation.CONTAINING); + matchBits(result); + return result.largestContaining; } - private OpResult doLookup(E addr) { + protected OpResult doLookup(E addr) { addr = checkBlockOrAddress(addr, true); OpResult result = new OpResult<>(addr, Operation.LOOKUP); matchBits(result); return result; } - private void removeSubnet(OpResult result) { + private void removeSubtree(OpResult result) { result.deleted = this; clear(); } - protected void remove(OpResult result) { + protected void removeOp(OpResult result) { result.deleted = this; remove(); } void matchBits(OpResult result) { - matchBits(0, result); - } - - void matchBits(int bitIndex, OpResult result) { - matchBits(this, bitIndex, result); + matchBitsFromIndex(0, result); } // traverses the tree, matching bits with prefix block nodes, until we can match no longer, // at which point it completes the operation, whatever that operation is - static void matchBits(TrieNode node, int bitIndex, OpResult result) { + void matchBitsFromIndex(int bitIndex, OpResult result) { + TrieNode matchNode = this; + + E newAddr = result.addr; + Operation op = result.op; + + TrieKeyData newKeyData = getTrieKeyCache(newAddr); + boolean simpleMatch = newKeyData != null && op != Operation.INSERT && op != Operation.NEAR && op != Operation.REMAP; + + E existingAddr = getKey(); + while(true) { - int bits = node.matchNodeBits(bitIndex, result); - if(bits >= 0) { + result.node = matchNode; + boolean continueToNext = matchAddressBits(simpleMatch, newAddr, existingAddr, bitIndex, result, newKeyData); + if(continueToNext) { + int bits = existingAddr.getPrefixLength(); + // matched all node bits up the given count, so move into sub-nodes - node = node.matchSubNode(bits, result); - if(node == null) { + matchNode = matchNode.matchSubNode(bits, result); + if(matchNode == null) { // reached the end of the line break; } // Matched a sub-node. - // The sub-node was chosen according to that next bit. + // The sub-node was chosen according to the next bit. // That bit is therefore now a match, // so increment the matched bits by 1, and keep going. bitIndex = bits + 1; + existingAddr = matchNode.getKey(); } else { - // reached the end of the line break; } } + result.node = null; } - int matchNodeBits(int bitIndex, OpResult result) { - E newAddr = result.addr; - Operation op = result.op; - AddressSegmentSeries existingAddr = getKey(); + static interface FollowingBits { + void setFollowingBits(long bits); + } + + static interface KeyCompareResult { + void bitsMatch(); + + void bitsDoNotMatch(int matchedBits); + + // When this is called, if the returned value is non-null, + // then setFollowingBits must be called on the returned instance with either zero or a non-zero value + // to indicate if the next bit following the prefix length of the node's address is 0 or 1 in the supplied address. + FollowingBits bitsMatchPartially(); + } + + // Providing TrieKeyData for trie keys makes lookup faster. + // However, it is optional, tries will work without it. + protected static class TrieKeyData { + // currently trie optimizations exist for 32 or 128 bits, + // so providing TrieKeyData for other bit sizes provides no benefit at this time + + public Integer prefixLength; + + // 32 bit key caches must override these 4 methods: + public boolean is32Bits() { + return false; + } + + public int getUint32Val() { + return 0; + } + + public int getMask32Val() { + return 0; + } + + public int getNextBitMask32Val() { + return 0; + } + + // 128 bit key caches must override these 6 methods: + public boolean is128Bits() { + return false; + } + + public long getUint64LowVal() { + return 0; + } + + public long getUint64HighVal() { + return 0; + } + + public long getMask64HighVal() { + return 0; + } + + public long getMask64LowVal() { + return 0; + } + + public long getNextBitMask64Val() { + return 0; + } + } + + protected TrieKeyData getTrieKeyCache(E addr) { + return null; + } + + boolean matchAddressBits(boolean simpleSearch, E newAddr, E existingAddr, int bitIndex, TrieNode.KeyCompareResult handleMatch, TrieKeyData newTrieCache) { + + // this is the optimized path for the case where we do not need to know how many of the initial bits match in a mismatch + // when we have a match, all bits match + // when we have a mismatch, we do not need to know how many of the initial bits match + // So there is no callback for a mismatch here. + + // The non-optimized code has 8 cases, 2 for each fully nested if or else block + // I have added comments to see how this code matches up to those 8 cases + + if(simpleSearch) { + TrieKeyData existingTrieCache = getTrieKeyCache(existingAddr); + if(existingTrieCache != null) { + if(existingTrieCache.is32Bits()) { + if(newTrieCache.is32Bits()) { + int existingVal = existingTrieCache.getUint32Val(); + Integer existingPrefLen = existingTrieCache.prefixLength; + if(existingPrefLen == null) { + int newVal = newTrieCache.getUint32Val(); + if(newVal == existingVal) { + handleMatch.bitsMatch(); + } else { + Integer newPrefLen = newTrieCache.prefixLength; + if(newPrefLen != null) { + int newMask = newTrieCache.getMask32Val(); + if((newVal & newMask) == (existingVal & newMask)) { + // rest of case 1 and rest of case 5 + handleMatch.bitsMatch(); + } + } + } + } else { + int existingPrefLenBits = existingPrefLen; + Integer newPrefLen = newTrieCache.prefixLength; + if(existingPrefLenBits == 0) { + if(newPrefLen != null && newPrefLen == 0) { + handleMatch.bitsMatch(); + } else { + FollowingBits followingBits = handleMatch.bitsMatchPartially(); + if(followingBits != null) { + followingBits.setFollowingBits(newTrieCache.getUint32Val() & 0x80000000); + return true; + } + } + } else if(existingPrefLenBits == bitIndex) { // optimized case where no matching is required because bit index had advanced by just one + if(newPrefLen != null && existingPrefLenBits >= newPrefLen) { + handleMatch.bitsMatch(); + } else { + FollowingBits followingBits = handleMatch.bitsMatchPartially(); + if(followingBits != null) { + int nextBitMask = existingTrieCache.getNextBitMask32Val(); + followingBits.setFollowingBits(newTrieCache.getUint32Val() & nextBitMask); + return true; + } + } + } else { + int existingMask = existingTrieCache.getMask32Val(); + int newVal = newTrieCache.getUint32Val(); + if((newVal & existingMask) == (existingVal & existingMask)) { + if(newPrefLen != null && existingPrefLenBits >= newPrefLen) { + handleMatch.bitsMatch(); + } else { + FollowingBits followingBits = handleMatch.bitsMatchPartially(); + if(followingBits != null) { + int nextBitMask = existingTrieCache.getNextBitMask32Val(); + followingBits.setFollowingBits(newVal & nextBitMask); + return true; + } + } + } else if(newPrefLen != null) { + int newPrefLenBits = newPrefLen; + if(existingPrefLenBits > newPrefLenBits) { + int newMask = newTrieCache.getMask32Val(); + if((newTrieCache.getUint32Val() & newMask) == (existingVal & newMask)) { + // rest of case 1 and rest of case 5 + handleMatch.bitsMatch(); + } + } + } // else case 4, 7 + } + } + return false; + } + } else if(existingTrieCache.is128Bits()) { + if(newTrieCache != null && newTrieCache.is128Bits()) { + Integer existingPrefLen = existingTrieCache.prefixLength; + if(existingPrefLen == null) { + long newLowVal = newTrieCache.getUint64LowVal(); + long existingLowVal = existingTrieCache.getUint64LowVal(); + if(newLowVal == existingLowVal && + newTrieCache.getUint64HighVal() == existingTrieCache.getUint64HighVal()) { + handleMatch.bitsMatch(); + } else { + Integer newPrefLen = newTrieCache.prefixLength; + if(newPrefLen != null) { + long newMaskLow = newTrieCache.getMask64LowVal(); + if((newLowVal & newMaskLow) == (existingLowVal & newMaskLow)) { + long newMaskHigh = newTrieCache.getMask64HighVal(); + if((newTrieCache.getUint64HighVal() & newMaskHigh) == (existingTrieCache.getUint64HighVal() & newMaskHigh)) { + // rest of case 1 and rest of case 5 + handleMatch.bitsMatch(); + } + } + } // else case 4, 7 + } + } else { + int existingPrefLenBits = existingPrefLen; + Integer newPrefLen = newTrieCache.prefixLength; + if(existingPrefLenBits == 0) { + if(newPrefLen != null && newPrefLen == 0) { + handleMatch.bitsMatch(); + } else { + FollowingBits followingBits = handleMatch.bitsMatchPartially(); + if(followingBits != null) { + followingBits.setFollowingBits(newTrieCache.getUint64HighVal() & 0x8000000000000000L); + return true; + } + } + } else if(existingPrefLenBits == bitIndex) { // optimized case where no matching is required because bit index had advanced by just one + if(newPrefLen != null && existingPrefLenBits >= newPrefLen) { + handleMatch.bitsMatch(); + } else { + FollowingBits followingBits = handleMatch.bitsMatchPartially(); + if(followingBits != null) { + long nextBitMask = existingTrieCache.getNextBitMask64Val(); + if(bitIndex > 63) /* IPv6BitCount - 65 */ { + followingBits.setFollowingBits(newTrieCache.getUint64LowVal() & nextBitMask); + } else { + followingBits.setFollowingBits(newTrieCache.getUint64HighVal() & nextBitMask); + } + return true; + } + } + } else if(existingPrefLenBits > 64) { + long existingMaskLow = existingTrieCache.getMask64LowVal(); + long newLowVal = newTrieCache.getUint64LowVal(); + if((newLowVal & existingMaskLow) == (existingTrieCache.getUint64LowVal() & existingMaskLow)) { + long existingMaskHigh = existingTrieCache.getMask64HighVal(); + if((newTrieCache.getUint64HighVal() & existingMaskHigh) == (existingTrieCache.getUint64HighVal() & existingMaskHigh)) { + if(newPrefLen != null && existingPrefLenBits >= newPrefLen) { + handleMatch.bitsMatch(); + } else { + FollowingBits followingBits = handleMatch.bitsMatchPartially(); + if(followingBits != null) { + long nextBitMask = existingTrieCache.getNextBitMask64Val(); + followingBits.setFollowingBits(newLowVal & nextBitMask); + return true; + } + } + } else if(newPrefLen != null && existingPrefLenBits > newPrefLen) { + long newMaskLow = newTrieCache.getMask64LowVal(); + if((newTrieCache.getUint64LowVal() & newMaskLow) == (existingTrieCache.getUint64LowVal() & newMaskLow)) { + long newMaskHigh = newTrieCache.getMask64HighVal(); + if((newTrieCache.getUint64HighVal() & newMaskHigh) == (existingTrieCache.getUint64HighVal() & newMaskHigh)) { + // rest of case 1 and rest of case 5 + handleMatch.bitsMatch(); + } + } + } // else case 4, 7 + } else if(newPrefLen != null && existingPrefLenBits > newPrefLen) { + long newMaskLow = newTrieCache.getMask64LowVal(); + if((newTrieCache.getUint64LowVal()&newMaskLow) == (existingTrieCache.getUint64LowVal()&newMaskLow)) { + long newMaskHigh = newTrieCache.getMask64HighVal(); + if((newTrieCache.getUint64HighVal() & newMaskHigh) == (existingTrieCache.getUint64HighVal() & newMaskHigh)) { + // rest of case 1 and rest of case 5 + handleMatch.bitsMatch(); + } + } + } // else case 4, 7 + } else if(existingPrefLenBits == 64) { + if(newTrieCache.getUint64HighVal() == existingTrieCache.getUint64HighVal()) { + if(newPrefLen != null && newPrefLen <= 64) { + handleMatch.bitsMatch(); + } else { + FollowingBits followingBits = handleMatch.bitsMatchPartially(); + if(followingBits != null) { + followingBits.setFollowingBits(newTrieCache.getUint64LowVal() & 0x8000000000000000L); + return true; + } + } + } else { + if(newPrefLen != null && newPrefLen < 64) { + long newMaskHigh = newTrieCache.getMask64HighVal(); + if((newTrieCache.getUint64HighVal() & newMaskHigh) == (existingTrieCache.getUint64HighVal() & newMaskHigh)) { + // rest of case 1 and rest of case 5 + handleMatch.bitsMatch(); + } + } + } // else case 4, 7 + } else { // existingPrefLen < 64 + long existingMaskHigh = existingTrieCache.getMask64HighVal(); + long newHighVal = newTrieCache.getUint64HighVal(); + if((newHighVal & existingMaskHigh) == (existingTrieCache.getUint64HighVal() & existingMaskHigh)) { + if(newPrefLen != null && existingPrefLenBits >= newPrefLen) { + handleMatch.bitsMatch(); + } else { + FollowingBits followingBits = handleMatch.bitsMatchPartially(); + if(followingBits != null) { + long nextBitMask = existingTrieCache.getNextBitMask64Val(); + followingBits.setFollowingBits(newHighVal & nextBitMask); + return true; + } + } + } else if(newPrefLen != null && existingPrefLenBits > newPrefLen) { + long newMaskHigh = newTrieCache.getMask64HighVal(); + if((newTrieCache.getUint64HighVal() & newMaskHigh) == (existingTrieCache.getUint64HighVal() & newMaskHigh)) { + // rest of case 1 and rest of case 5 + handleMatch.bitsMatch(); + } + } // else case 4, 7 + } + } + return false; + } + } + } + } + int bitsPerSegment = existingAddr.getBitsPerSegment(); - int segmentIndex = bitIndex / bitsPerSegment; + int bytesPerSegment = existingAddr.getBytesPerSegment(); + int segmentIndex = ParsedAddressGrouping.getHostSegmentIndex(bitIndex, bytesPerSegment, bitsPerSegment); int segmentCount = existingAddr.getSegmentCount(); - // this block handles cases like handling 1.2.3.4 and 1.2.3.4/32 - // but since those two return true for equals(), we do not allow both in our tries and we do not actually need this case, - // but we do keep it for alternative tries that do not need the collection to consistent with equals() - if(segmentIndex >= segmentCount) { - Integer existingPref = existingAddr.getPrefixLength(); - Integer newPref = newAddr.getPrefixLength(); - // note that "added" is already true here, we can only be here if explicitly inserted already - if(Objects.equals(existingPref, newPref)) { - result.containedBy = this; - handleMatch(result); - } else if(existingPref == null) { - result.containedBy = this; - handleContained(result, newPref); - } else { // newPref == null - handleContains(result); - return existingPref; - } - return -1; - } - if(newAddr.getSegmentCount() != segmentCount) { - // to handle this is tricky. For a:b:c:d:e:f I would need - // to convert to an address with prefix length 48, which I do not support. - // Not only that, the prefixed address would be equal with the original, which is an equality problem. - // So overall, it is not supported, it doesn't make sense. - // - // However, for MAC addresses, we do allow the first inserted address to determine the bit size of the trie. + if(newAddr.getSegmentCount() != segmentCount || bitsPerSegment != newAddr.getBitsPerSegment()) { throw new IllegalArgumentException(getMessage("ipaddress.error.mismatched.bit.size")); } - int bitsMatchedSoFar = segmentIndex * bitsPerSegment; - int extraBits = Integer.SIZE - bitsPerSegment; + Integer existingPref = existingAddr.getPrefixLength(); + Integer newPrefLen = newAddr.getPrefixLength(); + + // this block handles cases like where we matched matching ::ffff:102:304 to ::ffff:102:304/127, + // and we found a subnode to match, but we know the final bit is a match due to the subnode being lower or upper, + // so there is actually not more bits to match + if(segmentIndex >= segmentCount) { + // all the bits match + handleMatch.bitsMatch(); + return false; + } + + int bitsMatchedSoFar = ParsedAddressGrouping.getTotalBits(segmentIndex, bytesPerSegment, bitsPerSegment); while(true) { AddressSegment existingSegment = existingAddr.getSegment(segmentIndex); AddressSegment newSegment = newAddr.getSegment(segmentIndex); - Integer segmentPref = getSegmentPrefLen(existingAddr, bitsMatchedSoFar, existingSegment); - Integer newPref = getSegmentPrefLen(newAddr, bitsMatchedSoFar, newSegment); + Integer segmentPref = getSegmentPrefLen(existingAddr, existingPref, bitsPerSegment, bitsMatchedSoFar, existingSegment); + Integer newSegmentPref = getSegmentPrefLen(newAddr, newPrefLen, bitsPerSegment, bitsMatchedSoFar, newSegment); int newPrefixLen; if(segmentPref != null) { int segmentPrefLen = segmentPref; - if(newPref != null && (newPrefixLen = newPref) <= segmentPrefLen) { - int matchingBits = getMatchingBits(existingSegment, newSegment, newPrefixLen, extraBits); - if(matchingBits >= newPrefixLen) { // the bits of current prefix match - result.containedBy = this; - if(newPrefixLen == segmentPrefLen) { - if(isAdded()) { - handleMatch(result); - } else if(op == Operation.LOOKUP) { - result.existingNode = this; - } else if(op == Operation.INSERT) { - existingAdded(result); - } else if(op == Operation.SUBNET_DELETE) { - removeSubnet(result); - } else if(op == Operation.NEAR) { - findNearestFromMatch(result); - } else if(op == Operation.REMAP) { - remapNonAdded(result); - } - break; - } else { // newPrefixLen < segmentPrefLen, matchingBits >= newPrefixLen - handleContained(result, bitsMatchedSoFar + newPrefixLen); - } + if(newSegmentPref != null && (newPrefixLen = newSegmentPref) <= segmentPrefLen) { + int matchingBits = getMatchingBits(existingSegment, newSegment, newPrefixLen, bitsPerSegment); + if(matchingBits >= newPrefixLen) { + handleMatch.bitsMatch(); } else { // no match - the bits don't match // matchingBits < newPrefLen < segmentPrefLen - handleSplitNode(result, bitsMatchedSoFar + matchingBits); + handleMatch.bitsDoNotMatch(bitsMatchedSoFar + matchingBits); } } else { - int matchingBits = getMatchingBits(existingSegment, newSegment, segmentPrefLen, extraBits); + int matchingBits = getMatchingBits(existingSegment, newSegment, segmentPrefLen, bitsPerSegment); if(matchingBits >= segmentPrefLen) { // match - the current subnet/address is a match so far, and we must go further to check smaller subnets - if(isAdded()) { - handleContains(result); + FollowingBits followingBits = handleMatch.bitsMatchPartially(); + if(followingBits != null) { + // calculate the followingBitsFlag + + // check if at end of segment, advance to next if so + if(segmentPrefLen == bitsPerSegment) { + segmentIndex++; + if(segmentIndex == segmentCount) { + return true; + } + newSegment = newAddr.getSegment(segmentIndex); + segmentPrefLen = 0; + } + + // check the bit for followingBitsFlag + if(newSegment.isOneBit(segmentPrefLen)) { + followingBits.setFollowingBits(0x8000000000000000L); + } + return true; } - return segmentPrefLen + bitsMatchedSoFar; - } else { - // matchingBits < segmentPrefLen - no match - the bits in current prefix do not match the prefix of the existing address - handleSplitNode(result, bitsMatchedSoFar + matchingBits); + return false; } + // matchingBits < segmentPrefLen - no match - the bits in current prefix do not match the prefix of the existing address + handleMatch.bitsDoNotMatch(bitsMatchedSoFar + matchingBits); } - break; - } else if(newPref != null) { - newPrefixLen = newPref; - int matchingBits = getMatchingBits(existingSegment, newSegment, newPrefixLen, extraBits); + return false; + } else if(newSegmentPref != null) { + newPrefixLen = newSegmentPref; + int matchingBits = getMatchingBits(existingSegment, newSegment, newPrefixLen, bitsPerSegment); if(matchingBits >= newPrefixLen) { // the current bits match the current prefix, but the existing has no prefix - result.containedBy = this; - handleContained(result, bitsMatchedSoFar + newPrefixLen); + handleMatch.bitsMatch(); } else { // no match - the current subnet does not match the existing address - handleSplitNode(result, bitsMatchedSoFar + matchingBits); + handleMatch.bitsDoNotMatch(bitsMatchedSoFar + matchingBits); } - break; + return false; } else { - int matchingBits = getMatchingBits(existingSegment, newSegment, bitsPerSegment, extraBits); + int matchingBits = getMatchingBits(existingSegment, newSegment, bitsPerSegment, bitsPerSegment); if(matchingBits < bitsPerSegment) { // no match - the current subnet/address is not here - handleSplitNode(result, bitsMatchedSoFar + matchingBits); - break; + handleMatch.bitsDoNotMatch(bitsMatchedSoFar + matchingBits); + return false; } else if(++segmentIndex == segmentCount) { // match - the current subnet/address is a match - result.containedBy = this; // note that "added" is already true here, we can only be here if explicitly inserted already since it is a non-prefixed full address - handleMatch(result); - break; + handleMatch.bitsMatch(); + return false; } bitsMatchedSoFar += bitsPerSegment; } } - return -1; } private void handleContained(OpResult result, int newPref) { @@ -1120,8 +1539,8 @@ private void handleContained(OpResult result, int newPref) { // then there are no more bits to look at, and this makes the former a sub-node of the latter. // In most cases, however, there are more bits in existingAddr, the latter, to look at. replace(result, newPref); - } else if(op == Operation.SUBNET_DELETE) { - removeSubnet(result); + } else if(op == Operation.SUBTREE_DELETE) { + removeSubtree(result); } else if(op == Operation.NEAR) { findNearest(result, newPref); } else if(op == Operation.REMAP) { @@ -1130,11 +1549,14 @@ private void handleContained(OpResult result, int newPref) { } private boolean handleContains(OpResult result) { - result.smallestContaining = this; if(result.op == Operation.CONTAINING) { + result.largestContaining = this; + return true; + } else if(result.op == Operation.ALL_CONTAINING) { result.addContaining(this); return true; } + result.smallestContaining = this; return false; } @@ -1150,6 +1572,23 @@ private void handleSplitNode(OpResult result, int totalMatchingBits) { } } + // a node exists for the given key but the node is not added, + // so not a match, but a split not required + private void handleNodeMatch(OpResult result) { + Operation op = result.op; + if(op == Operation.LOOKUP) { + result.existingNode = this; + } else if(op == Operation.INSERT) { + existingAdded(result); + } else if(op == Operation.SUBTREE_DELETE) { + removeSubtree(result); + } else if(op == Operation.NEAR) { + findNearestFromMatch(result); + } else if(op == Operation.REMAP) { + remapNonAdded(result); + } + } + private void handleMatch(OpResult result) { result.exists = true; if(!handleContains(result)) { @@ -1159,9 +1598,9 @@ private void handleMatch(OpResult result) { } else if(op == Operation.INSERT) { matchedInserted(result); } else if(op == Operation.INSERTED_DELETE) { - remove(result); - } else if(op == Operation.SUBNET_DELETE) { - removeSubnet(result); + removeOp(result); + } else if(op == Operation.SUBTREE_DELETE) { + removeSubtree(result); } else if(op == Operation.NEAR) { if(result.nearExclusive) { findNearestFromMatch(result); @@ -1248,7 +1687,7 @@ private void inserted(OpResult result) { // ** overridden by map trie ** void added(OpResult result) { - setAdded(true); + setNodeAdded(true); adjustCount(1); changeTracker.changed(); } @@ -1268,7 +1707,7 @@ private void split(OpResult result, int totalMatchingBits, TrieNode newSub } else { newBlock = (E) key.setPrefixLength(totalMatchingBits).toPrefixBlock(); } - replace(newBlock, result, totalMatchingBits, newSubNode); + replaceToSub(newBlock, totalMatchingBits, newSubNode); newSubNode.inserted(result); } @@ -1280,7 +1719,7 @@ private void split(OpResult result, int totalMatchingBits, TrieNode newSub */ private void replace(OpResult result, int totalMatchingBits) { result.containedBy = this; - TrieNode newNode = replace(result.addr, result, totalMatchingBits, null); + TrieNode newNode = replaceToSub(result.addr, totalMatchingBits, null); newNode.inserted(result); } @@ -1294,7 +1733,7 @@ private void replace(OpResult result, int totalMatchingBits) { * @param newSubNode * @return */ - private TrieNode replace(E newAssignedAddr, OpResult result, int totalMatchingBits, TrieNode newSubNode) { + private TrieNode replaceToSub(E newAssignedAddr, int totalMatchingBits, TrieNode newSubNode) { TrieNode newNode = createNew(newAssignedAddr); newNode.size = size; TrieNode parent = getParent(); @@ -1413,7 +1852,12 @@ private TrieNode matchSubNode(int bitIndex, OpResult result) { setKey(newAddr); existingAdded(result); } - } else if(bitIndex < newAddr.getBitCount() && newAddr.isOneBit(bitIndex)) { + } else if(bitIndex >= newAddr.getBitCount()) { + // we matched all bits, yet somehow we are still going + // this can only happen when matching 1.2.3.4/32 to 1.2.3.4 + // which should never happen and so we do nothing + } else if(result.followingBits != 0L) { + result.setFollowingBits(0); TrieNode upper = getUpperSubNode(); if(upper == null) { // no match @@ -1457,11 +1901,6 @@ private TrieNode matchSubNode(int bitIndex, OpResult result) { return upper; } } else { - // if we have 1.2.3.4 and 1.2.3.4/32, and we are looking at the last segment, - // then there are no more bits to look at, and this makes the former a sub-node of the latter. - // However, because 1.2.3.4 and 1.2.3.4/32 return true for equals(), we avoid putting both in the tree, - // and instead we always convert to 1.2.3.4 first. - // In most cases, however, there are more bits in newAddr, the former, to look at. TrieNode lower = getLowerSubNode(); if(lower == null) { // no match @@ -1539,8 +1978,8 @@ public TrieNode clone() { } @Override - TrieNode cloneTree(Bounds bounds) { - return (TrieNode) super.cloneTree(bounds); + TrieNode cloneTreeBounds(Bounds bounds) { + return (TrieNode) super.cloneTreeBounds(bounds); } @Override @@ -1573,14 +2012,15 @@ protected AddressTrie(TrieNode root, AddressBounds bounds) { private static Integer getSegmentPrefLen( AddressSegmentSeries addr, + Integer prefLen, + int bitsPerSegment, int bitsMatchedSoFar, AddressSegment segment) { if(segment instanceof IPAddressSegment) { return ((IPAddressSegment) segment).getSegmentPrefixLength(); - } else if(addr.isPrefixed()) { - int existingPrefLen = addr.getPrefixLength(); - if(existingPrefLen <= bitsMatchedSoFar + addr.getBitsPerSegment()) { - Integer result = existingPrefLen - bitsMatchedSoFar; + } else if(prefLen != null) { + Integer result = prefLen - bitsMatchedSoFar; + if(result <= bitsPerSegment) { if(result < 0) { result = 0; } @@ -1590,19 +2030,21 @@ private static Integer getSegmentPrefLen( return null; } - private static int getMatchingBits(AddressSegment segment1, AddressSegment segment2, int maxBits, int adjustment) { + private static int getMatchingBits(AddressSegment segment1, AddressSegment segment2, int maxBits, int bitsPerSegment) { if(maxBits == 0) { return 0; } int val1 = segment1.getSegmentValue(); int val2 = segment2.getSegmentValue(); int xor = val1 ^ val2; - if(adjustment == IPv6Address.BITS_PER_SEGMENT) { - return numberOfLeadingZerosShort(xor); - } else if(adjustment == (32 - IPv4Address.BITS_PER_SEGMENT)) { + switch(bitsPerSegment) { + case IPv4Address.BITS_PER_SEGMENT: return numberOfLeadingZerosByte(xor); + case IPv6Address.BITS_PER_SEGMENT: + return numberOfLeadingZerosShort(xor); + default: + return Integer.numberOfLeadingZeros(xor) + bitsPerSegment - Integer.SIZE; } - return Integer.numberOfLeadingZeros(xor) - adjustment; } private static int numberOfLeadingZerosShort(int i) { @@ -1621,8 +2063,8 @@ private static int numberOfLeadingZerosByte(int i) { return 0; } int n = 1; - if (i >>> 4 == 0) { n += 4; i <<= 4; } - if (i >>> 6 == 0) { n += 2; i <<= 2; } + if (i >>> 4 == 0) { n += 4; i <<= 4; } + if (i >>> 6 == 0) { n += 2; i <<= 2; } n -= i >>> 7; return n; } @@ -1637,7 +2079,7 @@ public boolean isEmpty() { } /** - * Returns the number of nodes in the trie, which is more than the number of elements. + * Returns the number of nodes in the trie, which is more than the number of added elements. * * @return */ @@ -1881,7 +2323,7 @@ class IndentsNode { } TrieNode addNode(OpResult result, TrieNode fromNode, TrieNode nodeToAdd, boolean withValues) { - fromNode.matchBits(fromNode.getKey().getPrefixLength(), result); + fromNode.matchBitsFromIndex(fromNode.getKey().getPrefixLength(), result); TrieNode node = result.existingNode; return node == null ? result.inserted : node; } @@ -1918,6 +2360,7 @@ TrieNode addTrie(TrieNode tree, boolean withValues) { result.addr = addrNext; result.existingNode = null; result.inserted = null; + result.setFollowingBits(0); lastAddedNode = addNode(result, cachedNode, toAdd, withValues); } else { lastAddedNode = cachedNode; @@ -2004,7 +2447,25 @@ public TrieNode longestPrefixMatchNode(E addr) { } return absoluteRoot().longestPrefixMatchNode(addr); } - + + @Override + public E shortestPrefixMatch(E addr) { + if(bounds != null) { + // should never reach here when there are bounds, since this is not exposed from set/map code + throw new Error(); + } + return absoluteRoot().shortestPrefixMatch(addr); + } + + @Override + public TrieNode shortestPrefixMatchNode(E addr) { + if(bounds != null) { + // should never reach here when there are bounds, since this is not exposed from set/map code + throw new Error(); + } + return absoluteRoot().shortestPrefixMatchNode(addr); + } + @Override public boolean elementContains(E addr) { if(bounds != null) { @@ -2031,7 +2492,7 @@ AddressTrie elementsContainedByToSubTrie(E addr) { } return createSubTrie(newBounds); } - + AddressTrie elementsContainingToTrie(E addr) { if(isEmpty()) { return this; @@ -2067,7 +2528,7 @@ boolean elementContainsBounds(E addr) { // Now we need to know if any of the nodes are within the bounds return !createNewSameBoundsFromList(node).isEmpty(); } - + TrieNode smallestElementContainingBounds(E addr) { if(bounds == null) { return longestPrefixMatchNode(addr); @@ -2100,18 +2561,18 @@ TrieNode smallestElementContainingBounds(E addr) { } return node; } - + E longestPrefixMatchBounds(E addr) { TrieNode node = smallestElementContainingBounds(addr); return node == null ? null : node.getKey(); } - + // creates a new one-node trie with a new root and the given bounds protected abstract AddressTrie createNew(AddressBounds bounds); - + // create a trie with the same root as this one, but different bounds protected abstract AddressTrie createSubTrie(AddressBounds bounds); - + private AddressTrie createNewSameBoundsFromList(TrieNode node) { AddressTrie newTrie = createNew(bounds); TrieNode root = newTrie.absoluteRoot(); @@ -2140,7 +2601,7 @@ private AddressTrie createNewSameBoundsFromList(TrieNode node) { newTrie.root.size(); return newTrie; } - + @Override public TrieNode getNode(E addr) { TrieNode subRoot; @@ -2158,7 +2619,7 @@ public TrieNode getNode(E addr) { } return subRoot.getNode(addr); } - + @Override public Iterator> allNodeIterator(boolean forward) { if(bounds != null) { @@ -2219,7 +2680,7 @@ public Iterator> blockSizeAllNodeIterator(boolean lowerSub } return (Iterator>) iterator; } - + /** * Iterates all nodes, ordered by keys from largest prefix blocks to smallest, and then to individual addresses. *

@@ -2236,13 +2697,13 @@ public CachingIterator, E, C> blockSizeCachingAllNodeI @SuppressWarnings("unchecked") @Override - public CachingIterator, E, C> containingFirstIterator(boolean forwardSubNodeOrder) { - CachingIterator, E, C> iterator; + public Iterator> containingFirstIterator(boolean forwardSubNodeOrder) { + Iterator> iterator; if(bounds == null) { iterator = absoluteRoot().containingFirstIterator(forwardSubNodeOrder); } else { if(forwardSubNodeOrder) { - iterator = new PreOrderNodeIterator( + iterator = new PreOrderNodeIterator( bounds, true, true, // added only @@ -2250,7 +2711,7 @@ public CachingIterator, E, C> containingFirstIterator( null, absoluteRoot().changeTracker); } else { - iterator = new PostOrderNodeIterator( + iterator = new PostOrderNodeIterator( bounds, false, true, // added only @@ -2259,7 +2720,7 @@ public CachingIterator, E, C> containingFirstIterator( absoluteRoot().changeTracker); } } - return (CachingIterator, E, C>) iterator; + return (Iterator>) iterator; } @SuppressWarnings("unchecked") @@ -2337,7 +2798,7 @@ public Iterator> containedFirstAllNodeIterator(boolean for } return (Iterator>) iterator; } - + @Override public Spliterator spliterator() { return new KeySpliterator(nodeSpliterator(true, true), comparator()); @@ -2363,7 +2824,7 @@ public Spliterator> allNodeSpliterator(boolean forward) { } return absoluteRoot().nodeSpliterator(forward, false); } - + @SuppressWarnings("unchecked") Spliterator> nodeSpliterator(boolean forward, boolean addedNodesOnly) { Spliterator> spliterator; @@ -2384,7 +2845,7 @@ Spliterator> nodeSpliterator(boolean forward, boolean adde } return spliterator; } - + @SuppressWarnings("unchecked") @Override public Iterator> nodeIterator(boolean forward) { @@ -2557,6 +3018,11 @@ private TrieNode lowerNodeBounded(E addr) { return null; } + @Override + public E lower(E addr) { + return getNodeKey(lowerAddedNode(addr)); + } + @Override public TrieNode floorAddedNode(E addr) { if(bounds == null) { @@ -2576,6 +3042,11 @@ private TrieNode floorNodeBounded(E addr) { return null; } + @Override + public E floor(E addr) { + return getNodeKey(floorAddedNode(addr)); + } + @Override public TrieNode higherAddedNode(E addr) { if(bounds == null) { @@ -2595,6 +3066,11 @@ private TrieNode higherNodeBounded(E addr) { return null; } + @Override + public E higher(E addr) { + return getNodeKey(higherAddedNode(addr)); + } + @Override public TrieNode ceilingAddedNode(E addr) { if(bounds == null) { @@ -2614,6 +3090,15 @@ private TrieNode ceilingNodeBounded(E addr) { return null; } + @Override + public E ceiling(E addr) { + return getNodeKey(ceilingAddedNode(addr)); + } + + static E getNodeKey(TrieNode node) { + return (node == null) ? null : node.getKey(); + } + @Override public void clear() { if(bounds == null) { @@ -2638,17 +3123,17 @@ public AddressTrie clone() { } else { TrieNode root = absoluteRoot(); if(bounds.isInBounds(root.getKey())) { - result.root = root.cloneTree(bounds); + result.root = root.cloneTreeBounds(bounds); } else { // clone the root ourselves, then clone the trie starting from the subroot, and make it a child of the root BinaryTreeNode clonedRoot = root.cloneTreeNode(new ChangeTracker()); // clone root node only result.root = clonedRoot; - clonedRoot.setAdded(false); // not in bounds, so not part of new trie + clonedRoot.setNodeAdded(false); // not in bounds, so not part of new trie clonedRoot.setLower(null); clonedRoot.setUpper(null); TrieNode subRoot = getRoot(); if(subRoot != null) { - TrieNode subCloned = subRoot.cloneTree(bounds); + TrieNode subCloned = subRoot.cloneTreeBounds(bounds); if(subCloned != null) { result.absoluteRoot().init(subCloned);// attach cloned sub-root to root } else { @@ -2710,8 +3195,12 @@ void printTree(StringBuilder builder, Indents indents, boolean withNonAddedKeys) * @return */ public static String toString(boolean withNonAddedKeys, AddressTrie ...tries) { - StringBuilder builder = new StringBuilder('\n' + BinaryTreeNode.NON_ADDED_NODE_CIRCLE); - String topLabel = ' ' + Address.SEGMENT_WILDCARD_STR; + int totalEntrySize = 0; + for(int i=0; i < tries.length; i++) { + totalEntrySize += tries[i].size(); + } + StringBuilder builder = new StringBuilder(totalEntrySize * 120); + builder.append('\n').append(BinaryTreeNode.NON_ADDED_NODE_CIRCLE); boolean isEmpty = tries == null; if(!isEmpty) { AddressTrie lastTree = null; @@ -2732,7 +3221,7 @@ public static String toString(boolean withNonAddedKeys, AddressTrie ...tries) } } if(withNonAddedKeys) { - builder.append(topLabel).append(" (").append(totalSize).append(')'); + builder.append(' ').append(Address.SEGMENT_WILDCARD_STR).append(" (").append(totalSize).append(')'); } builder.append('\n'); for(int i = 0; i < lastTreeIndex; i++) { @@ -2746,7 +3235,7 @@ public static String toString(boolean withNonAddedKeys, AddressTrie ...tries) } if(isEmpty) { if(withNonAddedKeys) { - builder.append(topLabel).append(" (0)"); + builder.append(' ').append(Address.SEGMENT_WILDCARD_STR).append(" (0)"); } builder.append('\n'); } diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrieMap.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrieMap.java index 6fbc1540..eb5275cb 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrieMap.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrieMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -612,11 +612,7 @@ public AddressTrieMap tailMap(K fromKey, boolean inclusive) { @Override public Entry firstEntry() { - AssociativeTrieNode node = isReverse ? trie.lastAddedNode() : trie.firstAddedNode(); - if(node == null) { - return null; - } - return node; + return isReverse ? trie.lastAddedNode() : trie.firstAddedNode(); } @Override @@ -626,11 +622,7 @@ public K firstKey() { @Override public Entry lastEntry() { - AssociativeTrieNode node = isReverse ? trie.firstAddedNode() : trie.lastAddedNode(); - if(node == null) { - return null; - } - return node; + return isReverse ? trie.firstAddedNode() : trie.lastAddedNode(); } @Override @@ -640,11 +632,7 @@ public K lastKey() { @Override public Entry lowerEntry(K key) { - AssociativeTrieNode node = isReverse ? trie.higherAddedNode(key) : trie.lowerAddedNode(key); - if(node == null) { - return null; - } - return node; + return isReverse ? trie.higherAddedNode(key) : trie.lowerAddedNode(key); } @Override @@ -654,11 +642,7 @@ public K lowerKey(K key) { @Override public Entry floorEntry(K key) { - AssociativeTrieNode node = isReverse ? trie.ceilingAddedNode(key) : trie.floorAddedNode(key); - if(node == null) { - return null; - } - return node; + return isReverse ? trie.ceilingAddedNode(key) : trie.floorAddedNode(key); } @Override @@ -668,11 +652,7 @@ public K floorKey(K key) { @Override public Entry ceilingEntry(K key) { - AssociativeTrieNode node = isReverse ? trie.floorAddedNode(key) : trie.ceilingAddedNode(key); - if(node == null) { - return null; - } - return node; + return isReverse ? trie.floorAddedNode(key) : trie.ceilingAddedNode(key); } @Override @@ -682,11 +662,7 @@ public K ceilingKey(K key) { @Override public Entry higherEntry(K key) { - AssociativeTrieNode node = isReverse ? trie.lowerAddedNode(key) : trie.higherAddedNode(key); - if(node == null) { - return null; - } - return node; + return isReverse ? trie.lowerAddedNode(key) : trie.higherAddedNode(key); } @Override diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrieOps.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrieOps.java index b9a05394..31953a24 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrieOps.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrieOps.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -225,6 +225,47 @@ default TrieNode getAddedNode(E addr) { */ E longestPrefixMatch(E addr); + /** + * Finds the containing subnet or address in the trie with the largest subnet size, + * which is equivalent to finding the subnet or address with the shortest matching prefix. + * Returns the node corresponding to that subnet. + *

+ * If the given address is not a single address nor prefix block, then this method throws IllegalArgumentException. + *

+ * If not a single address nor prefix block, the {@link Partition} class can be used to convert the address before calling this method. + * See {@link AddressTrieAddOps#add(Address)} for more details. + *

+ * Returns null if no added subnet or address contains the given argument. + *

+ * Use {@link #elementContains(Address)} to check for the existence of a containing address.
+ * To get all the containing addresses, use {@link #elementsContaining(Address)}.
+ * Use {@link #shortestPrefixMatch(Address)} to get the address corresponding to the result of this method.
+ * + * @param addr + * @return + */ + TrieNode shortestPrefixMatchNode(E addr); + + /** + * Of all the added subnets or address whose prefix matches the given address, returns the one with the shortest prefix. + * This is equivalent to finding the containing subnet or address with the largest subnet size. + *

+ * If the given address is not a single address nor prefix block, then this method throws IllegalArgumentException. + *

+ * If not a single address nor prefix block, the {@link Partition} class can be used to convert the address before calling this method. + * See {@link AddressTrieAddOps#add(Address)} for more details. + *

+ * Returns null if no added subnet or address contains the given argument. + *

+ * Use {@link #elementContains(Address)} to check for the existence of a containing address.
+ * To get all the containing addresses (subnets with matching prefix), use {@link #elementsContaining(Address)}.
+ * To get the node corresponding to the result of this method, use {@link #shortestPrefixMatchNode(Address)}
+ * + * @param addr + * @return + */ + E shortestPrefixMatch(E addr); + @Override Iterator> nodeIterator(boolean forward); @@ -232,7 +273,7 @@ default TrieNode getAddedNode(E addr) { Iterator> allNodeIterator(boolean forward); @Override - CachingIterator, E, C> containingFirstIterator(boolean forwardSubNodeOrder); + Iterator> containingFirstIterator(boolean forwardSubNodeOrder); @Override CachingIterator, E, C> containingFirstAllNodeIterator(boolean forwardSubNodeOrder); @@ -305,6 +346,38 @@ default TrieNode getAddedNode(E addr) { */ TrieNode higherAddedNode(E addr); + /** + * Returns the highest added address less than or equal to the given address. + * + * @param addr + * @return + */ + E floor(E addr); + + /** + * Returns the highest added address strictly less than the given address. + * + * @param addr + * @return + */ + E lower(E addr); + + /** + * Returns the lowest added address greater than or equal to the given address. + * + * @param addr + * @return + */ + E ceiling(E addr); + + /** + * Returns the lowest added address strictly greater than the given address. + * + * @param addr + * @return + */ + E higher(E addr); + /** * Provides an interface to the trie add operations.

* Operations which take an address as an argument require that the address is an individual address or prefix block. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrieSet.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrieSet.java index 0be1cb6a..6ef38759 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrieSet.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AddressTrieSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -463,38 +463,22 @@ public E last() { @Override public E lower(E e) { - BinaryTreeNode node = isReverse ? trie.higherAddedNode(e) : trie.lowerAddedNode(e); - if(node == null) { - return null; - } - return node.getKey(); + return isReverse ? trie.higher(e) : trie.lower(e); } @Override public E floor(E e) { - BinaryTreeNode node = isReverse ? trie.ceilingAddedNode(e) : trie.floorAddedNode(e); - if(node == null) { - return null; - } - return node.getKey(); + return isReverse ? trie.ceiling(e) : trie.floor(e); } @Override public E ceiling(E e) { - BinaryTreeNode node = isReverse ? trie.floorAddedNode(e) : trie.ceilingAddedNode(e); - if(node == null) { - return null; - } - return node.getKey(); + return isReverse ? trie.floor(e) : trie.ceiling(e); } @Override public E higher(E e) { - BinaryTreeNode node = isReverse ? trie.lowerAddedNode(e) : trie.higherAddedNode(e); - if(node == null) { - return null; - } - return node.getKey(); + return isReverse ? trie.lower(e) : trie.higher(e); } @Override diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AssociativeAddedTree.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AssociativeAddedTree.java index 5de10bc6..c96e0df7 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AssociativeAddedTree.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AssociativeAddedTree.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Sean C Foley + * Copyright 2022-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AssociativeAddressTrie.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AssociativeAddressTrie.java index 23778953..1d9f5981 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AssociativeAddressTrie.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/AssociativeAddressTrie.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -101,10 +101,7 @@ public AssociativeTrieNode getParent() { @SuppressWarnings("unchecked") @Override public V get(K addr) { - addr = checkBlockOrAddress(addr, true); - OpResult result = new OpResult<>(addr, Operation.LOOKUP); - matchBits(result); - AssociativeTrieNode node = (AssociativeTrieNode) result.existingNode; + AssociativeTrieNode node = (AssociativeTrieNode) doLookup(addr).existingNode; return node == null ? null : node.getValue(); } @@ -192,8 +189,8 @@ public CachingIterator, K, C> blockSizeCa @SuppressWarnings("unchecked") @Override - public CachingIterator, K, C> containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator, K, C>) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator> containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator>) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") @@ -338,6 +335,12 @@ public AssociativeTrieNode longestPrefixMatchNode(K addr) { return (AssociativeTrieNode) super.longestPrefixMatchNode(addr); } + @SuppressWarnings("unchecked") + @Override + public AssociativeTrieNode shortestPrefixMatchNode(K addr) { + return (AssociativeTrieNode) super.shortestPrefixMatchNode(addr); + } + @Override @SuppressWarnings("unchecked") void matchedInserted(OpResult result) { @@ -373,7 +376,7 @@ boolean remap(OpResult result, boolean isMatch) { if(isMatch) { changeTracker.changedSince(change); clearValue(); - remove(result); + removeOp(result); } return false; } else if (isMatch) { @@ -519,13 +522,11 @@ protected void contructAssociativeAddedTree(AssociativeAddressTrie> thisIterator = containingFirstAllNodeIterator(true); - - //System.out.println("starting iteration"); + while(cachingIterator.hasNext()) { AssociativeTrieNode> newNext = cachingIterator.next(), parent; AssociativeTrieNode thisNext = thisIterator.next(); - //System.out.println("iterated on " + thisNext + " and " + newNext); - + SubNodesMappingAssociative mapping = new SubNodesMappingAssociative(); mapping.value = thisNext.getValue(); @@ -707,6 +708,12 @@ public AssociativeTrieNode longestPrefixMatchNode(K addr) { return (AssociativeTrieNode) super.longestPrefixMatchNode(addr); } + @SuppressWarnings("unchecked") + @Override + public AssociativeTrieNode shortestPrefixMatchNode(K addr) { + return (AssociativeTrieNode) super.shortestPrefixMatchNode(addr); + } + /** * Returns a java.util.NavigableMap backed by this associative trie. * The added elements of this trie are keys for the map, the associated values are the map values. @@ -773,8 +780,8 @@ public Iterator> blockSizeAllNodeIterator(boo @SuppressWarnings("unchecked") @Override - public CachingIterator, K, C> containingFirstIterator(boolean lowerSubNodeFirst) { - return (CachingIterator, K, C>) super.containingFirstIterator(lowerSubNodeFirst); + public Iterator> containingFirstIterator(boolean lowerSubNodeFirst) { + return (Iterator>) super.containingFirstIterator(lowerSubNodeFirst); } @SuppressWarnings("unchecked") diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/BaseDualIPv4v6Tries.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/BaseDualIPv4v6Tries.java new file mode 100644 index 00000000..0700d870 --- /dev/null +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/BaseDualIPv4v6Tries.java @@ -0,0 +1,646 @@ +/* + * Copyright 2024 Sean C Foley + * + * Licensed 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * or at + * https://github.com/seancfoley/IPAddress/blob/master/LICENSE + * + * 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 inet.ipaddr.format.util; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Spliterator; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +import inet.ipaddr.Address; +import inet.ipaddr.IPAddress; +import inet.ipaddr.format.util.AddressTrie.TrieNode; +import inet.ipaddr.format.util.AddressTrieOps.AddressTrieAddOps; +import inet.ipaddr.format.util.BinaryTreeNode.ChangeTracker; +import inet.ipaddr.format.util.BinaryTreeNode.ChangeTracker.Change; +import inet.ipaddr.ipv4.IPv4Address; +import inet.ipaddr.ipv6.IPv6Address; + + +/** + * Contains a pair of IPv4 and IPv6 tries for a data structure that can have fast look-up and containment checks of both IPv4 and IPv6 addresses. + * + * For a tree that is either IPv4 or IPv6, you can just use #{@link AddressTrie}. + * + * @author scfoley + * + */ +public abstract class BaseDualIPv4v6Tries, T6 extends AddressTrie> implements Iterable, Serializable, Cloneable { + + private static final long serialVersionUID = 1L; + + /** + * returns the contained IPv4 trie + * @return + */ + public abstract T4 getIPv4Trie(); + + /** + * returns the contained IPv6 trie + * @return + */ + public abstract T6 getIPv6Trie(); + + private ChangeTracker ipv4Tracker, ipv6Tracker; + + BaseDualIPv4v6Tries(AddressTrie ipv4Trie, AddressTrie ipv6Trie) { + assignTrackers(ipv4Trie, ipv6Trie); + } + + void assignTrackers(AddressTrie ipv4Trie, AddressTrie ipv6Trie) { + this.ipv4Tracker = ipv4Trie.absoluteRoot().changeTracker; + this.ipv6Tracker = ipv6Trie.absoluteRoot().changeTracker; + } + + @SuppressWarnings("unchecked") + @Override + public BaseDualIPv4v6Tries clone() { + try { + return (BaseDualIPv4v6Tries) super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + + @Override + public boolean equals(Object other) { + if(other instanceof BaseDualIPv4v6Tries) { + BaseDualIPv4v6Tries o = (BaseDualIPv4v6Tries) other; + return getIPv4Trie().equals(o.getIPv4Trie()) && getIPv6Trie().equals(o.getIPv6Trie()); + } + return false; + } + + @Override + public String toString() { + return AddressTrie.toString(true, getIPv4Trie(), getIPv6Trie()); + } + + static boolean addressPredicateOp(IPAddress addr, Predicate ipv4Op, Predicate ipv6Op) { + if(addr.isIPv4()) { + return ipv4Op.test(addr.toIPv4()); + } else if(addr.isIPv6()) { + return ipv6Op.test(addr.toIPv6()); + } + return false; + } + + static T addressFuncOp(IPAddress addr, Function ipv4Op, Function ipv6Op) { + if(addr.isIPv4()) { + return ipv4Op.apply(addr.toIPv4()); + } else if(addr.isIPv6()) { + return ipv6Op.apply(addr.toIPv6()); + } + return null; + } + + static V addressValValBiFuncOp(IPAddress addr, V value, BiFunction ipv4Op, BiFunction ipv6Op) { + if(addr.isIPv4()) { + return ipv4Op.apply(addr.toIPv4(), value); + } else if(addr.isIPv6()) { + return ipv6Op.apply(addr.toIPv6(), value); + } + return null; + } + + static R addressValBiFuncOp(IPAddress addr, V value, BiFunction ipv4Op, BiFunction ipv6Op) { + if(addr.isIPv4()) { + return ipv4Op.apply(addr.toIPv4(), value); + } else if(addr.isIPv6()) { + return ipv6Op.apply(addr.toIPv6(), value); + } + return null; + } + + static boolean addressValBiPredicateOp(IPAddress addr, V value, BiPredicate ipv4Op, BiPredicate ipv6Op) { + if(addr.isIPv4()) { + return ipv4Op.test(addr.toIPv4(), value); + } else if(addr.isIPv6()) { + return ipv6Op.test(addr.toIPv6(), value); + } + return false; + } + + @SuppressWarnings("unchecked") + static , + R extends TrieNode, + R1 extends TrieNode, + R2 extends TrieNode> R unaryOp(T trie, UnaryOperator ipv4Op, UnaryOperator ipv6Op) { + IPAddress addr = trie.getKey(); + if(addr.isIPv4()) { + return (R) ipv4Op.apply((R1) trie); + } else if(addr.isIPv6()) { + return (R) ipv6Op.apply((R2) trie); + } + return null; + } + + /** + * Returns the number of elements in the tries. + * Only added nodes are counted. + * When zero is returned, {@link #isEmpty()} returns true. + * + * @return + */ + public int size() { + return getIPv4Trie().size() + getIPv6Trie().size(); + } + + /** + * Returns true if there are no added nodes within the two tries + */ + public boolean isEmpty() { + return getIPv4Trie().isEmpty() && getIPv6Trie().isEmpty(); + } + + /** + * Adds the given single address or prefix block subnet to one of the two tries. + *

+ * If the given address is not a single address nor prefix block, then this method throws IllegalArgumentException. + *

+ * If not a single address nor prefix block, the {@link Partition} class can be used to convert the address before calling this method. + * Given a subnet s of type E and a trie of type AddressTrie<E>, such as {@link inet.ipaddr.ipv4.IPv4Address} and {@link inet.ipaddr.ipv4.IPv4AddressTrie}, + * you can convert and add the spanning prefix blocks with Partition.partitionWithSpanningBlocks(s).predicateForEach(trie::add), + * or you can convert and add using a single max block size with Partition.partitionWithSingleBlockSize(s).predicateForEach(trie::add). + *

+ * Returns true if the prefix block or address was inserted, false if already in one of the two tries. + * + * @param addr + * @return + */ + public boolean add(IPAddress addr) { + return addressPredicateOp(addr, getIPv4Trie()::add, getIPv6Trie()::add); + } + + /** + * Returns whether the given address or prefix block subnet is in one of the two tries (as an added element). + *

+ * If the given address is not a single address nor prefix block, then this method throws IllegalArgumentException. + *

+ * If not a single address nor prefix block, the {@link Partition} class can be used to convert the address before calling this method. + * See {@link AddressTrieAddOps#add(Address)} for more details. + *

+ * Returns true if the prefix block or address address exists already in one the two tries, false otherwise. + *

+ * Use {@link #getAddedNode(IPAddress)} to get the node for the address rather than just checking for its existence. + * + * @param addr + * @return + */ + public boolean contains(IPAddress addr) { + return addressPredicateOp(addr, getIPv4Trie()::contains, getIPv6Trie()::contains); + } + + /** + * Removes the given single address or prefix block subnet from the tries. + *

+ * Removing an element will not remove contained elements (nodes for contained blocks and addresses). + *

+ * If the given address is not a single address nor prefix block, then this method throws IllegalArgumentException. + *

+ * If not a single address nor prefix block, the {@link Partition} class can be used to convert the address before calling this method. + * See {@link AddressTrieAddOps#add(Address)} for more details. + *

+ * Returns true if the prefix block or address was removed, false if not already in one of the two tries. + *

+ * You can also remove by calling {@link #getAddedNode(IPAddress)} to get the node and then calling {@link BinaryTreeNode#remove()} on the node. + *

+ * When an address is removed, the corresponding node may remain in the trie if it remains a subnet block for two sub-nodes. + * If the corresponding node can be removed from the trie, it will be. + * + * @see #removeElementsContainedBy(IPAddress) + * @param addr + * @return + */ + public boolean remove(IPAddress addr) { + return addressPredicateOp(addr, getIPv4Trie()::remove, getIPv6Trie()::remove); + } + + /** + * Checks if a prefix block subnet or address in ones of the two tries contains the given subnet or address. + *

+ * If the given address is not a single address nor prefix block, then this method throws IllegalArgumentException. + *

+ * If not a single address nor prefix block, the {@link Partition} class can be used to convert the address before calling this method. + * See {@link AddressTrieAddOps#add(Address)} for more details. + *

+ * Returns true if the subnet or address is contained by a trie element, false otherwise. + *

+ * To get all the containing addresses, use {@link #elementsContaining(IPAddress)}. + * + * @param addr + * @return + */ + public boolean elementContains(IPAddress addr) { + return addressPredicateOp(addr, getIPv4Trie()::elementContains, getIPv6Trie()::elementContains); + } + + public TrieNode elementsContaining(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::elementsContaining, getIPv6Trie()::elementsContaining); + } + + public TrieNode elementsContainedBy(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::elementsContainedBy, getIPv6Trie()::elementsContainedBy); + } + + public TrieNode removeElementsContainedBy(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::removeElementsContainedBy, getIPv6Trie()::removeElementsContainedBy); + } + + public TrieNode getAddedNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::getAddedNode, getIPv6Trie()::getAddedNode); + } + + public TrieNode longestPrefixMatchNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::longestPrefixMatchNode, getIPv6Trie()::longestPrefixMatchNode); + } + + public IPAddress longestPrefixMatch(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::longestPrefixMatch, getIPv6Trie()::longestPrefixMatch); + } + + public TrieNode addNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::addNode, getIPv6Trie()::addNode); + } + + public TrieNode addTrie(TrieNode trie) { + return unaryOp(trie, getIPv4Trie()::addTrie, getIPv6Trie()::addTrie); + } + + public TrieNode floorAddedNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::floorAddedNode, getIPv6Trie()::floorAddedNode); + } + + public TrieNode lowerAddedNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::lowerAddedNode, getIPv6Trie()::lowerAddedNode); + } + + public TrieNode ceilingAddedNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::ceilingAddedNode, getIPv6Trie()::ceilingAddedNode); + } + + public TrieNode higherAddedNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::higherAddedNode, getIPv6Trie()::higherAddedNode); + } + + public IPAddress floor(IPAddress addr) { + return AddressTrie.getNodeKey(floorAddedNode(addr)); + } + + public IPAddress lower(IPAddress addr) { + return AddressTrie.getNodeKey(lowerAddedNode(addr)); + } + + public IPAddress ceiling(IPAddress addr) { + return AddressTrie.getNodeKey(ceilingAddedNode(addr)); + } + + public IPAddress higher(IPAddress addr) { + return AddressTrie.getNodeKey(higherAddedNode(addr)); + } + + @SuppressWarnings("unchecked") + @Override + public Iterator iterator() { + Iterator ipv4Iterator = getIPv4Trie().iterator(), ipv6Iterator = getIPv6Trie().iterator(); + return new DualIterator((Iterator) ipv4Iterator, (Iterator) ipv6Iterator, true); + } + + @SuppressWarnings("unchecked") + public Iterator descendingIterator() { + Iterator ipv4Iterator = getIPv4Trie().descendingIterator(), ipv6Iterator = getIPv6Trie().descendingIterator(); + return new DualIterator((Iterator) ipv4Iterator, (Iterator) ipv6Iterator, false); + } + + @SuppressWarnings("unchecked") + @Override + public Spliterator spliterator() { + Spliterator ipv4Iterator = getIPv4Trie().spliterator(), ipv6Iterator = getIPv6Trie().spliterator(); + return new DualSpliterator((Spliterator) ipv4Iterator, (Spliterator) ipv6Iterator); + } + + @SuppressWarnings("unchecked") + public Spliterator descendingSpliterator() { + Spliterator ipv4Iterator = getIPv4Trie().descendingSpliterator(), ipv6Iterator = getIPv6Trie().descendingSpliterator(); + return new DualSpliterator((Spliterator) ipv6Iterator, (Spliterator) ipv4Iterator); + } + + @SuppressWarnings("unchecked") + > Iterator combineNodeIterators( + boolean forward, + Iterator ipv4It, + Iterator ipv6It) { + Iterator ipv4I = (Iterator) ipv4It; + Iterator ipv6I = (Iterator) ipv6It; + return new DualIterator(ipv4I, ipv6I, forward); + } + + @SuppressWarnings("unchecked") + > Iterator combineBlockSizeNodeIterators( + boolean lowerSubNodeFirst, + Iterator ipv4It, + Iterator ipv6It) { + Iterator ipv4I = (Iterator) ipv4It; + Iterator ipv6I = (Iterator) ipv6It; + return new DualBlockSizeIterator(lowerSubNodeFirst, ipv4I, ipv6I); + } + + public abstract Iterator> nodeIterator(boolean forward); + + public abstract Iterator> containingFirstIterator(boolean forwardSubNodeOrder); + + public abstract Iterator> containedFirstIterator(boolean forwardSubNodeOrder); + + public abstract Iterator> blockSizeNodeIterator(boolean lowerSubNodeFirst); + + public abstract Spliterator> nodeSpliterator(boolean forward); + + @SuppressWarnings("unchecked") + > Spliterator combineNodeSpliterators( + boolean forward, + Spliterator ipv4It, + Spliterator ipv6It) { + Spliterator ipv4I = (Spliterator) ipv4It; + Spliterator ipv6I = (Spliterator) ipv6It; + if(forward) { + return new DualSpliterator(ipv4I, ipv6I); + } + return new DualSpliterator(ipv6I, ipv4I); + } + + static class BlockSizeComp implements Comparator { + private final boolean reverseBlocksEqualSize; + + BlockSizeComp(boolean reverseBlocksEqualSize) { + this.reverseBlocksEqualSize = reverseBlocksEqualSize; + } + + @Override + public int compare(E addr1, E addr2) { + if(addr1 == addr2) { + return 0; + } + if(addr1.isPrefixed()) { + if(addr2.isPrefixed()) { + int val = (addr2.getBitCount() - addr2.getPrefixLength()) + - (addr1.getBitCount() - addr1.getPrefixLength()); + if(val == 0) { + int compVal = compareLowValues(addr1, addr2); + return reverseBlocksEqualSize ? -compVal : compVal; + } + return val; + } + return -1; + } + if(addr2.isPrefixed()) { + return 1; + } + int compVal = compareLowValues(addr1, addr2); + return reverseBlocksEqualSize ? -compVal : compVal; + } + }; + + static int compareLowValues(Address one, Address two) { + return Address.ADDRESS_LOW_VALUE_COMPARATOR.compare(one, two); + } + + static final Comparator BLOCK_SIZE_COMP = new BlockSizeComp<>(false), REVERSE_BLOCK_SIZE_COMP = new BlockSizeComp<>(true); + + class BaseDualIterator { + Change ipv4CurrentChange, ipv6CurrentChange; + + BaseDualIterator() { + if(ipv4Tracker != null) { + ipv4CurrentChange = ipv4Tracker.getCurrent(); + } + if(ipv6Tracker != null) { + ipv6CurrentChange = ipv6Tracker.getCurrent(); + } + } + + void changedSince() { + if(ipv4Tracker != null) { + ipv4Tracker.changedSince(ipv4CurrentChange); + } + if(ipv6Tracker != null) { + ipv6Tracker.changedSince(ipv6CurrentChange); + } + } + } + + class DualBlockSizeIterator> extends BaseDualIterator implements Iterator { + T ipv4Item, ipv6Item; + Iterator ipv4Iterator, ipv6Iterator; + T lastItem; + Comparator comp; + + @SuppressWarnings("unchecked") + DualBlockSizeIterator(boolean lowerSubNodeFirst, Iterator ipv4Iterator, Iterator ipv6Iterator) { + boolean reverseBlocksEqualSize = !lowerSubNodeFirst; + comp = (Comparator) (reverseBlocksEqualSize ? REVERSE_BLOCK_SIZE_COMP : BLOCK_SIZE_COMP); + this.ipv4Iterator = ipv4Iterator; + this.ipv6Iterator = ipv6Iterator; + } + + @Override + public boolean hasNext() { + return ipv4Item != null || ipv6Item != null || ipv4Iterator.hasNext() || ipv6Iterator.hasNext(); + } + + @Override + public T next() { + if(hasNext()) { + changedSince(); + } else { + throw new NoSuchElementException(); + } + + // replace whatever was returned previously + if(ipv4Item == null && ipv4Iterator.hasNext()) { + ipv4Item = ipv4Iterator.next(); + } + if(ipv6Item == null && ipv6Iterator.hasNext()) { + ipv6Item = ipv6Iterator.next(); + } + + T result; + + // now return the lowest of the two + if(ipv4Item == null) { + result = lastItem = ipv6Item; + ipv6Item = null; + } else if(ipv6Item == null) { + result = lastItem = ipv4Item; + ipv4Item = null; + } else { + int cmp = comp.compare(ipv4Item.getKey(), ipv6Item.getKey()); + if(cmp < 0) { + result = lastItem = ipv4Item; + ipv4Item = null; + } else { + result = lastItem = ipv6Item; + ipv6Item = null; + } + } + return result; + } + + @Override + public void remove() { + if(lastItem == null) { + throw new IllegalStateException(); + } + changedSince(); + if(lastItem.getKey().isIPv4()) { + ipv4Iterator.remove(); + ipv4CurrentChange = ipv4Tracker.getCurrent(); + } else { + ipv6Iterator.remove(); + ipv6CurrentChange = ipv6Tracker.getCurrent(); + } + lastItem = null; + } + } + + class DualIterator extends BaseDualIterator implements Iterator { + private Iterator current; // always points to the previously-used iterator, so that "remove" works as intended, and any caching functionality as well + private Iterator first, last; + private boolean firstIsIPv4; + + DualIterator(Iterator ipv4Iterator, Iterator ipv6Iterator, boolean forward) { + if(forward) { + this.first = ipv4Iterator; + this.last = ipv6Iterator; + } else { + this.first = ipv6Iterator; + this.last = ipv4Iterator; + } + current = first; + firstIsIPv4 = forward; + } + + @Override + public boolean hasNext() { + if(current == last) { + return last.hasNext(); + } + return current.hasNext() || last.hasNext(); + } + + @Override + public T next() { + if(current != last && !first.hasNext()) { + current = last; + } + + // note that the next element is always pre-prepared for + // all iterator subtypes of AbstractNodeIterator + // so that means we know we can trust the result of hasNext + // even when the trie has been changed. + if(current.hasNext()) { + //TODO you really only need to check the non-current here, since current.next() checks the current, but is this optimization worth the bother? I suppose + changedSince(); + } + return current.next(); + } + + @Override + public void remove() { + changedSince(); + + current.remove(); + + if(current == first ? firstIsIPv4 : !firstIsIPv4) { + if(ipv4Tracker != null) { + ipv4CurrentChange = ipv4Tracker.getCurrent(); + } + } else if(ipv6Tracker != null) { + ipv6CurrentChange = ipv6Tracker.getCurrent(); + } + } + } + + class DualSpliterator extends BaseDualIterator implements Spliterator { + // before the first split we use first and second, + // after that we use current + Spliterator first, second, current; + + DualSpliterator(Spliterator first, Spliterator second) { + this.first = first; + this.second = second; + } + + @Override + public boolean tryAdvance(Consumer action) { + changedSince(); + if(current == null) { + if(first.tryAdvance(action)) { + return true; + } + return second.tryAdvance(action); + } + return current.tryAdvance(action); + } + + @Override + public Spliterator trySplit() { + changedSince(); + if(current == null) { + current = second; + return first; + } + return current.trySplit(); + } + + @Override + public void forEachRemaining(Consumer action) { + changedSince(); + if(current == null) { + current = second; + first.forEachRemaining(action); + second.forEachRemaining(action); + } else { + current.forEachRemaining(action); + } + } + + @Override + public long estimateSize() { + if(current == null) { + return first.estimateSize() + second.estimateSize(); + } + return current.estimateSize(); + } + + @Override + public int characteristics() { + if(current == null) { + return first.characteristics() & second.characteristics(); + } + return current.characteristics(); + } + } +} diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/BinaryTreeNode.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/BinaryTreeNode.java index 27b2da48..46ef2b7e 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/BinaryTreeNode.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/BinaryTreeNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,7 +60,7 @@ * * @param */ -public class BinaryTreeNode implements TreeOps, Cloneable, Serializable { +public class BinaryTreeNode implements TreeOps { private static final long serialVersionUID = 1L; @@ -599,12 +599,12 @@ public boolean isAdded() { */ public void setAdded() { if(!added) { - setAdded(true); + setNodeAdded(true); adjustCount(1); } } - protected void setAdded(boolean added) { + protected void setNodeAdded(boolean added) { this.added = added; } @@ -684,7 +684,7 @@ public void remove() { void removed() { adjustCount(-1); - setAdded(false); + setNodeAdded(false); changeTracker.changed(); } @@ -737,7 +737,7 @@ private void adjustTree(BinaryTreeNode parent, BinaryTreeNode replacement, protected void replaceThisRoot(BinaryTreeNode replacement) { if(replacement == null) { - setAdded(false); + setNodeAdded(false); setUpper(null); setLower(null); if(!FREEZE_ROOT) { @@ -746,7 +746,7 @@ protected void replaceThisRoot(BinaryTreeNode replacement) { size = 0; } else { // We never go here when FREEZE_ROOT is true - setAdded(replacement.isAdded()); + setNodeAdded(replacement.isAdded()); setUpper(replacement.getUpperSubNode()); setLower(replacement.getLowerSubNode()); setKey(replacement.getKey()); @@ -1182,7 +1182,7 @@ Iterator> blockSizeNodeIterator(boolean lowerSubNode } @Override - public CachingIterator, E, C> containingFirstIterator(boolean forwardSubNodeOrder) { + public Iterator> containingFirstIterator(boolean forwardSubNodeOrder) { return containingFirstIterator(forwardSubNodeOrder, true); } @@ -1982,7 +1982,7 @@ BinaryTreeNode toNext(BinaryTreeNode current) { @Override public void remove() { - if (current == null) { + if(current == null) { throw new IllegalStateException(getMessage("ipaddress.error.no.iterator.element.to.remove")); } ChangeTracker changeTracker = this.changeTracker; @@ -2157,11 +2157,12 @@ private NodeIterator provideIterator() { iter = createIterator(); iterator = iter; } - return iterator; + return iter; } @Override public boolean tryAdvance(Consumer> action) { + // change tracking exception handled by iterator BinaryTreeNode next = provideIterator().nextNoThrow(); if(next != null) { action.accept(next); @@ -2174,6 +2175,7 @@ public boolean tryAdvance(Consumer> action) { @Override public void forEachRemaining(Consumer> action) { + // change tracking exception handled by iterator BinaryTreeNode next = provideIterator().nextNoThrow(); if(next != null) { action.accept(next); @@ -2322,7 +2324,7 @@ void printTree(StringBuilder builder, subNodeIndent = cached.subNodeInd; } if(withNonAdded || next.isAdded()) { - builder.append(nodeIndent).append(next); + builder.append(nodeIndent).append(next); // appending next adds the ADDED_NODE_CIRCLE first if(withSizes) { builder.append(" (").append(next.size()).append(')'); } @@ -2486,7 +2488,7 @@ BinaryTreeNode cloneTree(ChangeTracker changeTracker, Bounds bounds) { return rootClone; } - BinaryTreeNode cloneTree(Bounds bounds) { + BinaryTreeNode cloneTreeBounds(Bounds bounds) { return cloneTree(new ChangeTracker(), bounds); } @@ -2495,7 +2497,7 @@ BinaryTreeNode cloneTree(Bounds bounds) { * The nodes are cloned, but their keys and values are not cloned. */ public BinaryTreeNode cloneTree() { - return cloneTree(null); + return cloneTreeBounds(null); } /** diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/DualIPv4v6AssociativeTries.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/DualIPv4v6AssociativeTries.java new file mode 100644 index 00000000..0369afa2 --- /dev/null +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/DualIPv4v6AssociativeTries.java @@ -0,0 +1,234 @@ +/* + * Copyright 2024 Sean C Foley + * + * Licensed 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * or at + * https://github.com/seancfoley/IPAddress/blob/master/LICENSE + * + * 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 inet.ipaddr.format.util; + +import java.util.Iterator; +import java.util.Spliterator; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +import inet.ipaddr.IPAddress; +import inet.ipaddr.format.util.AddressTrie.TrieNode; +import inet.ipaddr.format.util.AssociativeAddressTrie.AssociativeTrieNode; +import inet.ipaddr.ipv4.IPv4Address; +import inet.ipaddr.ipv4.IPv4AddressAssociativeTrie; +import inet.ipaddr.ipv6.IPv6Address; +import inet.ipaddr.ipv6.IPv6AddressAssociativeTrie; + + +/** + * Combines an IPv4 with an IPv6 associative trie to map both IPv4 and IPv6 addresses and prefix blocks. + * + * For a tree that is either IPv4 or IPv6, one or the other, you can just use #{@link AssociativeAddressTrie}. + * + * Another alternative to this data structure is to use a single IPv6 trie, while mapping IPv4 addresses to IPv6 with the default IPv4-mapped address mapping, or some other mapping. + * + * @author scfoley + * + */ +public class DualIPv4v6AssociativeTries extends BaseDualIPv4v6Tries, IPv6AddressAssociativeTrie> { + + private static final long serialVersionUID = 1L; + + private IPv6AddressAssociativeTrie ipv6Trie; + private IPv4AddressAssociativeTrie ipv4Trie; + + public DualIPv4v6AssociativeTries() { + this(new IPv4AddressAssociativeTrie(), new IPv6AddressAssociativeTrie()); + } + + public DualIPv4v6AssociativeTries(IPv4AddressAssociativeTrie ipv4Trie, IPv6AddressAssociativeTrie ipv6Trie) { + super(ipv4Trie, ipv6Trie); + this.ipv4Trie = ipv4Trie; + this.ipv6Trie = ipv6Trie; + } + + @Override + public DualIPv4v6AssociativeTries clone() { + DualIPv4v6AssociativeTries result = (DualIPv4v6AssociativeTries) super.clone(); + result.ipv4Trie = ipv4Trie.clone(); + result.ipv6Trie = ipv6Trie.clone(); + result.assignTrackers(result.ipv4Trie, result.ipv6Trie); + return result; + } + + @Override + public IPv4AddressAssociativeTrie getIPv4Trie() { + return ipv4Trie; + } + + @Override + public IPv6AddressAssociativeTrie getIPv6Trie() { + return ipv6Trie; + } + + @Override + public AssociativeTrieNode elementsContaining(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::elementsContaining, getIPv6Trie()::elementsContaining); + } + + @Override + public AssociativeTrieNode elementsContainedBy(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::elementsContainedBy, getIPv6Trie()::elementsContainedBy); + } + + @Override + public AssociativeTrieNode removeElementsContainedBy(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::removeElementsContainedBy, getIPv6Trie()::removeElementsContainedBy); + } + + @Override + public AssociativeTrieNode getAddedNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::getAddedNode, getIPv6Trie()::getAddedNode); + } + + @Override + public AssociativeTrieNode longestPrefixMatchNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::longestPrefixMatchNode, getIPv6Trie()::longestPrefixMatchNode); + } + + @Override + public AssociativeTrieNode addNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::addNode, getIPv6Trie()::addNode); + } + + @Override + public AssociativeTrieNode addTrie(TrieNode trie) { + return unaryOp(trie, getIPv4Trie()::addTrie, getIPv6Trie()::addTrie); + } + + @Override + public AssociativeTrieNode floorAddedNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::floorAddedNode, getIPv6Trie()::floorAddedNode); + } + + @Override + public AssociativeTrieNode lowerAddedNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::lowerAddedNode, getIPv6Trie()::lowerAddedNode); + } + + @Override + public AssociativeTrieNode ceilingAddedNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::ceilingAddedNode, getIPv6Trie()::ceilingAddedNode); + } + + @Override + public AssociativeTrieNode higherAddedNode(IPAddress addr) { + return addressFuncOp(addr, getIPv4Trie()::higherAddedNode, getIPv6Trie()::higherAddedNode); + } + + @Override + public Iterator> nodeIterator(boolean forward) { + return combineNodeIterators(forward, getIPv4Trie().nodeIterator(forward), getIPv6Trie().nodeIterator(forward)); + } + + @Override + public Iterator> containingFirstIterator(boolean forwardSubNodeOrder) { + return combineNodeIterators(forwardSubNodeOrder, getIPv4Trie().containingFirstIterator(forwardSubNodeOrder), getIPv6Trie().containingFirstIterator(forwardSubNodeOrder)); + } + + @Override + public Iterator> containedFirstIterator(boolean forwardSubNodeOrder) { + return combineNodeIterators(forwardSubNodeOrder, getIPv4Trie().containedFirstIterator(forwardSubNodeOrder), getIPv6Trie().containedFirstIterator(forwardSubNodeOrder)); + } + + @Override + public Iterator> blockSizeNodeIterator(boolean lowerSubNodeFirst) { + return combineBlockSizeNodeIterators(lowerSubNodeFirst, getIPv4Trie().blockSizeNodeIterator(lowerSubNodeFirst), getIPv6Trie().blockSizeNodeIterator(lowerSubNodeFirst)); + } + + @Override + public Spliterator> nodeSpliterator(boolean forward) { + return combineNodeSpliterators(forward, getIPv4Trie().nodeSpliterator(forward), getIPv6Trie().nodeSpliterator(forward)); + } + + public AssociativeTrieNode addTrie(AssociativeTrieNode trie) { + return DualIPv4v6Tries.unaryOp(trie, getIPv4Trie()::addTrie, getIPv6Trie()::addTrie); + } + + public V get(IPAddress addr) { + return DualIPv4v6Tries.addressFuncOp(addr, getIPv4Trie()::get, getIPv6Trie()::get); + } + + public V put(IPAddress addr, V value) { + return DualIPv4v6Tries.addressValValBiFuncOp(addr, value, getIPv4Trie()::put, getIPv6Trie()::put); + } + + public boolean putNew(IPAddress addr, V value) { + return DualIPv4v6Tries.addressValBiFuncOp(addr, value, getIPv4Trie()::putNew, getIPv6Trie()::putNew); + } + + public AssociativeTrieNode putNode(IPAddress addr, V value) { + return DualIPv4v6Tries.addressValBiFuncOp(addr, value, getIPv4Trie()::putNode, getIPv6Trie()::putNode); + } + + public AssociativeTrieNode putTrie(AssociativeTrieNode trie) { + return DualIPv4v6Tries.unaryOp(trie, getIPv4Trie()::putTrie, getIPv6Trie()::putTrie); + } + + public AssociativeTrieNode remap(IPAddress addr, Function remapper) { + return addressFuncOp(addr, remapper, getIPv4Trie()::remap, getIPv6Trie()::remap); + } + + public AssociativeTrieNode remapIfAbsent(IPAddress addr, Supplier remapper, boolean insertNull) { + return addressFuncBoolOp(addr, remapper, insertNull, getIPv4Trie()::remapIfAbsent, getIPv6Trie()::remapIfAbsent); + } + + static T addressFuncOp( + IPAddress addr, + F remapper, + BiFunction ipv4Op, + BiFunction ipv6Op) { + if(addr.isIPv4()) { + return ipv4Op.apply(addr.toIPv4(), remapper); + } else if(addr.isIPv6()) { + return ipv6Op.apply(addr.toIPv6(), remapper); + } + return null; + } + + static T addressFuncBoolOp( + IPAddress addr, + F remapper, + boolean insertNull, + TriBoolFunction ipv4Op, + TriBoolFunction ipv6Op) { + if(addr.isIPv4()) { + return ipv4Op.apply(addr.toIPv4(), remapper, insertNull); + } else if(addr.isIPv6()) { + return ipv6Op.apply(addr.toIPv6(), remapper, insertNull); + } + return null; + } + + @FunctionalInterface + public interface TriBoolFunction { + + /** + * Applies this function to the given arguments. + * + * @param t the first function argument + * @param u the second function argument + * @param b the third function argument + * @return the function result + */ + R apply(T t, U u, boolean b); + } +} diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/DualIPv4v6Tries.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/DualIPv4v6Tries.java new file mode 100644 index 00000000..581de847 --- /dev/null +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/DualIPv4v6Tries.java @@ -0,0 +1,99 @@ +/* + * Copyright 2024 Sean C Foley + * + * Licensed 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * or at + * https://github.com/seancfoley/IPAddress/blob/master/LICENSE + * + * 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 inet.ipaddr.format.util; + +import java.util.Iterator; +import java.util.Spliterator; + +import inet.ipaddr.IPAddress; +import inet.ipaddr.format.util.AddressTrie.TrieNode; +import inet.ipaddr.ipv4.IPv4AddressTrie; +import inet.ipaddr.ipv6.IPv6AddressTrie; + +/** + * Combines an IPv4 with an IPv6 trie to store both IPv4 and IPv6 addresses and prefix blocks. + * + * For a tree that is either IPv4 or IPv6, one or the other, you can just use #{@link AddressTrie}. + * + * Another alternative to this data structure is to use a single IPv6 trie, while mapping IPv4 addresses to IPv6 with the default IPv4-mapped address mapping, or some other mapping. + * + * @author scfoley + * + */ +public class DualIPv4v6Tries extends BaseDualIPv4v6Tries { + + private static final long serialVersionUID = 1L; + + private IPv6AddressTrie ipv6Trie; + private IPv4AddressTrie ipv4Trie; + + public DualIPv4v6Tries() { + this(new IPv4AddressTrie(), new IPv6AddressTrie()); + } + + public DualIPv4v6Tries(IPv4AddressTrie ipv4Trie, IPv6AddressTrie ipv6Trie) { + super(ipv4Trie, ipv6Trie); + this.ipv4Trie = ipv4Trie; + this.ipv6Trie = ipv6Trie; + } + + @Override + public IPv4AddressTrie getIPv4Trie() { + return ipv4Trie; + } + + @Override + public IPv6AddressTrie getIPv6Trie() { + return ipv6Trie; + } + + @Override + public DualIPv4v6Tries clone() { + DualIPv4v6Tries result = (DualIPv4v6Tries) super.clone(); + result.ipv4Trie = ipv4Trie.clone(); + result.ipv6Trie = ipv6Trie.clone(); + result.assignTrackers(result.ipv4Trie, result.ipv6Trie); + return result; + } + + @Override + public Iterator> nodeIterator(boolean forward) { + return combineNodeIterators(forward, getIPv4Trie().nodeIterator(forward), getIPv6Trie().nodeIterator(forward)); + } + + @Override + public Iterator> containingFirstIterator(boolean forwardSubNodeOrder) { + return combineNodeIterators(forwardSubNodeOrder, getIPv4Trie().containingFirstIterator(forwardSubNodeOrder), getIPv6Trie().containingFirstIterator(forwardSubNodeOrder)); + } + + @Override + public Iterator> containedFirstIterator(boolean forwardSubNodeOrder) { + return combineNodeIterators(forwardSubNodeOrder, getIPv4Trie().containedFirstIterator(forwardSubNodeOrder), getIPv6Trie().containedFirstIterator(forwardSubNodeOrder)); + } + + @Override + public Iterator> blockSizeNodeIterator(boolean lowerSubNodeFirst) { + return combineBlockSizeNodeIterators(lowerSubNodeFirst, getIPv4Trie().blockSizeNodeIterator(lowerSubNodeFirst), getIPv6Trie().blockSizeNodeIterator(lowerSubNodeFirst)); + } + + @Override + public Spliterator> nodeSpliterator(boolean forward) { + return combineNodeSpliterators(forward, getIPv4Trie().nodeSpliterator(forward), getIPv6Trie().nodeSpliterator(forward)); + } +} diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/TreeOps.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/TreeOps.java index 625aaa52..028766d8 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/TreeOps.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/TreeOps.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -146,6 +146,8 @@ public interface TreeOps extends Iterable, Serializable, Cloneable { /** * Traverses the added node keys in natural tree order. *

+ * This iterator supports the {@link java.util.Iterator#remove()} operation. + *

* See {@link TreeOps} for more details on the ordering. * * @return @@ -156,6 +158,8 @@ public interface TreeOps extends Iterable, Serializable, Cloneable { /** * Traverses the added node keys in reverse natural tree order. *

+ * This iterator supports the {@link java.util.Iterator#remove()} operation. + *

* See {@link TreeOps} for more details on the ordering. * @return */ @@ -217,23 +221,12 @@ default Spliterator descendingSpliterator() { *

* This iterator supports the {@link java.util.Iterator#remove()} operation. *

- * Once a given node is visited, the iterator allows you to cache an object corresponding to the - * lower or upper sub-node that can be retrieved when you later visit that sub-node. - *

- * Objects are cached only with nodes to be visited. - * So for this iterator that means an object will be cached with the first added lower or upper sub-node, - * the next lower or upper sub-node to be visited, - * which is not necessarily the direct lower or upper sub-node of a given node. - *

- * The caching allows you to provide iteration context from a parent to its sub-nodes when iterating. - * The caching and retrieval is done in constant-time and linear space (proportional to tree size). - *

- * See {@link TreeOps} for more details on the ordering. + * See the docs for {@link TreeOps} for more details on the ordering. * * @param forwardSubNodeOrder if true, a left sub-node will be visited before the right sub-node of the same parent node. * @return */ - CachingIterator, E, C> containingFirstIterator(boolean forwardSubNodeOrder); + Iterator> containingFirstIterator(boolean forwardSubNodeOrder); /** * Returns an iterator that does a pre-order binary tree traversal. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/package-info.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/package-info.java index 280171d6..c0a25b17 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/package-info.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/util/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Sean C Foley + * Copyright 2018-2019 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/AddressParseData.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/AddressParseData.java index 231be4c5..79fa029f 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/AddressParseData.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/AddressParseData.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/HostIdentifierStringValidator.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/HostIdentifierStringValidator.java index 4c0bcc45..c62d7279 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/HostIdentifierStringValidator.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/HostIdentifierStringValidator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/IPAddressProvider.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/IPAddressProvider.java index 2faa2e43..5b3be085 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/IPAddressProvider.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/IPAddressProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedAddressCreator.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedAddressCreator.java index 34e575d2..dd5cd387 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedAddressCreator.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedAddressCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2020 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedAddressGrouping.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedAddressGrouping.java index d554d5c5..fa0c29d8 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedAddressGrouping.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedAddressGrouping.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Sean C Foley + * Copyright 2018-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,25 @@ public static int getHostSegmentIndex(int networkPrefixLength, int bytesPerSegme } return networkPrefixLength >> 3; } + + /** + * Returns the total number of bits for the given segment count, with each segment having the given number of bits. + * The number of bytes must correspond to the number of bits. + * + * @param segmentCount + * @param bytesPerSegment + * @param bitsPerSegment + * @return + */ + public static int getTotalBits(int segmentCount, int bytesPerSegment, int bitsPerSegment) { + if(bytesPerSegment != 1) { + if(bytesPerSegment == 2) { + return segmentCount << 4; + } + return segmentCount * bitsPerSegment; + } + return segmentCount << 3; + } /** * Across an address prefixes are: diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedHost.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedHost.java index f92e148b..bb76d594 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedHost.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedHost.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2019 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedHostIdentifierStringQualifier.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedHostIdentifierStringQualifier.java index 5ccbac5c..82a20611 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedHostIdentifierStringQualifier.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedHostIdentifierStringQualifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedIPAddress.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedIPAddress.java index 5316906a..7b5be438 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedIPAddress.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedIPAddress.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Sean C Foley + * Copyright 2018-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedMACAddress.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedMACAddress.java index 1d203bc4..cdcc7dff 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedMACAddress.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/ParsedMACAddress.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Sean C Foley + * Copyright 2018-2020 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/Validator.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/Validator.java index 045084b1..ffd5dd41 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/Validator.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/format/validate/Validator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -224,6 +224,21 @@ private static boolean isSingleSegmentIPv4( return backIsIpv4; } + private static boolean hasExtraneousDigitsIPv4( + final IPAddressStringParameters validationOptions, + final IPv4AddressStringParameters ipv4SpecificOptions, + final IPv6AddressStringParameters ipv6SpecificOptions, + final int totalDigits) { + if(!ipv4SpecificOptions.inet_aton_extraneous_digits) { + return false; + } else if(!validationOptions.allowIPv6) { + return true; // any number of digits is allowed when IPv6 is ruled out + } else if(!ipv6SpecificOptions.allowBase85) { + return totalDigits < IPV6_SINGLE_SEGMENT_DIGIT_COUNT; + } + return totalDigits < IPV6_BASE85_SINGLE_SEGMENT_DIGIT_COUNT; + } + /** * This method is the mega-parser. * It is designed to go through the characters one-by-one as a big if/else. @@ -276,7 +291,7 @@ private static void validateAddress( stringFormatParams = null; parseData = ipAddressParseData.getAddressParseData(); ipv6SpecificOptions = validationOptions.getIPv6Parameters(); - canBeBase85 = ipv6SpecificOptions.allowBase85; + canBeBase85 = ipv6SpecificOptions.allowBase85 && validationOptions.allowIPv6; ipv4SpecificOptions = validationOptions.getIPv4Parameters(); } @@ -443,19 +458,20 @@ private static void validateAddress( throw new AddressStringException(str, "ipaddress.error.single.segment"); } - boolean isRange = rangeWildcardIndex >= 0; - if(isSingleSegmentIPv6(totalDigits, isRange, frontTotalDigits, ipv6SpecificOptions)) { - // we are not base 85, so throw if necessary - if(extendedCharacterIndex >= 0) { - throw new AddressStringException(str, extendedCharacterIndex); - } - isSingleIPv6 = true; - currentChar = IPv6Address.SEGMENT_SEPARATOR; - } else if(canBeBase85 && + if(canBeBase85 && parseBase85(validationOptions, str, strStartIndex, strEndIndex, ipAddressParseData, extendedRangeWildcardIndex, totalCharacterCount, index)) { break; - } else { + } + // we are not base 85, so throw if necessary + if(extendedCharacterIndex >= 0) { + throw new AddressStringException(str, extendedCharacterIndex); + } + boolean isRange = rangeWildcardIndex >= 0; + if(validationOptions.allowIPv6 && isSingleSegmentIPv6(totalDigits, isRange, frontTotalDigits, ipv6SpecificOptions)) { + isSingleIPv6 = true; + currentChar = IPv6Address.SEGMENT_SEPARATOR; + } else if(validationOptions.allowIPv4) { int leadingZeros = leadingZeroCount; if(leadingWithZero) { leadingZeros++; @@ -466,16 +482,62 @@ private static void validateAddress( isRange, frontDigitCount, frontTotalDigits, - ipv4SpecificOptions)) { - // we are not base 85, so throw if necessary - if(extendedCharacterIndex >= 0) { - throw new AddressStringException(str, extendedCharacterIndex); - } + ipv4SpecificOptions)) { // < 12 digits, or binary allowed and 32 digits currentChar = IPv4Address.SEGMENT_SEPARATOR; + } else if(hasExtraneousDigitsIPv4(validationOptions, ipv4SpecificOptions, ipv6SpecificOptions, totalDigits)) { + if(singleWildcardCount > 0 || wildcardCount > 0) { + throw new AddressStringException("ipaddress.error.invalid.character"); + } else if(isRange) { + throw new AddressStringException(str, rangeWildcardIndex); + } else if(ipAddressParseData.getQualifierIndex() >= 0) { + throw new AddressStringException(str, ipAddressParseData.getQualifierIndex()); + } + + // We cannot allow normal parsing for decimal, because the remainder (mod of max uint32) does not work with the way we shift-store values hex values currentValueHex. + // We cannot allow normal parsing for octal. This is because, even though the way we shift-store hex digits in currentValueHex works for octal, giving us the correct mod of max uint32, + // it does not allow us to check if overflow chars are octal. + // For hex, maybe we could use normal parsing, but we'd need to check for range chars here, we need to disallow them, and the code as written does not. + // So far all radices, we parse extraneous digits here. + int val; + int radix; + if(hexDelimiterIndex >= 0) { + if(!ipv4SpecificOptions.allowLeadingZeros) { + // the '0' preceding the 'x' is not allowed + throw new AddressStringException(str, "ipaddress.error.segment.leading.zeros"); + } else if(!ipv4SpecificOptions.inet_aton_hex) { + throw new AddressStringException(str, "ipaddress.error.ipv4.segment.hex"); + } else if(leadingZeros > 1 && !ipv4SpecificOptions.inet_aton_leading_zeros) { + // the '0' following the 'x' is not allowed + throw new AddressStringException(str, "ipaddress.error.segment.leading.zeros"); + } + radix = 16; + val = parseInt16(str, strEndIndex - 8, strEndIndex); + } else if(leadingZeros > 0 && ipv4SpecificOptions.inet_aton_octal) { + if(!ipv4SpecificOptions.allowLeadingZeros) { + throw new AddressStringException(str, "ipaddress.error.segment.leading.zeros"); + } else if(leadingZeros > 1 && !ipv4SpecificOptions.inet_aton_leading_zeros) { + throw new AddressStringException(str, "ipaddress.error.segment.leading.zeros"); + } + radix = 8; + val = parseInt8(str, strStartIndex, strEndIndex); + } else { + radix = 10; + val = parseInt10(str, strStartIndex, strEndIndex); + } + ipAddressParseData.setVersion(IPVersion.IPV4); + ipAddressParseData.set_has_inet_aton_value(true); + parseData.initSegmentData(1); + parseData.incrementSegmentCount(); + assign3Attributes1Values1Flags(strStartIndex + leadingZeros, strEndIndex, strStartIndex, parseData, 0, val, radix); + parseData.setSingleSegment(); + break; } else { throw new AddressStringException("ipaddress.error.too.few.segments.digit.count"); } + } else { + throw new AddressStringException("ipaddress.error.too.few.segments.digit.count"); } + // single segment IPv4 or single-segment IPv6 isSingleSegment = true; parseData.setSingleSegment(); checkCharCounts = false; // counted chars already @@ -539,7 +601,7 @@ private static void validateAddress( ipAddressParseData.setVersion(version = IPVersion.IPV4); stringFormatParams = ipv4SpecificOptions; canBeBase85 = false; - parseData.initSegmentData(IPv4Address.SEGMENT_COUNT); + parseData.initSegmentData(IPv4Address.SEGMENT_COUNT); // there may be 1, 2, 3, or 4 segments, so we allocate for the max isSegmented = true; } else if(ipAddressParseData.getProviderIPVersion().isIPv6()) { //mixed IPv6 address like 1:2:3:4:5:6:1.2.3.4 @@ -1583,9 +1645,6 @@ private static boolean parseBase85( AddressParseData parseData = ipAddressParseData.getAddressParseData(); if(extendedRangeWildcardIndex < 0) { if(totalCharacterCount == IPV6_BASE85_SINGLE_SEGMENT_DIGIT_COUNT) { - if(!validationOptions.allowIPv6) { - throw new AddressStringException(str, "ipaddress.error.ipv6"); - } ipAddressParseData.setVersion(IPVersion.IPV6); BigInteger val = parseBase85(str, strStartIndex, strEndIndex); long value = val.and(LOW_BITS_MASK).longValue(); @@ -1593,7 +1652,7 @@ private static boolean parseBase85( long extendedValue = shift64.longValue(); //note that even with the correct number of digits, we can have a value too large BigInteger shiftMore = shift64.shiftRight(Long.SIZE); - if(!shiftMore.equals(BigInteger.ZERO)) { + if(shiftMore.signum() != 0) { throw new AddressStringException(str, "ipaddress.error.address.too.large"); } parseData.initSegmentData(1); @@ -1606,9 +1665,6 @@ private static boolean parseBase85( if(totalCharacterCount == (IPV6_BASE85_SINGLE_SEGMENT_DIGIT_COUNT << 1) + 1 /* two base 85 addresses */ || (totalCharacterCount == IPV6_BASE85_SINGLE_SEGMENT_DIGIT_COUNT + 1 && (extendedRangeWildcardIndex == 0 || extendedRangeWildcardIndex + 1 == strEndIndex)) /* inferred boundary */) {/* note that we already check that extendedRangeWildcardIndex is at index 20 */ - if(!validationOptions.allowIPv6) { - throw new AddressStringException(str, "ipaddress.error.ipv6"); - } IPv6AddressStringParameters ipv6SpecificOptions = validationOptions.getIPv6Parameters(); if(!ipv6SpecificOptions.rangeOptions.allowsRangeSeparator()) { throw new AddressStringException(str, "ipaddress.error.no.range"); @@ -1634,7 +1690,7 @@ private static boolean parseBase85( BigInteger shiftMoreVal = shift64.shiftRight(Long.SIZE); if(!ipv6SpecificOptions.rangeOptions.allowsReverseRange()) { throw new AddressStringException(str, "ipaddress.error.invalidRange"); - } else if(!shiftMoreVal.equals(BigInteger.ZERO)) { + } else if(shiftMoreVal.signum() != 0) { throw new AddressStringException(str, "ipaddress.error.address.too.large"); } lowerStart = frontEndIndex + 1; @@ -1642,7 +1698,7 @@ private static boolean parseBase85( upperStart = strStartIndex; upperEnd = frontEndIndex; } else { - if(!shiftMoreVal2.equals(BigInteger.ZERO)) { + if(shiftMoreVal2.signum() != 0) { throw new AddressStringException(str, "ipaddress.error.address.too.large"); } lowerStart = strStartIndex; @@ -1672,7 +1728,7 @@ private static boolean parseBase85( BigInteger shift64 = val2.shiftRight(Long.SIZE); extendedValue2 = shift64.longValue(); BigInteger shiftMoreVal2 = shift64.shiftRight(Long.SIZE); - if(!shiftMoreVal2.equals(BigInteger.ZERO)) { + if(shiftMoreVal2.signum() != 0) { throw new AddressStringException(str, "ipaddress.error.address.too.large"); } upperStart = 1; @@ -1892,18 +1948,24 @@ private static void checkSegments( final IPv4AddressStringParameters ipv4Options = validationOptions.getIPv4Parameters(); boolean hasWildcardSeparator = addressParseData.hasWildcard() && ipv4Options.allowWildcardedSeparator; + boolean hasMissingSegs = false; + //single segments are handled in the parsing code with the allowSingleSegment setting - if(missingCount > 0 && segCount > 1) { - if(ipv4Options.inet_aton_joinedSegments) { - parseData.set_inet_aton_joined(true); - } else if(!hasWildcardSeparator) { - throw new AddressStringException(fullAddr, "ipaddress.error.ipv4.too.few.segments"); + if(missingCount > 0) { + if(segCount > 1) { + if(ipv4Options.inet_aton_joinedSegments) { + hasMissingSegs = true; + parseData.set_inet_aton_joined(true); + } else if(!hasWildcardSeparator) { + throw new AddressStringException(fullAddr, "ipaddress.error.ipv4.too.few.segments"); + } + } else { + hasMissingSegs = ipv4Options.inet_aton_joinedSegments; } } //here we check whether values are too large boolean notUnlimitedLength = !ipv4Options.allowUnlimitedLeadingZeros; - boolean hasMissingSegs = missingCount > 0 && ipv4Options.inet_aton_joinedSegments; for(int i = 0; i < segCount; i++) { long max; if(hasMissingSegs && i == segCount - 1) { @@ -2969,23 +3031,35 @@ private static long parseLong2(CharSequence s, int start, int end) { } return result; } - - @SuppressWarnings("unused") - private static long parseLong8(CharSequence s, int start, int end) { + + private static int parseInt8(CharSequence s, int start, int end) throws AddressStringException { int charArray[] = chars; - long result = charArray[s.charAt(start)]; + int result = charArray[s.charAt(start)]; + if(result >= 8) { + throw new AddressStringException(s, "ipaddress.error.ipv4.invalid.octal.digit"); + } while (++start < end) { - result = (result << 3) | charArray[s.charAt(start)]; - } - return result; + int next = charArray[s.charAt(start)]; + if(next >= 8) { + throw new AddressStringException(s, "ipaddress.error.ipv4.invalid.octal.digit"); + } + result = (result << 3) | next; + } + return result; } - - @SuppressWarnings("unused") - private static long parseLong10(CharSequence s, int start, int end) { + + private static int parseInt10(CharSequence s, int start, int end) throws AddressStringException { int charArray[] = chars; - long result = charArray[s.charAt(start)]; - while (++start < end) { - result = (result * 10) + charArray[s.charAt(start)]; + int result = charArray[s.charAt(start)]; + if(result >= 10) { + throw new AddressStringException(s, "ipaddress.error.ipv4.invalid.decimal.digit"); + } + while(++start < end) { + int next = charArray[s.charAt(start)]; + if(next >= 10) { + throw new AddressStringException(s, "ipaddress.error.ipv4.invalid.decimal.digit"); + } + result = (result * 10) + next; } return result; } @@ -2993,12 +3067,22 @@ private static long parseLong10(CharSequence s, int start, int end) { private static long parseLong16(CharSequence s, int start, int end) { int charArray[] = chars; long result = charArray[s.charAt(start)]; - while (++start < end) { + while(++start < end) { result = (result << 4) | charArray[s.charAt(start)]; } return result; } + private static int parseInt16(CharSequence s, int start, int end) { + int charArray[] = chars; + int result = charArray[s.charAt(start)]; + while(++start < end) { + int next = charArray[s.charAt(start)]; + result = (result << 4) | next; + } + return result; + } + static { BigInteger eightyFive = BigInteger.valueOf(IPv6Address.BASE_85_RADIX); BASE_85_POWERS[0] = BigInteger.ONE; diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4Address.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4Address.java index 9d74db7e..7f1b3cfa 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4Address.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4Address.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package inet.ipaddr.ipv4; +import java.math.BigInteger; import java.net.Inet4Address; import java.util.ArrayList; import java.util.Iterator; @@ -33,18 +34,20 @@ import inet.ipaddr.IPAddress; import inet.ipaddr.IPAddressConverter; import inet.ipaddr.IPAddressSection.IPStringBuilderOptions; +import inet.ipaddr.IPAddressSection.SeriesCreator; import inet.ipaddr.IPAddressSegmentSeries; import inet.ipaddr.IPAddressStringParameters; import inet.ipaddr.IncompatibleAddressException; import inet.ipaddr.PrefixLenException; import inet.ipaddr.format.string.IPAddressStringDivisionSeries; -import inet.ipaddr.format.util.AddressComponentSpliterator; import inet.ipaddr.format.util.AddressComponentRangeSpliterator; +import inet.ipaddr.format.util.AddressComponentSpliterator; import inet.ipaddr.format.util.IPAddressPartStringCollection; import inet.ipaddr.ipv4.IPv4AddressNetwork.IPv4AddressCreator; import inet.ipaddr.ipv4.IPv4AddressSection.IPv4AddressCache; import inet.ipaddr.ipv4.IPv4AddressSection.IPv4StringBuilderOptions; import inet.ipaddr.ipv4.IPv4AddressSection.IPv4StringCollection; +import inet.ipaddr.ipv4.IPv4AddressTrie.IPv4TrieNode.IPv4TrieKeyData; import inet.ipaddr.ipv6.IPv6Address; import inet.ipaddr.ipv6.IPv6Address.IPv6AddressConverter; import inet.ipaddr.ipv6.IPv6AddressNetwork; @@ -84,6 +87,8 @@ public class IPv4Address extends IPAddress implements Iterable { transient IPv4AddressCache addressCache; + private transient IPv4TrieKeyData cachedTrieKeyData; + /** * Constructs an IPv4 address or subnet. * @param segments the address segments @@ -470,6 +475,23 @@ public long upperLongValue() { return getSection().upperLongValue(); } + IPv4TrieKeyData getTrieKeyCache() { + IPv4TrieKeyData keyData = cachedTrieKeyData; + if(keyData == null) { + keyData = new IPv4TrieKeyData(); + Integer prefLen = getPrefixLength(); + keyData.prefixLength = prefLen; + keyData.uint32Val = intValue(); + if(prefLen != null) { + int bits = prefLen; + keyData.nextBitMask32Val = 0x80000000 >>> bits; + keyData.mask32Val = getNetwork().getNetworkMask(bits, false).intValue(); + } + cachedTrieKeyData = keyData; + } + return keyData; + } + /** * Replaces segments starting from startIndex and ending before endIndex with the same number of segments starting at replacementStartIndex from the replacement section * @@ -484,6 +506,19 @@ public IPv4Address replace(int startIndex, int endIndex, IPv4Address replacement return checkIdentity(getSection().replace(startIndex, endIndex, replacement.getSection(), replacementIndex, replacementIndex + (endIndex - startIndex))); } + /** + * Replaces segments starting from startIndex with as many segments as possible from the replacement section + * + * @param startIndex + * @param replacement + * @throws IndexOutOfBoundsException + * @return + */ + public IPv4Address replace(int startIndex, IPv4AddressSection replacement) { + int replacementCount = Math.min(IPv4Address.SEGMENT_COUNT - startIndex, replacement.getSegmentCount()); + return checkIdentity(getSection().replace(startIndex, startIndex + replacementCount, replacement, 0, replacementCount)); + } + @Override public IPv4Address reverseBits(boolean perByte) { return checkIdentity(getSection().reverseBits(perByte)); @@ -721,7 +756,32 @@ public IPv4Address increment(long increment) { public IPv4Address incrementBoundary(long increment) { return checkIdentity(getSection().incrementBoundary(increment)); } + + /** + * Indicates where an address sits relative to the subnet ordering. + *

+ * Equivalent to {@link #enumerate(IPAddress)} but returns a Long rather than a BigInteger. + */ + public Long enumerateIPv4(IPv4Address other){ + return IPv4AddressSection.enumerateIPv4(getSection(), other.getSection()); + } + @Override + public BigInteger enumerate(Address other) { + if(other instanceof IPv4Address) { + return IPv4AddressSection.enumerate(getSection(), other.getSection()); + } + return null; + } + + @Override + public BigInteger enumerate(IPAddress other) { + if(other.isIPv4()) { + return IPv4AddressSection.enumerate(getSection(), other.getSection()); + } + return null; + } + IPv4AddressCreator getAddressCreator() { return getNetwork().getAddressCreator(); } @@ -1039,6 +1099,11 @@ public IPv4Address[] mergeToSequentialBlocks(IPAddress ...addresses) throws Addr return blocks.toArray(new IPv4Address[blocks.size()]); } + @Override + protected SeriesCreator getSequentialSeriesCreator() { + return getAddressCreator()::createSequentialBlockAddress; + } + @Override public Inet4Address toUpperInetAddress() { return (Inet4Address) super.toUpperInetAddress(); diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressAssociativeTrie.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressAssociativeTrie.java index b9e5ee91..ffaa528d 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressAssociativeTrie.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressAssociativeTrie.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -177,8 +177,8 @@ public CachingIterator, IPv4Address, C> blockSize @SuppressWarnings("unchecked") @Override - public CachingIterator, IPv4Address, C> containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator, IPv4Address, C>) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator> containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator>) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") @@ -295,6 +295,11 @@ public IPv4AssociativeTrieNode clone() { public boolean equals(Object o) { return o instanceof IPv4AddressAssociativeTrie.IPv4AssociativeTrieNode && super.equals(o); } + + @Override + protected TrieKeyData getTrieKeyCache(IPv4Address addr) { + return addr.getTrieKeyCache(); + } } @Override @@ -370,8 +375,8 @@ public CachingIterator, IPv4Address, C> blockSize @SuppressWarnings("unchecked") @Override - public CachingIterator, IPv4Address, C> containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator, IPv4Address, C>) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator> containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator>) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressNetwork.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressNetwork.java index 79edd482..a6ce3e97 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressNetwork.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressNetwork.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2020 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressSection.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressSection.java index 36e7c38f..d593fbb3 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressSection.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressSection.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -113,8 +113,8 @@ static class IPv4AddressCache extends SectionCache {} transient IPv4StringCache stringCache; private transient SectionCache sectionCache; - private transient Integer cachedLowerVal; - + private transient Integer cachedLowerVal, cachedUpperVal; + /** * Constructs a single segment section. * @@ -557,7 +557,7 @@ private int calcValue(boolean lower) { private int getIntValue(boolean lower) { int result = 0; - if(lower) { + if(lower || !isMultiple()) { Integer cachedInt = this.cachedLowerVal; if(cachedInt == null) { result = calcValue(true); @@ -566,7 +566,13 @@ private int getIntValue(boolean lower) { result = cachedInt; } } else { - result = calcValue(false); + Integer cachedInt = this.cachedUpperVal; + if(cachedInt == null) { + result = calcValue(false); + this.cachedUpperVal = result; + } else { + result = cachedInt; + } } return result; } @@ -1204,17 +1210,14 @@ public IPv4AddressSection increment(long increment) { if(increment == 0 && !isMultiple()) { return this; } - long lowerValue = 0xffffffffL & intValue(); - long upperValue = 0xffffffffL & upperIntValue(); - long count = getCount().longValue(); - checkOverflow(increment, lowerValue, upperValue, count, () -> getMaxValue(getSegmentCount())); + checkOverflow(increment, this::longValue, this::upperLongValue, () -> getCount().longValue(), this::isSequential, () -> getMaxValue(getSegmentCount())); return increment( this, increment, getAddressCreator(), - count, - lowerValue, - upperValue, + () -> getCount().longValue(), + this::longValue, + this::upperLongValue, this::getLower, this::getUpper, getNetwork().getPrefixConfiguration().allPrefixedAddressesAreSubnets() ? null : getPrefixLength()); @@ -1359,7 +1362,12 @@ protected boolean isSameGrouping(AddressDivisionGroupingBase other) { public boolean equals(Object o) { return o == this || (o instanceof IPv4AddressSection && ((IPv4AddressSection) o).isSameGrouping(this)); } - + + @Override + public boolean overlaps(AddressSection other) { + return other instanceof IPv4AddressSection && overlaps(this, other); + } + @Override public boolean contains(AddressSection other) { return other instanceof IPv4AddressSection && super.contains(other); @@ -1386,6 +1394,41 @@ protected boolean containsNonZeroHostsImpl(IPAddressSection other, int otherPref return false; } + static BigInteger enumerate(IPv4AddressSection addr, AddressSection other) { + Long result = enumerateSmall(addr, other); + if(result != null) { + return BigInteger.valueOf(result); + } + return null; + } + + /** + * Indicates where an address section sits relative to the ordering of individual address sections within this section. + *

+ * Equivalent to {@link #enumerate(AddressSection)} but returns a Long rather than a BigInteger. + */ + public Long enumerateIPv4(IPv4AddressSection other){ + checkSegmentCount(other); + return enumerateSmall(this, other); + } + + // called by addresses + static Long enumerateIPv4(IPv4AddressSection addr, AddressSection other) { + return enumerateSmall(addr, other); + } + + @Override + public BigInteger enumerate(AddressSection other) { + if(other instanceof IPv4AddressSection) { + checkSegmentCount(other); + Long result = enumerateSmall(this, other); + if(result != null) { + return BigInteger.valueOf(result); + } + } + return null; + } + @Override public boolean prefixEquals(AddressSection other) { return other == this || (other instanceof IPv4AddressSection && prefixEquals(this, other, 0)); @@ -1517,7 +1560,7 @@ private IPv4AddressSection replace(int startIndex, int endIndex, IPv4AddressSect } /** - * Produces the subnet sections whose addresses are found in both this and the given argument. + * Produces the subnet sections whose individual sections are found in both this and the given argument. *

* This is also known as the conjunction of the two sets of address sections. *

@@ -1736,7 +1779,7 @@ public IPv4AddressSection toMaxHost(int prefixLength) { * @throws IncompatibleAddressException */ public IPv4AddressSection mask(IPv4AddressSection mask, boolean retainPrefix) throws IncompatibleAddressException, SizeMismatchException { - checkMaskSectionCount(mask); + checkMaskSegmentCount(mask); return getSubnetSegments( this, retainPrefix ? getPrefixLength() : null, @@ -1765,7 +1808,7 @@ public IPv4AddressSection mask(IPv4AddressSection mask) throws IncompatibleAddre * @throws IncompatibleAddressException */ public IPv4AddressSection maskNetwork(IPv4AddressSection mask, int networkPrefixLength) throws IncompatibleAddressException, PrefixLenException, SizeMismatchException { - checkMaskSectionCount(mask); + checkMaskSegmentCount(mask); IPv4AddressSection hostMask = getNetwork().getHostMaskSection(networkPrefixLength); return getSubnetSegments( this, @@ -1800,7 +1843,7 @@ public IPv4AddressSection bitwiseOr(IPv4AddressSection mask) throws Incompatible * @throws IncompatibleAddressException */ public IPv4AddressSection bitwiseOr(IPv4AddressSection mask, boolean retainPrefix) throws IncompatibleAddressException, SizeMismatchException { - checkMaskSectionCount(mask); + checkMaskSegmentCount(mask); return getOredSegments( this, retainPrefix ? getPrefixLength() : null, @@ -1820,7 +1863,7 @@ public IPv4AddressSection bitwiseOr(IPv4AddressSection mask, boolean retainPrefi * @throws IncompatibleAddressException */ public IPv4AddressSection bitwiseOrNetwork(IPv4AddressSection mask, int networkPrefixLength) throws IncompatibleAddressException, SizeMismatchException { - checkMaskSectionCount(mask); + checkMaskSegmentCount(mask); IPv4AddressSection networkMask = getNetwork().getNetworkMaskSection(networkPrefixLength); return getOredSegments( this, @@ -1908,7 +1951,7 @@ public IPv4AddressSection coverWithPrefixBlock() { } public IPv4AddressSection coverWithPrefixBlock(IPv4AddressSection other) throws AddressConversionException { - checkSectionCount(other); + checkSegmentCount(other); return coverWithPrefixBlock( this, other, diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressSegment.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressSegment.java index a7daf1ba..be281f7b 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressSegment.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressSegment.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -376,6 +376,11 @@ public boolean prefixEquals(AddressSegment other, int segmentPrefixLength) { return this == other || (super.prefixEquals(other, segmentPrefixLength) && other instanceof IPv4AddressSegment); } + @Override + public boolean overlaps(AddressSegment other) { + return this == other || (overlapsSeg(other) && other instanceof IPv4AddressSegment); + } + @Override public boolean contains(AddressSegment other) { return this == other || (containsSeg(other) && other instanceof IPv4AddressSegment); diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressSeqRange.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressSeqRange.java index 32e2db58..40279f93 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressSeqRange.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressSeqRange.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Sean C Foley + * Copyright 2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressStringParameters.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressStringParameters.java index 33882c7a..93d7c38e 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressStringParameters.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressStringParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,8 @@ public class IPv4AddressStringParameters extends IPAddressStringFormatParameters public static final boolean DEFAULT_ALLOW_IPV4_INET_ATON = true; public static final boolean DEFAULT_ALLOW_IPV4_INET_ATON_SINGLE_SEGMENT_MASK = false; //When not allowing prefixes beyond address size, whether 1.2.3.4/33 has a mask of ipv4 address 33 rather than treating it like a prefix - + public static final boolean DEFAULT_ALLOW_IPV4_inet_aton_extraneous_digits = false; + /** * Allows ipv4 inet_aton hexadecimal format 0xa.0xb.0xc.0xd */ @@ -68,6 +69,20 @@ public class IPv4AddressStringParameters extends IPAddressStringFormatParameters */ public final boolean inet_aton_single_segment_mask; + /** + * Allows single-segment inet_aton strings to have extraneous digits. + * This allows up to 31 digits when parsing for both IPv4 and IPv6. + * This allows an unlimited number of digits when parsing for just IPv4 (ie {@link IPAddressStringParameters#allowIPv6} is false). + *

+ * Digits that go beyond 32 bits are essentially ignored. + * The number of digits before exceeding 32 bits depends on the radix. + * The value of the most significant digit before exceeding 32 bits depends on the radix. + *

+ * The resulting address is the modulus of the address with the 32-bit unsigned int maximum value, + * or equivalently the truncation of the address to 32 bits. + */ + public final boolean inet_aton_extraneous_digits; + /** * The network that will be used to construct addresses - both parameters inside the network, and the network's address creator */ @@ -106,6 +121,7 @@ public IPv4AddressStringParameters( inet_aton_leading_zeros, inet_aton_joinedSegments, inet_aton_single_segment_mask, + false, network); } @@ -141,6 +157,7 @@ public IPv4AddressStringParameters( boolean inet_aton_leading_zeros, boolean inet_aton_joinedSegments, boolean inet_aton_single_segment_mask, + boolean inet_aton_extraneous_digits, IPv4AddressNetwork network) { super(allowBinary, allowLeadingZeros, allowCIDRPrefixLeadingZeros, allowUnlimitedLeadingZeros, rangeOptions, allowWildcardedSeparator, allowPrefixesBeyondAddressSize); this.inet_aton_hex = inet_aton_hex; @@ -148,50 +165,61 @@ public IPv4AddressStringParameters( this.inet_aton_leading_zeros = inet_aton_leading_zeros; this.inet_aton_joinedSegments = inet_aton_joinedSegments; this.inet_aton_single_segment_mask = inet_aton_single_segment_mask; + this.inet_aton_extraneous_digits = inet_aton_extraneous_digits; this.network = network; } - + public Builder toBuilder() { Builder builder = new Builder(); builder.inet_aton_hex = inet_aton_hex; builder.inet_aton_octal = inet_aton_octal; builder.inet_aton_joinedSegments = inet_aton_joinedSegments; - builder.inet_aton_single_segment_mask = this.inet_aton_single_segment_mask; + builder.inet_aton_single_segment_mask = inet_aton_single_segment_mask; + builder.inet_aton_extraneous_digits = inet_aton_extraneous_digits; builder.network = network; return (Builder) toBuilder(builder); } - + public static class Builder extends IPAddressStringFormatParameters.BuilderBase { private boolean inet_aton_hex = DEFAULT_ALLOW_IPV4_INET_ATON; private boolean inet_aton_octal = DEFAULT_ALLOW_IPV4_INET_ATON; private boolean inet_aton_leading_zeros = DEFAULT_ALLOW_IPV4_INET_ATON; private boolean inet_aton_joinedSegments = DEFAULT_ALLOW_IPV4_INET_ATON; private boolean inet_aton_single_segment_mask = DEFAULT_ALLOW_IPV4_INET_ATON_SINGLE_SEGMENT_MASK; + private boolean inet_aton_extraneous_digits = DEFAULT_ALLOW_IPV4_inet_aton_extraneous_digits; private IPv4AddressNetwork network; - + IPv6AddressStringParameters.Builder mixedParent; - + @Override protected void setMixedParent(IPv6AddressStringParameters.Builder parent) { mixedParent = parent; } - + public IPv6AddressStringParameters.Builder getEmbeddedIPv4AddressParentBuilder() { return mixedParent; } - + + /** + * Allows joined segments, resulting in just 2, 3 or 4 segments. Allows octal or hex segments. + * Allows an unlimited number of leading zeros. + * To allow just a single segment, use {@link IPAddressStringParameters.Builder#allowSingleSegment(boolean)} + * This does not affect whether extraneous digits are allowed, which can be allowed with {@link #inet_aton_extraneous_digits} + * @param allow + * @return + */ public Builder allow_inet_aton(boolean allow) { inet_aton_joinedSegments = inet_aton_octal = inet_aton_hex = allow; super.allowUnlimitedLeadingZeros(allow); return this; } - + @Override public Builder allowBinary(boolean allow) { super.allowBinary(allow); return this; } - + /** * @see IPv4AddressStringParameters#inet_aton_hex * @param allow @@ -201,7 +229,7 @@ public Builder allow_inet_aton_hex(boolean allow) { inet_aton_hex = allow; return this; } - + /** * @see IPv4AddressStringParameters#inet_aton_octal * @param allow @@ -211,7 +239,7 @@ public Builder allow_inet_aton_octal(boolean allow) { inet_aton_octal = allow; return this; } - + /** * @see IPv4AddressStringParameters#inet_aton_leading_zeros * @param allow @@ -221,7 +249,7 @@ public Builder allow_inet_aton_leading_zeros(boolean allow) { inet_aton_leading_zeros = allow; return this; } - + /** * @see IPv4AddressStringParameters#inet_aton_joinedSegments * @param allow @@ -231,7 +259,7 @@ public Builder allow_inet_aton_joined_segments(boolean allow) { inet_aton_joinedSegments = allow; return this; } - + /** * @see IPv4AddressStringParameters#inet_aton_single_segment_mask * @param allow @@ -241,7 +269,17 @@ public Builder allow_inet_aton_single_segment_mask(boolean allow) { inet_aton_single_segment_mask = allow; return this; } - + + /** + * @see IPv4AddressStringParameters#inet_aton_extraneous_digits + * @param allow + * @return the builder + */ + public Builder allow_inet_aton_extraneous_digits(boolean allow) { + inet_aton_extraneous_digits = allow; + return this; + } + /** * @see IPv4AddressStringParameters#network * @param network if null, the default network will be used @@ -251,43 +289,43 @@ public Builder setNetwork(IPv4AddressNetwork network) { this.network = network; return this; } - + @Override public Builder setRangeOptions(RangeParameters rangeOptions) { super.setRangeOptions(rangeOptions); return this; } - + @Override public Builder allowPrefixesBeyondAddressSize(boolean allow) { super.allowPrefixesBeyondAddressSize(allow); return this; } - + @Override public Builder allowWildcardedSeparator(boolean allow) { super.allowWildcardedSeparator(allow); return this; } - + @Override public Builder allowLeadingZeros(boolean allow) { super.allowLeadingZeros(allow); return this; } - + @Override public Builder allowPrefixLengthLeadingZeros(boolean allow) { super.allowPrefixLengthLeadingZeros(allow); return this; } - + @Override public Builder allowUnlimitedLeadingZeros(boolean allow) { super.allowUnlimitedLeadingZeros(allow); return this; } - + public IPv4AddressStringParameters toParams() { return new IPv4AddressStringParameters( allowLeadingZeros, @@ -302,6 +340,7 @@ public IPv4AddressStringParameters toParams() { inet_aton_leading_zeros, inet_aton_joinedSegments, inet_aton_single_segment_mask, + inet_aton_extraneous_digits, network); } } @@ -335,6 +374,9 @@ public int compareTo(IPv4AddressStringParameters o) { result = Boolean.compare(inet_aton_leading_zeros, o.inet_aton_leading_zeros); if(result == 0) { result = Boolean.compare(inet_aton_single_segment_mask, o.inet_aton_single_segment_mask); + if(result == 0) { + result = Boolean.compare(inet_aton_extraneous_digits, o.inet_aton_extraneous_digits); + } } } } @@ -352,7 +394,8 @@ public boolean equals(Object o) { && inet_aton_octal == other.inet_aton_octal && inet_aton_joinedSegments == other.inet_aton_joinedSegments && inet_aton_leading_zeros == other.inet_aton_leading_zeros - && inet_aton_single_segment_mask == other.inet_aton_single_segment_mask; + && inet_aton_single_segment_mask == other.inet_aton_single_segment_mask + && inet_aton_extraneous_digits == other.inet_aton_extraneous_digits; } } diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressTrie.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressTrie.java index ca5c23e5..d89096b9 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressTrie.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv4/IPv4AddressTrie.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -174,8 +174,8 @@ public CachingIterator blockSizeCachingAllNode @SuppressWarnings("unchecked") @Override - public CachingIterator containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") @@ -282,8 +282,37 @@ public IPv4TrieNode clone() { public boolean equals(Object o) { return o instanceof IPv4TrieNode && super.equals(o); } + + static class IPv4TrieKeyData extends TrieKeyData { + int uint32Val, mask32Val, nextBitMask32Val; + + @Override + public boolean is32Bits() { + return true; + } + + @Override + public int getUint32Val() { + return uint32Val; + } + + @Override + public int getMask32Val() { + return mask32Val; + } + + @Override + public int getNextBitMask32Val() { + return nextBitMask32Val; + } + } + + @Override + protected TrieKeyData getTrieKeyCache(IPv4Address addr) { + return addr.getTrieKeyCache(); + } } - + @Override public IPv4TrieNode removeElementsContainedBy(IPv4Address addr) { return (IPv4TrieNode) super.removeElementsContainedBy(addr); @@ -356,8 +385,8 @@ public CachingIterator blockSizeCachingAllNode @SuppressWarnings("unchecked") @Override - public CachingIterator containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6Address.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6Address.java index 66dece8a..4fcc9fea 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6Address.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6Address.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ import inet.ipaddr.IPAddressConverter; import inet.ipaddr.IPAddressSection.IPStringBuilderOptions; import inet.ipaddr.IPAddressSection.IPStringOptions; +import inet.ipaddr.IPAddressSection.SeriesCreator; import inet.ipaddr.IPAddressSegmentSeries; import inet.ipaddr.IPAddressString; import inet.ipaddr.IPAddressStringParameters; @@ -64,6 +65,7 @@ import inet.ipaddr.ipv6.IPv6AddressSection.IPv6StringCache; import inet.ipaddr.ipv6.IPv6AddressSection.IPv6StringCollection; import inet.ipaddr.ipv6.IPv6AddressSection.IPv6StringOptions; +import inet.ipaddr.ipv6.IPv6AddressTrie.IPv6TrieNode.IPv6TrieKeyData; import inet.ipaddr.mac.MACAddress; import inet.ipaddr.mac.MACAddressNetwork; import inet.ipaddr.mac.MACAddressNetwork.MACAddressCreator; @@ -384,6 +386,8 @@ static int checkIfScope(String zoneStr) { private transient IPv6StringCache stringCache; + private transient IPv6TrieKeyData cachedTrieKeyData; + transient IPv6AddressCache addressCache; IPv6Address(IPv6AddressSection section, CharSequence zone, boolean checkZone) throws AddressValueException { @@ -702,6 +706,52 @@ private IPv6Address(byte[] bytes, int byteStartIndex, int byteEndIndex, Integer this.zone = zone; } + /** + * Constructs an IPv6 address. + *

+ * The highBytes form the more significant 4 bytes of the address. + * + * @param highBytes the 4 more significant bytes in network byte order + * @param lowBytes the 4 least significant bytes in network byte order + * @throws AddressValueException if zone invalid + */ + public IPv6Address(long highBytes, long lowBytes, IPv6Zone zone) throws AddressValueException { + this(highBytes, lowBytes, null, zone); + } + + /** + * Constructs an IPv6 address. + *

+ * The highBytes form the more significant 4 bytes of the address. + * + * @param highBytes the 4 more significant bytes in network byte order + * @param lowBytes the 4 least significant bytes in network byte order + */ + public IPv6Address(long highBytes, long lowBytes) throws AddressValueException { + this(highBytes, lowBytes, null, null); + } + + /** + * Constructs an IPv6 address or subnet. + *

+ * The highBytes form the more significant 4 bytes of the address. + *

+ * When networkPrefixLength is non-null, depending on the prefix configuration (see {@link inet.ipaddr.AddressNetwork#getPrefixConfiguration()}, + * this object may represent either a single address with that network prefix length, or the prefix subnet block containing all addresses with the same network prefix. + *

+ * @param highBytes the 4 more significant bytes in network byte order + * @param lowBytes the 4 least significant bytes in network byte order + * @param networkPrefixLength the CIDR prefix, which can be null for no prefix length + */ + public IPv6Address(long highBytes, long lowBytes, Integer networkPrefixLength) throws AddressValueException { + this(highBytes, lowBytes, networkPrefixLength, null); + } + + private IPv6Address(long highBytes, long lowBytes, Integer networkPrefixLength, IPv6Zone zone) throws AddressValueException { + super(thisAddress -> ((IPv6Address) thisAddress).getDefaultCreator().createSection(highBytes, lowBytes, IPv6Address.SEGMENT_COUNT, networkPrefixLength)); + this.zone = zone; + } + /** * Constructs an IPv6 address or subnet. *

@@ -716,7 +766,7 @@ private IPv6Address(byte[] bytes, int byteStartIndex, int byteEndIndex, Integer public IPv6Address(SegmentValueProvider lowerValueProvider, SegmentValueProvider upperValueProvider, Integer networkPrefixLength) throws AddressValueException { this(lowerValueProvider, upperValueProvider, networkPrefixLength, null); } - + /** * Constructs an IPv6 address or subnet. * @@ -1257,7 +1307,53 @@ public IPv6Address getLower() { public IPv6Address getUpper() { return getLowestOrHighest(false, false); } - + + /** + * Returns a pair of longs with the lower address value in the range of this individual address or subnet. + * The high bits are in the first element, the low bits in the second. + * + * @return + */ + public long[] longValues() { + return getSection().longValues(); + } + + /** + * Returns a pair of longs with the upper address value in the range of this individual address or subnet. + * The high bits are in the first element, the low bits in the second. + * + * @return + */ + public long[] upperLongValues() { + return getSection().upperLongValues(); + } + + IPv6TrieKeyData getTrieKeyCache() { + IPv6TrieKeyData keyData = cachedTrieKeyData; + if(keyData == null) { + keyData = new IPv6TrieKeyData(); + Integer prefLen = getPrefixLength(); + keyData.prefixLength = prefLen; + long vals[] = longValues(); + keyData.uint64HighVal = vals[0]; + keyData.uint64LowVal = vals[1]; + if(prefLen != null) { + int bits = prefLen; + IPv6Address mask = getNetwork().getNetworkMask(bits, false); + vals = mask.longValues(); + keyData.mask64HighVal = vals[0]; + keyData.mask64LowVal = vals[1]; + if(bits > 63) { + keyData.nextBitMask64Val = 0x8000000000000000L >>> (bits - 64); + } else { + keyData.nextBitMask64Val = 0x8000000000000000L >>> bits; + } + } + cachedTrieKeyData = keyData; + } + return keyData; + } + /** * Replaces segments starting from startIndex and ending before endIndex with the same number of segments starting at replacementStartIndex from the replacement section * @@ -1272,6 +1368,19 @@ public IPv6Address replace(int startIndex, int endIndex, IPv6Address replacement return checkIdentity(getSection().replace(startIndex, endIndex, replacement.getSection(), replacementIndex, replacementIndex + (endIndex - startIndex))); } + /** + * Replaces segments starting from startIndex with as many segments as possible from the replacement section + * + * @param startIndex + * @param replacement + * @throws IndexOutOfBoundsException + * @return + */ + public IPv6Address replace(int startIndex, IPv6AddressSection replacement) { + int replacementCount = Math.min(IPv6Address.SEGMENT_COUNT - startIndex, replacement.getSegmentCount()); + return checkIdentity(getSection().replace(startIndex, startIndex + replacementCount, replacement, 0, replacementCount)); + } + @Override public IPv6Address reverseBits(boolean perByte) { return getCreator().createAddress(getSection().reverseBits(perByte)); @@ -1435,6 +1544,10 @@ public Iterable getIterable() { return this; } + public IPv6Address increment(BigInteger increment) { + return checkIdentity(getSection().increment(increment)); + } + @Override public IPv6Address increment(long increment) { return checkIdentity(getSection().increment(increment)); @@ -2089,6 +2202,11 @@ public IPv6Address[] mergeToSequentialBlocks(IPAddress ...addresses) throws Addr return blocks.toArray(new IPv6Address[blocks.size()]); } + @Override + protected SeriesCreator getSequentialSeriesCreator() { + return getDefaultCreator()::createSequentialBlockAddress; + } + /** * Returns whether {@link #getZone()} returns a non-null value * @@ -2280,6 +2398,27 @@ private boolean isSameZone(IPv6Address otherIPv6Address) { return Objects.equals(zone, otherIPv6Address.zone); } + /** + * + * @param other + * @return whether this subnet overlaps the given address + */ + @Override + public boolean overlaps(Address other) { + if(super.overlaps(other)) { + //must check the zone too + if(other != this) { + IPv6Address otherAddr = (IPv6Address) other; + if(hasZone() || otherAddr.hasZone()) { + //if it has a zone, then it does not overlap addresses from other zones + return isSameZone(otherAddr); + } + } + return true; + } + return false; + } + /** * * @param other @@ -2290,9 +2429,10 @@ public boolean contains(Address other) { if(super.contains(other)) { //must check the zone too if(other != this) { - if(hasZone()) { + IPv6Address otherAddr = (IPv6Address) other; + if(hasZone() || otherAddr.hasZone()) { //if it has a zone, then it does not contain addresses from other zones - return isSameZone((IPv6Address) other); + return isSameZone(otherAddr); } } return true; @@ -2300,6 +2440,22 @@ public boolean contains(Address other) { return false; } + @Override + public BigInteger enumerate(Address other) { + if(other instanceof IPv6Address) { + return IPv6AddressSection.enumerate(getSection(), other.getSection()); + } + return null; + } + + @Override + public BigInteger enumerate(IPAddress other) { + if(other.isIPv6()) { + return IPv6AddressSection.enumerate(getSection(), other.getSection()); + } + return null; + } + //////////////// string creation below /////////////////////////////////////////////////////////////////////////////////////////// @Override diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressAssociativeTrie.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressAssociativeTrie.java index 0243aa64..59d92267 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressAssociativeTrie.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressAssociativeTrie.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import inet.ipaddr.format.util.AssociativeAddressTrie; import inet.ipaddr.format.util.BinaryTreeNode; import inet.ipaddr.format.util.BinaryTreeNode.CachingIterator; +import inet.ipaddr.ipv6.IPv6AddressTrie.IPv6TrieNode.IPv6TrieKeyData; /** * An IPv6 address trie in which each node can be associated with a value. @@ -176,8 +177,8 @@ public CachingIterator, IPv6Address, C> blockSize @SuppressWarnings("unchecked") @Override - public CachingIterator, IPv6Address, C> containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator, IPv6Address, C>) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator> containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator>) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") @@ -294,6 +295,11 @@ public IPv6AssociativeTrieNode clone() { public boolean equals(Object o) { return o instanceof IPv6AddressAssociativeTrie.IPv6AssociativeTrieNode && super.equals(o); } + + @Override + protected IPv6TrieKeyData getTrieKeyCache(IPv6Address addr) { + return addr.getTrieKeyCache(); + } } @Override @@ -369,8 +375,8 @@ public CachingIterator, IPv6Address, C> blockSize @SuppressWarnings("unchecked") @Override - public CachingIterator, IPv6Address, C> containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator, IPv6Address, C>) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator> containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator>) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressNetwork.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressNetwork.java index a817428e..10698312 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressNetwork.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressNetwork.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -389,6 +389,10 @@ protected IPv6AddressSection createSection(byte bytes[], int byteStartIndex, int return new IPv6AddressSection(bytes, byteStartIndex, byteEndIndex, segmentCount, prefix, true, false); } + protected IPv6AddressSection createSection(long highBytes, long lowBytes, int segmentCount, Integer prefix) { + return new IPv6AddressSection(highBytes, lowBytes, segmentCount, prefix); + } + @Override public IPv6AddressSection createSection(byte bytes[], Integer prefix) { return new IPv6AddressSection(bytes, prefix); diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressSection.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressSection.java index a3c5eea9..1f9c3786 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressSection.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressSection.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -175,6 +175,8 @@ static class IPv6AddressCache extends SectionCache { private transient SectionCache sectionCache; + private transient long[] cachedLowerVals; + transient IPv4AddressSection embeddedIPv4Section;//the lowest 4 bytes as IPv4 transient IPv6v4MixedAddressSection defaultMixedAddressSection; @@ -579,12 +581,12 @@ public IPv6AddressSection getSection(int index) { public IPv6AddressSection getSection(int index, int endIndex) { return getSection(index, endIndex, this, getAddressCreator(addressSegmentIndex + index)); } - + @Override public IPv6AddressSegment[] getSegments() { return (IPv6AddressSegment[]) getDivisionsInternal().clone(); } - + void cache(IPv6AddressSection lower, IPv6AddressSection upper) { SectionCache cache = sectionCache; if((lower != null || upper != null) && @@ -607,11 +609,11 @@ void cache(IPv6AddressSection lower, IPv6AddressSection upper) { } } } - + protected IPv6AddressSection getSingleLowestOrHighestSection() { return getSingleLowestOrHighestSection(this); } - + IPv6AddressSection getLowestOrHighestSection(boolean lowest, boolean excludeZeroHost) { IPv6AddressSection result = getSingleLowestOrHighestSection(); if(result == null) { @@ -661,41 +663,87 @@ IPv6AddressSection getLowestOrHighestSection(boolean lowest, boolean excludeZero } return result; } - + @Override public IPv6AddressSection getLowerNonZeroHost() { return getLowestOrHighestSection(true, true); } - + @Override public IPv6AddressSection getLower() { return getLowestOrHighestSection(true, false); } - + @Override public IPv6AddressSection getUpper() { return getLowestOrHighestSection(false, false); } - + + public long[] longValues() { + long result[] = cachedLowerVals; + if(result == null) { + cachedLowerVals = result = calcLongValues(true); + } + return result; + } + + public long[] upperLongValues() { + if(!isMultiple()) { + return longValues(); + } + return calcLongValues(false); + } + + private long[] calcLongValues(boolean lower) { + int segCount = getSegmentCount(); + if(segCount != 0) { + int bitsPerSegment = IPv6Address.BITS_PER_SEGMENT; + long low, high; + if(segCount <= IPv6Address.SEGMENT_COUNT >> 1) { + high = 0; + low = getSegment(0).getDivisionValue(); + for(int i = 1; i < segCount; i++) { + IPv6AddressSegment seg = getSegment(i); + low = (low << bitsPerSegment) | (lower ? seg.getDivisionValue() : seg.getUpperDivisionValue()); + } + } else { + high = getSegment(0).getDivisionValue(); + int highCount = segCount - 4; + int i = 1; + for(; i < highCount; i++) { + IPv6AddressSegment seg = getSegment(i); + high = (high << bitsPerSegment) | (lower ? seg.getDivisionValue() : seg.getUpperDivisionValue()); + } + low = getSegment(i).getDivisionValue(); + for(i++; i < segCount; i++) { + IPv6AddressSegment seg = getSegment(i); + low = (low << bitsPerSegment) | (lower ? seg.getDivisionValue() : seg.getUpperDivisionValue()); + } + } + return new long[]{high, low}; + } + return new long[2]; + } + @Override public IPv6AddressSection reverseBits(boolean perByte) { return reverseBits(perByte, this, getAddressCreator(), i -> getSegment(i).reverseBits(perByte), true); } - + @Override public IPv6AddressSection reverseBytes() { return reverseBytes(false); } - + @Override public IPv6AddressSection reverseBytesPerSegment() { return reverseBytes(true); } - + private IPv6AddressSection reverseBytes(boolean perSegment) { return reverseBytes(perSegment, this, getAddressCreator(), i -> getSegment(i).reverseBytes(), true); } - + @Override public IPv6AddressSection reverseSegments() { if(getSegmentCount() <= 1) { @@ -703,7 +751,7 @@ public IPv6AddressSection reverseSegments() { } return reverseSegments(this, getAddressCreator(), i -> getSegment(i).withoutPrefixLength(), true); } - + @Override public Iterable getIterable() { return this; @@ -725,7 +773,7 @@ private Iterator iterator(Predicate ex useOriginal ? null : segmentsIterator(excludeFunc), isAllSubnets ? null : getPrefixLength()); } - + private AddressComponentSpliterator prefixSpliterator(boolean isBlockIterator) { Integer prefLength = getPrefixLength(); if(prefLength == null || prefLength > getBitCount()) { @@ -770,7 +818,7 @@ public Iterator nonZeroHostIterator() { public Iterator iterator() { return iterator(null); } - + @Override public AddressComponentSpliterator spliterator() { return spliterator(false); @@ -785,7 +833,7 @@ public Stream stream() { public Iterator prefixIterator() { return prefixIterator(false); } - + @Override public AddressComponentSpliterator prefixSpliterator() { return prefixSpliterator(false); @@ -800,7 +848,7 @@ public Stream prefixStream() { public Iterator prefixBlockIterator() { return prefixIterator(true); } - + @Override public AddressComponentSpliterator prefixBlockSpliterator() { return prefixSpliterator(true); @@ -838,7 +886,7 @@ private Iterator prefixIterator(boolean isBlockIterator) { isBlockIterator ? index -> getSegment(index).prefixBlockIterator() : index -> getSegment(index).prefixIterator()), prefLength); } - + @Override public Iterator blockIterator(int segmentCount) { if(segmentCount < 0) { @@ -947,7 +995,7 @@ private Iterator segmentsIterator(Predicate getSegment(index).iterator(!isAllSubnets), excludeFunc); } - + private Predicate excludeNonZeroHosts() { if(isPrefixed()) { int prefLength = getNetworkPrefixLength(); @@ -955,17 +1003,17 @@ private Predicate excludeNonZeroHosts() { } return null; } - + @Override public Iterator segmentsNonZeroHostIterator() { return segmentsIterator(excludeNonZeroHosts()); } - + @Override public Iterator segmentsIterator() { return segmentsIterator(null); } - + @Override public AddressComponentRangeSpliterator segmentsSpliterator() { int segmentCount = getSegmentCount(); @@ -1029,7 +1077,7 @@ AddressComponentRangeSpliterator segmentsSpli addr -> addr.getCount().compareTo(LONG_MAX) <= 0, addr -> longCount(addr.getSection(), segmentCount)); } - + @Override public Stream segmentsStream() { return StreamSupport.stream(segmentsSpliterator(), false); @@ -1084,7 +1132,7 @@ protected AddressComponentSpliterator spliterator(boolean ex section -> section.getCount().compareTo(LONG_MAX) <= 0, longSizer); } - + protected Iterator iterator( IPv6Address original, AddressCreator creator, @@ -1161,7 +1209,7 @@ Iterator prefixIterator(IPv6Address original, AddressCreator prefixIterator(IPv6Address original, AddressCreator creator, boolean isBlockIterator, int prefLength) { if(prefLength > getBitCount() || prefLength < 0) { throw new PrefixLenException(original, prefLength); @@ -1200,7 +1248,7 @@ AddressComponentSpliterator prefixSpliterator( } return prefixSpliterator(original, creator, isBlockIterator, prefLength); } - + AddressComponentSpliterator prefixSpliterator( IPv6Address original, IPv6AddressCreator creator, @@ -1230,7 +1278,7 @@ AddressComponentSpliterator prefixSpliterator( addr -> addr.getPrefixCount().compareTo(LONG_MAX) <= 0, addr -> longPrefixCount(addr.getSection(), prefixLength)); } - + Iterator blockIterator(IPv6Address original, AddressCreator creator, int segmentCount) { if(segmentCount < 0) { throw new IllegalArgumentException(); @@ -1301,7 +1349,7 @@ AddressComponentSpliterator blockSpliterator(IPv6Address original, addr -> addr.getBlockCount(segmentCount).compareTo(LONG_MAX) <= 0, addr -> longCount(addr.getSection(), segmentCount)); } - + protected boolean isZeroHost(IPv6AddressSegment segments[], int prefixLength) { return super.isZeroHost(segments, prefixLength); } @@ -1317,11 +1365,11 @@ protected boolean isZeroHost(IPv6AddressSegment segments[], int prefixLength) { BigInteger.valueOf(1).shiftLeft(16 * 7).subtract(BigInteger.ONE), BigInteger.valueOf(1).shiftLeft(16 * 8).subtract(BigInteger.ONE), }; - + public static BigInteger getMaxValue(int segmentCount) { return MAX_VALUES_BY_SEGMENT[segmentCount]; } - + @Override public IPv6AddressSection incrementBoundary(long increment) { if(increment <= 0) { @@ -1333,16 +1381,28 @@ public IPv6AddressSection incrementBoundary(long increment) { return getUpper().increment(increment); } + public IPv6AddressSection increment(BigInteger bigIncrement) { + if(bigIncrement.signum() == 0 && !isMultiple()) { + return this; + } + checkOverflow(bigIncrement, this::getValue, this::getUpperValue, this::getCount, this::isSequential, () -> getMaxValue(getSegmentCount())); + Integer prefixLength = getNetwork().getPrefixConfiguration().allPrefixedAddressesAreSubnets() ? null : getPrefixLength(); + return increment( + this, + bigIncrement, + getAddressCreator(), + this::getLower, + this::getUpper, + prefixLength); + }; + @Override public IPv6AddressSection increment(long increment) { if(increment == 0 && !isMultiple()) { return this; } - BigInteger lowerValue = getValue(); - BigInteger upperValue = getUpperValue(); - BigInteger count = getCount(); BigInteger bigIncrement = BigInteger.valueOf(increment); - checkOverflow(increment, bigIncrement, lowerValue, upperValue, count, () -> getMaxValue(getSegmentCount())); + checkOverflow(increment, bigIncrement, this::getValue, this::getUpperValue, this::getCount, this::isSequential, () -> getMaxValue(getSegmentCount())); Integer prefixLength = getNetwork().getPrefixConfiguration().allPrefixedAddressesAreSubnets() ? null : getPrefixLength(); IPv6AddressSection result = fastIncrement( this, @@ -1363,7 +1423,7 @@ public IPv6AddressSection increment(long increment) { this::getUpper, prefixLength); } - + @Override protected BigInteger getCountImpl(int segCount) { if(!isMultiple()) { @@ -1372,14 +1432,14 @@ protected BigInteger getCountImpl(int segCount) { BigInteger result = getCountIPv6(i -> getSegment(i).getValueCount(), segCount); return result; } - + private static BigInteger getCountIPv6(IntUnaryOperator segmentValueCountProvider, int segCount) { if(segCount < 0) { throw new IllegalArgumentException(); } return count(segmentValueCountProvider, segCount, 2, 0x7fffffffffffL); } - + @Override protected BigInteger getZeroHostCountImpl(int prefixLength, int segCount) { if(includesZeroHost(prefixLength)) { @@ -1401,7 +1461,7 @@ protected BigInteger getZeroHostCountImpl(int prefixLength, int segCount) { } return BigInteger.ZERO; } - + @Override protected BigInteger getPrefixCountImpl() { Integer prefixLength = getPrefixLength(); @@ -1468,12 +1528,12 @@ protected IPv6AddressSection createPrefixedSectionInternal(IPv6AddressSegment se } return creator; } - + @Override public IPv6AddressSegment getDivision(int index) { return (IPv6AddressSegment) super.getDivision(index); } - + @Override public IPv6AddressSegment getSegment(int index) { return (IPv6AddressSegment) super.getSegment(index); @@ -1488,7 +1548,7 @@ public void getSegments(int start, int end, Collection= other.addressSegmentIndex) { return prefixEquals(this, other, addressSegmentIndex - other.addressSegmentIndex); @@ -1966,13 +2057,12 @@ public boolean prefixEquals(AddressSection o) { } return false; } - + @Override public boolean prefixContains(IPAddressSection o) { if(o == this) { return true; - } - if(o instanceof IPv6AddressSection) { + } else if(o instanceof IPv6AddressSection) { IPv6AddressSection other = (IPv6AddressSection) o; if(addressSegmentIndex >= other.addressSegmentIndex) { return prefixContains(this, other, addressSegmentIndex - other.addressSegmentIndex); @@ -2035,11 +2125,11 @@ public IPv6AddressSection[] subtract(IPv6AddressSection other) throws SizeMismat public IPv6AddressNetwork getNetwork() { return Address.defaultIpv6Network(); } - + public IPv4AddressNetwork getIPv4Network() { return Address.defaultIpv4Network(); } - + public MACAddressNetwork getMACNetwork() { return Address.defaultMACNetwork(); } @@ -2048,12 +2138,12 @@ public MACAddressNetwork getMACNetwork() { public IPv6AddressSection adjustPrefixBySegment(boolean nextSegment) { return adjustPrefixBySegment(nextSegment, true); } - + @Override public IPv6AddressSection adjustPrefixBySegment(boolean nextSegment, boolean zeroed) { return (IPv6AddressSection) super.adjustPrefixBySegment(nextSegment, zeroed); } - + @Override public IPv6AddressSection adjustPrefixLength(int adjustment) { return adjustPrefixLength(adjustment, true); @@ -2100,12 +2190,12 @@ private IPv6AddressSection setPrefixLength(int networkPrefixLength, boolean with public IPv6AddressSection removePrefixLength() { return removePrefixLength(true); } - + @Override public IPv6AddressSection withoutPrefixLength() { return removePrefixLength(false); } - + @Override @Deprecated public IPv6AddressSection removePrefixLength(boolean zeroed) { return removePrefixLength(this, zeroed, getAddressCreator(), IPv6AddressSection::getSegment); @@ -2121,7 +2211,7 @@ public IPv6AddressSection removePrefixLength(boolean zeroed) { * @throws IncompatibleAddressException */ public IPv6AddressSection bitwiseOrNetwork(IPv6AddressSection mask, int networkPrefixLength) throws IncompatibleAddressException, PrefixLenException, SizeMismatchException { - checkMaskSectionCount(mask); + checkMaskSegmentCount(mask); IPv6AddressSection networkMask = getNetwork().getNetworkMaskSection(networkPrefixLength); return getOredSegments( this, @@ -2143,7 +2233,7 @@ public IPv6AddressSection bitwiseOrNetwork(IPv6AddressSection mask, int networkP public IPv6AddressSection bitwiseOr(IPv6AddressSection mask) throws IncompatibleAddressException { return bitwiseOr(mask, false); } - + /** * Does the bitwise disjunction with this address. Useful when subnetting. Similar to {@link #mask(IPv6AddressSection)} which does the bitwise conjunction. * @@ -2153,7 +2243,7 @@ public IPv6AddressSection bitwiseOr(IPv6AddressSection mask) throws Incompatible * @throws IncompatibleAddressException */ public IPv6AddressSection bitwiseOr(IPv6AddressSection mask, boolean retainPrefix) throws IncompatibleAddressException, SizeMismatchException { - checkMaskSectionCount(mask); + checkMaskSegmentCount(mask); return getOredSegments( this, retainPrefix ? getPrefixLength() : null, @@ -2162,7 +2252,7 @@ public IPv6AddressSection bitwiseOr(IPv6AddressSection mask, boolean retainPrefi this::getSegment, i -> mask.getSegment(i).getSegmentValue()); } - + @Override public IPv6AddressSection toZeroHost() throws IncompatibleAddressException { if(!isPrefixed()) { @@ -2179,7 +2269,7 @@ public IPv6AddressSection toZeroHost() throws IncompatibleAddressException { } return createZeroHost(false); } - + IPv6AddressSection createZeroHost(boolean boundariesOnly) { int prefixLength = getNetworkPrefixLength(); IPv6AddressNetwork network = getNetwork(); @@ -2193,7 +2283,7 @@ IPv6AddressSection createZeroHost(boolean boundariesOnly) { i -> mask.getSegment(i).getSegmentValue(), true); } - + @Override public IPv6AddressSection toZeroHost(int prefixLength) { if(isPrefixed() && prefixLength == getNetworkPrefixLength()) { @@ -2209,7 +2299,7 @@ public IPv6AddressSection toZeroHost(int prefixLength) { i -> mask.getSegment(i).getSegmentValue(), true); } - + @Override public IPv6AddressSection toZeroNetwork() { if(!isPrefixed()) { @@ -2218,7 +2308,7 @@ public IPv6AddressSection toZeroNetwork() { } return createZeroNetwork(); } - + IPv6AddressSection createZeroNetwork() { Integer prefixLength = getNetworkPrefixLength(); IPv6Address mask = getNetwork().getHostMask(prefixLength); @@ -2231,7 +2321,7 @@ IPv6AddressSection createZeroNetwork() { i -> mask.getSegment(i).getSegmentValue(), true); } - + @Override public IPv6AddressSection toMaxHost() throws IncompatibleAddressException { if(!isPrefixed()) { @@ -2246,7 +2336,7 @@ public IPv6AddressSection toMaxHost() throws IncompatibleAddressException { } return createMaxHost(); } - + public IPv6AddressSection createMaxHost() { Integer prefixLength = getNetworkPrefixLength();//we know it is prefixed here so no NullPointerException IPv6Address mask = getNetwork().getHostMask(prefixLength); @@ -2258,7 +2348,7 @@ public IPv6AddressSection createMaxHost() { this::getSegment, i -> mask.getSegment(i).getSegmentValue()); } - + @Override public IPv6AddressSection toMaxHost(int prefixLength) { if(isPrefixed() && prefixLength == this.getNetworkPrefixLength()) { @@ -2283,7 +2373,7 @@ public IPv6AddressSection toMaxHost(int prefixLength) { * @throws IncompatibleAddressException */ public IPv6AddressSection mask(IPv6AddressSection mask, boolean retainPrefix) throws IncompatibleAddressException, SizeMismatchException { - checkMaskSectionCount(mask); + checkMaskSegmentCount(mask); return getSubnetSegments( this, retainPrefix ? getPrefixLength() : null, @@ -2293,14 +2383,14 @@ public IPv6AddressSection mask(IPv6AddressSection mask, boolean retainPrefix) th i -> mask.getSegment(i).getSegmentValue(), false); } - + /** * Equivalent to {@link #mask(IPv6AddressSection, boolean)} with the second argument as false. */ public IPv6AddressSection mask(IPv6AddressSection mask) throws IncompatibleAddressException, SizeMismatchException { return mask(mask, false); } - + /** * Applies the given mask to the network section of the address as indicated by the given prefix length. * Useful for subnetting. Once you have zeroed a section of the network you can insert bits @@ -2312,7 +2402,7 @@ public IPv6AddressSection mask(IPv6AddressSection mask) throws IncompatibleAddre * @throws IncompatibleAddressException */ public IPv6AddressSection maskNetwork(IPv6AddressSection mask, int networkPrefixLength) throws IncompatibleAddressException, PrefixLenException, SizeMismatchException { - checkMaskSectionCount(mask); + checkMaskSegmentCount(mask); if(getNetwork().getPrefixConfiguration().allPrefixedAddressesAreSubnets()) { return getSubnetSegments( this, @@ -2338,11 +2428,11 @@ public IPv6AddressSection maskNetwork(IPv6AddressSection mask, int networkPrefix false ); } - + protected static Integer cacheBits(int i) { return IPAddressSection.cacheBits(i); } - + @Override public IPv6AddressSection getHostMask() { return (IPv6AddressSection) super.getHostMask(); @@ -2360,17 +2450,17 @@ public IPv6AddressSection getNetworkSection() { } return getNetworkSection(getBitCount()); } - + @Override public IPv6AddressSection getNetworkSection(int networkPrefixLength) throws PrefixLenException { return getNetworkSection(networkPrefixLength, true); } - + @Override public IPv6AddressSection getNetworkSection(int networkPrefixLength, boolean withPrefixLength) throws PrefixLenException { return getNetworkSection(this, networkPrefixLength, withPrefixLength, getAddressCreator(), (prefix, i) -> getSegment(i).toNetworkSegment(prefix, withPrefixLength)); } - + @Override public IPv6AddressSection getHostSection() { if(isPrefixed()) { @@ -2378,14 +2468,14 @@ public IPv6AddressSection getHostSection() { } return getHostSection(0); } - + @Override public IPv6AddressSection getHostSection(int networkPrefixLength) throws PrefixLenException { int hostSegmentCount = getHostSegmentCount(networkPrefixLength); IPv6AddressCreator creator = getAddressCreator(addressSegmentIndex + (getSegmentCount() - hostSegmentCount)); return getHostSection(this, networkPrefixLength, hostSegmentCount, creator, (prefix, i) -> getSegment(i).toHostSegment(prefix)); } - + @Override public IPv6AddressSection toPrefixBlock() { Integer prefixLength = getNetworkPrefixLength(); @@ -2416,7 +2506,7 @@ public IPv6AddressSection coverWithPrefixBlock() { } public IPv6AddressSection coverWithPrefixBlock(IPv6AddressSection other) throws AddressConversionException { - checkSectionCount(other); + checkSegmentCount(other); return coverWithPrefixBlock( this, other, @@ -2433,7 +2523,7 @@ protected static T coverWithPrefixBlock( Comparator comparator) throws AddressConversionException { return IPAddressSection.coverWithPrefixBlock(first, other, getLower, getUpper, comparator); } - + protected static IPAddressSegmentSeries coverWithPrefixBlock( IPAddressSegmentSeries original, IPAddressSegmentSeries lower, @@ -2458,7 +2548,7 @@ public IPv6AddressSection[] spanWithPrefixBlocks() { ArrayList list = (ArrayList) spanWithBlocks(true); return list.toArray(new IPv6AddressSection[list.size()]); } - + /** * Produces the list of prefix block subnets that span from this series to the given series. * @@ -2479,7 +2569,7 @@ public IPv6AddressSection[] spanWithPrefixBlocks(IPv6AddressSection other) throw IPv6AddressSection::withoutPrefixLength, getAddressCreator()::createSectionArray); } - + /** * * @param other @@ -2490,7 +2580,7 @@ public IPv6AddressSection[] spanWithPrefixBlocks(IPv6AddressSection other) throw public IPv6AddressSection[] spanWithRangedSegments(IPv6AddressSection other) { return spanWithSequentialBlocks(other); } - + /** * Produces an array of blocks that are sequential that cover the same set of sections as this. *

@@ -2507,7 +2597,7 @@ public IPv6AddressSection[] spanWithSequentialBlocks() throws AddressConversionE ArrayList list = (ArrayList) spanWithBlocks(false); return list.toArray(new IPv6AddressSection[list.size()]); } - + /** * Produces a list of range subnets that span from this series to the given series. * @@ -2527,7 +2617,7 @@ public IPv6AddressSection[] spanWithSequentialBlocks(IPv6AddressSection other) t IPv6AddressSection::withoutPrefixLength, getAddressCreator()); } - + /** * * @param sections @@ -2539,7 +2629,7 @@ public IPv6AddressSection[] spanWithSequentialBlocks(IPv6AddressSection other) t public IPv6AddressSection[] mergePrefixBlocks(IPv6AddressSection ...sections) throws SizeMismatchException { return mergeToPrefixBlocks(sections); } - + /** * Merges this with the list of sections to produce the smallest array of prefix blocks. *

@@ -2565,7 +2655,7 @@ private IPv6AddressSection[] getCloned(IPv6AddressSection... sections) { converted[0] = this; return converted; } - + private void checkSectionsMergeable(IPv6AddressSection sections[]) { for(int i = 0; i < sections.length; i++) { IPv6AddressSection section = sections[i]; @@ -2614,12 +2704,12 @@ protected boolean hasNoStringCache() { } return false; } - + @Override protected IPv6StringCache getStringCache() { return stringCache; } - + /** * This produces the shortest valid string for the address. */ @@ -2647,7 +2737,7 @@ public String toCanonicalString() { } return result; } - + /** * This produces the mixed IPv6/IPv4 string. It is the shortest such string (ie fully compressed). */ @@ -2671,7 +2761,7 @@ public String toFullString() { } return result; } - + @Override public String toCompressedWildcardString() { String result; @@ -2680,7 +2770,7 @@ public String toCompressedWildcardString() { } return result; } - + @Override public String toPrefixLengthString() { String result; @@ -2689,12 +2779,12 @@ public String toPrefixLengthString() { } return result; } - + @Override public String toSubnetString() { return toPrefixLengthString(); } - + @Override public String toCanonicalWildcardString() { String result; @@ -2703,7 +2793,7 @@ public String toCanonicalWildcardString() { } return result; } - + @Override public String toNormalizedWildcardString() { String result; @@ -2712,7 +2802,7 @@ public String toNormalizedWildcardString() { } return result; } - + @Override public String toSQLWildcardString() { String result; @@ -2721,7 +2811,7 @@ public String toSQLWildcardString() { } return result; } - + /** * The normalized string returned by this method is consistent with java.net.Inet6address. * IPs are not compressed nor mixed in this representation. @@ -2734,7 +2824,7 @@ public String toNormalizedString() { } return result; } - + /** * The base 85 string is described by RFC 1924 * @return @@ -2746,7 +2836,7 @@ public String toBase85String() throws IncompatibleAddressException { } return result; } - + String toBase85String(String zone) { Integer prefixLength = getNetworkPrefixLength(); IPAddressLargeDivision largeDiv; @@ -2765,7 +2855,7 @@ protected void cacheNormalizedString(String str) { getStringCache().normalizedString = str; } } - + @Override public String toReverseDNSLookupString() { String result; @@ -2775,7 +2865,7 @@ public String toReverseDNSLookupString() { } return result; } - + @Override public String toSegmentedBinaryString() { String result; @@ -2784,11 +2874,11 @@ public String toSegmentedBinaryString() { } return result; } - + protected String toSegmentedBinaryString(CharSequence zone) { return toNormalizedString(IPv6StringCache.segmentedBinaryParams, zone); } - + @Override protected String toBinaryString(CharSequence zone) throws IncompatibleAddressException { if(isDualString()) { @@ -2797,7 +2887,7 @@ protected String toBinaryString(CharSequence zone) throws IncompatibleAddressExc } return toNormalizedString(IPStringCache.binaryParams, zone); } - + @Override protected String toHexString(boolean with0xPrefix, CharSequence zone) throws IncompatibleAddressException { if(isDualString()) { @@ -2806,7 +2896,7 @@ protected String toHexString(boolean with0xPrefix, CharSequence zone) throws Inc } return toNormalizedString(with0xPrefix ? IPStringCache.hexPrefixedParams : IPStringCache.hexParams, zone); } - + @Override protected String toOctalString(boolean with0Prefix, CharSequence zone) throws IncompatibleAddressException { if(zone == null) { @@ -2834,17 +2924,17 @@ public String toNormalizedString(IPStringOptions options) { } return super.toNormalizedString(options); } - + public String toNormalizedString(IPv6StringOptions options) { return toNormalizedString(options, (String) null); } - + private String toNormalizedMixedString(IPv6v4MixedParams mixedParams, CharSequence zone) { IPv6v4MixedAddressSection mixed = getMixedAddressSection(); String result = mixedParams.toString(mixed, zone); return result; } - + String toNormalizedString(IPStringOptions options, CharSequence zone) { if(zone == null) { return toNormalizedString(options); @@ -2855,7 +2945,7 @@ String toNormalizedString(IPStringOptions options, CharSequence zone) { IPAddressStringParams params = toIPParams(options); return params.toString(this, zone); } - + public String toNormalizedString(IPv6StringOptions options, CharSequence zone) { IPv6StringParams stringParams; if(options.isCacheable()) { @@ -2884,7 +2974,7 @@ public String toNormalizedString(IPv6StringOptions options, CharSequence zone) { } return stringParams.toString(this, zone); } - + public static String toNormalizedString(IPStringOptions options, CharSequence zone, IPAddressStringDivisionSeries part) { AddressStringParams params = toIPParams(options); String result = params.toString(part, zone); @@ -2895,22 +2985,22 @@ public static String toNormalizedString(IPStringOptions options, CharSequence zo public IPAddressPartStringCollection toStandardStringCollection() { return toStringCollection(IPv6StringBuilderOptions.STANDARD_OPTS); } - + @Override public IPAddressPartStringCollection toAllStringCollection() { return toStringCollection(IPv6StringBuilderOptions.ALL_OPTS); } - + @Override public IPAddressPartStringCollection toDatabaseSearchStringCollection() { return toStringCollection(IPv6StringBuilderOptions.DATABASE_SEARCH_OPTS); } - + @Override public IPAddressPartStringCollection toStringCollection(IPStringBuilderOptions options) { return toStringCollection(IPv6StringBuilderOptions.from(options)); } - + public IPAddressPartStringCollection toStringCollection(IPv6StringBuilderOptions opts) { return toStringCollection(opts, null); } @@ -2931,12 +3021,12 @@ IPv6StringCollection toStringCollection(IPv6StringBuilderOptions opts, CharSeque } return collection; } - + @Override public IPAddressStringDivisionSeries[] getParts(IPStringBuilderOptions opts) { return getParts(IPv6StringBuilderOptions.from(opts)); } - + public IPAddressStringDivisionSeries[] getParts(IPv6StringBuilderOptions opts) { if(opts.includes(IPv6StringBuilderOptions.MIXED)) { if(opts.includes(IPStringBuilderOptions.BASIC)) { @@ -2946,14 +3036,14 @@ public IPAddressStringDivisionSeries[] getParts(IPv6StringBuilderOptions opts) { } return super.getParts(opts); } - + private static class IPv6StringMatcher extends SQLStringMatcher { IPv6StringMatcher( IPv6AddressSectionString networkString, IPAddressSQLTranslator translator) { super(networkString, networkString.addr.isEntireAddress(), translator); } - + @Override public StringBuilder getSQLCondition(StringBuilder builder, String columnName) { if(networkString.addr.isEntireAddress()) { @@ -2993,7 +3083,7 @@ public StringBuilder getSQLCondition(StringBuilder builder, String columnName) { return builder; } } - + public static class CompressOptions { public enum CompressionChoiceOptions { HOST_PREFERRED, //if there is a host section, compress the host along with any adjoining zero segments, otherwise compress a range of zero segments @@ -3005,7 +3095,7 @@ boolean compressHost() { return this != ZEROS; } } - + public enum MixedCompressionOptions { NO, //do not allow compression of a mixed section NO_HOST, //allow compression of a mixed section when there is no host section @@ -3036,21 +3126,21 @@ boolean compressMixed(IPv6AddressSection addressSection) { public final boolean compressSingle; public final CompressionChoiceOptions rangeSelection; - + //options for addresses with an ipv4 section public final MixedCompressionOptions compressMixedOptions; - + public CompressOptions(boolean compressSingle, CompressionChoiceOptions rangeSelection) { this(compressSingle, rangeSelection, MixedCompressionOptions.YES); } - + public CompressOptions(boolean compressSingle, CompressionChoiceOptions rangeSelection, MixedCompressionOptions compressMixedOptions) { this.compressSingle = compressSingle; this.rangeSelection = rangeSelection; this.compressMixedOptions = compressMixedOptions == null ? MixedCompressionOptions.YES : compressMixedOptions; } } - + /** * Provides a clear way to create a specific type of string. * @@ -3062,8 +3152,7 @@ public static class IPv6StringOptions extends IPStringOptions { //can be null, which means no compression public final CompressOptions compressOptions; - - + IPv6StringOptions( int base, boolean expandSegments, @@ -3092,15 +3181,15 @@ public static class IPv6StringOptions extends IPStringOptions { this.ipv4Opts = null; } } - + boolean isCacheable() { return compressOptions == null; } - + boolean makeMixed() { return ipv4Opts != null; } - + private IPv6StringParams from(IPv6AddressSection addr) { IPv6StringParams result = new IPv6StringParams(); if(compressOptions != null) { @@ -3131,7 +3220,7 @@ private IPv6StringParams from(IPv6AddressSection addr) { result.setSegmentStrPrefix(segmentStrPrefix); return result; } - + public static IPv6StringOptions from(IPStringOptions opts) { if(opts instanceof IPv6StringOptions) { return (IPv6StringOptions) opts; @@ -3153,92 +3242,91 @@ public static IPv6StringOptions from(IPStringOptions opts) { opts.splitDigits, opts.uppercase); } - + public static class Builder extends IPStringOptions.Builder { private boolean makeMixed; private IPStringOptions ipv4Options; - + //default is null, which means no compression private CompressOptions compressOptions; - - + public Builder() { super(IPv6Address.DEFAULT_TEXTUAL_RADIX, IPv6Address.SEGMENT_SEPARATOR); } - + public Builder setCompressOptions(CompressOptions compressOptions) { this.compressOptions = compressOptions; return this; } - + public Builder setMakeMixed(boolean makeMixed) { this.makeMixed = makeMixed; return this; } - + public Builder setMakeMixed(IPStringOptions ipv4Options) { this.makeMixed = true; this.ipv4Options = ipv4Options; return this; } - + @Override public Builder setWildcardOptions(WildcardOptions wildcardOptions) { return (Builder) super.setWildcardOptions(wildcardOptions); } - + @Override public Builder setExpandedSegments(boolean expandSegments) { return (Builder) super.setExpandedSegments(expandSegments); } - + @Override public Builder setRadix(int base) { return (Builder) super.setRadix(base); } - + @Override public Builder setSeparator(Character separator) { return (Builder) super.setSeparator(separator); } - + @Override public Builder setZoneSeparator(char separator) { return (Builder) super.setZoneSeparator(separator); } - + @Override public Builder setAddressSuffix(String suffix) { return (Builder) super.setAddressSuffix(suffix); } - + @Override public Builder setSegmentStrPrefix(String prefix) { return (Builder) super.setSegmentStrPrefix(prefix); } - + @Override public Builder setReverse(boolean reverse) { return (Builder) super.setReverse(reverse); } - + @Override public Builder setUppercase(boolean upper) { return (Builder) super.setUppercase(upper); } - + @Override public Builder setSplitDigits(boolean splitDigits) { return (Builder) super.setSplitDigits(splitDigits); } - + @Override public IPv6StringOptions toOptions() { return new IPv6StringOptions(base, expandSegments, wildcardOption, wildcards, segmentStrPrefix, makeMixed, ipv4Options, compressOptions, separator, zoneSeparator, addrLabel, addrSuffix, reverse, splitDigits, uppercase); } } } - + @Override public RangeList getZeroSegments() { if(zeroSegments == null) { @@ -3260,11 +3348,11 @@ public boolean isZero() { RangeList ranges = getZeroSegments(); return ranges.size() == 1 && ranges.getRange(0).length == getSegmentCount(); } - + private int[] getCompressIndexAndCount(CompressOptions options) { return getCompressIndexAndCount(options, false); } - + /** * Chooses a single segment to be compressed, or null if no segment could be chosen. * @param options @@ -3315,7 +3403,7 @@ private int[] getCompressIndexAndCount(CompressOptions options, boolean createMi } return null; } - + /** * Each IPv6StringParams has settings to write exactly one IPv6 address section string * @@ -3323,19 +3411,19 @@ private int[] getCompressIndexAndCount(CompressOptions options, boolean createMi * */ static class IPv6StringParams extends IPAddressStringParams { - + int firstCompressedSegmentIndex, nextUncompressedIndex; //the start and end of any compressed section - + boolean hostCompressed; //whether the host was compressed, which with some prefix configurations means we must print the network prefix to indicate that the host is full range - + IPv6StringParams() { this(-1, 0); } - + IPv6StringParams(int firstCompressedSegmentIndex, int compressedCount) { this(false, firstCompressedSegmentIndex, compressedCount, false, IPv6Address.SEGMENT_SEPARATOR, IPv6Address.ZONE_SEPARATOR); } - + private IPv6StringParams( boolean expandSegments, int firstCompressedSegmentIndex, @@ -3348,20 +3436,20 @@ private IPv6StringParams( this.firstCompressedSegmentIndex = firstCompressedSegmentIndex; this.nextUncompressedIndex = firstCompressedSegmentIndex + compressedCount; } - + public boolean endIsCompressed(IPAddressStringDivisionSeries addr) { return nextUncompressedIndex >= addr.getDivisionCount(); } - + public boolean isCompressed(IPAddressStringDivisionSeries addr) { return firstCompressedSegmentIndex >= 0; } - + @Override public int getTrailingSeparatorCount(IPv6AddressSection addr) { return getTrailingSepCount(addr); } - + public int getTrailingSepCount(IPAddressStringDivisionSeries addr) { int divisionCount = addr.getDivisionCount(); if(divisionCount == 0) { @@ -3377,7 +3465,7 @@ public int getTrailingSepCount(IPAddressStringDivisionSeries addr) { } return count; } - + @Override public int getStringLength(IPv6AddressSection addr) { int count = getSegmentsStringLength(addr); @@ -3388,7 +3476,7 @@ public int getStringLength(IPv6AddressSection addr) { count += getAddressLabelLength(); return count; } - + @Override public StringBuilder append(StringBuilder builder, IPv6AddressSection addr, CharSequence zone) { /* @@ -3480,28 +3568,28 @@ public IPv6StringParams clone() { static class EmbeddedIPv6AddressSection extends IPv6AddressSection { private static final long serialVersionUID = 4L; - + private final IPAddressSection encompassingSection; EmbeddedIPv6AddressSection(IPAddressSection encompassingSection, IPv6AddressSegment subSegments[], int startIndex) { super(subSegments, startIndex, false); this.encompassingSection = encompassingSection; } - + @Override public boolean isPrefixBlock() { return encompassingSection.isPrefixBlock(); } } - + public static class IPv6v4MixedAddressSection extends IPAddressDivisionGrouping { private static final long serialVersionUID = 4L; - + private final IPv6AddressSection ipv6Section; private final IPv4AddressSection ipv4Section; private String string; - + private IPv6v4MixedAddressSection( IPv6AddressSection ipv6Section, IPv4AddressSection ipv4Section) { @@ -3519,7 +3607,7 @@ private IPv6v4MixedAddressSection( this.ipv4Section = ipv4Section; this.ipv6Section = ipv6Section; } - + private static IPAddressDivision[] createSegments(IPv6AddressSection ipv6Section, IPv4AddressSection ipv4Section) { int ipv6Len = ipv6Section.getSegmentCount(); int ipv4Len = ipv4Section.getSegmentCount(); @@ -3556,7 +3644,7 @@ public boolean isPrefixBlock() { } return ipv4Section.isPrefixBlock(); } - + @Override public String toString() { if(string == null) { @@ -3568,7 +3656,7 @@ public String toString() { } return string; } - + @Override protected boolean isSameGrouping(AddressDivisionGroupingBase o) { if(o instanceof IPv6v4MixedAddressSection) { @@ -3577,7 +3665,7 @@ protected boolean isSameGrouping(AddressDivisionGroupingBase o) { } return false; } - + @Override public boolean equals(Object o) { if(o == this) { @@ -3590,32 +3678,32 @@ public boolean equals(Object o) { return false; } } - + static class IPv6v4MixedParams implements IPAddressStringWriter, Cloneable { private IPAddressStringParams ipv4Params; //params for the IPv4 part of a mixed IPv6/IPv4 address a:b:c:d:e:f:1.2.3.4 private IPv6StringParams ipv6Params; - + @SuppressWarnings("unchecked") IPv6v4MixedParams(IPv6AddressSectionString ipv6Variation, IPAddressPartConfiguredString ipv4Variation) { this.ipv4Params = (IPAddressStringParams) ipv4Variation.stringParams; this.ipv6Params = ipv6Variation.stringParams; } - + IPv6v4MixedParams(IPv6StringParams ipv6Params, IPStringOptions ipv4Opts) { this.ipv4Params = IPAddressSection.toIPParams(ipv4Opts); this.ipv6Params = ipv6Params; } - + @Override public char getTrailingSegmentSeparator() { return ipv4Params.getTrailingSegmentSeparator(); } - + @Override public int getTrailingSeparatorCount(IPv6v4MixedAddressSection addr) { return ipv4Params.getTrailingSeparatorCount(addr.ipv4Section); } - + public int getStringLength(IPv6v4MixedAddressSection addr, CharSequence zone) { int ipv6length = ipv6Params.getSegmentsStringLength(addr.ipv6Section); int ipv4length = ipv4Params.getSegmentsStringLength(addr.ipv4Section); @@ -3629,12 +3717,12 @@ public int getStringLength(IPv6v4MixedAddressSection addr, CharSequence zone) { length += ipv6Params.getAddressLabelLength(); return length; } - + @Override public String toString(IPv6v4MixedAddressSection addr) { return toString(addr, null); } - + @Override public String toString(IPv6v4MixedAddressSection addr, CharSequence zone) { int length = getStringLength(addr, zone); @@ -3643,17 +3731,17 @@ public String toString(IPv6v4MixedAddressSection addr, CharSequence zone) { AddressStringParams.checkLengths(length, builder); return builder.toString(); } - + @Override public int getDivisionStringLength(AddressStringDivision seg) { return ipv6Params.getDivisionStringLength(seg); } - + @Override public StringBuilder appendDivision(StringBuilder builder, AddressStringDivision seg) { return ipv6Params.appendDivision(builder, seg); } - + public StringBuilder append(StringBuilder builder, IPv6v4MixedAddressSection addr, CharSequence zone) { ipv6Params.appendLabel(builder); ipv6Params.appendSegments(builder, addr.ipv6Section); @@ -3661,7 +3749,7 @@ public StringBuilder append(StringBuilder builder, IPv6v4MixedAddressSection add builder.append(ipv6Params.getTrailingSegmentSeparator()); } ipv4Params.appendSegments(builder, addr.ipv4Section); - + /* * rfc 4038: for bracketed addresses, zone is inside and prefix outside, putting prefix after zone. * @@ -3684,21 +3772,21 @@ protected int getPrefixStringLength(IPv6v4MixedAddressSection addr) { } return 0; } - + public void appendPrefixIndicator(StringBuilder builder, IPv6v4MixedAddressSection addr) { if(requiresPrefixIndicator(addr.ipv6Section) || requiresPrefixIndicator(addr.ipv4Section)) { ipv6Params.appendPrefixIndicator(builder, addr); } } - + protected boolean requiresPrefixIndicator(IPv4AddressSection ipv4Section) { return ipv4Section.isPrefixed() && !ipv4Params.preferWildcards(); } - + protected boolean requiresPrefixIndicator(IPv6AddressSection ipv6Section) { return ipv6Section.isPrefixed() && (!ipv6Params.preferWildcards() || ipv6Params.hostCompressed); } - + @Override public IPv6v4MixedParams clone() { try { @@ -3714,12 +3802,12 @@ public IPv6v4MixedParams clone() { static class IPv6AddressSectionStringCollection extends IPAddressPartStringSubCollection { private final CharSequence zone; - + IPv6AddressSectionStringCollection(IPv6AddressSection addr, CharSequence zone) { super(addr); this.zone = zone; } - + @Override public Iterator iterator() { return new IPAddressConfigurableStringIterator() { @@ -3730,17 +3818,17 @@ public IPv6AddressSectionString next() { }; } } - + static class IPv6v4MixedStringCollection extends IPAddressPartStringSubCollection> { - + private final CharSequence zone; - + public IPv6v4MixedStringCollection(IPv6v4MixedAddressSection part, CharSequence zone) { super(part); this.zone = zone; } - + @Override public Iterator> iterator() { return new IPAddressConfigurableStringIterator() { @@ -3760,19 +3848,19 @@ public String getString() { }; } } - + static class IPv6StringCollection extends IPAddressPartStringCollection { - + @Override protected void add(IPAddressPartStringSubCollection collection) { super.add(collection); } - + @Override protected void addAll(IPAddressPartStringCollection collections) { super.addAll(collections); } - + /** * Capable of building any and all possible representations of IPv6 addresses. * Not all such representations are necessarily something you might consider valid. @@ -3794,11 +3882,11 @@ protected void addAll(IPAddressPartStringCollection collections) { */ static class IPv6StringBuilder extends AddressPartStringBuilder { - + IPv6StringBuilder(IPv6AddressSection address, IPv6StringBuilderOptions opts, CharSequence zone) { super(address, opts, new IPv6AddressSectionStringCollection(address, zone)); } - + private void addUppercaseVariations(ArrayList allParams, int base) { boolean lowerOnly = true; //by default we use NETWORK_ONLY wildcards (we use prefix notation otherwise) so here we check lower values only for alphabetic if(options.includes(IPv6StringBuilderOptions.UPPERCASE) && addressSection.hasUppercaseVariations(base, lowerOnly)) { @@ -3811,7 +3899,7 @@ private void addUppercaseVariations(ArrayList allParams, int b } } } - + private void addAllExpansions(int firstCompressedIndex, int count, int segmentCount) { IPv6StringParams stringParams = new IPv6StringParams(firstCompressedIndex, count); int base = stringParams.getRadix(); @@ -3853,9 +3941,9 @@ private void addAllExpansions(int firstCompressedIndex, int count, int segmentCo } } } - + addUppercaseVariations(allParams, base); - + for(int i=0; i { private final CharSequence zone; - + IPv6v4MixedStringBuilder(IPv6v4MixedAddressSection address, IPv6StringBuilderOptions opts, CharSequence zone) { super(address, opts, new IPv6v4MixedStringCollection(address, zone)); this.zone = zone; @@ -3953,29 +4041,29 @@ protected void addAllVariations() { } } } - + private static class IPv6AddressSectionString extends IPAddressPartConfiguredString { private final CharSequence zone; - + IPv6AddressSectionString(IPv6AddressSection addr, IPv6StringParams stringParams, CharSequence zone) { super(addr, stringParams); this.zone = zone; } - + @SuppressWarnings("unchecked") @Override public IPv6StringMatcher getNetworkStringMatcher(boolean isEntireAddress, IPAddressSQLTranslator translator) { return new IPv6StringMatcher(this, translator); } - + public boolean endIsCompressed() { return stringParams.endIsCompressed(addr); } - + public boolean isCompressed() { return stringParams.isCompressed(addr); } - + @Override public String getString() { if(string == null) { @@ -3984,7 +4072,7 @@ public String getString() { return string; } } - + public static class IPv6StringBuilderOptions extends IPStringBuilderOptions { public static final int MIXED = 0x2; @@ -4001,14 +4089,14 @@ public static class IPv6StringBuilderOptions extends IPStringBuilderOptions { public final IPv4StringBuilderOptions mixedOptions; public final IPv4StringBuilderOptions ipv4ConverterOptions; public final IPv4AddressConverter converter; - + public static final IPv6StringBuilderOptions STANDARD_OPTS = new IPv6StringBuilderOptions( IPStringBuilderOptions.BASIC | IPv6StringBuilderOptions.UPPERCASE | IPStringBuilderOptions.LEADING_ZEROS_FULL_ALL_SEGMENTS | IPv6StringBuilderOptions.COMPRESSION_ALL_FULL, new IPv4StringBuilderOptions(IPStringBuilderOptions.BASIC | IPStringBuilderOptions.LEADING_ZEROS_FULL_ALL_SEGMENTS)); - + public static final IPv6StringBuilderOptions ALL_OPTS = new IPv6StringBuilderOptions( IPStringBuilderOptions.BASIC | @@ -4037,7 +4125,7 @@ public IPv6StringBuilderOptions(int options) { public IPv6StringBuilderOptions(int options, IPv4StringBuilderOptions mixedOptions) { this(options, mixedOptions, null, null); } - + public IPv6StringBuilderOptions(int options, IPv4StringBuilderOptions mixedOptions, IPv4AddressConverter ipv4AddressConverter, IPv4StringBuilderOptions ipv4ConverterOptions) { super(options | (mixedOptions == null ? 0 : MIXED) | (ipv4ConverterOptions == null ? 0 : IPV4_CONVERSIONS)); if(includes(MIXED) && mixedOptions == null) { diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressSegment.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressSegment.java index 954a4dd7..4335ff27 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressSegment.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressSegment.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -448,7 +448,12 @@ public boolean prefixContains(IPAddressSegment other, int segmentPrefixLength) { public boolean prefixEquals(AddressSegment other, int segmentPrefixLength) { return this == other || (super.prefixEquals(other, segmentPrefixLength) && other instanceof IPv6AddressSegment); } - + + @Override + public boolean overlaps(AddressSegment other) { + return this == other || (overlapsSeg(other) && other instanceof IPv6AddressSegment); + } + @Override public boolean contains(AddressSegment other) { return this == other || (containsSeg(other) && other instanceof IPv6AddressSegment); diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressSeqRange.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressSeqRange.java index 070e7cfc..3e9164a0 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressSeqRange.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressSeqRange.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Sean C Foley + * Copyright 2018-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressStringParameters.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressStringParameters.java index 360ee60f..c13c9678 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressStringParameters.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressStringParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressTrie.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressTrie.java index f4ce94bd..95f3c35e 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressTrie.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/ipv6/IPv6AddressTrie.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -175,8 +175,8 @@ public CachingIterator blockSizeCachingAllNode @SuppressWarnings("unchecked") @Override - public CachingIterator containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") @@ -293,6 +293,45 @@ public IPv6TrieNode clone() { public boolean equals(Object o) { return o instanceof IPv6TrieNode && super.equals(o); } + + static class IPv6TrieKeyData extends TrieKeyData { + long uint64HighVal, uint64LowVal, mask64HighVal, mask64LowVal, nextBitMask64Val; + + @Override + public boolean is128Bits() { + return true; + } + + @Override + public long getUint64LowVal() { + return uint64LowVal; + } + + @Override + public long getUint64HighVal() { + return uint64HighVal; + } + + @Override + public long getMask64HighVal() { + return mask64HighVal; + } + + @Override + public long getMask64LowVal() { + return mask64LowVal; + } + + @Override + public long getNextBitMask64Val() { + return nextBitMask64Val; + } + } + + @Override + protected IPv6TrieKeyData getTrieKeyCache(IPv6Address addr) { + return addr.getTrieKeyCache(); + } } @Override @@ -367,8 +406,8 @@ public CachingIterator blockSizeCachingAllNode @SuppressWarnings("unchecked") @Override - public CachingIterator containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddress.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddress.java index 3c41a608..54aefe05 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddress.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddress.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package inet.ipaddr.mac; +import java.math.BigInteger; import java.net.NetworkInterface; import java.util.Iterator; import java.util.stream.Stream; @@ -201,6 +202,14 @@ protected static String getMessage(String key) { return Address.getMessage(key); } + @Override + public BigInteger enumerate(Address other) { + if(other instanceof MACAddress) { + return MACAddressSection.enumerate(getSection(), other.getSection()); + } + return null; + } + @Override public MACAddressNetwork getNetwork() { return defaultMACNetwork(); @@ -608,6 +617,19 @@ public MACAddress replace(int startIndex, int endIndex, MACAddress replacement, return checkIdentity(getSection().replace(startIndex, endIndex, replacement.getSection(), replacementIndex, replacementIndex + (endIndex - startIndex))); } + /** + * Replaces segments starting from startIndex with as many segments as possible from the replacement section + * + * @param startIndex + * @param replacement + * @throws IndexOutOfBoundsException + * @return + */ + public MACAddress replace(int startIndex, MACAddressSection replacement) { + int replacementCount = Math.min(getSegmentCount() - startIndex, replacement.getSegmentCount()); + return checkIdentity(getSection().replace(startIndex, startIndex + replacementCount, replacement, 0, replacementCount)); + } + public AddressDivisionGrouping getDottedAddress() { return getSection().getDottedGrouping(); } diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressAssociativeTrie.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressAssociativeTrie.java index 40eadda8..3bba0986 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressAssociativeTrie.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressAssociativeTrie.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -190,8 +190,8 @@ public CachingIterator, MACAddress, C> blockSizeCa @SuppressWarnings("unchecked") @Override - public CachingIterator, MACAddress, C> containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator, MACAddress, C>) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator> containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator>) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") @@ -383,8 +383,8 @@ public CachingIterator, MACAddress, C> blockSizeCa @SuppressWarnings("unchecked") @Override - public CachingIterator, MACAddress, C> containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator, MACAddress, C>) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator> containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator>) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressNetwork.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressNetwork.java index 4ede7b87..800f41e3 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressNetwork.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressNetwork.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2020 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressSection.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressSection.java index d4ab427e..c72265e2 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressSection.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressSection.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,8 +41,8 @@ import inet.ipaddr.format.standard.AddressDivisionGrouping; import inet.ipaddr.format.standard.AddressDivisionGrouping.StringOptions.Wildcards; import inet.ipaddr.format.string.AddressStringDivisionSeries; -import inet.ipaddr.format.util.AddressComponentSpliterator; import inet.ipaddr.format.util.AddressComponentRangeSpliterator; +import inet.ipaddr.format.util.AddressComponentSpliterator; import inet.ipaddr.ipv6.IPv6AddressNetwork; import inet.ipaddr.ipv6.IPv6AddressSection; import inet.ipaddr.mac.MACAddressNetwork.MACAddressCreator; @@ -969,9 +969,10 @@ public long upperLongValue() { private long getLongValue(boolean lower) { int segCount = getSegmentCount(); long result = 0; + int bitsPerSeg = getBitsPerSegment(); for(int i = 0; i < segCount; i++) { MACAddressSegment seg = getSegment(i); - result = (result << getBitsPerSegment()) | (lower ? seg.getSegmentValue() : seg.getUpperSegmentValue()); + result = (result << bitsPerSeg) | (lower ? seg.getSegmentValue() : seg.getUpperSegmentValue()); } return result; } @@ -1507,26 +1508,20 @@ public MACAddressSection increment(long increment) { return this; } if(!isExtended() || getSegmentCount() < 8) { - long lowerValue = longValue(); - long upperValue = upperLongValue(); - long count = getCount().longValue(); - checkOverflow(increment, lowerValue, upperValue, count, () -> getMaxValueLong(getSegmentCount())); + checkOverflow(increment, this::longValue, this::upperLongValue, () -> getCount().longValue(), this::isSequential, () -> getMaxValueLong(getSegmentCount())); return increment( this, increment, getAddressCreator(), - getCount().longValue(), - longValue(), - upperLongValue(), + () -> getCount().longValue(), + this::longValue, + this::upperLongValue, this::getLower, this::getUpper, getNetwork().getPrefixConfiguration().allPrefixedAddressesAreSubnets() ? null : getPrefixLength()); } - BigInteger lowerValue = getValue(); - BigInteger upperValue = getUpperValue(); - BigInteger count = getCount(); BigInteger bigIncrement = BigInteger.valueOf(increment); - checkOverflow(increment, bigIncrement, lowerValue, upperValue, count, () -> getMaxValue(getSegmentCount())); + checkOverflow(increment, bigIncrement, this::getValue, this::getUpperValue, this::getCount, this::isSequential, () -> getMaxValue(getSegmentCount())); Integer prefixLength = getNetwork().getPrefixConfiguration().allPrefixedAddressesAreSubnets() ? null : getPrefixLength(); MACAddressSection result = fastIncrement( this, @@ -1728,6 +1723,23 @@ static String toNormalizedString(IPStringOptions opts, AddressStringDivisionSeri return toParams(opts).toString(section); } + @Override + public boolean overlaps(AddressSection other) { + return other instanceof MACAddressSection && overlaps((MACAddressSection) other); + } + + /** + * @param other + * @return whether this section contains the given address section + */ + public boolean overlaps(MACAddressSection other) { + //check if they are comparable first + if(addressSegmentIndex != other.addressSegmentIndex || isExtended() != other.isExtended()) { + return false; + } + return overlaps(this, other); + } + @Override public boolean contains(AddressSection other) { return other instanceof MACAddressSection && contains((MACAddressSection) other); @@ -1750,6 +1762,37 @@ public boolean contains(MACAddressSection other) { return true; } + // only used by addresses, so we know the segment count is greater than 0 + static BigInteger enumerate(MACAddressSection addr, AddressSection other) { + if(!addr.isExtended()) { + Long result = enumerateSmall(addr, other); + if(result != null) { + return BigInteger.valueOf(result); + } + return null; + } + return enumerateBig(addr, other); + } + + @Override + public BigInteger enumerate(AddressSection other) { + if(other instanceof MACAddressSection) { + checkSegmentCount(other); + MACAddressSection otherSec = (MACAddressSection) other; + if(addressSegmentIndex != otherSec.addressSegmentIndex) { + throw new AddressPositionException(this, addressSegmentIndex, otherSec.addressSegmentIndex); + } else if(!isExtended() || getSegmentCount() <= 7) { + Long result = enumerateSmall(this, other); + if(result != null) { + return BigInteger.valueOf(result); + } + return null; + } + return enumerateBig(this, other); + } + return null; + } + @Override public boolean prefixEquals(AddressSection o) { if(o instanceof MACAddressSection) { diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressSegment.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressSegment.java index a4f65a56..85013fbb 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressSegment.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressSegment.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -370,6 +370,11 @@ public boolean prefixEquals(AddressSegment o, int segmentPrefixLength) { return false; } + @Override + public boolean overlaps(AddressSegment other) { + return this == other || (other.getSegmentValue() <= upperValue && other.getUpperSegmentValue() >= value && other instanceof MACAddressSegment); + } + @Override public boolean contains(AddressSegment other) { return other instanceof MACAddressSegment && other.getSegmentValue() >= value && other.getUpperSegmentValue() <= upperValue; diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressTrie.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressTrie.java index d5a8213d..99a81571 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressTrie.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/mac/MACAddressTrie.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Sean C Foley + * Copyright 2020-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -187,8 +187,8 @@ public CachingIterator blockSizeCachingAllNodeIt @SuppressWarnings("unchecked") @Override - public CachingIterator containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") @@ -379,8 +379,8 @@ public CachingIterator blockSizeCachingAllNodeIt @SuppressWarnings("unchecked") @Override - public CachingIterator containingFirstIterator(boolean forwardSubNodeOrder) { - return (CachingIterator) super.containingFirstIterator(forwardSubNodeOrder); + public Iterator containingFirstIterator(boolean forwardSubNodeOrder) { + return (Iterator) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/HostRangeTest.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/HostRangeTest.java index d3e3f5c0..1e655c2d 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/HostRangeTest.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/HostRangeTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2020 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/HostTest.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/HostTest.java index 3f9307d1..048f90ed 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/HostTest.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/HostTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/IPAddressAllTest.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/IPAddressAllTest.java index 0b474da9..76dd3ef1 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/IPAddressAllTest.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/IPAddressAllTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -514,14 +514,22 @@ public class IPAddressAllTest extends IPAddressRangeTest { }; private static final IPAddressStringParameters DEFAULT_OPTIONS = new IPAddressStringParameters.Builder().toParams(); - + private static final IPAddressStringParameters EXTRANEOUS_DIGITS_OPTIONS = DEFAULT_OPTIONS.toBuilder(). + getIPv4AddressParametersBuilder().allow_inet_aton_extraneous_digits(true).getParentBuilder().toParams(); + private static final IPAddressStringParameters EXTRANEOUS_DIGITS_OPTIONS_IPV4 = EXTRANEOUS_DIGITS_OPTIONS.toBuilder().allowIPv6(false).toParams(); + IPAddressAllTest(AddressCreator creator) { super(creator); } @Override - protected IPAddressString createInetAtonAddress(String x) { - return createAddress(x); + protected IPAddressString createInetAtonAddress(String x) { // IPv6 disallowed, extraneous chars allowed + return createAddress(x, EXTRANEOUS_DIGITS_OPTIONS_IPV4); + } + + @Override + protected IPAddressString createIPInetAtonAddress(String x) { // extraneous chars allowed + return createAddress(x, EXTRANEOUS_DIGITS_OPTIONS); } @Override @@ -534,6 +542,11 @@ boolean isLenient() { return true; } + @Override + boolean allowExtraneous() { + return true; + } + @Override void testStrings() { super.testStrings(); diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/IPAddressRangeTest.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/IPAddressRangeTest.java index e3baedb8..8b5fb2f9 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/IPAddressRangeTest.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/IPAddressRangeTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1234,9 +1234,6 @@ void testIPv6Strings(String addr, String singleOctal) { IPAddressString w = createAddress(addr); IPAddress ipAddr = w.getAddress(); - - //createList(w); - testIPv6Strings(w, ipAddr, normalizedString, @@ -1287,7 +1284,50 @@ void testRangeJoin(String inputs[], String expected[]) { addFailure(new Failure("failed expected: " + rangeList.get(i) + " actual: " + result[i], result[i])); } } - //System.out.println("matched up " + Arrays.asList(result)); + incrementTestCount(); + } + + void testOverlaps(boolean overlaps, String subnetStr1, String subnetStr2) { + IPAddress subnet1 = createAddress(subnetStr1).getAddress(); + IPAddress subnet2 = createAddress(subnetStr2).getAddress(); + boolean result = subnet1.overlaps(subnet2); + if(result != overlaps) { + addFailure(new Failure("failed expected overlap: " + overlaps + " for " + subnet1 + " with " + subnet2, subnet1)); + } + result = subnet2.overlaps(subnet1); + if(result != overlaps) { + addFailure(new Failure("failed expected overlap: " + overlaps + " for " + subnet2 + " with " + subnet1, subnet1)); + } + incrementTestCount(); + } + + void testOverlapsRange(boolean overlaps, String rangeLow, String rangeHigh, String subnetStr) { + IPAddressString w = createAddress(rangeLow); + IPAddressString w2 = createAddress(rangeHigh); + IPAddressSeqRange rng = w.getAddress().spanWithRange(w2.getAddress()); + IPAddress subnet = createAddress(subnetStr).getAddress(); + boolean result = rng.overlaps(subnet); + if(result != overlaps) { + addFailure(new Failure("failed expected overlap: " + overlaps + " for " + rng + " with " + subnet, subnet)); + } + incrementTestCount(); + } + + void testSubnetContainsRange(boolean contains, String rangeLow, String rangeHigh, String subnetStr) { + IPAddressString w = createAddress(rangeLow); + IPAddressString w2 = createAddress(rangeHigh); + IPAddressSeqRange rng = w.getAddress().spanWithRange(w2.getAddress()); + IPAddress subnet = createAddress(subnetStr).getAddress(); + boolean result = subnet.contains(rng); + if(result != contains) { + addFailure(new Failure("failed expected contains: " + contains + " for " + subnet + " containing " + rng, subnet));; + } + if(contains) { + result = rng.overlaps(subnet); + if(!result) { + addFailure(new Failure("failed expected overlap for " + rng + " with " + subnet, subnet));; + } + } incrementTestCount(); } @@ -4461,7 +4501,76 @@ void runTest() { !isNoAutoSubnets ? ((3 * 256) * (2 * 256)) - (3 * 256) : 0, RangeParameters.WILDCARD_AND_RANGE); - + testOverlapsRange(isAutoSubnets, "1.1.254.255", "1.2.0.0", "1.1.0.0/16"); + testOverlapsRange(isAutoSubnets, "1.1.254.255", "1.2.0.0", "1.0.0.0/14"); + testOverlapsRange(isAutoSubnets, "1.1.255.255", "1.2.0.0", "1.1.0.0/16"); + testOverlapsRange(isAutoSubnets, "1.1.255.255", "1.2.0.0", "1.0.0.0/14"); + testOverlapsRange(false, "1.1.254.255", "1.2.0.0", "1.1.253-254.1-3"); + testOverlapsRange(true, "1.1.254.255", "1.2.0.0", "1.1.253-255.1-3"); + testOverlapsRange(isAutoSubnets, "1.1.100.255", "1.2.0.0", "1.1.0.0/16"); + testOverlapsRange(true, "1.1.100.255", "1.2.0.0", "1.1.253-255.1-3"); + testOverlapsRange(false, "1.1.100.255", "1.2.0.0", "1.2.253-255.1-3"); + testOverlapsRange(false, "1.1.100.255", "1.2.0.0", "1.2-5.253-255.1-3"); + testOverlapsRange(true, "1.1.100.255", "1.2.0.0", "1.2-5.*.*"); + testOverlapsRange(true, "1.1.100.255", "1.2.0.0", "1.2-5.*.*"); + testOverlapsRange(true, "1.1.100.255", "1.2.0.0", "1.1-5.*.*"); + testOverlapsRange(true, "1.1.100.255", "1.2.0.0", "1.0-5.*.*"); + testOverlapsRange(true, "1.1.100.255", "1.2.0.0", "1.0-2.*.*"); + testOverlapsRange(true, "1.1.100.255", "1.2.0.0", "1.0-1.*.*"); + testOverlapsRange(isAutoSubnets, "1::1", "1::a:b:c:d", "1::/64"); + testOverlapsRange(false, "1::1", "1::a:b:c:d", "1:2::/64"); + testOverlapsRange(true, "1::1", "1::a:b:c:d", "1::a:b:c:d"); + testOverlapsRange(true, "1::1", "1::a:b:c:d", "1::1"); + testOverlapsRange(true, "1::1", "1::a:b:c:d", "1::a:b:c:*"); + testOverlapsRange(true, "1::2:1", "1::5:ff", "1::1-3:1"); + testOverlapsRange(true, "1::2:1", "1::5:ff", "1::2-3:1"); + testOverlapsRange(true, "1::2:1", "1::5:ff", "1::5-6:1"); + testOverlapsRange(false, "1::2:1", "1::5:ff", "1::5-6:fff"); + + testOverlaps(true, "1::2-4:1", "1::1-3:1"); + testContains("1::2-4:1", "1::1-3:1", false, false); + testOverlaps(true, "1::1-4:1", "1::1-3:1"); + testContains("1::1-4:1", "1::1-3:1", true, false); + testOverlaps(false, "1::1-4:1", "1::1-3:2"); + testContains("1::1-4:1", "1::1-3:2", false, false); + testOverlaps(false, "2::1-4:2", "1::1-3:2"); + testContains("2::1-4:2", "1::1-3:2", false, false); + testOverlaps(true, "1-2::1-4:2", "1::1-3:2"); + testContains("1-2::1-4:2", "1::1-3:2", true, false); + testOverlaps(true, "1-2::1-4:2", "1-2::1-4:2"); + testContains("1-2::1-4:2", "1-2::1-4:2", true, true); + + testSubnetContainsRange(false, "1.1.254.255", "1.2.0.0", "1.1.0.0/16"); + testSubnetContainsRange(isAutoSubnets, "1.1.254.255", "1.2.0.0", "1.0.0.0/14"); + testSubnetContainsRange(false, "1.1.255.255", "1.2.0.0", "1.1.0.0/16"); + testSubnetContainsRange(isAutoSubnets, "1.1.255.255", "1.2.0.0", "1.0.0.0/14"); + testSubnetContainsRange(false, "1.1.254.255", "1.2.0.0", "1.1.253-254.1-3"); + testSubnetContainsRange(false, "1.1.254.255", "1.2.0.0", "1.1.253-255.1-3"); + testSubnetContainsRange(false, "1.1.100.255", "1.2.0.0", "1.1.0.0/16"); + testSubnetContainsRange(false, "1.1.100.255", "1.2.0.0", "1.1.253-255.1-3"); + testSubnetContainsRange(false, "1.1.100.255", "1.2.0.0", "1.2.253-255.1-3"); + testSubnetContainsRange(false, "1.1.100.255", "1.2.0.0", "1.2-5.253-255.1-3"); + testSubnetContainsRange(false, "1.1.100.255", "1.2.0.0", "1.2-5.*.*"); + testSubnetContainsRange(false, "1.1.100.255", "1.2.0.0", "1.2-5.*.*"); + testSubnetContainsRange(true, "1.1.100.255", "1.2.0.0", "1.1-5.*.*"); + testSubnetContainsRange(true, "1.1.100.255", "1.2.0.0", "1.0-5.*.*"); + testSubnetContainsRange(true, "1.1.100.255", "1.2.0.0", "1.0-2.*.*"); + testSubnetContainsRange(false, "1.1.100.255", "1.2.0.0", "1.0-1.*.*"); + testSubnetContainsRange(isAutoSubnets, "1::1", "1::a:b:c:d", "1::/64"); + testSubnetContainsRange(false, "1::1", "1::a:b:c:d", "1:2::/64"); + testSubnetContainsRange(false, "1::1", "1::a:b:c:d", "1::a:b:c:d"); + testSubnetContainsRange(false, "1::1", "1::a:b:c:d", "1::1"); + testSubnetContainsRange(false, "1::1", "1::a:b:c:d", "1::a:b:c:*"); + testSubnetContainsRange(false, "1::a:2:c:1", "1::a:b:c:d", "1::a:*:c:*"); // 1::a:3:0:0 not in the subnet + testSubnetContainsRange(false, "1::a:2:c:1", "1::a:3:c:d", "1::a:*:c:*"); // 1::a:3:0:0 not in the subnet + testSubnetContainsRange(false, "1::a:ffff:c:1", "1::b:0:c:d", "1::a-b:*:c:*"); // 1::b:0:0:0 not in the subnet + testSubnetContainsRange(true, "1::a:2:c:1", "1::a:2:c:d", "1::a:*:c:*"); + testSubnetContainsRange(true, "1::a:2:c:1", "1::a:2:c:d", "1::a:2:c:*"); + testSubnetContainsRange(true, "1::a:2:c:1", "1::a:b:c:d", "1::a:*:*:*"); + testSubnetContainsRange(true, "1::a:b:c:1", "1::a:b:c:d", "1::a:b:c:*"); + testSubnetContainsRange(true, "1::1", "1::a:b:c:d", "1:*"); + testSubnetContainsRange(false, "1::2:1", "1::5:ff", "1::1-3:1"); + ipv4test(true, "1.1.*.100-101", RangeParameters.WILDCARD_AND_RANGE); ipv4test(true, "1.2.*.101-100", RangeParameters.WILDCARD_AND_RANGE);//downwards range ipv4test(false, "1.2.*.1010-100", RangeParameters.WILDCARD_AND_RANGE);//downwards range @@ -5533,28 +5642,61 @@ void runTest() { testMerge("*:*", "8000::/1", "::/1"); testMerge("8000::/1", "8000::/1", "8000::/1"); testMerge("::/1", "::/1", "::/1"); - + testMergeRange("*:*", "::/1", "8000::/1"); testMergeRange("*:*", "8000::/1", "::/1"); testMergeRange("8000::/1", "8000::/1", "8000::/1"); testMergeRange("::/1", "::/1", "::/1"); } - + testMerge("0-127.*", "0-127.*", "1.2.3.4"); - + testMergeRange("*.*", "*.*", "1.2.3.4"); testMergeRange("*.*", "1.2.3.4", "*.*"); testMergeRange("*.*", "*.*", "*.*"); - + testMergeRange("*:*", "*:*", "::"); testMergeRange("*:*", "::", "*:*"); testMergeRange("*:*", "*:*", "*:*"); + + if(!isNoAutoSubnets) { + testMergeMixed(new String[]{"1.2.2.0/25", "1.2.3.128/25", "192.168.0.0/28"}, new String[]{"1:2:3:4::/64"}, null, null, "192.168.0.0/29", "192.168.0.8/29", "1:2:3:4:8000::/65", "1.2.2.0/25", "1.2.3.128/25", "1.2.2.0/25", "1:2:3:4::/66", "1:2:3:4:4000::/66"); + testMergeMixed(new String[]{"1.2.2.0/25", "1.2.3.128/25", "192.168.0.0/28"}, new String[]{"1:2:3:4::/64"}, null, null, null, "192.168.0.0/29", null, "192.168.0.8/29", "1:2:3:4:8000::/65", "1.2.2.0/25", "1.2.3.128/25", "1.2.2.0/25", "1:2:3:4::/66", "1:2:3:4:4000::/66"); + testMergeMixed(new String[]{"1.2.2.0/25", "1.2.3.128/25", "192.168.0.0/28"}, new String[]{"1:2:3:4::/64"}, null, null, null, null, "192.168.0.0/29", null, "192.168.0.8/29", "1:2:3:4:8000::/65", "1.2.2.0/25", "1.2.3.128/25", "1.2.2.0/25", "1:2:3:4::/66", "1:2:3:4:4000::/66"); + testMergeMixed(new String[]{"1.2.2.0/25", "1.2.3.128/25", "192.168.0.0/28"}, new String[]{"1:2:3:4::/64"}, null, null, "192.168.0.0/29", null, "192.168.0.8/29", "1:2:3:4:8000::/65", "1.2.2.0/25", "1.2.3.128/25", "1.2.2.0/25", "1:2:3:4::/66", "1:2:3:4:4000::/66"); + testMergeMixed(new String[]{"1.2.2.0/25", "1.2.3.128/25", "192.168.0.0/28"}, new String[]{"1:2:3:4::/64"}, null, null, "192.168.0.0/29", "192.168.0.8/29", null, "1:2:3:4:8000::/65", "1.2.2.0/25", "1.2.3.128/25", "1.2.2.0/25", "1:2:3:4::/66", "1:2:3:4:4000::/66"); + testMergeMixed(new String[]{"1.2.2.0/25", "1.2.3.128/25", "192.168.0.0/28"}, new String[]{"1:2:3:4::/64"}, null, null, "192.168.0.0/29", "192.168.0.8/29", "1:2:3:4:8000::/65", "1.2.2.0/25", "1.2.3.128/25", null, "1.2.2.0/25", "1:2:3:4::/66", "1:2:3:4:4000::/66"); + testMergeMixed(new String[]{"1.2.2.0/25", "1.2.3.128/25", "192.168.0.0/28"}, new String[]{"1:2:3:4::/64"}, null, null, "192.168.0.0/29", "192.168.0.8/29", "1:2:3:4:8000::/65", "1.2.2.0/25", "1.2.3.128/25", "1.2.2.0/25", "1:2:3:4::/66", "1:2:3:4:4000::/66", null); + testMergeMixed(new String[]{"1.2.2.0/25", "1.2.3.128/25", "192.168.0.0/28"}, new String[]{"1:2:3:4::/64"}, null, null, "192.168.0.0/29", "192.168.0.8/29", "1:2:3:4:8000::/65", "1.2.2.0/25", "1.2.3.128/25", "1.2.2.0/25", "1:2:3:4::/66", "1:2:3:4:4000::/66", null, null); + testMergeMixed(new String[]{"1.2.2.0/25", "1.2.3.128/25", "192.168.0.0/28"}, new String[]{"1:2:3:4::/64"}, null, null, null, "192.168.0.0/29", "192.168.0.8/29", "1:2:3:4:8000::/65", "1.2.2.0/25", "1.2.3.128/25", "1.2.2.0/25", "1:2:3:4::/66", "1:2:3:4:4000::/66", null, null); + + testMergeMixed(new String[]{"1.2.3.0/24", "1.2.4.0/23"}, new String[]{"1:0-ff:*"}, new String[]{"1.2.3-5.*"}, null, null, "1.2.5.0/24", "1:1:*", "1:3-ff:*", "1:0:*", "1.2.4.0/24", "1:2:ffff:*", "1.2.3.0/24", "1:2:0-1:*", "1:2:1-fffe:*"); + testMergeMixed(new String[]{"1.2.3.0/24", "1.2.4.0/23"}, new String[]{"1:0-ff:*"}, new String[]{"1.2.3-5.*"}, null, null, null, "1.2.5.0/24", "1:1:*", "1:3-ff:*", "1:0:*", "1.2.4.0/24", "1:2:ffff:*", "1.2.3.0/24", "1:2:0-1:*", "1:2:1-fffe:*"); + testMergeMixed(new String[]{"1.2.3.0/24", "1.2.4.0/23"}, new String[]{"1:0-ff:*"}, new String[]{"1.2.3-5.*"}, null, null, "1.2.5.0/24", "1:1:*", "1:3-ff:*", "1:0:*", null, "1.2.4.0/24", "1:2:ffff:*", "1.2.3.0/24", "1:2:0-1:*", "1:2:1-fffe:*"); + testMergeMixed(new String[]{"1.2.3.0/24", "1.2.4.0/23"}, new String[]{"1:0-ff:*"}, new String[]{"1.2.3-5.*"}, null, null, "1.2.5.0/24", "1:1:*", "1:3-ff:*", "1:0:*", "1.2.4.0/24", "1:2:ffff:*", "1.2.3.0/24", "1:2:0-1:*", "1:2:1-fffe:*", null); + testMergeMixed(new String[]{"1.2.3.0/24", "1.2.4.0/23"}, new String[]{"1:0-ff:*"}, new String[]{"1.2.3-5.*"}, null, null, null, "1.2.5.0/24", "1:1:*", "1:3-ff:*", "1:0:*", "1.2.4.0/24", "1:2:ffff:*", "1.2.3.0/24", "1:2:0-1:*", "1:2:1-fffe:*", null); + testMergeMixed(new String[]{"1.2.3.0/24", "1.2.4.0/23"}, new String[]{"1:0-ff:*"}, new String[]{"1.2.3-5.*"}, null, "1.2.5.0/24", "1:1:*", "1:3-ff:*", "1:0:*", null, null, "1.2.4.0/24", "1:2:ffff:*", "1.2.3.0/24", "1:2:0-1:*", "1:2:1-fffe:*"); + testMergeMixed(new String[]{"1.2.3.0/24", "1.2.4.0/23"}, new String[]{"1:0-ff:*"}, new String[]{"1.2.3-5.*"}, null, "1.2.5.0/24", "1:1:*", "1:3-ff:*", "1:0:*", "1.2.4.0/24", "1:2:ffff:*", "1.2.3.0/24", "1:2:0-1:*", "1:2:1-fffe:*", null, null); + + // sequential merge shorter for both ipv4 and ipv6 with this one + testMergeMixed( + new String[]{"1.2.3.0/24", "1.2.4.0/23", "128.0.0.0"}, new String[]{"1:0-ff:*", "8::2:3:0/112", "8::2:4:0/111"}, + new String[]{"1.2.3-5.*", "128.0.0.0"}, new String[]{"1:0-ff:*", "8::2:3-5:*"}, + "1.2.5.0/24", "1:1:*", "1:3-ff:*", "8::2:3-5:*", "1:0:*", "1.2.4.0/24", "128.0.0.0", "1:2:ffff:*", "1.2.3.0/24", "1:2:0-1:*", "1:2:1-fffe:*", null, null); + } + testMergeMixed( + new String[]{"1.2.3.*", "1.2.4-5.*"}, + new String[]{"ffff::0-ff/120", "ffff::100"}, + new String[]{"1.2.3-5.*"}, + new String[]{"ffff::0-100"}, + "1.2.3.*", "1.2.4.*", "1.2.5.*", "ffff::0-100"); + testMergeRange("0-127.*", "0-127.*", "1.2.3.4"); - + testMerge("1.2.3.4/32", "1.2.3.4"); testMergeRange("1.2.3.4", "1.2.3.4"); - + testMerge(isNoAutoSubnets ? "192.168.0.0-15/28" : "192.168.0.0/28", "192.168.0.0", "192.168.0.1", "192.168.0.2", "192.168.0.3", "192.168.0.4", "192.168.0.5", "192.168.0.6", "192.168.0.7", "192.168.0.8", @@ -5702,6 +5844,16 @@ void runTest() { testIncrement("ffff:3-4:ffff:ffff:ffff:1-2:2-3::", 7, "ffff:4:ffff:ffff:ffff:2:3::"); testIncrement("ffff:3-4:ffff:ffff:ffff:1-2:2-3::", 9, "ffff:4:ffff:ffff:ffff:2:3:2"); + testIncrement("1.*.*.*/16", 65539, "1.1.0.3"); + testIncrement("1::*:*:*/80", 65539, "1::1:3"); + + testIncrement("1.*.*.1-254", 65539, "1.1.2.8"); + testIncrement("1::*:*:1-fffe", 65539, "1::1:6"); + + testIncrement("::2-4:1-3", BigInteger.ONE.shiftLeft(3), "::4:3"); + testIncrement("::2-4:1-3", BigInteger.ONE.shiftLeft(128), null); + testIncrement("::2-4:1-3", BigInteger.ONE.shiftLeft(3).subtract(BigInteger.ONE), "::4:2"); + testSpanAndMerge("1.2.3.0", "1.2.3.1", 1, isNoAutoSubnets ? new String[] {"1.2.3.0-1/31"} : new String[] {"1.2.3.0/31"}, 1, new String[] {"1.2.3.0-1"});//rangeCount testSpanAndMerge("1.2.3.4", "1.2.5.8", 9, new String[] {"1.2.3.4-7/30", "1.2.3.8-15/29", "1.2.3.16-31/28", "1.2.3.32-63/27", "1.2.3.64-127/26", "1.2.3.128-255/25", "1.2.4.0-255/24", "1.2.5.0-7/29", "1.2.5.8"}, 3, new String[] {"1.2.3.4-255", "1.2.4.*", "1.2.5.0-8"}); diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/IPAddressTest.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/IPAddressTest.java index c4929016..25f8a902 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/IPAddressTest.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/IPAddressTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ import inet.ipaddr.AddressValueException; import inet.ipaddr.HostIdentifierString; import inet.ipaddr.IPAddress; +import inet.ipaddr.IPAddress.DualIPv4Pv6Arrays; import inet.ipaddr.IPAddress.IPVersion; import inet.ipaddr.IPAddressNetwork; import inet.ipaddr.IPAddressNetwork.IPAddressCreator; @@ -575,6 +576,10 @@ void ipv4test(boolean pass, IPAddressString x, boolean isZero, boolean notBothTh iptest(pass, x, isZero, notBothTheSame, true); } + void ip_inet_aton_test(boolean pass, String x, boolean isZero) { + iptest(pass, createIPInetAtonAddress(x), isZero, false, true); + } + void ipv6testOnly(int pass, String x) { iptest(pass == 0 ? false : true, createAddress(x), false, true, false); } @@ -1093,6 +1098,11 @@ void testContains(String cidr1, String cidr2, boolean result, boolean equal) { } else if(equal ? !(w2.contains(w) || conversionContains(w2, w)) : (w2.contains(w) || conversionContains(w2, w))) { addFailure(new Failure("failed " + w, w2)); } + if(firstContains) { + if(!w.overlaps(w2) || !w2.overlaps(w)) { + addFailure(new Failure("overlap passed " + w2, w)); + } + } } if(!convCont) { testStringContains(result, equal, wstr, w2str); @@ -1502,9 +1512,6 @@ void testMatches(boolean matches, String host1Str, String host2Str, boolean inet IPAddressString h2 = inet_aton ? createInetAtonAddress(host2Str) : createAddress(host2Str); boolean straightMatch = h1.equals(h2); if(matches != straightMatch && matches != conversionMatches(h1, h2)) { - h1.equals(h2); - System.out.println(h1 + ": " + h1.getAddress()); - System.out.println(h2 + ": " + h2.getAddress()); addFailure(new Failure("failed: match " + (matches ? "fails" : "passes") + " with " + h2, h1)); } else { if(matches != h2.equals(h1) && matches != conversionMatches(h2, h1)) { @@ -3471,11 +3478,135 @@ void testMerge2(String result, String result2, boolean prefix, String ... addres } incrementTestCount(); } + + void testMergeMixed(String ipv4ResultPref[], String ipv6ResultPref[], String ipv4ResultSeq[], String ipv6ResultSeq[], String ... addresses) { + IPAddress[] ipv4ResultsPref, ipv6ResultsPref, ipv4ResultsSeq, ipv6ResultsSeq, addrs1; + + ipv4ResultsPref = new IPAddress[ipv4ResultPref.length]; + int i = 0; + for(String str: ipv4ResultPref) { + ipv4ResultsPref[i++] = createAddress(str).getAddress(); + } + + ipv6ResultsPref = new IPAddress[ipv6ResultPref.length]; + i = 0; + for(String str: ipv6ResultPref) { + ipv6ResultsPref[i++] = createAddress(str).getAddress(); + } + + if(ipv4ResultSeq == null) { + ipv4ResultsSeq = ipv4ResultsPref; + } else { + ipv4ResultsSeq = new IPAddress[ipv4ResultSeq.length]; + i = 0; + for(String str: ipv4ResultSeq) { + ipv4ResultsSeq[i++] = createAddress(str).getAddress(); + } + } + + if(ipv6ResultSeq == null) { + ipv6ResultsSeq = ipv6ResultsPref; + } else { + ipv6ResultsSeq = new IPAddress[ipv6ResultSeq.length]; + i = 0; + for(String str: ipv6ResultSeq) { + ipv6ResultsSeq[i++] = createAddress(str).getAddress(); + } + } + + i = 0; + addrs1 = new IPAddress[addresses.length]; + for(String str: addresses) { + if(str == null) { + addrs1[i++] = null; + } else { + addrs1[i++] = createAddress(str).getAddress(); + } + } + + DualIPv4Pv6Arrays arrays = IPAddress.mergeToDualPrefixBlocks(addrs1); + if(arrays.addressesIPv4.length != ipv4ResultsPref.length) { + addFailure(new Failure("merged ipv4 addr len " + arrays.addressesIPv4.length + " does not match "+ ipv4ResultsPref.length, (AddressItem) null)); + } else { + i = 0; + for(IPAddress addr : arrays.addressesIPv4) { + IPAddress expected = ipv4ResultsPref[i++]; + if(!addr.equals(expected)) { + addFailure(new Failure("merged addr "+ addr +" does not match "+expected, expected)); + } + } + } + if(arrays.addressesIPv6.length != ipv6ResultsPref.length) { + addFailure(new Failure("merged ipv6 addr len " + arrays.addressesIPv6.length + " does not match "+ ipv6ResultsPref.length, (AddressItem) null)); + } else { + i = 0; + for(IPAddress addr: arrays.addressesIPv6) { + IPAddress expected = ipv6ResultsPref[i++]; + if(!addr.equals(expected)) { + addFailure(new Failure("merged addr " + addr + " does not match "+ expected, expected)); + } + } + } + + arrays = IPAddress.mergeToDualSequentialBlocks(addrs1); + if(arrays.addressesIPv4.length != ipv4ResultsSeq.length) { + addFailure(new Failure("merged ipv4 seq addr len " + arrays.addressesIPv4.length + " does not match " +ipv4ResultsSeq.length, (AddressItem) null)); + } else { + i = 0; + for(IPAddress addr: arrays.addressesIPv4) { + IPAddress expected = ipv4ResultsSeq[i++]; + if(!addr.equals(expected)) { + addFailure(new Failure("merged addr "+ addr +" does not match " + expected, expected)); + } + } + } + if(arrays.addressesIPv6.length != ipv6ResultsSeq.length) { + addFailure(new Failure("merged ipv6 seq addr len " + arrays.addressesIPv6.length + " does not match "+ ipv6ResultsSeq.length, (AddressItem) null)); + } else { + i = 0; + for(IPAddress addr: arrays.addressesIPv6) { + IPAddress expected = ipv6ResultsSeq[i++]; + if(!addr.equals(expected)) { + addFailure(new Failure("merged addr " + addr + " does not match " + expected, expected)); + } + } + } + incrementTestCount(); + } void testIncrement(String originalStr, long increment, String resultStr) { testIncrement(createAddress(originalStr).getAddress(), increment, resultStr == null ? null : createAddress(resultStr).getAddress()); } + void testIncrement(IPAddress orig, long increment, IPAddress expectedResult) { + if(orig.isIPv6()) { // test the variant that takes BigInteger increments + if(expectedResult == null) { + testIncrement(orig.toIPv6(), BigInteger.valueOf(increment), null); + } else { + testIncrement(orig.toIPv6(), BigInteger.valueOf(increment), expectedResult.toIPv6()); + } + } + super.testIncrement(orig, increment, expectedResult); + } + + void testIncrement(String originalStr, BigInteger increment, String resultStr) { + testIncrement(createAddress(originalStr).getAddress().toIPv6(), increment, resultStr == null ? null : createAddress(resultStr).getAddress().toIPv6()); + } + + @Override + void testIncrement(IPv6Address orig, BigInteger increment, IPv6Address expectedResult) { + super.testIncrement(orig.toIPv6(), increment, expectedResult); + if(expectedResult != null) { + if(orig.isSequential()) { + IPv6Address newAddr = new IPv6Address(orig.getValue().add(increment)); + if(!newAddr.equals(expectedResult)) { + addFailure(new Failure("increment creation mismatch result " + + newAddr + " vs expected " + expectedResult, orig)); + } + } + } + } + void testMaskedIncompatibleAddress(String address, String lower, String upper) { testAddressStringRange(address, true, true, lower, upper, null, null, null); } @@ -4137,6 +4268,10 @@ boolean allowsRange() { return false; } + boolean allowExtraneous() { + return false; + } + @Override void runTest() { boolean allPrefixesAreSubnets = prefixConfiguration.allPrefixedAddressesAreSubnets(); @@ -5557,23 +5692,85 @@ void runTest() { ipv4testOnly(false, "1:2:3:4:5:6:7:8"); ipv4testOnly(false, "::1"); + // ipv6 not disallowed, but this can pass because < 20 digits, if the extraneous chars ipv4 option is enabled + ip_inet_aton_test(allowExtraneous(), "0xBAAAaaaaaaa7f000001", false); // 19 chars + + // these two always fail because they are not ipv4-only, and they exceed 19 chars. The only time we allow these is when ipv6 is disallowed. + ip_inet_aton_test(false, "0xBAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa7f000001", false); // 57 chars + ip_inet_aton_test(false, "30109660652968258587507720208869004917586231558044182760080879711850530871933298651275092531995635415866341562622743621197068644363147150162264995175351264755702053831226873618925872264083816948685971914830816722015764794244138634937665528586884556100653009798956899", false); // 57 chars + + // ipv6 disallowed parsing means these are allowed when the extraneous chars ipv4 option is enabled + ipv4_inet_aton_test(allowExtraneous(), "0xBAAAaaaaaaa7f000001"); // 19 chars + ipv4_inet_aton_test(allowExtraneous(), "0xBAAAaaaaaaaaaaaaaaaaaaa7f000001"); // 31 chars + ipv4_inet_aton_test(allowExtraneous(), "0xBAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa7f000001"); // 57 chars + ipv4_inet_aton_test(allowExtraneous(), "30109660652968258587507720208869004917586231558044182760080879711850530871933298651275092531995635415866341562622743621197068644363147150162264995175351264755702053831226873618925872264083816948685971914830816722015764794244138634937665528586884556100653009798956899"); // 31 chars + + testMatches(allowExtraneous(), "166.84.7.99", + "30109660652968258587507720208869004917586231558044182760080879711850530871933298651275092531995635415866341562622743621197068644363147150162264995175351264755702053831226873618925872264083816948685971914830816722015764794244138634937665528586884556100653009798956899", + true); + testMatches(isLenient(), "166.84.7.99", "2790524771"); + testMatches(isLenient(), "166.84.7.99", "2790524771"); + testMatches(isLenient(), "166.84.7.99", "0b10100110010101000000011101100011"); + testMatches(isLenient(), "166.84.7.99", "024625003543"); + testMatches(isLenient(), "166.84.7.99", "166.0x540763"); + testMatches(isLenient(), "166.84.7.99", "0246.84.07.0x63"); + + testMatches(isLenient(), "127.0.0.1", "127.0.00000000000000000000000000000000001"); + testMatches(isLenient(), "127.0.0.1", "0177.0.0.01"); + testMatches(isLenient(), "127.0.0.1", "0x7f.0x0.0x0.0x1"); + testMatches(isLenient(), "127.0.0.1", "0x7f000001"); + testMatches(allowExtraneous(), "127.0.0.1", "0xDEADBEEF7f000001", true); + testMatches(allowExtraneous(), "127.0.0.1", "0xBADF00D7f000001", true); + testMatches(allowExtraneous(), "127.0.0.1", "0xDEADC0DE7f000001", true); + testMatches(allowExtraneous(), "127.0.0.1", "0xBADC0DE7f000001", true); + + testMatches(false, "127.0.0.1", "0xBA C0DE7f000001", true); + testMatches(false, "127.0.0.1", "0xBA%C0DE7f000001", true);// + testMatches(false, "127.0.0.1", "0xBA.C0DE7f000001", true); + testMatches(false, "127.0.0.1", "0xBA:C0DE7f000001", true); + testMatches(false, "127.0.0.1", "0xBA-C0DE7f000001", true); + testMatches(false, "127.0.0.1", "0xBA_C0DE7f000001", true);// + testMatches(false, "127.0.0.1", "0xBA*C0DE7f000001", true);// + testMatches(false, "127.0.0.1", "0xBAXC0DE7f000001", true); + testMatches(false, "127.0.0.1", "0xBAxC0DE7f000001", true); + testMatches(false, "127.0.0.1", "0xBA" + IPAddressLargeDivision.EXTENDED_DIGITS_RANGE_SEPARATOR + "C0DE7f000001", true); + testMatches(false, "127.0.0.1", "0xBA" + IPv6Address.ALTERNATIVE_ZONE_SEPARATOR + "C0DE7f000001", true); + testMatches(false, "127.0.0.1", "0xBA?C0DE7f000001", true); + testMatches(false, "127.0.0.1", "0xBA+C0DE7f000001", true); + testMatches(false, "127.0.0.1", "0xBA/C0DE7f000001", true); + + testMatches(allowExtraneous(), "127.0.0.1", + "0xBAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa7f000001", + true); + testMatches(allowExtraneous(), "127.0.0.1", "0xBAAAaaaaaaaaaaaaaaaaaaa7f000001", true); // 31 chars + testMatches(isLenient(), "127.0.0.1", "2130706433"); + testMatches(isLenient(), "127.0.0.1", + "00000000000000000000000000000000000000000000000000177.1"); + testMatches(isLenient(), "127.0.0.1", "0x7f.1"); + testMatches(isLenient(), "127.0.0.1", "127.0x1"); + + testMatches(isLenient(), "172.217.166.174", "172.14263982"); + testMatches(isLenient(), "172.217.166.174", "0254.0xd9a6ae"); + testMatches(isLenient(), "172.217.166.174", "0xac.000000000000000000331.0246.174"); + testMatches(isLenient(), "172.217.166.174", "0254.14263982"); + //in this test, the validation will fail unless validation options have allowEmpty //if you allowempty and also emptyIsLoopback, then this will evaluate to either ipv4 //or ipv6 depending on the loopback //if loopback is ipv4, then ipv6 validation fails but general validation passes because ipv4 passes because loopback is ipv4 ipv6test(false, "", false, isLenient()); //ipv6test(0, ""); // empty string //this needs special validation options to be valid - + ipv6test(1, "/0"); ipv6test(1, "/1"); ipv6test(1, "/127"); ipv6test(1, "/128"); ipv6test(0, "/129"); - + ipv6test(1, "::/0", isNoAutoSubnets); ipv6test(0, ":1.2.3.4"); //invalid ipv6test(1, "::1.2.3.4"); - + ipv6test(1,"::1");// loopback, compressed, non-routable ipv6test(1,"::", true);// unspecified, compressed, non-routable ipv6test(1,"0:0:0:0:0:0:0:1");// loopback, full @@ -5585,14 +5782,14 @@ void runTest() { ipv6test(0,"2001:DB8:0:0:8:800:200C:417A:221");// unicast, full ipv6test(0,"FF01::101::2");// multicast, compressed ipv6test(1,"fe80::217:f2ff:fe07:ed62"); - + ipv6test(0,"[a::b:c:d:1.2.3.4]");//square brackets can enclose ipv6 in host names but not addresses ipv6testWithZone(0,"[a::b:c:d:1.2.3.4%x]");//zones not allowed when using [] ipv6testWithZone(true,"a::b:c:d:1.2.3.4%x"); //zones allowed ipv6test(0,"[2001:0000:1234:0000:0000:C1C0:ABCD:0876]");//square brackets can enclose ipv6 in host names but not addresses ipv6testWithZone(true,"2001:0000:1234:0000:0000:C1C0:ABCD:0876%x");//zones allowed ipv6testWithZone(0,"[2001:0000:1234:0000:0000:C1C0:ABCD:0876%x]");//zones not allowed when using [] - + ipv6test(1,"2001:0000:1234:0000:0000:C1C0:ABCD:0876"); ipv6test(1,"3ffe:0b00:0000:0000:0001:0000:0000:000a"); ipv6test(1,"FF02:0000:0000:0000:0000:0000:0000:0001"); @@ -5603,7 +5800,7 @@ void runTest() { ipv6test(0,"2001:0000:1234:0000:0000:C1C0:ABCD:0876 0"); // junk after valid address ipv6test(0,"0 2001:0000:1234:0000:0000:C1C0:ABCD:0876"); // junk before valid address ipv6test(0,"2001:0000:1234: 0000:0000:C1C0:ABCD:0876"); // internal space - + ipv6test(0,"3ffe:0b00:0000:0001:0000:0000:000a"); // seven segments ipv6test(0,"FF02:0000:0000:0000:0000:0000:0000:0000:0001"); // nine segments ipv6test(0,"3ffe:b00::1::a"); // double "::" @@ -5629,7 +5826,7 @@ void runTest() { ipv6test(1,"1::2:3:4"); ipv6test(1,"1::2:3"); ipv6test(1,"1::8"); - + ipv6test(1,"::2:3:4:5:6:7:8"); ipv6test(1,"::2:3:4:5:6:7"); ipv6test(1,"::2:3:4:5:6"); @@ -5650,22 +5847,22 @@ void runTest() { ipv6test(1,"1:2:3::7:8"); ipv6test(1,"1:2::7:8"); ipv6test(1,"1::7:8"); - + // IPv4 addresses as dotted-quads ipv6test(1,"1:2:3:4:5:6:1.2.3.4"); ipv6test(1,"0:0:0:0:0:0:0.0.0.0", true); - + ipv6test(1,"1:2:3:4:5::1.2.3.4"); ipv6test(1,"0:0:0:0:0::0.0.0.0", true); - + ipv6test(1,"0::0.0.0.0", true); ipv6test(1,"::0.0.0.0", true); - + ipv6test(0, "1:2:3:4:5:6:.2.3.4"); ipv6test(0, "1:2:3:4:5:6:1.2.3."); ipv6test(0, "1:2:3:4:5:6:1.2..4"); ipv6test(1, "1:2:3:4:5:6:1.2.3.4"); - + ipv6test(1,"1:2:3:4::1.2.3.4"); ipv6test(1,"1:2:3::1.2.3.4"); ipv6test(1,"1:2::1.2.3.4"); @@ -5738,28 +5935,28 @@ void runTest() { ipv6test(isLenient(),"::ffff:2.3.4"); ipv6test(0,"::ffff:257.1.2.3"); ipv6testOnly(0,"1.2.3.4"); - + //stuff that might be mistaken for mixed if we parse incorrectly ipv6test(0,"a:b:c:d:e:f:a:b:c:d:e:f:1.2.3.4"); ipv6test(0,"a:b:c:d:e:f:a:b:c:d:e:f:a:b."); ipv6test(0,"a:b:c:d:e:f:1.a:b:c:d:e:f:a"); ipv6test(0,"a:b:c:d:e:f:1.a:b:c:d:e:f:a:b"); ipv6test(0,"a:b:c:d:e:f:.a:b:c:d:e:f:a:b"); - + ipv6test(0,"::a:b:c:d:e:f:1.2.3.4"); ipv6test(0,"::a:b:c:d:e:f:a:b."); ipv6test(0,"::1.a:b:c:d:e:f:a"); ipv6test(0,"::1.a:b:c:d:e:f:a:b"); ipv6test(0,"::.a:b:c:d:e:f:a:b"); - + ipv6test(0,"1::a:b:c:d:e:f:1.2.3.4"); ipv6test(0,"1::a:b:c:d:e:f:a:b."); ipv6test(0,"1::1.a:b:c:d:e:f:a"); ipv6test(0,"1::1.a:b:c:d:e:f:a:b"); ipv6test(0,"1::.a:b:c:d:e:f:a:b"); - + ipv6test(1,"1:2:3:4:5:6:1.2.3.4/1:2:3:4:5:6:1.2.3.4"); - + // Testing IPv4 addresses represented as dotted-quads // Leading zero's in IPv4 addresses not allowed: some systems treat the leading "0" in ".086" as the start of an octal number // Update: The BNF in RFC-3986 explicitly defines the dec-octet (for IPv4 addresses) not to have a leading zero @@ -5772,8 +5969,8 @@ void runTest() { //ipv6test(0,"1111:2222:3333:4444:5555:6666:000.000.000.000"); ipv6test(1,"1111:2222:3333:4444:5555:6666:000.000.000.000"); ipv6test(0,"1111:2222:3333:4444:5555:6666:256.256.256.256"); - - + + // Not testing address with subnet mask // ipv6test(1,"2001:0DB8:0000:CD30:0000:0000:0000:0000/60");// full, with prefix // ipv6test(1,"2001:0DB8::CD30:0:0:0:0/60");// compressed, with prefix @@ -5784,7 +5981,7 @@ void runTest() { // ipv6test(1,"FE80::/10");// compressed, link-local unicast, non-routable // ipv6test(1,"FEC0::/10");// compressed, site-local unicast, deprecated // ipv6test(0,"124.15.6.89/60");// standard IPv4, prefix not allowed - + ipv6test(1,"fe80:0000:0000:0000:0204:61ff:fe9d:f156"); ipv6test(1,"fe80:0:0:0:204:61ff:fe9d:f156"); ipv6test(1,"fe80::204:61ff:fe9d:f156"); @@ -5793,34 +5990,34 @@ void runTest() { ipv6test(1,"fe80::1"); ipv6test(0,":"); ipv6test(1,"::ffff:c000:280"); - + // Aeron supplied these test cases - + ipv6test(0,"1111:2222:3333:4444::5555:"); ipv6test(0,"1111:2222:3333::5555:"); ipv6test(0,"1111:2222::5555:"); ipv6test(0,"1111::5555:"); ipv6test(0,"::5555:"); - - + + ipv6test(0,":::"); ipv6test(0,"1111:"); ipv6test(0,":"); - - + + ipv6test(0,":1111:2222:3333:4444::5555"); ipv6test(0,":1111:2222:3333::5555"); ipv6test(0,":1111:2222::5555"); ipv6test(0,":1111::5555"); - - + + ipv6test(0,":::5555"); ipv6test(0,":::"); - - + + // Additional test cases // from http://rt.cpan.org/Public/Bug/Display.html?id=50693 - + ipv6test(1,"2001:0db8:85a3:0000:0000:8a2e:0370:7334"); ipv6test(1,"2001:db8:85a3:0:0:8a2e:370:7334"); ipv6test(1,"2001:db8:85a3::8a2e:370:7334"); @@ -5837,7 +6034,7 @@ void runTest() { ipv6test(1,"2001:0db8:1234:ffff:ffff:ffff:ffff:ffff"); ipv6test(1,"2001:db8:a::123"); ipv6test(1,"fe80::"); - + ipv6test(false, "123", false, isLenient());//this is passing the ipv4 side as inet_aton ipv6test(0,"ldkfj"); ipv6test(0,"2001::FFD3::57ab"); @@ -5847,7 +6044,7 @@ void runTest() { ipv6test(0,"1::2::3"); ipv6test(0,"1:::3:4:5"); ipv6test(0,"1:2:3::4:5:6:7:8:9"); - + ipv6test(1,"1111:2222:3333:4444:5555:6666:7777:8888"); ipv6test(1,"1111:2222:3333:4444:5555:6666:7777::"); ipv6test(1,"1111:2222:3333:4444:5555:6666::"); @@ -5884,8 +6081,8 @@ void runTest() { ipv6test(1,"1111::3333:4444:5555:6666:7777:8888"); ipv6test(1,"::3333:4444:5555:6666:7777:8888"); ipv6test(1,"::2222:3333:4444:5555:6666:7777:8888"); - - + + ipv6test(1,"1111:2222:3333:4444:5555:6666:123.123.123.123"); ipv6test(1,"1111:2222:3333:4444:5555::123.123.123.123"); ipv6test(1,"1111:2222:3333:4444::123.123.123.123"); @@ -5907,12 +6104,12 @@ void runTest() { ipv6test(1,"::4444:5555:6666:123.123.123.123"); ipv6test(1,"1111::3333:4444:5555:6666:123.123.123.123"); ipv6test(1,"::2222:3333:4444:5555:6666:123.123.123.123"); - + ipv6test(0,"1::2:3:4:5:6:1.2.3.4"); - + ipv6test(1,"::", true); ipv6test(1,"0:0:0:0:0:0:0:0", true); - + // Playing with combinations of "0" and "::" // NB: these are all sytactically correct, but are bad form // because "0" adjacent to "::" should be combined into "::" @@ -5930,18 +6127,18 @@ void runTest() { ipv6test(1,"0:0:0::", true); ipv6test(1,"0:0::", true); ipv6test(1,"0::", true); - - - + + + // New invalid from Aeron // Invalid data ipv6test(0,"XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX"); - + // Too many components ipv6test(0,"1111:2222:3333:4444:5555:6666:7777:8888:9999"); ipv6test(0,"1111:2222:3333:4444:5555:6666:7777:8888::"); ipv6test(0,"::2222:3333:4444:5555:6666:7777:8888:9999"); - + // Too few components ipv6test(0,"1111:2222:3333:4444:5555:6666:7777"); ipv6test(0,"1111:2222:3333:4444:5555:6666"); @@ -5951,7 +6148,7 @@ void runTest() { ipv6test(0,"1111:2222"); ipv6test(false, "1111", false, isLenient());// this is passing the ipv4 side for inet_aton //ipv6test(0,"1111"); - + // Missing : ipv6test(0,"11112222:3333:4444:5555:6666:7777:8888"); ipv6test(0,"1111:22223333:4444:5555:6666:7777:8888"); @@ -5960,7 +6157,7 @@ void runTest() { ipv6test(0,"1111:2222:3333:4444:55556666:7777:8888"); ipv6test(0,"1111:2222:3333:4444:5555:66667777:8888"); ipv6test(0,"1111:2222:3333:4444:5555:6666:77778888"); - + // Missing : intended for :: ipv6test(0,"1111:2222:3333:4444:5555:6666:7777:8888:"); ipv6test(0,"1111:2222:3333:4444:5555:6666:7777:"); @@ -5979,7 +6176,7 @@ void runTest() { ipv6test(0,":3333:4444:5555:6666:7777:8888"); ipv6test(0,":2222:3333:4444:5555:6666:7777:8888"); ipv6test(0,":1111:2222:3333:4444:5555:6666:7777:8888"); - + // ::: ipv6test(0,":::2222:3333:4444:5555:6666:7777:8888"); ipv6test(0,"1111:::3333:4444:5555:6666:7777:8888"); @@ -5989,7 +6186,7 @@ void runTest() { ipv6test(0,"1111:2222:3333:4444:5555:::7777:8888"); ipv6test(0,"1111:2222:3333:4444:5555:6666:::8888"); ipv6test(0,"1111:2222:3333:4444:5555:6666:7777:::"); - + // Double ::"); ipv6test(0,"::2222::4444:5555:6666:7777:8888"); ipv6test(0,"::2222:3333::5555:6666:7777:8888"); @@ -5997,36 +6194,36 @@ void runTest() { ipv6test(0,"::2222:3333:4444:5555::7777:8888"); ipv6test(0,"::2222:3333:4444:5555:7777::8888"); ipv6test(0,"::2222:3333:4444:5555:7777:8888::"); - + ipv6test(0,"1111::3333::5555:6666:7777:8888"); ipv6test(0,"1111::3333:4444::6666:7777:8888"); ipv6test(0,"1111::3333:4444:5555::7777:8888"); ipv6test(0,"1111::3333:4444:5555:6666::8888"); ipv6test(0,"1111::3333:4444:5555:6666:7777::"); - + ipv6test(0,"1111:2222::4444::6666:7777:8888"); ipv6test(0,"1111:2222::4444:5555::7777:8888"); ipv6test(0,"1111:2222::4444:5555:6666::8888"); ipv6test(0,"1111:2222::4444:5555:6666:7777::"); - + ipv6test(0,"1111:2222:3333::5555::7777:8888"); ipv6test(0,"1111:2222:3333::5555:6666::8888"); ipv6test(0,"1111:2222:3333::5555:6666:7777::"); - + ipv6test(0,"1111:2222:3333:4444::6666::8888"); ipv6test(0,"1111:2222:3333:4444::6666:7777::"); - + ipv6test(0,"1111:2222:3333:4444:5555::7777::"); - - - + + + // Too many components" ipv6test(0,"1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4"); ipv6test(0,"1111:2222:3333:4444:5555:6666:7777:1.2.3.4"); ipv6test(0,"1111:2222:3333:4444:5555:6666::1.2.3.4"); ipv6test(0,"::2222:3333:4444:5555:6666:7777:1.2.3.4"); ipv6test(0,"1111:2222:3333:4444:5555:6666:1.2.3.4.5"); - + // Too few components ipv6test(0,"1111:2222:3333:4444:5555:1.2.3.4"); ipv6test(0,"1111:2222:3333:4444:1.2.3.4"); @@ -6034,7 +6231,7 @@ void runTest() { ipv6test(0,"1111:2222:1.2.3.4"); ipv6test(0,"1111:1.2.3.4"); ipv6testOnly(0,"1.2.3.4"); - + // Missing : ipv6test(0,"11112222:3333:4444:5555:6666:1.2.3.4"); ipv6test(0,"1111:22223333:4444:5555:6666:1.2.3.4"); @@ -6042,13 +6239,13 @@ void runTest() { ipv6test(0,"1111:2222:3333:44445555:6666:1.2.3.4"); ipv6test(0,"1111:2222:3333:4444:55556666:1.2.3.4"); ipv6test(0,"1111:2222:3333:4444:5555:66661.2.3.4"); - + // Missing . ipv6test(0,"1111:2222:3333:4444:5555:6666:255255.255.255"); ipv6test(0,"1111:2222:3333:4444:5555:6666:255.255255.255"); ipv6test(0,"1111:2222:3333:4444:5555:6666:255.255.255255"); - - + + // Missing : intended for :: ipv6test(0,":1.2.3.4"); ipv6test(0,":6666:1.2.3.4"); @@ -6057,7 +6254,7 @@ void runTest() { ipv6test(0,":3333:4444:5555:6666:1.2.3.4"); ipv6test(0,":2222:3333:4444:5555:6666:1.2.3.4"); ipv6test(0,":1111:2222:3333:4444:5555:6666:1.2.3.4"); - + // ::: ipv6test(0,":::2222:3333:4444:5555:6666:1.2.3.4"); ipv6test(0,"1111:::3333:4444:5555:6666:1.2.3.4"); @@ -6065,24 +6262,23 @@ void runTest() { ipv6test(0,"1111:2222:3333:::5555:6666:1.2.3.4"); ipv6test(0,"1111:2222:3333:4444:::6666:1.2.3.4"); ipv6test(0,"1111:2222:3333:4444:5555:::1.2.3.4"); - + // Double :: ipv6test(0,"::2222::4444:5555:6666:1.2.3.4"); ipv6test(0,"::2222:3333::5555:6666:1.2.3.4"); ipv6test(0,"::2222:3333:4444::6666:1.2.3.4"); ipv6test(0,"::2222:3333:4444:5555::1.2.3.4"); - + ipv6test(0,"1111::3333::5555:6666:1.2.3.4"); ipv6test(0,"1111::3333:4444::6666:1.2.3.4"); ipv6test(0,"1111::3333:4444:5555::1.2.3.4"); - + ipv6test(0,"1111:2222::4444::6666:1.2.3.4"); ipv6test(0,"1111:2222::4444:5555::1.2.3.4"); - + ipv6test(0,"1111:2222:3333::5555::1.2.3.4"); - - - + + // Missing parts ipv6test(0,"::."); ipv6test(0,"::.."); @@ -6096,8 +6292,8 @@ void runTest() { ipv6test(0,"::..3."); ipv6test(0,"::..3.4"); ipv6test(0,"::...4"); - - + + // Extra : in front ipv6test(0,":1111:2222:3333:4444:5555:6666:7777::"); ipv6test(0,":1111:2222:3333:4444:5555:6666::"); @@ -6135,8 +6331,8 @@ void runTest() { ipv6test(0,":1111::3333:4444:5555:6666:7777:8888"); ipv6test(0,":::3333:4444:5555:6666:7777:8888"); ipv6test(0,":::2222:3333:4444:5555:6666:7777:8888"); - - + + ipv6test(0,":1111:2222:3333:4444:5555:6666:1.2.3.4"); ipv6test(0,":1111:2222:3333:4444:5555::1.2.3.4"); ipv6test(0,":1111:2222:3333:4444::1.2.3.4"); @@ -6158,8 +6354,8 @@ void runTest() { ipv6test(0,":::4444:5555:6666:1.2.3.4"); ipv6test(0,":1111::3333:4444:5555:6666:1.2.3.4"); ipv6test(0,":::2222:3333:4444:5555:6666:1.2.3.4"); - - + + // Extra : at end ipv6test(0,"1111:2222:3333:4444:5555:6666:7777:::"); ipv6test(0,"1111:2222:3333:4444:5555:6666:::"); @@ -6197,38 +6393,38 @@ void runTest() { ipv6test(0,"1111::3333:4444:5555:6666:7777:8888:"); ipv6test(0,"::3333:4444:5555:6666:7777:8888:"); ipv6test(0,"::2222:3333:4444:5555:6666:7777:8888:"); - + // Additional cases: http://crisp.tweakblogs.net/blog/2031/ipv6-validation-%28and-caveats%29.html ipv6test(1,"0:a:b:c:d:e:f::"); ipv6test(1,"::0:a:b:c:d:e:f"); // syntactically correct, but bad form (::0:... could be combined) ipv6test(1,"a:b:c:d:e:f:0::"); ipv6test(0,"':10.0.0.1"); - + testInsertAndAppend("a:b:c:d:e:f:aa:bb", "1:2:3:4:5:6:7:8", new Integer[9]); testInsertAndAppend("1.2.3.4", "5.6.7.8", new Integer[5]); - + testReplace("a:b:c:d:e:f:aa:bb", "1:2:3:4:5:6:7:8"); testReplace("1.2.3.4", "5.6.7.8"); - + testSQLMatching(); - + testInvalidIpv4Values(); - + testInvalidIpv6Values(); - + testIPv4Values(new int[] {1, 2, 3, 4}, "16909060"); testIPv4Values(new int[4], "0"); testIPv4Values(new int[] {255, 255, 255, 255}, String.valueOf(0xffffffffL)); - + testIPv6Values(new int[] {1, 2, 3, 4, 5, 6, 7, 8}, "5192455318486707404433266433261576"); testIPv6Values(new int[8], "0"); BigInteger thirtyTwo = BigInteger.valueOf(0xffffffffL); BigInteger one28 = thirtyTwo.shiftLeft(96).or(thirtyTwo.shiftLeft(64).or(thirtyTwo.shiftLeft(32).or(thirtyTwo))); testIPv6Values(new int[] {0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, one28.toString()); - - + + testSub("10.0.0.0/22", "10.0.1.0/24", isNoAutoSubnets ? new String[] {"10.0.0.0/22"} : new String[] {"10.0.0.0/24", "10.0.2.0/23"}); - + testIntersect("1:1::/32", "1:1:1:1:1:1:1:1", isNoAutoSubnets ? null : "1:1:1:1:1:1:1:1");//1:1:0:0:0:0:0:0/32 testIntersect("1:1::/32", "1:1::/16", "1:1::/32", !allPrefixesAreSubnets); //1:1::/16 1:1:0:0:0:0:0:0/32 testIntersect("1:1::/32", "1:1::/48", "1:1::/48"); @@ -6237,21 +6433,21 @@ void runTest() { testIntersect("1:1::/32", "1:0:2:2::/64", null); testIntersect("10.0.0.0/22", "10.0.0.0/24", "10.0.0.0/24");//[10.0.0.0/24, 10.0.2.0/23] testIntersect("10.0.0.0/22", "10.0.1.0/24", isNoAutoSubnets ? null : "10.0.1.0/24");//[10.0.1-3.0/24] - + testToPrefixBlock("1:3::3:4", "1:3::3:4"); testToPrefixBlock("1.3.3.4", "1.3.3.4"); - + testMaxHost("1.2.3.4", allPrefixesAreSubnets ? "255.255.255.255" : "255.255.255.255/0"); testMaxHost("1.2.255.255/16", allPrefixesAreSubnets ? "1.2.255.255" : "1.2.255.255/16"); - + testMaxHost("1:2:3:4:5:6:7:8", allPrefixesAreSubnets ? "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" : "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/0"); testMaxHost("1:2:ffff:ffff:ffff:ffff:ffff:ffff/64", allPrefixesAreSubnets ? "1:2:ffff:ffff:ffff:ffff:ffff:ffff" : "1:2:ffff:ffff:ffff:ffff:ffff:ffff/64"); testMaxHost("1:2:3:4:5:6:7:8/64", allPrefixesAreSubnets ? "1:2:3:4:ffff:ffff:ffff:ffff" : "1:2:3:4:ffff:ffff:ffff:ffff/64"); testMaxHost("1:2:3:4:5:6:7:8/128", allPrefixesAreSubnets ? "1:2:3:4:5:6:7:8" : "1:2:3:4:5:6:7:8/128"); - + testZeroHost("1.2.3.4", allPrefixesAreSubnets ? "0.0.0.0" : "0.0.0.0/0"); testZeroHost("1.2.0.0/16", allPrefixesAreSubnets ? "1.2.0.0" : "1.2.0.0/16"); - + testZeroHost("1:2:3:4:5:6:7:8", allPrefixesAreSubnets ? "::" : "::/0"); testZeroHost("1:2::/64", allPrefixesAreSubnets ? "1:2::" : "1:2::/64"); testZeroHost("1:2:3:4:5:6:7:8/64", allPrefixesAreSubnets ? "1:2:3:4::" : "1:2:3:4::/64"); @@ -6259,13 +6455,13 @@ void runTest() { testZeroNetwork("1.2.3.4", "0.0.0.0"); testZeroNetwork("1.2.0.0/16", "0.0.0.0/16"); - + testZeroNetwork("1:2:3:4:5:6:7:8", "::"); testZeroNetwork("1:2::/64", "::/64"); testZeroNetwork("1:2:3:4:5:6:7:8/64", allPrefixesAreSubnets ? "::/64" : "::5:6:7:8/64"); testZeroNetwork("1:2:3:4:5:6:7:8/128", "::/128"); - + testPrefixBlocks("1.2.3.4", false, false); testPrefixBlocks("1.2.3.4/16", allPrefixesAreSubnets, allPrefixesAreSubnets); testPrefixBlocks("1.2.0.0/16", isAutoSubnets, isAutoSubnets); @@ -6273,7 +6469,7 @@ void runTest() { testPrefixBlocks("1.2.3.3/31", allPrefixesAreSubnets, allPrefixesAreSubnets); testPrefixBlocks("1.2.3.4/31", isAutoSubnets, isAutoSubnets); testPrefixBlocks("1.2.3.4/32", true, true); - + testPrefixBlocks("1.2.3.4", 8, false, false); testPrefixBlocks("1.2.3.4/16", 8, false, false); testPrefixBlocks("1.2.0.0/16", 8, false, false); @@ -6281,7 +6477,7 @@ void runTest() { testPrefixBlocks("1.2.3.4/8", 8, allPrefixesAreSubnets, allPrefixesAreSubnets); testPrefixBlocks("1.2.3.4/31", 8, false, false); testPrefixBlocks("1.2.3.4/32", 8, false, false); - + testPrefixBlocks("1.2.3.4", 24, false, false); testPrefixBlocks("1.2.3.4/16", 24, allPrefixesAreSubnets, false); testPrefixBlocks("1.2.0.0/16", 24, isAutoSubnets, false); @@ -6298,7 +6494,7 @@ void runTest() { testPrefixBlocks("a:b:c:d:e:f:a:b/0", allPrefixesAreSubnets, allPrefixesAreSubnets); testPrefixBlocks("a:b:c:d:e:f:a:b/127", allPrefixesAreSubnets, allPrefixesAreSubnets); testPrefixBlocks("a:b:c:d:e:f:a:b/128", true, true); - + testPrefixBlocks("a:b:c:d:e:f:a:b", 0, false, false); testPrefixBlocks("a:b:c:d:e:f:a:b/64", 0, false, false); testPrefixBlocks("a:b:c:d::/64", 0, false, false); @@ -6307,7 +6503,7 @@ void runTest() { testPrefixBlocks("a:b:c:d:e:f:a:b/0", 0, allPrefixesAreSubnets, allPrefixesAreSubnets); testPrefixBlocks("a:b:c:d:e:f:a:b/127", 0, false, false); testPrefixBlocks("a:b:c:d:e:f:a:b/128", 0, false, false); - + testPrefixBlocks("a:b:c:d:e:f:a:b", 63, false, false); testPrefixBlocks("a:b:c:d:e:f:a:b/64", 63, false, false); testPrefixBlocks("a:b:c:d::/64", 63, false, false); @@ -6316,7 +6512,7 @@ void runTest() { testPrefixBlocks("a:b:c:d:e:f:a:b/0", 63, allPrefixesAreSubnets, false); testPrefixBlocks("a:b:c:d:e:f:a:b/127", 63, false, false); testPrefixBlocks("a:b:c:d:e:f:a:b/128", 63, false, false); - + testPrefixBlocks("a:b:c:d:e:f:a:b", 64, false, false); testPrefixBlocks("a:b:c:d:e:f:a:b/64", 64, allPrefixesAreSubnets, allPrefixesAreSubnets); testPrefixBlocks("a:b:c:d::/64", 64, isAutoSubnets, isAutoSubnets); @@ -6325,7 +6521,7 @@ void runTest() { testPrefixBlocks("a:b:c:d:e:f:a:b/0", 64, allPrefixesAreSubnets, false); testPrefixBlocks("a:b:c:d:e:f:a:b/127", 64, false, false); testPrefixBlocks("a:b:c:d:e:f:a:b/128", 64, false, false); - + testPrefixBlocks("a:b:c:d:e:f:a:b", 65, false, false); testPrefixBlocks("a:b:c:d:e:f:a:b/64", 65, allPrefixesAreSubnets, false); testPrefixBlocks("a:b:c:d::/64", 65, isAutoSubnets, false); @@ -6343,7 +6539,7 @@ void runTest() { testPrefixBlocks("a:b:c:d:e:f:a:b/0", 128, true, !allPrefixesAreSubnets); testPrefixBlocks("a:b:c:d:e:f:a:b/127", 128, true, !allPrefixesAreSubnets); testPrefixBlocks("a:b:c:d:e:f:a:b/128", 128, true, true); - + testSplitBytes("1.2.3.4"); testSplitBytes("1.2.3.4/16"); testSplitBytes("1.2.3.4/0"); @@ -6352,8 +6548,8 @@ void runTest() { testSplitBytes("ffff:2:3:4:eeee:dddd:cccc:bbbb/64"); testSplitBytes("ffff:2:3:4:eeee:dddd:cccc:bbbb/0"); testSplitBytes("ffff:2:3:4:eeee:dddd:cccc:bbbb/128"); - - + + testByteExtension("255.255.255.255", new byte[][] { new byte[] {0, 0, -1, -1, -1, -1}, new byte[] {0, -1, -1, -1, -1}, @@ -6458,8 +6654,8 @@ void runTest() { new byte[] {2, 3}, new byte[] {4} }); - - + + testIncrement("1.2.3.4", 0, "1.2.3.4"); testIncrement("1.2.3.4", 1, "1.2.3.5"); testIncrement("1.2.3.4", -1, "1.2.3.3"); @@ -6477,15 +6673,15 @@ void runTest() { testIncrement("1.2.3.4", 4278058236L, null); testIncrement("255.0.0.4", -4278190084L, "0.0.0.0"); testIncrement("255.0.0.4", -4278190085L, null); - + testIncrement("ffff:ffff:ffff:ffff:f000::0", 1, "ffff:ffff:ffff:ffff:f000::1"); testIncrement("ffff:ffff:ffff:ffff:f000::0", -1, "ffff:ffff:ffff:ffff:efff:ffff:ffff:ffff"); testIncrement("ffff:ffff:ffff:ffff:8000::", Long.MIN_VALUE, "ffff:ffff:ffff:ffff::"); + testIncrement("ffff:ffff:ffff:ffff:8000::", Long.MIN_VALUE + 1, "ffff:ffff:ffff:ffff::1"); testIncrement("ffff:ffff:ffff:ffff:7fff:ffff:ffff:ffff", Long.MIN_VALUE, "ffff:ffff:ffff:fffe:ffff:ffff:ffff:ffff"); testIncrement("ffff:ffff:ffff:ffff:7fff:ffff:ffff:fffe", Long.MIN_VALUE, "ffff:ffff:ffff:fffe:ffff:ffff:ffff:fffe"); testIncrement("::8000:0:0:0", Long.MIN_VALUE, "::"); testIncrement("::7fff:ffff:ffff:ffff", Long.MIN_VALUE, null); - testIncrement("::7fff:ffff:ffff:ffff", Long.MIN_VALUE, null); testIncrement("::7fff:ffff:ffff:fffe", Long.MIN_VALUE, null); testIncrement("ffff:ffff:ffff:ffff:8000::0", Long.MAX_VALUE, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); testIncrement("ffff:ffff:ffff:ffff:8000::1", Long.MAX_VALUE, null); @@ -6497,7 +6693,7 @@ void runTest() { testIncrement("::2", -1, "::1"); testIncrement("::2", -2, "::"); testIncrement("::2", -3, null); - + testIncrement("1::1", 0, "1::1"); testIncrement("1::1", 1, "1::2"); testIncrement("1::1", -1, "1::"); @@ -6506,14 +6702,26 @@ void runTest() { testIncrement("1::2", -1, "1::1"); testIncrement("1::2", -2, "1::"); testIncrement("1::2", -3, "::ffff:ffff:ffff:ffff:ffff:ffff:ffff"); - + testIncrement("::fffe", 2, "::1:0"); testIncrement("::ffff", 2, "::1:1"); testIncrement("::1:ffff", 2, "::2:1"); testIncrement("::1:ffff", -2, "::1:fffd"); testIncrement("::1:ffff", -0x10000, "::ffff"); testIncrement("::1:ffff", -0x10001, "::fffe"); - + + testIncrement("1::1:ffff", BigInteger.ONE.shiftLeft(126), "4001::1:ffff"); + testIncrement("1::1:ffff", BigInteger.ONE.shiftLeft(127), "8001::1:ffff"); + testIncrement("1::1:ffff", BigInteger.ONE.shiftLeft(128), null); + testIncrement("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", BigInteger.ONE, null); + testIncrement("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe", BigInteger.ONE, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + testIncrement("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", BigInteger.ONE.negate(), "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe"); + testIncrement("::", BigInteger.ONE.negate(), null); + testIncrement("::1", BigInteger.ONE.negate(), "::"); + testIncrement("::", BigInteger.ZERO, "::"); + //testIncrement("1::1:ffff", BigInteger.ONE.shiftLeft(131), null); + + testLeadingZeroAddr("00.1.2.3", true); testLeadingZeroAddr("1.00.2.3", true); testLeadingZeroAddr("1.2.00.3", true); @@ -6526,7 +6734,7 @@ void runTest() { testLeadingZeroAddr("1.0.2.3", false); testLeadingZeroAddr("1.2.0.3", false); testLeadingZeroAddr("1.2.3.0", false); - + // octal and hex addresses are not allowed when we disallow leading zeros. // if we allow leading zeros, the inet aton settings determine if hex is allowed, // or whether leading zeros are interpreted as octal. @@ -6558,7 +6766,7 @@ void runTest() { testInetAtonLeadingZeroAddr("1.001.2.3", true, true, true); testInetAtonLeadingZeroAddr("1.2.001.3", true, true, true); testInetAtonLeadingZeroAddr("1.2.3.001", true, true, true); - + testLeadingZeroAddr("00:1:2:3::", true); testLeadingZeroAddr("1:00:2:3::", true); testLeadingZeroAddr("1:2:00:3::", true); @@ -6571,51 +6779,51 @@ void runTest() { testLeadingZeroAddr("1:0:2:3::", false); testLeadingZeroAddr("1:2:0:3::", false); testLeadingZeroAddr("1:2:3:0::", false); - + //a b x y testRangeJoin("1.2.3.4", "1.2.4.3", "1.2.4.5", "1.2.5.6", null, null); testRangeIntersect("1.2.3.4", "1.2.4.3", "1.2.4.5", "1.2.5.6", null, null); testRangeSubtract("1.2.3.4", "1.2.4.3", "1.2.4.5", "1.2.5.6", "1.2.3.4", "1.2.4.3"); - + testRangeExtend("1.2.3.4", "1.2.4.3", "1.2.4.5", "1.2.5.6", "1.2.3.4", "1.2.5.6"); testRangeExtend("1.2.3.4", null, "1.2.5.6", null, "1.2.3.4", "1.2.5.6"); testRangeExtend("1.2.3.4", "1.2.4.3", "1.2.5.6", null, "1.2.3.4", "1.2.5.6"); - + //a x b y testRangeJoin("1.2.3.4", "1.2.4.5", "1.2.4.3", "1.2.5.6", "1.2.3.4", "1.2.5.6"); testRangeIntersect("1.2.3.4", "1.2.4.5", "1.2.4.3", "1.2.5.6", "1.2.4.3", "1.2.4.5"); testRangeSubtract("1.2.3.4", "1.2.4.5", "1.2.4.3", "1.2.5.6", "1.2.3.4", "1.2.4.2"); - + testRangeExtend("1.2.3.4", "1.2.4.5", "1.2.4.3", "1.2.5.6", "1.2.3.4", "1.2.5.6"); testRangeExtend("1.2.3.4", null, "1.2.5.6", null, "1.2.3.4", "1.2.5.6"); testRangeExtend("1.2.3.4", "1.2.4.5", "1.2.5.6", null, "1.2.3.4", "1.2.5.6"); - + //a x y b testRangeJoin("1.2.3.4", "1.2.5.6", "1.2.4.3", "1.2.4.5", "1.2.3.4", "1.2.5.6"); testRangeIntersect("1.2.3.4", "1.2.5.6", "1.2.4.3", "1.2.4.5", "1.2.4.3", "1.2.4.5"); testRangeSubtract("1.2.3.4", "1.2.5.6", "1.2.4.3", "1.2.4.5", "1.2.3.4", "1.2.4.2", "1.2.4.6", "1.2.5.6"); - + testRangeExtend("1.2.3.4", "1.2.5.6", "1.2.4.3", "1.2.4.5", "1.2.3.4", "1.2.5.6"); testRangeExtend("1.2.3.4", "1.2.5.6", "1.2.4.3", null, "1.2.3.4", "1.2.5.6"); - + //a b x y testRangeJoin("1:2:3:4::", "1:2:4:3::", "1:2:4:5::", "1:2:5:6::", null, null); testRangeIntersect("1:2:3:4::", "1:2:4:3::", "1:2:4:5::", "1:2:5:6::", null, null); testRangeSubtract("1:2:3:4::", "1:2:4:3::", "1:2:4:5::", "1:2:5:6::", "1:2:3:4::", "1:2:4:3::"); - + testRangeExtend("1:2:3:4::", "1:2:4:3::", "1:2:4:5::", "1:2:5:6::", "1:2:3:4::", "1:2:5:6::"); testRangeExtend("1:2:3:4::", null, "1:2:5:6::", null, "1:2:3:4::", "1:2:5:6::"); testRangeExtend("1:2:3:4::", "1:2:4:3::", "1:2:5:6::", null, "1:2:3:4::", "1:2:5:6::"); - + //a x b y testRangeJoin("1:2:3:4::", "1:2:4:5::", "1:2:4:3::", "1:2:5:6::", "1:2:3:4::", "1:2:5:6::"); testRangeIntersect("1:2:3:4::", "1:2:4:5::", "1:2:4:3::", "1:2:5:6::", "1:2:4:3::", "1:2:4:5::"); testRangeSubtract("1:2:3:4::", "1:2:4:5::", "1:2:4:3::", "1:2:5:6::", "1:2:3:4::", "1:2:4:2:ffff:ffff:ffff:ffff"); - + testRangeExtend("1:2:3:4::", "1:2:4:5::", "1:2:4:3::", "1:2:5:6::", "1:2:3:4::", "1:2:5:6::"); testRangeExtend("1:2:3:4::", null, "1:2:5:6::", null, "1:2:3:4::", "1:2:5:6::"); testRangeExtend("1:2:3:4::", "1:2:4:5::", "1:2:5:6::", null, "1:2:3:4::", "1:2:5:6::"); - + //a x y b testRangeJoin("1:2:3:4::", "1:2:5:6::", "1:2:4:3::", "1:2:4:5::", "1:2:3:4::", "1:2:5:6::"); testRangeIntersect("1:2:3:4::", "1:2:5:6::", "1:2:4:3::", "1:2:4:5::", "1:2:4:3::", "1:2:4:5::"); @@ -6624,22 +6832,22 @@ void runTest() { testRangeExtend("1:2:3:4::", "1:2:5:6::", "1:2:4:3::", "1:2:4:5::", "1:2:3:4::", "1:2:5:6::"); testRangeExtend("1:2:5:6::", null, "1:2:3:4::", null, "1:2:3:4::", "1:2:5:6::"); testRangeExtend("1:2:5:6::", null, "1:2:3:4::", "1:2:4:5::", "1:2:3:4::", "1:2:5:6::"); - + testCustomNetwork(prefixConfiguration); - + testAddressStringRange("1.2.3.4", new Object[] {1, 2, 3, 4}); testAddressStringRange("a:b:cc:dd:e:f:1.2.3.4", new Object[] {0xa, 0xb, 0xcc, 0xdd, 0xe, 0xf, 1, 2, 3, 4}); testAddressStringRange("1:2:4:5:6:7:8:f", new Object[] {1, 2, 4, 5, 6, 7, 8, 0xf}); testAddressStringRange("1:2:4:5::", new Object[] {1, 2, 4, 5, 0}); testAddressStringRange("::1:2:4:5", new Object[] {0, 1, 2, 4, 5}); testAddressStringRange("1:2:4:5::6", new Object[] {1, 2, 4, 5, 0, 6}); - + testAddressStringRange("a:b:c::cc:d:1.255.3.128", new Object[] {0xa, 0xb, 0xc, 0x0, 0xcc, 0xd, 1, 255, 3, 128}); //[a, b, c, 0-ffff, cc, d, e, f] testAddressStringRange("a::cc:d:1.255.3.128", new Object[] {0xa, 0x0, 0xcc, 0xd, 1, 255, 3, 128}); //[a, 0-ffffffffffff, cc, d, e, f] testAddressStringRange("::cc:d:1.255.3.128", new Object[] {0x0, 0xcc, 0xd, 1, 255, 3, 128}); //[0-ffffffffffffffff, cc, d, e, f] - + // with prefix lengths - + if(isAutoSubnets) { testAddressStringRange("1.2.3.4/31", new Object[] {1, 2, 3, new Integer[] {4, 5}}, 31); testAddressStringRange("a:b:cc:dd:e:f:1.2.3.4/127", new Object[] {0xa, 0xb, 0xcc, 0xdd, 0xe, 0xf, 1, 2, 3, new Integer[] {4, 5}}, 127); @@ -6649,22 +6857,21 @@ void runTest() { testAddressStringRange("a:b:cc:dd:e:f:1.2.3.4/127", new Object[] {0xa, 0xb, 0xcc, 0xdd, 0xe, 0xf, 1, 2, 3, 4}, 127); testAddressStringRange("1:2:4:5::/64", new Object[] {1, 2, 4, 5, 0}, 64); } - + if(allPrefixesAreSubnets) { testAddressStringRange("1.2.3.4/15", new Object[] {1, new Integer[] {2, 3}, new Integer[] {0, 255}, new Integer[] {0, 255}}, 15); testAddressStringRange("a:b:cc:dd:e:f:1.2.3.4/63", new Object[] {0xa, 0xb, 0xcc, new Integer[] {0xdc, 0xdd}, new Integer[] {0, 0xffff}, new Integer[] {0, 0xffff}, new Integer[] {0, 255}, new Integer[] {0, 255}, new Integer[] {0, 255}, new Integer[] {0, 255}}, 63); testAddressStringRange("1:2:4:5::/63", new Object[] {1, 2, 4, new Integer[] {4, 5}, new BigInteger[] {BigInteger.ZERO, new BigInteger("ffffffffffffffff", 16)}}, 63); testAddressStringRange("::cc:d:1.255.3.128/16", new Object[] {new Long[] {0L, 0xffffffffffffL}, new Integer[] {0, 0xffff}, new Integer[] {0, 0xffff}, new Integer[] {0, 0xff}, new Integer[] {0, 0xff}, new Integer[] {0, 0xff}, new Integer[] {0, 0xff}}, 16); //[0-ffffffffffffffff, cc, d, e, f] - } else { testAddressStringRange("1.2.3.4/15", new Object[] {1, 2, 3, 4}, 15); testAddressStringRange("a:b:cc:dd:e:f:1.2.3.4/63", new Object[] {0xa, 0xb, 0xcc, 0xdd, 0xe, 0xf, 1, 2, 3, 4}, 63); testAddressStringRange("1:2:4:5::/63", new Object[] {1, 2, 4, 5, 0}, 63); testAddressStringRange("::cc:d:1.255.3.128/16", new Object[] {0x0, 0xcc, 0xd, 1, 255, 3, 128}, 16); //[0-ffffffffffffffff, cc, d, e, f] } - + // with masks - + testSubnetStringRange("::aaaa:bbbb:cccc/abcd:dcba:aaaa:bbbb:cccc::dddd", "::cccc", "::cccc", new Object[] {0, 0, 0, 0xcccc}); testSubnetStringRange("::aaaa:bbbb:cccc/abcd:abcd:dcba:aaaa:bbbb:cccc::dddd", @@ -6673,7 +6880,7 @@ void runTest() { "aa88:98ba::cccc", "aa88:98ba::cccc", new Object[] {0xaa88, 0x98ba, 0, 0xcccc}); testSubnetStringRange("aaaa:bbbb::/abcd:dcba:aaaa:bbbb:cccc::dddd", "aa88:98ba::", "aa88:98ba::", new Object[] {0xaa88, 0x98ba, 0}); - + testSubnetStringRange("3.3.3.3/175.80.81.83", "3.0.1.3", "3.0.1.3", new Object[] {3, 0, 1, 3}, @@ -6715,8 +6922,8 @@ void runTest() { new ExpectedBlock(4, "1::70/125"), new ExpectedBlock(3, "1::78/126") }); - - + + testAllocator(new String[]{"192.168.10.0/24"}, new int[]{5, 5, 2, 6, 2, 2}, new ExpectedBlock[] { new ExpectedBlock(64, "192.168.10.0/26"), new ExpectedBlock(32, "192.168.10.64/27"), diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/MACAddressRangeTest.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/MACAddressRangeTest.java index 122c72fb..cf441a55 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/MACAddressRangeTest.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/MACAddressRangeTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/MACAddressTest.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/MACAddressTest.java index c612ded2..53b42204 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/MACAddressTest.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/MACAddressTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2022 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/SpecialTypesTest.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/SpecialTypesTest.java index eeb18e29..5c558887 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/SpecialTypesTest.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/SpecialTypesTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/TestBase.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/TestBase.java index 1b9158b6..4219f8a9 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/TestBase.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/TestBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2024 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package inet.ipaddr.test; import java.io.Serializable; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -50,6 +51,7 @@ import inet.ipaddr.MACAddressString; import inet.ipaddr.MACAddressStringParameters; import inet.ipaddr.format.AddressItem; +import inet.ipaddr.format.util.BaseDualIPv4v6Tries; import inet.ipaddr.format.util.TreeOps; import inet.ipaddr.ipv4.IPv4Address; import inet.ipaddr.ipv4.IPv4AddressSection; @@ -68,12 +70,14 @@ public abstract class TestBase { public static PrefixConfiguration prefixConfiguration; public static class Failure { + StackTraceElement[] stack; + Class testClass; + HostIdentifierString addr; AddressItem item; String str; - StackTraceElement[] stack; - Class testClass; TreeOps trie; + BaseDualIPv4v6Tries dualTrie; Set set; Map map; @@ -96,6 +100,11 @@ public Failure(String str, TreeOps trie) { this.trie = trie; } + public Failure(String str, BaseDualIPv4v6Tries tries) { + this.str = str; + this.dualTrie = tries; + } + public Failure(String str, Set set) { this.str = str; this.set = set; @@ -125,6 +134,9 @@ String getObjectDescriptor() { if(trie != null) { return trie.toString(); } + if(dualTrie != null) { + return dualTrie.toString(); + } if(set != null) { return set.toString(); } @@ -504,6 +516,10 @@ protected IPAddressString createAddress(String x) { return createAddress(new IPAddressStringKey(x, ADDRESS_OPTIONS)); } + protected IPAddressString createIPInetAtonAddress(String x) { + return createInetAtonAddress(x); + } + protected MACAddressString createMACAddress(String x, MACAddressStringParameters opts) { MACAddressStringKey key = new MACAddressStringKey(x, opts); return createMACAddress(key); @@ -1004,7 +1020,7 @@ void testHostAddress(String addressStr) { IPAddress hostAddress = str.getHostAddress(); int prefixIndex = addressStr.indexOf(IPAddress.PREFIX_LEN_SEPARATOR); if(prefixIndex < 0) { - if(!address.equals(hostAddress) || !address.contains(hostAddress)) { + if(!address.equals(hostAddress) || !address.contains(hostAddress) || !address.overlaps(hostAddress)) { addFailure(new Failure("failed host address with no prefix: " + hostAddress + " expected: " + address, str)); } } else { @@ -1627,6 +1643,17 @@ void testIncrement(Address orig, long increment, Address expectedResult, boolean if(!result.equals(expectedResult)) { addFailure(new Failure("increment mismatch result " + result + " vs expected " + expectedResult, orig)); } + if(orig.toIPAddress() != null && orig.toIPAddress().toIPv4() != null) { + Long res = orig.toIPAddress().toIPv4().enumerateIPv4(result.toIPAddress().toIPv4()); + if(res != increment) { + addFailure(new Failure("enumerateIPv4 mismatch result " + res + " vs expected " + increment, orig)); + } + } + BigInteger enumerated = orig.enumerate(result); + if(enumerated.longValue() != increment) { + orig.enumerate(result); + addFailure(new Failure("enumerate mismatch result " + enumerated + " vs expected " + increment + ", " + result + " enumerated", orig)); + } if(first && !orig.isMultiple() && increment > Long.MIN_VALUE) {//negating Long.MIN_VALUE results in same address testIncrement(expectedResult, -increment, orig, false); } @@ -1638,6 +1665,36 @@ void testIncrement(Address orig, long increment, Address expectedResult, boolean } incrementTestCount(); } + + void testIncrement(IPv6Address orig, BigInteger increment, IPv6Address expectedResult) { + testIncrement(orig, increment, expectedResult, true); + } + + void testIncrement(IPv6Address orig, BigInteger increment, IPv6Address expectedResult, boolean first) { + try { + IPv6Address result = orig.increment(increment); + if(expectedResult == null) { + addFailure(new Failure("increment mismatch result " + result + " vs none expected", orig)); + } else { + if(!result.equals(expectedResult)) { + addFailure(new Failure("increment mismatch result " + result + " vs expected " + expectedResult, orig)); + } + BigInteger enumerated = orig.enumerate(result); + if(!enumerated.equals(increment)) { + orig.enumerate(result); + addFailure(new Failure("enumerate mismatch result " + enumerated + " vs expected " + increment + ", " + result + " enumerated", orig)); + } + if(first && !orig.isMultiple()) { + testIncrement(expectedResult, increment.negate(), orig, false); + } + } + } catch(AddressValueException e) { + if(expectedResult != null) { + addFailure(new Failure("increment mismatch exception " + e + ", expected " + expectedResult, orig)); + } + } + incrementTestCount(); + } } interface AddressCreator { diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/TestRunner.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/TestRunner.java index 9af76430..b880f988 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/TestRunner.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/TestRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2021 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/TrieTest.java b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/TrieTest.java index 1e7e4a5d..82cf5b86 100644 --- a/IPAddress/src/inet.ipaddr/inet/ipaddr/test/TrieTest.java +++ b/IPAddress/src/inet.ipaddr/inet/ipaddr/test/TrieTest.java @@ -1,3 +1,21 @@ +/* + * Copyright 2020-2024 Sean C Foley + * + * Licensed 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * or at + * https://github.com/seancfoley/IPAddress/blob/master/LICENSE + * + * 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 inet.ipaddr.test; import java.io.IOException; @@ -29,11 +47,15 @@ import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.IntFunction; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.ToIntFunction; +import java.util.function.UnaryOperator; import inet.ipaddr.Address; import inet.ipaddr.AddressNetwork.PrefixConfiguration; @@ -53,9 +75,13 @@ import inet.ipaddr.format.util.AssociativeAddedTree.AssociativeAddedTreeNode; import inet.ipaddr.format.util.AssociativeAddressTrie; import inet.ipaddr.format.util.AssociativeAddressTrie.AssociativeTrieNode; +import inet.ipaddr.format.util.BaseDualIPv4v6Tries; import inet.ipaddr.format.util.BinaryTreeNode; import inet.ipaddr.format.util.BinaryTreeNode.CachingIterator; +import inet.ipaddr.format.util.DualIPv4v6AssociativeTries; +import inet.ipaddr.format.util.DualIPv4v6Tries; import inet.ipaddr.format.util.Partition; +import inet.ipaddr.format.util.TreeOps; import inet.ipaddr.ipv4.IPv4Address; import inet.ipaddr.ipv4.IPv4AddressAssociativeTrie; import inet.ipaddr.ipv4.IPv4AddressTrie; @@ -77,11 +103,6 @@ public TrieTest(AddressCreator creator) { super(creator); } - @Override - protected IPAddressString createInetAtonAddress(String x) { - return createAddress(x); - } - @Override protected IPAddressString createAddress(String x) { return createAddress(x, DEFAULT_OPTIONS); @@ -216,12 +237,12 @@ static class Strings { Strings two = new Strings( new String[] { - "ff80::/8", - "ff80:8000::/16", - "ff80:8000::/24", + "ff80::", + "ff80:8000::", + "ff80:8000::/24", "ff80:8000::/32", "ff80:8000:c000::/34", - "ff80:8000:c800::/36", + "ff80:8000:c800::", "ff80:8000:cc00::/38", "ff80:8000:cc00::/40", }, @@ -391,12 +412,21 @@ static class Strings { "0.128.0.0/24", "0.128.0.128" }, { - "ff80::/8", - "ff80:8000::/16", - "ff80:8000::/24", + "ff80::", + "ff80:8000::", + "ff80:8000::/24", "ff80:8000::/32", "ff80:8000:c000::/34", - "ff80:8000:c800::/36", + "ff80:8000:c800::", + "ff80:8000:cc00::/38", + "ff80:8000:cc00::/40", + }, { + "ff80::/16", + "ff80:8000::/20", + "ff80:8000::/24", + "ff80:8000::/32", + "ff80:8000:c000::/34", + "ff80:8000:c800::/37", "ff80:8000:cc00::/38", "ff80:8000:cc00::/40", }, { @@ -447,7 +477,7 @@ static class Strings { void testIPv6Strings(Strings strs) { IPv6AddressTrie ipv6Tree = new IPv6AddressTrie(); - createSampleTree(ipv6Tree, strs.addrs); + createIPv6SampleTree(ipv6Tree, strs.addrs); String treeStr = ipv6Tree.toString(); if(!treeStr.contentEquals(strs.treeString)) { addFailure("trie string not right, got " + treeStr + " instead of expected " + strs.treeString, ipv6Tree); @@ -533,7 +563,7 @@ void count(AssociativeAddedTreeNode node) { void testIPv4Strings(Strings strs) { IPv4AddressTrie ipv4Tree = new IPv4AddressTrie(); - createSampleTree(ipv4Tree, strs.addrs); + createIPv4SampleTree(ipv4Tree, strs.addrs); String treeStr = ipv4Tree.toString(); if(!treeStr.contentEquals(strs.treeString)) { addFailure("trie string not right, got " + treeStr + " instead of expected " + strs.treeString, ipv4Tree); @@ -619,12 +649,12 @@ void count(AssociativeAddedTreeNode node) { incrementTestCount(); } - static void testRemove(TestBase testBase, String addrs[]) { + void testRemove(String addrs[]) { IPv6AddressTrie ipv6Tree = new IPv6AddressTrie(); IPv4AddressTrie ipv4Tree = new IPv4AddressTrie(); - testRemove(testBase, ipv6Tree, addrs, addrStr -> testBase.createAddress(addrStr).getAddress().toIPv6()); - testRemove(testBase, ipv4Tree, addrs, addrStr -> testBase.createAddress(addrStr).getAddress().toIPv4()); + testRemove(ipv6Tree, addrs, addrStr -> createAddress(addrStr).getAddress().toIPv6()); + testRemove(ipv4Tree, addrs, addrStr -> createAddress(addrStr).getAddress().toIPv4()); // reverse the address order String addrs2[] = addrs.clone(); @@ -635,14 +665,14 @@ static void testRemove(TestBase testBase, String addrs[]) { } // both trees should be empty now - testRemove(testBase, ipv6Tree, addrs, addrStr -> testBase.createAddress(addrStr).getAddress().toIPv6()); - testRemove(testBase, ipv4Tree, addrs, addrStr -> testBase.createAddress(addrStr).getAddress().toIPv4()); + testRemove(ipv6Tree, addrs, addrStr -> createAddress(addrStr).getAddress().toIPv6()); + testRemove(ipv4Tree, addrs, addrStr -> createAddress(addrStr).getAddress().toIPv4()); } - static void testRemoveMAC(TestBase testBase, String addrs[]) { + void testRemoveMAC(String addrs[]) { MACAddressTrie macTree = new MACAddressTrie(); - testRemove(testBase, macTree, addrs, addrStr -> testBase.createMACAddress(addrStr).getAddress()); + testRemove(macTree, addrs, addrStr -> createMACAddress(addrStr).getAddress()); // reverse the address order String addrs2[] = addrs.clone(); @@ -653,12 +683,11 @@ static void testRemoveMAC(TestBase testBase, String addrs[]) { } // tree should be empty now - testRemove(testBase, macTree, addrs, addrStr -> testBase.createMACAddress(addrStr).getAddress()); - testBase.incrementTestCount(); + testRemove(macTree, addrs, addrStr -> createMACAddress(addrStr).getAddress()); + incrementTestCount(); } - static void testRemove(TestBase testBase, - AddressTrie tree, String addrs[], Function converter) { + void testRemove(AddressTrie tree, String addrs[], Function converter) { int count = 0; ArrayList list = new ArrayList<>(addrs.length); HashSet dupChecker = new HashSet<>(); @@ -673,26 +702,10 @@ static void testRemove(TestBase testBase, } } } - testRemove(testBase, tree, count, list); - } - - static List collect(String addrs[], Function converter) { - ArrayList list = new ArrayList<>(addrs.length); - HashSet dupChecker = new HashSet<>(); - for(String str : addrs) { - E addr = converter.apply(str); - if(addr != null) { - if(!dupChecker.contains(addr)) { - dupChecker.add(addr); - list.add(addr); - } - } - } - return list; + testRemove(tree, count, list); } - static void testRemove(TestBase testBase, - AddressTrie tree, int count, List addrs) { + void testRemove(AddressTrie tree, int count, List addrs) { AddressTrie tree2 = tree.clone(); AddressTrie tree3 = tree2.clone(); AddressTrie tree4 = tree2.clone(); @@ -702,21 +715,21 @@ static void testRemove(TestBase testBase, tree5.addTrie(tree4.getRoot()); int nodeSize4 = tree4.nodeSize(); if(tree4.size() != count) { - addFailure(testBase, "trie size not right, got " + tree4.size() + " instead of expected " + count, tree4); + addTrieFailure("trie size not right, got " + tree4.size() + " instead of expected " + count, tree4); } tree4.clear(); if(tree4.size() != 0) { - addFailure(testBase, "trie size not zero, got " + tree4.size() + " after clearing trie", tree4); + addTrieFailure("trie size not zero, got " + tree4.size() + " after clearing trie", tree4); } if(tree4.nodeSize() != 1) { - addFailure(testBase, "node size not 1, got " + tree4.nodeSize() + " after clearing trie", tree4); + addTrieFailure("node size not 1, got " + tree4.nodeSize() + " after clearing trie", tree4); } if(tree5.size() != count) { //if(tree5.size() != count || tree5.size() == 0) { - addFailure(testBase, "trie size not right, got " + tree5.size() + " instead of expected " + count, tree5); + addTrieFailure("trie size not right, got " + tree5.size() + " instead of expected " + count, tree5); } if(tree5.nodeSize() != nodeSize4) { - addFailure(testBase, "trie size not right, got " + tree5.size() + " instead of expected " + nodeSize4, tree5); + addTrieFailure("trie size not right, got " + tree5.size() + " instead of expected " + nodeSize4, tree5); } int origSize = tree.size(); int origNodeSize = tree.nodeSize(); @@ -728,21 +741,21 @@ static void testRemove(TestBase testBase, iterator.remove(); int newSize = tree.size(); if(size - 1 != newSize) { - addFailure(testBase, "trie size mismatch, expected " + (size - 1) + " got " + newSize + " when removing node " + node, tree); + addTrieFailure("trie size mismatch, expected " + (size - 1) + " got " + newSize + " when removing node " + node, tree); } size = newSize; newSize = tree.nodeSize(); if(newSize > nodeSize) { - addFailure(testBase, "node size mismatch, expected smaller than " + nodeSize + " got " + newSize + " when removing node " + node, tree); + addTrieFailure("node size mismatch, expected smaller than " + nodeSize + " got " + newSize + " when removing node " + node, tree); } nodeSize = newSize; } if(tree.size() != 0 || !tree.isEmpty()) { - addFailure(testBase, "trie size not zero, got " + tree.size() + " after clearing trie", tree); + addTrieFailure("trie size not zero, got " + tree.size() + " after clearing trie", tree); } if(tree.nodeSize() != 1) { - addFailure(testBase, "node size not 1, got " + tree.nodeSize() + " after clearing trie", tree); + addTrieFailure("node size not 1, got " + tree.nodeSize() + " after clearing trie", tree); } size = origSize; @@ -755,12 +768,12 @@ static void testRemove(TestBase testBase, tree2.remove(addr); int newSize = tree2.size(); if(size - 1 != newSize) { - addFailure(testBase, "trie size mismatch, expected " + (size - 1) + " got " + newSize, tree2); + addTrieFailure("trie size mismatch, expected " + (size - 1) + " got " + newSize, tree2); } size = newSize; newSize = tree2.nodeSize(); if(newSize > nodeSize) { - addFailure(testBase, "node size mismatch, expected smaller than " + nodeSize + " got " + newSize, tree2); + addTrieFailure("node size mismatch, expected smaller than " + nodeSize + " got " + newSize, tree2); } nodeSize = newSize; } @@ -769,10 +782,10 @@ static void testRemove(TestBase testBase, if(tree2.size() != 0 || !tree2.isEmpty()) { - addFailure(testBase, "trie size not zero, got " + tree2.size() + " after clearing trie", tree2); + addTrieFailure("trie size not zero, got " + tree2.size() + " after clearing trie", tree2); } if(tree2.nodeSize() != 1) { - addFailure(testBase, "node size not 1, got " + tree2.nodeSize() + " after clearing trie", tree2); + addTrieFailure("node size not 1, got " + tree2.nodeSize() + " after clearing trie", tree2); } // now remove full subtrees at once int addressesRemoved = 0; @@ -801,51 +814,70 @@ static void testRemove(TestBase testBase, // we cannot check for smaller tree or node size because many elements might have been already erased int newSize = tree3.size(); if(newSize != preRemovalSize - nodeCountToBeRemoved) { - addFailure(testBase, "removal size mismatch, expected to remove " + nodeCountToBeRemoved + " but removed " + (preRemovalSize - newSize), tree3); + addTrieFailure("removal size mismatch, expected to remove " + nodeCountToBeRemoved + " but removed " + (preRemovalSize - newSize), tree3); } if(newSize > origSize - addressesRemoved) { - addFailure(testBase, "trie size mismatch, expected smaller than " + (origSize - addressesRemoved) + " got " + newSize, tree3); + addTrieFailure("trie size mismatch, expected smaller than " + (origSize - addressesRemoved) + " got " + newSize, tree3); } newSize = tree3.nodeSize(); if(newSize > origNodeSize - addressesRemoved && newSize > 1) { - addFailure(testBase, "node size mismatch, expected smaller than " + (origSize - addressesRemoved) + " got " + newSize, tree3); + addTrieFailure("node size mismatch, expected smaller than " + (origSize - addressesRemoved) + " got " + newSize, tree3); } } } if(tree3.size() != 0 || !tree3.isEmpty()) { - addFailure(testBase, "trie size not zero, got " + tree3.size() + " after clearing trie", tree3); + addTrieFailure("trie size not zero, got " + tree3.size() + " after clearing trie", tree3); } if(tree3.nodeSize() != 1) { - addFailure(testBase, "node size not 1, got " + tree3.nodeSize() + " after clearing trie", tree3); + addTrieFailure("node size not 1, got " + tree3.nodeSize() + " after clearing trie", tree3); } tree6.asSet().removeAll(tree6.asSet().clone()); if(tree6.size() != 0 || !tree6.isEmpty() || tree6.asSet().size() != 0 || !tree6.asSet().isEmpty()) { - addFailure(testBase, "trie size not zero, got " + tree6.size() + " after clearing trie with removeAll", tree6); + addTrieFailure("trie size not zero, got " + tree6.size() + " after clearing trie with removeAll", tree6); } - testBase.incrementTestCount(); + incrementTestCount(); + } + + static List collect(String addrs[], Function converter) { + ArrayList list = new ArrayList<>(addrs.length); + HashSet dupChecker = new HashSet<>(); + for(String str : addrs) { + E addr = converter.apply(str); + if(addr != null) { + if(!dupChecker.contains(addr)) { + dupChecker.add(addr); + list.add(addr); + } + } + } + return list; } - static , T extends Address> void addFailure(TestBase testBase, String str, R trie) { - testBase.addFailure(new Failure(str, trie)); + void addTrieFailure(String str, Object trie) { + if(trie instanceof TreeOps) { + addFailure(new Failure(str, (TreeOps) trie)); + } else { + addFailure(new Failure(str, (BaseDualIPv4v6Tries) trie)); + } } - static void partitionTest(TestBase testBase) { + void partitionTest() { String addrs = "1.2.1-15.*"; IPv4AddressTrie trie = new IPv4AddressTrie(); - IPv4Address addr = testBase.createAddress(addrs).getAddress().toIPv4(); - partitionForTrie(testBase, trie, addr); + IPv4Address addr = createAddress(addrs).getAddress().toIPv4(); + partitionForTrie(trie, addr); } - static void partitionForTrie(TestBase testBase, AddressTrie trie, T subnet) { + void partitionForTrie(AddressTrie trie, T subnet) { Partition.partitionWithSingleBlockSize(subnet).predicateForEach(trie::add); if(trie.size() != 15) { - addFailure(testBase, "partition size unexpected " + trie.size() + ", expected " + 15, trie); + addTrieFailure("partition size unexpected " + trie.size() + ", expected " + 15, trie); } Map> all = Partition.partitionWithSingleBlockSize(subnet).applyForEach(trie::getAddedNode); if(all.size() != 15) { - addFailure(testBase, "map size unexpected " + trie.size() + ", expected " + 15, trie); + addTrieFailure("map size unexpected " + trie.size() + ", expected " + 15, trie); } HashMap> all2 = new HashMap<>(); Partition.partitionWithSingleBlockSize(subnet).forEach(addr -> { @@ -853,37 +885,36 @@ static void partitionForTrie(TestBase testBase, AddressTri all2.put(addr, node); }); if(!all.equals(all2)) { - addFailure(testBase, "maps not equal " + all + " and " + all2, trie); + addTrieFailure("maps not equal " + all + " and " + all2, trie); } trie.clear(); Partition.partitionWithSpanningBlocks(subnet).predicateForEach(trie::add); if(trie.size() != 4) { - addFailure(testBase,"partition size unexpected " + trie.size() + ", expected " + 4, trie); + addTrieFailure("partition size unexpected " + trie.size() + ", expected " + 4, trie); } trie.clear(); Partition.partitionWithSingleBlockSize(subnet).predicateForEach(trie::add); Partition.partitionWithSpanningBlocks(subnet).predicateForEach(trie::add); if(trie.size() != 18) { - addFailure(testBase,"partition size unexpected " + trie.size() + ", expected " + 18, trie); + addTrieFailure("partition size unexpected " + trie.size() + ", expected " + 18, trie); } boolean allAreThere = Partition.partitionWithSingleBlockSize(subnet).predicateForEach(trie::contains); boolean allAreThere2 = Partition.partitionWithSpanningBlocks(subnet).predicateForEach(trie::contains); if(!(allAreThere && allAreThere2)) { - addFailure(testBase,"partition contains check failing", trie); + addTrieFailure("partition contains check failing", trie); } - testBase.incrementTestCount(); + incrementTestCount(); } - static , T extends Address> void testIterationContainment(TestBase testBase, R tree) { - testIterationContainment(testBase, tree, AddressTrie::blockSizeCachingAllNodeIterator, false); - testIterationContainment(testBase, tree, trie -> trie.containingFirstAllNodeIterator(true), false /* added only */); - testIterationContainment(testBase, tree, trie -> trie.containingFirstAllNodeIterator(false), false /* added only */); - testIterationContainment(testBase, tree, trie -> trie.containingFirstIterator(true), true /* added only */); - testIterationContainment(testBase, tree, trie -> trie.containingFirstIterator(false), true /* added only */); + , T extends Address> void testIterationContainment(R tree) { + testIterationContainment(tree, AddressTrie::blockSizeCachingAllNodeIterator, false); + testIterationContainment(tree, trie -> trie.containingFirstAllNodeIterator(true), false /* added only */); + testIterationContainment(tree, trie -> trie.containingFirstAllNodeIterator(false), false /* added only */); + //testIterationContainment(tree, trie -> trie.containingFirstIterator(true), true /* added only */); + //testIterationContainment(tree, trie -> trie.containingFirstIterator(false), true /* added only */); } - static , T extends Address> void testIterationContainment( - TestBase testBase, + , T extends Address> void testIterationContainment( R trie, Function, T, Integer>> iteratorFunc, boolean addedNodesOnly) { @@ -914,48 +945,47 @@ static , T extends Address> void testIterationContainme } Integer cached = iterator.getCached(); if(!skipCheck && !Objects.equals(cached, parentPrefix)) { - addFailure(testBase, "mismatched prefix for " + next + ", cached is " + iterator.getCached() + " and expected value is " + parentPrefix, trie); + addTrieFailure("mismatched prefix for " + next + ", cached is " + iterator.getCached() + " and expected value is " + parentPrefix, trie); } Integer prefLen = nextAddr.getPrefixLength(); iterator.cacheWithLowerSubNode(prefLen); iterator.cacheWithUpperSubNode(prefLen); } - testBase.incrementTestCount(); + incrementTestCount(); } - static , T extends Address> void testIterate(TestBase testBase, R tree) { - - testIterate(testBase, tree, trie -> trie.blockSizeNodeIterator(true), AddressTrie::size, true); - testIterate(testBase, tree, trie -> trie.blockSizeAllNodeIterator(true), AddressTrie::nodeSize, true); - testIterate(testBase, tree, trie -> trie.blockSizeNodeIterator(false), AddressTrie::size, true); - testIterate(testBase, tree, trie -> trie.blockSizeAllNodeIterator(false), AddressTrie::nodeSize, true); + , T extends Address> void testIterate(R tree) { + testIterate(tree, trie -> trie.blockSizeNodeIterator(true), AddressTrie::size, true); + testIterate(tree, trie -> trie.blockSizeAllNodeIterator(true), AddressTrie::nodeSize, true); + testIterate(tree, trie -> trie.blockSizeNodeIterator(false), AddressTrie::size, true); + testIterate(tree, trie -> trie.blockSizeAllNodeIterator(false), AddressTrie::nodeSize, true); - testIterate(testBase, tree, AddressTrie::blockSizeCachingAllNodeIterator, AddressTrie::nodeSize, true); + testIterate(tree, AddressTrie::blockSizeCachingAllNodeIterator, AddressTrie::nodeSize, true); - testIterate(testBase, tree, trie -> trie.nodeIterator(true), AddressTrie::size, true); - testIterate(testBase, tree, trie -> trie.allNodeIterator(true), AddressTrie::nodeSize, true); - testIterate(testBase, tree, trie -> trie.nodeIterator(false), AddressTrie::size, true); - testIterate(testBase, tree, trie -> trie.allNodeIterator(false), AddressTrie::nodeSize, true); + testIterate(tree, trie -> trie.nodeIterator(true), AddressTrie::size, true); + testIterate(tree, trie -> trie.allNodeIterator(true), AddressTrie::nodeSize, true); + testIterate(tree, trie -> trie.nodeIterator(false), AddressTrie::size, true); + testIterate(tree, trie -> trie.allNodeIterator(false), AddressTrie::nodeSize, true); - testIterate(testBase, tree, trie -> trie.containedFirstIterator(true), AddressTrie::size, true); - testIterate(testBase, tree, trie -> trie.containedFirstAllNodeIterator(true), AddressTrie::nodeSize, false); - testIterate(testBase, tree, trie -> trie.containedFirstIterator(false), AddressTrie::size, true); - testIterate(testBase, tree, trie -> trie.containedFirstAllNodeIterator(false), AddressTrie::nodeSize, false); + testIterate(tree, trie -> trie.containedFirstIterator(true), AddressTrie::size, true); + testIterate(tree, trie -> trie.containedFirstAllNodeIterator(true), AddressTrie::nodeSize, false); + testIterate(tree, trie -> trie.containedFirstIterator(false), AddressTrie::size, true); + testIterate(tree, trie -> trie.containedFirstAllNodeIterator(false), AddressTrie::nodeSize, false); - testIterate(testBase, tree, trie -> trie.containingFirstIterator(true), AddressTrie::size, true); - testIterate(testBase, tree, trie -> trie.containingFirstAllNodeIterator(true), AddressTrie::nodeSize, true); - testIterate(testBase, tree, trie -> trie.containingFirstIterator(false), AddressTrie::size, true); - testIterate(testBase, tree, trie -> trie.containingFirstAllNodeIterator(false), AddressTrie::nodeSize, true); + testIterate(tree, trie -> trie.containingFirstIterator(true), AddressTrie::size, true); + testIterate(tree, trie -> trie.containingFirstAllNodeIterator(true), AddressTrie::nodeSize, true); + testIterate(tree, trie -> trie.containingFirstIterator(false), AddressTrie::size, true); + testIterate(tree, trie -> trie.containingFirstAllNodeIterator(false), AddressTrie::nodeSize, true); - testIterate(testBase, tree, trie -> new SpliteratorWrapper<>(trie.nodeSpliterator(true)), AddressTrie::size, false); - testIterate(testBase, tree, trie -> new SpliteratorWrapper<>(trie.allNodeSpliterator(true)), AddressTrie::nodeSize, false); - testIterate(testBase, tree, trie -> new SpliteratorWrapper<>(trie.nodeSpliterator(false)), AddressTrie::size, false); - testIterate(testBase, tree, trie -> new SpliteratorWrapper<>(trie.allNodeSpliterator(false)), AddressTrie::nodeSize, false); + testIterate(tree, trie -> new SpliteratorWrapper<>(trie.nodeSpliterator(true)), AddressTrie::size, false); + testIterate(tree, trie -> new SpliteratorWrapper<>(trie.allNodeSpliterator(true)), AddressTrie::nodeSize, false); + testIterate(tree, trie -> new SpliteratorWrapper<>(trie.nodeSpliterator(false)), AddressTrie::size, false); + testIterate(tree, trie -> new SpliteratorWrapper<>(trie.allNodeSpliterator(false)), AddressTrie::nodeSize, false); - testIterationContainment(testBase, tree); + testIterationContainment(tree); - testBase.incrementTestCount(); + incrementTestCount(); } static class SpliteratorWrapper implements Iterator { @@ -984,53 +1014,129 @@ public E next() { } } + , T extends Address> void testDualIterate(R tree) { + testIterate(tree, trie -> trie.blockSizeNodeIterator(true), true); + testIterate(tree, trie -> trie.blockSizeNodeIterator(false), true); + + testIterate(tree, trie -> trie.nodeIterator(true), true); + testIterate(tree, trie -> trie.nodeIterator(false), true); + + testIterate(tree, trie -> trie.containedFirstIterator(true), true); + testIterate(tree, trie -> trie.containedFirstIterator(false), true); + + testIterate(tree, trie -> trie.containingFirstIterator(true), true); + testIterate(tree, trie -> trie.containingFirstIterator(false), true); + + testIterate(tree, trie -> new SpliteratorWrapper<>(trie.nodeSpliterator(true)), false); + testIterate(tree, trie -> new SpliteratorWrapper<>(trie.nodeSpliterator(false)), false); + + incrementTestCount(); + } + @SuppressWarnings("unchecked") - static , T extends Address> void testIterate( - TestBase testBase, + > void testIterate( R trie, - Function>> iteratorFunc, + Function>> iteratorFunc, + boolean removeAllowed) { + testIterate( + trie, + iteratorFunc, + BaseDualIPv4v6Tries::size, + BaseDualIPv4v6Tries::size, + null, + (t) -> { return (R) t.clone(); }, + (dualTrie) -> { + TrieNode node = dualTrie.getIPv4Trie().firstAddedNode(); + if(node == null) { + node = dualTrie.getIPv6Trie().firstAddedNode(); + } + return node; + }, + BaseDualIPv4v6Tries::add, + BaseDualIPv4v6Tries::contains, + null, + BaseDualIPv4v6Tries::getAddedNode, + BaseDualIPv4v6Tries::isEmpty, + removeAllowed); + } + + @SuppressWarnings("unchecked") + , T extends Address> void testIterate( + R trie, + Function>> iteratorFunc, ToIntFunction countFunc, boolean removeAllowed) { + testIterate( + trie, + iteratorFunc, + countFunc, + AddressTrie::size, + AddressTrie::nodeSize, + (t) -> { return (R) t.clone(); }, + AddressTrie::firstNode, + AddressTrie::add, + AddressTrie::contains, + AddressTrie::getNode, + AddressTrie::getAddedNode, + AddressTrie::isEmpty, + removeAllowed); + } + + void testIterate( + R trie, + Function>> iteratorFunc, + ToIntFunction iteratorCountFunc, + ToIntFunction trieSizeFunc, + ToIntFunction trieNodeSizeFunc, + UnaryOperator cloneFunc, + Function> firstNodeFunc, + BiPredicate addFunc, + BiPredicate containsFunc, + BiFunction> getNodeFunc, + BiFunction> getAddedNodeFunc, + Predicate isEmptyFunc, + boolean removeAllowed) { // iterate the tree, confirm the size by counting // clone the trie, iterate again, but remove each time, confirm the size // confirm trie is empty at the end - if(trie.size() > 0) { - R clonedTrie = (R) trie.clone(); - TrieNode node = clonedTrie.firstNode(); + if(trieSizeFunc.applyAsInt(trie) > 0) { + R clonedTrie = cloneFunc.apply(trie); + TrieNode node = firstNodeFunc.apply(clonedTrie); T toAdd = node.getKey(); node.remove(); - Iterator> modIterator = iteratorFunc.apply(clonedTrie); - int mod = clonedTrie.size() / 2; + Iterator> modIterator = iteratorFunc.apply(clonedTrie); + int mod = trieSizeFunc.applyAsInt(clonedTrie) / 2; int i = 0; boolean shouldThrow = false; try { while(modIterator.hasNext()) { if(++i == mod) { shouldThrow = true; - clonedTrie.add(toAdd); + addFunc.test(clonedTrie, toAdd); } modIterator.next(); if(shouldThrow) { - addFailure(testBase, "expected throw ", clonedTrie); + addTrieFailure("expected throw ", clonedTrie); + shouldThrow = false; } } } catch(ConcurrentModificationException e) { if(!shouldThrow) { - addFailure(testBase, "unexpected throw ", clonedTrie); + addTrieFailure("unexpected throw ", clonedTrie); } } } boolean firstTime = true; while(true) { - int expectedSize = countFunc.applyAsInt(trie); + int expectedSize = iteratorCountFunc.applyAsInt(trie); int actualSize = 0; Set set = new HashSet<>(); - Set> nodeSet = new HashSet<>(); - Iterator> iterator = iteratorFunc.apply(trie); + Set> nodeSet = new HashSet<>(); + Iterator> iterator = iteratorFunc.apply(trie); while(iterator.hasNext()) { - BinaryTreeNode next = iterator.next(); + BinaryTreeNode next = iterator.next(); nodeSet.add(next); T nextAddr = next.getKey(); set.add(nextAddr); @@ -1039,70 +1145,70 @@ static , T extends Address> void testIterate( try { iterator.remove(); if(!removeAllowed) { - addFailure(testBase, "removal " + next + " should not be supported", trie); - } else if(trie.contains(nextAddr)) { - addFailure(testBase, "after removal " + next + " still in trie ", trie); + addTrieFailure("removal " + next + " should not be supported", trie); + } else if(containsFunc.test(trie, nextAddr)) { + addTrieFailure("after removal " + next + " still in trie ", trie); } } catch(UnsupportedOperationException e) { if(removeAllowed) { - addFailure(testBase, "removal " + next + " should be supported", trie); + addTrieFailure("removal " + next + " should be supported", trie); } } } else { if(next.isAdded()) { - if(!trie.contains(nextAddr)) { - addFailure(testBase, "after iteration " + next + " not in trie ", trie); - } else if(trie.getAddedNode(nextAddr) == null) { - addFailure(testBase, "after iteration address node for " + nextAddr + " not in trie ", trie); + if(!containsFunc.test(trie, nextAddr)) { + addTrieFailure("after iteration " + next + " not in trie ", trie); + } else if(getAddedNodeFunc.apply(trie, nextAddr) == null) { + addTrieFailure("after iteration address node for " + nextAddr + " not in trie ", trie); } } else { - if(trie.contains(nextAddr)) { - addFailure(testBase, "non-added node " + next + " in trie ", trie); - } else if(trie.getNode(nextAddr) == null) { - addFailure(testBase, "after iteration address node for " + nextAddr + " not in trie ", trie); - } else if(trie.getAddedNode(nextAddr) != null) { - addFailure(testBase, "after iteration non-added node for " + nextAddr + " added in trie ", trie); + if(containsFunc.test(trie, nextAddr)) { + addTrieFailure("non-added node " + next + " in trie ", trie); + } else if(getNodeFunc == null || getNodeFunc.apply(trie, nextAddr) == null) { + addTrieFailure("after iteration address node for " + nextAddr + " not in trie ", trie); + } else if(getAddedNodeFunc.apply(trie, nextAddr) != null) { + addTrieFailure("after iteration non-added node for " + nextAddr + " added in trie ", trie); } } } } if(set.size() != expectedSize) { - addFailure(testBase, "set count was " + set.size() + " instead of expected " + expectedSize, trie); + addTrieFailure("set count was " + set.size() + " instead of expected " + expectedSize, trie); } else if(actualSize != expectedSize) { - addFailure(testBase, "count was " + actualSize + " instead of expected " + expectedSize, trie); + addTrieFailure("count was " + actualSize + " instead of expected " + expectedSize, trie); } - trie = (R) trie.clone(); + trie = cloneFunc.apply(trie); if(!firstTime) { break; } firstTime = false; } if(removeAllowed) { - if(!trie.isEmpty()) { - addFailure(testBase, "trie not empty, size " + trie.size() + " after removing everything", trie); - } else if(trie.nodeSize() > 1) { - addFailure(testBase, "trie node size not 1, " + trie.nodeSize() + " after removing everything", trie); - } else if(trie.size() > 0) { - addFailure(testBase, "trie size not 0, " + trie.size() + " after removing everything", trie); + if(!isEmptyFunc.test(trie)) { + addTrieFailure("trie not empty, size " + trieSizeFunc.applyAsInt(trie) + " after removing everything", trie); + } else if(trieNodeSizeFunc != null && trieNodeSizeFunc.applyAsInt(trie) > 1) { + addTrieFailure("trie node size not 1, " + trieNodeSizeFunc.applyAsInt(trie) + " after removing everything", trie); + } else if(trieSizeFunc.applyAsInt(trie) > 0) { + addTrieFailure("trie size not 0, " + trieSizeFunc.applyAsInt(trie) + " after removing everything", trie); } } - testBase.incrementTestCount(); + incrementTestCount(); } - - static , T extends Address> void testSpliterate(TestBase testBase, R tree) { + + , T extends Address> void testSpliterate(R tree) { Function> spliteratorFunc = AddressTrie::spliterator; - testSpliterate(testBase, tree, spliteratorFunc); + testSpliterate(tree, spliteratorFunc); spliteratorFunc = AddressTrie::descendingSpliterator; - testSpliterate(testBase, tree, spliteratorFunc); + testSpliterate(tree, spliteratorFunc); } - static , T extends Address> void testSpliterate(TestBase testBase, R tree, Function> spliteratorFunc) { + , T extends Address> void testSpliterate(R tree, Function> spliteratorFunc) { int size = tree.size(); - testSpliterate(testBase, tree, 0, size, spliteratorFunc); - testSpliterate(testBase, tree, 1, size, spliteratorFunc); - testSpliterate(testBase, tree, 5, size, spliteratorFunc); - testSpliterate(testBase, tree, -1, size, spliteratorFunc); + testSpliterate(tree, 0, size, spliteratorFunc); + testSpliterate(tree, 1, size, spliteratorFunc); + testSpliterate(tree, 5, size, spliteratorFunc); + testSpliterate(tree, -1, size, spliteratorFunc); } private static ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), @@ -1118,7 +1224,7 @@ public Thread newThread(Runnable r) { static int spliterateTestCounter = 0; @SuppressWarnings("unchecked") - static , T extends Address> Set

testSpliterate(TestBase testBase, R val, int splitCount, int number, + , T extends Address> Set
testSpliterate(R val, int splitCount, int number, Function> spliteratorFunc) { R modTrie = (R) val.clone(); @@ -1143,7 +1249,7 @@ static , T extends Address> Set
testSpliterate for(Spliterator toSplit : modList) { Spliterator split = toSplit.trySplit(); if(shouldThrow) { - addFailure(testBase, "expected throw ", modTrie); + addTrieFailure("expected throw ", modTrie); } if(split != null) { newModList.add(split); @@ -1153,7 +1259,7 @@ static , T extends Address> Set
testSpliterate modList = newModList; } catch(ConcurrentModificationException e) { if(!shouldThrow) { - addFailure(testBase, "unexpected throw ", modTrie); + addTrieFailure("unexpected throw ", modTrie); } } @@ -1167,9 +1273,9 @@ static , T extends Address> Set
testSpliterate if(size1 > 3) { long size2 = split.estimateSize(); if(2 * size1 < size2) { - addFailure(testBase, "unequal split " + size1 + " and " + size2, val); + addTrieFailure("unequal split " + size1 + " and " + size2, val); } else if(size2 * 2 < size1) { - addFailure(testBase, "unequal split " + size1 + " and " + size2, val); + addTrieFailure("unequal split " + size1 + " and " + size2, val); } } } @@ -1185,7 +1291,7 @@ static , T extends Address> Set
testSpliterate // This is limited by tree depth, because we split based on tree shape, // splitting the root at the top to two halves, so max would be 32. // - addFailure(testBase, "unable to split trie " + splitter + " but size is " + exactSize, val); + addTrieFailure("unable to split trie " + splitter + " but size is " + exactSize, val); //unable to split spliterator from ● 129.0.0.0 to ● 128.0.0.0/1 but size is 59, // └─● 128.0.0.0/1 (143) to here @@ -1217,7 +1323,7 @@ static , T extends Address> Set
testSpliterate newSize += splitter.estimateSize(); } if(newSize != originalSize) { - addFailure(testBase, "split size differs, got " + newSize + " but size is " + originalSize, val); + addTrieFailure("split size differs, got " + newSize + " but size is " + originalSize, val); } } AtomicInteger counter = new AtomicInteger(); @@ -1269,7 +1375,7 @@ public void run() { job.get(); } } catch (InterruptedException | ExecutionException e) { - addFailure(testBase, "unexpected interruption " + e, val); + addTrieFailure("unexpected interruption " + e, val); } if(newList.size() == 0) { break; @@ -1286,16 +1392,16 @@ public void run() { } //System.out.println("tested " + spliteratorCount + " spliterators, " + newSpliteratorCount + " split off, " + subSpliteratorCount + " split off from split off"); if(number < Integer.MAX_VALUE && set.size() != number) { - addFailure(testBase, "set count was " + set.size() + " instead of expected " + number, val); + addTrieFailure("set count was " + set.size() + " instead of expected " + number, val); } else if(number < Integer.MAX_VALUE && counter.intValue() != number) { - addFailure(testBase, "count was " + counter + " instead of expected " + number, val); + addTrieFailure("count was " + counter + " instead of expected " + number, val); } - testBase.incrementTestCount(); + incrementTestCount(); return set; } - void createSampleTree(MACAddressTrie tree, String addrs[]) { + void createMACSampleTree(MACAddressTrie tree, String addrs[]) { for(String addr : addrs) { MACAddressString addressStr = createMACAddress(addr); MACAddress address = addressStr.getAddress(); @@ -1303,7 +1409,7 @@ void createSampleTree(MACAddressTrie tree, String addrs[]) { } } - void createSampleTree(IPv6AddressTrie tree, String addrs[]) { + void createIPv6SampleTree(IPv6AddressTrie tree, String addrs[]) { for(String addr : addrs) { IPAddressString addressStr = createAddress(addr); if(addressStr.isIPv6()) { @@ -1313,7 +1419,7 @@ void createSampleTree(IPv6AddressTrie tree, String addrs[]) { } } - void createSampleTree(IPv6AddressTrie tree, IPAddress addrs[]) { + void createIPv6SampleTreeAddrs(IPv6AddressTrie tree, IPAddress addrs[]) { for(IPAddress addr : addrs) { if(addr.isIPv6()) { addr = Partition.checkBlockOrAddress(addr); @@ -1325,7 +1431,7 @@ void createSampleTree(IPv6AddressTrie tree, IPAddress addrs[]) { } } - void createSampleTree(IPv4AddressTrie tree, IPAddress addrs[]) { + void createIPv4SampleTreeAddrs(IPv4AddressTrie tree, IPAddress addrs[]) { for(IPAddress addr : addrs) { if(addr.isIPv4()) { addr = Partition.checkBlockOrAddress(addr); @@ -1337,7 +1443,7 @@ void createSampleTree(IPv4AddressTrie tree, IPAddress addrs[]) { } } - void createSampleTree(IPv4AddressTrie tree, String addrs[]) { + void createIPv4SampleTree(IPv4AddressTrie tree, String addrs[]) { for(String addr : addrs) { IPAddressString addressStr = createAddress(addr); if(addressStr.isIPv4()) { @@ -1347,6 +1453,28 @@ void createSampleTree(IPv4AddressTrie tree, String addrs[]) { } } + void createIPv6SampleAssocTree(IPv6AddressAssociativeTrie tree, String addrs[]) { + for(int i = 0; i < addrs.length; i++) { + String addr = addrs[i]; + IPAddressString addressStr = createAddress(addr); + if(addressStr.isIPv6()) { + IPv6Address address = addressStr.getAddress().toIPv6(); + tree.put(address, i); + } + } + } + + void createIPv4SampleAssocTree(IPv4AddressAssociativeTrie tree, String addrs[]) { + for(int i = 0; i < addrs.length; i++) { + String addr = addrs[i]; + IPAddressString addressStr = createAddress(addr); + if(addressStr.isIPv4()) { + IPv4Address address = addressStr.getAddress().toIPv4(); + tree.put(address, i); + } + } + } + , T extends Address> void addFailure(String str, R trie) { addFailure(new Failure(str, trie)); } @@ -1366,7 +1494,11 @@ void addFailure(String str, Address address) { @SuppressWarnings("unchecked") , T extends Address> void testContains(R trie) { if(trie.size() > 0) { - TrieNode last = trie.getAddedNode(trie.lastAddedNode().getKey()); + T lastKey = trie.lastAddedNode().getKey(); + TrieNode last = trie.getAddedNode(lastKey); + if(last == null) { + addFailure("failure " + last + " key not in trie ", trie); + } if(!trie.contains(last.getKey())) { addFailure("failure " + last + " not in trie ", trie); } @@ -1472,9 +1604,7 @@ , T extends Address> void testContains(R trie) { } incrementTestCount(); } - - - + @SuppressWarnings("unchecked") , T extends Address> void testEdges(R trie, List addrs) { R trie2 = (R) trie.clone(); @@ -1820,7 +1950,8 @@ , T extends Address, V> void testMap(R tri i = 0; for(T addr : addrs) { if(i % 2 == 0) { - trie2.getNode(addr).remove(); + AssociativeTrieNode node = trie2.getNode(addr); + node.remove(); } i++; } @@ -2362,7 +2493,6 @@ class Count { //i = 0; List> ordered = new ArrayList<>(addrs.size()); for(T addr : trie) { - //i++; ordered.add(trie.getAddedNode(addr)); } @@ -2480,6 +2610,7 @@ , T extends Address> void testContainment( } } else if(set instanceof WrappedMap) { + @SuppressWarnings("unchecked") WrappedMap wrapped = (WrappedMap) set; AddressTrieMap trieMap = wrapped.map; AssociativeAddressTrie trie = trieMap.asTrie(); @@ -3020,6 +3151,7 @@ private , T extends Address> void testBoundedClone(Navi AddressTrieSet trieSet = (AddressTrieSet) set; newTrie = trieSet.asTrie(); } else if(set instanceof WrappedMap) { + @SuppressWarnings("unchecked") WrappedMap wrapped = (WrappedMap) set; newTrie = wrapped.map.asTrie(); } else { @@ -3232,6 +3364,7 @@ void testSetEdges( testBoundedTrieIterators(trieSet, ordered, lowerInd, upperInd, AddressTrieSet::containingFirstIterator); testBoundedTrieIterators(trieSet, ordered, lowerInd, upperInd, AddressTrieSet::blockSizeIterator); } else if(set instanceof WrappedMap) { + @SuppressWarnings("unchecked") WrappedMap wrappedMap = (WrappedMap) set; testBoundedMapIterators(wrappedMap, ordered, lowerInd, upperInd, EntrySet::iterator); testBoundedMapIterators(wrappedMap, ordered, lowerInd, upperInd, EntrySet::containingFirstIterator); @@ -3498,10 +3631,10 @@ void testMACAddrBlock(String str) { void testConvertedBlock(String str, Integer expectedPrefLen) { IPAddress addr = createAddress(str).getAddress(); - testConvertedBlock(addr, expectedPrefLen); + testConvertedAddrBlock(addr, expectedPrefLen); } - void testConvertedBlock(Address addr, Integer expectedPrefLen) { + void testConvertedAddrBlock(Address addr, Integer expectedPrefLen) { Address result = Partition.checkBlockOrAddress(addr); if(result == null) { addFailure("unexpectedly got no single block or address for " + addr, addr); @@ -3515,7 +3648,7 @@ void testAddressCheck() { IPAddress addr = createAddress("1.2.3.4/16").getAddress(); PrefixConfiguration prefCon = addr.getNetwork().getPrefixConfiguration(); if(!prefCon.allPrefixedAddressesAreSubnets()) { - testConvertedBlock(addr, null); + testConvertedAddrBlock(addr, null); } else { testIPAddrBlock("1.2.3.4/16"); } @@ -3538,7 +3671,7 @@ void testAddressCheck() { } MACAddress mac = createMACAddress("a:b:c:*:*:*").getAddress(); mac = mac.setPrefixLength(48, false); - testConvertedBlock(mac, 24); + testConvertedAddrBlock(mac, 24); testMACAddrBlock("a:b:c:*:*:*"); testNonMACBlock("a:b:c:*:2:*"); testNonBlock("a:b:c:*:2:*"); // passes null into checkBlockOrAddress @@ -3646,24 +3779,26 @@ String[][] getSampleIPAddressTries() { @Override void runTest() { testAddressCheck(); - partitionTest(this); + partitionTest(); String[][] sampleIPAddressTries = getSampleIPAddressTries(); for(String treeAddrs[] : sampleIPAddressTries) { - testRemove(this, treeAddrs); + testRemove(treeAddrs); } boolean notDoneEmptyIPv6 = true; boolean notDoneEmptyIPv4 = true; - for(String treeAddrs[] : sampleIPAddressTries) { + for(int i = 0; i < sampleIPAddressTries.length; i++) { + String treeAddrs[] = sampleIPAddressTries[i]; + //for(String treeAddrs[] : sampleIPAddressTries) { IPv6AddressTrie ipv6Tree = new IPv6AddressTrie(); - createSampleTree(ipv6Tree, treeAddrs); + createIPv6SampleTree(ipv6Tree, treeAddrs); int size = ipv6Tree.size(); if(size > 0 || notDoneEmptyIPv6) { if(notDoneEmptyIPv6) { notDoneEmptyIPv6 = size != 0; } - testIterate(this, ipv6Tree); - testSpliterate(this, ipv6Tree); + testIterate(ipv6Tree); + testSpliterate(ipv6Tree); testContains(ipv6Tree); testSerialize(ipv6Tree); @@ -3673,14 +3808,14 @@ void runTest() { } IPv4AddressTrie ipv4Tree = new IPv4AddressTrie(); - createSampleTree(ipv4Tree, treeAddrs); + createIPv4SampleTree(ipv4Tree, treeAddrs); size = ipv4Tree.size(); if(size > 0 || notDoneEmptyIPv4) { if(notDoneEmptyIPv4) { notDoneEmptyIPv4 = size != 0; } - testIterate(this, ipv4Tree); - testSpliterate(this, ipv4Tree); + testIterate(ipv4Tree); + testSpliterate(ipv4Tree); testContains(ipv4Tree); testSerialize(ipv4Tree); @@ -3688,6 +3823,36 @@ void runTest() { // String s = ipv4Tree.toAddedNodesTreeString(); // System.out.println(s); } + + for(int j = i + 1; j < sampleIPAddressTries.length; j++) { + ipv6Tree = new IPv6AddressTrie(); + createIPv6SampleTree(ipv6Tree, treeAddrs); + ipv4Tree = new IPv4AddressTrie(); + createIPv4SampleTree(ipv4Tree, treeAddrs); + + String treeAddrs2[] = sampleIPAddressTries[j]; + createIPv6SampleTree(ipv6Tree, treeAddrs2); + createIPv4SampleTree(ipv4Tree, treeAddrs2); + DualIPv4v6Tries dualTries = new DualIPv4v6Tries(ipv4Tree, ipv6Tree); + testDualIterate(dualTries); + + } + + for(int j = i + 1; j < sampleIPAddressTries.length; j++) { + IPv6AddressAssociativeTrie ipv6Trie = new IPv6AddressAssociativeTrie<>(); + createIPv6SampleAssocTree(ipv6Trie, treeAddrs); + IPv4AddressAssociativeTrie ipv4Trie = new IPv4AddressAssociativeTrie<>(); + createIPv4SampleAssocTree(ipv4Trie, treeAddrs); + + String treeAddrs2[] = sampleIPAddressTries[j]; + createIPv6SampleAssocTree(ipv6Trie, treeAddrs2); + createIPv4SampleAssocTree(ipv4Trie, treeAddrs2); + DualIPv4v6AssociativeTries dualTries = new DualIPv4v6AssociativeTries<>(ipv4Trie, ipv6Trie); + + //System.out.println(dualTries); + testDualIterate(dualTries); + } + } notDoneEmptyIPv6 = true; notDoneEmptyIPv4 = true; @@ -3723,14 +3888,14 @@ void runTest() { boolean notDoneEmptyMAC = true; for(String treeAddrs[] : testMACTries) { MACAddressTrie macTree = new MACAddressTrie(); - createSampleTree(macTree, treeAddrs); + createMACSampleTree(macTree, treeAddrs); int size = macTree.size(); if(size > 0 || notDoneEmptyMAC) { if(notDoneEmptyMAC) { notDoneEmptyMAC = size != 0; } - testIterate(this, macTree); - testSpliterate(this, macTree); + testIterate(macTree); + testSpliterate(macTree); testContains(macTree); } } @@ -3745,14 +3910,21 @@ void runTest() { testAdd(new MACAddressTrie(), addrs); testEdges(new MACAddressTrie(), addrs); testSet(new MACAddressTrie(), addrs); - testMap(new MACAddressAssociativeTrie(), addrs, i -> i, i -> 3 * 1); + testMap(new MACAddressAssociativeTrie(), addrs, + i -> i, + i -> { + if(i != null) { + return 3 * i; + } + return 3; + }); testSetEdges(new MACAddressTrie(), addrs); testMapEdges(new MACAddressAssociativeTrie(), addrs, i -> ("foobar" + i)); } } for(String treeAddrs[] : testMACTries) { - testRemoveMAC(this, treeAddrs); + testRemoveMAC(treeAddrs); } IPAddress cached[] = getAllCached(); @@ -3760,17 +3932,17 @@ void runTest() { if(cached[0].getNetwork().getPrefixConfiguration().zeroHostsAreSubnets()) { didOneMegaTree = true; IPv6AddressTrie ipv6Tree1 = new IPv6AddressTrie(); - createSampleTree(ipv6Tree1, cached); + createIPv6SampleTreeAddrs(ipv6Tree1, cached); //System.out.println(ipv6Tree1); - testIterate(this, ipv6Tree1); - testSpliterate(this, ipv6Tree1); + testIterate(ipv6Tree1); + testSpliterate(ipv6Tree1); testContains(ipv6Tree1); IPv4AddressTrie ipv4Tree1 = new IPv4AddressTrie(); - createSampleTree(ipv4Tree1, cached); + createIPv4SampleTreeAddrs(ipv4Tree1, cached); //System.out.println(ipv4Tree1); - testIterate(this, ipv4Tree1); - testSpliterate(this, ipv4Tree1); + testIterate(ipv4Tree1); + testSpliterate(ipv4Tree1); testContains(ipv4Tree1); } } @@ -3859,7 +4031,7 @@ static class Node extends BinaryTreeNode { Node(int i) { super(i); - setAdded(true); + setNodeAdded(true); } protected void setUpper(int upper) { diff --git a/IPAddress/src/inet.ipaddr/module-info.java b/IPAddress/src/inet.ipaddr/module-info.java index 7acc9361..6716ac95 100644 --- a/IPAddress/src/inet.ipaddr/module-info.java +++ b/IPAddress/src/inet.ipaddr/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Sean C Foley + * Copyright 2016-2019 Sean C Foley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 065f33c9..d7396117 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # IPAddress Java library for handling IP addresses and subnets, both IPv4 and IPv6 -IP address and network manipulations, CIDR, address and subnet operations, iterations, containment checks, longest prefix match, subnetting, and address and subnet data structures, with polymorphic code +IP address and network manipulations, CIDR, address and subnet operations, iterations, containment checks, longest prefix match, subnetting, and address and subnet data structures including address tries, with polymorphic code [View Project Page](https://seancfoley.github.io/IPAddress/) @@ -14,8 +14,8 @@ IP address and network manipulations, CIDR, address and subnet operations, itera [In the Maven Central Repository](https://repo1.maven.org/maven2/com/github/seancfoley/ipaddress/), packaged as an OSGI bundle, packaged as a [Linux Fedora rpm](http://rpmfind.net/linux/rpm2html/search.php?query=ipaddress) - Maven group id: com.github.seancfoley - Maven artifact id: [ipaddress](https://search.maven.org/search?q=ipaddress) -- Maven versions: [2.0.2](https://search.maven.org/artifact/com.github.seancfoley/ipaddress/2.0.2/jar), [3.0.0](https://search.maven.org/artifact/com.github.seancfoley/ipaddress/3.0.0/jar), [4.3.3](https://search.maven.org/artifact/com.github.seancfoley/ipaddress/4.3.3/jar), [5.4.2](https://search.maven.org/artifact/com.github.seancfoley/ipaddress/5.4.2/jar) -- Latest Maven version: [![Maven Central](https://img.shields.io/maven-central/v/com.github.seancfoley/ipaddress.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.github.seancfoley%22%20AND%20a:%22ipaddress%22) +- Maven versions: [2.0.2](https://search.maven.org/artifact/com.github.seancfoley/ipaddress/2.0.2/jar), [3.0.0](https://search.maven.org/artifact/com.github.seancfoley/ipaddress/3.0.0/jar), [4.3.3](https://search.maven.org/artifact/com.github.seancfoley/ipaddress/4.3.3/jar), [5.5.1](https://search.maven.org/artifact/com.github.seancfoley/ipaddress/5.5.1/jar) +- Latest Maven version: [![Maven Central](https://img.shields.io/maven-central/v/com.github.seancfoley/ipaddress.svg?label=Maven%20Central)](https://central.sonatype.com/namespace/com.github.seancfoley) - OSGI bundle since version 5.3.1: com.github.seancfoley.ipaddress ![Java](https://github.com/seancfoley/IPAddress/blob/gh-pages/images/java_logo.png?raw=true) ![Kotlin](https://github.com/seancfoley/IPAddress/blob/gh-pages/images/kotlin_logo.png?raw=true)![Go](https://github.com/seancfoley/IPAddress/blob/gh-pages/images/go_logo.png?raw=true) ![Scala](https://github.com/seancfoley/IPAddress/blob/gh-pages/images/scala.png?raw=true)![Groovy](https://github.com/seancfoley/IPAddress/blob/gh-pages/images/groovy.png?raw=true)![Clojure](https://github.com/seancfoley/IPAddress/blob/gh-pages/images/clojure.png?raw=true) @@ -36,7 +36,7 @@ Version | Notes | [2.0.2](https://github.com/seancfoley/IPAddress/releases/tag/v2.0.2) | Requires Java 8 or higher | [3.0.0](https://github.com/seancfoley/IPAddress/releases/tag/v3.0.0) | Requires Java 8 or higher, features MAC address support, EUI-48 and EUI-64 MAC integration with IPv6, new address framework, new IP string formats parsed and produced, and other additions | [4.3.3](https://github.com/seancfoley/IPAddress/releases/tag/v4.3.3) | Requires Java 8 or higher. Features new prefix length handling. IPv4-network/IPv6-subnet-router-anycast/zero-host addresses are interpreted as the prefix block subnet, while other prefixed addresses are individual addresses. There exists the option to preserve the version 3 behaviour. Version 4.2.0 has additional methods for managing prefix blocks. Version 4.3 features improved parsing performance and a change to increment(long) behaviour for subnets. | -**[Latest Version 5.4.2](https://github.com/seancfoley/IPAddress/releases/tag/v5.4.2)** | Requires Java 8 or higher. Compatible with Android using Android API level 24 or higher. The code is compiled with Java 8, but provides a Java 9 compiled module-info.class file for those using the Java Platform Module System (JPMS) in Java 9 or later versions. You may need (or wish) to [delete the module-info](https://github.com/seancfoley/IPAddress/issues/16#issuecomment-452425235), which can be done [with gradle](https://github.com/seancfoley/IPAddress/issues/16#issuecomment-452564690), when using Java 8 environments. Version 5 features the addition of IPAddress sequential range classes IP\*AddressSeqRange, the reorganization of classes and interfaces in inet.ipaddr.format package to standard, large, and string subpackages, enhanced address block splitting and merging functionality, Java 8 stream and spliterator methods, additional parsing options, address tries, associative address tries, and the prefix block allocator. Other enhancements are listed on the releases page for [5.0.0](https://github.com/seancfoley/IPAddress/releases/tag/v5.0.0), [5.1.0](https://github.com/seancfoley/IPAddress/releases/tag/v5.1.0), [5.2.0](https://github.com/seancfoley/IPAddress/releases/tag/v5.2.0), [5.3.0](https://github.com/seancfoley/IPAddress/releases/tag/v5.3.0) and [5.4.0](https://github.com/seancfoley/IPAddress/releases/tag/v5.4.0) +**[Latest Version 5.5.1](https://github.com/seancfoley/IPAddress/releases/tag/v5.5.1)** | Requires Java 8 or higher. Compatible with Android using Android API level 24 or higher. The code is compiled with Java 8, but provides a Java 9 compiled module-info.class file for those using the Java Platform Module System (JPMS) in Java 9 or later versions. You may need (or wish) to [delete the module-info](https://github.com/seancfoley/IPAddress/issues/16#issuecomment-452425235), which can be done [with gradle](https://github.com/seancfoley/IPAddress/issues/16#issuecomment-452564690), when using Java 8 environments. Version 5 features the addition of IPAddress sequential range classes IP\*AddressSeqRange, the reorganization of classes and interfaces in inet.ipaddr.format package to standard, large, and string subpackages, enhanced address block splitting and merging functionality, Java 8 stream and spliterator methods, additional parsing options, address tries, associative address tries, and the prefix block allocator. Other enhancements are listed on the releases page for [5.0.0](https://github.com/seancfoley/IPAddress/releases/tag/v5.0.0), [5.1.0](https://github.com/seancfoley/IPAddress/releases/tag/v5.1.0), [5.2.0](https://github.com/seancfoley/IPAddress/releases/tag/v5.2.0), [5.3.0](https://github.com/seancfoley/IPAddress/releases/tag/v5.3.0), [5.4.0](https://github.com/seancfoley/IPAddress/releases/tag/v5.4.0) and [5.5.0](https://github.com/seancfoley/IPAddress/releases/tag/v5.5.0) ## Getting Started