package org.jruby.ext.socket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.regex.Pattern;
public class SocketUtilsIPV6 {
public static String getIPV6Address(String ip) {
return new IPv6Address().fromString(ip).toString();
}
public static String getIPV6NetMask(String ip) {
IPv6Network strangeNetwork = new IPv6Network().fromString(ip);
return strangeNetwork.getNetmask().asAddress().toString();
}
public static class IPv6Address {
private static final int N_SHORTS = 8;
private long highBits;
private long lowBits;
IPv6Address(long highBits, long lowBits) {
this.highBits = highBits;
this.lowBits = lowBits;
}
private IPv6Address() { }
public IPv6Address fromString(String string) {
if (string == null) {
throw new IllegalArgumentException("can not parse [null]");
}
final String withoutScope = removeScope(string);
final String withoutIPv4MappedNotation = rewriteIPv4MappedNotation(withoutScope);
final String longNotation = expandShortNotation(withoutIPv4MappedNotation);
final long[] longs = tryParseStringArrayIntoLongArray(string, longNotation);
return mergeLongArrayIntoIPv6Address(longs);
}
public InetAddress toInetAddress() throws UnknownHostException {
return Inet6Address.getByName(toString());
}
private IPv6Address mergeLongArrayIntoIPv6Address(long[] longs) {
long high = 0L;
long low = 0L;
for (int i = 0; i < longs.length; i++) {
if (inHighRange(i)) {
high |= (longs[i] << ((longs.length - i - 1) * 16));
} else {
low |= (longs[i] << ((longs.length - i - 1) * 16));
}
}
return new IPv6Address(high, low);
}
private String rewriteIPv4MappedNotation(String string) {
if (!string.contains(".")) {
return string;
} else {
int lastColon = string.lastIndexOf(":");
String firstPart = string.substring(0, lastColon + 1);
String mappedIPv4Part = string.substring(lastColon + 1);
if (mappedIPv4Part.contains(".")) {
String[] dotSplits = Pattern.compile("\\.").split(mappedIPv4Part);
if (dotSplits.length != 4) {
throw new IllegalArgumentException(String.format("can not parse [%s]", string));
}
StringBuilder rewrittenString = new StringBuilder();
rewrittenString.append(firstPart);
int byteZero = Integer.parseInt(dotSplits[0]);
int byteOne = Integer.parseInt(dotSplits[1]);
int byteTwo = Integer.parseInt(dotSplits[2]);
int byteThree = Integer.parseInt(dotSplits[3]);
rewrittenString.append(String.format("%02x", byteZero));
rewrittenString.append(String.format("%02x", byteOne));
rewrittenString.append(":");
rewrittenString.append(String.format("%02x", byteTwo));
rewrittenString.append(String.format("%02x", byteThree));
return rewrittenString.toString();
} else {
throw new IllegalArgumentException(String.format("can not parse [%s]", string));
}
}
}
private long[] tryParseStringArrayIntoLongArray(String string, String longNotation) {
try {
return parseStringArrayIntoLongArray(longNotation.split(":"));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("can not parse [" + string + "]");
}
}
private long[] parseStringArrayIntoLongArray(String[] strings) {
final long[] longs = new long[strings.length];
for (int i = 0; i < strings.length; i++) {
longs[i] = Long.parseLong(strings[i], 16);
}
return longs;
}
private String expandShortNotation(String string) {
if (!string.contains("::")) {
return string;
} else if (string.equals("::")) {
return generateZeroes(8);
} else {
final int numberOfColons = countOccurrences(string, ':');
if (string.startsWith("::")) {
return string.replace("::", generateZeroes((7 + 2) - numberOfColons));
} else if (string.endsWith("::")) {
return string.replace("::", ":" + generateZeroes((7 + 2) - numberOfColons));
} else {
return string.replace("::", ":" + generateZeroes((7 + 2 - 1) - numberOfColons));
}
}
}
private int countOccurrences(String haystack, char needle) {
int count = 0;
for (int i = 0; i < haystack.length(); i++) {
if (haystack.charAt(i) == needle) {
count++;
}
}
return count;
}
private String generateZeroes(int number) {
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < number; i++) {
builder.append("0:");
}
return builder.toString();
}
private String removeScope(String string) {
int hasScope = string.indexOf('%');
if (hasScope != -1) {
return string.substring(0, hasScope);
}
return string;
}
public IPv6Address add(int value) {
final long newLowBits = lowBits + value;
if (value >= 0) {
if (isLessThanUnsigned(newLowBits, lowBits)) {
return new IPv6Address(highBits + 1, newLowBits);
} else {
return new IPv6Address(highBits, newLowBits);
}
} else {
if (isLessThanUnsigned(lowBits, newLowBits)) {
return new IPv6Address(highBits - 1, newLowBits);
} else {
return new IPv6Address(highBits, newLowBits);
}
}
}
public IPv6Address subtract(int value) {
final long newLowBits = lowBits - value;
if (value >= 0) {
if (isLessThanUnsigned(lowBits, newLowBits)) {
return new IPv6Address(highBits - 1, newLowBits);
} else {
return new IPv6Address(highBits, newLowBits);
}
} else {
if (isLessThanUnsigned(newLowBits, lowBits)) {
return new IPv6Address(highBits + 1, newLowBits);
} else {
return new IPv6Address(highBits, newLowBits);
}
}
}
private boolean isLessThanUnsigned(long a, long b) {
return (a < b) ^ ((a < 0) != (b < 0));
}
public IPv6Address maskWithNetworkMask(final IPv6NetworkMask networkMask) {
if (networkMask.asPrefixLength() == 128) {
return this;
} else if (networkMask.asPrefixLength() == 64) {
return new IPv6Address(this.highBits, 0);
} else if (networkMask.asPrefixLength() == 0) {
return new IPv6Address(0, 0);
} else if (networkMask.asPrefixLength() > 64) {
final int remainingPrefixLength = networkMask.asPrefixLength() - 64;
return new IPv6Address(this.highBits, this.lowBits & (0xFFFFFFFFFFFFFFFFL << (64 - remainingPrefixLength)));
} else {
return new IPv6Address(this.highBits & (0xFFFFFFFFFFFFFFFFL << (64 - networkMask.asPrefixLength())), 0);
}
}
public IPv6Address maximumAddressWithNetworkMask(final IPv6NetworkMask networkMask) {
if (networkMask.asPrefixLength() == 128) {
return this;
} else if (networkMask.asPrefixLength() == 64) {
return new IPv6Address(this.highBits, 0xFFFFFFFFFFFFFFFFL);
} else if (networkMask.asPrefixLength() > 64) {
final int remainingPrefixLength = networkMask.asPrefixLength() - 64;
return new IPv6Address(this.highBits, this.lowBits | (0xFFFFFFFFFFFFFFFFL >>> remainingPrefixLength));
} else {
return new IPv6Address(this.highBits | (0xFFFFFFFFFFFFFFFFL >>> networkMask.asPrefixLength()), 0xFFFFFFFFFFFFFFFFL);
}
}
public boolean isIPv4Mapped() {
return this.highBits == 0
&& (this.lowBits & 0xFFFF000000000000L) == 0
&& (this.lowBits & 0x0000FFFF00000000L) == 0x0000FFFF00000000L;
}
@Override
public String toString() {
if (isIPv4Mapped()) {
return toIPv4MappedAddressString();
} else {
return toShortHandNotationString();
}
}
private String toIPv4MappedAddressString() {
int byteZero = (int) ((this.lowBits & 0x00000000FF000000L) >> 24);
int byteOne = (int) ((this.lowBits & 0x0000000000FF0000L) >> 16);
int byteTwo = (int) ((this.lowBits & 0x000000000000FF00L) >> 8);
int byteThree = (int) ((this.lowBits & 0x00000000000000FFL));
final StringBuilder result = new StringBuilder("::ffff:");
result.append(byteZero).append(".").append(byteOne).append(".").append(byteTwo).append(".").append(byteThree);
return result.toString();
}
private String toShortHandNotationString() {
final String[] strings = toArrayOfShortStrings();
final StringBuilder result = new StringBuilder();
int[] shortHandNotationPositionAndLength = startAndLengthOfLongestRunOfZeroes();
int shortHandNotationPosition = shortHandNotationPositionAndLength[0];
int shortHandNotationLength = shortHandNotationPositionAndLength[1];
boolean useShortHandNotation = shortHandNotationLength > 1;
for (int i = 0; i < strings.length; i++) {
if (useShortHandNotation && i == shortHandNotationPosition) {
if (i == 0) {
result.append("::");
} else {
result.append(":");
}
} else if (!(i > shortHandNotationPosition && i < shortHandNotationPosition + shortHandNotationLength)) {
result.append(strings[i]);
if (i < N_SHORTS - 1) {
result.append(":");
}
}
}
return result.toString().toLowerCase();
}
private String[] toArrayOfShortStrings() {
final short[] shorts = toShortArray();
final String[] strings = new String[shorts.length];
for (int i = 0; i < shorts.length; i++) {
strings[i] = String.format("%x", shorts[i]);
}
return strings;
}
private short[] toShortArray() {
final short[] shorts = new short[N_SHORTS];
for (int i = 0; i < N_SHORTS; i++) {
if (inHighRange(i)) {
shorts[i] = (short) (((highBits << i * 16) >>> 16 * (N_SHORTS - 1)) & 0xFFFF);
} else {
shorts[i] = (short) (((lowBits << i * 16) >>> 16 * (N_SHORTS - 1)) & 0xFFFF);
}
}
return shorts;
}
private boolean inHighRange(int shortNumber) {
return shortNumber >= 0 && shortNumber < 4;
}
int[] startAndLengthOfLongestRunOfZeroes() {
int longestConsecutiveZeroes = 0;
int longestConsecutiveZeroesPos = -1;
short[] shorts = toShortArray();
for (int pos = 0; pos < shorts.length; pos++) {
int consecutiveZeroesAtCurrentPos = countConsecutiveZeroes(shorts, pos);
if (consecutiveZeroesAtCurrentPos > longestConsecutiveZeroes) {
longestConsecutiveZeroes = consecutiveZeroesAtCurrentPos;
longestConsecutiveZeroesPos = pos;
}
}
return new int[]{longestConsecutiveZeroesPos, longestConsecutiveZeroes};
}
private int countConsecutiveZeroes(short[] shorts, int offset) {
int count = 0;
for (int i = offset; i < shorts.length && shorts[i] == 0; i++) {
count++;
}
return count;
}
public long getHighBits() {
return highBits;
}
public long getLowBits() {
return lowBits;
}
public int numberOfTrailingZeroes() {
return lowBits == 0
? Long.numberOfTrailingZeros(highBits) + 64
: Long.numberOfTrailingZeros(lowBits);
}
public int numberOfTrailingOnes() {
final IPv6Address plusOne = this.add(1);
return plusOne.getLowBits() == 0
? Long.numberOfTrailingZeros(plusOne.getHighBits()) + 64
: Long.numberOfTrailingZeros(plusOne.getLowBits());
}
public int numberOfLeadingZeroes() {
return highBits == 0
? Long.numberOfLeadingZeros(lowBits) + 64
: Long.numberOfLeadingZeros(highBits);
}
public int numberOfLeadingOnes() {
final IPv6Address flipped = new IPv6Address(~this.highBits, ~this.lowBits);
return flipped.numberOfLeadingZeroes();
}
}
public static class IPv6NetworkMask {
private final int prefixLength;
IPv6NetworkMask(int prefixLength) {
if (prefixLength < 0 || prefixLength > 128) {
throw new IllegalArgumentException("prefix length should be in interval [0, 128]");
}
this.prefixLength = prefixLength;
}
public int asPrefixLength() {
return prefixLength;
}
public IPv6Address asAddress() {
if (prefixLength == 128) {
return new IPv6Address(0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL);
} else if (prefixLength == 64) {
return new IPv6Address(0xFFFFFFFFFFFFFFFFL, 0L);
} else if (prefixLength > 64) {
final int remainingPrefixLength = prefixLength - 64;
return new IPv6Address(0xFFFFFFFFFFFFFFFFL, (0xFFFFFFFFFFFFFFFFL << (64 - remainingPrefixLength)));
} else {
return new IPv6Address(0xFFFFFFFFFFFFFFFFL << (64 - prefixLength), 0);
}
}
}
public static class IPv6Network {
private IPv6NetworkMask networkMask;
private IPv6Address first;
private IPv6Address last;
private IPv6Address address;
private IPv6Network(IPv6Address address, IPv6NetworkMask networkMask) {
this.first = address.maskWithNetworkMask(networkMask);
this.last = address.maximumAddressWithNetworkMask(networkMask);
this.address = address.maskWithNetworkMask(networkMask);
this.networkMask = networkMask;
}
private IPv6Network() {
}
public IPv6Network fromString(String string) {
if (string.indexOf('/') == -1) {
throw new IllegalArgumentException("Expected format is network-address/prefix-length");
}
final String networkAddressString = parseNetworkAddress(string);
int prefixLength = parsePrefixLength(string);
IPv6Address networkAddress = new IPv6Address().fromString(networkAddressString);
return new IPv6Network(networkAddress, new IPv6NetworkMask(prefixLength));
}
private String parseNetworkAddress(String string) {
return string.substring(0, string.indexOf('/'));
}
private int parsePrefixLength(String string) {
try {
return Integer.parseInt(string.substring(string.indexOf('/') + 1));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Prefix length should be a positive integer");
}
}
public IPv6NetworkMask getNetmask() {
return networkMask;
}
}
}