diff options
author | Torsten Grote <t@grobox.de> | 2020-05-29 15:53:25 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-06-01 11:08:27 -0300 |
commit | b8b5c986cdd3f50ea4f6e5441e8d8cc41c06ef60 (patch) | |
tree | 7bed388b3332b923fabb2827dedba8dc3ea42f6d /src | |
download | wallet-kotlin-b8b5c986cdd3f50ea4f6e5441e8d8cc41c06ef60.tar.gz wallet-kotlin-b8b5c986cdd3f50ea4f6e5441e8d8cc41c06ef60.tar.bz2 wallet-kotlin-b8b5c986cdd3f50ea4f6e5441e8d8cc41c06ef60.zip |
Initial commit
Diffstat (limited to 'src')
-rw-r--r-- | src/commonMain/kotlin/net/taler/wallet/kotlin/Base32Crockford.kt | 113 | ||||
-rw-r--r-- | src/commonTest/kotlin/net/taler/wallet/kotlin/Base32CrockfordTest.kt | 88 |
2 files changed, 201 insertions, 0 deletions
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Base32Crockford.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/Base32Crockford.kt new file mode 100644 index 0000000..ac49dc6 --- /dev/null +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Base32Crockford.kt @@ -0,0 +1,113 @@ +package net.taler.wallet.kotlin + + +class EncodingException : Exception("Invalid encoding") + + +object Base32Crockford { + + private fun ByteArray.getIntAt(index: Int): Int { + val x = this[index].toInt() + return if (x >= 0) x else (x + 256) + } + + private var encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" + + fun encode(data: ByteArray): String { + val sb = StringBuilder() + val size = data.size + var bitBuf = 0 + var numBits = 0 + var pos = 0 + while (pos < size || numBits > 0) { + if (pos < size && numBits < 5) { + val d = data.getIntAt(pos++) + bitBuf = (bitBuf shl 8) or d + numBits += 8 + } + if (numBits < 5) { + // zero-padding + bitBuf = bitBuf shl (5 - numBits) + numBits = 5 + } + val v = bitBuf.ushr(numBits - 5) and 31 + sb.append(encTable[v]) + numBits -= 5 + } + return sb.toString() + } + + fun decode(encoded: String): ByteArray { + val size = encoded.length + var bitpos = 0 + var bitbuf = 0 + var readPosition = 0 + var writePosition = 0 + val out = ByteArray(calculateDecodedDataLength(size)) + + while (readPosition < size || bitpos > 0) { + //println("at position $readPosition with bitpos $bitpos") + if (readPosition < size) { + val v = getValue(encoded[readPosition++]) + bitbuf = (bitbuf shl 5) or v + bitpos += 5 + } + while (bitpos >= 8) { + val d = (bitbuf ushr (bitpos - 8)) and 0xFF + out[writePosition] = d.toByte() + writePosition++ + bitpos -= 8 + } + if (readPosition == size && bitpos > 0) { + bitbuf = (bitbuf shl (8 - bitpos)) and 0xFF + bitpos = if (bitbuf == 0) 0 else 8 + } + } + return out + } + + private fun getValue(chr: Char): Int { + var a = chr + when (a) { + 'O', 'o' -> a = '0' + 'i', 'I', 'l', 'L' -> a = '1' + 'u', 'U' -> a = 'V' + } + if (a in '0'..'9') + return a - '0' + if (a in 'a'..'z') + a.toUpperCase() + var dec = 0 + if (a in 'A'..'Z') { + if ('I' < a) dec++ + if ('L' < a) dec++ + if ('O' < a) dec++ + if ('U' < a) dec++ + return a - 'A' + 10 - dec + } + throw EncodingException() + } + + /** + * Compute the length of the resulting string when encoding data of the given size + * in bytes. + * + * @param dataSize size of the data to encode in bytes + * @return size of the string that would result from encoding + */ + private fun calculateEncodedStringLength(dataSize: Int): Int { + return (dataSize * 8 + 4) / 5 + } + + /** + * Compute the length of the resulting data in bytes when decoding a (valid) string of the + * given size. + * + * @param stringSize size of the string to decode + * @return size of the resulting data in bytes + */ + private fun calculateDecodedDataLength(stringSize: Int): Int { + return stringSize * 5 / 8 + } + +} diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/Base32CrockfordTest.kt b/src/commonTest/kotlin/net/taler/wallet/kotlin/Base32CrockfordTest.kt new file mode 100644 index 0000000..c5940e6 --- /dev/null +++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/Base32CrockfordTest.kt @@ -0,0 +1,88 @@ +package net.taler.wallet.kotlin + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class Base32CrockfordTest { + + private class TestVector(val value: ByteArray, val encoding: List<String>) + + private val vectors = listOf( + TestVector(byteArrayOf(0), listOf("00", "0O", "0o")), + TestVector(byteArrayOf(0), listOf("00", "0O", "0o")), + TestVector(byteArrayOf(1), listOf("04")), + TestVector(byteArrayOf(2), listOf("08")), + TestVector(byteArrayOf(3), listOf("0C")), + TestVector(byteArrayOf(4), listOf("0G")), + TestVector(byteArrayOf(5), listOf("0M")), + TestVector(byteArrayOf(6), listOf("0R")), + TestVector(byteArrayOf(7), listOf("0W")), + TestVector(byteArrayOf(8), listOf("10")), + TestVector(byteArrayOf(9), listOf("14")), + TestVector(byteArrayOf(10), listOf("18")), + TestVector(byteArrayOf(11), listOf("1C")), + TestVector(byteArrayOf(12), listOf("1G")), + TestVector(byteArrayOf(13), listOf("1M")), + TestVector(byteArrayOf(14), listOf("1R")), + TestVector(byteArrayOf(15), listOf("1W")), + TestVector(byteArrayOf(16), listOf("20")), + TestVector(byteArrayOf(17), listOf("24")), + TestVector(byteArrayOf(18), listOf("28")), + TestVector(byteArrayOf(19), listOf("2C")), + TestVector(byteArrayOf(20), listOf("2G")), + TestVector(byteArrayOf(21), listOf("2M")), + TestVector(byteArrayOf(22), listOf("2R")), + TestVector(byteArrayOf(23), listOf("2W")), + TestVector(byteArrayOf(24), listOf("30")), + TestVector(byteArrayOf(25), listOf("34")), + TestVector(byteArrayOf(26), listOf("38")), + TestVector(byteArrayOf(27), listOf("3C")), + TestVector(byteArrayOf(28), listOf("3G")), + TestVector(byteArrayOf(29), listOf("3M")), + TestVector(byteArrayOf(30), listOf("3R")), + TestVector(byteArrayOf(31), listOf("3W")), + TestVector(byteArrayOf(0, 0), listOf("0000", "oooo", "OOOO", "0oO0")), + TestVector(byteArrayOf(1, 0), listOf("0400", "o4oo", "O4OO", "04oO")), + TestVector(byteArrayOf(0, 1), listOf("000G", "ooog", "OOOG", "0oOg")), + TestVector(byteArrayOf(1, 1), listOf("040G", "o4og", "O4og", "04Og")), + TestVector(byteArrayOf(136.toByte(), 64), listOf("H100", "hio0", "HLOo")), + TestVector(byteArrayOf(139.toByte(), 188.toByte()), listOf("HEY0", "heyo", "HeYO")), + TestVector(byteArrayOf(54, 31, 127), listOf("6RFQY", "6rfqy")), + TestVector( + byteArrayOf(72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33), + listOf("91JPRV3F41BPYWKCCGGG", "91jprv3f41bpywkccggg", "9Ljprv3f4ibpywkccggg") + ), + TestVector( + byteArrayOf(139.toByte(), 130.toByte(), 16, 112, 24, 11, 64), + listOf("HE110W0R1D00", "helloworld00", "heiiOw0RidoO") + ), + TestVector( + byteArrayOf(139.toByte(), 130.toByte(), 16, 112, 24, 11), + listOf("HE110W0R1C", "helloworlc", "heiiOw0RiC") + ), + TestVector( + byteArrayOf(139.toByte(), 130.toByte(), 16, 112, 24, 11, 0), + listOf("HE110W0R1C00", "helloworlc00", "heiiOw0RiC00") + ) + ) + + @Test + fun testEncode() { + for (vector in vectors) { + assertEquals(vector.encoding[0], Base32Crockford.encode(vector.value)) + } + } + + @Test + fun testDecode() { + for (vector in vectors) { + for (encoding in vector.encoding) { + assertTrue(vector.value contentEquals Base32Crockford.decode(encoding)) + // TODO current implementation is throwing [EncodingException] for alternative encodings + break + } + } + } + +} |