From 45d74d9cff9a3498c70b86afadd63aa0dd7cf589 Mon Sep 17 00:00:00 2001 From: Infendro Date: Tue, 13 Jan 2026 18:02:26 +0100 Subject: [PATCH 01/13] improve ci --- .gitea/workflows/ci.yaml | 92 ++++++++++++++++++++++++++++++++++++++ .gitea/workflows/main.yaml | 36 --------------- build.gradle.kts | 2 +- 3 files changed, 93 insertions(+), 37 deletions(-) create mode 100644 .gitea/workflows/ci.yaml delete mode 100644 .gitea/workflows/main.yaml diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..2f46684 --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,92 @@ +on: + push: + branches: [ develop, main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: linux + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + java-version: '24' + distribution: 'temurin' + + - name: Cache Gradle + uses: actions/cache@v4 + with: + key: gradle-${{ hashFiles('**/libs.versions.toml') }} + restore-keys: gradle- + path: | + ~/.gradle/caches/ + ~/.gradle/wrapper/ + + - name: Cache Konan + uses: actions/cache@v4 + with: + key: konan-${{ hashFiles('**/libs.versions.toml') }} + restore-keys: konan- + path: | + ~/.konan/ + + - name: Cache Node + uses: actions/cache@v4 + with: + key: node-${{ hashFiles('**/libs.versions.toml') }} + restore-keys: node- + path: | + ~/.gradle/nodejs + ~/.gradle/yarn + + - name: Test + run: ./gradlew allTests + + publish: + if: ${{ github.event_name == 'push' && github.ref_name == 'main' }} + needs: test + runs-on: linux + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + java-version: '24' + distribution: 'temurin' + + - name: Cache Gradle + uses: actions/cache@v4 + with: + key: gradle-${{ hashFiles('**/libs.versions.toml') }} + restore-keys: gradle- + path: | + ~/.gradle/caches/ + ~/.gradle/wrapper/ + + - name: Cache Konan + uses: actions/cache@v4 + with: + key: konan-${{ hashFiles('**/libs.versions.toml') }} + restore-keys: konan- + path: | + ~/.konan/ + + - name: Cache Node + uses: actions/cache@v4 + with: + key: node-${{ hashFiles('**/libs.versions.toml') }} + restore-keys: node- + path: | + ~/.gradle/nodejs + ~/.gradle/yarn + + - name: Publish + run: ./gradlew publish + env: + INFENDRO__TOKEN: ${{ secrets.TOKEN }} diff --git a/.gitea/workflows/main.yaml b/.gitea/workflows/main.yaml deleted file mode 100644 index 4885477..0000000 --- a/.gitea/workflows/main.yaml +++ /dev/null @@ -1,36 +0,0 @@ -on: - push: - branches: - - main - -env: - INFENDRO__TOKEN: ${{ secrets.TOKEN }} - -jobs: - publish: - runs-on: linux - steps: - - uses: actions/checkout@v4 - - - uses: actions/cache/restore@v4 - with: - key: gradle - path: | - ~/.gradle/caches/ - ~/.gradle/wrapper/ - ~/.konan/ - - - uses: actions/setup-java@v4 - with: - java-version: '24' - distribution: 'temurin' - - - run: ./gradlew publish - - - uses: actions/cache/save@v4 - with: - key: gradle - path: | - ~/.gradle/caches/ - ~/.gradle/wrapper/ - ~/.konan/ diff --git a/build.gradle.kts b/build.gradle.kts index 8e65a48..5d0b4fc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,10 +12,10 @@ plugins { kotlin { jvm() + linuxX64() js { nodejs() } - linuxX64() repositories { mavenCentral() From 55faf1d670045e1fee1198dc3505073bc79cbb27 Mon Sep 17 00:00:00 2001 From: Infendro Date: Sat, 17 Jan 2026 12:27:41 +0100 Subject: [PATCH 02/13] implement numeric encodings, optimize --- .../kotlin/com/infendro/encoding/Base16.kt | 16 ---- .../kotlin/com/infendro/encoding/Base2.kt | 15 --- .../kotlin/com/infendro/encoding/Base32.kt | 19 ---- .../kotlin/com/infendro/encoding/Base64.kt | 23 ----- .../kotlin/com/infendro/encoding/Base8.kt | 15 --- .../kotlin/com/infendro/encoding/Encoding.kt | 95 ++---------------- .../com/infendro/encoding/numeric/Base36.kt | 5 + .../com/infendro/encoding/numeric/Base62.kt | 5 + .../encoding/numeric/NumericEncoding.kt | 40 ++++++++ .../com/infendro/encoding/stream/Base16.kt | 5 + .../com/infendro/encoding/stream/Base2.kt | 5 + .../com/infendro/encoding/stream/Base32.kt | 6 ++ .../com/infendro/encoding/stream/Base64.kt | 6 ++ .../com/infendro/encoding/stream/Base8.kt | 5 + .../encoding/stream/StreamEncoding.kt | 96 +++++++++++++++++++ .../com/infendro/encoding/util/BigInt.kt | 89 +++++++++++++++++ .../kotlin/com/infendro/encoding/util/Math.kt | 21 ++-- .../kotlin/com/infendro/encoding/Base16.kt | 24 ++--- .../kotlin/com/infendro/encoding/Base2.kt | 42 ++++---- .../kotlin/com/infendro/encoding/Base32.kt | 24 ++--- .../kotlin/com/infendro/encoding/Base64.kt | 22 +++-- .../kotlin/com/infendro/encoding/Base8.kt | 26 ++--- 22 files changed, 355 insertions(+), 249 deletions(-) delete mode 100644 src/commonMain/kotlin/com/infendro/encoding/Base16.kt delete mode 100644 src/commonMain/kotlin/com/infendro/encoding/Base2.kt delete mode 100644 src/commonMain/kotlin/com/infendro/encoding/Base32.kt delete mode 100644 src/commonMain/kotlin/com/infendro/encoding/Base64.kt delete mode 100644 src/commonMain/kotlin/com/infendro/encoding/Base8.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/numeric/Base36.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/numeric/Base62.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/numeric/NumericEncoding.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/stream/Base16.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/stream/Base2.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/stream/Base32.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/stream/Base64.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/stream/Base8.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/stream/StreamEncoding.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/util/BigInt.kt diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base16.kt b/src/commonMain/kotlin/com/infendro/encoding/Base16.kt deleted file mode 100644 index 4c23230..0000000 --- a/src/commonMain/kotlin/com/infendro/encoding/Base16.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.infendro.encoding - -object Base16 : Encoding() { - private val CHARACTERS = ubyteArrayOf( - 0x30U, 0x31U, 0x32U, 0x33U, 0x34U, 0x35U, 0x36U, 0x37U, - 0x38U, 0x39U, 0x61U, 0x62U, 0x63U, 0x64U, 0x65U, 0x66U, - ) - - override fun encode( - bytes: UByteArray, - ) = encode(bytes, CHARACTERS) - - override fun decode( - bytes: UByteArray, - ) = decode(bytes, CHARACTERS) -} diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base2.kt b/src/commonMain/kotlin/com/infendro/encoding/Base2.kt deleted file mode 100644 index a6de71b..0000000 --- a/src/commonMain/kotlin/com/infendro/encoding/Base2.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.infendro.encoding - -object Base2 : Encoding() { - private val CHARACTERS = ubyteArrayOf( - 0x30U, 0x31U, - ) - - override fun encode( - bytes: UByteArray, - ) = encode(bytes, CHARACTERS) - - override fun decode( - bytes: UByteArray, - ) = decode(bytes, CHARACTERS) -} diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base32.kt b/src/commonMain/kotlin/com/infendro/encoding/Base32.kt deleted file mode 100644 index 007471f..0000000 --- a/src/commonMain/kotlin/com/infendro/encoding/Base32.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.infendro.encoding - -object Base32 : Encoding() { - private val CHARACTERS = ubyteArrayOf( - 0x41U, 0x42U, 0x43U, 0x44U, 0x45U, 0x46U, 0x47U, 0x48U, - 0x49U, 0x4aU, 0x4bU, 0x4cU, 0x4dU, 0x4eU, 0x4fU, 0x50U, - 0x51U, 0x52U, 0x53U, 0x54U, 0x55U, 0x56U, 0x57U, 0x58U, - 0x59U, 0x5aU, 0x32U, 0x33U, 0x34U, 0x35U, 0x36U, 0x37U, - ) - private const val PADDING: UByte = 0x3dU - - override fun encode( - bytes: UByteArray, - ) = encode(bytes, CHARACTERS, PADDING) - - override fun decode( - bytes: UByteArray, - ) = decode(bytes, CHARACTERS, PADDING) -} diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base64.kt b/src/commonMain/kotlin/com/infendro/encoding/Base64.kt deleted file mode 100644 index bcf1c6c..0000000 --- a/src/commonMain/kotlin/com/infendro/encoding/Base64.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.infendro.encoding - -object Base64 : Encoding() { - private val CHARACTERS = ubyteArrayOf( - 0x41U, 0x42U, 0x43U, 0x44U, 0x45U, 0x46U, 0x47U, 0x48U, - 0x49U, 0x4aU, 0x4bU, 0x4cU, 0x4dU, 0x4eU, 0x4fU, 0x50U, - 0x51U, 0x52U, 0x53U, 0x54U, 0x55U, 0x56U, 0x57U, 0x58U, - 0x59U, 0x5aU, 0x61U, 0x62U, 0x63U, 0x64U, 0x65U, 0x66U, - 0x67U, 0x68U, 0x69U, 0x6aU, 0x6bU, 0x6cU, 0x6dU, 0x6eU, - 0x6fU, 0x70U, 0x71U, 0x72U, 0x73U, 0x74U, 0x75U, 0x76U, - 0x77U, 0x78U, 0x79U, 0x7aU, 0x30U, 0x31U, 0x32U, 0x33U, - 0x34U, 0x35U, 0x36U, 0x37U, 0x38U, 0x39U, 0x2bU, 0x2fU, - ) - private const val PADDING: UByte = 0x3dU - - override fun encode( - bytes: UByteArray, - ) = encode(bytes, CHARACTERS, PADDING) - - override fun decode( - bytes: UByteArray, - ) = decode(bytes, CHARACTERS, PADDING) -} diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base8.kt b/src/commonMain/kotlin/com/infendro/encoding/Base8.kt deleted file mode 100644 index ac9dccc..0000000 --- a/src/commonMain/kotlin/com/infendro/encoding/Base8.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.infendro.encoding - -object Base8 : Encoding() { - private val CHARACTERS = ubyteArrayOf( - 0x30U, 0x31U, 0x32U, 0x33U, 0x34U, 0x35U, 0x36U, 0x37U, - ) - - override fun encode( - bytes: UByteArray, - ) = encode(bytes, CHARACTERS) - - override fun decode( - bytes: UByteArray, - ) = decode(bytes, CHARACTERS) -} diff --git a/src/commonMain/kotlin/com/infendro/encoding/Encoding.kt b/src/commonMain/kotlin/com/infendro/encoding/Encoding.kt index 211a271..3d1b456 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/Encoding.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Encoding.kt @@ -1,92 +1,15 @@ package com.infendro.encoding -import com.infendro.encoding.util.lcm -import com.infendro.encoding.util.log2 -import kotlin.math.pow - -sealed class Encoding { - abstract fun encode( - bytes: UByteArray, - ): UByteArray - - abstract fun decode( - bytes: UByteArray, - ): UByteArray - - protected fun encode( - bytes: UByteArray, - characters: UByteArray, - padding: UByte? = null, - ): UByteArray { - val length = log2(characters.size).toInt() - val mask = (2.0.pow(length) - 1).toUInt() - - val encoded = mutableListOf() - - var buffer = 0U - var bits = 0 - - for (byte in bytes) { - buffer = (buffer shl 8) + byte - bits += 8 - - while (bits >= length) { - val offset = bits - length - val index = (buffer shr offset).toInt() - buffer = buffer and (mask shl offset).inv() - bits -= length - - encoded.add(characters[index]) - } - } - if (bits > 0) { - buffer = buffer shl (length - bits) - val index = buffer.toInt() - encoded.add(characters[index]) - } - - if (padding != null) { - val block = lcm(length, 8) / length - while (encoded.size % block != 0) { - encoded.add(padding) - } - } - - return encoded.toUByteArray() +abstract class Encoding( + val alphabet: ByteArray, +) { + init { + require(alphabet.isNotEmpty()) } - protected fun decode( - bytes: UByteArray, - characters: UByteArray, - padding: UByte? = null, - ): UByteArray { - val length = log2(characters.size).toInt() - val mask = 0xffU + val base: Int + get() = alphabet.size - val decoded = mutableListOf() - - var buffer = 0U - var bits = 0 - - for (byte in bytes) { - if (byte == padding) break - - val index = characters.indexOf(byte) - if (index == -1) throw Exception() - - buffer = (buffer shl length) + index.toUByte() - bits += length - - if (bits >= 8) { - val offset = bits - 8 - val character = (buffer shr offset).toUByte() - buffer = buffer and (mask shl offset).inv() - bits -= 8 - - decoded.add(character) - } - } - - return decoded.toUByteArray() - } + abstract fun encode(bytes: ByteArray): ByteArray + abstract fun decode(bytes: ByteArray): ByteArray } diff --git a/src/commonMain/kotlin/com/infendro/encoding/numeric/Base36.kt b/src/commonMain/kotlin/com/infendro/encoding/numeric/Base36.kt new file mode 100644 index 0000000..dd1a268 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/numeric/Base36.kt @@ -0,0 +1,5 @@ +package com.infendro.encoding.numeric + +private val alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".encodeToByteArray() + +object Base36 : NumericEncoding(alphabet) diff --git a/src/commonMain/kotlin/com/infendro/encoding/numeric/Base62.kt b/src/commonMain/kotlin/com/infendro/encoding/numeric/Base62.kt new file mode 100644 index 0000000..d8e0dac --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/numeric/Base62.kt @@ -0,0 +1,5 @@ +package com.infendro.encoding.numeric + +private val alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".encodeToByteArray() + +object Base62 : NumericEncoding(alphabet) diff --git a/src/commonMain/kotlin/com/infendro/encoding/numeric/NumericEncoding.kt b/src/commonMain/kotlin/com/infendro/encoding/numeric/NumericEncoding.kt new file mode 100644 index 0000000..16b36d4 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/numeric/NumericEncoding.kt @@ -0,0 +1,40 @@ +package com.infendro.encoding.numeric + +import com.infendro.encoding.Encoding +import com.infendro.encoding.util.BigInt +import com.infendro.encoding.util.log2 +import kotlin.math.ceil + +open class NumericEncoding( + alphabet: ByteArray, +) : Encoding(alphabet) { + override fun encode(bytes: ByteArray): ByteArray { + if (bytes.isEmpty()) return byteArrayOf() + + val size = ceil(bytes.size * 8 / log2(base)).toInt() + val result = ByteArray(size) + + var x = BigInt.from(bytes) + var i = result.size + while (!x.isZero()) { + val (quotient, remainder) = x.divRem(base) + x = quotient + result[--i] = alphabet[remainder] + } + + return result.copyOfRange(i, result.size) + } + + override fun decode(bytes: ByteArray): ByteArray { + if (bytes.isEmpty()) return byteArrayOf() + + var value = BigInt.zero + for (i in 0.. size + else -> { + val block = lcm(bits, 8) / bits + size.align(block) + } + } + val result = ByteArray(encodedSize) + + var r = 0 + var buffer = 0 + var bufferBits = 0 + + for (i in bytes.indices) { + buffer = (buffer shl 8) + bytes[i] + bufferBits += 8 + + while (bufferBits >= bits) { + val offset = bufferBits - bits + val index = (buffer ushr offset) + buffer = buffer and ((base - 1) shl offset).inv() + bufferBits -= bits + + result[r++] = alphabet[index] + } + } + if (bufferBits > 0) { + buffer = buffer shl (bits - bufferBits) + val index = buffer + result[r++] = alphabet[index] + } + + if (padding != null) { + while (r < result.size) { + result[r++] = padding + } + } + + return result + } + + override fun decode(bytes: ByteArray): ByteArray { + val bits = log2(base).toInt() + val index = padding?.let { bytes.indexOf(it) } ?: -1 + val decodedSize = when (index) { + -1 -> bytes.size * bits / 8 + else -> index * bits / 8 + } + val result = ByteArray(decodedSize) + + var r = 0 + var buffer = 0 + var bufferBits = 0 + + for (i in bytes.indices) { + val byte = bytes[i] + if (byte == padding) break + + val index = alphabet.indexOf(byte) + if (index == -1) throw Exception() //TODO + + buffer = (buffer shl bits) + index.toByte() + bufferBits += bits + + if (bufferBits >= 8) { + val offset = bufferBits - 8 + val character = (buffer ushr offset).toByte() + buffer = buffer and (0xFF shl offset).inv() + bufferBits -= 8 + + result[r++] = character + } + } + + return result + } +} diff --git a/src/commonMain/kotlin/com/infendro/encoding/util/BigInt.kt b/src/commonMain/kotlin/com/infendro/encoding/util/BigInt.kt new file mode 100644 index 0000000..d78fd09 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/util/BigInt.kt @@ -0,0 +1,89 @@ +package com.infendro.encoding.util + +internal class BigInt private constructor( + val words: IntArray, +) { + init { + require(words.isNotEmpty()) + } + + fun isZero(): Boolean = words.all { it == 0 } + + fun timesAdd(multiplier: Int, addend: Int): BigInt { + val product = IntArray(words.size) + var carry = addend + + for (i in words.indices) { + val current = (words[i].toLong() and 0xFFFFFFFFL) * multiplier + carry + product[i] = current.toInt() + carry = (current ushr 32).toInt() + } + + when { + carry > 0 -> { + val expanded = product.copyOf(product.size + 1) + expanded[product.size] = carry + return BigInt(expanded) + } + else -> return BigInt(product) + } + } + + fun divRem(divisor: Int): Pair { + val quotient = IntArray(words.size) + var remainder = 0 + + for (i in words.indices.reversed()) { + val current = (remainder.toLong() shl 32) or (words[i].toLong() and 0xFFFFFFFFL) + quotient[i] = (current / divisor.toLong()).toInt() + remainder = (current % divisor.toLong()).toInt() + } + + when { + quotient.size > 1 && quotient.last() == 0 -> { + val contracted = quotient.copyOfRange(0, quotient.size - 1) + return Pair(BigInt(contracted), remainder) + } + else -> return Pair(BigInt(quotient), remainder) + } + } + + fun toByteArray(): ByteArray { + val msw = words.last() + val mswBytes = when { + (msw ushr 24) != 0 -> 4 + (msw ushr 16) != 0 -> 3 + (msw ushr 8) != 0 -> 2 + else -> 1 + } + val size = ((words.size - 1) * 4) + mswBytes + val result = ByteArray(size) + + for (i in 0.. Date: Sat, 17 Jan 2026 12:36:16 +0100 Subject: [PATCH 03/13] bump version to 1.3.0 --- README.md | 21 +++++---------------- build.gradle.kts | 19 +++++++++++-------- gradle/libs.versions.toml | 2 +- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 18d1c5f..cde6624 100644 --- a/README.md +++ b/README.md @@ -7,17 +7,10 @@ Supported encodings: - Base8 (octal) - Base16 (hexadecimal) - Base32 +- Base36 +- Base62 - Base64 -## Targets - -| Target | Status | -|------------------|---------------| -| JVM | Supported | -| JavaScript | Supported | -| Native (Linux) | Supported | -| Native (Windows) | Not Supported | - ## Installation Add the following to your `build.gradle.kts`. @@ -28,23 +21,19 @@ repositories { } dependencies { - implementation("com.infendro:encoding:1.2.0") + implementation("com.infendro:encoding:1.3.0") } ``` ## Usage ```kotlin -import com.infendro.encoding.Base16 - fun main() { // the string to encode - val test = "Hello World!" - .encodeToByteArray().asUByteArray() + val test = "Hello World!".encodeToByteArray() // encode the string with the desired encoding - val encoded = Base16.encode(test) - .asByteArray().decodeToString() + val encoded = Base16.encode(test).decodeToString() println(encoded) // 48656c6c6f20576f726c6421 } diff --git a/build.gradle.kts b/build.gradle.kts index 5d0b4fc..cbb9453 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,11 @@ +@file:OptIn(ExperimentalWasmDsl::class) + import com.infendro.plugin.infendro import com.infendro.plugin.token +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl group = "com.infendro" -version = "1.2.0" +version = "1.3.0" plugins { alias(libs.plugins.infendro) @@ -13,9 +16,13 @@ plugins { kotlin { jvm() linuxX64() - js { - nodejs() - } + linuxArm64() + mingwX64() + macosX64() + macosArm64() + js { nodejs() } + wasmJs { nodejs() } + wasmWasi { nodejs() } repositories { mavenCentral() @@ -26,10 +33,6 @@ kotlin { implementation(libs.test) } } - - compilerOptions { - freeCompilerArgs.add("-opt-in=kotlin.ExperimentalUnsignedTypes") - } } publishing.repositories { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 10d43ce..9194c62 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] infendro = "1.0.0" -kotlin = "2.2.21" +kotlin = "2.3.0" [plugins] infendro = { id = "com.infendro.plugin", version.ref = "infendro" } From 83fd77c8ee0eabdface47585d197e249d3695cbb Mon Sep 17 00:00:00 2001 From: Infendro Date: Sat, 17 Jan 2026 12:39:30 +0100 Subject: [PATCH 04/13] split ci --- .gitea/workflows/ci.yaml | 92 ----------------------------------- .gitea/workflows/publish.yaml | 47 ++++++++++++++++++ .gitea/workflows/test.yaml | 28 +++++++++++ 3 files changed, 75 insertions(+), 92 deletions(-) delete mode 100644 .gitea/workflows/ci.yaml create mode 100644 .gitea/workflows/publish.yaml create mode 100644 .gitea/workflows/test.yaml diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml deleted file mode 100644 index 2f46684..0000000 --- a/.gitea/workflows/ci.yaml +++ /dev/null @@ -1,92 +0,0 @@ -on: - push: - branches: [ develop, main ] - pull_request: - branches: [ main ] - -jobs: - test: - runs-on: linux - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Java - uses: actions/setup-java@v4 - with: - java-version: '24' - distribution: 'temurin' - - - name: Cache Gradle - uses: actions/cache@v4 - with: - key: gradle-${{ hashFiles('**/libs.versions.toml') }} - restore-keys: gradle- - path: | - ~/.gradle/caches/ - ~/.gradle/wrapper/ - - - name: Cache Konan - uses: actions/cache@v4 - with: - key: konan-${{ hashFiles('**/libs.versions.toml') }} - restore-keys: konan- - path: | - ~/.konan/ - - - name: Cache Node - uses: actions/cache@v4 - with: - key: node-${{ hashFiles('**/libs.versions.toml') }} - restore-keys: node- - path: | - ~/.gradle/nodejs - ~/.gradle/yarn - - - name: Test - run: ./gradlew allTests - - publish: - if: ${{ github.event_name == 'push' && github.ref_name == 'main' }} - needs: test - runs-on: linux - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Java - uses: actions/setup-java@v4 - with: - java-version: '24' - distribution: 'temurin' - - - name: Cache Gradle - uses: actions/cache@v4 - with: - key: gradle-${{ hashFiles('**/libs.versions.toml') }} - restore-keys: gradle- - path: | - ~/.gradle/caches/ - ~/.gradle/wrapper/ - - - name: Cache Konan - uses: actions/cache@v4 - with: - key: konan-${{ hashFiles('**/libs.versions.toml') }} - restore-keys: konan- - path: | - ~/.konan/ - - - name: Cache Node - uses: actions/cache@v4 - with: - key: node-${{ hashFiles('**/libs.versions.toml') }} - restore-keys: node- - path: | - ~/.gradle/nodejs - ~/.gradle/yarn - - - name: Publish - run: ./gradlew publish - env: - INFENDRO__TOKEN: ${{ secrets.TOKEN }} diff --git a/.gitea/workflows/publish.yaml b/.gitea/workflows/publish.yaml new file mode 100644 index 0000000..dad1306 --- /dev/null +++ b/.gitea/workflows/publish.yaml @@ -0,0 +1,47 @@ +on: + push: + branches: [ main ] + +jobs: + publish: + runs-on: linux + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + java-version: '24' + distribution: 'temurin' + + - name: Cache Gradle + uses: actions/cache@v4 + with: + key: gradle-${{ hashFiles('**/libs.versions.toml') }} + restore-keys: gradle + path: | + ~/.gradle/caches/ + ~/.gradle/wrapper/ + + - name: Cache Konan + uses: actions/cache@v4 + with: + key: konan-${{ hashFiles('**/libs.versions.toml') }} + restore-keys: konan + path: | + ~/.konan/ + + - name: Cache Node + uses: actions/cache@v4 + with: + key: node-${{ hashFiles('**/libs.versions.toml') }} + restore-keys: node + path: | + ~/.gradle/nodejs + ~/.gradle/yarn + + - name: Publish + run: ./gradlew publish + env: + INFENDRO__TOKEN: ${{ secrets.TOKEN }} diff --git a/.gitea/workflows/test.yaml b/.gitea/workflows/test.yaml new file mode 100644 index 0000000..bce9e35 --- /dev/null +++ b/.gitea/workflows/test.yaml @@ -0,0 +1,28 @@ +on: + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: linux + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + java-version: '24' + distribution: 'temurin' + + - name: Cache Gradle + uses: actions/cache@v4 + with: + key: gradle-${{ hashFiles('**/libs.versions.toml') }} + restore-keys: gradle + path: | + ~/.gradle/caches/ + ~/.gradle/wrapper/ + + - name: Test + run: ./gradlew jvmTest From a553aa6723adc789aa96b01f2613cad530303e21 Mon Sep 17 00:00:00 2001 From: Infendro Date: Sat, 17 Jan 2026 13:46:57 +0100 Subject: [PATCH 05/13] improve test coverage --- .../kotlin/com/infendro/encoding/Base16.kt | 35 --------------- .../kotlin/com/infendro/encoding/Base2.kt | 44 ------------------- .../kotlin/com/infendro/encoding/Base32.kt | 35 --------------- .../kotlin/com/infendro/encoding/Base64.kt | 34 -------------- .../kotlin/com/infendro/encoding/Base8.kt | 36 --------------- .../com/infendro/encoding/numeric/Base36.kt | 37 ++++++++++++++++ .../com/infendro/encoding/numeric/Base62.kt | 37 ++++++++++++++++ .../com/infendro/encoding/stream/Base16.kt | 37 ++++++++++++++++ .../com/infendro/encoding/stream/Base2.kt | 37 ++++++++++++++++ .../com/infendro/encoding/stream/Base32.kt | 37 ++++++++++++++++ .../com/infendro/encoding/stream/Base64.kt | 37 ++++++++++++++++ .../com/infendro/encoding/stream/Base8.kt | 37 ++++++++++++++++ 12 files changed, 259 insertions(+), 184 deletions(-) delete mode 100644 src/commonTest/kotlin/com/infendro/encoding/Base16.kt delete mode 100644 src/commonTest/kotlin/com/infendro/encoding/Base2.kt delete mode 100644 src/commonTest/kotlin/com/infendro/encoding/Base32.kt delete mode 100644 src/commonTest/kotlin/com/infendro/encoding/Base64.kt delete mode 100644 src/commonTest/kotlin/com/infendro/encoding/Base8.kt create mode 100644 src/commonTest/kotlin/com/infendro/encoding/numeric/Base36.kt create mode 100644 src/commonTest/kotlin/com/infendro/encoding/numeric/Base62.kt create mode 100644 src/commonTest/kotlin/com/infendro/encoding/stream/Base16.kt create mode 100644 src/commonTest/kotlin/com/infendro/encoding/stream/Base2.kt create mode 100644 src/commonTest/kotlin/com/infendro/encoding/stream/Base32.kt create mode 100644 src/commonTest/kotlin/com/infendro/encoding/stream/Base64.kt create mode 100644 src/commonTest/kotlin/com/infendro/encoding/stream/Base8.kt diff --git a/src/commonTest/kotlin/com/infendro/encoding/Base16.kt b/src/commonTest/kotlin/com/infendro/encoding/Base16.kt deleted file mode 100644 index 3600e6c..0000000 --- a/src/commonTest/kotlin/com/infendro/encoding/Base16.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.infendro.encoding - -import com.infendro.encoding.stream.Base16 -import com.infendro.encoding.stream.StreamEncoding -import kotlin.test.Test -import kotlin.test.assertContentEquals - -class `Base16 Test` { - val encoding: StreamEncoding - get() = Base16 - - val `Hello World!` = byteArrayOf( - 0x48, 0x65, 0x6c, 0x6c, - 0x6f, 0x20, 0x57, 0x6f, - 0x72, 0x6c, 0x64, 0x21, - ) - - @Test - fun `encode empty ByteArray`() { - val expected = byteArrayOf() - val actual = encoding.encode(byteArrayOf()) - assertContentEquals(expected, actual) - } - - @Test - fun `encode "Hello World!"`() { - val expected = byteArrayOf( - 0x34, 0x38, 0x36, 0x35, 0x36, 0x63, 0x36, 0x63, - 0x36, 0x66, 0x32, 0x30, 0x35, 0x37, 0x36, 0x66, - 0x37, 0x32, 0x36, 0x63, 0x36, 0x34, 0x32, 0x31, - ) - val actual = encoding.encode(`Hello World!`) - assertContentEquals(expected, actual) - } -} diff --git a/src/commonTest/kotlin/com/infendro/encoding/Base2.kt b/src/commonTest/kotlin/com/infendro/encoding/Base2.kt deleted file mode 100644 index 66b7800..0000000 --- a/src/commonTest/kotlin/com/infendro/encoding/Base2.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.infendro.encoding - -import com.infendro.encoding.stream.Base2 -import com.infendro.encoding.stream.StreamEncoding -import kotlin.test.Test -import kotlin.test.assertContentEquals - -class `Base2 Test` { - val encoding: StreamEncoding - get() = Base2 - - val `Hello World!` = byteArrayOf( - 0x48, 0x65, 0x6c, 0x6c, - 0x6f, 0x20, 0x57, 0x6f, - 0x72, 0x6c, 0x64, 0x21, - ) - - @Test - fun `encode empty ByteArray`() { - val expected = byteArrayOf() - val actual = encoding.encode(byteArrayOf()) - assertContentEquals(expected, actual) - } - - @Test - fun `encode "Hello World!"`() { - val expected = byteArrayOf( - 0x30, 0x31, 0x30, 0x30, 0x31, 0x30, 0x30, 0x30, - 0x30, 0x31, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, - 0x30, 0x31, 0x31, 0x30, 0x31, 0x31, 0x30, 0x30, - 0x30, 0x31, 0x31, 0x30, 0x31, 0x31, 0x30, 0x30, - 0x30, 0x31, 0x31, 0x30, 0x31, 0x31, 0x31, 0x31, - 0x30, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, - 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x31, 0x31, - 0x30, 0x31, 0x31, 0x30, 0x31, 0x31, 0x31, 0x31, - 0x30, 0x31, 0x31, 0x31, 0x30, 0x30, 0x31, 0x30, - 0x30, 0x31, 0x31, 0x30, 0x31, 0x31, 0x30, 0x30, - 0x30, 0x31, 0x31, 0x30, 0x30, 0x31, 0x30, 0x30, - 0x30, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x31, - ) - val actual = encoding.encode(`Hello World!`) - assertContentEquals(expected, actual) - } -} diff --git a/src/commonTest/kotlin/com/infendro/encoding/Base32.kt b/src/commonTest/kotlin/com/infendro/encoding/Base32.kt deleted file mode 100644 index c37032a..0000000 --- a/src/commonTest/kotlin/com/infendro/encoding/Base32.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.infendro.encoding - -import com.infendro.encoding.stream.Base32 -import com.infendro.encoding.stream.StreamEncoding -import kotlin.test.Test -import kotlin.test.assertContentEquals - -class `Base32 Test` { - val encoding: StreamEncoding - get() = Base32 - - val `Hello World!` = byteArrayOf( - 0x48, 0x65, 0x6c, 0x6c, - 0x6f, 0x20, 0x57, 0x6f, - 0x72, 0x6c, 0x64, 0x21, - ) - - @Test - fun `encode empty ByteArray`() { - val expected = byteArrayOf() - val actual = encoding.encode(byteArrayOf()) - assertContentEquals(expected, actual) - } - - @Test - fun `encode "Hello World!"`() { - val expected = byteArrayOf( - 0x4a, 0x42, 0x53, 0x57, 0x59, 0x33, 0x44, 0x50, - 0x45, 0x42, 0x4c, 0x57, 0x36, 0x34, 0x54, 0x4d, - 0x4d, 0x51, 0x51, 0x51, 0x3d, 0x3d, 0x3d, 0x3d, - ) - val actual = encoding.encode(`Hello World!`) - assertContentEquals(expected, actual) - } -} diff --git a/src/commonTest/kotlin/com/infendro/encoding/Base64.kt b/src/commonTest/kotlin/com/infendro/encoding/Base64.kt deleted file mode 100644 index 653093e..0000000 --- a/src/commonTest/kotlin/com/infendro/encoding/Base64.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.infendro.encoding - -import com.infendro.encoding.stream.Base64 -import com.infendro.encoding.stream.StreamEncoding -import kotlin.test.Test -import kotlin.test.assertContentEquals - -class `Base64 Test` { - val encoding: StreamEncoding - get() = Base64 - - val `Hello World!` = byteArrayOf( - 0x48, 0x65, 0x6c, 0x6c, - 0x6f, 0x20, 0x57, 0x6f, - 0x72, 0x6c, 0x64, 0x21, - ) - - @Test - fun `encode empty ByteArray`() { - val expected = byteArrayOf() - val actual = encoding.encode(byteArrayOf()) - assertContentEquals(expected, actual) - } - - @Test - fun `encode "Hello World!"`() { - val expected = byteArrayOf( - 0x53, 0x47, 0x56, 0x73, 0x62, 0x47, 0x38, 0x67, - 0x56, 0x32, 0x39, 0x79, 0x62, 0x47, 0x51, 0x68, - ) - val actual = encoding.encode(`Hello World!`) - assertContentEquals(expected, actual) - } -} diff --git a/src/commonTest/kotlin/com/infendro/encoding/Base8.kt b/src/commonTest/kotlin/com/infendro/encoding/Base8.kt deleted file mode 100644 index 3cbe37a..0000000 --- a/src/commonTest/kotlin/com/infendro/encoding/Base8.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.infendro.encoding - -import com.infendro.encoding.stream.Base8 -import com.infendro.encoding.stream.StreamEncoding -import kotlin.test.Test -import kotlin.test.assertContentEquals - -class `Base8 Test` { - val encoding: StreamEncoding - get() = Base8 - - val `Hello World!` = byteArrayOf( - 0x48, 0x65, 0x6c, 0x6c, - 0x6f, 0x20, 0x57, 0x6f, - 0x72, 0x6c, 0x64, 0x21, - ) - - @Test - fun `encode empty ByteArray`() { - val expected = byteArrayOf() - val actual = encoding.encode(byteArrayOf()) - assertContentEquals(expected, actual) - } - - @Test - fun `encode "Hello World!"`() { - val expected = byteArrayOf( - 0x32, 0x32, 0x30, 0x36, 0x32, 0x35, 0x35, 0x34, - 0x33, 0x33, 0x30, 0x36, 0x37, 0x34, 0x34, 0x30, - 0x32, 0x35, 0x36, 0x36, 0x37, 0x35, 0x36, 0x32, - 0x33, 0x33, 0x30, 0x36, 0x32, 0x30, 0x34, 0x31, - ) - val actual = encoding.encode(`Hello World!`) - assertContentEquals(expected, actual) - } -} diff --git a/src/commonTest/kotlin/com/infendro/encoding/numeric/Base36.kt b/src/commonTest/kotlin/com/infendro/encoding/numeric/Base36.kt new file mode 100644 index 0000000..cd55650 --- /dev/null +++ b/src/commonTest/kotlin/com/infendro/encoding/numeric/Base36.kt @@ -0,0 +1,37 @@ +package com.infendro.encoding.numeric + +import com.infendro.encoding.Encoding +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class `Base36 Test` { + val encoding: Encoding + get() = Base36 + + val original = "Hello World!".encodeToByteArray() + val encoded = "2678LX5GVMSV1DRO9B5".encodeToByteArray() + + @Test + fun `encode empty`() { + val actual = encoding.encode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun `decode empty`() { + val actual = encoding.decode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun encode() { + val actual = encoding.encode(original) + assertContentEquals(encoded, actual) + } + + @Test + fun decode() { + val actual = encoding.decode(encoded) + assertContentEquals(original, actual) + } +} diff --git a/src/commonTest/kotlin/com/infendro/encoding/numeric/Base62.kt b/src/commonTest/kotlin/com/infendro/encoding/numeric/Base62.kt new file mode 100644 index 0000000..b519ec5 --- /dev/null +++ b/src/commonTest/kotlin/com/infendro/encoding/numeric/Base62.kt @@ -0,0 +1,37 @@ +package com.infendro.encoding.numeric + +import com.infendro.encoding.Encoding +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class `Base62 Test` { + val encoding: Encoding + get() = Base62 + + val original = "Hello World!".encodeToByteArray() + val encoded = "T8dgcjRGkZ3aysdN".encodeToByteArray() + + @Test + fun `encode empty`() { + val actual = encoding.encode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun `decode empty`() { + val actual = encoding.decode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun encode() { + val actual = encoding.encode(original) + assertContentEquals(encoded, actual) + } + + @Test + fun decode() { + val actual = encoding.decode(encoded) + assertContentEquals(original, actual) + } +} diff --git a/src/commonTest/kotlin/com/infendro/encoding/stream/Base16.kt b/src/commonTest/kotlin/com/infendro/encoding/stream/Base16.kt new file mode 100644 index 0000000..1803488 --- /dev/null +++ b/src/commonTest/kotlin/com/infendro/encoding/stream/Base16.kt @@ -0,0 +1,37 @@ +package com.infendro.encoding.stream + +import com.infendro.encoding.Encoding +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class `Base16 Test` { + val encoding: Encoding + get() = Base16 + + val original = "Hello World!".encodeToByteArray() + val encoded = "48656C6C6F20576F726C6421".encodeToByteArray() + + @Test + fun `encode empty`() { + val actual = encoding.encode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun `decode empty`() { + val actual = encoding.decode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun encode() { + val actual = encoding.encode(original) + assertContentEquals(encoded, actual) + } + + @Test + fun decode() { + val actual = encoding.decode(encoded) + assertContentEquals(original, actual) + } +} diff --git a/src/commonTest/kotlin/com/infendro/encoding/stream/Base2.kt b/src/commonTest/kotlin/com/infendro/encoding/stream/Base2.kt new file mode 100644 index 0000000..e91105a --- /dev/null +++ b/src/commonTest/kotlin/com/infendro/encoding/stream/Base2.kt @@ -0,0 +1,37 @@ +package com.infendro.encoding.stream + +import com.infendro.encoding.Encoding +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class `Base2 Test` { + val encoding: Encoding + get() = Base2 + + val original = "Hello World!".encodeToByteArray() + val encoded = "010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001".encodeToByteArray() + + @Test + fun `encode empty`() { + val actual = encoding.encode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun `decode empty`() { + val actual = encoding.decode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun encode() { + val actual = encoding.encode(original) + assertContentEquals(encoded, actual) + } + + @Test + fun decode() { + val actual = encoding.decode(encoded) + assertContentEquals(original, actual) + } +} diff --git a/src/commonTest/kotlin/com/infendro/encoding/stream/Base32.kt b/src/commonTest/kotlin/com/infendro/encoding/stream/Base32.kt new file mode 100644 index 0000000..76a63a1 --- /dev/null +++ b/src/commonTest/kotlin/com/infendro/encoding/stream/Base32.kt @@ -0,0 +1,37 @@ +package com.infendro.encoding.stream + +import com.infendro.encoding.Encoding +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class `Base32 Test` { + val encoding: Encoding + get() = Base32 + + val original = "Hello World!".encodeToByteArray() + val encoded = "JBSWY3DPEBLW64TMMQQQ====".encodeToByteArray() + + @Test + fun `encode empty`() { + val actual = encoding.encode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun `decode empty`() { + val actual = encoding.decode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun encode() { + val actual = encoding.encode(original) + assertContentEquals(encoded, actual) + } + + @Test + fun decode() { + val actual = encoding.decode(encoded) + assertContentEquals(original, actual) + } +} diff --git a/src/commonTest/kotlin/com/infendro/encoding/stream/Base64.kt b/src/commonTest/kotlin/com/infendro/encoding/stream/Base64.kt new file mode 100644 index 0000000..87dd90c --- /dev/null +++ b/src/commonTest/kotlin/com/infendro/encoding/stream/Base64.kt @@ -0,0 +1,37 @@ +package com.infendro.encoding.stream + +import com.infendro.encoding.Encoding +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class `Base64 Test` { + val encoding: Encoding + get() = Base64 + + val original = "Hello World!".encodeToByteArray() + val encoded = "SGVsbG8gV29ybGQh".encodeToByteArray() + + @Test + fun `encode empty`() { + val actual = encoding.encode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun `decode empty`() { + val actual = encoding.decode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun encode() { + val actual = encoding.encode(original) + assertContentEquals(encoded, actual) + } + + @Test + fun decode() { + val actual = encoding.decode(encoded) + assertContentEquals(original, actual) + } +} diff --git a/src/commonTest/kotlin/com/infendro/encoding/stream/Base8.kt b/src/commonTest/kotlin/com/infendro/encoding/stream/Base8.kt new file mode 100644 index 0000000..b7b3a08 --- /dev/null +++ b/src/commonTest/kotlin/com/infendro/encoding/stream/Base8.kt @@ -0,0 +1,37 @@ +package com.infendro.encoding.stream + +import com.infendro.encoding.Encoding +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class `Base8 Test` { + val encoding: Encoding + get() = Base8 + + val original = "Hello World!".encodeToByteArray() + val encoded = "22062554330674402566756233062041".encodeToByteArray() + + @Test + fun `encode empty`() { + val actual = encoding.encode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun `decode empty`() { + val actual = encoding.decode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun encode() { + val actual = encoding.encode(original) + assertContentEquals(encoded, actual) + } + + @Test + fun decode() { + val actual = encoding.decode(encoded) + assertContentEquals(original, actual) + } +} From a347bd617ca4fd49ed4c5eb2caf01d9124fb9e7a Mon Sep 17 00:00:00 2001 From: Infendro Date: Sat, 17 Jan 2026 15:50:17 +0100 Subject: [PATCH 06/13] fix 2^n check --- .../kotlin/com/infendro/encoding/stream/StreamEncoding.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commonMain/kotlin/com/infendro/encoding/stream/StreamEncoding.kt b/src/commonMain/kotlin/com/infendro/encoding/stream/StreamEncoding.kt index ce7de96..6c9e02a 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/stream/StreamEncoding.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/stream/StreamEncoding.kt @@ -11,7 +11,7 @@ open class StreamEncoding( private val padding: Byte? = null, ) : Encoding(alphabet) { init { - require(base % 2 == 0) + require(base and (base - 1) == 0) } override fun encode(bytes: ByteArray): ByteArray { From 24c667bc9cf6c8231dda86f8611517065b5f5aa8 Mon Sep 17 00:00:00 2001 From: Infendro Date: Sun, 18 Jan 2026 21:48:05 +0100 Subject: [PATCH 07/13] implement decode exception --- .../infendro/encoding/exception/DecodeException.kt | 5 +++++ .../encoding/exception/IllegalByteException.kt | 12 ++++++++++++ .../com/infendro/encoding/numeric/NumericEncoding.kt | 8 ++++++-- .../com/infendro/encoding/stream/StreamEncoding.kt | 3 ++- 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 src/commonMain/kotlin/com/infendro/encoding/exception/DecodeException.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/exception/IllegalByteException.kt diff --git a/src/commonMain/kotlin/com/infendro/encoding/exception/DecodeException.kt b/src/commonMain/kotlin/com/infendro/encoding/exception/DecodeException.kt new file mode 100644 index 0000000..f81ba30 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/exception/DecodeException.kt @@ -0,0 +1,5 @@ +package com.infendro.encoding.exception + +abstract class DecodeException : Exception() { + abstract override val message: String +} diff --git a/src/commonMain/kotlin/com/infendro/encoding/exception/IllegalByteException.kt b/src/commonMain/kotlin/com/infendro/encoding/exception/IllegalByteException.kt new file mode 100644 index 0000000..5650bf6 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/exception/IllegalByteException.kt @@ -0,0 +1,12 @@ +package com.infendro.encoding.exception + +class IllegalByteException( + val byte: Byte, +) : DecodeException() { + override val message: String + get() { + val hex = "0x${byte.toHexString().padStart(2, '0')}" + val char = "'${byte.toInt().toChar()}'" + return "Illegal byte $hex ($char)." + } +} diff --git a/src/commonMain/kotlin/com/infendro/encoding/numeric/NumericEncoding.kt b/src/commonMain/kotlin/com/infendro/encoding/numeric/NumericEncoding.kt index 16b36d4..671a552 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/numeric/NumericEncoding.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/numeric/NumericEncoding.kt @@ -1,6 +1,7 @@ package com.infendro.encoding.numeric import com.infendro.encoding.Encoding +import com.infendro.encoding.exception.IllegalByteException import com.infendro.encoding.util.BigInt import com.infendro.encoding.util.log2 import kotlin.math.ceil @@ -30,8 +31,11 @@ open class NumericEncoding( var value = BigInt.zero for (i in 0.. Date: Sun, 18 Jan 2026 22:15:25 +0100 Subject: [PATCH 08/13] implement Base56 and Base58 --- .../com/infendro/encoding/numeric/Base56.kt | 5 +++ .../com/infendro/encoding/numeric/Base58.kt | 5 +++ .../com/infendro/encoding/numeric/Base56.kt | 37 +++++++++++++++++++ .../com/infendro/encoding/numeric/Base58.kt | 37 +++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 src/commonMain/kotlin/com/infendro/encoding/numeric/Base56.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/numeric/Base58.kt create mode 100644 src/commonTest/kotlin/com/infendro/encoding/numeric/Base56.kt create mode 100644 src/commonTest/kotlin/com/infendro/encoding/numeric/Base58.kt diff --git a/src/commonMain/kotlin/com/infendro/encoding/numeric/Base56.kt b/src/commonMain/kotlin/com/infendro/encoding/numeric/Base56.kt new file mode 100644 index 0000000..141d002 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/numeric/Base56.kt @@ -0,0 +1,5 @@ +package com.infendro.encoding.numeric + +private val alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz".encodeToByteArray() + +object Base56 : NumericEncoding(alphabet) diff --git a/src/commonMain/kotlin/com/infendro/encoding/numeric/Base58.kt b/src/commonMain/kotlin/com/infendro/encoding/numeric/Base58.kt new file mode 100644 index 0000000..471a293 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/numeric/Base58.kt @@ -0,0 +1,5 @@ +package com.infendro.encoding.numeric + +private val alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".encodeToByteArray() + +object Base58 : NumericEncoding(alphabet) diff --git a/src/commonTest/kotlin/com/infendro/encoding/numeric/Base56.kt b/src/commonTest/kotlin/com/infendro/encoding/numeric/Base56.kt new file mode 100644 index 0000000..8f460a7 --- /dev/null +++ b/src/commonTest/kotlin/com/infendro/encoding/numeric/Base56.kt @@ -0,0 +1,37 @@ +package com.infendro.encoding.numeric + +import com.infendro.encoding.Encoding +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class `Base56 Test` { + val encoding: Encoding + get() = Base56 + + val original = "Hello World!".encodeToByteArray() + val encoded = "4q9snPvV4iRDWVKjK".encodeToByteArray() + + @Test + fun `encode empty`() { + val actual = encoding.encode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun `decode empty`() { + val actual = encoding.decode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun encode() { + val actual = encoding.encode(original).also { println(it.decodeToString()) } + assertContentEquals(encoded, actual) + } + + @Test + fun decode() { + val actual = encoding.decode(encoded) + assertContentEquals(original, actual) + } +} diff --git a/src/commonTest/kotlin/com/infendro/encoding/numeric/Base58.kt b/src/commonTest/kotlin/com/infendro/encoding/numeric/Base58.kt new file mode 100644 index 0000000..5da5d2a --- /dev/null +++ b/src/commonTest/kotlin/com/infendro/encoding/numeric/Base58.kt @@ -0,0 +1,37 @@ +package com.infendro.encoding.numeric + +import com.infendro.encoding.Encoding +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class `Base58 Test` { + val encoding: Encoding + get() = Base58 + + val original = "Hello World!".encodeToByteArray() + val encoded = "2NEpo7TZRRrLZSi2U".encodeToByteArray() + + @Test + fun `encode empty`() { + val actual = encoding.encode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun `decode empty`() { + val actual = encoding.decode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun encode() { + val actual = encoding.encode(original) + assertContentEquals(encoded, actual) + } + + @Test + fun decode() { + val actual = encoding.decode(encoded) + assertContentEquals(original, actual) + } +} From c1b8800e82ee2572efdd2e8bf1659bb3a6b5408d Mon Sep 17 00:00:00 2001 From: Infendro Date: Mon, 19 Jan 2026 19:55:39 +0100 Subject: [PATCH 09/13] implement composition --- .../kotlin/com/infendro/encoding/Base10.kt | 5 +++ .../kotlin/com/infendro/encoding/Base16.kt | 5 +++ .../kotlin/com/infendro/encoding/Base2.kt | 5 +++ .../infendro/encoding/{stream => }/Base32.kt | 4 +-- .../infendro/encoding/{numeric => }/Base36.kt | 4 +-- .../infendro/encoding/{numeric => }/Base56.kt | 4 +-- .../infendro/encoding/{numeric => }/Base58.kt | 4 +-- .../infendro/encoding/{numeric => }/Base62.kt | 4 +-- .../infendro/encoding/{stream => }/Base64.kt | 4 +-- .../kotlin/com/infendro/encoding/Base8.kt | 5 +++ .../kotlin/com/infendro/encoding/Encoding.kt | 13 +++---- .../encoding/{numeric => }/NumericEncoding.kt | 11 +++--- .../encoding/{stream => }/StreamEncoding.kt | 11 +++--- .../com/infendro/encoding/stream/Base16.kt | 5 --- .../com/infendro/encoding/stream/Base2.kt | 5 --- .../com/infendro/encoding/stream/Base8.kt | 5 --- .../kotlin/com/infendro/encoding/Base10.kt | 36 +++++++++++++++++++ .../infendro/encoding/{stream => }/Base16.kt | 3 +- .../infendro/encoding/{stream => }/Base2.kt | 3 +- .../infendro/encoding/{stream => }/Base32.kt | 3 +- .../infendro/encoding/{numeric => }/Base36.kt | 3 +- .../infendro/encoding/{numeric => }/Base56.kt | 5 ++- .../infendro/encoding/{numeric => }/Base58.kt | 3 +- .../infendro/encoding/{numeric => }/Base62.kt | 3 +- .../infendro/encoding/{stream => }/Base64.kt | 3 +- .../infendro/encoding/{stream => }/Base8.kt | 3 +- 26 files changed, 91 insertions(+), 68 deletions(-) create mode 100644 src/commonMain/kotlin/com/infendro/encoding/Base10.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/Base16.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/Base2.kt rename src/commonMain/kotlin/com/infendro/encoding/{stream => }/Base32.kt (57%) rename src/commonMain/kotlin/com/infendro/encoding/{numeric => }/Base36.kt (50%) rename src/commonMain/kotlin/com/infendro/encoding/{numeric => }/Base56.kt (55%) rename src/commonMain/kotlin/com/infendro/encoding/{numeric => }/Base58.kt (55%) rename src/commonMain/kotlin/com/infendro/encoding/{numeric => }/Base62.kt (56%) rename src/commonMain/kotlin/com/infendro/encoding/{stream => }/Base64.kt (63%) create mode 100644 src/commonMain/kotlin/com/infendro/encoding/Base8.kt rename src/commonMain/kotlin/com/infendro/encoding/{numeric => }/NumericEncoding.kt (87%) rename src/commonMain/kotlin/com/infendro/encoding/{stream => }/StreamEncoding.kt (94%) delete mode 100644 src/commonMain/kotlin/com/infendro/encoding/stream/Base16.kt delete mode 100644 src/commonMain/kotlin/com/infendro/encoding/stream/Base2.kt delete mode 100644 src/commonMain/kotlin/com/infendro/encoding/stream/Base8.kt create mode 100644 src/commonTest/kotlin/com/infendro/encoding/Base10.kt rename src/commonTest/kotlin/com/infendro/encoding/{stream => }/Base16.kt (91%) rename src/commonTest/kotlin/com/infendro/encoding/{stream => }/Base2.kt (92%) rename src/commonTest/kotlin/com/infendro/encoding/{stream => }/Base32.kt (91%) rename src/commonTest/kotlin/com/infendro/encoding/{numeric => }/Base36.kt (91%) rename src/commonTest/kotlin/com/infendro/encoding/{numeric => }/Base56.kt (85%) rename src/commonTest/kotlin/com/infendro/encoding/{numeric => }/Base58.kt (91%) rename src/commonTest/kotlin/com/infendro/encoding/{numeric => }/Base62.kt (91%) rename src/commonTest/kotlin/com/infendro/encoding/{stream => }/Base64.kt (91%) rename src/commonTest/kotlin/com/infendro/encoding/{stream => }/Base8.kt (91%) diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base10.kt b/src/commonMain/kotlin/com/infendro/encoding/Base10.kt new file mode 100644 index 0000000..eab2713 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/Base10.kt @@ -0,0 +1,5 @@ +package com.infendro.encoding + +private val alphabet = "0123456789".encodeToByteArray() + +object Base10 : Encoding by NumericEncoding(alphabet) diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base16.kt b/src/commonMain/kotlin/com/infendro/encoding/Base16.kt new file mode 100644 index 0000000..eff1268 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/Base16.kt @@ -0,0 +1,5 @@ +package com.infendro.encoding + +private val alphabet = "0123456789ABCDEF".encodeToByteArray() + +object Base16 : Encoding by StreamEncoding(alphabet) diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base2.kt b/src/commonMain/kotlin/com/infendro/encoding/Base2.kt new file mode 100644 index 0000000..ec8db94 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/Base2.kt @@ -0,0 +1,5 @@ +package com.infendro.encoding + +private val alphabet = "01".encodeToByteArray() + +object Base2 : Encoding by StreamEncoding(alphabet) diff --git a/src/commonMain/kotlin/com/infendro/encoding/stream/Base32.kt b/src/commonMain/kotlin/com/infendro/encoding/Base32.kt similarity index 57% rename from src/commonMain/kotlin/com/infendro/encoding/stream/Base32.kt rename to src/commonMain/kotlin/com/infendro/encoding/Base32.kt index 61f6893..b89a8cb 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/stream/Base32.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Base32.kt @@ -1,6 +1,6 @@ -package com.infendro.encoding.stream +package com.infendro.encoding private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".encodeToByteArray() private const val padding = '='.code.toByte() -object Base32 : StreamEncoding(alphabet, padding) +object Base32 : Encoding by StreamEncoding(alphabet, padding) diff --git a/src/commonMain/kotlin/com/infendro/encoding/numeric/Base36.kt b/src/commonMain/kotlin/com/infendro/encoding/Base36.kt similarity index 50% rename from src/commonMain/kotlin/com/infendro/encoding/numeric/Base36.kt rename to src/commonMain/kotlin/com/infendro/encoding/Base36.kt index dd1a268..421b3cc 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/numeric/Base36.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Base36.kt @@ -1,5 +1,5 @@ -package com.infendro.encoding.numeric +package com.infendro.encoding private val alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".encodeToByteArray() -object Base36 : NumericEncoding(alphabet) +object Base36 : Encoding by NumericEncoding(alphabet) diff --git a/src/commonMain/kotlin/com/infendro/encoding/numeric/Base56.kt b/src/commonMain/kotlin/com/infendro/encoding/Base56.kt similarity index 55% rename from src/commonMain/kotlin/com/infendro/encoding/numeric/Base56.kt rename to src/commonMain/kotlin/com/infendro/encoding/Base56.kt index 141d002..206a0d6 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/numeric/Base56.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Base56.kt @@ -1,5 +1,5 @@ -package com.infendro.encoding.numeric +package com.infendro.encoding private val alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz".encodeToByteArray() -object Base56 : NumericEncoding(alphabet) +object Base56 : Encoding by NumericEncoding(alphabet) diff --git a/src/commonMain/kotlin/com/infendro/encoding/numeric/Base58.kt b/src/commonMain/kotlin/com/infendro/encoding/Base58.kt similarity index 55% rename from src/commonMain/kotlin/com/infendro/encoding/numeric/Base58.kt rename to src/commonMain/kotlin/com/infendro/encoding/Base58.kt index 471a293..3b12ab9 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/numeric/Base58.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Base58.kt @@ -1,5 +1,5 @@ -package com.infendro.encoding.numeric +package com.infendro.encoding private val alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".encodeToByteArray() -object Base58 : NumericEncoding(alphabet) +object Base58 : Encoding by NumericEncoding(alphabet) diff --git a/src/commonMain/kotlin/com/infendro/encoding/numeric/Base62.kt b/src/commonMain/kotlin/com/infendro/encoding/Base62.kt similarity index 56% rename from src/commonMain/kotlin/com/infendro/encoding/numeric/Base62.kt rename to src/commonMain/kotlin/com/infendro/encoding/Base62.kt index d8e0dac..a56ab12 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/numeric/Base62.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Base62.kt @@ -1,5 +1,5 @@ -package com.infendro.encoding.numeric +package com.infendro.encoding private val alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".encodeToByteArray() -object Base62 : NumericEncoding(alphabet) +object Base62 : Encoding by NumericEncoding(alphabet) diff --git a/src/commonMain/kotlin/com/infendro/encoding/stream/Base64.kt b/src/commonMain/kotlin/com/infendro/encoding/Base64.kt similarity index 63% rename from src/commonMain/kotlin/com/infendro/encoding/stream/Base64.kt rename to src/commonMain/kotlin/com/infendro/encoding/Base64.kt index 48a76b3..fdf8214 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/stream/Base64.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Base64.kt @@ -1,6 +1,6 @@ -package com.infendro.encoding.stream +package com.infendro.encoding private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".encodeToByteArray() private const val padding = '='.code.toByte() -object Base64 : StreamEncoding(alphabet, padding) +object Base64 : Encoding by StreamEncoding(alphabet, padding) diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base8.kt b/src/commonMain/kotlin/com/infendro/encoding/Base8.kt new file mode 100644 index 0000000..2f337d9 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/Base8.kt @@ -0,0 +1,5 @@ +package com.infendro.encoding + +private val alphabet = "01234567".encodeToByteArray() + +object Base8 : Encoding by StreamEncoding(alphabet) diff --git a/src/commonMain/kotlin/com/infendro/encoding/Encoding.kt b/src/commonMain/kotlin/com/infendro/encoding/Encoding.kt index 3d1b456..6949e9b 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/Encoding.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Encoding.kt @@ -1,15 +1,10 @@ package com.infendro.encoding -abstract class Encoding( - val alphabet: ByteArray, -) { - init { - require(alphabet.isNotEmpty()) - } - +interface Encoding { + val alphabet: ByteArray val base: Int get() = alphabet.size - abstract fun encode(bytes: ByteArray): ByteArray - abstract fun decode(bytes: ByteArray): ByteArray + fun encode(bytes: ByteArray): ByteArray + fun decode(bytes: ByteArray): ByteArray } diff --git a/src/commonMain/kotlin/com/infendro/encoding/numeric/NumericEncoding.kt b/src/commonMain/kotlin/com/infendro/encoding/NumericEncoding.kt similarity index 87% rename from src/commonMain/kotlin/com/infendro/encoding/numeric/NumericEncoding.kt rename to src/commonMain/kotlin/com/infendro/encoding/NumericEncoding.kt index 671a552..d0ab549 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/numeric/NumericEncoding.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/NumericEncoding.kt @@ -1,22 +1,21 @@ -package com.infendro.encoding.numeric +package com.infendro.encoding -import com.infendro.encoding.Encoding import com.infendro.encoding.exception.IllegalByteException import com.infendro.encoding.util.BigInt import com.infendro.encoding.util.log2 import kotlin.math.ceil -open class NumericEncoding( - alphabet: ByteArray, -) : Encoding(alphabet) { +class NumericEncoding internal constructor( + override val alphabet: ByteArray, +) : Encoding { override fun encode(bytes: ByteArray): ByteArray { if (bytes.isEmpty()) return byteArrayOf() val size = ceil(bytes.size * 8 / log2(base)).toInt() val result = ByteArray(size) - var x = BigInt.from(bytes) var i = result.size + var x = BigInt.from(bytes) while (!x.isZero()) { val (quotient, remainder) = x.divRem(base) x = quotient diff --git a/src/commonMain/kotlin/com/infendro/encoding/stream/StreamEncoding.kt b/src/commonMain/kotlin/com/infendro/encoding/StreamEncoding.kt similarity index 94% rename from src/commonMain/kotlin/com/infendro/encoding/stream/StreamEncoding.kt rename to src/commonMain/kotlin/com/infendro/encoding/StreamEncoding.kt index 6bce001..8f8a056 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/stream/StreamEncoding.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/StreamEncoding.kt @@ -1,16 +1,15 @@ -package com.infendro.encoding.stream +package com.infendro.encoding -import com.infendro.encoding.Encoding import com.infendro.encoding.exception.IllegalByteException import com.infendro.encoding.util.align import com.infendro.encoding.util.divCeil import com.infendro.encoding.util.lcm import com.infendro.encoding.util.log2 -open class StreamEncoding( - alphabet: ByteArray, +class StreamEncoding internal constructor( + override val alphabet: ByteArray, private val padding: Byte? = null, -) : Encoding(alphabet) { +) : Encoding { init { require(base and (base - 1) == 0) } @@ -30,7 +29,6 @@ open class StreamEncoding( var r = 0 var buffer = 0 var bufferBits = 0 - for (i in bytes.indices) { buffer = (buffer shl 8) + bytes[i] bufferBits += 8 @@ -71,7 +69,6 @@ open class StreamEncoding( var r = 0 var buffer = 0 var bufferBits = 0 - for (i in bytes.indices) { val byte = bytes[i] if (byte == padding) break diff --git a/src/commonMain/kotlin/com/infendro/encoding/stream/Base16.kt b/src/commonMain/kotlin/com/infendro/encoding/stream/Base16.kt deleted file mode 100644 index 0fc0b08..0000000 --- a/src/commonMain/kotlin/com/infendro/encoding/stream/Base16.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.infendro.encoding.stream - -private val alphabet = "0123456789ABCDEF".encodeToByteArray() - -object Base16 : StreamEncoding(alphabet) diff --git a/src/commonMain/kotlin/com/infendro/encoding/stream/Base2.kt b/src/commonMain/kotlin/com/infendro/encoding/stream/Base2.kt deleted file mode 100644 index d61a0e3..0000000 --- a/src/commonMain/kotlin/com/infendro/encoding/stream/Base2.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.infendro.encoding.stream - -private val alphabet = "01".encodeToByteArray() - -object Base2 : StreamEncoding(alphabet) diff --git a/src/commonMain/kotlin/com/infendro/encoding/stream/Base8.kt b/src/commonMain/kotlin/com/infendro/encoding/stream/Base8.kt deleted file mode 100644 index ae87faa..0000000 --- a/src/commonMain/kotlin/com/infendro/encoding/stream/Base8.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.infendro.encoding.stream - -private val alphabet = "01234567".encodeToByteArray() - -object Base8 : StreamEncoding(alphabet) diff --git a/src/commonTest/kotlin/com/infendro/encoding/Base10.kt b/src/commonTest/kotlin/com/infendro/encoding/Base10.kt new file mode 100644 index 0000000..017b8f7 --- /dev/null +++ b/src/commonTest/kotlin/com/infendro/encoding/Base10.kt @@ -0,0 +1,36 @@ +package com.infendro.encoding + +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class `Base10 Test` { + val encoding: Encoding + get() = Base10 + + val original = "Hello World!".encodeToByteArray() + val encoded = "22405534230753928650781647905".encodeToByteArray() + + @Test + fun `encode empty`() { + val actual = encoding.encode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun `decode empty`() { + val actual = encoding.decode(byteArrayOf()) + assertContentEquals(byteArrayOf(), actual) + } + + @Test + fun encode() { + val actual = encoding.encode(original) + assertContentEquals(encoded, actual) + } + + @Test + fun decode() { + val actual = encoding.decode(encoded) + assertContentEquals(original, actual) + } +} diff --git a/src/commonTest/kotlin/com/infendro/encoding/stream/Base16.kt b/src/commonTest/kotlin/com/infendro/encoding/Base16.kt similarity index 91% rename from src/commonTest/kotlin/com/infendro/encoding/stream/Base16.kt rename to src/commonTest/kotlin/com/infendro/encoding/Base16.kt index 1803488..380e5c9 100644 --- a/src/commonTest/kotlin/com/infendro/encoding/stream/Base16.kt +++ b/src/commonTest/kotlin/com/infendro/encoding/Base16.kt @@ -1,6 +1,5 @@ -package com.infendro.encoding.stream +package com.infendro.encoding -import com.infendro.encoding.Encoding import kotlin.test.Test import kotlin.test.assertContentEquals diff --git a/src/commonTest/kotlin/com/infendro/encoding/stream/Base2.kt b/src/commonTest/kotlin/com/infendro/encoding/Base2.kt similarity index 92% rename from src/commonTest/kotlin/com/infendro/encoding/stream/Base2.kt rename to src/commonTest/kotlin/com/infendro/encoding/Base2.kt index e91105a..f4235a6 100644 --- a/src/commonTest/kotlin/com/infendro/encoding/stream/Base2.kt +++ b/src/commonTest/kotlin/com/infendro/encoding/Base2.kt @@ -1,6 +1,5 @@ -package com.infendro.encoding.stream +package com.infendro.encoding -import com.infendro.encoding.Encoding import kotlin.test.Test import kotlin.test.assertContentEquals diff --git a/src/commonTest/kotlin/com/infendro/encoding/stream/Base32.kt b/src/commonTest/kotlin/com/infendro/encoding/Base32.kt similarity index 91% rename from src/commonTest/kotlin/com/infendro/encoding/stream/Base32.kt rename to src/commonTest/kotlin/com/infendro/encoding/Base32.kt index 76a63a1..5a10dfc 100644 --- a/src/commonTest/kotlin/com/infendro/encoding/stream/Base32.kt +++ b/src/commonTest/kotlin/com/infendro/encoding/Base32.kt @@ -1,6 +1,5 @@ -package com.infendro.encoding.stream +package com.infendro.encoding -import com.infendro.encoding.Encoding import kotlin.test.Test import kotlin.test.assertContentEquals diff --git a/src/commonTest/kotlin/com/infendro/encoding/numeric/Base36.kt b/src/commonTest/kotlin/com/infendro/encoding/Base36.kt similarity index 91% rename from src/commonTest/kotlin/com/infendro/encoding/numeric/Base36.kt rename to src/commonTest/kotlin/com/infendro/encoding/Base36.kt index cd55650..3dc916b 100644 --- a/src/commonTest/kotlin/com/infendro/encoding/numeric/Base36.kt +++ b/src/commonTest/kotlin/com/infendro/encoding/Base36.kt @@ -1,6 +1,5 @@ -package com.infendro.encoding.numeric +package com.infendro.encoding -import com.infendro.encoding.Encoding import kotlin.test.Test import kotlin.test.assertContentEquals diff --git a/src/commonTest/kotlin/com/infendro/encoding/numeric/Base56.kt b/src/commonTest/kotlin/com/infendro/encoding/Base56.kt similarity index 85% rename from src/commonTest/kotlin/com/infendro/encoding/numeric/Base56.kt rename to src/commonTest/kotlin/com/infendro/encoding/Base56.kt index 8f460a7..687ce11 100644 --- a/src/commonTest/kotlin/com/infendro/encoding/numeric/Base56.kt +++ b/src/commonTest/kotlin/com/infendro/encoding/Base56.kt @@ -1,6 +1,5 @@ -package com.infendro.encoding.numeric +package com.infendro.encoding -import com.infendro.encoding.Encoding import kotlin.test.Test import kotlin.test.assertContentEquals @@ -9,7 +8,7 @@ class `Base56 Test` { get() = Base56 val original = "Hello World!".encodeToByteArray() - val encoded = "4q9snPvV4iRDWVKjK".encodeToByteArray() + val encoded = "4Q9SNpVv4JrdwvjKj".encodeToByteArray() @Test fun `encode empty`() { diff --git a/src/commonTest/kotlin/com/infendro/encoding/numeric/Base58.kt b/src/commonTest/kotlin/com/infendro/encoding/Base58.kt similarity index 91% rename from src/commonTest/kotlin/com/infendro/encoding/numeric/Base58.kt rename to src/commonTest/kotlin/com/infendro/encoding/Base58.kt index 5da5d2a..5a8e82e 100644 --- a/src/commonTest/kotlin/com/infendro/encoding/numeric/Base58.kt +++ b/src/commonTest/kotlin/com/infendro/encoding/Base58.kt @@ -1,6 +1,5 @@ -package com.infendro.encoding.numeric +package com.infendro.encoding -import com.infendro.encoding.Encoding import kotlin.test.Test import kotlin.test.assertContentEquals diff --git a/src/commonTest/kotlin/com/infendro/encoding/numeric/Base62.kt b/src/commonTest/kotlin/com/infendro/encoding/Base62.kt similarity index 91% rename from src/commonTest/kotlin/com/infendro/encoding/numeric/Base62.kt rename to src/commonTest/kotlin/com/infendro/encoding/Base62.kt index b519ec5..fa67355 100644 --- a/src/commonTest/kotlin/com/infendro/encoding/numeric/Base62.kt +++ b/src/commonTest/kotlin/com/infendro/encoding/Base62.kt @@ -1,6 +1,5 @@ -package com.infendro.encoding.numeric +package com.infendro.encoding -import com.infendro.encoding.Encoding import kotlin.test.Test import kotlin.test.assertContentEquals diff --git a/src/commonTest/kotlin/com/infendro/encoding/stream/Base64.kt b/src/commonTest/kotlin/com/infendro/encoding/Base64.kt similarity index 91% rename from src/commonTest/kotlin/com/infendro/encoding/stream/Base64.kt rename to src/commonTest/kotlin/com/infendro/encoding/Base64.kt index 87dd90c..3ac84ef 100644 --- a/src/commonTest/kotlin/com/infendro/encoding/stream/Base64.kt +++ b/src/commonTest/kotlin/com/infendro/encoding/Base64.kt @@ -1,6 +1,5 @@ -package com.infendro.encoding.stream +package com.infendro.encoding -import com.infendro.encoding.Encoding import kotlin.test.Test import kotlin.test.assertContentEquals diff --git a/src/commonTest/kotlin/com/infendro/encoding/stream/Base8.kt b/src/commonTest/kotlin/com/infendro/encoding/Base8.kt similarity index 91% rename from src/commonTest/kotlin/com/infendro/encoding/stream/Base8.kt rename to src/commonTest/kotlin/com/infendro/encoding/Base8.kt index b7b3a08..3f16e9d 100644 --- a/src/commonTest/kotlin/com/infendro/encoding/stream/Base8.kt +++ b/src/commonTest/kotlin/com/infendro/encoding/Base8.kt @@ -1,6 +1,5 @@ -package com.infendro.encoding.stream +package com.infendro.encoding -import com.infendro.encoding.Encoding import kotlin.test.Test import kotlin.test.assertContentEquals From 829095575555375087ec0077005c0a6fcfb2e7e8 Mon Sep 17 00:00:00 2001 From: Infendro Date: Tue, 20 Jan 2026 09:52:58 +0100 Subject: [PATCH 10/13] split padding in StreamEncoding to PaddedStreamEncoding --- .../kotlin/com/infendro/encoding/Base32.kt | 2 +- .../kotlin/com/infendro/encoding/Base64.kt | 2 +- .../com/infendro/encoding/NumericEncoding.kt | 10 +-- .../infendro/encoding/PaddedStreamEncoding.kt | 78 +++++++++++++++++++ .../com/infendro/encoding/StreamEncoding.kt | 44 +++-------- 5 files changed, 93 insertions(+), 43 deletions(-) create mode 100644 src/commonMain/kotlin/com/infendro/encoding/PaddedStreamEncoding.kt diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base32.kt b/src/commonMain/kotlin/com/infendro/encoding/Base32.kt index b89a8cb..d30a863 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/Base32.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Base32.kt @@ -3,4 +3,4 @@ package com.infendro.encoding private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".encodeToByteArray() private const val padding = '='.code.toByte() -object Base32 : Encoding by StreamEncoding(alphabet, padding) +object Base32 : Encoding by PaddedStreamEncoding(alphabet, padding) diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base64.kt b/src/commonMain/kotlin/com/infendro/encoding/Base64.kt index fdf8214..2dfe8b1 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/Base64.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Base64.kt @@ -3,4 +3,4 @@ package com.infendro.encoding private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".encodeToByteArray() private const val padding = '='.code.toByte() -object Base64 : Encoding by StreamEncoding(alphabet, padding) +object Base64 : Encoding by PaddedStreamEncoding(alphabet, padding) diff --git a/src/commonMain/kotlin/com/infendro/encoding/NumericEncoding.kt b/src/commonMain/kotlin/com/infendro/encoding/NumericEncoding.kt index d0ab549..b360248 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/NumericEncoding.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/NumericEncoding.kt @@ -2,8 +2,6 @@ package com.infendro.encoding import com.infendro.encoding.exception.IllegalByteException import com.infendro.encoding.util.BigInt -import com.infendro.encoding.util.log2 -import kotlin.math.ceil class NumericEncoding internal constructor( override val alphabet: ByteArray, @@ -11,18 +9,16 @@ class NumericEncoding internal constructor( override fun encode(bytes: ByteArray): ByteArray { if (bytes.isEmpty()) return byteArrayOf() - val size = ceil(bytes.size * 8 / log2(base)).toInt() - val result = ByteArray(size) + val result = mutableListOf() - var i = result.size var x = BigInt.from(bytes) while (!x.isZero()) { val (quotient, remainder) = x.divRem(base) x = quotient - result[--i] = alphabet[remainder] + result += alphabet[remainder] } - return result.copyOfRange(i, result.size) + return result.asReversed().toByteArray() } override fun decode(bytes: ByteArray): ByteArray { diff --git a/src/commonMain/kotlin/com/infendro/encoding/PaddedStreamEncoding.kt b/src/commonMain/kotlin/com/infendro/encoding/PaddedStreamEncoding.kt new file mode 100644 index 0000000..12e224c --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/PaddedStreamEncoding.kt @@ -0,0 +1,78 @@ +package com.infendro.encoding + +import com.infendro.encoding.exception.IllegalByteException +import com.infendro.encoding.util.align +import com.infendro.encoding.util.divCeil +import com.infendro.encoding.util.lcm +import com.infendro.encoding.util.log2 + +class PaddedStreamEncoding internal constructor( + override val alphabet: ByteArray, + private val padding: Byte, +) : Encoding { + override fun encode(bytes: ByteArray): ByteArray { + val bits = log2(base).toInt() + + val block = lcm(bits, 8) / bits + val size = (bytes.size * 8).divCeil(bits).align(block) + val result = ByteArray(size) + + var r = 0 + var buffer = 0 + var bufferBits = 0 + for (i in bytes.indices) { + buffer = (buffer shl 8) + bytes[i] + bufferBits += 8 + + while (bufferBits >= bits) { + val offset = bufferBits - bits + val index = (buffer ushr offset) + buffer = buffer and ((base - 1) shl offset).inv() + bufferBits -= bits + + result[r++] = alphabet[index] + } + } + if (bufferBits > 0) { + val index = buffer shl (bits - bufferBits) + result[r++] = alphabet[index] + } + while (r < result.size) { + result[r++] = padding + } + + return result + } + + override fun decode(bytes: ByteArray): ByteArray { + val bits = log2(base).toInt() + + val end = bytes.indexOf(padding).takeIf { it != -1 } ?: bytes.size + val size = end * bits / 8 + val result = ByteArray(size) + + var r = 0 + var buffer = 0 + var bufferBits = 0 + for (i in 0..= 8) { + val offset = bufferBits - 8 + val character = (buffer ushr offset).toByte() + buffer = buffer and (0xFF shl offset).inv() + bufferBits -= 8 + + result[r++] = character + } + } + + return result + } +} diff --git a/src/commonMain/kotlin/com/infendro/encoding/StreamEncoding.kt b/src/commonMain/kotlin/com/infendro/encoding/StreamEncoding.kt index 8f8a056..daa5cb8 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/StreamEncoding.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/StreamEncoding.kt @@ -1,30 +1,17 @@ package com.infendro.encoding import com.infendro.encoding.exception.IllegalByteException -import com.infendro.encoding.util.align import com.infendro.encoding.util.divCeil -import com.infendro.encoding.util.lcm import com.infendro.encoding.util.log2 class StreamEncoding internal constructor( override val alphabet: ByteArray, - private val padding: Byte? = null, ) : Encoding { - init { - require(base and (base - 1) == 0) - } - override fun encode(bytes: ByteArray): ByteArray { val bits = log2(base).toInt() + val size = (bytes.size * 8).divCeil(bits) - val encodedSize = when (padding) { - null -> size - else -> { - val block = lcm(bits, 8) / bits - size.align(block) - } - } - val result = ByteArray(encodedSize) + val result = ByteArray(size) var r = 0 var buffer = 0 @@ -43,15 +30,8 @@ class StreamEncoding internal constructor( } } if (bufferBits > 0) { - buffer = buffer shl (bits - bufferBits) - val index = buffer - result[r++] = alphabet[index] - } - - if (padding != null) { - while (r < result.size) { - result[r++] = padding - } + val index = buffer shl (bits - bufferBits) + result[r] = alphabet[index] } return result @@ -59,24 +39,20 @@ class StreamEncoding internal constructor( override fun decode(bytes: ByteArray): ByteArray { val bits = log2(base).toInt() - val index = padding?.let { bytes.indexOf(it) } ?: -1 - val decodedSize = when (index) { - -1 -> bytes.size * bits / 8 - else -> index * bits / 8 - } - val result = ByteArray(decodedSize) + + val size = bytes.size * bits / 8 + val result = ByteArray(size) var r = 0 var buffer = 0 var bufferBits = 0 for (i in bytes.indices) { val byte = bytes[i] - if (byte == padding) break - val index = alphabet.indexOf(byte) - if (index == -1) throw IllegalByteException(byte) + val value = alphabet.indexOf(byte) + if (value == -1) throw IllegalByteException(byte) - buffer = (buffer shl bits) + index.toByte() + buffer = (buffer shl bits) + value.toByte() bufferBits += bits if (bufferBits >= 8) { From 6283c2f44df12917de47fcbb856e7921c9e46bd1 Mon Sep 17 00:00:00 2001 From: Infendro Date: Tue, 20 Jan 2026 16:59:05 +0100 Subject: [PATCH 11/13] refactor encodings --- .../kotlin/com/infendro/encoding/Base16.kt | 9 ++- .../kotlin/com/infendro/encoding/Base32.kt | 2 +- .../kotlin/com/infendro/encoding/Base64.kt | 9 ++- .../kotlin/com/infendro/encoding/Encoding.kt | 4 - .../com/infendro/encoding/NumericEncoding.kt | 6 +- .../infendro/encoding/PaddedStreamEncoding.kt | 78 ------------------- .../com/infendro/encoding/StreamEncoding.kt | 78 +++++++++++-------- .../com/infendro/encoding/util/BigInt.kt | 36 +++------ .../kotlin/com/infendro/encoding/util/Int.kt | 7 ++ .../kotlin/com/infendro/encoding/util/Math.kt | 6 +- .../kotlin/com/infendro/encoding/Base56.kt | 2 +- 11 files changed, 83 insertions(+), 154 deletions(-) delete mode 100644 src/commonMain/kotlin/com/infendro/encoding/PaddedStreamEncoding.kt create mode 100644 src/commonMain/kotlin/com/infendro/encoding/util/Int.kt diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base16.kt b/src/commonMain/kotlin/com/infendro/encoding/Base16.kt index eff1268..6530b7c 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/Base16.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Base16.kt @@ -1,5 +1,10 @@ package com.infendro.encoding -private val alphabet = "0123456789ABCDEF".encodeToByteArray() +private val uppercase = "0123456789ABCDEF".encodeToByteArray() +private val lowercase = "0123456789abcdef".encodeToByteArray() -object Base16 : Encoding by StreamEncoding(alphabet) +object Base16 : Encoding by StreamEncoding(uppercase) { + val UPPERCASE = this + + object LOWERCASE : Encoding by StreamEncoding(lowercase) +} diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base32.kt b/src/commonMain/kotlin/com/infendro/encoding/Base32.kt index d30a863..b89a8cb 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/Base32.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Base32.kt @@ -3,4 +3,4 @@ package com.infendro.encoding private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".encodeToByteArray() private const val padding = '='.code.toByte() -object Base32 : Encoding by PaddedStreamEncoding(alphabet, padding) +object Base32 : Encoding by StreamEncoding(alphabet, padding) diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base64.kt b/src/commonMain/kotlin/com/infendro/encoding/Base64.kt index 2dfe8b1..2ef7ab0 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/Base64.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Base64.kt @@ -1,6 +1,11 @@ package com.infendro.encoding -private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".encodeToByteArray() +private val standard = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".encodeToByteArray() +private val url = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".encodeToByteArray() private const val padding = '='.code.toByte() -object Base64 : Encoding by PaddedStreamEncoding(alphabet, padding) +object Base64 : Encoding by StreamEncoding(standard, padding) { + val STANDARD = this + + object URL : Encoding by StreamEncoding(url) +} diff --git a/src/commonMain/kotlin/com/infendro/encoding/Encoding.kt b/src/commonMain/kotlin/com/infendro/encoding/Encoding.kt index 6949e9b..d302e59 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/Encoding.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/Encoding.kt @@ -1,10 +1,6 @@ package com.infendro.encoding interface Encoding { - val alphabet: ByteArray - val base: Int - get() = alphabet.size - fun encode(bytes: ByteArray): ByteArray fun decode(bytes: ByteArray): ByteArray } diff --git a/src/commonMain/kotlin/com/infendro/encoding/NumericEncoding.kt b/src/commonMain/kotlin/com/infendro/encoding/NumericEncoding.kt index b360248..36e9555 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/NumericEncoding.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/NumericEncoding.kt @@ -4,8 +4,10 @@ import com.infendro.encoding.exception.IllegalByteException import com.infendro.encoding.util.BigInt class NumericEncoding internal constructor( - override val alphabet: ByteArray, + private val alphabet: ByteArray, ) : Encoding { + private val base: Int = alphabet.size + override fun encode(bytes: ByteArray): ByteArray { if (bytes.isEmpty()) return byteArrayOf() @@ -27,10 +29,8 @@ class NumericEncoding internal constructor( var value = BigInt.zero for (i in 0..= bits) { - val offset = bufferBits - bits - val index = (buffer ushr offset) - buffer = buffer and ((base - 1) shl offset).inv() - bufferBits -= bits - - result[r++] = alphabet[index] - } - } - if (bufferBits > 0) { - val index = buffer shl (bits - bufferBits) - result[r++] = alphabet[index] - } - while (r < result.size) { - result[r++] = padding - } - - return result - } - - override fun decode(bytes: ByteArray): ByteArray { - val bits = log2(base).toInt() - - val end = bytes.indexOf(padding).takeIf { it != -1 } ?: bytes.size - val size = end * bits / 8 - val result = ByteArray(size) - - var r = 0 - var buffer = 0 - var bufferBits = 0 - for (i in 0..= 8) { - val offset = bufferBits - 8 - val character = (buffer ushr offset).toByte() - buffer = buffer and (0xFF shl offset).inv() - bufferBits -= 8 - - result[r++] = character - } - } - - return result - } -} diff --git a/src/commonMain/kotlin/com/infendro/encoding/StreamEncoding.kt b/src/commonMain/kotlin/com/infendro/encoding/StreamEncoding.kt index daa5cb8..e4ad829 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/StreamEncoding.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/StreamEncoding.kt @@ -1,70 +1,80 @@ package com.infendro.encoding import com.infendro.encoding.exception.IllegalByteException +import com.infendro.encoding.util.align import com.infendro.encoding.util.divCeil +import com.infendro.encoding.util.lcm import com.infendro.encoding.util.log2 class StreamEncoding internal constructor( - override val alphabet: ByteArray, + private val alphabet: ByteArray, + private val padding: Byte? = null, ) : Encoding { + private val base: Int = alphabet.size + private val bits: Int = log2(base).toInt() + private val block: Int = lcm(bits, 8) / bits + private val mask: Int = (0x01 shl bits) - 1 + override fun encode(bytes: ByteArray): ByteArray { - val bits = log2(base).toInt() + val size = when (padding) { + null -> (bytes.size * 8).divCeil(bits) + else -> (bytes.size * 8).divCeil(bits).align(block) + } + val encoded = ByteArray(size) + var i = 0 - val size = (bytes.size * 8).divCeil(bits) - val result = ByteArray(size) - - var r = 0 var buffer = 0 var bufferBits = 0 - for (i in bytes.indices) { - buffer = (buffer shl 8) + bytes[i] + for (byte in bytes) { + buffer = (buffer shl 8) or (byte.toInt() and 0xFF) bufferBits += 8 while (bufferBits >= bits) { - val offset = bufferBits - bits - val index = (buffer ushr offset) - buffer = buffer and ((base - 1) shl offset).inv() + val index = (buffer shr (bufferBits - bits)) and mask bufferBits -= bits - - result[r++] = alphabet[index] + encoded[i++] = alphabet[index] } } if (bufferBits > 0) { - val index = buffer shl (bits - bufferBits) - result[r] = alphabet[index] + val index = (buffer shl (bits - bufferBits)) and mask + encoded[i++] = alphabet[index] } - return result + while (i < encoded.size) { + encoded[i++] = padding!! + } + + return encoded } override fun decode(bytes: ByteArray): ByteArray { - val bits = log2(base).toInt() + val end = when (padding) { + null -> bytes.size + else -> when (val index = bytes.indexOf(padding)) { + -1 -> bytes.size + else -> index + } + } + val size = end * bits / 8 + val decoded = ByteArray(size) + var i = 0 - val size = bytes.size * bits / 8 - val result = ByteArray(size) - - var r = 0 var buffer = 0 var bufferBits = 0 - for (i in bytes.indices) { - val byte = bytes[i] - - val value = alphabet.indexOf(byte) - if (value == -1) throw IllegalByteException(byte) - - buffer = (buffer shl bits) + value.toByte() + for (b in 0..= 8) { - val offset = bufferBits - 8 - val character = (buffer ushr offset).toByte() - buffer = buffer and (0xFF shl offset).inv() + val byte = (buffer shr (bufferBits - 8)) and 0xFF bufferBits -= 8 - - result[r++] = character + decoded[i++] = byte.toByte() } } - return result + return decoded } } diff --git a/src/commonMain/kotlin/com/infendro/encoding/util/BigInt.kt b/src/commonMain/kotlin/com/infendro/encoding/util/BigInt.kt index d78fd09..14c8d39 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/util/BigInt.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/util/BigInt.kt @@ -49,40 +49,24 @@ internal class BigInt private constructor( } fun toByteArray(): ByteArray { - val msw = words.last() - val mswBytes = when { - (msw ushr 24) != 0 -> 4 - (msw ushr 16) != 0 -> 3 - (msw ushr 8) != 0 -> 2 - else -> 1 + val bytes = ByteArray((words.size * 4) - words.last().countLeadingZeroBytes()) + for (i in bytes.indices) { + val (wi, bi) = (bytes.lastIndex - i).divRem(4) + bytes[i] = (words[wi] ushr (bi * 8)).toByte() } - val size = ((words.size - 1) * 4) + mswBytes - val result = ByteArray(size) - - for (i in 0.. = + Pair(this / divisor, this % divisor) + +fun Int.countLeadingZeroBytes(): Int = + countLeadingZeroBits() / 8 diff --git a/src/commonMain/kotlin/com/infendro/encoding/util/Math.kt b/src/commonMain/kotlin/com/infendro/encoding/util/Math.kt index a590e8b..a5ea675 100644 --- a/src/commonMain/kotlin/com/infendro/encoding/util/Math.kt +++ b/src/commonMain/kotlin/com/infendro/encoding/util/Math.kt @@ -2,15 +2,15 @@ package com.infendro.encoding.util import kotlin.math.log2 -internal fun log2(value: Int): Double = - log2(value.toDouble()) - internal fun Int.divCeil(n: Int): Int = (this + n - 1) / n internal fun Int.align(n: Int): Int = divCeil(n) * n +internal fun log2(value: Int): Double = + log2(value.toDouble()) + internal fun gcd(a: Int, b: Int): Int { var x = a var y = b diff --git a/src/commonTest/kotlin/com/infendro/encoding/Base56.kt b/src/commonTest/kotlin/com/infendro/encoding/Base56.kt index 687ce11..9147642 100644 --- a/src/commonTest/kotlin/com/infendro/encoding/Base56.kt +++ b/src/commonTest/kotlin/com/infendro/encoding/Base56.kt @@ -24,7 +24,7 @@ class `Base56 Test` { @Test fun encode() { - val actual = encoding.encode(original).also { println(it.decodeToString()) } + val actual = encoding.encode(original) assertContentEquals(encoded, actual) } From 61d6a8e191d70ce858ea6000aa317d0243921f23 Mon Sep 17 00:00:00 2001 From: Infendro Date: Wed, 21 Jan 2026 22:03:23 +0100 Subject: [PATCH 12/13] implement Base45 --- .../kotlin/com/infendro/encoding/Base45.kt | 51 +++++++++++++++++++ .../kotlin/com/infendro/encoding/Base45.kt | 36 +++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/commonMain/kotlin/com/infendro/encoding/Base45.kt create mode 100644 src/commonTest/kotlin/com/infendro/encoding/Base45.kt diff --git a/src/commonMain/kotlin/com/infendro/encoding/Base45.kt b/src/commonMain/kotlin/com/infendro/encoding/Base45.kt new file mode 100644 index 0000000..c24808a --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/encoding/Base45.kt @@ -0,0 +1,51 @@ +package com.infendro.encoding + +import com.infendro.encoding.exception.IllegalByteException + +object Base45 : Encoding { + private val alphabet: ByteArray = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".encodeToByteArray() + private val base: Int = alphabet.size + + override fun encode(bytes: ByteArray): ByteArray { + val result = mutableListOf() + + for (i in bytes.indices step 2) { + val remaining = minOf(bytes.size - i, 2) + + var value = 0 + for (j in 0..() + + for (i in bytes.indices step 3) { + val remaining = minOf(bytes.size - i, 3) + + var value = 0 + for (j in (0.. Date: Wed, 21 Jan 2026 22:09:31 +0100 Subject: [PATCH 13/13] fix README --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cde6624..6828ec2 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,15 @@ Supported encodings: - Base2 (binary) - Base8 (octal) -- Base16 (hexadecimal) +- Base10 (decimal) +- Base16 (hexadecimal, uppercase/lowercase) - Base32 - Base36 +- Base45 +- Base56 +- Base58 - Base62 -- Base64 +- Base64 (standard/URL) ## Installation @@ -35,6 +39,6 @@ fun main() { // encode the string with the desired encoding val encoded = Base16.encode(test).decodeToString() - println(encoded) // 48656c6c6f20576f726c6421 + println(encoded) // 48656C6C6F20576F726C6421 } ```