libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

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