summaryrefslogtreecommitdiff
path: root/src/commonMain/kotlin/net/taler/wallet/kotlin/Base32Crockford.kt
blob: 3bcf15a105552f923a1031fb37245268891952de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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 = 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
     */
    fun calculateDecodedDataLength(stringSize: Int): Int {
        return stringSize * 5 / 8
    }

}