From 829095575555375087ec0077005c0a6fcfb2e7e8 Mon Sep 17 00:00:00 2001 From: Infendro Date: Tue, 20 Jan 2026 09:52:58 +0100 Subject: [PATCH] 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) {