Generate CUIDs in Kotlin

Resources  |  Generate CUIDs in Kotlin

Generating a CUID (Collision-resistant Unique Identifier) in Kotlin involves using similar principles as in other languages: current timestamp, a counter, machine fingerprint, and randomness. Below is an example implementation in Kotlin.

Steps to Generate a CUID in Kotlin

  1. Timestamp: Get the current timestamp in milliseconds.
  2. Counter: Use a counter to handle multiple CUIDs generated in a short period.
  3. Fingerprint: Generate a machine-specific fingerprint.
  4. Randomness: Add random characters to further reduce the risk of collisions.
  5. Base36 Encoding: Encode the components using Base36 for readability.

Example Code

import java.net.InetAddress
import java.security.MessageDigest
import java.util.*
import kotlin.math.floor

object CUIDGenerator {
    private const val BASE36_CHARS = "0123456789abcdefghijklmnopqrstuvwxyz"
    private var counter = 0
    private var lastTimestamp: Long = 0

    @Synchronized
    fun generateCUID(): String {
        val timestamp = System.currentTimeMillis()

        if (timestamp == lastTimestamp) {
            counter++
        } else {
            lastTimestamp = timestamp
            counter = 0
        }

        val timestampBase36 = encodeBase36(timestamp)
        val counterBase36 = encodeBase36(counter.toLong())
        val fingerprint = getMachineFingerprint()
        val randomString = getRandomString(4)

        return "c$timestampBase36$counterBase36$fingerprint$randomString"
    }

    private fun encodeBase36(value: Long): String {
        var v = value
        val result = StringBuilder()
        while (v > 0) {
            result.insert(0, BASE36_CHARS[(v % 36).toInt()])
            v /= 36
        }
        return result.toString().padStart(8, '0')
    }

    private fun getMachineFingerprint(): String {
        return try {
            val hostname = InetAddress.getLocalHost().hostName
            val md = MessageDigest.getInstance("MD5")
            val hashBytes = md.digest(hostname.toByteArray())
            hashBytes.joinToString("") { "%02x".format(it) }.substring(0, 4)
        } catch (e: Exception) {
            "0000"
        }
    }

    private fun getRandomString(length: Int): String {
        val random = Random()
        val result = StringBuilder(length)
        repeat(length) {
            result.append(BASE36_CHARS[random.nextInt(36)])
        }
        return result.toString()
    }
}

fun main() {
    println(CUIDGenerator.generateCUID())
}

Explanation

  1. Base36 Encoding:

    • The encodeBase36 function converts a number to a Base36 encoded string, padded to ensure a consistent length.
  2. Timestamp:

    • val timestamp = System.currentTimeMillis() gets the current timestamp in milliseconds.
  3. Counter:

    • A counter is used to handle multiple CUIDs generated within the same millisecond. The counter is incremented and reset as needed. This is synchronized to ensure thread safety.
  4. Machine Fingerprint:

    • getMachineFingerprint generates a fingerprint using the MD5 hash of the machine's hostname.
  5. Random String:

    • getRandomString generates a random string of specified length using Base36 characters.
  6. Combine Components:

    • The generateCUID function combines the timestamp, counter, fingerprint, and random parts to form the final CUID.

Summary

This Kotlin implementation of CUID generation uses available functions and libraries to handle timestamps, hashing, randomness, and counters. This approach ensures that the generated CUIDs are unique, readable, and collision-resistant, making them suitable for various applications.