1.3.0 release #7
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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..<bytes.size) {
|
||||
val byte = bytes[i]
|
||||
|
||||
val index = alphabet.indexOf(byte)
|
||||
if (index == -1) throw IllegalByteException(byte)
|
||||
|
||||
value = value.timesAdd(base, index)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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..<end) {
|
||||
val byte = bytes[b]
|
||||
val index = alphabet.indexOf(byte)
|
||||
if (index == -1) throw IllegalByteException(byte)
|
||||
buffer = (buffer shl bits) or index
|
||||
bufferBits += bits
|
||||
|
||||
if (bufferBits >= 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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..<size) {
|
||||
val index = size - 1 - i
|
||||
val shift = (index % 4) * 8
|
||||
result[i] = (words[index / 4] ushr shift).toByte()
|
||||
}
|
||||
|
||||
return result
|
||||
return bytes
|
||||
}
|
||||
|
||||
companion object {
|
||||
val zero: BigInt
|
||||
get() = BigInt(intArrayOf(0))
|
||||
|
||||
fun from(value: ByteArray): BigInt {
|
||||
val size = value.size.divCeil(4)
|
||||
val words = IntArray(size)
|
||||
|
||||
for (i in value.indices) {
|
||||
val byte = value[i].toInt() and 0xFF
|
||||
val index = value.lastIndex - i
|
||||
val shift = (index % 4) * 8
|
||||
words[index / 4] = words[index / 4] or (byte shl shift)
|
||||
fun from(bytes: ByteArray): BigInt {
|
||||
val words = IntArray(bytes.size.divCeil(4))
|
||||
for (i in bytes.indices) {
|
||||
val (wi, bi) = (bytes.lastIndex - i).divRem(4)
|
||||
words[wi] = words[wi] or ((bytes[i].toInt() and 0xFF) shl (bi * 8))
|
||||
}
|
||||
|
||||
return BigInt(words)
|
||||
}
|
||||
}
|
||||
|
||||
7
src/commonMain/kotlin/com/infendro/encoding/util/Int.kt
Normal file
7
src/commonMain/kotlin/com/infendro/encoding/util/Int.kt
Normal file
@@ -0,0 +1,7 @@
|
||||
package com.infendro.encoding.util
|
||||
|
||||
fun Int.divRem(divisor: Int): Pair<Int, Int> =
|
||||
Pair(this / divisor, this % divisor)
|
||||
|
||||
fun Int.countLeadingZeroBytes(): Int =
|
||||
countLeadingZeroBits() / 8
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user