summaryrefslogtreecommitdiff
path: root/wallet/src/commonMain/kotlin/net/taler/lib/crypto/Base32Crockford.kt
blob: 88a8ebbd125f54a791ccf044c2ebc8b49fe991c8 (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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/*
 * This file is part of GNU Taler
 * (C) 2020 Taler Systems S.A.
 *
 * GNU Taler is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 3, or (at your option) any later version.
 *
 * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */

package net.taler.lib.crypto

internal class EncodingException : Exception("Invalid encoding")

internal object Base32Crockford {

    private fun ByteArray.getIntAt(index: Int): Int {
        val x = this[index].toInt()
        return if (x >= 0) x else (x + 256)
    }

    private const val encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"

    public 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()
    }

    public 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
     */
    internal fun calculateDecodedDataLength(stringSize: Int): Int {
        return stringSize * 5 / 8
    }

}