removed hex utils to it's own project
parent
3db390bef9
commit
3a28cc2219
|
@ -66,12 +66,6 @@ licensing {
|
|||
url("https://github.com/EsotericSoftware/kryo")
|
||||
}
|
||||
|
||||
extra("Kotlin Hex", License.MIT) {
|
||||
copyright(2017)
|
||||
author("ligi")
|
||||
url("https://github.com/komputing/KHex")
|
||||
}
|
||||
|
||||
extra("Base58", License.APACHE_2) {
|
||||
copyright(2018)
|
||||
author("Google Inc")
|
||||
|
|
|
@ -1,305 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2017 ligi
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package dorkbox.bytes
|
||||
|
||||
@JvmInline
|
||||
value class HexString(val string: String)
|
||||
|
||||
object Hex {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
const val version = BytesInfo.version
|
||||
|
||||
/**
|
||||
* Represents all the chars used for nibble
|
||||
*/
|
||||
private val HEX_CHARS = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
|
||||
private val UPPER_HEX_CHARS = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
|
||||
|
||||
|
||||
internal val HEX_REGEX = Regex("(0[xX])?[0-9a-fA-F]*")
|
||||
|
||||
/**
|
||||
* Encodes the given byte value as a hexadecimal character.
|
||||
*/
|
||||
fun encode(value: Byte): String {
|
||||
val hexString = CharArray(2)
|
||||
val toInt = value.toInt()
|
||||
hexString[0] = HEX_CHARS[toInt and 0xF0 shr 4]
|
||||
hexString[1] = HEX_CHARS[toInt and 0x0F]
|
||||
|
||||
return String(hexString)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given byte value as a hexadecimal character.
|
||||
*/
|
||||
fun encodeUpper(value: Byte): String {
|
||||
val hexString = CharArray(2)
|
||||
val toInt = value.toInt()
|
||||
hexString[0] = UPPER_HEX_CHARS[toInt and 0xF0 shr 4]
|
||||
hexString[1] = UPPER_HEX_CHARS[toInt and 0x0F]
|
||||
|
||||
return String(hexString)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given byte array value to its hexadecimal representations, and prepends the given prefix to it.
|
||||
*
|
||||
* Note that by default the 0x prefix is prepended to the result of the conversion.
|
||||
* If you want to have the representation without the 0x prefix, pass to this method an empty prefix.
|
||||
*/
|
||||
fun encode(bytes: ByteArray, prefix: String = "0x", start: Int = 0, length: Int = bytes.size, toUpperCase: Boolean = false): String {
|
||||
require(start >= 0) { "Start ($start) must be >= 0" }
|
||||
require(length >= 0) { "Limit ($length) must be >= 0" }
|
||||
require(bytes.isEmpty() || start < bytes.size) { "Start ($start) position must be smaller than the size of the byte array" }
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val length = if (length < bytes.size) length else bytes.size
|
||||
|
||||
val hexString = CharArray(prefix.length + ((2 * (length - start) )))
|
||||
if (prefix.isNotEmpty()) {
|
||||
prefix.toCharArray().copyInto(hexString)
|
||||
}
|
||||
|
||||
var j = prefix.length
|
||||
|
||||
if (toUpperCase) {
|
||||
for (i in start until length) {
|
||||
val toInt = bytes[i].toInt()
|
||||
hexString[j++] = UPPER_HEX_CHARS[toInt and 0xF0 shr 4]
|
||||
hexString[j++] = UPPER_HEX_CHARS[toInt and 0x0F]
|
||||
}
|
||||
} else {
|
||||
for (i in start until length) {
|
||||
val toInt = bytes[i].toInt()
|
||||
hexString[j++] = HEX_CHARS[toInt and 0xF0 shr 4]
|
||||
hexString[j++] = HEX_CHARS[toInt and 0x0F]
|
||||
}
|
||||
}
|
||||
|
||||
return String(hexString)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given ch into its integer representation considering it as a hexadecimal character.
|
||||
*/
|
||||
private fun hexToBin(ch: Char): Int = when (ch) {
|
||||
in '0'..'9' -> ch - '0'
|
||||
in 'A'..'F' -> ch - 'A' + 10
|
||||
in 'a'..'f' -> ch - 'a' + 10
|
||||
else -> throw(IllegalArgumentException("'$ch' is not a valid hex character"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given value reading it as an hexadecimal string, and returns its byte array representation.
|
||||
*
|
||||
* Note that either 0x-prefixed string and no-prefixed hex strings are supported.
|
||||
*
|
||||
* @throws IllegalArgumentException if the value is not a hexadecimal string.
|
||||
*/
|
||||
fun decode(value: String): ByteArray {
|
||||
// A hex string must always have length multiple of 2
|
||||
if (value.length % 2 != 0) {
|
||||
throw IllegalArgumentException("hex-string must have an even number of digits (nibbles)")
|
||||
}
|
||||
|
||||
// Remove the 0x or 0X prefix if it is set
|
||||
val cleanInput = if (value.startsWith("0x") || value.startsWith("0X")) value.substring(2) else value
|
||||
|
||||
return ByteArray(cleanInput.length / 2).apply {
|
||||
var i = 0
|
||||
while (i < cleanInput.length) {
|
||||
this[i / 2] = ((hexToBin(cleanInput[i]) shl 4) + hexToBin(cleanInput[i + 1])).toByte()
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts [this] [ByteArray] into its hexadecimal string representation prepending to it the given [prefix].
|
||||
*
|
||||
* Note that by default the 0x prefix is prepended to the result of the conversion.
|
||||
* If you want to have the representation without the 0x prefix, use the [toNoPrefixHexString] method or
|
||||
* pass to this method an empty [prefix].
|
||||
*/
|
||||
fun ByteArray.toHexString(prefix: String = "0x", start: Int = 0, length: Int = this.size, toUpperCase: Boolean = false): String = Hex.encode(this, prefix, start, length, toUpperCase)
|
||||
|
||||
/**
|
||||
* Converts [this] [ByteArray] into its hexadecimal representation without prepending any prefix to it.
|
||||
*/
|
||||
fun ByteArray.toNoPrefixHexString(start: Int = 0, length: Int = this.size, toUpperCase: Boolean = false): String = Hex.encode(this, "", start, length, toUpperCase)
|
||||
|
||||
|
||||
/**
|
||||
* Converts [this] [Collection] of bytes into its hexadecimal string representation prepending to it the given [prefix].
|
||||
*
|
||||
* Note that by default the 0x prefix is prepended to the result of the conversion.
|
||||
* If you want to have the representation without the 0x prefix, use the [toNoPrefixHexString] method or
|
||||
* pass to this method an empty [prefix].
|
||||
*/
|
||||
fun Collection<Byte>.toHexString(prefix: String = "0x", length: Int = this.size): String = Hex.encode(this.toByteArray(), prefix, length)
|
||||
|
||||
/**
|
||||
* Converts [this] [Collection] of bytes into its hexadecimal representation without prepending any prefix to it.
|
||||
*/
|
||||
fun Collection<Byte>.toNoPrefixHexString(length: Int = this.size): String = Hex.encode(this.toByteArray(), "", length)
|
||||
|
||||
|
||||
/**
|
||||
* Parses [this] [String] as an hexadecimal value and returns its [ByteArray] representation.
|
||||
*
|
||||
* Note that either 0x-prefixed string and no-prefixed hex strings are supported.
|
||||
*
|
||||
* @throws IllegalArgumentException if [this] is not a hexadecimal string.
|
||||
*/
|
||||
fun HexString.hexToByteArray(): ByteArray = Hex.decode(string)
|
||||
fun String.hexToByteArray(): ByteArray = Hex.decode(this)
|
||||
|
||||
/**
|
||||
* Returns `true` if and only if [this] value starts with the `0x` prefix.
|
||||
*/
|
||||
fun HexString.has0xPrefix(): Boolean = string.startsWith("0x")
|
||||
fun String.has0xPrefix(): Boolean = this.startsWith("0x")
|
||||
|
||||
/**
|
||||
* Returns a new [String] obtained by prepends the `0x` prefix to [this] value,
|
||||
* only if it does not already have it.
|
||||
*
|
||||
* Examples:
|
||||
* ```kotlin
|
||||
* val myString = HexString("123")
|
||||
* assertEquals("0x123", myString.prepend0xPrefix().string)
|
||||
* assertEquals("0x0x123", myString.prepend0xPrefix().prepend0xPrefix().string)
|
||||
* ```
|
||||
*/
|
||||
fun HexString.prepend0xPrefix(): HexString = if (has0xPrefix()) this else HexString("0x$string")
|
||||
fun String.prepend0xPrefix(): String = if (has0xPrefix()) this else "0x$this"
|
||||
|
||||
/**
|
||||
* Returns a new [String] obtained by removing the first occurrence of the `0x` prefix from [this] string, if it has it.
|
||||
*
|
||||
* Examples:
|
||||
* ```kotlin
|
||||
* assertEquals("123", HexString("123").clean0xPrefix().string)
|
||||
* assertEquals("123", HexString("0x123").clean0xPrefix().string)
|
||||
* assertEquals("0x123", HexString("0x0x123").clean0xPrefix().string)
|
||||
* ```
|
||||
*/
|
||||
fun HexString.clean0xPrefix(): HexString = if (has0xPrefix()) HexString(string.substring(2)) else this
|
||||
fun String.clean0xPrefix(): String = if (has0xPrefix()) this.substring(2) else this
|
||||
|
||||
/**
|
||||
* Returns if a given string is a valid hex-string - either with or without 0x prefix
|
||||
*/
|
||||
fun HexString.isValidHex(): Boolean = Hex.HEX_REGEX.matches(string)
|
||||
fun String.isValidHex(): Boolean = Hex.HEX_REGEX.matches(this)
|
||||
|
||||
|
||||
/**
|
||||
* Returns a HexString if a given string is a valid hex-string - either with or without 0x prefix
|
||||
*/
|
||||
fun String.asHex(): HexString {
|
||||
if (!this.isValidHex()) {
|
||||
throw IllegalArgumentException("String is not hex")
|
||||
}
|
||||
|
||||
return HexString(this)
|
||||
}
|
||||
|
||||
|
||||
fun Byte.toHexString(prefix: String = "0x", toUpperCase: Boolean = false): String {
|
||||
return if (toUpperCase) {
|
||||
prefix + Hex.encodeUpper(this)
|
||||
} else {
|
||||
prefix + Hex.encode(this)
|
||||
}
|
||||
}
|
||||
fun UByte.toHexString(prefix: String = "0x", toUpperCase: Boolean = false): String {
|
||||
return if (toUpperCase) {
|
||||
prefix + this.toString(16).uppercase()
|
||||
} else {
|
||||
prefix + this.toString(16)
|
||||
}
|
||||
}
|
||||
fun Short.toHexString(prefix: String = "0x", toUpperCase: Boolean = false): String {
|
||||
return if (toUpperCase) {
|
||||
prefix + this.toString(16).uppercase()
|
||||
} else {
|
||||
prefix + this.toString(16)
|
||||
}
|
||||
}
|
||||
fun UShort.toHexString(prefix: String = "0x", toUpperCase: Boolean = false): String {
|
||||
return if (toUpperCase) {
|
||||
prefix + this.toString(16).uppercase()
|
||||
} else {
|
||||
prefix + this.toString(16)
|
||||
}
|
||||
}
|
||||
fun Int.toHexString(prefix: String = "0x", toUpperCase: Boolean = false): String {
|
||||
return if (toUpperCase) {
|
||||
prefix + this.toString(16).uppercase()
|
||||
} else {
|
||||
prefix + this.toString(16)
|
||||
}
|
||||
}
|
||||
fun UInt.toHexString(prefix: String = "0x", toUpperCase: Boolean = false): String {
|
||||
return if (toUpperCase) {
|
||||
prefix + this.toString(16).uppercase()
|
||||
} else {
|
||||
prefix + this.toString(16)
|
||||
}
|
||||
}
|
||||
fun Long.toHexString(prefix: String = "0x", toUpperCase: Boolean = false): String {
|
||||
return if (toUpperCase) {
|
||||
prefix + this.toString(16).uppercase()
|
||||
} else {
|
||||
prefix + this.toString(16)
|
||||
}
|
||||
}
|
||||
fun ULong.toHexString(prefix: String = "0x", toUpperCase: Boolean = false): String {
|
||||
return if (toUpperCase) {
|
||||
prefix + this.toString(16).uppercase()
|
||||
} else {
|
||||
prefix + this.toString(16)
|
||||
}
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* 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.bytes
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
class TestHex {
|
||||
private val hexRegex = Regex("0[xX][0-9a-fA-F]+")
|
||||
|
||||
@Test
|
||||
fun weCanProduceSingleDigitHex() {
|
||||
assertEquals("00", Hex.encode(0.toByte()))
|
||||
assertEquals("01", Hex.encode(1.toByte()))
|
||||
assertEquals("0f", Hex.encode(15.toByte()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun weCanProduceDoubleDigitHex() {
|
||||
assertEquals("10", Hex.encode(16.toByte()))
|
||||
assertEquals("2a", Hex.encode(42.toByte()))
|
||||
assertEquals("ff", Hex.encode(255.toByte()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun prefixIsIgnored() {
|
||||
assertTrue(Hex.decode("0xab").contentEquals(Hex.decode("ab")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPrimatives() {
|
||||
assertEquals("0x0", 0.toHexString())
|
||||
assertEquals("0x1", 1.toHexString())
|
||||
assertEquals("0xa", 10.toHexString())
|
||||
assertEquals("0xf", 15.toHexString())
|
||||
assertEquals("0x10", 16.toHexString())
|
||||
assertEquals("0x11", 17.toHexString())
|
||||
assertEquals("0xff", 255.toHexString())
|
||||
assertEquals("0x100", 256.toHexString())
|
||||
assertEquals("0x4e9", 1257.toHexString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sizesAreOk() {
|
||||
assertEquals(0, Hex.decode("0x").size)
|
||||
assertEquals(1, Hex.decode("ff").size)
|
||||
assertEquals(2, Hex.decode("ffaa").size)
|
||||
assertEquals(3, Hex.decode("ffaabb").size)
|
||||
assertEquals(4, Hex.decode("ffaabb44").size)
|
||||
assertEquals(5, Hex.decode("0xffaabb4455").size)
|
||||
assertEquals(6, Hex.decode("0xffaabb445566").size)
|
||||
assertEquals(7, Hex.decode("ffaabb44556677").size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun byteArrayLimitWorks() {
|
||||
assertEquals("0x", Hex.encode(Hex.decode("00"), length = 0))
|
||||
assertEquals("0x00", Hex.encode(Hex.decode("00"), length = 1))
|
||||
assertEquals("0x", Hex.encode(Hex.decode("ff"), length = 0))
|
||||
assertEquals("0xff", Hex.encode(Hex.decode("ff"), length = 1))
|
||||
assertEquals("0x", Hex.encode(Hex.decode("abcdef"), length = 0))
|
||||
assertEquals("0xab", Hex.encode(Hex.decode("abcdef"), length = 1))
|
||||
assertEquals("0xabcd", Hex.encode(Hex.decode("abcdef"), length = 2))
|
||||
assertEquals("0xabcdef", Hex.encode(Hex.decode("abcdef"), length = 3))
|
||||
assertEquals("0xabcdef", Hex.encode(Hex.decode("abcdef"), length = 32))
|
||||
assertEquals("0xaa12456789bb", Hex.encode(Hex.decode("0xaa12456789bb"), length = 6))
|
||||
assertEquals("0xaa12456789bb", Hex.encode(Hex.decode("0xaa12456789bb"), length = 9))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun byteArrayStartWorks() {
|
||||
assertEquals("0x", Hex.encode(Hex.decode("abcdef"), start = 1, length = 1))
|
||||
assertEquals("0xcd", Hex.encode(Hex.decode("abcdef"), start = 1, length = 2))
|
||||
assertEquals("0xcdef", Hex.encode(Hex.decode("abcdef"), start = 1, length = 3))
|
||||
assertEquals("0xcdef", Hex.encode(Hex.decode("abcdef"), start = 1, length = 32))
|
||||
assertEquals("0x6789bb", Hex.encode(Hex.decode("0xaa12456789bb"), start = 3, length = 6))
|
||||
assertEquals("0x456789bb", Hex.encode(Hex.decode("0xaa12456789bb"), start = 2, length = 9))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun exceptionOnOddInput() {
|
||||
var exception: Exception? = null
|
||||
try {
|
||||
Hex.decode("0xa")
|
||||
} catch (e: Exception) {
|
||||
exception = e
|
||||
}
|
||||
assertTrue("Exception must be IllegalArgumentException", exception is IllegalArgumentException)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRoundTrip() {
|
||||
assertEquals("0x00", Hex.encode(Hex.decode("00")))
|
||||
assertEquals("0xff", Hex.encode(Hex.decode("ff")))
|
||||
assertEquals("0xabcdef", Hex.encode(Hex.decode("abcdef")))
|
||||
assertEquals("0xaa12456789bb", Hex.encode(Hex.decode("0xaa12456789bb")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun regexMatchesForHEX() {
|
||||
assertTrue(hexRegex.matches("0x00"))
|
||||
assertTrue(hexRegex.matches("0xabcdef123456"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun regexFailsForNonHEX() {
|
||||
assertFalse(hexRegex.matches("q"))
|
||||
assertFalse(hexRegex.matches(""))
|
||||
assertFalse(hexRegex.matches("0x+"))
|
||||
assertFalse(hexRegex.matches("0xgg"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun detectsInvalidHex() {
|
||||
var exception: Exception? = null
|
||||
try {
|
||||
Hex.decode("0xxx")
|
||||
} catch (e: Exception) {
|
||||
exception = e
|
||||
}
|
||||
|
||||
assertTrue("Exception must be IllegalArgumentException", exception is IllegalArgumentException)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHexString() {
|
||||
val myString = HexString("123")
|
||||
assertEquals("0x123", myString.prepend0xPrefix().string)
|
||||
assertEquals("0x123", myString.prepend0xPrefix().prepend0xPrefix().string)
|
||||
|
||||
assertEquals("123", HexString("123").clean0xPrefix().string)
|
||||
assertEquals("123", HexString("0x123").clean0xPrefix().string)
|
||||
assertEquals("0x123", HexString("0x0x123").clean0xPrefix().string)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStringAsHex() {
|
||||
assertEquals("0x123", "123".prepend0xPrefix())
|
||||
assertEquals("0x123", "123".prepend0xPrefix().prepend0xPrefix())
|
||||
|
||||
assertEquals("123", "123".clean0xPrefix())
|
||||
assertEquals("123", "0x123".clean0xPrefix())
|
||||
assertEquals("0x123", "0x0x123".clean0xPrefix())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue