summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-05-29 15:53:25 -0300
committerTorsten Grote <t@grobox.de>2020-06-01 11:08:27 -0300
commitb8b5c986cdd3f50ea4f6e5441e8d8cc41c06ef60 (patch)
tree7bed388b3332b923fabb2827dedba8dc3ea42f6d /src
downloadwallet-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.kt113
-rw-r--r--src/commonTest/kotlin/net/taler/wallet/kotlin/Base32CrockfordTest.kt88
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
+ }
+ }
+ }
+
+}