split padding in StreamEncoding to PaddedStreamEncoding

This commit is contained in:
2026-01-20 09:52:58 +01:00
parent c1b8800e82
commit 8290955755
5 changed files with 93 additions and 43 deletions

View File

@@ -3,4 +3,4 @@ package com.infendro.encoding
private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".encodeToByteArray() private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".encodeToByteArray()
private const val padding = '='.code.toByte() private const val padding = '='.code.toByte()
object Base32 : Encoding by StreamEncoding(alphabet, padding) object Base32 : Encoding by PaddedStreamEncoding(alphabet, padding)

View File

@@ -3,4 +3,4 @@ package com.infendro.encoding
private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".encodeToByteArray() private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".encodeToByteArray()
private const val padding = '='.code.toByte() private const val padding = '='.code.toByte()
object Base64 : Encoding by StreamEncoding(alphabet, padding) object Base64 : Encoding by PaddedStreamEncoding(alphabet, padding)

View File

@@ -2,8 +2,6 @@ package com.infendro.encoding
import com.infendro.encoding.exception.IllegalByteException import com.infendro.encoding.exception.IllegalByteException
import com.infendro.encoding.util.BigInt import com.infendro.encoding.util.BigInt
import com.infendro.encoding.util.log2
import kotlin.math.ceil
class NumericEncoding internal constructor( class NumericEncoding internal constructor(
override val alphabet: ByteArray, override val alphabet: ByteArray,
@@ -11,18 +9,16 @@ class NumericEncoding internal constructor(
override fun encode(bytes: ByteArray): ByteArray { override fun encode(bytes: ByteArray): ByteArray {
if (bytes.isEmpty()) return byteArrayOf() if (bytes.isEmpty()) return byteArrayOf()
val size = ceil(bytes.size * 8 / log2(base)).toInt() val result = mutableListOf<Byte>()
val result = ByteArray(size)
var i = result.size
var x = BigInt.from(bytes) var x = BigInt.from(bytes)
while (!x.isZero()) { while (!x.isZero()) {
val (quotient, remainder) = x.divRem(base) val (quotient, remainder) = x.divRem(base)
x = quotient 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 { override fun decode(bytes: ByteArray): ByteArray {

View File

@@ -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..<end) {
val byte = bytes[i]
val value = alphabet.indexOf(byte)
if (value == -1) throw IllegalByteException(byte)
buffer = (buffer shl bits) + value.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
}
}

View File

@@ -1,30 +1,17 @@
package com.infendro.encoding package com.infendro.encoding
import com.infendro.encoding.exception.IllegalByteException import com.infendro.encoding.exception.IllegalByteException
import com.infendro.encoding.util.align
import com.infendro.encoding.util.divCeil import com.infendro.encoding.util.divCeil
import com.infendro.encoding.util.lcm
import com.infendro.encoding.util.log2 import com.infendro.encoding.util.log2
class StreamEncoding internal constructor( class StreamEncoding internal constructor(
override val alphabet: ByteArray, override val alphabet: ByteArray,
private val padding: Byte? = null,
) : Encoding { ) : Encoding {
init {
require(base and (base - 1) == 0)
}
override fun encode(bytes: ByteArray): ByteArray { override fun encode(bytes: ByteArray): ByteArray {
val bits = log2(base).toInt() val bits = log2(base).toInt()
val size = (bytes.size * 8).divCeil(bits) val size = (bytes.size * 8).divCeil(bits)
val encodedSize = when (padding) { val result = ByteArray(size)
null -> size
else -> {
val block = lcm(bits, 8) / bits
size.align(block)
}
}
val result = ByteArray(encodedSize)
var r = 0 var r = 0
var buffer = 0 var buffer = 0
@@ -43,15 +30,8 @@ class StreamEncoding internal constructor(
} }
} }
if (bufferBits > 0) { if (bufferBits > 0) {
buffer = buffer shl (bits - bufferBits) val index = buffer shl (bits - bufferBits)
val index = buffer result[r] = alphabet[index]
result[r++] = alphabet[index]
}
if (padding != null) {
while (r < result.size) {
result[r++] = padding
}
} }
return result return result
@@ -59,24 +39,20 @@ class StreamEncoding internal constructor(
override fun decode(bytes: ByteArray): ByteArray { override fun decode(bytes: ByteArray): ByteArray {
val bits = log2(base).toInt() val bits = log2(base).toInt()
val index = padding?.let { bytes.indexOf(it) } ?: -1
val decodedSize = when (index) { val size = bytes.size * bits / 8
-1 -> bytes.size * bits / 8 val result = ByteArray(size)
else -> index * bits / 8
}
val result = ByteArray(decodedSize)
var r = 0 var r = 0
var buffer = 0 var buffer = 0
var bufferBits = 0 var bufferBits = 0
for (i in bytes.indices) { for (i in bytes.indices) {
val byte = bytes[i] val byte = bytes[i]
if (byte == padding) break
val index = alphabet.indexOf(byte) val value = alphabet.indexOf(byte)
if (index == -1) throw IllegalByteException(byte) if (value == -1) throw IllegalByteException(byte)
buffer = (buffer shl bits) + index.toByte() buffer = (buffer shl bits) + value.toByte()
bufferBits += bits bufferBits += bits
if (bufferBits >= 8) { if (bufferBits >= 8) {