2020-08-08 20:23:27 +02:00
/ *
* Copyright 2020 Dorkbox , llc
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License ,
* version 2.0 ( the " License " ) ; you may not use this file except in compliance
* with the License . You may obtain a copy of the License at :
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS , WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied . See the
* License for the specific language governing permissions and limitations
* under the License .
* /
package dorkbox.netUtil
2021-04-26 23:22:24 +02:00
import java.math.BigInteger
2021-04-08 15:26:53 +02:00
import java.net.*
2020-08-08 20:23:27 +02:00
/ * *
* A class that holds a number of network - related constants , also from :
* ( Netty , apache 2.0 license )
* https : //github.com/netty/netty/blob/4.1/common/src/main/java/io/netty/util/NetUtil.java
*
* This class borrowed some of its methods from a modified fork of the
* [ Inet6Util class ]
* ( http : //svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/luni/src/main/java/org/apache/harmony/luni/util/Inet6Util.java) which was part of Apache Harmony.
* /
object IPv6 {
2021-04-06 23:42:45 +02:00
/ * *
* Gets the version number .
* /
2022-03-03 00:39:25 +01:00
const val version = " 2.9.1 "
2021-04-27 10:56:20 +02:00
// used for subnet mask info
private val MINUS _ONE = BigInteger . valueOf ( - 1 )
2021-04-06 23:42:45 +02:00
2020-08-08 20:23:27 +02:00
/ * *
* Maximum amount of value adding characters in between IPV6 separators
* /
private const val IPV6 _MAX _CHAR _BETWEEN _SEPARATOR = 4
/ * *
* Minimum number of separators that must be present in an IPv6 string
* /
private const val IPV6 _MIN _SEPARATORS = 2
/ * *
* Maximum number of separators that must be present in an IPv6 string
* /
private const val IPV6 _MAX _SEPARATORS = 8
/ * *
* Maximum amount of value adding characters in between IPV4 separators
* /
private const val IPV4 _MAX _CHAR _BETWEEN _SEPARATOR = 3
/ * *
* Number of separators that must be present in an IPv4 string
* /
private const val IPV4 _SEPARATORS = 3
/ * *
* Number of bytes needed to represent and IPV6 value
* /
private const val IPV6 _BYTE _COUNT = 16
/ * *
* This defines how many words ( represented as ints ) are needed to represent an IPv6 address
* /
private const val IPV6 _WORD _COUNT = 8
/ * *
* The maximum number of characters for an IPV6 string with no scope
* /
private const val IPV6 _MAX _CHAR _COUNT = 39
/ * *
* Returns `true` if an IPv6 address should be preferred when a host has both an IPv4 address and an IPv6
* address . The default value of this property is `false` .
*
* @see [ Java SE
* networking properties ] ( https : //docs.oracle.com/javase/8/docs/api/java/net/doc-files/net-properties.html)
* /
val isPreferred = Common . getBoolean ( " java.net.preferIPv6Addresses " , false )
2020-09-10 00:22:47 +02:00
/ * *
* true if there is an IPv6 network interface available
* /
val isAvailable : Boolean by lazy {
// Retrieve the list of available network interfaces.
val netInterfaces = mutableListOf < NetworkInterface > ( )
try {
val interfaces = NetworkInterface . getNetworkInterfaces ( )
if ( interfaces != null ) {
while ( interfaces . hasMoreElements ( ) ) {
val iface : NetworkInterface = interfaces . nextElement ( )
// Use the interface with proper INET addresses only.
if ( SocketUtils . addressesFromNetworkInterface ( iface ) . hasMoreElements ( ) ) {
netInterfaces . add ( iface )
}
}
}
} catch ( e : SocketException ) {
Common . logger . warn ( " Failed to retrieve the list of available network interfaces " , e )
}
// check if IPv4 is possible.
var ipv6Possible = false
loop @ for ( iface in netInterfaces ) {
val i = SocketUtils . addressesFromNetworkInterface ( iface )
while ( i . hasMoreElements ( ) ) {
val addr : InetAddress = i . nextElement ( )
if ( addr is Inet6Address ) {
ipv6Possible = true
break @loop
}
}
}
ipv6Possible
}
2020-08-08 20:23:27 +02:00
/ * *
* The [ Inet6Address ] that represents the IPv6 loopback address ' :: 1 '
* /
val LOCALHOST : Inet6Address by lazy {
2020-09-08 23:54:16 +02:00
// Create IPv6 address, this will ALWAYS work
2020-08-08 20:23:27 +02:00
InetAddress . getByAddress ( " localhost " , byteArrayOf ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 ) ) as Inet6Address
}
2020-09-08 23:54:16 +02:00
/ * *
* The [ Inet4Address ] that represents the IPv4 wildcard address ' 0.0 . 0.0 '
* /
val WILDCARD : Inet6Address by lazy {
// Create IPv6 address, this will ALWAYS work
2020-09-18 03:01:51 +02:00
InetAddress . getByAddress ( null , byteArrayOf ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) ) as Inet6Address
2020-09-08 23:54:16 +02:00
}
2021-04-08 19:59:25 +02:00
/ * *
* the length of an address in this particular family .
* /
val length = 16
/ * *
* @return if the specified address is of this family type
* /
fun isFamily ( address : InetAddress ) : Boolean {
return address is Inet6Address
}
2020-08-08 20:23:27 +02:00
/ * *
* Takes a [ String ] and parses it to see if it is a valid IPV6 address .
*
* @return true , if the string represents an IPV6 address , false otherwise
* /
fun isValid ( ip : String ) : Boolean {
var end = ip . length
if ( end < 2 ) {
return false
}
// strip "[]"
var start : Int
var c = ip [ 0 ]
if ( c == '[' ) {
end --
if ( ip [ end ] != ']' ) {
// must have a close ]
return false
}
start = 1
c = ip [ 1 ]
}
else {
start = 0
}
var colons : Int
var compressBegin : Int
if ( c == ':' ) {
// an IPv6 address can start with "::" or with a number
if ( ip [ start + 1 ] != ':' ) {
return false
}
colons = 2
compressBegin = start
start += 2
}
else {
colons = 0
compressBegin = - 1
}
var wordLen = 0
loop @ for ( i in start until end ) {
c = ip [ i ]
if ( isValidHexChar ( c ) ) {
if ( wordLen < 4 ) {
wordLen ++
continue
}
return false
}
when ( c ) {
':' -> {
if ( colons > 7 ) {
return false
}
if ( ip [ i - 1 ] == ':' ) {
if ( compressBegin >= 0 ) {
return false
}
compressBegin = i - 1
}
else {
wordLen = 0
}
colons ++
}
'.' -> {
// case for the last 32-bits represented as IPv4 x:x:x:x:x:x:d.d.d.d
// check a normal case (6 single colons)
if ( compressBegin < 0 && colons != 6 || // a special case ::1:2:3:4:5:d.d.d.d allows 7 colons with an
// IPv4 ending, otherwise 7 :'s is bad
colons == 7 && compressBegin >= start || colons > 7 ) {
return false
}
// Verify this address is of the correct structure to contain an IPv4 address.
// It must be IPv4-Mapped or IPv4-Compatible
2021-04-08 20:55:05 +02:00
// a valid one is, for example: 0000:0000:0000:0000:0000:0000:192.168.89.9
2020-08-08 20:23:27 +02:00
// (see https://tools.ietf.org/html/rfc4291#section-2.5.5).
val ipv4Start = i - wordLen
var j = ipv4Start - 2 // index of character before the previous ':'.
if ( IPv4 . isValidIPv4MappedChar ( ip [ j ] ) ) {
2021-04-08 20:55:05 +02:00
if ( ! IPv4 . isValidIPv4MappedChar ( ip [ j - 1 ] ) ||
! IPv4 . isValidIPv4MappedChar ( ip [ j - 2 ] ) ||
! IPv4 . isValidIPv4MappedChar ( ip [ j - 3 ] ) ) {
2020-08-08 20:23:27 +02:00
return false
}
2021-04-08 20:55:05 +02:00
2020-08-08 20:23:27 +02:00
j -= 5
}
2021-04-08 20:55:05 +02:00
2020-08-08 20:23:27 +02:00
while ( j >= start ) {
val tmpChar = ip [ j ]
if ( tmpChar != '0' && tmpChar != ':' ) {
return false
}
-- j
}
// 7 - is minimum IPv4 address length
var ipv4End = ip . indexOf ( '%' , ipv4Start + 7 )
if ( ipv4End < 0 ) {
ipv4End = end
}
return IPv4 . isValidIpV4Address ( ip , ipv4Start , ipv4End )
}
'%' -> {
// strip the interface name/index after the percent sign
end = i
break @loop
}
else -> return false
}
}
// normal case without compression
return if ( compressBegin < 0 ) {
colons == 7 && wordLen > 0
}
else compressBegin + 2 == end || // 8 colons is valid only if compression in start or end
wordLen > 0 && ( colons < 8 || compressBegin <= start )
}
private fun isValidHexChar ( c : Char ) : Boolean {
return c in '0' .. '9' || c in 'A' .. 'F' || c in 'a' .. 'f'
}
/ * *
* Returns the [ Inet6Address ] representation of a [ CharSequence ] IP address .
*
* This method will treat all IPv4 type addresses as " IPv4 mapped " ( see [ . getByName ] )
*
* @param ip [ CharSequence ] IP address to be converted to a [ Inet6Address ]
* @return [ Inet6Address ] representation of the `ip` or `null` if not a valid IP address .
* /
2021-04-27 01:01:33 +02:00
fun toAddress ( ip : String ) : Inet6Address ? {
return toAddress ( ip , true )
2020-08-08 20:23:27 +02:00
}
/ * *
* Returns the [ Inet6Address ] representation of a [ String ] IP address .
* < p >
* The [ ipv4Mapped ] parameter specifies how IPv4 addresses should be treated . The " IPv4 mapped " format is
* defined in < a href = " http://tools.ietf.org/html/rfc4291#section-2.5.5 " > rfc 4291 section 2 < / a > is supported .
*
* @param ip [ String ] IP address to be converted to a [ Inet6Address ]
* @param ipv4Mapped
* < ul >
* < li > { @code true } To allow IPv4 mapped inputs to be translated into { @link Inet6Address } < / li >
* < li > { @code false } Consider IPv4 mapped addresses as invalid . < / li >
* < / ul >
* @return [ Inet6Address ] representation of the [ ip ] or [ null ] if not a valid IP address .
* /
2021-04-29 01:20:09 +02:00
fun toAddress ( ip : String , ipv4Mapped : Boolean = true ) : Inet6Address ? {
2020-08-08 20:23:27 +02:00
val bytes = getIPv6ByName ( ip , ipv4Mapped ) ?: return null
return try {
Inet6Address . getByAddress ( null , bytes , - 1 )
} catch ( e : UnknownHostException ) {
throw RuntimeException ( e ) // Should never happen
}
}
/ * *
* Returns the byte array representation of a [ CharSequence ] IP address .
*
*
* The `ipv4Mapped` parameter specifies how IPv4 addresses should be treated .
* " IPv4 mapped " format as
* defined in [ rfc 4291 section 2 ] ( http : //tools.ietf.org/html/rfc4291#section-2.5.5) is supported.
* @param ip [ CharSequence ] IP address to be converted to a [ Inet6Address ]
* @param ipv4Mapped
* * `true` To allow IPv4 mapped inputs to be translated into [ Inet6Address ]
* * `false` Consider IPv4 mapped addresses as invalid .
*
* @return byte array representation of the `ip` or `null` if not a valid IP address .
* /
2021-04-29 01:20:09 +02:00
private fun getIPv6ByName ( ip : CharSequence , ipv4Mapped : Boolean = true ) : ByteArray ? {
2020-08-08 20:23:27 +02:00
val bytes = ByteArray ( IPV6 _BYTE _COUNT )
val ipLength = ip . length
var compressBegin = 0
var compressLength = 0
var currentIndex = 0
var value = 0
var begin = - 1
var i = 0
var ipv6Separators = 0
var ipv4Separators = 0
var tmp : Int
var needsShift = false
while ( i < ipLength ) {
val c = ip [ i ]
when ( c ) {
':' -> {
++ ipv6Separators
if ( i - begin > IPV6 _MAX _CHAR _BETWEEN _SEPARATOR || ipv4Separators > 0 || ipv6Separators > IPV6 _MAX _SEPARATORS || currentIndex + 1 >= bytes . size ) {
return null
}
value = value shl ( IPV6 _MAX _CHAR _BETWEEN _SEPARATOR - ( i - begin ) shl 2 )
if ( compressLength > 0 ) {
compressLength -= 2
}
// The value integer holds at most 4 bytes from right (most significant) to left (least significant).
// The following bit shifting is used to extract and re-order the individual bytes to achieve a
// left (most significant) to right (least significant) ordering.
bytes [ currentIndex ++ ] = ( value and 0xf shl 4 or ( value shr 4 and 0xf ) ) . toByte ( )
bytes [ currentIndex ++ ] = ( value shr 8 and 0xf shl 4 or ( value shr 12 and 0xf ) ) . toByte ( )
tmp = i + 1
if ( tmp < ipLength && ip [ tmp ] == ':' ) {
++ tmp
if ( compressBegin != 0 || tmp < ipLength && ip [ tmp ] == ':' ) {
return null
}
++ ipv6Separators
needsShift = ipv6Separators == 2 && value == 0
compressBegin = currentIndex
compressLength = bytes . size - compressBegin - 2
++ i
}
value = 0
begin = - 1
}
'.' -> {
++ ipv4Separators
tmp = i - begin // tmp is the length of the current segment.
if ( tmp > IPV4 _MAX _CHAR _BETWEEN _SEPARATOR || begin < 0 || ipv4Separators > IPV4 _SEPARATORS || ipv6Separators > 0 && currentIndex + compressLength < 12 || i + 1 >= ipLength || currentIndex >= bytes . size || ipv4Separators == 1 &&
// We also parse pure IPv4 addresses as IPv4-Mapped for ease of use.
( ! ipv4Mapped || currentIndex != 0 && !is ValidIPv4Mapped ( bytes , currentIndex , compressBegin , compressLength ) || tmp == 3 && ( ! IPv4 . isValidNumericChar (
ip [ i - 1 ] ) || ! IPv4 . isValidNumericChar ( ip [ i - 2 ] ) || ! IPv4 . isValidNumericChar ( ip [ i - 3 ] ) ) || tmp == 2 && ( ! IPv4 . isValidNumericChar (
ip [ i - 1 ] ) || ! IPv4 . isValidNumericChar ( ip [ i - 2 ] ) ) || tmp == 1 && ! IPv4 . isValidNumericChar ( ip [ i - 1 ] ) ) ) {
return null
}
value = value shl ( IPV4 _MAX _CHAR _BETWEEN _SEPARATOR - tmp shl 2 )
// The value integer holds at most 3 bytes from right (most significant) to left (least significant).
// The following bit shifting is to restructure the bytes to be left (most significant) to
// right (least significant) while also accounting for each IPv4 digit is base 10.
begin = ( value and 0xf ) * 100 + ( value shr 4 and 0xf ) * 10 + ( value shr 8 and 0xf )
if ( begin < 0 || begin > 255 ) {
return null
}
bytes [ currentIndex ++ ] = begin . toByte ( )
value = 0
begin = - 1
}
else -> {
if ( !is ValidHexChar ( c ) || ipv4Separators > 0 && ! IPv4 . isValidNumericChar ( c ) ) {
return null
}
if ( begin < 0 ) {
begin = i
}
else if ( i - begin > IPV6 _MAX _CHAR _BETWEEN _SEPARATOR ) {
return null
}
// The value is treated as a sort of array of numbers because we are dealing with
// at most 4 consecutive bytes we can use bit shifting to accomplish this.
// The most significant byte will be encountered first, and reside in the right most
// position of the following integer
value += Common . decodeHexNibble ( c ) shl ( i - begin shl 2 )
}
}
++ i
}
val isCompressed = compressBegin > 0
// Finish up last set of data that was accumulated in the loop (or before the loop)
if ( ipv4Separators > 0 ) {
if ( begin > 0 && i - begin > IPV4 _MAX _CHAR _BETWEEN _SEPARATOR || ipv4Separators != IPV4 _SEPARATORS || currentIndex >= bytes . size ) {
return null
}
if ( ipv6Separators == 0 ) {
compressLength = 12
}
else if ( ipv6Separators >= IPV6 _MIN _SEPARATORS && ( !is Compressed && ipv6Separators == 6 && ip [ 0 ] != ':' || isCompressed && ipv6Separators < IPV6 _MAX _SEPARATORS && ( ip [ 0 ] != ':' || compressBegin <= 2 ) ) ) {
compressLength -= 2
}
else {
return null
}
value = value shl ( IPV4 _MAX _CHAR _BETWEEN _SEPARATOR - ( i - begin ) shl 2 )
// The value integer holds at most 3 bytes from right (most significant) to left (least significant).
// The following bit shifting is to restructure the bytes to be left (most significant) to
// right (least significant) while also accounting for each IPv4 digit is base 10.
begin = ( value and 0xf ) * 100 + ( value shr 4 and 0xf ) * 10 + ( value shr 8 and 0xf )
if ( begin < 0 || begin > 255 ) {
return null
}
bytes [ currentIndex ++ ] = begin . toByte ( )
}
else {
tmp = ipLength - 1
if ( begin > 0 && i - begin > IPV6 _MAX _CHAR _BETWEEN _SEPARATOR || ipv6Separators < IPV6 _MIN _SEPARATORS || !is Compressed && ( ipv6Separators + 1 != IPV6 _MAX _SEPARATORS || ip [ 0 ] == ':' || ip [ tmp ] == ':' ) || isCompressed && ( ipv6Separators > IPV6 _MAX _SEPARATORS || ipv6Separators == IPV6 _MAX _SEPARATORS && ( compressBegin <= 2 && ip [ 0 ] != ':' || compressBegin >= 14 && ip [ tmp ] != ':' ) ) || currentIndex + 1 >= bytes . size || begin < 0 && ip [ tmp - 1 ] != ':' || compressBegin > 2 && ip [ 0 ] == ':' ) {
return null
}
if ( begin >= 0 && i - begin <= IPV6 _MAX _CHAR _BETWEEN _SEPARATOR ) {
value = value shl ( IPV6 _MAX _CHAR _BETWEEN _SEPARATOR - ( i - begin ) shl 2 )
}
// The value integer holds at most 4 bytes from right (most significant) to left (least significant).
// The following bit shifting is used to extract and re-order the individual bytes to achieve a
// left (most significant) to right (least significant) ordering.
bytes [ currentIndex ++ ] = ( value and 0xf shl 4 or ( value shr 4 and 0xf ) ) . toByte ( )
bytes [ currentIndex ++ ] = ( value shr 8 and 0xf shl 4 or ( value shr 12 and 0xf ) ) . toByte ( )
}
i = currentIndex + compressLength
if ( needsShift || i >= bytes . size ) {
// Right shift array
if ( i >= bytes . size ) {
++ compressBegin
}
i = currentIndex
while ( i < bytes . size ) {
begin = bytes . size - 1
while ( begin >= compressBegin ) {
bytes [ begin ] = bytes [ begin - 1 ]
-- begin
}
bytes [ begin ] = 0
++ compressBegin
++ i
}
}
else {
// Selectively move elements
i = 0
while ( i < compressLength ) {
begin = i + compressBegin
currentIndex = begin + compressLength
if ( currentIndex < bytes . size ) {
bytes [ currentIndex ] = bytes [ begin ]
bytes [ begin ] = 0
}
else {
break
}
++ i
}
}
if ( ipv4Separators > 0 ) {
// We only support IPv4-Mapped addresses [1] because IPv4-Compatible addresses are deprecated [2].
// [1] https://tools.ietf.org/html/rfc4291#section-2.5.5.2
// [2] https://tools.ietf.org/html/rfc4291#section-2.5.5.1
bytes [ 11 ] = 0xff . toByte ( )
bytes [ 10 ] = bytes [ 11 ]
}
return bytes
}
private fun isValidIPv4Mapped ( bytes : ByteArray , currentIndex : Int , compressBegin : Int , compressLength : Int ) : Boolean {
val mustBeZero = compressBegin + compressLength >= 14
return currentIndex <= 12 && currentIndex >= 2 && ( ! mustBeZero || compressBegin < 12 )
&& isValidIPv4MappedSeparators ( bytes [ currentIndex - 1 ] , bytes [ currentIndex - 2 ] , mustBeZero ) &&
isZero ( bytes , 0 , currentIndex - 3 )
}
private fun isZero ( bytes : ByteArray , startPos : Int , length : Int ) : Boolean {
var start = startPos
val end = start + length
val zeroByteByte = 0. toByte ( )
while ( start < end ) {
if ( bytes [ start ++ ] != zeroByteByte ) {
return false
}
}
return true
}
private fun isValidIPv4MappedSeparators ( b0 : Byte , b1 : Byte , mustBeZero : Boolean ) : Boolean {
// We allow IPv4 Mapped (https://tools.ietf.org/html/rfc4291#section-2.5.5.1)
// and IPv4 compatible (https://tools.ietf.org/html/rfc4291#section-2.5.5.1).
// The IPv4 compatible is deprecated, but it allows parsing of plain IPv4 addressed into IPv6-Mapped addresses.
return b0 == b1 && ( b0 . toInt ( ) == 0 || ! mustBeZero && b1 . toInt ( ) == - 1 )
}
/ * *
2021-04-08 15:26:53 +02:00
* Creates an byte [ ] based on an ip string . No error handling is performed here .
2020-08-08 20:23:27 +02:00
* /
fun toBytesOrNull ( ipAddress : String ) : ByteArray ? {
if ( isValid ( ipAddress ) ) {
2021-04-27 01:01:33 +02:00
return toAddress ( ipAddress ) ?. address
2020-08-08 20:23:27 +02:00
}
return null
}
2021-04-08 15:26:53 +02:00
2020-08-08 20:23:27 +02:00
/ * *
2021-04-08 15:26:53 +02:00
* Creates an byte [ ] based on an ip string .
2020-08-08 20:23:27 +02:00
* /
fun toBytes ( ipAddress : String ) : ByteArray {
// always return a byte array
var fixedIp = ipAddress
if ( fixedIp [ 0 ] == '[' ) {
fixedIp = fixedIp . substring ( 1 , fixedIp . length - 1 )
}
val percentPos : Int = fixedIp . indexOf ( '%' )
if ( percentPos >= 0 ) {
fixedIp = fixedIp . substring ( 0 , percentPos )
}
2021-04-27 01:01:33 +02:00
return toAddress ( fixedIp ) ?. address ?: ByteArray ( 32 )
2020-08-08 20:23:27 +02:00
}
2021-04-26 23:22:24 +02:00
/ * *
* Converts a byte array into a 128 - bit BigInteger
* /
fun toInt ( ipBytes : ByteArray ) : BigInteger {
return BigInteger ( ipBytes )
}
/ * *
* Converts an IP address into a 128 - bit BigInteger
* /
fun toInt ( ipAsString : String ) : BigInteger {
return if ( isValid ( ipAsString ) ) {
toIntUnsafe ( ipAsString )
} else {
BigInteger ( ByteArray ( 0 ) )
}
}
/ * *
* Converts an IP address into a 128 - bit BigInteger , no validity checks are performed
* /
fun toIntUnsafe ( ipAsString : String ) : BigInteger {
val bytes = toBytes ( ipAsString )
return BigInteger ( bytes )
}
2020-08-08 20:23:27 +02:00
/ * *
* Returns the [ String ] representation of an [ InetAddress ] .
*
* * Inet4Address results are identical to [ InetAddress . getHostAddress ]
* * Inet6Address results adhere to
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4) if
* `ipv4Mapped` is false . If `ipv4Mapped` is true then " IPv4 mapped " format
* from [ rfc 4291 section 2 ] ( http : //tools.ietf.org/html/rfc4291#section-2.5.5) will be supported.
* The compressed result will always obey the compression rules defined in
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4)
*
*
* The output does not include Scope ID .
*
* @param bytes [ InetAddress ] to be converted to an address string
*
* @return `String` containing the text - formatted IP address
* /
fun toString ( bytes : ByteArray ) : String {
return toString ( bytes , 0 )
}
/ * *
* Returns the [ String ] representation of an [ InetAddress ] .
*
* * Inet4Address results are identical to [ InetAddress . getHostAddress ]
* * Inet6Address results adhere to
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4) if
* `ipv4Mapped` is false . If `ipv4Mapped` is true then " IPv4 mapped " format
* from [ rfc 4291 section 2 ] ( http : //tools.ietf.org/html/rfc4291#section-2.5.5) will be supported.
* The compressed result will always obey the compression rules defined in
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4)
*
*
2021-04-08 19:59:25 +02:00
* The output does not include Scope ID .
*
* @param ints [ InetAddress ] to be converted to an address string
*
* @return `String` containing the text - formatted IP address
* /
fun toString ( ints : IntArray ) : String {
return toString ( ints , 0 )
}
/ * *
* Returns the [ String ] representation of an [ InetAddress ] .
*
* * Inet4Address results are identical to [ InetAddress . getHostAddress ]
* * Inet6Address results adhere to
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4) if
* `ipv4Mapped` is false . If `ipv4Mapped` is true then " IPv4 mapped " format
* from [ rfc 4291 section 2 ] ( http : //tools.ietf.org/html/rfc4291#section-2.5.5) will be supported.
* The compressed result will always obey the compression rules defined in
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4)
*
* The output does not include Scope ID .
*
* @param bytes [ InetAddress ] to be converted to an address string
* @param buf the StringBuilder to build the address into
* /
fun toString ( bytes : ByteArray , buf : StringBuilder ) {
toString ( bytes , 0 , buf )
}
/ * *
* Returns the [ String ] representation of an [ InetAddress ] .
*
* * Inet4Address results are identical to [ InetAddress . getHostAddress ]
* * Inet6Address results adhere to
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4) if
* `ipv4Mapped` is false . If `ipv4Mapped` is true then " IPv4 mapped " format
* from [ rfc 4291 section 2 ] ( http : //tools.ietf.org/html/rfc4291#section-2.5.5) will be supported.
* The compressed result will always obey the compression rules defined in
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4)
*
* The output does not include Scope ID .
*
* @param ints [ InetAddress ] to be converted to an address string
* @param buf the StringBuilder to build the address into
* /
fun toString ( ints : IntArray , buf : StringBuilder ) {
toString ( ints , 0 , buf )
}
/ * *
* Returns the [ String ] representation of an [ InetAddress ] .
*
* * Inet4Address results are identical to [ InetAddress . getHostAddress ]
* * Inet6Address results adhere to
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4) if
* `ipv4Mapped` is false . If `ipv4Mapped` is true then " IPv4 mapped " format
* from [ rfc 4291 section 2 ] ( http : //tools.ietf.org/html/rfc4291#section-2.5.5) will be supported.
* The compressed result will always obey the compression rules defined in
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4)
*
2020-08-08 20:23:27 +02:00
*
* The output does not include Scope ID .
*
* @param bytes [ InetAddress ] to be converted to an address string
*
* @return `String` containing the text - formatted IP address
* /
fun toString ( bytes : ByteArray , offset : Int ) : String {
2021-04-08 19:59:25 +02:00
val buf = StringBuilder ( IPV6 _MAX _CHAR _COUNT )
toAddressString ( bytes , offset , false , buf )
return buf . toString ( )
2020-08-08 20:23:27 +02:00
}
/ * *
* Returns the [ String ] representation of an [ InetAddress ] .
*
* * Inet4Address results are identical to [ InetAddress . getHostAddress ]
* * Inet6Address results adhere to
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4) if
* `ipv4Mapped` is false . If `ipv4Mapped` is true then " IPv4 mapped " format
* from [ rfc 4291 section 2 ] ( http : //tools.ietf.org/html/rfc4291#section-2.5.5) will be supported.
* The compressed result will always obey the compression rules defined in
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4)
*
*
2021-04-08 19:59:25 +02:00
* The output does not include Scope ID .
*
* @param ints [ InetAddress ] to be converted to an address string
*
* @return `String` containing the text - formatted IP address
* /
fun toString ( ints : IntArray , offset : Int ) : String {
val buf = StringBuilder ( IPV6 _MAX _CHAR _COUNT )
toAddressString ( ints , offset , false , buf )
return buf . toString ( )
}
/ * *
* Returns the [ String ] representation of an [ InetAddress ] .
*
* * Inet4Address results are identical to [ InetAddress . getHostAddress ]
* * Inet6Address results adhere to
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4) if
* `ipv4Mapped` is false . If `ipv4Mapped` is true then " IPv4 mapped " format
* from [ rfc 4291 section 2 ] ( http : //tools.ietf.org/html/rfc4291#section-2.5.5) will be supported.
* The compressed result will always obey the compression rules defined in
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4)
*
2020-08-08 20:23:27 +02:00
*
* The output does not include Scope ID .
*
2021-04-08 19:59:25 +02:00
* @param bytes [ InetAddress ] to be converted to an address string
* @param offset the offset into the array to start
* @param buf the StringBuilder to build the address into
* /
fun toString ( bytes : ByteArray , offset : Int , buf : StringBuilder ) {
toAddressString ( bytes , offset , false , buf )
}
/ * *
* Returns the [ String ] representation of an [ InetAddress ] .
2020-08-08 20:23:27 +02:00
*
2021-04-08 19:59:25 +02:00
* * Inet4Address results are identical to [ InetAddress . getHostAddress ]
* * Inet6Address results adhere to
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4) if
* `ipv4Mapped` is false . If `ipv4Mapped` is true then " IPv4 mapped " format
* from [ rfc 4291 section 2 ] ( http : //tools.ietf.org/html/rfc4291#section-2.5.5) will be supported.
* The compressed result will always obey the compression rules defined in
2020-08-08 20:23:27 +02:00
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4)
*
2021-04-08 19:59:25 +02:00
*
* The output does not include Scope ID .
*
* @param ints [ InetAddress ] to be converted to an address string
* @param offset the offset into the array to start
* @param buf the StringBuilder to build the address into
* /
fun toString ( ints : IntArray , offset : Int , buf : StringBuilder ) {
toAddressString ( ints , offset , false , buf )
}
/ * *
* Returns the [ String ] representation of an [ InetAddress ] .
*
* * Inet4Address results are identical to [ InetAddress . getHostAddress ]
* * Inet6Address results adhere to
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4) if
* `ipv4Mapped` is false . If `ipv4Mapped` is true then " IPv4 mapped " format
* from [ rfc 4291 section 2 ] ( http : //tools.ietf.org/html/rfc4291#section-2.5.5) will be supported.
* The compressed result will always obey the compression rules defined in
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4)
*
*
* The output does not include Scope ID .
*
* @param ip [ InetAddress ] to be converted to an address string
* @param ipv4Mapped * `true` to stray from strict rfc 5952 and support the " IPv4 mapped " format
* defined in [ rfc 4291 section 2 ] ( http : //tools.ietf.org/html/rfc4291#section-2.5.5) while still
* following the updated guidelines in [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4)
* `false` to strictly follow rfc 5952
2020-08-08 20:23:27 +02:00
*
* @return `String` containing the text - formatted IP address
* /
2020-09-08 23:54:16 +02:00
fun toString ( ip : Inet6Address , ipv4Mapped : Boolean = false ) : String {
2021-04-08 19:59:25 +02:00
val buf = StringBuilder ( IPV6 _MAX _CHAR _COUNT )
toAddressString ( ip . address , 0 , ipv4Mapped , buf )
return buf . toString ( )
}
/ * *
* Returns the [ String ] representation of an [ InetAddress ] .
*
* * Inet4Address results are identical to [ InetAddress . getHostAddress ]
* * Inet6Address results adhere to
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4) if
* `ipv4Mapped` is false . If `ipv4Mapped` is true then " IPv4 mapped " format
* from [ rfc 4291 section 2 ] ( http : //tools.ietf.org/html/rfc4291#section-2.5.5) will be supported.
* The compressed result will always obey the compression rules defined in
* [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4)
*
*
* The output does not include Scope ID .
*
* @param ip [ InetAddress ] to be converted to an address string
* @param ipv4Mapped `true` to stray from strict rfc 5952 and support the " IPv4 mapped " format
* defined in [ rfc 4291 section 2 ] ( http : //tools.ietf.org/html/rfc4291#section-2.5.5) while still
* following the updated guidelines in [ rfc 5952 section 4 ] ( http : //tools.ietf.org/html/rfc5952#section-4)
* `false` to strictly follow rfc 5952
*
* @param buf the StringBuilder to build the address into
* /
fun toString ( ip : Inet6Address , ipv4Mapped : Boolean = false , buf : StringBuilder ) {
toAddressString ( ip . address , 0 , ipv4Mapped , buf )
2020-08-08 20:23:27 +02:00
}
2021-04-08 19:59:25 +02:00
private fun toAddressString ( bytes : ByteArray , offset : Int , ipv4Mapped : Boolean , buf : StringBuilder ) {
2020-08-08 20:23:27 +02:00
val words = IntArray ( IPV6 _WORD _COUNT )
var i : Int
val end = offset + words . size
i = offset
while ( i < end ) {
words [ i ] = bytes [ i shl 1 ] . toInt ( ) and 0xff shl 8 or ( bytes [ ( i shl 1 ) + 1 ] . toInt ( ) and 0xff )
++ i
}
// Find longest run of 0s, tie goes to first found instance
var currentStart = - 1
var currentLength : Int
var shortestStart = - 1
var shortestLength = 0
i = 0
while ( i < words . size ) {
if ( words [ i ] == 0 ) {
if ( currentStart < 0 ) {
currentStart = i
}
}
else if ( currentStart >= 0 ) {
currentLength = i - currentStart
if ( currentLength > shortestLength ) {
shortestStart = currentStart
shortestLength = currentLength
}
currentStart = - 1
}
++ i
}
// If the array ends on a streak of zeros, make sure we account for it
if ( currentStart >= 0 ) {
currentLength = i - currentStart
if ( currentLength > shortestLength ) {
shortestStart = currentStart
shortestLength = currentLength
}
}
// Ignore the longest streak if it is only 1 long
if ( shortestLength == 1 ) {
shortestLength = 0
shortestStart = - 1
}
// Translate to string taking into account longest consecutive 0s
val shortestEnd = shortestStart + shortestLength
if ( shortestEnd < 0 ) { // Optimization when there is no compressing needed
2021-04-08 19:59:25 +02:00
buf . append ( Integer . toHexString ( words [ 0 ] ) )
2020-08-08 20:23:27 +02:00
i = 1
while ( i < words . size ) {
2021-04-08 19:59:25 +02:00
buf . append ( ':' )
buf . append ( Integer . toHexString ( words [ i ] ) )
2020-08-08 20:23:27 +02:00
++ i
}
}
else { // General case that can handle compressing (and not compressing)
// Loop unroll the first index (so we don't constantly check i==0 cases in loop)
val isIpv4Mapped : Boolean
isIpv4Mapped = if ( inRangeEndExclusive ( 0 , shortestStart , shortestEnd ) ) {
2021-04-08 19:59:25 +02:00
buf . append ( " :: " )
2020-08-08 20:23:27 +02:00
ipv4Mapped && shortestEnd == 5 && words [ 5 ] == 0xffff
}
else {
2021-04-08 19:59:25 +02:00
buf . append ( Integer . toHexString ( words [ 0 ] ) )
2020-08-08 20:23:27 +02:00
false
}
i = 1
while ( i < words . size ) {
if ( !in RangeEndExclusive ( i , shortestStart , shortestEnd ) ) {
if ( !in RangeEndExclusive ( i - 1 , shortestStart , shortestEnd ) ) {
// If the last index was not part of the shortened sequence
if ( !is Ipv4Mapped || i == 6 ) {
2021-04-08 19:59:25 +02:00
buf . append ( ':' )
2020-08-08 20:23:27 +02:00
}
else {
2021-04-08 19:59:25 +02:00
buf . append ( '.' )
2020-08-08 20:23:27 +02:00
}
}
if ( isIpv4Mapped && i > 5 ) {
2021-04-08 19:59:25 +02:00
buf . append ( words [ i ] shr 8 )
buf . append ( '.' )
buf . append ( words [ i ] and 0xff )
2020-08-08 20:23:27 +02:00
}
else {
2021-04-08 19:59:25 +02:00
buf . append ( Integer . toHexString ( words [ i ] ) )
2020-08-08 20:23:27 +02:00
}
}
else if ( !in RangeEndExclusive ( i - 1 , shortestStart , shortestEnd ) ) {
// If we are in the shortened sequence and the last index was not
2021-04-08 19:59:25 +02:00
buf . append ( " :: " )
}
++ i
}
}
}
private fun toAddressString ( ints : IntArray , offset : Int , ipv4Mapped : Boolean , buf : StringBuilder ) {
val words = IntArray ( IPV6 _WORD _COUNT )
var i : Int
val end = offset + words . size
i = offset
while ( i < end ) {
words [ i ] = ints [ i shl 1 ] shl 8 or ( ints [ ( i shl 1 ) + 1 ] )
++ i
}
// Find longest run of 0s, tie goes to first found instance
var currentStart = - 1
var currentLength : Int
var shortestStart = - 1
var shortestLength = 0
i = 0
while ( i < words . size ) {
if ( words [ i ] == 0 ) {
if ( currentStart < 0 ) {
currentStart = i
}
}
else if ( currentStart >= 0 ) {
currentLength = i - currentStart
if ( currentLength > shortestLength ) {
shortestStart = currentStart
shortestLength = currentLength
}
currentStart = - 1
}
++ i
}
// If the array ends on a streak of zeros, make sure we account for it
if ( currentStart >= 0 ) {
currentLength = i - currentStart
if ( currentLength > shortestLength ) {
shortestStart = currentStart
shortestLength = currentLength
}
}
// Ignore the longest streak if it is only 1 long
if ( shortestLength == 1 ) {
shortestLength = 0
shortestStart = - 1
}
// Translate to string taking into account longest consecutive 0s
val shortestEnd = shortestStart + shortestLength
if ( shortestEnd < 0 ) { // Optimization when there is no compressing needed
buf . append ( Integer . toHexString ( words [ 0 ] ) )
i = 1
while ( i < words . size ) {
buf . append ( ':' )
buf . append ( Integer . toHexString ( words [ i ] ) )
++ i
}
}
else { // General case that can handle compressing (and not compressing)
// Loop unroll the first index (so we don't constantly check i==0 cases in loop)
val isIpv4Mapped : Boolean
isIpv4Mapped = if ( inRangeEndExclusive ( 0 , shortestStart , shortestEnd ) ) {
buf . append ( " :: " )
ipv4Mapped && shortestEnd == 5 && words [ 5 ] == 0xffff
}
else {
buf . append ( Integer . toHexString ( words [ 0 ] ) )
false
}
i = 1
while ( i < words . size ) {
if ( !in RangeEndExclusive ( i , shortestStart , shortestEnd ) ) {
if ( !in RangeEndExclusive ( i - 1 , shortestStart , shortestEnd ) ) {
// If the last index was not part of the shortened sequence
if ( !is Ipv4Mapped || i == 6 ) {
buf . append ( ':' )
}
else {
buf . append ( '.' )
}
}
if ( isIpv4Mapped && i > 5 ) {
buf . append ( words [ i ] shr 8 )
buf . append ( '.' )
buf . append ( words [ i ] and 0xff )
}
else {
buf . append ( Integer . toHexString ( words [ i ] ) )
}
}
else if ( !in RangeEndExclusive ( i - 1 , shortestStart , shortestEnd ) ) {
// If we are in the shortened sequence and the last index was not
buf . append ( " :: " )
2020-08-08 20:23:27 +02:00
}
++ i
}
}
}
/ * *
* Does a range check on `value` if is within `start` ( inclusive ) and `end` ( exclusive ) .
* @param value The value to checked if is within `start` ( inclusive ) and `end` ( exclusive )
* @param start The start of the range ( inclusive )
* @param end The end of the range ( exclusive )
* @return
*
* * `true` if `value` if is within `start` ( inclusive ) and `end` ( exclusive )
* * `false` otherwise
*
* /
private fun inRangeEndExclusive ( value : Int , start : Int , end : Int ) : Boolean {
return value >= start && value < end
}
2020-08-18 01:51:57 +02:00
/ * *
* IPv6 address reserved for loopback use is 0000 : 0000 : 0000 : 0000 : 0000 : 0000 : 0000 : 0001 / 128.
*
* This loopback address is so lengthy and can be further simplified as :: 1 / 128 , or just :: 1
*
* / 128 means EXACTLY this address
* /
fun isLoopback ( ipAsString : String ) : Boolean {
var oneCount = 0
// can only be with one "1", and nothing else
ipAsString . forEach { char ->
when ( char ) {
':' -> {
// this is always ok
}
'1' -> {
oneCount ++
if ( oneCount > 1 ) {
return false
}
}
else -> {
// not OK
return false
}
}
}
return oneCount == 1
}
2021-04-08 19:59:25 +02:00
/ * *
* Truncates an address to the specified number of bits . For example ,
* truncating the address 10.1 . 2.3 to 8 bits would yield 10.0 . 0.0 .
*
* @param address The source address
* @param maskLength The number of bits to truncate the address to .
* /
fun truncate ( address : Inet6Address , maskLength : Int ) : InetAddress ? {
val maxMaskLength = length * 8
require ( ! ( maskLength < 0 || maskLength > maxMaskLength ) ) { " invalid mask length " }
if ( maskLength == maxMaskLength ) {
return address
}
val bytes = address . address
for ( i in maskLength / 8 + 1 until bytes . size ) {
bytes [ i ] = 0
}
val maskBits = maskLength % 8
var bitmask = 0
for ( i in 0 until maskBits ) {
bitmask = bitmask or ( 1 shl 7 - i )
}
bytes [ maskLength / 8 ] = ( bytes [ maskLength / 8 ] . toInt ( ) and bitmask ) . toByte ( )
return try {
InetAddress . getByAddress ( bytes )
} catch ( e : UnknownHostException ) {
throw IllegalArgumentException ( " invalid address " )
}
}
2021-04-26 23:22:24 +02:00
/ * *
* @param cidrPrefix the CIDR notation , ie : / 24 , / 16 , etc that we want to convert into a netmask
*
* @return the netmask , or if there were errors , the default / 0 netmask
* /
fun cidrPrefixToSubnetMask ( cidrPrefix : Int ) : BigInteger {
return MINUS_ONE . shiftLeft ( 128 - cidrPrefix )
}
2020-08-08 20:23:27 +02:00
}