From 3a28cc2219d7817d567e975e4cc6ab5f81be4d8b Mon Sep 17 00:00:00 2001 From: Robinson Date: Sun, 20 Aug 2023 21:17:34 +0200 Subject: [PATCH] removed hex utils to it's own project --- build.gradle.kts | 6 - src/dorkbox/bytes/HexExtensions.kt | 305 ----------------------------- test/dorkbox/bytes/TestHex.kt | 158 --------------- 3 files changed, 469 deletions(-) delete mode 100644 src/dorkbox/bytes/HexExtensions.kt delete mode 100644 test/dorkbox/bytes/TestHex.kt diff --git a/build.gradle.kts b/build.gradle.kts index 6654ef7..9bd760c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") diff --git a/src/dorkbox/bytes/HexExtensions.kt b/src/dorkbox/bytes/HexExtensions.kt deleted file mode 100644 index 955c1d8..0000000 --- a/src/dorkbox/bytes/HexExtensions.kt +++ /dev/null @@ -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.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.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) - } -} diff --git a/test/dorkbox/bytes/TestHex.kt b/test/dorkbox/bytes/TestHex.kt deleted file mode 100644 index 7febc40..0000000 --- a/test/dorkbox/bytes/TestHex.kt +++ /dev/null @@ -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()) - } -}