Kotlin/Native - Base64 en/decoder [code]

Didn’t find base64 code that didn’t require importing java.this-or-that, so I tinkered a lil …

private const val BASE64_SET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
private val RX_BASE64_CLEANR = "[^="+BASE64_SET+"]".toRegex()

/**
 * Base64 encode a string.
 */
val String.base64encoded: String get() {
    val pad  = when (this.length % 3) {
        1 -> "=="
        2 -> "="
        else -> ""
    }
    var raw = this
    (1 .. pad.length).forEach { raw += 0.toChar() }
    return StringBuilder().apply {
        (0 until raw.length step 3).forEach {
            val n: Int = (0xFF.and(raw[ it ].toInt()) shl 16) +
                         (0xFF.and(raw[it+1].toInt()) shl  8) +
                          0xFF.and(raw[it+2].toInt())
            listOf<Int>( (n shr 18) and 0x3F,
                         (n shr 12) and 0x3F,
                         (n shr  6) and 0x3F,
                             n      and 0x3F).forEach { append(BASE64_SET[it]) }
        }
    }   .dropLast(pad.length)
        .toString() + pad
}

/**
 * Decode a Base64 string.
 */
val String.base64decoded: String get() {
    if (this.length % 4 != 0) throw IllegalArgumentException("The string \"${this}\" does not comply with BASE64 length requirement.")
    val clean = this.replace(RX_BASE64_CLEANR, "").replace("=", "A")
    val padLen = this.filter {it == '='}.length
    return StringBuilder().apply {
        (0 until clean.length step 4).forEach {
            val n: Int = (BASE64_SET.indexOf(clean[ it ]) shl 18) +
                         (BASE64_SET.indexOf(clean[it+1]) shl 12) +
                         (BASE64_SET.indexOf(clean[it+2]) shl  6) +
                          BASE64_SET.indexOf(clean[it+3])
            listOf<Int>( 0xFF.and(n shr 16),
                         0xFF.and(n shr  8),
                         0xFF.and(   n    )).forEach { append(it.toChar()) }
        }
    }   .dropLast(padLen)
        .toString()
}

Any suggestions how to improve that blob of code… (aside the fact that it’s strictly for strings at the moment)?

I like to convert code to an extreme and I’m doing it for fun.
Therefor don’t take my code to seriously :wink:
My first one is way less performant.
and I don’t even know if they work(because I don’t know what I’m doing.
but maybe there are some small thing in my code that you can use?

private const val BASE64_SET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
private val RX_BASE64_CLEANR = "[^=" + BASE64_SET + "]".toRegex()

/**
 * Base64 encode a string.
 */
val String.base64encoded: String
    get() {
        val pad = when (this.length % 3) {
            1 -> "=="
            2 -> "="
            else -> ""
        }

        val raw = this + 0.toChar().toString().repeat(minOf(0, pad.lastIndex))

        return raw.chunkedSequence(3) {
            Triple(
                it[0].toInt(),
                it[1].toInt(),
                it[2].toInt()
            )
        }.map { (frst, scnd, thrd) ->
            (0xFF.and(frst) shl 16) +
                    (0xFF.and(scnd) shl 8) +
                    0xFF.and(thrd)
        }.map { n ->
            sequenceOf(
                (n shr 18) and 0x3F,
                (n shr 12) and 0x3F,
                (n shr 6) and 0x3F,
                n and 0x3F
            )
        }.flatten()
            .map { BASE64_SET[it] }
            .joinToString("")
            .dropLast(pad.length) + pad
    }


/**
 * Decode a Base64 string.
 */
val String.base64decoded: String
    get() {
        require(this.length % 4 != 0) { "The string \"$this\" does not comply with BASE64 length requirement." }
        val clean = this.replace(RX_BASE64_CLEANR, "").replace("=", "A")
        val padLen: Int = this.count { it == '=' }

        return clean.chunkedSequence(4) {
            (BASE64_SET.indexOf(clean[0]) shl 18) +
                    (BASE64_SET.indexOf(clean[1]) shl 12) +
                    (BASE64_SET.indexOf(clean[2]) shl 6) +
                    BASE64_SET.indexOf(clean[3])
        }.map { n ->
            sequenceOf(
                0xFF.and(n shr 16),
                0xFF.and(n shr 8),
                0xFF.and(n)
            )
        }.flatten()
            .map { it.toChar() }
            .joinToString("")
            .dropLast(padLen)
    }

chunkedSequence, require, and maybe String.repeat can be usefull.

1 Like

Any updates here? :slight_smile:

Just a tiny one, to make stuff work with ByteArray

val String.base64encoded: String get() = this.encodeToByteArray().base64encoded

and change original val String.base64encoded to val ByteArray.base64encoded

1 Like

I went ahead an made a working KMM implementation that supports both standard and URL-safe Base64: https://github.com/saschpe/Kase64