Compare commits
3 Commits
45d74d9cff
...
83fd77c8ee
| Author | SHA1 | Date | |
|---|---|---|---|
|
83fd77c8ee
|
|||
|
456f33f2d9
|
|||
|
55faf1d670
|
@@ -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 }}
|
||||
47
.gitea/workflows/publish.yaml
Normal file
47
.gitea/workflows/publish.yaml
Normal file
@@ -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 }}
|
||||
28
.gitea/workflows/test.yaml
Normal file
28
.gitea/workflows/test.yaml
Normal file
@@ -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
|
||||
21
README.md
21
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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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<UByte>()
|
||||
|
||||
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<UByte>()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.infendro.encoding.numeric
|
||||
|
||||
private val alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".encodeToByteArray()
|
||||
|
||||
object Base36 : NumericEncoding(alphabet)
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.infendro.encoding.numeric
|
||||
|
||||
private val alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".encodeToByteArray()
|
||||
|
||||
object Base62 : NumericEncoding(alphabet)
|
||||
@@ -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..<bytes.size) {
|
||||
val index = alphabet.indexOf(bytes[i])
|
||||
if (index == -1) throw Exception() //TODO
|
||||
value = value.timesAdd(base, index)
|
||||
}
|
||||
|
||||
return value.toByteArray()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.infendro.encoding.stream
|
||||
|
||||
private val alphabet = "0123456789ABCDEF".encodeToByteArray()
|
||||
|
||||
object Base16 : StreamEncoding(alphabet)
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.infendro.encoding.stream
|
||||
|
||||
private val alphabet = "01".encodeToByteArray()
|
||||
|
||||
object Base2 : StreamEncoding(alphabet)
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.infendro.encoding.stream
|
||||
|
||||
private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".encodeToByteArray()
|
||||
private const val padding = '='.code.toByte()
|
||||
|
||||
object Base32 : StreamEncoding(alphabet, padding)
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.infendro.encoding.stream
|
||||
|
||||
private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".encodeToByteArray()
|
||||
private const val padding = '='.code.toByte()
|
||||
|
||||
object Base64 : StreamEncoding(alphabet, padding)
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.infendro.encoding.stream
|
||||
|
||||
private val alphabet = "01234567".encodeToByteArray()
|
||||
|
||||
object Base8 : StreamEncoding(alphabet)
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.infendro.encoding.stream
|
||||
|
||||
import com.infendro.encoding.Encoding
|
||||
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,
|
||||
private val padding: Byte? = null,
|
||||
) : Encoding(alphabet) {
|
||||
init {
|
||||
require(base % 2 == 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)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
89
src/commonMain/kotlin/com/infendro/encoding/util/BigInt.kt
Normal file
89
src/commonMain/kotlin/com/infendro/encoding/util/BigInt.kt
Normal file
@@ -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<BigInt, Int> {
|
||||
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..<size) {
|
||||
val index = size - 1 - i
|
||||
val shift = (index % 4) * 8
|
||||
result[i] = (words[index / 4] ushr shift).toByte()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return BigInt(words)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,16 @@ package com.infendro.encoding.util
|
||||
|
||||
import kotlin.math.log2
|
||||
|
||||
internal fun log2(
|
||||
value: Int,
|
||||
) = log2(value.toDouble())
|
||||
internal fun log2(value: Int): Double =
|
||||
log2(value.toDouble())
|
||||
|
||||
internal fun gcd(
|
||||
a: Int,
|
||||
b: Int,
|
||||
): Int {
|
||||
internal fun Int.divCeil(n: Int): Int =
|
||||
(this + n - 1) / n
|
||||
|
||||
internal fun Int.align(n: Int): Int =
|
||||
divCeil(n) * n
|
||||
|
||||
internal fun gcd(a: Int, b: Int): Int {
|
||||
var x = a
|
||||
var y = b
|
||||
while (y != 0) {
|
||||
@@ -20,9 +22,6 @@ internal fun gcd(
|
||||
return x
|
||||
}
|
||||
|
||||
internal fun lcm(
|
||||
a: Int,
|
||||
b: Int,
|
||||
): Int {
|
||||
internal fun lcm(a: Int, b: Int): Int {
|
||||
return (a * b) / gcd(a, b)
|
||||
}
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
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: Encoding
|
||||
val encoding: StreamEncoding
|
||||
get() = Base16
|
||||
|
||||
val `Hello World!` = ubyteArrayOf(
|
||||
0x48U, 0x65U, 0x6cU, 0x6cU,
|
||||
0x6fU, 0x20U, 0x57U, 0x6fU,
|
||||
0x72U, 0x6cU, 0x64U, 0x21U,
|
||||
val `Hello World!` = byteArrayOf(
|
||||
0x48, 0x65, 0x6c, 0x6c,
|
||||
0x6f, 0x20, 0x57, 0x6f,
|
||||
0x72, 0x6c, 0x64, 0x21,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `encode empty ByteArray`() {
|
||||
val expected = ubyteArrayOf()
|
||||
val actual = encoding.encode(ubyteArrayOf())
|
||||
val expected = byteArrayOf()
|
||||
val actual = encoding.encode(byteArrayOf())
|
||||
assertContentEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `encode "Hello World!"`() {
|
||||
val expected = ubyteArrayOf(
|
||||
0x34U, 0x38U, 0x36U, 0x35U, 0x36U, 0x63U, 0x36U, 0x63U,
|
||||
0x36U, 0x66U, 0x32U, 0x30U, 0x35U, 0x37U, 0x36U, 0x66U,
|
||||
0x37U, 0x32U, 0x36U, 0x63U, 0x36U, 0x34U, 0x32U, 0x31U,
|
||||
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)
|
||||
|
||||
@@ -1,40 +1,42 @@
|
||||
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: Encoding
|
||||
val encoding: StreamEncoding
|
||||
get() = Base2
|
||||
|
||||
val `Hello World!` = ubyteArrayOf(
|
||||
0x48U, 0x65U, 0x6cU, 0x6cU,
|
||||
0x6fU, 0x20U, 0x57U, 0x6fU,
|
||||
0x72U, 0x6cU, 0x64U, 0x21U,
|
||||
val `Hello World!` = byteArrayOf(
|
||||
0x48, 0x65, 0x6c, 0x6c,
|
||||
0x6f, 0x20, 0x57, 0x6f,
|
||||
0x72, 0x6c, 0x64, 0x21,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `encode empty ByteArray`() {
|
||||
val expected = ubyteArrayOf()
|
||||
val actual = encoding.encode(ubyteArrayOf())
|
||||
val expected = byteArrayOf()
|
||||
val actual = encoding.encode(byteArrayOf())
|
||||
assertContentEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `encode "Hello World!"`() {
|
||||
val expected = ubyteArrayOf(
|
||||
0x30U, 0x31U, 0x30U, 0x30U, 0x31U, 0x30U, 0x30U, 0x30U,
|
||||
0x30U, 0x31U, 0x31U, 0x30U, 0x30U, 0x31U, 0x30U, 0x31U,
|
||||
0x30U, 0x31U, 0x31U, 0x30U, 0x31U, 0x31U, 0x30U, 0x30U,
|
||||
0x30U, 0x31U, 0x31U, 0x30U, 0x31U, 0x31U, 0x30U, 0x30U,
|
||||
0x30U, 0x31U, 0x31U, 0x30U, 0x31U, 0x31U, 0x31U, 0x31U,
|
||||
0x30U, 0x30U, 0x31U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U,
|
||||
0x30U, 0x31U, 0x30U, 0x31U, 0x30U, 0x31U, 0x31U, 0x31U,
|
||||
0x30U, 0x31U, 0x31U, 0x30U, 0x31U, 0x31U, 0x31U, 0x31U,
|
||||
0x30U, 0x31U, 0x31U, 0x31U, 0x30U, 0x30U, 0x31U, 0x30U,
|
||||
0x30U, 0x31U, 0x31U, 0x30U, 0x31U, 0x31U, 0x30U, 0x30U,
|
||||
0x30U, 0x31U, 0x31U, 0x30U, 0x30U, 0x31U, 0x30U, 0x30U,
|
||||
0x30U, 0x30U, 0x31U, 0x30U, 0x30U, 0x30U, 0x30U, 0x31U,
|
||||
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)
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
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: Encoding
|
||||
val encoding: StreamEncoding
|
||||
get() = Base32
|
||||
|
||||
val `Hello World!` = ubyteArrayOf(
|
||||
0x48U, 0x65U, 0x6cU, 0x6cU,
|
||||
0x6fU, 0x20U, 0x57U, 0x6fU,
|
||||
0x72U, 0x6cU, 0x64U, 0x21U,
|
||||
val `Hello World!` = byteArrayOf(
|
||||
0x48, 0x65, 0x6c, 0x6c,
|
||||
0x6f, 0x20, 0x57, 0x6f,
|
||||
0x72, 0x6c, 0x64, 0x21,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `encode empty ByteArray`() {
|
||||
val expected = ubyteArrayOf()
|
||||
val actual = encoding.encode(ubyteArrayOf())
|
||||
val expected = byteArrayOf()
|
||||
val actual = encoding.encode(byteArrayOf())
|
||||
assertContentEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `encode "Hello World!"`() {
|
||||
val expected = ubyteArrayOf(
|
||||
0x4aU, 0x42U, 0x53U, 0x57U, 0x59U, 0x33U, 0x44U, 0x50U,
|
||||
0x45U, 0x42U, 0x4cU, 0x57U, 0x36U, 0x34U, 0x54U, 0x4dU,
|
||||
0x4dU, 0x51U, 0x51U, 0x51U, 0x3dU, 0x3dU, 0x3dU, 0x3dU,
|
||||
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)
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
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: Encoding
|
||||
val encoding: StreamEncoding
|
||||
get() = Base64
|
||||
|
||||
val `Hello World!` = ubyteArrayOf(
|
||||
0x48U, 0x65U, 0x6cU, 0x6cU,
|
||||
0x6fU, 0x20U, 0x57U, 0x6fU,
|
||||
0x72U, 0x6cU, 0x64U, 0x21U,
|
||||
val `Hello World!` = byteArrayOf(
|
||||
0x48, 0x65, 0x6c, 0x6c,
|
||||
0x6f, 0x20, 0x57, 0x6f,
|
||||
0x72, 0x6c, 0x64, 0x21,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `encode empty ByteArray`() {
|
||||
val expected = ubyteArrayOf()
|
||||
val actual = encoding.encode(ubyteArrayOf())
|
||||
val expected = byteArrayOf()
|
||||
val actual = encoding.encode(byteArrayOf())
|
||||
assertContentEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `encode "Hello World!"`() {
|
||||
val expected = ubyteArrayOf(
|
||||
0x53U, 0x47U, 0x56U, 0x73U, 0x62U, 0x47U, 0x38U, 0x67U,
|
||||
0x56U, 0x32U, 0x39U, 0x79U, 0x62U, 0x47U, 0x51U, 0x68U,
|
||||
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)
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
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: Encoding
|
||||
val encoding: StreamEncoding
|
||||
get() = Base8
|
||||
|
||||
val `Hello World!` = ubyteArrayOf(
|
||||
0x48U, 0x65U, 0x6cU, 0x6cU,
|
||||
0x6fU, 0x20U, 0x57U, 0x6fU,
|
||||
0x72U, 0x6cU, 0x64U, 0x21U,
|
||||
val `Hello World!` = byteArrayOf(
|
||||
0x48, 0x65, 0x6c, 0x6c,
|
||||
0x6f, 0x20, 0x57, 0x6f,
|
||||
0x72, 0x6c, 0x64, 0x21,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `encode empty ByteArray`() {
|
||||
val expected = ubyteArrayOf()
|
||||
val actual = encoding.encode(ubyteArrayOf())
|
||||
val expected = byteArrayOf()
|
||||
val actual = encoding.encode(byteArrayOf())
|
||||
assertContentEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `encode "Hello World!"`() {
|
||||
val expected = ubyteArrayOf(
|
||||
0x32U, 0x32U, 0x30U, 0x36U, 0x32U, 0x35U, 0x35U, 0x34U,
|
||||
0x33U, 0x33U, 0x30U, 0x36U, 0x37U, 0x34U, 0x34U, 0x30U,
|
||||
0x32U, 0x35U, 0x36U, 0x36U, 0x37U, 0x35U, 0x36U, 0x32U,
|
||||
0x33U, 0x33U, 0x30U, 0x36U, 0x32U, 0x30U, 0x34U, 0x31U,
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user