Encoding.kt (3500B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2024-2025 Taler Systems S.A. 4 5 * LibEuFin is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation; either version 3, or 8 * (at your option) any later version. 9 10 * LibEuFin is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 13 * Public License for more details. 14 15 * You should have received a copy of the GNU Affero General Public 16 * License along with LibEuFin; see the file COPYING. If not, see 17 * <http://www.gnu.org/licenses/> 18 */ 19 20 package tech.libeufin.common 21 22 /** Crockford's Base32 implementation */ 23 object Base32Crockford { 24 /** Crockford's Base32 alphabet */ 25 const val ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" 26 /** Base32 mark to extract 5 bits chunks */ 27 private const val MASK = 0b11111 28 /** Crockford's Base32 inversed alphabet */ 29 private val INV = intArrayOf( 30 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 31 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, 22, 23, 24, 25, 26, 27, 27, 28, 32 29, 30, 31, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 33 1, 20, 21, 0, 22, 23, 24, 25, 26, 27, 27, 28, 29, 30, 31, 34 ) 35 36 fun encode(data: ByteArray): String = buildString(encodedSize(data.size)) { 37 var buffer = 0 38 var bitsLeft = 0 39 40 for (byte in data) { 41 // Read input 42 buffer = (buffer shl 8) or (byte.toInt() and 0xFF) 43 bitsLeft += 8 44 // Write symbols 45 while (bitsLeft >= 5) { 46 append(ALPHABET[(buffer shr (bitsLeft - 5)) and MASK]) 47 bitsLeft -= 5 48 } 49 } 50 51 if (bitsLeft > 0) { 52 // Write remaining bits 53 append(ALPHABET[(buffer shl (5 - bitsLeft)) and MASK]) 54 } 55 } 56 57 fun decode(encoded: String): ByteArray { 58 val out = ByteArray(decodedSize(encoded.length)) 59 60 var bitsLeft = 0 61 var buffer = 0 62 var cursor = 0 63 64 for (char in encoded) { 65 // Read input 66 val index = char - '0' 67 require(index in 0..INV.size) { "invalid Base32 character: $char" } 68 val decoded = INV[index] 69 require(decoded != -1) { "invalid Base32 character: $char" } 70 buffer = (buffer shl 5) or decoded 71 bitsLeft += 5 72 // Write bytes 73 if (bitsLeft >= 8) { 74 out[cursor++] = (buffer shr (bitsLeft - 8)).toByte() 75 bitsLeft -= 8 // decrease of written bits. 76 } 77 } 78 79 return out 80 } 81 82 /** 83 * Compute the length of the resulting string when encoding data of the given size 84 * in bytes. 85 * 86 * @param dataSize size of the data to encode in bytes 87 * @return size of the string that would result from encoding 88 */ 89 fun encodedSize(dataSize: Int): Int { 90 return (dataSize * 8 + 4) / 5 91 } 92 93 /** 94 * Compute the length of the resulting data in bytes when decoding a (valid) string of the 95 * given size. 96 * 97 * @param stringSize size of the string to decode 98 * @return size of the resulting data in bytes 99 */ 100 fun decodedSize(stringSize: Int): Int { 101 return (stringSize * 5) / 8 102 } 103 } 104 105